Skip to content

Commit

Permalink
GODRIVER-3289 Add option to configure DEK cache lifetime.
Browse files Browse the repository at this point in the history
  • Loading branch information
qingyang-hu committed Jan 17, 2025
1 parent 0b2f755 commit 50eee42
Show file tree
Hide file tree
Showing 15 changed files with 732 additions and 60 deletions.
2 changes: 1 addition & 1 deletion etc/install-libmongocrypt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# This script installs libmongocrypt into an "install" directory.
set -eux

LIBMONGOCRYPT_TAG="1.11.0"
LIBMONGOCRYPT_TAG="1.12.0"

# Install libmongocrypt based on OS.
if [ "Windows_NT" = "${OS:-}" ]; then
Expand Down
2 changes: 2 additions & 0 deletions internal/integration/json_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ func createAutoEncryptionOptions(t testing.TB, opts bson.Raw) *options.AutoEncry
aeo.SetEncryptedFieldsMap(encryptedFieldsMap)
case "bypassQueryAnalysis":
aeo.SetBypassQueryAnalysis(opt.Boolean())
case "keyExpirationMS":
aeo.SetKeyExpiration(time.Duration(opt.Int32()) * time.Millisecond)
default:
t.Fatalf("unrecognized auto encryption option: %v", name)
}
Expand Down
16 changes: 10 additions & 6 deletions internal/integration/unified/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ type clientEncryptionOpts struct {
KeyVaultClient string `bson:"keyVaultClient"`
KeyVaultNamespace string `bson:"keyVaultNamespace"`
KmsProviders map[string]bson.Raw `bson:"kmsProviders"`
keyExpirationMS *int64 `bson:"keyExpirationMS"`
}

// EntityMap is used to store entities during tests. This type enforces uniqueness so no two entities can have the same
Expand Down Expand Up @@ -735,12 +736,15 @@ func (em *EntityMap) addClientEncryptionEntity(entityOptions *entityOptions) err
return newEntityNotFoundError("client", ceo.KeyVaultClient)
}

ce, err := mongo.NewClientEncryption(
keyVaultClient.Client,
options.ClientEncryption().
SetKeyVaultNamespace(ceo.KeyVaultNamespace).
SetTLSConfig(tlsconf).
SetKmsProviders(kmsProviders))
opts := options.ClientEncryption().
SetKeyVaultNamespace(ceo.KeyVaultNamespace).
SetTLSConfig(tlsconf).
SetKmsProviders(kmsProviders)
if ceo.keyExpirationMS != nil {
opts.SetKeyExpiration(time.Duration(*ceo.keyExpirationMS) * time.Millisecond)
}

ce, err := mongo.NewClientEncryption(keyVaultClient.Client, opts)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/integration/unified/schema_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

var (
supportedSchemaVersions = map[int]string{
1: "1.17",
1: "1.22",
}
)

