Learn how to secure AWS environments end to end - covering IAM least privilege, S3 public access blocking, CloudTrail audit logging, GuardDuty threat detection, Security Hub compliance, KMS encryption, Secrets Manager, VPC security groups, SCPs with AWS Organizations, and AWS Config compliance monitoring.
Before configuring any AWS security control, you need to understand where AWS's responsibility ends and yours begins. AWS secures the infrastructure that runs all AWS services — the physical data centers, hardware, networking, and hypervisors. You are responsible for securing everything you put on that infrastructure. ``` AWS is responsible for: You are responsible for: ├── Physical data centers ├── Your IAM users, roles, and policies ├── Network hardware ├── Your application code ├── Hypervisors ├── Your data (encryption, access control) ├── Managed service infrastructure ├── Your OS and network configuration └── Global network backbone ├── Your security group rules ├── Monitoring and threat detection └── Compliance with regulations ``` Most AWS security incidents are not caused by AWS infrastructure failures. They are caused by misconfigured IAM policies, exposed access keys, public S3 buckets, or missing encryption. All of these are within the customer's responsibility zone. This module covers the controls that address the most common real-world AWS security failures. ---
IAM is the foundation of AWS security. Every breach that starts with "someone got access to our AWS account" almost always traces back to an IAM problem — static credentials exposed, overly permissive roles, or the root user with no MFA. ### Never Use Root User Credentials ``` Root user rules: 1. Enable MFA on the root user immediately after creating the account 2. Create an admin IAM user for daily operations — never use root 3. Delete root access keys if they exist (root should only use console) 4. Monitor for root user usage in CloudTrail — it should almost never happen 5. For multi-account organizations, use IAM Identity Center instead of root in each account ``` ```bash # Check if root access keys exist (run as root initially to set up) aws iam get-account-summary | grep AccountAccessKeysPresent # Should return 0 — if not, delete root access keys immediately # List and delete root access keys aws iam list-access-keys --user-name root aws iam delete-access-key --access-key-id AKIAIOSFODNN7EXAMPLE ``` ### IAM Least Privilege — Grant Only What Is Needed ```json // BAD — wildcard everything { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": "*", "Resource": "*" }] } // BAD — full service access { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": "s3:*", "Resource": "*" }] } // GOOD — specific actions, specific resource, with conditions { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::my-app-bucket-prod", "arn:aws:s3:::my-app-bucket-prod/*" ], "Condition": { "StringEquals": { "aws:RequestedRegion": "us-east-1" } } }] } ``` ### Use IAM Roles for EC2, Lambda, and ECS — Never Static Keys on Instances ```bash # BAD — static keys in application code or environment variables on EC2 export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY # GOOD — attach an IAM role to the EC2 instance # The application automatically picks up temporary credentials from the instance metadata service # No credentials to rotate, no credentials to leak # Create a role for EC2 aws iam create-role \ --role-name my-app-ec2-role \ --assume-role-policy-document '{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "ec2.amazonaws.com"}, "Action": "sts:AssumeRole" }] }' # Attach only the permissions the application needs aws iam attach-role-policy \ --role-name my-app-ec2-role \ --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess # Create an instance profile and attach the role aws iam create-instance-profile --instance-profile-name my-app-profile aws iam add-role-to-instance-profile \ --instance-profile-name my-app-profile \ --role-name my-app-ec2-role # Launch EC2 with the instance profile aws ec2 run-instances \ --image-id ami-0123456789abcdef0 \ --instance-type t3.micro \ --iam-instance-profile Name=my-app-profile ``` ### Rotate and Audit Access Keys ```bash # List all IAM users and their access key ages aws iam list-users --query 'Users[*].UserName' --output text | \ tr '\t' '\n' | \ while read user; do aws iam list-access-keys \ --user-name "$user" \ --query "AccessKeyMetadata[*].{User:\"$user\",KeyId:AccessKeyId,Created:CreateDate,Status:Status}" \ --output table done # Keys older than 90 days should be rotated or deleted # Check last used date to identify unused keys: aws iam get-access-key-last-used --access-key-id AKIAIOSFODNN7EXAMPLE ``` ### Enable MFA for All IAM Users ```bash # Create a policy that requires MFA for all actions except MFA management # Apply this to all human IAM users cat > require-mfa-policy.json << 'EOF' { "Version": "2012-10-17", "Statement": [ { "Sid": "DenyWithoutMFA", "Effect": "Deny", "NotAction": [ "iam:CreateVirtualMFADevice", "iam:EnableMFADevice", "iam:GetUser", "iam:ListMFADevices", "iam:ListVirtualMFADevices", "sts:GetSessionToken" ], "Resource": "*", "Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" } } } ] } EOF aws iam create-policy --policy-name RequireMFA --policy-document file://require-mfa-policy.json ``` ---
IAM users with long-lived access keys are the old way. Modern AWS security uses AWS IAM Identity Center (formerly AWS SSO) to provide federated, short-lived credentials to all users — no static keys, no manual rotation, no shared passwords. ``` Traditional model: Developer → IAM user → Long-lived access key → AWS Modern model: Developer → Corporate IdP (Okta, Entra ID) → IAM Identity Center → Temporary credentials → AWS Credentials expire after 1–8 hours. No keys to rotate. No keys to leak. ``` ### Setting Up IAM Identity Center ```bash # Enable IAM Identity Center (requires AWS Organizations) # Done in the console: AWS Organizations → Enable IAM Identity Center # Or via CloudFormation/CDK — not directly available via CLI for initial setup # After enabling, create a permission set (equivalent of an IAM role) aws sso-admin create-permission-set --instance-arn arn:aws:sso:::instance/ssoins-abc123 --name "DeveloperReadOnly" --description "Read-only access for developers" --session-duration "PT4H" # Credentials expire after 4 hours # Attach a managed policy to the permission set aws sso-admin attach-managed-policy-to-permission-set --instance-arn arn:aws:sso:::instance/ssoins-abc123 --permission-set-arn arn:aws:sso:::permissionSet/ssoins-abc123/ps-abc123 --managed-policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess # Assign the permission set to a user for a specific account aws sso-admin create-account-assignment --instance-arn arn:aws:sso:::instance/ssoins-abc123 --target-id 123456789012 --target-type AWS_ACCOUNT --permission-set-arn arn:aws:sso:::permissionSet/ssoins-abc123/ps-abc123 --principal-type USER --principal-id user-id-from-identity-store ``` ### Developer Workflow with Identity Center ```bash # Developer logs in via browser (SSO portal or CLI) aws sso login --profile dev-readonly # Or configure the AWS CLI to use SSO cat >> ~/.aws/config << 'EOF' [profile dev-readonly] sso_start_url = https://mycompany.awsapps.com/start sso_region = us-east-1 sso_account_id = 123456789012 sso_role_name = DeveloperReadOnly region = us-east-1 EOF # Authenticate — opens browser for IdP login aws sso login --profile dev-readonly # Use profile normally — temporary credentials are fetched automatically aws s3 ls --profile dev-readonly ``` ### Why Identity Center Beats IAM Users ``` IAM Users IAM Identity Center ──────────────────────────────────────────────────────── Static access keys (never expire) Temporary tokens (1-8h) Manual rotation required Automatic expiry One set of credentials per service One login, multiple accounts Hard to audit "who did what" Full audit trail via CloudTrail No federation without extra setup Connects to Okta/Entra ID/Google ``` For new AWS environments, start with Identity Center from day one. For existing environments, migrate IAM users to Identity Center over time — it is the direction AWS is pushing and where enterprise security auditors focus their questions. ---
IAM Access Analyzer continuously monitors your account and reports when resources are accessible from outside your account or from the public internet — before an incident exposes them. ```bash # Enable IAM Access Analyzer (one per region) aws accessanalyzer create-analyzer --analyzer-name my-account-analyzer --type ACCOUNT # Monitors resources accessible outside this account # For AWS Organizations: use ORGANIZATION type to cover all member accounts aws accessanalyzer create-analyzer --analyzer-name org-analyzer --type ORGANIZATION --region us-east-1 # Only needs to run in one region for org-wide coverage # List all active findings (public or cross-account access) aws accessanalyzer list-findings --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/my-account-analyzer --filter '{"status":{"eq":["ACTIVE"]}}' --query 'findings[*].{Resource:resource,Type:resourceType,Status:status}' --output table # Validate an IAM policy for security issues BEFORE attaching it aws accessanalyzer validate-policy --policy-type IDENTITY_POLICY --policy-document file://my-policy.json --query 'findings[*].{Issue:issueCode,Type:findingType,Detail:learnMoreLink}' --output table ``` Access Analyzer is free for the basic account analyzer. It catches things that are easy to miss: ``` Common findings: - S3 bucket policy grants access to "Principal": "*" (public) - KMS key policy allows cross-account decryption - IAM role trust policy allows assume-role from an external account - SQS queue policy allows public sends - Lambda function has a resource-based policy allowing public invocation ``` The validate-policy feature is particularly valuable in CI/CD — run it on every new IAM policy before it is attached, the same way you run Checkov on Terraform: ```bash # In CI: validate all IAM policies in the policies/ directory for policy_file in policies/*.json; do echo "Validating $policy_file..." aws accessanalyzer validate-policy --policy-type IDENTITY_POLICY --policy-document "file://$policy_file" --query 'findings[?findingType==`ERROR` || findingType==`SECURITY_WARNING`]' --output json | python3 -c "import sys,json; findings=json.load(sys.stdin); sys.exit(1) if findings else None" done ``` ---
Publicly accessible S3 buckets remain one of the most common causes of data breaches. Block public access at the account level as a foundational control: ```bash # Block public access for ALL buckets in the account (account-level setting) aws s3control put-public-access-block \ --account-id 123456789012 \ --public-access-block-configuration \ "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" # Verify the account-level setting aws s3control get-public-access-block --account-id 123456789012 ``` Apply per-bucket as well (defense in depth): ```bash # Block public access on individual buckets for bucket in $(aws s3api list-buckets --query 'Buckets[*].Name' --output text); do echo "Blocking public access for $bucket..." aws s3api put-public-access-block \ --bucket "$bucket" \ --public-access-block-configuration \ "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" done # Enable versioning and encryption on all buckets for bucket in $(aws s3api list-buckets --query 'Buckets[*].Name' --output text); do # Enable versioning aws s3api put-bucket-versioning \ --bucket "$bucket" \ --versioning-configuration Status=Enabled # Enable encryption aws s3api put-bucket-encryption \ --bucket "$bucket" \ --server-side-encryption-configuration \ '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"},"BucketKeyEnabled":true}]}' done ``` ### Enforce HTTPS-Only Access ```json // Bucket policy that denies all non-HTTPS requests { "Version": "2012-10-17", "Statement": [{ "Sid": "DenyNonHTTPS", "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": [ "arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*" ], "Condition": { "Bool": { "aws:SecureTransport": "false" } } }] } ``` ---
CloudTrail records every API call made in your account. Without it, you have no forensic trail when an incident occurs. Enable it in all regions: ```bash # Create a multi-region trail (covers all regions including future ones) aws cloudtrail create-trail \ --name org-security-trail \ --s3-bucket-name my-cloudtrail-logs-bucket \ --is-multi-region-trail \ --enable-log-file-validation \ --kms-key-id arn:aws:kms:us-east-1:123456789012:key/abc-123 # Start logging aws cloudtrail start-logging --name org-security-trail # Verify the trail is active aws cloudtrail get-trail-status --name org-security-trail ``` ```bash # Also enable CloudTrail data events for S3 and Lambda # (Management events are logged by default; data events need explicit configuration) aws cloudtrail put-event-selectors \ --trail-name org-security-trail \ --event-selectors '[ { "ReadWriteType": "All", "IncludeManagementEvents": true, "DataResources": [ { "Type": "AWS::S3::Object", "Values": ["arn:aws:s3:::"] }, { "Type": "AWS::Lambda::Function", "Values": ["arn:aws:lambda"] } ] } ]' ``` ### Query CloudTrail with Athena ```sql -- Find all console sign-ins to the root account SELECT eventTime, userIdentity.type, sourceIPAddress, userAgent FROM cloudtrail_logs WHERE eventName = 'ConsoleLogin' AND userIdentity.type = 'Root' AND eventTime > '2026-01-01' ORDER BY eventTime DESC; -- Find all IAM changes in the last 30 days SELECT eventTime, userIdentity.arn, eventName, requestParameters FROM cloudtrail_logs WHERE eventSource = 'iam.amazonaws.com' AND eventTime > date_add('day', -30, current_date) ORDER BY eventTime DESC; -- Find all S3 bucket policy changes SELECT eventTime, userIdentity.arn, requestParameters FROM cloudtrail_logs WHERE eventSource = 's3.amazonaws.com' AND eventName IN ('PutBucketPolicy', 'DeleteBucketPolicy', 'PutBucketAcl') ORDER BY eventTime DESC; ``` ---
Before configuring any AWS security control, you need to understand where AWS's responsibility ends and yours begins. AW...
IAM is the foundation of AWS security. Every breach that starts with "someone got access to our AWS account" almost alwa...
IAM users with long-lived access keys are the old way. Modern AWS security uses AWS IAM Identity Center (formerly AWS SS...
IAM Access Analyzer continuously monitors your account and reports when resources are accessible from outside your accou...
Publicly accessible S3 buckets remain one of the most common causes of data breaches. Block public access at the account...
CloudTrail records every API call made in your account. Without it, you have no forensic trail when an incident occurs. ...
GuardDuty uses machine learning and threat intelligence to detect threats in CloudTrail, VPC Flow Logs, and DNS logs. En...
GuardDuty detects threats at runtime. Inspector identifies vulnerabilities before they are exploited — scanning EC2 inst...
Security Hub aggregates findings from GuardDuty, Macie, Inspector, and other services, and runs automated compliance che...
Enable Default EBS Encryption Use Secrets Manager for Application Credentials Retrieve from application code: When to Us...
Use Systems Manager Session Manager Instead of SSH Traditional SSH requires an inbound port 22 rule in your security gro...
For organizations with multiple AWS accounts, Service Control Policies (SCPs) set maximum permission boundaries that no ...
While Security Hub checks against known standards, AWS Config tracks resource configuration changes over time and evalua...
This lab establishes a security baseline on a fresh AWS account. Each step corresponds to one of the controls in this mo...
Aligns directly with DevOps, Site Reliability (SRE), and Platform Engineering job descriptions.