Skip to main content

google.iam.admin.v1.CreateServiceAccount

Event Information

  • Event purpose & scope

    • google.iam.admin.v1.CreateServiceAccount indicates that a new IAM service account was created in a GCP project (via Console, gcloud, API, or Terraform) using the IAM Admin API.
    • The payload typically includes the service account name, email, display name, and the actor (user/service) that initiated the creation.
  • Security & compliance relevance (e.g., ISO 27001, SOC 2, PCI-DSS)

    • Creation of service accounts introduces new identities that can hold roles and keys; this is a sensitive event for access control and least-privilege enforcement.
    • It should be monitored and logged in Cloud Audit Logs and forwarded to SIEM for anomaly detection, owner validation, and periodic access reviews.
  • Practical controls & monitoring

    • Set up log-based alerts (Cloud Logging) on protoPayload.methodName="google.iam.admin.v1.CreateServiceAccount" to notify security/operations when new service accounts are created.
    • Enforce policies via Organization Policy / IAM conditions and deployment pipelines (e.g., Terraform with code review) to restrict who can create service accounts and ensure naming conventions, owner tagging, and rotation/usage of keys align with compliance requirements.

Examples

  • Over-privileged / Mis-scoped service account for workloads

    • A new service account is created and then granted broad roles like roles/owner, roles/editor, or project-level roles/iam.serviceAccountTokenCreator. This allows lateral movement, privilege escalation, and token abuse if the workload or service account key is compromised.
    • Compliance: Violates least-privilege requirements in ISO 27001, SOC 2, PCI DSS (e.g., PCI DSS 7 – access needs to be restricted to business need-to-know).
  • Shadow / Backdoor service accounts created outside standard process

    • An attacker or insider with IAM admin rights creates a hidden service account (e.g., not registered in CMDB/ITSM) and binds it to critical resources, then uses it for persistence or data exfiltration.
    • Compliance: Breaks access governance and account lifecycle controls (e.g., ISO 27001 A.9, SOC 2 CC6, NIST 800-53 AC family).
  • Service accounts with user-like capabilities

    • Service account is created with roles that allow impersonating users or other service accounts (roles/iam.serviceAccountUser, roles/iam.serviceAccountTokenCreator across the project), enabling bypass of SSO/MFA, audit trails tied to the wrong principal, and impersonation of privileged identities.
    • Compliance: Undermines identity assurance and non-repudiation (e.g., PCI DSS 10, SOX logging/traceability, NIST 800-53 AU/IA families).

Remediation

Using Console

  • Remove broad / mis-scoped roles and re-scope to least privilege

    • In GCP Console: Go to IAM & Admin → IAM → locate the over-privileged service account → click Edit principal (pencil icon) → remove broad roles (Owner, Editor, project-wide Service Account Token Creator, etc.).
    • Create or use minimal custom roles: IAM & Admin → Roles → Create Role → add only the specific permissions needed for the workload → return to IAM and assign this custom role only at the narrowest scope (folder/project/specific resource) required.
    • For compliance (ISO 27001, SOC 2, PCI DSS 7): document the new role design, keep an approval record (e.g., in your ITSM), and enforce change control for any future privilege increases.
  • Detect and remove shadow/backdoor service accounts; enforce lifecycle controls

    • Discover rogue SAs: Go to IAM & Admin → Service Accounts → filter by project and compare against your CMDB/ITSM; look for SAs with no owner record, unusual names, or created by unexpected users (check Service account details → Audit logs).
    • For any unapproved SA: in Service Accounts, click the SA → Delete (or first revoke its bindings via IAM & Admin → IAM by removing all roles that reference that SA). Also remove any keys: Service account → Keys → Delete for each key.
    • Implement governance: restrict who can create SAs and bind roles by going to IAM and removing roles/iam.serviceAccountAdmin, roles/iam.admin, and roles/resourcemanager.projectIamAdmin from general users; keep these only in tightly controlled admin groups, and review SA inventory periodically against compliance requirements (ISO 27001 A.9, SOC 2 CC6, NIST AC).
  • Remove user-like capabilities and impersonation; constrain service account usage

    • In IAM & Admin → IAM, search for principals with roles/iam.serviceAccountUser or roles/iam.serviceAccountTokenCreator at project level; click Edit principal and remove project-wide bindings. Reassign these roles only on specific service accounts where absolutely necessary: open the SA under Service Accounts → Show Info Panel → Permissions → Grant Access, and grant Service Account User only to the minimal group/workload.
    • Disable broad impersonation: avoid granting SAs roles that allow impersonating users (e.g., custom roles with impersonate permissions or roles/iam.serviceAccountTokenCreator on many SAs). Where required, scope to a single SA and enforce usage through workload identity (e.g., GCE/GKE default SA) rather than keys.
    • For compliance (PCI DSS 10, SOX, NIST AU/IA): ensure auditability by confirming in IAM & Admin → Audit Logs that actions are performed by distinct, named principals; document which SAs are allowed to impersonate whom, require approvals, and periodically review these bindings.

