/
credentials_json_client.ts
129 lines (110 loc) · 3.65 KB
/
credentials_json_client.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
'use strict';
import { createSign } from 'crypto';
import {
isServiceAccountKey,
parseCredential,
ServiceAccountKey,
toBase64,
writeSecureFile,
} from '@google-github-actions/actions-utils';
import { AuthClient } from './auth_client';
/**
* Available options to create the CredentialsJSONClient.
*
* @param projectID User-supplied value for project ID. If not provided, the
* project ID is extracted from the credentials JSON.
* @param credentialsJSON Raw JSON credentials blob.
*/
interface CredentialsJSONClientOptions {
projectID?: string;
credentialsJSON: string;
}
/**
* CredentialsJSONClient is a client that accepts a service account key JSON
* credential.
*/
export class CredentialsJSONClient implements AuthClient {
readonly #projectID: string;
readonly #credentials: ServiceAccountKey;
constructor(opts: CredentialsJSONClientOptions) {
const credentials = parseCredential(opts.credentialsJSON);
if (!isServiceAccountKey(credentials)) {
throw new Error(`Provided credential is not a valid service account key JSON`);
}
this.#credentials = credentials;
this.#projectID = opts.projectID || this.#credentials.project_id;
}
/**
* getAuthToken generates a token capable of calling the iamcredentials API.
*/
async getAuthToken(): Promise<string> {
const header = {
alg: 'RS256',
typ: 'JWT',
kid: this.#credentials.private_key_id,
};
const now = Math.floor(new Date().getTime() / 1000);
const body = {
iss: this.#credentials.client_email,
sub: this.#credentials.client_email,
aud: 'https://iamcredentials.googleapis.com/',
iat: now,
exp: now + 3599,
};
const message = toBase64(JSON.stringify(header)) + '.' + toBase64(JSON.stringify(body));
try {
const signer = createSign('RSA-SHA256');
signer.write(message);
signer.end();
const signature = signer.sign(this.#credentials.private_key);
return message + '.' + toBase64(signature);
} catch (err) {
throw new Error(`Failed to sign auth token using ${await this.getServiceAccount()}: ${err}`);
}
}
/**
* signJWT signs the given JWT with the private key.
*
* @param unsignedJWT The JWT to sign.
*/
async signJWT(unsignedJWT: string): Promise<string> {
const header = {
alg: 'RS256',
typ: 'JWT',
kid: this.#credentials.private_key_id,
};
const message = toBase64(JSON.stringify(header)) + '.' + toBase64(unsignedJWT);
try {
const signer = createSign('RSA-SHA256');
signer.write(message);
signer.end();
const signature = signer.sign(this.#credentials.private_key);
const jwt = message + '.' + toBase64(signature);
return jwt;
} catch (err) {
throw new Error(`Failed to sign JWT using ${await this.getServiceAccount()}: ${err}`);
}
}
/**
* getProjectID returns the project ID. If an override was given, the override
* is returned. Otherwise, this will be the project ID that was extracted from
* the service account key JSON.
*/
async getProjectID(): Promise<string> {
return this.#projectID;
}
/**
* getServiceAccount returns the service account email for the authentication,
* extracted from the Service Account Key JSON.
*/
async getServiceAccount(): Promise<string> {
return this.#credentials.client_email;
}
/**
* createCredentialsFile creates a Google Cloud credentials file that can be
* set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
*/
async createCredentialsFile(outputPath: string): Promise<string> {
return await writeSecureFile(outputPath, JSON.stringify(this.#credentials));
}
}