mirror of
https://github.com/aws-actions/configure-aws-credentials.git
synced 2026-09-01 05:45:06 +09:00
fix: mask proxy URL credentials in job logs
Basic-auth userinfo in the http-proxy input or HTTP(S)_PROXY environment variables was never registered as a secret, so error messages carrying the proxy URL printed the credentials unmasked in the job log.
This commit is contained in:
@@ -4,7 +4,7 @@ import { defaultProvider } from '@aws-sdk/credential-provider-node';
|
||||
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
||||
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
||||
import { ProxyAgent } from 'proxy-agent';
|
||||
import { buildCustomUserAgent, errorMessage, getCallerIdentity } from './helpers';
|
||||
import { buildCustomUserAgent, errorMessage, getCallerIdentity, maskProxyCredentials } from './helpers';
|
||||
import { ProxyResolver } from './ProxyResolver';
|
||||
|
||||
if (!process.env.AWS_EXECUTION_ENV) {
|
||||
@@ -32,6 +32,7 @@ export class CredentialsClient {
|
||||
}
|
||||
if (props.proxyServer) {
|
||||
info('Configuring proxy handler for STS client');
|
||||
maskProxyCredentials(props.proxyServer);
|
||||
const proxyOptions: { httpProxy: string; httpsProxy: string; noProxy?: string } = {
|
||||
httpProxy: props.proxyServer,
|
||||
httpsProxy: props.proxyServer,
|
||||
|
||||
@@ -193,6 +193,29 @@ export function toCredentialIdentity(creds?: Partial<Credentials>): AwsCredentia
|
||||
};
|
||||
}
|
||||
|
||||
// Registers any userinfo embedded in a proxy URL as secrets so it is masked in job logs.
|
||||
// First the literal proxy string, then any username/password components if parseable.
|
||||
// If the username/password is percent-encoded, the decoded form is also masked.
|
||||
export function maskProxyCredentials(proxyServer: string): void {
|
||||
core.setSecret(proxyServer);
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(proxyServer);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
for (const part of [url.username, url.password]) {
|
||||
if (!part) continue;
|
||||
core.setSecret(part);
|
||||
try {
|
||||
const decoded = decodeURIComponent(part);
|
||||
if (decoded !== part) core.setSecret(decoded);
|
||||
} catch (_) {
|
||||
// malformed percent-encoding; the raw form is already masked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -126,6 +126,29 @@ describe('Configure AWS Credentials helpers', {}, () => {
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', '');
|
||||
});
|
||||
|
||||
describe('maskProxyCredentials', {}, () => {
|
||||
it('masks username and password embedded in a proxy URL', {}, () => {
|
||||
helpers.maskProxyCredentials('http://user:secretpass@proxy.example.com:8080');
|
||||
expect(core.setSecret).toHaveBeenCalledWith('user');
|
||||
expect(core.setSecret).toHaveBeenCalledWith('secretpass');
|
||||
});
|
||||
|
||||
it('masks both encoded and decoded forms of the credentials', {}, () => {
|
||||
helpers.maskProxyCredentials('http://user:p%40ss@proxy.example.com:8080');
|
||||
expect(core.setSecret).toHaveBeenCalledWith('p%40ss');
|
||||
expect(core.setSecret).toHaveBeenCalledWith('p@ss');
|
||||
});
|
||||
|
||||
it('masks the whole value even without embedded credentials or when unparseable', {}, () => {
|
||||
helpers.maskProxyCredentials('http://proxy.example.com:8080');
|
||||
expect(core.setSecret).toHaveBeenCalledWith('http://proxy.example.com:8080');
|
||||
helpers.maskProxyCredentials('not a url');
|
||||
expect(core.setSecret).toHaveBeenCalledWith('not a url');
|
||||
// no username/password parts, so exactly one mask per call
|
||||
expect(core.setSecret).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateAccountId', {}, () => {
|
||||
it('enforces the allow-list even when the first element is empty', {}, () => {
|
||||
expect(() => helpers.validateAccountId(['', '999999999999'], '111111111111')).toThrow(/does not match/);
|
||||
|
||||
@@ -1267,6 +1267,21 @@ describe('Configure AWS Credentials', {}, () => {
|
||||
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('masks credentials embedded in the proxy URL', async () => {
|
||||
vi.mocked(core.getInput).mockImplementation(
|
||||
mocks.getInput({
|
||||
...mocks.GH_OIDC_INPUTS,
|
||||
'http-proxy': 'http://user:secretpass@proxy.example.com:8080',
|
||||
}),
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
expect(core.setSecret).toHaveBeenCalledWith('user');
|
||||
expect(core.setSecret).toHaveBeenCalledWith('secretpass');
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('AWS Profile Support', {}, () => {
|
||||
|
||||
Reference in New Issue
Block a user