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:
parent
987616895d
commit
2b0f80b981
@ -85,6 +85,7 @@ type EnableAuthOptions struct {
|
||||
Type string `json:"type" structs:"type"`
|
||||
Description string `json:"description" structs:"description"`
|
||||
Local bool `json:"local" structs:"local"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
}
|
||||
|
||||
type AuthMount struct {
|
||||
@ -96,6 +97,7 @@ type AuthMount struct {
|
||||
}
|
||||
|
||||
type AuthConfigOutput struct {
|
||||
DefaultLeaseTTL int `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL int `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
DefaultLeaseTTL int `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL int `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ type MountConfigInput struct {
|
||||
DefaultLeaseTTL string `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL string `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
}
|
||||
|
||||
type MountOutput struct {
|
||||
@ -141,7 +142,8 @@ type MountOutput struct {
|
||||
}
|
||||
|
||||
type MountConfigOutput struct {
|
||||
DefaultLeaseTTL int `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL int `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
DefaultLeaseTTL int `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL int `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
}
|
||||
|
@ -13,10 +13,13 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.Backend.Setup(conf)
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend(conf *logical.BackendConfig) (backend, error) {
|
||||
func Backend(conf *logical.BackendConfig) (*backend, error) {
|
||||
var b backend
|
||||
b.MapAppId = &framework.PolicyMap{
|
||||
PathMap: framework.PathMap{
|
||||
@ -78,7 +81,7 @@ func Backend(conf *logical.BackendConfig) (backend, error) {
|
||||
b.MapAppId.SaltFunc = b.Salt
|
||||
b.MapUserId.SaltFunc = b.Salt
|
||||
|
||||
return b, nil
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestBackend_basic(t *testing.T) {
|
||||
var b backend
|
||||
var b *backend
|
||||
var err error
|
||||
var storage logical.Storage
|
||||
factory := func(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
@ -18,7 +18,10 @@ func TestBackend_basic(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
storage = conf.StorageView
|
||||
return b.Setup(conf)
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Factory: factory,
|
||||
|
@ -54,7 +54,10 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.Setup(conf)
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend(conf *logical.BackendConfig) (*backend, error) {
|
||||
|
@ -17,7 +17,7 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) {
|
||||
if b == nil {
|
||||
t.Fatalf("failed to create backend")
|
||||
}
|
||||
_, err = b.Backend.Setup(config)
|
||||
err = b.Backend.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -17,7 +17,10 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.Setup(conf)
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
|
@ -29,7 +29,7 @@ func TestBackend_CreateParseVerifyRoleTag(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -253,7 +253,7 @@ func TestBackend_ConfigTidyIdentities(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -307,7 +307,7 @@ func TestBackend_ConfigTidyRoleTags(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -361,7 +361,7 @@ func TestBackend_TidyIdentities(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -386,7 +386,7 @@ func TestBackend_TidyRoleTags(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -411,7 +411,7 @@ func TestBackend_ConfigClient(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -548,7 +548,7 @@ func TestBackend_pathConfigCertificate(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -703,7 +703,7 @@ func TestBackend_parseAndVerifyRoleTagValue(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -784,7 +784,7 @@ func TestBackend_PathRoleTag(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -849,7 +849,7 @@ func TestBackend_PathBlacklistRoleTag(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -997,7 +997,7 @@ func TestBackendAcc_LoginWithInstanceIdentityDocAndWhitelistIdentity(t *testing.
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -1177,7 +1177,7 @@ func TestBackend_pathStsConfig(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -1325,7 +1325,7 @@ func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ func TestBackend_pathConfigClient(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ func TestBackend_pathRoleEc2(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -146,7 +146,7 @@ func Test_enableIamIDResolution(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -221,7 +221,7 @@ func TestBackend_pathIam(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -385,7 +385,7 @@ func TestBackend_pathRoleMixedTypes(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -491,7 +491,7 @@ func TestAwsEc2_RoleCrud(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -617,7 +617,7 @@ func TestAwsEc2_RoleDurationSeconds(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -10,9 +10,8 @@ import (
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
b := Backend()
|
||||
_, err := b.Setup(conf)
|
||||
if err != nil {
|
||||
return b, err
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
@ -11,7 +11,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *backend {
|
||||
|
@ -13,7 +13,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *backend {
|
||||
|
@ -21,7 +21,7 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) {
|
||||
t.Fatalf("failed to create backend")
|
||||
}
|
||||
|
||||
_, err := b.Backend.Setup(config)
|
||||
err := b.Backend.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -8,7 +8,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *backend {
|
||||
|
@ -7,7 +7,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *backend {
|
||||
|
@ -7,7 +7,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *backend {
|
||||
|
@ -9,7 +9,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *backend {
|
||||
|
@ -14,7 +14,7 @@ func TestBackend_PathListRoles(t *testing.T) {
|
||||
config.StorageView = &logical.InmemStorage{}
|
||||
|
||||
b := Backend()
|
||||
if _, err := b.Setup(config); err != nil {
|
||||
if err := b.Setup(config); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,11 @@ import (
|
||||
|
||||
// Factory creates a new backend
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Backend contains the base information for the backend's functionality
|
||||
|
@ -6,7 +6,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *backend {
|
||||
|
@ -16,7 +16,11 @@ import (
|
||||
const databaseConfigPath = "database/config/"
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend(conf).Setup(conf)
|
||||
b := Backend(conf)
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend(conf *logical.BackendConfig) *databaseBackend {
|
||||
|
@ -12,7 +12,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *framework.Backend {
|
||||
|
@ -12,7 +12,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *backend {
|
||||
|
@ -12,7 +12,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *backend {
|
||||
|
@ -11,7 +11,11 @@ import (
|
||||
|
||||
// Factory creates a new backend implementing the logical.Backend interface
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Backend returns a new Backend framework struct
|
||||
|
@ -1870,7 +1870,7 @@ func TestBackend_PathFetchCertList(t *testing.T) {
|
||||
config.StorageView = storage
|
||||
|
||||
b := Backend()
|
||||
_, err := b.Setup(config)
|
||||
err := b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -1997,7 +1997,7 @@ func TestBackend_SignVerbatim(t *testing.T) {
|
||||
config.StorageView = storage
|
||||
|
||||
b := Backend()
|
||||
_, err := b.Setup(config)
|
||||
err := b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) {
|
||||
|
||||
var err error
|
||||
b := Backend()
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -13,7 +13,11 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend(conf).Setup(conf)
|
||||
b := Backend(conf)
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend(conf *logical.BackendConfig) *backend {
|
||||
|
@ -13,7 +13,11 @@ import (
|
||||
|
||||
// Factory creates and configures the backend
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend().Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Creates a new backend with all the paths and secrets belonging to it
|
||||
|
@ -13,7 +13,7 @@ func TestBackend_config_lease_RU(t *testing.T) {
|
||||
config := logical.TestBackendConfig()
|
||||
config.StorageView = &logical.InmemStorage{}
|
||||
b := Backend()
|
||||
if _, err = b.Setup(config); err != nil {
|
||||
if err = b.Setup(config); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,10 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.Setup(conf)
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend(conf *logical.BackendConfig) (*backend, error) {
|
||||
|
@ -106,7 +106,7 @@ func TestBackend_allowed_users(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ func TestSSH_ConfigCAStorageUpgrade(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.Setup(config)
|
||||
err = b.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -10,10 +10,14 @@ import (
|
||||
)
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return Backend(conf).Setup(conf)
|
||||
b := Backend()
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend(conf *logical.BackendConfig) *backend {
|
||||
func Backend() *backend {
|
||||
var b backend
|
||||
b.Backend = &framework.Backend{
|
||||
Help: strings.TrimSpace(backendHelp),
|
||||
|
@ -10,12 +10,10 @@ import (
|
||||
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
b := Backend(conf)
|
||||
be, err := b.Backend.Setup(conf)
|
||||
if err != nil {
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return be, nil
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend(conf *logical.BackendConfig) *backend {
|
||||
|
@ -31,7 +31,7 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) {
|
||||
if b == nil {
|
||||
t.Fatalf("failed to create backend")
|
||||
}
|
||||
_, err := b.Backend.Setup(config)
|
||||
err := b.Backend.Setup(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
39
builtin/plugin/backend.go
Normal file
39
builtin/plugin/backend.go
Normal file
@ -0,0 +1,39 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
bplugin "github.com/hashicorp/vault/logical/plugin"
|
||||
)
|
||||
|
||||
// Factory returns a configured plugin logical.Backend.
|
||||
func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
_, ok := conf.Config["plugin_name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("plugin_name not provided")
|
||||
}
|
||||
b, err := Backend(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := b.Setup(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Backend returns an instance of the backend, either as a plugin if external
|
||||
// or as a concrete implementation if builtin, casted as logical.Backend.
|
||||
func Backend(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
name := conf.Config["plugin_name"]
|
||||
sys := conf.System
|
||||
|
||||
b, err := bplugin.NewBackend(name, sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
101
builtin/plugin/backend_test.go
Normal file
101
builtin/plugin/backend_test.go
Normal file
@ -0,0 +1,101 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
"github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/plugin"
|
||||
"github.com/hashicorp/vault/logical/plugin/mock"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func TestBackend(t *testing.T) {
|
||||
config, cleanup := testConfig(t)
|
||||
defer cleanup()
|
||||
|
||||
_, err := Backend(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackend_Factory(t *testing.T) {
|
||||
config, cleanup := testConfig(t)
|
||||
defer cleanup()
|
||||
|
||||
_, err := Factory(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackend_PluginMain(t *testing.T) {
|
||||
if os.Getenv(pluginutil.PluginUnwrapTokenEnv) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
content := []byte(vault.TestClusterCACert)
|
||||
tmpfile, err := ioutil.TempFile("", "test-cacert")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.Remove(tmpfile.Name()) // clean up
|
||||
|
||||
if _, err := tmpfile.Write(content); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
args := []string{"--ca-cert=" + tmpfile.Name()}
|
||||
|
||||
apiClientMeta := &pluginutil.APIClientMeta{}
|
||||
flags := apiClientMeta.FlagSet()
|
||||
flags.Parse(args)
|
||||
tlsConfig := apiClientMeta.GetTLSConfig()
|
||||
tlsProviderFunc := pluginutil.VaultPluginTLSProvider(tlsConfig)
|
||||
|
||||
err = plugin.Serve(&plugin.ServeOpts{
|
||||
BackendFactoryFunc: mock.Factory,
|
||||
TLSProviderFunc: tlsProviderFunc,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testConfig(t *testing.T) (*logical.BackendConfig, func()) {
|
||||
coreConfig := &vault.CoreConfig{}
|
||||
|
||||
cluster := vault.NewTestCluster(t, coreConfig, true)
|
||||
cluster.StartListeners()
|
||||
cores := cluster.Cores
|
||||
|
||||
cores[0].Handler.Handle("/", http.Handler(cores[0].Core))
|
||||
cores[1].Handler.Handle("/", http.Handler(cores[1].Core))
|
||||
cores[2].Handler.Handle("/", http.Handler(cores[2].Core))
|
||||
|
||||
core := cores[0]
|
||||
|
||||
sys := vault.TestDynamicSystemView(core.Core)
|
||||
|
||||
config := &logical.BackendConfig{
|
||||
Logger: nil,
|
||||
System: sys,
|
||||
Config: map[string]string{
|
||||
"plugin_name": "mock-plugin",
|
||||
},
|
||||
}
|
||||
|
||||
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", "TestBackend_PluginMain")
|
||||
|
||||
return config, func() {
|
||||
cluster.CloseListeners()
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ import (
|
||||
"github.com/hashicorp/vault/builtin/logical/ssh"
|
||||
"github.com/hashicorp/vault/builtin/logical/totp"
|
||||
"github.com/hashicorp/vault/builtin/logical/transit"
|
||||
"github.com/hashicorp/vault/builtin/plugin"
|
||||
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/command"
|
||||
@ -79,6 +80,7 @@ func Commands(metaPtr *meta.Meta) map[string]cli.CommandFactory {
|
||||
"ldap": credLdap.Factory,
|
||||
"okta": credOkta.Factory,
|
||||
"radius": credRadius.Factory,
|
||||
"plugin": plugin.Factory,
|
||||
},
|
||||
LogicalBackends: map[string]logical.Factory{
|
||||
"aws": aws.Factory,
|
||||
@ -94,6 +96,7 @@ func Commands(metaPtr *meta.Meta) map[string]cli.CommandFactory {
|
||||
"rabbitmq": rabbitmq.Factory,
|
||||
"database": database.Factory,
|
||||
"totp": totp.Factory,
|
||||
"plugin": plugin.Factory,
|
||||
},
|
||||
ShutdownCh: command.MakeShutdownCh(),
|
||||
SighupCh: command.MakeSighupCh(),
|
||||
|
@ -14,11 +14,12 @@ type AuthEnableCommand struct {
|
||||
}
|
||||
|
||||
func (c *AuthEnableCommand) Run(args []string) int {
|
||||
var description, path string
|
||||
var description, path, pluginName string
|
||||
var local bool
|
||||
flags := c.Meta.FlagSet("auth-enable", meta.FlagSetDefault)
|
||||
flags.StringVar(&description, "description", "", "")
|
||||
flags.StringVar(&path, "path", "", "")
|
||||
flags.StringVar(&pluginName, "plugin-name", "", "")
|
||||
flags.BoolVar(&local, "local", false, "")
|
||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
@ -36,8 +37,13 @@ func (c *AuthEnableCommand) Run(args []string) int {
|
||||
authType := args[0]
|
||||
|
||||
// If no path is specified, we default the path to the backend type
|
||||
// or use the plugin name if it's a plugin backend
|
||||
if path == "" {
|
||||
path = authType
|
||||
if authType == "plugin" {
|
||||
path = pluginName
|
||||
} else {
|
||||
path = authType
|
||||
}
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
@ -50,6 +56,7 @@ func (c *AuthEnableCommand) Run(args []string) int {
|
||||
if err := client.Sys().EnableAuthWithOptions(path, &api.EnableAuthOptions{
|
||||
Type: authType,
|
||||
Description: description,
|
||||
PluginName: pluginName,
|
||||
Local: local,
|
||||
}); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
@ -89,6 +96,9 @@ Auth Enable Options:
|
||||
to the type of the mount. This will make the auth
|
||||
provider available at "/auth/<path>"
|
||||
|
||||
-plugin-name Name of the auth plugin to use based from the name
|
||||
in the plugin catalog.
|
||||
|
||||
-local Mark the mount as a local mount. Local mounts
|
||||
are not replicated nor (if a secondary)
|
||||
removed by replication.
|
||||
|
@ -14,13 +14,14 @@ type MountCommand struct {
|
||||
}
|
||||
|
||||
func (c *MountCommand) Run(args []string) int {
|
||||
var description, path, defaultLeaseTTL, maxLeaseTTL string
|
||||
var description, path, defaultLeaseTTL, maxLeaseTTL, pluginName string
|
||||
var local, forceNoCache bool
|
||||
flags := c.Meta.FlagSet("mount", meta.FlagSetDefault)
|
||||
flags.StringVar(&description, "description", "", "")
|
||||
flags.StringVar(&path, "path", "", "")
|
||||
flags.StringVar(&defaultLeaseTTL, "default-lease-ttl", "", "")
|
||||
flags.StringVar(&maxLeaseTTL, "max-lease-ttl", "", "")
|
||||
flags.StringVar(&pluginName, "plugin-name", "", "")
|
||||
flags.BoolVar(&forceNoCache, "force-no-cache", false, "")
|
||||
flags.BoolVar(&local, "local", false, "")
|
||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
@ -39,8 +40,13 @@ func (c *MountCommand) Run(args []string) int {
|
||||
mountType := args[0]
|
||||
|
||||
// If no path is specified, we default the path to the backend type
|
||||
// or use the plugin name if it's a plugin backend
|
||||
if path == "" {
|
||||
path = mountType
|
||||
if mountType == "plugin" {
|
||||
path = pluginName
|
||||
} else {
|
||||
path = mountType
|
||||
}
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
@ -57,6 +63,7 @@ func (c *MountCommand) Run(args []string) int {
|
||||
DefaultLeaseTTL: defaultLeaseTTL,
|
||||
MaxLeaseTTL: maxLeaseTTL,
|
||||
ForceNoCache: forceNoCache,
|
||||
PluginName: pluginName,
|
||||
},
|
||||
Local: local,
|
||||
}
|
||||
@ -67,9 +74,14 @@ func (c *MountCommand) Run(args []string) int {
|
||||
return 2
|
||||
}
|
||||
|
||||
mountPart := fmt.Sprintf("'%s'", mountType)
|
||||
if mountType == "plugin" {
|
||||
mountPart = fmt.Sprintf("plugin '%s'", pluginName)
|
||||
}
|
||||
|
||||
c.Ui.Output(fmt.Sprintf(
|
||||
"Successfully mounted '%s' at '%s'!",
|
||||
mountType, path))
|
||||
"Successfully mounted %s at '%s'!",
|
||||
mountPart, path))
|
||||
|
||||
return 0
|
||||
}
|
||||
@ -112,10 +124,12 @@ Mount Options:
|
||||
not affect caching of the underlying encrypted
|
||||
data storage.
|
||||
|
||||
-plugin-name Name of the plugin to mount based from the name
|
||||
in the plugin catalog.
|
||||
|
||||
-local Mark the mount as a local mount. Local mounts
|
||||
are not replicated nor (if a secondary)
|
||||
removed by replication.
|
||||
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
@ -42,9 +42,13 @@ func (c *MountsCommand) Run(args []string) int {
|
||||
}
|
||||
sort.Strings(paths)
|
||||
|
||||
columns := []string{"Path | Type | Accessor | Default TTL | Max TTL | Force No Cache | Replication Behavior | Description"}
|
||||
columns := []string{"Path | Type | Accessor | Plugin | Default TTL | Max TTL | Force No Cache | Replication Behavior | Description"}
|
||||
for _, path := range paths {
|
||||
mount := mounts[path]
|
||||
pluginName := "n/a"
|
||||
if mount.Config.PluginName != "" {
|
||||
pluginName = mount.Config.PluginName
|
||||
}
|
||||
defTTL := "system"
|
||||
switch {
|
||||
case mount.Type == "system":
|
||||
@ -68,7 +72,7 @@ func (c *MountsCommand) Run(args []string) int {
|
||||
replicatedBehavior = "local"
|
||||
}
|
||||
columns = append(columns, fmt.Sprintf(
|
||||
"%s | %s | %s | %s | %s | %v | %s | %s", path, mount.Type, mount.Accessor, defTTL, maxTTL,
|
||||
"%s | %s | %s | %s | %s | %s | %v | %s | %s", path, mount.Type, mount.Accessor, pluginName, defTTL, maxTTL,
|
||||
mount.Config.ForceNoCache, replicatedBehavior, mount.Description))
|
||||
}
|
||||
|
||||
|
@ -199,8 +199,8 @@ func TestRekey_init_pgp(t *testing.T) {
|
||||
MaxLeaseTTLVal: time.Hour * 24 * 32,
|
||||
},
|
||||
}
|
||||
sysBE := vault.NewSystemBackend(core)
|
||||
sysBackend, err := sysBE.Backend.Setup(bc)
|
||||
sysBackend := vault.NewSystemBackend(core)
|
||||
err := sysBackend.Backend.Setup(bc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -9,9 +9,11 @@ import (
|
||||
"github.com/hashicorp/vault/plugins/database/postgresql"
|
||||
)
|
||||
|
||||
// BuiltinFactory is the func signature that should be returned by
|
||||
// the plugin's New() func.
|
||||
type BuiltinFactory func() (interface{}, error)
|
||||
|
||||
var plugins map[string]BuiltinFactory = map[string]BuiltinFactory{
|
||||
var plugins = map[string]BuiltinFactory{
|
||||
// These four plugins all use the same mysql implementation but with
|
||||
// different username settings passed by the constructor.
|
||||
"mysql-database-plugin": mysql.New(mysql.MetadataLen, mysql.UsernameLen),
|
||||
@ -26,11 +28,14 @@ var plugins map[string]BuiltinFactory = map[string]BuiltinFactory{
|
||||
"hana-database-plugin": hana.New,
|
||||
}
|
||||
|
||||
// Get returns the BuiltinFactory func for a particular backend plugin
|
||||
// from the plugins map.
|
||||
func Get(name string) (BuiltinFactory, bool) {
|
||||
f, ok := plugins[name]
|
||||
return f, ok
|
||||
}
|
||||
|
||||
// Keys returns the list of plugin names that are considered builtin plugins.
|
||||
func Keys() []string {
|
||||
keys := make([]string, len(plugins))
|
||||
|
||||
|
@ -35,12 +35,12 @@ type LookRunnerUtil interface {
|
||||
// PluginRunner defines the metadata needed to run a plugin securely with
|
||||
// go-plugin.
|
||||
type PluginRunner struct {
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
Args []string `json:"args"`
|
||||
Sha256 []byte `json:"sha256"`
|
||||
Builtin bool `json:"builtin"`
|
||||
BuiltinFactory func() (interface{}, error) `json:"-"`
|
||||
Name string `json:"name" structs:"name"`
|
||||
Command string `json:"command" structs:"command"`
|
||||
Args []string `json:"args" structs:"args"`
|
||||
Sha256 []byte `json:"sha256" structs:"sha256"`
|
||||
Builtin bool `json:"builtin" structs:"builtin"`
|
||||
BuiltinFactory func() (interface{}, error) `json:"-" structs:"-"`
|
||||
}
|
||||
|
||||
// Run takes a wrapper instance, and the go-plugin paramaters and executes a
|
||||
|
@ -126,7 +126,7 @@ func VaultPluginTLSProvider(apiTLSConfig *api.TLSConfig) func() (*tls.Config, er
|
||||
// Parse the JWT and retrieve the vault address
|
||||
wt, err := jws.ParseJWT([]byte(unwrapToken))
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("error decoding token: %s", err))
|
||||
return nil, fmt.Errorf("error decoding token: %s", err)
|
||||
}
|
||||
if wt == nil {
|
||||
return nil, errors.New("nil decoded token")
|
||||
@ -146,7 +146,7 @@ func VaultPluginTLSProvider(apiTLSConfig *api.TLSConfig) func() (*tls.Config, er
|
||||
|
||||
// Sanity check the value
|
||||
if _, err := url.Parse(vaultAddr); err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("error parsing the vault address: %s", err))
|
||||
return nil, fmt.Errorf("error parsing the vault address: %s", err)
|
||||
}
|
||||
|
||||
// Unwrap the token
|
||||
@ -165,7 +165,7 @@ func VaultPluginTLSProvider(apiTLSConfig *api.TLSConfig) func() (*tls.Config, er
|
||||
return nil, errwrap.Wrapf("error during token unwrap request: {{err}}", err)
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, errors.New("error during token unwrap request secret is nil")
|
||||
return nil, errors.New("error during token unwrap request: secret is nil")
|
||||
}
|
||||
|
||||
// Retrieve and parse the server's certificate
|
||||
|
@ -82,6 +82,12 @@ type Backend struct {
|
||||
// See the built-in AuthRenew helpers in lease.go for common callbacks.
|
||||
AuthRenew OperationFunc
|
||||
|
||||
// LicenseRegistration is called to register the license for a backend.
|
||||
LicenseRegistration LicenseRegistrationFunc
|
||||
|
||||
// Type is the logical.BackendType for the backend implementation
|
||||
BackendType logical.BackendType
|
||||
|
||||
logger log.Logger
|
||||
system logical.SystemView
|
||||
once sync.Once
|
||||
@ -107,6 +113,10 @@ type InitializeFunc func() error
|
||||
// InvalidateFunc is the callback for backend key invalidation.
|
||||
type InvalidateFunc func(string)
|
||||
|
||||
// LicenseRegistrationFunc is the callback for backend license registration.
|
||||
type LicenseRegistrationFunc func(interface{}) error
|
||||
|
||||
// HandleExistenceCheck is the logical.Backend implementation.
|
||||
func (b *Backend) HandleExistenceCheck(req *logical.Request) (checkFound bool, exists bool, err error) {
|
||||
b.once.Do(b.init)
|
||||
|
||||
@ -154,7 +164,7 @@ func (b *Backend) HandleExistenceCheck(req *logical.Request) (checkFound bool, e
|
||||
return
|
||||
}
|
||||
|
||||
// logical.Backend impl.
|
||||
// HandleRequest is the logical.Backend implementation.
|
||||
func (b *Backend) HandleRequest(req *logical.Request) (*logical.Response, error) {
|
||||
b.once.Do(b.init)
|
||||
|
||||
@ -221,18 +231,11 @@ func (b *Backend) HandleRequest(req *logical.Request) (*logical.Response, error)
|
||||
return callback(req, &fd)
|
||||
}
|
||||
|
||||
// logical.Backend impl.
|
||||
// SpecialPaths is the logical.Backend implementation.
|
||||
func (b *Backend) SpecialPaths() *logical.Paths {
|
||||
return b.PathsSpecial
|
||||
}
|
||||
|
||||
// Setup is used to initialize the backend with the initial backend configuration
|
||||
func (b *Backend) Setup(config *logical.BackendConfig) (logical.Backend, error) {
|
||||
b.logger = config.Logger
|
||||
b.system = config.System
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Cleanup is used to release resources and prepare to stop the backend
|
||||
func (b *Backend) Cleanup() {
|
||||
if b.Clean != nil {
|
||||
@ -240,6 +243,7 @@ func (b *Backend) Cleanup() {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize calls the backend's Init func if set.
|
||||
func (b *Backend) Initialize() error {
|
||||
if b.Init != nil {
|
||||
return b.Init()
|
||||
@ -255,6 +259,13 @@ func (b *Backend) InvalidateKey(key string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Setup is used to initialize the backend with the initial backend configuration
|
||||
func (b *Backend) Setup(config *logical.BackendConfig) error {
|
||||
b.logger = config.Logger
|
||||
b.system = config.System
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logger can be used to get the logger. If no logger has been set,
|
||||
// the logs will be discarded.
|
||||
func (b *Backend) Logger() log.Logger {
|
||||
@ -265,11 +276,25 @@ func (b *Backend) Logger() log.Logger {
|
||||
return logformat.NewVaultLoggerWithWriter(ioutil.Discard, log.LevelOff)
|
||||
}
|
||||
|
||||
// System returns the backend's system view.
|
||||
func (b *Backend) System() logical.SystemView {
|
||||
return b.system
|
||||
}
|
||||
|
||||
// This method takes in the TTL and MaxTTL values provided by the user,
|
||||
// Type returns the backend type
|
||||
func (b *Backend) Type() logical.BackendType {
|
||||
return b.BackendType
|
||||
}
|
||||
|
||||
// RegisterLicense performs backend license registration.
|
||||
func (b *Backend) RegisterLicense(license interface{}) error {
|
||||
if b.LicenseRegistration == nil {
|
||||
return nil
|
||||
}
|
||||
return b.LicenseRegistration(license)
|
||||
}
|
||||
|
||||
// SanitizeTTLStr takes in the TTL and MaxTTL values provided by the user,
|
||||
// compares those with the SystemView values. If they are empty a value of 0 is
|
||||
// set, which will cause initial secret or LeaseExtend operations to use the
|
||||
// mount/system defaults. If they are set, their boundaries are validated.
|
||||
@ -297,7 +322,8 @@ func (b *Backend) SanitizeTTLStr(ttlStr, maxTTLStr string) (ttl, maxTTL time.Dur
|
||||
return
|
||||
}
|
||||
|
||||
// Caps the boundaries of ttl and max_ttl values to the backend mount's max_ttl value.
|
||||
// SanitizeTTL caps the boundaries of ttl and max_ttl values to the
|
||||
// backend mount's max_ttl value.
|
||||
func (b *Backend) SanitizeTTL(ttl, maxTTL time.Duration) (time.Duration, time.Duration, error) {
|
||||
sysMaxTTL := b.System().MaxLeaseTTL()
|
||||
if ttl > sysMaxTTL {
|
||||
@ -575,6 +601,7 @@ func (s *FieldSchema) DefaultOrZero() interface{} {
|
||||
return s.Type.Zero()
|
||||
}
|
||||
|
||||
// Zero returns the correct zero-value for a specific FieldType
|
||||
func (t FieldType) Zero() interface{} {
|
||||
switch t {
|
||||
case TypeString:
|
||||
|
@ -2,6 +2,29 @@ package logical
|
||||
|
||||
import log "github.com/mgutz/logxi/v1"
|
||||
|
||||
// BackendType is the type of backend that is being implemented
|
||||
type BackendType uint32
|
||||
|
||||
// The these are the types of backends that can be derived from
|
||||
// logical.Backend
|
||||
const (
|
||||
TypeUnknown BackendType = 0 // This is also the zero-value for BackendType
|
||||
TypeLogical BackendType = 1
|
||||
TypeCredential BackendType = 2
|
||||
)
|
||||
|
||||
// Stringer implementation
|
||||
func (b BackendType) String() string {
|
||||
switch b {
|
||||
case TypeLogical:
|
||||
return "secret"
|
||||
case TypeCredential:
|
||||
return "auth"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// Backend interface must be implemented to be "mountable" at
|
||||
// a given path. Requests flow through a router which has various mount
|
||||
// points that flow to a logical backend. The logic of each backend is flexible,
|
||||
@ -27,6 +50,11 @@ type Backend interface {
|
||||
// information, such as globally configured default and max lease TTLs.
|
||||
System() SystemView
|
||||
|
||||
// Logger provides an interface to access the underlying logger. This
|
||||
// is useful when a struct embeds a Backend-implemented struct that
|
||||
// contains a private instance of logger.
|
||||
Logger() log.Logger
|
||||
|
||||
// HandleExistenceCheck is used to handle a request and generate a response
|
||||
// indicating whether the given path exists or not; this is used to
|
||||
// understand whether the request must have a Create or Update capability
|
||||
@ -47,6 +75,16 @@ type Backend interface {
|
||||
// to the backend. The backend can use this to clear any caches or reset
|
||||
// internal state as needed.
|
||||
InvalidateKey(key string)
|
||||
|
||||
// Setup is used to set up the backend based on the provided backend
|
||||
// configuration.
|
||||
Setup(*BackendConfig) error
|
||||
|
||||
// Type returns the BackendType for the particular backend
|
||||
Type() BackendType
|
||||
|
||||
// RegisterLicense performs backend license registration
|
||||
RegisterLicense(interface{}) error
|
||||
}
|
||||
|
||||
// BackendConfig is provided to the factory to initialize the backend
|
||||
|
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)
|
||||
}
|
||||
}
|
@ -95,9 +95,13 @@ func (c *Core) enableCredential(entry *MountEntry) error {
|
||||
viewPath := credentialBarrierPrefix + entry.UUID + "/"
|
||||
view := NewBarrierView(c.barrier, viewPath)
|
||||
sysView := c.mountEntrySysView(entry)
|
||||
conf := make(map[string]string)
|
||||
if entry.Config.PluginName != "" {
|
||||
conf["plugin_name"] = entry.Config.PluginName
|
||||
}
|
||||
|
||||
// Create the new backend
|
||||
backend, err := c.newCredentialBackend(entry.Type, sysView, view, nil)
|
||||
backend, err := c.newCredentialBackend(entry.Type, sysView, view, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -105,6 +109,12 @@ func (c *Core) enableCredential(entry *MountEntry) error {
|
||||
return fmt.Errorf("nil backend returned from %q factory", entry.Type)
|
||||
}
|
||||
|
||||
// Check for the correct backend type
|
||||
backendType := backend.Type()
|
||||
if entry.Type == "plugin" && backendType != logical.TypeCredential {
|
||||
return fmt.Errorf("cannot mount '%s' of type '%s' as an auth backend", entry.Config.PluginName, backendType)
|
||||
}
|
||||
|
||||
if err := backend.Initialize(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -406,9 +416,13 @@ func (c *Core) setupCredentials() error {
|
||||
viewPath := credentialBarrierPrefix + entry.UUID + "/"
|
||||
view = NewBarrierView(c.barrier, viewPath)
|
||||
sysView := c.mountEntrySysView(entry)
|
||||
conf := make(map[string]string)
|
||||
if entry.Config.PluginName != "" {
|
||||
conf["plugin_name"] = entry.Config.PluginName
|
||||
}
|
||||
|
||||
// Initialize the backend
|
||||
backend, err = c.newCredentialBackend(entry.Type, sysView, view, nil)
|
||||
backend, err = c.newCredentialBackend(entry.Type, sysView, view, conf)
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to create credential entry", "path", entry.Path, "error", err)
|
||||
return errLoadAuthFailed
|
||||
@ -417,6 +431,12 @@ func (c *Core) setupCredentials() error {
|
||||
return fmt.Errorf("nil backend returned from %q factory", entry.Type)
|
||||
}
|
||||
|
||||
// Check for the correct backend type
|
||||
backendType := backend.Type()
|
||||
if entry.Type == "plugin" && backendType != logical.TypeCredential {
|
||||
return fmt.Errorf("cannot mount '%s' of type '%s' as an auth backend", entry.Config.PluginName, backendType)
|
||||
}
|
||||
|
||||
if err := backend.Initialize(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -516,7 +516,10 @@ func NewCore(conf *CoreConfig) (*Core, error) {
|
||||
logicalBackends["cubbyhole"] = CubbyholeBackendFactory
|
||||
logicalBackends["system"] = func(config *logical.BackendConfig) (logical.Backend, error) {
|
||||
b := NewSystemBackend(c)
|
||||
return b.Backend.Setup(config)
|
||||
if err := b.Setup(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
c.logicalBackends = logicalBackends
|
||||
|
||||
|
@ -1459,5 +1459,10 @@ func badRenewFactory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
},
|
||||
}
|
||||
|
||||
return be.Setup(conf)
|
||||
err := be.Setup(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return be, nil
|
||||
}
|
||||
|
@ -17,13 +17,13 @@ func PassthroughBackendFactory(conf *logical.BackendConfig) (logical.Backend, er
|
||||
return LeaseSwitchedPassthroughBackend(conf, false)
|
||||
}
|
||||
|
||||
// PassthroughBackendWithLeasesFactory returns a PassthroughBackend
|
||||
// LeasedPassthroughBackendFactory returns a PassthroughBackend
|
||||
// with leases switched on
|
||||
func LeasedPassthroughBackendFactory(conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
return LeaseSwitchedPassthroughBackend(conf, true)
|
||||
}
|
||||
|
||||
// LeaseSwitchedPassthroughBackendFactory returns a PassthroughBackend
|
||||
// LeaseSwitchedPassthroughBackend returns a PassthroughBackend
|
||||
// with leases switched on or off
|
||||
func LeaseSwitchedPassthroughBackend(conf *logical.BackendConfig, leases bool) (logical.Backend, error) {
|
||||
var b PassthroughBackend
|
||||
@ -147,6 +147,10 @@ func (b *PassthroughBackend) handleRead(
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *PassthroughBackend) GeneratesLeases() bool {
|
||||
return b.generateLeases
|
||||
}
|
||||
|
||||
func (b *PassthroughBackend) handleWrite(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
// Check that some fields are given
|
||||
@ -202,10 +206,6 @@ func (b *PassthroughBackend) handleList(
|
||||
return logical.ListResponse(keys), nil
|
||||
}
|
||||
|
||||
func (b *PassthroughBackend) GeneratesLeases() bool {
|
||||
return b.generateLeases
|
||||
}
|
||||
|
||||
const passthroughHelp = `
|
||||
The generic backend reads and writes arbitrary secrets to the backend.
|
||||
The secrets are encrypted/decrypted by Vault: they are never stored
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/hashicorp/vault/helper/consts"
|
||||
"github.com/hashicorp/vault/helper/parseutil"
|
||||
"github.com/hashicorp/vault/helper/wrapping"
|
||||
@ -486,6 +487,10 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["auth_desc"][0]),
|
||||
},
|
||||
"plugin_name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["auth_plugin"][0]),
|
||||
},
|
||||
"local": &framework.FieldSchema{
|
||||
Type: framework.TypeBool,
|
||||
Default: false,
|
||||
@ -775,7 +780,7 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
||||
HelpDescription: strings.TrimSpace(sysHelp["audited-headers"][1]),
|
||||
},
|
||||
&framework.Path{
|
||||
Pattern: "plugins/catalog/$",
|
||||
Pattern: "plugins/catalog/?$",
|
||||
|
||||
Fields: map[string]*framework.FieldSchema{},
|
||||
|
||||
@ -792,18 +797,15 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "The name of the plugin",
|
||||
Description: strings.TrimSpace(sysHelp["plugin-catalog_name"][0]),
|
||||
},
|
||||
"sha_256": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: `The SHA256 sum of the executable used in the
|
||||
command field. This should be HEX encoded.`,
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["plugin-catalog_sha-256"][0]),
|
||||
},
|
||||
"command": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: `The command used to start the plugin. The
|
||||
executable defined in this command must exist in vault's
|
||||
plugin directory.`,
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["plugin-catalog_command"][0]),
|
||||
},
|
||||
},
|
||||
|
||||
@ -943,10 +945,11 @@ func (b *SystemBackend) handlePluginCatalogRead(req *logical.Request, d *framewo
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Create a map of data to be returned and remove sensitive information from it
|
||||
data := structs.New(plugin).Map()
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"plugin": plugin,
|
||||
},
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -1157,18 +1160,17 @@ func (b *SystemBackend) handleMountTable(
|
||||
}
|
||||
|
||||
for _, entry := range b.Core.mounts.Entries {
|
||||
// Populate mount info
|
||||
structConfig := structs.New(entry.Config).Map()
|
||||
structConfig["default_lease_ttl"] = int64(structConfig["default_lease_ttl"].(time.Duration).Seconds())
|
||||
structConfig["max_lease_ttl"] = int64(structConfig["max_lease_ttl"].(time.Duration).Seconds())
|
||||
info := map[string]interface{}{
|
||||
"type": entry.Type,
|
||||
"description": entry.Description,
|
||||
"accessor": entry.Accessor,
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": int64(entry.Config.DefaultLeaseTTL.Seconds()),
|
||||
"max_lease_ttl": int64(entry.Config.MaxLeaseTTL.Seconds()),
|
||||
"force_no_cache": entry.Config.ForceNoCache,
|
||||
},
|
||||
"local": entry.Local,
|
||||
"config": structConfig,
|
||||
"local": entry.Local,
|
||||
}
|
||||
|
||||
resp.Data[entry.Path] = info
|
||||
}
|
||||
|
||||
@ -1195,12 +1197,8 @@ func (b *SystemBackend) handleMount(
|
||||
path = sanitizeMountPath(path)
|
||||
|
||||
var config MountConfig
|
||||
var apiConfig APIMountConfig
|
||||
|
||||
var apiConfig struct {
|
||||
DefaultLeaseTTL string `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL string `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
}
|
||||
configMap := data.Get("config").(map[string]interface{})
|
||||
if configMap != nil && len(configMap) != 0 {
|
||||
err := mapstructure.Decode(configMap, &apiConfig)
|
||||
@ -1249,6 +1247,11 @@ func (b *SystemBackend) handleMount(
|
||||
logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Only set plugin-name if mount is of type plugin
|
||||
if logicalType == "plugin" && apiConfig.PluginName != "" {
|
||||
config.PluginName = apiConfig.PluginName
|
||||
}
|
||||
|
||||
// Copy over the force no cache if set
|
||||
if apiConfig.ForceNoCache {
|
||||
config.ForceNoCache = true
|
||||
@ -1685,6 +1688,14 @@ func (b *SystemBackend) handleEnableAuth(
|
||||
path := data.Get("path").(string)
|
||||
logicalType := data.Get("type").(string)
|
||||
description := data.Get("description").(string)
|
||||
pluginName := data.Get("plugin_name").(string)
|
||||
|
||||
var config MountConfig
|
||||
|
||||
// Only set plugin name if mount is of type plugin
|
||||
if logicalType == "plugin" && pluginName != "" {
|
||||
config.PluginName = pluginName
|
||||
}
|
||||
|
||||
if logicalType == "" {
|
||||
return logical.ErrorResponse(
|
||||
@ -1700,6 +1711,7 @@ func (b *SystemBackend) handleEnableAuth(
|
||||
Path: path,
|
||||
Type: logicalType,
|
||||
Description: description,
|
||||
Config: config,
|
||||
Local: local,
|
||||
}
|
||||
|
||||
@ -2585,6 +2597,11 @@ Example: you might have an OAuth backend for GitHub, and one for Google Apps.
|
||||
"",
|
||||
},
|
||||
|
||||
"auth_plugin": {
|
||||
`Name of the auth plugin to use based from the name in the plugin catalog.`,
|
||||
"",
|
||||
},
|
||||
|
||||
"policy-list": {
|
||||
`List the configured access control policies.`,
|
||||
`
|
||||
@ -2764,23 +2781,38 @@ This path responds to the following HTTP methods.
|
||||
"Lists the headers configured to be audited.",
|
||||
`Returns a list of headers that have been configured to be audited.`,
|
||||
},
|
||||
"plugins/catalog": {
|
||||
`Configures the plugins known to vault`,
|
||||
"plugin-catalog": {
|
||||
"Configures the plugins known to vault",
|
||||
`
|
||||
This path responds to the following HTTP methods.
|
||||
LIST /
|
||||
Returns a list of names of configured plugins.
|
||||
LIST /
|
||||
Returns a list of names of configured plugins.
|
||||
|
||||
GET /<name>
|
||||
Retrieve the metadata for the named plugin.
|
||||
GET /<name>
|
||||
Retrieve the metadata for the named plugin.
|
||||
|
||||
PUT /<name>
|
||||
Add or update plugin.
|
||||
PUT /<name>
|
||||
Add or update plugin.
|
||||
|
||||
DELETE /<name>
|
||||
Delete the plugin with the given name.
|
||||
DELETE /<name>
|
||||
Delete the plugin with the given name.
|
||||
`,
|
||||
},
|
||||
"plugin-catalog_name": {
|
||||
"The name of the plugin",
|
||||
"",
|
||||
},
|
||||
"plugin-catalog_sha-256": {
|
||||
`The SHA256 sum of the executable used in the
|
||||
command field. This should be HEX encoded.`,
|
||||
"",
|
||||
},
|
||||
"plugin-catalog_command": {
|
||||
`The command used to start the plugin. The
|
||||
executable defined in this command must exist in vault's
|
||||
plugin directory.`,
|
||||
"",
|
||||
},
|
||||
"leases": {
|
||||
`View or list lease metadata.`,
|
||||
`
|
||||
|
104
vault/logical_system_integ_test.go
Normal file
104
vault/logical_system_integ_test.go
Normal file
@ -0,0 +1,104 @@
|
||||
package vault_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/builtin/plugin"
|
||||
"github.com/hashicorp/vault/helper/logformat"
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
"github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
lplugin "github.com/hashicorp/vault/logical/plugin"
|
||||
"github.com/hashicorp/vault/logical/plugin/mock"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
)
|
||||
|
||||
func TestSystemBackend_enableAuth_plugin(t *testing.T) {
|
||||
coreConfig := &vault.CoreConfig{
|
||||
CredentialBackends: map[string]logical.Factory{
|
||||
"plugin": plugin.Factory,
|
||||
},
|
||||
}
|
||||
|
||||
cluster := vault.NewTestCluster(t, coreConfig, true)
|
||||
cluster.StartListeners()
|
||||
defer cluster.CloseListeners()
|
||||
cores := cluster.Cores
|
||||
|
||||
cores[0].Handler.Handle("/", http.Handler(cores[0].Core))
|
||||
cores[1].Handler.Handle("/", http.Handler(cores[1].Core))
|
||||
cores[2].Handler.Handle("/", http.Handler(cores[2].Core))
|
||||
|
||||
core := cores[0]
|
||||
|
||||
b := vault.NewSystemBackend(core.Core)
|
||||
logger := logformat.NewVaultLogger(log.LevelTrace)
|
||||
bc := &logical.BackendConfig{
|
||||
Logger: logger,
|
||||
System: logical.StaticSystemView{
|
||||
DefaultLeaseTTLVal: time.Hour * 24,
|
||||
MaxLeaseTTLVal: time.Hour * 24 * 32,
|
||||
},
|
||||
}
|
||||
|
||||
err := b.Backend.Setup(bc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vault.TestAddTestPlugin(t, core.Core, "mock-plugin", "TestBackend_PluginMain")
|
||||
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "auth/mock-plugin")
|
||||
req.Data["type"] = "plugin"
|
||||
req.Data["plugin_name"] = "mock-plugin"
|
||||
|
||||
resp, err := b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if resp != nil {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackend_PluginMain(t *testing.T) {
|
||||
if os.Getenv(pluginutil.PluginUnwrapTokenEnv) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
content := []byte(vault.TestClusterCACert)
|
||||
tmpfile, err := ioutil.TempFile("", "test-cacert")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.Remove(tmpfile.Name()) // clean up
|
||||
|
||||
if _, err := tmpfile.Write(content); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
factoryFunc := mock.FactoryType(logical.TypeCredential)
|
||||
|
||||
args := []string{"--ca-cert=" + tmpfile.Name()}
|
||||
|
||||
apiClientMeta := &pluginutil.APIClientMeta{}
|
||||
flags := apiClientMeta.FlagSet()
|
||||
flags.Parse(args)
|
||||
tlsConfig := apiClientMeta.GetTLSConfig()
|
||||
tlsProviderFunc := pluginutil.VaultPluginTLSProvider(tlsConfig)
|
||||
err = lplugin.Serve(&lplugin.ServeOpts{
|
||||
BackendFactoryFunc: factoryFunc,
|
||||
TLSProviderFunc: tlsProviderFunc,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
@ -985,8 +985,8 @@ func TestSystemBackend_revokePrefixAuth(t *testing.T) {
|
||||
MaxLeaseTTLVal: time.Hour * 24 * 32,
|
||||
},
|
||||
}
|
||||
be := NewSystemBackend(core)
|
||||
b, err := be.Backend.Setup(bc)
|
||||
b := NewSystemBackend(core)
|
||||
err := b.Backend.Setup(bc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -1049,8 +1049,8 @@ func TestSystemBackend_revokePrefixAuth_origUrl(t *testing.T) {
|
||||
MaxLeaseTTLVal: time.Hour * 24 * 32,
|
||||
},
|
||||
}
|
||||
be := NewSystemBackend(core)
|
||||
b, err := be.Backend.Setup(bc)
|
||||
b := NewSystemBackend(core)
|
||||
err := b.Backend.Setup(bc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -1591,7 +1591,7 @@ func testSystemBackend(t *testing.T) logical.Backend {
|
||||
}
|
||||
|
||||
b := NewSystemBackend(c)
|
||||
_, err := b.Backend.Setup(bc)
|
||||
err := b.Backend.Setup(bc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -1610,7 +1610,7 @@ func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) {
|
||||
}
|
||||
|
||||
b := NewSystemBackend(c)
|
||||
_, err := b.Backend.Setup(bc)
|
||||
err := b.Backend.Setup(bc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -1641,22 +1641,16 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
actualRespData := resp.Data
|
||||
|
||||
expectedBuiltin := &pluginutil.PluginRunner{
|
||||
Name: "mysql-database-plugin",
|
||||
Builtin: true,
|
||||
}
|
||||
expectedBuiltin.BuiltinFactory, _ = builtinplugins.Get("mysql-database-plugin")
|
||||
expectedRespData := structs.New(expectedBuiltin).Map()
|
||||
|
||||
p := resp.Data["plugin"].(*pluginutil.PluginRunner)
|
||||
if &(p.BuiltinFactory) == &(expectedBuiltin.BuiltinFactory) {
|
||||
t.Fatal("expected BuiltinFactory did not match actual")
|
||||
}
|
||||
|
||||
expectedBuiltin.BuiltinFactory = nil
|
||||
p.BuiltinFactory = nil
|
||||
if !reflect.DeepEqual(p, expectedBuiltin) {
|
||||
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", resp.Data["plugin"].(*pluginutil.PluginRunner), expectedBuiltin)
|
||||
if !reflect.DeepEqual(actualRespData, expectedRespData) {
|
||||
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actualRespData, expectedRespData)
|
||||
}
|
||||
|
||||
// Set a plugin
|
||||
@ -1680,16 +1674,19 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
actual := resp.Data
|
||||
|
||||
expected := &pluginutil.PluginRunner{
|
||||
expectedRunner := &pluginutil.PluginRunner{
|
||||
Name: "test-plugin",
|
||||
Command: filepath.Join(sym, filepath.Base(file.Name())),
|
||||
Args: []string{"--test"},
|
||||
Sha256: []byte{'1'},
|
||||
Builtin: false,
|
||||
}
|
||||
if !reflect.DeepEqual(resp.Data["plugin"].(*pluginutil.PluginRunner), expected) {
|
||||
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", resp.Data["plugin"].(*pluginutil.PluginRunner), expected)
|
||||
expected := structs.New(expectedRunner).Map()
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actual, expected)
|
||||
}
|
||||
|
||||
// Delete plugin
|
||||
|
@ -167,6 +167,15 @@ type MountConfig struct {
|
||||
DefaultLeaseTTL time.Duration `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` // Override for global default
|
||||
MaxLeaseTTL time.Duration `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` // Override for global default
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` // Override for global default
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
}
|
||||
|
||||
// APIMountConfig is an embedded struct of api.MountConfigInput
|
||||
type APIMountConfig struct {
|
||||
DefaultLeaseTTL string `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL string `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
}
|
||||
|
||||
// Mount is used to mount a new backend to the mount table.
|
||||
@ -216,8 +225,13 @@ func (c *Core) mount(entry *MountEntry) error {
|
||||
viewPath := backendBarrierPrefix + entry.UUID + "/"
|
||||
view := NewBarrierView(c.barrier, viewPath)
|
||||
sysView := c.mountEntrySysView(entry)
|
||||
conf := make(map[string]string)
|
||||
if entry.Config.PluginName != "" {
|
||||
conf["plugin_name"] = entry.Config.PluginName
|
||||
}
|
||||
|
||||
backend, err := c.newLogicalBackend(entry.Type, sysView, view, nil)
|
||||
// Consider having plugin name under entry.Options
|
||||
backend, err := c.newLogicalBackend(entry.Type, sysView, view, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -225,8 +239,14 @@ func (c *Core) mount(entry *MountEntry) error {
|
||||
return fmt.Errorf("nil backend of type %q returned from creation function", entry.Type)
|
||||
}
|
||||
|
||||
// Check for the correct backend type
|
||||
backendType := backend.Type()
|
||||
if entry.Type == "plugin" && backendType != logical.TypeLogical {
|
||||
return fmt.Errorf("cannot mount '%s' of type '%s' as a logical backend", entry.Config.PluginName, backendType)
|
||||
}
|
||||
|
||||
// Call initialize; this takes care of init tasks that must be run after
|
||||
// the ignore paths are collected
|
||||
// the ignore paths are collected.
|
||||
if err := backend.Initialize(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -658,9 +678,13 @@ func (c *Core) setupMounts() error {
|
||||
// Create a barrier view using the UUID
|
||||
view = NewBarrierView(c.barrier, barrierPath)
|
||||
sysView := c.mountEntrySysView(entry)
|
||||
// Initialize the backend
|
||||
// Set up conf to pass in plugin_name
|
||||
conf := make(map[string]string)
|
||||
if entry.Config.PluginName != "" {
|
||||
conf["plugin_name"] = entry.Config.PluginName
|
||||
}
|
||||
// Create the new backend
|
||||
backend, err = c.newLogicalBackend(entry.Type, sysView, view, nil)
|
||||
backend, err = c.newLogicalBackend(entry.Type, sysView, view, conf)
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to create mount entry", "path", entry.Path, "error", err)
|
||||
return errLoadMountsFailed
|
||||
@ -669,6 +693,12 @@ func (c *Core) setupMounts() error {
|
||||
return fmt.Errorf("created mount entry of type %q is nil", entry.Type)
|
||||
}
|
||||
|
||||
// Check for the correct backend type
|
||||
backendType := backend.Type()
|
||||
if entry.Type == "plugin" && backendType != logical.TypeLogical {
|
||||
return fmt.Errorf("cannot mount '%s' of type '%s' as a logical backend", entry.Config.PluginName, backendType)
|
||||
}
|
||||
|
||||
if err := backend.Initialize(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -687,10 +717,9 @@ func (c *Core) setupMounts() error {
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to mount entry", "path", entry.Path, "error", err)
|
||||
return errLoadMountsFailed
|
||||
} else {
|
||||
if c.logger.IsInfo() {
|
||||
c.logger.Info("core: successfully mounted backend", "type", entry.Type, "path", entry.Path)
|
||||
}
|
||||
}
|
||||
if c.logger.IsInfo() {
|
||||
c.logger.Info("core: successfully mounted backend", "type", entry.Type, "path", entry.Path)
|
||||
}
|
||||
|
||||
// Ensure the path is tainted if set in the mount table
|
||||
|
@ -2,6 +2,7 @@ package vault
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -9,7 +10,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/logformat"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
)
|
||||
|
||||
type NoopBackend struct {
|
||||
@ -63,10 +66,26 @@ func (n *NoopBackend) InvalidateKey(k string) {
|
||||
n.Invalidations = append(n.Invalidations, k)
|
||||
}
|
||||
|
||||
func (n *NoopBackend) Setup(config *logical.BackendConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NoopBackend) Logger() log.Logger {
|
||||
return logformat.NewVaultLoggerWithWriter(ioutil.Discard, log.LevelOff)
|
||||
}
|
||||
|
||||
func (n *NoopBackend) Initialize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NoopBackend) Type() logical.BackendType {
|
||||
return logical.TypeLogical
|
||||
}
|
||||
|
||||
func (n *NoopBackend) RegisterLicense(license interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRouter_Mount(t *testing.T) {
|
||||
r := NewRouter()
|
||||
_, barrier, _ := mockBarrier(t)
|
||||
|
@ -520,6 +520,10 @@ func (n *rawHTTP) System() logical.SystemView {
|
||||
}
|
||||
}
|
||||
|
||||
func (n *rawHTTP) Logger() log.Logger {
|
||||
return logformat.NewVaultLogger(log.LevelTrace)
|
||||
}
|
||||
|
||||
func (n *rawHTTP) Cleanup() {
|
||||
// noop
|
||||
}
|
||||
@ -533,6 +537,19 @@ func (n *rawHTTP) InvalidateKey(string) {
|
||||
// noop
|
||||
}
|
||||
|
||||
func (n *rawHTTP) Setup(config *logical.BackendConfig) error {
|
||||
// noop
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *rawHTTP) Type() logical.BackendType {
|
||||
return logical.TypeUnknown
|
||||
}
|
||||
|
||||
func (n *rawHTTP) RegisterLicense(license interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateRandBytes(length int) ([]byte, error) {
|
||||
if length < 0 {
|
||||
return nil, fmt.Errorf("length must be >= 0")
|
||||
|
44
website/source/docs/plugin/index.html.md
Normal file
44
website/source/docs/plugin/index.html.md
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
layout: "docs"
|
||||
page_title: "Plugin Backends"
|
||||
sidebar_current: "docs-plugin"
|
||||
description: |-
|
||||
Plugin backends are mountable backends that are implemented unsing Vault's plugin system.
|
||||
---
|
||||
|
||||
# Plugin Backends
|
||||
|
||||
Plugin backends are the components in Vault that can be implemented separately from Vault's
|
||||
builtin backends. These backends can be either authentication or secret backends.
|
||||
|
||||
Detailed information regarding the plugin system can be found in the
|
||||
[internals documentation](https://www.vaultproject.io/docs/internals/plugins.html).
|
||||
|
||||
# Mounting/unmounting Plugin Backends
|
||||
|
||||
Before a plugin backend can be mounted, it needs to be registered via the
|
||||
[plugin catalog](https://www.vaultproject.io/docs/internals/plugins.html#plugin-catalog). After
|
||||
the plugin is registered, it can be mounted by specifying the registered plugin name:
|
||||
|
||||
```
|
||||
$ vault mount -path=my-secrets -plugin-name=passthrough-plugin plugin
|
||||
Successfully mounted plugin 'passthrough-plugin' at 'my-secrets'!
|
||||
```
|
||||
|
||||
Listing mounts will display backends that are mounted as plugins, along with the
|
||||
name of plugin backend that is mounted:
|
||||
|
||||
```
|
||||
$ vault mounts
|
||||
Path Type Accessor Plugin Default TTL Max TTL Force No Cache Replication Behavior Description
|
||||
my-secrets/ plugin plugin_deb84140 passthrough-plugin system system false replicated
|
||||
...
|
||||
```
|
||||
|
||||
Unmounting a plugin backend is the identical to unmounting internal backends:
|
||||
|
||||
```
|
||||
$ vault unmount my-secrets
|
||||
```
|
||||
|
||||
|
@ -316,6 +316,10 @@
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-plugin") %>>
|
||||
<a href="/docs/plugin/index.html">Plugin Backends</a>
|
||||
</li>
|
||||
|
||||
<hr>
|
||||
|
||||
<li<%= sidebar_current("docs-vault-enterprise") %>>
|
||||
|
Loading…
Reference in New Issue
Block a user