Skip to content

[CDRIVER-4454] Automatic Azure KMS Credentials #1097

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b9fface
Initial automatic Azure KMS based on DRIVERS-2411
vector-of-bool Aug 18, 2022
5b20fc8
Split request_send into testable components
vector-of-bool Aug 26, 2022
e758329
Add private API for querying Azure IMDS
vector-of-bool Aug 26, 2022
3d055b7
Test cases for Azure IMDS
vector-of-bool Aug 26, 2022
2ac4fad
Convenience macro for test installation
vector-of-bool Aug 26, 2022
c5a8f57
Use separate IMSD request API
vector-of-bool Aug 26, 2022
d14b796
No managedIdentity parameters for now
vector-of-bool Aug 26, 2022
b0b31c0
Spelling
vector-of-bool Aug 26, 2022
3a5cbe3
Unused func, doc comment
vector-of-bool Aug 26, 2022
73c207b
Speling
vector-of-bool Aug 26, 2022
b81d47e
Refactor HTTP retry logic into a dedicated func
vector-of-bool Aug 29, 2022
ac3b6ab
Break out if the wait period becomes too large
vector-of-bool Aug 29, 2022
494adb5
Fix: Clear error when checking for Azure requests
vector-of-bool Aug 30, 2022
a1abbf7
PR feedback
vector-of-bool Aug 30, 2022
1e245c9
Drop HTTP retry logic
vector-of-bool Aug 30, 2022
dce0a4e
Weast const
vector-of-bool Aug 30, 2022
57c37fc
Simple point-in-time and duration abstraction
vector-of-bool Sep 1, 2022
b89decd
Parse the "expires_in" from azure tokens
vector-of-bool Sep 1, 2022
9b4f52c
Cache the Azure access token
vector-of-bool Sep 1, 2022
ba2807d
Handle strtoll parse errors
vector-of-bool Sep 1, 2022
cad3810
Normal form
vector-of-bool Sep 1, 2022
c5ecf83
Overflow guards in time utils
vector-of-bool Sep 1, 2022
bfcc84a
Naming tweak
vector-of-bool Sep 1, 2022
c502346
Simpler error clearing
vector-of-bool Sep 1, 2022
93a125b
Tweak comment wording
vector-of-bool Sep 1, 2022
afe21a5
Speling
vector-of-bool Sep 1, 2022
9af22d5
Prove correctness of integer bounds checks.
vector-of-bool Sep 8, 2022
fc2d5b5
Typed assert
vector-of-bool Sep 8, 2022
39ae7dc
Fix mult overflow proof, some test cases
vector-of-bool Sep 8, 2022
39854a6
'static' for test fns
vector-of-bool Sep 8, 2022
b93e72d
Clean up headers, linkages, preludes
vector-of-bool Sep 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/libmongoc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ if (ENABLE_APPLE_FRAMEWORK)
endif ()