Using CLI

  • Constrain over-privileged / mis-scoped service accounts

    • Identify and review risky bindings (e.g., roles/owner, roles/editor, wide serviceAccountTokenCreator) and export for approval:
      gcloud projects get-iam-policy PROJECT_ID --format=json > iam-policy.json
      gcloud iam service-accounts list --project=PROJECT_ID
      gcloud projects get-iam-policy PROJECT_ID \
      --flatten="bindings[].members" \
      --filter="bindings.members:serviceAccount:" \
      --format="table(bindings.role, bindings.members)"
    • Remove broad roles and replace with least-privilege, role-scoped bindings (prefer custom roles and per-service-project scope):
      # Remove project-level owner/editor from SA
      gcloud projects remove-iam-policy-binding PROJECT_ID \
      --member="serviceAccount:SA_NAME@PROJECT_ID.iam.gserviceaccount.com" \
      --role="roles/editor"

      # Grant minimal custom role at narrow resource scope
      gcloud projects add-iam-policy-binding PROJECT_ID \
      --member="serviceAccount:SA_NAME@PROJECT_ID.iam.gserviceaccount.com" \
      --role="projects/PROJECT_ID/roles/CUSTOM_MIN_ROLE"
    • Enforce prevention controls aligned with ISO 27001 / PCI DSS 7 (policy-as-code, org policy, CI checks):
      # Example org policy to disable basic roles at project level
      gcloud org-policies set-policy policy.yaml --organization=ORG_ID
      # policy.yaml: constraint=iam.disableServiceAccountKeyCreation / iam.allowedPolicyMemberDomains etc.
  • Detect and remove shadow / backdoor service accounts

    • Discover unregistered / shadow SAs and bindings by comparing GCP to CMDB lists:
      gcloud iam service-accounts list --project=PROJECT_ID \
      --format="table(email,disabled,oauth2ClientId)"
      gcloud projects get-iam-policy PROJECT_ID \
      --flatten="bindings[].members" \
      --filter="bindings.members:serviceAccount:" \
      --format="table(bindings.role, bindings.members)"
    • Disable or delete backdoor SAs (after impact review) and strip their bindings to meet ISO 27001 A.9 / NIST AC lifecycle controls:
      # Remove all bindings for backdoor SA
      gcloud projects get-iam-policy PROJECT_ID --format=json > policy.json
      # edit policy.json to remove that SA, then:
      gcloud projects set-iam-policy PROJECT_ID policy.json

      # Optionally disable before delete
      gcloud iam service-accounts disable BACKDOOR_SA@PROJECT_ID.iam.gserviceaccount.com
      gcloud iam service-accounts delete BACKDOOR_SA@PROJECT_ID.iam.gserviceaccount.com
    • Hardening and governance (prevent future shadow SAs): require Terraform/Deployment Manager + approval for SA creation and monitor with log-based alerts:
      gcloud logging sinks create sa-creation-sink STORAGE_BUCKET \
      --log-filter='protoPayload.methodName="google.iam.admin.v1.CreateServiceAccount"'
  • Restrict service accounts with user-like capabilities (impersonation / token creation)

    • Enumerate who can impersonate or mint tokens (violates non-repudiation in PCI DSS 10 / SOX if overly broad):
      # List Service Account User / TokenCreator bindings
      gcloud projects get-iam-policy PROJECT_ID \
      --flatten="bindings[].members" \
      --filter='bindings.role:roles/iam.serviceAccountUser OR bindings.role:roles/iam.serviceAccountTokenCreator' \
      --format="table(bindings.role, bindings.members)"
    • Remove project-wide impersonation and re-scope to specific identities and SAs only where needed:
      gcloud projects remove-iam-policy-binding PROJECT_ID \
      --member="user:USER@EXAMPLE.COM" \
      --role="roles/iam.serviceAccountUser"

      # Grant SA User only on specific SA
      gcloud iam service-accounts add-iam-policy-binding SA_NAME@PROJECT_ID.iam.gserviceaccount.com \
      --member="user:USER@EXAMPLE.COM" \
      --role="roles/iam.serviceAccountUser"
    • Enforce controls that keep SA usage non-interactive and traceable: disallow SA keys, require Workload Identity Federation, and alert on high-risk methods:
      # Org policy to disable service account key creation
      gcloud org-policies set-policy disable-sa-keys.yaml --organization=ORG_ID
      # disable-sa-keys.yaml uses constraint: iam.disableServiceAccountKeyCreation

      # Alert on token creation / signBlob/signJwt
      gcloud logging sinks create sa-token-abuse-sink PUBSUB_TOPIC \
      --log-filter='protoPayload.methodName=("google.iam.credentials.v1.IAMCredentials.GenerateAccessToken" OR "google.iam.credentials.v1.IAMCredentials.SignJwt" OR "google.iam.credentials.v1.IAMCredentials.SignBlob")'

