Week 8: Cloud Security Fundamentals

Moving to the cloud doesn't hand security to your provider — it draws a line, and everything on your side of that line is still entirely your job. This week is about knowing exactly where that line sits, applying least privilege to cloud identities the same way Week 3 applied it to Linux users, understanding the specific misconfiguration that's caused more public data breaches than almost any other single mistake, and the network and logging controls that make a cloud environment defensible.

Module 7 of 15 Week 8 of 16 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Explain the shared responsibility model and where it shifts across IaaS, PaaS and SaaS
  • Write a least-privilege IAM policy and correctly secure an S3 bucket
  • Configure security groups and VPC boundaries, and query CloudTrail for a specific event

1. The Shared Responsibility Model

Every major cloud provider draws the same fundamental line: the provider secures the cloud itself (physical data centers, host hardware, the virtualization layer), and you secure what you put in it. Where exactly that line sits shifts depending on the service model:

where the line moves
IaaS (e.g. EC2):  You manage the OS, patching, network config, IAM, app code, data.
                  Provider manages: physical hosts, hypervisor, base network hardware.

PaaS (e.g. RDS, Lambda): Provider also manages the OS and runtime patching.
                  You still manage: IAM, access policies, app code, data, configuration.

SaaS (e.g. Google Workspace): Provider manages almost everything.
                  You still manage: WHO has access, what they can do, and your own data.

Notice what never moves, regardless of service model: identity and access management, and your own data. No cloud provider, at any service tier, will ever stop a legitimate credential from being misused, or a bucket you configured as public from being read by the public. That's exactly why Sections 2–3 matter most.

"Secure of the cloud" vs. "secure in the cloud"

This is the phrase every major provider uses, and it's worth internalizing exactly: the provider guarantees the infrastructure itself isn't compromised. It says nothing about whether your S3 bucket policy, your IAM permissions, or your security group rules are configured correctly — those are entirely, unambiguously your responsibility, at every service tier.

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.

an overly broad policy, and the fix
// 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.

roles over long-lived credentials
# 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.
Start narrow, widen with evidence — not the reverse

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.

a bucket policy that makes everything 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:

the actual defense
# 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"
"Not linked from anywhere" is not the same as "not 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, default-deny per Week 2
# 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.

0.0.0.0/0 on SSH is a near-universal red flag

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.

what a CloudTrail event actually captures
{
  "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.

Alert on the log, don't just collect it

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

Hands-on

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:

  1. Create an IAM user (never use the root account for daily work) with a policy granting only s3:GetObject and s3:PutObject on one specific bucket — not a broad managed policy.
  2. 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.
  3. 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:

  1. 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.
  2. Fix it: remove the public policy, enable "Block Public Access" at the bucket level, and confirm the same anonymous request now fails.
  3. 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).
Hint

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:

  1. Create a security group for a hypothetical web server allowing only 443 from anywhere and 22 from your own current IP only (not 0.0.0.0/0) — write out the exact rules.
  2. 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.
  3. 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.
Hint

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?

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?

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?

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?

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.