1
0
Fork 0

Merge pull request #887 from souravchanduka/main-oidc-client

Added OIDC client functionality in core package
pull/894/head
Tingluo Huang 2021-08-25 13:14:28 -07:00 committed by GitHub
commit 3da67ac4cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 223 additions and 3 deletions

View File

@ -257,3 +257,51 @@ var pid = core.getState("pidToKill");
process.kill(pid); process.kill(pid);
``` ```
#### OIDC Token
You can use these methods to interact with the GitHub OIDC provider and get a JWT ID token which would help to get access token from third party cloud providers.
**Method Name**: getIDToken()
**Inputs**
audience : optional
**Outputs**
A [JWT](https://jwt.io/) ID Token
In action's `main.ts`:
```js
const core = require('@actions/core');
async function getIDTokenAction(): Promise<void> {
const audience = core.getInput('audience', {required: false})
const id_token1 = await core.getIDToken() // ID Token with default audience
const id_token2 = await core.getIDToken(audience) // ID token with custom audience
// this id_token can be used to get access token from third party cloud providers
}
getIDTokenAction()
```
In action's `actions.yml`:
```yaml
name: 'GetIDToken'
description: 'Get ID token from Github OIDC provider'
inputs:
audience:
description: 'Audience for which the ID token is intended for'
required: false
outputs:
id_token1:
description: 'ID token obtained from OIDC provider'
id_token2:
description: 'ID token obtained from OIDC provider'
runs:
using: 'node12'
main: 'dist/index.js'
```

View File

@ -1,5 +1,8 @@
# @actions/core Releases # @actions/core Releases
### 1.6.0
- [Added OIDC Client function `getIDToken`](https://github.com/actions/toolkit/pull/887)
### 1.5.0 ### 1.5.0
- [Added support for notice annotations and more annotation fields](https://github.com/actions/toolkit/pull/855) - [Added support for notice annotations and more annotation fields](https://github.com/actions/toolkit/pull/855)

View File

@ -2,6 +2,7 @@ import * as fs from 'fs'
import * as os from 'os' import * as os from 'os'
import * as path from 'path' import * as path from 'path'
import * as core from '../src/core' import * as core from '../src/core'
import {HttpClient} from '@actions/http-client'
import {toCommandProperties} from '../src/utils' import {toCommandProperties} from '../src/utils'
/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/unbound-method */
@ -434,3 +435,20 @@ function verifyFileCommand(command: string, expectedContents: string): void {
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
} }
} }
function getTokenEndPoint(): string {
return 'https://vstoken.actions.githubusercontent.com/.well-known/openid-configuration'
}
describe('oidc-client-tests', () => {
it('Get Http Client', async () => {
const http = new HttpClient('actions/oidc-client')
expect(http).toBeDefined()
})
it('HTTP get request to get token endpoint', async () => {
const http = new HttpClient('actions/oidc-client')
const res = await http.get(getTokenEndPoint())
expect(res.message.statusCode).toBe(200)
})
})

View File

@ -1,14 +1,62 @@
{ {
"name": "@actions/core", "name": "@actions/core",
"version": "1.4.0", "version": "1.6.0",
"lockfileVersion": 1, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": {
"": {
"name": "@actions/core",
"version": "1.6.0",
"license": "MIT",
"dependencies": { "dependencies": {
"@actions/http-client": "^1.0.11"
},
"devDependencies": {
"@types/node": "^12.0.2"
}
},
"node_modules/@actions/http-client": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"dependencies": {
"tunnel": "0.0.6"
}
},
"node_modules/@types/node": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
"dev": true
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
}
},
"dependencies": {
"@actions/http-client": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"requires": {
"tunnel": "0.0.6"
}
},
"@types/node": { "@types/node": {
"version": "12.0.2", "version": "12.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==", "integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
"dev": true "dev": true
},
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
} }
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@actions/core", "name": "@actions/core",
"version": "1.5.0", "version": "1.6.0",
"description": "Actions core lib", "description": "Actions core lib",
"keywords": [ "keywords": [
"github", "github",
@ -35,6 +35,9 @@
"bugs": { "bugs": {
"url": "https://github.com/actions/toolkit/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"dependencies": {
"@actions/http-client": "^1.0.11"
},
"devDependencies": { "devDependencies": {
"@types/node": "^12.0.2" "@types/node": "^12.0.2"
} }

View File

@ -5,6 +5,8 @@ import {toCommandProperties, toCommandValue} from './utils'
import * as os from 'os' import * as os from 'os'
import * as path from 'path' import * as path from 'path'
import {OidcClient} from './oidc-utils'
/** /**
* Interface for getInput options * Interface for getInput options
*/ */
@ -348,3 +350,7 @@ export function saveState(name: string, value: any): void {
export function getState(name: string): string { export function getState(name: string): string {
return process.env[`STATE_${name}`] || '' return process.env[`STATE_${name}`] || ''
} }
export async function getIDToken(aud?: string): Promise<string> {
return await OidcClient.getIDToken(aud)
}

View File

@ -0,0 +1,94 @@
/* eslint-disable @typescript-eslint/no-extraneous-class */
import * as actions_http_client from '@actions/http-client'
import {IRequestOptions} from '@actions/http-client/interfaces'
import {HttpClient} from '@actions/http-client'
import {BearerCredentialHandler} from '@actions/http-client/auth'
import {debug, setSecret} from './core'
interface TokenRequest {
aud?: string
}
interface TokenResponse {
value?: string
}
export class OidcClient {
private static createHttpClient(
allowRetry = true,
maxRetry = 10
): actions_http_client.HttpClient {
const requestOptions: IRequestOptions = {
allowRetries: allowRetry,
maxRetries: maxRetry
}
return new HttpClient(
'actions/oidc-client',
[new BearerCredentialHandler(OidcClient.getRuntimeToken())],
requestOptions
)
}
private static getApiVersion(): string {
return '2.0'
}
private static getRuntimeToken(): string {
const token = process.env['ACTIONS_RUNTIME_TOKEN']
if (!token) {
throw new Error('Unable to get ACTIONS_RUNTIME_TOKEN env variable')
}
return token
}
private static getIDTokenUrl(): string {
const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL']
if (!runtimeUrl) {
throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable')
}
return `${runtimeUrl}?api-version=${OidcClient.getApiVersion()}`
}
private static async postCall(
id_token_url: string,
data: TokenRequest
): Promise<string> {
const httpclient = OidcClient.createHttpClient()
const res = await httpclient
.postJson<TokenResponse>(id_token_url, data)
.catch(error => {
throw new Error(
`Failed to get ID Token. \n
Error Code : ${error.statusCode}\n
Error Message: ${error.result.message}`
)
})
const id_token = res.result?.value
if (!id_token) {
throw new Error('Response json body do not have ID Token field')
}
return id_token
}
static async getIDToken(audience?: string): Promise<string> {
try {
// New ID Token is requested from action service
const id_token_url: string = OidcClient.getIDTokenUrl()
debug(`ID token url is ${id_token_url}`)
const data: TokenRequest = {aud: audience}
debug(`audience is ${audience ? audience : 'not defined'}`)
const id_token = await OidcClient.postCall(id_token_url, data)
setSecret(id_token)
return id_token
} catch (error) {
throw new Error(`Error message: ${error.message}`)
}
}
}