github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/go.mongodb.org/mongo-driver/mongo/mongocryptd.go (about)

     1  // Copyright (C) MongoDB, Inc. 2017-present.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  package mongo
     8  
     9  import (
    10  	"context"
    11  	"os/exec"
    12  	"strings"
    13  	"time"
    14  
    15  	"go.mongodb.org/mongo-driver/mongo/options"
    16  	"go.mongodb.org/mongo-driver/mongo/readconcern"
    17  	"go.mongodb.org/mongo-driver/mongo/readpref"
    18  	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
    19  )
    20  
    21  const (
    22  	defaultServerSelectionTimeout = 10 * time.Second
    23  	defaultURI                    = "mongodb://localhost:27020"
    24  	defaultPath                   = "mongocryptd"
    25  	serverSelectionTimeoutStr     = "server selection error"
    26  )
    27  
    28  var defaultTimeoutArgs = []string{"--idleShutdownTimeoutSecs=60"}
    29  var databaseOpts = options.Database().SetReadConcern(readconcern.New()).SetReadPreference(readpref.Primary())
    30  
    31  type mongocryptdClient struct {
    32  	bypassSpawn bool
    33  	client      *Client
    34  	path        string
    35  	spawnArgs   []string
    36  }
    37  
    38  // newMongocryptdClient creates a client to mongocryptd.
    39  // newMongocryptdClient is expected to not be called if the crypt shared library is available.
    40  // The crypt shared library replaces all mongocryptd functionality.
    41  func newMongocryptdClient(opts *options.AutoEncryptionOptions) (*mongocryptdClient, error) {
    42  	// create mcryptClient instance and spawn process if necessary
    43  	var bypassSpawn bool
    44  	var bypassAutoEncryption bool
    45  
    46  	if bypass, ok := opts.ExtraOptions["mongocryptdBypassSpawn"]; ok {
    47  		bypassSpawn = bypass.(bool)
    48  	}
    49  	if opts.BypassAutoEncryption != nil {
    50  		bypassAutoEncryption = *opts.BypassAutoEncryption
    51  	}
    52  
    53  	bypassQueryAnalysis := opts.BypassQueryAnalysis != nil && *opts.BypassQueryAnalysis
    54  
    55  	mc := &mongocryptdClient{
    56  		// mongocryptd should not be spawned if any of these conditions are true:
    57  		// - mongocryptdBypassSpawn is passed
    58  		// - bypassAutoEncryption is true because mongocryptd is not used during decryption
    59  		// - bypassQueryAnalysis is true because mongocryptd is not used during decryption
    60  		bypassSpawn: bypassSpawn || bypassAutoEncryption || bypassQueryAnalysis,
    61  	}
    62  
    63  	if !mc.bypassSpawn {
    64  		mc.path, mc.spawnArgs = createSpawnArgs(opts.ExtraOptions)
    65  		if err := mc.spawnProcess(); err != nil {
    66  			return nil, err
    67  		}
    68  	}
    69  
    70  	// get connection string
    71  	uri := defaultURI
    72  	if u, ok := opts.ExtraOptions["mongocryptdURI"]; ok {
    73  		uri = u.(string)
    74  	}
    75  
    76  	// create client
    77  	client, err := NewClient(options.Client().ApplyURI(uri).SetServerSelectionTimeout(defaultServerSelectionTimeout))
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	mc.client = client
    82  
    83  	return mc, nil
    84  }
    85  
    86  // markCommand executes the given command on mongocryptd.
    87  func (mc *mongocryptdClient) markCommand(ctx context.Context, dbName string, cmd bsoncore.Document) (bsoncore.Document, error) {
    88  	// Remove the explicit session from the context if one is set.
    89  	// The explicit session will be from a different client.
    90  	// If an explicit session is set, it is applied after automatic encryption.
    91  	ctx = NewSessionContext(ctx, nil)
    92  	db := mc.client.Database(dbName, databaseOpts)
    93  
    94  	res, err := db.RunCommand(ctx, cmd).DecodeBytes()
    95  	// propagate original result
    96  	if err == nil {
    97  		return bsoncore.Document(res), nil
    98  	}
    99  	// wrap original error
   100  	if mc.bypassSpawn || !strings.Contains(err.Error(), serverSelectionTimeoutStr) {
   101  		return nil, MongocryptdError{Wrapped: err}
   102  	}
   103  
   104  	// re-spawn and retry
   105  	if err = mc.spawnProcess(); err != nil {
   106  		return nil, err
   107  	}
   108  	res, err = db.RunCommand(ctx, cmd).DecodeBytes()
   109  	if err != nil {
   110  		return nil, MongocryptdError{Wrapped: err}
   111  	}
   112  	return bsoncore.Document(res), nil
   113  }
   114  
   115  // connect connects the underlying Client instance. This must be called before performing any mark operations.
   116  func (mc *mongocryptdClient) connect(ctx context.Context) error {
   117  	return mc.client.Connect(ctx)
   118  }
   119  
   120  // disconnect disconnects the underlying Client instance. This should be called after all operations have completed.
   121  func (mc *mongocryptdClient) disconnect(ctx context.Context) error {
   122  	return mc.client.Disconnect(ctx)
   123  }
   124  
   125  func (mc *mongocryptdClient) spawnProcess() error {
   126  	// Ignore gosec warning about subprocess launched with externally-provided path variable.
   127  	/* #nosec G204 */
   128  	cmd := exec.Command(mc.path, mc.spawnArgs...)
   129  	cmd.Stdout = nil
   130  	cmd.Stderr = nil
   131  	return cmd.Start()
   132  }
   133  
   134  // createSpawnArgs creates arguments to spawn mcryptClient. It returns the path and a slice of arguments.
   135  func createSpawnArgs(opts map[string]interface{}) (string, []string) {
   136  	var spawnArgs []string
   137  
   138  	// get command path
   139  	path := defaultPath
   140  	if p, ok := opts["mongocryptdPath"]; ok {
   141  		path = p.(string)
   142  	}
   143  
   144  	// add specified options
   145  	if sa, ok := opts["mongocryptdSpawnArgs"]; ok {
   146  		spawnArgs = append(spawnArgs, sa.([]string)...)
   147  	}
   148  
   149  	// add timeout options if necessary
   150  	var foundTimeout bool
   151  	for _, arg := range spawnArgs {
   152  		// need to use HasPrefix instead of doing an exact equality check because both
   153  		// mongocryptd supports both [--idleShutdownTimeoutSecs, 0] and [--idleShutdownTimeoutSecs=0]
   154  		if strings.HasPrefix(arg, "--idleShutdownTimeoutSecs") {
   155  			foundTimeout = true
   156  			break
   157  		}
   158  	}
   159  	if !foundTimeout {
   160  		spawnArgs = append(spawnArgs, defaultTimeoutArgs...)
   161  	}
   162  
   163  	return path, spawnArgs
   164  }