GOOGLE CLOUD IAM → STACKIT

GCP IAM and service account migration to STACKIT

A Google service account becomes a STACKIT service account, and that is the easy part. The role bindings, custom roles, keys and GKE workload identity around it are where the work sits: StackToIT writes the account and puts each of the others on a review list for a person, because Google roles and STACKIT roles do not line up one to one.

A service account, its key and its role, assessed

A small identity estate written for this page and assessed by StackToIT on 4 November 2026 (eight resources). Left: the CI deployer account, its key and its project binding. Right: the only block of that group the engine writes as Terraform; the review comments above it in the file are left out here. The input blocks are not adjacent in main.tf, so each has its own pane, verbatim.

Input: google_service_account.deployermain.tf
resource "google_service_account" "deployer" {
  account_id   = "ci-deployer"
  display_name = "CI deployer"
}
Input: google_service_account_key.deployermain.tf
resource "google_service_account_key" "deployer" {
  service_account_id = google_service_account.deployer.name
}
Input: google_project_iam_member.deployer_editormain.tf
resource "google_project_iam_member" "deployer_editor" {
  project = "shop-platform-prod"
  role    = "roles/editor"
  member  = "serviceAccount:${google_service_account.deployer.email}"
}
STACKIT Terraform by StackToITterraform/identity.tf
resource "stackit_service_account" "deployer" {
  project_id = var.project_id
  name       = "ci-deployer"
}
  • The name is kept, the address is not

    The account name comes from account_id. The e-mail address changes, because STACKIT generates it, so every reference to the old iam.gserviceaccount.com address needs an update. Google's address format is name, project id and iam.gserviceaccount.com; look for it in application configuration, bucket policies and CI variables.

  • The display name has no home

    stackit_service_account takes a name and a project id and nothing else, so the display name is listed as not carried. Keep the description in your own inventory.

  • Eight resources, two units

    Only the two service accounts are units. The key is attached to the deployer unit as a facet. The three project bindings, the custom role and the workload identity binding are recorded as support resources that no unit reads, and the draft writes nothing for them.

What the run leaves to a person

From the same run: an excerpt of the deployer unit in assessment.json, with the values exactly as the run wrote them. Two critical settings stay open: the role bindings and the long-lived key.

StackToIT assessmentassessment.json
{
  "id": "google_service_account.deployer",
  "facets": [
    "google_service_account_key.deployer"
  ],
  "result": {
    "class": "RECONFIGURE",
    "confidence": "MEDIUM",
    "target": {
      "resources": [
        "stackit_service_account",
        "stackit_authorization_project_role_assignment"
      ]
    },
    "findings": [
      {
        "severity": "info",
        "message": "Long-lived JSON keys are issued; they become STACKIT service account keys — or disappear where workload identity replaces them."
      }
    ],
    "semantics": {
      "critical_open": [
        "long-lived-keys",
        "role-bindings"
      ]
    }
  }
}
  • Role bindings: a person re-maps them

    The target of each account lists stackit_authorization_project_role_assignment, which the provider marks as part of an opt-in iam feature that may still change. The run does not translate roles/editor, roles/viewer or the custom role into STACKIT roles; it opens a review record per account.

  • Keys: decide whether they survive

    A long-lived JSON key is flagged. STACKIT service accounts can have keys too, and STACKIT's own page says to treat one like a password for the application; Google itself advises against keys wherever a more secure alternative exists. Decide per caller whether it still needs one.

  • The binding targets are read as unknown

    In this run the engine reads a member written as serviceAccount: plus the account's generated address as an unknown value, and so it cannot show who a binding names. Check the binding list by hand against your real policy export.

How the Google constructs map

Google Cloud constructIn the STACKIT runWhat you do
Service accountstackit_service_account, class RECONFIGURECreate it; update every reference to its e-mail address
Project IAM binding (member)Support resource, no draft blockRe-map the role to a STACKIT role and assign it to the account in the project
Basic role (Owner, Editor, Viewer)Support resource, role left to reviewReplace with the narrowest STACKIT role that covers the work
Custom roleSupport resource, permissions listed for reviewRebuild as the combination of STACKIT roles that fits
Service account keyFacet of the account, long-lived-keys under reviewReplace with workload identity federation, or issue a STACKIT key
GKE workload identity bindingSupport resource, no draft blockFederate the cluster's OIDC issuer, or issue explicit credentials
Group as memberAppears in the graph as a plain stringRebuild the group or its members in your STACKIT identity setup

Rows two to seven come from the support list in terraform/README.md and engineering/review.csv. Google's own guidance is to avoid basic roles in production and that a custom role is limited to the project or organisation it was created in.

The migration in order

  1. Inventory principals and grants

    Export every service account, key, binding and custom role of the projects you migrate. The Terraform in the estate shows what you manage as code; bindings made in the console or by other tools are not in it, and the run cannot see them.

  2. Classify each account

    Split accounts into application runtime identities, CI identities and Google-managed service agents. The last group usually has no STACKIT counterpart to create; confirm that per account.

  3. Create the accounts

    Apply the stackit_service_account blocks. STACKIT service accounts have no access by default; permissions come from roles assigned in the project, so nothing works until step four.

  4. Assign roles deliberately

    For each binding, pick the STACKIT role with the least privilege that does the job. The provider resource takes resource_id, role and a lowercase subject; available roles are read from the authorization API.

  5. Replace keys with federation

    A STACKIT service account can trust an external OIDC issuer: the issuer URL must be HTTPS without a trailing slash, an aud assertion is required, and only equality assertions are supported. The Terraform provider itself can authenticate this way in CI, which removes the need for a stored key.

  6. Retarget workloads and clean up

    Point applications at the new credentials, watch for denied calls, and delete the Google accounts and keys only after a quiet period. Deleting a STACKIT service account is permanent, so keep an export.

Decisions the architect owns

  • The role model

    Whether to mirror Google's grants or to restart from the STACKIT roles. Mirroring carries over old over-grants such as roles/editor.

  • Where workloads authenticate from

    Pods that used Google's cluster identity (the binding names a Kubernetes service account in a project pool) have no equivalent on STACKIT until you federate their issuer or give them a key.

  • Who may create keys

    A key is a password for the account. Decide who may issue one and where it is stored before the first one exists.

  • Project boundaries

    The run places every resource in the one project given by var.project_id, and says that a source spread over several projects is collapsed into it. Decide how many projects you want, because role assignments are made per project.

Questions security teams ask

Does StackToIT translate Google roles into STACKIT roles?

No. It maps the service account and leaves the role bindings to a person, because the permissions of a Google role and a STACKIT role are not the same set. The run records an open review item for it.

What happens to GKE Workload Identity bindings?

They are listed as support resources and nothing is drafted. Google's model grants a role to a Kubernetes identity through a project pool; on STACKIT you federate the cluster's OIDC issuer with a service account or issue explicit credentials.

Are service account keys carried over?

No. The run flags them for a decision. Where an application can use federation instead, that avoids a stored secret altogether; where it cannot, STACKIT can issue a key.

Is the role assignment resource stable?

The provider documents stackit_authorization_project_role_assignment as part of an opt-in feature it says may change significantly. Plan for that when you put it into a pipeline.

Sources

Your next move: STACKIT.

Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.