You are viewing a preview of this lesson. Sign in to start learning
Back to Mastering AWS

Zero-Trust Security

Implementing zero-trust on AWS with encryption, secrets management, and security services

Zero-Trust Security on AWS

Master Zero-Trust Security architecture on AWS with free flashcards and spaced repetition practice. This lesson covers identity verification principles, network segmentation, least-privilege access, and continuous monitoringβ€”essential concepts for building secure cloud infrastructure that assumes breach by default.

Welcome to Zero-Trust Security πŸ”’

The traditional "castle-and-moat" security model is dead. In today's cloud-native world, the network perimeter has dissolved. Employees work remotely, applications span multiple clouds, and APIs expose services to countless external systems. Zero-Trust Security represents a fundamental shift: never trust, always verify.

Unlike legacy models that grant broad access once someone is "inside" the network, Zero-Trust assumes that threats exist both outside and inside the network. Every requestβ€”regardless of sourceβ€”must be authenticated, authorized, and encrypted before access is granted. On AWS, this means rethinking how we design IAM policies, network architecture, logging, and monitoring.

πŸ’‘ Key Philosophy: Trust nothing by default. Verify everything explicitly.

Core Concepts of Zero-Trust Architecture πŸ—οΈ

1. Identity-Centric Security (Not Network-Centric) πŸ‘€

Traditional security focused on network location: "Are you inside the VPC?" Zero-Trust focuses on identity: "Who are you, and what do you need?"

AWS Implementation:

  • AWS IAM Identity Center (formerly SSO): Centralized identity management with multi-factor authentication (MFA)
  • IAM Roles and Policies: Fine-grained permissions attached to identities, not network segments
  • AWS Cognito: User identity management for applications
  • Temporary Credentials: Use AWS STS (Security Token Service) to issue short-lived tokens instead of long-term access keys
Traditional Model vs Zero-Trust

   TRADITIONAL                    ZERO-TRUST
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  🏰 Firewall    β”‚          β”‚  πŸ” Identity    β”‚
β”‚  (Hard Shell)   β”‚          β”‚  (Every Request)β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€          β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ βœ“ Inside = Trustβ”‚          β”‚ βœ“ Verify Always β”‚
β”‚ βœ— Outside = No  β”‚          β”‚ βœ“ Least Privilegeβ”‚
β”‚ βœ— Lateral Move  β”‚          β”‚ βœ“ Micro-segment β”‚
β”‚ βœ— Broad Access  β”‚          β”‚ βœ“ Monitor All   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Example IAM Policy (Least-Privilege):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::my-app-bucket/user-uploads/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": ["203.0.113.0/24"]
        },
        "Bool": {
          "aws:SecureTransport": "true"
        },
        "StringEquals": {
          "s3:x-amz-server-side-encryption": "AES256"
        }
      }
    }
  ]
}

πŸ” Notice: This policy grants access ONLY to specific objects, from specific IPs, over encrypted connections, with server-side encryption required. This is Zero-Trust in action.

2. Micro-Segmentation (Not Broad Network Zones) 🧩

Instead of large security groups that trust everything within a zone, Zero-Trust creates tiny segmentsβ€”often per-workload or per-service.

AWS Implementation:

  • Security Groups: Act as stateful firewalls for individual EC2 instances, Lambda functions, or RDS databases
  • NACLs (Network Access Control Lists): Stateless subnet-level controls for additional defense-in-depth
  • AWS PrivateLink: Private connectivity between VPCs and AWS services without traversing the internet
  • VPC Endpoints: Direct, private access to S3, DynamoDB, etc., without internet gateways

Example Security Group Design:

## Web tier - only accepts HTTPS from internet
resource "aws_security_group" "web_tier" {
  name = "web-tier-sg"
  
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  egress {
    from_port       = 8080
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [aws_security_group.app_tier.id]
  }
}

## App tier - only accepts from web tier
resource "aws_security_group" "app_tier" {
  name = "app-tier-sg"
  
  ingress {
    from_port       = 8080
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [aws_security_group.web_tier.id]
  }
  
  egress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.db_tier.id]
  }
}

