Article 14 – Mastering IAM in Google Cloud

Alright, so you have built your infrastructure. You have your VPCs, your VMs, your databases — everything is running. Now comes the part that most people get wrong. Who gets access to what? Give everyone admin access and you are basically leaving the front door wide open. Give nobody access and nothing gets done. This is where Identity and Access Management (IAM) comes in, and honestly, it is the single most important security layer in all of Google Cloud.

I am going to walk you through the entire IAM model, the way I think about it. Basically, it all comes down to one question:

Who can do what on which resource?

Now, let us break this down piece by piece.

The “Who” — Principals

Before we grant any permissions, we need to define who we are granting them to. In IAM, the “who” is called a Principal. There are four main types you will work with:

  • Google Account — This is a person. A human user identified by their email address, like [email protected]. If you are clicking around in the Console or running gcloud commands, you are a Google Account principal.

  • Service Account — This is NOT a person. This is an identity for your code. When your VM needs to write a file to a Cloud Storage bucket, it authenticates as a service account. Think of it as a robot identity for your applications. It has to be noted that this one is so important, I am going to dedicate an entire section to it later.

  • Google Group — A collection of Google Accounts and Service Accounts. Instead of giving five developers the same permissions one by one, you put them all in a group (e.g., [email protected]) and grant permissions to the group. This is a critical best practice for any real environment.

  • Google Workspace / Cloud Identity Domain — This represents your entire organization. You can grant a permission to everyone in your yourcompany.com domain. Useful for broad, low-risk access like letting everyone view a dashboard.

The “Can Do What” — Roles

Now that we know who the actors are, we need to define what they are allowed to do. This is where Roles come in. A role is simply a bundle of permissions.

Basic Roles — The Blunt Instruments

In the early days, things were simple. There were three powerful roles:

  • Viewer (roles/viewer) — Read-only access. Can see resources but cannot change them.
  • Editor (roles/editor) — Read and write access. Can create, modify, and delete most resources. But it cannot manage IAM policies or billing.
  • Owner (roles/owner) — God mode. Everything an Editor can do, plus managing IAM, configuring billing, and deleting the project.

Now, for learning and personal projects, these are fine. But for production? Avoid them. Giving a developer the Editor role just so they can manage VMs also gives them permission to delete your production database. It violates the most important rule in security…

The Principle of Least Privilege

This principle says: a user or service should only have the absolute minimum permissions required to do its job. Nothing more. The basic roles are the enemy of this principle.

Predefined Roles — The Scalpels

This is where modern IAM really shines. Google provides thousands of predefined roles that are specific to a service and a job function. Instead of the all-powerful Editor, you can use roles like:

roles/compute.instanceAdmin   # Full control over Compute Engine instances
roles/storage.objectAdmin     # Full control over objects in Cloud Storage
roles/storage.objectViewer    # Can only read objects from buckets
roles/cloudsql.client         # Allows connecting to a Cloud SQL database

Now let us say you have a VM administrator and a data analyst. You give the VM admin compute.instanceAdmin and the data analyst storage.objectViewer. Neither has permissions they do not need. That is the Principle of Least Privilege in action.

Custom Roles — When Predefined Is Not Enough

Sometimes, even Google’s massive library of predefined roles does not fit your exact use case. Let me give you an example. Imagine you want a “junior operator” role that can start and stop VMs, but you explicitly do NOT want them to delete VMs. No predefined role gives you exactly that.

So you create a Custom Role. You hand-pick the exact permissions you need:

# Custom role definition file: junior-operator.yaml
title: "Junior VM Operator"
description: "Can start and stop VMs, but cannot delete them"
includedPermissions:
  - compute.instances.start
  - compute.instances.stop
  - compute.instances.get
  - compute.instances.list
# Create the custom role
gcloud iam roles create juniorOperator \
    --project=my-cool-project \
    --file=junior-operator.yaml
The Binding — Policies and the Resource Hierarchy

We have our “who” (principals) and our “what” (roles). The final piece is the “on which resource.” A role is useless until you bind it to a principal on a specific resource. This connection is called an IAM Policy Binding.

Here is the critical thing to understand: you do not apply the policy to the user. You attach the policy to the resource.

And which resource? This is where GCP’s IAM becomes incredibly powerful — because you can attach a policy at any level of the Resource Hierarchy:

  • Organization — The root node for your entire company. A policy set here flows down to everything. This is where you grant roles like Organization Administrator or Billing Account Administrator.

  • Folders — A way to group projects by department (e.g., “Finance,” “Engineering”). A policy on the “Engineering” folder is inherited by all projects inside it.

  • Projects — The most common level. A policy set on a project applies to every resource inside that project. This is where you would grant your dev team specific roles.

  • Resources — The most granular level. You can set IAM directly on a single Cloud Storage bucket, a single VM, or even a single Pub/Sub topic.

