Cloud & Container Security · beginner · ~11 min
**What you will learn** - Explain the **shared-responsibility model** and split any control into "provider secures" vs "you secure." - Describe what **IAM** is (principals, policies, roles) and why *identity is the perimeter* in the cloud. - Recognise the four failures that cause most cloud breaches: over-permissioning, long-lived keys, broadly-assumable roles, and `iam:PassRole` misuse. - Read a simple IAM policy and tell an over-permissive one (`"*"`) from a **least-privilege** one. - Draw a basic cloud **threat model** and identify where the trust boundary sits. - Apply and *verify* least-privilege remediations in an authorized lab, and know which logs detect IAM abuse.
Security objective. The asset you are protecting is your cloud account and everything in it — data, compute, and the ability to spend money or create new identities. The threat is an attacker who obtains one credential (a leaked access key, a compromised laptop, a phished login) and then uses excess permissions to move from that foothold up to full control. In this lesson you learn to detect identities with too much power and prevent the privilege-escalation paths that turn a small leak into a total compromise.
In traditional hosting you owned the server, so security asked "is the box patched and behind a firewall?" In the cloud, platforms such as AWS, Azure, and GCP replace servers you rack with on-demand, API-driven services. That changes the core question from "is the box patched?" to "who can do what?"
Two ideas answer that question:
This lesson has no prerequisites — it is the entry point to the Cloud & Container Security track. It sets up the mindset you will reuse everywhere: the very next lesson, Storage buckets, security groups, and public exposure (cloud-storage-exposure), is really a special case of the same question — an IAM/config decision that accidentally grants access to the whole internet.
In real, authorized cloud work the single most valuable finding is usually not an exotic exploit — it is an identity with more power than its job requires. Industry breach reports year after year attribute the majority of cloud incidents to customer-side misconfiguration and identity mistakes, not to the provider's infrastructure failing.
Why this matters professionally:
Definition. A model that draws a line between what the cloud provider secures and what the customer secures.
Plain explanation. The provider runs the building, the electricity, the locks on the data-centre doors, and the software that runs virtual machines. You are the tenant: you decide who gets a key to your apartment and what you leave lying on the table.
How it works. The line moves depending on the service model:
| Service model | Provider secures | You secure |
|---|---|---|
| IaaS (e.g. a virtual machine) | Hardware, hypervisor, network fabric | OS patches, apps, data, IAM, firewall rules |
| PaaS (e.g. managed database) | Above plus the OS and DB engine | Schema, data, access policies, credentials |
| SaaS (e.g. hosted email) | Almost everything technical | Your users, their permissions, your data-sharing settings |
When it applies / when not. It always applies, but the boundary shifts by service — a frequent mistake is assuming "managed" means "secured for me." The provider secures the service; you still secure your use of it.
Pitfall. Believing a breach must be the provider's fault. Almost always the misconfiguration was on the customer side of the line.
Definition. Identity and Access Management is the system that decides which identity may perform which action on which resource.
Plain explanation. Three moving parts:
How it works. When a principal calls the cloud API, the platform evaluates every attached policy. An explicit Deny always wins; otherwise the request must be explicitly Allowed, or it is refused by default (deny-by-default).
When it applies / when not. IAM governs control-plane access (creating, reading, changing cloud resources). It does not, by itself, secure what happens inside an app you deploy — that is still your application's job.
Pitfall. Reading a role's name and assuming it is safe. ReadOnlyLogsRole may still carry a policy allowing iam:CreateAccessKey. Read the policy, not the name.
Definition. The recurring IAM mistakes that create most cloud attack paths.
"Action": "*" / "Resource": "*" or AdministratorAccess broadly. One leaked key becomes total compromise.iam:PassRole misuse — permission to hand an existing powerful role to a new service (e.g. launch a VM as an admin role). Combined with a compute-launch permission, a limited user escalates to admin without ever "being" admin.How it works together. Attackers chain these: enumerate identities → find one they control → find a role it can assume or a PassRole it can abuse → repeat until they reach admin. This is a privilege-escalation path.
When it applies / when not. These are configuration issues, present regardless of how well-patched your servers are. They are not provider bugs.
Pitfall. Fixing one over-grant and declaring victory. Escalation needs only one path; you must map all of them.
Definition. Grant only the permissions a principal actually needs, for only as long as needed.
Plain explanation. Start from nothing and add the specific actions and resources the task requires, prefer short-lived role credentials over static keys, and remove permissions that go unused.
How it works. Scope by action (s3:GetObject, not s3:*), by resource (one bucket ARN, not *), and by condition (only from your VPC, only with MFA). Rotate and expire credentials automatically.
Pitfall. "We'll tighten it later." Broad grants added "temporarily" are the ones found in breaches years later. Tighten first, loosen only with evidence.
CLOUD IAM THREAT MODEL (single account)
Entry points (untrusted) Trust boundary: the cloud IAM control plane
------------------------ --------------------------------------------
[ Leaked access key ] ---.
[ Phished console login ] -+--> ( AWS/Azure/GCP API ) ==BOUNDARY==> ASSETS
[ Compromised CI token ] --' | evaluates policies - Data (buckets, DBs)
[ SSRF on an EC2 app ] -----> (instance metadata) --> temp role creds - Compute (VMs, functions)
- Billing / spend
Principals inside boundary: - IAM itself (create users!)
user -> policy(read-only?)
\__ can it assume a role? ----> role(admin) <-- escalation path
\__ can it iam:PassRole + launch compute? ---> runs AS admin role
Defender's job: shrink each principal's allowed actions so no entry
point reaches the assets, and LOG every assume-role / policy change.
Knowledge check.
sts:AssumeRole on an admin role. What insecure assumption made this dangerous, and which single log event would reveal the escalation?The heart of IAM is a policy document (JSON in AWS; similar shapes in Azure/GCP). Read it as: this principal may (or may not) perform these Actions on these Resources, under these Conditions.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadOneBucketOnly", // human label for the statement
"Effect": "Allow", // Allow or Deny (Deny always wins)
"Action": ["s3:GetObject"], // scoped verb, NOT "s3:*"
"Resource": "arn:aws:s3:::lab-reports-bucket/*", // one resource, NOT "*"
"Condition": { // optional extra guardrails
"Bool": { "aws:MultiFactorAuthPresent": "true" }
}
}
]
}
The read-only inspection commands you would use in an authorized account (all safe, non-mutating):
# WHO am I acting as right now? (confirms which principal a credential maps to)
aws sts get-caller-identity
# List policies attached to a role, then read one
aws iam list-attached-role-policies --role-name lab-app-role
aws iam get-policy-version --policy-arn <arn> --version-id v1
Key structural points: Action and Resource are where over-permissioning hides ("*" is the red flag); sts:AssumeRole in an Action list means the principal can become another role; iam:PassRole means it can hand a role to a service.
Cloud platforms such as AWS, Azure, and GCP replace servers you own with on-demand services.
This changes the core security question. It is no longer "is the box patched?" but "who can do what?"
Responsibility is split between you and the provider:
Most cloud breaches come from customer-side misconfigurations, not provider failures.
Identity and Access Management (IAM) controls access in the cloud. It has two parts:
In the cloud there is no network edge to hide behind. Identity is the perimeter.
The failures that show up again and again:
*:* or AdministratorAccess everywhere. One leaked key then becomes total compromise.iam:PassRole or role chaining (linking one role to another to gain more access).Cloud penetration testing is mostly IAM analysis. The work is to:
This is the cloud version of Active Directory attack paths.
The fix is least privilege: scoped policies, short-lived credentials, and removing unused permissions.
The example below shows the required INSECURE → SECURE → VERIFY shape using AWS IAM policy JSON and safe, read-only CLI checks. Run only in a lab account you own.
An app role that was given far more than it needs:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "WayTooMuch",
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
Why it is dangerous: this role can do anything, including iam:CreateAccessKey, iam:AttachUserPolicy, and launching compute with iam:PassRole. If the app is hit by SSRF and its temporary credentials leak, the attacker owns the whole account. The insecure assumption is "the app is internal, so a broad grant is convenient." Convenience here equals total blast radius.
Grant only the two actions this app actually performs (read its reports bucket, write to one log group), scoped to named resources, and require the request originate from the app's VPC:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadReports",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::lab-reports-bucket",
"arn:aws:s3:::lab-reports-bucket/*"
]
},
{
"Sid": "WriteOwnLogs",
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:us-east-1:111122223333:log-group:/lab/app:*"
},
{
"Sid": "DenyIamEscalation",
"Effect": "Deny",
"Action": ["iam:*", "sts:AssumeRole"],
"Resource": "*"
}
]
}
The explicit Deny on iam:* and sts:AssumeRole is a belt-and-braces guard: even if a future edit accidentally adds an IAM allow, the Deny still wins.
AWS provides a dry-run simulator so you can test policies without performing the actions:
# GOOD path must be Allowed:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:role/lab-app-role \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::lab-reports-bucket/report-1.pdf
# expected evalDecision: allowed
# BAD path must be Denied:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:role/lab-app-role \
--action-names iam:CreateAccessKey \
--resource-arns arn:aws:iam::111122223333:user/admin
# expected evalDecision: explicitDeny
Expected output. The first call returns "EvalDecision": "allowed" for s3:GetObject; the second returns "EvalDecision": "explicitDeny" for iam:CreateAccessKey. Together they demonstrate the policy accepts the legitimate task and rejects the escalation attempt — the essence of a verified least-privilege fix. (111122223333 is AWS's documentation placeholder account id; the ARNs are illustrative.)
Walking the secure policy and the verify step:
| Step | Line / element | What happens | Why it matters |
|---|---|---|---|
| 1 | "Version": "2012-10-17" |
Selects the current IAM policy language version | Fixed date string; not "today" — using the wrong value silently changes how the doc is parsed |
| 2 | Sid: ReadReports, Effect: Allow |
Opens a statement that grants something | Each statement is evaluated independently and combined |
| 3 | Action: [s3:GetObject, s3:ListBucket] |
Allows exactly two verbs | Scoped — not s3:*, so delete/put are still denied by default |
| 4 | Resource: [bucket, bucket/*] |
Both the bucket and its objects | ListBucket needs the bucket ARN; GetObject needs the /* object ARN — a classic off-by-one that breaks apps if you list only one |
| 5 | Sid: WriteOwnLogs |
Second Allow, one log group only | The app can write telemetry but cannot read or delete other groups |
| 6 | Sid: DenyIamEscalation, Effect: Deny |
Explicitly forbids all iam:* and sts:AssumeRole |
Deny beats Allow in evaluation, so this blocks escalation even if a later Allow is added by mistake |
| 7 | simulate-principal-policy ... s3:GetObject |
Asks the engine to evaluate (not perform) the good action | Returns allowed → legitimate work still functions |
| 8 | simulate-principal-policy ... iam:CreateAccessKey |
Evaluates the escalation action | Returns explicitDeny → the attack path is closed |
How the decision is reached (evaluation order): start deny-by-default → is there an explicit Deny? If yes → deny (step 8 hits the DenyIamEscalation statement). If no → is there an explicit Allow? If yes → allow (step 7 hits ReadReports). If no → implicit deny. That ordering is why adding the belt-and-braces Deny is safe and strong.
Mistake 1 — "Decoding the role/token tells me it's safe."
ReadOnlyRole can carry iam:CreateAccessKey. Signature/authority checks and the actual attached policy are what matter.list-attached-role-policies, get-policy-version) and simulate the sensitive actions.Mistake 2 — Using "Action": "*" to "get it working."
"*" in Action/Resource; alert on AdministratorAccess attachments.Mistake 3 — Long-lived access keys in code or CI.
AKIA... key committed to git or pasted in a pipeline variable.Mistake 4 — "The scanner passed, so we're secure."
PassRole + RunInstances combo). A pass is not a proof; nothing is "completely secure."Mistake 5 — Forgetting ListBucket needs the bucket ARN.
s3:GetObject on bucket/* only, then wondering why listing fails.GetObject acts on objects (/*); ListBucket acts on the bucket itself.Common symptoms and what to check:
AccessDenied on a legitimate call. Read the full message — modern clouds name the missing action and the resource. Add exactly that action, scoped to that resource, then re-simulate. Do not jump to *.explicitDeny even after adding an Allow. A Deny statement (in this policy, a permission boundary, an SCP/org policy, or a resource policy) is overriding you. Deny always wins — find and adjust the correct layer, don't pile on Allows.aws sts get-caller-identity first.Questions to ask when an IAM change misbehaves:
get-caller-identity)Deny, permission boundary, or org/SCP policy above me?Condition (MFA, IP, VPC) unmet?Safe debugging workflow: prefer non-mutating tools — simulate-principal-policy and get-caller-identity change nothing, so you can iterate on a policy in a lab without risk before applying it.
Security & safety — detection and logging for IAM.
IAM is invisible unless you log it. Capture the control-plane audit trail (AWS CloudTrail, Azure Activity/Entra sign-in logs, GCP Cloud Audit Logs) and, for each security-relevant event, record:
Events that signal abuse (watch and alert on these):
CreateAccessKey, CreateUser, AttachUserPolicy, PutUserPolicy — especially outside change windows.AssumeRole chains, and any use of PassRole paired with compute launch (RunInstances, function create/update).AccessDenied from one principal (enumeration/brute-forcing permissions).StopLogging, deleting a trail) — a top-priority alert.Never log: passwords, access keys/secret keys, session tokens or cookies, private keys, MFA seeds, full payment card numbers (PANs), or unnecessary PII. Log that a credential was used and its identifier, never the secret material.
False positives arise from legitimate automation (CI creating short-lived sessions), a new admin onboarding, or a VPN changing a user's source IP. Baseline normal behaviour, tune alerts to context (who/when/how often), and route to a human before locking accounts — over-alerting trains responders to ignore the console, which is its own risk.
Authorized real-world use case. A company hires you to assess its AWS account before a product launch. With written scope and read-only credentials, you enumerate roles and policies, find that a build role holds iam:PassRole plus ec2:RunInstances (a launch-as-admin escalation path) and that three developers still have long-lived keys over a year old. You report each with severity tied to exploitability and impact, propose scoped policies, and — crucially — hand over verification steps so the fix can be proven.
Professional best-practice habits:
| Beginner focus | Advanced focus | |
|---|---|---|
| IAM reading | Spot "*" and admin grants; read one policy |
Map multi-step escalation chains across roles and accounts |
| Credentials | Find and rotate long-lived keys | Design OIDC/short-lived federation, permission boundaries, SCPs |
| Verification | Run the policy simulator on one action | Automated least-privilege tests in CI; detect drift |
| Detection | Know which log records IAM changes | Build alerts, baselines, and response runbooks |
Authorization checklist (any lab or engagement): (1) written permission naming exact accounts/scope; (2) a lab you own or a sanctioned target (localhost, personal dev account, intentionally-vulnerable range, CTF) — never a third party; (3) agreed time window and rules of engagement; (4) read-only where possible; (5) a rollback/cleanup plan agreed in advance.
All tasks are lab-only: use a personal, isolated cloud account (or a free-tier sandbox / intentionally-vulnerable practice range you control). Never test an account you do not own. Each ends by remediating and verifying.
Beginner 1 — Split the responsibility.
Beginner 2 — Spot the over-grant.
"Action": "*" implies (name at least three, e.g. iam:CreateAccessKey), and state the one-line insecure assumption behind the grant.Intermediate 1 — Write and simulate a least-privilege policy.
lab-reports-bucket, write an Allow-only policy; then use aws iam simulate-principal-policy (or your cloud's equivalent) to show s3:GetObject is allowed and s3:DeleteObject is denied."*" in Action or Resource; read-only actions only.ListBucket needs the bucket ARN too.Intermediate 2 — Map one escalation path (analysis only).
iam:PassRole an admin role and call ec2:RunInstances, describe in words how those two permissions combine into admin, then write the remediation (which permission to remove or scope) and the verification (simulator result proving the path is closed).PassRole, role chaining, remediation + verification.PassRole's Resource, not deleting RunInstances.PassRole, RunInstances) an analyst would alert on.Challenge — Least-privilege review with detection design.
"*" or long-lived keys; (2) rewrite one over-permissive policy to least privilege; (3) verify with the simulator (good action allowed, dangerous action denied); (4) design a detection: list the exact audit-log events and the alert conditions that would catch abuse of the original permissions, and note two likely false positives.Lab cleanup / reset (all tasks): delete any test users, roles, policies, keys, or instances you created; confirm no long-lived keys remain (aws iam list-access-keys); and check the audit log shows your session ended. Leave the account as you found it.
Main concepts.
"*"), long-lived keys, broadly-assumable roles, and iam:PassRole misuse. Attackers chain them into privilege-escalation paths.Key syntax / commands. Policy JSON (Effect/Action/Resource/Condition, explicit Deny wins); aws sts get-caller-identity (who am I?); aws iam simulate-principal-policy (test allow/deny without acting).
Common mistakes. Trusting a role's name instead of its policy; decoding a token and calling it verified; "*" "to get it working"; committing long-lived keys; treating a passed scanner as proof of security (nothing is "completely secure").
What to remember. Ask "who can do what?", start from zero and add only what is needed, always verify the fix (good action allowed, dangerous action denied), log every IAM change (never the secrets themselves), and test only in accounts you own or are authorized to assess.