AWS Cloud Development Kit (AWS CDK) is an open source software framework that allows users to define and provision AWS infrastructure using familiar programming languages. Using CDK, you can version control infrastructure, and the Infrastructure-as-Code concept opens up new opportunities to manage AWS infrastructure more efficiently and reliably.

But when planning to deploy new AWS resources or updating them, you must make sure these changes aren’t introducing vulnerabilities, or your organization might have baselines that you must follow per compliance requirements. And it’s time to set criteria to define operational readiness and keep security baselines to ensure that the infrastructure changes don’t cause problems. Open Policy Agent (OPA), a Cloud Native Computing Foundation incubating project, is designed to automate policy tests. OPA provides a unified framework and language for declaring, implementing, and controlling the policies of various components in cloud native solutions.

Integrated with AWS CDK, OPA can help you realize Policy-as-Code capability that tests the changes before AWS CDK makes changes in your AWS environment. This bring benefits in different ways, including:

  • Individual team members could easily conduct check on changes.
  • Policy evaluation can be automated through integration with CI/CD.
  • Users can enforce preventative controls on the Infrastructure-as-Code input.
  • Users can author custom OPA policies based on technical control requirements from industry best practice frameworks, such as CIS AWS Benchmark.

All in all, engineers will have a shortened feedback loop.

The rest of this post will walk through how to use OPA with AWS CDK for Policy-as-Code. Here is a list of high-level tasks that will be covered:

  • Create an AWS CDK project to deploy AWS resources.
  • Write a simple OPA policy based on REGO policy language.
  • Leverage the OPA policy to validate the CDK infrastructure code.

Prerequisites

Creating an EC2 environment in AWS Cloud9

Follow the instructions to create an Amazon Linux Cloud9 EC2 environment as the workspace, and you can use the Cloud9 environment for the rest of the post.

Ensure AWS CDK is installed

$ cdk --version 

Install OPA

$ curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64 $ chmod 755 ./opa

Getting Started

Create an AWS CDK project

The CDK project will create an AWS CloudFormation stack of an Amazon Elastic Compute Cloud (Amazon EC2) instance, an AWS Identity and Access Management (IAM) role, a security group attached to the EC2, etc.

Apply project template app for Python

$ mkdir cdk-demo
$ cd cdk-demo/
$ cdk init --language python --app cdk-demo

Install the virtualenv

$ python3 -m venv .venv

Enter the virtualenv

$ source .venv/bin/activate

Install all required packages

$ cat >requirements.txt <<EOF
aws-cdk.aws-ec2
aws-cdk.core
-e .
EOF $ pip install -r requirements.txt

Edit the AWS CDK project to create resources

cat >app.py <<EOF
#!/usr/bin/env python3 import os
from aws_cdk import core
from aws_cdk.core import Environment
from cdk_demo.cdk_demo_stack import CdkDemoStack app = core.App() ACCOUNT = app.node.try_get_context('account') or os.environ.get( 'CDK_DEFAULT_ACCOUNT', 'unknown')
REGION = app.node.try_get_context('region') or os.environ.get( 'CDK_DEFAULT_REGION', 'unknown') AWS_ENV = Environment(region=REGION, account=ACCOUNT) CdkDemoStack(app, "cdk-demo", env=AWS_ENV) app.synth()
EOF cat >cdk_demo/cdk_demo_stack.py <<EOF from aws_cdk import core, aws_ec2 class CdkDemoStack(core.Stack): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) self.vpc = aws_ec2.Vpc.from_lookup(self, 'default_vpc', is_default=True) self.sg_ssh = aws_ec2.SecurityGroup( self, 'ssh', vpc=self.vpc, description="Allow SSH from anywhere", security_group_name="SSH from anywhere" ) self.sg_ssh.add_ingress_rule(aws_ec2.Peer.any_ipv4(), aws_ec2.Port.tcp(22)) self.ami = aws_ec2.LookupMachineImage( name="ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*", owners=["099720109477"], ) self.ec2 = aws_ec2.Instance( self, 'cdk-demo', instance_type=aws_ec2.InstanceType('t2.micro'), machine_image=self.ami, vpc=self.vpc, security_group=self.sg_ssh, )
EOF

Create AWS CloudFormation template

$ cdk synth

The template will be generated as cdk.out/cdk-demo.template.json. If we run cdk deploy now, the CloudFormation stack will be launched and all the resources declared will be created. Before applying the changes, however, let us do validation with OPA to ensure there are no violations on security or operation policies.

Write OPA policy in Rego

The policies in this example will check the AWS CloudFormation template on three aspects:

  • Whether it creates more than 10 EC2 instances
  • Whether SSH is enabled on EC2 instance
  • Whether an IAM role is created
cat > opa_cdk.rego <<EOF package opa_cdk import input # deny if it creates more than 10 EC2 instances
deny_too_many_ec2[deny] { instances := [res | res:=input.Resources[_]; res.Type == "AWS::EC2::Instance"] count(instances) > 10 deny := true
} # deny if ssh is enabled
deny_ssh_enabled[deny] { input.Resources[_].Properties.SecurityGroupIngress[_].ToPort == 22 deny := true
} # deny if it creates IAM role
deny_role_created[deny] { input.Resources[_].Type == "AWS::IAM::Role" deny := true
}
EOF

Evaluate the OPA policy

Run OPA policy against the AWS CloudFormation template:

cd ..
./opa eval --format pretty -i cdk-demo/cdk.out/cdk-demo.template.json -d cdk-demo/opa_cdk.rego "data"

Output after running OPA policy against the AWS CloudFormation template

The output shows that the CloudFormation template does pass the check for the number of EC2 instances to be created, but violates the policies of not creating IAM role and not enabling SSH on an EC2 instance.

The deploy shouldn’t be conducted until the template passes all the policy tests. OPA helps to ensure only policy-passed infrastructure changes will go to production.

Clean up

Delete the Cloud9 EC2 environment from the AWS Console.

Wrap up

The example above is a hello world demonstration that shows a way of using OPA to validate and determine whether a proposed AWS CDK update is authorized. I think this example can be applied to other use cases as well, such as ensuring all resources have tags before they are created, making sure naming standards for resources are followed, meeting security or operational requirements, and so on.

Knowing early whether an application or infrastructure deployment is in violation of policy definitions is useful. Thus, this policy evaluation should be a step in the workflows of an infrastructure lifecycle and to fail deployments that create non-compliant resources.

OPA can run as a daemon side-by-side with your service and provide RESTful APIs using JSON over HTTP. For services written with Go, OPA can instead be embedded and used as a library. Using OPA with AWS CDK highly extends on Infrastructure-as-Code, which provides self-service capability and end-to-end automation for infrastructure policy testing.

This blog post provided an example use case that you can apply in your context, in your organization. There are dozens of other policies you will want to enforce in your environment for security, cost, and availability reasons. You could start with your own requirements for access control and for putting constraints on containerized workloads, and you can use OPA with the AWS CDK to build policy-driven infrastructure, to enforce constraints, to provide guardrails, and to govern cloud resources with confidence.

Now you are one step closer to DevSecOps—you could integrate OPA with your CDK, AWS CloudFormation, or a Terraform-backed Infrastructure-as-Code pipeline, and make use of the OPA output to automatically fail or pass a deployment. With the power of these open source tools, the possibilities are almost endless.

Feature image via Pixabay.