I once audited an AWS account and found a role attached to a web server with AdministratorAccess. Not because anyone decided that; because two years earlier someone had been debugging a permissions error at the end of a long day, attached the broad policy to unblock themselves, and never came back to narrow it. The comment in the ticket said "temporary."
That role could read every bucket, delete every database and create new users. It was attached to the most internet-exposed machine in the estate.
Nothing about that story is unusual, and it is why I have come to think of cloud security as roughly 70% identity and access management, 20% not exposing things by accident, and 10% everything else people spend their time on.
Why IAM Is the Whole Game
In a traditional data centre, an attacker who compromises a server gets that server. They then have to work to move sideways, and network controls stand in their way.
In the cloud, a compromised workload gets whatever its identity can do — and identity has no distance. If that role can call the storage API, the attacker can call the storage API, from anywhere, over the internet, without touching another machine. Lateral movement is an API call.
This is why an over-permissioned role is not a hygiene issue. It is the difference between an incident and a catastrophe.
The Rules That Prevent Most of It
No long-lived access keys. Anywhere. Workloads use instance roles or workload identity. Humans use SSO with temporary credentials. CI authenticates with OIDC federation rather than a stored secret. Static keys leak — into repositories, laptops, chat messages, screenshots — and unlike a session, they do not expire on their own.
# GitHub Actions assuming a role directly. No secret to leak.
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/deploy-orders-api
aws-region: eu-west-1
Grant permissions to roles, never to people. Humans assume roles. This gives you one place to change access, an audit trail of who assumed what and when, and the ability to revoke by removing group membership rather than hunting through attached policies.
Wildcards are a review failure. "Action": "s3:*" on "Resource": "*" is the pattern behind most of the bad findings I have seen. Start from nothing, add what breaks, and write the specific actions. It takes an extra twenty minutes and it is the difference between a contained incident and a total one.
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::orders-uploads/incoming/*",
"Condition": {
"StringEquals": { "aws:SourceVpce": "vpce-0a1b2c3d" }
}
}
Two actions, one prefix in one bucket, and only from a specific VPC endpoint. Compare that to what "give the service access to S3" usually produces.
Use the access analysis tools. Both AWS and Azure will now tell you which permissions a principal has actually used over the past months. Comparing granted against used is the fastest path to safely shrinking a policy, and it turns "we cannot narrow this, we do not know what it needs" into an answerable question.
Guardrails above account level. Service control policies on AWS, or Azure Policy, let you set boundaries nobody can exceed — even an administrator. Denying the disabling of logging, denying public access to storage at the organisation level, restricting which regions can be used. This is the control that survives human error, and it is underused.
Azure Specifics Worth Knowing
The concepts map across, the failure modes differ slightly.
Use managed identities for anything running in Azure — the direct equivalent of instance roles, and the reason to never put a client secret in an app setting. Prefer role assignments at the narrowest scope that works: resource, then resource group, then subscription. Owner at subscription scope is handed out far too casually, and it includes the ability to grant more access, which makes it effectively permanent.
Watch Entra ID application permissions in particular. An app registration with broad Graph permissions granted by an admin is a quiet, powerful backdoor that does not show up in any resource-level review. And turn on Privileged Identity Management so administrative roles are activated for a few hours with approval rather than held permanently.
The Second Category: Things Exposed by Accident
After IAM, almost every cloud breach I have read about comes down to something being reachable that should not have been.
Storage open to the public because a permission was widened during a debugging session. A database with a public endpoint and a weak password, because the managed service defaulted that way and nobody changed it. A management interface on a virtual machine reachable from the internet. A snapshot or backup shared publicly by mistake.
The reliable defence is not vigilance. It is making the exposure impossible at a level above the person doing the work: account-level blocks on public storage access, databases created only with private endpoints, security groups reviewed as code in pull requests rather than clicked in a console.
Which is the real argument for infrastructure as code in a security context. It is not repeatability — it is that a human being reads the change before it exists, and a scanner can read it too.
Logging: Turn It On Before You Need It
During an incident, the question is always what the attacker did. If logging was not enabled beforehand, that question has no answer, and the honest report says "we cannot determine the scope."
The minimum I would want everywhere: API audit logs enabled in every region, not just the ones you use — attackers work in the unused ones precisely because nobody looks there. Logs delivered to a separate account with write-once retention, so someone with access to the main account cannot erase their trail. Data-plane logging on storage holding anything sensitive. And a small number of alerts that actually fire at a human: root account used, logging disabled, IAM policy widened, unusual region activity, mass deletion.
Five alerts that someone reads beat a thousand feeding a dashboard nobody opens.
Compliance, Pragmatically
Frameworks like SOC 2 and ISO 27001 come up as soon as you sell to larger customers, and engineers tend to treat them as pure overhead. Some of it is. But the underlying requirements — access review, change control, logging, encryption, incident response — are things a well-run system should have anyway.
The practical approach: use the cloud provider's own benchmark as your baseline, run the posture tooling continuously rather than before an audit, and generate evidence automatically. If demonstrating that access is reviewed requires a person to assemble screenshots for a week, the control is not really operating; it is being performed for the auditor.
And be careful with the shared responsibility line. The provider secures the infrastructure. Your data, your identities, your configuration and your application are yours. Nearly every cloud breach is on the customer's side of that line.
Where to Start on Monday
If you inherited a cloud account and want the highest return for a day's work: list every principal with administrative permissions and justify each one. Find and delete long-lived access keys, starting with the oldest. Turn on the account-level public access blocks. Enable audit logging everywhere and send it somewhere immutable. Set five alerts. Then run the provider's posture check and work the critical findings.
That will not make you unbreachable. It will remove the specific failures that appear in almost every incident report — including the "temporary" administrator policy that has been attached to a web server for two years, waiting.



