Skip to content

Commit

Permalink
feat(aws-cdk): add proxy support (#666)
Browse files Browse the repository at this point in the history
A proxy can be specified using the `--proxy` command-line argument, or is automatically read from the `HTTPS_PROXY` or `https_proxy` environment variables.

Also fix recent breakage in the SDK usage of CredentialProviderChain.

Fixes #645.
  • Loading branch information
rix0rrr authored Sep 5, 2018
1 parent 29dc3c2 commit 0c7bcd8
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 26 deletions.
36 changes: 23 additions & 13 deletions docs/src/tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,31 @@ Below are the actions you can take on your CDK app:
init [TEMPLATE] Create a new, empty CDK project from a template.
Invoked without TEMPLATE, the app template will be
used.
doctor Check your set-up for potential problems
Options:
--help Show help [boolean]
--app, -a REQUIRED: Command-line of cloud executable (e.g. "node
bin/my-app.js") [string]
--context, -c Add contextual string parameter. [array]
--plugin, -p Name or path of a node package that extend the CDK features.
Can be specified multiple times [array]
--rename Rename stack name if different then the one defined in the
cloud executable [string]
--trace Print trace for stack warnings [boolean]
--strict Do not construct stacks with warnings [boolean]
--json, -j Use JSON output instead of YAML [boolean]
--verbose, -v Show debug logs [boolean]
--version Show version number [boolean]
--app, -a REQUIRED: Command-line for executing your CDK app (e.g.
"node bin/my-app.js") [string]
--context, -c Add contextual string parameter. [array]
--plugin, -p Name or path of a node package that extend the CDK
features. Can be specified multiple times [array]
--rename Rename stack name if different then the one defined in
the cloud executable [string]
--trace Print trace for stack warnings [boolean]
--strict Do not construct stacks with warnings [boolean]
--ignore-errors Ignores synthesis errors, which will likely produce an
invalid output [boolean] [default: false]
--json, -j Use JSON output instead of YAML [boolean]
--verbose, -v Show debug logs [boolean]
--profile Use the indicated AWS profile as the default environment
[string]
--proxy Use the indicated proxy. Will read from HTTPS_PROXY
environment variable if not specified. [string]
--version-reporting Disable insersion of the CDKMetadata resource in
synthesized templates [boolean]
--version Show version number [boolean]
--help Show help [boolean]
If your app has a single stack, there is no need to specify the stack name
Expand Down
3 changes: 2 additions & 1 deletion packages/aws-cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async function parseCommandLineArguments() {
.option('json', { type: 'boolean', alias: 'j', desc: 'Use JSON output instead of YAML' })
.option('verbose', { type: 'boolean', alias: 'v', desc: 'Show debug logs' })
.option('profile', { type: 'string', desc: 'Use the indicated AWS profile as the default environment' })
.option('proxy', { type: 'string', desc: 'Use the indicated proxy. Will read from HTTPS_PROXY environment variable if not specified.' })
// tslint:disable-next-line:max-line-length
.option('version-reporting', { type: 'boolean', desc: 'Disable insersion of the CDKMetadata resource in synthesized templates', default: undefined })
.command([ 'list', 'ls' ], 'Lists all stacks in the app', yargs => yargs
Expand Down Expand Up @@ -108,7 +109,7 @@ async function initCommandLine() {

debug('Command line arguments:', argv);

const aws = new SDK(argv.profile);
const aws = new SDK(argv.profile, argv.proxy);

const availableContextProviders: contextplugins.ProviderMap = {
'availability-zones': new contextplugins.AZContextProviderPlugin(aws),
Expand Down
59 changes: 47 additions & 12 deletions packages/aws-cdk/lib/api/util/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,50 +19,61 @@ import { SharedIniFile } from './sdk_ini_file';
* to the requested account.
*/
export class SDK {
private readonly userAgent: string;
private readonly defaultAwsAccount: DefaultAWSAccount;
private readonly credentialsCache: CredentialsCache;
private readonly defaultClientArgs: any = {};

constructor(private readonly profile: string | undefined) {
// Find the package.json from the main toolkit
const pkg = (require.main as any).require('../package.json');
this.userAgent = `${pkg.name}/${pkg.version}`;

constructor(private readonly profile: string | undefined, proxyAddress: string | undefined) {
const defaultCredentialProvider = makeCLICompatibleCredentialProvider(profile);

this.defaultAwsAccount = new DefaultAWSAccount(defaultCredentialProvider);
this.credentialsCache = new CredentialsCache(this.defaultAwsAccount, defaultCredentialProvider);

// Find the package.json from the main toolkit
const pkg = (require.main as any).require('../package.json');
this.defaultClientArgs.userAgent = `${pkg.name}/${pkg.version}`;

// https://aws.amazon.com/blogs/developer/using-the-aws-sdk-for-javascript-from-behind-a-proxy/
if (proxyAddress === undefined) {
proxyAddress = httpsProxyFromEnvironment();
}
if (proxyAddress) { // Ignore empty string on purpose
debug('Using proxy server: %s', proxyAddress);
this.defaultClientArgs.httpOptions = {
agent: require('proxy-agent')(proxyAddress)
};
}
}

public async cloudFormation(environment: Environment, mode: Mode): Promise<AWS.CloudFormation> {
return new AWS.CloudFormation({
region: environment.region,
credentials: await this.credentialsCache.get(environment.account, mode),
customUserAgent: this.userAgent
...this.defaultClientArgs
});
}

public async ec2(awsAccountId: string | undefined, region: string | undefined, mode: Mode): Promise<AWS.EC2> {
return new AWS.EC2({
region,
credentials: await this.credentialsCache.get(awsAccountId, mode),
customUserAgent: this.userAgent
...this.defaultClientArgs
});
}

public async ssm(awsAccountId: string | undefined, region: string | undefined, mode: Mode): Promise<AWS.SSM> {
return new AWS.SSM({
region,
credentials: await this.credentialsCache.get(awsAccountId, mode),
customUserAgent: this.userAgent
...this.defaultClientArgs
});
}

public async s3(environment: Environment, mode: Mode): Promise<AWS.S3> {
return new AWS.S3({
region: environment.region,
credentials: await this.credentialsCache.get(environment.account, mode),
customUserAgent: this.userAgent
...this.defaultClientArgs
});
}

Expand Down Expand Up @@ -109,7 +120,11 @@ class CredentialsCache {
const defaultAccount = await this.defaultAwsAccount.get();
if (!awsAccountId || awsAccountId === defaultAccount) {
debug(`Using default AWS SDK credentials for account ${awsAccountId}`);
return this.defaultCredentialProvider;

// CredentialProviderChain extends Credentials, but that is a lie.
// https://github.com/aws/aws-sdk-js/issues/2235
// Call resolve() instead.
return (await this.defaultCredentialProvider).resolvePromise();
}

const triedSources: CredentialProviderSource[] = [];
Expand All @@ -122,7 +137,14 @@ class CredentialsCache {
triedSources.push(source);
if (!(await source.canProvideCredentials(awsAccountId))) { continue; }
debug(`Using ${source.name} credentials for account ${awsAccountId}`);
return await source.getProvider(awsAccountId, mode);
const providerOrCreds = await source.getProvider(awsAccountId, mode);

// Backwards compatibility: if the plugin returns a ProviderChain, resolve that chain.
// Otherwise it must have returned credentials.
if ((providerOrCreds as any).resolvePromise) {
return await (providerOrCreds as any).resolvePromise();
}
return providerOrCreds;
}
const sourceNames = ['default credentials'].concat(triedSources.map(s => s.name)).join(', ');
throw new Error(`Need to perform AWS calls for account ${awsAccountId}, but no credentials found. Tried: ${sourceNames}.`);
Expand Down Expand Up @@ -256,3 +278,16 @@ async function getCLICompatibleDefaultRegion(profile: string | undefined): Promi

return region;
}

/**
* Find and return the configured HTTPS proxy address
*/
function httpsProxyFromEnvironment(): string | undefined {
if (process.env.https_proxy) {
return process.env.https_proxy;
}
if (process.env.HTTPS_PROXY) {
return process.env.HTTPS_PROXY;
}
return undefined;
}
1 change: 1 addition & 0 deletions packages/aws-cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"json-diff": "^0.3.1",
"minimatch": ">=3.0",
"promptly": "^0.2.0",
"proxy-agent": "^3.0.1",
"request": "^2.83.0",
"source-map-support": "^0.5.6",
"yamljs": "^0.2.0",
Expand Down

0 comments on commit 0c7bcd8

Please sign in to comment.