# How to Lock Down Google Cloud in 9 Steps Using Custom IAM Roles (GSP190 Hands-On Guide)

# How to Build Least-Privilege Custom IAM Roles in Google Cloud (Without Breaking Production)

> - *Stop Giving Developers the "Editor" Role: A Practical Guide to Google Cloud Custom IAM Roles*
> - *The Art of the Cloud Scalpel: How to Build, Update, and Recover Custom IAM Roles in Google Cloud*

---

Picture this: It is 4:45 PM on a Friday. A junior developer needs to deploy a new App Engine version. Because nobody wants to spend an hour debugging permission errors before the weekend, an exhausted admin grants them the predefined **Editor** role.

Problem solved in ten seconds. Right?

Wrong. That developer and any script or credential running on their machine can now accidentally wipe out production Cloud Storage buckets, terminate Compute Engine VMs, or alter infrastructure they were never meant to touch.

> *"Broad permissions are security debt disguised as speed. In the cloud, convenience compounds into catastrophe; least privilege compounds into peace of mind."*

In Google Cloud, you never grant permissions directly to users. Instead, you bundle permissions into **roles** and assign those roles to users, groups, or service accounts. That way, you map actual job functions to exact access boundaries.

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/063ba1b2-15cf-4d24-98b1-fc92a6894e2a.png align="center")

---

## Predefined Roles vs. Custom Roles: Sledgehammer vs. Scalpel

Google Cloud IAM gives you two ways to hand out keys to your kingdom:

- **Predefined Roles:** Created and maintained by Google. When Google adds new features or services, these roles update automatically. They are convenient, zero-maintenance, and great for broad job descriptions but sometimes they give away too much power.
- **Custom Roles:** Created and maintained by **you**. You hand-pick the exact permissions required for a specific task and bundle them together. When Google rolls out new permissions or services, your custom roles *do not* update automatically which is a feature, not a bug. Nothing changes unless you say it changes.

### The Anatomy of a Google Cloud Permission

If you want to master Cloud IAM, you only need to memorize one three-part formula. Every permission in Google Cloud is written as:

```text
<service>.<resource>.<verb>

```

Notice the clarity. There is zero guesswork:

- `compute.instances.list` lets a user **list** Compute Engine **instances**.
- `compute.instances.stop` lets a user **stop** a VM.
- `pubsub.topics.publish` lets a caller execute the `topic.publish()` REST method.

In fact, permissions in Google Cloud correspond almost **1:1 with REST API methods**. Every time you run a CLI command or click a button in the console, an API call fires in the background, and IAM checks if your role holds the matching `service.resource.verb` ticket.

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/96305258-a403-420d-8357-010499ab68e1.png align="center")

### Three Rules Every Cloud Architect Must Know Before Building

1. **Where Custom Roles Live:** You can create a custom role at the **Organization level** or the **Project level**. You **cannot** create custom roles at the Folder level.
2. **Scope Boundaries:** Custom roles can only grant permissions inside the specific project or organization that owns them. You cannot take a custom role from Project A and apply it to a resource owned by Project B.
3. **Who Holds the Keys to Create Roles:** To build a custom role, the caller needs the `iam.roles.create` permission. If you aren't a Project Owner, you must hold either the **Organization Role Administrator** role (`roles/iam.organizationRoleAdmin`) or the **IAM Role Administrator** role (`roles/iam.roleAdmin`). Need someone to audit roles without touching them? Assign them the **IAM Security Reviewer** role (`roles/iam.securityReviewer`).

---

## What We Will Build Today (GSP190 Lab Walkthrough)

By the end of this walkthrough, you will know how to:

- Inspect testable permissions and grantable roles on any Google Cloud resource.
- Build custom IAM roles from scratch using both **YAML definitions** (for GitOps/automation) and **CLI flags** (for speed).
- Safely update live custom roles using `etag` concurrency controls so two admins never overwrite each other.
- Disable, delete, and resurrect custom IAM roles within Google Cloud's 37-day lifecycle window.

