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
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,22 @@ const DEFAULT_TIMEOUT = 300000;
*/
export class AwsCliCompatible {
private readonly ioHelper: IoHelper;
private readonly requestHandler: NodeHttpHandlerOptions;
private readonly logger?: Logger;

public constructor(ioHelper: IoHelper) {
public constructor(ioHelper: IoHelper, requestHandler: NodeHttpHandlerOptions, logger?: Logger) {
this.ioHelper = ioHelper;
this.requestHandler = requestHandler;
this.logger = logger;
}

public async baseConfig(profile?: string): Promise<{ credentialProvider: AwsCredentialIdentityProvider; defaultRegion: string }> {
const credentialProvider = await this.credentialChainBuilder({
profile,
logger: this.logger,
});
const defaultRegion = await this.region(profile);
return { credentialProvider, defaultRegion };
}

/**
Expand All @@ -38,7 +51,7 @@ export class AwsCliCompatible {
options: CredentialChainOptions = {},
): Promise<AwsCredentialIdentityProvider> {
const clientConfig = {
requestHandler: await this.requestHandlerBuilder(options.httpOptions),
requestHandler: this.requestHandler,
customUserAgent: 'aws-cdk',
logger: options.logger,
};
Expand Down Expand Up @@ -115,17 +128,6 @@ export class AwsCliCompatible {
: nodeProviderChain;
}

public async requestHandlerBuilder(options: SdkHttpOptions = {}): Promise<NodeHttpHandlerOptions> {
const agent = await new ProxyAgentProvider(this.ioHelper).create(options);

return {
connectionTimeout: DEFAULT_CONNECTION_TIMEOUT,
requestTimeout: DEFAULT_TIMEOUT,
httpsAgent: agent,
httpAgent: agent,
};
}

/**
* Attempts to get the region from a number of sources and falls back to us-east-1 if no region can be found,
* as is done in the AWS CLI.
Expand Down Expand Up @@ -265,6 +267,16 @@ function shouldPrioritizeEnv() {

export interface CredentialChainOptions {
readonly profile?: string;
readonly httpOptions?: SdkHttpOptions;
readonly logger?: Logger;
}

export async function makeRequestHandler(ioHelper: IoHelper, options: SdkHttpOptions = {}): Promise<NodeHttpHandlerOptions> {
const agent = await new ProxyAgentProvider(ioHelper).create(options);

return {
connectionTimeout: DEFAULT_CONNECTION_TIMEOUT,
requestTimeout: DEFAULT_TIMEOUT,
httpsAgent: agent,
httpAgent: agent,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,13 @@ export type AssumeRoleAdditionalOptions = Partial<Omit<AssumeRoleCommandInput, '
/**
* Options for the default SDK provider
*/
export interface SdkProviderOptions {
/**
* IoHelper for messaging
*/
readonly ioHelper: IoHelper;

export interface SdkProviderOptions extends SdkProviderServices {
/**
* Profile to read from ~/.aws
*
* @default - No profile
*/
readonly profile?: string;

/**
* HTTP options for SDK
*/
readonly httpOptions?: SdkHttpOptions;

/**
* The logger for sdk calls.
*/
readonly logger?: Logger;

/**
* The plugin host to use
*
* @default - an empty plugin host
*/
readonly pluginHost?: PluginHost;
}

/**
Expand Down Expand Up @@ -133,33 +111,29 @@ export class SdkProvider {
* class `AwsCliCompatible` for the details.
*/
public static async withAwsCliCompatibleDefaults(options: SdkProviderOptions) {
const builder = new AwsCliCompatible(options.ioHelper);
callTrace(SdkProvider.withAwsCliCompatibleDefaults.name, SdkProvider.constructor.name, options.logger);
const credentialProvider = await builder.credentialChainBuilder({
profile: options.profile,
httpOptions: options.httpOptions,
logger: options.logger,
});

const region = await builder.region(options.profile);
const requestHandler = await builder.requestHandlerBuilder(options.httpOptions);
return new SdkProvider(credentialProvider, region, requestHandler, options.pluginHost ?? new PluginHost(), options.ioHelper, options.logger);
const config = await new AwsCliCompatible(options.ioHelper, options.requestHandler ?? {}, options.logger).baseConfig(options.profile);
return new SdkProvider(config.credentialProvider, config.defaultRegion, options);
}

public readonly defaultRegion: string;
private readonly defaultCredentialProvider: AwsCredentialIdentityProvider;
private readonly plugins;
private readonly requestHandler: NodeHttpHandlerOptions;
private readonly ioHelper: IoHelper;
private readonly logger?: Logger;

public constructor(
private readonly defaultCredentialProvider: AwsCredentialIdentityProvider,
/**
* Default region
*/
public readonly defaultRegion: string,
private readonly requestHandler: NodeHttpHandlerOptions = {},
pluginHost: PluginHost,
private readonly ioHelper: IoHelper,
private readonly logger?: Logger,
defaultCredentialProvider: AwsCredentialIdentityProvider,
defaultRegion: string | undefined,
services: SdkProviderServices,
) {
this.plugins = new CredentialPlugins(pluginHost, ioHelper);
this.defaultCredentialProvider = defaultCredentialProvider;
this.defaultRegion = defaultRegion ?? 'us-east-1';
this.requestHandler = services.requestHandler ?? {};
this.ioHelper = services.ioHelper;
this.logger = services.logger;
this.plugins = new CredentialPlugins(services.pluginHost ?? new PluginHost(), this.ioHelper);
}

/**
Expand Down Expand Up @@ -572,3 +546,25 @@ export async function initContextProviderSdk(aws: SdkProvider, options: ContextL

return (await aws.forEnvironment(EnvironmentUtils.make(account, region), Mode.ForReading, creds)).sdk;
}

export interface SdkProviderServices {
/**
* An IO helper for emitting messages
*/
readonly ioHelper: IoHelper;

/**
* The request handler settings
*/
readonly requestHandler?: NodeHttpHandlerOptions;

/**
* A plugin host
*/
readonly pluginHost?: PluginHost;

/**
* An SDK logger
*/
readonly logger?: Logger;
}
133 changes: 132 additions & 1 deletion packages/@aws-cdk/toolkit-lib/lib/api/aws-auth/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import type { AwsCredentialIdentityProvider } from '@smithy/types';
import type { SdkProviderServices } from '../shared-private';
import { AwsCliCompatible } from '../shared-private';

/**
* Options for the default SDK provider
*/
export interface SdkConfig {
/**
* Profile to read from ~/.aws
* The base credentials and region used to seed the Toolkit with
*
* @default BaseCredentials.awsCliCompatible()
*/
readonly baseCredentials?: BaseCredentials;

/**
* Profile to read from ~/.aws for base credentials
*
* @default - No profile
* @deprecated Use `baseCredentials` instead
*/
readonly profile?: string;

Expand Down Expand Up @@ -34,3 +45,123 @@ export interface SdkHttpOptions {
*/
readonly caBundlePath?: string;
}

export abstract class BaseCredentials {
/**
* Use no base credentials
*
* There will be no current account and no current region during synthesis. To
* successfully deploy with this set of base credentials:
*
* - The CDK app must provide concrete accounts and regions during synthesis
* - Credential plugins must be installed to provide credentials for those
* accounts.
*/
public static none(): BaseCredentials {
return new class extends BaseCredentials {
public async makeSdkConfig(): Promise<SdkBaseConfig> {
return {
credentialProvider: () => {
// eslint-disable-next-line @cdklabs/no-throw-default-error
throw new Error('No credentials available due to BaseCredentials.none()');
},
};
}

public toString() {
return 'BaseCredentials.none()';
}
};
}

/**
* Obtain base credentials and base region the same way the AWS CLI would
*
* Credentials and region will be read from the environment first, falling back
* to INI files or other sources if available.
*
* The profile name is configurable.
*/
public static awsCliCompatible(options: AwsCliCompatibleOptions = {}): BaseCredentials {
return new class extends BaseCredentials {
public makeSdkConfig(services: SdkProviderServices): Promise<SdkBaseConfig> {
const awsCli = new AwsCliCompatible(services.ioHelper, services.requestHandler ?? {}, services.logger);
return awsCli.baseConfig(options.profile);
}

public toString() {
return `BaseCredentials.awsCliCompatible(${JSON.stringify(options)})`;
}
};
}

/**
* Use a custom SDK identity provider for the base credentials
*
* If your provider uses STS calls to obtain base credentials, you must make
* sure to also configure the necessary HTTP options (like proxy and user
* agent) and the region on the STS client directly; the toolkit code cannot
* do this for you.
*/
public static custom(options: CustomBaseCredentialsOption): BaseCredentials {
return new class extends BaseCredentials {
public makeSdkConfig(): Promise<SdkBaseConfig> {
return Promise.resolve({
credentialProvider: options.provider,
defaultRegion: options.region,
});
}

public toString() {
return `BaseCredentials.custom(${JSON.stringify({
...options,
provider: '...',
})})`;
}
};
}

/**
* Make SDK config from the BaseCredentials settings
*/
public abstract makeSdkConfig(services: SdkProviderServices): Promise<SdkBaseConfig>;
}

export interface AwsCliCompatibleOptions {
/**
* The profile to read from `~/.aws/credentials`.
*
* If not supplied the environment variable AWS_PROFILE will be used.
*
* @default - Use environment variable if set.
*/
readonly profile?: string;
}

export interface CustomBaseCredentialsOption {
/**
* The credentials provider to use to obtain base credentials
*
* If your provider uses STS calls to obtain base credentials, you must make
* sure to also configure the necessary HTTP options (like proxy and user
* agent) on the STS client directly; the toolkit code cannot do this for you.
*/
readonly provider: AwsCredentialIdentityProvider;

/**
* The default region to synthesize for
*
* CDK applications can override this region. NOTE: this region will *not*
* affect any STS calls made by the given provider, if any. You need to configure
* your credential provider separately.
*
* @default 'us-east-1'
*/
readonly region?: string;
}

export interface SdkBaseConfig {
readonly credentialProvider: AwsCredentialIdentityProvider;

readonly defaultRegion?: string;
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/toolkit-lib/lib/api/shared-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from '../../../tmp-toolkit-helpers/src/api/io/private';
export * from '../../../tmp-toolkit-helpers/src/private';
export * from '../../../tmp-toolkit-helpers/src/api';
export * as cfnApi from '../../../tmp-toolkit-helpers/src/api/deployments/cfn-api';
export { makeRequestHandler } from '../../../tmp-toolkit-helpers/src/api/aws-auth/awscli-compatible';

// Context Providers
export * as contextproviders from '../../../tmp-toolkit-helpers/src/context-providers';
Loading