Move plugin code into sub directory
This commit is contained in:
parent
2e23cf58b8
commit
62d59e5f4e
|
@ -4,50 +4,23 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
|
||||
"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
const databaseConfigPath = "database/dbs/"
|
||||
|
||||
// DatabaseType is the interface that all database objects must implement.
|
||||
type DatabaseType interface {
|
||||
Type() string
|
||||
CreateUser(statements Statements, username, password, expiration string) error
|
||||
RenewUser(statements Statements, username, expiration string) error
|
||||
RevokeUser(statements Statements, username string) error
|
||||
|
||||
Initialize(map[string]interface{}) error
|
||||
Close() error
|
||||
|
||||
GenerateUsername(displayName string) (string, error)
|
||||
GeneratePassword() (string, error)
|
||||
GenerateExpiration(ttl time.Duration) (string, error)
|
||||
}
|
||||
|
||||
// DatabaseConfig is used by the Factory function to configure a DatabaseType
|
||||
// object.
|
||||
type DatabaseConfig struct {
|
||||
PluginName string `json:"plugin_name" structs:"plugin_name" mapstructure:"plugin_name"`
|
||||
// ConnectionDetails stores the database specific connection settings needed
|
||||
// by each database type.
|
||||
ConnectionDetails map[string]interface{} `json:"connection_details" structs:"connection_details" mapstructure:"connection_details"`
|
||||
MaxOpenConnections int `json:"max_open_connections" structs:"max_open_connections" mapstructure:"max_open_connections"`
|
||||
MaxIdleConnections int `json:"max_idle_connections" structs:"max_idle_connections" mapstructure:"max_idle_connections"`
|
||||
MaxConnectionLifetime time.Duration `json:"max_connection_lifetime" structs:"max_connection_lifetime" mapstructure:"max_connection_lifetime"`
|
||||
}
|
||||
|
||||
// Statements set in role creation and passed into the database type's functions.
|
||||
// TODO: Add a way of setting defaults here.
|
||||
type Statements struct {
|
||||
CreationStatements string `json:"creation_statments" mapstructure:"creation_statements" structs:"creation_statments"`
|
||||
RevocationStatements string `json:"revocation_statements" mapstructure:"revocation_statements" structs:"revocation_statements"`
|
||||
RollbackStatements string `json:"rollback_statements" mapstructure:"rollback_statements" structs:"rollback_statements"`
|
||||
RenewStatements string `json:"renew_statements" mapstructure:"renew_statements" structs:"renew_statements"`
|
||||
ConnectionDetails map[string]interface{} `json:"connection_details" structs:"connection_details" mapstructure:"connection_details"`
|
||||
}
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
|
@ -83,12 +56,12 @@ func Backend(conf *logical.BackendConfig) *databaseBackend {
|
|||
}
|
||||
|
||||
b.logger = conf.Logger
|
||||
b.connections = make(map[string]DatabaseType)
|
||||
b.connections = make(map[string]dbplugin.DatabaseType)
|
||||
return &b
|
||||
}
|
||||
|
||||
type databaseBackend struct {
|
||||
connections map[string]DatabaseType
|
||||
connections map[string]dbplugin.DatabaseType
|
||||
logger log.Logger
|
||||
|
||||
*framework.Backend
|
||||
|
@ -108,7 +81,7 @@ func (b *databaseBackend) closeAllDBs() {
|
|||
// This function is used to retrieve a database object either from the cached
|
||||
// connection map or by using the database config in storage. The caller of this
|
||||
// function needs to hold the backend's lock.
|
||||
func (b *databaseBackend) getOrCreateDBObj(s logical.Storage, name string) (DatabaseType, error) {
|
||||
func (b *databaseBackend) getOrCreateDBObj(s logical.Storage, name string) (dbplugin.DatabaseType, error) {
|
||||
// if the object already is built and cached, return it
|
||||
db, ok := b.connections[name]
|
||||
if ok {
|
||||
|
@ -128,7 +101,7 @@ func (b *databaseBackend) getOrCreateDBObj(s logical.Storage, name string) (Data
|
|||
return nil, err
|
||||
}
|
||||
|
||||
db, err = PluginFactory(&config, b.System(), b.logger)
|
||||
db, err = dbplugin.PluginFactory(config.PluginName, b.System(), b.logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
package dbplugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/rpc"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
)
|
||||
|
||||
// DatabasePluginClient embeds a databasePluginRPCClient and wraps it's close
|
||||
// method to also call Kill() on the plugin.Client.
|
||||
type DatabasePluginClient struct {
|
||||
client *plugin.Client
|
||||
sync.Mutex
|
||||
|
||||
*databasePluginRPCClient
|
||||
}
|
||||
|
||||
func (dc *DatabasePluginClient) Close() error {
|
||||
err := dc.databasePluginRPCClient.Close()
|
||||
dc.client.Kill()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// newPluginClient returns a databaseRPCClient with a connection to a running
|
||||
// plugin. The client is wrapped in a DatabasePluginClient object to ensure the
|
||||
// plugin is killed on call of Close().
|
||||
func newPluginClient(sys pluginutil.Wrapper, pluginRunner *pluginutil.PluginRunner) (DatabaseType, error) {
|
||||
// pluginMap is the map of plugins we can dispense.
|
||||
var pluginMap = map[string]plugin.Plugin{
|
||||
"database": new(DatabasePlugin),
|
||||
}
|
||||
|
||||
client, err := pluginRunner.Run(sys, pluginMap, handshakeConfig, []string{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Connect via RPC
|
||||
rpcClient, err := client.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Request the plugin
|
||||
raw, err := rpcClient.Dispense("database")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We should have a Greeter now! This feels like a normal interface
|
||||
// implementation but is in fact over an RPC connection.
|
||||
databaseRPC := raw.(*databasePluginRPCClient)
|
||||
|
||||
return &DatabasePluginClient{
|
||||
client: client,
|
||||
databasePluginRPCClient: databaseRPC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ---- RPC client domain ----
|
||||
|
||||
// databasePluginRPCClient impliments DatabaseType and is used on the client to
|
||||
// make RPC calls to a plugin.
|
||||
type databasePluginRPCClient struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) Type() string {
|
||||
var dbType string
|
||||
//TODO: catch error
|
||||
dr.client.Call("Plugin.Type", struct{}{}, &dbType)
|
||||
|
||||
return fmt.Sprintf("plugin-%s", dbType)
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) CreateUser(statements Statements, username, password, expiration string) error {
|
||||
req := CreateUserRequest{
|
||||
Statements: statements,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Expiration: expiration,
|
||||
}
|
||||
|
||||
err := dr.client.Call("Plugin.CreateUser", req, &struct{}{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) RenewUser(statements Statements, username, expiration string) error {
|
||||
req := RenewUserRequest{
|
||||
Statements: statements,
|
||||
Username: username,
|
||||
Expiration: expiration,
|
||||
}
|
||||
|
||||
err := dr.client.Call("Plugin.RenewUser", req, &struct{}{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) RevokeUser(statements Statements, username string) error {
|
||||
req := RevokeUserRequest{
|
||||
Statements: statements,
|
||||
Username: username,
|
||||
}
|
||||
|
||||
err := dr.client.Call("Plugin.RevokeUser", req, &struct{}{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) Initialize(conf map[string]interface{}) error {
|
||||
err := dr.client.Call("Plugin.Initialize", conf, &struct{}{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) Close() error {
|
||||
err := dr.client.Call("Plugin.Close", struct{}{}, &struct{}{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) GenerateUsername(displayName string) (string, error) {
|
||||
resp := &GenerateUsernameResponse{}
|
||||
err := dr.client.Call("Plugin.GenerateUsername", displayName, resp)
|
||||
|
||||
return resp.Username, err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) GeneratePassword() (string, error) {
|
||||
resp := &GeneratePasswordResponse{}
|
||||
err := dr.client.Call("Plugin.GeneratePassword", struct{}{}, resp)
|
||||
|
||||
return resp.Password, err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) GenerateExpiration(duration time.Duration) (string, error) {
|
||||
resp := &GenerateExpirationResponse{}
|
||||
err := dr.client.Call("Plugin.GenerateExpiration", duration, resp)
|
||||
|
||||
return resp.Expiration, err
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package database
|
||||
package dbplugin
|
||||
|
||||
import (
|
||||
"time"
|
|
@ -0,0 +1,126 @@
|
|||
package dbplugin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/rpc"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyPluginName = errors.New("empty plugin name")
|
||||
)
|
||||
|
||||
// DatabaseType is the interface that all database objects must implement.
|
||||
type DatabaseType interface {
|
||||
Type() string
|
||||
CreateUser(statements Statements, username, password, expiration string) error
|
||||
RenewUser(statements Statements, username, expiration string) error
|
||||
RevokeUser(statements Statements, username string) error
|
||||
|
||||
Initialize(map[string]interface{}) error
|
||||
Close() error
|
||||
|
||||
GenerateUsername(displayName string) (string, error)
|
||||
GeneratePassword() (string, error)
|
||||
GenerateExpiration(ttl time.Duration) (string, error)
|
||||
}
|
||||
|
||||
// Statements set in role creation and passed into the database type's functions.
|
||||
// TODO: Add a way of setting defaults here.
|
||||
type Statements struct {
|
||||
CreationStatements string `json:"creation_statments" mapstructure:"creation_statements" structs:"creation_statments"`
|
||||
RevocationStatements string `json:"revocation_statements" mapstructure:"revocation_statements" structs:"revocation_statements"`
|
||||
RollbackStatements string `json:"rollback_statements" mapstructure:"rollback_statements" structs:"rollback_statements"`
|
||||
RenewStatements string `json:"renew_statements" mapstructure:"renew_statements" structs:"renew_statements"`
|
||||
}
|
||||
|
||||
// PluginFactory is used to build plugin database types. It wraps the database
|
||||
// object in a logging and metrics middleware.
|
||||
func PluginFactory(pluginName string, sys pluginutil.LookWrapper, logger log.Logger) (DatabaseType, error) {
|
||||
if pluginName == "" {
|
||||
return nil, ErrEmptyPluginName
|
||||
}
|
||||
|
||||
pluginMeta, err := sys.LookupPlugin(pluginName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := newPluginClient(sys, pluginMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wrap with metrics middleware
|
||||
db = &databaseMetricsMiddleware{
|
||||
next: db,
|
||||
typeStr: db.Type(),
|
||||
}
|
||||
|
||||
// Wrap with tracing middleware
|
||||
db = &databaseTracingMiddleware{
|
||||
next: db,
|
||||
typeStr: db.Type(),
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// handshakeConfigs are used to just do a basic handshake between
|
||||
// a plugin and host. If the handshake fails, a user friendly error is shown.
|
||||
// This prevents users from executing bad plugins or executing a plugin
|
||||
// directory. It is a UX feature, not a security feature.
|
||||
var handshakeConfig = plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
MagicCookieKey: "VAULT_DATABASE_PLUGIN",
|
||||
MagicCookieValue: "926a0820-aea2-be28-51d6-83cdf00e8edb",
|
||||
}
|
||||
|
||||
type DatabasePlugin struct {
|
||||
impl DatabaseType
|
||||
}
|
||||
|
||||
func (d DatabasePlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
||||
return &databasePluginRPCServer{impl: d.impl}, nil
|
||||
}
|
||||
|
||||
func (DatabasePlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||
return &databasePluginRPCClient{client: c}, nil
|
||||
}
|
||||
|
||||
// ---- RPC Request Args Domain ----
|
||||
|
||||
type CreateUserRequest struct {
|
||||
Statements Statements
|
||||
Username string
|
||||
Password string
|
||||
Expiration string
|
||||
}
|
||||
|
||||
type RenewUserRequest struct {
|
||||
Statements Statements
|
||||
Username string
|
||||
Expiration string
|
||||
}
|
||||
|
||||
type RevokeUserRequest struct {
|
||||
Statements Statements
|
||||
Username string
|
||||
}
|
||||
|
||||
// ---- RPC Response Args Domain ----
|
||||
|
||||
type GenerateUsernameResponse struct {
|
||||
Username string
|
||||
}
|
||||
type GenerateExpirationResponse struct {
|
||||
Expiration string
|
||||
}
|
||||
type GeneratePasswordResponse struct {
|
||||
Password string
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package database
|
||||
package dbplugin
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
|
@ -0,0 +1,90 @@
|
|||
package dbplugin
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
)
|
||||
|
||||
// NewPluginServer is called from within a plugin and wraps the provided
|
||||
// DatabaseType implimentation in a databasePluginRPCServer object and starts a
|
||||
// RPC server.
|
||||
func NewPluginServer(db DatabaseType) {
|
||||
dbPlugin := &DatabasePlugin{
|
||||
impl: db,
|
||||
}
|
||||
|
||||
// pluginMap is the map of plugins we can dispense.
|
||||
var pluginMap = map[string]plugin.Plugin{
|
||||
"database": dbPlugin,
|
||||
}
|
||||
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: handshakeConfig,
|
||||
Plugins: pluginMap,
|
||||
TLSProvider: pluginutil.VaultPluginTLSProvider,
|
||||
})
|
||||
}
|
||||
|
||||
// ---- RPC server domain ----
|
||||
|
||||
// databasePluginRPCServer impliments DatabaseType and is run inside a plugin
|
||||
type databasePluginRPCServer struct {
|
||||
impl DatabaseType
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) Type(_ struct{}, resp *string) error {
|
||||
*resp = ds.impl.Type()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) CreateUser(args *CreateUserRequest, _ *struct{}) error {
|
||||
err := ds.impl.CreateUser(args.Statements, args.Username, args.Password, args.Expiration)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) RenewUser(args *RenewUserRequest, _ *struct{}) error {
|
||||
err := ds.impl.RenewUser(args.Statements, args.Username, args.Expiration)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) RevokeUser(args *RevokeUserRequest, _ *struct{}) error {
|
||||
err := ds.impl.RevokeUser(args.Statements, args.Username)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) Initialize(args map[string]interface{}, _ *struct{}) error {
|
||||
err := ds.impl.Initialize(args)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) Close(_ struct{}, _ *struct{}) error {
|
||||
ds.impl.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) GenerateUsername(args string, resp *GenerateUsernameResponse) error {
|
||||
var err error
|
||||
resp.Username, err = ds.impl.GenerateUsername(args)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) GeneratePassword(_ struct{}, resp *GeneratePasswordResponse) error {
|
||||
var err error
|
||||
resp.Password, err = ds.impl.GeneratePassword()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) GenerateExpiration(args time.Duration, resp *GenerateExpirationResponse) error {
|
||||
var err error
|
||||
resp.Expiration, err = ds.impl.GenerateExpiration(args)
|
||||
|
||||
return err
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
@ -163,7 +164,7 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
|
|||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
db, err := PluginFactory(config, b.System(), b.logger)
|
||||
db, err := dbplugin.PluginFactory(config.PluginName, b.System(), b.logger)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
@ -155,7 +156,7 @@ func (b *databaseBackend) pathRoleCreate(req *logical.Request, data *framework.F
|
|||
"Invalid max_ttl: %s", err)), nil
|
||||
}
|
||||
|
||||
statements := Statements{
|
||||
statements := dbplugin.Statements{
|
||||
CreationStatements: creationStmts,
|
||||
RevocationStatements: revocationStmts,
|
||||
RollbackStatements: rollbackStmts,
|
||||
|
@ -182,10 +183,10 @@ func (b *databaseBackend) pathRoleCreate(req *logical.Request, data *framework.F
|
|||
}
|
||||
|
||||
type roleEntry struct {
|
||||
DBName string `json:"db_name" mapstructure:"db_name" structs:"db_name"`
|
||||
Statements Statements `json:"statments" mapstructure:"statements" structs:"statments"`
|
||||
DefaultTTL time.Duration `json:"default_ttl" mapstructure:"default_ttl" structs:"default_ttl"`
|
||||
MaxTTL time.Duration `json:"max_ttl" mapstructure:"max_ttl" structs:"max_ttl"`
|
||||
DBName string `json:"db_name" mapstructure:"db_name" structs:"db_name"`
|
||||
Statements dbplugin.Statements `json:"statments" mapstructure:"statements" structs:"statments"`
|
||||
DefaultTTL time.Duration `json:"default_ttl" mapstructure:"default_ttl" structs:"default_ttl"`
|
||||
MaxTTL time.Duration `json:"max_ttl" mapstructure:"max_ttl" structs:"max_ttl"`
|
||||
}
|
||||
|
||||
const pathRoleHelpSyn = `
|
||||
|
|
|
@ -1,324 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/rpc"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyPluginName = errors.New("empty plugin name")
|
||||
)
|
||||
|
||||
// PluginFactory is used to build plugin database types. It wraps the database
|
||||
// object in a logging and metrics middleware.
|
||||
func PluginFactory(conf *DatabaseConfig, sys logical.SystemView, logger log.Logger) (DatabaseType, error) {
|
||||
if conf.PluginName == "" {
|
||||
return nil, ErrEmptyPluginName
|
||||
}
|
||||
|
||||
pluginMeta, err := sys.LookupPlugin(conf.PluginName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := newPluginClient(sys, pluginMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wrap with metrics middleware
|
||||
db = &databaseMetricsMiddleware{
|
||||
next: db,
|
||||
typeStr: db.Type(),
|
||||
}
|
||||
|
||||
// Wrap with tracing middleware
|
||||
db = &databaseTracingMiddleware{
|
||||
next: db,
|
||||
typeStr: db.Type(),
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// handshakeConfigs are used to just do a basic handshake between
|
||||
// a plugin and host. If the handshake fails, a user friendly error is shown.
|
||||
// This prevents users from executing bad plugins or executing a plugin
|
||||
// directory. It is a UX feature, not a security feature.
|
||||
var handshakeConfig = plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
MagicCookieKey: "VAULT_DATABASE_PLUGIN",
|
||||
MagicCookieValue: "926a0820-aea2-be28-51d6-83cdf00e8edb",
|
||||
}
|
||||
|
||||
type DatabasePlugin struct {
|
||||
impl DatabaseType
|
||||
}
|
||||
|
||||
func (d DatabasePlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
||||
return &databasePluginRPCServer{impl: d.impl}, nil
|
||||
}
|
||||
|
||||
func (DatabasePlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||
return &databasePluginRPCClient{client: c}, nil
|
||||
}
|
||||
|
||||
// DatabasePluginClient embeds a databasePluginRPCClient and wraps it's close
|
||||
// method to also call Kill() on the plugin.Client.
|
||||
type DatabasePluginClient struct {
|
||||
client *plugin.Client
|
||||
sync.Mutex
|
||||
|
||||
*databasePluginRPCClient
|
||||
}
|
||||
|
||||
func (dc *DatabasePluginClient) Close() error {
|
||||
err := dc.databasePluginRPCClient.Close()
|
||||
dc.client.Kill()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// newPluginClient returns a databaseRPCClient with a connection to a running
|
||||
// plugin. The client is wrapped in a DatabasePluginClient object to ensure the
|
||||
// plugin is killed on call of Close().
|
||||
func newPluginClient(sys pluginutil.Wrapper, pluginRunner *pluginutil.PluginRunner) (DatabaseType, error) {
|
||||
// pluginMap is the map of plugins we can dispense.
|
||||
var pluginMap = map[string]plugin.Plugin{
|
||||
"database": new(DatabasePlugin),
|
||||
}
|
||||
|
||||
client, err := pluginRunner.Run(sys, pluginMap, handshakeConfig, []string{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Connect via RPC
|
||||
rpcClient, err := client.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Request the plugin
|
||||
raw, err := rpcClient.Dispense("database")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We should have a Greeter now! This feels like a normal interface
|
||||
// implementation but is in fact over an RPC connection.
|
||||
databaseRPC := raw.(*databasePluginRPCClient)
|
||||
|
||||
return &DatabasePluginClient{
|
||||
client: client,
|
||||
databasePluginRPCClient: databaseRPC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewPluginServer is called from within a plugin and wraps the provided
|
||||
// DatabaseType implimentation in a databasePluginRPCServer object and starts a
|
||||
// RPC server.
|
||||
func NewPluginServer(db DatabaseType) {
|
||||
dbPlugin := &DatabasePlugin{
|
||||
impl: db,
|
||||
}
|
||||
|
||||
// pluginMap is the map of plugins we can dispense.
|
||||
var pluginMap = map[string]plugin.Plugin{
|
||||
"database": dbPlugin,
|
||||
}
|
||||
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: handshakeConfig,
|
||||
Plugins: pluginMap,
|
||||
TLSProvider: pluginutil.VaultPluginTLSProvider,
|
||||
})
|
||||
}
|
||||
|
||||
// ---- RPC client domain ----
|
||||
|
||||
// databasePluginRPCClient impliments DatabaseType and is used on the client to
|
||||
// make RPC calls to a plugin.
|
||||
type databasePluginRPCClient struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) Type() string {
|
||||
var dbType string
|
||||
//TODO: catch error
|
||||
dr.client.Call("Plugin.Type", struct{}{}, &dbType)
|
||||
|
||||
return fmt.Sprintf("plugin-%s", dbType)
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) CreateUser(statements Statements, username, password, expiration string) error {
|
||||
req := CreateUserRequest{
|
||||
Statements: statements,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Expiration: expiration,
|
||||
}
|
||||
|
||||
err := dr.client.Call("Plugin.CreateUser", req, &struct{}{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) RenewUser(statements Statements, username, expiration string) error {
|
||||
req := RenewUserRequest{
|
||||
Statements: statements,
|
||||
Username: username,
|
||||
Expiration: expiration,
|
||||
}
|
||||
|
||||
err := dr.client.Call("Plugin.RenewUser", req, &struct{}{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) RevokeUser(statements Statements, username string) error {
|
||||
req := RevokeUserRequest{
|
||||
Statements: statements,
|
||||
Username: username,
|
||||
}
|
||||
|
||||
err := dr.client.Call("Plugin.RevokeUser", req, &struct{}{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) Initialize(conf map[string]interface{}) error {
|
||||
err := dr.client.Call("Plugin.Initialize", conf, &struct{}{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) Close() error {
|
||||
err := dr.client.Call("Plugin.Close", struct{}{}, &struct{}{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) GenerateUsername(displayName string) (string, error) {
|
||||
resp := &GenerateUsernameResponse{}
|
||||
err := dr.client.Call("Plugin.GenerateUsername", displayName, resp)
|
||||
|
||||
return resp.Username, err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) GeneratePassword() (string, error) {
|
||||
resp := &GeneratePasswordResponse{}
|
||||
err := dr.client.Call("Plugin.GeneratePassword", struct{}{}, resp)
|
||||
|
||||
return resp.Password, err
|
||||
}
|
||||
|
||||
func (dr *databasePluginRPCClient) GenerateExpiration(duration time.Duration) (string, error) {
|
||||
resp := &GenerateExpirationResponse{}
|
||||
err := dr.client.Call("Plugin.GenerateExpiration", duration, resp)
|
||||
|
||||
return resp.Expiration, err
|
||||
}
|
||||
|
||||
// ---- RPC server domain ----
|
||||
|
||||
// databasePluginRPCServer impliments DatabaseType and is run inside a plugin
|
||||
type databasePluginRPCServer struct {
|
||||
impl DatabaseType
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) Type(_ struct{}, resp *string) error {
|
||||
*resp = ds.impl.Type()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) CreateUser(args *CreateUserRequest, _ *struct{}) error {
|
||||
err := ds.impl.CreateUser(args.Statements, args.Username, args.Password, args.Expiration)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) RenewUser(args *RenewUserRequest, _ *struct{}) error {
|
||||
err := ds.impl.RenewUser(args.Statements, args.Username, args.Expiration)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) RevokeUser(args *RevokeUserRequest, _ *struct{}) error {
|
||||
err := ds.impl.RevokeUser(args.Statements, args.Username)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) Initialize(args map[string]interface{}, _ *struct{}) error {
|
||||
err := ds.impl.Initialize(args)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) Close(_ struct{}, _ *struct{}) error {
|
||||
ds.impl.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) GenerateUsername(args string, resp *GenerateUsernameResponse) error {
|
||||
var err error
|
||||
resp.Username, err = ds.impl.GenerateUsername(args)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) GeneratePassword(_ struct{}, resp *GeneratePasswordResponse) error {
|
||||
var err error
|
||||
resp.Password, err = ds.impl.GeneratePassword()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds *databasePluginRPCServer) GenerateExpiration(args time.Duration, resp *GenerateExpirationResponse) error {
|
||||
var err error
|
||||
resp.Expiration, err = ds.impl.GenerateExpiration(args)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ---- Request Args Domain ----
|
||||
|
||||
type CreateUserRequest struct {
|
||||
Statements Statements
|
||||
Username string
|
||||
Password string
|
||||
Expiration string
|
||||
}
|
||||
|
||||
type RenewUserRequest struct {
|
||||
Statements Statements
|
||||
Username string
|
||||
Expiration string
|
||||
}
|
||||
|
||||
type RevokeUserRequest struct {
|
||||
Statements Statements
|
||||
Username string
|
||||
}
|
||||
|
||||
// ---- Response Args Domain ----
|
||||
|
||||
type GenerateUsernameResponse struct {
|
||||
Username string
|
||||
}
|
||||
type GenerateExpirationResponse struct {
|
||||
Expiration string
|
||||
}
|
||||
type GeneratePasswordResponse struct {
|
||||
Password string
|
||||
}
|
|
@ -12,6 +12,11 @@ type Looker interface {
|
|||
LookupPlugin(string) (*PluginRunner, error)
|
||||
}
|
||||
|
||||
type LookWrapper interface {
|
||||
Looker
|
||||
Wrapper
|
||||
}
|
||||
|
||||
type PluginRunner struct {
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
|
|
Loading…
Reference in New Issue