set (SOURCES ${SOURCES}
${PROJECT_SOURCE_DIR}/src/mongoc/mcd-azure.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-aggregate.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-apm.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-array.c
Expand Down Expand Up @@ -564,7 +565,6 @@ set (SOURCES ${SOURCES}
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-socket.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-buffered.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-buffered.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-file.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-gridfs.c
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-gridfs-download.c
Expand Down Expand Up @@ -1061,6 +1061,8 @@ set (test-libmongoc-sources
${PROJECT_SOURCE_DIR}/tests/test-mongoc-with-transaction.c
${PROJECT_SOURCE_DIR}/tests/test-mongoc-write-commands.c
${PROJECT_SOURCE_DIR}/tests/test-mongoc-write-concern.c
${PROJECT_SOURCE_DIR}/tests/test-mcd-azure-imds.c
${PROJECT_SOURCE_DIR}/tests/test-mcd-integer.c
${PROJECT_SOURCE_DIR}/tests/TestSuite.c
${PROJECT_SOURCE_DIR}/tests/unified/operation.c
${PROJECT_SOURCE_DIR}/tests/unified/entity-map.c
Expand Down
4 changes: 4 additions & 0 deletions src/libmongoc/src/mongoc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ extra_dist_generated (
)

set (src_libmongoc_src_mongoc_DIST_noinst_hs
mcd-azure.h
mcd-integer.h
mcd-time.h
mongoc-aggregate-private.h
mongoc-apm-private.h
mongoc-array-private.h
Expand Down Expand Up @@ -178,6 +181,7 @@ set (src_libmongoc_src_mongoc_DIST_noinst_hs
)

set (src_libmongoc_src_mongoc_DIST_cs
mcd-azure.c
mongoc-aggregate.c
mongoc-apm.c
mongoc-array.c
Expand Down
145 changes: 145 additions & 0 deletions src/libmongoc/src/mongoc/mcd-azure.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* Copyright 2022 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "./mcd-azure.h"

#include "mongoc-util-private.h"

#define AZURE_API_VERSION "2018-02-01"

static const char *const DEFAULT_METADATA_PATH =
"/metadata/identity/oauth2/"
"token?api-version=" AZURE_API_VERSION
"&resource=https%3A%2F%2Fvault.azure.net";

void
mcd_azure_imds_request_init (mcd_azure_imds_request *req)
{
BSON_ASSERT_PARAM (req);
_mongoc_http_request_init (&req->req);
// The HTTP host of the IMDS server
req->req.host = "169.254.169.254";
// No body
req->req.body = "";
// We GET
req->req.method = "GET";
// 'Metadata: true' is required
req->req.extra_headers = "Metadata: true\r\n"
"Accept: application/json\r\n";
// The default path is suitable. In the future, we may want to add query
// parameters to disambiguate a managed identity.
req->req.path = bson_strdup (DEFAULT_METADATA_PATH);
}

void
mcd_azure_imds_request_destroy (mcd_azure_imds_request *req)
{
BSON_ASSERT_PARAM (req);
bson_free ((void *) req->req.path);
*req = (mcd_azure_imds_request){0};
}

bool
mcd_azure_access_token_try_init_from_json_str (mcd_azure_access_token *out,
const char *json,
int len,
bson_error_t *error)
{
BSON_ASSERT_PARAM (out);
BSON_ASSERT_PARAM (json);
bool okay = false;

if (len < 0) {
// Detect from a null-terminated string
len = strlen (json);
}

// Zero the output
*out = (mcd_azure_access_token){0};

// Parse the JSON data
bson_t bson;
if (!bson_init_from_json (&bson, json, len, error)) {
return false;
}

bson_iter_t iter;
// access_token
bool found = bson_iter_init_find (&iter, &bson, "access_token");
const char *const access_token =
!found ? NULL : bson_iter_utf8 (&iter, NULL);
// resource
found = bson_iter_init_find (&iter, &bson, "resource");
const char *const resource = !found ? NULL : bson_iter_utf8 (&iter, NULL);
// token_type
found = bson_iter_init_find (&iter, &bson, "token_type");
const char *const token_type = !found ? NULL : bson_iter_utf8 (&iter, NULL);
// expires_in
found = bson_iter_init_find (&iter, &bson, "expires_in");
uint32_t expires_in_len = 0;
const char *const expires_in_str =
!found ? NULL : bson_iter_utf8 (&iter, &expires_in_len);

if (!(access_token && resource && token_type && expires_in_str)) {
bson_set_error (
error,
MONGOC_ERROR_PROTOCOL_ERROR,
64,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 64 is arbitrary. The error domain/code system is getting pretty messy. What should be done to make this more useful?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the error codes in each domain can have a different meaning. Two domains can have the same error code.

Consider adding an error code to mongoc_error_code_t, like MONGOC_ERROR_PROTOCOL_HTTP to signal an error in an HTTP response.

"One or more required JSON properties are missing/invalid: data: %.*s",
len,
json);
} else {
// Set the output, duplicate each string
*out = (mcd_azure_access_token){
.access_token = bson_strdup (access_token),
.resource = bson_strdup (resource),
.token_type = bson_strdup (token_type),
};
// "expires_in" encodes the number of seconds since the issue time for
// which the token will be valid. strtoll() will saturate on range errors
// and return zero on parse errors.
char *parse_end;
long long s = strtoll (expires_in_str, &parse_end, 0);
if (parse_end != expires_in_str + expires_in_len) {
// Did not parse the entire string. Bad
bson_set_error (
error,
MONGOC_ERROR_PROTOCOL,
65,
"Invalid 'expires_in' string \"%.*s\" from IMDS server",
expires_in_len,
expires_in_str);
} else {
out->expires_in = mcd_seconds (s);
okay = true;
}
}

bson_destroy (&bson);
return okay;
}


void
mcd_azure_access_token_destroy (mcd_azure_access_token *c)
{
bson_free (c->access_token);
bson_free (c->resource);
bson_free (c->token_type);
c->access_token = NULL;
c->resource = NULL;
c->token_type = NULL;
}
98 changes: 98 additions & 0 deletions src/libmongoc/src/mongoc/mcd-azure.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Copyright 2022 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef MCD_AZURE_H_INCLUDED
#define MCD_AZURE_H_INCLUDED

#include "mongoc-prelude.h"

#include <mongoc/mongoc.h>

#include <mongoc/mongoc-http-private.h>

#include <mcd-time.h>

/**
* @brief An Azure OAuth2 access token obtained from the Azure API
*/
typedef struct mcd_azure_access_token {
/// The access token string
char *access_token;
/// The resource of the token (the Azure resource for which it is valid)
char *resource;
/// The HTTP type of the token
char *token_type;
/// The duration after which it will the token will expires. This is relative
/// to the "issue time" of the token.
mcd_duration expires_in;
} mcd_azure_access_token;

/**
* @brief Try to parse an Azure access token from an IMDS metadata JSON response
*
* @param out The token to initialize. Should be uninitialized. Must later be
* destroyed by the caller.
* @param json The JSON string body
* @param len The length of 'body'
* @param error An output parameter for errors
* @retval true If 'out' was successfully initialized to a token.
* @retval false Otherwise
*
* @note The 'out' token must later be given to @ref
* mcd_azure_access_token_destroy
*/
bool
mcd_azure_access_token_try_init_from_json_str (mcd_azure_access_token *out,
const char *json,
int len,
bson_error_t *error)
BSON_GNUC_WARN_UNUSED_RESULT;

/**
* @brief Destroy and zero-fill an access token object
*
* @param token The access token to destroy
*/
void
mcd_azure_access_token_destroy (mcd_azure_access_token *token);

/**
* @brief An Azure IMDS HTTP request
*/
typedef struct mcd_azure_imds_request {
/// The underlying HTTP request object to be sent
mongoc_http_request_t req;
} mcd_azure_imds_request;

/**
* @brief Initialize a new IMDS HTTP request
*
* @param out The object to initialize
*
* @note the request must later be destroyed with mcd_azure_imds_request_destroy
*/
void
mcd_azure_imds_request_init (mcd_azure_imds_request *out);

/**
* @brief Destroy an IMDS request created with mcd_azure_imds_request_init()
*
* @param req
*/
void
mcd_azure_imds_request_destroy (mcd_azure_imds_request *req);

#endif // MCD_AZURE_H_INCLUDED
Loading