diff --git a/lib/start-proxy-action-post.js b/lib/start-proxy-action-post.js new file mode 100644 index 0000000000..29e0957f7e --- /dev/null +++ b/lib/start-proxy-action-post.js @@ -0,0 +1,45 @@ +"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; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * This file is the entry point for the `post:` hook of `start-proxy-action.yml`. + * It will run after the all steps in this job, in reverse order in relation to + * other `post:` hooks. + */ +const core = __importStar(require("@actions/core")); +const util_1 = require("./util"); +async function runWrapper() { + try { + const pid = core.getState("proxy-process-pid"); + if (pid) { + process.kill(Number(pid)); + } + } + catch (error) { + core.setFailed(`start-proxy post-action step failed: ${(0, util_1.wrapError)(error).message}`); + } +} +void runWrapper(); +//# sourceMappingURL=start-proxy-action-post.js.map \ No newline at end of file diff --git a/lib/start-proxy-action-post.js.map b/lib/start-proxy-action-post.js.map new file mode 100644 index 0000000000..a04148ef2b --- /dev/null +++ b/lib/start-proxy-action-post.js.map @@ -0,0 +1 @@ +{"version":3,"file":"start-proxy-action-post.js","sourceRoot":"","sources":["../src/start-proxy-action-post.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;GAIG;AACH,oDAAsC;AAEtC,iCAAmC;AAEnC,KAAK,UAAU,UAAU;IACvB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;QAC/C,IAAI,GAAG,EAAE,CAAC;YACR,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,SAAS,CACZ,wCAAwC,IAAA,gBAAS,EAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CACnE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,EAAE,CAAC"} \ No newline at end of file diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js new file mode 100644 index 0000000000..2cdf2da8b7 --- /dev/null +++ b/lib/start-proxy-action.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; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const child_process_1 = require("child_process"); +const path = __importStar(require("path")); +const core = __importStar(require("@actions/core")); +const node_forge_1 = require("node-forge"); +const actionsUtil = __importStar(require("./actions-util")); +const util = __importStar(require("./util")); +const PROXY_USER = "proxy_user"; +const KEY_SIZE = 2048; +const KEY_EXPIRY_YEARS = 2; +const CERT_SUBJECT = [ + { + name: "commonName", + value: "Dependabot Internal CA", + }, + { + name: "organizationName", + value: "GitHub ic.", + }, + { + shortName: "OU", + value: "Dependabot", + }, + { + name: "countryName", + value: "US", + }, + { + shortName: "ST", + value: "California", + }, + { + name: "localityName", + value: "San Francisco", + }, +]; +function generateCertificateAuthority() { + const keys = node_forge_1.pki.rsa.generateKeyPair(KEY_SIZE); + const cert = node_forge_1.pki.createCertificate(); + cert.publicKey = keys.publicKey; + cert.serialNumber = "01"; + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + KEY_EXPIRY_YEARS); + cert.setSubject(CERT_SUBJECT); + cert.setIssuer(CERT_SUBJECT); + cert.setExtensions([{ name: "basicConstraints", cA: true }]); + cert.sign(keys.privateKey); + const pem = node_forge_1.pki.certificateToPem(cert); + const key = node_forge_1.pki.privateKeyToPem(keys.privateKey); + return { cert: pem, key }; +} +async function runWrapper() { + const tempDir = actionsUtil.getTemporaryDirectory(); + const logFilePath = path.resolve(tempDir, "proxy.log"); + const input = actionsUtil.getOptionalInput("registry_secrets") || "[]"; + const credentials = JSON.parse(input); + const ca = generateCertificateAuthority(); + const proxy_password = actionsUtil.getOptionalInput("proxy_password"); + let proxy_auth = undefined; + if (proxy_password) { + core.setSecret(proxy_password); + proxy_auth = { + username: PROXY_USER, + password: proxy_password, + }; + } + const proxyConfig = { + all_credentials: credentials, + ca, + proxy_auth, + }; + const host = "127.0.0.1"; + const proxyBin = path.resolve(__dirname, "..", "bin", "update-job-proxy"); + let port = 49152; + try { + let subprocess = undefined; + let tries = 5; + let subprocessError = undefined; + while (tries-- > 0 && !subprocess && !subprocessError) { + subprocess = (0, child_process_1.spawn)(proxyBin, ["-addr", `${host}:${port}`, "-config", "-", "-logfile", logFilePath], { + detached: true, + stdio: ["pipe", "ignore", "ignore"], + }); + subprocess.unref(); + if (subprocess.pid) { + core.saveState("proxy-process-pid", `${subprocess.pid}`); + } + subprocess.on("error", (error) => { + subprocessError = error; + }); + subprocess.on("exit", (code) => { + if (code !== 0) { + port = Math.floor(Math.random() * (65535 - 49152) + 49152); + subprocess = undefined; + } + }); + subprocess.stdin?.write(JSON.stringify(proxyConfig)); + subprocess.stdin?.end(); + // Wait a little to allow the proxy to start + await util.delay(1000); + } + if (subprocessError) { + throw subprocessError; + } + core.info(`Proxy started on ${host}:${port}`); + core.setOutput("proxy_host", host); + core.setOutput("proxy_port", port.toString()); + core.setOutput("proxy_ca_certificate", ca.cert); + } + catch (error) { + core.setFailed(`start-proxy action failed: ${util.wrapError(error).message}`); + } +} +void runWrapper(); +//# sourceMappingURL=start-proxy-action.js.map \ No newline at end of file diff --git a/lib/start-proxy-action.js.map b/lib/start-proxy-action.js.map new file mode 100644 index 0000000000..e424dd7018 --- /dev/null +++ b/lib/start-proxy-action.js.map @@ -0,0 +1 @@ +{"version":3,"file":"start-proxy-action.js","sourceRoot":"","sources":["../src/start-proxy-action.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/start-proxy-action-post.ts b/src/start-proxy-action-post.ts new file mode 100644 index 0000000000..902ea92b6d --- /dev/null +++ b/src/start-proxy-action-post.ts @@ -0,0 +1,23 @@ +/** + * This file is the entry point for the `post:` hook of `start-proxy-action.yml`. + * It will run after the all steps in this job, in reverse order in relation to + * other `post:` hooks. + */ +import * as core from "@actions/core"; + +import { wrapError } from "./util"; + +async function runWrapper() { + try { + const pid = core.getState("proxy-process-pid"); + if (pid) { + process.kill(Number(pid)); + } + } catch (error) { + core.setFailed( + `start-proxy post-action step failed: ${wrapError(error).message}`, + ); + } +} + +void runWrapper(); diff --git a/src/start-proxy-action.ts b/src/start-proxy-action.ts new file mode 100644 index 0000000000..18b50d0e27 --- /dev/null +++ b/src/start-proxy-action.ts @@ -0,0 +1,154 @@ +import { ChildProcess, spawn } from "child_process"; +import * as path from "path"; + +import * as core from "@actions/core"; +import { pki } from "node-forge"; + +import * as actionsUtil from "./actions-util"; +import * as util from "./util"; + +const PROXY_USER = "proxy_user"; +const KEY_SIZE = 2048; +const KEY_EXPIRY_YEARS = 2; + +export type CertificateAuthority = { + cert: string; + key: string; +}; + +export type Credential = { + type: string; + host: string; + username?: string; + password?: string; + token?: string; +}; + +export type BasicAuthCredentials = { + username: string; + password: string; +}; + +export type ProxyConfig = { + all_credentials: Credential[]; + ca: CertificateAuthority; + proxy_auth?: BasicAuthCredentials; +}; + +const CERT_SUBJECT = [ + { + name: "commonName", + value: "Dependabot Internal CA", + }, + { + name: "organizationName", + value: "GitHub ic.", + }, + { + shortName: "OU", + value: "Dependabot", + }, + { + name: "countryName", + value: "US", + }, + { + shortName: "ST", + value: "California", + }, + { + name: "localityName", + value: "San Francisco", + }, +]; + +function generateCertificateAuthority(): CertificateAuthority { + const keys = pki.rsa.generateKeyPair(KEY_SIZE); + const cert = pki.createCertificate(); + cert.publicKey = keys.publicKey; + cert.serialNumber = "01"; + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setFullYear( + cert.validity.notBefore.getFullYear() + KEY_EXPIRY_YEARS, + ); + + cert.setSubject(CERT_SUBJECT); + cert.setIssuer(CERT_SUBJECT); + cert.setExtensions([{ name: "basicConstraints", cA: true }]); + cert.sign(keys.privateKey); + + const pem = pki.certificateToPem(cert); + const key = pki.privateKeyToPem(keys.privateKey); + return { cert: pem, key }; +} + +async function runWrapper() { + const tempDir = actionsUtil.getTemporaryDirectory(); + const logFilePath = path.resolve(tempDir, "proxy.log"); + const input = actionsUtil.getOptionalInput("registry_secrets") || "[]"; + const credentials = JSON.parse(input) as Credential[]; + const ca = generateCertificateAuthority(); + const proxy_password = actionsUtil.getOptionalInput("proxy_password"); + let proxy_auth: BasicAuthCredentials | undefined = undefined; + if (proxy_password) { + core.setSecret(proxy_password); + proxy_auth = { + username: PROXY_USER, + password: proxy_password, + }; + } + const proxyConfig: ProxyConfig = { + all_credentials: credentials, + ca, + proxy_auth, + }; + const host = "127.0.0.1"; + const proxyBin = path.resolve(__dirname, "..", "bin", "update-job-proxy"); + let port = 49152; + try { + let subprocess: ChildProcess | undefined = undefined; + let tries = 5; + let subprocessError: Error | undefined = undefined; + while (tries-- > 0 && !subprocess && !subprocessError) { + subprocess = spawn( + proxyBin, + ["-addr", `${host}:${port}`, "-config", "-", "-logfile", logFilePath], + { + detached: true, + stdio: ["pipe", "ignore", "ignore"], + }, + ); + subprocess.unref(); + if (subprocess.pid) { + core.saveState("proxy-process-pid", `${subprocess.pid}`); + } + subprocess.on("error", (error) => { + subprocessError = error; + }); + subprocess.on("exit", (code) => { + if (code !== 0) { + port = Math.floor(Math.random() * (65535 - 49152) + 49152); + subprocess = undefined; + } + }); + subprocess.stdin?.write(JSON.stringify(proxyConfig)); + subprocess.stdin?.end(); + // Wait a little to allow the proxy to start + await util.delay(1000); + } + if (subprocessError) { + throw subprocessError; + } + core.info(`Proxy started on ${host}:${port}`); + core.setOutput("proxy_host", host); + core.setOutput("proxy_port", port.toString()); + core.setOutput("proxy_ca_certificate", ca.cert); + } catch (error) { + core.setFailed( + `start-proxy action failed: ${util.wrapError(error).message}`, + ); + } +} + +void runWrapper(); diff --git a/start-proxy/action.yml b/start-proxy/action.yml new file mode 100644 index 0000000000..ff8782ac73 --- /dev/null +++ b/start-proxy/action.yml @@ -0,0 +1,22 @@ +name: "CodeQL: Start proxy" +description: "Start HTTP proxy server" +author: "GitHub" +inputs: + registry_secrets: + description: The URLs and credentials of package registries + required: false + default: "[]" + proxy_password: + required: false + description: The password of the proxy +outputs: + proxy_host: + description: The IP address of the proxy + proxy_port: + description: The port of the proxy + proxy_ca_certificate: + description: The proxy's internal CA certificate in PEM format +runs: + using: node20 + main: "../lib/start-proxy-action.js" + post: "../lib/start-proxy-action-post.js"