Security Engineering / Fundamentals / AWS
Fundamental 06

AWS IAM is the whole game. If you can read a policy evaluation from first principles, trace a role assumption in CloudTrail, and spot the four classic escalation primitives, you can secure most of an AWS estate without knowing every service.

requires Cloud fundamentals4 tiers
Tier 0 · Groundstructure

Accounts, organisations, regions

Module 0.1

The account is the blast-radius boundary

Multi-account design and SCPs

An AWS account is the strongest isolation boundary available. Mature estates run many: production, staging, security/logging, shared services, sandbox — grouped into Organizational Units under an Organization.

Service Control Policies (SCPs) are guardrails applied at OU or account level. They never grant permission; they set the ceiling of what any principal in that account can do, including the root user. Canonical uses:

{
  "Effect": "Deny",
  "Action": ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail",
              "guardduty:DeleteDetector", "config:DeleteConfigurationRecorder"],
  "Resource": "*"
}

Also common: deny use of regions you do not operate in (shrinks attack surface and cost surprises), deny disabling of encryption defaults, and deny root-user actions outside break-glass.

Module 0.2

IAM policy evaluation logic

The algorithm, in order
1Is there an explicit Deny anywhere (identity policy, resource policy, SCP, permission boundary, session policy)? → DENY. Nothing overrides this.
2Does an SCP permit the action for this account? If not → DENY.
3Does a permission boundary (if attached) permit it? If not → DENY.
4Is there an explicit Allow in an identity or resource policy? If yes → ALLOW.
5Otherwise → implicit DENY (the default).

Policy anatomy, with the parts that decide real outcomes:

{
  "Effect":   "Allow",
  "Action":   ["s3:GetObject"],
  "Resource": "arn:aws:s3:::finance-reports/*",
  "Condition": {
     "StringEquals": {"aws:PrincipalOrgID": "o-abc123"},
     "Bool":         {"aws:MultiFactorAuthPresent": "true"},
     "IpAddress":    {"aws:SourceIp": ["203.0.113.0/24"]}
  }
}

Conditions are where security actually lives. aws:PrincipalOrgID stops confused-deputy access from outside your org; aws:MultiFactorAuthPresent gates sensitive actions; aws:SourceVpce restricts to a VPC endpoint. A policy with "Resource": "*" and no conditions is the thing you are looking for in a review.

