Intro 

Security and compliance controls are an important part of the software development life cycle.  When organizations and teams move software delivery from months to hours, the processes related to compliance evaluation can become a bottleneck for delivery.  In his article, “Compliance in a DevOps Culture,” Carl Nygard outlines different approaches teams can take to address these compliance bottlenecks within large organizations. One of those strategies is Point-of-Change compliance. This approach seeks to validate compliance controls as close as possible to deploy those changes. Applying this approach to cloud infrastructure changes via CloudFormation will require reviewing templates and changesets before execution. Let’s review some of the tools available for implementing Point-of-Change compliance with CloudFormation. 

Gaps within existing compliance tools

Numerous policy-as-code tools exist to evaluate CloudFormation templates automatically. Tools such as cfn_nag, Guard, Regula, or OPA work at the template level. They consume an existing YAML or JSON document and provide feedback to the developer if they passed the compliance rules. The benefit is straightforward: compliance rules are codified, and templates are vetted against them before being used. Or are they? Since these tools are executed outside of the CloudFormation stack lifecycle, enforcement of those results is dependent on pipeline integrations and process convention within an organization. There is no technical roadblock to launching a CloudFormation stack that has failed those automated compliance checks. Therefore, none of these hit the ideal mark for being able to enforce Point-of-Change compliance.

Can we do better? Using the AWS Cloud Development Kit (CDK), Om Prakash Jha outlines how to use aspects to enforce compliance requirements. The compliance rules are part of the CDK lifecycle and get evaluated prior to CDK’s synthesis into a CloudFormation template. This is a good model for Point-of-Change compliance. The rules get evaluated alongside the change and enforced when we execute the update. However, since the compliance rules are integrated alongside the infrastructure code, the rules may be vulnerable to being disabled by the infrastructure change’s author.

The best way to enforce Point-of-Change compliance would be to hook directly into the lifecycle of the CloudFormation service. Such an approach would allow compliance rules to exist separate from the change and tied as close as possible to executing those changes.

Introduce Hooks

While other AWS services have provided lifecycle hooks(e.g., EC2 Autoscaling Groups), CloudFormation interactions were previously restricted to actions directly on the stack (create, update, rollback, delete) and cfn-signal for specific resources. AWS is updating this with the launch of CloudFormation Hooks.

CloudFormation Hooks allow developers to inject code into the CloudFormation lifecycle. A hook has several different attributes that configure how it interacts with a stack. A hook is executed based on the combination of target resources (e.g., AWS::ECR::Repository), invocation points (before provisioning), and actions (create, update, delete). When those three criteria are met, CloudFormation will invoke the handler of the hook. The handler receives information about the stack and target resource, which it can then use to either pass or fail the resource. If a hook invocation fails, the stack can either fail or log the warning depending on the configuration.

Using hooks improves the ability to implement Point-of-Change compliance, as resources can be checked before provisioning directly within the CloudFormation lifecycle. A sample hook is explored for setting compliance rules on the AWS::ECR::Repository resource below.

Example Hook for Compliance

Consider the following compliance rules around an AWS::ECR::Repository:

  1. All ECR repos must use KMS encryption
  2. All ECR repos must enforce immutable tags
  3. All ECR repos must scan images on push

These rules can be implemented in a CloudFormation Hook, ensuring that any repo resource created via CloudFormation has the appropriate attributes set. A partial hook implementation for these three conditions is provided below. The full example is available on Github.

handlers.py
@hook.handler(HookInvocationPoint.CREATE_PRE_PROVISION)
def pre_create_handler(
        session: Optional[SessionProxy],
        request: HookHandlerRequest,
        callback_context: MutableMapping[str, Any],
        type_configuration: TypeConfigurationModel
) -> ProgressEvent:
    target_model = request.hookContext.targetModel
    target_name = request.hookContext.targetName
    progress: ProgressEvent = ProgressEvent(
        status=OperationStatus.IN_PROGRESS
    )

    try:
        # Verify this is a ECR Repo resource
        if "AWS::ECR::Repository" == target_name:
            resource_properties = target_model.get("resourceProperties")

            error_messages = []

            # Check Encryption
            repo_encryption_type = resource_properties.get("EncryptionConfiguration", {}).get("EncryptionType")
            if repo_encryption_type != "KMS":
                error_messages.append("EncryptionType must be KMS")

            # Check Scan on Push
            scan_on_push = resource_properties.get("ImageScanningConfiguration", {}).get("ScanOnPush")
            if scan_on_push != "true":
                error_messages.append("ScanOnPush must be enabled")

            # Check Image Tag Mutability
            image_tag_mutability = resource_properties.get("ImageTagMutability")
            if image_tag_mutability != "IMMUTABLE":
                error_messages.append("ImageTagMutability must be IMMUTABLE")

            if error_messages:
                progress.status = OperationStatus.FAILED
                progress.message = "\n".join(error_messages)
                progress.errorCode = HandlerErrorCode.NonCompliant
            else:
                progress.status = OperationStatus.SUCCESS
                progress.message = "No issues found"
        else:
            raise exceptions.InvalidRequest(f"Unknown target type: {target_name}")
    except TypeError as e:
        raise exceptions.InternalFailure(f"was not expecting type {e}")

    return progress

The hook implementation is invoked before creating each AWS::ECR::Repository resource. The resource properties are available for inspection and checked for compliance with each of our three rules above. If any are not set as expected, the hook returns a failed status and appropriate error message, presented to the user in stack events (Figure 1). 

CloudFormation Hook Details

Figure 1

 

With the use of Hooks, enforcement of these compliance rules occurs immediately before creating the resources. Additional hooks can be written for other AWS resources to meet the expectations of the compliance organization. As shown above, each hook is tailored to the specific CloudFormation resource for which it is invoked. 

Looking ahead

The launch of Hooks fills a gap in the ability to enforce Point-of-Change compliance with infrastructure automation using CloudFormation. The barrier to entry in using this as a compliance tool is still pretty high. Compliance rules must be written in Python or Java and targeted to specific resources. Teams with rules developed for existing tools that review templates will have to rewrite those into hooks or utilize mixed strategies. One solution might be to have analysis tools sign a scan of a template and then use a hook to verify that scan at the Point-of-Change. In the future, AWS could also expand Hooks to support rule definition using open-source rule engines, such as Rego, to reduce the coding effort for compliance teams to write hooks.

Conclusion

With CloudFormation Hooks, organizations can now push compliance rules closer to Point-of-Change, improving the effectiveness of the compliance process while eliminating roadblocks for developers. Check out the documentation and write your first hook today!

Stelligent Amazon Pollycast
Voiced by Amazon Polly