RAPID CYBER AI
WEEK 1 OF 8
0 steps completed
WEEK 1

AWS Security Fundamentals

Build a production-grade AWS security foundation from scratch

8-10 hours

Lab 1: IAM Hardening

~90 minutes // Least-privilege policies, MFA, Access Analyzer, permissions boundaries

Create a least-privilege IAM policy

Create a custom policy scoped to EC2/VPC actions in us-east-1 only. This region lock prevents compromised credentials from being used in unmonitored regions.

bootcamp-policy.json
$ cat > bootcamp-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EC2VPCAccess",
      "Effect": "Allow",
      "Action": [
        "ec2:Describe*", "ec2:Create*", "ec2:Delete*",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:RevokeSecurityGroupIngress"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {"aws:RequestedRegion": "us-east-1"}
      }
    }
  ]
}
EOF
WHY THIS MATTERS

IAM misconfigurations are the #1 root cause of AWS breaches. The Capital One breach? Overly permissive IAM role. Getting IAM right is the single most impactful thing you can do.

Create the policy in AWS

Run this command and save the ARN from the output — you'll need it next.

Create policy
$ aws iam create-policy \
    --policy-name BootcampLabPolicy \
    --policy-document file://bootcamp-policy.json \
    --description 'Scoped policy for Cloud Security Bootcamp labs'

# Save the ARN from output:
# arn:aws:iam::ACCOUNT_ID:policy/BootcampLabPolicy

Create a dedicated IAM user

Never run labs with admin credentials. Create a scoped user and configure a separate CLI profile.

Scoped user
$ aws iam create-user --user-name bootcamp-student

$ aws iam attach-user-policy \
    --user-name bootcamp-student \
    --policy-arn arn:aws:iam::ACCOUNT_ID:policy/BootcampLabPolicy

$ aws iam create-access-key --user-name bootcamp-student

# Configure new CLI profile:
$ aws configure --profile bootcamp

Enable MFA on root account

Go to IAM > Security credentials > Assign MFA device. Use Google Authenticator or Authy. This is one of the few steps requiring the AWS Console.

WHY THIS MATTERS

If root is compromised without MFA, the attacker owns your entire AWS organization — unlimited access, unlimited charges.

Set up IAM Access Analyzer

Continuously monitors your account for overly permissive access.

Access Analyzer
$ aws accessanalyzer create-analyzer \
    --analyzer-name bootcamp-analyzer \
    --type ACCOUNT

# List findings:
$ aws accessanalyzer list-findings \
    --analyzer-arn $(aws accessanalyzer list-analyzers \
      --query 'analyzers[0].arn' --output text)

Create a permissions boundary

Prevents privilege escalation — even if someone modifies their own policy, the boundary caps what they can execute.

Boundary policy
$ cat > boundary-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ec2:*", "s3:*", "cloudtrail:*",
                 "guardduty:*", "config:*", "logs:*"],
      "Resource": "*",
      "Condition": {
        "StringEquals": {"aws:RequestedRegion": "us-east-1"}
      }
    },
    {
      "Effect": "Deny",
      "Action": ["iam:CreateUser", "iam:DeleteUser",
                 "iam:AttachUserPolicy", "organizations:*"],
      "Resource": "*"
    }
  ]
}
EOF

$ aws iam create-policy \
    --policy-name BootcampBoundary \
    --policy-document file://boundary-policy.json

$ aws iam put-user-permissions-boundary \
    --user-name bootcamp-student \
    --permissions-boundary arn:aws:iam::ACCOUNT_ID:policy/BootcampBoundary

Verify — test the boundary

Confirm allowed actions succeed and denied actions fail.

Verification
# This should SUCCEED:
$ aws ec2 describe-vpcs --profile bootcamp

# This should FAIL (boundary blocks it):
$ aws iam create-user --user-name test-escalation --profile bootcamp
# Expected: AccessDenied

Lab 2: VPC Architecture

~90 minutes // Production VPC, public/private subnets, security groups, flow logs

Create the VPC

Create a /16 VPC with DNS support. Tag everything at creation time — untagged resources are audit red flags.

VPC creation
$ VPC_ID=$(aws ec2 create-vpc \
    --cidr-block 10.0.0.0/16 \
    --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=bootcamp-vpc},{Key=Environment,Value=lab}]' \
    --query 'Vpc.VpcId' --output text)

$ aws ec2 modify-vpc-attribute \
    --vpc-id $VPC_ID \
    --enable-dns-hostnames

$ echo "VPC ID: $VPC_ID"

Create subnets (2 public + 2 private)

Spread across two AZs for high availability. Use higher third-octet values for private subnets so you can visually distinguish them.