The key rule: permissions always flow downwards. If you are an Editor on a project, you are an Editor on every resource inside that project. This inheritance model lets you set broad policies at the top and more specific ones further down.

Let me show you how to bind a user to a role on a project:

# Grant Alice the Compute Instance Admin role on our project
gcloud projects add-iam-policy-binding my-cool-project \
    --member="user:[email protected]" \
    --role="roles/compute.instanceAdmin"

Output:

Updated IAM policy for project [my-cool-project].
bindings:
- members:
  - user:[email protected]
  role: roles/compute.instanceAdmin

This command adds a binding to the IAM policy of my-cool-project. It says: for this project, [email protected] gets the compute.instanceAdmin role.

Service Accounts — The Robot Identity

Now let us talk about the most important and most misunderstood principal: the Service Account.

Here is the problem. Your application running on a VM needs to read a file from Cloud Storage. How does it authenticate? You could hardcode a developer’s credentials into the code, but that is a security nightmare. What happens when that developer leaves?

The correct way is for the application to authenticate using its own identity — a service account. It is an IAM identity for code, not for people.

How Service Accounts Authenticate

There are two ways to do this, and one is significantly better than the other:

1. Service Account Keys (The Last Resort)

You can generate a JSON key file for a service account. This file contains a private key that your application uses to authenticate. Treat this key like a password. If it leaks, anyone who has it can act as your service account.

You should only use keys when you have absolutely no other choice — like when your application is running on-premises or in another cloud provider.

2. Attached Service Accounts (The Gold Standard)

This is the right way. When you create a Compute Engine VM, a Cloud Function, or an App Engine app, you attach a service account to it. The resource becomes that identity. The code running on that VM can then get authentication tokens from the local metadata server. No keys to manage. Google handles the secure delivery and rotation of credentials behind the scenes.

# Create a VM with a specific service account attached
gcloud compute instances create my-vm \
    --zone=us-central1-a \
    --service-account=my-app-sa@my-cool-project.iam.gserviceaccount.com

Now the application code running on my-vm can call Cloud Storage, BigQuery, whatever — and it authenticates automatically as my-app-sa@my-cool-project. No keys. No secrets. No headaches.

Common Pitfalls and Best Practices

Let me list the mistakes I see most often:

Pitfall: Using basic roles (Owner, Editor, Viewer) in production environments. They are overly permissive.
Best Practice: Use predefined roles. Grant only the permissions necessary for a task. If no predefined role fits, create a custom role.

Pitfall: Granting permissions to individual users (user:bob@...). This becomes a management nightmare when people join or leave teams.
Best Practice: Grant roles to Google Groups (group:web-devs@...). Manage team membership within the group, and the permissions update automatically.

Pitfall: Leaving unused or default service accounts with broad permissions. The default Compute Engine service account has the Editor role, which is way too powerful.
Best Practice: Create dedicated service accounts for each application with the minimal set of roles they need.

Pitfall: Downloading and managing service account keys unless absolutely necessary.
Best Practice: Use attached service accounts on GCE, GKE, Cloud Functions, etc. Avoid keys whenever possible.

Pitfall: Not knowing who did what.
Best Practice: Regularly review Cloud Audit Logs. These logs record all API calls — who did what, on which resource, and when.

Quick Reference Command Center

Here is your gcloud cheatsheet for IAM:

# Get a project's IAM policy
gcloud projects get-iam-policy my-project

# Add a role binding to a project
gcloud projects add-iam-policy-binding my-project \
    --member="user:[email protected]" \
    --role="roles/compute.instanceAdmin"

# Remove a role binding from a project
gcloud projects remove-iam-policy-binding my-project \
    --member="user:[email protected]" \
    --role="roles/compute.instanceAdmin"

# Create a service account
gcloud iam service-accounts create my-app-sa \
    --display-name="My Cool App SA"

# Create a service account key (use only when necessary)
gcloud iam service-accounts keys create key.json \
    [email protected]

# Bind a role to a service account
gcloud projects add-iam-policy-binding my-project \
    --member="serviceAccount:[email protected]" \
    --role="roles/storage.objectViewer"

# Create a custom role from a YAML file
gcloud iam roles create juniorOperator \
    --project=my-project \
    --file=role-definition.yaml

# Create a VM with a specific service account
gcloud compute instances create my-vm \
    [email protected] \
    --scopes=cloud-platform