Cloud Security

Securing AWS infrastructure: IAM least privilege, preventive controls (SCPs, resource policies), detective controls (GuardDuty, CloudTrail, Security Hub), and network security (WAF, Security Groups, N...

Securing AWS infrastructure: IAM least privilege, preventive controls (SCPs, resource policies), detective controls (GuardDuty, CloudTrail, Security Hub), and network security (WAF, Security Groups, NACLs).


IAM Best Practices

Never use root account — lock it, enable MFA, no access keys
Use IAM roles for everything that runs (EC2, Lambda, ECS tasks, GitHub Actions OIDC)
Least privilege — start with deny all, add only what's needed
Review unused permissions with IAM Access Analyzer
// Least-privilege Lambda execution role example
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadOrdersTable",
      "Effect": "Allow",
      "Action": ["dynamodb:GetItem", "dynamodb:Query"],
      "Resource": "arn:aws:dynamodb:eu-west-1:123456789:table/Orders"
    },
    {
      "Sid": "ReadSecretsManagerSecret",
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": "arn:aws:secretsmanager:eu-west-1:123456789:secret:myapp/db-*"
    },
    {
      "Sid": "BasicLambdaLogging",
      "Effect": "Allow",
      "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}

Service Control Policies (SCPs)

SCPs are guardrails on AWS Organizations. They restrict what member accounts can do even if their IAM policies allow it. They do not grant permissions.

// Deny leaving the organisation
{
  "Sid": "DenyLeaveOrg",
  "Effect": "Deny",
  "Action": "organizations:LeaveOrganization",
  "Resource": "*"
}

// Deny creating resources outside approved regions
{
  "Sid": "DenyNonEURegions",
  "Effect": "Deny",
  "NotAction": [
    "iam:*",
    "organizations:*",
    "support:*",
    "cloudfront:*"
  ],
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:RequestedRegion": ["eu-west-1", "eu-west-2", "eu-central-1"]
    }
  }
}

// Require encryption on S3 buckets
{
  "Sid": "DenyUnencryptedS3Puts",
  "Effect": "Deny",
  "Action": "s3:PutObject",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "s3:x-amz-server-side-encryption": ["aws:kms", "AES256"]
    }
  }
}

GuardDuty

Threat detection service. Analyses CloudTrail, VPC Flow Logs, DNS logs, S3 data events, EKS audit logs. No agents. ML-based anomaly detection.

# Enable GuardDuty
aws guardduty create-detector --enable --finding-publishing-frequency SIX_HOURS

# List high-severity findings
aws guardduty list-findings \
  --detector-id $(aws guardduty list-detectors --query 'DetectorIds[0]' --output text) \
  --finding-criteria '{
    "Criterion": {
      "severity": {"Gte": 7}
    }
  }'

Key finding types: UnauthorizedAccess:IAMUser/ConsoleLogin, CryptoCurrency:EC2/BitcoinTool, Recon:IAMUser/UserPermissions, Trojan:EC2/BlackholeTraffic.


AWS WAF

Web Application Firewall. Protects CloudFront, ALB, API Gateway, AppSync from OWASP Top 10, bots, and rate abuse.

# Create WAF WebACL with managed rule groups
aws wafv2 create-web-acl \
  --name myapp-waf \
  --scope REGIONAL \
  --default-action Allow={} \
  --rules '[
    {
      "Name": "AWSManagedRulesCommonRuleSet",
      "Priority": 1,
      "OverrideAction": {"None": {}},
      "Statement": {
        "ManagedRuleGroupStatement": {
          "VendorName": "AWS",
          "Name": "AWSManagedRulesCommonRuleSet"
        }
      },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "CommonRuleSet"
      }
    },
    {
      "Name": "RateLimitRule",
      "Priority": 2,
      "Action": {"Block": {}},
      "Statement": {
        "RateBasedStatement": {
          "Limit": 2000,
          "AggregateKeyType": "IP"
        }
      },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "RateLimit"
      }
    }
  ]' \
  --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=myapp-waf \
  --region eu-west-1

Security Hub

