diff --git a/.eslintrc.js b/.eslintrc.js index 12788eeb7..8ef4c8183 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { plugins: ['@typescript-eslint'], extends: [ 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], diff --git a/.prettierrc.js b/.prettierrc.js index 6e79ec831..075c84d44 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -18,7 +18,6 @@ module.exports = { arrowParens: 'always', bracketSpacing: true, endOfLine: 'auto', - jsxBracketSameLine: true, jsxSingleQuote: true, printWidth: 80, quoteProps: 'consistent', diff --git a/README.md b/README.md index d3c225397..277cb6354 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Or integrate natively with other Google Cloud GitHub Actions: ## 📢 NOTICE -**Previously this repository contained the code for ALL of the GCP GithHub Actions. Now each +**Previously this repository contained the code for ALL of the GCP GithHub Actions. Now each action has it's own repo and this repo is only for setup-gcloud** ### Use google-github-actions/setup-gcloud @@ -77,7 +77,7 @@ steps: | `service_account_key` | _optional_ | | The service account key which will be used for authentication credentials. This key should be [created](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) and stored as a [secret](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets). It can be encoded as a [Base64](https://en.wikipedia.org/wiki/Base64) string or as JSON. | | `service_account_email` | _optional_ | | Service account email address to use for authentication. This is required for legacy .p12 keys but can be omitted for JSON keys. This is usually of the format `@.iam.gserviceaccount.com`. | | `export_default_credentials`| _optional_ |`false`| Exports the path to [Default Application Credentials][dac] as the environment variable `GOOGLE_APPLICATION_CREDENTIALS` to be available in later steps. Google Cloud services automatically use this environment variable to find credentials. | -| `credentials_file_path` | _optional_ | `GITHUB_WORKSPACE` | Only valid when `export_default_credentials` is `true`. Sets the path at which the credentials should be written. | +| `credentials_file_path` | _optional_ | (temporary file) | Only valid when `export_default_credentials` is `true`. Sets the path at which the credentials should be written. **WARNING:** If you write credentials outside of the GitHub Actions temporary path, they may be cached on self-hosted runners and exposed in future runs! See [Sharing Credentials](#sharing-credentials) for more information. | ## Example Workflows @@ -93,6 +93,14 @@ code to [App Engine](https://cloud.google.com/appengine), a fully managed server * [Cloud Build](./example-workflows/cloud-build/README.md): An example workflow that uses GitHub Actions to build a container image with [Cloud Build](https://cloud.google.com/cloud-build). + +## Sharing Credentials + +If `export_default_credentials` is true, this GitHub Action will automatically export the credentials to be available in future steps in the job. By default, the credentials are exported to a temporary file that is automatically cleaned up when the job finishes. This file is available to all steps in the job. + +If you want to export credentials to be available to all jobs in a workflow, you can choose a custom `credentials_file_path` that resides in `GITHUB_WORKSPACE`. However, we do **NOT** recommend this approach, as this directory is not automatically cleaned up and can leak credentials files over time. + + ## Contributing See [CONTRIBUTING](CONTRIBUTING.md). diff --git a/dist/index.js b/dist/index.js index 556505a4e..de9debf44 100644 --- a/dist/index.js +++ b/dist/index.js @@ -10097,58 +10097,6 @@ module.exports = require("tls"); /***/ }), -/***/ 22: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; - -var _validate = _interopRequireDefault(__webpack_require__(78)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function parse(uuid) { - if (!(0, _validate.default)(uuid)) { - throw TypeError('Invalid UUID'); - } - - let v; - const arr = new Uint8Array(16); // Parse ########-....-....-....-............ - - arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24; - arr[1] = v >>> 16 & 0xff; - arr[2] = v >>> 8 & 0xff; - arr[3] = v & 0xff; // Parse ........-####-....-....-............ - - arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8; - arr[5] = v & 0xff; // Parse ........-....-####-....-............ - - arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8; - arr[7] = v & 0xff; // Parse ........-....-....-####-............ - - arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8; - arr[9] = v & 0xff; // Parse ........-....-....-....-############ - // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) - - arr[10] = (v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000 & 0xff; - arr[11] = v / 0x100000000 & 0xff; - arr[12] = v >>> 24 & 0xff; - arr[13] = v >>> 16 & 0xff; - arr[14] = v >>> 8 & 0xff; - arr[15] = v & 0xff; - return arr; -} - -var _default = parse; -exports.default = _default; - -/***/ }), - /***/ 31: /***/ (function(module, exports, __webpack_require__) { @@ -10284,116 +10232,6 @@ exports._readLinuxVersionFile = _readLinuxVersionFile; /***/ }), -/***/ 62: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -Object.defineProperty(exports, "v1", { - enumerable: true, - get: function () { - return _v.default; - } -}); -Object.defineProperty(exports, "v3", { - enumerable: true, - get: function () { - return _v2.default; - } -}); -Object.defineProperty(exports, "v4", { - enumerable: true, - get: function () { - return _v3.default; - } -}); -Object.defineProperty(exports, "v5", { - enumerable: true, - get: function () { - return _v4.default; - } -}); -Object.defineProperty(exports, "NIL", { - enumerable: true, - get: function () { - return _nil.default; - } -}); -Object.defineProperty(exports, "version", { - enumerable: true, - get: function () { - return _version.default; - } -}); -Object.defineProperty(exports, "validate", { - enumerable: true, - get: function () { - return _validate.default; - } -}); -Object.defineProperty(exports, "stringify", { - enumerable: true, - get: function () { - return _stringify.default; - } -}); -Object.defineProperty(exports, "parse", { - enumerable: true, - get: function () { - return _parse.default; - } -}); - -var _v = _interopRequireDefault(__webpack_require__(893)); - -var _v2 = _interopRequireDefault(__webpack_require__(209)); - -var _v3 = _interopRequireDefault(__webpack_require__(733)); - -var _v4 = _interopRequireDefault(__webpack_require__(384)); - -var _nil = _interopRequireDefault(__webpack_require__(327)); - -var _version = _interopRequireDefault(__webpack_require__(695)); - -var _validate = _interopRequireDefault(__webpack_require__(78)); - -var _stringify = _interopRequireDefault(__webpack_require__(411)); - -var _parse = _interopRequireDefault(__webpack_require__(22)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/***/ }), - -/***/ 78: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; - -var _regex = _interopRequireDefault(__webpack_require__(456)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function validate(uuid) { - return typeof uuid === 'string' && _regex.default.test(uuid); -} - -var _default = validate; -exports.default = _default; - -/***/ }), - /***/ 82: /***/ (function(__unusedmodule, exports) { @@ -10774,29 +10612,6 @@ if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { exports.debug = debug; // for test -/***/ }), - -/***/ 209: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; - -var _v = _interopRequireDefault(__webpack_require__(212)); - -var _md = _interopRequireDefault(__webpack_require__(803)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const v3 = (0, _v.default)('v3', 0x30, _md.default); -var _default = v3; -exports.default = _default; - /***/ }), /***/ 211: @@ -10806,91 +10621,6 @@ module.exports = require("https"); /***/ }), -/***/ 212: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = _default; -exports.URL = exports.DNS = void 0; - -var _stringify = _interopRequireDefault(__webpack_require__(411)); - -var _parse = _interopRequireDefault(__webpack_require__(22)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function stringToBytes(str) { - str = unescape(encodeURIComponent(str)); // UTF8 escape - - const bytes = []; - - for (let i = 0; i < str.length; ++i) { - bytes.push(str.charCodeAt(i)); - } - - return bytes; -} - -const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; -exports.DNS = DNS; -const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; -exports.URL = URL; - -function _default(name, version, hashfunc) { - function generateUUID(value, namespace, buf, offset) { - if (typeof value === 'string') { - value = stringToBytes(value); - } - - if (typeof namespace === 'string') { - namespace = (0, _parse.default)(namespace); - } - - if (namespace.length !== 16) { - throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)'); - } // Compute hash of namespace and value, Per 4.3 - // Future: Use spread syntax when supported on all platforms, e.g. `bytes = - // hashfunc([...namespace, ... value])` - - - let bytes = new Uint8Array(16 + value.length); - bytes.set(namespace); - bytes.set(value, namespace.length); - bytes = hashfunc(bytes); - bytes[6] = bytes[6] & 0x0f | version; - bytes[8] = bytes[8] & 0x3f | 0x80; - - if (buf) { - offset = offset || 0; - - for (let i = 0; i < 16; ++i) { - buf[offset + i] = bytes[i]; - } - - return buf; - } - - return (0, _stringify.default)(bytes); - } // Function#name is not settable on some platforms (#270) - - - try { - generateUUID.name = name; // eslint-disable-next-line no-empty - } catch (err) {} // For CommonJS default export support - - - generateUUID.DNS = DNS; - generateUUID.URL = URL; - return generateUUID; -} - -/***/ }), - /***/ 213: /***/ (function(module) { @@ -10996,21 +10726,6 @@ const setup_gcloud_1 = __webpack_require__(738); (0, setup_gcloud_1.run)(); -/***/ }), - -/***/ 327: -/***/ (function(__unusedmodule, exports) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _default = '00000000-0000-0000-0000-000000000000'; -exports.default = _default; - /***/ }), /***/ 357: @@ -11020,75 +10735,6 @@ module.exports = require("assert"); /***/ }), -/***/ 384: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; - -var _v = _interopRequireDefault(__webpack_require__(212)); - -var _sha = _interopRequireDefault(__webpack_require__(498)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const v5 = (0, _v.default)('v5', 0x50, _sha.default); -var _default = v5; -exports.default = _default; - -/***/ }), - -/***/ 411: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; - -var _validate = _interopRequireDefault(__webpack_require__(78)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/** - * Convert array of 16 byte values to UUID string format of the form: - * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - */ -const byteToHex = []; - -for (let i = 0; i < 256; ++i) { - byteToHex.push((i + 0x100).toString(16).substr(1)); -} - -function stringify(arr, offset = 0) { - // Note: Be careful editing this code! It's been tuned for performance - // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 - const uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); // Consistency check for valid UUID. If this throws, it's likely due to one - // of the following: - // - One or more input array values don't map to a hex octet (leading to - // "undefined" in the uuid) - // - Invalid input values for the RFC `version` or `variant` fields - - if (!(0, _validate.default)(uuid)) { - throw TypeError('Stringified UUID is invalid'); - } - - return uuid; -} - -var _default = stringify; -exports.default = _default; - -/***/ }), - /***/ 413: /***/ (function(module, __unusedexports, __webpack_require__) { @@ -11203,21 +10849,6 @@ function escapeProperty(s) { /***/ }), -/***/ 456: -/***/ (function(__unusedmodule, exports) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; -exports.default = _default; - -/***/ }), - /***/ 470: /***/ (function(__unusedmodule, exports, __webpack_require__) { @@ -11537,36 +11168,6 @@ exports.getIDToken = getIDToken; /***/ }), -/***/ 498: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; - -var _crypto = _interopRequireDefault(__webpack_require__(417)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function sha1(bytes) { - if (Array.isArray(bytes)) { - bytes = Buffer.from(bytes); - } else if (typeof bytes === 'string') { - bytes = Buffer.from(bytes, 'utf8'); - } - - return _crypto.default.createHash('sha1').update(bytes).digest(); -} - -var _default = sha1; -exports.default = _default; - -/***/ }), - /***/ 533: /***/ (function(__unusedmodule, exports, __webpack_require__) { @@ -14654,78 +14255,6 @@ exports.getCmdPath = getCmdPath; /***/ }), -/***/ 695: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; - -var _validate = _interopRequireDefault(__webpack_require__(78)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function version(uuid) { - if (!(0, _validate.default)(uuid)) { - throw TypeError('Invalid UUID'); - } - - return parseInt(uuid.substr(14, 1), 16); -} - -var _default = version; -exports.default = _default; - -/***/ }), - -/***/ 733: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; - -var _rng = _interopRequireDefault(__webpack_require__(844)); - -var _stringify = _interopRequireDefault(__webpack_require__(411)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function v4(options, buf, offset) { - options = options || {}; - - const rnds = options.random || (options.rng || _rng.default)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` - - - rnds[6] = rnds[6] & 0x0f | 0x40; - rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided - - if (buf) { - offset = offset || 0; - - for (let i = 0; i < 16; ++i) { - buf[offset + i] = rnds[i]; - } - - return buf; - } - - return (0, _stringify.default)(rnds); -} - -var _default = v4; -exports.default = _default; - -/***/ }), - /***/ 738: /***/ (function(__unusedmodule, exports, __webpack_require__) { @@ -14778,15 +14307,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.run = exports.GCLOUD_METRICS_LABEL = exports.GCLOUD_METRICS_ENV_VAR = void 0; +exports.run = exports.writeSecureFile = exports.GCLOUD_METRICS_LABEL = exports.GCLOUD_METRICS_ENV_VAR = void 0; const core = __importStar(__webpack_require__(470)); const toolCache = __importStar(__webpack_require__(533)); const setup_cloud_sdk_1 = __webpack_require__(10); const fs_1 = __webpack_require__(747); const path_1 = __importDefault(__webpack_require__(622)); -const uuid_1 = __webpack_require__(62); +const crypto_1 = __importDefault(__webpack_require__(417)); exports.GCLOUD_METRICS_ENV_VAR = 'CLOUDSDK_METRICS_ENVIRONMENT'; exports.GCLOUD_METRICS_LABEL = 'github-actions-setup-gcloud'; +/** + * writeSecureFile writes a file to disk in a given directory with a + * random name. + * + * @param outputPath Path in which to create random file in. + * @param data Data to write to file. + * @returns Path to written file. + */ +function writeSecureFile(outputPath, data) { + return __awaiter(this, void 0, void 0, function* () { + // Write the file as 0640 so the owner has RW, group as R, and the file is + // otherwise unreadable. Also write with EXCL to prevent a symlink attack. + yield fs_1.promises.writeFile(outputPath, data, { mode: 0o640, flag: 'wx' }); + return outputPath; + }); +} +exports.writeSecureFile = writeSecureFile; function run() { return __awaiter(this, void 0, void 0, function* () { core.exportVariable(exports.GCLOUD_METRICS_ENV_VAR, exports.GCLOUD_METRICS_LABEL); @@ -14818,29 +14364,35 @@ function run() { else { yield (0, setup_cloud_sdk_1.authenticateGcloudSDK)(serviceAccountKey); } - // Export credentials if requested - these credentials must be exported in - // the shared workspace directory, since the filesystem must be shared among - // all steps. - const exportCreds = core.getInput('export_default_credentials'); - if (String(exportCreds).toLowerCase() === 'true') { - let credsPath = core.getInput('credentials_file_path'); - if (!credsPath) { - const credsDir = process.env.GITHUB_WORKSPACE; - if (!credsDir) { - throw new Error('No path for credentials. Set credentials_file_path or process.env.GITHUB_WORKSPACE'); - } - credsPath = path_1.default.join(credsDir, (0, uuid_1.v4)()); - } - const serviceAccountKeyObj = (0, setup_cloud_sdk_1.parseServiceAccountKey)(serviceAccountKey); - yield fs_1.promises.writeFile(credsPath, JSON.stringify(serviceAccountKeyObj, null, 2)); - core.exportVariable('GCLOUD_PROJECT', projectId ? projectId : serviceAccountKeyObj.project_id); // If projectId is set export it, else export projectId from SA - core.exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credsPath); - core.info('Successfully exported Default Application Credentials'); + // Export credentials if requested - these credentials are exported in the + // shared temp directory, so the filesystem is available among all steps. + const exportCreds = core.getBooleanInput('export_default_credentials'); + if (!exportCreds) { + return; } + let credsPath = core.getInput('credentials_file_path'); + if (!credsPath) { + const runnerTempDir = process.env.RUNNER_TEMP; + if (!runnerTempDir) { + throw new Error('$RUNNER_TEMP is not set'); + } + // Generate a random filename to store the credential. 12 bytes is 24 + // characters in hex. It's not the ideal entropy, but we have to be under + // the 255 character limit for Windows filenames (which includes their + // entire leading path). + const uniqueName = crypto_1.default.randomBytes(12).toString('hex'); + credsPath = path_1.default.join(runnerTempDir, uniqueName); + } + const serviceAccountKeyObj = (0, setup_cloud_sdk_1.parseServiceAccountKey)(serviceAccountKey); + yield writeSecureFile(credsPath, JSON.stringify(serviceAccountKeyObj)); + // If projectId is set export it, else export projectId from SA + core.exportVariable('GCLOUD_PROJECT', projectId ? projectId : serviceAccountKeyObj.project_id); + core.exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credsPath); + core.info('Successfully exported Default Application Credentials'); } catch (error) { const msg = error instanceof Error ? error.message : error; - core.setFailed(`'setup-gcloud' failed to be installed: ${msg}`); + core.setFailed(`google-github-actions/setup-gcloud failed with: ${msg}`); } }); } @@ -14954,36 +14506,6 @@ module.exports = require("stream"); /***/ }), -/***/ 803: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; - -var _crypto = _interopRequireDefault(__webpack_require__(417)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function md5(bytes) { - if (Array.isArray(bytes)) { - bytes = Buffer.from(bytes); - } else if (typeof bytes === 'string') { - bytes = Buffer.from(bytes, 'utf8'); - } - - return _crypto.default.createHash('md5').update(bytes).digest(); -} - -var _default = md5; -exports.default = _default; - -/***/ }), - /***/ 835: /***/ (function(module) { @@ -14991,151 +14513,6 @@ module.exports = require("url"); /***/ }), -/***/ 844: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = rng; - -var _crypto = _interopRequireDefault(__webpack_require__(417)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const rnds8Pool = new Uint8Array(256); // # of random values to pre-allocate - -let poolPtr = rnds8Pool.length; - -function rng() { - if (poolPtr > rnds8Pool.length - 16) { - _crypto.default.randomFillSync(rnds8Pool); - - poolPtr = 0; - } - - return rnds8Pool.slice(poolPtr, poolPtr += 16); -} - -/***/ }), - -/***/ 893: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; - -var _rng = _interopRequireDefault(__webpack_require__(844)); - -var _stringify = _interopRequireDefault(__webpack_require__(411)); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -// **`v1()` - Generate time-based UUID** -// -// Inspired by https://github.com/LiosK/UUID.js -// and http://docs.python.org/library/uuid.html -let _nodeId; - -let _clockseq; // Previous uuid creation time - - -let _lastMSecs = 0; -let _lastNSecs = 0; // See https://github.com/uuidjs/uuid for API details - -function v1(options, buf, offset) { - let i = buf && offset || 0; - const b = buf || new Array(16); - options = options || {}; - let node = options.node || _nodeId; - let clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; // node and clockseq need to be initialized to random values if they're not - // specified. We do this lazily to minimize issues related to insufficient - // system entropy. See #189 - - if (node == null || clockseq == null) { - const seedBytes = options.random || (options.rng || _rng.default)(); - - if (node == null) { - // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) - node = _nodeId = [seedBytes[0] | 0x01, seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5]]; - } - - if (clockseq == null) { - // Per 4.2.2, randomize (14 bit) clockseq - clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff; - } - } // UUID timestamps are 100 nano-second units since the Gregorian epoch, - // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so - // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs' - // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00. - - - let msecs = options.msecs !== undefined ? options.msecs : Date.now(); // Per 4.2.1.2, use count of uuid's generated during the current clock - // cycle to simulate higher resolution clock - - let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; // Time since last uuid creation (in msecs) - - const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; // Per 4.2.1.2, Bump clockseq on clock regression - - if (dt < 0 && options.clockseq === undefined) { - clockseq = clockseq + 1 & 0x3fff; - } // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new - // time interval - - - if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) { - nsecs = 0; - } // Per 4.2.1.2 Throw error if too many uuids are requested - - - if (nsecs >= 10000) { - throw new Error("uuid.v1(): Can't create more than 10M uuids/sec"); - } - - _lastMSecs = msecs; - _lastNSecs = nsecs; - _clockseq = clockseq; // Per 4.1.4 - Convert from unix epoch to Gregorian epoch - - msecs += 12219292800000; // `time_low` - - const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; - b[i++] = tl >>> 24 & 0xff; - b[i++] = tl >>> 16 & 0xff; - b[i++] = tl >>> 8 & 0xff; - b[i++] = tl & 0xff; // `time_mid` - - const tmh = msecs / 0x100000000 * 10000 & 0xfffffff; - b[i++] = tmh >>> 8 & 0xff; - b[i++] = tmh & 0xff; // `time_high_and_version` - - b[i++] = tmh >>> 24 & 0xf | 0x10; // include version - - b[i++] = tmh >>> 16 & 0xff; // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant) - - b[i++] = clockseq >>> 8 | 0x80; // `clock_seq_low` - - b[i++] = clockseq & 0xff; // `node` - - for (let n = 0; n < 6; ++n) { - b[i + n] = node[n]; - } - - return buf || (0, _stringify.default)(b); -} - -var _default = v1; -exports.default = _default; - -/***/ }), - /***/ 950: /***/ (function(__unusedmodule, exports) { diff --git a/package-lock.json b/package-lock.json index ab53dcfc1..426a00aa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,15 +11,13 @@ "dependencies": { "@actions/core": "^1.6.0", "@actions/tool-cache": "^1.7.1", - "@google-github-actions/setup-cloud-sdk": "^0.2.0", - "uuid": "^8.3.2" + "@google-github-actions/setup-cloud-sdk": "^0.2.0" }, "devDependencies": { "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/node": "^16.7.1", "@types/sinon": "^10.0.2", - "@types/uuid": "^8.3.1", "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", "@zeit/ncc": "^0.22.3", @@ -76,15 +74,6 @@ "uuid": "^3.3.2" } }, - "node_modules/@actions/tool-cache/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -299,12 +288,6 @@ "@sinonjs/fake-timers": "^7.1.0" } }, - "node_modules/@types/uuid": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.3.tgz", - "integrity": "sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA==", - "dev": true - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz", @@ -2575,11 +2558,12 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "bin/uuid" } }, "node_modules/v8-compile-cache": { @@ -2761,13 +2745,6 @@ "@actions/io": "^1.1.1", "semver": "^6.1.0", "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - } } }, "@cspotcode/source-map-consumer": { @@ -2962,12 +2939,6 @@ "@sinonjs/fake-timers": "^7.1.0" } }, - "@types/uuid": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.3.tgz", - "integrity": "sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA==", - "dev": true - }, "@typescript-eslint/eslint-plugin": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz", @@ -4573,9 +4544,9 @@ } }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "v8-compile-cache": { "version": "2.3.0", diff --git a/package.json b/package.json index cd2a7beb8..3788de145 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,13 @@ "dependencies": { "@actions/core": "^1.6.0", "@actions/tool-cache": "^1.7.1", - "@google-github-actions/setup-cloud-sdk": "^0.2.0", - "uuid": "^8.3.2" + "@google-github-actions/setup-cloud-sdk": "^0.2.0" }, "devDependencies": { "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/node": "^16.7.1", "@types/sinon": "^10.0.2", - "@types/uuid": "^8.3.1", "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", "@zeit/ncc": "^0.22.3", diff --git a/src/setup-gcloud.ts b/src/setup-gcloud.ts index cf193bf3a..48150f919 100644 --- a/src/setup-gcloud.ts +++ b/src/setup-gcloud.ts @@ -26,11 +26,29 @@ import { } from '@google-github-actions/setup-cloud-sdk'; import { promises as fs } from 'fs'; import path from 'path'; -import { v4 as uuidv4 } from 'uuid'; +import crypto from 'crypto'; export const GCLOUD_METRICS_ENV_VAR = 'CLOUDSDK_METRICS_ENVIRONMENT'; export const GCLOUD_METRICS_LABEL = 'github-actions-setup-gcloud'; +/** + * writeSecureFile writes a file to disk in a given directory with a + * random name. + * + * @param outputPath Path in which to create random file in. + * @param data Data to write to file. + * @returns Path to written file. + */ +export async function writeSecureFile( + outputPath: string, + data: string, +): Promise { + // Write the file as 0640 so the owner has RW, group as R, and the file is + // otherwise unreadable. Also write with EXCL to prevent a symlink attack. + await fs.writeFile(outputPath, data, { mode: 0o640, flag: 'wx' }); + return outputPath; +} + export async function run(): Promise { core.exportVariable(GCLOUD_METRICS_ENV_VAR, GCLOUD_METRICS_LABEL); try { @@ -63,39 +81,41 @@ export async function run(): Promise { await authenticateGcloudSDK(serviceAccountKey); } - // Export credentials if requested - these credentials must be exported in - // the shared workspace directory, since the filesystem must be shared among - // all steps. - const exportCreds = core.getInput('export_default_credentials'); - if (String(exportCreds).toLowerCase() === 'true') { + // Export credentials if requested - these credentials are exported in the + // shared temp directory, so the filesystem is available among all steps. + const exportCreds = core.getBooleanInput('export_default_credentials'); + if (exportCreds) { let credsPath = core.getInput('credentials_file_path'); - if (!credsPath) { - const credsDir = process.env.GITHUB_WORKSPACE; - if (!credsDir) { - throw new Error( - 'No path for credentials. Set credentials_file_path or process.env.GITHUB_WORKSPACE', - ); + const runnerTempDir = process.env.RUNNER_TEMP; + if (!runnerTempDir) { + throw new Error('$RUNNER_TEMP is not set'); } - credsPath = path.join(credsDir, uuidv4()); + // Generate a random filename to store the credential. 12 bytes is 24 + // characters in hex. It's not the ideal entropy, but we have to be under + // the 255 character limit for Windows filenames (which includes their + // entire leading path). + const uniqueName = crypto.randomBytes(12).toString('hex'); + credsPath = path.join(runnerTempDir, uniqueName); } const serviceAccountKeyObj = parseServiceAccountKey(serviceAccountKey); - - await fs.writeFile( + await writeSecureFile( credsPath, JSON.stringify(serviceAccountKeyObj, null, 2), // Print to file as string w/ indents ); + + // If projectId is set export it, else export projectId from SA core.exportVariable( 'GCLOUD_PROJECT', projectId ? projectId : serviceAccountKeyObj.project_id, - ); // If projectId is set export it, else export projectId from SA + ); core.exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credsPath); core.info('Successfully exported Default Application Credentials'); } } catch (error) { const msg = error instanceof Error ? error.message : error; - core.setFailed(`'setup-gcloud' failed to be installed: ${msg}`); + core.setFailed(`google-github-actions/setup-gcloud failed with: ${msg}`); } } diff --git a/tests/setup-gcloud.test.ts b/tests/setup-gcloud.test.ts index c1f48ccfd..01bddcb26 100644 --- a/tests/setup-gcloud.test.ts +++ b/tests/setup-gcloud.test.ts @@ -34,7 +34,6 @@ const fakeInputs: { [key: string]: string } = { version: '999', project_id: 'test', service_account_key: 'abc', - export_default_credentials: 'false', credentials_file_path: '/creds', }; @@ -46,6 +45,7 @@ describe('#run', function () { beforeEach(async function () { this.stubs = { getInput: sinon.stub(core, 'getInput').callsFake(getInputMock), + getBooleanInput: sinon.stub(core, 'getBooleanInput').returns(false), exportVariable: sinon.stub(core, 'exportVariable'), setFailed: sinon.stub(core, 'setFailed'), installGcloudSDK: sinon.stub(setupGcloud, 'installGcloudSDK'), @@ -133,60 +133,29 @@ describe('#run', function () { expect(this.stubs.authenticateGcloudSDK.withArgs('key').callCount).to.eq(1); }); - it('writes default credentials to disk and exports the path if export_default_credentials=true', async function () { - this.stubs.env.value({ GITHUB_WORKSPACE: '/usr/workspace' }); - this.stubs.getInput.withArgs('export_default_credentials').returns('true'); - this.stubs.getInput.withArgs('credentials_file_path').returns(''); - this.stubs.getInput.withArgs('service_account_key').returns('key'); - this.stubs.parseServiceAccountKey.withArgs('key').returns({ json: true }); + it('writes default credentials to RUNNER_TEMP if export_default_credentials is true', async function () { + this.stubs.env.value({ RUNNER_TEMP: os.tmpdir() }); + this.stubs.getBooleanInput + .withArgs('export_default_credentials') + .returns(true); + this.stubs.getInput + .withArgs('service_account_key') + .returns('{"json":"key"}'); await run(); - expect(this.stubs.parseServiceAccountKey.withArgs('key').callCount).to.eq( - 1, - ); - - let expectedPath; - if (os.platform() === 'win32') { - expectedPath = sinon.match('\\usr\\workspace'); - } else { - expectedPath = sinon.match('/usr/workspace'); - } - - expect( - this.stubs.writeFile.withArgs(expectedPath, sinon.match.string).callCount, - ).to.eq(1); - + expect(this.stubs.parseServiceAccountKey.callCount).to.eq(1); + expect(this.stubs.writeFile.callCount).to.eq(1); expect( - this.stubs.exportVariable.withArgs( - 'GOOGLE_APPLICATION_CREDENTIALS', - expectedPath, - ).callCount, + this.stubs.exportVariable.withArgs('GOOGLE_APPLICATION_CREDENTIALS') + .callCount, ).to.eq(1); }); - it('works if export_default_credentials is a boolean', async function () { - this.stubs.getInput.withArgs('export_default_credentials').returns(true); - this.stubs.getInput.withArgs('credentials_file_path').returns('/'); - this.stubs.getInput.withArgs('service_account_key').returns('key'); - - await run(); - - expect(this.stubs.writeFile.callCount).to.eq(1); - }); - - it('works if export_default_credentials is all caps', async function () { - this.stubs.getInput.withArgs('export_default_credentials').returns('TRUE'); - this.stubs.getInput.withArgs('credentials_file_path').returns('/'); - this.stubs.getInput.withArgs('service_account_key').returns('key'); - - await run(); - - expect(this.stubs.writeFile.callCount).to.eq(1); - }); - it('writes credentials to the given path if provided', async function () { - this.stubs.getInput.withArgs('export_default_credentials').returns('true'); + this.stubs.getBooleanInput + .withArgs('export_default_credentials') + .returns(true); this.stubs.getInput.withArgs('credentials_file_path').returns('/usr/creds'); await run(); @@ -200,8 +169,10 @@ describe('#run', function () { ).to.eq(1); }); - it('throws an error if credentials_file_path is not provided and GITHUB_WORKSPACE is not set', async function () { - this.stubs.getInput.withArgs('export_default_credentials').returns('true'); + it('throws an error if credentials_file_path is not provided and RUNNER_TEMP is not set', async function () { + this.stubs.getBooleanInput + .withArgs('export_default_credentials') + .returns('true'); this.stubs.getInput.withArgs('credentials_file_path').returns(''); await run(); expect(this.stubs.setFailed.callCount).to.eq(1); diff --git a/tsconfig.json b/tsconfig.json index c00f749bc..6b24dd3d7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,7 @@ "es6" ], "outDir": "./dist", - "rootDirs": ["./src"], + "rootDir": "./src", "strict": true, "noImplicitAny": true, "esModuleInterop": true