AWS Startup Landing Zone Lite


1. When To Use#

如果 Control Tower + AWS Config + Security Hub 对非常早期的 startup 太重,可以先用这个 Lite 方案。它不是最终状态,但比单账号安全很多。

use Lite when:
    team <= 5 engineers
    no compliance audit yet
    AWS monthly cost must stay very low
    production is small but already customer-facing

do not use Lite when:
    customer asks for SOC2 / ISO27001 / security questionnaire
    production handles regulated data
    multiple teams share AWS
    you need repeatable account vending

2. Minimal Accounts#

Account Purpose
management Organizations, billing, IAM Identity Center
security-log GuardDuty delegated admin, CloudTrail logs, security read-only
dev development workloads
uat pre-production workloads
prod production workloads

Lite 版本把 security-toolinglog-archive 合并成 security-log,减少账号和运维成本。公司进入合规阶段后再拆开。

AWS Organizations
├── Security
│   └── security-log
├── Workloads
│   ├── Dev
│   │   └── dev
│   ├── UAT
│   │   └── uat
│   └── Prod
│       └── prod
└── Suspended

3. Setup Order#

1. create management account
2. enable root MFA
3. enable AWS Organizations all features
4. enable IAM Identity Center
5. create Security / Workloads / Suspended OUs
6. create security-log / dev / uat / prod accounts
7. create organization CloudTrail to security-log S3 bucket
8. delegate GuardDuty admin to security-log
9. attach baseline SCP
10. create GitHub OIDC deploy roles
11. create budgets and root-login alerts

4. Must-Have Controls#

Control Lite Default
Human access IAM Identity Center only
Machine access IAM Role / OIDC only
CloudTrail organization trail, multi-region
GuardDuty all accounts, all active regions
Security Hub optional first month, enable before customer review
AWS Config optional, enable for prod if budget allows
Logs CloudTrail central S3 bucket
SCP root deny, leave org deny, security baseline deny, prod IAM user deny

5. Baseline SCP#

Use the same SCP examples from AWS Startup Landing Zone:

attach to Workloads OU:
    DenyRootUser
    DenyLeaveOrganization
    DenyDisableSecurityBaseline
    RequireIMDSv2
    RequireEBSEncryption

attach to Workloads/Prod:
    DenyIAMUsersAndAccessKeysInProd
    DenyPublicS3Policy

attach to Suspended:
    QuarantineAccount

Attach to Workloads OU: workloads-baseline-lite.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyRootUser",
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringLike": {
          "aws:PrincipalArn": "arn:aws:iam::*:root"
        }
      }
    },
    {
      "Sid": "DenyLeaveOrganization",
      "Effect": "Deny",
      "Action": "organizations:LeaveOrganization",
      "Resource": "*"
    },
    {
      "Sid": "DenyDisableSecurityBaseline",
      "Effect": "Deny",
      "Action": [
        "cloudtrail:StopLogging",
        "cloudtrail:DeleteTrail",
        "cloudtrail:UpdateTrail",
        "cloudtrail:PutEventSelectors",
        "config:DeleteConfigurationRecorder",
        "config:StopConfigurationRecorder",
        "config:DeleteDeliveryChannel",
        "guardduty:DeleteDetector",
        "guardduty:DisassociateFromAdministratorAccount",
        "securityhub:DisableSecurityHub",
        "securityhub:DisassociateFromAdministratorAccount",
        "access-analyzer:DeleteAnalyzer"
      ],
      "Resource": "*",
      "Condition": {
        "ArnNotLike": {
          "aws:PrincipalArn": [
            "arn:aws:iam::*:role/OrganizationSecurityAdmin",
            "arn:aws:iam::*:role/AWSReservedSSO_BreakGlassAdmin_*"
          ]
        }
      }
    },
    {
      "Sid": "RequireIMDSv2",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringNotEquals": {
          "ec2:MetadataHttpTokens": "required"
        }
      }
    },
    {
      "Sid": "RequireEBSEncryption",
      "Effect": "Deny",
      "Action": [
        "ec2:CreateVolume",
        "ec2:RunInstances"
      ],
      "Resource": "*",
      "Condition": {
        "Bool": {
          "ec2:Encrypted": "false"
        }
      }
    }
  ]
}

Attach to Workloads/Prod: prod-baseline-lite.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyIAMUsersAndAccessKeysInProd",
      "Effect": "Deny",
      "Action": [
        "iam:CreateUser",
        "iam:CreateLoginProfile",
        "iam:CreateAccessKey",
        "iam:UpdateAccessKey",
        "iam:AttachUserPolicy",
        "iam:PutUserPolicy"
      ],
      "Resource": "*",
      "Condition": {
        "ArnNotLike": {
          "aws:PrincipalArn": [
            "arn:aws:iam::*:role/OrganizationSecurityAdmin",
            "arn:aws:iam::*:role/AWSReservedSSO_BreakGlassAdmin_*"
          ]
        }
      }
    },
    {
      "Sid": "DenyPublicS3PolicyAndAclInProd",
      "Effect": "Deny",
      "Action": [
        "s3:PutBucketPolicy",
        "s3:PutBucketAcl",
        "s3:PutObjectAcl"
      ],
      "Resource": "*",
      "Condition": {
        "ArnNotLike": {
          "aws:PrincipalArn": [
            "arn:aws:iam::*:role/OrganizationSecurityAdmin",
            "arn:aws:iam::*:role/AWSReservedSSO_BreakGlassAdmin_*"
          ]
        }
      }
    }
  ]
}

