Revert "feat: support custom STS endpoints (#1762)"

This reverts commit 8d52d05d7a.
This commit is contained in:
Tom Keller 2026-05-25 12:14:45 -07:00
commit b191e12986
6 changed files with 117 additions and 201 deletions

View file

@ -467,12 +467,6 @@ with:
</details>
### Custom STS endpoint
Use the `sts-endpoint` input to override the AWS STS endpoint URL. Most users
should not set this option and instead let the SDK derive the correct endpoint
from the specified region.
## OIDC Configuration Details
We recommend using

View file

@ -103,9 +103,6 @@ inputs:
custom-tags:
description: Additional tags to apply to the assumed role session. Must be a JSON object provided as a string.
required: false
sts-endpoint:
description: Custom STS endpoint URL. Use this to point to an STS-compatible API (e.g. MinIO, LocalStack) instead of the default AWS STS endpoint for the region.
required: false
outputs:
aws-account-id:

View file

@ -12,7 +12,6 @@ export interface CredentialsClientProps {
region?: string;
proxyServer?: string;
noProxy?: string;
stsEndpoint?: string;
roleChaining: boolean;
}
@ -20,7 +19,6 @@ export class CredentialsClient {
public region?: string;
private _stsClient?: STSClient;
private readonly requestHandler?: NodeHttpHandler;
private readonly stsEndpoint?: string;
private roleChaining?: boolean;
constructor(props: CredentialsClientProps) {
@ -43,9 +41,6 @@ export class CredentialsClient {
httpAgent: handler,
});
}
if (props.stsEndpoint) {
this.stsEndpoint = props.stsEndpoint;
}
this.roleChaining = props.roleChaining;
}
@ -54,11 +49,9 @@ export class CredentialsClient {
const config = { customUserAgent: USER_AGENT } as {
customUserAgent: string;
region?: string;
endpoint?: string;
requestHandler?: NodeHttpHandler;
};
if (this.region !== undefined) config.region = this.region;
if (this.stsEndpoint !== undefined) config.endpoint = this.stsEndpoint;
if (this.requestHandler !== undefined) config.requestHandler = this.requestHandler;
this._stsClient = new STSClient(config);
}

View file

