Enforcing Policy as Code with Cloudify, Terraform, and Open Policy Agent 

In my previous article, I provided an example of using Cloudify’s native REST plugin to send a policy evaluation request to an Open Policy Agent (OPA) service. While dispatching requests to an upstream OPA endpoint is a great way to integrate policy enforcement throughout an environment blueprint, Cloudify has also been working toward native integration of OPA throughout our ecosystem. 

Cloudify took the first step toward this ecosystem-level integration by adding initial OPA support directly to our Terraform plugin. You can now run OPA policy evaluation against a generated Terraform plan using new capabilities in the Cloudify Terraform plugin. In this article, I’ll show you an example of how this integration works and the new interface operations that Cloudify introduced to enable this functionality. 


This article discusses a simple cloud use case. A single EC2 instance is deployed into the default VPC, and a security group is attached to the instance. The security group controls the IP addresses that can connect to the EC2 instance via SSH. The infrastructure components are managed using a Terraform module, and the Cloudify blueprint allows the user to specify the permitted SSH IP addresses. 

The policy goal is simple: prevent the user from adding an overly permissive IP range, such as “”, to the security group. The policy is expressed using OPA’s Rego language. Cloudify’s official Terraform plugin generates a Terraform plan and evaluates the policy against the plan. 

You can follow along with these examples by downloading the code from our community GitHub repository. 

The Policy 

The policy’s main.rego defines a terraform package with two rules: deny and allow. The deny rule further evaluates the deny_permissive_security_group rule, and the allow rule is a negation of the deny rule: 

# main.rego 

package terraform 

import future.keywords.contains 

deny contains msg { 

  msg := deny_permissive_security_group 


allow { 

  not deny 


The security_groups.rego file defines the deny_permissive_security_group rule within the terraform package. This rule searches across all ingress CIDR blocks in any security groups defined by the Terraform plan. If a security group contains the overly permissive “” rule, then it will raise a message indicating that disallowed IP address ranges were found: 

# security_groups.rego 

package terraform 

import future.keywords.in 

import future.keywords.if 

import future.keywords.contains 

import input as tfplan 

deny_permissive_security_group contains msg { 

  some task in tfplan["planned_values"]["root_module"]["resources"] 

  task["type"] == "aws_security_group" 

  task["values"]["ingress"][_]["cidr_blocks"][_] == "" 

  msg := "You have disallowed IP addresses in the ingress CIDR blocks for your security group" 


The policy is zipped up as a bundle in the resources/ directory within the blueprint package: 

# cd policy/ 

# zip -r ../resources/policy.zip * 

  adding: main.rego (deflated 21%) 

  adding: security_groups.rego (deflated 48%)

The Blueprint 

The blueprint enables self-service provisioning of the EC2 instance defined by the Terraform module. Cloudify’s integration with Terraform and OPA ensures that policy is enforced for the requested environment. Inputs to the blueprint allow the user to specify the SSH IP addresses passed to the Terraform module: 



    description: List of IP addresses for SSH access 

    display_label: SSH IP Addresses

The node template definition for the Terraform module is a standard Cloudify Terraform module node template with additional configuration to support OPA. The opa_config property specifies the location of the OPA bundle within the blueprint package. The Cloudify lifecycle interfaces include the addition of the tf.cloudify_tf.tasks.evaluate_opa_policy task as part of the configure operation. The other Terraform-related tasks, such as tf.cloudify_tf.tasks.apply and tf.cloudify_tf.tasks.state_pull are moved “down” in the chain of interface operations that are executed during the built-in install workflow. The tf.cloudify_tf.tasks.evaluate_opa_policy operation accepts the decision to evaluate (“terraform/deny”) as an input. This allows you to evaluate different decisions inside of a policy based on the needs of the blueprint. 


    type: cloudify.nodes.terraform.Module 




          - name: opa_bundle 

            location: resources/policy.zip 



          location: resources/instance.zip 


          access_key: { get_secret: aws_access_key_id } 

          secret_key: { get_secret: aws_secret_access_key } 

          ssh_ips: { get_input: ssh_ips } 


      - target: terraform 

        type: cloudify.terraform.relationships.run_on_host 




          implementation: tf.cloudify_tf.tasks.evaluate_opa_policy 


            decision: terraform/deny 

            opa_config: { get_property: [SELF, opa_config ] } 


          implementation: tf.cloudify_tf.tasks.apply 


          implementation: tf.cloudify_tf.tasks.state_pull

Running the Example 

This example can be executed from Cloudify’s UI, CLI, or API. First, upload the blueprint package from GitHub to a Cloudify 7.0 manager: 

# cfy blueprint upload -b OPA-Example https://github.com/cloudify-community/tf-opa-example/archive/refs/heads/main.zip 

Publishing blueprint archive https://github.com/cloudify-community/tf-opa-example/archive/refs/heads/main.zip... 

 main.zip |############################################################| 100.0% 

Blueprint `OPA-Example` upload started. 

2023-04-10 14:07:04.941  CFY <None> Starting 'upload_blueprint' workflow execution 

2023-04-10 14:07:05.428  LOG <None> INFO: Blueprint archive uploaded. Extracting... 

2023-04-10 14:07:05.474  LOG <None> INFO: Blueprint archive extracted. Parsing... 

2023-04-10 14:07:06.044  LOG <None> INFO: Blueprint parsed. Updating DB with blueprint plan. 

2023-04-10 14:07:06.126  CFY <None> 'upload_blueprint' workflow execution succeeded 

Blueprint uploaded. The blueprint's id is OPA-Example

Next, create a deployment with a forbidden value of “” for the SSH IP addresses: 

Cloudify initiates the installation process and builds the Execution Task Graph. The installation fails during evaluation of the OPA policy: 

The failure’s error message indicates that the policy evaluation failed, and it points the user toward the opa_evaluation_result_json runtime property for further information: 

The opa_evaluation_result_json runtime property for the “ec2-instance” node instance provides the exact error message raised by the policy definition: 

You can also create a deployment that passes the policy evaluation by specifying IP addresses that do not violate the policy constraints. For example, you can create a deployment with a value of “” for the SSH IP address. The Execution Task Graph runs to completion, and the EC2 instance’s IP address is available as a Deployment Capability: 

Wrapping Up 

In this article, I introduced you to our initial work toward integrating OPA into Cloudify. Providing self-service environment capabilities for Terraform modules is a very common use case, and we believe that it is an excellent starting point for developing powerful policy-as-code guardrails. OPA has proven itself as a best-in-class tool for policy evaluation, and we are excited to continue working toward expanded OPA support across the Cloudify portfolio. 

You can find the full list of Cloudify 7 features in the release notes


    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Back to top