aeat.adapters.outbound.google._impersonation module

Service-account impersonation credential source for Google API access.

A gestor operating for several represented entities may want one shared Google identity backing the Sheets/Drive export mirror instead of every team member running the interactive per-profile OAuth Desktop consent flow (run_login_flow()). Google’s supported mechanism for this is service-account (SA) impersonation: a locally-discoverable identity — Application Default Credentials (ADC) — is granted IAM roles/iam.serviceAccountTokenCreator on a target SA, and every API call mints a short-lived, scoped access token for that SA rather than presenting the ADC identity’s own token directly.

GoogleImpersonationConfig is the typed, frozen configuration record; resolve_impersonated_credentials() performs the two-step resolution (ADC discovery, then impersonation wrapping) and eagerly validates the grant with one real token refresh so a misconfigured SA fails loudly at resolution time rather than deep inside a later Sheets write.

That eager refresh also covers ADC freshness: Google’s own impersonated- credentials refresh implementation refreshes a stale or invalid SOURCE credential internally before minting the impersonated token (google.auth.impersonated_credentials.Credentials._perform_refresh_token calls source_credentials.refresh(request) whenever source_credentials.token_state is STALE or INVALID), so a merely-stale (but still refreshable) ADC user credential is transparently renewed with no operator action. resolve_impersonated_credentials() additionally distinguishes the two ways that refresh can still fail: a genuinely revoked/expired ADC SOURCE credential (the operator’s local gcloud auth application-default login grant itself is dead) raises GoogleAuthAdcStaleError naming the gcloud re-login remediation, while every other refresh failure (a real IAM Token Creator grant problem on target_principal) raises GoogleAuthImpersonationRefusedError naming the IAM role-grant remediation instead. Per no-silent-under-declaration, a stale token is never silently reused or misreported as an unrelated IAM refusal.

Unlike build_google_credentials() (the existing OAuth-Desktop path), the resolved credential itself persists NOTHING: ADC is discovered fresh from the host environment on every call (GOOGLE_APPLICATION_CREDENTIALS, gcloud user credentials, or an attached workload identity), and the impersonated access token is held only in memory for the process lifetime, never written to secure storage or workflow state (sensitive-financial-data-secure-storage-only: there is no long-lived secret here to protect because none is stored). GoogleCredentialSourceSelection persists only the non-secret CONFIGURATION (which kind a profile has chosen, and the target SA email / scopes) — never a credential.

