From 40e9a1512937c03a753ed071d3723c595b403b40 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Fri, 3 Feb 2023 03:33:09 +0100 Subject: [PATCH 1/4] buildx(inputs): rename some methods Signed-off-by: CrazyMax --- __tests__/buildx/inputs.test.ts | 22 +++++++++++----------- src/buildx/inputs.ts | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/__tests__/buildx/inputs.test.ts b/__tests__/buildx/inputs.test.ts index 15b15fc..6e16f24 100644 --- a/__tests__/buildx/inputs.test.ts +++ b/__tests__/buildx/inputs.test.ts @@ -50,7 +50,7 @@ afterEach(() => { rimraf.sync(tmpDir); }); -describe('getBuildImageID', () => { +describe('resolveBuildImageID', () => { it('matches', async () => { const buildx = new Buildx({ context: new Context() @@ -58,31 +58,31 @@ describe('getBuildImageID', () => { const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9'; const imageIDFile = buildx.inputs.getBuildImageIDFilePath(); await fs.writeFileSync(imageIDFile, imageID); - const expected = buildx.inputs.getBuildImageID(); + const expected = buildx.inputs.resolveBuildImageID(); expect(expected).toEqual(imageID); }); }); -describe('getBuildMetadata', () => { +describe('resolveBuildMetadata', () => { it('matches', async () => { const buildx = new Buildx({ context: new Context() }); const metadataFile = buildx.inputs.getBuildMetadataFilePath(); await fs.writeFileSync(metadataFile, metadata); - const expected = buildx.inputs.getBuildMetadata(); + const expected = buildx.inputs.resolveBuildMetadata(); expect(expected).toEqual(metadata); }); }); -describe('getDigest', () => { +describe('resolveDigest', () => { it('matches', async () => { const buildx = new Buildx({ context: new Context() }); const metadataFile = buildx.inputs.getBuildMetadataFilePath(); await fs.writeFileSync(metadataFile, metadata); - const expected = buildx.inputs.getDigest(); + const expected = buildx.inputs.resolveDigest(); expect(expected).toEqual('sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c'); }); }); @@ -136,7 +136,7 @@ describe('getProvenanceInput', () => { }); }); -describe('getProvenanceAttrs', () => { +describe('resolveProvenanceAttrs', () => { // prettier-ignore test.each([ [ @@ -163,11 +163,11 @@ describe('getProvenanceAttrs', () => { const buildx = new Buildx({ context: new Context() }); - expect(buildx.inputs.getProvenanceAttrs(input)).toEqual(expected); + expect(buildx.inputs.resolveProvenanceAttrs(input)).toEqual(expected); }); }); -describe('generateBuildSecret', () => { +describe('resolveBuildSecret', () => { test.each([ ['A_SECRET=abcdef0123456789', false, 'A_SECRET', 'abcdef0123456789', null], ['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789', false, 'GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789', null], @@ -184,9 +184,9 @@ describe('generateBuildSecret', () => { }); let secret: string; if (file) { - secret = buildx.inputs.generateBuildSecretFile(kvp); + secret = buildx.inputs.resolveBuildSecretFile(kvp); } else { - secret = buildx.inputs.generateBuildSecretString(kvp); + secret = buildx.inputs.resolveBuildSecretString(kvp); } expect(secret).toEqual(`id=${exKey},src=${tmpName}`); expect(fs.readFileSync(tmpName, 'utf-8')).toEqual(exValue); diff --git a/src/buildx/inputs.ts b/src/buildx/inputs.ts index a5cae52..c07ea05 100644 --- a/src/buildx/inputs.ts +++ b/src/buildx/inputs.ts @@ -36,7 +36,7 @@ export class Inputs { return path.join(this.context.tmpDir(), 'metadata-file').split(path.sep).join(path.posix.sep); } - public getBuildImageID(): string | undefined { + public resolveBuildImageID(): string | undefined { const iidFile = this.getBuildImageIDFilePath(); if (!fs.existsSync(iidFile)) { return undefined; @@ -44,7 +44,7 @@ export class Inputs { return fs.readFileSync(iidFile, {encoding: 'utf-8'}).trim(); } - public getBuildMetadata(): string | undefined { + public resolveBuildMetadata(): string | undefined { const metadataFile = this.getBuildMetadataFilePath(); if (!fs.existsSync(metadataFile)) { return undefined; @@ -56,8 +56,8 @@ export class Inputs { return content; } - public getDigest(): string | undefined { - const metadata = this.getBuildMetadata(); + public resolveDigest(): string | undefined { + const metadata = this.resolveBuildMetadata(); if (metadata === undefined) { return undefined; } @@ -68,15 +68,15 @@ export class Inputs { return undefined; } - public generateBuildSecretString(kvp: string): string { - return this.generateBuildSecret(kvp, false); + public resolveBuildSecretString(kvp: string): string { + return this.resolveBuildSecret(kvp, false); } - public generateBuildSecretFile(kvp: string): string { - return this.generateBuildSecret(kvp, true); + public resolveBuildSecretFile(kvp: string): string { + return this.resolveBuildSecret(kvp, true); } - public generateBuildSecret(kvp: string, file: boolean): string { + public resolveBuildSecret(kvp: string, file: boolean): string { const delimiterIndex = kvp.indexOf('='); const key = kvp.substring(0, delimiterIndex); let value = kvp.substring(delimiterIndex + 1); @@ -105,11 +105,11 @@ export class Inputs { return core.getBooleanInput(name) ? `builder-id=${builderID}` : 'false'; } catch (err) { // not a valid boolean, so we assume it's a string - return this.getProvenanceAttrs(input); + return this.resolveProvenanceAttrs(input); } } - public getProvenanceAttrs(input: string): string { + public resolveProvenanceAttrs(input: string): string { if (!input) { return `builder-id=${this.context.provenanceBuilderID}`; } From ad59af8cf293e8a497b83eefaac39f40d674612f Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Fri, 3 Feb 2023 03:33:22 +0100 Subject: [PATCH 2/4] github: printActionsRuntimeToken Signed-off-by: CrazyMax --- __tests__/github.test.ts | 31 +++++++++++++++++++++++++++++++ src/github.ts | 10 ++++++++++ 2 files changed, 41 insertions(+) diff --git a/__tests__/github.test.ts b/__tests__/github.test.ts index b98b6f7..900a7ed 100644 --- a/__tests__/github.test.ts +++ b/__tests__/github.test.ts @@ -17,6 +17,7 @@ import {describe, expect, jest, it, beforeEach, afterEach} from '@jest/globals'; import * as fs from 'fs'; import * as path from 'path'; +import * as core from '@actions/core'; import {GitHub} from '../src/github'; import {GitHubRepo} from '../src/types/github'; @@ -110,3 +111,33 @@ describe('actionsRuntimeToken', () => { expect(runtimeToken.iss).toEqual('vstoken.actions.githubusercontent.com'); }); }); + +describe('printActionsRuntimeToken', () => { + const originalEnv = process.env; + beforeEach(() => { + jest.resetModules(); + process.env = { + ...originalEnv + }; + }); + afterEach(() => { + process.env = originalEnv; + }); + it('empty', async () => { + const execSpy = jest.spyOn(core, 'info'); + process.env.ACTIONS_RUNTIME_TOKEN = ''; + GitHub.printActionsRuntimeToken(); + expect(execSpy).toHaveBeenCalledWith(`ACTIONS_RUNTIME_TOKEN not set`); + }); + it('prints ac', () => { + const execSpy = jest.spyOn(core, 'info'); + process.env.ACTIONS_RUNTIME_TOKEN = fs.readFileSync(path.join(__dirname, 'fixtures', 'runtimeToken.txt')).toString().trim(); + GitHub.printActionsRuntimeToken(); + expect(execSpy).toHaveBeenCalledWith(`[ + { + "Scope": "refs/heads/master", + "Permission": 3 + } +]`); + }); +}); diff --git a/src/github.ts b/src/github.ts index 232f6f4..5fa840a 100644 --- a/src/github.ts +++ b/src/github.ts @@ -15,6 +15,7 @@ */ import {GitHub as Octokit} from '@actions/github/lib/utils'; +import * as core from '@actions/core'; import * as github from '@actions/github'; import {Context} from '@actions/github/lib/context'; import jwt_decode from 'jwt-decode'; @@ -52,4 +53,13 @@ export class GitHub { const token = process.env['ACTIONS_RUNTIME_TOKEN'] || ''; return token ? jwt_decode(token) : {}; } + + public static async printActionsRuntimeToken() { + const actionsRuntimeToken = process.env['ACTIONS_RUNTIME_TOKEN']; + if (actionsRuntimeToken) { + core.info(JSON.stringify(JSON.parse(GitHub.actionsRuntimeToken.ac as string), undefined, 2)); + } else { + core.info(`ACTIONS_RUNTIME_TOKEN not set`); + } + } } From 3e2548a8ed006153be44c8175f76911ab047b408 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Fri, 3 Feb 2023 04:09:04 +0100 Subject: [PATCH 3/4] github: translate access controls permissions Signed-off-by: CrazyMax --- __tests__/github.test.ts | 19 +++++++------------ src/github.ts | 36 ++++++++++++++++++++++++++++-------- src/types/github.ts | 5 +++++ 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/__tests__/github.test.ts b/__tests__/github.test.ts index 900a7ed..1b77450 100644 --- a/__tests__/github.test.ts +++ b/__tests__/github.test.ts @@ -107,12 +107,12 @@ describe('actionsRuntimeToken', () => { it('fixture', async () => { process.env.ACTIONS_RUNTIME_TOKEN = fs.readFileSync(path.join(__dirname, 'fixtures', 'runtimeToken.txt')).toString().trim(); const runtimeToken = GitHub.actionsRuntimeToken; - expect(runtimeToken.ac).toEqual('[{"Scope":"refs/heads/master","Permission":3}]'); - expect(runtimeToken.iss).toEqual('vstoken.actions.githubusercontent.com'); + expect(runtimeToken?.ac).toEqual('[{"Scope":"refs/heads/master","Permission":3}]'); + expect(runtimeToken?.iss).toEqual('vstoken.actions.githubusercontent.com'); }); }); -describe('printActionsRuntimeToken', () => { +describe('printActionsRuntimeTokenACs', () => { const originalEnv = process.env; beforeEach(() => { jest.resetModules(); @@ -126,18 +126,13 @@ describe('printActionsRuntimeToken', () => { it('empty', async () => { const execSpy = jest.spyOn(core, 'info'); process.env.ACTIONS_RUNTIME_TOKEN = ''; - GitHub.printActionsRuntimeToken(); + await GitHub.printActionsRuntimeTokenACs(); expect(execSpy).toHaveBeenCalledWith(`ACTIONS_RUNTIME_TOKEN not set`); }); - it('prints ac', () => { + it('refs/heads/master', async () => { const execSpy = jest.spyOn(core, 'info'); process.env.ACTIONS_RUNTIME_TOKEN = fs.readFileSync(path.join(__dirname, 'fixtures', 'runtimeToken.txt')).toString().trim(); - GitHub.printActionsRuntimeToken(); - expect(execSpy).toHaveBeenCalledWith(`[ - { - "Scope": "refs/heads/master", - "Permission": 3 - } -]`); + await GitHub.printActionsRuntimeTokenACs(); + expect(execSpy).toHaveBeenCalledWith(`refs/heads/master: read/write`); }); }); diff --git a/src/github.ts b/src/github.ts index 5fa840a..6c8c2d0 100644 --- a/src/github.ts +++ b/src/github.ts @@ -20,7 +20,7 @@ import * as github from '@actions/github'; import {Context} from '@actions/github/lib/context'; import jwt_decode from 'jwt-decode'; -import {GitHubActionsRuntimeToken, GitHubRepo} from './types/github'; +import {GitHubActionsRuntimeToken, GitHubActionsRuntimeTokenAC, GitHubRepo} from './types/github'; export interface GitHubOpts { token?: string; @@ -49,17 +49,37 @@ export class GitHub { return process.env.GITHUB_API_URL || 'https://api.github.com'; } - static get actionsRuntimeToken(): GitHubActionsRuntimeToken { + static get actionsRuntimeToken(): GitHubActionsRuntimeToken | undefined { const token = process.env['ACTIONS_RUNTIME_TOKEN'] || ''; - return token ? jwt_decode(token) : {}; + return token ? jwt_decode(token) : undefined; } - public static async printActionsRuntimeToken() { - const actionsRuntimeToken = process.env['ACTIONS_RUNTIME_TOKEN']; - if (actionsRuntimeToken) { - core.info(JSON.stringify(JSON.parse(GitHub.actionsRuntimeToken.ac as string), undefined, 2)); - } else { + public static async printActionsRuntimeTokenACs() { + const jwt = GitHub.actionsRuntimeToken; + if (!jwt) { core.info(`ACTIONS_RUNTIME_TOKEN not set`); + return; + } + try { + >JSON.parse(`${jwt.ac}`).forEach(ac => { + let permission: string; + switch (ac.Permission) { + case 1: + permission = 'read'; + break; + case 2: + permission = 'write'; + break; + case 3: + permission = 'read/write'; + break; + default: + permission = `unimplemented (${ac.Permission})`; + } + core.info(`${ac.Scope}: ${permission}`); + }); + } catch (e) { + core.warning(`Cannot parse Actions Runtime Token Access Controls: ${e.message}`); } } } diff --git a/src/types/github.ts b/src/types/github.ts index 833a405..a7b9c71 100644 --- a/src/types/github.ts +++ b/src/types/github.ts @@ -29,3 +29,8 @@ export type GitHubRepo = OctoOpenApiTypes['schemas']['repository']; export interface GitHubActionsRuntimeToken extends JwtPayload { ac?: string; } + +export interface GitHubActionsRuntimeTokenAC { + Scope: string; + Permission: number; +} From 7a9a28cd909408ec7d51620f49ecd364e43ddf67 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Fri, 3 Feb 2023 04:37:26 +0100 Subject: [PATCH 4/4] github: handle malformed runtime token Signed-off-by: CrazyMax --- __tests__/github.test.ts | 25 ++++++++++++++++++++----- src/github.ts | 10 ++++++++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/__tests__/github.test.ts b/__tests__/github.test.ts index 1b77450..08c73a4 100644 --- a/__tests__/github.test.ts +++ b/__tests__/github.test.ts @@ -102,7 +102,13 @@ describe('actionsRuntimeToken', () => { }); it('empty', async () => { process.env.ACTIONS_RUNTIME_TOKEN = ''; - expect(GitHub.actionsRuntimeToken).toEqual({}); + expect(GitHub.actionsRuntimeToken).toBeUndefined(); + }); + it('malformed', async () => { + process.env.ACTIONS_RUNTIME_TOKEN = 'foo'; + expect(() => { + GitHub.actionsRuntimeToken; + }).toThrowError(); }); it('fixture', async () => { process.env.ACTIONS_RUNTIME_TOKEN = fs.readFileSync(path.join(__dirname, 'fixtures', 'runtimeToken.txt')).toString().trim(); @@ -124,15 +130,24 @@ describe('printActionsRuntimeTokenACs', () => { process.env = originalEnv; }); it('empty', async () => { - const execSpy = jest.spyOn(core, 'info'); + const warnSpy = jest.spyOn(core, 'warning'); process.env.ACTIONS_RUNTIME_TOKEN = ''; await GitHub.printActionsRuntimeTokenACs(); - expect(execSpy).toHaveBeenCalledWith(`ACTIONS_RUNTIME_TOKEN not set`); + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith(`ACTIONS_RUNTIME_TOKEN not set`); + }); + it('malformed', async () => { + const warnSpy = jest.spyOn(core, 'warning'); + process.env.ACTIONS_RUNTIME_TOKEN = 'foo'; + await GitHub.printActionsRuntimeTokenACs(); + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith(`Cannot parse Actions Runtime Token: Invalid token specified: Cannot read properties of undefined (reading 'replace')`); }); it('refs/heads/master', async () => { - const execSpy = jest.spyOn(core, 'info'); + const infoSpy = jest.spyOn(core, 'info'); process.env.ACTIONS_RUNTIME_TOKEN = fs.readFileSync(path.join(__dirname, 'fixtures', 'runtimeToken.txt')).toString().trim(); await GitHub.printActionsRuntimeTokenACs(); - expect(execSpy).toHaveBeenCalledWith(`refs/heads/master: read/write`); + expect(infoSpy).toHaveBeenCalledTimes(1); + expect(infoSpy).toHaveBeenCalledWith(`refs/heads/master: read/write`); }); }); diff --git a/src/github.ts b/src/github.ts index 6c8c2d0..e7e0404 100644 --- a/src/github.ts +++ b/src/github.ts @@ -55,9 +55,15 @@ export class GitHub { } public static async printActionsRuntimeTokenACs() { - const jwt = GitHub.actionsRuntimeToken; + let jwt: GitHubActionsRuntimeToken | undefined; + try { + jwt = GitHub.actionsRuntimeToken; + } catch (e) { + core.warning(`Cannot parse Actions Runtime Token: ${e.message}`); + return; + } if (!jwt) { - core.info(`ACTIONS_RUNTIME_TOKEN not set`); + core.warning(`ACTIONS_RUNTIME_TOKEN not set`); return; } try {