Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fixing Implementation of GoogleAuth.sign() for external account credentials #1397

Merged
merged 3 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 12 additions & 20 deletions src/auth/googleauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -730,8 +730,10 @@ export class GoogleAuth<T extends AuthClient = JSONClient> {
/**
* The callback function handles a credential object that contains the
* client_email and private_key (if exists).
* getCredentials checks for these values from the user JSON at first.
* If it doesn't exist, and the environment is on GCE, it gets the
* getCredentials first checks if the client is using an external account and
* uses the service account email in place of client_email.
* If that doesn't exist, it checks for these values from the user JSON.
* If the user JSON doesn't exist, and the environment is on GCE, it gets the
* client_email from the cloud metadata server.
* @param callback Callback that handles the credential object that contains
* a client_email and optional private key, or the error.
Expand All @@ -752,7 +754,14 @@ export class GoogleAuth<T extends AuthClient = JSONClient> {
}

private async getCredentialsAsync(): Promise<CredentialBody> {
await this.getClient();
const client = await this.getClient();

if (client instanceof BaseExternalAccountClient) {
const serviceAccountEmail = client.getServiceAccountEmail();
if (serviceAccountEmail) {
return {client_email: serviceAccountEmail};
}
}

if (this.jsonContent) {
const credential: CredentialBody = {
Expand Down Expand Up @@ -884,23 +893,6 @@ export class GoogleAuth<T extends AuthClient = JSONClient> {
return sign;
}

// signBlob requires a service account email and the underlying
// access token to have iam.serviceAccounts.signBlob permission
// on the specified resource name.
// The "Service Account Token Creator" role should cover this.
// As a result external account credentials can support this
// operation when service account impersonation is enabled.
if (
client instanceof BaseExternalAccountClient &&
client.getServiceAccountEmail()
) {
return this.signBlob(
crypto,
client.getServiceAccountEmail() as string,
data
);
}

const projectId = await this.getProjectId();
if (!projectId) {
throw new Error('Cannot sign data without a project ID.');
Expand Down
16 changes: 15 additions & 1 deletion test/test.googleauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2239,7 +2239,7 @@ describe('googleauth', () => {

it('should use IAMCredentials endpoint when impersonation is used', async () => {
const scopes = mockGetAccessTokenAndProjectId(
false,
true,
['https://www.googleapis.com/auth/cloud-platform'],
true
);
Expand Down Expand Up @@ -2340,6 +2340,20 @@ describe('googleauth', () => {
assert.deepStrictEqual(res.data, data);
scopes.forEach(s => s.done());
});

describe('getCredentials()', () => {
it('getCredentials() should return the service account email for external accounts', async () => {
// Set up a mock to return path to a valid credentials file.
const email = saEmail;
const configWithImpersonation = createExternalAccountJSON();
configWithImpersonation.service_account_impersonation_url =
getServiceAccountImpersonationUrl();
const auth = new GoogleAuth({credentials: configWithImpersonation});
const body = await auth.getCredentials();
assert.notStrictEqual(null, body);
assert.strictEqual(email, body.client_email);
});
});
});
});

Expand Down