Intermediate

IAM & Service Accounts for AI Agents

IAM is your first line of defense. By giving AI agents only the permissions they need — and explicitly denying destructive permissions — you can prevent accidental resource deletion at the identity level.

GCP IAM Model Overview

GCP IAM uses a role-based access control model with three key concepts:

ConceptDescriptionExample
PermissionsIndividual operations (verb on a resource type)compute.instances.delete
RolesCollections of permissionsroles/compute.instanceAdmin.v1
BindingsAssociate a member with a role on a resourceSA agent@proj.iam gets role X on project Y
Never use primitive roles for agents: The roles/editor and roles/owner roles grant broad destructive permissions including delete, destroy, and update operations across all services. Always use predefined or custom roles for AI agent service accounts.

Predefined Roles vs Custom Roles

GCP offers predefined roles for each service, but many include *.delete permissions. For AI agents, custom roles are strongly recommended:

gcloud - List permissions in a predefined role
# See what permissions roles/compute.instanceAdmin.v1 includes
gcloud iam roles describe roles/compute.instanceAdmin.v1 \
  --format="table(includedPermissions)"

# Filter for destructive permissions
gcloud iam roles describe roles/compute.instanceAdmin.v1 \
  --format="value(includedPermissions)" | grep -i "delete"

# Output shows dangerous permissions:
# compute.instances.delete
# compute.disks.delete
# compute.snapshots.delete

Creating Custom Roles That Exclude Delete Permissions

Create a custom role that includes only the permissions your agent needs, explicitly omitting all *.delete operations:

YAML - Custom Role Definition (agent-compute-readonly.yaml)
title: "AI Agent Compute Read-Write (No Delete)"
description: "Allows AI agents to create and manage Compute Engine instances without delete permissions"
stage: "GA"
includedPermissions:
  - compute.instances.get
  - compute.instances.list
  - compute.instances.create
  - compute.instances.setMetadata
  - compute.instances.setLabels
  - compute.instances.setTags
  - compute.instances.start
  - compute.instances.stop
  - compute.instances.reset
  - compute.disks.get
  - compute.disks.list
  - compute.disks.create
  - compute.disks.setLabels
  - compute.zones.get
  - compute.zones.list
  - compute.networks.get
  - compute.subnetworks.get
  - compute.subnetworks.use
  # NOTE: No *.delete permissions included
gcloud - Create and assign the custom role
# Create the custom role at the organization level
gcloud iam roles create agentComputeNoDelete \
  --organization=123456789 \
  --file=agent-compute-readonly.yaml

# Or create at the project level
gcloud iam roles create agentComputeNoDelete \
  --project=my-project \
  --file=agent-compute-readonly.yaml

# Assign to a service account
gcloud projects add-iam-policy-binding my-project \
  --member="serviceAccount:ai-agent@my-project.iam.gserviceaccount.com" \
  --role="organizations/123456789/roles/agentComputeNoDelete"

Service Accounts with Minimal Permissions

Every AI agent should run under a dedicated service account with the absolute minimum permissions required:

gcloud - Create a dedicated agent service account
# Create a service account specifically for the AI agent
gcloud iam service-accounts create ai-agent-worker \
  --display-name="AI Agent Worker (No Delete)" \
  --description="Service account for AI agents with read/write but no delete permissions" \
  --project=my-project

# Grant only the custom role (no predefined roles)
gcloud projects add-iam-policy-binding my-project \
  --member="serviceAccount:ai-agent-worker@my-project.iam.gserviceaccount.com" \
  --role="projects/my-project/roles/agentComputeNoDelete"

# Verify the service account's effective permissions
gcloud projects get-iam-policy my-project \
  --flatten="bindings[].members" \
  --filter="bindings.members:ai-agent-worker@my-project.iam.gserviceaccount.com" \
  --format="table(bindings.role)"
One agent, one service account: Never share service accounts between different agents or between agents and human users. Each agent should have its own identity for audit trail clarity and permission isolation.

Workload Identity Federation for External Agents

If your AI agent runs outside GCP (for example, in GitHub Actions, a local machine, or another cloud), use Workload Identity Federation instead of service account keys:

gcloud - Set up Workload Identity Federation
# Create a Workload Identity Pool
gcloud iam workload-identity-pools create ai-agent-pool \
  --location="global" \
  --display-name="AI Agent Pool" \
  --description="Pool for external AI agent identities"

# Create a provider (example: GitHub Actions OIDC)
gcloud iam workload-identity-pools providers create-oidc github-provider \
  --location="global" \
  --workload-identity-pool="ai-agent-pool" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
  --attribute-condition="assertion.repository=='my-org/my-agent-repo'"

# Allow the external identity to impersonate the service account
gcloud iam service-accounts add-iam-policy-binding \
  ai-agent-worker@my-project.iam.gserviceaccount.com \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/PROJECT_NUM/locations/global/workloadIdentityPools/ai-agent-pool/attribute.repository/my-org/my-agent-repo"
💡
No service account keys: Workload Identity Federation eliminates the need for long-lived service account key files. Keys can be leaked, stolen, or accidentally committed to source control. WIF uses short-lived tokens automatically.

