From 3088522ce8955f12395d0bd90fdf60abbd1365fb Mon Sep 17 00:00:00 2001 From: peterwoodworth Date: Wed, 22 Mar 2023 16:32:37 -0700 Subject: [PATCH] chore: build and set cleanup file --- action.yml | 2 +- dist/cleanup/CredentialsClient.js | 140 ++++++++++++++++++++++ dist/cleanup/cleanup/index.js | 64 ++++++++++ dist/cleanup/helpers.js | 188 ++++++++++++++++++++++++++++++ dist/index.js | 29 ++++- package.json | 2 +- src/CredentialsClient.ts | 4 + src/index.ts | 1 - test/helpers.test.ts | 4 +- 9 files changed, 428 insertions(+), 6 deletions(-) create mode 100644 dist/cleanup/CredentialsClient.js create mode 100644 dist/cleanup/cleanup/index.js create mode 100644 dist/cleanup/helpers.js diff --git a/action.yml b/action.yml index 8b99025..719dbff 100644 --- a/action.yml +++ b/action.yml @@ -4,7 +4,7 @@ description: Configures AWS credentials for use in subsequent steps in a GitHub runs: using: node16 main: dist/index.js - post: dist/cleanup/index.js + post: dist/cleanup/cleanup/index.js branding: color: orange icon: cloud diff --git a/dist/cleanup/CredentialsClient.js b/dist/cleanup/CredentialsClient.js new file mode 100644 index 0000000..fac813f --- /dev/null +++ b/dist/cleanup/CredentialsClient.js @@ -0,0 +1,140 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +exports.__esModule = true; +exports.CredentialsClient = void 0; +var core = __importStar(require("@actions/core")); +var client_sts_1 = require("@aws-sdk/client-sts"); +var node_http_handler_1 = require("@aws-sdk/node-http-handler"); +var https_proxy_agent_1 = __importDefault(require("https-proxy-agent")); +var helpers_1 = require("./helpers"); +var USER_AGENT = 'configure-aws-credentials-for-github-actions'; +var CredentialsClient = /** @class */ (function () { + function CredentialsClient(props) { + if (props.region) { + this.region = props.region; + } + else { + core.info('No region provided, using global STS endpoint'); + } + if (props.proxyServer) { + core.info('Configurint proxy handler for STS client'); + var handler = (0, https_proxy_agent_1["default"])(props.proxyServer); + this.requestHandler = new node_http_handler_1.NodeHttpHandler({ + httpAgent: handler, + httpsAgent: handler + }); + } + } + CredentialsClient.prototype.getStsClient = function () { + if (!this.stsClient) { + this.stsClient = new client_sts_1.STSClient({ + region: this.region ? this.region : undefined, + customUserAgent: USER_AGENT, + requestHandler: this.requestHandler ? this.requestHandler : undefined, + useGlobalEndpoint: this.region ? false : true + }); + } + return this.stsClient; + }; + CredentialsClient.prototype.validateCredentials = function (expectedAccessKeyId) { + return __awaiter(this, void 0, void 0, function () { + var credentials, error_1, actualAccessKeyId; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, this.loadCredentials()]; + case 1: + credentials = _a.sent(); + if (!credentials.accessKeyId) { + throw new Error('Access key ID empty after loading credentials'); + } + return [3 /*break*/, 3]; + case 2: + error_1 = _a.sent(); + throw new Error("Credentials could not be loaded, please check your action inputs: ".concat((0, helpers_1.errorMessage)(error_1))); + case 3: + actualAccessKeyId = credentials.accessKeyId; + if (expectedAccessKeyId && expectedAccessKeyId !== actualAccessKeyId) { + throw new Error('Unexpected failure: Credentials loaded by the SDK do not match the access key ID configured by the action'); + } + return [2 /*return*/]; + } + }); + }); + }; + CredentialsClient.prototype.loadCredentials = function () { + return __awaiter(this, void 0, void 0, function () { + var client; + return __generator(this, function (_a) { + client = new client_sts_1.STSClient({ + requestHandler: this.requestHandler ? this.requestHandler : undefined + }); + return [2 /*return*/, client.config.credentials()]; + }); + }); + }; + return CredentialsClient; +}()); +exports.CredentialsClient = CredentialsClient; diff --git a/dist/cleanup/cleanup/index.js b/dist/cleanup/cleanup/index.js new file mode 100644 index 0000000..74bc912 --- /dev/null +++ b/dist/cleanup/cleanup/index.js @@ -0,0 +1,64 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +exports.__esModule = true; +exports.cleanup = void 0; +var core = __importStar(require("@actions/core")); +var helpers_1 = require("../helpers"); +/** + * When the GitHub Actions job is done, clean up any environment variables that + * may have been set by the configure-aws-credentials steps in the job. + * + * Environment variables are not intended to be shared across different jobs in + * the same GitHub Actions workflow: GitHub Actions documentation states that + * each job runs in a fresh instance. However, doing our own cleanup will + * give us additional assurance that these environment variables are not shared + * with any other jobs. + */ +function cleanup() { + try { + // The GitHub Actions toolkit does not have an option to completely unset + // environment variables, so we overwrite the current value with an empty + // string. The AWS CLI and AWS SDKs will behave correctly: they treat an + // empty string value as if the environment variable does not exist. + core.exportVariable('AWS_ACCESS_KEY_ID', ''); + core.exportVariable('AWS_SECRET_ACCESS_KEY', ''); + core.exportVariable('AWS_SESSION_TOKEN', ''); + core.exportVariable('AWS_DEFAULT_REGION', ''); + core.exportVariable('AWS_REGION', ''); + } + catch (error) { + core.setFailed((0, helpers_1.errorMessage)(error)); + } +} +exports.cleanup = cleanup; +/* c8 ignore start */ +if (require.main === module) { + try { + cleanup(); + } + catch (error) { + core.setFailed((0, helpers_1.errorMessage)(error)); + } +} diff --git a/dist/cleanup/helpers.js b/dist/cleanup/helpers.js new file mode 100644 index 0000000..c262df5 --- /dev/null +++ b/dist/cleanup/helpers.js @@ -0,0 +1,188 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.isDefined = exports.errorMessage = exports.retryAndBackoff = exports.reset = exports.withsleep = exports.defaultSleep = exports.sanitizeGitHubVariables = exports.exportAccountId = exports.exportRegion = exports.exportCredentials = void 0; +var core = __importStar(require("@actions/core")); +var client_sts_1 = require("@aws-sdk/client-sts"); +var MAX_TAG_VALUE_LENGTH = 256; +var SANITIZATION_CHARACTER = '_'; +// Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets. +// Setting the credentials as secrets masks them in Github Actions logs +function exportCredentials(creds) { + if (creds === null || creds === void 0 ? void 0 : creds.AccessKeyId) { + core.setSecret(creds.AccessKeyId); + core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId); + } + if (creds === null || creds === void 0 ? void 0 : creds.SecretAccessKey) { + core.setSecret(creds.SecretAccessKey); + core.exportVariable('AWS_SECRET_ACCESS_KEY', creds.SecretAccessKey); + } + if (creds === null || creds === void 0 ? void 0 : creds.SessionToken) { + core.setSecret(creds.SessionToken); + core.exportVariable('AWS_SESSION_TOKEN', creds.SessionToken); + } + else if (process.env['AWS_SESSION_TOKEN']) { + // clear session token from previous credentials action + core.exportVariable('AWS_SESSION_TOKEN', ''); + } +} +exports.exportCredentials = exportCredentials; +function exportRegion(region) { + core.exportVariable('AWS_DEFAULT_REGION', region); + core.exportVariable('AWS_REGION', region); +} +exports.exportRegion = exportRegion; +// Obtains account ID from STS Client and sets it as output +function exportAccountId(credentialsClient, maskAccountId) { + return __awaiter(this, void 0, void 0, function () { + var client, identity, accountId; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + client = credentialsClient.getStsClient(); + return [4 /*yield*/, client.send(new client_sts_1.GetCallerIdentityCommand({}))]; + case 1: + identity = _a.sent(); + accountId = identity.Account; + if (!accountId) { + throw new Error('Could not get Account ID from STS. Did you set credentials?'); + } + if (maskAccountId) { + core.setSecret(accountId); + } + core.setOutput('aws-account-id', accountId); + return [2 /*return*/, accountId]; + } + }); + }); +} +exports.exportAccountId = exportAccountId; +// Tags have a more restrictive set of acceptable characters than GitHub environment variables can. +// This replaces anything not conforming to the tag restrictions by inverting the regular expression. +// See the AWS documentation for constraint specifics https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html. +function sanitizeGitHubVariables(name) { + var nameWithoutSpecialCharacters = name.replace(/[^\p{L}\p{Z}\p{N}_.:/=+\-@]/gu, SANITIZATION_CHARACTER); + var nameTruncated = nameWithoutSpecialCharacters.slice(0, MAX_TAG_VALUE_LENGTH); + return nameTruncated; +} +exports.sanitizeGitHubVariables = sanitizeGitHubVariables; +function defaultSleep(ms) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, new Promise(function (resolve) { return setTimeout(resolve, ms); })]; + }); + }); +} +exports.defaultSleep = defaultSleep; +var sleep = defaultSleep; +function withsleep(s) { + sleep = s; +} +exports.withsleep = withsleep; +function reset() { + sleep = defaultSleep; +} +exports.reset = reset; +// Retries the promise with exponential backoff if the error isRetryable up to maxRetries time. +function retryAndBackoff(fn, isRetryable, retries, maxRetries, base) { + if (retries === void 0) { retries = 0; } + if (maxRetries === void 0) { maxRetries = 12; } + if (base === void 0) { base = 50; } + return __awaiter(this, void 0, void 0, function () { + var err_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 5]); + return [4 /*yield*/, fn()]; + case 1: return [2 /*return*/, _a.sent()]; + case 2: + err_1 = _a.sent(); + if (!isRetryable) { + throw err_1; + } + // It's retryable, so sleep and retry. + return [4 /*yield*/, sleep(Math.random() * (Math.pow(2, retries) * base))]; + case 3: + // It's retryable, so sleep and retry. + _a.sent(); + retries += 1; + if (retries === maxRetries) { + throw err_1; + } + return [4 /*yield*/, retryAndBackoff(fn, isRetryable, retries, maxRetries, base)]; + case 4: return [2 /*return*/, _a.sent()]; + case 5: return [2 /*return*/]; + } + }); + }); +} +exports.retryAndBackoff = retryAndBackoff; +/* c8 ignore start */ +function errorMessage(error) { + return error instanceof Error ? error.message : String(error); +} +exports.errorMessage = errorMessage; +function isDefined(i) { + return i !== undefined && i !== null; +} +exports.isDefined = isDefined; +/* c8 ignore stop */ diff --git a/dist/index.js b/dist/index.js index 77b9093..733672b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -6,11 +6,35 @@ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.CredentialsClient = void 0; +const core = __importStar(__nccwpck_require__(2186)); const client_sts_1 = __nccwpck_require__(2209); const node_http_handler_1 = __nccwpck_require__(8805); const https_proxy_agent_1 = __importDefault(__nccwpck_require__(7219)); @@ -21,7 +45,11 @@ class CredentialsClient { if (props.region) { this.region = props.region; } + else { + core.info('No region provided, using global STS endpoint'); + } if (props.proxyServer) { + core.info('Configurint proxy handler for STS client'); const handler = (0, https_proxy_agent_1.default)(props.proxyServer); this.requestHandler = new node_http_handler_1.NodeHttpHandler({ httpAgent: handler, @@ -421,7 +449,6 @@ async function run() { }; // Validate and export region if (region) { - core.info('Using global STS endpoint'); if (!region.match(REGION_REGEX)) { throw new Error(`Region is not valid: ${region}`); } diff --git a/package.json b/package.json index 312a76e..1adefb7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "scripts": { "build": "tsc --project tsconfig.build.json", "lint": "eslint .", - "package": "npm run build && ncc build --license THIRD-PARTY -o dist && copyup -E dist/THIRD-PARTY . && del-cli dist/THIRD-PARTY", + "package": "npm run build && ncc build --license THIRD-PARTY -o dist && tsc src/cleanup/index.ts --skipLibCheck --esModuleInterop --outdir dist/cleanup && copyup -E dist/THIRD-PARTY . && del-cli dist/THIRD-PARTY", "test": "npm run lint && jest --verbose" }, "author": { diff --git a/src/CredentialsClient.ts b/src/CredentialsClient.ts index debb090..46b9126 100644 --- a/src/CredentialsClient.ts +++ b/src/CredentialsClient.ts @@ -1,3 +1,4 @@ +import * as core from '@actions/core'; import { STSClient } from '@aws-sdk/client-sts'; import { NodeHttpHandler } from '@aws-sdk/node-http-handler'; import proxy from 'https-proxy-agent'; @@ -18,8 +19,11 @@ export class CredentialsClient { constructor(props: CredentialsClientProps) { if (props.region) { this.region = props.region; + } else { + core.info('No region provided, using global STS endpoint'); } if (props.proxyServer) { + core.info('Configurint proxy handler for STS client'); const handler = proxy(props.proxyServer); this.requestHandler = new NodeHttpHandler({ httpAgent: handler, diff --git a/src/index.ts b/src/index.ts index c1cca20..77f82b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,7 +58,6 @@ export async function run() { // Validate and export region if (region) { - core.info('Using global STS endpoint'); if (!region.match(REGION_REGEX)) { throw new Error(`Region is not valid: ${region}`); } diff --git a/test/helpers.test.ts b/test/helpers.test.ts index ecd19b2..b603714 100644 --- a/test/helpers.test.ts +++ b/test/helpers.test.ts @@ -6,11 +6,11 @@ describe('helpers', () => { }); test('removes brackets from GitHub Actor', () => { - expect(helpers.sanitizeGithubActor('foo[bot]')).toEqual('foo_bot_'); + expect(helpers.sanitizeGitHubVariables('foo[bot]')).toEqual('foo_bot_'); }); test('removes special characters from worflow names', () => { - expect(helpers.sanitizeGithubWorkflowName('sdf234@#$%$^&*()_+{}|:"<>?')).toEqual('sdf234@__________+___:_<>?'); + expect(helpers.sanitizeGitHubVariables('sdf234@#$%$^&*()_+{}|:"<>?')).toEqual('sdf234@__________+___:_<>?'); }); test('can sleep', () => {