AWS DevOps & Developer Productivity Blog
Implementing DevSecOps Using AWS CodePipeline
DevOps is a combination of cultural philosophies, practices, and tools that emphasizes collaboration and communication between software developers and IT infrastructure teams while automating an organization’s ability to deliver applications and services rapidly, frequently, and more reliably.
CI/CD stands for continuous integration and continuous deployment. These concepts represent everything related to automation of application development and the deployment pipeline — from the moment a developer adds a change to a central repository until that code winds up in production.
DevSecOps covers security of and in the CI/CD pipeline, including automating security operations and auditing. The goals of DevSecOps are to:
- Embed security knowledge into DevOps teams so that they can secure the pipelines they design and automate.
- Embed application development knowledge and automated tools and processes into security teams so that they can provide security at scale in the cloud.
The Security Cloud Adoption Framework (CAF) whitepaper provides prescriptive controls to improve the security posture of your AWS accounts. These controls are in line with a DevOps blog post published last year about the control-monitor-fix governance model.
Security CAF controls are grouped into four categories:
- Directive: controls establish the governance, risk, and compliance models on AWS.
- Preventive: controls protect your workloads and mitigate threats and vulnerabilities.
- Detective: controls provide full visibility and transparency over the operation of your deployments in AWS.
- Responsive: controls drive remediation of potential deviations from your security baselines.
To embed the DevSecOps discipline in the enterprise, AWS customers are automating CAF controls using a combination of AWS and third-party solutions.
- AWS solutions: Amazon Cloudwatch Alarms, AWS CloudTrail, Amazon CloudWatch Events, AWS Lambda, AWS Config
- Third-party solutions: Dome9, Evident.IO
In this blog post, I will show you how to use a CI/CD pipeline to automate preventive and detective security controls. I’ll use an example that show how you can take the creation of a simple security group through the CI/CD pipeline stages and enforce security CAF controls at various stages of the deployment. I’ll use AWS CodePipeline to orchestrate the steps in a continuous delivery pipeline.
These resources are being used in this example:
- An AWS CloudFormation template to create the demo pipeline.
- A Lambda function to perform the static code analysis of the CloudFormation template.
- A Lambda function to perform dynamic stack validation for the security groups in scope.
- An S3 bucket as the sample code repository.
- An AWS CloudFormation source template file to create the security groups.
- Two VPCs to deploy the test and production security groups.
These are the high-level security checks enforced by the pipeline:
- During the Source stage, static code analysis for any open security groups. The pipeline will fail if there are any violations.
- During the Test stage, dynamic analysis to make sure port 22 (SSH) is open only to the approved IP CIDR range. The pipeline will fail if there are any violations.
These are the pipeline stages:
1. Source stage: In this example, the pipeline gets the CloudFormation code that creates the security group from S3, the code repository service.
This stage passes the CloudFormation template and pipeline name to a Lambda function, CFNValidateLambda. This function performs the static code analysis. It uses the regular expression language to find patterns and identify security group policy violations. If it finds violations, then Lambda fails the pipeline and includes the violation details.
Here is the regular expression that Lambda function using for static code analysis of the open SSH port:
"^.*Ingress.*(([fF]rom[pP]ort|[tT]o[pP]ort).\s*:\s*u?.(22).*[cC]idr[iI]p.\s*:\s*u?.((0\.){3}0\/0)|[cC]idr[iI]p.\s*:\s*u?.((0\.){3}0\/0).*([fF]rom[pP]ort|[tT]o[pP]ort).\s*:\s*u?.(22))"
2. Test stage: After the static code analysis is completed successfully, the pipeline executes the following steps:
a. Create stack: This step creates the stack in the test VPC, as described in the test configuration.
b. Stack validation: This step triggers the StackValidationLambda Lambda function. It passes the stack name and pipeline name in the event parameters. Lambda validates the security group for the following security controls. If it finds violations, then Lambda deletes the stack, stops the pipeline, and returns an error message.
The following is the sample Python code used by AWS Lambda to check if the SSH port is open to the approved IP CIDR range (in this example, 72.21.196.67/32):
for n in regions:
client = boto3.client('ec2', region_name=n)
response = client.describe_security_groups(
Filters=[{'Name': 'tag:aws:cloudformation:stack-name', 'Values': [stackName]}])
for m in response['SecurityGroups']:
if "72.21.196.67/32" not in str(m['IpPermissions']):
for o in m['IpPermissions']:
try:
if int(o['FromPort']) <= 22 <= int(o['ToPort']):
result = False
failReason = "Found Security Group with port 22 open to the wrong source IP range"
offenders.append(str(m['GroupId']))
except:
if str(o['IpProtocol']) == "-1":
result = False
failReason = "Found Security Group with port 22 open to the wrong source IP range"
offenders.append(str(n) + " : " + str(m['GroupId']))
c. Approve test stack: This step creates a manual approval task for stack review. This step could be eliminated for automated deployments.
d. Delete test stack: After all the stack validations are successfully completed, this step deletes the stack in the test environment to avoid unnecessary costs.
3. Production stage: After the static and dynamic security checks are completed successfully, this stage creates the stack in the production VPC using the production configuration supplied in the template.
a. Create change set: This step creates the change set for the resources in the scope.
b. Execute change set: This step executes the change set and creates/updates the security group in the production VPC.
Source code and CloudFormation template
You’ll find the source code at https://github.com/awslabs/automating-governance-sample/tree/master/DevSecOps-Blog-Code
basic-sg-3-cfn.json creates the pipeline in AWS CodePipeline with all the stages previously described. It also creates the static code analysis and stack validation Lambda functions.
The CloudFormation template points to a shared S3 bucket. The codepipeline-lambda.zip file contains the Lambda functions. Before you run the template, upload the zip file to your S3 bucket and then update the CloudFormation template to point to your S3 bucket location.
The CloudFormation template uses the codepipe-single-sg.zip file, which contains the sample security group and test and production configurations. Update these configurations with your VPC details, and then upload the modified zip file to your S3 bucket.
Update these parts of the code to point to your S3 bucket:
"S3Bucket": {
"Default": "codepipeline-devsecops-demo",
"Description": "The name of the S3 bucket that contains the source artifact, which must be in the same region as this stack",
"Type": "String"
},
"SourceS3Key": {
"Default": "codepipe-single-sg.zip",
"Description": "The file name of the source artifact, such as myfolder/myartifact.zip",
"Type": "String"
},
"LambdaS3Key": {
"Default": "codepipeline-lambda.zip",
"Description": "The file name of the source artifact of the Lambda code, such as myfolder/myartifact.zip",
"Type": "String"
},
"OutputS3Bucket": {
"Default": "codepipeline-devsecops-demo",
"Description": "The name of the output S3 bucket that contains the processed artifact, which must be in the same region as this stack",
"Type": "String"
},
After the stack is created, AWS CodePipeline executes the pipeline and starts deploying the sample CloudFormation template. In the default template, security groups have wide-open ports (0.0.0.0/0), so the pipeline execution will fail. Update the CloudFormation template in codepipe-single-sg.zip with more restrictive ports and then upload the modified zip file to S3 bucket. Open the AWS CodePipeline console, and choose the Release Change button. This time the pipeline will successfully create the security groups.
You could expand the security checks in the pipeline to include other AWS resources, not just security groups. The following table shows the sample controls you could enforce in the pipeline using the static and dynamic analysis Lambda functions.
If you have feedback about this post, please add it to the Comments section below. If you have questions about implementing the example used in this post, please open a thread on the Developer Tools forum.