Security Notes
Cloud

AWS Security Fundamentals

26 min read 13 sections 3 model answers

The deep dive on IAM — the most important AWS interview topic — lives in iam-deep-dive.md. This file is the product primer: what each AWS security service is, the problem it solves, and where it falls short.


AWS Security Products — What Each One Is For

A map of the AWS security toolbox in plain language. The mental model: AWS gives you logging (what happened), detection (what's bad), posture (what's misconfigured), data protection (keys & secrets), and network controls — and you stitch them together.

ServiceWhat it isProblem it solvesThe catch / where it falls short
IAMThe authorization engine for every API call"Who can do what to which resource"Complex; easy to over-permission; see deep dive
CloudTrailRecords every AWS API callThe audit log / forensic timeline — the source of truth in IRData events (S3 object reads, Lambda invokes) are off by default and cost extra — you often discover this during an incident when the evidence isn't there
GuardDutyManaged threat detectionFinds active threats (mining, recon, cred exfil, anomalies) without you writing rulesIt detects, doesn't prevent or auto-respond; findings need triage; not a substitute for your own detections
Security HubAggregates findings + posture checksOne pane for GuardDuty/Inspector/Macie/Config + CIS/PCI benchmarksAggregator only — it surfaces, it doesn't fix; can be noisy
AWS ConfigRecords resource configuration over time + rules"Is anything misconfigured / has it drifted?" and config history for IRPer-resource cost adds up; rules detect, remediation is extra setup
InspectorVulnerability scanner (EC2, ECR images, Lambda)Finds CVEs in your workloads automaticallyVuln visibility, not patching; noise without prioritization
MacieFinds sensitive data (PII) in S3"Where is our sensitive data and who's touching it?"Can be expensive at scale; S3-focused
IAM Access AnalyzerFlags resources shared externally + validates policies"What's exposed outside the account?" and least-privilege helpFindings need review; doesn't enforce
DetectiveVisualizes/links findings into investigationsSpeeds root-cause across CloudTrail/VPC/GuardDutyAn investigation aid, not detection
KMSManaged encryption keysEncrypt data at rest with auditable, access-controlled keysKey policy misconfig can lock you out or over-share; region-bound
Secrets ManagerStores & rotates secretsStop hardcoding DB passwords/API keys; auto-rotationCosts per secret; you still must control who can read them
WAFLayer-7 web firewallBlock SQLi/XSS/bad bots at the edge (CloudFront/ALB/API GW)Rule tuning required; not a substitute for fixing the app
ShieldDDoS protectionAbsorbs volumetric/L3-L4 attacks (Standard is free; Advanced paid)Advanced is pricey; app-layer needs WAF too
VPC controls (SG/NACL/Flow Logs)Network firewalling + traffic logsSegment the network; record connections for IRSG/NACL semantics confuse people (stateful vs stateless); Flow Logs miss payload
Memory hook

the #1 AWS IR gotcha: CloudTrail data events are OFF by default. Management events (who changed IAM, who launched EC2) are logged automatically, but data-plane events — who read which S3 object, who invoked which Lambda — are not, and they cost extra to enable. Teams discover this the hard way mid-incident when asked "what data did they actually exfiltrate from the bucket?" and the answer is "we can't tell, the events weren't recorded." Enabling S3/Lambda data events on sensitive resources before an incident is a top preparation item.

Memory hook

GuardDuty detects, it doesn't defendA common interview trap is treating GuardDuty as a firewall. It's a detective control — it reads CloudTrail, VPC Flow Logs, and DNS logs and raises findings. It blocks nothing on its own; you wire findings to EventBridge → Lambda for auto-response. "Detect vs prevent" is the framing: GuardDuty/Macie/Inspector/Config detect; SCPs, SGs, WAF, and IAM prevent.

The Same Toolbox, in Plain Language

Grouped by the job each one does. The thing to notice is the hand-off: logging feeds detection, detection feeds the dashboard, the dashboard feeds response.

Logging — "what happened"

CloudTrail

is the security camera plus the receipts for the control plane: it writes down every API call — who, when, from where, and whether it was allowed. It's the first thing you open in any investigation. By itself it only records; you point alarms and queries at it.

VPC Flow Logs

are the phone records of your network: which address talked to which, on what port, accepted or rejected — never the actual contents. You pair them with CloudTrail to answer "…and did that box then connect out to somewhere it shouldn't?"

Threat detection — "what's actively bad right now"

GuardDuty