Using Python

  • Detect and right-size over-privileged / mis-scoped service accounts

    • Enumerate service accounts and flag broad roles (roles/owner, roles/editor, project-level roles/iam.serviceAccount*); export for review and replace with least-privilege custom or predefined roles.
      from google.cloud import resourcemanager_v3, iam_v1

      project_id = "YOUR_PROJECT_ID"
      broad_roles = {
      "roles/owner",
      "roles/editor",
      "roles/iam.serviceAccountTokenCreator",
      "roles/iam.serviceAccountUser",
      }

      def get_project_number(project_id):
      client = resourcemanager_v3.ProjectsClient()
      proj = client.get_project(name=f"projects/{project_id}")
      return proj.name.split("/")[-1]

      def list_bindings_with_broad_roles(project_id):
      project_number = get_project_number(project_id)
      resource = f"projects/{project_number}"
      client = iam_v1.IAMPolicyClient()
      policy = client.get_iam_policy(resource=resource)
      for b in policy.bindings:
      if b.role in broad_roles:
      print(f"ROLE: {b.role}")
      for m in b.members:
      if m.startswith("serviceAccount:"):
      print(f" SA: {m}")

      if __name__ == "__main__":
      list_bindings_with_broad_roles(project_id)
    • To remediate, adjust the binding: remove broad role from SA and add granular roles on specific resources (e.g., specific GCS buckets, Pub/Sub topics).
      from google.cloud import iam_v1

      project_id = "YOUR_PROJECT_ID"
      sa_email = "sa-name@YOUR_PROJECT_ID.iam.gserviceaccount.com"
      role_to_remove = "roles/editor"
      roles_to_add = ["roles/storage.objectViewer"] # example

      def update_sa_bindings(project_id, sa_email, role_to_remove, roles_to_add):
      resource = f"projects/{project_id}"
      client = iam_v1.IAMPolicyClient()
      policy = client.get_iam_policy(resource=resource)

      member = f"serviceAccount:{sa_email}"
      new_bindings = []
      for b in policy.bindings:
      if b.role == role_to_remove:
      b.members[:] = [m for m in b.members if m != member]
      if not b.members:
      continue
      new_bindings.append(b)
      policy.bindings[:] = new_bindings

      for r in roles_to_add:
      found = False
      for b in policy.bindings:
      if b.role == r:
      if member not in b.members:
      b.members.append(member)
      found = True
      break
      if not found:
      policy.bindings.append(
      iam_v1.Binding(role=r, members=[member])
      )

      client.set_iam_policy(resource=resource, policy=policy)

      if __name__ == "__main__":
      update_sa_bindings(project_id, sa_email, role_to_remove, roles_to_add)
    • Governance: enforce least-privilege via CI/CD checks and Org Policies (constraints/iam.allowedPolicyMemberDomains, constraints/iam.disableServiceAccountKeyCreation), and maintain approvals/records for ISO 27001/SOC 2/PCI DSS evidence.

  • Detect and remove shadow/backdoor service accounts

    • List all service accounts and compare against an “approved” inventory (from CMDB/ITSM) to detect unregistered SAs; immediately review and, if unauthorized, disable and remove bindings.
      from google.cloud import iam_credentials_v1
      from google.cloud import iam_v1

      project_id = "YOUR_PROJECT_ID"
      approved_sas = {
      "approved1@YOUR_PROJECT_ID.iam.gserviceaccount.com",
      "approved2@YOUR_PROJECT_ID.iam.gserviceaccount.com",
      }

      def list_service_accounts(project_id):
      client = iam_v1.IAMClient()
      parent = f"projects/{project_id}"
      return list(client.list_service_accounts(name=parent))

      if __name__ == "__main__":
      sas = list_service_accounts(project_id)
      for sa in sas:
      if sa.email not in approved_sas:
      print(f"UNAPPROVED SA: {sa.email}")
    • For any unapproved SA, revoke IAM bindings and disable/delete account, keeping logs for audit:
      from google.cloud import iam_v1

      project_id = "YOUR_PROJECT_ID"
      sa_email = "suspicious@YOUR_PROJECT_ID.iam.gserviceaccount.com"

      def remove_sa_from_project_bindings(project_id, sa_email):
      resource = f"projects/{project_id}"
      client = iam_v1.IAMPolicyClient()
      policy = client.get_iam_policy(resource=resource)
      member = f"serviceAccount:{sa_email}"

      new_bindings = []
      for b in policy.bindings:
      b.members[:] = [m for m in b.members if m != member]
      if b.members:
      new_bindings.append(b)
      policy.bindings[:] = new_bindings
      client.set_iam_policy(resource=resource, policy=policy)

      def disable_and_delete_sa(project_id, sa_email):
      client = iam_v1.IAMClient()
      name = f"projects/{project_id}/serviceAccounts/{sa_email}"
      client.disable_service_account(name=name)
      # wait / validate, then:
      client.delete_service_account(name=name)

      if __name__ == "__main__":
      remove_sa_from_project_bindings(project_id, sa_email)
      disable_and_delete_sa(project_id, sa_email)
    • Governance: restrict who can create SAs (roles/iam.serviceAccountAdmin) at org/folder level, require change tickets for any SA, and periodically reconcile against CMDB to satisfy ISO 27001 A.9, SOC 2 CC6, NIST AC controls.

  • Limit service accounts with user-like/impersonation capabilities

    • Enumerate bindings for roles/iam.serviceAccountUser and roles/iam.serviceAccountTokenCreator and verify they are only granted to tightly scoped technical identities on specific SAs, never broad “project-wide” or to generic groups.
      from google.cloud import iam_v1

      project_id = "YOUR_PROJECT_ID"
      sensitive_roles = {
      "roles/iam.serviceAccountUser",
      "roles/iam.serviceAccountTokenCreator",
      }

      def list_impersonation_bindings(project_id):
      resource = f"projects/{project_id}"
      client = iam_v1.IAMPolicyClient()
      policy = client.get_iam_policy(resource=resource)
      for b in policy.bindings:
      if b.role in sensitive_roles:
      print(f"SENSITIVE ROLE: {b.role}")
      for m in b.members:
      print(f" MEMBER: {m}")

      if __name__ == "__main__":
      list_impersonation_bindings(project_id)
    • Where over-broad, remove project-level impersonation and re-grant at the individual service account resource path (e.g., projects/{p}/serviceAccounts/{sa}) only to approved identities, ensuring user access still flows through SSO/MFA and that logs clearly attribute actions.
      from google.cloud import iam_v1

      project_id = "YOUR_PROJECT_ID"
      sa_email = "target-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com"
      member_to_allow = "user:admin@example.com"
      role = "roles/iam.serviceAccountUser"

      def set_sa_level_impersonation(project_id, sa_email, member):
      resource = f"projects/{project_id}/serviceAccounts/{sa_email}"
      client = iam_v1.IAMPolicyClient()
      policy = client.get_iam_policy(resource=resource)

      for b in policy.bindings:
      if b.role == role and member in b.members:
      return # already present

      policy.bindings.append(
      iam_v1.Binding(role=role, members=[member])
      )
      client.set_iam_policy(resource=resource, policy=policy)

      if __name__ == "__main__":
      set_sa_level_impersonation(project_id, sa_email, member_to_allow)
    • Governance: enable Cloud Audit Logs for all admin/data access, monitor SA-impersonation events (e.g., GenerateAccessToken, SignJwt), and document mappings of who may impersonate which SA to satisfy PCI DSS 10, SOX, and NIST AU/IA non‑repudiation requirements.