Skip to content

Commit

Permalink
Merge pull request #603 from fuller-inc/fix-failing-test
Browse files Browse the repository at this point in the history
dummy server: graceful shutdown
  • Loading branch information
shogo82148 authored Sep 22, 2023
2 parents fdfbff0 + 8d9dafb commit a6e9424
Show file tree
Hide file tree
Showing 14 changed files with 240 additions and 164 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version-file: .node-version
node-version-file: action/package.json
cache: "npm"
cache-dependency-path: action/package-lock.json

Expand Down
1 change: 0 additions & 1 deletion .node-version

This file was deleted.

1 change: 1 addition & 0 deletions action/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/node_modules/
/lib/
/dummy.log
10 changes: 1 addition & 9 deletions action/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid",
"parser": "typescript"
"printWidth": 120
}
106 changes: 62 additions & 44 deletions action/__test__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,103 @@
import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import * as child_process from 'child_process';
import * as index from '../src/index';
import * as os from "os";
import * as fs from "fs";
import * as path from "path";
import * as exec from "@actions/exec";
import * as io from "@actions/io";
import * as core from "@actions/core";
import * as child_process from "child_process";
import * as index from "../src/index";

const sep = path.sep;

jest.mock("@actions/core");

// extension of executable files
const binExt = os.platform() === 'win32' ? '.exe' : '';
const binExt = os.platform() === "win32" ? ".exe" : "";

process.env.GITHUB_REPOSITORY = "fuller-inc/actions-aws-assume-role";
process.env.GITHUB_WORKFLOW = "test";
process.env.GITHUB_RUN_ID = "1234567890";
process.env.GITHUB_ACTOR = "fuller-inc";
process.env.GITHUB_SHA = "e3a45c6c16c1464826b36a598ff39e6cc98c4da4";
process.env.GITHUB_REF = "ref/heads/main";

process.env.GITHUB_REPOSITORY = 'shogo82148/actions-aws-assume-role';
process.env.GITHUB_WORKFLOW = 'test';
process.env.GITHUB_RUN_ID = '1234567890';
process.env.GITHUB_ACTOR = 'shogo82148';
process.env.GITHUB_SHA = 'e3a45c6c16c1464826b36a598ff39e6cc98c4da4';
process.env.GITHUB_REF = 'ref/heads/main';
// set dummy id token endpoint
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = "dummy";
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = "https://example.com";

