Skip to content

Commit 686bff8

Browse files
[CDRIVER-4454] Automatic Azure KMS Credentials (#1097)
* Initial automatic Azure KMS based on DRIVERS-2411 * Split request_send into testable components * Add private API for querying Azure IMDS * Test cases for Azure IMDS * Convenience macro for test installation * Use separate IMSD request API * No managedIdentity parameters for now * Simple point-in-time and duration abstraction * Cache the Azure access token
1 parent 92f415f commit 686bff8

File tree

12 files changed

+1273
-390
lines changed

12 files changed

+1273
-390
lines changed

src/libmongoc/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ if (ENABLE_APPLE_FRAMEWORK)
494494
endif ()
495495

496496
set (SOURCES ${SOURCES}
497+
${PROJECT_SOURCE_DIR}/src/mongoc/mcd-azure.c
497498
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-aggregate.c
498499
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-apm.c
499500
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-array.c
@@ -564,7 +565,6 @@ set (SOURCES ${SOURCES}
564565
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-socket.c
565566
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-buffered.c
566567
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream.c
567-
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-buffered.c
568568
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-file.c
569569
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-gridfs.c
570570
${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-gridfs-download.c
@@ -1061,6 +1061,8 @@ set (test-libmongoc-sources
10611061
${PROJECT_SOURCE_DIR}/tests/test-mongoc-with-transaction.c
10621062
${PROJECT_SOURCE_DIR}/tests/test-mongoc-write-commands.c
10631063
${PROJECT_SOURCE_DIR}/tests/test-mongoc-write-concern.c
1064+
${PROJECT_SOURCE_DIR}/tests/test-mcd-azure-imds.c
1065+
${PROJECT_SOURCE_DIR}/tests/test-mcd-integer.c
10641066
${PROJECT_SOURCE_DIR}/tests/TestSuite.c
10651067
${PROJECT_SOURCE_DIR}/tests/unified/operation.c
10661068
${PROJECT_SOURCE_DIR}/tests/unified/entity-map.c

src/libmongoc/src/mongoc/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ extra_dist_generated (
7474
)
7575

7676
set (src_libmongoc_src_mongoc_DIST_noinst_hs
77+
mcd-azure.h
78+
mcd-integer.h
79+
mcd-time.h
7780
mongoc-aggregate-private.h
7881
mongoc-apm-private.h
7982
mongoc-array-private.h
@@ -178,6 +181,7 @@ set (src_libmongoc_src_mongoc_DIST_noinst_hs
178181
)
179182

180183
set (src_libmongoc_src_mongoc_DIST_cs
184+
mcd-azure.c
181185
mongoc-aggregate.c
182186
mongoc-apm.c
183187
mongoc-array.c

src/libmongoc/src/mongoc/mcd-azure.c

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* Copyright 2022 MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "./mcd-azure.h"
18+
19+
#include "mongoc-util-private.h"
20+
21+
#define AZURE_API_VERSION "2018-02-01"
22+
23+
static const char *const DEFAULT_METADATA_PATH =
24+
"/metadata/identity/oauth2/"
25+
"token?api-version=" AZURE_API_VERSION
26+
"&resource=https%3A%2F%2Fvault.azure.net";
27+
28+
void
29+
mcd_azure_imds_request_init (mcd_azure_imds_request *req)
30+
{
31+
BSON_ASSERT_PARAM (req);
32+
_mongoc_http_request_init (&req->req);
33+
// The HTTP host of the IMDS server
34+
req->req.host = "169.254.169.254";
35+
// No body
36+
req->req.body = "";
37+
// We GET
38+
req->req.method = "GET";
39+
// 'Metadata: true' is required
40+
req->req.extra_headers = "Metadata: true\r\n"
41+
"Accept: application/json\r\n";
42+
// The default path is suitable. In the future, we may want to add query
43+
// parameters to disambiguate a managed identity.
44+
req->req.path = bson_strdup (DEFAULT_METADATA_PATH);
45+
}
46+
47+
void
48+
mcd_azure_imds_request_destroy (mcd_azure_imds_request *req)
49+
{
50+
BSON_ASSERT_PARAM (req);
51+
bson_free ((void *) req->req.path);
52+
*req = (mcd_azure_imds_request){0};
53+
}
54+
55+
bool
56+
mcd_azure_access_token_try_init_from_json_str (mcd_azure_access_token *out,
57+
const char *json,
58+
int len,
59+
bson_error_t *error)
60+
{
61+
BSON_ASSERT_PARAM (out);
62+
BSON_ASSERT_PARAM (json);
63+
bool okay = false;
64+
65+
if (len < 0) {
66+
// Detect from a null-terminated string
67+
len = strlen (json);
68+
}
69+
70+
// Zero the output
71+
*out = (mcd_azure_access_token){0};
72+
73+
// Parse the JSON data
74+
bson_t bson;
75+
if (!bson_init_from_json (&bson, json, len, error)) {
76+
return false;
77+
}
78+
79+
bson_iter_t iter;
80+
// access_token
81+
bool found = bson_iter_init_find (&iter, &bson, "access_token");
82+
const char *const access_token =
83+
!found ? NULL : bson_iter_utf8 (&iter, NULL);
84+
// resource
85+
found = bson_iter_init_find (&iter, &bson, "resource");
86+
const char *const resource = !found ? NULL : bson_iter_utf8 (&iter, NULL);
87+
// token_type
88+
found = bson_iter_init_find (&iter, &bson, "token_type");
89+
const char *const token_type = !found ? NULL : bson_iter_utf8 (&iter, NULL);
90+
// expires_in
91+
found = bson_iter_init_find (&iter, &bson, "expires_in");
92+
uint32_t expires_in_len = 0;
93+
const char *const expires_in_str =
94+
!found ? NULL : bson_iter_utf8 (&iter, &expires_in_len);
95+
96+
if (!(access_token && resource && token_type && expires_in_str)) {
97+
bson_set_error (
98+
error,
99+
MONGOC_ERROR_PROTOCOL_ERROR,
100+
64,
101+
"One or more required JSON properties are missing/invalid: data: %.*s",
102+
len,
103+
json);
104+
} else {
105+
// Set the output, duplicate each string
106+
*out = (mcd_azure_access_token){
107+
.access_token = bson_strdup (access_token),
108+
.resource = bson_strdup (resource),
109+
.token_type = bson_strdup (token_type),
110+
};
111+
// "expires_in" encodes the number of seconds since the issue time for
112+
// which the token will be valid. strtoll() will saturate on range errors
113+
// and return zero on parse errors.
114+
char *parse_end;
115+
long long s = strtoll (expires_in_str, &parse_end, 0);
116+
if (parse_end != expires_in_str + expires_in_len) {
117+
// Did not parse the entire string. Bad
118+
bson_set_error (
119+
error,
120+
MONGOC_ERROR_PROTOCOL,
121+
65,
122+
"Invalid 'expires_in' string \"%.*s\" from IMDS server",
123+
expires_in_len,
124+
expires_in_str);
125+
} else {
126+
out->expires_in = mcd_seconds (s);
127+
okay = true;
128+
}
129+
}
130+
131+
bson_destroy (&bson);
132+
return okay;
133+
}
134+
135+
136+
void
137+
mcd_azure_access_token_destroy (mcd_azure_access_token *c)
138+
{
139+
bson_free (c->access_token);
140+
bson_free (c->resource);
141+
bson_free (c->token_type);
142+
c->access_token = NULL;
143+
c->resource = NULL;
144+
c->token_type = NULL;
145+
}

src/libmongoc/src/mongoc/mcd-azure.h

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Copyright 2022 MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef MCD_AZURE_H_INCLUDED
18+
#define MCD_AZURE_H_INCLUDED
19+
20+
#include "mongoc-prelude.h"
21+
22+
#include <mongoc/mongoc.h>
23+
24+
#include <mongoc/mongoc-http-private.h>
25+
26+
#include <mcd-time.h>
27+
28+
/**
29+
* @brief An Azure OAuth2 access token obtained from the Azure API
30+
*/
31+
typedef struct mcd_azure_access_token {
32+
/// The access token string
33+
char *access_token;
34+
/// The resource of the token (the Azure resource for which it is valid)
35+
char *resource;
36+
/// The HTTP type of the token
37+
char *token_type;
38+
/// The duration after which it will the token will expires. This is relative
39+
/// to the "issue time" of the token.
40+
mcd_duration expires_in;
41+
} mcd_azure_access_token;
42+
43+
/**
44+
* @brief Try to parse an Azure access token from an IMDS metadata JSON response
45+
*
46+
* @param out The token to initialize. Should be uninitialized. Must later be
47+
* destroyed by the caller.
48+
* @param json The JSON string body
49+
* @param len The length of 'body'
50+
* @param error An output parameter for errors
51+
* @retval true If 'out' was successfully initialized to a token.
52+
* @retval false Otherwise
53+
*
54+
* @note The 'out' token must later be given to @ref
55+
* mcd_azure_access_token_destroy
56+
*/
57+
bool
58+
mcd_azure_access_token_try_init_from_json_str (mcd_azure_access_token *out,
59+
const char *json,
60+
int len,
61+
bson_error_t *error)
62+
BSON_GNUC_WARN_UNUSED_RESULT;
63+
64+
/**
65+
* @brief Destroy and zero-fill an access token object
66+
*
67+
* @param token The access token to destroy
68+
*/
69+
void
70+
mcd_azure_access_token_destroy (mcd_azure_access_token *token);
71+
72+
/**
73+
* @brief An Azure IMDS HTTP request
74+
*/
75+
typedef struct mcd_azure_imds_request {
76+
/// The underlying HTTP request object to be sent
77+
mongoc_http_request_t req;
78+
} mcd_azure_imds_request;
79+
80+
/**
81+
* @brief Initialize a new IMDS HTTP request
82+
*
83+
* @param out The object to initialize
84+
*
85+
* @note the request must later be destroyed with mcd_azure_imds_request_destroy
86+
*/
87+
void
88+
mcd_azure_imds_request_init (mcd_azure_imds_request *out);
89+
90+
/**
91+
* @brief Destroy an IMDS request created with mcd_azure_imds_request_init()
92+
*
93+
* @param req
94+
*/
95+
void
96+
mcd_azure_imds_request_destroy (mcd_azure_imds_request *req);
97+
98+
#endif // MCD_AZURE_H_INCLUDED

0 commit comments

Comments
 (0)