Full access is the default trap in AWS: it's easy to grant and easy to forget about. Here's how I walked an IAM user back from full S3 access to read-only, why the AWS account model makes this necessary, and how I proved the change actually took effect instead of just assuming it did.
Two Sessions, One Account, Very Different Realities
I like to teach permissions by showing them side by side rather than describing them in the abstract. So for this walkthrough I had two console sessions open at once: one logged in as an administrator with full access to everything, and the other logged in as a demo IAM user I'd created called user-s3. On the admin side, everything works. On the user-s3 side, almost everything throws an access denied error. That contrast is the whole point.
It's tempting to think of that restricted user as operating in some kind of sandboxed corner of AWS — their own little space where they can do a limited set of things. That's not how it works, and clearing up that misunderstanding early saves a lot of confusion later.
Before touching any policies, I wanted both windows visible so that every change I made on the admin side would have an immediate, visible consequence on the user side. That before-and-after structure is what makes a permissions demo actually convincing instead of just theoretical.
There's No Such Thing as a Personal AWS Workspace
Here's the point of confusion I run into constantly with people new to AWS: they assume that each IAM user gets some kind of individual workspace, like a personal folder or a walled-off environment within the account. They don't. An AWS account is a single shared account. Every bucket, every instance, every resource lives in that one shared space, regardless of which user is looking at it.
What differs between users isn't what exists — it's what they're allowed to see and do. That distinction is entirely governed by policies, and those policies attach to one of three things: individual users, groups, or roles. There's no environment-level isolation happening. If user-s3 can't see a bucket, it's not because the bucket lives somewhere else — it's because no policy grants that visibility.
This matters because it reframes the entire task. Locking down access isn't about moving a user into a smaller room. It's about editing the specific document — the policy — that defines what they're permitted to touch inside the one room everyone shares. Once that clicks, the rest of the workflow makes a lot more sense, because you stop looking for a settings toggle on the user and start looking at the policies attached to them.
Why Full Access Is a Problem Even When Nothing's Gone Wrong Yet
Before making any changes, I wanted to actually show what's at stake. From the admin session, I pulled up the full list of S3 buckets in the account. Every one of them was visible, and — this is the part that matters — every one of them was deletable. Not hypothetically deletable. Actually, right now, one click away from being gone.
I didn't delete anything. I want to be clear about that, because it would have been trivial to do and it would have made for a punchier demo moment. But destroying a real bucket just to prove a point is bad practice, full stop — there's no version of "I meant to do that" if something in there mattered. The risk illustration works fine without the actual damage.
That's really the motivating idea behind this entire exercise. Full access isn't dangerous because someone is definitely going to misuse it. It's dangerous because it's available to be misused — by a mistake, a compromised credential, a rushed Tuesday afternoon click. The fix isn't reactive; it's structural. You remove the capability before it becomes a problem, not after.
Editing the Group Policy
With the risk established, I moved into IAM to make the actual change. The user-s3 account belonged to a group, and that group had a policy attached that granted full access — which is exactly the kind of broad grant I'd just spent the last section arguing against.
First, a small housekeeping step: I renamed the policy label so it actually described what it was about to become, rather than leaving a stale "full access" name sitting on something that would soon be much narrower. It's a minor thing, but mislabeled policies are how permission sprawl becomes permanently confusing six months later.
Then I attached AWS's pre-built managed policy for S3 read-only access. This is one of the nice things about working within AWS's ecosystem — you're rarely writing least-privilege policies from scratch for common use cases. AWS maintains a library of managed policies for exactly this kind of situation, and S3 read-only access does what it says: view and retrieve objects, but no writing, no deleting, no modifying bucket configuration.
At this point, the group had two policies attached simultaneously: the original full-access policy and the new read-only one. That's an intentional intermediate state, not a mistake — it set up the next step.
Removing the Redundant Policy — and Why I Didn't Trust a Refresh
With both policies attached, full access still won. IAM permissions are additive by default, so as long as the full-access policy was still attached to the group, the read-only policy sitting next to it was effectively decorative. The fix was straightforward: delete the full-access policy from the group so read-only access was the only thing left governing what the group could do.
Here's the part I want to flag honestly, because it's a habit worth correcting. After making the change, my first instinct was to just refresh the console and see if the access had tightened up. That's a dangerous move to rely on — a refresh doesn't actually confirm anything about whether a policy change has taken effect. It's checking the wrong thing. The console re-rendering has nothing to do with whether the underlying permission change has propagated through AWS's systems.
The more reliable check is to go look at the user's own view — specifically, the policies actually attached to them — rather than inferring anything from how the console behaves after a refresh. It's worth noting that AWS doesn't publish a guaranteed propagation window for IAM policy changes. The common rule of thumb is that changes take effect within a few seconds, but that's not a documented SLA, and behavior can vary — so if timing matters for your workflow, it's worth checking AWS's current documentation rather than treating that few-seconds figure as a hard guarantee. The habit that actually matters here isn't waiting a specific number of seconds; it's verifying against the source of truth (the attached policy list) instead of a proxy signal like a page reload.
Proving It: Two Failed Actions That Were Actually the Point
Talk is cheap in permissions work, so I went back to the user-s3 session and tried to break the new restriction on purpose. First, I attempted to delete an existing object in the bucket. It failed — an access error, exactly as it should. Then I tried uploading a new file to the same bucket. That failed too.
These two failures were the actual payoff of the entire walkthrough. It's one thing to attach a managed policy and assume it does what its name suggests. It's another to watch, in real time, a restricted user get turned away from the exact actions that policy was designed to block. That gap between "I configured this" and "I confirmed this" is where a lot of security assumptions quietly go wrong, and it's worth building the habit of closing it every time, not just when something feels uncertain.
Worth noting: read access still worked fine on the user-s3 side. That's not a side detail — it's the whole design goal. The point was never to lock the user out entirely; it was to narrow what they could do down to exactly what their role required. Read-only access, correctly enforced, means someone can still do their job — viewing objects, retrieving files — without also carrying the ability to quietly delete or overwrite production data.
Read-Only Is a Floor, Not a Ceiling
I want to be straightforward about what this change does and doesn't accomplish. Restricting a group to S3 read-only access is a meaningful, real reduction in blast radius. It's also not a complete security posture on its own, and I don't want to present it as one.
There are several layers still missing here: MFA delete, which adds a second factor requirement specifically around destructive S3 actions; more granular per-user restrictions, since right now the policy applies at the group level rather than being tailored to individual roles; and compliance or retention policies that govern how long data has to be kept and under what conditions it can be removed at all. Those are deliberately left for later — they're bigger topics on their own, and bolting them onto this walkthrough would have diluted the one thing I wanted to land clearly here: the difference between having a permission and having it actually be the minimum necessary permission.
One more thing worth keeping in mind as you think about the ceiling on all of this: even the AWS root user — the account's most powerful identity — can be constrained by organizational guardrails, specifically through AWS Organizations Service Control Policies. The exact behavior depends on how those SCPs are configured, and there are documented exceptions to how far that restriction can go, so it's not an absolute rule to take at face value. But it's a useful mental model shift: least privilege isn't just something you apply to junior IAM users. With the right organizational controls, it can apply almost all the way to the top.
Conclusion
If you've got a group or user in your own AWS account still sitting on a full-access policy out of convenience, this is a good moment to go check — not to overhaul your whole security setup in one sitting, but just to see what a swap to a scoped managed policy like S3 read-only access would actually break, and what it wouldn't.
Official AWS references
KEEP PRACTICING THE DECISION
Learn the concept, then test the edge case.
The Solutions Architect Associate course in TutorialRepo combines guided lessons, scenario questions, review, and course-aware explanations.