Skip to content

Commit

Permalink
Refactor to support access and id tokens (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo authored Sep 18, 2021
1 parent afef6a5 commit cb396c3
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 93 deletions.
62 changes: 52 additions & 10 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,34 @@ on:
- 'main'

jobs:
run:
name: 'test'
unit:
name: 'unit'
runs-on: 'ubuntu-latest'

steps:
- uses: 'actions/checkout@v2'

- uses: 'actions/setup-node@master'
with:
node-version: '12.x'

- name: 'npm install'
run: 'npm install'

- name: 'npm lint'
run: 'npm run lint'

- name: 'npm test'
run: 'npm run test'

access_token:
name: 'access_token'
permissions:
id-token: write
contents: read
runs-on: '${{ matrix.operating-system }}'
strategy:
fail-fast: false
matrix:
operating-system:
- 'ubuntu-latest'
Expand All @@ -28,19 +49,40 @@ jobs:
with:
node-version: '12.x'

- id: 'integration'
- id: 'access-token'
name: 'integration'
uses: './'
with:
token_format: 'access_token'
workload_identity_provider: 'projects/469401941463/locations/global/workloadIdentityPools/github-actions/providers/github-oidc-auth-google-cloud'
service_account: '[email protected]'
id_token_audience: 'foo'

- name: 'npm install'
run: 'npm install'
id_token:
name: 'id_token'
permissions:
id-token: write
contents: read
runs-on: '${{ matrix.operating-system }}'
strategy:
fail-fast: false
matrix:
operating-system:
- 'ubuntu-latest'
- 'windows-latest'
- 'macos-latest'
steps:
- uses: 'actions/checkout@v2'

- name: 'npm lint'
run: 'npm run lint'
- uses: 'actions/setup-node@master'
with:
node-version: '12.x'

- name: 'npm test'
run: 'npm run test'
- id: 'id-token'
name: 'integration'
uses: './'
with:
token_format: 'id_token'
workload_identity_provider: 'projects/469401941463/locations/global/workloadIdentityPools/github-actions/providers/github-oidc-auth-google-cloud'
service_account: '[email protected]'
id_token_audience: 'my-aud'
id_token_include_email: true
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
name: 'Authenticate to Google Cloud'
uses: 'github.com/sethvargo/oidc-auth-google-cloud'
with:
token_format: 'access_token'
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
service_account: '[email protected]'

Expand Down Expand Up @@ -74,23 +75,40 @@ jobs:
`"sigstore"`, but this variable exists in case custom values are permitted
in the future. The default value is `"sigstore"`.

- `token_format`: (Optional) Format of the generated token. For OAuth 2.0
access tokens, specify "access_token". For OIDC tokens, specify "id_token".
The default value is "access_token".

- `delegates`: (Optional) List of additional service account emails or unique
identities to use for impersonation in the chain. By default there are no
delegates.

- `lifetime`: (Optional) Desired lifetime duration of the access token, in
seconds. This must be specified as the number of seconds with a trailing "s"
(e.g. 30s). The default value is 1 hour (3600s).
- `access_token_lifetime`: (Optional) Desired lifetime duration of the access
token, in seconds. This must be specified as the number of seconds with a
trailing "s" (e.g. 30s). The default value is 1 hour (3600s).

- `access_token_scopes`: (Optional) List of OAuth 2.0 access scopes to be
included in the generated token. This is only valid when "token_format" is
"access_token". The default value is:

```text
https://www.googleapis.com/auth/cloud-platform
```

- `id_token_audience`: (Optional) The audience for the generated ID Token.

- `id_token_include_email`: (Optional) Optional parameter of whether to
include the service account email in the generated token. If true, the token
will contain "email" and "email_verified" claims. This is only valid when
"token_format" is "access_token". The default value is false.

## Outputs

- `access_token`: The authenticated Google Cloud access token for calling
other Google Cloud APIs.

- `expiration`: The RFC3339 UTC "Zulu" format timestamp when the token
expires.
- `access_token_expiration`: The RFC3339 UTC "Zulu" format timestamp when the
token expires.

- `id_token`: The authenticated Google Cloud ID token. This token is only
generated when `id_token_audience` input parameter is provided.
Expand Down
47 changes: 37 additions & 10 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
name: 'OIDC Authenticate to Google Cloud'
author: 'sethvargo'
description: |-
Authenticate to Google Cloud from GitHub Actions using an OIDC token and
Workload Identity Federation.
Generate credentials to authenticate to Google Cloud from GitHub Actions using
an OIDC token and Workload Identity Federation.
inputs:
workload_identity_provider:
Expand All @@ -38,35 +38,62 @@ inputs:
exists in case custom values are permitted in the future.
default: 'sigstore'
required: false
token_format:
description: |-
Format for the generated token. For OAuth 2.0 access tokens, specify
"access_token". For OIDC tokens, specify "id_token".
default: 'access_token'
required: true
delegates:
description: |-
List of additional service account emails or unique identities to use for
impersonation in the chain.
default: ''
required: false
lifetime:

# access token params
access_token_lifetime:
description: |-
Desired lifetime duration of the access token, in seconds. This must be
specified as the number of seconds with a trailing "s" (e.g. 30s).
specified as the number of seconds with a trailing "s" (e.g. 30s). This is
only valid when "token_format" is "access_token".
default: '3600s'
required: false
access_token_scopes:
description: |-
List of OAuth 2.0 access scopes to be included in the generated token.
This is only valid when "token_format" is "access_token".
default: 'https://www.googleapis.com/auth/cloud-platform'

# id token params
id_token_audience:
description: |-
The audience for the generated Google Cloud ID Token.
The audience (aud) for the generated Google Cloud ID Token. This is only
valid when "token_format" is "id_token".
default: ''
required: false
id_token_include_email:
description: |-
Optional parameter of whether to include the service account email in the
generated token. If true, the token will contain "email" and
"email_verified" claims. This is only valid when "token_format" is
"access_token".
default: false
required: false

outputs:
access_token:
description: |-
The Google Cloud access token for calling other Google Cloud APIs.
expiration:
The Google Cloud access token for calling other Google Cloud APIs. This
is only available when "token_format" is "access_token".
access_token_expiration:
description: |-
The expiration timestamp for the access token.
The expiration timestamp for the access token. This is only available
when "token_format" is "access_token".
id_token:
description: |-
The Google Cloud ID token. This token is only generated when
`id_token_audience` input parameter was provided.
The Google Cloud ID token. This is only available when "token_format" is
"id_token".
branding:
icon: 'lock'
Expand Down
66 changes: 40 additions & 26 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,37 +225,51 @@ function run() {
});
const serviceAccount = core.getInput('service_account', { required: true });
const audience = core.getInput('audience');
const tokenFormat = core.getInput('token_format', { required: true });
const delegates = explodeStrings(core.getInput('delegates'));
const lifetime = core.getInput('lifetime');
const accessTokenLifetime = core.getInput('access_token_lifetime');
const accessTokenScopes = explodeStrings(core.getInput('access_token_scopes'));
const idTokenAudience = core.getInput('id_token_audience');
const idTokenIncludeEmail = core.getBooleanInput('id_token_include_email');
// Get the GitHub OIDC token.
const githubOIDCToken = yield core.getIDToken(audience);