Subnets
# Public subnets
$ PUB_1A=$(aws ec2 create-subnet --vpc-id $VPC_ID \
    --cidr-block 10.0.1.0/24 --availability-zone us-east-1a \
    --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-1a}]' \
    --query 'Subnet.SubnetId' --output text)

$ PUB_1B=$(aws ec2 create-subnet --vpc-id $VPC_ID \
    --cidr-block 10.0.2.0/24 --availability-zone us-east-1b \
    --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-1b}]' \
    --query 'Subnet.SubnetId' --output text)

# Private subnets
$ PRIV_1A=$(aws ec2 create-subnet --vpc-id $VPC_ID \
    --cidr-block 10.0.10.0/24 --availability-zone us-east-1a \
    --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-1a}]' \
    --query 'Subnet.SubnetId' --output text)

$ PRIV_1B=$(aws ec2 create-subnet --vpc-id $VPC_ID \
    --cidr-block 10.0.11.0/24 --availability-zone us-east-1b \
    --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-1b}]' \
    --query 'Subnet.SubnetId' --output text)

Internet Gateway + NAT Gateway

IGW for public subnets. NAT Gateway gives private subnets outbound-only internet access.

Gateways
$ IGW_ID=$(aws ec2 create-internet-gateway \
    --query 'InternetGateway.InternetGatewayId' --output text)
$ aws ec2 attach-internet-gateway \
    --internet-gateway-id $IGW_ID --vpc-id $VPC_ID

$ EIP_ID=$(aws ec2 allocate-address --domain vpc \
    --query 'AllocationId' --output text)

$ NAT_ID=$(aws ec2 create-nat-gateway \
    --subnet-id $PUB_1A --allocation-id $EIP_ID \
    --query 'NatGateway.NatGatewayId' --output text)

# Wait ~2 min for NAT Gateway
$ aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_ID
WHY THIS MATTERS

NAT Gateways cost ~$0.045/hr ($32/mo). Delete when not labbing. Private subnets can reach the internet for updates but block all inbound connections — your databases and app servers live here.

Route tables

Public route table → IGW. Private route table → NAT Gateway. Without these associations, subnets don't function as public or private.

Routes
# Public route table
$ PUB_RT=$(aws ec2 create-route-table --vpc-id $VPC_ID \
    --query 'RouteTable.RouteTableId' --output text)
$ aws ec2 create-route --route-table-id $PUB_RT \
    --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID
$ aws ec2 associate-route-table --route-table-id $PUB_RT --subnet-id $PUB_1A
$ aws ec2 associate-route-table --route-table-id $PUB_RT --subnet-id $PUB_1B

# Private route table
$ PRIV_RT=$(aws ec2 create-route-table --vpc-id $VPC_ID \
    --query 'RouteTable.RouteTableId' --output text)
$ aws ec2 create-route --route-table-id $PRIV_RT \
    --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_ID
$ aws ec2 associate-route-table --route-table-id $PRIV_RT --subnet-id $PRIV_1A
$ aws ec2 associate-route-table --route-table-id $PRIV_RT --subnet-id $PRIV_1B

Security groups — 3-tier architecture

Web tier: HTTPS + SSH from your IP. App tier: traffic only from web SG. DB tier: PostgreSQL only from app SG. Each layer only accepts traffic from the layer above it.