When you delete a custom IAM role in Google Cloud (and as stated in Task 8 of the GSP190 lab), the deletion clock is split into two distinct phases:

1. **The Undelete (Resurrection) Window:** **7 days**

- For the first **7 days** after deletion, the role is in a soft-deleted state. Existing bindings remain inactive, and you can restore the role using `gcloud iam roles undelete`.

2. **The Permanent Deletion Purge:** **30 days**

- Once the 7-day window closes, the role cannot be restored and enters a **30-day** permanent deletion process.

3. **Total Deletion Lifecycle:**

$$\text{7 days (undelete window)} + \text{30 days (permanent purge)} = \text{37 days total}$$

- Only after **37 days** is the Role ID completely freed up to be used again.

### The Verdict

**37 days is the exact total deletion lifecycle** in Google Cloud ($7 + 30 = 37$) you can only **resurrect (undelete)** the role within the first **7 days**, not all 37.

*"Disable, delete, and restore custom IAM roles inside Google Cloud's 7-day recovery window (and understand the 37-day Role ID cooldown)."*

Let's open the terminal and get our hands dirty.

---

## Step 0: Activate Cloud Shell and Configure Your Environment

Cloud Shell is your built-in Linux jump box in the browsera virtual machine pre-loaded with the `gcloud` CLI and a persistent 5GB home directory.

1. Click **Activate Cloud Shell** at the top of the Google Cloud console.

   ![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/2953042a-63b0-430f-8840-2c73ba53d09b.png align="center")
2. Click through the confirmation prompts:

- Continue through the Cloud Shell information window.

  ![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/7fc4bb4a-af1d-4854-8a97-30f27d5337c9.png align="center")
- Authorize Cloud Shell to use your credentials to make Google Cloud API calls.

  ![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/d2b3612d-b480-4bab-9c5d-435cd5384296.png align="center")

Once connected, you are already authenticated, and your session is locked onto your `Project_ID` (`qwiklabs-gcp-04-535f1ab197d2`):

```text
 Your Cloud Platform project in this session is set to qwiklabs-gcp-04-535f1ab197d2

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/ccd8b6f9-a76b-41e8-801d-1398dac00b66.png align="center")

*Google Cloud Console top bar highlighting the "Activate Cloud Shell" terminal icon and the active terminal banner showing the initialized Project ID.*

### Verify Your Active Account and Project (Optional)

Before changing cloud infrastructure, always verify *who* you are and *where* you are. Run the following command to list the active account:

```bash
gcloud auth list

```

Click **Authorize** if prompted. Your output will confirm your active identity:

```text
ACTIVE: *
ACCOUNT: student-01-9b917c7fafc3@qwiklabs.net
To set the active account, run:
    $ gcloud config set account `ACCOUNT`

```

Next, verify your project ID with this command:

```bash
gcloud config list project

```

**Output:**

```text
[core]
project = qwiklabs-gcp-04-535f1ab197d2

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/c160bc2d-cb7e-4f95-a607-30d161b5d15e.png align="center")

### Set the Default Compute Region

Lock in your default project region (`us-east4`) by running:

```bash
gcloud config set compute/region us-east4

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/67d296d3-0aef-4e37-a82b-846301afe1be.png align="center")

---

## Reconnaissance First: Look Before You Build

> *"Measure twice, cut once. In Cloud Ops, inspect the API permissions twice, write the YAML once."*

Before you invent a new role, you need to answer three questions:

1. What permissions can actually be applied to this resource?
2. What roles are grantable on this resource right now?
3. What is inside an existing role's metadata?

---

## Task 1. View the Available Permissions for a Resource

You don't have to guess permission names or dig through 40 pages of documentation. You can query Google Cloud directly to list every testable permission available for your project and the resources underneath it.

- Run the following command to pull the list of permissions available for your project:

```bash
gcloud iam list-testable-permissions //cloudresourcemanager.googleapis.com/projects/$DEVSHELL_PROJECT_ID

