Compliance & Policy as Code

Cloud-Native Guardrails

18 min Lesson 7 of 27

Cloud-Native Guardrails

Policy as Code at the Kubernetes layer (OPA, Kyverno) handles workload-level decisions. But the guardrails that govern your entire cloud account — what regions are allowed, which services can be provisioned, what encryption must be applied — live one layer higher, inside the cloud control plane itself. In AWS, three overlapping mechanisms enforce this layer: Service Control Policies (SCPs), AWS Config rules, and Conformance Packs. Understanding when to use each, and how they fail, is the difference between a compliance posture that holds under pressure and one that breaks the first time an engineer works around it.

Service Control Policies: the Outer Fence

SCPs are IAM policy documents attached to AWS Organizations accounts or Organizational Units (OUs). Unlike IAM policies — which grant permissions — SCPs define the maximum permission boundary. Even an account's root user cannot do what an SCP denies. This is the property that makes SCPs uniquely powerful: they apply to every principal in the account without exception.

Key design principles for SCPs at production scale:

  • Deny-by-default is the correct mental model. AWS Organizations ships with a built-in FullAWSAccess policy that allows everything. Your SCPs layer additional explicit denies on top of it. SCPs act as an allow-filter on what IAM can grant, not as grants themselves.
  • SCPs do not grant permissions. A principal still needs an IAM policy that allows the action. SCP + IAM allow = effective permission. SCP deny (or no SCP allow within a FullAWSAccess + deny model) = blocked, regardless of IAM.
  • Exemptions require a condition, not a separate policy. Rather than creating two SCPs — one that denies for most and one that allows for an exception — use ArnNotLike or StringNotEquals conditions to exempt specific roles inline. This keeps the policy surface minimal and auditable.
  • Test in a sandbox OU first. A broken SCP that denies sts:AssumeRole in a production account will lock out all cross-account automation. You cannot fix it from inside the account — only an Organizations administrator can remove the SCP.
# SCP: Deny any action outside approved AWS regions # Attach to the Workloads OU; exempts global services that have no region concept { "Version": "2012-10-17", "Statement": [ { "Sid": "DenyNonApprovedRegions", "Effect": "Deny", "NotAction": [ "iam:*", "organizations:*", "route53:*", "budgets:*", "cloudfront:*", "sts:*", "support:*", "trustedadvisor:*" ], "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": [ "us-east-1", "eu-west-1" ] } } } ] }
The region-restriction SCP is one of the most common account lock-out vectors. If you forget to add IAM and STS to NotAction, CloudFormation stack deployments will fail silently because CloudFormation calls sts:AssumeRole internally — and the SCP will block it even when your deployment role has the right IAM policies. Always enumerate global services in NotAction before deploying this type of SCP to a production OU.

A second common SCP enforces that all S3 buckets must have encryption and block public access. This is not redundant with AWS Config — Config detects after the fact, whereas an SCP prevents provisioning in the first place:

# SCP: Deny S3 PutObject unless server-side encryption header is present # Prevents unencrypted uploads at the API layer { "Version": "2012-10-17", "Statement": [ { "Sid": "DenyUnencryptedS3Uploads", "Effect": "Deny", "Action": "s3:PutObject", "Resource": "*", "Condition": { "StringNotEquals": { "s3:x-amz-server-side-encryption": [ "aws:kms", "AES256" ] } } }, { "Sid": "DenyS3PublicAccess", "Effect": "Deny", "Action": [ "s3:PutBucketPublicAccessBlock", "s3:DeletePublicAccessBlock" ], "Resource": "*", "Condition": { "StringEquals": { "s3:publicAccessBlockConfiguration": "false" } } } ] }
AWS Guardrail layers: SCP, AWS Config, and Conformance Packs AWS Cloud Guardrail Layers Layer 1 — SCP (Preventive) • Blocks API calls before they reach the service • Applies to ALL principals including root; cannot be overridden by account-level IAM Scope: AWS Organizations account or OU Granularity: Action / Resource / Condition Latency: zero (synchronous at control plane) Layer 2 — AWS Config Rules (Detective) • Continuously evaluates resource configuration against desired state • Marks resources COMPLIANT / NON_COMPLIANT; triggers SNS / Lambda remediation Scope: individual AWS account (or aggregator) Granularity: resource type + attribute Latency: minutes (event-driven or periodic) Layer 3 — Conformance Packs (Reporting) • Groups Config rules into a named framework mapped to SOC 2 / PCI / CIS benchmarks • Produces aggregated compliance score and per-rule evidence for auditors Scope: multi-account via AWS Organizations
The three AWS guardrail layers: SCPs prevent, Config rules detect, Conformance Packs report.

