Header menu logo FsCDK

Governance and Compliance with AWS Organizations

This guide covers enterprise governance patterns, compliance automation, and organizational controls for AWS infrastructure. Based on AWS Security Best Practices and patterns from Fortune 500 implementations.

Understanding AWS Governance Services

AWS provides multiple services for governance and compliance. Understanding their interaction is critical for effective organizational control.

AWS Organizations: Multi-account management and consolidated billing Service Control Policies (SCPs): Permission guardrails at organizational level AWS Config: Resource compliance monitoring and change tracking AWS Control Tower: Automated multi-account governance Tag Policies: Standardize resource tagging across organization

This layered approach follows the defense-in-depth principle recommended by AWS Security Reference Architecture.

Reference: AWS Security Reference Architecture (https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/)

Multi-Account Strategy

AWS recommends a multi-account architecture for security isolation and blast radius reduction. This is not optional for enterprises handling sensitive data or subject to compliance requirements.

Recommended Account Structure

Based on AWS best practices and implementations at companies like Capital One and Netflix:

open Amazon.CDK.AWS.Lambda
open Amazon.CDK.AWS.DynamoDB
open Amazon.CDK.AWS.S3
open Amazon.CDK.AWS.CloudWatch

#r "../src/bin/Release/net8.0/publish/Amazon.JSII.Runtime.dll"
#r "../src/bin/Release/net8.0/publish/Constructs.dll"
#r "../src/bin/Release/net8.0/publish/Amazon.CDK.Lib.dll"
#r "../src/bin/Release/net8.0/publish/FsCDK.dll"

open FsCDK
open Amazon.CDK
open Amazon.CDK.AWS.Organizations
open Amazon.CDK.AWS.Config

Management Account (root)

Security Account

Log Archive Account

Shared Services Account

Production Account(s)

Development/Staging Accounts

Reference: AWS Multi-Account Strategy whitepaper (https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/)

Why Multi-Account Architecture Matters

The AWS CTO Werner Vogels emphasizes in "Modern Applications at AWS" that account boundaries provide the strongest isolation mechanism in AWS. A compromised account cannot directly affect resources in other accounts.

Real-world incident: Capital One breach (2019) was contained to development accounts due to multi-account isolation. Production accounts remained secure.

Service Control Policies

SCPs provide organization-wide guardrails that even account administrators cannot bypass. They act as permission boundaries, not grants.

Critical SCPs for Enterprise

These policies prevent common security mistakes and enforce organizational standards.

Prevent Disabling Security Services

This SCP prevents disabling CloudTrail, GuardDuty, and Config in any account. Required for PCI DSS, HIPAA, and SOC 2 compliance.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "cloudtrail:StopLogging",
        "cloudtrail:DeleteTrail",
        "guardduty:DeleteDetector",
        "guardduty:DisassociateFromMasterAccount",
        "config:DeleteConfigurationRecorder",
        "config:DeleteDeliveryChannel",
        "config:StopConfigurationRecorder"
      ],
      "Resource": "*"
    }
  ]
}

Reference: CIS AWS Foundations Benchmark v1.4, Control 3.1

Require Encryption

Prevent creating unencrypted resources. Critical for HIPAA, PCI DSS, and GDPR compliance.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "s3:PutObject"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": ["AES256", "aws:kms"]
        }
      }
    },
    {
      "Effect": "Deny",
      "Action": "rds:CreateDBInstance",
      "Resource": "*",
      "Condition": {
        "Bool": {
          "rds:StorageEncrypted": "false"
        }
      }
    }
  ]
}

Restrict Regions

Limit resource creation to approved regions. Required for data residency compliance (GDPR, CCPA, SOX).

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "NotAction": [
        "iam:*",
        "sts:*",
        "cloudfront:*",
        "route53:*",
        "support:*"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": [
            "us-east-1",
            "us-west-2",
            "eu-west-1"
          ]
        }
      }
    }
  ]
}

Reference: AWS Organizations SCP Best Practices (https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps_examples.html)

Testing SCPs Safely

