Skip to content

Commit 0161956

Browse files
author
Chase Coalwell
committed
add credential_process provider
1 parent 63364f8 commit 0161956

File tree

1 file changed

+144
-0
lines changed
  • packages/credential-provider-process/src

1 file changed

+144
-0
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import {CredentialProvider, Credentials} from '@aws-sdk/types';
2+
import {ProviderError} from '@aws-sdk/property-provider';
3+
import {
4+
loadSharedConfigFiles,
5+
ParsedIniData,
6+
SharedConfigFiles,
7+
SharedConfigInit,
8+
} from '@aws-sdk/shared-ini-file-loader';
9+
import { exec } from 'child_process';
10+
11+
const DEFAULT_PROFILE = 'default';
12+
export const ENV_PROFILE = 'AWS_PROFILE';
13+
14+
export interface FromIniInit extends SharedConfigInit {
15+
/**
16+
* The configuration profile to use.
17+
*/
18+
profile?: string;
19+
20+
/**
21+
* A promise that will be resolved with loaded and parsed credentials files.
22+
* Used to avoid loading shared config files multiple times.
23+
*/
24+
loadedConfig?: Promise<SharedConfigFiles>;
25+
}
26+
27+
/**
28+
* Creates a credential provider that will read from a credential_process specified
29+
* in ini files.
30+
*/
31+
export function fromProcess(init: FromIniInit = {}): CredentialProvider {
32+
return () => parseKnownFiles(init).then(profiles => resolveProcessCredentials(
33+
getMasterProfileName(init),
34+
profiles,
35+
init
36+
));
37+
}
38+
39+
function getMasterProfileName(init: FromIniInit): string {
40+
return init.profile || process.env[ENV_PROFILE] || DEFAULT_PROFILE;
41+
}
42+
43+
async function resolveProcessCredentials(
44+
profileName: string,
45+
profiles: ParsedIniData,
46+
options: FromIniInit
47+
): Promise<Credentials> {
48+
const profile = profiles[profileName];
49+
50+
if (profiles[profileName]) {
51+
const credentialProcess = profile['credential_process'];
52+
if (credentialProcess !== undefined) {
53+
return await execPromise(credentialProcess)
54+
.then((processResult: any) => {
55+
let data;
56+
try {
57+
data = JSON.parse(processResult);
58+
} catch {
59+
throw Error(`Profile ${profileName} credential_process returned invalid JSON.`)
60+
}
61+
62+
const {
63+
Version: version,
64+
AccessKeyId: accessKeyId,
65+
SecretAccessKey: secretAccessKey,
66+
SessionToken: sessionToken,
67+
Expiration: expiration
68+
} = data;
69+
70+
71+
if (version !== 1) {
72+
throw Error(`Profile ${profileName} credential_process did not return Version 1.`)
73+
}
74+
75+
if (accessKeyId === undefined || secretAccessKey === undefined) {
76+
throw Error(`Profile ${profileName} credential_process returned invalid credentials.`)
77+
}
78+
79+
if (expiration) {
80+
const currentTime = new Date();
81+
const expireTime = new Date(expiration);
82+
if (expireTime < currentTime) {
83+
throw Error(`Profile ${profileName} credential_process returned expired credentials.`)
84+
}
85+
return {
86+
accessKeyId,
87+
secretAccessKey,
88+
sessionToken,
89+
expiration: Math.floor(
90+
(new Date(expiration)).valueOf() / 1000
91+
),
92+
}
93+
} else {
94+
return {
95+
accessKeyId,
96+
secretAccessKey,
97+
sessionToken
98+
}
99+
}
100+
}).catch((error: Error) => {
101+
throw new ProviderError(
102+
error.message
103+
)
104+
});
105+
} else {
106+
throw new ProviderError(
107+
`Profile ${profileName} did not contain credential_process.`
108+
);
109+
}
110+
} else {
111+
// If the profile cannot be parsed or does not contain the default or
112+
// specified profile throw an error. This should be considered a terminal
113+
// resolution error if a profile has been specified by the user (whether via
114+
// a parameter, anenvironment variable, or another profile's `source_profile` key).
115+
throw new ProviderError(
116+
`Profile ${profileName} could not be found in shared credentials file.`
117+
);
118+
}
119+
}
120+
121+
function parseKnownFiles(init: FromIniInit): Promise<ParsedIniData> {
122+
const {loadedConfig = loadSharedConfigFiles(init)} = init;
123+
124+
return loadedConfig.then(parsedFiles => {
125+
const {configFile, credentialsFile} = parsedFiles;
126+
return {
127+
...configFile,
128+
...credentialsFile,
129+
};
130+
});
131+
}
132+
133+
function execPromise(command: string) {
134+
return new Promise(function(resolve, reject) {
135+
exec(command, (error, stdout, stderr) => {
136+
if (error) {
137+
reject(error);
138+
return;
139+
}
140+
141+
resolve(stdout.trim());
142+
});
143+
});
144+
}

0 commit comments

Comments
 (0)