Support GCP service account impersonation (keyless auth from Workload Identity Federation)
Description
The GCP module resolves credentials with Application Default Credentials and hands the result straight to the Google API clients. That supports two auth methods today: a JSON key file via GOOGLE_APPLICATION_CREDENTIALS, or the GCE/host default service account.
There's no way to run keylessly from a federated identity. If ADC resolves to a Workload Identity Federation source (an external_account credential), the client build fails:
source credential of type external_account is not supported
I'd like to add a third option: impersonation. Cartography uses whatever ADC it resolves (JSON key, GCE default, or a keyless external_account source) as a source identity, and mints short-lived impersonated credentials for a target service account that holds the read roles.
Two flags following the existing --gcp-* convention:
--gcp-impersonate-service-account <email> - target SA to impersonate. When set, the resolved ADC is wrapped with google.auth.impersonated_credentials.Credentials before any client is built. That class accepts an external_account source, which is what makes the keyless case work.
--gcp-impersonate-delegates <sa-a,sa-b> - optional comma-separated delegation chain for multi-hop impersonation, ignored unless the target SA is set.
With neither flag set, nothing changes for existing key/GCE users.
On IAM: the source identity needs roles/iam.serviceAccountTokenCreator on the target SA (and each delegate needs it on the next principal in a chain). The target SA keeps the read roles already documented in the config guide (roles/iam.securityReviewer, roles/resourcemanager.organizationViewer, and so on).
One clarification, since it comes up: this is separate from the existing WIF support, which enumerates GCPWorkloadIdentityPool / GCPWorkloadIdentityProvider resources into the graph. That models WIF; this authenticates using a WIF source identity.
Motivation
Exported JSON service-account keys are long-lived static credentials, and WIF plus impersonation lets you drop them entirely - useful when running Cartography from CI or across a trust boundary. Impersonating a target SA from a federated source is the pattern GCP recommends for this, and it's what other tools in the space do (Prowler, for example, builds the impersonated credential in-SDK rather than passing the raw external_account source to the client).
The change is contained: it sits at the one credential-resolution entrypoint (cartography/intel/gcp/clients.py::get_gcp_credentials), so every downstream client picks it up without per-module changes, and the no-flag path is untouched.
Alternatives Considered
- Exchanging the federated token for an SA access token out-of-band (e.g. via
gcloud) before running Cartography. Pushes token refresh/expiry handling onto the operator and doesn't sit well with long-running syncs.
- Patching client construction to accept the raw
external_account source. The Google clients don't support that by design; impersonation is the supported bridge and gives short-lived, auditable tokens.
- Impersonating automatically when a target is discoverable. Impersonation changes the effective identity and the required IAM, so it should be explicit.
Relevant Links
I have a working branch for this with unit tests and updated docs, and can open a PR. Wanted to check the flag naming and approach here first.
Support GCP service account impersonation (keyless auth from Workload Identity Federation)
Description
The GCP module resolves credentials with Application Default Credentials and hands the result straight to the Google API clients. That supports two auth methods today: a JSON key file via
GOOGLE_APPLICATION_CREDENTIALS, or the GCE/host default service account.There's no way to run keylessly from a federated identity. If ADC resolves to a Workload Identity Federation source (an
external_accountcredential), the client build fails:I'd like to add a third option: impersonation. Cartography uses whatever ADC it resolves (JSON key, GCE default, or a keyless
external_accountsource) as a source identity, and mints short-lived impersonated credentials for a target service account that holds the read roles.Two flags following the existing
--gcp-*convention:--gcp-impersonate-service-account <email>- target SA to impersonate. When set, the resolved ADC is wrapped withgoogle.auth.impersonated_credentials.Credentialsbefore any client is built. That class accepts anexternal_accountsource, which is what makes the keyless case work.--gcp-impersonate-delegates <sa-a,sa-b>- optional comma-separated delegation chain for multi-hop impersonation, ignored unless the target SA is set.With neither flag set, nothing changes for existing key/GCE users.
On IAM: the source identity needs
roles/iam.serviceAccountTokenCreatoron the target SA (and each delegate needs it on the next principal in a chain). The target SA keeps the read roles already documented in the config guide (roles/iam.securityReviewer,roles/resourcemanager.organizationViewer, and so on).One clarification, since it comes up: this is separate from the existing WIF support, which enumerates
GCPWorkloadIdentityPool/GCPWorkloadIdentityProviderresources into the graph. That models WIF; this authenticates using a WIF source identity.Motivation
Exported JSON service-account keys are long-lived static credentials, and WIF plus impersonation lets you drop them entirely - useful when running Cartography from CI or across a trust boundary. Impersonating a target SA from a federated source is the pattern GCP recommends for this, and it's what other tools in the space do (Prowler, for example, builds the impersonated credential in-SDK rather than passing the raw
external_accountsource to the client).The change is contained: it sits at the one credential-resolution entrypoint (
cartography/intel/gcp/clients.py::get_gcp_credentials), so every downstream client picks it up without per-module changes, and the no-flag path is untouched.Alternatives Considered
gcloud) before running Cartography. Pushes token refresh/expiry handling onto the operator and doesn't sit well with long-running syncs.external_accountsource. The Google clients don't support that by design; impersonation is the supported bridge and gives short-lived, auditable tokens.Relevant Links
google.auth.impersonated_credentials- already available in the pinnedgoogle-auth>=2.37.0, so no dependency bumpcartography/intel/gcp/clients.py::get_gcp_credentialsI have a working branch for this with unit tests and updated docs, and can open a PR. Wanted to check the flag naming and approach here first.