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