## DB tier - only accepts from app tier
resource "aws_security_group" "db_tier" {
  name = "db-tier-sg"
  
  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.app_tier.id]
  }
  
  egress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
Micro-Segmentation Architecture

  Internet
     β”‚
     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  🌐 ALB (HTTPS)     β”‚
β”‚  (443 only)         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ–₯️ Web Tier SG     β”‚
β”‚  Allows: 443 from ⬆ β”‚
β”‚  Allows: 8080 to ⬇  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  βš™οΈ App Tier SG      β”‚
β”‚  Allows: 8080 from ⬆│
β”‚  Allows: 5432 to ⬇  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ—„οΈ DB Tier SG       β”‚
β”‚  Allows: 5432 from ⬆│
β”‚  Denies: all else   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
3. Continuous Verification and Monitoring πŸ“Š

Zero-Trust doesn't verify once and forget. It continuously validates that access is appropriate and monitors for anomalies.

AWS Implementation:

  • AWS CloudTrail: Logs every API call across your AWS account
  • Amazon GuardDuty: Threat detection using machine learning to identify unusual behavior
  • AWS Security Hub: Centralized security findings aggregation
  • AWS Config: Continuous compliance monitoring and configuration tracking
  • Amazon Detective: Investigates security incidents by analyzing CloudTrail, VPC Flow Logs, and GuardDuty findings

Example CloudTrail Query (Athena):

SELECT 
  useridentity.principalid,
  eventname,
  sourceipaddress,
  COUNT(*) as event_count,
  MIN(eventtime) as first_seen,
  MAX(eventtime) as last_seen
FROM cloudtrail_logs
WHERE 
  eventtime > date_add('day', -7, now())
  AND errorcode IS NOT NULL
  AND useridentity.type = 'IAMUser'
GROUP BY 
  useridentity.principalid,
  eventname,
  sourceipaddress
HAVING COUNT(*) > 100
ORDER BY event_count DESC;

πŸ’‘ Use Case: This query identifies IAM users with unusually high error ratesβ€”potential indicators of compromised credentials or misconfigured applications.

GuardDuty Finding Example:

{
  "schemaVersion": "2.0",
  "accountId": "123456789012",
  "region": "us-east-1",
  "type": "UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration",
  "severity": 8,
  "title": "API calls from an unauthorized location",
  "description": "APIs commonly used to discover resources in an AWS environment were invoked from an IP address in a country that you don't typically use.",
  "resource": {
    "instanceDetails": {
      "instanceId": "i-0abcd1234efgh5678",
      "instanceType": "t3.medium"
    }
  },
  "service": {
    "action": {
      "actionType": "AWS_API_CALL",
      "awsApiCallAction": {
        "api": "DescribeInstances",
        "remoteIpDetails": {
          "ipAddressV4": "198.51.100.42",
          "country": {
            "countryName": "Unknown"
          }
        }
      }
    }
  }
}

⚠️ Action Required: This finding suggests credentials may be compromised. Immediately rotate credentials, review CloudTrail logs, and investigate the instance.

4. Encryption Everywhere πŸ”

Zero-Trust mandates encryption for data at rest and in transit. Never assume the network is safe.

AWS Implementation:

  • TLS/SSL Everywhere: Use ACM (AWS Certificate Manager) for free certificates
  • S3 Encryption: Server-side encryption (SSE-S3, SSE-KMS, SSE-C) or client-side encryption
  • EBS Encryption: Encrypt volumes using KMS keys
  • RDS Encryption: Encrypt databases at rest and enforce SSL connections
  • AWS KMS: Centralized key management with audit trails
  • AWS Secrets Manager: Encrypted storage and automatic rotation of credentials

Enforce SSL on S3 Bucket Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyInsecureTransport",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::my-secure-bucket",
        "arn:aws:s3:::my-secure-bucket/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}

RDS SSL Connection (Python):

import psycopg2
import ssl

conn = psycopg2.connect(
    host="mydb.cluster-abc123.us-east-1.rds.amazonaws.com",
    database="myapp",
    user="dbuser",
    password="secure_password",
    sslmode="verify-full",
    sslrootcert="/path/to/rds-ca-cert.pem"
)