```

**Output:**

```yaml
name: appengine.applications.create
stage: GA
---
name: appengine.applications.get
stage: GA
---
name: appengine.applications.update
stage: GA
---
name: appengine.instances.delete
stage: GA
---
name: appengine.instances.get
stage: GA
---
name: appengine.instances.list
stage: GA
---
customRolesSupportLevel: TESTING
name: appengine.memcache.addKey
stage: BETA
---
customRolesSupportLevel: TESTING
name: appengine.memcache.flush
stage: BETA
---

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/eda3c532-fa16-4f5c-a908-02bf80498f2e.png align="center")

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/17753cac-f8f1-42f7-bab7-868de9d336e1.png align="center")

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/5318344b-20b0-46cb-9f55-a24bc6cfb5e4.png align="center")

`gcloud iam list-testable-permissions` *running in Cloud Shell, highlighting the* `stage: GA` *vs* `customRolesSupportLevel: TESTING` *lines - see how Google flags beta permissions.*

---

## Task 2. Get the Role Metadata

Why reinvent the wheel if a predefined role already does 90% of what you need? By inspecting an existing role's metadata, you can see its exact role ID, launch stage, and bundled permissions.

- Run the following command, replacing `[ROLE_NAME]` with the role you want to inspect (for example, `roles/viewer` or `roles/editor`):

```bash
gcloud iam roles describe [ROLE_NAME]

```

**Example output (for** `roles/viewer`**):**

```yaml
description: Read access to all custom roles in the project.
etag: AA==
includedPermissions:
- iam.roles.get
- iam.roles.list
- resourcemanager.projects.get
- resourcemanager.projects.getIamPolicy
...
...
name: roles/iam.roleViewer
stage: GA
title: Viewer
```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/08f2bf18-8e20-45b4-bf92-802827e6af95.png align="center")

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/576a36a6-3fee-47de-b34b-0eae2302bd82.png align="center")

`gcloud iam roles describe roles/viewer` *output in Cloud Shell, highlighter is* `includedPermissions` *list and the* `etag` *field.*

---

## Task 3. View the Grantable Roles on Resources

Want to see every role that can legally be attached to your project? Use `list-grantable-roles`.

- Run the following command to list all grantable roles for your project:

```bash
gcloud iam list-grantable-roles //cloudresourcemanager.googleapis.com/projects/$DEVSHELL_PROJECT_ID

```

**Your output will look something like this:**

```yaml
---
description: Full management of App Engine apps (but not storage).
name: roles/appengine.appAdmin
title: App Engine Admin
---
description: Ability to view App Engine app status.
name: roles/appengine.appViewer
title: App Engine Viewer
---
description: Ability to view App Engine app status and deployed source code.
name: roles/appengine.codeViewer
title: App Engine Code Viewer
---
...
...

```

---

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/a31ec2b3-ae12-4a83-bfac-1634f8a9d452.png align="center")

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/f2f555fc-64a2-42aa-a0f0-4485b6f0cec4.png align="center")

## Task 4. Create a Custom Role (Two Ways: Declarative YAML vs. Imperative Flags)

> *"Declarative files build repeatable systems. CLI flags solve immediate problems. Master both."*

Remember: To create a custom role, you need the `iam.roles.create` permission (held by Project Owners, `roles/iam.roleAdmin`, or `roles/iam.organizationRoleAdmin`). You also must declare whether your role lives at the organization level (`--organization [ORGANIZATION_ID]`) or project level (`--project [PROJECT_ID]`).

Let's create two project-level custom roles using both methods.

### Method A: Create a Custom Role Using a YAML File

Using a YAML file is the gold standard for Infrastructure-as-Code workflows because you can commit the file directly into Git. Every custom role YAML file follows this structure:

```yaml
title: [ROLE_TITLE]
description: [ROLE_DESCRIPTION]
stage: [LAUNCH_STAGE]
includedPermissions:
- [PERMISSION_1]
- [PERMISSION_2]

```

Here is what each field does:

- `[ROLE_TITLE]`: A human-friendly name for the role (like `Role Viewer`).
- `[ROLE_DESCRIPTION]`: A crisp sentence explaining why this role exists.
- `[LAUNCH_STAGE]`: The lifecycle stage of your role (`ALPHA`, `BETA`, or `GA`).
- `includedPermissions`: The exact list of permissions bundled into this role.

Let's build a custom `editor` role scoped strictly to creating and deleting App Engine versions:

1. Create your role definition YAML file by running:

```bash
nano role-definition.yaml

```

2. Add this custom role definition to the YAML file:

```yaml
title: "Role Editor"
description: "Edit access for App Versions"
stage: "ALPHA"
includedPermissions:
- appengine.versions.create
- appengine.versions.delete

```

3. Save and close the file by pressing **CTRL+X**, **Y**, and then **ENTER**.
4. Run the following command to create the custom role at the project level:

```bash
gcloud iam roles create editor --project $DEVSHELL_PROJECT_ID --file role-definition.yaml

```

If the role was created successfully, the following response is returned:

```yaml
Created role [editor].
description: Edit access for App Versions
etag: BwVs4O4E3e4=
includedPermissions:
- appengine.versions.create
- appengine.versions.delete
name: projects/qwiklabs-gcp-04-535f1ab197d2/roles/editor
stage: ALPHA
title: Role Editor

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/9853ca9f-d0c5-42cc-9494-1df8e67336ce.png align="center")

`Created role [editor]` *terminal confirmation.*

### Method B: Create a Custom Role Using Flags

When you are writing a quick bash script or don't want to manage local files, you can pass the exact same structure inline using CLI flags.

- Run the following command to create a new `viewer` role using flags:

```bash
gcloud iam roles create viewer --project $DEVSHELL_PROJECT_ID \
--title "Role Viewer" --description "Custom role description." \
--permissions compute.instances.get,compute.instances.list --stage ALPHA

```

**Example output:**

```yaml
Created role [viewer].
description: Custom role description.
etag: BwVs4PYHqYI=
includedPermissions:
- compute.instances.get
- compute.instances.list
name: projects/qwiklabs-gcp-04-535f1ab197d2/roles/viewer
stage: ALPHA
title: Role Viewer

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/1f5e9c87-560d-4b86-81de-5ec9672517d1.png align="center")

`gcloud iam roles create viewer` *command execution and its output in Cloud Shell.*

---

## Task 5. List the Custom Roles

Trust, but verify. Now that both custom roles exist, let's query our project to see them listed side-by-side.

1. Run the following command to list your project-level custom roles:

```bash
gcloud iam roles list --project $DEVSHELL_PROJECT_ID

```

**Example output:**

```yaml
---
description: Edit access for App Versions
etag: BwVxLgrnawQ=
name: projects/qwiklabs-gcp-04-535f1ab197d2/roles/editor
title: Role Editor
---
description: Custom role description.
etag: BwVxLg18IQg=
name: projects/qwiklabs-gcp-04-535f1ab197d2/roles/viewer
title: Role Viewer

```

*(Pro Tip: If you ever want to see custom roles that were recently deleted, pass the* `--show-deleted` *flag to this command.)*

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/87e036b6-2fe8-4547-b035-ebc10d00866e.png align="center")

2. Want to list all predefined roles across Google Cloud instead? Simply drop the `--project` flag and run:

```bash
gcloud iam roles list
```

---

## Task 6. Update an Existing Custom Role (And How `etag` Saves Teams from Chaos)

Here is a classic distributed systems problem:

Imagine Admin Alice and Admin Bob both pull down the definition of the `editor` role at 10:00 AM.

- At 10:02 AM, Alice adds Cloud Storage permissions and pushes her update.
- At 10:03 AM, Bob adds Cloud SQL permissions to his 10:00 AM copy and pushes *his* update.

Without protection, Bob's push silently wipes out Alice's work.

Google Cloud IAM prevents this collision using an `etag` property a cryptographic fingerprint of the role's current version. Every time you update a custom role via YAML, Cloud IAM compares the `etag` in your file against the live `etag` on Google's servers. If they match, your write succeeds and a brand-new `etag` is generated. If someone changed the role while you were editing, the `etag` mismatch blocks the overwrite and protects your team.

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/37e81f3a-3049-4a42-937f-bd0815a75abf.png align="center")

