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:
| Concept | Description | Example |
|---|---|---|
| Permissions | Individual operations (verb on a resource type) | compute.instances.delete |
| Roles | Collections of permissions | roles/compute.instanceAdmin.v1 |
| Bindings | Associate a member with a role on a resource | SA agent@proj.iam gets role X on project Y |
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:
# 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:
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
# 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:
# 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)"
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:
# 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"
IAM Conditions: Time-Based and Resource-Based Restrictions
IAM Conditions allow you to further restrict when and where permissions apply:
# 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"
# 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:
{
"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"
]
}
}
]
}
# 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
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:
# 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", ] } } }
Lilly Tech Systems