- Notifications
You must be signed in to change notification settings - Fork 24
INTPYTHON-527 Add Queryable Encryption support #329
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
bc52c8e
38fb110
65bd15a
e08945b
7b34b44
8e83ada
4da895c
ed54a9b
8a7766c
eab2f2e
10a361e
01d5485
db32487
b2be223
27d4b8e
c4d1c66
2772aff
d2ddf4e
e25357e
6487086
bc76db3
4dbaa8f
9cc5ad2
c751b2d
b13a07f
534da6b
13578ab
3342d7f
9fd21e4
9bbe741
d1eb737
176f016
264b37a
1771f56
819058a
9a3c18e
071192e
b2a0534
81cc887
be3dd16
a2342e2
05a7610
96b3fda
1eb71d5
08209d3
90fe562
8c2b84c
ab680fd
4a267f5
3fdc1f7
d562a76
163758d
b95c343
5205a0b
b07c3e6
e557632
c5f8888
a7bc5c5
09423bc
c756cf8
841797c
2386397
d685d2a
08ea317
3e839d7
75c6936
534452f
bf26a8a
bf078ad
2780e32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# Queryable Encryption helpers | ||
# | ||
# TODO: Decide if these helpers should even exist, and if so, find a permanent | ||
# place for them. | ||
| ||
from bson.binary import STANDARD | ||
from bson.codec_options import CodecOptions | ||
from pymongo.encryption import AutoEncryptionOpts, ClientEncryption | ||
| ||
| ||
def get_encrypted_client(auto_encryption_opts, encrypted_connection): | ||
""" | ||
Returns a `ClientEncryption` instance for MongoDB Client-Side Field Level | ||
Encryption (CSFLE) that can be used to create an encrypted collection. | ||
""" | ||
| ||
key_vault_namespace = auto_encryption_opts._key_vault_namespace | ||
kms_providers = auto_encryption_opts._kms_providers | ||
codec_options = CodecOptions(uuid_representation=STANDARD) | ||
return ClientEncryption(kms_providers, key_vault_namespace, encrypted_connection, codec_options) | ||
| ||
| ||
def get_auto_encryption_opts(crypt_shared_lib_path=None, kms_providers=None): | ||
aclark4life marked this conversation as resolved. Show resolved Hide resolved | ||
""" | ||
Returns an `AutoEncryptionOpts` instance for MongoDB Client-Side Field | ||
Level Encryption (CSFLE) that can be used to create an encrypted connection. | ||
""" | ||
key_vault_database_name = "encryption" | ||
key_vault_collection_name = "__keyVault" | ||
key_vault_namespace = f"{key_vault_database_name}.{key_vault_collection_name}" | ||
return AutoEncryptionOpts( | ||
key_vault_namespace=key_vault_namespace, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that either
Otherwise, not passing a value gives:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know you're a fan of making things as simple as possible, and I fail to see the value of requiring users to pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can judge! My current thinking is:
Obviously relying on settings is not ideal but it's currently the least terrible way I can think of. | ||
kms_providers=kms_providers, | ||
crypt_shared_lib_path=crypt_shared_lib_path, | ||
) | ||
| ||
| ||
def get_customer_master_key(): | ||
""" | ||
Returns a 96-byte local master key for use with MongoDB Client-Side Field Level | ||
Encryption (CSFLE). For local testing purposes only. In production, use a secure KMS | ||
like AWS, Azure, GCP, or KMIP. | ||
Returns: | ||
bytes: A 96-byte key. | ||
""" | ||
# WARNING: This is a static key for testing only. | ||
# Generate with: os.urandom(96) | ||
return bytes.fromhex( | ||
"000102030405060708090a0b0c0d0e0f" | ||
"101112131415161718191a1b1c1d1e1f" | ||
"202122232425262728292a2b2c2d2e2f" | ||
"303132333435363738393a3b3c3d3e3f" | ||
"404142434445464748494a4b4c4d4e4f" | ||
"505152535455565758595a5b5c5d5e5f" | ||
) | ||
| ||
| ||
def get_kms_providers(): | ||
""" | ||
Return supported KMS providers for MongoDB Client-Side Field Level Encryption (CSFLE). | ||
""" | ||
return { | ||
"local": { | ||
"key": get_customer_master_key(), | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from django.db import models | ||
| ||
| ||
class EncryptedCharField(models.CharField): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.encrypted = True | ||
aclark4life marked this conversation as resolved. Show resolved Hide resolved |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
Encrypted models | ||
================ | ||
| ||
``EncryptedCharField`` | ||
---------------------- | ||
| ||
The basics | ||
~~~~~~~~~~ | ||
| ||
Let's consider this example:: | ||
| ||
from django.db import models | ||
| ||
from django_mongodb_backend.fields import EncryptedCharField | ||
from django_mongodb_backend.models import EncryptedModel | ||
| ||
| ||
class Person(EncryptedModel): | ||
ssn = EncryptedCharField("ssn", max_length=11) | ||
| ||
def __str__(self): | ||
return self.ssn |
Original file line number | Diff line number | Diff line change |
---|---|---|
| @@ -10,4 +10,5 @@ know: | |
| ||
cache | ||
embedded-models | ||
encrypted-models | ||
known-issues |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from django_mongodb_backend.fields import EncryptedCharField | ||
from django_mongodb_backend.models import EncryptedModel | ||
| ||
| ||
class Person(EncryptedModel): | ||
ssn = EncryptedCharField("ssn", max_length=11) | ||
| ||
aclark4life marked this conversation as resolved. Show resolved Hide resolved | ||
def __str__(self): | ||
return self.ssn |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
from django.test import TestCase | ||
| ||
from .models import Person | ||
| ||
| ||
class EncryptedModelTests(TestCase): | ||
@classmethod | ||
def setUpTestData(cls): | ||
cls.objs = [Person.objects.create()] | ||
| ||
def test_encrypted_fields_map_on_class(self): | ||
expected = { | ||
"fields": { | ||
"ssn": "EncryptedCharField", | ||
} | ||
} | ||
self.assertEqual(Person.encrypted_fields_map, expected) | ||
| ||
def test_encrypted_fields_map_on_instance(self): | ||
instance = Person(ssn="123-45-6789") | ||
expected = { | ||
"fields": { | ||
"ssn": "EncryptedCharField", | ||
} | ||
} | ||
self.assertEqual(instance.encrypted_fields_map, expected) | ||
| ||
def test_non_encrypted_fields_not_included(self): | ||
aclark4life marked this conversation as resolved. Show resolved Hide resolved | ||
encrypted_field_names = Person.encrypted_fields_map.keys() | ||
self.assertNotIn("ssn", encrypted_field_names) |
Uh oh!
There was an error while loading. Please reload this page.