Skip to content

Commit 3cac271

Browse files
committed
feat(credential-provider-assume-role): add fromTokenFile
1 parent bbb1ea0 commit 3cac271

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

packages/credential-provider-assume-role/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ The following options are supported:
4040
`~/.aws/config` by default.
4141
- `roleAssumer` - A function that assumes a role and returns a promise
4242
fulfilled with credentials for the assumed role.
43+
- `roleAssumerWithWebIdentity` - A function that assumes a role with web identity
44+
and returns a promise fulfilled with credentials for the assumed role.
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { AssumeRoleCommandInput, AssumeRoleWithWebIdentityCommandInput } from "@aws-sdk/client-sts";
2+
import { getMasterProfileName, parseKnownFiles, SourceProfileInit } from "@aws-sdk/credential-provider-ini";
3+
import { ProviderError } from "@aws-sdk/property-provider";
4+
import { ParsedIniData, Profile } from "@aws-sdk/shared-ini-file-loader";
5+
import { CredentialProvider, Credentials } from "@aws-sdk/types";
6+
import { readFileSync } from "fs";
7+
8+
export const ENV_TOKEN_FILE = "AWS_WEB_IDENTITY_TOKEN_FILE";
9+
export const ENV_ROLE_ARN = "AWS_ROLE_ARN";
10+
export const ENV_ROLE_SESSION_NAME = "AWS_ROLE_SESSION_NAME";
11+
12+
export interface FromTokenFileInit extends SourceProfileInit {
13+
/**
14+
* A function that assumes a role with web identity and returns a promise fulfilled with
15+
* credentials for the assumed role.
16+
*
17+
* @param sourceCreds The credentials with which to assume a role.
18+
* @param params
19+
*/
20+
roleAssumerWithWebIdentity: (params: AssumeRoleWithWebIdentityCommandInput) => Promise<Credentials>;
21+
/**
22+
* A function that assumes a role and returns a promise fulfilled with
23+
* credentials for the assumed role.
24+
*
25+
* @param sourceCreds The credentials with which to assume a role.
26+
* @param params
27+
*/
28+
roleAssumer?: (sourceCreds: Credentials, params: AssumeRoleCommandInput) => Promise<Credentials>;
29+
}
30+
31+
/**
32+
* Represents OIDC credentials from a file on disk.
33+
*/
34+
export const fromTokenFile = (init: FromTokenFileInit): CredentialProvider => async () => {
35+
if (process.env[ENV_TOKEN_FILE] && process.env[ENV_ROLE_ARN]) {
36+
return resolveCredentialsFromEnv(init);
37+
}
38+
const profiles = await parseKnownFiles(init);
39+
return resolveCredentialsFromIni(getMasterProfileName(init), profiles, init);
40+
};
41+
42+
const resolveCredentialsFromEnv = async (options: FromTokenFileInit) => {
43+
if (!options.roleAssumerWithWebIdentity) {
44+
throw new ProviderError(
45+
`Role Arn '${process.env[ENV_ROLE_ARN]}' needs to be assumed with web identity,` +
46+
` but no role assumption callback was provided.`,
47+
false
48+
);
49+
}
50+
return options.roleAssumerWithWebIdentity({
51+
WebIdentityToken: readFileSync(process.env[ENV_TOKEN_FILE]!, { encoding: "ascii" }),
52+
RoleArn: process.env[ENV_ROLE_ARN],
53+
RoleSessionName: process.env[ENV_ROLE_SESSION_NAME] || `aws-sdk-js-session-${Date.now()}`,
54+
});
55+
};
56+
57+
const resolveCredentialsFromIni = async (
58+
profileName: string,
59+
profiles: ParsedIniData,
60+
options: FromTokenFileInit,
61+
visitedProfiles: { [profileName: string]: true } = {}
62+
): Promise<Credentials> => {
63+
const data = profiles[profileName];
64+
65+
if (isAssumeRoleSourceProfile(data)) {
66+
const {
67+
role_arn: RoleArn,
68+
role_session_name: RoleSessionName = `aws-sdk-js-session-${Date.now()}`,
69+
source_profile,
70+
} = data;
71+
72+
if (!options.roleAssumer) {
73+
throw new ProviderError(
74+
`Profile '${profileName}' requires a role to be assumed,` + ` but no role assumption callback was provided.`,
75+
false
76+
);
77+
}
78+
79+
if (source_profile in visitedProfiles) {
80+
throw new ProviderError(
81+
`Detected a cycle attempting to resolve credentials for profile` +
82+
` ${getMasterProfileName(options)}. Profiles visited: ` +
83+
Object.keys(visitedProfiles).join(", "),
84+
false
85+
);
86+
}
87+
88+
const sourceCreds = resolveCredentialsFromIni(source_profile, profiles, options, {
89+
...visitedProfiles,
90+
[source_profile]: true,
91+
});
92+
93+
return options.roleAssumer(await sourceCreds, { RoleArn, RoleSessionName });
94+
} else {
95+
const {
96+
web_identity_token_file,
97+
role_arn: RoleArn,
98+
role_session_name: RoleSessionName = `aws-sdk-js-session-${Date.now()}`,
99+
} = data;
100+
101+
if (!options.roleAssumerWithWebIdentity) {
102+
throw new ProviderError(
103+
`Profile '${profileName}' requires a role to be assumed with web identity,` +
104+
` but no role assumption callback was provided.`,
105+
false
106+
);
107+
}
108+
109+
const WebIdentityToken = readFileSync(web_identity_token_file!, { encoding: "ascii" });
110+
return options.roleAssumerWithWebIdentity({ WebIdentityToken, RoleArn, RoleSessionName });
111+
}
112+
};
113+
114+
interface AssumeRoleSourceProfile extends Profile {
115+
role_arn: string;
116+
source_profile: string;
117+
}
118+
119+
const isAssumeRoleSourceProfile = (arg: any): arg is AssumeRoleSourceProfile =>
120+
Boolean(arg) &&
121+
typeof arg === "object" &&
122+
typeof arg.role_arn === "string" &&
123+
typeof arg.source_profile === "string" &&
124+
["undefined", "string"].indexOf(typeof arg.role_session_name) > -1 &&
125+
["undefined", "string"].indexOf(typeof arg.external_id) > -1 &&
126+
["undefined", "string"].indexOf(typeof arg.mfa_serial) > -1;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./fromTokenFile";

0 commit comments

Comments
 (0)