AWS Config Rules: Detective Controls at Scale

Where SCPs are preventive — they block before anything happens — AWS Config rules are detective. Config records the configuration state of every supported AWS resource and evaluates it against rules you define. When a resource drifts out of compliance, Config marks it NON_COMPLIANT and can trigger automated remediation via an SSM Automation document or a Lambda function.

Config rules come in two forms:

  • AWS-managed rules: pre-built rules for the most common compliance checks. encrypted-volumes, rds-storage-encrypted, s3-bucket-ssl-requests-only, iam-password-policy, mfa-enabled-for-iam-console-access — over 200 rules available. Use these before writing custom rules; they are maintained by AWS and updated when services change.
  • Custom rules: Lambda functions you write and deploy that receive a configuration snapshot and return a compliance verdict. Use these when a control is specific to your architecture — e.g., enforcing that every RDS cluster must have a specific tag set for cost allocation, or that ECS task definitions must not run as root.
Config rules evaluate on change or on a schedule. Change-triggered rules fire within minutes of a resource modification — ideal for encryption and access controls where drift is an immediate risk. Periodic rules (1hr, 3hr, 6hr, 12hr, 24hr) are better for controls that are expensive to evaluate on every API call, such as scanning IAM policy documents for wildcards. Most compliance-critical rules should be change-triggered.

Deploying a Config rule via Terraform — the correct way to manage Config at scale:

# Terraform: Deploy two managed Config rules with automatic remediation resource "aws_config_config_rule" "rds_encrypted" { name = "rds-storage-encrypted" source { owner = "AWS" source_identifier = "RDS_STORAGE_ENCRYPTED" } depends_on = [aws_config_configuration_recorder.main] } resource "aws_config_config_rule" "s3_ssl_only" { name = "s3-bucket-ssl-requests-only" source { owner = "AWS" source_identifier = "S3_BUCKET_SSL_REQUESTS_ONLY" } depends_on = [aws_config_configuration_recorder.main] } # Auto-remediation: enable RDS encryption on non-compliant instances resource "aws_config_remediation_configuration" "rds_encrypt_remediation" { config_rule_name = aws_config_config_rule.rds_encrypted.name target_type = "SSM_DOCUMENT" target_id = "AWS-EnableRDSEncryption" automatic = true maximum_automatic_attempts = 3 retry_attempt_seconds = 60 parameter { name = "DBInstanceIdentifier" resource_value = "RESOURCE_ID" } }
Auto-remediation can destroy data if misconfigured. Enabling encryption on a running RDS instance requires stopping the instance and creating a new encrypted snapshot — this is a disruptive operation with downtime. Always test remediation documents in a non-production account, set automatic = false initially, and require human approval via an SNS notification before switching to fully automatic remediation on stateful resources.

Conformance Packs: Mapping Rules to Frameworks

A Conformance Pack is a YAML template that bundles a set of Config rules — and optionally their remediation actions — into a single deployable unit mapped to a compliance framework. AWS ships pre-built packs for CIS AWS Foundations Benchmark, PCI DSS, HIPAA, NIST 800-53, and SOC 2. You deploy them to individual accounts or across an entire Organization via the AWS Config console, CLI, or Terraform.

The primary value of conformance packs is audit evidence. Instead of manually running Config rule queries and assembling a spreadsheet, you can point an auditor at the Conformance Pack dashboard: a single page showing per-rule compliance percentage, the list of non-compliant resources, and the timestamps of all evaluations. This turns a week of audit prep into a URL.

