diff --git a/packages/credential-provider-http/src/fromHttp/fromHttp.ts b/packages/credential-provider-http/src/fromHttp/fromHttp.ts index a7310afe7721e..eafe5c045b68d 100644 --- a/packages/credential-provider-http/src/fromHttp/fromHttp.ts +++ b/packages/credential-provider-http/src/fromHttp/fromHttp.ts @@ -47,7 +47,10 @@ export const fromHttp = (options: FromHttpOptions): AwsCredentialIdentityProvide } else if (relative) { host = `${DEFAULT_LINK_LOCAL_HOST}${relative}`; } else { - throw new CredentialsProviderError("No HTTP credential provider host provided."); + throw new CredentialsProviderError( + `No HTTP credential provider host provided. +Set AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.` + ); } // throws if invalid format. @@ -56,7 +59,10 @@ export const fromHttp = (options: FromHttpOptions): AwsCredentialIdentityProvide // throws if not to spec for provider. checkUrl(url); - const requestHandler = new NodeHttpHandler(); + const requestHandler = new NodeHttpHandler({ + requestTimeout: options.timeout ?? 1000, + connectionTimeout: options.timeout ?? 1000, + }); return retryWrapper( async (): Promise => { @@ -69,8 +75,12 @@ export const fromHttp = (options: FromHttpOptions): AwsCredentialIdentityProvide // to allow for updates to the file contents. request.headers.Authorization = (await fs.readFile(tokenFile)).toString(); } - const result = await requestHandler.handle(request); - return getCredentials(result.response); + try { + const result = await requestHandler.handle(request); + return getCredentials(result.response); + } catch (e: unknown) { + throw new CredentialsProviderError(String(e)); + } }, options.maxRetries ?? 3, options.timeout ?? 1000 diff --git a/packages/credential-provider-ini/src/resolveProfileData.spec.ts b/packages/credential-provider-ini/src/resolveProfileData.spec.ts index 397685ee690c8..24f24402a04c8 100644 --- a/packages/credential-provider-ini/src/resolveProfileData.spec.ts +++ b/packages/credential-provider-ini/src/resolveProfileData.spec.ts @@ -117,6 +117,6 @@ describe(resolveProfileData.name, () => { (resolveSsoCredentials as jest.Mock).mockImplementation(() => Promise.resolve(mockCreds)); const receivedCreds = await resolveProfileData(mockProfileName, mockProfiles, mockOptions); expect(receivedCreds).toStrictEqual(mockCreds); - expect(resolveSsoCredentials).toHaveBeenCalledWith(mockProfileName); + expect(resolveSsoCredentials).toHaveBeenCalledWith(mockProfileName, mockOptions); }); }); diff --git a/packages/credential-provider-ini/src/resolveProfileData.ts b/packages/credential-provider-ini/src/resolveProfileData.ts index f5dd121c91d92..fb4eb179c3ec2 100644 --- a/packages/credential-provider-ini/src/resolveProfileData.ts +++ b/packages/credential-provider-ini/src/resolveProfileData.ts @@ -51,7 +51,7 @@ export const resolveProfileData = async ( } if (isSsoProfile(data)) { - return await resolveSsoCredentials(profileName); + return await resolveSsoCredentials(profileName, options); } // If the profile cannot be parsed or contains neither static credentials diff --git a/packages/credential-provider-ini/src/resolveSsoCredentials.ts b/packages/credential-provider-ini/src/resolveSsoCredentials.ts index 1151f7b89315a..ca0f0271559b1 100644 --- a/packages/credential-provider-ini/src/resolveSsoCredentials.ts +++ b/packages/credential-provider-ini/src/resolveSsoCredentials.ts @@ -1,13 +1,15 @@ import type { SsoProfile } from "@aws-sdk/credential-provider-sso"; +import type { CredentialProviderOptions } from "@aws-sdk/types"; import type { Profile } from "@smithy/types"; /** * @internal */ -export const resolveSsoCredentials = async (profile: string) => { +export const resolveSsoCredentials = async (profile: string, options: CredentialProviderOptions = {}) => { const { fromSSO } = await import("@aws-sdk/credential-provider-sso"); return fromSSO({ profile, + logger: options.logger, })(); }; diff --git a/packages/credential-provider-ini/src/resolveWebIdentityCredentials.ts b/packages/credential-provider-ini/src/resolveWebIdentityCredentials.ts index f55a9c34a12e1..938db28e7fe1d 100644 --- a/packages/credential-provider-ini/src/resolveWebIdentityCredentials.ts +++ b/packages/credential-provider-ini/src/resolveWebIdentityCredentials.ts @@ -34,5 +34,6 @@ export const resolveWebIdentityCredentials = async ( roleArn: profile.role_arn, roleSessionName: profile.role_session_name, roleAssumerWithWebIdentity: options.roleAssumerWithWebIdentity, + logger: options.logger, })() ); diff --git a/packages/credential-provider-node/package.json b/packages/credential-provider-node/package.json index f42519b76efd3..ac24068d085a4 100644 --- a/packages/credential-provider-node/package.json +++ b/packages/credential-provider-node/package.json @@ -28,6 +28,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-sdk/credential-provider-env": "*", + "@aws-sdk/credential-provider-http": "*", "@aws-sdk/credential-provider-ini": "*", "@aws-sdk/credential-provider-process": "*", "@aws-sdk/credential-provider-sso": "*", diff --git a/packages/credential-provider-node/src/remoteProvider.spec.ts b/packages/credential-provider-node/src/remoteProvider.spec.ts index 320b91b9d1e5e..de91e24e1dea4 100644 --- a/packages/credential-provider-node/src/remoteProvider.spec.ts +++ b/packages/credential-provider-node/src/remoteProvider.spec.ts @@ -21,6 +21,10 @@ describe(remoteProvider.name, () => { accessKeyId: "mockInstanceMetadataAccessKeyId", secretAccessKey: "mockInstanceMetadataSecretAccessKey", }; + const mockFromHttp = jest.fn().mockReturnValue(async () => mockCredsFromContainer); + + const sampleFullUri = "http://localhost"; + const sampleRelativeUri = "/"; beforeEach(() => { process.env = { @@ -29,6 +33,10 @@ describe(remoteProvider.name, () => { [ENV_CMDS_FULL_URI]: undefined, [ENV_IMDS_DISABLED]: undefined, }; + + jest.mock("@aws-sdk/credential-provider-http", () => ({ + fromHttp: mockFromHttp, + })); (fromContainerMetadata as jest.Mock).mockReturnValue(async () => mockCredsFromContainer); (fromInstanceMetadata as jest.Mock).mockReturnValue(async () => mockSourceCredsFromInstanceMetadata); }); @@ -38,16 +46,23 @@ describe(remoteProvider.name, () => { jest.clearAllMocks(); }); - it.each([ENV_CMDS_RELATIVE_URI, ENV_CMDS_FULL_URI])( - "returns fromContainerMetadata if env['%s'] is set", - async (key) => { - process.env[key] = "defined"; - const receivedCreds = await (await remoteProvider(mockInit))(); - expect(receivedCreds).toStrictEqual(mockCredsFromContainer); - expect(fromContainerMetadata).toHaveBeenCalledWith(mockInit); - expect(fromInstanceMetadata).not.toHaveBeenCalled(); - } - ); + it(`returns fromContainerMetadata if env[${ENV_CMDS_RELATIVE_URI}] is set`, async () => { + process.env[ENV_CMDS_RELATIVE_URI] = sampleRelativeUri; + const receivedCreds = await (await remoteProvider(mockInit))(); + expect(receivedCreds).toStrictEqual(mockCredsFromContainer); + expect(mockFromHttp).toHaveBeenCalledWith(mockInit); + expect(fromContainerMetadata).toHaveBeenCalledWith(mockInit); + expect(fromInstanceMetadata).not.toHaveBeenCalled(); + }); + + it(`returns fromContainerMetadata if env[${ENV_CMDS_FULL_URI}] is set`, async () => { + process.env[ENV_CMDS_FULL_URI] = sampleFullUri; + const receivedCreds = await (await remoteProvider(mockInit))(); + expect(receivedCreds).toStrictEqual(mockCredsFromContainer); + expect(mockFromHttp).toHaveBeenCalledWith(mockInit); + expect(fromContainerMetadata).toHaveBeenCalledWith(mockInit); + expect(fromInstanceMetadata).not.toHaveBeenCalled(); + }); it(`throws if env['${ENV_IMDS_DISABLED}'] is set`, async () => { process.env[ENV_IMDS_DISABLED] = "1"; @@ -60,6 +75,7 @@ describe(remoteProvider.name, () => { } catch (error) { expect(error).toStrictEqual(expectedError); } + expect(mockFromHttp).not.toHaveBeenCalled(); expect(fromContainerMetadata).not.toHaveBeenCalled(); expect(fromInstanceMetadata).not.toHaveBeenCalled(); }); @@ -69,5 +85,6 @@ describe(remoteProvider.name, () => { expect(receivedCreds).toStrictEqual(mockSourceCredsFromInstanceMetadata); expect(fromInstanceMetadata).toHaveBeenCalledWith(mockInit); expect(fromContainerMetadata).not.toHaveBeenCalled(); + expect(mockFromHttp).not.toHaveBeenCalled(); }); }); diff --git a/packages/credential-provider-node/src/remoteProvider.ts b/packages/credential-provider-node/src/remoteProvider.ts index 7bd4d93da1b40..318d8fdbe9388 100644 --- a/packages/credential-provider-node/src/remoteProvider.ts +++ b/packages/credential-provider-node/src/remoteProvider.ts @@ -1,7 +1,10 @@ import type { RemoteProviderInit } from "@smithy/credential-provider-imds"; -import { CredentialsProviderError } from "@smithy/property-provider"; +import { chain, CredentialsProviderError } from "@smithy/property-provider"; import type { AwsCredentialIdentityProvider } from "@smithy/types"; +/** + * @internal + */ export const ENV_IMDS_DISABLED = "AWS_EC2_METADATA_DISABLED"; /** @@ -13,8 +16,9 @@ export const remoteProvider = async (init: RemoteProviderInit): Promise { expect(validateSsoProfile).toHaveBeenCalledWith(mockSsoProfile); }); - it("calls resolveSSOCredentials with values from validated Sso profile", async () => { + it("calls resolveSSOCredentials with values from validated SSO profile", async () => { const mockValidatedSsoProfile = { sso_start_url: "mock_sso_start_url", sso_account_id: "mock_sso_account_id", @@ -119,7 +119,8 @@ describe(fromSSO.name, () => { ssoRoleName: mockValidatedSsoProfile.sso_role_name, profile: mockProfileName, ssoSession: undefined, - ssoClient: expect.any(SSOClient), + ssoClient: undefined, + clientConfig: undefined, }); }); }); diff --git a/packages/credential-provider-sso/src/fromSSO.ts b/packages/credential-provider-sso/src/fromSSO.ts index b39e05211b708..7ba762e292a9d 100644 --- a/packages/credential-provider-sso/src/fromSSO.ts +++ b/packages/credential-provider-sso/src/fromSSO.ts @@ -83,11 +83,7 @@ export const fromSSO = async () => { init.logger?.debug("@aws-sdk/credential-provider-sso", "fromSSO"); const { ssoStartUrl, ssoAccountId, ssoRegion, ssoRoleName, ssoSession } = init; - let { ssoClient } = init; - if (!ssoClient) { - const { SSOClient } = await import("./loadSso"); - ssoClient = new SSOClient(init.clientConfig ?? {}); - } + const { ssoClient } = init; const profileName = getProfileName(init); if (!ssoStartUrl && !ssoAccountId && !ssoRegion && !ssoRoleName && !ssoSession) { @@ -125,6 +121,7 @@ export const fromSSO = ssoRegion: sso_region, ssoRoleName: sso_role_name, ssoClient: ssoClient, + clientConfig: init.clientConfig, profile: profileName, }); } else if (!ssoStartUrl || !ssoAccountId || !ssoRegion || !ssoRoleName) { @@ -140,6 +137,7 @@ export const fromSSO = ssoRegion, ssoRoleName, ssoClient, + clientConfig: init.clientConfig, profile: profileName, }); } diff --git a/packages/credential-provider-sso/src/resolveSSOCredentials.ts b/packages/credential-provider-sso/src/resolveSSOCredentials.ts index 298f5ea4beca2..d7e78ffdf999f 100644 --- a/packages/credential-provider-sso/src/resolveSSOCredentials.ts +++ b/packages/credential-provider-sso/src/resolveSSOCredentials.ts @@ -18,6 +18,7 @@ export const resolveSSOCredentials = async ({ ssoRegion, ssoRoleName, ssoClient, + clientConfig, profile, }: FromSSOInit & SsoCredentialsParameters): Promise => { let token: SSOToken; @@ -55,7 +56,13 @@ export const resolveSSOCredentials = async ({ const { SSOClient, GetRoleCredentialsCommand } = await import("./loadSso"); - const sso = ssoClient || new SSOClient({ region: ssoRegion }); + const sso = + ssoClient || + new SSOClient( + Object.assign({}, clientConfig ?? {}, { + region: clientConfig?.region ?? ssoRegion, + }) + ); let ssoResp: GetRoleCredentialsCommandOutput; try { ssoResp = await sso.send(