Security groups
$ MY_IP=$(curl -s https://checkip.amazonaws.com)/32

# Web tier
$ WEB_SG=$(aws ec2 create-security-group --group-name web-tier \
    --description 'Web tier' --vpc-id $VPC_ID --query 'GroupId' --output text)
$ aws ec2 authorize-security-group-ingress --group-id $WEB_SG \
    --protocol tcp --port 443 --cidr 0.0.0.0/0
$ aws ec2 authorize-security-group-ingress --group-id $WEB_SG \
    --protocol tcp --port 22 --cidr $MY_IP

# App tier — only from web SG
$ APP_SG=$(aws ec2 create-security-group --group-name app-tier \
    --description 'App tier' --vpc-id $VPC_ID --query 'GroupId' --output text)
$ aws ec2 authorize-security-group-ingress --group-id $APP_SG \
    --protocol tcp --port 8080 --source-group $WEB_SG

# DB tier — only from app SG
$ DB_SG=$(aws ec2 create-security-group --group-name db-tier \
    --description 'DB tier' --vpc-id $VPC_ID --query 'GroupId' --output text)
$ aws ec2 authorize-security-group-ingress --group-id $DB_SG \
    --protocol tcp --port 5432 --source-group $APP_SG

Enable VPC Flow Logs

Capture REJECT traffic only — you see every blocked connection attempt without the noise and cost of logging all traffic.

Flow Logs
$ aws logs create-log-group \
    --log-group-name /vpc/bootcamp-flow-logs

$ aws ec2 create-flow-logs \
    --resource-type VPC \
    --resource-ids $VPC_ID \
    --traffic-type REJECT \
    --log-destination-type cloud-watch-logs \
    --log-group-name /vpc/bootcamp-flow-logs

Lab 3: CloudTrail & Logging

~60 minutes // Audit trail, hardened S3 log bucket, tamper-proof validation

Create a hardened S3 log bucket

Versioning prevents log deletion. Encryption protects data at rest. Public access block prevents accidental exposure.

Secure log bucket
$ ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
$ LOG_BUCKET="bootcamp-cloudtrail-logs-${ACCOUNT_ID}"

$ aws s3api create-bucket --bucket $LOG_BUCKET --region us-east-1

$ aws s3api put-bucket-versioning --bucket $LOG_BUCKET \
    --versioning-configuration Status=Enabled

$ aws s3api put-public-access-block --bucket $LOG_BUCKET \
    --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

$ aws s3api put-bucket-encryption --bucket $LOG_BUCKET \
    --server-side-encryption-configuration \
    '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'

Apply bucket policy for CloudTrail

CloudTrail needs GetBucketAcl (to verify ownership) and PutObject (to write logs).

Bucket policy
$ cat > trail-bucket-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AWSCloudTrailAclCheck",
      "Effect": "Allow",
      "Principal": {"Service": "cloudtrail.amazonaws.com"},
      "Action": "s3:GetBucketAcl",
      "Resource": "arn:aws:s3:::${LOG_BUCKET}"
    },
    {
      "Sid": "AWSCloudTrailWrite",
      "Effect": "Allow",
      "Principal": {"Service": "cloudtrail.amazonaws.com"},
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::${LOG_BUCKET}/AWSLogs/*",
      "Condition": {
        "StringEquals": {"s3:x-amz-acl": "bucket-owner-full-control"}
      }
    }
  ]
}
EOF

$ aws s3api put-bucket-policy --bucket $LOG_BUCKET \
    --policy file://trail-bucket-policy.json

Create and start the CloudTrail

Multi-region trail with log file validation — your forensic evidence chain.

CloudTrail
$ aws cloudtrail create-trail \
    --name bootcamp-trail \
    --s3-bucket-name $LOG_BUCKET \
    --is-multi-region-trail \
    --enable-log-file-validation \
    --include-global-service-events

$ aws cloudtrail start-logging --name bootcamp-trail

$ aws cloudtrail get-trail-status --name bootcamp-trail
WHY THIS MATTERS

Log file validation creates a digital signature chain. If an attacker modifies logs to cover their tracks, the validation detects tampering. This is your forensic evidence — without it, you can't prove what happened during an incident.

Verify logging

Make a test API call, wait 5-10 minutes, then check for delivered logs.

Verify
# Make a test call
$ aws s3 ls

# Check for log delivery (wait 5-10 min)
$ aws s3 ls s3://$LOG_BUCKET/AWSLogs/ --recursive | tail -5

# Validate integrity
$ aws cloudtrail validate-logs \
    --trail-arn arn:aws:cloudtrail:us-east-1:${ACCOUNT_ID}:trail/bootcamp-trail \
    --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-1H +%Y-%m-%dT%H:%M:%SZ)

Lab 4: GuardDuty & Config

~60 minutes // Threat detection, compliance rules, auto-remediation

Enable GuardDuty

Automated threat detection — analyzes CloudTrail, VPC Flow Logs, and DNS logs for suspicious activity.

GuardDuty
$ aws guardduty create-detector --enable \
    --finding-publishing-frequency FIFTEEN_MINUTES

$ DETECTOR_ID=$(aws guardduty list-detectors \
    --query 'DetectorIds[0]' --output text)

# Generate sample findings to test
$ aws guardduty create-sample-findings \
    --detector-id $DETECTOR_ID \
    --finding-types 'UnauthorizedAccess:EC2/MaliciousIPCaller.Custom'

# View findings
$ aws guardduty list-findings --detector-id $DETECTOR_ID

Enable AWS Config

Continuous compliance monitoring — evaluates resources against rules you define.

Config recorder
# Create Config service-linked role (if not exists)
$ aws iam create-service-linked-role \
    --aws-service-name config.amazonaws.com 2>/dev/null || true

# Start recording
$ aws configservice put-configuration-recorder \
    --configuration-recorder name=default,roleARN=arn:aws:iam::${ACCOUNT_ID}:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig \
    --recording-group allSupported=true,includeGlobalResourceTypes=true

$ aws configservice put-delivery-channel \
    --delivery-channel name=default,s3BucketName=$LOG_BUCKET

$ aws configservice start-configuration-recorder \
    --configuration-recorder-name default

Add compliance rules

Three managed rules: S3 public read, CloudTrail enabled, root MFA. These evaluate continuously, not just at creation.

Config rules
$ aws configservice put-config-rule --config-rule '{
    "ConfigRuleName": "s3-bucket-public-read-prohibited",
    "Source": {
        "Owner": "AWS",
        "SourceIdentifier": "S3_BUCKET_PUBLIC_READ_PROHIBITED"
    }
}'

$ aws configservice put-config-rule --config-rule '{
    "ConfigRuleName": "cloudtrail-enabled",
    "Source": {
        "Owner": "AWS",
        "SourceIdentifier": "CLOUD_TRAIL_ENABLED"
    }
}'

$ aws configservice put-config-rule --config-rule '{
    "ConfigRuleName": "root-account-mfa-enabled",
    "Source": {
        "Owner": "AWS",
        "SourceIdentifier": "ROOT_ACCOUNT_MFA_ENABLED"
    }
}'

Check compliance

Review which resources pass and which fail your rules.

Compliance check
# Wait a few minutes for evaluation
$ aws configservice get-compliance-summary-by-config-rule

# Details on failures:
$ aws configservice get-compliance-details-by-config-rule \
    --config-rule-name s3-bucket-public-read-prohibited \
    --compliance-types NON_COMPLIANT

Lab 5: Security Audit

~60 minutes // Full environment audit — your first portfolio artifact

IAM audit

Check users, policies, MFA status, and Access Analyzer findings.

IAM checks
$ aws iam list-users --query 'Users[].UserName' --output table

$ aws iam list-attached-user-policies --user-name bootcamp-student

$ aws iam get-user --user-name bootcamp-student \
    --query 'User.PermissionsBoundary'

$ aws accessanalyzer list-findings \
    --analyzer-arn $(aws accessanalyzer list-analyzers \
      --query 'analyzers[0].arn' --output text)

Network audit

Check security groups, subnet routing, and flow logs.

Network checks
$ aws ec2 describe-security-groups \
    --query 'SecurityGroups[*].{Name:GroupName,ID:GroupId,Rules:IpPermissions}' \
    --output table

$ aws ec2 describe-subnets \
    --filters 'Name=vpc-id,Values='$VPC_ID \
    --query 'Subnets[].{Name:Tags[?Key==`Name`].Value|[0],Public:MapPublicIpOnLaunch}'

$ aws ec2 describe-flow-logs \
    --filter 'Name=resource-id,Values='$VPC_ID

Logging & detection audit

Verify CloudTrail, GuardDuty, and Config are all active.

Logging checks
$ aws cloudtrail get-trail-status --name bootcamp-trail \
    --query '{Logging:IsLogging,LatestDelivery:LatestDeliveryTime}'

$ aws cloudtrail describe-trails \
    --query 'trailList[].{Name:Name,Validation:LogFileValidationEnabled}'

$ aws s3api get-public-access-block --bucket $LOG_BUCKET

$ aws guardduty get-detector --detector-id $DETECTOR_ID \
    --query '{Status:Status,Updated:UpdatedAt}'

Generate your findings report

Create a markdown audit report — this is your first portfolio artifact for interviews.

Audit report template
$ cat > week1-audit-report.md << 'EOF'
# Week 1 Security Audit Report
## Date: $(date +%Y-%m-%d)
## Auditor: [YOUR NAME]

## IAM
- [ ] Root account MFA: ENABLED / NOT ENABLED
- [ ] No root access keys: PASS / FAIL
- [ ] Least-privilege policy applied: PASS / FAIL
- [ ] Permissions boundary set: PASS / FAIL
- [ ] Access Analyzer findings: X findings

## Network
- [ ] VPC Flow Logs enabled: PASS / FAIL
- [ ] No 0.0.0.0/0 on SSH/RDP: PASS / FAIL
- [ ] Private subnets have no IGW route: PASS / FAIL
- [ ] Security groups use source-group refs: PASS / FAIL

## Logging & Detection
- [ ] CloudTrail multi-region: PASS / FAIL
- [ ] Log file validation: PASS / FAIL
- [ ] S3 log bucket not public: PASS / FAIL
- [ ] GuardDuty enabled: PASS / FAIL
- [ ] AWS Config rules active: PASS / FAIL
EOF

Cleanup — avoid charges

Delete the NAT Gateway to stop the ~$32/month charge. Keep everything else (free tier).

Cost management
# Delete NAT Gateway (biggest cost item)
$ aws ec2 delete-nat-gateway --nat-gateway-id $NAT_ID

# Release Elastic IP
$ aws ec2 release-address --allocation-id $EIP_ID

# Keep everything else — free tier:
# CloudTrail: first trail free
# GuardDuty: 30-day trial
# Config: first 3 rules free
# VPC, subnets, SGs: free
WHY THIS MATTERS

NAT Gateways cost $0.045/hr (~$32/mo). Always delete when not actively labbing. You can recreate in 2 minutes when needed.