Skip to main content

Compliance-Ready Infrastructure on AWS Guide

9 min read
Build compliance into my AWS

We build audit logging, automated controls and guardrails into the account from the start.

Build AWS infrastructure that meets SOC 2, HIPAA, and GDPR compliance requirements with automated controls, audit logging, and security guardrails.

Compliance as Architecture

Compliance is often treated as an afterthought - a checklist exercise before an audit. This approach leads to expensive retroactive fixes, manual evidence collection, and infrastructure that barely passes requirements. A better approach builds compliance controls directly into the infrastructure architecture from day one.

This guide covers practical patterns for building AWS infrastructure that satisfies SOC 2, HIPAA, and GDPR requirements through automated controls and continuous monitoring. These patterns form the core of our security and compliance services.

Foundational AWS Account Structure

AWS Organizations with SCPs

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyRootUser",
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringLike": {
          "aws:PrincipalArn": "arn:aws:iam::*:root"
        }
      }
    },
    {
      "Sid": "RequireIMDSv2",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringNotEquals": {
          "ec2:MetadataHttpTokens": "required"
        }
      }
    },
    {
      "Sid": "DenyUnencryptedS3",
      "Effect": "Deny",
      "Action": "s3:PutObject",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms"
        }
      }
    },
    {
      "Sid": "RestrictRegions",
      "Effect": "Deny",
      "NotAction": [
        "iam:*",
        "organizations:*",
        "sts:*",
        "support:*",
        "cloudfront:*"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": ["us-east-1", "eu-west-1"]
        }
      }
    }
  ]
}

Service Control Policies enforce guardrails across all accounts in the organization. These policies prevent root user access, require IMDSv2 (preventing SSRF-based credential theft), enforce S3 encryption, and restrict deployment to approved regions.

Encryption Everywhere

KMS Key Management

resource "aws_kms_key" "main" {
  description             = "Primary encryption key for production data"
  deletion_window_in_days = 30
  enable_key_rotation     = true
  multi_region            = true

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "KeyAdministration"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/SecurityAdmin"
        }
        Action = [
          "kms:Create*",
          "kms:Describe*",
          "kms:Enable*",
          "kms:List*",
          "kms:Put*",
          "kms:Update*",
          "kms:Revoke*",
          "kms:Disable*",
          "kms:Get*",
          "kms:Delete*",
          "kms:ScheduleKeyDeletion",
          "kms:CancelKeyDeletion"
        ]
        Resource = "*"
      },
      {
        Sid    = "KeyUsage"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/ApplicationRole"
        }
        Action = [
          "kms:Decrypt",
          "kms:DescribeKey",
          "kms:Encrypt",
          "kms:GenerateDataKey*"
        ]
        Resource = "*"
      }
    ]
  })
}

Data at Rest Encryption

# RDS encryption
resource "aws_db_instance" "production" {
  storage_encrypted = true
  kms_key_id       = aws_kms_key.main.arn
}

# EBS encryption (default for all volumes)
resource "aws_ebs_encryption_by_default" "enabled" {
  enabled = true
}

resource "aws_ebs_default_kms_key" "main" {
  key_arn = aws_kms_key.main.arn
}

# S3 bucket encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "main" {
  bucket = aws_s3_bucket.data.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.main.id
    }
    bucket_key_enabled = true
  }
}

Audit Logging

CloudTrail Configuration

resource "aws_cloudtrail" "organization" {
  name                          = "organization-trail"
  s3_bucket_name                = aws_s3_bucket.audit_logs.id
  is_organization_trail         = true
  is_multi_region_trail         = true
  enable_log_file_validation    = true
  include_global_service_events = true
  kms_key_id                    = aws_kms_key.audit.arn

  cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail.arn}:*"
  cloud_watch_logs_role_arn  = aws_iam_role.cloudtrail_cloudwatch.arn

  event_selector {
    read_write_type           = "All"
    include_management_events = true

    data_resource {
      type   = "AWS::S3::Object"
      values = ["arn:aws:s3"]
    }
  }

  insight_selectors {
    insight_type = "ApiCallRateInsight"
  }

  insight_selectors {
    insight_type = "ApiErrorRateInsight"
  }
}

# Immutable log storage
resource "aws_s3_bucket_object_lock_configuration" "audit_logs" {
  bucket = aws_s3_bucket.audit_logs.id

  rule {
    default_retention {
      mode = "COMPLIANCE"
      days = 365
    }
  }
}

