Skip to content

Commit

Permalink
feat: Add impersonated signer (#1694)
Browse files Browse the repository at this point in the history
* adding fixes

* fix sample

* return signedblobresponse except for gcs clients

Signed-off-by: salrashid123 <salrashid123@gmail.com>

* update test

* fixes

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

---------

Signed-off-by: salrashid123 <salrashid123@gmail.com>
Co-authored-by: Daniel Bankhead <danielbankhead@google.com>
Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 29, 2023
1 parent a735ec5 commit cb78a1b
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/google-auth-librar
| Oauth2-code Verifier | [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/oauth2-codeVerifier.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/oauth2-codeVerifier.js,samples/README.md) |
| Oauth2 | [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/oauth2.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/oauth2.js,samples/README.md) |
| Sign Blob | [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/signBlob.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/signBlob.js,samples/README.md) |
| Sign Blob Impersonated | [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/signBlobImpersonated.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/signBlobImpersonated.js,samples/README.md) |
| Verify Google Id Token | [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/verifyGoogleIdToken.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/verifyGoogleIdToken.js,samples/README.md) |
| Verifying ID Tokens from Identity-Aware Proxy (IAP) | [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/verifyIdToken-iap.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/verifyIdToken-iap.js,samples/README.md) |
| Verify Id Token | [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/verifyIdToken.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/verifyIdToken.js,samples/README.md) |
Expand Down
18 changes: 18 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ This is Google's officially supported [node.js](http://nodejs.org/) client libra
* [Oauth2-code Verifier](#oauth2-code-verifier)
* [Oauth2](#oauth2)
* [Sign Blob](#sign-blob)
* [Sign Blob Impersonated](#sign-blob-impersonated)
* [Verify Google Id Token](#verify-google-id-token)
* [Verifying ID Tokens from Identity-Aware Proxy (IAP)](#verifying-id-tokens-from-identity-aware-proxy-iap)
* [Verify Id Token](#verify-id-token)
Expand Down Expand Up @@ -359,6 +360,23 @@ __Usage:__



### Sign Blob Impersonated

View the [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/signBlobImpersonated.js).

[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/google-auth-library-nodejs&page=editor&open_in_editor=samples/signBlobImpersonated.js,samples/README.md)

__Usage:__


`node samples/signBlobImpersonated.js`


-----




### Verify Google Id Token

View the [source code](https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/verifyGoogleIdToken.js).
Expand Down
73 changes: 73 additions & 0 deletions samples/signBlobImpersonated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2023 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const {GoogleAuth, Impersonated} = require('google-auth-library');

/**
* Use the iamcredentials API to sign a blob of data.
*/
async function main() {
// get source credentials
const auth = new GoogleAuth();
const client = await auth.getClient();

// First impersonate
const scopes = ['https://www.googleapis.com/auth/cloud-platform'];

const targetPrincipal = 'target@project.iam.gserviceaccount.com';
const targetClient = new Impersonated({
sourceClient: client,
targetPrincipal: targetPrincipal,
lifetime: 30,
delegates: [],
targetScopes: [scopes],
});

const signedData = await targetClient.sign('some data');
console.log(signedData.signedBlob);

// or use the client to create a GCS signedURL
// const { Storage } = require('@google-cloud/storage');

// const projectId = 'yourProjectID'
// const bucketName = 'yourBucket'
// const objectName = 'yourObject'

// // use the impersonated client to access gcs
// const storageOptions = {
// projectId,
// authClient: targetClient,
// };

// const storage = new Storage(storageOptions);

// const signOptions = {
// version: 'v4',
// action: 'read',
// expires: Date.now() + 15 * 60 * 1000, // 15 minutes
// };

// const signedURL = await storage
// .bucket(bucketName)
// .file(objectName)
// .getSignedUrl(signOptions);

// console.log(signedURL);
}

main().catch(e => {
console.error(e);
throw e;
});
10 changes: 10 additions & 0 deletions src/auth/googleauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,10 @@ export class GoogleAuth<T extends AuthClient = JSONClient> {
private async getCredentialsAsync(): Promise<CredentialBody> {
const client = await this.getClient();

if (client instanceof Impersonated) {
return {client_email: client.getTargetPrincipal()};
}

if (client instanceof BaseExternalAccountClient) {
const serviceAccountEmail = client.getServiceAccountEmail();
if (serviceAccountEmail) {
Expand Down Expand Up @@ -1059,6 +1063,12 @@ export class GoogleAuth<T extends AuthClient = JSONClient> {
*/
async sign(data: string): Promise<string> {
const client = await this.getClient();

if (client instanceof Impersonated) {
const signed = await client.sign(data);
return signed.signedBlob;
}

const crypto = createCrypto();
if (client instanceof JWT && client.key) {
const sign = await crypto.sign(client.key, data);
Expand Down
29 changes: 29 additions & 0 deletions src/auth/impersonated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import {AuthClient} from './authclient';
import {IdTokenProvider} from './idtokenclient';
import {GaxiosError} from 'gaxios';
import {SignBlobResponse} from './googleauth';

export interface ImpersonatedOptions extends OAuth2ClientOptions {
/**
Expand Down Expand Up @@ -126,6 +127,34 @@ export class Impersonated extends OAuth2Client implements IdTokenProvider {
this.endpoint = options.endpoint ?? 'https://iamcredentials.googleapis.com';
}

/**
* Signs some bytes.
*
* {@link https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob Reference Documentation}
* @param blobToSign String to sign.
* @return <SignBlobResponse> denoting the keyyID and signedBlob in base64 string
*/
async sign(blobToSign: string): Promise<SignBlobResponse> {
await this.sourceClient.getAccessToken();
const name = `projects/-/serviceAccounts/${this.targetPrincipal}`;
const u = `${this.endpoint}/v1/${name}:signBlob`;
const body = {
delegates: this.delegates,
payload: Buffer.from(blobToSign).toString('base64'),
};
const res = await this.sourceClient.request<SignBlobResponse>({
url: u,
data: body,
method: 'POST',
});
return res.data;
}

/** The service account email to be impersonated. */
getTargetPrincipal(): string {
return this.targetPrincipal;
}

/**
* Refreshes the access token.
* @param refreshToken Unused parameter
Expand Down
56 changes: 56 additions & 0 deletions test/test.googleauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1578,6 +1578,62 @@ describe('googleauth', () => {
.post('/token')
.reply(200, {});
}
describe('for impersonated types', () => {
describe('for impersonated credentials signing', () => {
const now = new Date().getTime();
const saSuccessResponse = {
accessToken: 'SA_ACCESS_TOKEN',
expireTime: new Date(now + 3600 * 1000).toISOString(),
};

it('should use IAMCredentials signBlob endpoint when impersonation is used', async () => {
// Set up a mock to return path to a valid credentials file.
mockEnvVar(
'GOOGLE_APPLICATION_CREDENTIALS',
'./test/fixtures/impersonated_application_default_credentials.json'
);

// Set up a mock to explicity return the Project ID, as needed for impersonated ADC
mockEnvVar('GCLOUD_PROJECT', STUB_PROJECT);

const auth = new GoogleAuth();
const client = await auth.getClient();

const email = 'target@project.iam.gserviceaccount.com';
const iamUri = 'https://iamcredentials.googleapis.com';
const iamPath = `/v1/projects/-/serviceAccounts/${email}:signBlob`;
const signedBlob = 'erutangis';
const keyId = '12345';
const data = 'abc123';
const scopes = [
nock('https://oauth2.googleapis.com').post('/token').reply(200, {
access_token: saSuccessResponse.accessToken,
}),
nock(iamUri)
.post(
iamPath,
{
delegates: [],
payload: Buffer.from(data, 'utf-8').toString('base64'),
},
{
reqheaders: {
Authorization: `Bearer ${saSuccessResponse.accessToken}`,
'Content-Type': 'application/json',
},
}
)
.reply(200, {keyId: keyId, signedBlob: signedBlob}),
];

const signed = await auth.sign(data);

scopes.forEach(x => x.done());
assert(client instanceof Impersonated);
assert.strictEqual(signed, signedBlob);
});
});
});

describe('for external_account types', () => {
let fromJsonSpy: sinon.SinonSpy<
Expand Down
44 changes: 44 additions & 0 deletions test/test.impersonated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,4 +455,48 @@ describe('impersonated', () => {

scopes.forEach(s => s.done());
});

it('should sign a blob', async () => {
const expectedKeyID = '12345';
const expectedSignedBlob = 'signed';
const expectedBlobToSign = 'signme';
const expectedDeligates = ['deligate-1', 'deligate-2'];
const email = 'target@project.iam.gserviceaccount.com';

const scopes = [
createGTokenMock({
access_token: 'abc123',
}),
nock('https://iamcredentials.googleapis.com')
.post(
`/v1/projects/-/serviceAccounts/${email}:signBlob`,
(body: {delegates: string[]; payload: string}) => {
assert.strictEqual(
body.payload,
Buffer.from(expectedBlobToSign).toString('base64')
);
assert.deepStrictEqual(body.delegates, expectedDeligates);
return true;
}
)
.reply(200, {
keyId: expectedKeyID,
signedBlob: expectedSignedBlob,
}),
];

const impersonated = new Impersonated({
sourceClient: createSampleJWTClient(),
targetPrincipal: email,
lifetime: 30,
delegates: expectedDeligates,
targetScopes: ['https://www.googleapis.com/auth/cloud-platform'],
});

const resp = await impersonated.sign(expectedBlobToSign);
assert.equal(email, impersonated.getTargetPrincipal());
assert.equal(resp.keyId, expectedKeyID);
assert.equal(resp.signedBlob, expectedSignedBlob);
scopes.forEach(s => s.done());
});
});

0 comments on commit cb78a1b

Please sign in to comment.