Read ARNs carefully. arn:aws:s3:::bucket is the bucket itself (for ListBucket); arn:aws:s3:::bucket/* is the objects inside (for GetObject). Confusing the two is the most common cause of both broken and over-broad policies.
Module 0.3

Roles and STS — how credentials really flow

Trust policy vs permission policy

A role has two policies: the trust policy (who may assume it) and the permission policy (what it can then do). Assuming it calls sts:AssumeRole and returns temporary credentials with an expiry.

# trust policy — the more security-critical of the two
{
  "Principal": {"Federated": "arn:aws:iam::123:oidc-provider/token.actions.githubusercontent.com"},
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {"StringLike": {
      "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/main"}}
}

That pattern — GitHub Actions federating into AWS with no stored keys — is the modern replacement for CI access keys. Note the sub condition: without it, any GitHub repository in the world could assume your role. Missing or wildcarded trust conditions are a critical finding.

In CloudTrail, an assumed role appears as userIdentity.type = AssumedRole with an arn ending in the session name. Always set meaningful session names — it is the only way to attribute actions back to a human once federation is in play.

Tier 1 · Mechanicsservices that matter

Network, storage, secrets, detection

Module 1.1

VPC design and egress

Security groups vs NACLs, and why endpoints matter
Security groupNetwork ACL
Attaches toENI (instance-level)Subnet
StateStateful — replies allowed automaticallyStateless — need both directions
RulesAllow onlyAllow and deny, numbered order
Best forEveryday microsegmentationCoarse blocks, e.g. blackhole an IP

Good habit: security groups referencing other security groups rather than CIDRs — "the app tier may reach the database tier" survives IP changes and reads like intent.

VPC endpoints keep traffic to AWS services off the internet, and their policies can restrict which buckets are reachable from your VPC — a strong exfiltration control (aws:PrincipalOrgID on an S3 endpoint policy prevents copying data into an attacker's account from inside your network).

Module 1.2

S3 exposure, properly understood

Four independent mechanisms decide access
  1. Block Public Access — account and bucket level; the master switch. Enable at the account level and treat exceptions as incidents.
  2. Bucket policy — resource policy; "Principal": "*" is the classic public exposure.
  3. ACLs — legacy, now disabled by default with Object Ownership enforced. Good.
  4. IAM identity policies — what your principals can do.
  5. Pre-signed URLs — time-limited links that bypass all of the above by design. Frequently overlooked in reviews; check who can generate them and for how long.

Also: default encryption (SSE-S3 or SSE-KMS), versioning plus object lock for ransomware resilience, and CloudTrail data events on anything sensitive.

Module 1.3

Detection services and what they actually catch

GuardDuty, Config, Security Hub, Access Analyzer
ServiceCatchesLimitation
GuardDutyCredential exfiltration, crypto-mining, anomalous API use, malicious IPsDetective only, some findings are noisy
ConfigResource configuration drift over timeCosts add up; needs rules defined
Security HubAggregated findings + CIS/FSBP standards scoringAggregator, not a detector
IAM Access AnalyzerResources shared outside the org; unused accessOnly external sharing, not internal over-permission
MacieSensitive data discovery in S3Sampling cost model

GuardDuty finding worth knowing by name: UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration — instance role credentials used from outside AWS. That is an SSRF or a stolen credential, and it is one of the highest-fidelity alerts in the platform. Route it straight to a page, not a queue.

Tier 2 · Adversarialattack paths

How AWS accounts actually fall over

Module 2.1

The four escalation primitives

Permissions that quietly mean "become admin"
  • iam:PassRole (+ a compute service) — pass a powerful role to a new EC2 instance or Lambda you control, then read its credentials. The single most abused pair in AWS.
  • iam:CreatePolicyVersion / AttachUserPolicy — grant yourself anything.
  • iam:CreateAccessKey on another user — become them, persistently.
  • lambda:UpdateFunctionCode on a function with a privileged role — run your code as that role.

Also worth memorising: sts:AssumeRole chains where a low-privilege role can assume a higher one because someone wrote a permissive trust policy, and ssm:SendCommand which is remote code execution on any managed instance.

A realistic chain

1SSRF in a web app reaches 169.254.169.254 and returns instance role credentials
2Attacker uses them from their own machine — GuardDuty fires if enabled
3Role has s3:GetObject *; customer data is copied out via a pre-signed URL or to their own bucket
4Role also has iam:PassRole → new Lambda with an admin role → persistence

Controls that break this chain, in order of value: IMDSv2 required (session-token-based, defeats basic SSRF), least-privilege instance roles scoped to specific buckets, VPC endpoint policy restricting S3 to your org, GuardDuty routed to a real pager, and SCPs denying IAM changes in production accounts.

Module 2.2

Investigating with CloudTrail

The fields you pivot on
# the fields that answer investigations
eventTime, eventName, eventSource      what was called
userIdentity.type / .arn / .sessionContext   who — follow the role chain
sourceIPAddress, userAgent             from where, with what tool
requestParameters, responseElements    the detail
errorCode                              AccessDenied bursts = enumeration
# patterns to hunt
userAgent CONTAINS "aws-cli" AND userIdentity.type = "AssumedRole"
   AND sourceIPAddress NOT IN (corporate/POP ranges)
eventName IN (CreateAccessKey, AttachUserPolicy, PutUserPolicy,
              CreateLoginProfile, UpdateAssumeRolePolicy)
eventName = ConsoleLogin AND additionalEventData.MFAUsed = "No"
many errorCode = "AccessDenied" from one principal in a short window
In Google SecOps: CloudTrail maps into UDM as generic events with principal, target and metadata.product_event_type populated from eventName. Learning these raw field names first makes the UDM mapping obvious rather than mysterious.
Drill 1

Which single control would most reduce the impact of an SSRF vulnerability in an EC2-hosted application?

IMDSv2. It requires a PUT request with a token and enforces a low hop limit, which defeats the naive "fetch this URL" SSRF that steals instance credentials. A WAF helps but is signature-dependent and bypassable; versioning is recovery, not prevention; rotation is irrelevant since instance credentials are already short-lived and auto-rotated.
Referencesearchable

Glossary