cursor = conn.cursor()
cursor.execute("SELECT version();")
print(cursor.fetchone())
5. Least-Privilege Access (Just Enough, Just in Time) ⏱️

Grant the minimum permissions necessary for the shortest time required.

AWS Implementation:

  • IAM Access Analyzer: Identifies resources shared with external entities
  • AWS IAM Policy Simulator: Test policies before deployment
  • Service Control Policies (SCPs): Organization-wide guardrails
  • Permission Boundaries: Maximum permissions limit for IAM entities
  • Session Policies: Further restrict temporary credentials

Example: Assume Role with Session Duration:

import boto3

sts_client = boto3.client('sts')

response = sts_client.assume_role(
    RoleArn='arn:aws:iam::123456789012:role/DataAnalystRole',
    RoleSessionName='data-analysis-session',
    DurationSeconds=3600,  # 1 hour only
    Policy='''
    {
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Action": ["s3:GetObject"],
            "Resource": "arn:aws:s3:::analytics-bucket/2024/01/*"
        }]
    }
    '''
)

temp_credentials = response['Credentials']
s3_client = boto3.client(
    's3',
    aws_access_key_id=temp_credentials['AccessKeyId'],
    aws_secret_access_key=temp_credentials['SecretAccessKey'],
    aws_session_token=temp_credentials['SessionToken']
)

πŸ’‘ Notice: This assumes a role with a 1-hour session AND further restricts it to read-only access to a specific S3 prefix. This is Just-Enough-Just-In-Time (JEJIT) access.

IAM Access Analyzer Findings:

aws accessanalyzer list-findings --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/MyAnalyzer

Output:

{
  "findings": [
    {
      "id": "finding-12345",
      "resourceType": "AWS::S3::Bucket",
      "resource": "arn:aws:s3:::public-data-bucket",
      "condition": {},
      "principal": {"AWS": "*"},
      "action": ["s3:GetObject"],
      "status": "ACTIVE",
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ]
}

⚠️ Warning: This finding shows an S3 bucket accessible to the public. Review immediately to determine if this is intentional.

Detailed Examples 🎯

Example 1: Implementing Zero-Trust for a Three-Tier Web Application

Scenario: You're deploying a web application with a React frontend, Node.js API backend, and PostgreSQL database.

Zero-Trust Implementation:

  1. Identity Layer:
## Cognito User Pool for authentication
Resources:
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: MyAppUserPool
      MfaConfiguration: REQUIRED
      EnabledMfas:
        - SOFTWARE_TOKEN_MFA
      Policies:
        PasswordPolicy:
          MinimumLength: 12
          RequireUppercase: true
          RequireLowercase: true
          RequireNumbers: true
          RequireSymbols: true
      AccountRecoverySetting:
        RecoveryMechanisms:
          - Name: verified_email
            Priority: 1
  1. Network Segmentation:
import aws_cdk as cdk
from aws_cdk import aws_ec2 as ec2

vpc = ec2.Vpc(self, "AppVpc",
    max_azs=3,
    nat_gateways=1,
    subnet_configuration=[
        ec2.SubnetConfiguration(
            name="Public",
            subnet_type=ec2.SubnetType.PUBLIC,
            cidr_mask=24
        ),
        ec2.SubnetConfiguration(
            name="Private",
            subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS,
            cidr_mask=24
        ),
        ec2.SubnetConfiguration(
            name="Isolated",
            subnet_type=ec2.SubnetType.PRIVATE_ISOLATED,
            cidr_mask=24
        )
    ]
)

## ALB in public subnet
alb_sg = ec2.SecurityGroup(self, "AlbSg",
    vpc=vpc,
    description="ALB security group",
    allow_all_outbound=False
)
alb_sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(443))
alb_sg.add_egress_rule(app_sg, ec2.Port.tcp(8080))

## ECS Fargate in private subnet
app_sg = ec2.SecurityGroup(self, "AppSg",
    vpc=vpc,
    description="Application security group",
    allow_all_outbound=False
)
app_sg.add_ingress_rule(alb_sg, ec2.Port.tcp(8080))
app_sg.add_egress_rule(db_sg, ec2.Port.tcp(5432))

