Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Registries Proxy: Support feeding a base64 encoded configuration #2404

Merged
merged 8 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 53 additions & 24 deletions lib/start-proxy-action.js

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

2 changes: 1 addition & 1 deletion lib/start-proxy-action.js.map

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

94 changes: 66 additions & 28 deletions src/start-proxy-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import * as actionsUtil from "./actions-util";
import * as util from "./util";
import { getActionsLogger, Logger } from "./logging";

const UPDATEJOB_PROXY = "update-job-proxy";
const UPDATEJOB_PROXY_VERSION = "v2.0.20240722180912";
Expand Down Expand Up @@ -89,46 +90,41 @@
}

async function runWrapper() {
const logger = 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) as Credential[];
const proxyLogFilePath = path.resolve(tempDir, "proxy.log");
marcogario marked this conversation as resolved.
Show resolved Hide resolved
core.saveState("proxy-log-file", proxyLogFilePath);

// Get the configuration options
const credentials = getCredentials();
logger.debug(`Credentials loaded for the following URLs:\n ${credentials.map(c => c.host).join("\n")}`)
Fixed Show fixed Hide fixed

const ca = generateCertificateAuthority();
const proxy_password = actionsUtil.getOptionalInput("proxy_password");
core.saveState("proxy-log-file", logFilePath);
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;
let tries = 5;
let subprocessError: Error | undefined = undefined;
while (tries-- > 0 && !subprocess && !subprocessError) {
subprocess = spawn(
proxyBin,
binPath,
["-addr", `${host}:${port}`, "-config", "-", "-logfile", logFilePath],
{
detached: true,
Expand All @@ -149,23 +145,65 @@
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);
}
if (subprocessError) {
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(): Credential[] {
const encodedCredentials = actionsUtil.getOptionalInput("registries_credentials");
if (encodedCredentials !== undefined) {
const credentialsStr = Buffer.from(encodedCredentials, "base64").toString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like some extra hoops to go through for the workflow file to send base64 encoded credentials. Does it make more sense to accept non-encoded credentials and only base64 encode them in the action?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, there are some extra steps, but I think they are justified.

The proxy takes a JSON object as previously defined in registry_secrets. The problem is that Actions recommends not using structured data within secrets:

To help ensure that GitHub redacts your secret in logs, avoid using structured data as the values of secrets.

Therefore, we encode it as a single string to make scrubbing possible.

The underlying reason we need a struct is that (due to the integration with default setup) we need to provide a potentially arbitrary number of credentials, and we cannot easily know which ones/how many beforehand.

return JSON.parse(credentialsStr) as Credential[];
}
core.info(`Using structured credentials.`);
const registrySecrets = actionsUtil.getOptionalInput("registry_secrets") || "[]";
return JSON.parse(registrySecrets) as Credential[];
}

// 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;
}

void runWrapper();
Loading
Loading