Skip to content

Commit 8c4af6c

Browse files
authored
MONGOCRYPT-793 support mixing QE and unencrypted JSON schemas (#1076)
1 parent 9c8522f commit 8c4af6c

14 files changed

+526
-8
lines changed

src/mc-schema-broker-private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ mc_schema_broker_t *mc_schema_broker_new(void);
3434

3535
void mc_schema_broker_destroy(mc_schema_broker_t *sb);
3636

37+
void mc_schema_broker_support_mixing_schemas(mc_schema_broker_t *sb);
38+
3739
// mc_schema_broker_request requests a schema for a collection. Ignores duplicates.
3840
// Returns error if two requests have different databases (not-yet supported).
3941
bool mc_schema_broker_request(mc_schema_broker_t *sb, const char *db, const char *coll, mongocrypt_status_t *status);

src/mc-schema-broker.c

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ struct mc_schema_broker_t {
5050
char *db; // Database shared by all schemas.
5151
mc_schema_entry_t *ll;
5252
size_t ll_len;
53+
bool schema_mixing_is_supported;
5354
};
5455

5556
mc_schema_broker_t *mc_schema_broker_new(void) {
@@ -92,6 +93,11 @@ bool mc_schema_broker_has_multiple_requests(const mc_schema_broker_t *sb) {
9293
return sb->ll_len > 1;
9394
}
9495

96+
void mc_schema_broker_support_mixing_schemas(mc_schema_broker_t *sb) {
97+
BSON_ASSERT_PARAM(sb);
98+
sb->schema_mixing_is_supported = true;
99+
}
100+
95101
void mc_schema_broker_destroy(mc_schema_broker_t *sb) {
96102
if (!sb) {
97103
return;
@@ -702,6 +708,11 @@ static bool append_encryptionInformation(const mc_schema_broker_t *sb,
702708
}
703709

704710
for (mc_schema_entry_t *se = sb->ll; se != NULL; se = se->next) {
711+
// encryptedFields is preferred over jsonSchema
712+
if (!se->encryptedFields.set && se->jsonSchema.set) {
713+
continue;
714+
}
715+
705716
BSON_ASSERT(se->satisfied);
706717
bool loop_ok = false;
707718
char *ns = bson_strdup_printf("%s.%s", sb->db, se->coll);
@@ -917,10 +928,20 @@ static bool insert_encryptionInformation(const mc_schema_broker_t *sb,
917928
return ok;
918929
}
919930

931+
static bool any_entry_includes_encryptedFields(mc_schema_entry_t *head) {
932+
for (mc_schema_entry_t *se = head; se != NULL; se = se->next) {
933+
if (se->encryptedFields.set) {
934+
return true;
935+
}
936+
}
937+
938+
return false;
939+
}
940+
920941
// insert_csfleEncryptionSchemas appends schema information to a command for CSFLE.
921942
// Only consumed by query analysis (mongocryptd/crypt_shared).
922943
// For one JSON schema, use `jsonSchema` for backwards compatibility.
923-
// For multiple JSON schemas, use `csfleEncryptionSchemas` (added in server 8.2).
944+
// For multiple JSON schemas, use `csfleEncryptionSchemas` (added in server 8.1).
924945
static bool insert_csfleEncryptionSchemas(const mc_schema_broker_t *sb,
925946
bson_t *cmd /* in/out */,
926947
mc_cmd_target_t cmd_target,
@@ -965,7 +986,13 @@ static bool insert_csfleEncryptionSchemas(const mc_schema_broker_t *sb,
965986
return false;
966987
}
967988

989+
const bool skip_empty_schemas = any_entry_includes_encryptedFields(sb->ll);
990+
968991
for (mc_schema_entry_t *se = sb->ll; se != NULL; se = se->next) {
992+
if (se->encryptedFields.set || (!se->jsonSchema.set && skip_empty_schemas)) {
993+
continue;
994+
}
995+
969996
BSON_ASSERT(se->satisfied);
970997

971998
char *ns = bson_strdup_printf("%s.%s", sb->db, se->coll);
@@ -1033,10 +1060,15 @@ bool mc_schema_broker_add_schemas_to_cmd(const mc_schema_broker_t *sb,
10331060
}
10341061

10351062
if (has_encryptedFields && has_jsonSchema) {
1036-
// If any collection has encryptedFields, error if any collection only has a JSON Schema.
1063+
if (sb->schema_mixing_is_supported) {
1064+
return insert_encryptionInformation(sb, cmd_name, cmd, cmd_target, status)
1065+
&& insert_csfleEncryptionSchemas(sb, cmd, cmd_target, status);
1066+
}
1067+
10371068
CLIENT_ERR("Collection '%s' has an encryptedFields configured, but collection '%s' has a JSON schema "
1038-
"configured. This is currently not supported. To ignore the JSON schema, add an empty entry for "
1039-
"'%s' to AutoEncryptionOpts.encryptedFieldsMap: \"%s\": { \"fields\": [] }",
1069+
"configured. This is not supported on mongocryptd/crypt_shared versions below 8.2. To ignore the "
1070+
"JSON schema, add an empty entry for '%s' to AutoEncryptionOpts.encryptedFieldsMap: \"%s\": { "
1071+
"\"fields\": [] }",
10401072
coll_with_encryptedFields,
10411073
coll_with_jsonSchema,
10421074
coll_with_jsonSchema,

src/mongocrypt-ctx-encrypt.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "mc-efc-private.h"
1818
#include "mc-fle-blob-subtype-private.h"
1919
#include "mc-fle2-rfds-private.h"
20+
#include "mc-schema-broker-private.h"
2021
#include "mc-tokens-private.h"
2122
#include "mongocrypt-ciphertext-private.h"
2223
#include "mongocrypt-crypto-private.h"
@@ -2582,9 +2583,11 @@ bool mongocrypt_ctx_encrypt_init(mongocrypt_ctx_t *ctx, const char *db, int32_t
25822583

25832584
#define WIRE_VERSION_SERVER_6 17
25842585
#define WIRE_VERSION_SERVER_8_1 26
2586+
#define WIRE_VERSION_SERVER_8_2 27
25852587
// The crypt_shared version format is defined in mongo_crypt-v1.h.
25862588
// Example: server 6.2.1 is encoded as 0x0006000200010000
25872589
#define CRYPT_SHARED_8_1 0x0008000100000000ull
2590+
#define CRYPT_SHARED_8_2 0x0008000200000000ull
25882591

25892592
/* mongocrypt_ctx_encrypt_ismaster_done is called when:
25902593
* 1. The max wire version of mongocryptd is known.
@@ -2626,6 +2629,10 @@ static bool mongocrypt_ctx_encrypt_ismaster_done(mongocrypt_ctx_t *ctx) {
26262629
_mongocrypt_ctx_fail(ctx);
26272630
return false;
26282631
}
2632+
2633+
if (ectx->ismaster.maxwireversion >= WIRE_VERSION_SERVER_8_2) {
2634+
mc_schema_broker_support_mixing_schemas(ectx->sb);
2635+
}
26292636
}
26302637
}
26312638

@@ -2643,6 +2650,10 @@ static bool mongocrypt_ctx_encrypt_ismaster_done(mongocrypt_ctx_t *ctx) {
26432650
_mongocrypt_ctx_fail(ctx);
26442651
return false;
26452652
}
2653+
2654+
if (version >= CRYPT_SHARED_8_2) {
2655+
mc_schema_broker_support_mixing_schemas(ectx->sb);
2656+
}
26462657
}
26472658
}
26482659

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"aggregate": "c1",
3+
"pipeline": [
4+
{
5+
"$lookup": {
6+
"from": "c2",
7+
"localField": "joinme",
8+
"foreignField": "joinme",
9+
"as": "matched"
10+
}
11+
},
12+
{
13+
"$match": {
14+
"encryptedUnindexed": "foo"
15+
}
16+
}
17+
],
18+
"cursor": {},
19+
"encryptionInformation": {
20+
"type": {
21+
"$numberInt": "1"
22+
},
23+
"schema": {
24+
"db.c1": {
25+
"escCollection": "enxcol_.c1.esc",
26+
"ecocCollection": "enxcol_.c1.ecoc",
27+
"fields": [
28+
{
29+
"keyId": {
30+
"$binary": {
31+
"base64": "q83vqxI0mHYSNBI0VniQEg==",
32+
"subType": "04"
33+
}
34+
},
35+
"path": "encryptedUnindexed",
36+
"bsonType": "string"
37+
}
38+
]
39+
}
40+
}
41+
},
42+
"csfleEncryptionSchemas": {
43+
"db.c2": {
44+
"jsonSchema": {
45+
"required": [
46+
"foo"
47+
]
48+
},
49+
"isRemoteSchema": true
50+
}
51+
}
52+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"aggregate": "c1",
3+
"pipeline": [
4+
{
5+
"$lookup": {
6+
"from": "c2",
7+
"as": "matched",
8+
"localField": "joinme",
9+
"foreignField": "joinme"
10+
}
11+
},
12+
{
13+
"$match": {
14+
"encryptedUnindexed": {
15+
"$binary": {
16+
"base64": "DIkAAAAFZAAgAAAAAJg7KMGBFzQvSG5ipqeBSZ9oz6bVVQ7VWhOlAEb/g286BXMAIAAAAAAyC0j6Jl0oR17xumRgOxHlZSHkHtaQDkaV+ooVn7dVXgVsACAAAAAAxCCKn0pGHRJv9x2o1rDoFdTQfqbGN0anbP9RVNoYbrESY20AAAAAAAAAAAAA",
17+
"subType": "06"
18+
}
19+
}
20+
}
21+
}
22+
],
23+
"cursor": {},
24+
"encryptionInformation": {
25+
"type": {
26+
"$numberInt": "1"
27+
},
28+
"schema": {
29+
"db.c1": {
30+
"escCollection": "enxcol_.c1.esc",
31+
"ecocCollection": "enxcol_.c1.ecoc",
32+
"fields": [
33+
{
34+
"keyId": {
35+
"$binary": {
36+
"base64": "q83vqxI0mHYSNBI0VniQEg==",
37+
"subType": "04"
38+
}
39+
},
40+
"path": "encryptedUnindexed",
41+
"bsonType": "string"
42+
}
43+
]
44+
}
45+
}
46+
}
47+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"aggregate": "c1",
3+
"pipeline": [
4+
{
5+
"$lookup": {
6+
"from": "c2",
7+
"localField": "joinme",
8+
"foreignField": "joinme",
9+
"as": "matched"
10+
}
11+
},
12+
{
13+
"$match": {
14+
"encryptedUnindexed": "foo"
15+
}
16+
}
17+
],
18+
"cursor": {}
19+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "c1",
3+
"type": "collection",
4+
"options": {
5+
"encryptedFields": {
6+
"escCollection": "enxcol_.c1.esc",
7+
"ecocCollection": "enxcol_.c1.ecoc",
8+
"fields": [
9+
{
10+
"keyId": {
11+
"$binary": {
12+
"base64": "q83vqxI0mHYSNBI0VniQEg==",
13+
"subType": "04"
14+
}
15+
},
16+
"path": "encryptedUnindexed",
17+
"bsonType": "string"
18+
}
19+
]
20+
}
21+
},
22+
"info": {
23+
"readOnly": false,
24+
"uuid": {
25+
"$binary": {
26+
"base64": "1dVWZUeyRZGZ3/NwfKTdLQ==",
27+
"subType": "04"
28+
}
29+
}
30+
},
31+
"idIndex": {
32+
"v": 2,
33+
"key": {
34+
"_id": 1
35+
},
36+
"name": "_id_"
37+
}
38+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"type": "collection",
3+
"name": "c2",
4+
"idIndex": {
5+
"ns": "db.c2",
6+
"name": "_id_",
7+
"key": {
8+
"_id": {
9+
"$numberInt": "1"
10+
}
11+
},
12+
"v": {
13+
"$numberInt": "2"
14+
}
15+
},
16+
"options": {
17+
"validator": {
18+
"$jsonSchema": {
19+
"required": [
20+
"foo"
21+
]
22+
},
23+
"foo": "bar"
24+
}
25+
}
26+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"_id": {
3+
"$binary": {
4+
"base64": "uJ2Njy8YQDuYKbzu2vEKQg==",
5+
"subType": "04"
6+
}
7+
},
8+
"keyMaterial": {
9+
"$binary": {
10+
"base64": "NTpfCae5j0TnbRGsSvHOw7LcIPDhlg8//4N+TQllLZfH0nlj/G3+huCZmWcra+DPH2VbDnmcEmUTCmyPA+Qijjo0v+oL7qNNWUttL4dS8w5GOagQ3/kUtH+ZppgrjbYb1EcPP2G783iYLrTN+9J+fsLV3G36u2hLatGhDmRqDeV9erJOB0bEC69ouki054RWCNJ3AUockMNxxUDe/aQBKw==",
11+
"subType": "00"
12+
}
13+
},
14+
"creationDate": {
15+
"$date": {
16+
"$numberLong": "1733927626824"
17+
}
18+
},
19+
"updateDate": {
20+
"$date": {
21+
"$numberLong": "1733927626824"
22+
}
23+
},
24+
"status": {
25+
"$numberInt": "0"
26+
},
27+
"masterKey": {
28+
"provider": "local"
29+
}
30+
}

0 commit comments

Comments
 (0)