Merge pull request #23 from crazy-max/github-print-art

github: printActionsRuntimeTokenACs
This commit is contained in:
CrazyMax
2023-02-03 13:22:41 +01:00
committed by GitHub
5 changed files with 110 additions and 28 deletions

View File

@@ -50,7 +50,7 @@ afterEach(() => {
rimraf.sync(tmpDir); rimraf.sync(tmpDir);
}); });
describe('getBuildImageID', () => { describe('resolveBuildImageID', () => {
it('matches', async () => { it('matches', async () => {
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context() context: new Context()
@@ -58,31 +58,31 @@ describe('getBuildImageID', () => {
const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9'; const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9';
const imageIDFile = buildx.inputs.getBuildImageIDFilePath(); const imageIDFile = buildx.inputs.getBuildImageIDFilePath();
await fs.writeFileSync(imageIDFile, imageID); await fs.writeFileSync(imageIDFile, imageID);
const expected = buildx.inputs.getBuildImageID(); const expected = buildx.inputs.resolveBuildImageID();
expect(expected).toEqual(imageID); expect(expected).toEqual(imageID);
}); });
}); });
describe('getBuildMetadata', () => { describe('resolveBuildMetadata', () => {
it('matches', async () => { it('matches', async () => {
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context() context: new Context()
}); });
const metadataFile = buildx.inputs.getBuildMetadataFilePath(); const metadataFile = buildx.inputs.getBuildMetadataFilePath();
await fs.writeFileSync(metadataFile, metadata); await fs.writeFileSync(metadataFile, metadata);
const expected = buildx.inputs.getBuildMetadata(); const expected = buildx.inputs.resolveBuildMetadata();
expect(expected).toEqual(metadata); expect(expected).toEqual(metadata);
}); });
}); });
describe('getDigest', () => { describe('resolveDigest', () => {
it('matches', async () => { it('matches', async () => {
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context() context: new Context()
}); });
const metadataFile = buildx.inputs.getBuildMetadataFilePath(); const metadataFile = buildx.inputs.getBuildMetadataFilePath();
await fs.writeFileSync(metadataFile, metadata); await fs.writeFileSync(metadataFile, metadata);
const expected = buildx.inputs.getDigest(); const expected = buildx.inputs.resolveDigest();
expect(expected).toEqual('sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c'); expect(expected).toEqual('sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c');
}); });
}); });
@@ -136,7 +136,7 @@ describe('getProvenanceInput', () => {
}); });
}); });
describe('getProvenanceAttrs', () => { describe('resolveProvenanceAttrs', () => {
// prettier-ignore // prettier-ignore
test.each([ test.each([
[ [
@@ -163,11 +163,11 @@ describe('getProvenanceAttrs', () => {
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context() context: new Context()
}); });
expect(buildx.inputs.getProvenanceAttrs(input)).toEqual(expected); expect(buildx.inputs.resolveProvenanceAttrs(input)).toEqual(expected);
}); });
}); });
describe('generateBuildSecret', () => { describe('resolveBuildSecret', () => {
test.each([ test.each([
['A_SECRET=abcdef0123456789', false, 'A_SECRET', 'abcdef0123456789', null], ['A_SECRET=abcdef0123456789', false, 'A_SECRET', 'abcdef0123456789', null],
['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789', false, 'GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789', null], ['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789', false, 'GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789', null],
@@ -184,9 +184,9 @@ describe('generateBuildSecret', () => {
}); });
let secret: string; let secret: string;
if (file) { if (file) {
secret = buildx.inputs.generateBuildSecretFile(kvp); secret = buildx.inputs.resolveBuildSecretFile(kvp);
} else { } else {
secret = buildx.inputs.generateBuildSecretString(kvp); secret = buildx.inputs.resolveBuildSecretString(kvp);
} }
expect(secret).toEqual(`id=${exKey},src=${tmpName}`); expect(secret).toEqual(`id=${exKey},src=${tmpName}`);
expect(fs.readFileSync(tmpName, 'utf-8')).toEqual(exValue); expect(fs.readFileSync(tmpName, 'utf-8')).toEqual(exValue);

View File

@@ -17,6 +17,7 @@
import {describe, expect, jest, it, beforeEach, afterEach} from '@jest/globals'; import {describe, expect, jest, it, beforeEach, afterEach} from '@jest/globals';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as core from '@actions/core';
import {GitHub} from '../src/github'; import {GitHub} from '../src/github';
import {GitHubRepo} from '../src/types/github'; import {GitHubRepo} from '../src/types/github';
@@ -101,12 +102,52 @@ describe('actionsRuntimeToken', () => {
}); });
it('empty', async () => { it('empty', async () => {
process.env.ACTIONS_RUNTIME_TOKEN = ''; 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 () => { it('fixture', async () => {
process.env.ACTIONS_RUNTIME_TOKEN = fs.readFileSync(path.join(__dirname, 'fixtures', 'runtimeToken.txt')).toString().trim(); process.env.ACTIONS_RUNTIME_TOKEN = fs.readFileSync(path.join(__dirname, 'fixtures', 'runtimeToken.txt')).toString().trim();
const runtimeToken = GitHub.actionsRuntimeToken; const runtimeToken = GitHub.actionsRuntimeToken;
expect(runtimeToken.ac).toEqual('[{"Scope":"refs/heads/master","Permission":3}]'); expect(runtimeToken?.ac).toEqual('[{"Scope":"refs/heads/master","Permission":3}]');
expect(runtimeToken.iss).toEqual('vstoken.actions.githubusercontent.com'); expect(runtimeToken?.iss).toEqual('vstoken.actions.githubusercontent.com');
});
});
describe('printActionsRuntimeTokenACs', () => {
const originalEnv = process.env;
beforeEach(() => {
jest.resetModules();
process.env = {
...originalEnv
};
});
afterEach(() => {
process.env = originalEnv;
});
it('empty', async () => {
const warnSpy = jest.spyOn(core, 'warning');
process.env.ACTIONS_RUNTIME_TOKEN = '';
await GitHub.printActionsRuntimeTokenACs();
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 infoSpy = jest.spyOn(core, 'info');
process.env.ACTIONS_RUNTIME_TOKEN = fs.readFileSync(path.join(__dirname, 'fixtures', 'runtimeToken.txt')).toString().trim();
await GitHub.printActionsRuntimeTokenACs();
expect(infoSpy).toHaveBeenCalledTimes(1);
expect(infoSpy).toHaveBeenCalledWith(`refs/heads/master: read/write`);
}); });
}); });

View File

@@ -36,7 +36,7 @@ export class Inputs {
return path.join(this.context.tmpDir(), 'metadata-file').split(path.sep).join(path.posix.sep); 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(); const iidFile = this.getBuildImageIDFilePath();
if (!fs.existsSync(iidFile)) { if (!fs.existsSync(iidFile)) {
return undefined; return undefined;
@@ -44,7 +44,7 @@ export class Inputs {
return fs.readFileSync(iidFile, {encoding: 'utf-8'}).trim(); return fs.readFileSync(iidFile, {encoding: 'utf-8'}).trim();
} }
public getBuildMetadata(): string | undefined { public resolveBuildMetadata(): string | undefined {
const metadataFile = this.getBuildMetadataFilePath(); const metadataFile = this.getBuildMetadataFilePath();
if (!fs.existsSync(metadataFile)) { if (!fs.existsSync(metadataFile)) {
return undefined; return undefined;
@@ -56,8 +56,8 @@ export class Inputs {
return content; return content;
} }
public getDigest(): string | undefined { public resolveDigest(): string | undefined {
const metadata = this.getBuildMetadata(); const metadata = this.resolveBuildMetadata();
if (metadata === undefined) { if (metadata === undefined) {
return undefined; return undefined;
} }
@@ -68,15 +68,15 @@ export class Inputs {
return undefined; return undefined;
} }
public generateBuildSecretString(kvp: string): string { public resolveBuildSecretString(kvp: string): string {
return this.generateBuildSecret(kvp, false); return this.resolveBuildSecret(kvp, false);
} }
public generateBuildSecretFile(kvp: string): string { public resolveBuildSecretFile(kvp: string): string {
return this.generateBuildSecret(kvp, true); return this.resolveBuildSecret(kvp, true);
} }
public generateBuildSecret(kvp: string, file: boolean): string { public resolveBuildSecret(kvp: string, file: boolean): string {
const delimiterIndex = kvp.indexOf('='); const delimiterIndex = kvp.indexOf('=');
const key = kvp.substring(0, delimiterIndex); const key = kvp.substring(0, delimiterIndex);
let value = kvp.substring(delimiterIndex + 1); let value = kvp.substring(delimiterIndex + 1);
@@ -105,11 +105,11 @@ export class Inputs {
return core.getBooleanInput(name) ? `builder-id=${builderID}` : 'false'; return core.getBooleanInput(name) ? `builder-id=${builderID}` : 'false';
} catch (err) { } catch (err) {
// not a valid boolean, so we assume it's a string // 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) { if (!input) {
return `builder-id=${this.context.provenanceBuilderID}`; return `builder-id=${this.context.provenanceBuilderID}`;
} }

View File

@@ -15,11 +15,12 @@
*/ */
import {GitHub as Octokit} from '@actions/github/lib/utils'; import {GitHub as Octokit} from '@actions/github/lib/utils';
import * as core from '@actions/core';
import * as github from '@actions/github'; import * as github from '@actions/github';
import {Context} from '@actions/github/lib/context'; import {Context} from '@actions/github/lib/context';
import jwt_decode from 'jwt-decode'; import jwt_decode from 'jwt-decode';
import {GitHubActionsRuntimeToken, GitHubRepo} from './types/github'; import {GitHubActionsRuntimeToken, GitHubActionsRuntimeTokenAC, GitHubRepo} from './types/github';
export interface GitHubOpts { export interface GitHubOpts {
token?: string; token?: string;
@@ -48,8 +49,43 @@ export class GitHub {
return process.env.GITHUB_API_URL || 'https://api.github.com'; 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'] || ''; const token = process.env['ACTIONS_RUNTIME_TOKEN'] || '';
return token ? jwt_decode<GitHubActionsRuntimeToken>(token) : {}; return token ? jwt_decode<GitHubActionsRuntimeToken>(token) : undefined;
}
public static async printActionsRuntimeTokenACs() {
let jwt: GitHubActionsRuntimeToken | undefined;
try {
jwt = GitHub.actionsRuntimeToken;
} catch (e) {
core.warning(`Cannot parse Actions Runtime Token: ${e.message}`);
return;
}
if (!jwt) {
core.warning(`ACTIONS_RUNTIME_TOKEN not set`);
return;
}
try {
<Array<GitHubActionsRuntimeTokenAC>>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}`);
}
} }
} }

View File

@@ -29,3 +29,8 @@ export type GitHubRepo = OctoOpenApiTypes['schemas']['repository'];
export interface GitHubActionsRuntimeToken extends JwtPayload { export interface GitHubActionsRuntimeToken extends JwtPayload {
ac?: string; ac?: string;
} }
export interface GitHubActionsRuntimeTokenAC {
Scope: string;
Permission: number;
}