Aggregates findings from GuardDuty, Inspector, Macie, IAM Access Analyzer, Firewall Manager, and third-party tools. Scores compliance against CIS AWS Foundations Benchmark, PCI DSS, AWS Foundational Security Best Practices.

aws securityhub enable-security-hub --enable-default-standards

# Get critical findings
aws securityhub get-findings \
  --filters '{"SeverityLabel": [{"Value": "CRITICAL", "Comparison": "EQUALS"}], "RecordState": [{"Value": "ACTIVE", "Comparison": "EQUALS"}]}' \
  --query 'Findings[*].[Title, ProductName, AwsAccountId]' \
  --output table

Secrets — Never in Code or Environment Variables

# Store in Secrets Manager, not .env
aws secretsmanager create-secret \
  --name myapp/prod/database \
  --secret-string '{"host":"db.prod","password":"secure123","username":"app"}'

# Rotate automatically
aws secretsmanager rotate-secret \
  --secret-id myapp/prod/database \
  --rotation-lambda-arn arn:aws:lambda:eu-west-1:123456789:function:SecretsRotation

Use External Secrets Operator to sync Secrets Manager → Kubernetes Secrets. Never mount raw credentials as environment variables in ECS or Lambda.


CloudTrail — Audit Log

# Enable CloudTrail for all regions
aws cloudtrail create-trail \
  --name myapp-audit \
  --s3-bucket-name myapp-cloudtrail-logs \
  --is-multi-region-trail \
  --enable-log-file-validation \
  --include-global-service-events

# Query CloudTrail with Athena for incident investigation
# SELECT * FROM cloudtrail_logs WHERE eventname = 'DeleteBucket' AND eventtime > '2026-05-01'

Common Failure Cases

IAM role with wildcards granting unintended S3 write access to production buckets Why: A developer role created with s3:* on arn:aws:s3:::* to unblock a task is never tightened; it remains in place and is later assumed by CI/CD, granting write access to every bucket in the account including production. Detect: IAM Access Analyzer generates a finding for the role; CloudTrail shows the role performing s3:DeleteObject or s3:PutObject on buckets outside its intended scope. Fix: Replace wildcards with resource-scoped actions (s3:GetObject on arn:aws:s3:::my-bucket/*); set a permission boundary on developer roles to cap the maximum effective permissions.

SCP deny blocks break break-glass emergency access Why: A region-restriction or service-restriction SCP applies to all principals including the emergency IAM role; during an incident, the SRE cannot access the affected resources. Detect: Emergency role assumptions fail with ExplicitDeny from organizations in CloudTrail; the SCP blocks the exact actions needed during incident response. Fix: Use SCP condition keys (aws:PrincipalTag/BreakGlass: true) to exempt the emergency role from restrictive SCPs; test the emergency access path in a non-production account quarterly.

GuardDuty enabled but findings never actioned because no alert routing is configured Why: GuardDuty generates findings but they remain in the console unread; no EventBridge rule routes HIGH/CRITICAL findings to SNS or a ticketing system. Detect: GuardDuty console shows dozens of findings days old with no acknowledgement; no GuardDuty-related SNS topics or EventBridge rules exist. Fix: Create an EventBridge rule matching {"source": ["aws.guardduty"], "detail.severity": [{"numeric": [">=", 7]}]} and route to an SNS topic that pages on-call; triage findings within 24 hours of creation.

Secrets Manager secret rotated but application still caches the old value Why: The application reads the secret at startup and caches it in memory; after rotation the cached value is stale and DB connections fail. Detect: Rotation succeeds in Secrets Manager but the application begins returning 500s immediately after the rotation window; CloudWatch logs show authentication failures against the database. Fix: Use the Secrets Manager SDK's built-in cache client (AWS Secrets Manager Caching Library) which handles rotation automatically, or implement a fallback that re-fetches on auth failure before raising an exception.

Connections

cloud-hub · cloud/secrets-management · cloud/cloud-networking · cloud/aws-cdk · security/guardrails · cs-fundamentals/auth-patterns

Open Questions

  • What monitoring and alerting matter most when this is deployed in production?
  • At what scale or workload does this approach hit its practical limits?