is a managed analyst that reads CloudTrail, Flow Logs, and DNS for you and raises a flag on known-bad behaviour (crypto-mining, recon, stolen-credential use) with no rules to write. It only tells you — turning that into a response is your job.

Detective

is the investigation corkboard: it links a single GuardDuty finding to all the surrounding activity so you see the whole story, not one isolated alert.

Inspector

walks your EC2 instances, container images, and Lambda functions looking for known software vulnerabilities (CVEs) — the "unlocked windows," spotted before anyone climbs through.

Macie

scans S3 to tell you where your sensitive data actually lives (personal data, credentials) and warns on unusual access — so you know which buckets are the ones that matter.

Posture — "what's misconfigured"

AWS Config

is a time-machine for resource settings: it records how everything was configured over time and rule-checks it ("is any S3 bucket public?"). Invaluable for "when did this drift?" during an incident.

Security Hub

is the single dashboard: it gathers findings from GuardDuty, Inspector, Macie, and Config and scores you against benchmarks (CIS, PCI). It aggregates and surfaces; it doesn't fix anything.

IAM Access Analyzer

answers one focused question — "what in here can be reached from outside the account?" — flagging externally-shared buckets, roles, and keys.

Data protection

KMS

is the key safe: it holds the encryption keys, controls who may use them, and logs every use. The keys themselves never leave it (that's the envelope-encryption trick shown later).

Secrets Manager

is a password manager for machines — it stores database passwords and API keys and rotates them automatically, so nothing is hardcoded in your app.

Network & edge

Security Groups / NACLs

are the firewalls: a Security Group is a stateful firewall wrapped around each instance; a NACL is a stateless one on each subnet.

WAF

is the bouncer at the web-app door (in front of CloudFront, an ALB, or API Gateway), blocking SQL injection, cross-site scripting, and bad bots. Shield soaks up volumetric DDoS floods.


How It All Fits Together (Big Picture)

Before the per-service detail, here's the one mental model everything hangs off: every action in AWS is an API call, IAM checks every policy before it runs, and CloudTrail records it. Services, identities, policies, and logging are all wired around that single path.

                         THE LIFE OF ONE AWS API CALL
                         (e.g. "s3:GetObject on my-bucket")

   Principal                                                          Resource
   ┌──────────────┐   signed request (SigV4)     ┌───────────────┐   ┌──────────────┐
   │ IAM user  /  │ ──────────────────────────►  │   AWS API     │──►│ S3 / EC2 /   │
   │ assumed role │   "who · what · which arn"   │   endpoint    │   │ KMS / Lambda │
   └──────┬───────┘                              └───────┬───────┘   └──────────────┘
          │ creds come from…                             │ before ANYTHING runs, IAM
          │                                              ▼ evaluates EVERY policy:
   ┌──────┴───────────────┐               ┌──────────────────────────────────────────┐
   │ • access keys (user) │               │  1. explicit DENY anywhere?      → STOP   │
   │ • STS temp creds via │               │  2. SCP allows? (org guardrail)           │
   │   IMDS (EC2 role) /  │               │  3. resource policy? (on the target)      │
   │   IRSA / AssumeRole  │               │  4. identity policy? (on the caller)      │
   └──────────────────────┘               │  5. permission boundary? session policy?  │
                                          │  ALL must allow  ·  ANY explicit deny wins │
                                          └───────────────────────┬──────────────────┘
                                                                  │ allowed → executes
                          EVERYTHING above is recorded by  ───►  CloudTrail

And the split that trips people up — two planes, logged differently:

CONTROL PLANE (management events)          DATA PLANE (data events)
"who changed / created / deleted what"     "who read / wrote the actual data"
  CreateUser, RunInstances, PutPolicy        s3:GetObject, lambda:Invoke, dynamodb:GetItem
  → logged by CloudTrail BY DEFAULT          → NOT logged unless you enable data events ($)
Memory hook

That right-hand column is the recurring theme of this whole file: AWS gives you the control-plane trail for free, but the data-plane evidence — and most prevention — is something you opt into and wire up.


Multi-Account & Cross-Account Security

What is this?

Serious AWS users don't run everything in one account. They split into many accounts under one umbrella — production in one, development in another, security tooling in its own — all grouped into an Organization (AWS's term for the family of accounts billed and governed together). The point is blast-radius isolation: a mistake or a breach in the dev account can't automatically reach production, because they're separate accounts with separate permissions. The security products are built to span this: you nominate one account as the security hub, and it watches all the others.

Why it matters