// Exchange the GitHub OIDC token for a Google Federated Token.
const googleFederatedToken = yield client_1.Client.googleFederatedToken({
providerID: workloadIdentityProvider,
token: githubOIDCToken,
});
core.setSecret(googleFederatedToken);
// Exchange the Google Federated Token for an access token.
const { accessToken, expiration } = yield client_1.Client.googleAccessToken({
token: googleFederatedToken,
serviceAccount: serviceAccount,
delegates: delegates,
lifetime: lifetime,
});
core.setSecret(accessToken);
core.setOutput('access_token', accessToken);
core.setOutput('expiration', expiration);
// Exchange the Google Federated Token for an ID token.
if (idTokenAudience != '') {
const { token } = yield client_1.Client.googleIDToken({
token: googleFederatedToken,
serviceAccount: serviceAccount,
delegates: delegates,
audience: idTokenAudience,
});
core.setSecret(token);
core.setOutput('id_token', token);
switch (tokenFormat) {
case 'access_token': {
// Exchange the Google Federated Token for an access token.
const { accessToken, expiration } = yield client_1.Client.googleAccessToken({
token: googleFederatedToken,
serviceAccount: serviceAccount,
delegates: delegates,
lifetime: accessTokenLifetime,
scopes: accessTokenScopes,
});
core.setSecret(accessToken);
core.setOutput('access_token', accessToken);
core.setOutput('access_token_expiration', expiration);
break;
}
case 'id_token': {
// Exchange the Google Federated Token for an id token.
const { token } = yield client_1.Client.googleIDToken({
token: googleFederatedToken,
serviceAccount: serviceAccount,
delegates: delegates,
audience: idTokenAudience,
includeEmail: idTokenIncludeEmail,
});
core.setSecret(token);
core.setOutput('id_token', token);
break;
}
default: {
throw new Error(`unknown token format "${tokenFormat}"`);
}
}
}
catch (err) {
Expand Down Expand Up @@ -1880,14 +1894,14 @@ class Client {
* googleAccessToken generates a Google Cloud access token for the provided
* service account email or unique id.
*/
static googleAccessToken({ token, serviceAccount, delegates, lifetime, }) {
static googleAccessToken({ token, serviceAccount, delegates, scopes, lifetime, }) {
return __awaiter(this, void 0, void 0, function* () {
const serviceAccountID = `projects/-/serviceAccounts/${serviceAccount}`;
const tokenURL = new url_1.URL(`https://iamcredentials.googleapis.com/v1/${serviceAccountID}:generateAccessToken`);
const data = {
delegates: delegates,
scope: 'https://www.googleapis.com/auth/cloud-platform',
lifetime: lifetime,
scope: scopes,
};
const opts = {
hostname: tokenURL.hostname,
Expand Down Expand Up @@ -1917,14 +1931,14 @@ class Client {
* googleIDToken generates a Google Cloud ID token for the provided
* service account email or unique id.
*/
static googleIDToken({ token, serviceAccount, audience, delegates, }) {
static googleIDToken({ token, serviceAccount, audience, delegates, includeEmail, }) {
return __awaiter(this, void 0, void 0, function* () {
const serviceAccountID = `projects/-/serviceAccounts/${serviceAccount}`;
const tokenURL = new url_1.URL(`https://iamcredentials.googleapis.com/v1/${serviceAccountID}:generateIdToken`);
const data = {
delegates: delegates,
audience: audience,
includeEmail: true,
includeEmail: includeEmail,
};
const opts = {
hostname: tokenURL.hostname,
Expand Down
Loading

0 comments on commit cb396c3

Please sign in to comment.