## RDS in isolated subnet
db_sg = ec2.SecurityGroup(self, "DbSg",
    vpc=vpc,
    description="Database security group",
    allow_all_outbound=False
)
db_sg.add_ingress_rule(app_sg, ec2.Port.tcp(5432))
  1. API Gateway with IAM Authorization:
Resources:
  ApiGateway:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: MyAppApi
      ProtocolType: HTTP
      CorsConfiguration:
        AllowOrigins:
          - https://app.example.com
        AllowMethods:
          - GET
          - POST
        AllowHeaders:
          - Authorization
          - Content-Type
  
  ApiRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref ApiGateway
      RouteKey: 'POST /data'
      AuthorizationType: JWT
      AuthorizerId: !Ref JwtAuthorizer
      Target: !Join
        - '/'
        - - integrations
          - !Ref ApiIntegration
  1. CloudWatch Alarms for Anomalous Behavior:
import aws_cdk.aws_cloudwatch as cloudwatch
import aws_cdk.aws_cloudwatch_actions as actions
import aws_cdk.aws_sns as sns

## Alarm for excessive 403 errors (potential unauthorized access attempts)
alarm = cloudwatch.Alarm(self, "UnauthorizedAccessAlarm",
    metric=cloudwatch.Metric(
        namespace="AWS/ApiGateway",
        metric_name="4XXError",
        dimensions_map={"ApiId": api.api_id},
        statistic="Sum",
        period=cdk.Duration.minutes(5)
    ),
    threshold=50,
    evaluation_periods=2,
    comparison_operator=cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD
)

topic = sns.Topic(self, "AlertTopic")
alarm.add_alarm_action(actions.SnsAction(topic))
Example 2: Zero-Trust for Multi-Account AWS Organization

Scenario: Enterprise with separate AWS accounts for dev, staging, and production.

Implementation:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyLeavingOrganization",
      "Effect": "Deny",
      "Action": "organizations:LeaveOrganization",
      "Resource": "*"
    },
    {
      "Sid": "RequireMFAForSensitiveActions",
      "Effect": "Deny",
      "Action": [
        "iam:DeleteUser",
        "iam:DeleteRole",
        "ec2:TerminateInstances",
        "rds:DeleteDBInstance"
      ],
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    },
    {
      "Sid": "RestrictRegions",
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": [
            "us-east-1",
            "us-west-2"
          ]
        },
        "ForAllValues:StringNotLike": {
          "aws:PrincipalOrgPaths": "o-*/r-*/ou-*/"
        }
      }
    }
  ]
}

Cross-Account Access with Conditions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "unique-external-id-12345"
        },
        "IpAddress": {
          "aws:SourceIp": ["203.0.113.0/24"]
        },
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]
}

πŸ’‘ Best Practice: Cross-account roles should always require an external ID, MFA, and source IP restrictions.

Example 3: Enforcing Zero-Trust with AWS Network Firewall

Scenario: Block outbound traffic to known malicious domains and allow only approved destinations.

