Skip to content

Commit d6b8cce

Browse files
[DRIVERS-2411] Add behavior for automatic Azure KMS credentials (#1291)
* Add behavior for automatic Azure KMS credentials * Cache automatically obtained Azure tokens
1 parent 23bd4db commit d6b8cce

File tree

3 files changed

+148
-59
lines changed

3 files changed

+148
-59
lines changed

source/client-side-encryption/client-side-encryption.rst

Lines changed: 127 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ Client Side Encryption
1010
:Status: Accepted
1111
:Type: Standards
1212
:Minimum Server Version: 4.2 (CSFLE), 6.0 (Queryable Encryption)
13-
:Last Modified: 2022-09-09
14-
:Version: 1.11.1
13+
:Last Modified: 2022-09-26
14+
:Version: 1.12.0
1515

1616
.. _lmc-c-api: https://github.com/mongodb/libmongocrypt/blob/master/src/mongocrypt.h.in
1717

@@ -26,6 +26,8 @@ Client Side Encryption
2626
.. |true| replace:: :ts:`true`
2727
.. |false| replace:: :ts:`false`
2828

29+
.. |--| unicode:: 0x2014 .. em dash
30+
2931
.. contents::
3032

3133
--------
@@ -324,6 +326,8 @@ specified as a BSON binary with subtype 0x04 as described in
324326
MongoClient Changes
325327
-------------------
326328

329+
.. _MongoClient:
330+
327331
.. code:: typescript
328332
329333
class MongoClient {
@@ -335,6 +339,10 @@ MongoClient Changes
335339
private MongoClient keyvault_client; // Client used to run find on the key vault collection. This is either an external MongoClient, the parent MongoClient, or internal_client.
336340
private MongoClient metadata_client; // Client used to run listCollections. This is either the parent MongoClient or internal_client.
337341
private Optional<MongoClient> internal_client; // An internal MongoClient. Created if no external keyVaultClient was set, or if a metadataClient is needed
342+
343+
// Exposition-only, used for caching automatic Azure credentials
344+
private cachedAzureAccessToken?: AzureAccessToken;
345+
private azureAccessTokenExpireTime?: PointInTime;
338346
}
339347
340348
.. _AutoEncryptionOpts:
@@ -461,6 +469,7 @@ See `What's the deal with metadataClient, keyVaultClient, and the internal clien
461469
.. _KMSProviderName:
462470
.. _AWSKMSOptions:
463471
.. _GCPKMSOptions:
472+
.. _AzureAccessToken:
464473

465474
kmsProviders
466475
^^^^^^^^^^^^
@@ -480,9 +489,9 @@ accept arbitrary strings at runtime for forward-compatibility.
480489
.. code:: typescript
481490
482491
interface KMSProviders {
483-
aws?: AWSKMSOptions | { /* Empty. (See "Automatic Credentials") */ };
484-
azure?: AzureKMSOptions;
485-
gcp?: GCPKMSOptions | { /* Empty. (See "Automatic Credentials") */ };
492+
aws?: AWSKMSOptions | { /* Empty (See "Automatic Credentials") */ };
493+
azure?: AzureKMSOptions | { /* Empty (See "Automatic Credentials") */ };
494+
gcp?: GCPKMSOptions | { /* Empty (See "Automatic Credentials") */ };
486495
local?: LocalKMSOptions;
487496
kmip?: KMIPKMSOptions;
488497
};
@@ -497,13 +506,19 @@ accept arbitrary strings at runtime for forward-compatibility.
497506
sessionToken?: string; // Required for temporary AWS credentials.
498507
};
499508
500-
interface AzureKMSOptions {
509+
type AzureKMSOptions = AzureKMSCredentials | AzureAccessToken;
510+
511+
interface AzureKMSCredentials {
501512
tenantId: string;
502513
clientId: string;
503514
clientSecret: string;
504515
identityPlatformEndpoint?: string; // Defaults to login.microsoftonline.com
505516
};
506517
518+
interface AzureAccessToken {
519+
accessToken: string;
520+
};
521+
507522
type GCPKMSOptions = GCPKMSCredentials | GCPKMSAccessToken
508523
509524
interface GCPKMSCredentials {
@@ -530,51 +545,67 @@ Automatic Credentials
530545

531546
.. versionadded:: 1.9.0 2022/06/22
532547

533-
If the ``aws`` or ``gcp`` provider properties of a KMSProviders_ are present and
534-
are an empty map/object, the driver MUST be able to populate an AWSKMSOptions_ or
535-
GCPKMSOptions_ object on-demand if-and-only-if AWS or GCP credentials are
536-
needed.
548+
Certain values of KMSProviders_ indicate a request by the user that the
549+
associated KMS providers should be populated lazily on-demand. The driver MUST
550+
be able to populate the respective options object on-demand if-and-only-if such
551+
respective credentials are needed. The request for KMS credentials will be
552+
indicated by libmongocrypt_ only once they are needed.
553+
554+
When such a state is detected, libmongocrypt_ will call back to the driver by
555+
entering the ``MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS`` state, upon which the
556+
driver should fill in the KMS options automatically.
557+
558+
.. note:: Drivers MUST NOT eagerly fill an empty KMS options property.
537559

538-
.. note:: Drivers MUST NOT eagerly fill an empty ``aws`` or ``gcp`` map property.
560+
.. default-role:: math
539561

540-
libmongocrypt_ will interpret an empty KMSProviders_ map properties as a request
541-
by the user to lazily load the credentials for the respective `KMS provider`_.
542-
libmongocrypt_ will call back to the driver to fill an empty KMS provider
543-
property only once the associated credentials are needed by entering
544-
the ``MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS`` state.
562+
Once requested, drivers MUST create a new KMSProviders_ `P` according to the
563+
following process:
545564

546-
Once requested, drivers MUST create a new KMSProviders_ :math:`P` according to
547-
the following process:
565+
1. Let `K` be the kmsProviders_ value provided by the user as part of the
566+
original ClientEncryptionOpts_ or AutoEncryptionOpts_.
567+
2. Initialize `P` to an empty KMSProviders_ object.
568+
3. If `K` contains an ``aws`` property, and that property is an empty map:
548569

549-
1. Initialize :math:`P` to an empty KMSProviders_ object.
550-
2. If the user-provided kmsProviders_ (from ClientEncryptionOpts_ or
551-
AutoEncryptionOpts_) contains an ``aws`` property, and that property is an
552-
empty map:
570+
1. Attempt to obtain credentials `C` from the environment using similar logic
571+
as is detailed in `the obtaining-AWS-credentials section from the Driver
572+
Authentication specification`__, but ignoring the case of loading the
573+
credentials from a URI
574+
2. If credentials `C` were successfully loaded, create a new AWSKMSOptions_
575+
map from `C` and insert that map onto `P` as the ``aws`` property.
553576

554-
1. Attempt to obtain credentials :math:`C` from the environment using similar
555-
logic as is detailed in `the obtaining-AWS-credentials section from the
556-
Driver Authentication specification`__, but ignoring the case of loading
557-
the credentials from a URI
558-
2. If credentials :math:`C` were successfully loaded, create a new
559-
AWSKMSOptions_ map from :math:`C` and insert that map onto :math:`P` as
560-
the ``aws`` property.
577+
4. If `K` contains an ``gcp`` property, and that property is an empty map:
561578

562-
3. If the user-provided kmsProviders_ (from ClientEncryptionOpts_ or
563-
AutoEncryptionOpts_) contains an ``gcp`` property, and that property is an
564-
empty map:
579+
1. Attempt to obtain credentials `C` from the environment logic as is
580+
detailed in `Obtaining GCP Credentials`_.
581+
2. If credentials `C` were successfully loaded, create a new GCPKMSOptions_
582+
map from `C` and insert that map onto `P` as the ``gcp`` property.
565583

566-
1. Attempt to obtain credentials :math:`C` from the environment
567-
logic as is detailed in `Obtaining GCP Credentials`_.
568-
2. If credentials :math:`C` were successfully loaded, create a new
569-
GCPKMSOptions_ map from :math:`C` and insert that map onto :math:`P` as
570-
the ``gcp`` property.
584+
5. If `K` contains an ``azure`` property, and that property is an empty map:
571585

572-
4. Return :math:`P` as the additional KMS providers to libmongocrypt_.
586+
1. If the current MongoClient_ has a ``cachedAzureAccessToken`` AND the
587+
duration until ``azureAccessTokenExpireTime`` is greater than one minute,
588+
insert ``cachedAzureAccessToken`` as the ``azure`` property on `P`.
589+
2. Otherwise:
590+
591+
1. Let `t_0` be the current time.
592+
2. Attempt to obtain an Azure VM Managed Identity Access Token `T` as
593+
detailed in `Obtaining an Access Token for Azure Key Vault`_.
594+
3. If a token `T` with expire duration `d_{exp}` were obtained
595+
successfully, create a new AzureAccessToken_ object with `T` as the
596+
``accessToken`` property. Insert that AzureAccessToken_ object into `P`
597+
as the ``azure`` property. Record the generated AzureAccessToken_ in
598+
``cachedAzureAccessToken``. Record the ``azureAccessTokenExpireTime``
599+
as `t_0 + d_{exp}`.
600+
601+
6. Return `P` as the additional KMS providers to libmongocrypt_.
573602

574603
__ ../auth/auth.html#obtaining-credentials
575604

605+
.. default-role:: literal
606+
576607
Obtaining GCP Credentials
577-
^^^^^^^^^^^^^^^^^^^^^^^^^
608+
`````````````````````````
578609

579610
.. versionadded:: 1.11.0 2022/07/20
580611

@@ -596,6 +627,61 @@ message.
596627

597628
Return "access_token" as the credential.
598629

630+
631+
Obtaining an Access Token for Azure Key Vault
632+
`````````````````````````````````````````````
633+
634+
.. versionadded:: 1.12.0 2022/08/30
635+
636+
Virtual machines running on the Azure platform have one or more *Managed
637+
Identities* associated with them. From within the VM, an identity can be used by
638+
obtaining an access token via HTTP from the *Azure Instance Metadata Service*
639+
(IMDS). `See this documentation for more information`__
640+
641+
__ https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
642+
643+
.. default-role:: math
644+
645+
The below steps should be taken:
646+
647+
1. Let `U` be a new URL, initialized from the URL string
648+
:ts:`"http://169.254.169.254/metadata/identity/oauth2/token"`
649+
2. Add a query parameter ``api-version=2018-02-01`` to `U`.
650+
3. Add a query parameter ``resource=http://vault.azure.com/`` to `U`.
651+
4. Prepare an HTTP GET request `Req` based on `U`.
652+
653+
.. note:: All query parameters on `U` should be appropriately percent-encoded
654+
655+
5. Add HTTP headers ``Metadata: true`` and ``Accept: application/json`` to
656+
`Req`.
657+
6. Issue `Req` to the Azure IMDS server ``168.254.169.254:80``. Let `Resp` be
658+
the response from the server.
659+
7. If `Resp_{status} ≠ 200`, obtaining the access token has failed, and the
660+
HTTP response body of `Resp` encodes information about the error that
661+
occurred. Return an error instead of an access token.
662+
8. Otherwise, let `J` be the JSON document encoded in the HTTP response body
663+
of `Resp`.
664+
9. The result access token `T` is given as the ``access_token`` string property
665+
of `J`. Return `T` as the resulting access token.
666+
10. The resulting "expires in" duration `d_{exp}` is a count of seconds given as an
667+
ASCII-encoded integer string ``expires_in`` property of `J`.
668+
669+
.. note::
670+
671+
If JSON decoding of `Resp` fails, or the ``access_token`` property is absent
672+
from `J`, this is a protocol error from IMDS. Indicate this error to the
673+
requester of the access token.
674+
675+
.. note::
676+
677+
If an Azure VM has more than one managed identity, requesting an access token
678+
requires additional query parameters to disambiguate the request. For
679+
simplicity, these parameters are omitted, and only VMs that have a single
680+
managed identity are supported.
681+
682+
.. default-role:: literal
683+
684+
599685
KMS provider TLS options
600686
````````````````````````
601687

@@ -2435,7 +2521,7 @@ Changelog
24352521
:align: left
24362522
24372523
Date, Description
2438-
2524+
22-09-26, Add behavior for automatic Azure KeyVault credentials for ``kmsProviders``.
24392525
22-09-09, Prohibit ``rewrapManyDataKey`` with libmongocrypt <= 1.5.1.
24402526
22-07-20, Add behavior for automatic GCP credential loading in ``kmsProviders``.
24412527
22-06-30, Add behavior for automatic AWS credential loading in ``kmsProviders``.
@@ -2448,7 +2534,7 @@ Changelog
24482534
22-06-08, Add ``Queryable Encryption`` to abstract.
24492535
22-06-02, Rename ``FLE 2`` to ``Queryable Encryption``
24502536
22-05-31, Rename ``csfle`` to ``crypt_shared``
2451-
22-05-27, Define ECC, ECOC, and ESC acronyms within encryptedFields
2537+
22-05-27, "Define ECC, ECOC, and ESC acronyms within encryptedFields"
24522538
22-05-26, Clarify how ``encryptedFields`` interacts with ``create`` and ``drop`` commands
24532539
22-05-24, Add key management API functions
24542540
22-05-18, Add createKey and rewrapManyDataKey

source/client-side-encryption/tests/README.rst

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,9 +2061,10 @@ For "local", do not set a masterKey document.
20612061
Run the following test case for each pair of KMS providers (referred to as ``srcProvider`` and ``dstProvider``).
20622062
Include pairs where ``srcProvider`` equals ``dstProvider``.
20632063

2064-
1. Drop the collection ``keyvault.datakeys``.
2064+
1. Drop the collection ``keyvault.datakeys``.
20652065

20662066
2. Create a ``ClientEncryption`` object named ``clientEncryption1`` with these options:
2067+
20672068
.. code:: typescript
20682069
20692070
ClientEncryptionOpts {
@@ -2073,6 +2074,7 @@ Include pairs where ``srcProvider`` equals ``dstProvider``.
20732074
}
20742075
20752076
3. Call ``clientEncryption1.createDataKey`` with ``srcProvider`` and these options:
2077+
20762078
.. code:: typescript
20772079
20782080
class DataKeyOpts {
@@ -2082,6 +2084,7 @@ Include pairs where ``srcProvider`` equals ``dstProvider``.
20822084
Store the return value in ``keyID``.
20832085

20842086
4. Call ``clientEncryption1.encrypt`` with the value "test" and these options:
2087+
20852088
.. code:: typescript
20862089
20872090
class EncryptOpts {
@@ -2092,13 +2095,14 @@ Include pairs where ``srcProvider`` equals ``dstProvider``.
20922095
Store the return value in ``ciphertext``.
20932096

20942097
5. Create a ``ClientEncryption`` object named ``clientEncryption2`` with these options:
2098+
20952099
.. code:: typescript
20962100
2097-
ClientEncryptionOpts {
2098-
keyVaultClient: <new MongoClient>;
2099-
keyVaultNamespace: "keyvault.datakeys";
2100-
kmsProviders: <all KMS providers>
2101-
}
2101+
ClientEncryptionOpts {
2102+
keyVaultClient: <new MongoClient>;
2103+
keyVaultNamespace: "keyvault.datakeys";
2104+
kmsProviders: <all KMS providers>
2105+
}
21022106
21032107
6. Call ``clientEncryption2.rewrapManyDataKey`` with an empty ``filter`` and these options:
21042108

@@ -2115,8 +2119,9 @@ Include pairs where ``srcProvider`` equals ``dstProvider``.
21152119

21162120
8. Call ``clientEncryption2.decrypt`` with the ``ciphertext``. Assert the return value is "test".
21172121

2118-
17. On-demand GCP Credentials
2119-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2122+
2123+
17. On-demand GCP Credentials
2124+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21202125

21212126
Refer: `Automatic GCP Credentials`_.
21222127

source/unified-test-format/unified-test-format.rst

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ Test runners MUST support the following types of entities:
295295
<../server-discovery-and-monitoring/server-discovery-and-monitoring.rst#topologydescription>`__
296296
at a certain point in time. These entities are not defined in
297297
`createEntities`_ but are instead created via `recordTopologyDescription`_
298-
test runner operations.
298+
test runner operations.
299299

300300
This is an exhaustive list of supported types for the entity map. Test runners
301301
MUST raise an error if an attempt is made to store an unsupported type in the
@@ -1147,13 +1147,11 @@ of ``eventType`` in the corresponding `expectedEventsForClient`_
11471147
object, which can have one of the following values:
11481148

11491149
- ``command`` or omitted: only the event types defined in
1150-
`expectedCommandEvent`_ are allowed.
1150+
`expectedCommandEvent`_ are allowed.
11511151

1152-
- ``cmap``: only the event types defined in `expectedCmapEvent`_ are
1153-
allowed.
1152+
- ``cmap``: only the event types defined in `expectedCmapEvent`_ are allowed.
11541153

1155-
- ``sdam``: only the event types defined in `expectedSdamEvent`_ are
1156-
allowed.
1154+
- ``sdam``: only the event types defined in `expectedSdamEvent`_ are allowed.
11571155

11581156
expectedCommandEvent
11591157
````````````````````
@@ -1310,7 +1308,7 @@ The structure of this object is as follows:
13101308
The structure of this object is as follows:
13111309

13121310
- ``previousDescription``: Optional object. A value corresponding to the server
1313-
description as it was before the change that triggered this event.
1311+
description as it was before the change that triggered this event.
13141312

13151313
- ``newDescription``: Optional object. A value corresponding to the server
13161314
description as it was after the change that triggered this event.
@@ -1322,7 +1320,7 @@ The structure of this object is as follows:
13221320
runners MUST assert that the type in the published event matches this
13231321
value. See `SDAM: ServerType
13241322
<../server-discovery-and-monitoring/server-discovery-and-monitoring.rst#servertype>`__
1325-
for a list of valid values.
1323+
for a list of valid values.
13261324

13271325
hasServiceId
13281326
`````````````
@@ -2627,7 +2625,7 @@ An example of this operation follows::
26272625
object: testRunner
26282626
arguments:
26292627
thread: *thread0
2630-
2628+
26312629

26322630
waitForEvent
26332631
~~~~~~~~~~~~
@@ -2795,7 +2793,7 @@ For example::
27952793
arguments:
27962794
ms: 1000
27972795

2798-
2796+
27992797
Special Placeholder Value
28002798
-------------------------
28012799

0 commit comments

Comments
 (0)