S3 Object Lock in Compliance mode ensures audit logs cannot be deleted or modified, even by the root account. This satisfies SOC 2 and HIPAA requirements for tamper-proof audit trails.

Access Management

Least Privilege IAM Policies

resource "aws_iam_policy" "application" {
  name = "application-policy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "ReadOwnBucket"
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:PutObject",
          "s3:ListBucket"
        ]
        Resource = [
          aws_s3_bucket.app_data.arn,
          "${aws_s3_bucket.app_data.arn}/*"
        ]
      },
      {
        Sid    = "ReadSecrets"
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue"
        ]
        Resource = "arn:aws:secretsmanager:us-east-1:*:secret:production/*"
      },
      {
        Sid    = "DecryptWithKMS"
        Effect = "Allow"
        Action = [
          "kms:Decrypt",
          "kms:GenerateDataKey"
        ]
        Resource = aws_kms_key.main.arn
      }
    ]
  })
}

MFA Enforcement

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyAllExceptMFASetup",
      "Effect": "Deny",
      "NotAction": [
        "iam:CreateVirtualMFADevice",
        "iam:EnableMFADevice",
        "iam:GetUser",
        "iam:ListMFADevices",
        "iam:ListVirtualMFADevices",
        "iam:ResyncMFADevice",
        "sts:GetSessionToken"
      ],
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    }
  ]
}

Automated Compliance Monitoring

AWS Config Rules

resource "aws_config_config_rule" "encrypted_volumes" {
  name = "encrypted-volumes"
  source {
    owner             = "AWS"
    source_identifier = "ENCRYPTED_VOLUMES"
  }
}

resource "aws_config_config_rule" "s3_encryption" {
  name = "s3-bucket-server-side-encryption-enabled"
  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
  }
}

resource "aws_config_config_rule" "rds_encrypted" {
  name = "rds-storage-encrypted"
  source {
    owner             = "AWS"
    source_identifier = "RDS_STORAGE_ENCRYPTED"
  }
}

resource "aws_config_config_rule" "mfa_enabled" {
  name = "iam-user-mfa-enabled"
  source {
    owner             = "AWS"
    source_identifier = "IAM_USER_MFA_ENABLED"
  }
}

resource "aws_config_config_rule" "root_mfa" {
  name = "root-account-mfa-enabled"
  source {
    owner             = "AWS"
    source_identifier = "ROOT_ACCOUNT_MFA_ENABLED"
  }
}

Security Hub Integration

Enable AWS Security Hub with compliance standards:

resource "aws_securityhub_account" "main" {}

resource "aws_securityhub_standards_subscription" "cis" {
  depends_on    = [aws_securityhub_account.main]
  standards_arn = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.4.0"
}

resource "aws_securityhub_standards_subscription" "pci" {
  depends_on    = [aws_securityhub_account.main]
  standards_arn = "arn:aws:securityhub:::standards/pci-dss/v/3.2.1"
}

GDPR-Specific Controls

Data Classification and Retention

resource "aws_s3_bucket_lifecycle_configuration" "pii_data" {
  bucket = aws_s3_bucket.pii.id

  rule {
    id     = "delete-after-retention"
    status = "Enabled"

    expiration {
      days = 730  # 2-year retention for PII
    }

    transition {
      days          = 90
      storage_class = "GLACIER"
    }
  }
}

Right to Erasure Implementation

Build automated data deletion pipelines triggered by customer requests, covering all data stores - RDS, S3, Elasticsearch, and backups. Log every deletion action for audit purposes.

Compliance Evidence Automation

Automate evidence collection for audits:

#!/bin/bash
# Quarterly compliance evidence collection
REPORT_DATE=$(date +%Y-Q$(( ($(date +%-m) - 1) / 3 + 1 )))

# IAM access review
aws iam generate-credential-report
aws iam get-credential-report --output text --query Content | base64 -d > "iam-report-${REPORT_DATE}.csv"

# Config compliance summary
aws configservice get-compliance-summary-by-config-rule > "config-compliance-${REPORT_DATE}.json"

# Security Hub findings
aws securityhub get-findings --filters '{"ComplianceStatus":[{"Value":"FAILED","Comparison":"EQUALS"}]}' > "securityhub-findings-${REPORT_DATE}.json"

# CloudTrail digest validation
aws cloudtrail validate-logs --trail-arn arn:aws:cloudtrail:us-east-1:123456789:trail/organization-trail --start-time $(date -d '-90 days' +%Y-%m-%dT%H:%M:%SZ)

