From 95c0332395d1203e8b00fda153fe08e70d0387c5 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 28 Aug 2020 13:18:26 +0200 Subject: [PATCH] fix(cli): AssumeRole profiles require a [default] profile (#10032) This works around a bug in the AWS SDK for JS that only surfaced when we switched to `AWS_STS_REGIONAL_ENDPOINTS=regional`, requiring a `[default]` profile with a region for all users. The bug was that the INI-file AssumeRole provider would ignore the region in the profile, and always fall back to the region in: * The profile specified using `$AWS_PROFILE` (which we don't use). * Otherwise the region in the `[default]` profile (which a user may or may not have). Traditionally it didn't really matter whether the STS client got a region or not because it would always connect to `us-east-1` no matter what, but when we switched to `AWS_STS_REGIONAL_ENDPOINTS=regional`, it became illegal to not have a region. Fix the upstream bug by basically replicating the important parts of `SharedIniFileCredentials` of the AWS SDK in our codebase and patching the bug. Reported upstreeam as https://github.com/aws/aws-sdk-js/issues/3418 Fixes #9937 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/api/aws-auth/aws-sdk-inifile.ts | 161 ++++++++ .../lib/api/aws-auth/awscli-compatible.ts | 4 +- .../aws-cdk/test/api/sdk-provider.test.ts | 378 ++++++++++-------- 3 files changed, 380 insertions(+), 163 deletions(-) create mode 100644 packages/aws-cdk/lib/api/aws-auth/aws-sdk-inifile.ts diff --git a/packages/aws-cdk/lib/api/aws-auth/aws-sdk-inifile.ts b/packages/aws-cdk/lib/api/aws-auth/aws-sdk-inifile.ts new file mode 100644 index 0000000000000..af12393fff381 --- /dev/null +++ b/packages/aws-cdk/lib/api/aws-auth/aws-sdk-inifile.ts @@ -0,0 +1,161 @@ +import * as AWS from 'aws-sdk'; + + +/** + * Hack-fix + * + * There are a number of issues in the upstream version of SharedIniFileCredentials + * that need fixing: + * + * 1. The upstream aws-sdk contains an incorrect instantiation of an `AWS.STS` + * client, which *should* have taken the region from the requested profile + * but doesn't. It will use the region from the default profile, which + * may not exist, defaulting to `us-east-1` (since we switched to + * AWS_STS_REGIONAL_ENDPOINTS=regional, that default is not even allowed anymore + * and the absence of a default region will lead to an error). + * + * 2. The simple fix is to get the region from the `config` file. profiles + * are made up of a combination of `credentials` and `config`, and the region is + * generally in `config` with the rest in `credentials`. However, a bug in + * `getProfilesFromSharedConfig` overwrites ALL `config` data with `credentials` + * data, so we also need to do extra work to fish the `region` out of the config. + * + * See https://github.com/aws/aws-sdk-js/issues/3418 for all the gory details. + */ +export class PatchedSharedIniFileCredentials extends AWS.SharedIniFileCredentials { + declare private profile: string; + declare private filename: string; + declare private disableAssumeRole: boolean; + declare private options: Record; + declare private roleArn: string; + declare private httpOptions?: AWS.HTTPOptions; + declare private tokenCodeFn?: (mfaSerial: string, callback: (err?: Error, token?: string) => void) => void; + + public loadRoleProfile( + creds: Record>, + roleProfile: Record, + callback: (err?: Error, data?: any) => void) { + + // Need to duplicate the whole implementation here -- the function is long and has been written in + // such a way that there are no small monkey patches possible. + + if (this.disableAssumeRole) { + throw (AWS as any).util.error( + new Error('Role assumption profiles are disabled. ' + + 'Failed to load profile ' + this.profile + + ' from ' + creds.filename), + { code: 'SharedIniFileCredentialsProviderFailure' }, + ); + } + + var self = this; + var roleArn = roleProfile.role_arn; + var roleSessionName = roleProfile.role_session_name; + var externalId = roleProfile.external_id; + var mfaSerial = roleProfile.mfa_serial; + var sourceProfileName = roleProfile.source_profile; + + if (!sourceProfileName) { + throw (AWS as any).util.error( + new Error('source_profile is not set using profile ' + this.profile), + { code: 'SharedIniFileCredentialsProviderFailure' }, + ); + } + + var sourceProfileExistanceTest = creds[sourceProfileName]; + + if (typeof sourceProfileExistanceTest !== 'object') { + throw (AWS as any).util.error( + new Error('source_profile ' + sourceProfileName + ' using profile ' + + this.profile + ' does not exist'), + { code: 'SharedIniFileCredentialsProviderFailure' }, + ); + } + + var sourceCredentials = new AWS.SharedIniFileCredentials( + (AWS as any).util.merge(this.options || {}, { + profile: sourceProfileName, + preferStaticCredentials: true, + }), + ); + + // --------- THIS IS NEW ---------------------- + const profiles = loadProfilesProper(this.filename); + const region = profiles[this.profile]?.region ?? profiles.default?.region ?? 'us-east-1'; + // --------- /THIS IS NEW ---------------------- + + this.roleArn = roleArn; + var sts = new AWS.STS({ + credentials: sourceCredentials, + region, + httpOptions: this.httpOptions, + }); + + var roleParams: AWS.STS.AssumeRoleRequest = { + RoleArn: roleArn, + RoleSessionName: roleSessionName || 'aws-sdk-js-' + Date.now(), + }; + + if (externalId) { + roleParams.ExternalId = externalId; + } + + if (mfaSerial && self.tokenCodeFn) { + roleParams.SerialNumber = mfaSerial; + self.tokenCodeFn(mfaSerial, function(err, token) { + if (err) { + var message; + if (err instanceof Error) { + message = err.message; + } else { + message = err; + } + callback( + (AWS as any).util.error( + new Error('Error fetching MFA token: ' + message), + { code: 'SharedIniFileCredentialsProviderFailure' }, + )); + return; + } + + roleParams.TokenCode = token; + sts.assumeRole(roleParams, callback); + }); + return; + } + sts.assumeRole(roleParams, callback); + + } +} + +/** + * A function to load profiles from disk that MERGES credentials and config instead of overwriting + * + * @see https://github.com/aws/aws-sdk-js/blob/5ae5a7d7d24d1000dbc089cc15f8ed2c7b06c542/lib/util.js#L956 + */ +function loadProfilesProper(filename: string) { + const util = (AWS as any).util; // Does exists even though there aren't any typings for it + const iniLoader = util.iniLoader; + const profiles: Record> = {}; + let profilesFromConfig: Record> = {}; + if (process.env[util.configOptInEnv]) { + profilesFromConfig = iniLoader.loadFrom({ + isConfig: true, + filename: process.env[util.sharedConfigFileEnv], + }); + } + var profilesFromCreds: Record> = iniLoader.loadFrom({ + filename: filename || + (process.env[util.configOptInEnv] && process.env[util.sharedCredentialsFileEnv]), + }); + for (const [name, profile] of Object.entries(profilesFromConfig)) { + profiles[name] = profile; + } + for (const [name, profile] of Object.entries(profilesFromCreds)) { + profiles[name] = { + ...profiles[name], + ...profile, + }; + } + return profiles; +} diff --git a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts index 6d85e08310128..515c0a548643a 100644 --- a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts +++ b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts @@ -6,6 +6,7 @@ import * as AWS from 'aws-sdk'; import * as fs from 'fs-extra'; import * as promptly from 'promptly'; import { debug } from '../../logging'; +import { PatchedSharedIniFileCredentials } from './aws-sdk-inifile'; import { SharedIniFile } from './sdk_ini_file'; /** @@ -44,7 +45,7 @@ export class AwsCliCompatible { // Force reading the `config` file if it exists by setting the appropriate // environment variable. await forceSdkToReadConfigIfPresent(); - sources.push(() => new AWS.SharedIniFileCredentials({ + sources.push(() => new PatchedSharedIniFileCredentials({ profile, filename: credentialsFileName(), httpOptions: options.httpOptions, @@ -310,3 +311,4 @@ async function tokenCodeFn(serialArn: string, cb: (err?: Error, token?: string) cb(err); } } + diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index 9d7bd5d701e39..e72225bbf82ee 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -32,53 +32,6 @@ beforeEach(() => { logging.setLogLevel(logging.LogLevel.TRACE); - bockfs({ - '/home/me/.bxt/credentials': dedent(` - [default] - aws_access_key_id=${uid}access - aws_secret_access_key=secret - - [foo] - aws_access_key_id=${uid}fooccess - aws_secret_access_key=secret - - [assumer] - aws_access_key_id=${uid}assumer - aws_secret_access_key=secret - - [mfa] - aws_access_key_id=${uid}mfaccess - aws_secret_access_key=secret - `), - '/home/me/.bxt/config': dedent(` - [default] - region=eu-bla-5 - - [profile foo] - region=eu-west-1 - - [profile boo] - aws_access_key_id=${uid}booccess - aws_secret_access_key=boocret - # No region here - - [profile assumable] - role_arn=arn:aws:iam::12356789012:role/Assumable - source_profile=assumer - - [profile assumer] - region=us-east-2 - - [profile mfa] - region=eu-west-1 - - [profile mfa-role] - source_profile=mfa - role_arn=arn:aws:iam::account:role/role - mfa_serial=arn:aws:iam::account:mfa/user - `), - }); - SDKMock.mock('STS', 'getCallerIdentity', (cb: AwsCallback) => { return cb(null, { Account: `${uid}the_account_#`, @@ -104,10 +57,8 @@ beforeEach(() => { defaultEnv = cxapi.EnvironmentUtils.make(`${uid}the_account_#`, 'def'); - // Set environment variables that we want - process.env.AWS_CONFIG_FILE = bockfs.path('/home/me/.bxt/config'); - process.env.AWS_SHARED_CREDENTIALS_FILE = bockfs.path('/home/me/.bxt/credentials'); // Scrub some environment variables that might be set if we're running on CodeBuild which will interfere with the tests. + delete process.env.AWS_PROFILE; delete process.env.AWS_REGION; delete process.env.AWS_DEFAULT_REGION; delete process.env.AWS_ACCESS_KEY_ID; @@ -122,146 +73,249 @@ afterEach(() => { bockfs.restore(); }); -describe('CLI compatible credentials loading', () => { - test('default config credentials', async () => { - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); - - // THEN - expect(provider.defaultRegion).toEqual('eu-bla-5'); - await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); - const sdk = await provider.forEnvironment({ ...defaultEnv, region: 'rgn' }, Mode.ForReading); - expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}access`); - expect(sdkConfig(sdk).region).toEqual('rgn'); - }); - - test('unknown account and region uses current', async () => { - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); +describe('with default config files', () => { + beforeEach(() => { + bockfs({ + '/home/me/.bxt/credentials': dedent(` + [default] + aws_access_key_id=${uid}access + aws_secret_access_key=secret + + [foo] + aws_access_key_id=${uid}fooccess + aws_secret_access_key=secret + + [assumer] + aws_access_key_id=${uid}assumer + aws_secret_access_key=secret + + [mfa] + aws_access_key_id=${uid}mfaccess + aws_secret_access_key=secret + `), + '/home/me/.bxt/config': dedent(` + [default] + region=eu-bla-5 + + [profile foo] + region=eu-west-1 + + [profile boo] + aws_access_key_id=${uid}booccess + aws_secret_access_key=boocret + # No region here + + [profile assumable] + role_arn=arn:aws:iam::12356789012:role/Assumable + source_profile=assumer + + [profile assumer] + region=us-east-2 + + [profile mfa] + region=eu-west-1 + + [profile mfa-role] + source_profile=mfa + role_arn=arn:aws:iam::account:role/role + mfa_serial=arn:aws:iam::account:mfa/user + `), + }); - // THEN - const sdk = await provider.forEnvironment(cxapi.EnvironmentUtils.make(cxapi.UNKNOWN_ACCOUNT, cxapi.UNKNOWN_REGION), Mode.ForReading); - expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}access`); - expect(sdkConfig(sdk).region).toEqual('eu-bla-5'); + // Set environment variables that we want + process.env.AWS_CONFIG_FILE = bockfs.path('/home/me/.bxt/config'); + process.env.AWS_SHARED_CREDENTIALS_FILE = bockfs.path('/home/me/.bxt/credentials'); }); - test('mixed profile credentials', async () => { - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'foo' }); + describe('CLI compatible credentials loading', () => { + test('default config credentials', async () => { + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); + + // THEN + expect(provider.defaultRegion).toEqual('eu-bla-5'); + await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); + const sdk = await provider.forEnvironment({ ...defaultEnv, region: 'rgn' }, Mode.ForReading); + expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}access`); + expect(sdkConfig(sdk).region).toEqual('rgn'); + }); - // THEN - expect(provider.defaultRegion).toEqual('eu-west-1'); - await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); - const sdk = await provider.forEnvironment(defaultEnv, Mode.ForReading); - expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}fooccess`); - }); + test('unknown account and region uses current', async () => { + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); - test('pure config credentials', async () => { - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'boo' }); + // THEN + const sdk = await provider.forEnvironment(cxapi.EnvironmentUtils.make(cxapi.UNKNOWN_ACCOUNT, cxapi.UNKNOWN_REGION), Mode.ForReading); + expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}access`); + expect(sdkConfig(sdk).region).toEqual('eu-bla-5'); + }); - // THEN - expect(provider.defaultRegion).toEqual('eu-bla-5'); // Fall back to default config - await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); - const sdk = await provider.forEnvironment(defaultEnv, Mode.ForReading); - expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}booccess`); - }); + test('mixed profile credentials', async () => { + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'foo' }); - test('mfa_serial in profile will ask user for token', async () => { - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'mfa-role' }); - - // THEN - try { - await provider.withAssumedRole('arn:aws:iam::account:role/role', undefined, undefined); - } catch (e) { - // Mock response was set to fail with message test to make sure we don't call STS - expect(e.message).toEqual('Error fetching MFA token: test'); - } - }); + // THEN + expect(provider.defaultRegion).toEqual('eu-west-1'); + await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); + const sdk = await provider.forEnvironment(defaultEnv, Mode.ForReading); + expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}fooccess`); + }); - test('different account throws', async () => { - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'boo' }); + test('pure config credentials', async () => { + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'boo' }); - await expect(provider.forEnvironment({ ...defaultEnv, account: `${uid}some_account_#` }, Mode.ForReading)).rejects.toThrow('Need to perform AWS calls'); - }); + // THEN + expect(provider.defaultRegion).toEqual('eu-bla-5'); // Fall back to default config + await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); + const sdk = await provider.forEnvironment(defaultEnv, Mode.ForReading); + expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}booccess`); + }); - test('even when using a profile to assume another profile, STS calls goes through the proxy', async () => { - // Messy mocking - let called = false; - jest.mock('proxy-agent', () => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - class FakeAgent extends require('https').Agent { - public addRequest(_: any, __: any) { - // FIXME: this error takes 6 seconds to be completely handled. It - // might be retries in the SDK somewhere, or something about the Node - // event loop. I've spent an hour trying to figure it out and I can't, - // and I gave up. We'll just have to live with this until someone gets - // inspired. - const error = new Error('ABORTED BY TEST'); - (error as any).code = 'RequestAbortedError'; - (error as any).retryable = false; - called = true; - throw error; - } + test('mfa_serial in profile will ask user for token', async () => { + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'mfa-role' }); + + // THEN + try { + await provider.withAssumedRole('arn:aws:iam::account:role/role', undefined, undefined); + } catch (e) { + // Mock response was set to fail with message test to make sure we don't call STS + expect(e.message).toEqual('Error fetching MFA token: test'); } - return FakeAgent; }); - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ - ...defaultCredOptions, - profile: 'assumable', - httpOptions: { - proxyAddress: 'http://DOESNTMATTER/', - }, + test('different account throws', async () => { + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'boo' }); + + await expect(provider.forEnvironment({ ...defaultEnv, account: `${uid}some_account_#` }, Mode.ForReading)).rejects.toThrow('Need to perform AWS calls'); }); - await provider.defaultAccount(); + test('even when using a profile to assume another profile, STS calls goes through the proxy', async () => { + // Messy mocking + let called = false; + jest.mock('proxy-agent', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + class FakeAgent extends require('https').Agent { + public addRequest(_: any, __: any) { + // FIXME: this error takes 6 seconds to be completely handled. It + // might be retries in the SDK somewhere, or something about the Node + // event loop. I've spent an hour trying to figure it out and I can't, + // and I gave up. We'll just have to live with this until someone gets + // inspired. + const error = new Error('ABORTED BY TEST'); + (error as any).code = 'RequestAbortedError'; + (error as any).retryable = false; + called = true; + throw error; + } + } + return FakeAgent; + }); - // THEN -- the fake proxy agent got called, we don't care about the result - expect(called).toEqual(true); - }); + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ + ...defaultCredOptions, + profile: 'assumable', + httpOptions: { + proxyAddress: 'http://DOESNTMATTER/', + }, + }); + + await provider.defaultAccount(); + + // THEN -- the fake proxy agent got called, we don't care about the result + expect(called).toEqual(true); + }); + + test('error we get from assuming a role is useful', async () => { + // GIVEN + // Because of the way ChainableTemporaryCredentials gets its STS client, it's not mockable + // using 'mock-aws-sdk'. So instead, we have to mess around with its internals. + function makeAssumeRoleFail(s: ISDK) { + (s as any).credentials.service.assumeRole = jest.fn().mockImplementation((_request, cb) => { + cb(new Error('Nope!')); + }); + } - test('error we get from assuming a role is useful', async () => { - // GIVEN - // Because of the way ChainableTemporaryCredentials gets its STS client, it's not mockable - // using 'mock-aws-sdk'. So instead, we have to mess around with its internals. - function makeAssumeRoleFail(s: ISDK) { - (s as any).credentials.service.assumeRole = jest.fn().mockImplementation((_request, cb) => { - cb(new Error('Nope!')); + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ + ...defaultCredOptions, + httpOptions: { + proxyAddress: 'http://localhost:8080/', + }, }); - } - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ - ...defaultCredOptions, - httpOptions: { - proxyAddress: 'http://localhost:8080/', - }, + // WHEN + const sdk = await provider.withAssumedRole('bla.role.arn', undefined, undefined); + makeAssumeRoleFail(sdk); + + // THEN - error message contains both a helpful hint and the underlying AssumeRole message + await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('did you bootstrap'); + await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('Nope!'); }); + }); - // WHEN - const sdk = await provider.withAssumedRole('bla.role.arn', undefined, undefined); - makeAssumeRoleFail(sdk); + describe('Plugins', () => { + test('does not use plugins if current credentials are for expected account', async () => { + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); + await provider.forEnvironment(defaultEnv, Mode.ForReading); + expect(pluginQueried).toEqual(false); + }); - // THEN - error message contains both a helpful hint and the underlying AssumeRole message - await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('did you bootstrap'); - await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('Nope!'); + test('uses plugin for other account', async () => { + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); + await provider.forEnvironment({ ...defaultEnv, account: `${uid}plugin_account_#` }, Mode.ForReading); + expect(pluginQueried).toEqual(true); + }); }); }); -describe('Plugins', () => { - test('does not use plugins if current credentials are for expected account', async () => { - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); - await provider.forEnvironment(defaultEnv, Mode.ForReading); - expect(pluginQueried).toEqual(false); +test('can assume role without a [default] profile', async () => { + // GIVEN + bockfs({ + '/home/me/.bxt/credentials': dedent(` + [assumer] + aws_access_key_id=${uid}assumer + aws_secret_access_key=secret + + [assumable] + role_arn=arn:aws:iam::12356789012:role/Assumable + source_profile=assumer + `), + '/home/me/.bxt/config': dedent(` + [profile assumable] + region=eu-bla-5 + `), + }); + + SDKMock.mock('STS', 'assumeRole', (_request: AWS.STS.AssumeRoleRequest, cb: AwsCallback) => { + return cb(null, { + Credentials: { + AccessKeyId: `${uid}access`, // Needs UID in here otherwise key will be cached + Expiration: new Date(Date.now() + 10000), + SecretAccessKey: 'b', + SessionToken: 'c', + }, + }); }); - test('uses plugin for other account', async () => { - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); - await provider.forEnvironment({ ...defaultEnv, account: `${uid}plugin_account_#` }, Mode.ForReading); - expect(pluginQueried).toEqual(true); + // Set environment variables that we want + process.env.AWS_CONFIG_FILE = bockfs.path('/home/me/.bxt/config'); + process.env.AWS_SHARED_CREDENTIALS_FILE = bockfs.path('/home/me/.bxt/credentials'); + + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ + ...defaultCredOptions, + profile: 'assumable', + httpOptions: { + proxyAddress: 'http://DOESNTMATTER/', + }, }); + + const account = await provider.defaultAccount(); + + // THEN + expect(account?.accountId).toEqual(`${uid}the_account_#`); }); /**