2. IAM & Least Privilege in the Cloud
Week 3 applied least privilege to Linux users and groups; IAM (Identity and Access Management) is the same discipline applied to cloud identities — users, roles, and the services acting on their behalf.
// BAD: "*" on both action and resource -- this identity can do
// literally anything to literally every resource in the account
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
// GOOD: scoped to exactly what a specific service needs
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-app-uploads/*"
}
The most common real-world failure mode isn't malice, it's convenience: attaching a broad managed policy (or the equivalent of admin access) "to get unblocked," intending to scope it down later — and later never comes. A compromised credential or a misconfigured service with overly broad IAM access has the same blast radius as a stolen root password.
# Prefer IAM roles (temporary, auto-rotating credentials assumed by a
# service) over long-lived access keys embedded in application config
# A long-lived key that leaks (committed to a repo, logged accidentally --
# Week 10 covers this class of leak directly) is valid until someone
# manually revokes it. A role's temporary credentials expire on their own,
# shrinking the window a leaked credential is even useful.
Grant the minimum permissions a service needs to function, watch what it actually calls (many providers offer access-analysis tooling for exactly this), and widen only when a real, observed need shows up. Starting broad "to be safe" and meaning to narrow it later is how most IAM sprawl happens — the narrowing step gets deprioritized indefinitely.
3. S3 Buckets & the Misconfigurations Behind Real Breaches
A shocking number of major, publicly reported data breaches trace back to one specific mistake: an object storage bucket (S3 or equivalent) left readable by anyone on the internet, holding data that was never meant to be public.
{
"Effect": "Allow",
"Principal": "*", // "*" here means literally anyone, unauthenticated
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::customer-data-backups/*"
}
// This policy alone -- no compromised credential needed -- makes every
// object in the bucket downloadable by anyone who finds or guesses the URL
This is often introduced accidentally: a developer sets a bucket public temporarily to debug something, or copies a policy example from documentation without adjusting the principal, and it's never reverted. Cloud providers now ship account-level and bucket-level "block public access" settings specifically because this mistake was so common:
# Enable "Block Public Access" at the account level as a default,
# overridable only deliberately, per-bucket, when a bucket genuinely
# needs to serve public content (like static website assets).
$ aws s3api put-public-access-block \
--bucket my-bucket \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
# Then audit: which buckets genuinely need public access, and confirm
# every other one has it explicitly blocked, not just "not intentionally public"
A public bucket with no obvious link to it is still world-readable — bucket names are often guessable (company name + "backups", "-data", "-logs"), and automated scanners specifically enumerate common naming patterns looking for exactly this. Public means public, discoverable by anyone eventually, not just anyone who happens to already know the URL.
4. Security Groups & VPC Boundaries
A VPC (Virtual Private Cloud) is Week 2's network segmentation, expressed as cloud infrastructure — your own isolated network within the provider, subdivided into subnets, with security groups acting as instance-level firewalls.
# A security group's default is deny-all inbound -- you explicitly allow
# only what's needed, same discipline as Week 2's firewall rules
Inbound rules for "web-server-sg":
ALLOW tcp/443 from 0.0.0.0/0 # public HTTPS
ALLOW tcp/22 from bastion-sg # SSH only from the bastion's security group
Inbound rules for "db-sg":
ALLOW tcp/5432 from web-server-sg # only the web tier can reach the database
# nothing else allowed -- not even from the VPC's other subnets by default
Referencing another security group as a source (rather than a raw IP range) is a distinctly cloud-native pattern worth knowing: it means the rule automatically covers every instance in that group, present and future, without hardcoding IP addresses that change as instances scale up and down.
An SSH port open to the entire internet is one of the most commonly flagged findings in any cloud security audit — it means every automated bot scanning the internet for open port 22 is a live brute-force attempt against your server. SSH access should come through a bastion host, a VPN, or a provider's session-manager equivalent — never a security group rule allowing the whole internet.
5. Cloud-Native Logging with CloudTrail
Week 3's auditd logs actions on a single Linux box. CloudTrail
(AWS's version — every provider has an equivalent) does the same thing for the entire
cloud account: every API call, who made it, from where, and whether it succeeded.
{
"eventName": "PutBucketPolicy",
"eventTime": "2026-08-20T14:32:01Z",
"userIdentity": { "arn": "arn:aws:iam::123456789012:user/deploy-bot" },
"sourceIPAddress": "203.0.113.42",
"requestParameters": { "bucketName": "customer-data-backups", "policy": "..." }
}
// This is EXACTLY the record that would answer "who made this bucket public,
# and when" -- but only if CloudTrail was already enabled before it happened
This is the cloud-native version of Week 3's "logs you didn't turn on don't exist when you need them" — CloudTrail should be enabled account-wide, from day one, with its logs shipped to a separate, access-restricted account or bucket so a compromised account can't simply delete the evidence of its own compromise.
A CloudTrail log with nobody watching it is a forensic record after the fact, not a real-time defense. Week 12 covers turning logs like this into actual alerts — for now, know that "CloudTrail is enabled" is necessary but not sufficient; someone (or some automated rule) needs to actually be looking at events like a new public bucket policy or a root account login as they happen.
6. Hands-on Exercise
Audit and lock down a small AWS free-tier account
Using an AWS free-tier account (or a written policy exercise if you'd rather not create real cloud resources), apply this week's controls end to end.
Part 1 — IAM least privilege:
- Create an IAM user (never use the root account for daily work) with a policy granting only
s3:GetObjectands3:PutObjecton one specific bucket — not a broad managed policy. - Attempt an action outside that scope (e.g. deleting a different bucket, or listing IAM users) using that identity, and confirm it's denied — screenshot or record the denial.
- Write a one-paragraph justification for exactly why this identity needs exactly these two actions, on exactly this resource — the same discipline as Section 2's "start narrow" principle.
Part 2 — Find and fix an S3 misconfiguration:
- Create a bucket, deliberately attach a public-read policy (Section 3's example), and confirm — from a private/incognito browser window, logged out of AWS entirely — that you can fetch an object's URL directly with no credentials.
- Fix it: remove the public policy, enable "Block Public Access" at the bucket level, and confirm the same anonymous request now fails.
- Enable default encryption at rest on the bucket, and explain in one sentence what this protects against that access-control alone doesn't (hint: think about who else might physically handle the underlying storage).
Clean up after this exercise — delete the bucket and any test objects when you're done, and never leave a deliberately-public bucket around "just for later." A test bucket that outlives its exercise is exactly the kind of forgotten misconfiguration Section 3 is about.
Part 3 — Security groups and CloudTrail:
- Create a security group for a hypothetical web server allowing only
443from anywhere and22from your own current IP only (not0.0.0.0/0) — write out the exact rules. - Enable CloudTrail (a free-tier trail is sufficient) if not already on, then perform an action like creating a bucket or modifying a security group rule.
- Find that exact event in CloudTrail's event history, and note the fields from Section 5's example (event name, identity, source IP, timestamp) — confirm you can answer "who did what, when" purely from the log, without relying on your own memory of doing it.
If you'd rather not create real AWS resources at all, do this exercise entirely as a written design document: draft the exact IAM policy JSON, the exact bucket policy and its fix, and the exact security group rules — the discipline of writing precise, minimal permissions is the actual skill, whether or not you deploy it.
7. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Under the shared responsibility model, does moving from IaaS to SaaS mean you have fewer security responsibilities overall?
Under the shared responsibility model, does moving from IaaS to SaaS mean you have fewer security responsibilities overall?
The provider takes on more infrastructure and runtime responsibility as you move toward SaaS, but identity/access management and your own data are always yours, at every tier. A misconfigured SaaS permission or a compromised identity can still cause a real breach — "the provider handles security" is never fully true regardless of service model.
Q2
Why is an IAM role with temporary, auto-rotating credentials generally safer than a long-lived access key embedded in application config?
Why is an IAM role with temporary, auto-rotating credentials generally safer than a long-lived access key embedded in application config?
A long-lived key remains valid indefinitely until someone manually revokes it — if it leaks (committed to a repo, logged accidentally), it's a standing risk with no expiry. A role's credentials are temporary and expire on their own, meaning even a leaked credential has a naturally shrinking window of usefulness to an attacker.
Q3
A bucket has no public link anywhere on the company's website. Is it safe to assume it's not publicly accessible?
A bucket has no public link anywhere on the company's website. Is it safe to assume it's not publicly accessible?
No. If the bucket's policy actually grants public read access, it's reachable by anyone who discovers or guesses its name — regardless of whether it's linked from anywhere. Automated scanners specifically enumerate common bucket-naming patterns looking for exactly this. "Not linked" is obscurity, not access control.
Q4
Why does referencing another security group as a rule's source (instead of a raw IP range) matter in a cloud environment specifically?
Why does referencing another security group as a rule's source (instead of a raw IP range) matter in a cloud environment specifically?
Cloud instances are ephemeral — they're created, scaled, and replaced with new IP addresses constantly. A rule referencing another security group automatically covers every current and future instance in that group, without needing to be updated every time the underlying IP addresses change, which a hardcoded IP-range rule would require.