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

feat: Adds text/json credential_source support to IdentityPoolClients #1059

Merged
merged 18 commits into from
Sep 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
11b0cb7
refactor(samples): idtoken-cloudrun => idtoken-serverless (#1025)
grayside Aug 7, 2020
4df48df
docs: README updated with new sample paths
yoshi-automation Aug 11, 2020
6dfee1d
chore: release 6.0.7 (#1023)
release-please[bot] Aug 12, 2020
b976c8a
build: credential-file-override is no longer required (#1029)
yoshi-automation Aug 12, 2020
4830a53
chore: update cloud rad kokoro build job (#1034)
yoshi-automation Aug 12, 2020
eb54ee9
fix(deps): roll back dependency google-auth-library to ^6.0.6 (#1033)
renovate-bot Aug 12, 2020
714247d
build: perform publish using Node 12 (#1035)
SurferJeffAtGoogle Aug 13, 2020
211e042
chore: release 6.0.8 (#1036)
release-please[bot] Aug 13, 2020
d4e56c0
docs: correct spelling of its (#1040)
xethlyx Aug 18, 2020
6c15139
docs: fix usage in readme (#1031)
banyan Aug 19, 2020
0c8e086
chore: start tracking obsolete files (#1043)
yoshi-automation Aug 19, 2020
5164845
build: move system and samples test from Node 10 to Node 12 (#1045)
yoshi-automation Aug 21, 2020
55b9817
build: track flaky tests for "nightly", add new secrets for tagging (…
yoshi-automation Aug 27, 2020
d835af7
build(test): recursively find test files; fail on unsupported depende…
yoshi-automation Sep 12, 2020
b4d139d
feat: default self-signed JWTs (#1054)
bcoe Sep 22, 2020
73f0b0f
chore: release 6.1.0 (#1057)
release-please[bot] Sep 22, 2020
9c5172a
Merge remote-tracking branch 'upstream/byoid'
bojeil-google Sep 24, 2020
ae57474
feat: Adds text/json credential_source support to IdentityPoolClients
bojeil-google Sep 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 81 additions & 7 deletions src/auth/identitypoolclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ import {RefreshOptions} from './oauth2client';

const readFile = promisify(fs.readFile);

type SubjectTokenFormatType = 'json' | 'text';

interface SubjectTokenJsonResponse {
[key: string]: string;
}

/**
* Url-sourced/file-sourced credentials json interface.
* This is used for K8s and Azure workloads.
Expand All @@ -36,6 +42,10 @@ export interface IdentityPoolClientOptions
headers?: {
[key: string]: string;
};
format?: {
type: SubjectTokenFormatType;
subject_token_field_name?: string;
};
};
}

Expand All @@ -47,6 +57,8 @@ export class IdentityPoolClient extends BaseExternalAccountClient {
private readonly file?: string;
private readonly url?: string;
private readonly headers?: {[key: string]: string};
private readonly formatType: SubjectTokenFormatType;
private readonly formatSubjectTokenFieldName?: string;

/**
* Instantiate an IdentityPoolClient instance using the provided JSON
Expand All @@ -70,6 +82,18 @@ export class IdentityPoolClient extends BaseExternalAccountClient {
if (!this.file && !this.url) {
throw new Error('No valid Identity Pool "credential_source" provided');
}
// Text is the default format type.
this.formatType = options.credential_source.format?.type || 'text';
this.formatSubjectTokenFieldName =
options.credential_source.format?.subject_token_field_name;
if (this.formatType !== 'json' && this.formatType !== 'text') {
throw new Error(`Invalid credential_source format "${this.formatType}"`);
}
if (this.formatType === 'json' && !this.formatSubjectTokenFieldName) {
throw new Error(
'Missing subject_token_field_name for JSON credential_source format'
);
}
}

/**
Expand All @@ -84,19 +108,36 @@ export class IdentityPoolClient extends BaseExternalAccountClient {
*/
async retrieveSubjectToken(): Promise<string> {
if (this.file) {
return await this.getTokenFromFile(this.file!);
return await this.getTokenFromFile(
this.file!,
this.formatType,
this.formatSubjectTokenFieldName
);
} else {
return await this.getTokenFromUrl(this.url!, this.headers);
return await this.getTokenFromUrl(
this.url!,
this.formatType,
this.formatSubjectTokenFieldName,
this.headers
);
}
}

/**
* Looks up the external subject token in the file path provided and
* resolves with that token.
* @param file The file path where the external credential is located.
* @param formatType The token file or URL response type (JSON or text).
* @param formatSubjectTokenFieldName For JSON response types, this is the
* subject_token field name. For Azure, this is access_token. For text
* response types, this is ignored.
* @return A promise that resolves with the external subject token.
*/
private getTokenFromFile(filePath: string): Promise<string> {
private async getTokenFromFile(
filePath: string,
formatType: SubjectTokenFormatType,
formatSubjectTokenFieldName?: string
): Promise<string> {
// Make sure there is a file at the path. lstatSync will throw if there is
// nothing there.
try {
Expand All @@ -112,29 +153,62 @@ export class IdentityPoolClient extends BaseExternalAccountClient {
throw err;
}

return readFile(filePath, {encoding: 'utf8'});
let subjectToken: string | undefined;
const rawText = await readFile(filePath, {encoding: 'utf8'});
if (formatType === 'text') {
subjectToken = rawText;
} else if (formatType === 'json' && formatSubjectTokenFieldName) {
const json = JSON.parse(rawText) as SubjectTokenJsonResponse;
subjectToken = json[formatSubjectTokenFieldName];
}
if (!subjectToken) {
throw new Error(
'Unable to parse the subject_token from the credential_source file'
);
}
return subjectToken;
}

/**
* Sends a GET request to the URL provided and resolves with the returned
* external subject token.
* @param url The URL to call to retrieve the subject token. This is typically
* a local metadata server.
* @param formatType The token file or URL response type (JSON or text).
* @param formatSubjectTokenFieldName For JSON response types, this is the
* subject_token field name. For Azure, this is access_token. For text
* response types, this is ignored.
* @param headers The optional additional headers to send with the request to
* the metadata server url.
* @return A promise that resolves with the external subject token.
*/
private async getTokenFromUrl(
url: string,
formatType: SubjectTokenFormatType,
formatSubjectTokenFieldName?: string,
headers?: {[key: string]: string}
): Promise<string> {
const opts: GaxiosOptions = {
url,
method: 'GET',
headers,
responseType: 'text',
responseType: formatType,
};
const response = await this.transporter.request<string>(opts);
return response.data;
let subjectToken: string | undefined;
if (formatType === 'text') {
const response = await this.transporter.request<string>(opts);
subjectToken = response.data;
} else if (formatType === 'json' && formatSubjectTokenFieldName) {
const response = await this.transporter.request<SubjectTokenJsonResponse>(
opts
);
subjectToken = response.data[formatSubjectTokenFieldName];
}
if (!subjectToken) {
throw new Error(
'Unable to parse the subject_token from the credential_source URL'
);
}
return subjectToken;
}
}
3 changes: 3 additions & 0 deletions test/fixtures/external-subject-token.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"access_token": "HEADER.SIMULATED_JWT_PAYLOAD.SIGNATURE"
}
Loading