Skip to content

Commit 99e65f8

Browse files
authored
MOTOR-866 Key Management API (#175)
1 parent 6e8e2ee commit 99e65f8

8 files changed

Lines changed: 142 additions & 7 deletions

File tree

.evergreen/config.yml

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,13 +328,75 @@ functions:
328328
sh ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh
329329
330330
"run tox":
331+
# If testing FLE, start the KMS mock servers, first create the virtualenv.
332+
- command: shell.exec
333+
params:
334+
script: |
335+
${PREPARE_SHELL}
336+
cd ${DRIVERS_TOOLS}/.evergreen/csfle
337+
. ./activate_venv.sh
338+
# Run in the background so the mock servers don't block the EVG task.
339+
- command: shell.exec
340+
params:
341+
background: true
342+
script: |
343+
${PREPARE_SHELL}
344+
cd ${DRIVERS_TOOLS}/.evergreen/csfle
345+
. ./activate_venv.sh
346+
# The -u options forces the stdout and stderr streams to be unbuffered.
347+
# TMPDIR is required to avoid "AF_UNIX path too long" errors.
348+
TMPDIR="$(dirname $DRIVERS_TOOLS)" python -u kms_kmip_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/server.pem --port 5698 &
349+
python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 8000 &
350+
python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/wrong-host.pem --port 8001 &
351+
python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/server.pem --port 8002 --require_client_cert &
352+
# Wait up to 10 seconds for the KMIP server to start.
353+
- command: shell.exec
354+
params:
355+
script: |
356+
${PREPARE_SHELL}
357+
cd ${DRIVERS_TOOLS}/.evergreen/csfle
358+
. ./activate_venv.sh
359+
for i in $(seq 1 1 10); do
360+
sleep 1
361+
if python -u kms_kmip_client.py; then
362+
echo 'KMS KMIP server started!'
363+
exit 0
364+
fi
365+
done
366+
echo 'Failed to start KMIP server!'
367+
exit 1
368+
- command: shell.exec
369+
type: test
370+
params:
371+
silent: true
372+
working_dir: "src"
373+
script: |
374+
cat <<EOT > fle_creds.sh
375+
export FLE_AWS_KEY="${fle_aws_key}"
376+
export FLE_AWS_SECRET="${fle_aws_secret}"
377+
export FLE_AZURE_CLIENTID="${fle_azure_clientid}"
378+
export FLE_AZURE_TENANTID="${fle_azure_tenantid}"
379+
export FLE_AZURE_CLIENTSECRET="${fle_azure_clientsecret}"
380+
export FLE_GCP_EMAIL="${fle_gcp_email}"
381+
export FLE_GCP_PRIVATEKEY="${fle_gcp_privatekey}"
382+
# Needed for generating temporary aws credentials.
383+
export AWS_ACCESS_KEY_ID="${fle_aws_key}"
384+
export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}"
385+
export AWS_DEFAULT_REGION=us-east-1
386+
EOT
331387
- command: shell.exec
332388
type: test
333389
params:
334390
working_dir: "src"
335391
script: |
336392
${PREPARE_SHELL}
337-
393+
# Disable xtrace (just in case it was accidentally set).
394+
set +x
395+
. ./fle_creds.sh
396+
rm -f ./fle_creds.sh
397+
set -x
398+
export LIBMONGOCRYPT_URL="${libmongocrypt_url}"
399+
export TEST_ENCRYPTION=1
338400
PYTHON_BINARY="${PYTHON_BINARY}" \
339401
TOX_BINARY="${TOX_BINARY}" \
340402
TOX_ENV="${TOX_ENV}" \

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ setup.cfg
1212
doc/_build/
1313
.idea/
1414
xunit-results
15+
xunit-synchro-results
1516
.eggs

motor/core.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1952,8 +1952,22 @@ class AgnosticClientEncryption(AgnosticBase):
19521952
decrypt = AsyncCommand()
19531953
close = AsyncCommand(doc=docstrings.close_doc)
19541954