Resources:
  NetworkFirewall:
    Type: AWS::NetworkFirewall::Firewall
    Properties:
      FirewallName: ZeroTrustFirewall
      VpcId: !Ref VPC
      SubnetMappings:
        - SubnetId: !Ref FirewallSubnetA
        - SubnetId: !Ref FirewallSubnetB
      FirewallPolicyArn: !Ref FirewallPolicy

  FirewallPolicy:
    Type: AWS::NetworkFirewall::FirewallPolicy
    Properties:
      FirewallPolicyName: ZeroTrustPolicy
      FirewallPolicy:
        StatelessDefaultActions:
          - aws:forward_to_sfe
        StatelessFragmentDefaultActions:
          - aws:forward_to_sfe
        StatefulRuleGroupReferences:
          - ResourceArn: !Ref DomainAllowListRuleGroup
          - ResourceArn: !Ref ThreatIntelRuleGroup

  DomainAllowListRuleGroup:
    Type: AWS::NetworkFirewall::RuleGroup
    Properties:
      RuleGroupName: AllowedDomains
      Type: STATEFUL
      Capacity: 100
      RuleGroup:
        RulesSource:
          RulesSourceList:
            TargetTypes:
              - HTTP_HOST
              - TLS_SNI
            Targets:
              - ".amazonaws.com"
              - ".example.com"
              - "api.github.com"
            GeneratedRulesType: ALLOWLIST

  ThreatIntelRuleGroup:
    Type: AWS::NetworkFirewall::RuleGroup
    Properties:
      RuleGroupName: BlockMaliciousDomains
      Type: STATEFUL
      Capacity: 100
      RuleGroup:
        RulesSource:
          RulesSourceList:
            TargetTypes:
              - HTTP_HOST
              - TLS_SNI
            Targets:
              - ".malicious-domain.com"
              - "phishing-site.net"
            GeneratedRulesType: DENYLIST

Network Firewall Logs (CloudWatch):

import boto3
import json

logs_client = boto3.client('logs')

response = logs_client.filter_log_events(
    logGroupName='/aws/networkfirewall/alert',
    filterPattern='{ $.event_type = "alert" }',
    limit=10
)

for event in response['events']:
    log_data = json.loads(event['message'])
    print(f"Alert: {log_data['alert']['signature']}")
    print(f"Source: {log_data['src_ip']}:{log_data['src_port']}")
    print(f"Destination: {log_data['dest_ip']}:{log_data['dest_port']}")
    print(f"Action: {log_data['event_type']}")
    print("---")
Example 4: Zero-Trust with AWS Verified Access

AWS Verified Access (released 2022) provides secure access to applications without VPN.

## Create Verified Access instance
aws ec2 create-verified-access-instance \
  --description "Zero-Trust access for internal apps"

## Create Verified Access trust provider (Okta integration)
aws ec2 create-verified-access-trust-provider \
  --policy-reference-name OktaProvider \
  --trust-provider-type user \
  --user-trust-provider-type oidc \
  --oidc-options Issuer="https://example.okta.com",\
AuthorizationEndpoint="https://example.okta.com/oauth2/v1/authorize",\
TokenEndpoint="https://example.okta.com/oauth2/v1/token",\
ClientId="abc123",\
ClientSecret="secret456"

## Create Verified Access endpoint
aws ec2 create-verified-access-endpoint \
  --verified-access-group-id vag-0123456789abcdef0 \
  --endpoint-type network-interface \
  --attachment-type vpc \
  --domain-certificate-arn arn:aws:acm:us-east-1:123456789012:certificate/abc-123 \
  --application-domain app.internal.example.com \
  --endpoint-domain-prefix myapp \
  --network-interface-options NetworkInterfaceId=eni-0abcd1234,\
Protocol=https,\
Port=443 \
  --policy-document file://policy.json

Verified Access Policy:

{
  "Version": "1.0",
  "Statement": [
    {
      "Sid": "AllowAuthenticatedUsers",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "verified-access:Connect",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "verified-access:user-email-verified": "true",
          "verified-access:device-posture-compliant": "true"
        },
        "StringLike": {
          "verified-access:user-email": "*@example.com"
        }
      }
    }
  ]
}

πŸ’‘ Use Case: Employees can access internal applications from any device, but only if:

  • They authenticate via Okta (MFA enforced)
  • Their email is verified
  • Their device passes posture checks (antivirus updated, disk encrypted, etc.)
  • Their email domain matches company domain

Common Mistakes to Avoid ⚠️

1. Over-Reliance on Network Controls

❌ Wrong:

## Security group that trusts the entire VPC
app_sg.add_ingress_rule(
    ec2.Peer.ipv4("10.0.0.0/16"),  # Entire VPC!
    ec2.Port.all_traffic()
)

βœ… Right:

## Security group that trusts only specific source security groups
app_sg.add_ingress_rule(
    ec2.Peer.security_group_id(alb_sg.security_group_id),
    ec2.Port.tcp(8080)
)
2. Using Long-Lived Credentials