A single AWS account is one big shared fate — anyone who gets admin sees everything. Multiple accounts turn that into compartments. Interviewers call the overall design a "landing zone" and expect you to know the standard shape: where the logs go, which account holds the security tooling, and why the management account is the crown jewels. It's also how real cross-account attacks happen — an attacker who lands in one account tries to assume a role into another, and your job is to make that hard and visible.

How it works

The standard layout (what AWS Control Tower sets up for you):

                          AWS Organization
                  ┌───────────────────────────────┐
                  │  MANAGEMENT (payer) account     │  ← creates the org, owns billing,
                  │  root here = the crown jewels   │    attaches SCP guardrails. Use rarely.
                  └────────────────┬───────────────┘
                                   │ SCPs flow DOWN the tree as guardrails
        ┌──────────────────────────┼────────────────────────────┐
        ▼                          ▼                             ▼
   OU: Security              OU: Infrastructure            OU: Workloads
   ┌────────────────┐        ┌────────────────┐        ┌──────────────────────┐
   │ Log Archive acct│        │ Shared services │        │ prod / dev / test     │
   │ central S3 store│        │ (networking,    │        │ accounts — the actual │
   │ for ALL logs    │        │  CI/CD, DNS)    │        │ applications live here│
   ├────────────────┤        └────────────────┘        └──────────────────────┘
   │ Security Tooling│  ◄── "delegated administrator" for GuardDuty / Security Hub /
   │ (audit) account │      Config / Macie / Access Analyzer → sees EVERY account at once
   └────────────────┘

(OU = Organizational Unit, just a folder for grouping accounts so an SCP can apply to all of them. The SCP JSON examples are in Organizations & SCPs below.)

How the products reach across accountsEach security service has an "organization" mode: you appoint the Security Tooling account as its delegated administrator, and from then on it auto-enrolls every member account and pulls their findings into one place — you don't log into 50 accounts to check 50 GuardDuty consoles.

ProductHow it spans the org
CloudTrailOne organization trail writes every account's API activity into a single S3 bucket in the Log Archive account
ConfigAn aggregator in the security account collects configuration + compliance from all members
GuardDutyDelegated admin in the security account auto-enables members; all findings land there
Security HubDelegated admin aggregates findings (and cross-region) into one dashboard
Macie / Access Analyzer / DetectiveSame delegated-admin model; Access Analyzer's "zone of trust" becomes the whole org

The data flows up into the central accounts:

   each workload account                          central accounts
   ┌──────────────┐   org CloudTrail trail    ┌──────────────────────────┐
   │ API activity │ ─────────────────────────►│ LOG ARCHIVE: one S3 bucket│
   ├──────────────┤   Config recorder         │ (+ one KMS key) holds ALL │
   │ resource cfg │ ─────────────────────────►│ accounts' logs. Members   │
   ├──────────────┤                           │ can WRITE but not DELETE  │
   │ threats /    │   GuardDuty + Sec Hub      │ → tamper-evident archive  │
   │ findings     │ ─────────────────────────►├──────────────────────────┤
   └──────────────┘   (member → admin)        │ SECURITY TOOLING: GuardDuty│
                                              │ + Security Hub admin =     │
                                              │ single pane over every acct│
                                              └──────────────────────────┘

The actual mechanic that makes any cross-account access work is AssumeRole. A role in the target account has a trust policy naming who's allowed in; the caller's identity also needs sts:AssumeRole. Both must agree (the "permission funnel" from above, now across an account boundary):

   Identity in Account A                         Role in Account B
   ┌────────────────────┐  sts:AssumeRole        ┌────────────────────────┐
   │ user / role         │ ──────────────────────►│ trust policy: "Allow    │
   │ needs sts:AssumeRole│                        │ Principal acct-A : role"│
   │ on B's role ARN     │ ◄──────────────────────│ (+ ExternalId for 3rd   │
   └────────────────────┘   STS returns ASIA…     │  parties)               │
        now acts in B as     temp creds           └────────────────────────┘
        assumed-role/…/session   (CloudTrail in B logs the AssumeRole + every call)

For third-party SaaS (a monitoring vendor assuming a role in your account), the trust policy adds an ExternalId — a shared secret the vendor must present — which defeats the confused-deputy problem (another customer of that vendor tricking it into assuming your role). For the central log bucket and its KMS key, the cross-account grant is the other way round: a resource-based bucket policy and key policy in the Log Archive account that trust every member account to write.

Security angle

The management account is the top prize.

Its root user can do things no SCP can block (close accounts, change billing, leave/modify the org) — SCPs don't restrict the management account. So you put almost nothing in it, lock root with hardware MFA, and never use it day-to-day. (Root specifics: iam-deep-dive.md.)

