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 }