❌ Wrong:

## Hardcoded credentials in code
import boto3

s3_client = boto3.client(
    's3',
    aws_access_key_id='AKIAIOSFODNN7EXAMPLE',
    aws_secret_access_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
)

βœ… Right:

## Use IAM roles for EC2/ECS/Lambda
import boto3

## Automatically uses instance profile credentials
s3_client = boto3.client('s3')

## OR use temporary credentials from STS
sts_client = boto3.client('sts')
response = sts_client.assume_role(
    RoleArn='arn:aws:iam::123456789012:role/MyRole',
    RoleSessionName='session1',
    DurationSeconds=3600
)
3. Granting * Permissions

❌ Wrong:

{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}

βœ… Right:

{
  "Effect": "Allow",
  "Action": [
    "s3:GetObject",
    "s3:PutObject"
  ],
  "Resource": "arn:aws:s3:::my-specific-bucket/my-prefix/*"
}
4. Not Encrypting Data in Transit

❌ Wrong:

import requests

## HTTP (unencrypted)
response = requests.get('http://api.example.com/data')

βœ… Right:

import requests

## HTTPS (encrypted)
response = requests.get('https://api.example.com/data', verify=True)
5. Ignoring CloudTrail Logs

❌ Wrong: CloudTrail enabled but logs never reviewed.

βœ… Right:

import boto3
from datetime import datetime, timedelta

cloudtrail = boto3.client('cloudtrail')

## Review failed login attempts daily
response = cloudtrail.lookup_events(
    LookupAttributes=[
        {'AttributeKey': 'EventName', 'AttributeValue': 'ConsoleLogin'}
    ],
    StartTime=datetime.now() - timedelta(days=1),
    EndTime=datetime.now()
)

failed_logins = [
    event for event in response['Events']
    if 'errorCode' in event.get('CloudTrailEvent', '{}')
]

if len(failed_logins) > 10:
    # Alert security team
    print(f"Alert: {len(failed_logins)} failed console logins in last 24h")
6. Not Using MFA for Privileged Access

❌ Wrong: Admin users without MFA.

βœ… Right:

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

Key Takeaways 🎯

πŸ“‹ Zero-Trust Security Quick Reference

Principle AWS Implementation Key Service
Never Trust, Always Verify Authenticate every request regardless of source IAM, Cognito, STS
Least Privilege Grant minimum necessary permissions for minimum time IAM Policies, SCPs
Micro-Segmentation Isolate workloads with fine-grained network controls Security Groups, NACLs
Encrypt Everything Protect data at rest and in transit KMS, ACM, SSL/TLS
Continuous Monitoring Log all activity and detect anomalies CloudTrail, GuardDuty
Assume Breach Design systems to limit blast radius Detective, Security Hub

🧠 Memory Device: NELMA-C

  • Never Trust
  • Encrypt
  • Least Privilege
  • Micro-Segment
  • Assume Breach
  • Continuous Monitoring

Implementation Checklist:

  • βœ… Enable MFA for all users (especially privileged accounts)
  • βœ… Use IAM roles instead of access keys wherever possible
  • βœ… Implement fine-grained security groups (no 0.0.0.0/0 for databases)
  • βœ… Enable CloudTrail in all regions and protect logs
  • βœ… Activate GuardDuty for threat detection
  • βœ… Encrypt all S3 buckets, EBS volumes, and RDS instances
  • βœ… Use VPC endpoints for AWS service access
  • βœ… Review IAM Access Analyzer findings regularly
  • βœ… Set up CloudWatch alarms for suspicious activity
  • βœ… Conduct regular access reviews and remove unused permissions

πŸ“š Further Study

  1. AWS Well-Architected Framework - Security Pillar: https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html
  2. AWS Zero Trust Whitepaper: https://aws.amazon.com/security/zero-trust/
  3. NIST Zero Trust Architecture (SP 800-207): https://csrc.nist.gov/publications/detail/sp/800-207/final

πŸŽ“ Next Steps: Practice implementing these patterns in your own AWS environment. Start smallβ€”secure one application with Zero-Trust principles, then expand organization-wide.