Cross-account AssumeRole is the lateral-movement path.

An attacker who pops one account hunts for roles it can assume into others — over-broad trust policies (e.g. Principal: "*" or a whole account with no condition) are the gap. Audit trust policies, and watch CloudTrail for AssumeRole events crossing account boundaries.

A separate Log Archive account is what defeats "the attacker turned off logging."

Because the logs live in an account the workload identities can't touch — write-only, no delete — popping a workload account doesn't let them erase the evidence. This is the architectural version of the anti-tamper alarm.

Centralised, delegated GuardDuty/Security Hub

means detection isn't something an attacker in a member account can quietly disable — it's owned by the security account.

Interview Q&A

Q
Why split into many AWS accounts instead of using one big account with good IAM?
Model answer

For blast-radius isolation — separate accounts are a hard boundary, so a breach or a runaway script in dev can't reach prod the way an IAM misconfiguration in a single account could. You group accounts in an Organization with SCP guardrails, and centralise logging and detection into dedicated Log Archive and Security Tooling accounts. IAM controls who can do what; separate accounts control what's even reachable, and you want both.

Q
How do you get one view of security findings across 50 accounts without logging into each?
Model answer

You use each service's organization mode and appoint a single security account as the delegated administrator — GuardDuty, Security Hub, Config, and Macie all support this, auto-enrolling member accounts and aggregating their findings into that one account. CloudTrail uses an organization trail writing every account's events to one S3 bucket in a Log Archive account. The result is a single pane of glass, and detection that an attacker in a member account can't switch off because they don't own it.

Q
A third-party monitoring SaaS needs to read resources in your account. How do you set that up safely?
Model answer

Create a role they assume cross-account rather than handing over static keys, and put an ExternalId condition in the role's trust policy — a shared secret the vendor must present, which prevents the confused-deputy attack where another of their customers tricks them into assuming your role. Scope the role to least privilege, and you'll see every AssumeRole and subsequent call in your CloudTrail.


IAM (Identity and Access Management)

This is a summary. The full, readable IAM deep dive — roles, policy evaluation, permission boundaries, how compute gets credentials, provisioning, and compromised-key response — is in iam-deep-dive.md.

Core Concepts

ConceptDescription
PrincipalEntity that can make a request (user, role, service)
PolicyJSON document defining permissions
RoleAssumed by services, EC2 instances, Lambda, cross-account
SCP (Service Control Policy)Org-level guardrails that limit what accounts/OUs can do
Permission BoundaryMax permissions an entity can have (used to delegate safely)

How IAM Entities Relate