1955+
# Key Management API
1956+
rewrap_many_data_key = AsyncCommand()
1957+
delete_key = AsyncCommand()
1958+
get_key = AsyncCommand()
1959+
add_key_alt_name = AsyncCommand()
1960+
get_key_by_alt_name = AsyncCommand()
1961+
remove_key_alt_name = AsyncCommand()
1962+
19551963
def __init__(
1956-
self, kms_providers, key_vault_namespace, key_vault_client, codec_options, io_loop=None
1964+
self,
1965+
kms_providers,
1966+
key_vault_namespace,
1967+
key_vault_client,
1968+
codec_options,
1969+
io_loop=None,
1970+
kms_tls_options=None,
19571971
):
19581972
"""Explicit client-side field level encryption.
19591973
@@ -1970,7 +1984,7 @@ def __init__(
19701984
io_loop = None
19711985
sync_client = key_vault_client.delegate
19721986
delegate = self.__delegate_class__(
1973-
kms_providers, key_vault_namespace, sync_client, codec_options
1987+
kms_providers, key_vault_namespace, sync_client, codec_options, kms_tls_options
19741988
)
19751989
super().__init__(delegate)
19761990
self._io_loop = io_loop
@@ -1996,3 +2010,7 @@ def __enter__(self):
19962010

19972011
def __exit__(self, exc_type, exc_val, exc_tb):
19982012
pass
2013+
2014+
async def get_keys(self):
2015+
cursor_class = create_class_with_framework(AgnosticCursor, self._framework, self.__module__)
2016+
return cursor_class(self.delegate.get_keys(), self)

synchro/__init__.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -768,13 +768,27 @@ def __init__(self, kms_providers, key_vault_namespace, key_vault_client=None, **
768768
class ClientEncryption(Synchro):
769769
__delegate_class__ = motor.MotorClientEncryption
770770

771-
def __init__(self, kms_providers, key_vault_namespace, key_vault_client, codec_options):
771+
def __init__(
772+
self,
773+
kms_providers,
774+
key_vault_namespace,
775+
key_vault_client,
776+
codec_options,
777+
kms_tls_options=None,
778+
):
772779
self.delegate = motor.MotorClientEncryption(
773-
kms_providers, key_vault_namespace, key_vault_client.delegate, codec_options
780+
kms_providers,
781+
key_vault_namespace,
782+
key_vault_client.delegate,
783+
codec_options,
784+
kms_tls_options=kms_tls_options,
774785
)
775786

776787
def __enter__(self):
777788
return self
778789

779790
def __exit__(self, *args):
780791
return self.synchronize(self.delegate.__aexit__)(*args)
792+
793+
def get_keys(self):
794+
return Cursor(self.synchronize(self.delegate.get_keys)())

synchro/synchrotest.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,18 @@
170170
"*.test_md5",
171171
# Causes a deadlock.
172172
"TestFork.*",
173-
# Also causes a deadlock
173+
# Also causes a deadlock.
174174
"TestClientSimple.test_fork",
175+
# These methods are picked up by nose despite not being a unittest.
176+
"TestRewrapWithSeparateClientEncryption.run_test",
177+
"TestCustomEndpoint.run_test_expected_success",
178+
"TestDataKeyDoubleEncryption.run_test",
179+
# Motor does not support CSOT.
180+
"TestCsotGridfsFind.*",
181+
# These tests are failing right now.
182+
"TestUnifiedFindShutdownError.test_Concurrent_shutdown_error_on_find",
183+
"TestUnifiedInsertShutdownError.test_Concurrent_shutdown_error_on_insert",
184+
"TestUnifiedPoolClearedError.test_PoolClearedError_does_not_mark_server_unknown",
175185
]
176186

177187

test/asyncio_tests/test_asyncio_cursor.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,18 @@ async def test_context_manager(self):
584584
await contrast_cursor.close()
585585
self.assertTrue(contrast_cursor.closed)
586586

587+
@asyncio_test
588+
async def test_generate_keys(self):
589+
c = self.collection
590+
KMS_PROVIDERS = {"local": {"key": b"\x00" * 96}}
591+
592+
async with motor_asyncio.AsyncIOMotorClientEncryption(
593+
KMS_PROVIDERS, "keyvault.datakeys", c, bson.codec_options.CodecOptions()
594+
) as client_encryption:
595+
self.assertIsInstance(
596+
await client_encryption.get_keys(), motor_asyncio.AsyncIOMotorCursor
597+
)
598+
587599

588600
class TestAsyncIOCursorMaxTimeMS(AsyncIOTestCase):
589601
def setUp(self):

test/tornado_tests/test_motor_cursor.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,16 @@ async def test_raw_batches(self):
516516
lst = await method().batch_size(2).to_list(length=1)
517517
self.assertEqual([{"_id": 0}, {"_id": 1}], bson.decode_all(lst[0]))
518518

519+
@gen_test
520+
async def test_generate_keys(self):
521+
c = self.collection
522+
KMS_PROVIDERS = {"local": {"key": b"\x00" * 96}}
523+
524+
async with motor.MotorClientEncryption(
525+
KMS_PROVIDERS, "keyvault.datakeys", c, bson.codec_options.CodecOptions()
526+
) as client_encryption:
527+
self.assertIsInstance(await client_encryption.get_keys(), motor.MotorCursor)
528+
519529

520530
class MotorCursorMaxTimeMSTest(MotorTest):
521531
def setUp(self):

tox.ini

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ passenv =
4343
DB_PASSWORD
4444
CERT_DIR
4545
ASYNC_TEST_TIMEOUT
46+
FLE_AWS_KEY
47+
FLE_AWS_SECRET
48+
FLE_AZURE_CLIENTID
49+
FLE_AZURE_TENANTID
50+
FLE_AZURE_CLIENTSECRET
51+
FLE_GCP_EMAIL
52+
FLE_GCP_PRIVATEKEY
53+
4654

4755
basepython =
4856
py37,synchro37: {env:PYTHON_BINARY:python3.7}
@@ -111,7 +119,7 @@ setenv =
111119
PYTHONPATH = {envtmpdir}/mongo-python-driver
112120
commands =
113121
git clone --depth 1 --branch master https://github.com/mongodb/mongo-python-driver.git {envtmpdir}/mongo-python-driver
114-
pip install -e {envtmpdir}/mongo-python-driver
122+
python3 -m pip install -e {envtmpdir}/mongo-python-driver
115123
python3 -m synchro.synchrotest --with-xunit --xunit-file=xunit-synchro-results -v -w {envtmpdir}/mongo-python-driver {posargs}
116124

117125
[testenv:lint]

0 commit comments

Comments
 (0)