11import fs from 'fs-extra' ;
22import path from 'path' ;
33import os from 'os' ;
4- import { STSClient , AssumeRoleCommand } from '@aws-sdk/client-sts' ;
4+ import { STSClient , AssumeRoleCommand , AssumeRoleCommandOutput } from '@aws-sdk/client-sts' ;
55import { Logger } from './logger' ;
66
77/**
@@ -81,14 +81,7 @@ export class CredentialManager {
8181 }
8282 try {
8383 this . logger . info ( 'Refreshing credentials...' ) ;
84- const sts = new STSClient ( { } ) ;
85- const assumed = await sts . send (
86- new AssumeRoleCommand ( {
87- RoleArn : this . source . roleArn ,
88- RoleSessionName : `gen2-migration-e2e-${ Date . now ( ) } ` ,
89- DurationSeconds : SESSION_DURATION_SECONDS ,
90- } ) ,
91- ) ;
84+ const assumed = await this . assumeRoleAsOriginalIdentity ( ) ;
9285 const creds = assumed . Credentials ;
9386 if ( ! creds ?. AccessKeyId || ! creds ?. SecretAccessKey || ! creds ?. SessionToken ) {
9487 throw new Error ( 'STS AssumeRole returned incomplete credentials' ) ;
@@ -110,6 +103,45 @@ export class CredentialManager {
110103 }
111104 }
112105
106+ /**
107+ * Call STS AssumeRole as the original caller (e.g., CodeBuild instance role),
108+ * not as whoever is currently in `AWS_*` env vars. After the first refresh,
109+ * env vars hold the assumed-role credentials; re-assuming from those would
110+ * fail with "not authorized to perform sts:AssumeRole on <role>" because the
111+ * role's trust policy only allows the original caller, not itself.
112+ *
113+ * Temporarily removing env vars forces the SDK default chain to skip them
114+ * and fall through to container/instance metadata, which points at the
115+ * original identity.
116+ */
117+ private async assumeRoleAsOriginalIdentity ( ) : Promise < AssumeRoleCommandOutput > {
118+ if ( this . source . kind !== 'role' ) {
119+ throw new Error ( 'assumeRoleAsOriginalIdentity called outside role mode' ) ;
120+ }
121+ const saved = {
122+ AWS_ACCESS_KEY_ID : process . env . AWS_ACCESS_KEY_ID ,
123+ AWS_SECRET_ACCESS_KEY : process . env . AWS_SECRET_ACCESS_KEY ,
124+ AWS_SESSION_TOKEN : process . env . AWS_SESSION_TOKEN ,
125+ } ;
126+ delete process . env . AWS_ACCESS_KEY_ID ;
127+ delete process . env . AWS_SECRET_ACCESS_KEY ;
128+ delete process . env . AWS_SESSION_TOKEN ;
129+ try {
130+ const sts = new STSClient ( { } ) ;
131+ return await sts . send (
132+ new AssumeRoleCommand ( {
133+ RoleArn : this . source . roleArn ,
134+ RoleSessionName : `gen2-migration-e2e-${ Date . now ( ) } ` ,
135+ DurationSeconds : SESSION_DURATION_SECONDS ,
136+ } ) ,
137+ ) ;
138+ } finally {
139+ if ( saved . AWS_ACCESS_KEY_ID !== undefined ) process . env . AWS_ACCESS_KEY_ID = saved . AWS_ACCESS_KEY_ID ;
140+ if ( saved . AWS_SECRET_ACCESS_KEY !== undefined ) process . env . AWS_SECRET_ACCESS_KEY = saved . AWS_SECRET_ACCESS_KEY ;
141+ if ( saved . AWS_SESSION_TOKEN !== undefined ) process . env . AWS_SESSION_TOKEN = saved . AWS_SESSION_TOKEN ;
142+ }
143+ }
144+
113145 private writeCredentialsFile ( accessKeyId : string , secretAccessKey : string , sessionToken : string ) : void {
114146 const awsDir = path . join ( os . homedir ( ) , '.aws' ) ;
115147 fs . mkdirSync ( awsDir , { recursive : true } ) ;
0 commit comments