Policies attach to identities (what they can do) and to resources (who can touch them). A role is special: it carries two policies — a trust policy (who's allowed to assume it) and permissions policies (what it can do once assumed).

                    ┌──────────────┐   attached to     ┌───────────────────┐
                    │   POLICY      │◄─────────────────│  USER  /  GROUP    │
                    │ (JSON: what   │  (identity-based) │  (humans, CI keys)│
                    │  is allowed)  │                   └─────────┬─────────┘
                    └──────┬───────┘                              │ member of
                           │ also attached to                     ▼
                           │                              users inherit group policies
                           ▼
   permissions policy ┌──────────────┐   trust policy    ┌───────────────────┐
   (what it can do) ─►│    ROLE       │  (who may assume) │ PRINCIPAL allowed  │
                      │ (no creds of  │◄──── names ───────│ to AssumeRole:     │
                      │  its own;     │                   │ EC2, Lambda, a     │
                      │  assumed)     │                   │ user, another acct │
                      └──────┬───────┘                    └───────────────────┘
                             │ wrapped by (EC2 only)
                             ▼
                    ┌────────────────────┐  attached to  ┌───────────────────┐
                    │  INSTANCE PROFILE   │──────────────►│   EC2 instance     │
                    │  (holds exactly 1   │               │ (borrows the role's│
                    │   role)             │               │  creds via IMDS)   │
                    └────────────────────┘               └───────────────────┘

Policy Evaluation Order

Explicit DENY > SCP > Resource policy > Identity policy > Permissions boundary

All must allow → access granted. Any explicit deny → always denied.

Dangerous IAM Patterns

PatternRisk
"Action": "*", "Resource": "*"Full admin access
iam:PassRole without restrictionAttach any role to a service → privilege escalation
sts:AssumeRole on *Assume any role in account
Inline policies on usersHard to audit; prefer managed policies
Long-lived access keysRotate; prefer role-based auth
Cross-account AssumeRole without ExternalIdConfused deputy attack

Privilege Escalation via IAM

iam:CreatePolicyVersion → create new policy version with admin permissions
iam:SetDefaultPolicyVersion → switch to malicious version
iam:PassRole + ec2:RunInstances → run EC2 with admin role → get instance creds
iam:CreateAccessKey → create creds for another user
lambda:UpdateFunctionCode + lambda:InvokeFunction → modify and invoke with admin role

Tool: Pacu (AWS exploitation framework), PMapper (IAM privilege escalation graph)


AWS Policy Types Deep Dive

AWS has 6 distinct policy types. Understanding how they interact is critical for both security design and interviews.

The 6 Policy Types

Policy TypeAttached ToWhat It ControlsCan Grant?
Identity-based policyIAM user, group, roleWhat the identity can doYes
Resource-based policyS3 bucket, KMS key, Lambda, SQS, etc.Who can access the resourceYes (cross-account too)
SCP (Service Control Policy)AWS Organization OU or accountMax permissions for the entire accountNo (only restricts)
Permission boundaryIAM user or roleMax permissions the identity can haveNo (only restricts)
Session policyAssumeRole / GetFederationToken callMax permissions for this specific sessionNo (only restricts)
VPC endpoint policyVPC endpointWhich principals/actions over the endpointRestricts

How They Interact (Evaluation Logic)

Request arrives →
  1. Explicit DENY in ANY policy?  → DENY (always wins)
  2. SCP allows the action?        → proceed (if no SCP, default allow)
  3. Resource-based policy allows? → ALLOW (can skip identity check for cross-account)
  4. Identity-based policy allows? → proceed
  5. Permission boundary allows?   → proceed (if set)
  6. Session policy allows?        → proceed (if assumed role with session policy)
  7. All remaining checks pass?    → ALLOW

For same-account access: identity-based OR resource-based must allow. For cross-account access: BOTH identity-based AND resource-based must allow.

The Permission Funnel (visualize the layers)

Effective permissions are the intersection of every layer. Only two layers can grant (identity and resource policies); the rest can only take away. And an explicit Deny in any of them punches through everything.

   what you COULD theoretically do
   ┌────────────────────────────────────────────────────┐
   │  SCP — org guardrail, caps the whole account         │
   │  ┌──────────────────────────────────────────────┐   │
   │  │ Permission boundary — caps this identity       │   │
   │  │  ┌────────────────────────────────────────┐   │   │
   │  │  │ Session policy — caps this session       │   │   │
   │  │  │  ┌──────────────────────────────────┐   │   │   │
   │  │  │  │ Identity policy ∪ Resource policy │   │   │   │  ◄ the ONLY layer
   │  │  │  │   = what is actually allowed      │   │   │   │    that GRANTS
   │  │  │  └──────────────────────────────────┘   │   │   │
   │  │  └────────────────────────────────────────┘   │   │
   │  └──────────────────────────────────────────────┘   │
   └────────────────────────────────────────────────────┘
        ANY explicit Deny anywhere ─► punches through all of it ─► DENIED

Identity-Based Policies

json
// Managed policy (reusable, attach to multiple identities)
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:GetObject", "s3:PutObject"],
    "Resource": "arn:aws:s3:::my-bucket/*"
  }]
}
AWS managed

maintained by AWS (e.g., AmazonS3ReadOnlyAccess); safe defaults, less granular

Customer managed

your own, full control, reusable

Inline

embedded in a single identity; not reusable; use only when strict 1:1 relationship required

Resource-Based Policies

