Skip to content

Commit 0ca3da3

Browse files
authored
fix(credential-provider-ini): pass clientConfig to sso and sso-oidc inner clients (#6688)
* fix(credential-provider-ini): pass clientConfig to sso and sso-oidc inner clients * fix: undefined check
1 parent 0982bc4 commit 0ca3da3

11 files changed

+96
-50
lines changed

packages/credential-provider-ini/src/fromIni.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export interface FromIniInit extends SourceProfileInit, CredentialProviderOption
3939
roleAssumerWithWebIdentity?: (params: AssumeRoleWithWebIdentityParams) => Promise<AwsCredentialIdentity>;
4040

4141
/**
42-
* STSClientConfig to be used for creating STS Client for assuming role.
42+
* STSClientConfig or SSOClientConfig to be used for creating inner client
43+
* for auth operations.
4344
* @internal
4445
*/
4546
clientConfig?: any;

packages/credential-provider-ini/src/resolveSsoCredentials.spec.ts

+35
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,39 @@ describe(resolveSsoCredentials.name, () => {
5656
profile: mockProfileName,
5757
});
5858
});
59+
60+
it("passes through clientConfig and parentClientConfig to the fromSSO provider", async () => {
61+
const mockProfileName = "mockProfileName";
62+
const mockCreds: AwsCredentialIdentity = {
63+
accessKeyId: "mockAccessKeyId",
64+
secretAccessKey: "mockSecretAccessKey",
65+
};
66+
const requestHandler = vi.fn();
67+
const logger = vi.fn();
68+
69+
vi.mocked(fromSSO).mockReturnValue(() => Promise.resolve(mockCreds));
70+
71+
const receivedCreds = await resolveSsoCredentials(
72+
mockProfileName,
73+
{},
74+
{
75+
clientConfig: {
76+
requestHandler,
77+
},
78+
parentClientConfig: {
79+
logger,
80+
},
81+
}
82+
);
83+
expect(receivedCreds).toStrictEqual(mockCreds);
84+
expect(fromSSO).toHaveBeenCalledWith({
85+
profile: mockProfileName,
86+
clientConfig: {
87+
requestHandler,
88+
},
89+
parentClientConfig: {
90+
logger,
91+
},
92+
});
93+
});
5994
});

packages/credential-provider-ini/src/resolveSsoCredentials.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import { setCredentialFeature } from "@aws-sdk/core/client";
22
import type { SsoProfile } from "@aws-sdk/credential-provider-sso";
3-
import type { CredentialProviderOptions } from "@aws-sdk/types";
43
import type { IniSection, Profile } from "@smithy/types";
54

5+
import type { FromIniInit } from "./fromIni";
6+
67
/**
78
* @internal
89
*/
9-
export const resolveSsoCredentials = async (
10-
profile: string,
11-
profileData: IniSection,
12-
options: CredentialProviderOptions = {}
13-
) => {
10+
export const resolveSsoCredentials = async (profile: string, profileData: IniSection, options: FromIniInit = {}) => {
1411
const { fromSSO } = await import("@aws-sdk/credential-provider-sso");
1512
return fromSSO({
1613
profile,
1714
logger: options.logger,
15+
parentClientConfig: options.parentClientConfig,
16+
clientConfig: options.clientConfig,
1817
})().then((creds) => {
1918
if (profileData.sso_session) {
2019
return setCredentialFeature(creds, "CREDENTIALS_PROFILE_SSO", "r");

packages/credential-provider-sso/src/fromSSO.ts

+2
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export const fromSSO =
133133
ssoRoleName: sso_role_name,
134134
ssoClient: ssoClient,
135135
clientConfig: init.clientConfig,
136+
parentClientConfig: init.parentClientConfig,
136137
profile: profileName,
137138
});
138139
} else if (!ssoStartUrl || !ssoAccountId || !ssoRegion || !ssoRoleName) {
@@ -150,6 +151,7 @@ export const fromSSO =
150151
ssoRoleName,
151152
ssoClient,
152153
clientConfig: init.clientConfig,
154+
parentClientConfig: init.parentClientConfig,
153155
profile: profileName,
154156
});
155157
}