IAM Conditions: Time-Based and Resource-Based Restrictions

IAM Conditions allow you to further restrict when and where permissions apply:

gcloud - IAM Condition: Only during business hours
# Grant role only during business hours (UTC)
gcloud projects add-iam-policy-binding my-project \
  --member="serviceAccount:ai-agent-worker@my-project.iam.gserviceaccount.com" \
  --role="projects/my-project/roles/agentComputeNoDelete" \
  --condition="expression=request.time.getHours('America/New_York') >= 9 && request.time.getHours('America/New_York') <= 17,title=business-hours-only,description=Agent can only operate during business hours"
gcloud - IAM Condition: Only on resources with specific tags
# Grant role only on resources tagged as "agent-managed"
gcloud projects add-iam-policy-binding my-project \
  --member="serviceAccount:ai-agent-worker@my-project.iam.gserviceaccount.com" \
  --role="projects/my-project/roles/agentComputeNoDelete" \
  --condition="expression=resource.matchTag('123456789/environment', 'agent-managed'),title=agent-managed-only,description=Agent can only access resources tagged agent-managed"

IAM Deny Policies

IAM Deny Policies are a newer GCP feature that acts as a hard block on specific permissions, regardless of any allow policies. This is the strongest IAM-level guardrail:

JSON - IAM Deny Policy (deny-agent-delete.json)
{
  "displayName": "Deny all delete operations for AI agents",
  "rules": [
    {
      "denyRule": {
        "deniedPrincipals": [
          "serviceAccount:ai-agent-worker@my-project.iam.gserviceaccount.com"
        ],
        "deniedPermissions": [
          "compute.googleapis.com/instances.delete",
          "compute.googleapis.com/disks.delete",
          "sqladmin.googleapis.com/instances.delete",
          "storage.googleapis.com/buckets.delete",
          "storage.googleapis.com/objects.delete",
          "container.googleapis.com/clusters.delete",
          "bigquery.googleapis.com/datasets.delete",
          "bigquery.googleapis.com/tables.delete",
          "cloudresourcemanager.googleapis.com/projects.delete"
        ]
      }
    }
  ]
}
gcloud - Apply the Deny Policy
# Create the deny policy at the project level
gcloud iam policies create deny-agent-delete \
  --attachment-point="cloudresourcemanager.googleapis.com/projects/my-project" \
  --kind=denypolicies \
  --policy-file=deny-agent-delete.json

# Or apply at the organization level for all projects
gcloud iam policies create deny-agent-delete \
  --attachment-point="cloudresourcemanager.googleapis.com/organizations/123456789" \
  --kind=denypolicies \
  --policy-file=deny-agent-delete.json

# List existing deny policies
gcloud iam policies list \
  --attachment-point="cloudresourcemanager.googleapis.com/projects/my-project" \
  --kind=denypolicies
Deny always wins: IAM Deny Policies take precedence over all allow policies. Even if a service account has roles/owner, a deny policy will block the specified permissions. This makes deny policies the most reliable IAM guardrail.

Terraform Configuration

Here is a complete Terraform example that sets up a secure service account with custom roles and deny policies:

Terraform - Complete agent IAM setup
# Service account for the AI agent
resource "google_service_account" "ai_agent" {
  account_id   = "ai-agent-worker"
  display_name = "AI Agent Worker (No Delete)"
  description  = "Service account for AI agents with restricted permissions"
  project      = var.project_id
}

# Custom role excluding all delete permissions
resource "google_project_iam_custom_role" "agent_compute_no_delete" {
  role_id     = "agentComputeNoDelete"
  title       = "AI Agent Compute (No Delete)"
  description = "Compute read/write without delete"
  project     = var.project_id
  permissions = [
    "compute.instances.get",
    "compute.instances.list",
    "compute.instances.create",
    "compute.instances.setMetadata",
    "compute.instances.setLabels",
    "compute.instances.start",
    "compute.instances.stop",
    "compute.zones.get",
    "compute.zones.list",
    "compute.networks.get",
    "compute.subnetworks.get",
    "compute.subnetworks.use",
  ]
}

# Bind custom role to the service account
resource "google_project_iam_member" "agent_binding" {
  project = var.project_id
  role    = google_project_iam_custom_role.agent_compute_no_delete.id
  member  = "serviceAccount:${google_service_account.ai_agent.email}"
}

# IAM Deny Policy - block all delete operations
resource "google_iam_deny_policy" "deny_agent_delete" {
  parent       = "cloudresourcemanager.googleapis.com/projects/${var.project_id}"
  name         = "deny-agent-delete"
  display_name = "Deny AI Agent Delete Operations"

  rules {
    deny_rule {
      denied_principals  = ["serviceAccount:${google_service_account.ai_agent.email}"]
      denied_permissions = [
        "compute.googleapis.com/instances.delete",
        "compute.googleapis.com/disks.delete",
        "sqladmin.googleapis.com/instances.delete",
        "storage.googleapis.com/buckets.delete",
        "storage.googleapis.com/objects.delete",
        "container.googleapis.com/clusters.delete",
        "bigquery.googleapis.com/datasets.delete",
        "cloudresourcemanager.googleapis.com/projects.delete",
      ]
    }
  }
}