Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
38 changes: 37 additions & 1 deletion packages/aws-cdk/lib/cli/proxy-agent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
import { ToolkitError } from '@aws-cdk/toolkit-lib';
import * as fs from 'fs-extra';
import { ProxyAgent } from 'proxy-agent';
import { ProxyAgent, proxies } from 'proxy-agent';
import type { IoHelper } from '../api-private';

/**
* Validate a proxy address up front.
*
* `proxy-agent` only rejects an address with a missing or unsupported protocol
* lazily, on the first request, so without `-vvv` the CLI appears to hang or
* fails later with a misleading error (e.g. missing credentials). Fail fast
* here with an actionable message instead.
*/
export function validateProxyAddress(proxyAddress: string): void {
let protocol: string;
try {
protocol = new URL(proxyAddress).protocol.replace(/:$/, '');
} catch {
throw new ToolkitError(
'InvalidProxyAddress',
`Invalid proxy address '${proxyAddress}': it must be a URL that includes a protocol, e.g. 'http://${proxyAddress}'.`,
);
}

if (!(protocol in proxies)) {
throw new ToolkitError(
'InvalidProxyAddress',
`Unsupported protocol '${protocol}' in proxy address '${proxyAddress}'. Supported protocols are: ${Object.keys(proxies).join(', ')}.`,
);
}
}

/**
* Options for proxy-agent SDKs
*/
Expand Down Expand Up @@ -29,6 +57,14 @@ export class ProxyAgentProvider {
}

public async create(options: ProxyAgentOptions) {
// Only validate when an actual proxy address was configured. When `--proxy`
// is not given the setting is unset (and can surface at runtime as an empty
// string or empty array), in which case we skip validation and let
// ProxyAgent fall back to environment-variable detection.
if (typeof options.proxyAddress === 'string' && options.proxyAddress.length > 0) {
validateProxyAddress(options.proxyAddress);
}

// Force it to use the proxy provided through the command line.
// Otherwise, let the ProxyAgent auto-detect the proxy using environment variables.
const getProxyForUrl = options.proxyAddress != null
Expand Down
49 changes: 49 additions & 0 deletions packages/aws-cdk/test/cli/proxy-agent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ProxyAgentProvider, validateProxyAddress } from '../../lib/cli/proxy-agent';
import { TestIoHost } from '../_helpers/io-host';

describe('validateProxyAddress', () => {
test.each([
'http://localhost:1234',
'https://proxy.example.com:8080',
'socks5://localhost:1080',
'pac+http://localhost/proxy.pac',
])('accepts a proxy address with a supported protocol: %s', (address) => {
expect(() => validateProxyAddress(address)).not.toThrow();
});

test.each([
'localhost:1234',
'1.2.3.4:8080',
'proxy.example.com',
])('rejects a proxy address without a usable protocol: %s', (address) => {
expect(() => validateProxyAddress(address)).toThrow(/proxy address/i);
});

test('rejects a proxy address with an unsupported protocol', () => {
expect(() => validateProxyAddress('ftp://localhost:1234')).toThrow(/Unsupported protocol/i);
});
});

describe('ProxyAgentProvider', () => {
const ioHost = new TestIoHost();

test('create() fails fast with a clear error when the proxy address has no protocol', async () => {
const provider = new ProxyAgentProvider(ioHost.asHelper('deploy'));
await expect(provider.create({ proxyAddress: 'localhost:1234' })).rejects.toThrow(/proxy address/i);
});

test('create() succeeds with a valid proxy address', async () => {
const provider = new ProxyAgentProvider(ioHost.asHelper('deploy'));
await expect(provider.create({ proxyAddress: 'http://localhost:1234' })).resolves.toBeDefined();
});

test.each([
['undefined', undefined],
['an empty string', ''],
// Settings.get() can surface an unset value as an empty array at runtime.
['an empty array', [] as unknown as string],
])('create() does not validate when the proxy address is %s (no --proxy given)', async (_desc, proxyAddress) => {
const provider = new ProxyAgentProvider(ioHost.asHelper('deploy'));
await expect(provider.create({ proxyAddress })).resolves.toBeDefined();
});
});
Loading