AWS Security Fundamentals
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.
| Service | What it is | Problem it solves | The catch / where it falls short |
|---|---|---|---|
| IAM | The authorization engine for every API call | "Who can do what to which resource" | Complex; easy to over-permission; see deep dive |
| CloudTrail | Records every AWS API call | The audit log / forensic timeline — the source of truth in IR | Data 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 |
| GuardDuty | Managed threat detection | Finds active threats (mining, recon, cred exfil, anomalies) without you writing rules | It detects, doesn't prevent or auto-respond; findings need triage; not a substitute for your own detections |
| Security Hub | Aggregates findings + posture checks | One pane for GuardDuty/Inspector/Macie/Config + CIS/PCI benchmarks | Aggregator only — it surfaces, it doesn't fix; can be noisy |
| AWS Config | Records resource configuration over time + rules | "Is anything misconfigured / has it drifted?" and config history for IR | Per-resource cost adds up; rules detect, remediation is extra setup |
| Inspector | Vulnerability scanner (EC2, ECR images, Lambda) | Finds CVEs in your workloads automatically | Vuln visibility, not patching; noise without prioritization |
| Macie | Finds sensitive data (PII) in S3 | "Where is our sensitive data and who's touching it?" | Can be expensive at scale; S3-focused |
| IAM Access Analyzer | Flags resources shared externally + validates policies | "What's exposed outside the account?" and least-privilege help | Findings need review; doesn't enforce |
| Detective | Visualizes/links findings into investigations | Speeds root-cause across CloudTrail/VPC/GuardDuty | An investigation aid, not detection |
| KMS | Managed encryption keys | Encrypt data at rest with auditable, access-controlled keys | Key policy misconfig can lock you out or over-share; region-bound |
| Secrets Manager | Stores & rotates secrets | Stop hardcoding DB passwords/API keys; auto-rotation | Costs per secret; you still must control who can read them |
| WAF | Layer-7 web firewall | Block SQLi/XSS/bad bots at the edge (CloudFront/ALB/API GW) | Rule tuning required; not a substitute for fixing the app |
| Shield | DDoS protection | Absorbs 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 logs | Segment the network; record connections for IR | SG/NACL semantics confuse people (stateful vs stateless); Flow Logs miss payload |
Memory hookthe #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 hookGuardDuty 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"
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.
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"
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.
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.
walks your EC2 instances, container images, and Lambda functions looking for known software vulnerabilities (CVEs) — the "unlocked windows," spotted before anyone climbs through.
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"
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.
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.
answers one focused question — "what in here can be reached from outside the account?" — flagging externally-shared buckets, roles, and keys.
Data protection
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).
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
are the firewalls: a Security Group is a stateful firewall wrapped around each instance; a NACL is a stateless one on each subnet.
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 ───► CloudTrailAnd 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 hookThat 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.
| Product | How it spans the org |
|---|---|
| CloudTrail | One organization trail writes every account's API activity into a single S3 bucket in the Log Archive account |
| Config | An aggregator in the security account collects configuration + compliance from all members |
| GuardDuty | Delegated admin in the security account auto-enables members; all findings land there |
| Security Hub | Delegated admin aggregates findings (and cross-region) into one dashboard |
| Macie / Access Analyzer / Detective | Same 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
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.)
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.
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.
means detection isn't something an attacker in a member account can quietly disable — it's owned by the security account.
Interview Q&A
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.
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.
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
| Concept | Description |
|---|---|
| Principal | Entity that can make a request (user, role, service) |
| Policy | JSON document defining permissions |
| Role | Assumed by services, EC2 instances, Lambda, cross-account |
| SCP (Service Control Policy) | Org-level guardrails that limit what accounts/OUs can do |
| Permission Boundary | Max 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 boundaryAll must allow → access granted. Any explicit deny → always denied.
Dangerous IAM Patterns
| Pattern | Risk |
|---|---|
"Action": "*", "Resource": "*" | Full admin access |
iam:PassRole without restriction | Attach any role to a service → privilege escalation |
sts:AssumeRole on * | Assume any role in account |
| Inline policies on users | Hard to audit; prefer managed policies |
| Long-lived access keys | Rotate; prefer role-based auth |
Cross-account AssumeRole without ExternalId | Confused 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 roleTool: 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 Type | Attached To | What It Controls | Can Grant? |
|---|---|---|---|
| Identity-based policy | IAM user, group, role | What the identity can do | Yes |
| Resource-based policy | S3 bucket, KMS key, Lambda, SQS, etc. | Who can access the resource | Yes (cross-account too) |
| SCP (Service Control Policy) | AWS Organization OU or account | Max permissions for the entire account | No (only restricts) |
| Permission boundary | IAM user or role | Max permissions the identity can have | No (only restricts) |
| Session policy | AssumeRole / GetFederationToken call | Max permissions for this specific session | No (only restricts) |
| VPC endpoint policy | VPC endpoint | Which principals/actions over the endpoint | Restricts |
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? → ALLOWFor 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 ─► DENIEDIdentity-Based Policies
// 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/*"
}]
}maintained by AWS (e.g., AmazonS3ReadOnlyAccess); safe defaults, less granular
your own, full control, reusable
embedded in a single identity; not reusable; use only when strict 1:1 relationship required
Resource-Based Policies
// 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 hookThe 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)
// 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
// 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": "*"
}]
}# 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
# 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
// 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| Control | Stateful? | Applied at | Default |
|---|---|---|---|
| Security Group | Yes (tracks connections) | ENI/instance | Deny all inbound |
| NACL | No (separate rules per direction) | Subnet | Allow 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
How the Security Services Link Together
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 hookGuardDuty, 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 locationRecon:EC2/PortProbeUnprotectedPort— port scanningCryptoCurrency:EC2/BitcoinTool.B!DNS— crypto miningExfiltration:S3/ObjectRead.Unusual— S3 data exfilPrivilegeEscalation: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)
full control, audit via CloudTrail, key policy
managed by AWS per service, limited control
resource-based policy; must explicitly allow account root or specific principals
{
"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
| Control | Purpose |
|---|---|
| Block Public Access (account/bucket level) | Prevent accidental public exposure |
| Bucket Policy | Resource-based; allow/deny by principal, IP, VPC endpoint |
| ACLs | Legacy; prefer bucket policies |
| Object Lock | WORM — prevent deletion (compliance mode vs governance mode) |
| Server-side encryption | SSE-S3, SSE-KMS, SSE-C |
| Access logging | Log all requests to S3 access log bucket |
| Versioning | Recover deleted/overwritten objects |
Dangerous pattern"Principal": "*" with "Effect": "Allow" = public bucket.
EC2 Security
- IMDSv2 — token-based metadata service; prevents SSRF attacks that read instance metadatabash
# 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 policiesMemory hookThis is why SSRF against
169.254.169.254is 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 roleThe 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 hookSecurity 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 iniam-deep-dive.md.
Lambda Security
who can invoke the function
what the function can do in AWS
place in private subnet for RDS/internal access; needs NAT for internet
use KMS for secrets (prefer Secrets Manager)
layers from public ARNs can contain malicious code
Organizations & SCPs
// 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?