github.com/hashicorp/vault/sdk@v0.13.0/database/dbplugin/plugin.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package dbplugin
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"time"
    10  
    11  	"github.com/hashicorp/errwrap"
    12  	log "github.com/hashicorp/go-hclog"
    13  	plugin "github.com/hashicorp/go-plugin"
    14  	"github.com/hashicorp/vault/sdk/helper/consts"
    15  	"github.com/hashicorp/vault/sdk/helper/pluginutil"
    16  	"google.golang.org/grpc"
    17  )
    18  
    19  // Database is the interface that all database objects must implement.
    20  type Database interface {
    21  	// Type returns the TypeName for the particular database backend
    22  	// implementation. This type name is usually set as a constant within the
    23  	// database backend implementation, e.g. "mysql" for the MySQL database
    24  	// backend.
    25  	Type() (string, error)
    26  
    27  	// CreateUser is called on `$ vault read database/creds/:role-name` and it's
    28  	// also the first time anything is touched from `$ vault write
    29  	// database/roles/:role-name`. This is likely to be the highest-throughput
    30  	// method for most plugins.
    31  	CreateUser(ctx context.Context, statements Statements, usernameConfig UsernameConfig, expiration time.Time) (username string, password string, err error)
    32  
    33  	// RenewUser is triggered by a renewal call to the API. In many database
    34  	// backends, this triggers a call on the underlying database that extends a
    35  	// VALID UNTIL clause on a user. However, if no such need exists, setting
    36  	// this as a NO-OP means that when renewal is called, the lease renewal time
    37  	// is pushed further out as appropriate, thus pushing out the time until the
    38  	// RevokeUser method is called.
    39  	RenewUser(ctx context.Context, statements Statements, username string, expiration time.Time) error
    40  
    41  	// RevokeUser is triggered either automatically by a lease expiration, or by
    42  	// a revocation call to the API.
    43  	RevokeUser(ctx context.Context, statements Statements, username string) error
    44  
    45  	// RotateRootCredentials is triggered by a root credential rotation call to
    46  	// the API.
    47  	RotateRootCredentials(ctx context.Context, statements []string) (config map[string]interface{}, err error)
    48  
    49  	// GenerateCredentials returns a generated password for the plugin. This is
    50  	// used in combination with SetCredentials to set a specific password for a
    51  	// database user and preserve the password in WAL entries.
    52  	GenerateCredentials(ctx context.Context) (string, error)
    53  
    54  	// SetCredentials uses provided information to create or set the credentials
    55  	// for a database user. Unlike CreateUser, this method requires both a
    56  	// username and a password given instead of generating them. This is used for
    57  	// creating and setting the password of static accounts, as well as rolling
    58  	// back passwords in the database in the event an updated database fails to
    59  	// save in Vault's storage.
    60  	SetCredentials(ctx context.Context, statements Statements, staticConfig StaticUserConfig) (username string, password string, err error)
    61  
    62  	// Init is called on `$ vault write database/config/:db-name`, or when you
    63  	// do a creds call after Vault's been restarted. The config provided won't
    64  	// hold all the keys and values provided in the API call, some will be
    65  	// stripped by the database engine before the config is provided. The config
    66  	// returned will be stored, which will persist it across shutdowns.
    67  	Init(ctx context.Context, config map[string]interface{}, verifyConnection bool) (saveConfig map[string]interface{}, err error)
    68  
    69  	// Close attempts to close the underlying database connection that was
    70  	// established by the backend.
    71  	Close() error
    72  }
    73  
    74  // PluginFactory is used to build plugin database types. It wraps the database
    75  // object in a logging and metrics middleware.
    76  func PluginFactory(ctx context.Context, pluginName string, pluginVersion string, sys pluginutil.LookRunnerUtil, logger log.Logger) (Database, error) {
    77  	return PluginFactoryVersion(ctx, pluginName, "", sys, logger)
    78  }
    79  
    80  // PluginFactory is used to build plugin database types with a version specified.
    81  // It wraps the database object in a logging and metrics middleware.
    82  func PluginFactoryVersion(ctx context.Context, pluginName string, pluginVersion string, sys pluginutil.LookRunnerUtil, logger log.Logger) (Database, error) {
    83  	// Look for plugin in the plugin catalog
    84  	pluginRunner, err := sys.LookupPluginVersion(ctx, pluginName, consts.PluginTypeDatabase, pluginVersion)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	namedLogger := logger.Named(pluginName)
    90  
    91  	var transport string
    92  	var db Database
    93  	if pluginRunner.Builtin {
    94  		// Plugin is builtin so we can retrieve an instance of the interface
    95  		// from the pluginRunner. Then cast it to a Database.
    96  		dbRaw, err := pluginRunner.BuiltinFactory()
    97  		if err != nil {
    98  			return nil, errwrap.Wrapf("error initializing plugin: {{err}}", err)
    99  		}
   100  
   101  		var ok bool
   102  		db, ok = dbRaw.(Database)
   103  		if !ok {
   104  			return nil, fmt.Errorf("unsupported database type: %q", pluginName)
   105  		}
   106  
   107  		transport = "builtin"
   108  
   109  	} else {
   110  		// create a DatabasePluginClient instance
   111  		db, err = NewPluginClient(ctx, sys, pluginRunner, namedLogger, false)
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  
   116  		// Switch on the underlying database client type to get the transport
   117  		// method.
   118  		switch db.(*DatabasePluginClient).Database.(type) {
   119  		case *gRPCClient:
   120  			transport = "gRPC"
   121  		}
   122  
   123  	}
   124  
   125  	typeStr, err := db.Type()
   126  	if err != nil {
   127  		return nil, errwrap.Wrapf("error getting plugin type: {{err}}", err)
   128  	}
   129  
   130  	// Wrap with metrics middleware
   131  	db = &databaseMetricsMiddleware{
   132  		next:    db,
   133  		typeStr: typeStr,
   134  	}
   135  
   136  	// Wrap with tracing middleware
   137  	if namedLogger.IsTrace() {
   138  		db = &databaseTracingMiddleware{
   139  			next:   db,
   140  			logger: namedLogger.With("transport", transport),
   141  		}
   142  	}
   143  
   144  	return db, nil
   145  }
   146  
   147  // handshakeConfigs are used to just do a basic handshake between
   148  // a plugin and host. If the handshake fails, a user friendly error is shown.
   149  // This prevents users from executing bad plugins or executing a plugin
   150  // directory. It is a UX feature, not a security feature.
   151  var handshakeConfig = plugin.HandshakeConfig{
   152  	ProtocolVersion:  4,
   153  	MagicCookieKey:   "VAULT_DATABASE_PLUGIN",
   154  	MagicCookieValue: "926a0820-aea2-be28-51d6-83cdf00e8edb",
   155  }
   156  
   157  var (
   158  	_ plugin.Plugin     = &GRPCDatabasePlugin{}
   159  	_ plugin.GRPCPlugin = &GRPCDatabasePlugin{}
   160  )
   161  
   162  // GRPCDatabasePlugin is the plugin.Plugin implementation that only supports GRPC
   163  // transport
   164  type GRPCDatabasePlugin struct {
   165  	Impl Database
   166  
   167  	// Embeding this will disable the netRPC protocol
   168  	plugin.NetRPCUnsupportedPlugin
   169  }
   170  
   171  func (d GRPCDatabasePlugin) GRPCServer(_ *plugin.GRPCBroker, s *grpc.Server) error {
   172  	impl := &DatabaseErrorSanitizerMiddleware{
   173  		next: d.Impl,
   174  	}
   175  
   176  	RegisterDatabaseServer(s, &gRPCServer{impl: impl})
   177  	return nil
   178  }
   179  
   180  func (GRPCDatabasePlugin) GRPCClient(doneCtx context.Context, _ *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
   181  	return &gRPCClient{
   182  		client:     NewDatabaseClient(c),
   183  		clientConn: c,
   184  		doneCtx:    doneCtx,
   185  	}, nil
   186  }