Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
55 changes: 39 additions & 16 deletions packages/amplify-e2e-core/src/utils/credentials-rotator.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts';
import { fromContainerMetadata } from '@aws-sdk/credential-providers';
import { generateRandomShortId, TEST_PROFILE_NAME } from './index';
import * as ini from 'ini';
import * as fs from 'fs-extra';
import { pathManager } from '@aws-amplify/amplify-cli-core';
const refreshCredentials = async (roleArn: string) => {
import { generateRandomShortId, TEST_PROFILE_NAME } from './index';

const refreshCredentials = async (roleArn: string, useCurrentCreds: boolean = false) => {
console.log(`Refreshing credentials for arn ${roleArn}`);
let credentials = undefined;
if (!useCurrentCreds) {
console.log('Using container credentials');
credentials = fromContainerMetadata();
} else {
console.log('Using current credentials');
}
const client = new STSClient({
// Use CodeBuild role to assume test account role. I.e. don't read credentials from process.env
credentials: fromContainerMetadata(),
credentials,
});
const sessionName = `testSession${generateRandomShortId()}`;
const command = new AssumeRoleCommand({
Expand All @@ -27,11 +35,16 @@
process.env.AWS_SECRET_ACCESS_KEY = response.Credentials.SecretAccessKey;
process.env.AWS_SESSION_TOKEN = response.Credentials.SessionToken;
await fs.writeFile(pathManager.getAWSCredentialsFilePath(), ini.stringify(credentialsContents));
console.log(`Refreshed credentials for arn ${roleArn}`);
};

const tryRefreshCredentials = async (roleArn: string) => {
const tryRefreshCredentials = async (parentRoleArn: string, childRoleArn?: string) => {
try {
await refreshCredentials(roleArn);
if (childRoleArn) {
await refreshCredentials(childRoleArn, true);
} else {
await refreshCredentials(parentRoleArn);
}
console.log('Test profile credentials refreshed');
} catch (e) {
console.error('Test profile credentials request failed');
Expand All @@ -50,20 +63,30 @@
* No-op if a background task has already been scheduled.
*/
export const tryScheduleCredentialRefresh = () => {
if (!process.env.CI || !process.env.TEST_ACCOUNT_ROLE || isRotationBackgroundTaskAlreadyScheduled) {
console.log('Scheduling credentials refresh');
console.dir(process.env);
if (!process.env.CI || !(process.env.TEST_ACCOUNT_ROLE || process.env.CHILD_ACCOUNT_ROLE) || isRotationBackgroundTaskAlreadyScheduled) {
return;
}

if (!process.env.USE_PARENT_ACCOUNT) {
throw new Error('Credentials rotator supports only tests running in parent account at this time');
}
if (process.env.USE_PARENT_ACCOUNT) {
// Attempts to refresh credentials in background every 15 minutes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion.

I'd reduce interval to something smaller (5 minutes)?

The reason is - test framework spawns subprocess that inherit env vars. So their credential validity period is how_much_time_since_last_rotation + subprocess_lifespan. Some of these processes take long time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this rotator be started again for the subprocess, specifically for the test that invokes it?

Copy link
Contributor

@sobolk sobolk Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test subprocesses yes they are covered.

what I have in mind is nspawn(gen1CLI); - this may take long time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, if a specific CLI command (on any spawned process from a test) takes a long time (>45 minutes) we could have a problem. I'll update it to 10 minutes (don't want to be too aggressive until we know it to be a problem)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In searchable tests (in cli repo) this refresh was (is?) too sparse sometimes. (we haven't adjusted this on CLI side though).

setInterval(() => {
void tryRefreshCredentials(process.env.TEST_ACCOUNT_ROLE);
}, 15 * 60 * 1000);

// Attempts to refresh credentials in background every 15 minutes.
setInterval(() => {
void tryRefreshCredentials(process.env.TEST_ACCOUNT_ROLE);
}, 15 * 60 * 1000);
console.log('Test profile credentials refresh was scheduled for parent account');
return;
} else if (process.env.CHILD_ACCOUNT_ROLE) {
// Attempts to refresh credentials in background every 15 minutes.
setInterval(() => {
void tryRefreshCredentials(process.env.CHILD_ACCOUNT_ROLE);
}, 15 * 60 * 1000);

isRotationBackgroundTaskAlreadyScheduled = true;
console.log('Test profile credentials refresh was scheduled for child account');
} else {
throw new Error('Credentials rotator could not find any role to rotate credentials for');
}

console.log('Test profile credentials refresh was scheduled');
isRotationBackgroundTaskAlreadyScheduled = true;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
initJSProjectWithProfile,
setupRDSInstanceAndData,
sleep,
tryScheduleCredentialRefresh,
updateAuthAddUserGroups,
} from 'amplify-category-api-e2e-core';
import { existsSync, writeFileSync, removeSync } from 'fs-extra';
Expand Down Expand Up @@ -79,6 +80,7 @@ export const testOIDCFieldAuth = (engine: ImportedRDSType): void => {
console.log(sqlCreateStatements(engine));
projRoot = await createNewProjectDir(projName);
await setupAmplifyProject();
tryScheduleCredentialRefresh();
});

afterAll(async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as path from 'path';
import { createNewProjectDir, deleteProjectDir, deleteProject } from 'amplify-category-api-e2e-core';
import { createNewProjectDir, deleteProjectDir, deleteProject, tryScheduleCredentialRefresh } from 'amplify-category-api-e2e-core';
import { initCDKProject, cdkDeploy, cdkDestroy, createGen1ProjectForMigration, deleteDDBTables } from '../../commands';
import { graphql } from '../../graphql-request';
import { TestDefinition, writeStackConfig, writeTestDefinitions, writeOverrides } from '../../utils';
Expand All @@ -14,6 +14,10 @@ describe('Many-to-many Migration', () => {
let gen2ProjFolderName: string;
let dataSourceMapping: Record<string, string>;

beforeAll(() => {
tryScheduleCredentialRefresh();
});

beforeEach(async () => {
gen1ProjFolderName = 'mtmmigrationgen1';
gen2ProjFolderName = 'mtmmigrationgen2';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as path from 'path';
import { createNewProjectDir, deleteProjectDir, deleteProject } from 'amplify-category-api-e2e-core';
import { createNewProjectDir, deleteProjectDir, deleteProject, tryScheduleCredentialRefresh } from 'amplify-category-api-e2e-core';
import { CloudFormationClient, ListStacksCommand, DescribeStackEventsCommand, StackEvent } from '@aws-sdk/client-cloudformation';
import { initCDKProject, cdkDeploy, cdkDestroy, createGen1ProjectForMigration, deleteDDBTables } from '../../commands';
import { TestDefinition, writeStackConfig, writeTestDefinitions, writeOverrides } from '../../utils';
Expand All @@ -15,6 +15,7 @@ describe('Migration table import validation', () => {
let dataSourceMapping: Record<string, string>;

beforeAll(async () => {
tryScheduleCredentialRefresh();
gen1ProjFolderName = 'validategen1';
gen1ProjRoot = await createNewProjectDir(gen1ProjFolderName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TestOptions } from '../utils/sql-test-config-helper';
import { DURATION_1_HOUR } from '../utils/duration-constants';
import { testGraphQLAPI } from '../sql-tests-common/sql-models';
import { sqlCreateStatements } from '../sql-tests-common/tests-sources/sql-models/provider';
import { tryScheduleCredentialRefresh } from 'amplify-category-api-e2e-core';

jest.setTimeout(DURATION_1_HOUR);

Expand Down
4 changes: 3 additions & 1 deletion shared-scripts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -417,11 +417,13 @@ function useChildAccountCredentials {
echo "Unable to find a child account. Falling back to parent AWS account"
return
fi
creds=$(aws sts assume-role --role-arn arn:aws:iam::${pick_acct}:role/OrganizationAccountAccessRole --role-session-name testSession${session_id} --duration-seconds 3600)
account_role=arn:aws:iam::${pick_acct}:role/OrganizationAccountAccessRole
creds=$(aws sts assume-role --role-arn ${account_role} --role-session-name testSession${session_id} --duration-seconds 3600)
if [ -z $(echo $creds | jq -c -r '.AssumedRoleUser.Arn') ]; then
echo "Unable to assume child account role. Falling back to parent AWS account"
return
fi
export CHILD_ACCOUNT_ROLE=$account_role
export ORGANIZATION_SIZE=$org_size
export CREDS=$creds
echo "Using account credentials for $(echo $creds | jq -c -r '.AssumedRoleUser.Arn')"
Expand Down
Loading