mirror of https://github.com/actions/toolkit
Full release of actions/core 1.6.0 with oidc behavior (#919)
* OIDC Client for actions/core Co-authored-by: Sourav Chanduka <souravchanduka37@gmail.com> Co-authored-by: Sourav Chanduka <souravchanduka@users.noreply.github.com> Co-authored-by: Tingluo Huang <tingluohuang@github.com>pull/920/head
parent
60145e408c
commit
27f76dfe1a
File diff suppressed because it is too large
Load Diff
|
@ -262,3 +262,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'
|
||||||
|
```
|
|
@ -1,5 +1,9 @@
|
||||||
# @actions/core Releases
|
# @actions/core Releases
|
||||||
|
|
||||||
|
### 1.6.0
|
||||||
|
- [Added OIDC Client function `getIDToken`](https://github.com/actions/toolkit/pull/919)
|
||||||
|
- [Added `file` parameter to `AnnotationProperties`](https://github.com/actions/toolkit/pull/896)
|
||||||
|
|
||||||
### 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)
|
||||||
|
|
||||||
|
|
|
@ -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 */
|
||||||
|
@ -469,3 +470,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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -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": {
|
||||||
|
"@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": {
|
"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=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -353,3 +355,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)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* 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 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.getRequestToken())],
|
||||||
|
requestOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getRequestToken(): string {
|
||||||
|
const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'Unable to get ACTIONS_ID_TOKEN_REQUEST_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
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async getCall(id_token_url: string): Promise<string> {
|
||||||
|
const httpclient = OidcClient.createHttpClient()
|
||||||
|
|
||||||
|
const res = await httpclient
|
||||||
|
.getJson<TokenResponse>(id_token_url)
|
||||||
|
.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
|
||||||
|
let id_token_url: string = OidcClient.getIDTokenUrl()
|
||||||
|
if (audience) {
|
||||||
|
const encodedAudience = encodeURIComponent(audience)
|
||||||
|
id_token_url = `${id_token_url}&audience=${encodedAudience}`
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(`ID token url is ${id_token_url}`)
|
||||||
|
|
||||||
|
const id_token = await OidcClient.getCall(id_token_url)
|
||||||
|
setSecret(id_token)
|
||||||
|
return id_token
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error message: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue