Identity is the real perimeter in the cloud. Write least-privilege IAM policies, prefer short-lived roles over long-lived keys, and hunt down the wildcard permissions that turn one leak into a full compromise.
In the cloud, what an identity is allowed to do matters more than where a request comes from — a valid credential works from anywhere. So the blast radius of any compromise equals the permissions of the identity that was compromised. Least privilege is therefore the highest-leverage cloud control: it caps how much damage a leaked key or hacked service can do.
On-prem: "are you inside the network?" (perimeter = firewall)
Cloud: "what is this identity allowed to do?" (perimeter = IAM)
Blast radius of a compromise = the identity's permissions.
leaked key with AdministratorAccess -> entire account gone
leaked key scoped to read ONE bucket -> one bucket exposed
Every permission you grant is potential blast radius. Grant the least
that works, and the worst day is survivable.A good IAM policy names specific actions on specific resources, not wildcards. Start from zero and add exactly what the workload needs — this one read/write to a single prefix of one bucket, nothing else. Add conditions (encryption required, source IP, MFA) to tighten further. The discipline is "deny by default, allow the minimum."
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "RWOwnPrefixOnly",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::app-uploads/${aws:username}/*",
"Condition": {
"Bool": { "aws:SecureTransport": "true" },
"StringEquals": { "s3:x-amz-server-side-encryption": "aws:kms" }
}
}]
}
// specific actions, one bucket + per-user prefix, TLS + encryption required.
// Contrast the danger next.The permissions that cause disasters look convenient: Action "*" on Resource "*", or a broad managed policy attached "just to unblock" someone. These turn any credential leak into total account compromise. Audit for them relentlessly, and use the provider's access analyzer to right-size permissions down to what was actually used.
// THE policy that ends companies — never ship this:
{ "Effect": "Allow", "Action": "*", "Resource": "*" } // = admin
// find over-broad grants:
aws accessanalyzer list-findings --analyzer-arn <arn> # external access
aws iam generate-service-last-accessed-details --arn <role-arn> # unused perms
// right-size from real usage:
# IAM Access Analyzer can generate a policy from CloudTrail history —
# i.e. "only the actions this role actually used in 90 days". Start broad
# in dev, then tighten to observed usage before prod.A long-lived access key is a permanent secret that can leak and be used forever. Prefer roles that grant short-lived, automatically-rotated credentials: instance/pod roles for workloads, and federated identity (OIDC/SSO) for humans and CI. This is the same OIDC pattern the DevSecOps Pipeline course uses — no standing secret means nothing to steal.
# a workload assumes a role and gets temporary creds — no stored key
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/app-runtime \
--role-session-name app
# returns creds that EXPIRE in ~1h and auto-rotate.
# humans: federate via SSO/OIDC, don't create IAM users with keys.
# CI: use OIDC federation (see DevSecOps Pipeline course), not an access key.
# audit for the risky long-lived keys that DO exist:
aws iam list-access-keys --user-name someuser
# rotate/remove keys older than your policy (e.g. 90 days) — or eliminate users.