Skip to content

Commit ef0c0ab

Browse files
GODRIVER-2493 Automatically create Queryable Encryption keys. (#1154)
* GODRIVER-2493 Automatically create Queryable Encryption keys. Co-authored-by: Benjamin Rewis <[email protected]> Co-authored-by: Benjamin Rewis <[email protected]>
1 parent e1bf885 commit ef0c0ab

File tree

2 files changed

+195
-2
lines changed

2 files changed

+195
-2
lines changed

mongo/client_encryption.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strings"
1414

1515
"go.mongodb.org/mongo-driver/bson"
16+
"go.mongodb.org/mongo-driver/bson/bsonrw"
1617
"go.mongodb.org/mongo-driver/bson/primitive"
1718
"go.mongodb.org/mongo-driver/mongo/options"
1819
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
@@ -72,6 +73,62 @@ func NewClientEncryption(keyVaultClient *Client, opts ...*options.ClientEncrypti
7273
return ce, nil
7374
}
7475

76+
// CreateEncryptedCollection creates a new collection with the help of automatic generation of new encryption data keys for null keyIds.
77+
// It returns the created collection and the encrypted fields document used to create it.
78+
func (ce *ClientEncryption) CreateEncryptedCollection(ctx context.Context,
79+
db *Database, coll string, createOpts *options.CreateCollectionOptions,
80+
kmsProvider string, dkOpts *options.DataKeyOptions) (*Collection, bson.M, error) {
81+
if createOpts == nil {
82+
return nil, nil, errors.New("nil CreateCollectionOptions")
83+
}
84+
ef := createOpts.EncryptedFields
85+
if ef == nil {
86+
// Otherwise, try to get EncryptedFields from EncryptedFieldsMap.
87+
ef = db.getEncryptedFieldsFromMap(coll)
88+
}
89+
if ef == nil {
90+
return nil, nil, errors.New("no EncryptedFields defined for the collection")
91+
}
92+
93+
efBSON, err := transformBsoncoreDocument(db.registry, ef, true, "encryptedFields")
94+
if err != nil {
95+
return nil, nil, err
96+
}
97+
r := bsonrw.NewBSONDocumentReader(efBSON)
98+
dec, err := bson.NewDecoder(r)
99+
if err != nil {
100+
return nil, nil, err
101+
}
102+
var m bson.M
103+
err = dec.Decode(&m)
104+
if err != nil {
105+
return nil, nil, err
106+
}
107+
108+
if v, ok := m["fields"]; ok {
109+
if fields, ok := v.(bson.A); ok {
110+
for _, field := range fields {
111+
if f, ok := field.(bson.M); !ok {
112+
continue
113+
} else if v, ok := f["keyId"]; ok && v == nil {
114+
keyid, err := ce.CreateDataKey(ctx, kmsProvider, dkOpts)
115+
if err != nil {
116+
createOpts.EncryptedFields = m
117+
return nil, m, err
118+
}
119+
f["keyId"] = keyid
120+
}
121+
}
122+
createOpts.EncryptedFields = m
123+
}
124+
}
125+
err = db.CreateCollection(ctx, coll, createOpts)
126+
if err != nil {
127+
return nil, m, err
128+
}
129+
return db.Collection(coll), m, nil
130+
}
131+
75132
// AddKeyAltName adds a keyAltName to the keyAltNames array of the key document in the key vault collection with the
76133
// given UUID (BSON binary subtype 0x04). Returns the previous version of the key document.
77134
func (ce *ClientEncryption) AddKeyAltName(ctx context.Context, id primitive.Binary, keyAltName string) *SingleResult {
@@ -166,9 +223,9 @@ func (ce *ClientEncryption) Encrypt(ctx context.Context, val bson.RawValue,
166223
// On success, `result` is populated with the resulting BSON document.
167224
// `expr` is expected to be a BSON document of one of the following forms:
168225
// 1. A Match Expression of this form:
169-
// {$and: [{<field>: {$gt: <value1>}}, {<field>: {$lt: <value2> }}]}
226+
// {$and: [{<field>: {$gt: <value1>}}, {<field>: {$lt: <value2> }}]}
170227
// 2. An Aggregate Expression of this form:
171-
// {$and: [{$gt: [<fieldpath>, <value1>]}, {$lt: [<fieldpath>, <value2>]}]
228+
// {$and: [{$gt: [<fieldpath>, <value1>]}, {$lt: [<fieldpath>, <value2>]}]
172229
// $gt may also be $gte. $lt may also be $lte.
173230
// Only supported for queryType "rangePreview"
174231
// NOTE(kevinAlbs): The Range algorithm is experimental only. It is not intended for public use. It is subject to breaking changes.

mongo/integration/client_side_encryption_prose_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2060,6 +2060,142 @@ func TestClientSideEncryptionProse(t *testing.T) {
20602060
assert.Nil(mt, err, "InsertOne error: %v", err)
20612061
})
20622062

2063+
autoKeyRunOpts := mtest.NewOptions().MinServerVersion("6.0").Topologies(mtest.ReplicaSet, mtest.Sharded, mtest.LoadBalanced, mtest.ShardedReplicaSet)
2064+
mt.RunOpts("21. automatic data encryption keys", autoKeyRunOpts, func(mt *mtest.T) {
2065+
setup := func() (*mongo.Client, *mongo.ClientEncryption, error) {
2066+
opts := options.Client().ApplyURI(mtest.ClusterURI())
2067+
client, err := mongo.Connect(context.Background(), opts)
2068+
if err != nil {
2069+
return nil, nil, err
2070+
}
2071+
client.Database("keyvault").Collection("datakeys").Drop(context.Background())
2072+
client.Database("db").Drop(context.Background())
2073+
ceo := options.ClientEncryption().
2074+
SetKmsProviders(fullKmsProvidersMap).
2075+
SetKeyVaultNamespace(kvNamespace)
2076+
clientEnc, err := mongo.NewClientEncryption(client, ceo)
2077+
if err != nil {
2078+
return nil, nil, err
2079+
}
2080+
return client, clientEnc, nil
2081+
}
2082+
2083+
mt.Run("case 1: simple creation and validation", func(mt *mtest.T) {
2084+
client, clientEnc, err := setup()
2085+
assert.Nil(mt, err, "setup error: %v", err)
2086+
defer func() {
2087+
err := clientEnc.Close(context.Background())
2088+
assert.Nil(mt, err, "error in Close")
2089+
}()
2090+
2091+
var encryptedFields bson.Raw
2092+
err = bson.UnmarshalExtJSON([]byte(`{
2093+
"fields": [{
2094+
"path": "ssn",
2095+
"bsonType": "string",
2096+
"keyId": null
2097+
}]
2098+
}`), true /* canonical */, &encryptedFields)
2099+
assert.Nil(mt, err, "Unmarshal error: %v", err)
2100+
2101+
coll, _, err := clientEnc.CreateEncryptedCollection(
2102+
context.Background(),
2103+
client.Database("db"),
2104+
"testing1", options.CreateCollection().SetEncryptedFields(encryptedFields),
2105+
"local", nil,
2106+
)
2107+
assert.Nil(mt, err, "CreateCollection error: %v", err)
2108+
2109+
_, err = coll.InsertOne(context.Background(), bson.D{{"ssn", "123-45-6789"}})
2110+
assert.ErrorContains(mt, err, "Document failed validation")
2111+
})
2112+
mt.Run("case 2: missing encryptedFields", func(mt *mtest.T) {
2113+
client, clientEnc, err := setup()
2114+
assert.Nil(mt, err, "setup error: %v", err)
2115+
defer func() {
2116+
err := clientEnc.Close(context.Background())
2117+
assert.Nil(mt, err, "error in Close")
2118+
}()
2119+
2120+
coll, _, err := clientEnc.CreateEncryptedCollection(
2121+
context.Background(),
2122+
client.Database("db"),
2123+
"testing1", options.CreateCollection(),
2124+
"local", nil,
2125+
)
2126+
assert.Nil(mt, coll, "expect nil collection")
2127+
assert.EqualError(mt, err, "no EncryptedFields defined for the collection")
2128+
})
2129+
mt.Run("case 3: invalid keyId", func(mt *mtest.T) {
2130+
client, clientEnc, err := setup()
2131+
assert.Nil(mt, err, "setup error: %v", err)
2132+
defer func() {
2133+
err := clientEnc.Close(context.Background())
2134+
assert.Nil(mt, err, "error in Close")
2135+
}()
2136+
2137+
var encryptedFields bson.Raw
2138+
err = bson.UnmarshalExtJSON([]byte(`{
2139+
"fields": [{
2140+
"path": "ssn",
2141+
"bsonType": "string",
2142+
"keyId": false
2143+
}]
2144+
}`), true /* canonical */, &encryptedFields)
2145+
assert.Nil(mt, err, "Unmarshal error: %v", err)
2146+
2147+
_, _, err = clientEnc.CreateEncryptedCollection(
2148+
context.Background(),
2149+
client.Database("db"),
2150+
"testing1", options.CreateCollection().SetEncryptedFields(encryptedFields),
2151+
"local", nil,
2152+
)
2153+
assert.ErrorContains(mt, err, "BSON field 'create.encryptedFields.fields.keyId' is the wrong type 'bool', expected type 'binData'")
2154+
})
2155+
mt.Run("case 4: insert encrypted value", func(mt *mtest.T) {
2156+
client, clientEnc, err := setup()
2157+
assert.Nil(mt, err, "setup error: %v", err)
2158+
defer func() {
2159+
err := clientEnc.Close(context.Background())
2160+
assert.Nil(mt, err, "error in Close")
2161+
}()
2162+
2163+
var encryptedFields bson.Raw
2164+
err = bson.UnmarshalExtJSON([]byte(`{
2165+
"fields": [{
2166+
"path": "ssn",
2167+
"bsonType": "string",
2168+
"keyId": null
2169+
}]
2170+
}`), true /* canonical */, &encryptedFields)
2171+
assert.Nil(mt, err, "Unmarshal error: %v", err)
2172+
2173+
coll, ef, err := clientEnc.CreateEncryptedCollection(
2174+
context.Background(),
2175+
client.Database("db"),
2176+
"testing1", options.CreateCollection().SetEncryptedFields(encryptedFields),
2177+
"local", nil,
2178+
)
2179+
assert.Nil(mt, err, "CreateCollection error: %v", err)
2180+
2181+
keyid := ef["fields"].(bson.A)[0].(bson.M)["keyId"].(primitive.Binary)
2182+
rawValueType, rawValueData, err := bson.MarshalValue("123-45-6789")
2183+
assert.Nil(mt, err, "MarshalValue error: %v", err)
2184+
rawValue := bson.RawValue{Type: rawValueType, Value: rawValueData}
2185+
encryptionOpts := options.Encrypt().
2186+
SetAlgorithm("Unindexed").
2187+
SetKeyID(keyid)
2188+
encryptedField, err := clientEnc.Encrypt(
2189+
context.Background(),
2190+
rawValue,
2191+
encryptionOpts)
2192+
assert.Nil(mt, err, "Encrypt error: %v", err)
2193+
2194+
_, err = coll.InsertOne(context.Background(), bson.D{{"ssn", encryptedField}})
2195+
assert.Nil(mt, err, "InsertOne error: %v", err)
2196+
})
2197+
})
2198+
20632199
rangeRunOpts := mtest.NewOptions().MinServerVersion("6.2").Topologies(mtest.ReplicaSet, mtest.Sharded, mtest.LoadBalanced, mtest.ShardedReplicaSet)
20642200
mt.RunOpts("22. range explicit encryption", rangeRunOpts, func(mt *mtest.T) {
20652201
type testcase struct {

0 commit comments

Comments
 (0)