json
// S3 bucket policy — allow another account to read
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"AWS": "arn:aws:iam::TRUSTED-ACCOUNT-ID:root"},
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::my-bucket/*"
  }]
}
  • Attached to the resource itself (S3, KMS keys, SNS, SQS, Lambda, Secrets Manager, ECR, Glacier)
  • Trust policy on a role is a resource-based policy — it defines who can AssumeRole
  • Only resource-based policies support granting access to anonymous ("Principal": "*") — dangerous

Cross-account access needs BOTH sides to agree — and encrypted data needs a third:

   Account A (caller)                          Account B (resource owner)
   ┌─────────────────────┐                     ┌──────────────────────────┐
   │ role/Alice           │   s3:GetObject      │  S3 bucket  my-data       │
   │  identity policy:    │                     │  bucket policy:           │
   │  Allow s3:GetObject  ├────────────────────►│  Allow Principal A:role/  │
   │  on B's bucket       │                     │  Alice, Action GetObject  │
   └─────────────────────┘                     └────────────┬─────────────┘
        ▲ A side must allow                                  │ if SSE-KMS encrypted:
        └──────────────── BOTH required ─────────────────────┤
                                                             ▼
                                              ┌──────────────────────────┐
                                              │ KMS key policy in B must   │
                                              │ ALSO allow A:role/Alice    │
                                              │ kms:Decrypt — else you get │
                                              │ AccessDenied on the GET    │
                                              │ even though S3 said yes    │
                                              └──────────────────────────┘
Memory hook

The classic head-scratcher: the bucket policy and the IAM policy both allow it, but the object is KMS-encrypted and the key policy doesn't trust the caller — so the read fails. Three policies must align for cross-account access to encrypted data.

SCPs (Service Control Policies)

json
// SCP: deny all actions outside of eu-west-1 (except global services)
{
  "Effect": "Deny",
  "NotAction": ["iam:*", "sts:*", "cloudfront:*", "route53:*", "support:*", "budgets:*"],
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {"aws:RequestedRegion": ["eu-west-1"]}
  }
}
  • Applied at AWS Organizations OU or account level
  • Does not grant permissions — only restricts; even if an identity policy says Allow, SCP can block
  • Doesn't apply to the management (master) account
  • Doesn't apply to service-linked roles
  • Use to enforce: region restrictions, deny root account use, prevent disabling CloudTrail/GuardDuty

Permission Boundaries

json
// Permission boundary: user can only do S3 actions at most
// Even if the user has Admin policy attached, they can only do S3
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "s3:*",
    "Resource": "*"
  }]
}
bash
# Attach permission boundary when creating a role
aws iam create-role \
  --role-name DeveloperRole \
  --permissions-boundary arn:aws:iam::123456789012:policy/DeveloperBoundary \
  --assume-role-policy-document file://trust.json
  • Use case: safe delegation — allow developers to create IAM roles, but boundary ensures they can't create roles with more permissions than they have themselves
  • Without boundary: developer with iam:* could create an admin role and self-escalate

Session Policies

bash
# Assume a role with a session policy that further restricts to read-only S3
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/PowerUser \
  --role-session-name read-only-session \
  --policy '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:Get*","s3:List*"],"Resource":"*"}]}'
  • Passed inline at assume-role time; restricts the resulting session further
  • Useful for: vending temporary scoped credentials to a Lambda/service for a single operation

VPC Endpoint Policies

json
// S3 gateway endpoint policy: only allow access to specific bucket
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": "*",
    "Action": ["s3:GetObject", "s3:PutObject"],
    "Resource": "arn:aws:s3:::allowed-internal-bucket/*"
  }]
}
  • Restricts traffic going through VPC endpoints
  • Use to prevent data exfiltration via S3 endpoint (allow only internal buckets, deny access to public S3 buckets like s3://attacker-exfil-bucket)

VPC Security

Internet Gateway ← Route Table → Subnet (Public)
                                       ↓
                              Security Group (stateful)
                              NACL (Network ACL, stateless)
                                       ↓
                              EC2 / RDS / Lambda
ControlStateful?Applied atDefault
Security GroupYes (tracks connections)ENI/instanceDeny all inbound
NACLNo (separate rules per direction)SubnetAllow all

VPC Flow Logs — log accepted/rejected traffic to S3 or CloudWatch Logs. Essential for IR.

VPC Endpoints — keep traffic to AWS services inside the AWS network (no internet gateway needed):

  • Interface endpoint (PrivateLink) — creates ENI in subnet
  • Gateway endpoint — route table entry (S3, DynamoDB only)

Key Security Services

The data flow that turns raw activity into findings, posture, and response. The split to remember: the services on the left detect (read-only observers — see the "detect vs prevent" memory hook above); the right side responds.

   SOURCES (raw signals)          DETECTION                AGGREGATION       RESPONSE
   ┌──────────────────┐
   │ CloudTrail (API) │──┐
   ├──────────────────┤  │     ┌────────────────┐
   │ VPC Flow Logs    │──┼────►│   GuardDuty     │──┐
   ├──────────────────┤  │     │  (threats)      │  │    ┌────────────────┐
   │ DNS logs         │──┘     └────────────────┘  ├───►│  Security Hub   │
   ├──────────────────┤        ┌────────────────┐  │    │ (one pane +     │
   │ EKS audit logs   │───────►│   Inspector     │──┤    │  CIS/PCI score) │
   └──────────────────┘        │  (CVEs)         │  │    └────────┬───────┘
   ┌──────────────────┐        ├────────────────┤  │             │ findings
   │ S3 objects       │───────►│   Macie (PII)   │──┤             ▼
   ├──────────────────┤        ├────────────────┤  │    ┌────────────────┐
   │ Resource configs │───────►│   Config (drift)│──┘    │   EventBridge   │
   └──────────────────┘        └────────────────┘       │      rule       │
                                       │                 └────────┬───────┘
                       findings feed ► │                          ▼ triggers
                              ┌────────────────┐         ┌────────────────┐
                              │   Detective     │         │  Lambda / SSM   │
                              │ (investigate /  │         │  auto-remediate │
                              │  link findings) │         │ (isolate, revoke)│
                              └────────────────┘         └────────────────┘
Memory hook

GuardDuty, Inspector, Macie, and Config all just raise findings; Security Hub collects them; EventBridge is the wire that turns a finding into an action (a Lambda that quarantines an instance, revokes a key, etc.). Nothing here blocks an attack by itself — you build the response leg.

CloudTrail

  • Logs all API calls (management events)
  • Data events: S3 object-level, Lambda invocations (opt-in, high volume)
  • Multi-region trail → single S3 bucket
  • Enable CloudTrail Insights for anomaly detection
  • Protect: enable log file validation, S3 bucket policy deny delete, Object Lock

GuardDuty

  • Threat detection: analyzes CloudTrail, VPC Flow Logs, DNS logs, EKS audit logs
  • Key finding types:
    • UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B — login from unusual location
    • Recon:EC2/PortProbeUnprotectedPort — port scanning
    • CryptoCurrency:EC2/BitcoinTool.B!DNS — crypto mining
    • Exfiltration:S3/ObjectRead.Unusual — S3 data exfil
    • PrivilegeEscalation:IAMUser/AnomalousBehavior
  • Integrates with EventBridge → Lambda → auto-remediation

Security Hub

  • Aggregates findings from GuardDuty, Inspector, Macie, IAM Access Analyzer
  • Maps to CSPM frameworks (CIS, PCI DSS, AWS Foundational)
  • Consolidate findings across accounts/regions

AWS Config

  • Records resource configuration state over time
  • Config Rules: detect non-compliant resources (e.g., S3 bucket with public access, unencrypted EBS)
  • Conformance Packs: bundle of rules mapped to compliance framework
  • Remediation: auto-remediate via SSM Automation

Inspector

  • Vulnerability scanning for EC2, ECR images, Lambda functions
  • Integrates with ECR: scans images on push
  • Reports CVEs with CVSS scores and exploitability context

Macie

  • Discovers and classifies sensitive data in S3 (PII, credentials, financial data)
  • ML-based; alerts on unusual data access patterns

IAM Access Analyzer

  • Identifies resources shared with external accounts (S3, KMS, IAM roles, Lambda)
  • Policy validation: unused permissions, policy generation from CloudTrail

KMS (Key Management Service)

CMK (Customer Managed Key)

full control, audit via CloudTrail, key policy

AWS Managed Key

managed by AWS per service, limited control

Key policy

resource-based policy; must explicitly allow account root or specific principals

json
{
  "Effect": "Allow",
  "Principal": {"AWS": "arn:aws:iam::123456789012:role/SecurityAudit"},
  "Action": ["kms:Decrypt", "kms:DescribeKey"],
  "Resource": "*"
}
  • Automatic key rotation: enabled for CMKs (1-year rotation)

Envelope encryption — why KMS scales to huge data without the data ever going to KMS:

   plaintext data ──┐
                    ▼
            ┌───────────────┐   encrypts     ┌───────────────────────┐
            │  Data Key      │───────────────►│ ciphertext (in S3/EBS) │
            │ (random AES key│                └───────────────────────┘
            └──────┬────────┘
                   │ KMS encrypts the data key WITH the CMK
                   │ (the CMK itself NEVER leaves KMS)
                   ▼
            ┌───────────────────────┐   stored next to the ciphertext
            │ encrypted data key     │   to read: ask KMS to Decrypt just the data key,
            └───────────────────────┘   then decrypt the data locally. One CMK protects
                                         millions of objects; only tiny keys hit KMS.

S3 Security

ControlPurpose
Block Public Access (account/bucket level)Prevent accidental public exposure
Bucket PolicyResource-based; allow/deny by principal, IP, VPC endpoint
ACLsLegacy; prefer bucket policies
Object LockWORM — prevent deletion (compliance mode vs governance mode)
Server-side encryptionSSE-S3, SSE-KMS, SSE-C
Access loggingLog all requests to S3 access log bucket
VersioningRecover deleted/overwritten objects

Dangerous pattern"Principal": "*" with "Effect": "Allow" = public bucket.


EC2 Security

  • IMDSv2 — token-based metadata service; prevents SSRF attacks that read instance metadata
    bash
    # IMDSv2 — requires token
    TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
    curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/
  • Instance Profiles — attach IAM role to EC2; credentials rotated automatically
  • SSM Session Manager — shell access without SSH/bastion; full audit log
  • EBS encryption — encrypt at rest; copy unencrypted snapshot to encrypted before use

How Compute Gets Credentials (no keys on disk)

Neither EC2 nor Lambda stores AWS keys. Both borrow a role's temporary credentials at runtime — which is why a compromised box has exactly the role's permissions, no more, and why you can't "find the keys" on it.

   EC2 instance                                       Lambda function
   ┌────────────────────┐                             ┌────────────────────┐
   │ app makes AWS call  │                             │ handler makes call  │
   └─────────┬──────────┘                             └─────────┬──────────┘
             │ SDK fetches creds from…                          │ SDK reads creds from
             ▼                                                   ▼ injected env vars
     IMDS  169.254.169.254                              execution-role creds
             │ hands back temp creds for…                       │ are for…
             ▼                                                   ▼
     Instance Profile ──holds──► IAM Role          Lambda Execution Role (an IAM role)
             │                       │                           │
             └───────────────────────┴──── STS mints ───────────┘
                                     ASIA… temporary creds, auto-rotated
                          Nothing on disk · power = the role's attached policies
Memory hook

This is why SSRF against 169.254.169.254 is so dangerous on EC2 (it returns the role's live creds) and why IMDSv2 — requiring a PUT-issued token header — blunts it.

IRSA — giving an EKS pod its own IAM role

IRSA = IAM Roles for Service Accounts. It's the EKS-specific answer to "how does a single pod get AWS permissions without sharing the node's role or carrying static keys?" On a Kubernetes cluster many pods share one worker node; if a pod just used the node's IAM role, every pod on that node would inherit the same AWS power — no per-application least privilege. IRSA gives each pod its own IAM role, tied to its Kubernetes service account (the pod's in-cluster identity).

How the pieces connect:

   pod  (Kubernetes service account "webapp")
     │ EKS projects a short-lived, SIGNED token (JWT) into the pod
     ▼
   STS  AssumeRoleWithWebIdentity( roleArn, token )
     │ STS checks TWO things, like any AssumeRole:
     │   1. is the cluster's OIDC issuer a registered IAM identity provider?
     │   2. does the role's TRUST policy allow this exact service account?
     ▼
   temporary creds (ASIA…) for the pod's OWN role — NOT the node's role

The wiring is a single annotation on the service account (eks.amazonaws.com/role-arn: arn:…:role/webapp); the rest happens automatically in the SDK. (OIDC = OpenID Connect, the standard token format the cluster uses to prove "this token really is service account X.")

Memory hook

Security pointscope the role's trust policy to the exact namespace:serviceaccount — a wildcard there lets any pod assume the role — and keep its permissions tight. A too-broad IRSA role has the same account-wide blast radius as a compromised EC2 instance. The newer EKS Pod Identity (2023) does the same job with simpler setup. Hands-on version: labs/eks-scenario; more in iam-deep-dive.md.


Lambda Security

Resource-based policy

who can invoke the function

Execution role

what the function can do in AWS

VPC Lambda

place in private subnet for RDS/internal access; needs NAT for internet

Environment variable encryption

use KMS for secrets (prefer Secrets Manager)

Layer security

layers from public ARNs can contain malicious code


Organizations & SCPs

json
// SCP: deny leaving the org
{
  "Effect": "Deny",
  "Action": ["organizations:LeaveOrganization"],
  "Resource": "*"
}

// SCP: restrict to specific regions
{
  "Effect": "Deny",
  "NotAction": ["iam:*", "sts:*", "support:*"],
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:RequestedRegion": ["eu-west-1", "us-east-1"]
    }
  }
}

SCPs don't grant permissions — they only restrict what identity policies can allow.


Interview Questions: AWS Security

  • Explain the IAM policy evaluation order with an example.
  • What's the difference between a Security Group and a NACL?
  • How would you prevent SSRF attacks against the EC2 metadata service?
  • Explain IAM privilege escalation via iam:PassRole.
  • How does GuardDuty differ from CloudTrail?
  • A GuardDuty finding shows UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B — what do you do?
  • What's envelope encryption and why is it used in AWS KMS?
  • How do SCPs interact with IAM permission boundaries?