packages/credential-provider-sso/src/resolveSSOCredentials.ts

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const resolveSSOCredentials = async ({
2020
ssoRoleName,
2121
ssoClient,
2222
clientConfig,
23+
parentClientConfig,
2324
profile,
2425
logger,
2526
}: FromSSOInit & SsoCredentialsParameters): Promise<AwsCredentialIdentity> => {
@@ -65,6 +66,7 @@ export const resolveSSOCredentials = async ({
6566
ssoClient ||
6667
new SSOClient(
6768
Object.assign({}, clientConfig ?? {}, {
69+
logger: clientConfig?.logger ?? parentClientConfig?.logger,
6870
region: clientConfig?.region ?? ssoRegion,
6971
})
7072
);

packages/token-providers/src/fromSso.spec.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ describe(fromSso.name, () => {
4848
accessToken: "mockNewAccessToken",
4949
expiresIn: 3600,
5050
refreshToken: "mockNewRefreshToken",
51+
$metadata: {},
5152
};
5253
const mockNewToken = {
5354
token: mockNewTokenFromService.accessToken,
@@ -166,7 +167,7 @@ describe(fromSso.name, () => {
166167
const { fromSso } = await import("./fromSso");
167168
await expect(fromSso(mockInit)()).resolves.toStrictEqual(mockNewToken);
168169
expect(getNewSsoOidcToken).toHaveBeenCalledTimes(1);
169-
expect(getNewSsoOidcToken).toHaveBeenCalledWith(mockSsoToken, mockSsoSession.sso_region);
170+
expect(getNewSsoOidcToken).toHaveBeenCalledWith(mockSsoToken, mockSsoSession.sso_region, mockInit);
170171

171172
// Simulate token expiration.
172173
const ssoTokenExpiryError = new TokenProviderError(`SSO Token is expired. ${REFRESH_MESSAGE}`, false);
@@ -182,7 +183,7 @@ describe(fromSso.name, () => {
182183
const { fromSso } = await import("./fromSso");
183184
await expect(fromSso(mockInit)()).resolves.toStrictEqual(mockNewToken);
184185
expect(getNewSsoOidcToken).toHaveBeenCalledTimes(1);
185-
expect(getNewSsoOidcToken).toHaveBeenCalledWith(mockSsoToken, mockSsoSession.sso_region);
186+
expect(getNewSsoOidcToken).toHaveBeenCalledWith(mockSsoToken, mockSsoSession.sso_region, mockInit);
186187

187188
// Return a valid token for second call.
188189
const mockValidSsoToken = {
@@ -230,7 +231,11 @@ describe(fromSso.name, () => {
230231
token: mockValidSsoTokenInExpiryWindow.accessToken,
231232
expiration: new Date(mockValidSsoTokenInExpiryWindow.expiresAt),
232233
});
233-
expect(getNewSsoOidcToken).toHaveBeenCalledWith(mockValidSsoTokenInExpiryWindow, mockSsoSession.sso_region);
234+
expect(getNewSsoOidcToken).toHaveBeenCalledWith(
235+
mockValidSsoTokenInExpiryWindow,
236+
mockSsoSession.sso_region,
237+
mockInit
238+
);
234239
};
235240

236241
const throwErrorExpiredTokenTest = async (fromSsoImpl: typeof fromSso) => {
@@ -239,7 +244,7 @@ describe(fromSso.name, () => {
239244
throw ssoTokenExpiryError;
240245
});
241246
await expect(fromSsoImpl(mockInit)()).rejects.toStrictEqual(ssoTokenExpiryError);
242-
expect(getNewSsoOidcToken).toHaveBeenCalledWith(mockSsoToken, mockSsoSession.sso_region);
247+
expect(getNewSsoOidcToken).toHaveBeenCalledWith(mockSsoToken, mockSsoSession.sso_region, mockInit);
243248
};
244249

245250
afterEach(() => {
@@ -285,7 +290,7 @@ describe(fromSso.name, () => {
285290
const { fromSso } = await import("./fromSso");
286291
await expect(fromSso(mockInit)()).resolves.toStrictEqual(mockNewToken);
287292
expect(getNewSsoOidcToken).toHaveBeenCalledTimes(1);
288-
expect(getNewSsoOidcToken).toHaveBeenCalledWith(mockSsoToken, mockSsoSession.sso_region);
293+
expect(getNewSsoOidcToken).toHaveBeenCalledWith(mockSsoToken, mockSsoSession.sso_region, mockInit);
289294

290295
expect(writeSSOTokenToFile).toHaveBeenCalledWith(mockSsoSessionName, {
291296
...mockSsoToken,

packages/token-providers/src/fromSso.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import { writeSSOTokenToFile } from "./writeSSOTokenToFile";
2020
*/
2121
const lastRefreshAttemptTime = new Date(0);
2222

23-
export interface FromSsoInit extends SourceProfileInit, CredentialProviderOptions {}
23+
export interface FromSsoInit extends SourceProfileInit, CredentialProviderOptions {
24+
/**
25+
* @see SSOOIDCClientConfig in \@aws-sdk/client-sso-oidc.
26+
*/
27+
clientConfig?: any;
28+
}
2429

2530
/**
2631
* Creates a token provider that will read from SSO token cache or ssoOidc.createToken() call.
@@ -101,7 +106,7 @@ export const fromSso =
101106

102107
try {
103108
lastRefreshAttemptTime.setTime(Date.now());
104-
const newSsoOidcToken = await getNewSsoOidcToken(ssoToken, ssoRegion);
109+
const newSsoOidcToken = await getNewSsoOidcToken(ssoToken, ssoRegion, init);
105110
validateTokenKey("accessToken", newSsoOidcToken.accessToken);
106111
validateTokenKey("expiresIn", newSsoOidcToken.expiresIn);
107112
const newTokenExpiration = new Date(Date.now() + newSsoOidcToken.expiresIn! * 1000);

packages/token-providers/src/getNewSsoOidcToken.spec.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe(getNewSsoOidcToken.name, () => {
4949
} catch (error) {
5050
expect(error).toStrictEqual(mockError);
5151
}
52-
expect(getSsoOidcClient).toHaveBeenCalledWith(mockSsoRegion);
52+
expect(getSsoOidcClient).toHaveBeenCalledWith(mockSsoRegion, {});
5353
expect(mockSend).not.toHaveBeenCalled();
5454
expect(CreateTokenCommand).not.toHaveBeenCalled();
5555
});
@@ -63,7 +63,7 @@ describe(getNewSsoOidcToken.name, () => {
6363
} catch (error) {
6464
expect(error).toStrictEqual(mockError);
6565
}
66-
expect(getSsoOidcClient).toHaveBeenCalledWith(mockSsoRegion);
66+
expect(getSsoOidcClient).toHaveBeenCalledWith(mockSsoRegion, {});
6767
expect(mockSendWithError).toHaveBeenCalledWith(mockCreateTokenArgs);
6868
expect(CreateTokenCommand).toHaveBeenCalledWith(mockCreateTokenArgs);
6969
});
@@ -78,7 +78,7 @@ describe(getNewSsoOidcToken.name, () => {
7878
} catch (error) {
7979
expect(error).toStrictEqual(mockError);
8080
}
81-
expect(getSsoOidcClient).toHaveBeenCalledWith(mockSsoRegion);
81+
expect(getSsoOidcClient).toHaveBeenCalledWith(mockSsoRegion, {});
8282
expect(mockSend).not.toHaveBeenCalled();
8383
expect(CreateTokenCommand).toHaveBeenCalledWith(mockCreateTokenArgs);
8484
});
@@ -90,6 +90,6 @@ describe(getNewSsoOidcToken.name, () => {
9090
expect(newSsoOidcToken).toEqual(mockNewToken as any);
9191
expect(CreateTokenCommand).toHaveBeenCalledWith(mockCreateTokenArgs);
9292
expect(mockSend).toHaveBeenCalledWith(mockCreateTokenArgs);
93-
expect(getSsoOidcClient).toHaveBeenCalledWith(mockSsoRegion);
93+
expect(getSsoOidcClient).toHaveBeenCalledWith(mockSsoRegion, {});
9494
});
9595
});

packages/token-providers/src/getNewSsoOidcToken.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { SSOToken } from "@smithy/shared-ini-file-loader";
22

3+
import { FromSsoInit } from "./fromSso";
34
import { getSsoOidcClient } from "./getSsoOidcClient";
45

56
/**
67
* Returns a new SSO OIDC token from ssoOids.createToken() API call.
78
* @internal
89
*/
9-
export const getNewSsoOidcToken = async (ssoToken: SSOToken, ssoRegion: string) => {
10+
export const getNewSsoOidcToken = async (ssoToken: SSOToken, ssoRegion: string, init: FromSsoInit = {}) => {
1011
// @ts-ignore Cannot find module '@aws-sdk/client-sso-oidc'
1112
const { CreateTokenCommand } = await import("@aws-sdk/client-sso-oidc");
1213

13-
const ssoOidcClient = await getSsoOidcClient(ssoRegion);
14+
const ssoOidcClient = await getSsoOidcClient(ssoRegion, init);
1415
return ssoOidcClient.send(
1516
new CreateTokenCommand({
1617
clientId: ssoToken.clientId,

packages/token-providers/src/getSsoOidcClient.spec.ts

+17-16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ vi.mock("@aws-sdk/client-sso-oidc");
55

66
describe("getSsoOidcClient", () => {
77
const mockSsoRegion = "mockSsoRegion";
8+
const mockRequestHandler = {
9+
protocol: "http",
10+
};
811
const getMockClient = (region: string) => ({ region });
912

1013
beforeEach(() => {
@@ -22,24 +25,22 @@ describe("getSsoOidcClient", () => {
2225
expect(SSOOIDCClient).toHaveBeenCalledTimes(1);
2326
});
2427

25-
it("returns SSOOIDC client from hash if already created", async () => {
26-
const { getSsoOidcClient } = await import("./getSsoOidcClient");
27-
expect(await getSsoOidcClient(mockSsoRegion)).toEqual(getMockClient(mockSsoRegion) as any);
28-
expect(SSOOIDCClient).toHaveBeenCalledTimes(1);
29-
expect(await getSsoOidcClient(mockSsoRegion)).toEqual(getMockClient(mockSsoRegion) as any);
30-
expect(SSOOIDCClient).toHaveBeenCalledTimes(1);
31-
});
32-
33-
it("creates new SSOOIDC client per region", async () => {
28+
it("passes through clientConfig and parentClientConfig.logger", async () => {
3429
const { getSsoOidcClient } = await import("./getSsoOidcClient");
3530
const mockSsoRegion1 = `${mockSsoRegion}1`;
36-
expect(await getSsoOidcClient(mockSsoRegion1)).toEqual(getMockClient(mockSsoRegion1) as any);
31+
expect(
32+
await getSsoOidcClient(mockSsoRegion1, {
33+
clientConfig: { requestHandler: mockRequestHandler },
34+
parentClientConfig: { logger: console },
35+
})
36+
).toEqual({
37+
region: mockSsoRegion1,
38+
} as any);
3739
expect(SSOOIDCClient).toHaveBeenCalledTimes(1);
38-
expect(SSOOIDCClient).toHaveBeenCalledWith({ region: mockSsoRegion1 });
39-
40-
const mockSsoRegion2 = `${mockSsoRegion}2`;
41-
expect(await getSsoOidcClient(mockSsoRegion2)).toEqual(getMockClient(mockSsoRegion2) as any);
42-
expect(SSOOIDCClient).toHaveBeenCalledTimes(2);
43-
expect(SSOOIDCClient).toHaveBeenNthCalledWith(2, { region: mockSsoRegion2 });
40+
expect(SSOOIDCClient).toHaveBeenCalledWith({
41+
region: mockSsoRegion1,
42+
requestHandler: mockRequestHandler,
43+
logger: console,
44+
});
4445
});
4546
});
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
1-
const ssoOidcClientsHash: Record<string, any> = {};
1+
import { FromSsoInit } from "./fromSso";
22

33
/**
4-
* Returns a SSOOIDC client for the given region. If the client has already been created,
5-
* it will be returned from the hash.
4+
* Returns a SSOOIDC client for the given region.
65
* @internal
76
*/
8-
export const getSsoOidcClient = async (ssoRegion: string) => {
7+
export const getSsoOidcClient = async (ssoRegion: string, init: FromSsoInit = {}) => {
98
// @ts-ignore Cannot find module '@aws-sdk/client-sso-oidc'
109
const { SSOOIDCClient } = await import("@aws-sdk/client-sso-oidc");
1110

12-
// return ssoOidsClient if already created.
13-
if (ssoOidcClientsHash[ssoRegion]) {
14-
return ssoOidcClientsHash[ssoRegion];
15-
}
16-
17-
// Create new SSOOIDC client, and store is in hash.
18-
// If we need to support configuration of SsoOidc client in future through code,
19-
// the provision to pass region from client configuration needs to be added.
20-
const ssoOidcClient = new SSOOIDCClient({ region: ssoRegion });
21-
ssoOidcClientsHash[ssoRegion] = ssoOidcClient;
11+
const ssoOidcClient = new SSOOIDCClient(
12+
Object.assign({}, init.clientConfig ?? {}, {
13+
region: ssoRegion ?? init.clientConfig?.region,
14+
logger: init.clientConfig?.logger ?? init.parentClientConfig?.logger,
15+
})
16+
);
2217
return ssoOidcClient;
2318
};

0 commit comments

Comments
 (0)