Never apply SCPs directly to production. Use this testing workflow recommended by AWS Solutions Architects:

  1. Create test Organizational Unit (OU)
  2. Move test account into OU
  3. Apply SCP to OU
  4. Validate permissions work as expected
  5. Gradually expand to production OUs

Warning: SCPs can lock you out. Always maintain break-glass access via management account.

AWS Config for Compliance Automation

AWS Config continuously monitors resource configurations and evaluates them against rules. This is required for automated compliance reporting.

Essential Config Rules

Implement these rules for baseline security posture:

// Example Config rule for encrypted volumes
// In practice, use AWS CDK constructs or CloudFormation

Required Config Rules by Framework:

PCI DSS:

HIPAA:

SOC 2:

Reference: AWS Config Conformance Packs (https://docs.aws.amazon.com/config/latest/developerguide/conformance-packs.html)

Automated Remediation

AWS Config supports automatic remediation using Systems Manager Automation documents. This reduces manual toil and compliance drift.

Example remediations:

Reference: AWS Config Remediation Actions (https://docs.aws.amazon.com/config/latest/developerguide/remediation.html)

Tagging Strategy for Governance

Resource tagging enables cost allocation, access control, and compliance tracking. AWS recommends mandatory tagging enforced via Tag Policies.

Mandatory Tag Schema

Based on AWS Tagging Best Practices and implementations at AWS customers:

Required Tags:

Optional but Recommended:

Enforcing Tags with Tag Policies

Tag policies standardize tags across accounts. This example requires specific tags on EC2 instances:

{
  "tags": {
    "Environment": {
      "tag_key": {
        "@@assign": "Environment"
      },
      "tag_value": {
        "@@assign": ["dev", "staging", "production"]
      },
      "enforced_for": {
        "@@assign": ["ec2:instance", "rds:db"]
      }
    },
    "CostCenter": {
      "tag_key": {
        "@@assign": "CostCenter"
      },
      "enforced_for": {
        "@@assign": ["ec2:*", "rds:*", "s3:bucket"]
      }
    }
  }
}

Reference: AWS Tagging Best Practices (https://docs.aws.amazon.com/whitepapers/latest/tagging-best-practices/)

FsCDK Tag Enforcement

FsCDK can apply tags at stack level:

stack "TaggedInfrastructure" {
    tags
        [ "Environment", "production"
          "Owner", "platform-team"
          "CostCenter", "engineering-infrastructure"
          "Application", "user-service"
          "Compliance", "pci-dss" ]

    // All resources in this stack inherit these tags
    lambda "ApiHandler" {
        runtime Runtime.DOTNET_8
        handler "Handler::process"
        code "./publish"
    }

    table "UserData" {
        partitionKey "userId" AttributeType.STRING
        billingMode BillingMode.PAY_PER_REQUEST
    }
}

AWS Control Tower for Landing Zones

AWS Control Tower automates multi-account setup with pre-configured guardrails. Recommended for new AWS Organizations implementations.

Control Tower Benefits

Reference: AWS Control Tower documentation (https://docs.aws.amazon.com/controltower/latest/userguide/what-is-control-tower.html)

When to Use Control Tower

Use Control Tower if:

Don't use Control Tower if:

Cost Allocation and Chargeback

Governance includes financial accountability. Use Cost Allocation Tags and AWS Cost Explorer for chargeback.

Implementing Chargeback

  1. Activate Cost Allocation Tags in billing console
  2. Tag all resources with CostCenter, Team, Application
  3. Create Cost Explorer filters by tag values
  4. Generate monthly reports per cost center
  5. Automate reports using AWS Cost and Usage Reports API

Cost allocation takes 24 hours to activate after enabling tags.

FinOps Best Practices

From "Cloud FinOps" by J.R. Storment and Mike Fuller (O'Reilly, 2019):

Compliance Frameworks Mapping

PCI DSS Requirements

AWS provides PCI DSS Level 1 infrastructure. You inherit infrastructure controls but must implement application-level controls.

Inherited Controls:

Your Responsibility:

Reference: AWS PCI DSS Compliance documentation (https://aws.amazon.com/compliance/pci-dss-level-1-faqs/)

HIPAA Compliance

AWS is HIPAA eligible. You must sign a Business Associate Agreement (BAA) and implement technical safeguards.

Required Technical Safeguards:

Recommended AWS Services for HIPAA:

Reference: AWS HIPAA Compliance whitepaper (https://docs.aws.amazon.com/whitepapers/latest/architecting-hipaa-security-and-compliance-on-aws/)

For disaster recovery and backup strategies required by HIPAA 164.308(a)(7), see the Backup and Disaster Recovery guide.

SOX Compliance

Sarbanes-Oxley requires controls over financial data. AWS Config and CloudTrail provide audit evidence.

Key SOX Controls:

Implementation:

GDPR Compliance

General Data Protection Regulation requires data protection controls and data residency.

GDPR Requirements:

AWS Services for GDPR:

Reference: AWS GDPR Center (https://aws.amazon.com/compliance/gdpr-center/)

Incident Response and Forensics

Governance includes incident response capabilities. Implement these AWS services for security incidents.

CloudTrail for Audit Logging

Enable CloudTrail in all accounts with immutable storage:

bucket "AuditLogBucket" {
    versioned true
    encryption BucketEncryption.KMS_MANAGED

    // Prevent deletion
    lifecycleRule {
        enabled true
        noncurrentVersionExpiration (Duration.Days(2555.0)) // 7 years for SOX
    }

// MFA delete for extra protection
// Enable via AWS CLI: aws s3api put-bucket-versioning --bucket name --versioning-configuration Status=Enabled,MFADelete=Enabled
}

GuardDuty for Threat Detection

Enable GuardDuty in all accounts for continuous threat monitoring. GuardDuty uses machine learning to detect anomalous behavior.

Cost: \(4.54/GB of CloudTrail data analyzed +\)0.50/million VPC Flow Log records

Reference: Amazon GuardDuty Best Practices (https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_best-practices.html)

Security Hub for Centralized Findings

Security Hub aggregates findings from GuardDuty, Inspector, Config, and third-party tools. Enables centralized security posture management.

Real-World Implementation: Enterprise Case Study

Company: Large financial services company (anonymized) Challenge: Achieve PCI DSS and SOX compliance across 200+ AWS accounts Solution: Multi-account strategy with Control Tower and SCPs

Implementation:

  1. Migrated to AWS Organizations with OU structure
  2. Applied SCPs preventing security service disablement
  3. Enabled AWS Config in all accounts with conformance packs
  4. Implemented automated remediation for non-compliant resources
  5. Deployed Security Hub for centralized monitoring
  6. Enabled mandatory tagging for cost allocation

Results:

Reference: Similar case studies in AWS Security Blog (https://aws.amazon.com/blogs/security/)

Monitoring and Alerting

Set up CloudWatch alarms for governance violations:

cloudwatchAlarm "RootAccountUsageAlert" {
    metricName "RootAccountUsage"
    metricNamespace "CloudTrailMetrics"
    threshold 1.0
    evaluationPeriods 1
    statistic "Sum"
    comparisonOperator ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD
}

cloudwatchAlarm "UnauthorizedAPICallsAlert" {
    metricName "UnauthorizedAPICalls"
    metricNamespace "CloudTrailMetrics"
    threshold 5.0
    evaluationPeriods 1
    statistic "Sum"
    comparisonOperator ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD
}

Cost of Governance

Governance services cost breakdown:

Service

Cost

ROI

AWS Organizations

FREE

Account isolation

Service Control Policies

FREE

Preventive controls

AWS Config

~$2/rule/account/month

Compliance automation

CloudTrail

~$2/100K events

Audit trail (required)

GuardDuty

~$4.50/GB analyzed

Threat detection

Security Hub

~$0.0012/finding

Centralized security

Total governance cost: $500-2000/month for 10-50 accounts

Savings from governance:

Additional Resources

AWS Official Documentation:

AWS Whitepapers:

Community Resources:

Books:

Expert Blogs:


This guide reflects AWS governance best practices as of 2025. Always consult your compliance team and legal counsel when implementing governance for regulated industries.

namespace Amazon
namespace Amazon.CDK
namespace Amazon.CDK.AWS
namespace Amazon.CDK.AWS.Lambda
namespace Amazon.CDK.AWS.DynamoDB
namespace Amazon.CDK.AWS.S3
namespace Amazon.CDK.AWS.CloudWatch
namespace FsCDK
namespace Amazon.CDK.AWS.Organizations
namespace Amazon.CDK.AWS.Config
val stack: name: string -> StackBuilder
<summary>Creates an AWS CDK Stack construct.</summary>
<param name="name">The name of the stack.</param>
<code lang="fsharp"> stack "MyStack" { lambda myFunction bucket myBucket } </code>
custom operation: tags ((string * string) list) Calls StackBuilder.Tags
<summary>Adds tags to the stack.</summary>
<param name="config">The current stack configuration.</param>
<param name="tags">A list of key-value pairs for tagging.</param>
<code lang="fsharp"> stack "MyStack" { tags [ "Environment", "Production"; "Team", "DevOps" ] } </code>
val lambda: name: string -> FunctionBuilder
<summary>Creates a Lambda function configuration.</summary>
<param name="name">The function name.</param>
<code lang="fsharp"> lambda "MyFunction" { handler "index.handler" runtime Runtime.NODEJS_18_X code "./lambda" timeout 30.0 } </code>
custom operation: runtime (Runtime) Calls FunctionBuilder.Runtime
<summary>Sets the runtime for the Lambda function.</summary>
<param name="config">The function configuration.</param>
<param name="runtime">The Lambda runtime.</param>
<code lang="fsharp"> lambda "MyFunction" { runtime Runtime.NODEJS_18_X } </code>
Multiple items
type Runtime = inherit DeputyBase new: name: string * ?family: Nullable<RuntimeFamily> * ?props: ILambdaRuntimeProps -> unit member RuntimeEquals: other: Runtime -> bool member ToString: unit -> string member BundlingImage: DockerImage member Family: Nullable<RuntimeFamily> member IsVariable: bool member Name: string member SupportsCodeGuruProfiling: bool member SupportsInlineCode: bool ...

--------------------
Runtime(name: string, ?family: System.Nullable<RuntimeFamily>, ?props: ILambdaRuntimeProps) : Runtime
property Runtime.DOTNET_8: Runtime with get
custom operation: handler (string) Calls FunctionBuilder.Handler
<summary>Sets the handler for the Lambda function.</summary>
<param name="config">The function configuration.</param>
<param name="handler">The handler name (e.g., "index.handler").</param>
<code lang="fsharp"> lambda "MyFunction" { handler "index.handler" } </code>
custom operation: code (Code) Calls FunctionBuilder.Code
<summary>Sets the code source from a Code object.</summary>
<param name="config">The function configuration.</param>
<param name="path">The Code object.</param>
<code lang="fsharp"> lambda "MyFunction" { code (Code.FromBucket myBucket "lambda.zip") } </code>
val table: name: string -> TableBuilder
<summary>Creates a DynamoDB table configuration.</summary>
<param name="name">The table name.</param>
<code lang="fsharp"> table "MyTable" { partitionKey "id" AttributeType.STRING billingMode BillingMode.PAY_PER_REQUEST } </code>
custom operation: partitionKey (string) (AttributeType) Calls TableBuilder.PartitionKey
<summary>Sets the partition key for the table.</summary>
<param name="config">The current table configuration.</param>
<param name="name">The attribute name for the partition key.</param>
<param name="attrType">The attribute type (STRING, NUMBER, or BINARY).</param>
<code lang="fsharp"> table "MyTable" { partitionKey "id" AttributeType.STRING } </code>
[<Struct>] type AttributeType = | BINARY = 0 | NUMBER = 1 | STRING = 2
field AttributeType.STRING: AttributeType = 2
custom operation: billingMode (BillingMode) Calls TableBuilder.BillingMode
<summary>Sets the billing mode for the table.</summary>
<param name="config">The current table configuration.</param>
<param name="mode">The billing mode (PAY_PER_REQUEST or PROVISIONED).</param>
<code lang="fsharp"> table "MyTable" { billingMode BillingMode.PAY_PER_REQUEST } </code>
[<Struct>] type BillingMode = | PAY_PER_REQUEST = 0 | PROVISIONED = 1
field BillingMode.PAY_PER_REQUEST: BillingMode = 0
val bucket: name: string -> BucketBuilder
custom operation: versioned (bool) Calls BucketBuilder.Versioned
<summary> Enables or disables versioning for the S3 bucket. **Security Best Practice:** Enable versioning for: - Critical data that requires audit trails - Data subject to compliance requirements (HIPAA, SOC2, etc.) - Production buckets storing business data **Cost Consideration:** Versioning stores all versions of objects, increasing storage costs. Only disable for: - Temporary/cache buckets - Build artifacts with short lifecycle - Development/testing buckets **Default:** false (opt-in for cost optimization) </summary>
<param name="value">True to enable versioning, false to disable.</param>
<param name="config">The current bucket configuration.</param>
<code lang="fsharp"> bucket "production-data" { versioned true // Enable for production } bucket "cache-bucket" { versioned false // Disable for temp data } </code>
custom operation: encryption (BucketEncryption) Calls BucketBuilder.Encryption
[<Struct>] type BucketEncryption = | UNENCRYPTED = 0 | KMS_MANAGED = 1 | S3_MANAGED = 2 | KMS = 3 | DSSE_MANAGED = 4 | DSSE = 5
field BucketEncryption.KMS_MANAGED: BucketEncryption = 1
val lifecycleRule: LifecycleRuleBuilder
custom operation: enabled (bool) Calls LifecycleRuleBuilder.Enabled
custom operation: noncurrentVersionExpiration (Duration) Calls LifecycleRuleBuilder.NoncurrentVersionExpiration
type Duration = inherit DeputyBase member FormatTokenToNumber: unit -> string member IsUnresolved: unit -> bool member Minus: rhs: Duration -> Duration member Plus: rhs: Duration -> Duration member ToDays: ?opts: ITimeConversionOptions -> float member ToHours: ?opts: ITimeConversionOptions -> float member ToHumanString: unit -> string member ToIsoString: unit -> string member ToMilliseconds: ?opts: ITimeConversionOptions -> float ...
Duration.Days(amount: float) : Duration
val cloudwatchAlarm: name: string -> CloudWatchAlarmBuilder
custom operation: metricName (string) Calls CloudWatchAlarmBuilder.MetricName
<summary>Sets the metric name (e.g., "Errors", "CPUUtilization").</summary>
custom operation: metricNamespace (string) Calls CloudWatchAlarmBuilder.MetricNamespace
<summary>Sets the CloudWatch metric namespace (e.g., "AWS/Lambda", "AWS/RDS").</summary>
custom operation: threshold (float) Calls CloudWatchAlarmBuilder.Threshold
<summary>Sets the alarm threshold value.</summary>
custom operation: evaluationPeriods (int) Calls CloudWatchAlarmBuilder.EvaluationPeriods
<summary>Sets the number of periods to evaluate.</summary>
custom operation: statistic (string) Calls CloudWatchAlarmBuilder.Statistic
<summary>Sets the statistic (Average, Sum, Minimum, Maximum, SampleCount).</summary>
custom operation: comparisonOperator (ComparisonOperator) Calls CloudWatchAlarmBuilder.ComparisonOperator
<summary>Sets the comparison operator.</summary>
[<Struct>] type ComparisonOperator = | GREATER_THAN_OR_EQUAL_TO_THRESHOLD = 0 | GREATER_THAN_THRESHOLD = 1 | LESS_THAN_THRESHOLD = 2 | LESS_THAN_OR_EQUAL_TO_THRESHOLD = 3 | LESS_THAN_LOWER_OR_GREATER_THAN_UPPER_THRESHOLD = 4 | GREATER_THAN_UPPER_THRESHOLD = 5 | LESS_THAN_LOWER_THRESHOLD = 6
field ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD: ComparisonOperator = 0

Type something to start searching.