Let's update our custom roles using both the YAML method and the CLI flag method.

### Method A: Update a Custom Role Using a YAML File

1. First, fetch the current definition (and current `etag`) for the `editor` role:

```bash
gcloud iam roles describe editor --project $DEVSHELL_PROJECT_ID
```

The `describe` command returns output similar to the following:

```yaml
description: Edit access for App Versions
etag: [ETAG_VALUE]
includedPermissions:
- appengine.versions.create
- appengine.versions.delete
name: projects/qwiklabs-gcp-04-535f1ab197d2/roles/editor
stage: ALPHA
title: Role Editor

```

Copy the output including your unique `etag` string to use in the next step.

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/9ff14552-d548-4b41-91c1-5460015ef648.png align="center")

2. Open a new YAML file named `new-role-definition.yaml` in your editor:

```bash
nano new-role-definition.yaml

```

Paste in the exact output from your `describe` command, and add two new permissions under `includedPermissions`: `storage.buckets.get` and `storage.buckets.list`.

Your YAML file should look like this when you are finished (keep your own `etag` and project ID):

```yaml
description: Edit access for App Versions
etag: BwVxIAbRq_I=
includedPermissions:
- appengine.versions.create
- appengine.versions.delete
- storage.buckets.get
- storage.buckets.list
name: projects/qwiklabs-gcp-04-535f1ab197d2/roles/editor
stage: ALPHA
title: Role Editor

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/ce7b3245-8251-430d-9f01-de2deab61040.png align="center")

3. Save and close the file by pressing **CTRL+X**, **Y**, and then **ENTER**.
4. Apply the update to the `editor` custom role by running:

```bash
gcloud iam roles update editor --project $DEVSHELL_PROJECT_ID \
--file new-role-definition.yaml

```

If the role was updated successfully, notice how the response shows your four permissions and a brand-new `etag`:

```yaml
description: Edit access for App Versions
etag: BwVxIBjfN3M=
includedPermissions:
- appengine.versions.create
- appengine.versions.delete
- storage.buckets.get
- storage.buckets.list
name: projects/qwiklabs-gcp-04-535f1ab197d2/roles/editor
stage: ALPHA
title: Role Editor

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/a7c6fe9a-0621-4f38-a83f-b4c157888719.png align="center")

`gcloud iam roles update editor` *output, highlighting how the* `etag` *value changed before and after the update(in above yaml etag was different).*

### Method B: Update a Custom Role Using Flags

Don't want to copy-paste YAML files just to add a couple of permissions? You can modify permissions directly from the command line using three surgical flags:

- `--add-permissions`: Appends one or more comma-separated permissions to the existing role.
- `--remove-permissions`: Strips one or more comma-separated permissions from the role.
- `--permissions [PERMISSIONS]`: Replaces the entire existing permission list with a brand-new comma-separated list.
- Run the following command to append Cloud Storage read permissions to your `viewer` role using flags:

```bash
gcloud iam roles update viewer --project $DEVSHELL_PROJECT_ID \
--add-permissions storage.buckets.get,storage.buckets.list

```

If the role was updated successfully, the following response is returned:

```yaml
description: Custom role description.
etag: BwVxLi4wTvk=
includedPermissions:
- compute.instances.get
- compute.instances.list
- storage.buckets.get
- storage.buckets.list
name: projects/qwiklabs-gcp-04-535f1ab197d2/roles/viewer
stage: ALPHA
title: Role Viewer

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/b7a4cf75-91cb-4b4c-bb8e-afdfa11763a2.png align="center")

---

## The Kill Switch & The Time Machine: Disabling, Deleting, and Restoring Roles

What happens when a custom role is no longer needed or worse, you suspect it grants too much access, and you want to turn it off without breaking policy bindings permanently?

Google Cloud gives you a staged off-ramp: **Disable -> Delete -> 7-Day Recovery Window -> 30-Day Purge**.

---

## Task 7. Disable a Custom Role

When you set a custom role's stage to `DISABLED`, every IAM policy binding attached to that role immediately goes dormant. Even if a user is assigned the role, none of its permissions are granted. It is the cleanest way to test what breaks before deleting a role.

- Run the following command to disable the `viewer` role:

```bash
gcloud iam roles update viewer --project $DEVSHELL_PROJECT_ID \
--stage DISABLED