The CLI verb and locale strings for configuring this source are still deferred (see .vault/adr/2026-07-04-google-sa-impersonation-adr.md, google-sa-impersonation ADR, and GitHub issue #591 remainder); the per-profile persistence and build_google_credentials() dispatch wiring described there are implemented by GoogleCredentialSourceSelection and its session-store persistence functions, consumed by the factory.

See also

GoogleCredentialSourceKind

The closed taxonomy this module implements one member of.

build_google_credentials()

The existing default (interactive OAuth Desktop) credential source this module is an alternative to, never a replacement for; also the dispatch point that reads GoogleCredentialSourceSelection.

REQUIRED_SCOPES

The OAuth-Desktop scope bundle; this module’s default target_scopes excludes the identity scopes (openid, email) that only apply to a human OAuth consent grant.

exception GoogleAuthAdcUnavailableError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: GoogleAuthError

Raised when Application Default Credentials cannot be discovered on this host.

Emitted when google.auth.default() fails to locate any of the ADC discovery sources (GOOGLE_APPLICATION_CREDENTIALS env var, the gcloud auth application-default login user credential file, or an attached GCE/GKE/Cloud Run workload identity).

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
exception GoogleAuthAdcStaleError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: GoogleAuthError

Raised when a discovered ADC source credential can no longer be refreshed.

ADC was discovered (unlike GoogleAuthAdcUnavailableError, where discovery itself fails), but the source credential’s own refresh failed — the common case is a gcloud auth application-default login grant that was revoked or expired since it was issued. This is distinct from GoogleAuthImpersonationRefusedError: here the ADC identity itself is the problem (re-authenticate it), not the IAM grant on target_principal (grant Token Creator).

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
exception GoogleAuthImpersonationRefusedError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: GoogleAuthError

Raised when IAM refuses to mint an impersonated token for the target principal.

The most common cause is that the ADC identity lacks roles/iam.serviceAccountTokenCreator on target_principal (or, for a chained delegates sequence, on the first delegate). The exception context carries target_principal so a caller can render the exact IAM grant the operator needs to add.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
class GoogleImpersonationConfig(**data)[source]

Bases: BaseModel

Typed, frozen configuration for one service-account impersonation grant.

Variables:
  • target_principal – The service-account email being impersonated (e.g. "aeat-export@my-project.iam.gserviceaccount.com").

  • target_scopes – OAuth scopes requested for the minted token. Defaults to the Sheets/Drive data-access scopes (DRIVE_FILE_SCOPE, SHEETS_SCOPE); the identity scopes (openid, email) do not apply to a service account and are intentionally excluded from the default.

  • delegates – Optional chained impersonation sequence. When set, each entry must hold Token Creator on the next, and the ADC identity must hold Token Creator on delegates[0].

  • subject – Optional user email to impersonate via Workspace domain-wide delegation. Only meaningful when the target SA has been granted domain-wide delegation by a Workspace administrator; this module cannot verify that grant and surfaces whatever refusal Google’s token endpoint returns.

  • lifetime_s – Requested token lifetime in seconds, bounded to Google’s 3600s ceiling.

Parameters:
target_principal: str
target_scopes: tuple[str, ...]
delegates: tuple[str, ...]
subject: str | None
lifetime_s: int
class GoogleCredentialSourceSelection(**data)[source]

Bases: BaseModel

Per-profile persisted choice of GoogleCredentialSourceKind.

Persisted via save_credential_source_selection() / load_credential_source_selection() and read by build_google_credentials() to decide whether to hydrate the default per-profile OAuth-Desktop credential or dispatch to resolve_impersonated_credentials().

Carries no long-lived secret: kind = OAUTH_DESKTOP needs no additional field (the existing OAuthClient / OAuthToken records already hold that path’s credential); kind = SERVICE_ACCOUNT_IMPERSONATION requires impersonation to be populated with the target SA email and scopes, which are configuration, not a secret — the actual access token is re-derived from Application Default Credentials on every use and is never written to secure storage.

Parameters:
kind: GoogleCredentialSourceKind
impersonation: GoogleImpersonationConfig | None
describe_impersonation_target(config)[source]

Return the exact service-account email config would impersonate.

Returns config.target_principal verbatim. Exists as a named accessor (rather than reading the field directly at every call site) so a future CLI show/status verb can surface “you are about to grant IAM roles to exactly this identity” without requiring a live token exchange first — satisfying the operator’s need to confirm the SA identity before approving an IAM role grant.

Return type:

str

Parameters:

config (GoogleImpersonationConfig)

resolve_impersonated_credentials(config)[source]

Resolve config into a validated, impersonated Credentials object.

Three-step resolution:

  1. Discover Application Default Credentials via google.auth.default(), scoped to config.target_scopes.

  2. Eagerly refresh the discovered SOURCE credential when it is stale or invalid (_ensure_source_credential_is_fresh()). A merely-stale ADC user credential (past its access-token lifetime but still holding a live refresh token) is silently renewed here — the normal case for a long-running process reusing a gcloud auth application-default login grant. A genuinely dead grant (revoked, expired refresh token) raises GoogleAuthAdcStaleError naming the exact gcloud re-authentication remediation, distinct from an IAM grant problem on the impersonation target.

  3. Wrap the (now-fresh) source credentials in google.auth.impersonated_credentials.Credentials targeting config.target_principal, then eagerly call .refresh() once so a misconfigured grant (missing Token Creator role, wrong scopes, revoked domain-wide delegation) is caught here rather than silently later inside a Sheets/Drive call.

Parameters:

config (GoogleImpersonationConfig) – The impersonation target and scope configuration.

Return type:

Credentials

Returns:

A google.auth.impersonated_credentials.Credentials instance carrying a valid, short-lived access token for target_principal. Nothing from this resolution is persisted by this application.

Raises:
  • GoogleAuthAdcUnavailableError – When ADC discovery finds no usable credential source on this host.

  • GoogleAuthAdcStaleError – When ADC was discovered but its own refresh (renewing an expired access token, or exchanging a long-lived refresh token) fails — the ADC identity itself must be re-authenticated.

  • GoogleAuthImpersonationRefusedError – When IAM refuses to mint a token for target_principal (commonly a missing Token Creator grant, or a subject requiring domain-wide delegation the target SA was never granted).