Add request_reason for plumbing though user-supplied audit information (#413)
Fixes https://github.com/google-github-actions/auth/issues/412
This commit is contained in:
parent
34baaec3f3
commit
e0122d6a97
11
README.md
11
README.md
@ -269,13 +269,22 @@ regardless of the authentication mechanism.
|
|||||||
https://cloud.google.com. Trusted Partner Cloud and Google Distributed
|
https://cloud.google.com. Trusted Partner Cloud and Google Distributed
|
||||||
Hosted Cloud should set this to their universe address.
|
Hosted Cloud should set this to their universe address.
|
||||||
|
|
||||||
You can also override individual API endpoints by setting the environment variable `GHA_ENDPOINT_OVERRIDE_<endpoint>` where endpoint is the API endpoint to override. This only applies to the `auth` action and does not persist to other steps. For example:
|
You can also override individual API endpoints by setting the environment
|
||||||
|
variable `GHA_ENDPOINT_OVERRIDE_<endpoint>` where endpoint is the API
|
||||||
|
endpoint to override. This only applies to the `auth` action and does not
|
||||||
|
persist to other steps. For example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
env:
|
env:
|
||||||
GHA_ENDPOINT_OVERRIDE_oauth2: 'https://oauth2.myapi.endpoint/v1'
|
GHA_ENDPOINT_OVERRIDE_oauth2: 'https://oauth2.myapi.endpoint/v1'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- `request_reason`: (Optional) An optional Reason Request [System
|
||||||
|
Parameter](https://cloud.google.com/apis/docs/system-parameters) for each
|
||||||
|
API call made by the GitHub Action. This will inject the
|
||||||
|
"X-Goog-Request-Reason" HTTP header, which will provide user-supplied
|
||||||
|
information in Google Cloud audit logs.
|
||||||
|
|
||||||
- `cleanup_credentials`: (Optional) If true, the action will remove any
|
- `cleanup_credentials`: (Optional) If true, the action will remove any
|
||||||
created credentials from the filesystem upon completion. This only applies
|
created credentials from the filesystem upon completion. This only applies
|
||||||
if "create_credentials_file" is true. The default is true.
|
if "create_credentials_file" is true. The default is true.
|
||||||
|
@ -102,6 +102,12 @@ inputs:
|
|||||||
Hosted Cloud should set this to their universe address.
|
Hosted Cloud should set this to their universe address.
|
||||||
required: false
|
required: false
|
||||||
default: 'googleapis.com'
|
default: 'googleapis.com'
|
||||||
|
request_reason:
|
||||||
|
description: |-
|
||||||
|
An optional Reason Request System Parameter for each API call made by the
|
||||||
|
GitHub Action. This will inject the "X-Goog-Request-Reason" HTTP header,
|
||||||
|
which will provide user-supplied information in Google Cloud audit logs.
|
||||||
|
required: false
|
||||||
cleanup_credentials:
|
cleanup_credentials:
|
||||||
description: |-
|
description: |-
|
||||||
If true, the action will remove any created credentials from the
|
If true, the action will remove any created credentials from the
|
||||||
|
@ -45,12 +45,13 @@ export interface AuthClient {
|
|||||||
export interface ClientParameters {
|
export interface ClientParameters {
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
universe: string;
|
universe: string;
|
||||||
child: string;
|
requestReason?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Client {
|
export abstract class Client {
|
||||||
protected readonly _logger: Logger;
|
protected readonly _logger: Logger;
|
||||||
protected readonly _httpClient: HttpClient;
|
protected readonly _httpClient: HttpClient;
|
||||||
|
private readonly _requestReason: string | undefined;
|
||||||
|
|
||||||
protected readonly _endpoints = {
|
protected readonly _endpoints = {
|
||||||
iam: 'https://iam.{universe}/v1',
|
iam: 'https://iam.{universe}/v1',
|
||||||
@ -60,8 +61,8 @@ export class Client {
|
|||||||
www: 'https://www.{universe}',
|
www: 'https://www.{universe}',
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(opts: ClientParameters) {
|
constructor(child: string, opts: ClientParameters) {
|
||||||
this._logger = opts.logger.withNamespace(opts.child);
|
this._logger = opts.logger.withNamespace(child);
|
||||||
|
|
||||||
// Create the http client with our user agent.
|
// Create the http client with our user agent.
|
||||||
this._httpClient = new HttpClient(userAgent, undefined, {
|
this._httpClient = new HttpClient(userAgent, undefined, {
|
||||||
@ -73,6 +74,18 @@ export class Client {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._endpoints = expandUniverseEndpoints(this._endpoints, opts.universe);
|
this._endpoints = expandUniverseEndpoints(this._endpoints, opts.universe);
|
||||||
|
this._requestReason = opts.requestReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _headers returns any added headers to apply to HTTP API calls.
|
||||||
|
*/
|
||||||
|
protected _headers(): Record<string, string> {
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
if (this._requestReason) {
|
||||||
|
headers['X-Goog-Request-Reason'] = this._requestReason;
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export { IAMCredentialsClient, IAMCredentialsClientParameters } from './iamcredentials';
|
export { IAMCredentialsClient, IAMCredentialsClientParameters } from './iamcredentials';
|
||||||
|
@ -16,8 +16,7 @@ import { URLSearchParams } from 'url';
|
|||||||
|
|
||||||
import { errorMessage } from '@google-github-actions/actions-utils';
|
import { errorMessage } from '@google-github-actions/actions-utils';
|
||||||
|
|
||||||
import { Client } from './client';
|
import { Client, ClientParameters } from './client';
|
||||||
import { Logger } from '../logger';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GenerateAccessTokenParameters are the inputs to the generateAccessToken call.
|
* GenerateAccessTokenParameters are the inputs to the generateAccessToken call.
|
||||||
@ -42,10 +41,7 @@ export interface GenerateIDTokenParameters {
|
|||||||
/**
|
/**
|
||||||
* IAMCredentialsClientParameters are the inputs to the IAM client.
|
* IAMCredentialsClientParameters are the inputs to the IAM client.
|
||||||
*/
|
*/
|
||||||
export interface IAMCredentialsClientParameters {
|
export interface IAMCredentialsClientParameters extends ClientParameters {
|
||||||
readonly logger: Logger;
|
|
||||||
readonly universe: string;
|
|
||||||
|
|
||||||
readonly authToken: string;
|
readonly authToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,11 +53,7 @@ export class IAMCredentialsClient extends Client {
|
|||||||
readonly #authToken: string;
|
readonly #authToken: string;
|
||||||
|
|
||||||
constructor(opts: IAMCredentialsClientParameters) {
|
constructor(opts: IAMCredentialsClientParameters) {
|
||||||
super({
|
super('IAMCredentialsClient', opts);
|
||||||
logger: opts.logger,
|
|
||||||
universe: opts.universe,
|
|
||||||
child: `IAMCredentialsClient`,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.#authToken = opts.authToken;
|
this.#authToken = opts.authToken;
|
||||||
}
|
}
|
||||||
@ -80,7 +72,9 @@ export class IAMCredentialsClient extends Client {
|
|||||||
|
|
||||||
const pth = `${this._endpoints.iamcredentials}/projects/-/serviceAccounts/${serviceAccount}:generateAccessToken`;
|
const pth = `${this._endpoints.iamcredentials}/projects/-/serviceAccounts/${serviceAccount}:generateAccessToken`;
|
||||||
|
|
||||||
const headers = { Authorization: `Bearer ${this.#authToken}` };
|
const headers = Object.assign(this._headers(), {
|
||||||
|
Authorization: `Bearer ${this.#authToken}`,
|
||||||
|
});
|
||||||
|
|
||||||
const body: Record<string, string | Array<string>> = {};
|
const body: Record<string, string | Array<string>> = {};
|
||||||
if (delegates && delegates.length > 0) {
|
if (delegates && delegates.length > 0) {
|
||||||
@ -126,10 +120,10 @@ export class IAMCredentialsClient extends Client {
|
|||||||
|
|
||||||
const pth = `${this._endpoints.oauth2}/token`;
|
const pth = `${this._endpoints.oauth2}/token`;
|
||||||
|
|
||||||
const headers = {
|
const headers = Object.assign(this._headers(), {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
};
|
});
|
||||||
|
|
||||||
const body = new URLSearchParams();
|
const body = new URLSearchParams();
|
||||||
body.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
|
body.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
|
||||||
@ -173,7 +167,9 @@ export class IAMCredentialsClient extends Client {
|
|||||||
|
|
||||||
const pth = `${this._endpoints.iamcredentials}/projects/-/serviceAccounts/${serviceAccount}:generateIdToken`;
|
const pth = `${this._endpoints.iamcredentials}/projects/-/serviceAccounts/${serviceAccount}:generateIdToken`;
|
||||||
|
|
||||||
const headers = { Authorization: `Bearer ${this.#authToken}` };
|
const headers = Object.assign(this._headers(), {
|
||||||
|
Authorization: `Bearer ${this.#authToken}`,
|
||||||
|
});
|
||||||
|
|
||||||
const body: Record<string, string | string[] | boolean> = {
|
const body: Record<string, string | string[] | boolean> = {
|
||||||
audience: audience,
|
audience: audience,
|
||||||
|
@ -23,17 +23,13 @@ import {
|
|||||||
writeSecureFile,
|
writeSecureFile,
|
||||||
} from '@google-github-actions/actions-utils';
|
} from '@google-github-actions/actions-utils';
|
||||||
|
|
||||||
import { AuthClient, Client } from './client';
|
import { AuthClient, Client, ClientParameters } from './client';
|
||||||
import { Logger } from '../logger';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServiceAccountKeyClientParameters is used as input to the
|
* ServiceAccountKeyClientParameters is used as input to the
|
||||||
* ServiceAccountKeyClient.
|
* ServiceAccountKeyClient.
|
||||||
*/
|
*/
|
||||||
export interface ServiceAccountKeyClientParameters {
|
export interface ServiceAccountKeyClientParameters extends ClientParameters {
|
||||||
readonly logger: Logger;
|
|
||||||
readonly universe: string;
|
|
||||||
|
|
||||||
readonly serviceAccountKey: string;
|
readonly serviceAccountKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,11 +42,7 @@ export class ServiceAccountKeyClient extends Client implements AuthClient {
|
|||||||
readonly #audience: string;
|
readonly #audience: string;
|
||||||
|
|
||||||
constructor(opts: ServiceAccountKeyClientParameters) {
|
constructor(opts: ServiceAccountKeyClientParameters) {
|
||||||
super({
|
super('ServiceAccountKeyClient', opts);
|
||||||
logger: opts.logger,
|
|
||||||
universe: opts.universe,
|
|
||||||
child: `ServiceAccountKeyClient`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const serviceAccountKey = parseCredential(opts.serviceAccountKey);
|
const serviceAccountKey = parseCredential(opts.serviceAccountKey);
|
||||||
if (!isServiceAccountKey(serviceAccountKey)) {
|
if (!isServiceAccountKey(serviceAccountKey)) {
|
||||||
|
@ -14,17 +14,13 @@
|
|||||||
|
|
||||||
import { errorMessage, writeSecureFile } from '@google-github-actions/actions-utils';
|
import { errorMessage, writeSecureFile } from '@google-github-actions/actions-utils';
|
||||||
|
|
||||||
import { AuthClient, Client } from './client';
|
import { AuthClient, Client, ClientParameters } from './client';
|
||||||
import { Logger } from '../logger';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WorkloadIdentityFederationClientParameters is used as input to the
|
* WorkloadIdentityFederationClientParameters is used as input to the
|
||||||
* WorkloadIdentityFederationClient.
|
* WorkloadIdentityFederationClient.
|
||||||
*/
|
*/
|
||||||
export interface WorkloadIdentityFederationClientParameters {
|
export interface WorkloadIdentityFederationClientParameters extends ClientParameters {
|
||||||
readonly logger: Logger;
|
|
||||||
readonly universe: string;
|
|
||||||
|
|
||||||
readonly githubOIDCToken: string;
|
readonly githubOIDCToken: string;
|
||||||
readonly githubOIDCTokenRequestURL: string;
|
readonly githubOIDCTokenRequestURL: string;
|
||||||
readonly githubOIDCTokenRequestToken: string;
|
readonly githubOIDCTokenRequestToken: string;
|
||||||
@ -51,11 +47,7 @@ export class WorkloadIdentityFederationClient extends Client implements AuthClie
|
|||||||
#cachedAt?: number;
|
#cachedAt?: number;
|
||||||
|
|
||||||
constructor(opts: WorkloadIdentityFederationClientParameters) {
|
constructor(opts: WorkloadIdentityFederationClientParameters) {
|
||||||
super({
|
super('WorkloadIdentityFederationClient', opts);
|
||||||
logger: opts.logger,
|
|
||||||
universe: opts.universe,
|
|
||||||
child: `WorkloadIdentityFederationClient`,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.#githubOIDCToken = opts.githubOIDCToken;
|
this.#githubOIDCToken = opts.githubOIDCToken;
|
||||||
this.#githubOIDCTokenRequestURL = opts.githubOIDCTokenRequestURL;
|
this.#githubOIDCTokenRequestURL = opts.githubOIDCTokenRequestURL;
|
||||||
@ -90,6 +82,8 @@ export class WorkloadIdentityFederationClient extends Client implements AuthClie
|
|||||||
|
|
||||||
const pth = `${this._endpoints.sts}/token`;
|
const pth = `${this._endpoints.sts}/token`;
|
||||||
|
|
||||||
|
const headers = Object.assign(this._headers(), {});
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
audience: this.#audience,
|
audience: this.#audience,
|
||||||
grantType: `urn:ietf:params:oauth:grant-type:token-exchange`,
|
grantType: `urn:ietf:params:oauth:grant-type:token-exchange`,
|
||||||
@ -106,7 +100,7 @@ export class WorkloadIdentityFederationClient extends Client implements AuthClie
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await this._httpClient.postJson<{ access_token: string }>(pth, body);
|
const resp = await this._httpClient.postJson<{ access_token: string }>(pth, body, headers);
|
||||||
const statusCode = resp.statusCode || 500;
|
const statusCode = resp.statusCode || 500;
|
||||||
if (statusCode < 200 || statusCode > 299) {
|
if (statusCode < 200 || statusCode > 299) {
|
||||||
throw new Error(`Failed to call ${pth}: HTTP ${statusCode}: ${resp.result || '[no body]'}`);
|
throw new Error(`Failed to call ${pth}: HTTP ${statusCode}: ${resp.result || '[no body]'}`);
|
||||||
@ -140,9 +134,9 @@ export class WorkloadIdentityFederationClient extends Client implements AuthClie
|
|||||||
|
|
||||||
const pth = `${this._endpoints.iamcredentials}/projects/-/serviceAccounts/${this.#serviceAccount}:signJwt`;
|
const pth = `${this._endpoints.iamcredentials}/projects/-/serviceAccounts/${this.#serviceAccount}:signJwt`;
|
||||||
|
|
||||||
const headers = {
|
const headers = Object.assign(this._headers(), {
|
||||||
Authorization: `Bearer ${await this.getToken()}`,
|
Authorization: `Bearer ${await this.getToken()}`,
|
||||||
};
|
});
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
payload: claims,
|
payload: claims,
|
||||||
|
@ -84,6 +84,7 @@ export async function run(logger: Logger) {
|
|||||||
const tokenFormat = getInput(`token_format`);
|
const tokenFormat = getInput(`token_format`);
|
||||||
const delegates = parseMultilineCSV(getInput(`delegates`));
|
const delegates = parseMultilineCSV(getInput(`delegates`));
|
||||||
const universe = getInput(`universe`);
|
const universe = getInput(`universe`);
|
||||||
|
const requestReason = getInput(`request_reason`);
|
||||||
|
|
||||||
// Ensure exactly one of workload_identity_provider and credentials_json was
|
// Ensure exactly one of workload_identity_provider and credentials_json was
|
||||||
// provided.
|
// provided.
|
||||||
@ -113,6 +114,7 @@ export async function run(logger: Logger) {
|
|||||||
client = new WorkloadIdentityFederationClient({
|
client = new WorkloadIdentityFederationClient({
|
||||||
logger: logger,
|
logger: logger,
|
||||||
universe: universe,
|
universe: universe,
|
||||||
|
requestReason: requestReason,
|
||||||
|
|
||||||
githubOIDCToken: oidcToken,
|
githubOIDCToken: oidcToken,
|
||||||
githubOIDCTokenRequestURL: oidcTokenRequestURL,
|
githubOIDCTokenRequestURL: oidcTokenRequestURL,
|
||||||
@ -126,6 +128,7 @@ export async function run(logger: Logger) {
|
|||||||
client = new ServiceAccountKeyClient({
|
client = new ServiceAccountKeyClient({
|
||||||
logger: logger,
|
logger: logger,
|
||||||
universe: universe,
|
universe: universe,
|
||||||
|
requestReason: requestReason,
|
||||||
|
|
||||||
serviceAccountKey: credentialsJSON,
|
serviceAccountKey: credentialsJSON,
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user