Skip to content

Commit

Permalink
feat(cli): support custom CA certificate bundles
Browse files Browse the repository at this point in the history
Some large orgs enforce HTTPS proxies to communicate with services, which means they often have internal certificate authorities that generate leaf certificates on the fly. This commit adds basic support for specifying a root CA certificate for trust.

Fixes #5294
  • Loading branch information
ed-at-work authored and rix0rrr committed Dec 20, 2019
1 parent ea095f0 commit ac748c1
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 18 deletions.
2 changes: 2 additions & 0 deletions packages/aws-cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ async function parseCommandLineArguments() {
.option('verbose', { type: 'boolean', alias: 'v', desc: 'Show debug logs', default: false })
.option('profile', { type: 'string', desc: 'Use the indicated AWS profile as the default environment', requiresArg: true })
.option('proxy', { type: 'string', desc: 'Use the indicated proxy. Will read from HTTPS_PROXY environment variable if not specified.', requiresArg: true })
.option('ca-bundle-path', { type: 'string', desc: 'Path to CA certificate to use when validating HTTPS requests. Will read from AWS_CA_BUNDLE environment variable if not specified.', requiresArg: true })
.option('ec2creds', { type: 'boolean', alias: 'i', default: undefined, desc: 'Force trying to fetch EC2 instance credentials. Default: guess EC2 instance status.' })
.option('version-reporting', { type: 'boolean', desc: 'Include the "AWS::CDK::Metadata" resource in synthesized templates (enabled by default)', default: undefined })
.option('path-metadata', { type: 'boolean', desc: 'Include "aws:cdk:path" CloudFormation metadata for each resource (enabled by default)', default: true })
Expand Down Expand Up @@ -107,6 +108,7 @@ async function initCommandLine() {
const aws = new SDK({
profile: argv.profile,
proxyAddress: argv.proxy,
caBundlePath: argv['ca-bundle-path'],
ec2creds: argv.ec2creds,
});

Expand Down
62 changes: 45 additions & 17 deletions packages/aws-cdk/lib/api/util/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as cxapi from '@aws-cdk/cx-api';
import * as AWS from 'aws-sdk';
import * as child_process from 'child_process';
import * as fs from 'fs-extra';
import * as https from 'https';
import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
Expand Down Expand Up @@ -53,6 +54,13 @@ export interface SDKOptions {
* @default Automatically determine.
*/
ec2creds?: boolean;

/**
* A path to a certificate bundle that contains a cert to be trusted.
*
* @default No certificate bundle
*/
caBundlePath?: string;
}

/**
Expand Down Expand Up @@ -88,23 +96,7 @@ export class SDK implements ISDK {

const defaultCredentialProvider = makeCLICompatibleCredentialProvider(options.profile, options.ec2creds);

// Find the package.json from the main toolkit
const pkg = (require.main as any).require('../package.json');
AWS.config.update({
customUserAgent: `${pkg.name}/${pkg.version}`
});

// https://aws.amazon.com/blogs/developer/using-the-aws-sdk-for-javascript-from-behind-a-proxy/
if (options.proxyAddress === undefined) {
options.proxyAddress = httpsProxyFromEnvironment();
}
if (options.proxyAddress) { // Ignore empty string on purpose
debug('Using proxy server: %s', options.proxyAddress);
AWS.config.update({
// eslint-disable-next-line @typescript-eslint/no-require-imports
httpOptions: { agent: require('proxy-agent')(options.proxyAddress) }
});
}
this.configureSDKHttpOptions(options);

this.defaultAwsAccount = new DefaultAWSAccount(defaultCredentialProvider, getCLICompatibleDefaultRegionGetter(this.profile));
this.credentialsCache = new CredentialsCache(this.defaultAwsAccount, defaultCredentialProvider);
Expand Down Expand Up @@ -196,6 +188,29 @@ export class SDK implements ISDK {
return environment;
}

private async configureSDKHttpOptions(options: SDKOptions) {
const config: {[k: string]: any} = {};
const httpOptions: {[k: string]: any} = {};
// Find the package.json from the main toolkit
const pkg = (require.main as any).require('../package.json');
config.customUserAgent = `${pkg.name}/${pkg.version}`;

// https://aws.amazon.com/blogs/developer/using-the-aws-sdk-for-javascript-from-behind-a-proxy/
options.proxyAddress = options.proxyAddress || httpsProxyFromEnvironment();
options.caBundlePath = options.caBundlePath || caBundlePathFromEnvironment();

if (options.proxyAddress) { // Ignore empty string on purpose
debug('Using proxy server: %s', options.proxyAddress);
httpOptions.proxy = options.proxyAddress;
}
if (options.caBundlePath) {
debug('Using ca bundle path: %s', options.caBundlePath);
httpOptions.agent = new https.Agent({ca: await readIfPossible(options.caBundlePath)});
}
config.httpOptions = httpOptions;

AWS.config.update(config);
}
}

/**
Expand Down Expand Up @@ -438,6 +453,19 @@ function httpsProxyFromEnvironment(): string | undefined {
return undefined;
}

/**
* Find and return a CA certificate bundle path to be passed into the SDK.
*/
function caBundlePathFromEnvironment(): string | undefined {
if (process.env.aws_ca_bundle) {
return process.env.aws_ca_bundle;
}
if (process.env.AWS_CA_BUNDLE) {
return process.env.AWS_CA_BUNDLE;
}
return undefined;
}

/**
* Return whether it looks like we'll have ECS credentials available
*/
Expand Down
1 change: 0 additions & 1 deletion packages/aws-cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@
"json-diff": "^0.5.4",
"minimatch": ">=3.0",
"promptly": "^3.0.3",
"proxy-agent": "^3.1.1",
"request": "^2.88.0",
"semver": "^7.1.1",
"source-map-support": "^0.5.16",
Expand Down

0 comments on commit ac748c1

Please sign in to comment.