Skip to content

Commit 5d28330

Browse files
TingDaoKgraebm
andauthored
profile credential provider binding (#214)
- Profile credential provider - Process credential provider - Environment credential provider - Chain credential provider Co-authored-by: Dengke Tang <dengket@amazon.com> Co-authored-by: Michael Graeb <graebm@amazon.com>
1 parent 1029f70 commit 5d28330

File tree

7 files changed

+417
-9
lines changed

7 files changed

+417
-9
lines changed

awscrt/auth.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ def new_default_chain(cls, client_bootstrap):
117117
3. (conditional, off by default) ECS
118118
4. (conditional, on by default) EC2 Instance Metadata
119119
120+
Args:
121+
client_bootstrap (ClientBootstrap): Client bootstrap to use when initiating socket connection.
122+
120123
Returns:
121124
AwsCredentialsProvider:
122125
"""
@@ -130,6 +133,11 @@ def new_static(cls, access_key_id, secret_access_key, session_token=None):
130133
"""
131134
Create a simple provider that just returns a fixed set of credentials.
132135
136+
Args:
137+
access_key_id (str): Access key ID
138+
secret_access_key (str): Secret access key
139+
session_token (Optional[str]): Optional session token
140+
133141
Returns:
134142
AwsCredentialsProvider:
135143
"""
@@ -140,6 +148,113 @@ def new_static(cls, access_key_id, secret_access_key, session_token=None):
140148
binding = _awscrt.credentials_provider_new_static(access_key_id, secret_access_key, session_token)
141149
return cls(binding)
142150

151+
@classmethod
152+
def new_profile(
153+
cls,
154+
client_bootstrap,
155+
profile_name=None,
156+
config_filepath=None,
157+
credentials_filepath=None):
158+
"""
159+
Creates a provider that sources credentials from key-value profiles
160+
loaded from the aws credentials file.
161+
162+
Args:
163+
client_bootstrap: Client bootstrap to use when initiating socket connection.
164+
165+
profile_name (Optional[str]): Name of profile to use.
166+
If not set, uses value from AWS_PROFILE environment variable.
167+
If that is not set, uses value of "default"
168+
169+
config_filepath (Optional[str]): Path to profile config file.
170+
If not set, uses value from AWS_CONFIG_FILE environment variable.
171+
If that is not set, uses value of "~/.aws/config"
172+
173+
credentials_filepath (Optional[str]): Path to profile credentials file.
174+
If not set, uses value from AWS_SHARED_CREDENTIALS_FILE environment variable.
175+
If that is not set, uses value of "~/.aws/credentials"
176+
177+
Returns:
178+
AwsCredentialsProvider:
179+
"""
180+
assert isinstance(client_bootstrap, ClientBootstrap)
181+
assert isinstance(profile_name, str) or profile_name is None
182+
assert isinstance(config_filepath, str) or config_filepath is None
183+
assert isinstance(credentials_filepath, str) or credentials_filepath is None
184+
185+
binding = _awscrt.credentials_provider_new_profile(
186+
client_bootstrap, profile_name, config_filepath, credentials_filepath)
187+
return cls(binding)
188+
189+
@classmethod
190+
def new_process(cls, profile_to_use=None):
191+
"""
192+
Creates a provider that sources credentials from running an external command or process.
193+
194+
The command to run is sourced from a profile in the AWS config file, using the standard
195+
profile selection rules. The profile key the command is read from is "credential_process."
196+
E.g.:
197+
[default]
198+
credential_process=/opt/amazon/bin/my-credential-fetcher --argsA=abc
199+
On successfully running the command, the output should be a json data with the following
200+
format:
201+
{
202+
"Version": 1,
203+
"AccessKeyId": "accesskey",
204+
"SecretAccessKey": "secretAccessKey"
205+
"SessionToken": "....",
206+
"Expiration": "2019-05-29T00:21:43Z"
207+
}
208+
Version here identifies the command output format version.
209+
This provider is not part of the default provider chain.
210+
211+
Args:
212+
profile_to_use (Optional[str]): Name of profile in which to look for credential_process.
213+
If not set, uses value from AWS_PROFILE environment variable.
214+
If that is not set, uses value of "default"
215+
216+
Returns:
217+
AwsCredentialsProvider:
218+
"""
219+
220+
binding = _awscrt.credentials_provider_new_process(profile_to_use)
221+
return cls(binding)
222+
223+
@classmethod
224+
def new_environment(cls):
225+
"""
226+
Creates a provider that returns credentials sourced from environment variables.
227+
228+
* AWS_ACCESS_KEY_ID
229+
* AWS_SECRET_ACCESS_KEY
230+
* AWS_SESSION_TOKEN
231+
232+
Returns:
233+
AwsCredentialsProvider:
234+
"""
235+
236+
binding = _awscrt.credentials_provider_new_environment()
237+
return cls(binding)
238+
239+
@classmethod
240+
def new_chain(cls, providers):
241+
"""
242+
Creates a provider that sources credentials from an ordered sequence of providers.
243+
244+
This provider uses the first set of credentials successfully queried.
245+
Providers are queried one at a time; a provider is not queried until the
246+
preceding provider has failed to source credentials.
247+
248+
Args:
249+
providers (List[AwsCredentialsProvider]): List of credentials providers.
250+
251+
Returns:
252+
AwsCredentialsProvider:
253+
"""
254+
255+
binding = _awscrt.credentials_provider_new_chain(providers)
256+
return cls(binding)
257+
143258
def get_credentials(self):
144259
future = Future()
145260

source/auth.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ PyObject *aws_py_credentials_session_token(PyObject *self, PyObject *args);
1616
PyObject *aws_py_credentials_provider_get_credentials(PyObject *self, PyObject *args);
1717
PyObject *aws_py_credentials_provider_new_chain_default(PyObject *self, PyObject *args);
1818
PyObject *aws_py_credentials_provider_new_static(PyObject *self, PyObject *args);
19+
PyObject *aws_py_credentials_provider_new_profile(PyObject *self, PyObject *args);
20+
PyObject *aws_py_credentials_provider_new_process(PyObject *self, PyObject *args);
21+
PyObject *aws_py_credentials_provider_new_environment(PyObject *self, PyObject *args);
22+
PyObject *aws_py_credentials_provider_new_chain(PyObject *self, PyObject *args);
1923

2024
PyObject *aws_py_signing_config_new(PyObject *self, PyObject *args);
2125
PyObject *aws_py_signing_config_get_algorithm(PyObject *self, PyObject *args);

source/auth_credentials.c

Lines changed: 212 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,10 @@ PyObject *aws_py_credentials_session_token(PyObject *self, PyObject *args) {
120120
*/
121121
struct credentials_provider_binding {
122122
struct aws_credentials_provider *native;
123-
124-
/* Dependencies that must outlive this.
125-
* Note that different types of providers have different dependencies */
126-
PyObject *bootstrap;
127123
};
128124

129125
/* Finally clean up binding (after capsule destructor runs and credentials provider shutdown completes) */
130126
static void s_credentials_provider_binding_clean_up(struct credentials_provider_binding *binding) {
131-
Py_XDECREF(binding->bootstrap);
132127
aws_mem_release(aws_py_get_allocator(), binding);
133128
}
134129

@@ -273,9 +268,6 @@ PyObject *aws_py_credentials_provider_new_chain_default(PyObject *self, PyObject
273268
/* From hereon, we need to clean up if errors occur.
274269
* Fortunately, the capsule destructor will clean up anything stored inside the binding */
275270

276-
binding->bootstrap = bootstrap_py;
277-
Py_INCREF(binding->bootstrap);
278-
279271
struct aws_credentials_provider_chain_default_options options = {
280272
.bootstrap = bootstrap,
281273
.shutdown_options = {s_credentials_provider_shutdown_complete, binding},
@@ -340,3 +332,215 @@ PyObject *aws_py_credentials_provider_new_static(PyObject *self, PyObject *args)
340332
Py_DECREF(capsule);
341333
return NULL;
342334
}
335+
336+
PyObject *aws_py_credentials_provider_new_profile(PyObject *self, PyObject *args) {
337+
(void)self;
338+
struct aws_allocator *allocator = aws_py_get_allocator();
339+
340+
PyObject *bootstrap_py;
341+
struct aws_byte_cursor profile_name;
342+
struct aws_byte_cursor config_file_name;
343+
struct aws_byte_cursor credentials_file_name;
344+
345+
if (!PyArg_ParseTuple(
346+
args,
347+
"Oz#z#z#",
348+
&bootstrap_py,
349+
&profile_name.ptr,
350+
&profile_name.len,
351+
&config_file_name.ptr,
352+
&config_file_name.len,
353+
&credentials_file_name.ptr,
354+
&credentials_file_name.len)) {
355+
return NULL;
356+
}
357+
358+
struct aws_client_bootstrap *bootstrap = aws_py_get_client_bootstrap(bootstrap_py);
359+
if (!bootstrap) {
360+
return NULL;
361+
}
362+
363+
struct credentials_provider_binding *binding;
364+
PyObject *capsule = s_new_credentials_provider_binding_and_capsule(&binding);
365+
if (!capsule) {
366+
return NULL;
367+
}
368+
369+
/* From hereon, we need to clean up if errors occur.
370+
* Fortunately, the capsule destructor will clean up anything stored inside the binding */
371+
372+
struct aws_credentials_provider_profile_options options = {
373+
.bootstrap = bootstrap,
374+
.profile_name_override = profile_name,
375+
.config_file_name_override = config_file_name,
376+
.credentials_file_name_override = credentials_file_name,
377+
.shutdown_options =
378+
{
379+
.shutdown_callback = s_credentials_provider_shutdown_complete,
380+
.shutdown_user_data = binding,
381+
},
382+
};
383+
384+
binding->native = aws_credentials_provider_new_profile(allocator, &options);
385+
if (!binding->native) {
386+
PyErr_SetAwsLastError();
387+
goto error;
388+
}
389+
390+
return capsule;
391+
error:
392+
Py_DECREF(capsule);
393+
return NULL;
394+
}
395+
396+
PyObject *aws_py_credentials_provider_new_process(PyObject *self, PyObject *args) {
397+
(void)self;
398+
struct aws_allocator *allocator = aws_py_get_allocator();
399+
400+
struct aws_byte_cursor profile_to_use;
401+
402+
if (!PyArg_ParseTuple(args, "z#", &profile_to_use.ptr, &profile_to_use.len)) {
403+
return NULL;
404+
}
405+
406+
struct credentials_provider_binding *binding;
407+
PyObject *capsule = s_new_credentials_provider_binding_and_capsule(&binding);
408+
if (!capsule) {
409+
return NULL;
410+
}
411+
412+
/* From hereon, we need to clean up if errors occur.
413+
* Fortunately, the capsule destructor will clean up anything stored inside the binding */
414+
415+
struct aws_credentials_provider_process_options options = {
416+
.profile_to_use = profile_to_use,
417+
.shutdown_options =
418+
{
419+
.shutdown_callback = s_credentials_provider_shutdown_complete,
420+
.shutdown_user_data = binding,
421+
},
422+
};
423+
424+
binding->native = aws_credentials_provider_new_process(allocator, &options);
425+
if (!binding->native) {
426+
PyErr_SetAwsLastError();
427+
goto error;
428+
}
429+
430+
return capsule;
431+
error:
432+
Py_DECREF(capsule);
433+
return NULL;
434+
}
435+
436+
PyObject *aws_py_credentials_provider_new_environment(PyObject *self, PyObject *args) {
437+
(void)self;
438+
(void)args;
439+
struct aws_allocator *allocator = aws_py_get_allocator();
440+
441+
struct credentials_provider_binding *binding;
442+
PyObject *capsule = s_new_credentials_provider_binding_and_capsule(&binding);
443+
if (!capsule) {
444+
return NULL;
445+
}
446+
447+
/* From hereon, we need to clean up if errors occur.
448+
* Fortunately, the capsule destructor will clean up anything stored inside the binding */
449+
450+
struct aws_credentials_provider_environment_options options = {
451+
.shutdown_options =
452+
{
453+
.shutdown_callback = s_credentials_provider_shutdown_complete,
454+
.shutdown_user_data = binding,
455+
},
456+
};
457+
458+
binding->native = aws_credentials_provider_new_environment(allocator, &options);
459+
if (!binding->native) {
460+
PyErr_SetAwsLastError();
461+
goto error;
462+
}
463+
464+
return capsule;
465+
error:
466+
Py_DECREF(capsule);
467+
return NULL;
468+
}
469+
470+
PyObject *aws_py_credentials_provider_new_chain(PyObject *self, PyObject *args) {
471+
(void)self;
472+
struct aws_allocator *allocator = aws_py_get_allocator();
473+
474+
PyObject *providers_arg;
475+
476+
if (!PyArg_ParseTuple(args, "O", &providers_arg)) {
477+
return NULL;
478+
}
479+
480+
/* From hereon, we need to clean up if errors occur.
481+
* Fortunately, the capsule destructor will clean up anything stored inside the binding */
482+
bool success = false;
483+
PyObject *providers_pyseq = NULL;
484+
struct aws_credentials_provider **providers_carray = NULL;
485+
PyObject *capsule = NULL;
486+
487+
/* Need temporary C-array of pointers to underlying aws_credentials_provider structs */
488+
providers_pyseq = PySequence_Fast(providers_arg, "Expected sequence of AwsCredentialsProviders");
489+
if (!providers_pyseq) {
490+
goto done;
491+
}
492+
size_t provider_count = (size_t)PySequence_Fast_GET_SIZE(providers_pyseq);
493+
if (provider_count == 0) {
494+
PyErr_SetString(PyExc_ValueError, "Must supply at least one AwsCredentialsProvider.");
495+
goto done;
496+
}
497+
498+
providers_carray = aws_mem_calloc(allocator, provider_count, sizeof(void *));
499+
if (!providers_carray) {
500+
PyErr_SetAwsLastError();
501+
goto done;
502+
}
503+
504+
for (size_t i = 0; i < provider_count; ++i) {
505+
PyObject *provider_py = PySequence_Fast_GET_ITEM(providers_pyseq, i);
506+
providers_carray[i] = aws_py_get_credentials_provider(provider_py);
507+
if (!providers_carray[i]) {
508+
goto done;
509+
}
510+
}
511+
512+
struct credentials_provider_binding *binding;
513+
capsule = s_new_credentials_provider_binding_and_capsule(&binding);
514+
if (!capsule) {
515+
goto done;
516+
}
517+
518+
struct aws_credentials_provider_chain_options options = {
519+
.provider_count = provider_count,
520+
.providers = providers_carray,
521+
.shutdown_options =
522+
{
523+
.shutdown_callback = s_credentials_provider_shutdown_complete,
524+
.shutdown_user_data = binding,
525+
},
526+
};
527+
528+
binding->native = aws_credentials_provider_new_chain(allocator, &options);
529+
if (!binding->native) {
530+
PyErr_SetAwsLastError();
531+
goto done;
532+
}
533+
534+
success = true;
535+
536+
done:
537+
Py_XDECREF(providers_pyseq);
538+
aws_mem_release(allocator, providers_carray);
539+
540+
if (success) {
541+
return capsule;
542+
}
543+
544+
Py_XDECREF(capsule);
545+
return NULL;
546+
}

0 commit comments

Comments
 (0)