diff --git a/packages/aws-cdk/lib/util/asset-publishing.ts b/packages/aws-cdk/lib/util/asset-publishing.ts index 670231627ec92..2f85350f28f5e 100644 --- a/packages/aws-cdk/lib/util/asset-publishing.ts +++ b/packages/aws-cdk/lib/util/asset-publishing.ts @@ -50,7 +50,15 @@ class PublishingAws implements cdk_assets.IAws { } public async discoverCurrentAccount(): Promise { - return (await this.sdk({})).currentAccount(); + const account = await this.aws.defaultAccount(); + return account ?? { + accountId: '', + partition: 'aws', + }; + } + + public async discoverTargetAccount(options: cdk_assets.ClientOptions): Promise { + return (await this.sdk(options)).currentAccount(); } public async s3Client(options: cdk_assets.ClientOptions): Promise { diff --git a/packages/cdk-assets/lib/aws.ts b/packages/cdk-assets/lib/aws.ts index 936f0d94954e6..c35dedb38bbe2 100644 --- a/packages/cdk-assets/lib/aws.ts +++ b/packages/cdk-assets/lib/aws.ts @@ -8,6 +8,7 @@ export interface IAws { discoverDefaultRegion(): Promise; discoverCurrentAccount(): Promise; + discoverTargetAccount(options: ClientOptions): Promise; s3Client(options: ClientOptions): Promise; ecrClient(options: ClientOptions): Promise; secretsManagerClient(options: ClientOptions): Promise; @@ -94,6 +95,18 @@ export class DefaultAwsClient implements IAws { return this.account; } + public async discoverTargetAccount(options: ClientOptions): Promise { + const sts = new this.AWS.STS(await this.awsOptions(options)); + const response = await sts.getCallerIdentity().promise(); + if (!response.Account || !response.Arn) { + throw new Error(`Unrecognized reponse from STS: '${JSON.stringify(response)}'`); + } + return { + accountId: response.Account!, + partition: response.Arn!.split(':')[1], + }; + } + private async awsOptions(options: ClientOptions) { let credentials; diff --git a/packages/cdk-assets/lib/private/handlers/files.ts b/packages/cdk-assets/lib/private/handlers/files.ts index 0350edb27c39d..f9e2807753c79 100644 --- a/packages/cdk-assets/lib/private/handlers/files.ts +++ b/packages/cdk-assets/lib/private/handlers/files.ts @@ -30,7 +30,7 @@ export class FileAssetHandler implements IAssetHandler { // A thunk for describing the current account. Used when we need to format an error // message, not in the success case. - const account = async () => (await this.host.aws.discoverCurrentAccount())?.accountId; + const account = async () => (await this.host.aws.discoverTargetAccount(destination))?.accountId; switch (await bucketInfo.bucketOwnership(s3, destination.bucketName)) { case BucketOwnership.MINE: break; diff --git a/packages/cdk-assets/test/fake-listener.ts b/packages/cdk-assets/test/fake-listener.ts new file mode 100644 index 0000000000000..46b7f31a3ecc7 --- /dev/null +++ b/packages/cdk-assets/test/fake-listener.ts @@ -0,0 +1,16 @@ +import { IPublishProgressListener, EventType, IPublishProgress } from '../lib/progress'; + +export class FakeListener implements IPublishProgressListener { + public readonly messages = new Array(); + + constructor(private readonly doAbort = false) { + } + + public onPublishEvent(_type: EventType, event: IPublishProgress): void { + this.messages.push(event.message); + + if (this.doAbort) { + event.abort(); + } + } +} \ No newline at end of file diff --git a/packages/cdk-assets/test/files.test.ts b/packages/cdk-assets/test/files.test.ts index a2cbb592263dc..074d08308027f 100644 --- a/packages/cdk-assets/test/files.test.ts +++ b/packages/cdk-assets/test/files.test.ts @@ -3,6 +3,7 @@ jest.mock('child_process'); import { Manifest } from '@aws-cdk/cloud-assembly-schema'; import * as mockfs from 'mock-fs'; import { AssetManifest, AssetPublishing } from '../lib'; +import { FakeListener } from './fake-listener'; import { mockAws, mockedApiFailure, mockedApiResult, mockUpload } from './mock-aws'; import { mockSpawn } from './mock-child_process'; @@ -226,7 +227,8 @@ test('will only read bucketEncryption once even for multiple assets', async () = }); test('no server side encryption header if access denied for bucket encryption', async () => { - const pub = new AssetPublishing(AssetManifest.fromPath('/simple/cdk.out'), { aws }); + const progressListener = new FakeListener(); + const pub = new AssetPublishing(AssetManifest.fromPath('/simple/cdk.out'), { aws, progressListener }); aws.mockS3.getBucketEncryption = mockedApiFailure('AccessDenied', 'Access Denied'); @@ -243,7 +245,8 @@ test('no server side encryption header if access denied for bucket encryption', ServerSideEncryption: 'AES256', })); - // We'll just have to assume the contents are correct + // Error message references target_account, not current_account + expect(progressListener.messages).toContainEqual(expect.stringMatching(/ACCES_DENIED.*target_account/)); }); test('correctly looks up content type', async () => { @@ -294,6 +297,7 @@ test('successful run does not need to query account ID', async () => { await pub.publish(); expect(aws.discoverCurrentAccount).not.toHaveBeenCalled(); + expect(aws.discoverTargetAccount).not.toHaveBeenCalled(); }); test('correctly identify asset path if path is absolute', async () => { diff --git a/packages/cdk-assets/test/mock-aws.ts b/packages/cdk-assets/test/mock-aws.ts index fcf2fdffa6447..26b4f5d80d914 100644 --- a/packages/cdk-assets/test/mock-aws.ts +++ b/packages/cdk-assets/test/mock-aws.ts @@ -24,6 +24,7 @@ export function mockAws() { discoverPartition: jest.fn(() => Promise.resolve('swa')), discoverCurrentAccount: jest.fn(() => Promise.resolve({ accountId: 'current_account', partition: 'swa' })), discoverDefaultRegion: jest.fn(() => Promise.resolve('current_region')), + discoverTargetAccount: jest.fn(() => Promise.resolve({ accountId: 'target_account', partition: 'swa' })), ecrClient: jest.fn(() => Promise.resolve(mockEcr)), s3Client: jest.fn(() => Promise.resolve(mockS3)), secretsManagerClient: jest.fn(() => Promise.resolve(mockSecretsManager)), @@ -69,4 +70,4 @@ export function mockUpload(expectContent?: string) { }); }), })); -} +} \ No newline at end of file diff --git a/packages/cdk-assets/test/progress.test.ts b/packages/cdk-assets/test/progress.test.ts index d18af611465ba..4003ec7df5482 100644 --- a/packages/cdk-assets/test/progress.test.ts +++ b/packages/cdk-assets/test/progress.test.ts @@ -1,6 +1,7 @@ import { Manifest } from '@aws-cdk/cloud-assembly-schema'; import * as mockfs from 'mock-fs'; -import { AssetManifest, AssetPublishing, EventType, IPublishProgress, IPublishProgressListener } from '../lib'; +import { AssetManifest, AssetPublishing } from '../lib'; +import { FakeListener } from './fake-listener'; import { mockAws, mockedApiResult, mockUpload } from './mock-aws'; let aws: ReturnType; @@ -68,19 +69,4 @@ test('test abort', async () => { // We never get to asset 2 expect(allMessages).not.toContain('theAsset:theDestination2'); -}); - -class FakeListener implements IPublishProgressListener { - public readonly messages = new Array(); - - constructor(private readonly doAbort = false) { - } - - public onPublishEvent(_type: EventType, event: IPublishProgress): void { - this.messages.push(event.message); - - if (this.doAbort) { - event.abort(); - } - } -} +}); \ No newline at end of file