# Deploy the AWS-managed CIS Level 2 Conformance Pack via CLI # This enables ~52 Config rules aligned to CIS AWS Foundations Benchmark v1.4 aws configservice put-conformance-pack \ --conformance-pack-name "CISLevel2Benchmark" \ --template-s3-uri "s3://aws-service-catalog-us-east-1-artifacts/conformance-packs/operational-best-practices-for-cis-aws-benchmark-level-2.yaml" \ --region us-east-1 # Check overall compliance score for the pack aws configservice describe-conformance-pack-compliance \ --conformance-pack-name "CISLevel2Benchmark" \ --query "ConformancePackRuleComplianceList[?ComplianceType==\'NON_COMPLIANT\']" \ --output table # List all non-compliant resources for a specific rule within the pack aws configservice get-compliance-details-by-config-rule \ --config-rule-name "CIS-2-1-ensure-cloudtrail-enabled-in-all-regions" \ --compliance-types NON_COMPLIANT \ --output json

For custom packs — when you need to map your specific internal policies to a named framework — author the YAML template directly:

# Custom Conformance Pack: Production Encryption Baseline # File: conformance-packs/encryption-baseline.yaml Parameters: S3TargetBucketName: Type: String Default: "" Resources: RDSStorageEncrypted: Type: AWS::Config::ConfigRule Properties: ConfigRuleName: rds-storage-encrypted Source: Owner: AWS SourceIdentifier: RDS_STORAGE_ENCRYPTED EBSVolumeEncrypted: Type: AWS::Config::ConfigRule Properties: ConfigRuleName: encrypted-volumes Source: Owner: AWS SourceIdentifier: ENCRYPTED_VOLUMES S3BucketSSLOnly: Type: AWS::Config::ConfigRule Properties: ConfigRuleName: s3-bucket-ssl-requests-only Source: Owner: AWS SourceIdentifier: S3_BUCKET_SSL_REQUESTS_ONLY SecretsManagerRotationEnabled: Type: AWS::Config::ConfigRule Properties: ConfigRuleName: secretsmanager-rotation-enabled-check Source: Owner: AWS SourceIdentifier: SECRETSMANAGER_ROTATION_ENABLED_CHECK # Deploy this pack across all accounts in an OU via Organizations: # aws configservice put-organization-conformance-pack \ # --organization-conformance-pack-name "EncryptionBaseline" \ # --template-body file://conformance-packs/encryption-baseline.yaml \ # --delivery-s3-bucket your-config-delivery-bucket

Layering the Three Mechanisms

At a well-run organization, these three tools are not alternatives — they form a layered defense where each handles the cases the others cannot:

  • SCPs enforce the absolute boundaries of what the account can do. Region restrictions, root account lockdown, disabling services that have no legitimate use in your workloads (e.g., deny lightsail:* in an account that only runs ECS). SCPs cannot evaluate resource attributes — they act on the API call, not on the resulting resource state.
  • Config rules evaluate resource configuration continuously. They catch drift that SCPs cannot — for example, a bucket that existed before the encryption SCP was applied, or a tag that was removed after provisioning. Config rules are the primary mechanism for ongoing drift detection.
  • Conformance Packs aggregate Config rule results into framework-mapped reports. They do not add new enforcement; they add audit-ready visibility and a governance layer that names the framework each rule satisfies.
Production pattern used at large-scale AWS environments: SCPs live in version-controlled JSON in a dedicated scp/ folder, deployed via a Terraform root module in the management account. Config rules and Conformance Packs are deployed via a separate config/ module that runs in every member account through an Organizations StackSet. A central Config Aggregator in the management account rolls up compliance state from all accounts into a single dashboard. Non-compliant findings over a configurable age threshold trigger a Jira ticket via EventBridge + Lambda, and the ticket is linked to the Config evaluation for audit evidence. This closes the loop between detection and remediation without any manual spreadsheet work.