Expand Down
23 changes: 23 additions & 0 deletions internal/integration/unified/testrunner_operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,29 @@ func executeTestRunnerOperation(ctx context.Context, op *operation, loopDone <-c
defer cancel()

return waitForEvent(wfeCtx, wfeArgs)
case "decrypt":
rawValue, err := args.LookupErr("value")
if err != nil {
return fmt.Errorf("'value' argument not found in decrypt operation")
}
t, d, ok := rawValue.BinaryOK()
if !ok {
return fmt.Errorf("'value' argument is not a BSON binary")
}
ee, ok := entities(ctx).clientEncryptionEntities[op.Object]
if !ok {
return fmt.Errorf("unknown clientEncryption: %s", op.Object)
}
rawValue, err = ee.Decrypt(ctx, bson.Binary{Subtype: t, Data: d})
if err != nil {
return fmt.Errorf("error decrypting: %v", err)
}
if op.ExpectedResult != nil {
if err := verifyValuesMatch(ctx, *op.ExpectedResult, rawValue, false); err != nil {
return fmt.Errorf("result verification failed: %v", err)
}
}
return nil
default:
return fmt.Errorf("unrecognized testRunner operation %q", op.Name)
}
Expand Down
81 changes: 30 additions & 51 deletions mongo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func newClient(opts ...*options.ClientOptions) (*Client, error) {
}
// AutoEncryptionOptions
if clientOpts.AutoEncryptionOptions != nil {
if err := client.configureAutoEncryption(clientOpts); err != nil {
if err = client.configureAutoEncryption(clientOpts); err != nil {
return nil, err
}
} else {
Expand Down Expand Up @@ -471,30 +471,48 @@ func (c *Client) endSessions(ctx context.Context) {
}

func (c *Client) configureAutoEncryption(args *options.ClientOptions) error {
c.encryptedFieldsMap = args.AutoEncryptionOptions.EncryptedFieldsMap
aeOpts := args.AutoEncryptionOptions
c.encryptedFieldsMap = aeOpts.EncryptedFieldsMap
if err := c.configureKeyVaultClientFLE(args); err != nil {
return err
}

if err := c.configureMetadataClientFLE(args); err != nil {
return err
}

mc, err := c.newMongoCrypt(args.AutoEncryptionOptions)
mc, err := c.newMongoCrypt(aeOpts)
if err != nil {
return err
}

// If the crypt_shared library was not loaded, try to spawn and connect to mongocryptd.
if mc.CryptSharedLibVersionString() == "" {
mongocryptdFLE, err := newMongocryptdClient(args.AutoEncryptionOptions)
c.mongocryptdFLE, err = newMongocryptdClient(aeOpts)
if err != nil {
return err
}
c.mongocryptdFLE = mongocryptdFLE
}

c.configureCryptFLE(mc, args.AutoEncryptionOptions)
kr := keyRetriever{coll: c.keyVaultCollFLE}
var cir collInfoRetriever
bypass := aeOpts.BypassAutoEncryption != nil && *aeOpts.BypassAutoEncryption
if !bypass {
if args.MaxPoolSize != nil && *args.MaxPoolSize == 0 {
c.metadataClientFLE = c
} else {
c.metadataClientFLE, err = c.getOrCreateInternalClient(args)
if err != nil {
return err
}
}
cir.client = c.metadataClientFLE
}

c.cryptFLE = driver.NewCrypt(&driver.CryptOptions{
MongoCrypt: mc,
CollInfoFn: cir.cryptCollInfo,
KeyFn: kr.cryptKeys,
MarkFn: c.mongocryptdFLE.markCommand,
TLSConfig: aeOpts.TLSConfig,
BypassAutoEncryption: bypass,
})
return nil
}

Expand Down Expand Up @@ -537,24 +555,6 @@ func (c *Client) configureKeyVaultClientFLE(clientOpts *options.ClientOptions) e
return nil
}

func (c *Client) configureMetadataClientFLE(clientOpts *options.ClientOptions) error {
aeOpts := clientOpts.AutoEncryptionOptions

if aeOpts.BypassAutoEncryption != nil && *aeOpts.BypassAutoEncryption {
// no need for a metadata client.
return nil
}
if clientOpts.MaxPoolSize != nil && *clientOpts.MaxPoolSize == 0 {
c.metadataClientFLE = c
return nil
}

var err error
c.metadataClientFLE, err = c.getOrCreateInternalClient(clientOpts)

return err
}

func (c *Client) newMongoCrypt(opts *options.AutoEncryptionOptions) (*mongocrypt.MongoCrypt, error) {
// convert schemas in SchemaMap to bsoncore documents
cryptSchemaMap := make(map[string]bsoncore.Document)
Expand Down Expand Up @@ -611,7 +611,8 @@ func (c *Client) newMongoCrypt(opts *options.AutoEncryptionOptions) (*mongocrypt
SetEncryptedFieldsMap(cryptEncryptedFieldsMap).
SetCryptSharedLibDisabled(cryptSharedLibDisabled || bypassAutoEncryption).
SetCryptSharedLibOverridePath(cryptSharedLibPath).
SetHTTPClient(opts.HTTPClient))
SetHTTPClient(opts.HTTPClient).
SetKeyExpiration(opts.KeyExpiration))
if err != nil {
return nil, err
}
Expand All @@ -637,28 +638,6 @@ func (c *Client) newMongoCrypt(opts *options.AutoEncryptionOptions) (*mongocrypt
return mc, nil
}

//nolint:unused // the unused linter thinks that this function is unreachable because "c.newMongoCrypt" always panics without the "cse" build tag set.
func (c *Client) configureCryptFLE(mc *mongocrypt.MongoCrypt, opts *options.AutoEncryptionOptions) {
bypass := opts.BypassAutoEncryption != nil && *opts.BypassAutoEncryption
kr := keyRetriever{coll: c.keyVaultCollFLE}
var cir collInfoRetriever
// If bypass is true, c.metadataClientFLE is nil and the collInfoRetriever
// will not be used. If bypass is false, to the parent client or the
// internal client.
if !bypass {
cir = collInfoRetriever{client: c.metadataClientFLE}
}

c.cryptFLE = driver.NewCrypt(&driver.CryptOptions{
MongoCrypt: mc,
CollInfoFn: cir.cryptCollInfo,
KeyFn: kr.cryptKeys,
MarkFn: c.mongocryptdFLE.markCommand,
TLSConfig: opts.TLSConfig,
BypassAutoEncryption: bypass,
})
}

// validSession returns an error if the session doesn't belong to the client
func (c *Client) validSession(sess *session.Client) error {
if sess != nil && sess.ClientID != c.id {
Expand Down
3 changes: 2 additions & 1 deletion mongo/client_encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ func NewClientEncryption(keyVaultClient *Client, opts ...options.Lister[options.
// ClientEncryption because it's only needed for AutoEncryption and we don't expect users to
// have the crypt_shared library installed if they're using ClientEncryption.
SetCryptSharedLibDisabled(true).
SetHTTPClient(cea.HTTPClient))
SetHTTPClient(cea.HTTPClient).
SetKeyExpiration(cea.KeyExpiration))
if err != nil {
return nil, err
}
Expand Down
9 changes: 9 additions & 0 deletions mongo/options/autoencryptionoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package options
import (
"crypto/tls"
"net/http"
"time"

"go.mongodb.org/mongo-driver/v2/internal/httputil"
)
Expand Down Expand Up @@ -40,6 +41,7 @@ type AutoEncryptionOptions struct {
HTTPClient *http.Client
EncryptedFieldsMap map[string]interface{}
BypassQueryAnalysis *bool
KeyExpiration *time.Duration
}

// AutoEncryption creates a new AutoEncryptionOptions configured with default values.
Expand Down Expand Up @@ -164,3 +166,10 @@ func (a *AutoEncryptionOptions) SetBypassQueryAnalysis(bypass bool) *AutoEncrypt

return a
}

// SetKeyExpiration specifies duration for the key expiration. 0 means "never expire".
func (a *AutoEncryptionOptions) SetKeyExpiration(expiration time.Duration) *AutoEncryptionOptions {
a.KeyExpiration = &expiration

return a
}
13 changes: 13 additions & 0 deletions mongo/options/clientencryptionoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"crypto/tls"
"fmt"
"net/http"
"time"

"go.mongodb.org/mongo-driver/v2/internal/httputil"
)
Expand All @@ -22,6 +23,7 @@ type ClientEncryptionOptions struct {
KmsProviders map[string]map[string]interface{}
TLSConfig map[string]*tls.Config
HTTPClient *http.Client
KeyExpiration *time.Duration
}

// ClientEncryptionOptionsBuilder contains options to configure client
Expand Down Expand Up @@ -80,6 +82,17 @@ func (c *ClientEncryptionOptionsBuilder) SetTLSConfig(cfg map[string]*tls.Config
return c
}

// SetKeyExpiration specifies duration for the key expiration. 0 means "never expire".
func (c *ClientEncryptionOptionsBuilder) SetKeyExpiration(expiration time.Duration) *ClientEncryptionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *ClientEncryptionOptions) error {
opts.KeyExpiration = &expiration

return nil
})

return c
}

// BuildTLSConfig specifies tls.Config options for each KMS provider to use to configure TLS on all connections created
// to the KMS provider. The input map should contain a mapping from each KMS provider to a document containing the necessary
// options, as follows:
Expand Down
Loading

0 comments on commit 50eee42

Please sign in to comment.