Summary
fireway (this fork on the fix-auth-firebase branch — the version AS8 consumes via yarn.lock) hardcodes a service-account name in its impersonation chain and double-impersonates when ADC is already configured for impersonation. The combination makes it work in CI (which uses an OIDC-derived external_account credential) but fail locally (where engineers authenticate via gcloud auth application-default login). We need it to work in both contexts so engineers can run migrations from their laptops.
What the code does today
src/index.js:307-313 (this fork):
const client = await auth.getClient();
targetClient = new Impersonated({
sourceClient: client,
targetPrincipal: `main-service-account@${projectId}.iam.gserviceaccount.com`,
lifetime: 60 * 15,
delegates: [],
targetScopes: ['https://www.googleapis.com/auth/cloud-platform'],
});
Two issues here:
- Hardcoded SA name.
main-service-account@${projectId}.iam.gserviceaccount.com is an Akeneo-AS8 naming convention. Consumers in other projects (or future AS8 envs that don't follow this convention) can't use this fork.
- Forced impersonation regardless of ADC. If ADC is already configured to impersonate the same SA (which is what
gcloud auth application-default login --impersonate-service-account=… produces locally for engineers in the as8-squad group), the chain becomes "main-SA impersonating main-SA", which the IAM Credentials API rejects with INVALID_ARGUMENT: unable to impersonate: Request contains an invalid argument.
What goes wrong locally — three scenarios we hit
Reproduced while building https://github.com/akeneo/as8/pull/6411 (a local-deploy script):
| Local ADC |
Outcome |
Plain user (gcloud auth application-default login) |
auth.getClient() returns user; fireway tries user → main-SA; user is in as8-squad (which has tokenCreator); should work, but… |
Plain user + stale nested @google-cloud/firestore@5.0.2 under node_modules/fireway/node_modules/ |
Firestore@5.0.2 doesn't fully honour authClient: targetClient — calls go out as user → PERMISSION_DENIED / UNAUTHENTICATED. The fix from a fresh install (8.x) works. But it's a footgun: anyone with stale node_modules from before this fork bumped its firestore dep hits this. |
Impersonated (gcloud auth application-default login --impersonate-service-account=main-service-account@...) |
auth.getClient() returns a client whose tokens are already main-SA tokens. fireway's wrapper then asks IAM to "impersonate main-SA using main-SA token" → INVALID_ARGUMENT: unable to impersonate. |
In CI, an external_account credentials file is set up by the OIDC orb. auth.getClient() returns an external_account client whose token-fetch path goes through STS+impersonation transparently, and fireway's wrapper happens to layer cleanly. So CI works, locally we're stuck.
Proposed fixes
A safe, minimal approach:
- Make the impersonation target configurable — read
FIREWAY_IMPERSONATE_SERVICE_ACCOUNT env var (or --impersonate-service-account= CLI flag), default to no impersonation. Keep the current AS8 default behind that env var if you want zero-config for the AS8 use case.
- Skip the explicit
Impersonated wrap when not needed. If FIREWAY_IMPERSONATE_SERVICE_ACCOUNT is unset (or matches the SA already in service_account_impersonation_url of ADC), use auth.getClient() directly — let the underlying ADC handle impersonation. This eliminates the double-impersonation case for engineers using gcloud auth application-default login --impersonate-service-account=….
- Document the auth contract in the README so consumers know whether to set the env var or use ADC's impersonation flag.
A version that's even smaller: just stop wrapping in Impersonated at all. Let consumers configure ADC how they want. Both CI's external_account creds file and engineers' gcloud auth application-default login --impersonate-service-account=… produce ADC that already mints the right token; fireway doesn't need to add another layer.
Side issue: stale nested deps
Even with the above fixed, this fork's node_modules/fireway/node_modules/@google-cloud/firestore can drift to an older version (we saw 5.0.2) for engineers whose node_modules predates a dep bump. v5 doesn't fully honour authClient, breaking auth in subtle ways. Worth either:
- Pinning peerDependencies more tightly so yarn doesn't keep nested copies around, or
- Documenting "if local migrations fail with
UNAUTHENTICATED, run rm -rf node_modules/fireway/node_modules && yarn install".
Repro
In a workspace with stale @google-cloud/firestore@5.0.2 under node_modules/fireway/node_modules/:
gcloud auth application-default login # plain user ADC, no --impersonate flag
fireway --path migrations migrate --projectId=<your-gcp-project>
# → ERROR: 16 UNAUTHENTICATED: Request is missing required authentication credential.
After cleaning the stale nested dep:
rm -rf node_modules/fireway/node_modules/@google-cloud
yarn install --check-files
fireway --path migrations migrate --projectId=<your-gcp-project>
# → succeeds (because root @google-cloud/firestore@8.3.0 honours authClient)
With ADC impersonating the same SA fireway hardcodes:
gcloud auth application-default login --impersonate-service-account=main-service-account@<project>.iam.gserviceaccount.com
fireway --path migrations migrate --projectId=<your-gcp-project>
# → ERROR: INVALID_ARGUMENT: unable to impersonate: Request contains an invalid argument.
Why this matters
The AS8 team is moving sandbox/dev deploys from CircleCI onto engineers' laptops (akeneo/as8#6411 / #6413), partly to stop CI from holding "incomplete" pipelines open behind never-clicked approval gates and partly to recover ~293k CircleCI credits / month wasted on builds that never deploy. Local migrations are part of that flow. While gcp-projects PR akeneo/gcp-projects#2105 grants impersonation to the as8-squad group, fireway's auth chain still doesn't compose cleanly with engineer ADC. Fixing this here unblocks the local path.
Summary
fireway(this fork on thefix-auth-firebasebranch — the version AS8 consumes viayarn.lock) hardcodes a service-account name in its impersonation chain and double-impersonates when ADC is already configured for impersonation. The combination makes it work in CI (which uses an OIDC-derived external_account credential) but fail locally (where engineers authenticate viagcloud auth application-default login). We need it to work in both contexts so engineers can run migrations from their laptops.What the code does today
src/index.js:307-313(this fork):Two issues here:
main-service-account@${projectId}.iam.gserviceaccount.comis an Akeneo-AS8 naming convention. Consumers in other projects (or future AS8 envs that don't follow this convention) can't use this fork.gcloud auth application-default login --impersonate-service-account=…produces locally for engineers in theas8-squadgroup), the chain becomes "main-SA impersonating main-SA", which the IAM Credentials API rejects withINVALID_ARGUMENT: unable to impersonate: Request contains an invalid argument.What goes wrong locally — three scenarios we hit
Reproduced while building https://github.com/akeneo/as8/pull/6411 (a local-deploy script):
gcloud auth application-default login)auth.getClient()returns user; fireway triesuser → main-SA; user is inas8-squad(which hastokenCreator); should work, but…@google-cloud/firestore@5.0.2undernode_modules/fireway/node_modules/Firestore@5.0.2doesn't fully honourauthClient: targetClient— calls go out as user →PERMISSION_DENIED/UNAUTHENTICATED. The fix from a fresh install (8.x) works. But it's a footgun: anyone with stalenode_modulesfrom before this fork bumped its firestore dep hits this.gcloud auth application-default login --impersonate-service-account=main-service-account@...)auth.getClient()returns a client whose tokens are already main-SA tokens. fireway's wrapper then asks IAM to "impersonate main-SA using main-SA token" →INVALID_ARGUMENT: unable to impersonate.In CI, an external_account credentials file is set up by the OIDC orb.
auth.getClient()returns an external_account client whose token-fetch path goes through STS+impersonation transparently, and fireway's wrapper happens to layer cleanly. So CI works, locally we're stuck.Proposed fixes
A safe, minimal approach:
FIREWAY_IMPERSONATE_SERVICE_ACCOUNTenv var (or--impersonate-service-account=CLI flag), default to no impersonation. Keep the current AS8 default behind that env var if you want zero-config for the AS8 use case.Impersonatedwrap when not needed. IfFIREWAY_IMPERSONATE_SERVICE_ACCOUNTis unset (or matches the SA already inservice_account_impersonation_urlof ADC), useauth.getClient()directly — let the underlying ADC handle impersonation. This eliminates the double-impersonation case for engineers usinggcloud auth application-default login --impersonate-service-account=….A version that's even smaller: just stop wrapping in
Impersonatedat all. Let consumers configure ADC how they want. Both CI's external_account creds file and engineers'gcloud auth application-default login --impersonate-service-account=…produce ADC that already mints the right token; fireway doesn't need to add another layer.Side issue: stale nested deps
Even with the above fixed, this fork's
node_modules/fireway/node_modules/@google-cloud/firestorecan drift to an older version (we saw 5.0.2) for engineers whosenode_modulespredates a dep bump. v5 doesn't fully honourauthClient, breaking auth in subtle ways. Worth either:UNAUTHENTICATED, runrm -rf node_modules/fireway/node_modules && yarn install".Repro
In a workspace with stale
@google-cloud/firestore@5.0.2undernode_modules/fireway/node_modules/:After cleaning the stale nested dep:
With ADC impersonating the same SA fireway hardcodes:
Why this matters
The AS8 team is moving sandbox/dev deploys from CircleCI onto engineers' laptops (akeneo/as8#6411 / #6413), partly to stop CI from holding "incomplete" pipelines open behind never-clicked approval gates and partly to recover ~293k CircleCI credits / month wasted on builds that never deploy. Local migrations are part of that flow. While
gcp-projectsPR akeneo/gcp-projects#2105 grants impersonation to theas8-squadgroup, fireway's auth chain still doesn't compose cleanly with engineer ADC. Fixing this here unblocks the local path.