Cloud Governance with CDK using AspectsCloudGovernancewithCDKusingAspectsCloudGovernancewithCDKusingAspects
8 Mar 2022
Share
Manage Cloud Governance with AWS CDK, Aspects and Open Policy Agent
Nowadays with the established concepts of cloud computing, infrastructure as code, and automation; the volume and complexity of environments are increasing exponentially.
This landscape makes it necessary to implement a clear set of rules and policies regarding the lifecycle of cloud resources, otherwise known as Cloud Governance.
Cloud Operations Management: A clear definition of resources allocated to the service over time, Performance SLAs, Ongoing monitoring, Access control requirements
Cloud Data Management: Automation of data lifecycle management, Ensuring all data is encrypted, Developing a tiering strategy
Cloud Security and Compliance Management: Risk assessment, Identity and access management, Data management and encryption, Application security
“ AWS CDK lets you build reliable, scalable, cost-effective applications in the cloud with the considerable expressive power of a programming language”
For building these examples you will need a working AWS Account and the AWS CDK CLI installed with the appropriate permissions on your AWS Account.
A simple CDK stack
This snippet shows a complete declaration of a stack containing an S3 bucket without encryption:
Aspects are a way to change constructs in a given scope based on any characteristics of that node. CDK Aspects implements the Visitor pattern using the interface IAspect .
Workflow using Aspects
When using CDK Aspects, before the changes have been applied in the AWS environment, all registered aspects on a stack are visited and you can implement validations and make changes in nodes. You also have access to a complete tree of current node (stack, resources, etc). In this aspect, you can also insert information or error annotations in nodes.
A policy to block the creation of buckets without encryption
In this example we can see the declaration of a class that implements the IAspect interface. The visit method verifies if the node is an S3 Bucket with encryption. If the answer is negative, an error annotation is included in the node:
// => lib/s3-checker.tsexportclassBucketEncryptionCheckerimplementscdk.IAspect{publicvisit(node:IConstruct):void{if(node instanceofcdk.aws_s3.CfnBucket&&!node.bucketEncryption){Annotations.of(node).addError('Bucket encryption is not enabled');}}}// => lib/simple-test.tstest('S3 Bucket Not Created Without Encryption',()=>{const app =newcdk.App();// WHENlet stack =newSimple.SimpleStack(app,'MyTestStack'); cdk.Aspects.of(stack).add(newBucketEncryptionChecker());// THEN Annotations.fromStack(stack).hasError('/MyTestStack/MyBucket/InsecureBucket/Resource','AWS::S3::Bucket::NoEncryption');});
js
A rich example using OPA
When we talk about dozens of policies and their validation, in some contexts it can turn the codebases into a mess and very hard to maintain and deal with. In this case the Open Policy Agent or just OPA can help us!
Policy-based control for cloud native environments
OPA brings us stable, simple and flexible fine-grained control for all aspects of elements in a stack. With this feature you can decouple your Cloud Governance policies and rules from your services and maintain a concise base for those without losing performance or availability.
OPA Architecture
You can use OPA as Daemon receiving inputs from an HTTP API or as a library. You can build and distribute compiled policies in webassembly to be used as well.
Let's imagine that we want to enforce that the applied stacks have an explicit repository tag and the current Error Budget of the project is greater than 0. We can do that by declaring a policy like this:
# => test/policies/change.rego
package policy.change
import future.keywords.in
default allow = false
allow {
some repoTag in input.stackMetadata
repoTag.type == "repoTag"
repoTag.data != ""
some errorBudget in input.stackMetadata
errorBudget.type == "errorBudget"
errorBudget.data > 0
}
An OPA Checker that implements IAspect
In this implementation, we create a data object with information about node/resource and submit that to OPA. If the response is different than { result: { allow: true } } an error annotation is created blocking the process of deployment:
// => lib/opa-checker.tsexportclassOpaCheckerimplementsIAspect{private id =''privateopClient:IOpaClientconstructor(id: string,opClient:IOpaClient){this.id= id
this.opClient= opClient
}publicvisit(node:IConstruct):void{const data =this.buildData(node)this.loadTagsData(node, data)this.loadCnfResourceData(node, data)const opaResult =this.opClient.submit({input: data
})if(opaResult.error){Annotations.of(node).addError(`OpaChecker::${this.id}::${opaResult.errorMsg}`);}if(opaResult.result.allow){Annotations.of(node).addInfo(`OpaChecker::${this.id}::Allowed`);}else{Annotations.of(node).addError(`OpaChecker::${this.id}::NotAllowed`);}}// ...methods omitted
js
Bringing it all together
Here is a complete implementation of a stack that has three Aspect Checkers:
BucketEncryptionChecker
OpaChecker for Change Policy
OpaChecker for Financial Policy
// => bin/simple.tsconst app =newApp();const stack =newSecureStack(app,'SimpleStack',{});Tags.of(stack).add("projectId","my-project")Tags.of(stack).add('active','yes')Tags.of(stack).add('hasBudget','yes')stack.node.addMetadata("repoTag","v0.0.0")stack.node.addMetadata("errorBudget",0.1)Aspects.of(stack).add(newBucketEncryptionChecker());const opaServerEndpoing ="http://localhost:8181/v1/data"const policyChangeClient =newDefaultOpaClient(`${opaServerEndpoing}/policy/change`)const policyFinancialClient =newDefaultOpaClient(`${opaServerEndpoing}/policy/financial`)Aspects.of(stack).add(newOpaChecker('PolicyChange', policyChangeClient))Aspects.of(stack).add(newOpaChecker('PolicyFinancial', policyFinancialClient))// => generated payload that will be sent to OPA{"input":{"id":"Resource","addr":"c8400522ef47ac4b17b2337c6266232ef0f0bec571","path":"SimpleStack/MyBucket/SecureBucket/Resource","type":"AWS::S3::Bucket","stackMetadata":[{"type":"repoTag","data":"v0.0.0"},{"type":"errorBudget","data":0.1}],"isStack":false,"tags":{"active":"yes","hasBudget":"yes","projectId":"my-project"}}}
js
Conclusion
Aspects is an interesting way to control DevOps flows based on project characteristics and business rules. The combination of this tool with OPA brings us flexibility and power to implement Cloud Governance rules and policies in a clear and centralized way, and it’s pretty good!