@ -64,7 +64,6 @@ export async function run() {
.map((s) => s.trim());
const forceSkipOidc = getBooleanInput('force-skip-oidc', { required: false });
const noProxy = core.getInput('no-proxy', { required: false });
const stsEndpoint = core.getInput('sts-endpoint', { required: false });
const globalTimeout = Number.parseInt(core.getInput('action-timeout-s', { required: false })) || 0;
let timeoutId: NodeJS.Timeout | undefined;
@ -129,19 +128,12 @@ export async function run() {
exportRegion(region, outputEnvCredentials);
// Instantiate credentials client
const clientProps: {
region: string;
proxyServer?: string;
noProxy?: string;
stsEndpoint?: string;
roleChaining: boolean;
} = {
const clientProps: { region: string; proxyServer?: string; noProxy?: string; roleChaining: boolean } = {
region,
roleChaining,
};
if (proxyServer) clientProps.proxyServer = proxyServer;
if (noProxy) clientProps.noProxy = noProxy;
if (stsEndpoint) clientProps.stsEndpoint = stsEndpoint;
const credentialsClient = new CredentialsClient(clientProps);
let sourceAccountId: string;
let webIdentityToken: string;

View file

@ -311,7 +311,9 @@ describe('Configure AWS Credentials', {}, () => {
});
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
await run();
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining("'custom-tags' is set but will be ignored"));
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining("'custom-tags' is set but will be ignored"),
);
});
});
@ -799,54 +801,6 @@ describe('Configure AWS Credentials', {}, () => {
});
});
describe('Custom STS Endpoint', {}, () => {
it('passes sts-endpoint to the STS client', async () => {
const client = new CredentialsClient({
region: 'us-east-1',
stsEndpoint: 'https://sts.custom.example.com',
roleChaining: false,
});
const endpoint = await client.stsClient.config.endpoint();
expect(endpoint).toMatchObject({ hostname: 'sts.custom.example.com', protocol: 'https:' });
});
it('does not override endpoint when sts-endpoint is not provided', () => {
const client = new CredentialsClient({
region: 'us-east-1',
roleChaining: false,
});
expect(client.stsClient.config.endpoint).toBeUndefined();
});
it('works with http endpoints for local services', async () => {
const client = new CredentialsClient({
region: 'us-east-1',
stsEndpoint: 'http://localhost:9000',
roleChaining: false,
});
const endpoint = await client.stsClient.config.endpoint();
expect(endpoint).toMatchObject({ hostname: 'localhost', protocol: 'http:', port: 9000 });
});
it('succeeds in a full action run with sts-endpoint input', async () => {
vi.mocked(core.getInput).mockImplementation(
mocks.getInput({
...mocks.GH_OIDC_INPUTS,
'sts-endpoint': 'https://sts.custom.example.com',
}),
);
vi.mocked(core.getIDToken).mockResolvedValue('testoidctoken');
mockedSTSClient.on(GetCallerIdentityCommand).resolvesOnce({ ...mocks.outputs.GET_CALLER_IDENTITY });
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
await run();
expect(core.setFailed).not.toHaveBeenCalled();
expect(core.info).toHaveBeenCalledWith('Authenticated as assumedRoleId AROAFAKEASSUMEDROLEID');
});
});
describe('HTTP Proxy Configuration', {}, () => {
beforeEach(() => {
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.GH_OIDC_INPUTS));

View file

@ -95,10 +95,7 @@ describe('Profile Manager', {}, () => {
});
it('round-trips through parseIni', {}, () => {
const data = {
dev: { aws_access_key_id: 'AKIA', aws_secret_access_key: 'secret' },
'profile prod': { region: 'us-west-2' },
};
const data = { dev: { aws_access_key_id: 'AKIA', aws_secret_access_key: 'secret' }, 'profile prod': { region: 'us-west-2' } };
const roundTripped = parseIni(stringifyIni(data));
expect(roundTripped).toEqual(data);
});
@ -199,15 +196,10 @@ describe('Profile Manager', {}, () => {
const filePath = '/home/runner/.aws/credentials';
fs.mkdirSync('/home/runner/.aws', { recursive: true });
mergeProfileSection(
filePath,
'dev',
{
aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE',
aws_secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
},
false,
);
mergeProfileSection(filePath, 'dev', {
aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE',
aws_secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
}, false);
const content = fs.readFileSync(filePath, 'utf-8');
const parsed = parseIni(content);
@ -222,26 +214,16 @@ describe('Profile Manager', {}, () => {
fs.mkdirSync('/home/runner/.aws', { recursive: true });
// Create initial profile
mergeProfileSection(
filePath,
'dev',
{
aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE',
aws_secret_access_key: 'devSecretKey',
},
false,
);
mergeProfileSection(filePath, 'dev', {
aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE',
aws_secret_access_key: 'devSecretKey',
}, false);
// Add second profile
mergeProfileSection(
filePath,
'prod',
{
aws_access_key_id: 'AKIAPRODEXAMPLE',
aws_secret_access_key: 'prodSecretKey',
},
false,
);
mergeProfileSection(filePath, 'prod', {
aws_access_key_id: 'AKIAPRODEXAMPLE',
aws_secret_access_key: 'prodSecretKey',
}, false);
const content = fs.readFileSync(filePath, 'utf-8');
const parsed = parseIni(content);
@ -257,28 +239,18 @@ describe('Profile Manager', {}, () => {
fs.mkdirSync('/home/runner/.aws', { recursive: true });
// Create initial profile
mergeProfileSection(
filePath,
'dev',
{
aws_access_key_id: 'OLD_KEY',
aws_secret_access_key: 'oldSecretKey',
aws_session_token: 'oldSessionToken',
},
false,
);
mergeProfileSection(filePath, 'dev', {
aws_access_key_id: 'OLD_KEY',
aws_secret_access_key: 'oldSecretKey',
aws_session_token: 'oldSessionToken'
}, false);
// Overwrite with new credentials
mergeProfileSection(
filePath,
'dev',
{
aws_access_key_id: 'NEW_KEY',
aws_secret_access_key: 'newSecretKey',
aws_session_token: 'newSessionToken',
},
true,
);
mergeProfileSection(filePath, 'dev', {
aws_access_key_id: 'NEW_KEY',
aws_secret_access_key: 'newSecretKey',
aws_session_token: 'newSessionToken',
}, true);
const content = fs.readFileSync(filePath, 'utf-8');
const parsed = parseIni(content);
@ -293,27 +265,17 @@ describe('Profile Manager', {}, () => {
fs.mkdirSync('/home/runner/.aws', { recursive: true });
// Create profile with session token
mergeProfileSection(
filePath,
'dev',
{
aws_access_key_id: 'AKIA',
aws_secret_access_key: 'secret',
aws_session_token: 'old-token',
},
false,
);
mergeProfileSection(filePath, 'dev', {
aws_access_key_id: 'AKIA',
aws_secret_access_key: 'secret',
aws_session_token: 'old-token',
}, false);
// Overwrite without session token
mergeProfileSection(
filePath,
'dev',
{
aws_access_key_id: 'AKIA2',
aws_secret_access_key: 'secret2',
},
true,
);
mergeProfileSection(filePath, 'dev', {
aws_access_key_id: 'AKIA2',
aws_secret_access_key: 'secret2',
}, true);
const content = fs.readFileSync(filePath, 'utf-8');
const parsed = parseIni(content);
@ -328,15 +290,10 @@ describe('Profile Manager', {}, () => {
fs.mkdirSync('/home/runner/.aws', { recursive: true });
fs.writeFileSync(filePath, '', { mode: 0o600 });
mergeProfileSection(
filePath,
'dev',
{
aws_access_key_id: 'AKIA',
aws_secret_access_key: 'secret',
},
false,
);
mergeProfileSection(filePath, 'dev', {
aws_access_key_id: 'AKIA',
aws_secret_access_key: 'secret',
}, false);
const content = fs.readFileSync(filePath, 'utf-8');
const parsed = parseIni(content);
@ -350,31 +307,17 @@ describe('Profile Manager', {}, () => {
fs.mkdirSync('/home/runner/.aws', { recursive: true });
// Create initial profile
mergeProfileSection(
filePath,
'dev',
{
aws_access_key_id: 'OLD_KEY',
aws_secret_access_key: 'oldSecretKey',
},
false,
);
mergeProfileSection(filePath, 'dev', {
aws_access_key_id: 'OLD_KEY',
aws_secret_access_key: 'oldSecretKey',
}, false);
// Overwrite with new credentials
expect(() =>
mergeProfileSection(
filePath,
'dev',
{
aws_access_key_id: 'NEW_KEY',
aws_secret_access_key: 'newSecretKey',
aws_session_token: 'sessionToken',
},
false,
),
).toThrow(
`Profile with name "dev" already exists. Please use the overwrite-aws-profile input if you want to overwrite existing profiles.`,
);
expect(() => mergeProfileSection(filePath, 'dev', {
aws_access_key_id: 'NEW_KEY',
aws_secret_access_key: 'newSecretKey',
aws_session_token: 'sessionToken',
}, false)).toThrow(`Profile with name "dev" already exists. Please use the overwrite-aws-profile input if you want to overwrite existing profiles.`);
const content = fs.readFileSync(filePath, 'utf-8');
const parsed = parseIni(content);
@ -525,7 +468,7 @@ describe('Profile Manager', {}, () => {
SecretAccessKey: 'secret',
},
'us-east-1',
false,
false
),
).toThrow('whitespace');
});
@ -543,7 +486,7 @@ describe('Profile Manager', {}, () => {
SecretAccessKey: 'secret',
},
'us-east-1',
false,
false
);
expect(fs.existsSync('/custom/credentials')).toBe(true);
@ -558,7 +501,7 @@ describe('Profile Manager', {}, () => {
SecretAccessKey: 'secret',
},
'us-east-1',
false,
false
);
expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: dev');
@ -574,7 +517,12 @@ describe('Profile Manager', {}, () => {
'[personal]\naws_access_key_id=AKIAPERSONAL\naws_secret_access_key=personalSecret\naws_session_token=personalToken\n',
);
writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-east-1', false);
writeProfileFiles(
'dev',
{ AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' },
'us-east-1',
false,
);
const content = fs.readFileSync(credsPath, 'utf-8');
const parsed = parseIni(content);
@ -592,9 +540,17 @@ describe('Profile Manager', {}, () => {
it('preserves pre-existing config with extra keys', {}, () => {
const configPath = getProfileFilePaths().config;
fs.mkdirSync(require('node:path').dirname(configPath), { recursive: true });
fs.writeFileSync(configPath, '[profile personal]\nregion=eu-west-1\noutput=json\ncli_pager=\n');
fs.writeFileSync(
configPath,
'[profile personal]\nregion=eu-west-1\noutput=json\ncli_pager=\n',
);
writeProfileFiles('dev', { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, 'us-east-1', false);
writeProfileFiles(
'dev',
{ AccessKeyId: 'AKIA', SecretAccessKey: 'secret' },
'us-east-1',
false
);
const content = fs.readFileSync(configPath, 'utf-8');
const parsed = parseIni(content);
@ -610,9 +566,17 @@ describe('Profile Manager', {}, () => {
it('preserves pre-existing default profile when writing a named profile', {}, () => {
const credsPath = getProfileFilePaths().credentials;
fs.mkdirSync(require('node:path').dirname(credsPath), { recursive: true });
fs.writeFileSync(credsPath, '[default]\naws_access_key_id=AKIADEFAULT\naws_secret_access_key=defaultSecret\n');
fs.writeFileSync(
credsPath,
'[default]\naws_access_key_id=AKIADEFAULT\naws_secret_access_key=defaultSecret\n',
);
writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-west-2', false);
writeProfileFiles(
'dev',
{ AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' },
'us-west-2',
false
);
const content = fs.readFileSync(credsPath, 'utf-8');
const parsed = parseIni(content);
@ -632,7 +596,12 @@ describe('Profile Manager', {}, () => {
'# My important comment\n[personal]\naws_access_key_id=AKIA\naws_secret_access_key=secret\n',
);
writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-east-1', false);
writeProfileFiles(
'dev',
{ AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' },
'us-east-1',
false
);
const content = fs.readFileSync(credsPath, 'utf-8') as string;
@ -666,7 +635,12 @@ describe('Profile Manager', {}, () => {
fs.mkdirSync('/custom-creds', { recursive: true });
writeProfileFiles('dev', { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, 'us-east-1', false);
writeProfileFiles(
'dev',
{ AccessKeyId: 'AKIA', SecretAccessKey: 'secret' },
'us-east-1',
false
);
expect(fs.existsSync('/custom-creds/credentials')).toBe(true);
// Config file should be at the default path (under homedir)
@ -683,7 +657,7 @@ describe('Profile Manager', {}, () => {
SessionToken: 'FwoGZXIvYXdzEBYaDEXAMPLE',
},
'us-east-1',
false,
false
);
const credsPath = getProfileFilePaths().credentials;
@ -698,20 +672,28 @@ describe('Profile Manager', {}, () => {
// - LF line endings, trailing newline
expect(credContent).toBe(
'[dev]\n' +
'aws_access_key_id = AKIAIOSFODNN7EXAMPLE\n' +
'aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n' +
'aws_session_token = FwoGZXIvYXdzEBYaDEXAMPLE\n',
'aws_access_key_id = AKIAIOSFODNN7EXAMPLE\n' +
'aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n' +
'aws_session_token = FwoGZXIvYXdzEBYaDEXAMPLE\n',
);
expect(configContent).toBe(
'[profile dev]\n' +
'region = us-east-1\n',
);
expect(configContent).toBe('[profile dev]\n' + 'region = us-east-1\n');
});
it('golden file for multi-profile output', {}, () => {
writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-east-1', false);
writeProfileFiles(
'dev',
{ AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' },
'us-east-1',
false
);
writeProfileFiles(
'prod',
{ AccessKeyId: 'AKIAPROD', SecretAccessKey: 'prodSecret', SessionToken: 'prodToken' },
'us-west-2',
false,
false
);
const credsPath = getProfileFilePaths().credentials;
@ -722,16 +704,20 @@ describe('Profile Manager', {}, () => {
expect(credContent).toBe(
'[dev]\n' +
'aws_access_key_id = AKIADEV\n' +
'aws_secret_access_key = devSecret\n' +
'\n' +
'[prod]\n' +
'aws_access_key_id = AKIAPROD\n' +
'aws_secret_access_key = prodSecret\n' +
'aws_session_token = prodToken\n',
'aws_access_key_id = AKIADEV\n' +
'aws_secret_access_key = devSecret\n' +
'\n' +
'[prod]\n' +
'aws_access_key_id = AKIAPROD\n' +
'aws_secret_access_key = prodSecret\n' +
'aws_session_token = prodToken\n',
);
expect(configContent).toBe(
'[profile dev]\n' + 'region = us-east-1\n' + '\n' + '[profile prod]\n' + 'region = us-west-2\n',
'[profile dev]\n' +
'region = us-east-1\n' +
'\n' +
'[profile prod]\n' +
'region = us-west-2\n',
);
});
});