Attach to Suspended: quarantine-account.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "QuarantineAccount",
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "ArnNotLike": {
          "aws:PrincipalArn": [
            "arn:aws:iam::*:role/AWSReservedSSO_BreakGlassAdmin_*",
            "arn:aws:iam::*:role/OrganizationSecurityAdmin"
          ]
        }
      }
    }
  ]
}

6. Organization CloudTrail#

Create the bucket in security-log account:

aws s3api create-bucket \
  --bucket org-cloudtrail-logs-o-exampleorgid \
  --region ap-east-1 \
  --create-bucket-configuration LocationConstraint=ap-east-1

aws s3api put-public-access-block \
  --bucket org-cloudtrail-logs-o-exampleorgid \
  --public-access-block-configuration \
  BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

aws s3api put-bucket-versioning \
  --bucket org-cloudtrail-logs-o-exampleorgid \
  --versioning-configuration Status=Enabled

Create the organization trail from management account:

aws cloudtrail create-trail \
  --name org-management-events \
  --s3-bucket-name org-cloudtrail-logs-o-exampleorgid \
  --is-organization-trail \
  --is-multi-region-trail \
  --enable-log-file-validation \
  --region ap-east-1

aws cloudtrail start-logging \
  --name org-management-events \
  --region ap-east-1

7. GuardDuty#

export SECURITY_LOG_ACCOUNT_ID="111122223333"
export REGION="ap-east-1"

aws organizations enable-aws-service-access \
  --service-principal guardduty.amazonaws.com

aws guardduty enable-organization-admin-account \
  --admin-account-id "$SECURITY_LOG_ACCOUNT_ID" \
  --region "$REGION"

In security-log account:

DETECTOR_ID="$(aws guardduty list-detectors \
  --region ap-east-1 \
  --query 'DetectorIds[0]' \
  --output text)"

aws guardduty update-organization-configuration \
  --detector-id "$DETECTOR_ID" \
  --auto-enable-organization-members ALL \
  --region ap-east-1

8. IAM Roles#

Role Account Purpose
OrganizationSecurityAdmin security-log manage GuardDuty, read logs
SecurityAuditRole dev/uat/prod allow security-log read-only audit
GitHubDeployRole dev/uat/prod CI/CD deploy
BreakGlassAdmin all accounts emergency access

SecurityAuditRole should attach:

SecurityAudit
ViewOnlyAccess

GitHubDeployRole must restrict:

aud:
    sts.amazonaws.com

sub:
    repo:<GITHUB_ORG>/<REPO>:environment:<ENV>

prod:
    GitHub Environment approval required
    role policy cannot create IAM users
    role policy can pass only application task roles

9. Startup Defaults#

network:
    one VPC per environment account
    no VPC peering at first
    SSM Session Manager instead of bastion
    no public database

compute:
    ECS Fargate is simpler than EKS for most early startups
    use EKS only when Kubernetes skill already exists

database:
    RDS encrypted, private, automated backup
    prod deletion protection on

storage:
    S3 block public access on
    CloudFront OAC for public static content

secrets:
    Secrets Manager for prod app secrets
    SSM Parameter Store SecureString acceptable for low-frequency dev secrets

10. Upgrade Path To Full Landing Zone#

when:
    first enterprise customer security review
    SOC2 / ISO27001 preparation
    production team grows beyond 5-8 engineers
    more than one product team shares AWS

upgrade:
    create separate log-archive account
    keep security-log as security-tooling account
    enable Control Tower
    enroll existing accounts
    enable Security Hub and AWS Config
    add shared-services account
    add stronger region deny and proactive controls

11. Checklist#

must finish before production:
    [ ] management root MFA
    [ ] no IAM users for humans
    [ ] dev / uat / prod separated by account
    [ ] organization CloudTrail logging
    [ ] GuardDuty organization auto-enable
    [ ] root login alert
    [ ] monthly budget alert
    [ ] prod database private and encrypted
    [ ] prod secrets outside source code
    [ ] GitHub OIDC deploy role, no static AWS keys
    [ ] break-glass access tested

do next:
    [ ] Security Hub
    [ ] AWS Config
    [ ] VPC Flow Logs for prod
    [ ] Macie for sensitive S3 buckets
    [ ] Control Tower migration