From ac748c1786e68774f5d0ea9cfbec439034166c40 Mon Sep 17 00:00:00 2001 From: Ed Epstein Date: Fri, 20 Dec 2019 01:58:43 -0800 Subject: [PATCH] feat(cli): support custom CA certificate bundles 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 --- packages/aws-cdk/bin/cdk.ts | 2 + packages/aws-cdk/lib/api/util/sdk.ts | 62 ++++++++++++++++++++-------- packages/aws-cdk/package.json | 1 - 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 8c3d9f3309d11..6e4eacb20293e 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -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 }) @@ -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, }); diff --git a/packages/aws-cdk/lib/api/util/sdk.ts b/packages/aws-cdk/lib/api/util/sdk.ts index 0546987b7dcd4..8ecb566450c28 100644 --- a/packages/aws-cdk/lib/api/util/sdk.ts +++ b/packages/aws-cdk/lib/api/util/sdk.ts @@ -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'; @@ -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; } /** @@ -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); @@ -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); + } } /** @@ -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 */ diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index f1c456eadae1b..de81ee942db80 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -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",