```

If the role was updated successfully, the following response is returned:

```yaml
description: Custom role description.
etag: BwVxLkIYHrQ=
includedPermissions:
- compute.instances.get
- compute.instances.list
- storage.buckets.get
- storage.buckets.list
name: projects/qwiklabs-gcp-04-535f1ab197d2/roles/viewer
stage: DISABLED
title: Role Viewer

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/eda4011f-22e4-40ad-bdfc-9e458de8a83e.png align="center")

*terminal output showing the* `viewer` *role marked as* `stage: DISABLED`*.*

---

## Task 8. Delete a Custom Role

Once you confirm nobody needs the role, you can delete it.

- Use the `gcloud iam roles delete` command to delete the `viewer` custom role:

```bash
gcloud iam roles delete viewer --project $DEVSHELL_PROJECT_ID

```

**Example output:**

```yaml
deleted: true
description: Custom role description.
etag: BwVxLkf_epw=
includedPermissions:
- compute.instances.get
- compute.instances.list
- storage.buckets.get
- storage.buckets.list
name: projects/qwiklabs-gcp-04-535f1ab197d2/roles/viewer
stage: DISABLED
title: Role Viewer
```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/fee7790b-3881-4d56-ab21-234b3979d914.png align="center")

### The 37-Day Rule: What Actually Happens When You Delete a Role?

Deleting a custom role in Google Cloud does not vaporize it on the spot. Instead, it triggers a strict **37-day clock**:

1. **Days 1 to 7 (The Grace Period):** Existing role bindings remain in your IAM policy, but they are completely inactive. Anytime within these **7 days**, you can undelete the role and bring it right back to life.
2. **Days 8 to 37 (Permanent Deletion):** After 7 days, the point of no return passes. The role enters a permanent deletion process that lasts **30 days**.
3. **Day 37 Onward (Role ID Freed):** After **37 days** total, the Role ID (`viewer`) is finally released and available to be reused for a new role.

> **Architect's Pro Tip:** Phasing out a role across a large engineering org? Before disabling or deleting it, set `role.stage` to `DEPRECATED` and add a `deprecation_message` telling developers which new role to migrate to.

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/18ced708-b367-44f5-8f9d-29942d6cdf1c.png align="center")

---

## Task 9. Restore a Custom Role

Made a mistake? Or realized a legacy microservice still depended on that custom role? As long as you are inside the 7-day window, one command reverses the deletion.

- Run the following command within the 7-day window to restore the `viewer` custom role:

```bash
gcloud iam roles undelete viewer --project $DEVSHELL_PROJECT_ID

```

![](https://cdn.hashnode.com/uploads/covers/6366a5a0667570bca93510a5/6737b039-3a62-4796-8b6c-4e671e7f64a8.png align="center")

`gcloud iam roles undelete viewer` *returning the restored role definition with* `deleted: true` *removed.*

---

## Final Takeaways

If you remember nothing else from this guide, carry these four rules into your next architecture review:

1. **Permissions are liabilities.** Every unnecessary permission attached to a user or service account increases your blast radius. Use predefined roles to move fast in sandboxes; use custom roles to lock down production.
2. **Think in REST verbs.** Every Cloud IAM permission is simply `<service>.<resource>.<verb>`. Know the API action, and you know the permission.
3. **Never skip the** `etag`**.** When automating custom role updates through YAML, always pull the latest `etag` first so your pipeline never overwrites a teammate's security change.
4. **Disable before you delete.** Setting `--stage DISABLED` gives you an instant rollback switch without starting the 37-day deletion clock.

---

**Open to Cloud Ops Remote and technical writing projects.**
