Skip to content

Commit

Permalink
GODRIVER-2493 Automatically create Queryable Encryption keys. (mongod…
Browse files Browse the repository at this point in the history
…b#1154)

* GODRIVER-2493 Automatically create Queryable Encryption keys.

Co-authored-by: Benjamin Rewis <32186188+benjirewis@users.noreply.github.com>

Co-authored-by: Benjamin Rewis <32186188+benjirewis@users.noreply.github.com>
  • Loading branch information
qingyang-hu and benjirewis authored Jan 4, 2023
1 parent e1bf885 commit ef0c0ab
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 2 deletions.
61 changes: 59 additions & 2 deletions mongo/client_encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"

"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
Expand Down Expand Up @@ -72,6 +73,62 @@ func NewClientEncryption(keyVaultClient *Client, opts ...*options.ClientEncrypti
return ce, nil
}

// CreateEncryptedCollection creates a new collection with the help of automatic generation of new encryption data keys for null keyIds.
// It returns the created collection and the encrypted fields document used to create it.
func (ce *ClientEncryption) CreateEncryptedCollection(ctx context.Context,
db *Database, coll string, createOpts *options.CreateCollectionOptions,
kmsProvider string, dkOpts *options.DataKeyOptions) (*Collection, bson.M, error) {
if createOpts == nil {
return nil, nil, errors.New("nil CreateCollectionOptions")
}
ef := createOpts.EncryptedFields
if ef == nil {
// Otherwise, try to get EncryptedFields from EncryptedFieldsMap.
ef = db.getEncryptedFieldsFromMap(coll)
}
if ef == nil {
return nil, nil, errors.New("no EncryptedFields defined for the collection")
}

efBSON, err := transformBsoncoreDocument(db.registry, ef, true, "encryptedFields")
if err != nil {
return nil, nil, err
}
r := bsonrw.NewBSONDocumentReader(efBSON)
dec, err := bson.NewDecoder(r)
if err != nil {
return nil, nil, err
}
var m bson.M
err = dec.Decode(&m)
if err != nil {
return nil, nil, err
}

if v, ok := m["fields"]; ok {
if fields, ok := v.(bson.A); ok {
for _, field := range fields {
if f, ok := field.(bson.M); !ok {
continue
} else if v, ok := f["keyId"]; ok && v == nil {
keyid, err := ce.CreateDataKey(ctx, kmsProvider, dkOpts)
if err != nil {
createOpts.EncryptedFields = m
return nil, m, err
}
f["keyId"] = keyid
}
}
createOpts.EncryptedFields = m
}
}
err = db.CreateCollection(ctx, coll, createOpts)
if err != nil {
return nil, m, err
}
return db.Collection(coll), m, nil
}

// AddKeyAltName adds a keyAltName to the keyAltNames array of the key document in the key vault collection with the
// given UUID (BSON binary subtype 0x04). Returns the previous version of the key document.
func (ce *ClientEncryption) AddKeyAltName(ctx context.Context, id primitive.Binary, keyAltName string) *SingleResult {
Expand Down Expand Up @@ -166,9 +223,9 @@ func (ce *ClientEncryption) Encrypt(ctx context.Context, val bson.RawValue,
// On success, `result` is populated with the resulting BSON document.
// `expr` is expected to be a BSON document of one of the following forms:
// 1. A Match Expression of this form:
// {$and: [{<field>: {$gt: <value1>}}, {<field>: {$lt: <value2> }}]}
// {$and: [{<field>: {$gt: <value1>}}, {<field>: {$lt: <value2> }}]}
// 2. An Aggregate Expression of this form:
// {$and: [{$gt: [<fieldpath>, <value1>]}, {$lt: [<fieldpath>, <value2>]}]
// {$and: [{$gt: [<fieldpath>, <value1>]}, {$lt: [<fieldpath>, <value2>]}]
// $gt may also be $gte. $lt may also be $lte.
// Only supported for queryType "rangePreview"
// NOTE(kevinAlbs): The Range algorithm is experimental only. It is not intended for public use. It is subject to breaking changes.
Expand Down
136 changes: 136 additions & 0 deletions mongo/integration/client_side_encryption_prose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2060,6 +2060,142 @@ func TestClientSideEncryptionProse(t *testing.T) {
assert.Nil(mt, err, "InsertOne error: %v", err)
})

autoKeyRunOpts := mtest.NewOptions().MinServerVersion("6.0").Topologies(mtest.ReplicaSet, mtest.Sharded, mtest.LoadBalanced, mtest.ShardedReplicaSet)
mt.RunOpts("21. automatic data encryption keys", autoKeyRunOpts, func(mt *mtest.T) {
setup := func() (*mongo.Client, *mongo.ClientEncryption, error) {
opts := options.Client().ApplyURI(mtest.ClusterURI())
client, err := mongo.Connect(context.Background(), opts)
if err != nil {
return nil, nil, err
}
client.Database("keyvault").Collection("datakeys").Drop(context.Background())
client.Database("db").Drop(context.Background())
ceo := options.ClientEncryption().
SetKmsProviders(fullKmsProvidersMap).
SetKeyVaultNamespace(kvNamespace)
clientEnc, err := mongo.NewClientEncryption(client, ceo)
if err != nil {
return nil, nil, err
}
return client, clientEnc, nil
}

mt.Run("case 1: simple creation and validation", func(mt *mtest.T) {
client, clientEnc, err := setup()
assert.Nil(mt, err, "setup error: %v", err)
defer func() {
err := clientEnc.Close(context.Background())
assert.Nil(mt, err, "error in Close")
}()

var encryptedFields bson.Raw
err = bson.UnmarshalExtJSON([]byte(`{
"fields": [{
"path": "ssn",
"bsonType": "string",
"keyId": null
}]
}`), true /* canonical */, &encryptedFields)
assert.Nil(mt, err, "Unmarshal error: %v", err)

coll, _, err := clientEnc.CreateEncryptedCollection(
context.Background(),
client.Database("db"),
"testing1", options.CreateCollection().SetEncryptedFields(encryptedFields),
"local", nil,
)
assert.Nil(mt, err, "CreateCollection error: %v", err)

_, err = coll.InsertOne(context.Background(), bson.D{{"ssn", "123-45-6789"}})
assert.ErrorContains(mt, err, "Document failed validation")
})
mt.Run("case 2: missing encryptedFields", func(mt *mtest.T) {
client, clientEnc, err := setup()
assert.Nil(mt, err, "setup error: %v", err)
defer func() {
err := clientEnc.Close(context.Background())
assert.Nil(mt, err, "error in Close")
}()

coll, _, err := clientEnc.CreateEncryptedCollection(
context.Background(),
client.Database("db"),
"testing1", options.CreateCollection(),
"local", nil,
)
assert.Nil(mt, coll, "expect nil collection")
assert.EqualError(mt, err, "no EncryptedFields defined for the collection")
})
mt.Run("case 3: invalid keyId", func(mt *mtest.T) {
client, clientEnc, err := setup()
assert.Nil(mt, err, "setup error: %v", err)
defer func() {
err := clientEnc.Close(context.Background())
assert.Nil(mt, err, "error in Close")
}()

var encryptedFields bson.Raw
err = bson.UnmarshalExtJSON([]byte(`{
"fields": [{
"path": "ssn",
"bsonType": "string",
"keyId": false
}]
}`), true /* canonical */, &encryptedFields)
assert.Nil(mt, err, "Unmarshal error: %v", err)

_, _, err = clientEnc.CreateEncryptedCollection(
context.Background(),
client.Database("db"),
"testing1", options.CreateCollection().SetEncryptedFields(encryptedFields),
"local", nil,
)
assert.ErrorContains(mt, err, "BSON field 'create.encryptedFields.fields.keyId' is the wrong type 'bool', expected type 'binData'")
})
mt.Run("case 4: insert encrypted value", func(mt *mtest.T) {
client, clientEnc, err := setup()
assert.Nil(mt, err, "setup error: %v", err)
defer func() {
err := clientEnc.Close(context.Background())
assert.Nil(mt, err, "error in Close")
}()

var encryptedFields bson.Raw
err = bson.UnmarshalExtJSON([]byte(`{
"fields": [{
"path": "ssn",
"bsonType": "string",
"keyId": null
}]
}`), true /* canonical */, &encryptedFields)
assert.Nil(mt, err, "Unmarshal error: %v", err)

coll, ef, err := clientEnc.CreateEncryptedCollection(
context.Background(),
client.Database("db"),
"testing1", options.CreateCollection().SetEncryptedFields(encryptedFields),
"local", nil,
)
assert.Nil(mt, err, "CreateCollection error: %v", err)

keyid := ef["fields"].(bson.A)[0].(bson.M)["keyId"].(primitive.Binary)
rawValueType, rawValueData, err := bson.MarshalValue("123-45-6789")
assert.Nil(mt, err, "MarshalValue error: %v", err)
rawValue := bson.RawValue{Type: rawValueType, Value: rawValueData}
encryptionOpts := options.Encrypt().
SetAlgorithm("Unindexed").
SetKeyID(keyid)
encryptedField, err := clientEnc.Encrypt(
context.Background(),
rawValue,
encryptionOpts)
assert.Nil(mt, err, "Encrypt error: %v", err)

_, err = coll.InsertOne(context.Background(), bson.D{{"ssn", encryptedField}})
assert.Nil(mt, err, "InsertOne error: %v", err)
})
})

rangeRunOpts := mtest.NewOptions().MinServerVersion("6.2").Topologies(mtest.ReplicaSet, mtest.Sharded, mtest.LoadBalanced, mtest.ShardedReplicaSet)
mt.RunOpts("22. range explicit encryption", rangeRunOpts, func(mt *mtest.T) {
type testcase struct {
Expand Down

0 comments on commit ef0c0ab

Please sign in to comment.