describe('tests', () => {
let tmpdir = '';
describe("tests", () => {
let tmpdir = "";
let subprocess: child_process.ChildProcess;
beforeAll(async () => {
tmpdir = await mkdtemp();
const bin = `${tmpdir}${sep}dummy${binExt}`;

console.log('compiling dummy server');
console.log("compiling dummy server");
await exec.exec(
'go',
['build', '-o', bin, 'github.com/fuller-inc/actions-aws-assume-role/provider/assume-role/cmd/dummy'],
"go",
["build", "-o", bin, "github.com/fuller-inc/actions-aws-assume-role/provider/assume-role/cmd/dummy"],
{
cwd: `..${sep}provider${sep}assume-role`
}
cwd: `..${sep}provider${sep}assume-role`,
},
);

console.log('starting dummy server');
console.log("starting dummy server");
subprocess = child_process.spawn(bin, [], {
detached: true,
stdio: 'ignore'
stdio: "ignore",
});
await sleep(1); // wait for starting process
}, 5 * 60000);

afterAll(async () => {
console.log('killing dummy server');
subprocess?.kill('SIGTERM');
console.log("killing dummy server");
subprocess?.kill("SIGTERM");
await sleep(1); // wait for stopping process
await io.rmRF(tmpdir);
});

it('succeed', async () => {
it("succeed", async () => {
(core.getIDToken as jest.Mock).mockResolvedValueOnce("dummyGitHubIDToken");

await index.assumeRole({
githubToken: 'ghs_dummyGitHubToken',
awsRegion: 'us-east-1',
roleToAssume: 'arn:aws:iam::123456789012:role/assume-role-test',
githubToken: "ghs_dummyGitHubToken",
awsRegion: "us-east-1",
roleToAssume: "arn:aws:iam::123456789012:role/assume-role-test",
roleDurationSeconds: 900,
roleSessionName: 'GitHubActions',
roleSessionName: "GitHubActions",
roleSessionTagging: true,
providerEndpoint: 'http://localhost:8080',
providerEndpoint: "http://localhost:8080",
useNodeId: false,
obfuscateRepository: ''
obfuscateRepository: "",
});
expect(process.env.AWS_ACCESS_KEY_ID).toBe('AKIAIOSFODNN7EXAMPLE');
expect(process.env.AWS_SECRET_ACCESS_KEY).toBe('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');
expect(process.env.AWS_SESSION_TOKEN).toBe('session-token');
expect(process.env.AWS_DEFAULT_REGION).toBe('us-east-1');
expect(process.env.AWS_REGION).toBe('us-east-1');

const exportVariable = core.exportVariable as jest.Mock;
expect(exportVariable).toHaveBeenCalledWith("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE");
expect(exportVariable).toHaveBeenCalledWith("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");
expect(exportVariable).toHaveBeenCalledWith("AWS_SESSION_TOKEN", "session-token");
expect(exportVariable).toHaveBeenCalledWith("AWS_DEFAULT_REGION", "us-east-1");
expect(exportVariable).toHaveBeenCalledWith("AWS_REGION", "us-east-1");

const setSecret = core.setSecret as jest.Mock;
expect(setSecret).toHaveBeenCalledWith("AKIAIOSFODNN7EXAMPLE");
expect(setSecret).toHaveBeenCalledWith("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");
expect(setSecret).toHaveBeenCalledWith("session-token");
});

it('invalid GitHub Token', async () => {
it("invalid GitHub ID Token", async () => {
await expect(async () => {
(core.getIDToken as jest.Mock).mockResolvedValueOnce("invalid");

await index.assumeRole({
githubToken: 'ghp_dummyPersonalGitHubToken',
awsRegion: 'us-east-1',
roleToAssume: 'arn:aws:iam::123456789012:role/assume-role-test',
githubToken: "ghp_dummyPersonalGitHubToken",
awsRegion: "us-east-1",
roleToAssume: "arn:aws:iam::123456789012:role/assume-role-test",
roleDurationSeconds: 900,
roleSessionName: 'GitHubActions',
roleSessionName: "GitHubActions",
roleSessionTagging: true,
providerEndpoint: 'http://localhost:8080',
providerEndpoint: "http://localhost:8080",
useNodeId: false,
obfuscateRepository: ''
obfuscateRepository: "",
});
}).rejects.toThrow();
});
Expand Down
13 changes: 6 additions & 7 deletions action/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
moduleFileExtensions: ["js", "ts"],
testEnvironment: "node",
testMatch: ["**/*.test.ts"],
transform: {
'^.+\\.ts$': 'ts-jest'
"^.+\\.ts$": "ts-jest",
},
verbose: true
}
verbose: true,
};
1 change: 0 additions & 1 deletion action/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion action/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"format": "prettier --write **/*.ts",
"test": "jest"
},
"engines": {
"node": ">=20.0.0"
},
"author": "Fuller, Inc.",
"license": "MIT",
"dependencies": {
Expand All @@ -19,7 +22,6 @@
"@types/jest": "^29.5.5",
"@types/node": "^20.5.9",
"jest": "^29.7.0",
"jest-circus": "^29.6.0",
"prettier": "^3.0.3",
"ts-jest": "^29.1.1",
"typescript": "^5.2.2"
Expand Down
70 changes: 35 additions & 35 deletions action/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as core from '@actions/core';
import * as http from '@actions/http-client';
import * as core from "@actions/core";
import * as http from "@actions/http-client";

interface AssumeRoleParams {
githubToken: string;
Expand Down Expand Up @@ -45,47 +45,47 @@ interface AssumeRoleError {

function validateGitHubToken(token: string) {
if (token.length < 4) {
throw new Error('GITHUB_TOKEN has invalid format');
throw new Error("GITHUB_TOKEN has invalid format");
}
switch (token.substring(0, 4)) {
case 'ghp_':
case "ghp_":
// Personal Access Tokens
throw new Error(
'GITHUB_TOKEN looks like Personal Access Token. `github-token` must be `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}`.'
"GITHUB_TOKEN looks like Personal Access Token. `github-token` must be `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}`.",
);

case 'gho_':
case "gho_":
// OAuth Access tokens
throw new Error(
'GITHUB_TOKEN looks like OAuth Access token. `github-token` must be `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}`.'
"GITHUB_TOKEN looks like OAuth Access token. `github-token` must be `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}`.",
);

case 'ghu_':
case "ghu_":
// GitHub App user-to-server tokens
throw new Error(
'GITHUB_TOKEN looks like GitHub App user-to-server token. `github-token` must be `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}`.'
"GITHUB_TOKEN looks like GitHub App user-to-server token. `github-token` must be `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}`.",
);

case 'ghs_':
case "ghs_":
// GitHub App server-to-server tokens
return; // it's OK

case 'ghr_':
case "ghr_":
throw new Error(
'GITHUB_TOKEN looks like GitHub App refresh token. `github-token` must be `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}`.'
"GITHUB_TOKEN looks like GitHub App refresh token. `github-token` must be `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}`.",
);
}
// maybe Old Format Personal Access Tokens
throw new Error(
'GITHUB_TOKEN looks like Personal Access Token. `github-token` must be `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}`.'
"GITHUB_TOKEN looks like Personal Access Token. `github-token` must be `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}`.",
);
}

// comes from the article "AWS federation comes to GitHub Actions"
// https://awsteele.com/blog/2021/09/15/aws-federation-comes-to-github-actions.html
function isIdTokenAvailable(): boolean {
const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'];
const url = process.env['ACTIONS_ID_TOKEN_REQUEST_URL'];
const token = process.env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"];
const url = process.env["ACTIONS_ID_TOKEN_REQUEST_URL"];
return token && url ? true : false;
}

Expand All @@ -96,14 +96,14 @@ function assertIsDefined<T>(val: T): asserts val is NonNullable<T> {
}

export async function assumeRole(params: AssumeRoleParams) {
const {GITHUB_REPOSITORY, GITHUB_WORKFLOW, GITHUB_RUN_ID, GITHUB_ACTOR, GITHUB_SHA, GITHUB_REF} = process.env;
const { GITHUB_REPOSITORY, GITHUB_WORKFLOW, GITHUB_RUN_ID, GITHUB_ACTOR, GITHUB_SHA, GITHUB_REF } = process.env;
assertIsDefined(GITHUB_REPOSITORY);
assertIsDefined(GITHUB_WORKFLOW);
assertIsDefined(GITHUB_RUN_ID);
assertIsDefined(GITHUB_ACTOR);
assertIsDefined(GITHUB_SHA);
validateGitHubToken(params.githubToken);
const GITHUB_API_URL = process.env['GITHUB_API_URL'] || 'https://api.github.com';
const GITHUB_API_URL = process.env["GITHUB_API_URL"] || "https://api.github.com";

let idToken: string | undefined;
if (isIdTokenAvailable()) {
Expand All @@ -125,9 +125,9 @@ export async function assumeRole(params: AssumeRoleParams) {
run_id: GITHUB_RUN_ID,
workflow: GITHUB_WORKFLOW,
actor: GITHUB_ACTOR,
branch: GITHUB_REF || ''
branch: GITHUB_REF || "",
};
const client = new http.HttpClient('actions-aws-assume-role');
const client = new http.HttpClient("actions-aws-assume-role");
const result = await client.postJson<AssumeRoleResult | AssumeRoleError>(params.providerEndpoint, payload);
if (result.statusCode !== 200) {
const resp = result.result as AssumeRoleError;
Expand All @@ -145,33 +145,33 @@ export async function assumeRole(params: AssumeRoleParams) {
}

core.setSecret(resp.access_key_id);
core.exportVariable('AWS_ACCESS_KEY_ID', resp.access_key_id);
core.exportVariable("AWS_ACCESS_KEY_ID", resp.access_key_id);

core.setSecret(resp.secret_access_key);
core.exportVariable('AWS_SECRET_ACCESS_KEY', resp.secret_access_key);
core.exportVariable("AWS_SECRET_ACCESS_KEY", resp.secret_access_key);

core.setSecret(resp.session_token);
core.exportVariable('AWS_SESSION_TOKEN', resp.session_token);
core.exportVariable("AWS_SESSION_TOKEN", resp.session_token);

core.exportVariable('AWS_DEFAULT_REGION', params.awsRegion);
core.exportVariable('AWS_REGION', params.awsRegion);
core.exportVariable("AWS_DEFAULT_REGION", params.awsRegion);
core.exportVariable("AWS_REGION", params.awsRegion);
}

async function run() {
try {
const required = {
required: true
required: true,
};
const githubToken = core.getInput('github-token', required);
const awsRegion = core.getInput('aws-region', required);
const roleToAssume = core.getInput('role-to-assume', required);
const roleDurationSeconds = Number.parseInt(core.getInput('role-duration-seconds', required));
const roleSessionName = core.getInput('role-session-name', required);
const roleSessionTagging = core.getBooleanInput('role-session-tagging', required);
const githubToken = core.getInput("github-token", required);
const awsRegion = core.getInput("aws-region", required);
const roleToAssume = core.getInput("role-to-assume", required);
const roleDurationSeconds = Number.parseInt(core.getInput("role-duration-seconds", required));
const roleSessionName = core.getInput("role-session-name", required);
const roleSessionTagging = core.getBooleanInput("role-session-tagging", required);
const providerEndpoint =
core.getInput('provider-endpoint') || 'https://uw4qs7ndjj.execute-api.us-east-1.amazonaws.com/assume-role';
const useNodeId = core.getBooleanInput('use-node-id', required);
const obfuscateRepository = core.getInput('obfuscate-repository');
core.getInput("provider-endpoint") || "https://uw4qs7ndjj.execute-api.us-east-1.amazonaws.com/assume-role";
const useNodeId = core.getBooleanInput("use-node-id", required);
const obfuscateRepository = core.getInput("obfuscate-repository");
if (roleDurationSeconds <= 0 || roleDurationSeconds > 60 * 60) {
core.setFailed(`invalid role-duration-seconds ${roleDurationSeconds}, it should be from 1 to 3600`);
}
Expand All @@ -184,7 +184,7 @@ async function run() {
roleSessionTagging,
providerEndpoint,
useNodeId,
obfuscateRepository
obfuscateRepository,
});
} catch (error) {
if (error instanceof Error) {
Expand Down
Loading

0 comments on commit a6e9424

Please sign in to comment.