Merge pull request #2404 from github/marcogario/proxy_64

Registries Proxy: Support feeding a base64 encoded configuration
This commit is contained in:
Arthur Baars 2024-08-20 12:10:33 +02:00 committed by GitHub
commit 512e3066dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 234 additions and 71 deletions

View file

@ -28,7 +28,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
* It will run after the all steps in this job, in reverse order in relation to
* other `post:` hooks.
*/
const fs = __importStar(require("fs"));
const artifact = __importStar(require("@actions/artifact"));
const core = __importStar(require("@actions/core"));
const actionsUtil = __importStar(require("./actions-util"));
const configUtils = __importStar(require("./config-utils"));
@ -46,9 +46,18 @@ async function runWrapper() {
const config = await configUtils.getConfig(actionsUtil.getTemporaryDirectory(), core);
if ((config && config.debugMode) || core.isDebug()) {
const logFilePath = core.getState("proxy-log-file");
if (logFilePath) {
const readStream = fs.createReadStream(logFilePath);
readStream.pipe(process.stdout, { end: true });
core.info("Debug mode is on. Uploading proxy log as Actions debugging artifact...");
try {
await artifact
.create()
.uploadArtifact("proxy-log-file", [logFilePath], actionsUtil.getTemporaryDirectory(), {
continueOnError: true,
retentionDays: 7,
});
}
catch (e) {
// A failure to upload debug artifacts should not fail the entire action.
core.warning(`Failed to upload debug artifacts: ${e}`);
}
}
}

View file

@ -1 +1 @@
{"version":3,"file":"start-proxy-action-post.js","sourceRoot":"","sources":["../src/start-proxy-action-post.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;GAIG;AACH,uCAAyB;AAEzB,oDAAsC;AAEtC,4DAA8C;AAC9C,4DAA8C;AAC9C,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;IACD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,SAAS,CACxC,WAAW,CAAC,qBAAqB,EAAE,EACnC,IAAI,CACL,CAAC;IAEF,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACnD,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACpD,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,UAAU,GAAG,EAAE,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;YACpD,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,EAAE,CAAC"}
{"version":3,"file":"start-proxy-action-post.js","sourceRoot":"","sources":["../src/start-proxy-action-post.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;GAIG;AACH,4DAA8C;AAC9C,oDAAsC;AAEtC,4DAA8C;AAC9C,4DAA8C;AAC9C,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;IACD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,SAAS,CACxC,WAAW,CAAC,qBAAqB,EAAE,EACnC,IAAI,CACL,CAAC;IAEF,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACnD,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI,CACP,wEAAwE,CACzE,CAAC;QACF,IAAI,CAAC;YACH,MAAM,QAAQ;iBACX,MAAM,EAAE;iBACR,cAAc,CACb,gBAAgB,EAChB,CAAC,WAAW,CAAC,EACb,WAAW,CAAC,qBAAqB,EAAE,EACnC;gBACE,eAAe,EAAE,IAAI;gBACrB,aAAa,EAAE,CAAC;aACjB,CACF,CAAC;QACN,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,yEAAyE;YACzE,IAAI,CAAC,OAAO,CAAC,qCAAqC,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,EAAE,CAAC"}

View file

@ -29,6 +29,7 @@ const core = __importStar(require("@actions/core"));
const toolcache = __importStar(require("@actions/tool-cache"));
const node_forge_1 = require("node-forge");
const actionsUtil = __importStar(require("./actions-util"));
const logging_1 = require("./logging");
const util = __importStar(require("./util"));
const UPDATEJOB_PROXY = "update-job-proxy";
const UPDATEJOB_PROXY_VERSION = "v2.0.20240722180912";
@ -79,40 +80,36 @@ function generateCertificateAuthority() {
return { cert: pem, key };
}
async function runWrapper() {
const logger = (0, logging_1.getActionsLogger)();
// Setup logging for the proxy
const tempDir = actionsUtil.getTemporaryDirectory();
const logFilePath = path.resolve(tempDir, "proxy.log");
const input = actionsUtil.getOptionalInput("registry_secrets") || "[]";
const credentials = JSON.parse(input);
const proxyLogFilePath = path.resolve(tempDir, "proxy.log");
core.saveState("proxy-log-file", proxyLogFilePath);
// Get the configuration options
const credentials = getCredentials(logger);
logger.info(`Credentials loaded for the following registries:\n ${credentials
.map((c) => credentialToStr(c))
.join("\n")}`);
const ca = generateCertificateAuthority();
const proxy_password = actionsUtil.getOptionalInput("proxy_password");
core.saveState("proxy-log-file", logFilePath);
let proxy_auth = undefined;
if (proxy_password) {
proxy_auth = {
username: PROXY_USER,
password: proxy_password,
};
}
const proxyAuth = getProxyAuth();
const proxyConfig = {
all_credentials: credentials,
ca,
proxy_auth,
proxy_auth: proxyAuth,
};
// Start the Proxy
const proxyBin = await getProxyBinaryPath();
await startProxy(proxyBin, proxyConfig, proxyLogFilePath, logger);
}
async function startProxy(binPath, config, logFilePath, logger) {
const host = "127.0.0.1";
let proxyBin = toolcache.find(UPDATEJOB_PROXY, UPDATEJOB_PROXY_VERSION);
if (!proxyBin) {
const temp = await toolcache.downloadTool(UPDATEJOB_PROXY_URL);
const extracted = await toolcache.extractTar(temp);
proxyBin = await toolcache.cacheDir(extracted, UPDATEJOB_PROXY, UPDATEJOB_PROXY_VERSION);
}
proxyBin = path.join(proxyBin, UPDATEJOB_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], {
subprocess = (0, child_process_1.spawn)(binPath, ["-addr", `${host}:${port}`, "-config", "-", "-logfile", logFilePath], {
detached: true,
stdio: ["pipe", "ignore", "ignore"],
});
@ -130,7 +127,7 @@ async function runWrapper() {
subprocess = undefined;
}
});
subprocess.stdin?.write(JSON.stringify(proxyConfig));
subprocess.stdin?.write(JSON.stringify(config));
subprocess.stdin?.end();
// Wait a little to allow the proxy to start
await util.delay(1000);
@ -139,14 +136,75 @@ async function runWrapper() {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw subprocessError;
}
core.info(`Proxy started on ${host}:${port}`);
logger.info(`Proxy started on ${host}:${port}`);
core.setOutput("proxy_host", host);
core.setOutput("proxy_port", port.toString());
core.setOutput("proxy_ca_certificate", ca.cert);
core.setOutput("proxy_ca_certificate", config.ca.cert);
}
catch (error) {
core.setFailed(`start-proxy action failed: ${util.wrapError(error).message}`);
}
}
// getCredentials returns registry credentials from action inputs.
// It prefers `registries_credentials` over `registry_secrets`.
// If neither is set, it returns an empty array.
function getCredentials(logger) {
const registriesCredentials = actionsUtil.getOptionalInput("registries_credentials");
const registrySecrets = actionsUtil.getOptionalInput("registry_secrets");
let credentialsStr;
if (registriesCredentials !== undefined) {
logger.info(`Using registries_credentials input.`);
credentialsStr = Buffer.from(registriesCredentials, "base64").toString();
}
else if (registrySecrets !== undefined) {
logger.info(`Using registry_secrets input.`);
credentialsStr = registrySecrets;
}
else {
logger.info(`No credentials defined.`);
return [];
}
// Parse and validate the credentials
const parsed = JSON.parse(credentialsStr);
const out = [];
for (const e of parsed) {
if (e.url === undefined && e.host === undefined) {
throw new Error("Invalid credentials - must specify host or url");
}
out.push({
type: e.type,
host: e.host,
url: e.url,
username: e.username,
password: e.password,
token: e.token,
});
}
return out;
}
// getProxyAuth returns the authentication information for the proxy itself.
function getProxyAuth() {
const proxy_password = actionsUtil.getOptionalInput("proxy_password");
if (proxy_password) {
return {
username: PROXY_USER,
password: proxy_password,
};
}
return;
}
async function getProxyBinaryPath() {
let proxyBin = toolcache.find(UPDATEJOB_PROXY, UPDATEJOB_PROXY_VERSION);
if (!proxyBin) {
const temp = await toolcache.downloadTool(UPDATEJOB_PROXY_URL);
const extracted = await toolcache.extractTar(temp);
proxyBin = await toolcache.cacheDir(extracted, UPDATEJOB_PROXY, UPDATEJOB_PROXY_VERSION);
}
proxyBin = path.join(proxyBin, UPDATEJOB_PROXY);
return proxyBin;
}
function credentialToStr(c) {
return `Type: ${c.type}; Host: ${c.host}; Url: ${c.url} Username: ${c.username}; Password: ${c.password !== undefined}; Token: ${c.token !== undefined}`;
}
void runWrapper();
//# sourceMappingURL=start-proxy-action.js.map

File diff suppressed because one or more lines are too long

View file

@ -3,8 +3,7 @@
* It will run after the all steps in this job, in reverse order in relation to
* other `post:` hooks.
*/
import * as fs from "fs";
import * as artifact from "@actions/artifact";
import * as core from "@actions/core";
import * as actionsUtil from "./actions-util";
@ -29,9 +28,24 @@ async function runWrapper() {
if ((config && config.debugMode) || core.isDebug()) {
const logFilePath = core.getState("proxy-log-file");
if (logFilePath) {
const readStream = fs.createReadStream(logFilePath);
readStream.pipe(process.stdout, { end: true });
core.info(
"Debug mode is on. Uploading proxy log as Actions debugging artifact...",
);
try {
await artifact
.create()
.uploadArtifact(
"proxy-log-file",
[logFilePath],
actionsUtil.getTemporaryDirectory(),
{
continueOnError: true,
retentionDays: 7,
},
);
} catch (e) {
// A failure to upload debug artifacts should not fail the entire action.
core.warning(`Failed to upload debug artifacts: ${e}`);
}
}
}

View file

@ -6,6 +6,7 @@ import * as toolcache from "@actions/tool-cache";
import { pki } from "node-forge";
import * as actionsUtil from "./actions-util";
import { getActionsLogger, Logger } from "./logging";
import * as util from "./util";
const UPDATEJOB_PROXY = "update-job-proxy";
@ -16,25 +17,26 @@ const PROXY_USER = "proxy_user";
const KEY_SIZE = 2048;
const KEY_EXPIRY_YEARS = 2;
export type CertificateAuthority = {
type CertificateAuthority = {
cert: string;
key: string;
};
export type Credential = {
type Credential = {
type: string;
host: string;
host?: string;
url?: string;
username?: string;
password?: string;
token?: string;
};
export type BasicAuthCredentials = {
type BasicAuthCredentials = {
username: string;
password: string;
};
export type ProxyConfig = {
type ProxyConfig = {
all_credentials: Credential[];
ca: CertificateAuthority;
proxy_auth?: BasicAuthCredentials;
@ -89,38 +91,42 @@ function generateCertificateAuthority(): CertificateAuthority {
}
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");
core.saveState("proxy-log-file", logFilePath);
const logger = getActionsLogger();
// Setup logging for the proxy
const tempDir = actionsUtil.getTemporaryDirectory();
const proxyLogFilePath = path.resolve(tempDir, "proxy.log");
core.saveState("proxy-log-file", proxyLogFilePath);
// Get the configuration options
const credentials = getCredentials(logger);
logger.info(
`Credentials loaded for the following registries:\n ${credentials
.map((c) => credentialToStr(c))
.join("\n")}`,
);
const ca = generateCertificateAuthority();
const proxyAuth = getProxyAuth();
let proxy_auth: BasicAuthCredentials | undefined = undefined;
if (proxy_password) {
proxy_auth = {
username: PROXY_USER,
password: proxy_password,
};
}
const proxyConfig: ProxyConfig = {
all_credentials: credentials,
ca,
proxy_auth,
proxy_auth: proxyAuth,
};
// Start the Proxy
const proxyBin = await getProxyBinaryPath();
await startProxy(proxyBin, proxyConfig, proxyLogFilePath, logger);
}
async function startProxy(
binPath: string,
config: ProxyConfig,
logFilePath: string,
logger: Logger,
) {
const host = "127.0.0.1";
let proxyBin = toolcache.find(UPDATEJOB_PROXY, UPDATEJOB_PROXY_VERSION);
if (!proxyBin) {
const temp = await toolcache.downloadTool(UPDATEJOB_PROXY_URL);
const extracted = await toolcache.extractTar(temp);
proxyBin = await toolcache.cacheDir(
extracted,
UPDATEJOB_PROXY,
UPDATEJOB_PROXY_VERSION,
);
}
proxyBin = path.join(proxyBin, UPDATEJOB_PROXY);
let port = 49152;
try {
let subprocess: ChildProcess | undefined = undefined;
@ -128,7 +134,7 @@ async function runWrapper() {
let subprocessError: Error | undefined = undefined;
while (tries-- > 0 && !subprocess && !subprocessError) {
subprocess = spawn(
proxyBin,
binPath,
["-addr", `${host}:${port}`, "-config", "-", "-logfile", logFilePath],
{
detached: true,
@ -149,7 +155,7 @@ async function runWrapper() {
subprocess = undefined;
}
});
subprocess.stdin?.write(JSON.stringify(proxyConfig));
subprocess.stdin?.write(JSON.stringify(config));
subprocess.stdin?.end();
// Wait a little to allow the proxy to start
await util.delay(1000);
@ -158,10 +164,10 @@ async function runWrapper() {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw subprocessError;
}
core.info(`Proxy started on ${host}:${port}`);
logger.info(`Proxy started on ${host}:${port}`);
core.setOutput("proxy_host", host);
core.setOutput("proxy_port", port.toString());
core.setOutput("proxy_ca_certificate", ca.cert);
core.setOutput("proxy_ca_certificate", config.ca.cert);
} catch (error) {
core.setFailed(
`start-proxy action failed: ${util.wrapError(error).message}`,
@ -169,4 +175,77 @@ async function runWrapper() {
}
}
// getCredentials returns registry credentials from action inputs.
// It prefers `registries_credentials` over `registry_secrets`.
// If neither is set, it returns an empty array.
function getCredentials(logger: Logger): Credential[] {
const registriesCredentials = actionsUtil.getOptionalInput(
"registries_credentials",
);
const registrySecrets = actionsUtil.getOptionalInput("registry_secrets");
let credentialsStr: string;
if (registriesCredentials !== undefined) {
logger.info(`Using registries_credentials input.`);
credentialsStr = Buffer.from(registriesCredentials, "base64").toString();
} else if (registrySecrets !== undefined) {
logger.info(`Using registry_secrets input.`);
credentialsStr = registrySecrets;
} else {
logger.info(`No credentials defined.`);
return [];
}
// Parse and validate the credentials
const parsed = JSON.parse(credentialsStr) as Credential[];
const out: Credential[] = [];
for (const e of parsed) {
if (e.url === undefined && e.host === undefined) {
throw new Error("Invalid credentials - must specify host or url");
}
out.push({
type: e.type,
host: e.host,
url: e.url,
username: e.username,
password: e.password,
token: e.token,
});
}
return out;
}
// getProxyAuth returns the authentication information for the proxy itself.
function getProxyAuth(): BasicAuthCredentials | undefined {
const proxy_password = actionsUtil.getOptionalInput("proxy_password");
if (proxy_password) {
return {
username: PROXY_USER,
password: proxy_password,
};
}
return;
}
async function getProxyBinaryPath(): Promise<string> {
let proxyBin = toolcache.find(UPDATEJOB_PROXY, UPDATEJOB_PROXY_VERSION);
if (!proxyBin) {
const temp = await toolcache.downloadTool(UPDATEJOB_PROXY_URL);
const extracted = await toolcache.extractTar(temp);
proxyBin = await toolcache.cacheDir(
extracted,
UPDATEJOB_PROXY,
UPDATEJOB_PROXY_VERSION,
);
}
proxyBin = path.join(proxyBin, UPDATEJOB_PROXY);
return proxyBin;
}
function credentialToStr(c: Credential): string {
return `Type: ${c.type}; Host: ${c.host}; Url: ${c.url} Username: ${
c.username
}; Password: ${c.password !== undefined}; Token: ${c.token !== undefined}`;
}
void runWrapper();

View file

@ -1,11 +1,14 @@
name: "CodeQL: Start proxy"
description: "[Experimental] Start HTTP proxy server"
description: "[Experimental] Start HTTP proxy server. This action is for internal GitHub used only and will change without notice."
author: "GitHub"
inputs:
registry_secrets:
description: The URLs and credentials of package registries
required: false
default: "[]"
registries_credentials:
description: Base64 encoded JSON configuration for the URLs and credentials of the package registries
required: false
proxy_password:
required: false
description: The password of the proxy