mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	Backend plugin system (#2874)
* Add backend plugin changes * Fix totp backend plugin tests * Fix logical/plugin InvalidateKey test * Fix plugin catalog CRUD test, fix NoopBackend * Clean up commented code block * Fix system backend mount test * Set plugin_name to omitempty, fix handleMountTable config parsing * Clean up comments, keep shim connections alive until cleanup * Include pluginClient, disallow LookupPlugin call from within a plugin * Add wrapper around backendPluginClient for proper cleanup * Add logger shim tests * Add logger, storage, and system shim tests * Use pointer receivers for system view shim * Use plugin name if no path is provided on mount * Enable plugins for auth backends * Add backend type attribute, move builtin/plugin/package * Fix merge conflict * Fix missing plugin name in mount config * Add integration tests on enabling auth backend plugins * Remove dependency cycle on mock-plugin * Add passthrough backend plugin, use logical.BackendType to determine lease generation * Remove vault package dependency on passthrough package * Add basic impl test for passthrough plugin * Incorporate feedback; set b.backend after shims creation on backendPluginServer * Fix totp plugin test * Add plugin backends docs * Fix tests * Fix builtin/plugin tests * Remove flatten from PluginRunner fields * Move mock plugin to logical/plugin, remove totp and passthrough plugins * Move pluginMap into newPluginClient * Do not create storage RPC connection on HandleRequest and HandleExistenceCheck * Change shim logger's Fatal to no-op * Change BackendType to uint32, match UX backend types * Change framework.Backend Setup signature * Add Setup func to logical.Backend interface * Move OptionallyEnableMlock call into plugin.Serve, update docs and comments * Remove commented var in plugin package * RegisterLicense on logical.Backend interface (#3017) * Add RegisterLicense to logical.Backend interface * Update RegisterLicense to use callback func on framework.Backend * Refactor framework.Backend.RegisterLicense * plugin: Prevent plugin.SystemViewClient.ResponseWrapData from getting JWTs * plugin: Revert BackendType to remove TypePassthrough and related references * Fix typo in plugin backends docs
This commit is contained in:
		 Calvin Leung Huang
					Calvin Leung Huang
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							987616895d
						
					
				
				
					commit
					2b0f80b981
				
			| @@ -85,6 +85,7 @@ type EnableAuthOptions struct { | |||||||
| 	Type        string `json:"type" structs:"type"` | 	Type        string `json:"type" structs:"type"` | ||||||
| 	Description string `json:"description" structs:"description"` | 	Description string `json:"description" structs:"description"` | ||||||
| 	Local       bool   `json:"local" structs:"local"` | 	Local       bool   `json:"local" structs:"local"` | ||||||
|  | 	PluginName  string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AuthMount struct { | type AuthMount struct { | ||||||
| @@ -96,6 +97,7 @@ type AuthMount struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type AuthConfigOutput struct { | type AuthConfigOutput struct { | ||||||
| 	DefaultLeaseTTL int `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_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"` | 	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"` | 	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"` | 	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"` | 	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 { | type MountOutput struct { | ||||||
| @@ -141,7 +142,8 @@ type MountOutput struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type MountConfigOutput struct { | type MountConfigOutput struct { | ||||||
| 	DefaultLeaseTTL int  `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_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"` | 	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"` | 	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 { | 	if err != nil { | ||||||
| 		return nil, err | 		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 | 	var b backend | ||||||
| 	b.MapAppId = &framework.PolicyMap{ | 	b.MapAppId = &framework.PolicyMap{ | ||||||
| 		PathMap: framework.PathMap{ | 		PathMap: framework.PathMap{ | ||||||
| @@ -78,7 +81,7 @@ func Backend(conf *logical.BackendConfig) (backend, error) { | |||||||
| 	b.MapAppId.SaltFunc = b.Salt | 	b.MapAppId.SaltFunc = b.Salt | ||||||
| 	b.MapUserId.SaltFunc = b.Salt | 	b.MapUserId.SaltFunc = b.Salt | ||||||
|  |  | ||||||
| 	return b, nil | 	return &b, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| type backend struct { | type backend struct { | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestBackend_basic(t *testing.T) { | func TestBackend_basic(t *testing.T) { | ||||||
| 	var b backend | 	var b *backend | ||||||
| 	var err error | 	var err error | ||||||
| 	var storage logical.Storage | 	var storage logical.Storage | ||||||
| 	factory := func(conf *logical.BackendConfig) (logical.Backend, error) { | 	factory := func(conf *logical.BackendConfig) (logical.Backend, error) { | ||||||
| @@ -18,7 +18,10 @@ func TestBackend_basic(t *testing.T) { | |||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		storage = conf.StorageView | 		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{ | 	logicaltest.Test(t, logicaltest.TestCase{ | ||||||
| 		Factory: factory, | 		Factory: factory, | ||||||
|   | |||||||
| @@ -54,7 +54,10 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		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) { | func Backend(conf *logical.BackendConfig) (*backend, error) { | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) { | |||||||
| 	if b == nil { | 	if b == nil { | ||||||
| 		t.Fatalf("failed to create backend") | 		t.Fatalf("failed to create backend") | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Backend.Setup(config) | 	err = b.Backend.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -17,7 +17,10 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return b.Setup(conf) | 	if err := b.Setup(conf); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return b, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| type backend struct { | type backend struct { | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ func TestBackend_CreateParseVerifyRoleTag(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -253,7 +253,7 @@ func TestBackend_ConfigTidyIdentities(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -307,7 +307,7 @@ func TestBackend_ConfigTidyRoleTags(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -361,7 +361,7 @@ func TestBackend_TidyIdentities(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -386,7 +386,7 @@ func TestBackend_TidyRoleTags(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -411,7 +411,7 @@ func TestBackend_ConfigClient(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -548,7 +548,7 @@ func TestBackend_pathConfigCertificate(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -703,7 +703,7 @@ func TestBackend_parseAndVerifyRoleTagValue(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -784,7 +784,7 @@ func TestBackend_PathRoleTag(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -849,7 +849,7 @@ func TestBackend_PathBlacklistRoleTag(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -997,7 +997,7 @@ func TestBackendAcc_LoginWithInstanceIdentityDocAndWhitelistIdentity(t *testing. | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -1177,7 +1177,7 @@ func TestBackend_pathStsConfig(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -1325,7 +1325,7 @@ func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ func TestBackend_pathConfigClient(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ func TestBackend_pathRoleEc2(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -146,7 +146,7 @@ func Test_enableIamIDResolution(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -221,7 +221,7 @@ func TestBackend_pathIam(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -385,7 +385,7 @@ func TestBackend_pathRoleMixedTypes(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -491,7 +491,7 @@ func TestAwsEc2_RoleCrud(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -617,7 +617,7 @@ func TestAwsEc2_RoleDurationSeconds(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -10,9 +10,8 @@ import ( | |||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | ||||||
| 	b := Backend() | 	b := Backend() | ||||||
| 	_, err := b.Setup(conf) | 	if err := b.Setup(conf); err != nil { | ||||||
| 	if err != nil { | 		return nil, err | ||||||
| 		return b, err |  | ||||||
| 	} | 	} | ||||||
| 	return b, nil | 	return b, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,7 +11,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend() *backend { | ||||||
|   | |||||||
| @@ -13,7 +13,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend() *backend { | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) { | |||||||
| 		t.Fatalf("failed to create backend") | 		t.Fatalf("failed to create backend") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	_, err := b.Backend.Setup(config) | 	err := b.Backend.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -8,7 +8,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend() *backend { | ||||||
|   | |||||||
| @@ -7,7 +7,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend() *backend { | ||||||
|   | |||||||
| @@ -7,7 +7,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend() *backend { | ||||||
|   | |||||||
| @@ -9,7 +9,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend() *backend { | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ func TestBackend_PathListRoles(t *testing.T) { | |||||||
| 	config.StorageView = &logical.InmemStorage{} | 	config.StorageView = &logical.InmemStorage{} | ||||||
|  |  | ||||||
| 	b := Backend() | 	b := Backend() | ||||||
| 	if _, err := b.Setup(config); err != nil { | 	if err := b.Setup(config); err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,11 @@ import ( | |||||||
|  |  | ||||||
| // Factory creates a new backend | // Factory creates a new backend | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 | // Backend contains the base information for the backend's functionality | ||||||
|   | |||||||
| @@ -6,7 +6,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend() *backend { | ||||||
|   | |||||||
| @@ -16,7 +16,11 @@ import ( | |||||||
| const databaseConfigPath = "database/config/" | const databaseConfigPath = "database/config/" | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend(conf *logical.BackendConfig) *databaseBackend { | ||||||
|   | |||||||
| @@ -12,7 +12,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend() *framework.Backend { | ||||||
|   | |||||||
| @@ -12,7 +12,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend() *backend { | ||||||
|   | |||||||
| @@ -12,7 +12,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend() *backend { | ||||||
|   | |||||||
| @@ -11,7 +11,11 @@ import ( | |||||||
|  |  | ||||||
| // Factory creates a new backend implementing the logical.Backend interface | // Factory creates a new backend implementing the logical.Backend interface | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 | // Backend returns a new Backend framework struct | ||||||
|   | |||||||
| @@ -1870,7 +1870,7 @@ func TestBackend_PathFetchCertList(t *testing.T) { | |||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  |  | ||||||
| 	b := Backend() | 	b := Backend() | ||||||
| 	_, err := b.Setup(config) | 	err := b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -1997,7 +1997,7 @@ func TestBackend_SignVerbatim(t *testing.T) { | |||||||
| 	config.StorageView = storage | 	config.StorageView = storage | ||||||
|  |  | ||||||
| 	b := Backend() | 	b := Backend() | ||||||
| 	_, err := b.Setup(config) | 	err := b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) { | |||||||
|  |  | ||||||
| 	var err error | 	var err error | ||||||
| 	b := Backend() | 	b := Backend() | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -13,7 +13,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 { | func Backend(conf *logical.BackendConfig) *backend { | ||||||
|   | |||||||
| @@ -13,7 +13,11 @@ import ( | |||||||
|  |  | ||||||
| // Factory creates and configures the backend | // Factory creates and configures the backend | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 | // 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 := logical.TestBackendConfig() | ||||||
| 	config.StorageView = &logical.InmemStorage{} | 	config.StorageView = &logical.InmemStorage{} | ||||||
| 	b := Backend() | 	b := Backend() | ||||||
| 	if _, err = b.Setup(config); err != nil { | 	if err = b.Setup(config); err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,7 +21,10 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		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) { | func Backend(conf *logical.BackendConfig) (*backend, error) { | ||||||
|   | |||||||
| @@ -106,7 +106,7 @@ func TestBackend_allowed_users(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ func TestSSH_ConfigCAStorageUpgrade(t *testing.T) { | |||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	_, err = b.Setup(config) | 	err = b.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -10,10 +10,14 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | 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 | 	var b backend | ||||||
| 	b.Backend = &framework.Backend{ | 	b.Backend = &framework.Backend{ | ||||||
| 		Help: strings.TrimSpace(backendHelp), | 		Help: strings.TrimSpace(backendHelp), | ||||||
|   | |||||||
| @@ -10,12 +10,10 @@ import ( | |||||||
|  |  | ||||||
| func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | func Factory(conf *logical.BackendConfig) (logical.Backend, error) { | ||||||
| 	b := Backend(conf) | 	b := Backend(conf) | ||||||
| 	be, err := b.Backend.Setup(conf) | 	if err := b.Setup(conf); err != nil { | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | 	return b, nil | ||||||
| 	return be, nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func Backend(conf *logical.BackendConfig) *backend { | func Backend(conf *logical.BackendConfig) *backend { | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) { | |||||||
| 	if b == nil { | 	if b == nil { | ||||||
| 		t.Fatalf("failed to create backend") | 		t.Fatalf("failed to create backend") | ||||||
| 	} | 	} | ||||||
| 	_, err := b.Backend.Setup(config) | 	err := b.Backend.Setup(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		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/ssh" | ||||||
| 	"github.com/hashicorp/vault/builtin/logical/totp" | 	"github.com/hashicorp/vault/builtin/logical/totp" | ||||||
| 	"github.com/hashicorp/vault/builtin/logical/transit" | 	"github.com/hashicorp/vault/builtin/logical/transit" | ||||||
|  | 	"github.com/hashicorp/vault/builtin/plugin" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/vault/audit" | 	"github.com/hashicorp/vault/audit" | ||||||
| 	"github.com/hashicorp/vault/command" | 	"github.com/hashicorp/vault/command" | ||||||
| @@ -79,6 +80,7 @@ func Commands(metaPtr *meta.Meta) map[string]cli.CommandFactory { | |||||||
| 					"ldap":     credLdap.Factory, | 					"ldap":     credLdap.Factory, | ||||||
| 					"okta":     credOkta.Factory, | 					"okta":     credOkta.Factory, | ||||||
| 					"radius":   credRadius.Factory, | 					"radius":   credRadius.Factory, | ||||||
|  | 					"plugin":   plugin.Factory, | ||||||
| 				}, | 				}, | ||||||
| 				LogicalBackends: map[string]logical.Factory{ | 				LogicalBackends: map[string]logical.Factory{ | ||||||
| 					"aws":        aws.Factory, | 					"aws":        aws.Factory, | ||||||
| @@ -94,6 +96,7 @@ func Commands(metaPtr *meta.Meta) map[string]cli.CommandFactory { | |||||||
| 					"rabbitmq":   rabbitmq.Factory, | 					"rabbitmq":   rabbitmq.Factory, | ||||||
| 					"database":   database.Factory, | 					"database":   database.Factory, | ||||||
| 					"totp":       totp.Factory, | 					"totp":       totp.Factory, | ||||||
|  | 					"plugin":     plugin.Factory, | ||||||
| 				}, | 				}, | ||||||
| 				ShutdownCh: command.MakeShutdownCh(), | 				ShutdownCh: command.MakeShutdownCh(), | ||||||
| 				SighupCh:   command.MakeSighupCh(), | 				SighupCh:   command.MakeSighupCh(), | ||||||
|   | |||||||
| @@ -14,11 +14,12 @@ type AuthEnableCommand struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *AuthEnableCommand) Run(args []string) int { | func (c *AuthEnableCommand) Run(args []string) int { | ||||||
| 	var description, path string | 	var description, path, pluginName string | ||||||
| 	var local bool | 	var local bool | ||||||
| 	flags := c.Meta.FlagSet("auth-enable", meta.FlagSetDefault) | 	flags := c.Meta.FlagSet("auth-enable", meta.FlagSetDefault) | ||||||
| 	flags.StringVar(&description, "description", "", "") | 	flags.StringVar(&description, "description", "", "") | ||||||
| 	flags.StringVar(&path, "path", "", "") | 	flags.StringVar(&path, "path", "", "") | ||||||
|  | 	flags.StringVar(&pluginName, "plugin-name", "", "") | ||||||
| 	flags.BoolVar(&local, "local", false, "") | 	flags.BoolVar(&local, "local", false, "") | ||||||
| 	flags.Usage = func() { c.Ui.Error(c.Help()) } | 	flags.Usage = func() { c.Ui.Error(c.Help()) } | ||||||
| 	if err := flags.Parse(args); err != nil { | 	if err := flags.Parse(args); err != nil { | ||||||
| @@ -36,8 +37,13 @@ func (c *AuthEnableCommand) Run(args []string) int { | |||||||
| 	authType := args[0] | 	authType := args[0] | ||||||
|  |  | ||||||
| 	// If no path is specified, we default the path to the backend type | 	// 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 == "" { | 	if path == "" { | ||||||
| 		path = authType | 		if authType == "plugin" { | ||||||
|  | 			path = pluginName | ||||||
|  | 		} else { | ||||||
|  | 			path = authType | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	client, err := c.Client() | 	client, err := c.Client() | ||||||
| @@ -50,6 +56,7 @@ func (c *AuthEnableCommand) Run(args []string) int { | |||||||
| 	if err := client.Sys().EnableAuthWithOptions(path, &api.EnableAuthOptions{ | 	if err := client.Sys().EnableAuthWithOptions(path, &api.EnableAuthOptions{ | ||||||
| 		Type:        authType, | 		Type:        authType, | ||||||
| 		Description: description, | 		Description: description, | ||||||
|  | 		PluginName:  pluginName, | ||||||
| 		Local:       local, | 		Local:       local, | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
| 		c.Ui.Error(fmt.Sprintf( | 		c.Ui.Error(fmt.Sprintf( | ||||||
| @@ -89,6 +96,9 @@ Auth Enable Options: | |||||||
|                           to the type of the mount. This will make the auth |                           to the type of the mount. This will make the auth | ||||||
|                           provider available at "/auth/<path>" |                           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 |   -local                  Mark the mount as a local mount. Local mounts | ||||||
|                           are not replicated nor (if a secondary) |                           are not replicated nor (if a secondary) | ||||||
|                           removed by replication. |                           removed by replication. | ||||||
|   | |||||||
| @@ -14,13 +14,14 @@ type MountCommand struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *MountCommand) Run(args []string) int { | func (c *MountCommand) Run(args []string) int { | ||||||
| 	var description, path, defaultLeaseTTL, maxLeaseTTL string | 	var description, path, defaultLeaseTTL, maxLeaseTTL, pluginName string | ||||||
| 	var local, forceNoCache bool | 	var local, forceNoCache bool | ||||||
| 	flags := c.Meta.FlagSet("mount", meta.FlagSetDefault) | 	flags := c.Meta.FlagSet("mount", meta.FlagSetDefault) | ||||||
| 	flags.StringVar(&description, "description", "", "") | 	flags.StringVar(&description, "description", "", "") | ||||||
| 	flags.StringVar(&path, "path", "", "") | 	flags.StringVar(&path, "path", "", "") | ||||||
| 	flags.StringVar(&defaultLeaseTTL, "default-lease-ttl", "", "") | 	flags.StringVar(&defaultLeaseTTL, "default-lease-ttl", "", "") | ||||||
| 	flags.StringVar(&maxLeaseTTL, "max-lease-ttl", "", "") | 	flags.StringVar(&maxLeaseTTL, "max-lease-ttl", "", "") | ||||||
|  | 	flags.StringVar(&pluginName, "plugin-name", "", "") | ||||||
| 	flags.BoolVar(&forceNoCache, "force-no-cache", false, "") | 	flags.BoolVar(&forceNoCache, "force-no-cache", false, "") | ||||||
| 	flags.BoolVar(&local, "local", false, "") | 	flags.BoolVar(&local, "local", false, "") | ||||||
| 	flags.Usage = func() { c.Ui.Error(c.Help()) } | 	flags.Usage = func() { c.Ui.Error(c.Help()) } | ||||||
| @@ -39,8 +40,13 @@ func (c *MountCommand) Run(args []string) int { | |||||||
| 	mountType := args[0] | 	mountType := args[0] | ||||||
|  |  | ||||||
| 	// If no path is specified, we default the path to the backend type | 	// 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 == "" { | 	if path == "" { | ||||||
| 		path = mountType | 		if mountType == "plugin" { | ||||||
|  | 			path = pluginName | ||||||
|  | 		} else { | ||||||
|  | 			path = mountType | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	client, err := c.Client() | 	client, err := c.Client() | ||||||
| @@ -57,6 +63,7 @@ func (c *MountCommand) Run(args []string) int { | |||||||
| 			DefaultLeaseTTL: defaultLeaseTTL, | 			DefaultLeaseTTL: defaultLeaseTTL, | ||||||
| 			MaxLeaseTTL:     maxLeaseTTL, | 			MaxLeaseTTL:     maxLeaseTTL, | ||||||
| 			ForceNoCache:    forceNoCache, | 			ForceNoCache:    forceNoCache, | ||||||
|  | 			PluginName:      pluginName, | ||||||
| 		}, | 		}, | ||||||
| 		Local: local, | 		Local: local, | ||||||
| 	} | 	} | ||||||
| @@ -67,9 +74,14 @@ func (c *MountCommand) Run(args []string) int { | |||||||
| 		return 2 | 		return 2 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	mountPart := fmt.Sprintf("'%s'", mountType) | ||||||
|  | 	if mountType == "plugin" { | ||||||
|  | 		mountPart = fmt.Sprintf("plugin '%s'", pluginName) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	c.Ui.Output(fmt.Sprintf( | 	c.Ui.Output(fmt.Sprintf( | ||||||
| 		"Successfully mounted '%s' at '%s'!", | 		"Successfully mounted %s at '%s'!", | ||||||
| 		mountType, path)) | 		mountPart, path)) | ||||||
|  |  | ||||||
| 	return 0 | 	return 0 | ||||||
| } | } | ||||||
| @@ -112,10 +124,12 @@ Mount Options: | |||||||
|                                  not affect caching of the underlying encrypted |                                  not affect caching of the underlying encrypted | ||||||
|                                  data storage. |                                  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 |   -local                         Mark the mount as a local mount. Local mounts | ||||||
|                                  are not replicated nor (if a secondary) |                                  are not replicated nor (if a secondary) | ||||||
|                                  removed by replication. |                                  removed by replication. | ||||||
|  |  | ||||||
| ` | ` | ||||||
| 	return strings.TrimSpace(helpText) | 	return strings.TrimSpace(helpText) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -42,9 +42,13 @@ func (c *MountsCommand) Run(args []string) int { | |||||||
| 	} | 	} | ||||||
| 	sort.Strings(paths) | 	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 { | 	for _, path := range paths { | ||||||
| 		mount := mounts[path] | 		mount := mounts[path] | ||||||
|  | 		pluginName := "n/a" | ||||||
|  | 		if mount.Config.PluginName != "" { | ||||||
|  | 			pluginName = mount.Config.PluginName | ||||||
|  | 		} | ||||||
| 		defTTL := "system" | 		defTTL := "system" | ||||||
| 		switch { | 		switch { | ||||||
| 		case mount.Type == "system": | 		case mount.Type == "system": | ||||||
| @@ -68,7 +72,7 @@ func (c *MountsCommand) Run(args []string) int { | |||||||
| 			replicatedBehavior = "local" | 			replicatedBehavior = "local" | ||||||
| 		} | 		} | ||||||
| 		columns = append(columns, fmt.Sprintf( | 		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)) | 			mount.Config.ForceNoCache, replicatedBehavior, mount.Description)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -199,8 +199,8 @@ func TestRekey_init_pgp(t *testing.T) { | |||||||
| 			MaxLeaseTTLVal:     time.Hour * 24 * 32, | 			MaxLeaseTTLVal:     time.Hour * 24 * 32, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	sysBE := vault.NewSystemBackend(core) | 	sysBackend := vault.NewSystemBackend(core) | ||||||
| 	sysBackend, err := sysBE.Backend.Setup(bc) | 	err := sysBackend.Backend.Setup(bc) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -9,9 +9,11 @@ import ( | |||||||
| 	"github.com/hashicorp/vault/plugins/database/postgresql" | 	"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) | 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 | 	// These four plugins all use the same mysql implementation but with | ||||||
| 	// different username settings passed by the constructor. | 	// different username settings passed by the constructor. | ||||||
| 	"mysql-database-plugin":        mysql.New(mysql.MetadataLen, mysql.UsernameLen), | 	"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, | 	"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) { | func Get(name string) (BuiltinFactory, bool) { | ||||||
| 	f, ok := plugins[name] | 	f, ok := plugins[name] | ||||||
| 	return f, ok | 	return f, ok | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Keys returns the list of plugin names that are considered builtin plugins. | ||||||
| func Keys() []string { | func Keys() []string { | ||||||
| 	keys := make([]string, len(plugins)) | 	keys := make([]string, len(plugins)) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -35,12 +35,12 @@ type LookRunnerUtil interface { | |||||||
| // PluginRunner defines the metadata needed to run a plugin securely with | // PluginRunner defines the metadata needed to run a plugin securely with | ||||||
| // go-plugin. | // go-plugin. | ||||||
| type PluginRunner struct { | type PluginRunner struct { | ||||||
| 	Name           string                      `json:"name"` | 	Name           string                      `json:"name" structs:"name"` | ||||||
| 	Command        string                      `json:"command"` | 	Command        string                      `json:"command" structs:"command"` | ||||||
| 	Args           []string                    `json:"args"` | 	Args           []string                    `json:"args" structs:"args"` | ||||||
| 	Sha256         []byte                      `json:"sha256"` | 	Sha256         []byte                      `json:"sha256" structs:"sha256"` | ||||||
| 	Builtin        bool                        `json:"builtin"` | 	Builtin        bool                        `json:"builtin" structs:"builtin"` | ||||||
| 	BuiltinFactory func() (interface{}, error) `json:"-"` | 	BuiltinFactory func() (interface{}, error) `json:"-" structs:"-"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // Run takes a wrapper instance, and the go-plugin paramaters and executes a | // 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 | 		// Parse the JWT and retrieve the vault address | ||||||
| 		wt, err := jws.ParseJWT([]byte(unwrapToken)) | 		wt, err := jws.ParseJWT([]byte(unwrapToken)) | ||||||
| 		if err != nil { | 		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 { | 		if wt == nil { | ||||||
| 			return nil, errors.New("nil decoded token") | 			return nil, errors.New("nil decoded token") | ||||||
| @@ -146,7 +146,7 @@ func VaultPluginTLSProvider(apiTLSConfig *api.TLSConfig) func() (*tls.Config, er | |||||||
|  |  | ||||||
| 		// Sanity check the value | 		// Sanity check the value | ||||||
| 		if _, err := url.Parse(vaultAddr); err != nil { | 		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 | 		// 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) | 			return nil, errwrap.Wrapf("error during token unwrap request: {{err}}", err) | ||||||
| 		} | 		} | ||||||
| 		if secret == nil { | 		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 | 		// 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. | 	// See the built-in AuthRenew helpers in lease.go for common callbacks. | ||||||
| 	AuthRenew OperationFunc | 	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 | 	logger  log.Logger | ||||||
| 	system  logical.SystemView | 	system  logical.SystemView | ||||||
| 	once    sync.Once | 	once    sync.Once | ||||||
| @@ -107,6 +113,10 @@ type InitializeFunc func() error | |||||||
| // InvalidateFunc is the callback for backend key invalidation. | // InvalidateFunc is the callback for backend key invalidation. | ||||||
| type InvalidateFunc func(string) | 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) { | func (b *Backend) HandleExistenceCheck(req *logical.Request) (checkFound bool, exists bool, err error) { | ||||||
| 	b.once.Do(b.init) | 	b.once.Do(b.init) | ||||||
|  |  | ||||||
| @@ -154,7 +164,7 @@ func (b *Backend) HandleExistenceCheck(req *logical.Request) (checkFound bool, e | |||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| // logical.Backend impl. | // HandleRequest is the logical.Backend implementation. | ||||||
| func (b *Backend) HandleRequest(req *logical.Request) (*logical.Response, error) { | func (b *Backend) HandleRequest(req *logical.Request) (*logical.Response, error) { | ||||||
| 	b.once.Do(b.init) | 	b.once.Do(b.init) | ||||||
|  |  | ||||||
| @@ -221,18 +231,11 @@ func (b *Backend) HandleRequest(req *logical.Request) (*logical.Response, error) | |||||||
| 	return callback(req, &fd) | 	return callback(req, &fd) | ||||||
| } | } | ||||||
|  |  | ||||||
| // logical.Backend impl. | // SpecialPaths is the logical.Backend implementation. | ||||||
| func (b *Backend) SpecialPaths() *logical.Paths { | func (b *Backend) SpecialPaths() *logical.Paths { | ||||||
| 	return b.PathsSpecial | 	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 | // Cleanup is used to release resources and prepare to stop the backend | ||||||
| func (b *Backend) Cleanup() { | func (b *Backend) Cleanup() { | ||||||
| 	if b.Clean != nil { | 	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 { | func (b *Backend) Initialize() error { | ||||||
| 	if b.Init != nil { | 	if b.Init != nil { | ||||||
| 		return b.Init() | 		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, | // Logger can be used to get the logger. If no logger has been set, | ||||||
| // the logs will be discarded. | // the logs will be discarded. | ||||||
| func (b *Backend) Logger() log.Logger { | func (b *Backend) Logger() log.Logger { | ||||||
| @@ -265,11 +276,25 @@ func (b *Backend) Logger() log.Logger { | |||||||
| 	return logformat.NewVaultLoggerWithWriter(ioutil.Discard, log.LevelOff) | 	return logformat.NewVaultLoggerWithWriter(ioutil.Discard, log.LevelOff) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // System returns the backend's system view. | ||||||
| func (b *Backend) System() logical.SystemView { | func (b *Backend) System() logical.SystemView { | ||||||
| 	return b.system | 	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 | // 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 | // set, which will cause initial secret or LeaseExtend operations to use the | ||||||
| // mount/system defaults.  If they are set, their boundaries are validated. | // 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 | 	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) { | func (b *Backend) SanitizeTTL(ttl, maxTTL time.Duration) (time.Duration, time.Duration, error) { | ||||||
| 	sysMaxTTL := b.System().MaxLeaseTTL() | 	sysMaxTTL := b.System().MaxLeaseTTL() | ||||||
| 	if ttl > sysMaxTTL { | 	if ttl > sysMaxTTL { | ||||||
| @@ -575,6 +601,7 @@ func (s *FieldSchema) DefaultOrZero() interface{} { | |||||||
| 	return s.Type.Zero() | 	return s.Type.Zero() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Zero returns the correct zero-value for a specific FieldType | ||||||
| func (t FieldType) Zero() interface{} { | func (t FieldType) Zero() interface{} { | ||||||
| 	switch t { | 	switch t { | ||||||
| 	case TypeString: | 	case TypeString: | ||||||
|   | |||||||
| @@ -2,6 +2,29 @@ package logical | |||||||
|  |  | ||||||
| import log "github.com/mgutz/logxi/v1" | 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 | // Backend interface must be implemented to be "mountable" at | ||||||
| // a given path. Requests flow through a router which has various mount | // 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, | // 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. | 	// information, such as globally configured default and max lease TTLs. | ||||||
| 	System() SystemView | 	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 | 	// HandleExistenceCheck is used to handle a request and generate a response | ||||||
| 	// indicating whether the given path exists or not; this is used to | 	// indicating whether the given path exists or not; this is used to | ||||||
| 	// understand whether the request must have a Create or Update capability | 	// 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 | 	// to the backend. The backend can use this to clear any caches or reset | ||||||
| 	// internal state as needed. | 	// internal state as needed. | ||||||
| 	InvalidateKey(key string) | 	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 | // 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 + "/" | 	viewPath := credentialBarrierPrefix + entry.UUID + "/" | ||||||
| 	view := NewBarrierView(c.barrier, viewPath) | 	view := NewBarrierView(c.barrier, viewPath) | ||||||
| 	sysView := c.mountEntrySysView(entry) | 	sysView := c.mountEntrySysView(entry) | ||||||
|  | 	conf := make(map[string]string) | ||||||
|  | 	if entry.Config.PluginName != "" { | ||||||
|  | 		conf["plugin_name"] = entry.Config.PluginName | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Create the new backend | 	// 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 { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -105,6 +109,12 @@ func (c *Core) enableCredential(entry *MountEntry) error { | |||||||
| 		return fmt.Errorf("nil backend returned from %q factory", entry.Type) | 		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 { | 	if err := backend.Initialize(); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -406,9 +416,13 @@ func (c *Core) setupCredentials() error { | |||||||
| 		viewPath := credentialBarrierPrefix + entry.UUID + "/" | 		viewPath := credentialBarrierPrefix + entry.UUID + "/" | ||||||
| 		view = NewBarrierView(c.barrier, viewPath) | 		view = NewBarrierView(c.barrier, viewPath) | ||||||
| 		sysView := c.mountEntrySysView(entry) | 		sysView := c.mountEntrySysView(entry) | ||||||
|  | 		conf := make(map[string]string) | ||||||
|  | 		if entry.Config.PluginName != "" { | ||||||
|  | 			conf["plugin_name"] = entry.Config.PluginName | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// Initialize the backend | 		// Initialize the backend | ||||||
| 		backend, err = c.newCredentialBackend(entry.Type, sysView, view, nil) | 		backend, err = c.newCredentialBackend(entry.Type, sysView, view, conf) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			c.logger.Error("core: failed to create credential entry", "path", entry.Path, "error", err) | 			c.logger.Error("core: failed to create credential entry", "path", entry.Path, "error", err) | ||||||
| 			return errLoadAuthFailed | 			return errLoadAuthFailed | ||||||
| @@ -417,6 +431,12 @@ func (c *Core) setupCredentials() error { | |||||||
| 			return fmt.Errorf("nil backend returned from %q factory", entry.Type) | 			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 { | 		if err := backend.Initialize(); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -516,7 +516,10 @@ func NewCore(conf *CoreConfig) (*Core, error) { | |||||||
| 	logicalBackends["cubbyhole"] = CubbyholeBackendFactory | 	logicalBackends["cubbyhole"] = CubbyholeBackendFactory | ||||||
| 	logicalBackends["system"] = func(config *logical.BackendConfig) (logical.Backend, error) { | 	logicalBackends["system"] = func(config *logical.BackendConfig) (logical.Backend, error) { | ||||||
| 		b := NewSystemBackend(c) | 		b := NewSystemBackend(c) | ||||||
| 		return b.Backend.Setup(config) | 		if err := b.Setup(config); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return b, nil | ||||||
| 	} | 	} | ||||||
| 	c.logicalBackends = logicalBackends | 	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) | 	return LeaseSwitchedPassthroughBackend(conf, false) | ||||||
| } | } | ||||||
|  |  | ||||||
| // PassthroughBackendWithLeasesFactory returns a PassthroughBackend | // LeasedPassthroughBackendFactory returns a PassthroughBackend | ||||||
| // with leases switched on | // with leases switched on | ||||||
| func LeasedPassthroughBackendFactory(conf *logical.BackendConfig) (logical.Backend, error) { | func LeasedPassthroughBackendFactory(conf *logical.BackendConfig) (logical.Backend, error) { | ||||||
| 	return LeaseSwitchedPassthroughBackend(conf, true) | 	return LeaseSwitchedPassthroughBackend(conf, true) | ||||||
| } | } | ||||||
|  |  | ||||||
| // LeaseSwitchedPassthroughBackendFactory returns a PassthroughBackend | // LeaseSwitchedPassthroughBackend returns a PassthroughBackend | ||||||
| // with leases switched on or off | // with leases switched on or off | ||||||
| func LeaseSwitchedPassthroughBackend(conf *logical.BackendConfig, leases bool) (logical.Backend, error) { | func LeaseSwitchedPassthroughBackend(conf *logical.BackendConfig, leases bool) (logical.Backend, error) { | ||||||
| 	var b PassthroughBackend | 	var b PassthroughBackend | ||||||
| @@ -147,6 +147,10 @@ func (b *PassthroughBackend) handleRead( | |||||||
| 	return resp, nil | 	return resp, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (b *PassthroughBackend) GeneratesLeases() bool { | ||||||
|  | 	return b.generateLeases | ||||||
|  | } | ||||||
|  |  | ||||||
| func (b *PassthroughBackend) handleWrite( | func (b *PassthroughBackend) handleWrite( | ||||||
| 	req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | 	req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||||||
| 	// Check that some fields are given | 	// Check that some fields are given | ||||||
| @@ -202,10 +206,6 @@ func (b *PassthroughBackend) handleList( | |||||||
| 	return logical.ListResponse(keys), nil | 	return logical.ListResponse(keys), nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *PassthroughBackend) GeneratesLeases() bool { |  | ||||||
| 	return b.generateLeases |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const passthroughHelp = ` | const passthroughHelp = ` | ||||||
| The generic backend reads and writes arbitrary secrets to the backend. | The generic backend reads and writes arbitrary secrets to the backend. | ||||||
| The secrets are encrypted/decrypted by Vault: they are never stored | The secrets are encrypted/decrypted by Vault: they are never stored | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import ( | |||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/fatih/structs" | ||||||
| 	"github.com/hashicorp/vault/helper/consts" | 	"github.com/hashicorp/vault/helper/consts" | ||||||
| 	"github.com/hashicorp/vault/helper/parseutil" | 	"github.com/hashicorp/vault/helper/parseutil" | ||||||
| 	"github.com/hashicorp/vault/helper/wrapping" | 	"github.com/hashicorp/vault/helper/wrapping" | ||||||
| @@ -486,6 +487,10 @@ func NewSystemBackend(core *Core) *SystemBackend { | |||||||
| 						Type:        framework.TypeString, | 						Type:        framework.TypeString, | ||||||
| 						Description: strings.TrimSpace(sysHelp["auth_desc"][0]), | 						Description: strings.TrimSpace(sysHelp["auth_desc"][0]), | ||||||
| 					}, | 					}, | ||||||
|  | 					"plugin_name": &framework.FieldSchema{ | ||||||
|  | 						Type:        framework.TypeString, | ||||||
|  | 						Description: strings.TrimSpace(sysHelp["auth_plugin"][0]), | ||||||
|  | 					}, | ||||||
| 					"local": &framework.FieldSchema{ | 					"local": &framework.FieldSchema{ | ||||||
| 						Type:        framework.TypeBool, | 						Type:        framework.TypeBool, | ||||||
| 						Default:     false, | 						Default:     false, | ||||||
| @@ -775,7 +780,7 @@ func NewSystemBackend(core *Core) *SystemBackend { | |||||||
| 				HelpDescription: strings.TrimSpace(sysHelp["audited-headers"][1]), | 				HelpDescription: strings.TrimSpace(sysHelp["audited-headers"][1]), | ||||||
| 			}, | 			}, | ||||||
| 			&framework.Path{ | 			&framework.Path{ | ||||||
| 				Pattern: "plugins/catalog/$", | 				Pattern: "plugins/catalog/?$", | ||||||
|  |  | ||||||
| 				Fields: map[string]*framework.FieldSchema{}, | 				Fields: map[string]*framework.FieldSchema{}, | ||||||
|  |  | ||||||
| @@ -792,18 +797,15 @@ func NewSystemBackend(core *Core) *SystemBackend { | |||||||
| 				Fields: map[string]*framework.FieldSchema{ | 				Fields: map[string]*framework.FieldSchema{ | ||||||
| 					"name": &framework.FieldSchema{ | 					"name": &framework.FieldSchema{ | ||||||
| 						Type:        framework.TypeString, | 						Type:        framework.TypeString, | ||||||
| 						Description: "The name of the plugin", | 						Description: strings.TrimSpace(sysHelp["plugin-catalog_name"][0]), | ||||||
| 					}, | 					}, | ||||||
| 					"sha_256": &framework.FieldSchema{ | 					"sha_256": &framework.FieldSchema{ | ||||||
| 						Type: framework.TypeString, | 						Type:        framework.TypeString, | ||||||
| 						Description: `The SHA256 sum of the executable used in the | 						Description: strings.TrimSpace(sysHelp["plugin-catalog_sha-256"][0]), | ||||||
| 						command field. This should be HEX encoded.`, |  | ||||||
| 					}, | 					}, | ||||||
| 					"command": &framework.FieldSchema{ | 					"command": &framework.FieldSchema{ | ||||||
| 						Type: framework.TypeString, | 						Type:        framework.TypeString, | ||||||
| 						Description: `The command used to start the plugin. The | 						Description: strings.TrimSpace(sysHelp["plugin-catalog_command"][0]), | ||||||
| 						executable defined in this command must exist in vault's |  | ||||||
| 						plugin directory.`, |  | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
|  |  | ||||||
| @@ -943,10 +945,11 @@ func (b *SystemBackend) handlePluginCatalogRead(req *logical.Request, d *framewo | |||||||
| 		return nil, nil | 		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{ | 	return &logical.Response{ | ||||||
| 		Data: map[string]interface{}{ | 		Data: data, | ||||||
| 			"plugin": plugin, |  | ||||||
| 		}, |  | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1157,18 +1160,17 @@ func (b *SystemBackend) handleMountTable( | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, entry := range b.Core.mounts.Entries { | 	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{}{ | 		info := map[string]interface{}{ | ||||||
| 			"type":        entry.Type, | 			"type":        entry.Type, | ||||||
| 			"description": entry.Description, | 			"description": entry.Description, | ||||||
| 			"accessor":    entry.Accessor, | 			"accessor":    entry.Accessor, | ||||||
| 			"config": map[string]interface{}{ | 			"config":      structConfig, | ||||||
| 				"default_lease_ttl": int64(entry.Config.DefaultLeaseTTL.Seconds()), | 			"local":       entry.Local, | ||||||
| 				"max_lease_ttl":     int64(entry.Config.MaxLeaseTTL.Seconds()), |  | ||||||
| 				"force_no_cache":    entry.Config.ForceNoCache, |  | ||||||
| 			}, |  | ||||||
| 			"local": entry.Local, |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		resp.Data[entry.Path] = info | 		resp.Data[entry.Path] = info | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -1195,12 +1197,8 @@ func (b *SystemBackend) handleMount( | |||||||
| 	path = sanitizeMountPath(path) | 	path = sanitizeMountPath(path) | ||||||
|  |  | ||||||
| 	var config MountConfig | 	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{}) | 	configMap := data.Get("config").(map[string]interface{}) | ||||||
| 	if configMap != nil && len(configMap) != 0 { | 	if configMap != nil && len(configMap) != 0 { | ||||||
| 		err := mapstructure.Decode(configMap, &apiConfig) | 		err := mapstructure.Decode(configMap, &apiConfig) | ||||||
| @@ -1249,6 +1247,11 @@ func (b *SystemBackend) handleMount( | |||||||
| 			logical.ErrInvalidRequest | 			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 | 	// Copy over the force no cache if set | ||||||
| 	if apiConfig.ForceNoCache { | 	if apiConfig.ForceNoCache { | ||||||
| 		config.ForceNoCache = true | 		config.ForceNoCache = true | ||||||
| @@ -1685,6 +1688,14 @@ func (b *SystemBackend) handleEnableAuth( | |||||||
| 	path := data.Get("path").(string) | 	path := data.Get("path").(string) | ||||||
| 	logicalType := data.Get("type").(string) | 	logicalType := data.Get("type").(string) | ||||||
| 	description := data.Get("description").(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 == "" { | 	if logicalType == "" { | ||||||
| 		return logical.ErrorResponse( | 		return logical.ErrorResponse( | ||||||
| @@ -1700,6 +1711,7 @@ func (b *SystemBackend) handleEnableAuth( | |||||||
| 		Path:        path, | 		Path:        path, | ||||||
| 		Type:        logicalType, | 		Type:        logicalType, | ||||||
| 		Description: description, | 		Description: description, | ||||||
|  | 		Config:      config, | ||||||
| 		Local:       local, | 		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": { | 	"policy-list": { | ||||||
| 		`List the configured access control policies.`, | 		`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.", | 		"Lists the headers configured to be audited.", | ||||||
| 		`Returns a list of headers that have been configured to be audited.`, | 		`Returns a list of headers that have been configured to be audited.`, | ||||||
| 	}, | 	}, | ||||||
| 	"plugins/catalog": { | 	"plugin-catalog": { | ||||||
| 		`Configures the plugins known to vault`, | 		"Configures the plugins known to vault", | ||||||
| 		` | 		` | ||||||
| This path responds to the following HTTP methods. | This path responds to the following HTTP methods. | ||||||
|     LIST / | 		LIST / | ||||||
|         Returns a list of names of configured plugins. | 			Returns a list of names of configured plugins. | ||||||
|  |  | ||||||
|     GET /<name> | 		GET /<name> | ||||||
|         Retrieve the metadata for the named plugin. | 			Retrieve the metadata for the named plugin. | ||||||
|  |  | ||||||
|     PUT /<name> | 		PUT /<name> | ||||||
|         Add or update plugin. | 			Add or update plugin. | ||||||
|  |  | ||||||
|     DELETE /<name> | 		DELETE /<name> | ||||||
|         Delete the plugin with the given 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": { | 	"leases": { | ||||||
| 		`View or list lease metadata.`, | 		`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, | 			MaxLeaseTTLVal:     time.Hour * 24 * 32, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	be := NewSystemBackend(core) | 	b := NewSystemBackend(core) | ||||||
| 	b, err := be.Backend.Setup(bc) | 	err := b.Backend.Setup(bc) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -1049,8 +1049,8 @@ func TestSystemBackend_revokePrefixAuth_origUrl(t *testing.T) { | |||||||
| 			MaxLeaseTTLVal:     time.Hour * 24 * 32, | 			MaxLeaseTTLVal:     time.Hour * 24 * 32, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	be := NewSystemBackend(core) | 	b := NewSystemBackend(core) | ||||||
| 	b, err := be.Backend.Setup(bc) | 	err := b.Backend.Setup(bc) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -1591,7 +1591,7 @@ func testSystemBackend(t *testing.T) logical.Backend { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	b := NewSystemBackend(c) | 	b := NewSystemBackend(c) | ||||||
| 	_, err := b.Backend.Setup(bc) | 	err := b.Backend.Setup(bc) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -1610,7 +1610,7 @@ func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	b := NewSystemBackend(c) | 	b := NewSystemBackend(c) | ||||||
| 	_, err := b.Backend.Setup(bc) | 	err := b.Backend.Setup(bc) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -1641,22 +1641,16 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("err: %v", err) | 		t.Fatalf("err: %v", err) | ||||||
| 	} | 	} | ||||||
|  | 	actualRespData := resp.Data | ||||||
|  |  | ||||||
| 	expectedBuiltin := &pluginutil.PluginRunner{ | 	expectedBuiltin := &pluginutil.PluginRunner{ | ||||||
| 		Name:    "mysql-database-plugin", | 		Name:    "mysql-database-plugin", | ||||||
| 		Builtin: true, | 		Builtin: true, | ||||||
| 	} | 	} | ||||||
| 	expectedBuiltin.BuiltinFactory, _ = builtinplugins.Get("mysql-database-plugin") | 	expectedRespData := structs.New(expectedBuiltin).Map() | ||||||
|  |  | ||||||
| 	p := resp.Data["plugin"].(*pluginutil.PluginRunner) | 	if !reflect.DeepEqual(actualRespData, expectedRespData) { | ||||||
| 	if &(p.BuiltinFactory) == &(expectedBuiltin.BuiltinFactory) { | 		t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actualRespData, expectedRespData) | ||||||
| 		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) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Set a plugin | 	// Set a plugin | ||||||
| @@ -1680,16 +1674,19 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("err: %v", err) | 		t.Fatalf("err: %v", err) | ||||||
| 	} | 	} | ||||||
|  | 	actual := resp.Data | ||||||
|  |  | ||||||
| 	expected := &pluginutil.PluginRunner{ | 	expectedRunner := &pluginutil.PluginRunner{ | ||||||
| 		Name:    "test-plugin", | 		Name:    "test-plugin", | ||||||
| 		Command: filepath.Join(sym, filepath.Base(file.Name())), | 		Command: filepath.Join(sym, filepath.Base(file.Name())), | ||||||
| 		Args:    []string{"--test"}, | 		Args:    []string{"--test"}, | ||||||
| 		Sha256:  []byte{'1'}, | 		Sha256:  []byte{'1'}, | ||||||
| 		Builtin: false, | 		Builtin: false, | ||||||
| 	} | 	} | ||||||
| 	if !reflect.DeepEqual(resp.Data["plugin"].(*pluginutil.PluginRunner), expected) { | 	expected := structs.New(expectedRunner).Map() | ||||||
| 		t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", resp.Data["plugin"].(*pluginutil.PluginRunner), expected) |  | ||||||
|  | 	if !reflect.DeepEqual(actual, expected) { | ||||||
|  | 		t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actual, expected) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Delete plugin | 	// 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 | 	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 | 	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 | 	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. | // 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 + "/" | 	viewPath := backendBarrierPrefix + entry.UUID + "/" | ||||||
| 	view := NewBarrierView(c.barrier, viewPath) | 	view := NewBarrierView(c.barrier, viewPath) | ||||||
| 	sysView := c.mountEntrySysView(entry) | 	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 { | 	if err != nil { | ||||||
| 		return err | 		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) | 		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 | 	// 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 { | 	if err := backend.Initialize(); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -658,9 +678,13 @@ func (c *Core) setupMounts() error { | |||||||
| 		// Create a barrier view using the UUID | 		// Create a barrier view using the UUID | ||||||
| 		view = NewBarrierView(c.barrier, barrierPath) | 		view = NewBarrierView(c.barrier, barrierPath) | ||||||
| 		sysView := c.mountEntrySysView(entry) | 		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 | 		// 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 { | 		if err != nil { | ||||||
| 			c.logger.Error("core: failed to create mount entry", "path", entry.Path, "error", err) | 			c.logger.Error("core: failed to create mount entry", "path", entry.Path, "error", err) | ||||||
| 			return errLoadMountsFailed | 			return errLoadMountsFailed | ||||||
| @@ -669,6 +693,12 @@ func (c *Core) setupMounts() error { | |||||||
| 			return fmt.Errorf("created mount entry of type %q is nil", entry.Type) | 			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 { | 		if err := backend.Initialize(); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @@ -687,10 +717,9 @@ func (c *Core) setupMounts() error { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			c.logger.Error("core: failed to mount entry", "path", entry.Path, "error", err) | 			c.logger.Error("core: failed to mount entry", "path", entry.Path, "error", err) | ||||||
| 			return errLoadMountsFailed | 			return errLoadMountsFailed | ||||||
| 		} else { | 		} | ||||||
| 			if c.logger.IsInfo() { | 		if c.logger.IsInfo() { | ||||||
| 				c.logger.Info("core: successfully mounted backend", "type", entry.Type, "path", entry.Path) | 			c.logger.Info("core: successfully mounted backend", "type", entry.Type, "path", entry.Path) | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Ensure the path is tainted if set in the mount table | 		// Ensure the path is tainted if set in the mount table | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package vault | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| @@ -9,7 +10,9 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/go-uuid" | 	"github.com/hashicorp/go-uuid" | ||||||
|  | 	"github.com/hashicorp/vault/helper/logformat" | ||||||
| 	"github.com/hashicorp/vault/logical" | 	"github.com/hashicorp/vault/logical" | ||||||
|  | 	log "github.com/mgutz/logxi/v1" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type NoopBackend struct { | type NoopBackend struct { | ||||||
| @@ -63,10 +66,26 @@ func (n *NoopBackend) InvalidateKey(k string) { | |||||||
| 	n.Invalidations = append(n.Invalidations, k) | 	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 { | func (n *NoopBackend) Initialize() error { | ||||||
| 	return nil | 	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) { | func TestRouter_Mount(t *testing.T) { | ||||||
| 	r := NewRouter() | 	r := NewRouter() | ||||||
| 	_, barrier, _ := mockBarrier(t) | 	_, 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() { | func (n *rawHTTP) Cleanup() { | ||||||
| 	// noop | 	// noop | ||||||
| } | } | ||||||
| @@ -533,6 +537,19 @@ func (n *rawHTTP) InvalidateKey(string) { | |||||||
| 	// noop | 	// 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) { | func GenerateRandBytes(length int) ([]byte, error) { | ||||||
| 	if length < 0 { | 	if length < 0 { | ||||||
| 		return nil, fmt.Errorf("length must be >= 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> |         </ul> | ||||||
|       </li> |       </li> | ||||||
|  |  | ||||||
|  |       <li<%= sidebar_current("docs-plugin") %>> | ||||||
|  |         <a href="/docs/plugin/index.html">Plugin Backends</a> | ||||||
|  |       </li> | ||||||
|  |  | ||||||
|       <hr> |       <hr> | ||||||
|  |  | ||||||
|       <li<%= sidebar_current("docs-vault-enterprise") %>> |       <li<%= sidebar_current("docs-vault-enterprise") %>> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user