mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 11:38:02 +00:00
Backend plugin system (#2874)
* Add backend plugin changes * Fix totp backend plugin tests * Fix logical/plugin InvalidateKey test * Fix plugin catalog CRUD test, fix NoopBackend * Clean up commented code block * Fix system backend mount test * Set plugin_name to omitempty, fix handleMountTable config parsing * Clean up comments, keep shim connections alive until cleanup * Include pluginClient, disallow LookupPlugin call from within a plugin * Add wrapper around backendPluginClient for proper cleanup * Add logger shim tests * Add logger, storage, and system shim tests * Use pointer receivers for system view shim * Use plugin name if no path is provided on mount * Enable plugins for auth backends * Add backend type attribute, move builtin/plugin/package * Fix merge conflict * Fix missing plugin name in mount config * Add integration tests on enabling auth backend plugins * Remove dependency cycle on mock-plugin * Add passthrough backend plugin, use logical.BackendType to determine lease generation * Remove vault package dependency on passthrough package * Add basic impl test for passthrough plugin * Incorporate feedback; set b.backend after shims creation on backendPluginServer * Fix totp plugin test * Add plugin backends docs * Fix tests * Fix builtin/plugin tests * Remove flatten from PluginRunner fields * Move mock plugin to logical/plugin, remove totp and passthrough plugins * Move pluginMap into newPluginClient * Do not create storage RPC connection on HandleRequest and HandleExistenceCheck * Change shim logger's Fatal to no-op * Change BackendType to uint32, match UX backend types * Change framework.Backend Setup signature * Add Setup func to logical.Backend interface * Move OptionallyEnableMlock call into plugin.Serve, update docs and comments * Remove commented var in plugin package * RegisterLicense on logical.Backend interface (#3017) * Add RegisterLicense to logical.Backend interface * Update RegisterLicense to use callback func on framework.Backend * Refactor framework.Backend.RegisterLicense * plugin: Prevent plugin.SystemViewClient.ResponseWrapData from getting JWTs * plugin: Revert BackendType to remove TypePassthrough and related references * Fix typo in plugin backends docs
This commit is contained in:
committed by
GitHub
parent
987616895d
commit
2b0f80b981
23
logical/plugin/backend.go
Normal file
23
logical/plugin/backend.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"net/rpc"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
// BackendPlugin is the plugin.Plugin implementation
|
||||
type BackendPlugin struct {
|
||||
Factory func(*logical.BackendConfig) (logical.Backend, error)
|
||||
}
|
||||
|
||||
// Server gets called when on plugin.Serve()
|
||||
func (b *BackendPlugin) Server(broker *plugin.MuxBroker) (interface{}, error) {
|
||||
return &backendPluginServer{factory: b.Factory, broker: broker}, nil
|
||||
}
|
||||
|
||||
// Client gets called on plugin.NewClient()
|
||||
func (b BackendPlugin) Client(broker *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||
return &backendPluginClient{client: c, broker: broker}, nil
|
||||
}
|
||||
228
logical/plugin/backend_client.go
Normal file
228
logical/plugin/backend_client.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"net/rpc"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
)
|
||||
|
||||
// backendPluginClient implements logical.Backend and is the
|
||||
// go-plugin client.
|
||||
type backendPluginClient struct {
|
||||
broker *plugin.MuxBroker
|
||||
client *rpc.Client
|
||||
pluginClient *plugin.Client
|
||||
|
||||
system logical.SystemView
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// HandleRequestArgs is the args for HandleRequest method.
|
||||
type HandleRequestArgs struct {
|
||||
StorageID uint32
|
||||
Request *logical.Request
|
||||
}
|
||||
|
||||
// HandleRequestReply is the reply for HandleRequest method.
|
||||
type HandleRequestReply struct {
|
||||
Response *logical.Response
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
|
||||
// SpecialPathsReply is the reply for SpecialPaths method.
|
||||
type SpecialPathsReply struct {
|
||||
Paths *logical.Paths
|
||||
}
|
||||
|
||||
// SystemReply is the reply for System method.
|
||||
type SystemReply struct {
|
||||
SystemView logical.SystemView
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
|
||||
// HandleExistenceCheckArgs is the args for HandleExistenceCheck method.
|
||||
type HandleExistenceCheckArgs struct {
|
||||
StorageID uint32
|
||||
Request *logical.Request
|
||||
}
|
||||
|
||||
// HandleExistenceCheckReply is the reply for HandleExistenceCheck method.
|
||||
type HandleExistenceCheckReply struct {
|
||||
CheckFound bool
|
||||
Exists bool
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
|
||||
// SetupArgs is the args for Setup method.
|
||||
type SetupArgs struct {
|
||||
StorageID uint32
|
||||
LoggerID uint32
|
||||
SysViewID uint32
|
||||
Config map[string]string
|
||||
}
|
||||
|
||||
// SetupReply is the reply for Setup method.
|
||||
type SetupReply struct {
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
|
||||
// TypeReply is the reply for the Type method.
|
||||
type TypeReply struct {
|
||||
Type logical.BackendType
|
||||
}
|
||||
|
||||
// RegisterLicenseArgs is the args for the RegisterLicense method.
|
||||
type RegisterLicenseArgs struct {
|
||||
License interface{}
|
||||
}
|
||||
|
||||
// RegisterLicenseReply is the reply for the RegisterLicense method.
|
||||
type RegisterLicenseReply struct {
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
|
||||
func (b *backendPluginClient) HandleRequest(req *logical.Request) (*logical.Response, error) {
|
||||
args := &HandleRequestArgs{
|
||||
Request: req,
|
||||
}
|
||||
var reply HandleRequestReply
|
||||
|
||||
err := b.client.Call("Plugin.HandleRequest", args, &reply)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
if reply.Error.Error() == logical.ErrUnsupportedOperation.Error() {
|
||||
return nil, logical.ErrUnsupportedOperation
|
||||
}
|
||||
return nil, reply.Error
|
||||
}
|
||||
|
||||
return reply.Response, nil
|
||||
}
|
||||
|
||||
func (b *backendPluginClient) SpecialPaths() *logical.Paths {
|
||||
var reply SpecialPathsReply
|
||||
err := b.client.Call("Plugin.SpecialPaths", new(interface{}), &reply)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return reply.Paths
|
||||
}
|
||||
|
||||
// System returns vault's system view. The backend client stores the view during
|
||||
// Setup, so there is no need to shim the system just to get it back.
|
||||
func (b *backendPluginClient) System() logical.SystemView {
|
||||
return b.system
|
||||
}
|
||||
|
||||
// Logger returns vault's logger. The backend client stores the logger during
|
||||
// Setup, so there is no need to shim the logger just to get it back.
|
||||
func (b *backendPluginClient) Logger() log.Logger {
|
||||
return b.logger
|
||||
}
|
||||
|
||||
func (b *backendPluginClient) HandleExistenceCheck(req *logical.Request) (bool, bool, error) {
|
||||
args := &HandleExistenceCheckArgs{
|
||||
Request: req,
|
||||
}
|
||||
var reply HandleExistenceCheckReply
|
||||
|
||||
err := b.client.Call("Plugin.HandleExistenceCheck", args, &reply)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
// THINKING: Should be be a switch on all error types?
|
||||
if reply.Error.Error() == logical.ErrUnsupportedPath.Error() {
|
||||
return false, false, logical.ErrUnsupportedPath
|
||||
}
|
||||
return false, false, reply.Error
|
||||
}
|
||||
|
||||
return reply.CheckFound, reply.Exists, nil
|
||||
}
|
||||
|
||||
func (b *backendPluginClient) Cleanup() {
|
||||
b.client.Call("Plugin.Cleanup", new(interface{}), &struct{}{})
|
||||
}
|
||||
|
||||
func (b *backendPluginClient) Initialize() error {
|
||||
err := b.client.Call("Plugin.Initialize", new(interface{}), &struct{}{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *backendPluginClient) InvalidateKey(key string) {
|
||||
b.client.Call("Plugin.InvalidateKey", key, &struct{}{})
|
||||
}
|
||||
|
||||
func (b *backendPluginClient) Setup(config *logical.BackendConfig) error {
|
||||
// Shim logical.Storage
|
||||
storageID := b.broker.NextId()
|
||||
go b.broker.AcceptAndServe(storageID, &StorageServer{
|
||||
impl: config.StorageView,
|
||||
})
|
||||
|
||||
// Shim log.Logger
|
||||
loggerID := b.broker.NextId()
|
||||
go b.broker.AcceptAndServe(loggerID, &LoggerServer{
|
||||
logger: config.Logger,
|
||||
})
|
||||
|
||||
// Shim logical.SystemView
|
||||
sysViewID := b.broker.NextId()
|
||||
go b.broker.AcceptAndServe(sysViewID, &SystemViewServer{
|
||||
impl: config.System,
|
||||
})
|
||||
|
||||
args := &SetupArgs{
|
||||
StorageID: storageID,
|
||||
LoggerID: loggerID,
|
||||
SysViewID: sysViewID,
|
||||
Config: config.Config,
|
||||
}
|
||||
var reply SetupReply
|
||||
|
||||
err := b.client.Call("Plugin.Setup", args, &reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
return reply.Error
|
||||
}
|
||||
|
||||
// Set system and logger for getter methods
|
||||
b.system = config.System
|
||||
b.logger = config.Logger
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backendPluginClient) Type() logical.BackendType {
|
||||
var reply TypeReply
|
||||
err := b.client.Call("Plugin.Type", new(interface{}), &reply)
|
||||
if err != nil {
|
||||
return logical.TypeUnknown
|
||||
}
|
||||
|
||||
return logical.BackendType(reply.Type)
|
||||
}
|
||||
|
||||
func (b *backendPluginClient) RegisterLicense(license interface{}) error {
|
||||
var reply RegisterLicenseReply
|
||||
args := RegisterLicenseArgs{
|
||||
License: license,
|
||||
}
|
||||
err := b.client.Call("Plugin.RegisterLicense", args, &reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
return reply.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
156
logical/plugin/backend_server.go
Normal file
156
logical/plugin/backend_server.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"net/rpc"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
// backendPluginServer is the RPC server that backendPluginClient talks to,
|
||||
// it methods conforming to requirements by net/rpc
|
||||
type backendPluginServer struct {
|
||||
broker *plugin.MuxBroker
|
||||
backend logical.Backend
|
||||
factory func(*logical.BackendConfig) (logical.Backend, error)
|
||||
|
||||
loggerClient *rpc.Client
|
||||
sysViewClient *rpc.Client
|
||||
storageClient *rpc.Client
|
||||
}
|
||||
|
||||
func (b *backendPluginServer) HandleRequest(args *HandleRequestArgs, reply *HandleRequestReply) error {
|
||||
storage := &StorageClient{client: b.storageClient}
|
||||
args.Request.Storage = storage
|
||||
|
||||
resp, err := b.backend.HandleRequest(args.Request)
|
||||
*reply = HandleRequestReply{
|
||||
Response: resp,
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backendPluginServer) SpecialPaths(_ interface{}, reply *SpecialPathsReply) error {
|
||||
*reply = SpecialPathsReply{
|
||||
Paths: b.backend.SpecialPaths(),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backendPluginServer) HandleExistenceCheck(args *HandleExistenceCheckArgs, reply *HandleExistenceCheckReply) error {
|
||||
storage := &StorageClient{client: b.storageClient}
|
||||
args.Request.Storage = storage
|
||||
|
||||
checkFound, exists, err := b.backend.HandleExistenceCheck(args.Request)
|
||||
*reply = HandleExistenceCheckReply{
|
||||
CheckFound: checkFound,
|
||||
Exists: exists,
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backendPluginServer) Cleanup(_ interface{}, _ *struct{}) error {
|
||||
b.backend.Cleanup()
|
||||
|
||||
// Close rpc clients
|
||||
b.loggerClient.Close()
|
||||
b.sysViewClient.Close()
|
||||
b.storageClient.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backendPluginServer) Initialize(_ interface{}, _ *struct{}) error {
|
||||
err := b.backend.Initialize()
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *backendPluginServer) InvalidateKey(args string, _ *struct{}) error {
|
||||
b.backend.InvalidateKey(args)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setup dials into the plugin's broker to get a shimmed storage, logger, and
|
||||
// system view of the backend. This method also instantiates the underlying
|
||||
// backend through its factory func for the server side of the plugin.
|
||||
func (b *backendPluginServer) Setup(args *SetupArgs, reply *SetupReply) error {
|
||||
// Dial for storage
|
||||
storageConn, err := b.broker.Dial(args.StorageID)
|
||||
if err != nil {
|
||||
*reply = SetupReply{
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
rawStorageClient := rpc.NewClient(storageConn)
|
||||
b.storageClient = rawStorageClient
|
||||
|
||||
storage := &StorageClient{client: rawStorageClient}
|
||||
|
||||
// Dial for logger
|
||||
loggerConn, err := b.broker.Dial(args.LoggerID)
|
||||
if err != nil {
|
||||
*reply = SetupReply{
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
rawLoggerClient := rpc.NewClient(loggerConn)
|
||||
b.loggerClient = rawLoggerClient
|
||||
|
||||
logger := &LoggerClient{client: rawLoggerClient}
|
||||
|
||||
// Dial for sys view
|
||||
sysViewConn, err := b.broker.Dial(args.SysViewID)
|
||||
if err != nil {
|
||||
*reply = SetupReply{
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
rawSysViewClient := rpc.NewClient(sysViewConn)
|
||||
b.sysViewClient = rawSysViewClient
|
||||
|
||||
sysView := &SystemViewClient{client: rawSysViewClient}
|
||||
|
||||
config := &logical.BackendConfig{
|
||||
StorageView: storage,
|
||||
Logger: logger,
|
||||
System: sysView,
|
||||
Config: args.Config,
|
||||
}
|
||||
|
||||
// Call the underlying backend factory after shims have been created
|
||||
// to set b.backend
|
||||
backend, err := b.factory(config)
|
||||
if err != nil {
|
||||
*reply = SetupReply{
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
}
|
||||
b.backend = backend
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backendPluginServer) Type(_ interface{}, reply *TypeReply) error {
|
||||
*reply = TypeReply{
|
||||
Type: b.backend.Type(),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backendPluginServer) RegisterLicense(args *RegisterLicenseArgs, reply *RegisterLicenseReply) error {
|
||||
err := b.backend.RegisterLicense(args.License)
|
||||
if err != nil {
|
||||
*reply = RegisterLicenseReply{
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
176
logical/plugin/backend_test.go
Normal file
176
logical/plugin/backend_test.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gplugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/helper/logformat"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/plugin/mock"
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
)
|
||||
|
||||
func TestBackendPlugin_impl(t *testing.T) {
|
||||
var _ gplugin.Plugin = new(BackendPlugin)
|
||||
var _ logical.Backend = new(backendPluginClient)
|
||||
}
|
||||
|
||||
func TestBackendPlugin_HandleRequest(t *testing.T) {
|
||||
b, cleanup := testBackend(t)
|
||||
defer cleanup()
|
||||
|
||||
resp, err := b.HandleRequest(&logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "test/ing",
|
||||
Data: map[string]interface{}{"value": "foo"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Data["value"] != "foo" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendPlugin_SpecialPaths(t *testing.T) {
|
||||
b, cleanup := testBackend(t)
|
||||
defer cleanup()
|
||||
|
||||
paths := b.SpecialPaths()
|
||||
if paths == nil {
|
||||
t.Fatal("SpecialPaths() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendPlugin_System(t *testing.T) {
|
||||
b, cleanup := testBackend(t)
|
||||
defer cleanup()
|
||||
|
||||
sys := b.System()
|
||||
if sys == nil {
|
||||
t.Fatal("System() returned nil")
|
||||
}
|
||||
|
||||
actual := sys.DefaultLeaseTTL()
|
||||
expected := 300 * time.Second
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %v, expected %v", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendPlugin_Logger(t *testing.T) {
|
||||
b, cleanup := testBackend(t)
|
||||
defer cleanup()
|
||||
|
||||
logger := b.Logger()
|
||||
if logger == nil {
|
||||
t.Fatal("Logger() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendPlugin_HandleExistenceCheck(t *testing.T) {
|
||||
b, cleanup := testBackend(t)
|
||||
defer cleanup()
|
||||
|
||||
checkFound, exists, err := b.HandleExistenceCheck(&logical.Request{
|
||||
Operation: logical.CreateOperation,
|
||||
Path: "test/ing",
|
||||
Data: map[string]interface{}{"value": "foo"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !checkFound {
|
||||
t.Fatal("existence check not found for path 'test/ing'")
|
||||
}
|
||||
if exists {
|
||||
t.Fatal("existence check should have returned 'false' for 'testing/read'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendPlugin_Cleanup(t *testing.T) {
|
||||
b, cleanup := testBackend(t)
|
||||
defer cleanup()
|
||||
|
||||
b.Cleanup()
|
||||
}
|
||||
|
||||
func TestBackendPlugin_Initialize(t *testing.T) {
|
||||
b, cleanup := testBackend(t)
|
||||
defer cleanup()
|
||||
|
||||
err := b.Initialize()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendPlugin_InvalidateKey(t *testing.T) {
|
||||
b, cleanup := testBackend(t)
|
||||
defer cleanup()
|
||||
|
||||
resp, err := b.HandleRequest(&logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "internal",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Data["value"] == "" {
|
||||
t.Fatalf("bad: %#v, expected non-empty value", resp)
|
||||
}
|
||||
|
||||
b.InvalidateKey("internal")
|
||||
|
||||
resp, err = b.HandleRequest(&logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "internal",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Data["value"] != "" {
|
||||
t.Fatalf("bad: expected empty response data, got %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendPlugin_Setup(t *testing.T) {
|
||||
_, cleanup := testBackend(t)
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
func testBackend(t *testing.T) (logical.Backend, func()) {
|
||||
// Create a mock provider
|
||||
pluginMap := map[string]gplugin.Plugin{
|
||||
"backend": &BackendPlugin{
|
||||
Factory: mock.Factory,
|
||||
},
|
||||
}
|
||||
client, _ := gplugin.TestPluginRPCConn(t, pluginMap)
|
||||
cleanup := func() {
|
||||
client.Close()
|
||||
}
|
||||
|
||||
// Request the backend
|
||||
raw, err := client.Dispense(BackendPluginName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := raw.(logical.Backend)
|
||||
|
||||
err = b.Setup(&logical.BackendConfig{
|
||||
Logger: logformat.NewVaultLogger(log.LevelTrace),
|
||||
System: &logical.StaticSystemView{
|
||||
DefaultLeaseTTLVal: 300 * time.Second,
|
||||
MaxLeaseTTLVal: 1800 * time.Second,
|
||||
},
|
||||
StorageView: &logical.InmemStorage{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return b, cleanup
|
||||
}
|
||||
205
logical/plugin/logger.go
Normal file
205
logical/plugin/logger.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"net/rpc"
|
||||
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
)
|
||||
|
||||
type LoggerClient struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func (l *LoggerClient) Trace(msg string, args ...interface{}) {
|
||||
cArgs := &LoggerArgs{
|
||||
Msg: msg,
|
||||
Args: args,
|
||||
}
|
||||
l.client.Call("Plugin.Trace", cArgs, &struct{}{})
|
||||
}
|
||||
|
||||
func (l *LoggerClient) Debug(msg string, args ...interface{}) {
|
||||
cArgs := &LoggerArgs{
|
||||
Msg: msg,
|
||||
Args: args,
|
||||
}
|
||||
l.client.Call("Plugin.Debug", cArgs, &struct{}{})
|
||||
}
|
||||
|
||||
func (l *LoggerClient) Info(msg string, args ...interface{}) {
|
||||
cArgs := &LoggerArgs{
|
||||
Msg: msg,
|
||||
Args: args,
|
||||
}
|
||||
l.client.Call("Plugin.Info", cArgs, &struct{}{})
|
||||
}
|
||||
func (l *LoggerClient) Warn(msg string, args ...interface{}) error {
|
||||
var reply LoggerReply
|
||||
cArgs := &LoggerArgs{
|
||||
Msg: msg,
|
||||
Args: args,
|
||||
}
|
||||
err := l.client.Call("Plugin.Warn", cArgs, &reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
return reply.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (l *LoggerClient) Error(msg string, args ...interface{}) error {
|
||||
var reply LoggerReply
|
||||
cArgs := &LoggerArgs{
|
||||
Msg: msg,
|
||||
Args: args,
|
||||
}
|
||||
err := l.client.Call("Plugin.Error", cArgs, &reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
return reply.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerClient) Fatal(msg string, args ...interface{}) {
|
||||
// NOOP since it's not actually used within vault
|
||||
return
|
||||
}
|
||||
|
||||
func (l *LoggerClient) Log(level int, msg string, args []interface{}) {
|
||||
cArgs := &LoggerArgs{
|
||||
Level: level,
|
||||
Msg: msg,
|
||||
Args: args,
|
||||
}
|
||||
l.client.Call("Plugin.Log", cArgs, &struct{}{})
|
||||
}
|
||||
|
||||
func (l *LoggerClient) SetLevel(level int) {
|
||||
l.client.Call("Plugin.SetLevel", level, &struct{}{})
|
||||
}
|
||||
|
||||
func (l *LoggerClient) IsTrace() bool {
|
||||
var reply LoggerReply
|
||||
l.client.Call("Plugin.IsTrace", new(interface{}), &reply)
|
||||
return reply.IsTrue
|
||||
}
|
||||
func (l *LoggerClient) IsDebug() bool {
|
||||
var reply LoggerReply
|
||||
l.client.Call("Plugin.IsDebug", new(interface{}), &reply)
|
||||
return reply.IsTrue
|
||||
}
|
||||
|
||||
func (l *LoggerClient) IsInfo() bool {
|
||||
var reply LoggerReply
|
||||
l.client.Call("Plugin.IsInfo", new(interface{}), &reply)
|
||||
return reply.IsTrue
|
||||
}
|
||||
|
||||
func (l *LoggerClient) IsWarn() bool {
|
||||
var reply LoggerReply
|
||||
l.client.Call("Plugin.IsWarn", new(interface{}), &reply)
|
||||
return reply.IsTrue
|
||||
}
|
||||
|
||||
type LoggerServer struct {
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func (l *LoggerServer) Trace(args *LoggerArgs, _ *struct{}) error {
|
||||
l.logger.Trace(args.Msg, args.Args)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerServer) Debug(args *LoggerArgs, _ *struct{}) error {
|
||||
l.logger.Debug(args.Msg, args.Args)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerServer) Info(args *LoggerArgs, _ *struct{}) error {
|
||||
l.logger.Info(args.Msg, args.Args)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerServer) Warn(args *LoggerArgs, reply *LoggerReply) error {
|
||||
err := l.logger.Warn(args.Msg, args.Args)
|
||||
if err != nil {
|
||||
*reply = LoggerReply{
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerServer) Error(args *LoggerArgs, reply *LoggerReply) error {
|
||||
err := l.logger.Error(args.Msg, args.Args)
|
||||
if err != nil {
|
||||
*reply = LoggerReply{
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerServer) Log(args *LoggerArgs, _ *struct{}) error {
|
||||
l.logger.Log(args.Level, args.Msg, args.Args)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerServer) SetLevel(args int, _ *struct{}) error {
|
||||
l.logger.SetLevel(args)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerServer) IsTrace(args interface{}, reply *LoggerReply) error {
|
||||
result := l.logger.IsTrace()
|
||||
*reply = LoggerReply{
|
||||
IsTrue: result,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerServer) IsDebug(args interface{}, reply *LoggerReply) error {
|
||||
result := l.logger.IsDebug()
|
||||
*reply = LoggerReply{
|
||||
IsTrue: result,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerServer) IsInfo(args interface{}, reply *LoggerReply) error {
|
||||
result := l.logger.IsInfo()
|
||||
*reply = LoggerReply{
|
||||
IsTrue: result,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LoggerServer) IsWarn(args interface{}, reply *LoggerReply) error {
|
||||
result := l.logger.IsWarn()
|
||||
*reply = LoggerReply{
|
||||
IsTrue: result,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LoggerArgs struct {
|
||||
Level int
|
||||
Msg string
|
||||
Args []interface{}
|
||||
}
|
||||
|
||||
// LoggerReply contains the RPC reply. Not all fields may be used
|
||||
// for a particular RPC call.
|
||||
type LoggerReply struct {
|
||||
IsTrue bool
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
163
logical/plugin/logger_test.go
Normal file
163
logical/plugin/logger_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/helper/logformat"
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
)
|
||||
|
||||
func TestLogger_impl(t *testing.T) {
|
||||
var _ log.Logger = new(LoggerClient)
|
||||
}
|
||||
|
||||
func TestLogger_levels(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := bufio.NewWriter(&buf)
|
||||
|
||||
l := logformat.NewVaultLoggerWithWriter(writer, log.LevelTrace)
|
||||
|
||||
server.RegisterName("Plugin", &LoggerServer{
|
||||
logger: l,
|
||||
})
|
||||
|
||||
expected := "foobar"
|
||||
testLogger := &LoggerClient{client: client}
|
||||
|
||||
// Test trace
|
||||
testLogger.Trace(expected)
|
||||
if err := writer.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result := buf.String()
|
||||
buf.Reset()
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Fatalf("expected log to contain %s, got %s", expected, result)
|
||||
}
|
||||
|
||||
// Test debug
|
||||
testLogger.Debug(expected)
|
||||
if err := writer.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result = buf.String()
|
||||
buf.Reset()
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Fatalf("expected log to contain %s, got %s", expected, result)
|
||||
}
|
||||
|
||||
// Test debug
|
||||
testLogger.Info(expected)
|
||||
if err := writer.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result = buf.String()
|
||||
buf.Reset()
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Fatalf("expected log to contain %s, got %s", expected, result)
|
||||
}
|
||||
|
||||
// Test warn
|
||||
testLogger.Warn(expected)
|
||||
if err := writer.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result = buf.String()
|
||||
buf.Reset()
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Fatalf("expected log to contain %s, got %s", expected, result)
|
||||
}
|
||||
|
||||
// Test error
|
||||
testLogger.Error(expected)
|
||||
if err := writer.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result = buf.String()
|
||||
buf.Reset()
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Fatalf("expected log to contain %s, got %s", expected, result)
|
||||
}
|
||||
|
||||
// Test fatal
|
||||
testLogger.Fatal(expected)
|
||||
if err := writer.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result = buf.String()
|
||||
buf.Reset()
|
||||
if result != "" {
|
||||
t.Fatalf("expected log Fatal() to be no-op, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_isLevels(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
l := logformat.NewVaultLoggerWithWriter(ioutil.Discard, log.LevelAll)
|
||||
|
||||
server.RegisterName("Plugin", &LoggerServer{
|
||||
logger: l,
|
||||
})
|
||||
|
||||
testLogger := &LoggerClient{client: client}
|
||||
|
||||
if !testLogger.IsDebug() || !testLogger.IsInfo() || !testLogger.IsTrace() || !testLogger.IsWarn() {
|
||||
t.Fatal("expected logger to return true for all logger level checks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_log(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := bufio.NewWriter(&buf)
|
||||
|
||||
l := logformat.NewVaultLoggerWithWriter(writer, log.LevelTrace)
|
||||
|
||||
server.RegisterName("Plugin", &LoggerServer{
|
||||
logger: l,
|
||||
})
|
||||
|
||||
expected := "foobar"
|
||||
testLogger := &LoggerClient{client: client}
|
||||
|
||||
// Test trace
|
||||
testLogger.Log(log.LevelInfo, expected, nil)
|
||||
if err := writer.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result := buf.String()
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Fatalf("expected log to contain %s, got %s", expected, result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLogger_setLevel(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
l := log.NewLogger(ioutil.Discard, "test-logger")
|
||||
|
||||
server.RegisterName("Plugin", &LoggerServer{
|
||||
logger: l,
|
||||
})
|
||||
|
||||
testLogger := &LoggerClient{client: client}
|
||||
testLogger.SetLevel(log.LevelWarn)
|
||||
|
||||
if !testLogger.IsWarn() {
|
||||
t.Fatal("expected logger to support warn level")
|
||||
}
|
||||
}
|
||||
69
logical/plugin/mock/backend.go
Normal file
69
logical/plugin/mock/backend.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
// New returns a new backend as an interface. This func
|
||||
// is only necessary for builtin backend plugins.
|
||||
func New() (interface{}, error) {
|
||||
return Backend(), nil
|
||||
}
|
||||
|
||||
// Factory returns a new backend as logical.Backend.
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// FactoryType is a wrapper func that allows the Factory func to specify
|
||||
// the backend type for the mock backend plugin instance.
|
||||
func FactoryType(backendType logical.BackendType) func(*logical.BackendConfig) (logical.Backend, error) {
|
||||
return func(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
b := Backend()
|
||||
b.BackendType = backendType
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Backend returns a private embedded struct of framework.Backend.
|
||||
func Backend() *backend {
|
||||
var b backend
|
||||
b.Backend = &framework.Backend{
|
||||
Help: "",
|
||||
Paths: []*framework.Path{
|
||||
pathTesting(&b),
|
||||
pathInternal(&b),
|
||||
},
|
||||
PathsSpecial: &logical.Paths{
|
||||
Unauthenticated: []string{
|
||||
"special",
|
||||
},
|
||||
},
|
||||
Secrets: []*framework.Secret{},
|
||||
Invalidate: b.invalidate,
|
||||
}
|
||||
b.internal = "bar"
|
||||
return &b
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
*framework.Backend
|
||||
|
||||
// internal is used to test invalidate
|
||||
internal string
|
||||
}
|
||||
|
||||
func (b *backend) invalidate(key string) {
|
||||
switch key {
|
||||
case "internal":
|
||||
b.internal = ""
|
||||
}
|
||||
}
|
||||
11
logical/plugin/mock/backend_test.go
Normal file
11
logical/plugin/mock/backend_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func TestMockBackend_impl(t *testing.T) {
|
||||
var _ logical.Backend = new(backend)
|
||||
}
|
||||
28
logical/plugin/mock/mock-plugin/main.go
Normal file
28
logical/plugin/mock/mock-plugin/main.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
"github.com/hashicorp/vault/logical/plugin"
|
||||
"github.com/hashicorp/vault/logical/plugin/mock"
|
||||
)
|
||||
|
||||
func main() {
|
||||
apiClientMeta := &pluginutil.APIClientMeta{}
|
||||
flags := apiClientMeta.FlagSet()
|
||||
flags.Parse(os.Args)
|
||||
|
||||
tlsConfig := apiClientMeta.GetTLSConfig()
|
||||
tlsProviderFunc := pluginutil.VaultPluginTLSProvider(tlsConfig)
|
||||
|
||||
err := plugin.Serve(&plugin.ServeOpts{
|
||||
BackendFactoryFunc: mock.Factory,
|
||||
TLSProviderFunc: tlsProviderFunc,
|
||||
})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
28
logical/plugin/mock/path_internal.go
Normal file
28
logical/plugin/mock/path_internal.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
func pathInternal(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "internal",
|
||||
Fields: map[string]*framework.FieldSchema{},
|
||||
ExistenceCheck: b.pathTestingExistenceCheck,
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathTestingReadInternal,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathTestingReadInternal(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
// Return the secret
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"value": b.internal,
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
56
logical/plugin/mock/path_testing.go
Normal file
56
logical/plugin/mock/path_testing.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
func pathTesting(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "test/ing",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"value": &framework.FieldSchema{Type: framework.TypeString},
|
||||
},
|
||||
ExistenceCheck: b.pathTestingExistenceCheck,
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathTestingRead,
|
||||
logical.CreateOperation: b.pathTestingCreate,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathTestingRead(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
// Return the secret
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"value": data.Get("value"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathTestingCreate(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
val := data.Get("value").(string)
|
||||
|
||||
entry := &logical.StorageEntry{
|
||||
Key: "test/ing",
|
||||
Value: []byte(val),
|
||||
}
|
||||
|
||||
s := req.Storage
|
||||
err := s.Put(entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"value": data.Get("value"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathTestingExistenceCheck(req *logical.Request, data *framework.FieldData) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
96
logical/plugin/plugin.go
Normal file
96
logical/plugin/plugin.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
// BackendPluginClient is a wrapper around backendPluginClient
|
||||
// that also contains its plugin.Client instance. It's primarily
|
||||
// used to cleanly kill the client on Cleanup()
|
||||
type BackendPluginClient struct {
|
||||
client *plugin.Client
|
||||
sync.Mutex
|
||||
|
||||
*backendPluginClient
|
||||
}
|
||||
|
||||
// Cleanup calls the RPC client's Cleanup() func and also calls
|
||||
// the go-plugin's client Kill() func
|
||||
func (b *BackendPluginClient) Cleanup() {
|
||||
b.backendPluginClient.Cleanup()
|
||||
b.client.Kill()
|
||||
}
|
||||
|
||||
// NewBackend will return an instance of an RPC-based client implementation of the backend for
|
||||
// external plugins, or a concrete implementation of the backend if it is a builtin backend.
|
||||
// The backend is returned as a logical.Backend interface.
|
||||
func NewBackend(pluginName string, sys pluginutil.LookRunnerUtil) (logical.Backend, error) {
|
||||
// Look for plugin in the plugin catalog
|
||||
pluginRunner, err := sys.LookupPlugin(pluginName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var backend logical.Backend
|
||||
if pluginRunner.Builtin {
|
||||
// Plugin is builtin so we can retrieve an instance of the interface
|
||||
// from the pluginRunner. Then cast it to logical.Backend.
|
||||
backendRaw, err := pluginRunner.BuiltinFactory()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting plugin type: %s", err)
|
||||
}
|
||||
|
||||
var ok bool
|
||||
backend, ok = backendRaw.(logical.Backend)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsuported backend type: %s", pluginName)
|
||||
}
|
||||
|
||||
} else {
|
||||
// create a backendPluginClient instance
|
||||
backend, err = newPluginClient(sys, pluginRunner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func newPluginClient(sys pluginutil.RunnerUtil, pluginRunner *pluginutil.PluginRunner) (logical.Backend, error) {
|
||||
// pluginMap is the map of plugins we can dispense.
|
||||
pluginMap := map[string]plugin.Plugin{
|
||||
"backend": &BackendPlugin{},
|
||||
}
|
||||
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("backend")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We should have a logical backend type now. This feels like a normal interface
|
||||
// implementation but is in fact over an RPC connection.
|
||||
backendRPC := raw.(*backendPluginClient)
|
||||
|
||||
return &BackendPluginClient{
|
||||
client: client,
|
||||
backendPluginClient: backendRPC,
|
||||
}, nil
|
||||
}
|
||||
54
logical/plugin/serve.go
Normal file
54
logical/plugin/serve.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
// BackendPluginName is the name of the plugin that can be
|
||||
// dispensed rom the plugin server.
|
||||
const BackendPluginName = "backend"
|
||||
|
||||
type BackendFactoryFunc func(*logical.BackendConfig) (logical.Backend, error)
|
||||
type TLSProdiverFunc func() (*tls.Config, error)
|
||||
|
||||
type ServeOpts struct {
|
||||
BackendFactoryFunc BackendFactoryFunc
|
||||
TLSProviderFunc TLSProdiverFunc
|
||||
}
|
||||
|
||||
// Serve is used to serve a backend plugin
|
||||
func Serve(opts *ServeOpts) error {
|
||||
// pluginMap is the map of plugins we can dispense.
|
||||
var pluginMap = map[string]plugin.Plugin{
|
||||
"backend": &BackendPlugin{
|
||||
Factory: opts.BackendFactoryFunc,
|
||||
},
|
||||
}
|
||||
|
||||
err := pluginutil.OptionallyEnableMlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: handshakeConfig,
|
||||
Plugins: pluginMap,
|
||||
TLSProvider: opts.TLSProviderFunc,
|
||||
})
|
||||
|
||||
return 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_BACKEND_PLUGIN",
|
||||
MagicCookieValue: "6669da05-b1c8-4f49-97d9-c8e5bed98e20",
|
||||
}
|
||||
119
logical/plugin/storage.go
Normal file
119
logical/plugin/storage.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"net/rpc"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
// StorageClient is an implementation of logical.Storage that communicates
|
||||
// over RPC.
|
||||
type StorageClient struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func (s *StorageClient) List(prefix string) ([]string, error) {
|
||||
var reply StorageListReply
|
||||
err := s.client.Call("Plugin.List", prefix, &reply)
|
||||
if err != nil {
|
||||
return reply.Keys, err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
return reply.Keys, reply.Error
|
||||
}
|
||||
return reply.Keys, nil
|
||||
}
|
||||
|
||||
func (s *StorageClient) Get(key string) (*logical.StorageEntry, error) {
|
||||
var reply StorageGetReply
|
||||
err := s.client.Call("Plugin.Get", key, &reply)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
return nil, reply.Error
|
||||
}
|
||||
return reply.StorageEntry, nil
|
||||
}
|
||||
|
||||
func (s *StorageClient) Put(entry *logical.StorageEntry) error {
|
||||
var reply StoragePutReply
|
||||
err := s.client.Call("Plugin.Put", entry, &reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
return reply.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StorageClient) Delete(key string) error {
|
||||
var reply StorageDeleteReply
|
||||
err := s.client.Call("Plugin.Delete", key, &reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
return reply.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StorageServer is a net/rpc compatible structure for serving
|
||||
type StorageServer struct {
|
||||
impl logical.Storage
|
||||
}
|
||||
|
||||
func (s *StorageServer) List(prefix string, reply *StorageListReply) error {
|
||||
keys, err := s.impl.List(prefix)
|
||||
*reply = StorageListReply{
|
||||
Keys: keys,
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StorageServer) Get(key string, reply *StorageGetReply) error {
|
||||
storageEntry, err := s.impl.Get(key)
|
||||
*reply = StorageGetReply{
|
||||
StorageEntry: storageEntry,
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StorageServer) Put(entry *logical.StorageEntry, reply *StoragePutReply) error {
|
||||
err := s.impl.Put(entry)
|
||||
*reply = StoragePutReply{
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StorageServer) Delete(key string, reply *StorageDeleteReply) error {
|
||||
err := s.impl.Delete(key)
|
||||
*reply = StorageDeleteReply{
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type StorageListReply struct {
|
||||
Keys []string
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
|
||||
type StorageGetReply struct {
|
||||
StorageEntry *logical.StorageEntry
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
|
||||
type StoragePutReply struct {
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
|
||||
type StorageDeleteReply struct {
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
27
logical/plugin/storage_test.go
Normal file
27
logical/plugin/storage_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func TestStorage_impl(t *testing.T) {
|
||||
var _ logical.Storage = new(StorageClient)
|
||||
}
|
||||
|
||||
func TestStorage_operations(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
storage := &logical.InmemStorage{}
|
||||
|
||||
server.RegisterName("Plugin", &StorageServer{
|
||||
impl: storage,
|
||||
})
|
||||
|
||||
testStorage := &StorageClient{client: client}
|
||||
|
||||
logical.TestStorage(t, testStorage)
|
||||
}
|
||||
247
logical/plugin/system.go
Normal file
247
logical/plugin/system.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"net/rpc"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/helper/consts"
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
"github.com/hashicorp/vault/helper/wrapping"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
type SystemViewClient struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func (s *SystemViewClient) DefaultLeaseTTL() time.Duration {
|
||||
var reply DefaultLeaseTTLReply
|
||||
err := s.client.Call("Plugin.DefaultLeaseTTL", new(interface{}), &reply)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return reply.DefaultLeaseTTL
|
||||
}
|
||||
|
||||
func (s *SystemViewClient) MaxLeaseTTL() time.Duration {
|
||||
var reply MaxLeaseTTLReply
|
||||
err := s.client.Call("Plugin.MaxLeaseTTL", new(interface{}), &reply)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return reply.MaxLeaseTTL
|
||||
}
|
||||
|
||||
func (s *SystemViewClient) SudoPrivilege(path string, token string) bool {
|
||||
var reply SudoPrivilegeReply
|
||||
args := &SudoPrivilegeArgs{
|
||||
Path: path,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
err := s.client.Call("Plugin.SudoPrivilege", args, &reply)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return reply.Sudo
|
||||
}
|
||||
|
||||
func (s *SystemViewClient) Tainted() bool {
|
||||
var reply TaintedReply
|
||||
|
||||
err := s.client.Call("Plugin.Tainted", new(interface{}), &reply)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return reply.Tainted
|
||||
}
|
||||
|
||||
func (s *SystemViewClient) CachingDisabled() bool {
|
||||
var reply CachingDisabledReply
|
||||
|
||||
err := s.client.Call("Plugin.CachingDisabled", new(interface{}), &reply)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return reply.CachingDisabled
|
||||
}
|
||||
|
||||
func (s *SystemViewClient) ReplicationState() consts.ReplicationState {
|
||||
var reply ReplicationStateReply
|
||||
|
||||
err := s.client.Call("Plugin.ReplicationState", new(interface{}), &reply)
|
||||
if err != nil {
|
||||
return consts.ReplicationDisabled
|
||||
}
|
||||
|
||||
return reply.ReplicationState
|
||||
}
|
||||
|
||||
func (s *SystemViewClient) ResponseWrapData(data map[string]interface{}, ttl time.Duration, jwt bool) (*wrapping.ResponseWrapInfo, error) {
|
||||
var reply ResponseWrapDataReply
|
||||
// Do not allow JWTs to be returned
|
||||
args := &ResponseWrapDataArgs{
|
||||
Data: data,
|
||||
TTL: ttl,
|
||||
JWT: false,
|
||||
}
|
||||
|
||||
err := s.client.Call("Plugin.ResponseWrapData", args, &reply)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reply.Error != nil {
|
||||
return nil, reply.Error
|
||||
}
|
||||
|
||||
return reply.ResponseWrapInfo, nil
|
||||
}
|
||||
|
||||
func (s *SystemViewClient) LookupPlugin(name string) (*pluginutil.PluginRunner, error) {
|
||||
return nil, fmt.Errorf("cannot call LookupPlugin from a plugin backend")
|
||||
}
|
||||
|
||||
func (s *SystemViewClient) MlockEnabled() bool {
|
||||
var reply MlockEnabledReply
|
||||
err := s.client.Call("Plugin.MlockEnabled", new(interface{}), &reply)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return reply.MlockEnabled
|
||||
}
|
||||
|
||||
type SystemViewServer struct {
|
||||
impl logical.SystemView
|
||||
}
|
||||
|
||||
func (s *SystemViewServer) DefaultLeaseTTL(_ interface{}, reply *DefaultLeaseTTLReply) error {
|
||||
ttl := s.impl.DefaultLeaseTTL()
|
||||
*reply = DefaultLeaseTTLReply{
|
||||
DefaultLeaseTTL: ttl,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemViewServer) MaxLeaseTTL(_ interface{}, reply *MaxLeaseTTLReply) error {
|
||||
ttl := s.impl.MaxLeaseTTL()
|
||||
*reply = MaxLeaseTTLReply{
|
||||
MaxLeaseTTL: ttl,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemViewServer) SudoPrivilege(args *SudoPrivilegeArgs, reply *SudoPrivilegeReply) error {
|
||||
sudo := s.impl.SudoPrivilege(args.Path, args.Token)
|
||||
*reply = SudoPrivilegeReply{
|
||||
Sudo: sudo,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemViewServer) Tainted(_ interface{}, reply *TaintedReply) error {
|
||||
tainted := s.impl.Tainted()
|
||||
*reply = TaintedReply{
|
||||
Tainted: tainted,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemViewServer) CachingDisabled(_ interface{}, reply *CachingDisabledReply) error {
|
||||
cachingDisabled := s.impl.CachingDisabled()
|
||||
*reply = CachingDisabledReply{
|
||||
CachingDisabled: cachingDisabled,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemViewServer) ReplicationState(_ interface{}, reply *ReplicationStateReply) error {
|
||||
replicationState := s.impl.ReplicationState()
|
||||
*reply = ReplicationStateReply{
|
||||
ReplicationState: replicationState,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemViewServer) ResponseWrapData(args *ResponseWrapDataArgs, reply *ResponseWrapDataReply) error {
|
||||
// Do not allow JWTs to be returned
|
||||
info, err := s.impl.ResponseWrapData(args.Data, args.TTL, false)
|
||||
if err != nil {
|
||||
*reply = ResponseWrapDataReply{
|
||||
Error: plugin.NewBasicError(err),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*reply = ResponseWrapDataReply{
|
||||
ResponseWrapInfo: info,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemViewServer) MlockEnabled(_ interface{}, reply *MlockEnabledReply) error {
|
||||
enabled := s.impl.MlockEnabled()
|
||||
*reply = MlockEnabledReply{
|
||||
MlockEnabled: enabled,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type DefaultLeaseTTLReply struct {
|
||||
DefaultLeaseTTL time.Duration
|
||||
}
|
||||
|
||||
type MaxLeaseTTLReply struct {
|
||||
MaxLeaseTTL time.Duration
|
||||
}
|
||||
|
||||
type SudoPrivilegeArgs struct {
|
||||
Path string
|
||||
Token string
|
||||
}
|
||||
|
||||
type SudoPrivilegeReply struct {
|
||||
Sudo bool
|
||||
}
|
||||
|
||||
type TaintedReply struct {
|
||||
Tainted bool
|
||||
}
|
||||
|
||||
type CachingDisabledReply struct {
|
||||
CachingDisabled bool
|
||||
}
|
||||
|
||||
type ReplicationStateReply struct {
|
||||
ReplicationState consts.ReplicationState
|
||||
}
|
||||
|
||||
type ResponseWrapDataArgs struct {
|
||||
Data map[string]interface{}
|
||||
TTL time.Duration
|
||||
JWT bool
|
||||
}
|
||||
|
||||
type ResponseWrapDataReply struct {
|
||||
ResponseWrapInfo *wrapping.ResponseWrapInfo
|
||||
Error *plugin.BasicError
|
||||
}
|
||||
|
||||
type MlockEnabledReply struct {
|
||||
MlockEnabled bool
|
||||
}
|
||||
174
logical/plugin/system_test.go
Normal file
174
logical/plugin/system_test.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"reflect"
|
||||
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vault/helper/consts"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func Test_impl(t *testing.T) {
|
||||
var _ logical.SystemView = new(SystemViewClient)
|
||||
}
|
||||
|
||||
func TestSystem_defaultLeaseTTL(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
sys := logical.TestSystemView()
|
||||
|
||||
server.RegisterName("Plugin", &SystemViewServer{
|
||||
impl: sys,
|
||||
})
|
||||
|
||||
testSystemView := &SystemViewClient{client: client}
|
||||
|
||||
expected := sys.DefaultLeaseTTL()
|
||||
actual := testSystemView.DefaultLeaseTTL()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("expected: %v, got: %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystem_maxLeaseTTL(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
sys := logical.TestSystemView()
|
||||
|
||||
server.RegisterName("Plugin", &SystemViewServer{
|
||||
impl: sys,
|
||||
})
|
||||
|
||||
testSystemView := &SystemViewClient{client: client}
|
||||
|
||||
expected := sys.MaxLeaseTTL()
|
||||
actual := testSystemView.MaxLeaseTTL()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("expected: %v, got: %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystem_sudoPrivilege(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
sys := logical.TestSystemView()
|
||||
sys.SudoPrivilegeVal = true
|
||||
|
||||
server.RegisterName("Plugin", &SystemViewServer{
|
||||
impl: sys,
|
||||
})
|
||||
|
||||
testSystemView := &SystemViewClient{client: client}
|
||||
|
||||
expected := sys.SudoPrivilege("foo", "bar")
|
||||
actual := testSystemView.SudoPrivilege("foo", "bar")
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("expected: %v, got: %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystem_tainted(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
sys := logical.TestSystemView()
|
||||
sys.TaintedVal = true
|
||||
|
||||
server.RegisterName("Plugin", &SystemViewServer{
|
||||
impl: sys,
|
||||
})
|
||||
|
||||
testSystemView := &SystemViewClient{client: client}
|
||||
|
||||
expected := sys.Tainted()
|
||||
actual := testSystemView.Tainted()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("expected: %v, got: %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystem_cachingDisabled(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
sys := logical.TestSystemView()
|
||||
sys.CachingDisabledVal = true
|
||||
|
||||
server.RegisterName("Plugin", &SystemViewServer{
|
||||
impl: sys,
|
||||
})
|
||||
|
||||
testSystemView := &SystemViewClient{client: client}
|
||||
|
||||
expected := sys.CachingDisabled()
|
||||
actual := testSystemView.CachingDisabled()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("expected: %v, got: %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystem_replicationState(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
sys := logical.TestSystemView()
|
||||
sys.ReplicationStateVal = consts.ReplicationPrimary
|
||||
|
||||
server.RegisterName("Plugin", &SystemViewServer{
|
||||
impl: sys,
|
||||
})
|
||||
|
||||
testSystemView := &SystemViewClient{client: client}
|
||||
|
||||
expected := sys.ReplicationState()
|
||||
actual := testSystemView.ReplicationState()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("expected: %v, got: %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystem_responseWrapData(t *testing.T) {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
func TestSystem_lookupPlugin(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
sys := logical.TestSystemView()
|
||||
|
||||
server.RegisterName("Plugin", &SystemViewServer{
|
||||
impl: sys,
|
||||
})
|
||||
|
||||
testSystemView := &SystemViewClient{client: client}
|
||||
|
||||
if _, err := testSystemView.LookupPlugin("foo"); err == nil {
|
||||
t.Fatal("LookPlugin(): expected error on due to unsupported call from plugin")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystem_mlockEnabled(t *testing.T) {
|
||||
client, server := plugin.TestRPCConn(t)
|
||||
defer client.Close()
|
||||
|
||||
sys := logical.TestSystemView()
|
||||
sys.EnableMlock = true
|
||||
|
||||
server.RegisterName("Plugin", &SystemViewServer{
|
||||
impl: sys,
|
||||
})
|
||||
|
||||
testSystemView := &SystemViewClient{client: client}
|
||||
|
||||
expected := sys.MlockEnabled()
|
||||
actual := testSystemView.MlockEnabled()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("expected: %v, got: %v", expected, actual)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user