Skip to content

Commit 3ed55ed

Browse files
[CDRIVER-4382]: Implement on-demand credentials for AWS in KMS (#1043)
* Allow a null URI for obtaining AWS credentials * Implement on-demand credential loading for the AWS KMS provider * New error handling since we always use NEEDS_CREDENTIALS
1 parent b448feb commit 3ed55ed

File tree

3 files changed

+144
-30
lines changed

3 files changed

+144
-30
lines changed

src/libmongoc/src/mongoc/mongoc-cluster-aws.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -513,12 +513,14 @@ _mongoc_aws_credentials_obtain (mongoc_uri_t *uri,
513513
creds->secret_access_key = NULL;
514514
creds->session_token = NULL;
515515

516-
TRACE ("%s", "checking URI for credentials");
517-
if (!_obtain_creds_from_uri (creds, uri, error)) {
518-
goto fail;
519-
}
520-
if (!_creds_empty (creds)) {
521-
goto succeed;
516+
if (uri) {
517+
TRACE ("%s", "checking URI for credentials");
518+
if (!_obtain_creds_from_uri (creds, uri, error)) {
519+
goto fail;
520+
}
521+
if (!_creds_empty (creds)) {
522+
goto succeed;
523+
}
522524
}
523525

524526
TRACE ("%s", "checking environment variables for credentials");

src/libmongoc/src/mongoc/mongoc-crypt.c

Lines changed: 134 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "mongoc-host-list-private.h"
2828
#include "mongoc-stream-private.h"
2929
#include "mongoc-ssl-private.h"
30+
#include "mongoc-cluster-aws-private.h"
3031
#include "mongoc-util-private.h"
3132

3233
struct __mongoc_crypt_t {
@@ -35,6 +36,10 @@ struct __mongoc_crypt_t {
3536
mongoc_ssl_opt_t aws_tls_opt;
3637
mongoc_ssl_opt_t azure_tls_opt;
3738
mongoc_ssl_opt_t gcp_tls_opt;
39+
/// The kmsProviders that were provided by the user when encryption was
40+
/// initiated. We need to remember this in case we need to load on-demand
41+
/// credentials.
42+
bson_t kms_providers;
3843
mc_kms_credentials_callback creds_cb;
3944
};
4045

@@ -576,37 +581,144 @@ _state_need_kms (_state_machine_t *state_machine, bson_error_t *error)
576581
#undef BUFFER_SIZE
577582
}
578583

584+
/**
585+
* @brief Determine whether the given kmsProviders has an empty 'aws'
586+
* subdocument
587+
*
588+
* @param kms_providers The user-provided kmsProviders
589+
* @param error Output parameter for possible errors.
590+
* @return true If 'aws' is present and an empty subdocument
591+
* @return false Otherwise or on error
592+
*/
593+
static bool
594+
_needs_on_demand_aws_kms (bson_t const *kms_providers, bson_error_t *error)
595+
{
596+
bson_iter_t iter;
597+
if (!bson_iter_init_find (&iter, kms_providers, "aws")) {
598+
// No "aws" subdocument
599+
return false;
600+
}
601+
602+
if (!BSON_ITER_HOLDS_DOCUMENT (&iter)) {
603+
// "aws" is not a document? Should be validated by libmongocrypt
604+
return false;
605+
}
606+
607+
const uint8_t *dataptr;
608+
uint32_t datalen;
609+
bson_iter_document (&iter, &datalen, &dataptr);
610+
bson_t subdoc;
611+
if (!bson_init_static (&subdoc, dataptr, datalen)) {
612+
// Invalid "aws" document? Should be validated by libmongocrypt
613+
return false;
614+
}
615+
616+
if (bson_empty (&subdoc)) {
617+
// "aws" is present and is an empty subdocument, which means that the user
618+
// requests that the AWS credentials be loaded on-demand from the
619+
// environment.
620+
return true;
621+
} else {
622+
// "aws" is present and is non-empty, which means that the user has
623+
// already provided credentials for AWS.
624+
return false;
625+
}
626+
}
627+
628+
/**
629+
* @brief Attempt to load AWS credentials from the environment and insert them
630+
* into the given kmsProviders bson document on the "aws" property.
631+
*
632+
* @param out A kmsProviders object to update
633+
* @param error An error-out parameter
634+
* @return true If there was no error and we successfully loaded credentials.
635+
* @return false If there was an error while updating the BSON data or obtaining
636+
* credentials.
637+
*/
638+
static bool
639+
_try_add_aws_from_env (bson_t *out, bson_error_t *error)
640+
{
641+
// Attempt to obtain AWS credentials from the environment.
642+
_mongoc_aws_credentials_t creds;
643+
if (!_mongoc_aws_credentials_obtain (NULL, &creds, error)) {
644+
// Error while obtaining credentials
645+
return false;
646+
}
647+
648+
// Build the new "aws" subdoc
649+
bson_t aws;
650+
bool okay =
651+
BSON_APPEND_DOCUMENT_BEGIN (out, "aws", &aws)
652+
// Add the accessKeyId and the secretAccessKey
653+
&& BSON_APPEND_UTF8 (&aws, "accessKeyId", creds.access_key_id) //
654+
&& BSON_APPEND_UTF8 (&aws, "secretAccessKey", creds.secret_access_key) //
655+
// Add the sessionToken, if we got one:
656+
&& (!creds.session_token ||
657+
BSON_APPEND_UTF8 (&aws, "sessionToken", creds.session_token)) //
658+
// Finish the document
659+
&& bson_append_document_end (out, &aws);
660+
BSON_ASSERT (okay && "Failed to build aws credentials document");
661+
// Good!
662+
return true;
663+
}
664+
579665
static bool
580666
_state_need_kms_credentials (_state_machine_t *sm, bson_error_t *error)
581667
{
582668
bson_t creds = BSON_INITIALIZER;
583-
BSON_ASSERT (sm->crypt->creds_cb.fn);
584669
const bson_t empty = BSON_INITIALIZER;
670+
bool okay = false;
671+
672+
if (sm->crypt->creds_cb.fn) {
673+
// We have a user-provided credentials callback. Try it.
674+
if (!sm->crypt->creds_cb.fn (
675+
sm->crypt->creds_cb.userdata, &empty, &creds, error)) {
676+
// User-provided callback indicated failure
677+
if (!error->code) {
678+
// The callback did not set an error, so we'll provide a default
679+
// one.
680+
bson_set_error (error,
681+
MONGOC_ERROR_CLIENT_SIDE_ENCRYPTION,
682+
MONGOC_ERROR_CLIENT_INVALID_ENCRYPTION_ARG,
683+
"The user-provided callback for on-demand KMS "
684+
"credentials failed.");
685+
}
686+
goto fail;
687+
}
688+
// The user's callback reported success
689+
}
585690

586-
if (!sm->crypt->creds_cb.fn (
587-
sm->crypt->creds_cb.userdata, &empty, &creds, error)) {
588-
// The callback reports that it has failed
589-
if (!error->code) {
590-
bson_set_error (error,
591-
MONGOC_ERROR_CLIENT_SIDE_ENCRYPTION,
592-
MONGOC_ERROR_CLIENT_INVALID_ENCRYPTION_ARG,
593-
"Unknown error from user-provided callback for "
594-
"on-demand KMS credentials");
691+
bson_iter_t iter;
692+
const bool callback_provided_aws =
693+
bson_iter_init_find (&iter, &creds, "aws");
694+
695+
if (!callback_provided_aws &&
696+
_needs_on_demand_aws_kms (&sm->crypt->kms_providers, error)) {
697+
// The original kmsProviders had an empty "aws" property, and the
698+
// user-provided callback did not fill in a new "aws" property for us.
699+
// Attempt instead to load the AWS credentials from the environment:
700+
if (!_try_add_aws_from_env (&creds, error)) {
701+
// Error while trying to add AWS credentials
702+
goto fail;
595703
}
596-
return false;
597704
}
598705

599-
mongocrypt_binary_t *data = mongocrypt_binary_new_from_data (
706+
// Now actually send that data to libmongocrypt
707+
mongocrypt_binary_t *const def = mongocrypt_binary_new_from_data (
600708
(uint8_t *) bson_get_data (&creds), creds.len);
601-
bool okay = mongocrypt_ctx_provide_kms_providers (sm->ctx, data);
709+
okay = mongocrypt_ctx_provide_kms_providers (sm->ctx, def);
602710
if (!okay) {
603711
_ctx_check_error (sm->ctx, error, true);
604712
}
605-
mongocrypt_binary_destroy (data);
713+
mongocrypt_binary_destroy (def);
714+
715+
fail:
606716
bson_destroy (&creds);
717+
607718
return okay;
608719
}
609720

721+
610722
static bool
611723
_state_ready (_state_machine_t *state_machine,
612724
bson_t *result,
@@ -965,6 +1077,10 @@ _mongoc_crypt_new (const bson_t *kms_providers,
9651077
crypt = bson_malloc0 (sizeof (*crypt));
9661078
crypt->handle = mongocrypt_new ();
9671079

1080+
// Stash away a copy of the user's kmsProviders in case we need to lazily
1081+
// load credentials.
1082+
bson_copy_to (kms_providers, &crypt->kms_providers);
1083+
9681084
if (!_parse_all_tls_opts (crypt, tls_opts, error)) {
9691085
goto fail;
9701086
}
@@ -979,12 +1095,6 @@ _mongoc_crypt_new (const bson_t *kms_providers,
9791095
goto fail;
9801096
}
9811097

982-
if (creds_cb.fn) {
983-
// The user has provided a callback to lazily obtain KMS credentials. We
984-
// need to opt-in to the libmongocrypt feature.
985-
mongocrypt_setopt_use_need_kms_credentials_state (crypt->handle);
986-
}
987-
9881098
if (schema_map) {
9891099
schema_map_bin = mongocrypt_binary_new_from_data (
9901100
(uint8_t *) bson_get_data (schema_map), schema_map->len);
@@ -1028,6 +1138,9 @@ _mongoc_crypt_new (const bson_t *kms_providers,
10281138
}
10291139
}
10301140

1141+
// Enable the NEEDS_CREDENTIALS state for on-demand credential loading
1142+
mongocrypt_setopt_use_need_kms_credentials_state (crypt->handle);
1143+
10311144
if (!mongocrypt_init (crypt->handle)) {
10321145
_crypt_check_error (crypt->handle, error, true);
10331146
goto fail;
@@ -1082,6 +1195,7 @@ _mongoc_crypt_destroy (_mongoc_crypt_t *crypt)
10821195
_mongoc_ssl_opts_cleanup (&crypt->aws_tls_opt, true /* free_internal */);
10831196
_mongoc_ssl_opts_cleanup (&crypt->azure_tls_opt, true /* free_internal */);
10841197
_mongoc_ssl_opts_cleanup (&crypt->gcp_tls_opt, true /* free_internal */);
1198+
bson_destroy (&crypt->kms_providers);
10851199
bson_free (crypt);
10861200
}
10871201

src/libmongoc/tests/test-mongoc-client-side-encryption.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4830,10 +4830,8 @@ test_kms_callback (void *unused)
48304830
enc, "local", dk_opts, &keyid, &error);
48314831
mongoc_client_encryption_destroy (enc);
48324832

4833-
ASSERT_ERROR_CONTAINS (error,
4834-
MONGOC_ERROR_CLIENT_SIDE_ENCRYPTION,
4835-
1,
4836-
"Requested kms provider not configured");
4833+
ASSERT_ERROR_CONTAINS (
4834+
error, MONGOC_ERROR_CLIENT_SIDE_ENCRYPTION, 1, "no kms provider set");
48374835
}
48384836

48394837

0 commit comments

Comments
 (0)