Best Practices

  • Treat compliance controls as code - version control all policies and configurations
  • Automate evidence collection to reduce audit preparation from weeks to hours
  • Use AWS Organizations with SCPs to enforce guardrails across all accounts
  • Enable CloudTrail with log file validation and immutable storage
  • Rotate encryption keys automatically with KMS key rotation
  • Conduct quarterly access reviews and revoke unused permissions
  • Integrate compliance monitoring into your infrastructure management workflows

Building compliance into your architecture from the start costs a fraction of retrofitting it later. The automated controls and continuous monitoring described here satisfy auditors while reducing operational burden on engineering teams.

Frequently asked questions

Which AWS Service Control Policies should a compliance-ready AWS organization start with?

A baseline set of SCPs can deny every action by the root user, block ec2:RunInstances unless IMDSv2 is required, reject S3 PutObject calls that do not use aws:kms server-side encryption, and deny activity outside approved regions (us-east-1 and eu-west-1 in the example, with global services such as IAM, STS and CloudFront exempted). Requiring IMDSv2 helps prevent SSRF-based credential theft. AWS Organizations applies SCPs across every account, so the guardrails cover the whole organization.

How do I make AWS CloudTrail logs tamper-proof for SOC 2 and HIPAA audits?

Send CloudTrail logs to an S3 bucket with Object Lock in COMPLIANCE mode, which stops the logs from being deleted or modified, even by the root account. The example sets a default retention of 365 days. That setup satisfies the SOC 2 and HIPAA requirements for tamper-proof audit trails. The trail itself is an organization-wide, multi-region trail with log file validation, KMS encryption and global service events enabled.

How should AWS KMS keys be configured for encrypting compliance data?

Create a dedicated KMS key with automatic key rotation, a 30-day deletion window and, in the example, multi-region support. The key policy separates duties, letting a SecurityAdmin role administer the key while an ApplicationRole can only encrypt, decrypt, describe the key and generate data keys. That key then encrypts RDS storage, EBS volumes through EBS encryption by default, and S3 buckets through aws:kms server-side encryption with bucket keys enabled.

How can I monitor AWS compliance continuously between audits?

AWS Config rules and AWS Security Hub check resources continuously. Managed Config rules such as ENCRYPTED_VOLUMES, S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED, RDS_STORAGE_ENCRYPTED, IAM_USER_MFA_ENABLED and ROOT_ACCOUNT_MFA_ENABLED flag unencrypted storage and missing MFA. Security Hub adds compliance standards on top, and the example subscribes to the CIS AWS Foundations Benchmark v1.4.0 and PCI DSS v3.2.1.

How do I enforce MFA for IAM users on AWS?

Attach an IAM policy that denies every action except the MFA setup calls whenever aws:MultiFactorAuthPresent is false. The exempted actions in the example are iam:CreateVirtualMFADevice, iam:EnableMFADevice, iam:GetUser, iam:ListMFADevices, iam:ListVirtualMFADevices, iam:ResyncMFADevice and sts:GetSessionToken, checked with a BoolIfExists condition. A user who signs in without MFA can register a device and do nothing else.

How do I implement GDPR data retention and the right to erasure on AWS?

For retention, an S3 lifecycle rule on the PII bucket can move objects to GLACIER after 90 days and delete them after 730 days, which gives a 2-year retention period. For erasure, build automated deletion pipelines triggered by customer requests that cover every data store, including RDS, S3, Elasticsearch and backups. Log each deletion action so it can be shown during an audit.

How can I automate evidence collection for an AWS compliance audit?

Run a quarterly script with the AWS CLI that saves each report under a quarter label. The example generates and downloads the IAM credential report, saves the AWS Config compliance summary by rule, exports Security Hub findings whose compliance status is FAILED, and runs aws cloudtrail validate-logs for the last 90 days. Automating evidence collection this way can cut audit preparation from weeks to hours.

Is it cheaper to build compliance into AWS infrastructure from the start?

Yes. Building compliance controls into the architecture from day one costs a fraction of retrofitting them later. Treating compliance as a checklist exercise before an audit tends to produce expensive retroactive fixes, manual evidence collection and infrastructure that barely passes. Controls kept as code in version control and checked by continuous monitoring satisfy auditors and reduce the operational burden on engineering teams.

Read how we handle it in Cybersecurity.