When I set up the first service accounts for my Google Cloud project, I kept it simple. Gave them the roles that worked. Things ran. No complaints.
Then I took a proper look at what those accounts could actually do.
One of them had roles/editor bound at the project level. It could read secrets, write to buckets, deploy Cloud Functions, and delete datasets. The workload using it needed exactly one of those things.
That is when I stopped treating IAM as a checkbox.
The Part Nobody Warns You About
Google Cloud gives you a lot of predefined roles. They are convenient. They are also blunt instruments.
roles/editor sounds reasonable when you are moving fast. But editor-level access at the project level means a compromised service account can touch almost everything. A misconfigured workload, a leaked key, a supply chain issue — and the blast radius is your entire project.
The problem is not that people are careless. The problem is that starting with broad permissions is fast, and tightening them later requires effort that is easy to postpone.
I postponed it. Most people do.
IAM Conditions Looked Like the Answer
When I started looking at how to scope access properly, IAM Conditions came up everywhere. Bind a role, attach a condition, restrict by resource name, by time, by request path.
On paper it sounds clean.
In practice, I found the documentation dense, the CEL syntax not obvious, and the feedback loop slow. You define a condition, bind it, test it, and discover it does not behave the way you expected. Then you debug why.
IAM Conditions are powerful. But they are not where I would start if the goal is simply to stop giving accounts more than they need.
What Actually Worked
The fix was less clever than I expected. It was just slower and more deliberate.
Step one: audit what each service account actually calls.
Cloud Audit Logs and the Policy Analyzer show you what permissions are being used. Most accounts in my case used a small subset of what their role granted.
Step two: replace broad roles with custom roles.
I created custom roles with only the permissions each workload actually needed. No editor. No viewer-at-project-level when bucket-level is enough. This took time but produced something I could reason about.
Step three: enforce one service account per workload.
Shared service accounts make auditing harder. If something calls the same SA for unrelated purposes, you cannot tell from the logs what actually happened or where a compromise started.
Step four: stop using service account keys where possible.
Workload Identity Federation removes the need to manage keys for workloads running on GKE, Cloud Run, or GitHub Actions. No key file means no key to leak. I moved [X out of Y workloads — add your own number here] off static keys.
What I Would Still Caution About
IAM Conditions are worth learning if you need fine-grained conditional access — time-based access, resource name restrictions, or temporary elevated access with expiry. For those cases, they are the right tool.
But they add complexity. If your team is still at the stage where service accounts have roles/editor on production, conditions are not the first problem to solve.
Least privilege first. Conditions later.
My Takeaway
GCP IAM is not hard to understand. It is easy to defer.
The service accounts I had were not obviously wrong. They worked. The risk was invisible until I chose to look for it.
Audit your service accounts. Remove what they do not need. Use Workload Identity where you can. That is most of the security improvement, without touching CEL once
🙏 If you found this article helpful, give it a 👏 and hit Follow — it helps more people discover it.
🌱 Good ideas tend to spread. I truly appreciate it when readers pass them along.
📬 I also write more focused content on JavaScript, React, Python, DevOps, and more — no noise, just useful insights. Take a look if you're curious.