Imported from reason-machines/security-skills (
skills/aws-cloud-security-essentials-lab/SKILL.md). Install upstream withnpx skills add reason-machines/security-skills --skill aws-cloud-security-essentials-lab. Copyright stays with the author.
AWS Cloud Security Essentials Lab
Skill by ara.so — Security Skills collection.
This skill provides guidance for working with the IKB42603 Cloud Computing Security Essentials lab repository, which contains hands-on exercises covering fundamental AWS security concepts including IAM, VPC isolation, encryption, access control, and security monitoring.
What This Project Does
This is an educational repository structured around five core AWS security labs:
- Lab 1: Account Security and IAM (Identity and Access Management)
- Lab 2: Secure Isolation and Multitenancy (VPC, Security Groups)
- Lab 3: Encryption and Key Management (KMS, data protection)
- Lab 4: Access Control and Network Security (Network ACLs, Security Groups)
- Lab 5: Monitoring, Logging, and Incident Detection (CloudTrail, CloudWatch)
Each lab teaches practical AWS security implementation through hands-on exercises.
Repository Setup
Initial Repository Creation
# Clone the repository
git clone https://github.com/<username>/IKB42603-CLOUD-COMPUTING-SECURITY-ESSENTIALS.git
cd IKB42603-CLOUD-COMPUTING-SECURITY-ESSENTIALS
# Create lab structure
mkdir -p Lab{1..5}
touch Lab0_Environment_Setup.md
touch Lab1_Account_Security_and_IAM.md
touch Lab2_Secure_Isolation_and_Multitenancy.md
touch Lab3_Encryption_and_Key_Management.md
touch Lab4_Access_Control_and_Network_Security.md
touch Lab5_Monitoring_Logging_and_Incident_Detection.md
# Initial commit
git add .
git commit -m "Initial lab structure setup"
git push origin main
Standard Lab Documentation Structure
Each lab markdown file should follow this structure:
# Lab X - [Lab Title]
## Objective
[What you will accomplish]
## Learning Outcomes
- Outcome 1
- Outcome 2
## Prerequisites
- AWS Account
- AWS CLI installed
- Appropriate IAM permissions
## Environment Setup
[Required tools and configuration]
## Implementation Steps
### Step 1: [Task Name]
[Description]
**Commands:**
```bash
# Command with explanation
aws [service] [action] --options
Screenshot: Description
Step 2: [Next Task]
...
Verification
[How to verify successful completion]
Challenges Encountered
[Document any issues and solutions]
Lessons Learned
[Key takeaways]
Cleanup
# Commands to remove resources
References
- [Documentation links]
## Lab 1: Account Security and IAM
### Creating IAM Users with MFA
```bash
# Create IAM user
aws iam create-user --user-name lab-user-01
# Create login profile
aws iam create-login-profile \
--user-name lab-user-01 \
--password 'TempPassword123!' \
--password-reset-required
# Attach policy for MFA enforcement
aws iam attach-user-policy \
--user-name lab-user-01 \
--policy-arn arn:aws:iam::aws:policy/IAMUserChangePassword
# Enable virtual MFA device
aws iam create-virtual-mfa-device \
--virtual-mfa-device-name lab-user-01-mfa \
--outfile QRCode.png \
--bootstrap-method QRCodePNG
Creating Custom IAM Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadOnly",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::lab-bucket-*",
"arn:aws:s3:::lab-bucket-*/*"
]
},
{
"Sid": "DenyWithoutMFA",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
# Create the policy
aws iam create-policy \
--policy-name LabS3ReadOnlyWithMFA \
--policy-document file://policy.json
# Attach to user
aws iam attach-user-policy \
--user-name lab-user-01 \
--policy-arn arn:aws:iam::ACCOUNT_ID:policy/LabS3ReadOnlyWithMFA
Lab 2: Secure Isolation and Multitenancy
Creating Isolated VPC
# Create VPC
aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=lab-secure-vpc}]'
# Store VPC ID
VPC_ID=$(aws ec2 describe-vpcs \
--filters "Name=tag:Name,Values=lab-secure-vpc" \
--query 'Vpcs[0].VpcId' \
--output text)
# Create public subnet
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=lab-public-subnet}]'
# Create private subnet
aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.2.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=lab-private-subnet}]'
# Create Internet Gateway
aws ec2 create-internet-gateway \
--tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=lab-igw}]'
IGW_ID=$(aws ec2 describe-internet-gateways \
--filters "Name=tag:Name,Values=lab-igw" \
--query 'InternetGateways[0].InternetGatewayId' \
--output text)
# Attach IGW to VPC
aws ec2 attach-internet-gateway \
--vpc-id $VPC_ID \
--internet-gateway-id $IGW_ID
Security Groups for Multi-tier Architecture
# Create web tier security group
aws ec2 create-security-group \
--group-name lab-web-sg \
--description "Security group for web tier" \
--vpc-id $VPC_ID
WEB_SG_ID=$(aws ec2 describe-security-groups \
--filters "Name=group-name,Values=lab-web-sg" \
--query 'SecurityGroups[0].GroupId' \
--output text)
# Allow HTTP/HTTPS from internet
aws ec2 authorize-security-group-ingress \
--group-id $WEB_SG_ID \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id $WEB_SG_ID \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0
# Create database tier security group
aws ec2 create-security-group \
--group-name lab-db-sg \
--description "Security group for database tier" \
--vpc-id $VPC_ID
DB_SG_ID=$(aws ec2 describe-security-groups \
--filters "Name=group-name,Values=lab-db-sg" \
--query 'SecurityGroups[0].GroupId' \
--output text)
# Allow MySQL only from web tier
aws ec2 authorize-security-group-ingress \
--group-id $DB_SG_ID \
--protocol tcp \
--port 3306 \
--source-group $WEB_SG_ID
Lab 3: Encryption and Key Management
Creating and Using KMS Keys
# Create customer managed key
aws kms create-key \
--description "Lab encryption key for S3" \
--key-usage ENCRYPT_DECRYPT \
--origin AWS_KMS
KEY_ID=$(aws kms list-keys --query 'Keys[0].KeyId' --output text)
# Create alias
aws kms create-alias \
--alias-name alias/lab-s3-key \
--target-key-id $KEY_ID
# Create encrypted S3 bucket
aws s3api create-bucket \
--bucket lab-encrypted-bucket-$(date +%s) \
--region us-east-1
BUCKET_NAME=$(aws s3api list-buckets \
--query 'Buckets[?contains(Name, `lab-encrypted-bucket`)].Name' \
--output text)
# Enable default encryption
aws s3api put-bucket-encryption \
--bucket $BUCKET_NAME \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "'$KEY_ID'"
},
"BucketKeyEnabled": true
}]
}'
# Upload encrypted file
echo "Sensitive data" > test-file.txt
aws s3 cp test-file.txt s3://$BUCKET_NAME/ \
--server-side-encryption aws:kms \
--ssekms-key-id $KEY_ID
Encrypting EBS Volumes
# Create encrypted EBS volume
aws ec2 create-volume \
--availability-zone us-east-1a \
--size 10 \
--volume-type gp3 \
--encrypted \
--kms-key-id $KEY_ID \
--tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=lab-encrypted-volume}]'
# Verify encryption
aws ec2 describe-volumes \
--filters "Name=tag:Name,Values=lab-encrypted-volume" \
--query 'Volumes[0].[Encrypted,KmsKeyId]' \
--output table
Lab 4: Access Control and Network Security
Network ACL Configuration
# Get subnet ID
SUBNET_ID=$(aws ec2 describe-subnets \
--filters "Name=tag:Name,Values=lab-public-subnet" \
--query 'Subnets[0].SubnetId' \
--output text)
# Create Network ACL
aws ec2 create-network-acl \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=lab-nacl}]'
NACL_ID=$(aws ec2 describe-network-acls \
--filters "Name=tag:Name,Values=lab-nacl" \
--query 'NetworkAcls[0].NetworkAclId' \
--output text)
# Allow inbound HTTP
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 100 \
--protocol tcp \
--port-range From=80,To=80 \
--cidr-block 0.0.0.0/0 \
--rule-action allow \
--ingress
# Allow inbound HTTPS
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 110 \
--protocol tcp \
--port-range From=443,To=443 \
--cidr-block 0.0.0.0/0 \
--rule-action allow \
--ingress
# Deny specific IP range
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 50 \
--protocol -1 \
--cidr-block 10.0.100.0/24 \
--rule-action deny \
--ingress
# Allow outbound traffic
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 100 \
--protocol -1 \
--cidr-block 0.0.0.0/0 \
--rule-action allow \
--egress
VPC Flow Logs
# Create CloudWatch log group
aws logs create-log-group --log-group-name /aws/vpc/flowlogs
# Create IAM role for flow logs
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "vpc-flow-logs.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role \
--role-name VPCFlowLogsRole \
--assume-role-policy-document file://trust-policy.json
# Attach policy
cat > flow-logs-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
],
"Resource": "*"
}
]
}
EOF
aws iam put-role-policy \
--role-name VPCFlowLogsRole \
--policy-name VPCFlowLogsPolicy \
--policy-document file://flow-logs-policy.json
# Enable flow logs
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids $VPC_ID \
--traffic-type ALL \
--log-destination-type cloud-watch-logs \
--log-group-name /aws/vpc/flowlogs \
--deliver-logs-permission-arn arn:aws:iam::ACCOUNT_ID:role/VPCFlowLogsRole
Lab 5: Monitoring, Logging, and Incident Detection
CloudTrail Setup
# Create S3 bucket for CloudTrail logs
aws s3api create-bucket \
--bucket lab-cloudtrail-logs-$(date +%s) \
--region us-east-1
TRAIL_BUCKET=$(aws s3api list-buckets \
--query 'Buckets[?contains(Name, `lab-cloudtrail-logs`)].Name' \
--output text)
# Apply bucket policy
cat > 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:::$TRAIL_BUCKET"
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::$TRAIL_BUCKET/AWSLogs/ACCOUNT_ID/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
}
]
}
EOF
aws s3api put-bucket-policy \
--bucket $TRAIL_BUCKET \
--policy file://bucket-policy.json
# Create trail
aws cloudtrail create-trail \
--name lab-security-trail \
--s3-bucket-name $TRAIL_BUCKET \
--is-multi-region-trail
# Start logging
aws cloudtrail start-logging --name lab-security-trail
# Enable log file validation
aws cloudtrail update-trail \
--name lab-security-trail \
--enable-log-file-validation
CloudWatch Alarms for Security Events
# Create SNS topic for alerts
aws sns create-topic --name lab-security-alerts
TOPIC_ARN=$(aws sns list-topics \
--query 'Topics[?contains(TopicArn, `lab-security-alerts`)].TopicArn' \
--output text)
# Subscribe email
aws sns subscribe \
--topic-arn $TOPIC_ARN \
--protocol email \
--notification-endpoint ${ADMIN_EMAIL}
# Create metric filter for root account usage
aws logs put-metric-filter \
--log-group-name CloudTrail/logs \
--filter-name RootAccountUsage \
--filter-pattern '{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }' \
--metric-transformations \
metricName=RootAccountUsageCount,metricNamespace=CloudTrailMetrics,metricValue=1
# Create alarm
aws cloudwatch put-metric-alarm \
--alarm-name RootAccountUsageAlarm \
--alarm-description "Alarm when root account is used" \
--metric-name RootAccountUsageCount \
--namespace CloudTrailMetrics \
--statistic Sum \
--period 300 \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold \
--evaluation-periods 1 \
--alarm-actions $TOPIC_ARN
# Metric filter for unauthorized API calls
aws logs put-metric-filter \
--log-group-name CloudTrail/logs \
--filter-name UnauthorizedAPICalls \
--filter-pattern '{ ($.errorCode = "*UnauthorizedOperation") || ($.errorCode = "AccessDenied*") }' \
--metric-transformations \
metricName=UnauthorizedAPICallsCount,metricNamespace=CloudTrailMetrics,metricValue=1
aws cloudwatch put-metric-alarm \
--alarm-name UnauthorizedAPICallsAlarm \
--alarm-description "Alarm for unauthorized API calls" \
--metric-name UnauthorizedAPICallsCount \
--namespace CloudTrailMetrics \
--statistic Sum \
--period 300 \
--threshold 5 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 1 \
--alarm-actions $TOPIC_ARN
GuardDuty Setup
# Enable GuardDuty
aws guardduty create-detector --enable
DETECTOR_ID=$(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)
# Create SNS topic for findings
aws sns create-topic --name lab-guardduty-findings
GUARDDUTY_TOPIC_ARN=$(aws sns list-topics \
--query 'Topics[?contains(TopicArn, `lab-guardduty-findings`)].TopicArn' \
--output text)
# Subscribe to findings
aws sns subscribe \
--topic-arn $GUARDDUTY_TOPIC_ARN \
--protocol email \
--notification-endpoint ${SECURITY_EMAIL}
# Create EventBridge rule for GuardDuty findings
cat > guardduty-rule.json <<EOF
{
"source": ["aws.guardduty"],
"detail-type": ["GuardDuty Finding"],
"detail": {
"severity": [7, 7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9]
}
}
EOF
aws events put-rule \
--name lab-guardduty-high-severity \
--event-pattern file://guardduty-rule.json \
--state ENABLED
aws events put-targets \
--rule lab-guardduty-high-severity \
--targets "Id"="1","Arn"="$GUARDDUTY_TOPIC_ARN"
Common Patterns
Resource Tagging for Security Compliance
# Apply consistent tags to all resources
aws ec2 create-tags \
--resources $VPC_ID $SUBNET_ID $SG_ID \
--tags \
Key=Environment,Value=Lab \
Key=Owner,Value=Student \
Key=Course,Value=IKB42603 \
Key=CostCenter,Value=Education \
Key=DataClassification,Value=Public
Automated Cleanup Script
#!/bin/bash
# cleanup-lab.sh - Remove all lab resources
# Variables
VPC_NAME="lab-secure-vpc"
# Get VPC ID
VPC_ID=$(aws ec2 describe-vpcs \
--filters "Name=tag:Name,Values=$VPC_NAME" \
--query 'Vpcs[0].VpcId' \
--output text)
if [ "$VPC_ID" != "None" ]; then
# Delete NAT Gateways
for NAT_ID in $(aws ec2 describe-nat-gateways \
--filter "Name=vpc-id,Values=$VPC_ID" \
--query 'NatGateways[*].NatGatewayId' \
--output text); do
aws ec2 delete-nat-gateway --nat-gateway-id $NAT_ID
echo "Deleted NAT Gateway: $NAT_ID"
done
# Delete EC2 instances
for INSTANCE_ID in $(aws ec2 describe-instances \
--filters "Name=vpc-id,Values=$VPC_ID" "Name=instance-state-name,Values=running,stopped" \
--query 'Reservations[*].Instances[*].InstanceId' \
--output text); do
aws ec2 terminate-instances --instance-ids $INSTANCE_ID
echo "Terminated instance: $INSTANCE_ID"
done
# Wait for instances to terminate
aws ec2 wait instance-terminated \
--filters "Name=vpc-id,Values=$VPC_ID"
# Delete security groups (except default)
for SG_ID in $(aws ec2 describe-security-groups \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query 'SecurityGroups[?GroupName!=`default`].GroupId' \
--output text); do
aws ec2 delete-security-group --group-id $SG_ID
echo "Deleted security group: $SG_ID"
done
# Detach and delete internet gateway
for IGW_ID in $(aws ec2 describe-internet-gateways \
--filters "Name=attachment.vpc-id,Values=$VPC_ID" \
--query 'InternetGateways[*].InternetGatewayId' \
--output text); do
aws ec2 detach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID
aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID
echo "Deleted IGW: $IGW_ID"
done
# Delete subnets
for SUBNET_ID in $(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query 'Subnets[*].SubnetId' \
--output text); do
aws ec2 delete-subnet --subnet-id $SUBNET_ID
echo "Deleted subnet: $SUBNET_ID"
done
# Delete VPC
aws ec2 delete-vpc --vpc-id $VPC_ID
echo "Deleted VPC: $VPC_ID"
fi
echo "Cleanup complete"
Troubleshooting
AWS CLI Authentication Issues
# Verify credentials
aws sts get-caller-identity
# Check configuration
aws configure list
# Use named profile
aws configure --profile lab-profile
aws s3 ls --profile lab-profile
# Set environment variables
export AWS_PROFILE=lab-profile
export AWS_DEFAULT_REGION=us-east-1
Permission Denied Errors
# Check current user permissions
aws iam get-user
# List attached policies
aws iam list-attached-user-policies --user-name $(aws iam get-user --query 'User.UserName' --output text)
# Simulate policy evaluation
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::ACCOUNT_ID:user/lab-user \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::bucket-name/*
VPC Resource Deletion Blocked
# Find dependencies preventing VPC deletion
aws ec2 describe-vpcs --vpc-ids $VPC_ID
# Check for running instances
aws ec2 describe-instances \
--filters "Name=vpc-id,Values=$VPC_ID" "Name=instance-state-name,Values=running"
# Check for network interfaces
aws ec2 describe-network-interfaces \
--filters "Name=vpc-id,Values=$VPC_ID"
# Force detach and delete ENIs
for ENI_ID in $(aws ec2 describe-network-interfaces \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query 'NetworkInterfaces[*].NetworkInterfaceId' \
--output text); do
ATTACHMENT_ID=$(aws ec2 describe-network-interfaces \
--network-interface-ids $ENI_ID \
--query 'NetworkInterfaces[0].Attachment.AttachmentId' \
--output text)
if [ "$ATTACHMENT_ID" != "None" ]; then
aws ec2 detach-network-interface --attachment-id $ATTACHMENT_ID --force
fi
aws ec2 delete-network-interface --network-interface-id $ENI_ID
done
CloudTrail Not Logging
# Verify trail status
aws cloudtrail get-trail-status --name lab-security-trail
# Check trail configuration
aws cloudtrail describe-trails --trail-name-list lab-security-trail
# Validate S3 bucket permissions
aws s3api get-bucket-policy --bucket $TRAIL_BUCKET
# Test logging
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=CreateUser \
--max-results 10
KMS Key Access Issues
# Check key policy
aws kms get-key-policy \
--key-id $KEY_ID \
--policy-name default
# List grants
aws kms list-grants --key-id $KEY_ID
# Verify key state
aws kms describe-key --key-id $KEY_ID --query 'KeyMetadata.KeyState'
# Test encryption
aws kms encrypt \
--key-id $KEY_ID \
--plaintext "test" \
--output text \
--query CiphertextBlob
Git Workflow for Labs
# Start new lab
git checkout -b lab-3-encryption
mkdir -p Lab3/screenshots
# Make changes and commit incrementally
git add Lab3/
git commit -m "Lab 3: Create KMS key and configure encryption"
# Continue work
git add Lab3/
git commit -m "Lab 3: Implement S3 bucket encryption"
# Final commit
git add Lab3/
git commit -m "Lab 3: Complete encryption lab with documentation"
# Merge to main
git checkout main
git merge lab-3-encryption
git push origin main
Environment Variables
Always use environment variables for sensitive information:
# Set in ~/.aws/config or export
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export AWS_DEFAULT_REGION=us-east-1
export ADMIN_EMAIL=admin@example.com
export SECURITY_EMAIL=security@example.com
Additional Resources
- AWS Security Best Practices: https://aws.amazon.com/security/best-practices/
- AWS Well-Architected Framework - Security Pillar
- AWS CLI Command Reference: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/
- CloudTrail Log File Examples
- GuardDuty Finding Types Documentation