Support setting plugin TMPDIR in config as well as env (#24978)

This commit is contained in:
Tom Proctor
2024-01-30 13:10:23 +00:00
committed by GitHub
parent 78ef25e70c
commit 6e111d92fe
16 changed files with 139 additions and 38 deletions

3
changelog/24978.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
core: Added new `plugin_tmpdir` config option for containerized plugins, in addition to the existing `VAULT_PLUGIN_TMPDIR` environment variable.
```

View File

@@ -97,6 +97,9 @@ const (
// logged at startup _per node_. This was initially introduced for the events // logged at startup _per node_. This was initially introduced for the events
// system being developed over multiple release cycles. // system being developed over multiple release cycles.
EnvVaultExperiments = "VAULT_EXPERIMENTS" EnvVaultExperiments = "VAULT_EXPERIMENTS"
// EnvVaultPluginTmpdir sets the folder to use for Unix sockets when setting
// up containerized plugins.
EnvVaultPluginTmpdir = "VAULT_PLUGIN_TMPDIR"
// flagNameAddress is the flag used in the base command to read in the // flagNameAddress is the flag used in the base command to read in the
// address of the Vault server. // address of the Vault server.

View File

@@ -1153,6 +1153,10 @@ func (c *ServerCommand) Run(args []string) int {
config.License = envLicense config.License = envLicense
} }
if envPluginTmpdir := os.Getenv(EnvVaultPluginTmpdir); envPluginTmpdir != "" {
config.PluginTmpdir = envPluginTmpdir
}
if err := server.ExperimentsFromEnvAndCLI(config, EnvVaultExperiments, c.flagExperiments); err != nil { if err := server.ExperimentsFromEnvAndCLI(config, EnvVaultExperiments, c.flagExperiments); err != nil {
c.UI.Error(err.Error()) c.UI.Error(err.Error())
return 1 return 1
@@ -3082,6 +3086,7 @@ func createCoreConfig(c *ServerCommand, config *server.Config, backend physical.
ClusterName: config.ClusterName, ClusterName: config.ClusterName,
CacheSize: config.CacheSize, CacheSize: config.CacheSize,
PluginDirectory: config.PluginDirectory, PluginDirectory: config.PluginDirectory,
PluginTmpdir: config.PluginTmpdir,
PluginFileUid: config.PluginFileUid, PluginFileUid: config.PluginFileUid,
PluginFilePermissions: config.PluginFilePermissions, PluginFilePermissions: config.PluginFilePermissions,
EnableUI: config.EnableUI, EnableUI: config.EnableUI,

View File

@@ -69,6 +69,7 @@ type Config struct {
ClusterCipherSuites string `hcl:"cluster_cipher_suites"` ClusterCipherSuites string `hcl:"cluster_cipher_suites"`
PluginDirectory string `hcl:"plugin_directory"` PluginDirectory string `hcl:"plugin_directory"`
PluginTmpdir string `hcl:"plugin_tmpdir"`
PluginFileUid int `hcl:"plugin_file_uid"` PluginFileUid int `hcl:"plugin_file_uid"`
@@ -363,6 +364,11 @@ func (c *Config) Merge(c2 *Config) *Config {
result.PluginDirectory = c2.PluginDirectory result.PluginDirectory = c2.PluginDirectory
} }
result.PluginTmpdir = c.PluginTmpdir
if c2.PluginTmpdir != "" {
result.PluginTmpdir = c2.PluginTmpdir
}
result.PluginFileUid = c.PluginFileUid result.PluginFileUid = c.PluginFileUid
if c2.PluginFileUid != 0 { if c2.PluginFileUid != 0 {
result.PluginFileUid = c2.PluginFileUid result.PluginFileUid = c2.PluginFileUid
@@ -1114,6 +1120,7 @@ func (c *Config) Sanitized() map[string]interface{} {
"cluster_cipher_suites": c.ClusterCipherSuites, "cluster_cipher_suites": c.ClusterCipherSuites,
"plugin_directory": c.PluginDirectory, "plugin_directory": c.PluginDirectory,
"plugin_tmpdir": c.PluginTmpdir,
"plugin_file_uid": c.PluginFileUid, "plugin_file_uid": c.PluginFileUid,

View File

@@ -476,6 +476,9 @@ func testLoadConfigFile(t *testing.T) {
EnableResponseHeaderRaftNodeIDRaw: true, EnableResponseHeaderRaftNodeIDRaw: true,
LicensePath: "/path/to/license", LicensePath: "/path/to/license",
PluginDirectory: "/path/to/plugins",
PluginTmpdir: "/tmp/plugins",
} }
addExpectedEntConfig(expected, []string{}) addExpectedEntConfig(expected, []string{})
@@ -802,6 +805,7 @@ func testConfig_Sanitized(t *testing.T) {
"max_lease_ttl": (30 * 24 * time.Hour) / time.Second, "max_lease_ttl": (30 * 24 * time.Hour) / time.Second,
"pid_file": "./pidfile", "pid_file": "./pidfile",
"plugin_directory": "", "plugin_directory": "",
"plugin_tmpdir": "",
"seals": []interface{}{ "seals": []interface{}{
map[string]interface{}{ map[string]interface{}{
"disabled": false, "disabled": false,

View File

@@ -51,4 +51,6 @@ disable_sealwrap = true
disable_printable_check = true disable_printable_check = true
enable_response_header_hostname = true enable_response_header_hostname = true
enable_response_header_raft_node_id = true enable_response_header_raft_node_id = true
license_path = "/path/to/license" license_path = "/path/to/license"
plugin_directory = "/path/to/plugins"
plugin_tmpdir = "/tmp/plugins"

View File

@@ -162,6 +162,7 @@ func TestSysConfigState_Sanitized(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"pid_file": "", "pid_file": "",
"plugin_directory": "", "plugin_directory": "",
"plugin_tmpdir": "",
"plugin_file_uid": json.Number("0"), "plugin_file_uid": json.Number("0"),
"plugin_file_permissions": json.Number("0"), "plugin_file_permissions": json.Number("0"),
"enable_response_header_hostname": false, "enable_response_header_hostname": false,

View File

@@ -56,6 +56,7 @@ type runConfig struct {
runtimeConfig *pluginruntimeutil.PluginRuntimeConfig runtimeConfig *pluginruntimeutil.PluginRuntimeConfig
PluginClientConfig PluginClientConfig
tmpdir string
} }
func (rc runConfig) mlockEnabled() bool { func (rc runConfig) mlockEnabled() bool {
@@ -144,7 +145,7 @@ func (rc runConfig) makeConfig(ctx context.Context) (*plugin.ClientConfig, error
clientConfig.RunnerFunc = containerCfg.NewContainerRunner clientConfig.RunnerFunc = containerCfg.NewContainerRunner
clientConfig.UnixSocketConfig = &plugin.UnixSocketConfig{ clientConfig.UnixSocketConfig = &plugin.UnixSocketConfig{
Group: strconv.Itoa(containerCfg.GroupAdd), Group: strconv.Itoa(containerCfg.GroupAdd),
TempDir: os.Getenv("VAULT_PLUGIN_TMPDIR"), TempDir: rc.tmpdir,
} }
clientConfig.GRPCBrokerMultiplex = true clientConfig.GRPCBrokerMultiplex = true
} }
@@ -271,6 +272,7 @@ func (r *PluginRunner) RunConfig(ctx context.Context, opts ...RunOpt) (*plugin.C
sha256: r.Sha256, sha256: r.Sha256,
env: r.Env, env: r.Env,
runtimeConfig: r.RuntimeConfig, runtimeConfig: r.RuntimeConfig,
tmpdir: r.Tmpdir,
PluginClientConfig: PluginClientConfig{ PluginClientConfig: PluginClientConfig{
Name: r.Name, Name: r.Name,
PluginType: r.Type, PluginType: r.Type,

View File

@@ -69,6 +69,7 @@ type PluginRunner struct {
Builtin bool `json:"builtin" structs:"builtin"` Builtin bool `json:"builtin" structs:"builtin"`
BuiltinFactory func() (interface{}, error) `json:"-" structs:"-"` BuiltinFactory func() (interface{}, error) `json:"-" structs:"-"`
RuntimeConfig *prutil.PluginRuntimeConfig `json:"-" structs:"-"` RuntimeConfig *prutil.PluginRuntimeConfig `json:"-" structs:"-"`
Tmpdir string `json:"-" structs:"-"`
} }
// BinaryReference returns either the OCI image reference if it's a container // BinaryReference returns either the OCI image reference if it's a container

View File

@@ -535,6 +535,9 @@ type Core struct {
// pluginDirectory is the location vault will look for plugin binaries // pluginDirectory is the location vault will look for plugin binaries
pluginDirectory string pluginDirectory string
// pluginTmpdir is the location vault will use for containerized plugin
// temporary files
pluginTmpdir string
// pluginFileUid is the uid of the plugin files and directory // pluginFileUid is the uid of the plugin files and directory
pluginFileUid int pluginFileUid int
@@ -822,6 +825,7 @@ type CoreConfig struct {
EnableIntrospection bool EnableIntrospection bool
PluginDirectory string PluginDirectory string
PluginTmpdir string
PluginFileUid int PluginFileUid int
@@ -1240,6 +1244,12 @@ func NewCore(conf *CoreConfig) (*Core, error) {
return nil, fmt.Errorf("core setup failed, could not verify plugin directory: %w", err) return nil, fmt.Errorf("core setup failed, could not verify plugin directory: %w", err)
} }
} }
if conf.PluginTmpdir != "" {
c.pluginTmpdir, err = filepath.Abs(conf.PluginTmpdir)
if err != nil {
return nil, fmt.Errorf("core setup failed, could not verify plugin tmpdir: %w", err)
}
}
if conf.PluginFileUid != 0 { if conf.PluginFileUid != 0 {
c.pluginFileUid = conf.PluginFileUid c.pluginFileUid = conf.PluginFileUid
@@ -2517,7 +2527,15 @@ func (c *Core) setupPluginRuntimeCatalog(ctx context.Context) error {
// this method can be included in the slice of functions returned by the // this method can be included in the slice of functions returned by the
// buildUnsealSetupFunctionsSlice function. // buildUnsealSetupFunctionsSlice function.
func (c *Core) setupPluginCatalog(ctx context.Context) error { func (c *Core) setupPluginCatalog(ctx context.Context) error {
pluginCatalog, err := plugincatalog.SetupPluginCatalog(ctx, c.logger, c.builtinRegistry, NewBarrierView(c.barrier, pluginCatalogPath), c.pluginDirectory, c.enableMlock, c.pluginRuntimeCatalog) pluginCatalog, err := plugincatalog.SetupPluginCatalog(ctx, &plugincatalog.PluginCatalogInput{
Logger: c.logger,
BuiltinRegistry: c.builtinRegistry,
CatalogView: NewBarrierView(c.barrier, pluginCatalogPath),
PluginDirectory: c.pluginDirectory,
Tmpdir: c.pluginTmpdir,
EnableMlock: c.enableMlock,
PluginRuntimeCatalog: c.pluginRuntimeCatalog,
})
if err != nil { if err != nil {
return err return err
} }

View File

@@ -17,7 +17,7 @@ import (
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
) )
func testClusterWithContainerPlugins(t *testing.T, types []consts.PluginType) (*Core, []pluginhelpers.TestPlugin) { func testClusterWithContainerPlugins(t *testing.T, types []consts.PluginType) (*TestClusterCore, []pluginhelpers.TestPlugin) {
var plugins []*TestPluginConfig var plugins []*TestPluginConfig
for _, typ := range types { for _, typ := range types {
plugins = append(plugins, &TestPluginConfig{ plugins = append(plugins, &TestPluginConfig{
@@ -26,15 +26,28 @@ func testClusterWithContainerPlugins(t *testing.T, types []consts.PluginType) (*
Container: true, Container: true,
}) })
} }
cluster := NewTestCluster(t, &CoreConfig{}, &TestClusterOptions{ // Use os.MkdirTemp because t.TempDir() exceeds the Unix socket length limit.
// See https://www.man7.org/linux/man-pages/man7/unix.7.html for details.
tmpdir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := os.RemoveAll(tmpdir); err != nil {
t.Fatal(err)
}
})
cluster := NewTestCluster(t, &CoreConfig{
PluginTmpdir: tmpdir,
}, &TestClusterOptions{
Plugins: plugins, Plugins: plugins,
}) })
cluster.Start() cluster.Start()
t.Cleanup(cluster.Cleanup) t.Cleanup(cluster.Cleanup)
core := cluster.Cores[0].Core core := cluster.Cores[0]
TestWaitActive(t, core) TestWaitActive(t, core.Core)
return core, cluster.Plugins return core, cluster.Plugins
} }
@@ -81,7 +94,7 @@ func TestExternalPluginInContainer_MountAndUnmount(t *testing.T) {
}) })
} }
func mountAndUnmountContainerPlugin_WithRuntime(t *testing.T, c *Core, plugin pluginhelpers.TestPlugin, ociRuntime string, rootless bool) { func mountAndUnmountContainerPlugin_WithRuntime(t *testing.T, c *TestClusterCore, plugin pluginhelpers.TestPlugin, ociRuntime string, rootless bool) {
if ociRuntime != "" { if ociRuntime != "" {
registerPluginRuntime(t, c.systemBackend, ociRuntime, rootless) registerPluginRuntime(t, c.systemBackend, ociRuntime, rootless)
} }
@@ -89,6 +102,18 @@ func mountAndUnmountContainerPlugin_WithRuntime(t *testing.T, c *Core, plugin pl
mountPlugin(t, c.systemBackend, plugin.Name, plugin.Typ, "v1.0.0", "") mountPlugin(t, c.systemBackend, plugin.Name, plugin.Typ, "v1.0.0", "")
expectTmpdirEntries := func(expected int) {
t.Helper()
entries, err := os.ReadDir(c.CoreConfig.PluginTmpdir)
if err != nil {
t.Fatal(err)
}
if len(entries) != expected {
t.Fatalf("expected %d in tmpdir, got %v", expected, entries)
}
}
expectTmpdirEntries(1)
routeRequest := func(expectMatch bool) { routeRequest := func(expectMatch bool) {
pluginPath := "foo/bar" pluginPath := "foo/bar"
if plugin.Typ == consts.PluginTypeCredential { if plugin.Typ == consts.PluginTypeCredential {
@@ -106,6 +131,7 @@ func mountAndUnmountContainerPlugin_WithRuntime(t *testing.T, c *Core, plugin pl
routeRequest(true) routeRequest(true)
unmountPlugin(t, c.systemBackend, plugin.Typ, "foo") unmountPlugin(t, c.systemBackend, plugin.Typ, "foo")
routeRequest(false) routeRequest(false)
expectTmpdirEntries(0)
} }
func registerContainerPlugin(t *testing.T, sys *SystemBackend, pluginName, pluginType, version, sha, image, runtime string) { func registerContainerPlugin(t *testing.T, sys *SystemBackend, pluginName, pluginType, version, sha, image, runtime string) {

View File

@@ -48,6 +48,7 @@ type PluginCatalog struct {
builtinRegistry BuiltinRegistry builtinRegistry BuiltinRegistry
catalogView logical.Storage catalogView logical.Storage
directory string directory string
tmpdir string
logger log.Logger logger log.Logger
// externalPlugins holds plugin process connections by a key which is // externalPlugins holds plugin process connections by a key which is
@@ -138,37 +139,42 @@ type pluginClient struct {
plugin.ClientProtocol plugin.ClientProtocol
} }
func SetupPluginCatalog( type PluginCatalogInput struct {
ctx context.Context, Logger log.Logger
logger log.Logger, BuiltinRegistry BuiltinRegistry
builtinRegistry BuiltinRegistry, CatalogView logical.Storage
catalogView logical.Storage, PluginDirectory string
pluginDirectory string, Tmpdir string
enableMlock bool, EnableMlock bool
pluginRuntimeCatalog *PluginRuntimeCatalog, PluginRuntimeCatalog *PluginRuntimeCatalog
) (*PluginCatalog, error) { }
pluginCatalog := &PluginCatalog{
builtinRegistry: builtinRegistry, func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCatalog, error) {
catalogView: catalogView, logger := in.Logger
directory: pluginDirectory, catalog := &PluginCatalog{
builtinRegistry: in.BuiltinRegistry,
catalogView: in.CatalogView,
directory: in.PluginDirectory,
tmpdir: in.Tmpdir,
logger: logger, logger: logger,
mlockPlugins: enableMlock, mlockPlugins: in.EnableMlock,
wrapper: logical.StaticSystemView{VersionString: version.GetVersion().Version}, wrapper: logical.StaticSystemView{VersionString: version.GetVersion().Version},
runtimeCatalog: pluginRuntimeCatalog, runtimeCatalog: in.PluginRuntimeCatalog,
} }
// Run upgrade if untyped plugins exist // Run upgrade if untyped plugins exist
err := pluginCatalog.UpgradePlugins(ctx, logger) err := catalog.upgradePlugins(ctx, logger)
if err != nil { if err != nil {
logger.Error("error while upgrading plugin storage", "error", err) logger.Error("error while upgrading plugin storage", "error", err)
return nil, err return nil, err
} }
if logger.IsInfo() { logger.Info("successfully setup plugin catalog", "plugin-directory", catalog.directory)
logger.Info("successfully setup plugin catalog", "plugin-directory", pluginDirectory) if catalog.tmpdir != "" {
logger.Debug("plugin temporary directory configured", "tmpdir", catalog.tmpdir)
} }
return pluginCatalog, nil return catalog, nil
} }
type pluginClientConn struct { type pluginClientConn struct {
@@ -723,9 +729,9 @@ func (c *PluginCatalog) isDatabasePlugin(ctx context.Context, pluginRunner *plug
return merr.ErrorOrNil() return merr.ErrorOrNil()
} }
// UpgradePlugins will loop over all the plugins of unknown type and attempt to // upgradePlugins will loop over all the plugins of unknown type and attempt to
// upgrade them to typed plugins // upgrade them to typed plugins
func (c *PluginCatalog) UpgradePlugins(ctx context.Context, logger log.Logger) error { func (c *PluginCatalog) upgradePlugins(ctx context.Context, logger log.Logger) error {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
@@ -739,6 +745,10 @@ func (c *PluginCatalog) UpgradePlugins(ctx context.Context, logger log.Logger) e
if err != nil { if err != nil {
return err return err
} }
if len(pluginsRaw) == 0 {
return nil
}
plugins := make([]string, 0, len(pluginsRaw)) plugins := make([]string, 0, len(pluginsRaw))
for _, p := range pluginsRaw { for _, p := range pluginsRaw {
if !strings.HasSuffix(p, "/") { if !strings.HasSuffix(p, "/") {
@@ -838,6 +848,7 @@ func (c *PluginCatalog) get(ctx context.Context, name string, pluginType consts.
// If none of the cases are satisfied, we'll search for a builtin plugin below. // If none of the cases are satisfied, we'll search for a builtin plugin below.
switch { switch {
case entry.OCIImage != "": case entry.OCIImage != "":
entry.Tmpdir = c.tmpdir
if entry.Runtime != "" { if entry.Runtime != "" {
entry.RuntimeConfig, err = c.runtimeCatalog.Get(ctx, entry.Runtime, consts.PluginRuntimeTypeContainer) entry.RuntimeConfig, err = c.runtimeCatalog.Get(ctx, entry.Runtime, consts.PluginRuntimeTypeContainer)
if err != nil { if err != nil {
@@ -1085,6 +1096,9 @@ func (c *PluginCatalog) ListPluginsWithRuntime(ctx context.Context, runtime stri
if plugin.Runtime == runtime { if plugin.Runtime == runtime {
ret = append(ret, plugin.Name) ret = append(ret, plugin.Name)
} }
if plugin.OCIImage != "" {
plugin.Tmpdir = c.tmpdir
}
} }
return ret, nil return ret, nil
} }
@@ -1145,6 +1159,10 @@ func (c *PluginCatalog) listInternal(ctx context.Context, pluginType consts.Plug
continue continue
} }
if plugin.OCIImage != "" {
plugin.Tmpdir = c.tmpdir
}
result = append(result, pluginutil.VersionedPlugin{ result = append(result, pluginutil.VersionedPlugin{
Name: plugin.Name, Name: plugin.Name,
Type: plugin.Type.String(), Type: plugin.Type.String(),

View File

@@ -54,12 +54,14 @@ func testPluginCatalog(t *testing.T) *PluginCatalog {
pluginRuntimeCatalog := testPluginRuntimeCatalog(t) pluginRuntimeCatalog := testPluginRuntimeCatalog(t)
pluginCatalog, err := SetupPluginCatalog( pluginCatalog, err := SetupPluginCatalog(
context.Background(), context.Background(),
logger, &PluginCatalogInput{
corehelpers.NewMockBuiltinRegistry(), Logger: logger,
logical.NewLogicalStorage(storage), BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(),
testDir, CatalogView: logical.NewLogicalStorage(storage),
false, PluginDirectory: testDir,
pluginRuntimeCatalog, EnableMlock: false,
PluginRuntimeCatalog: pluginRuntimeCatalog,
},
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@@ -40,9 +40,7 @@ func SetupPluginRuntimeCatalog(ctx context.Context, logger log.Logger, catalogVi
logger: logger, logger: logger,
} }
if logger.IsInfo() { logger.Info("successfully setup plugin runtime catalog")
logger.Info("successfully setup plugin runtime catalog")
}
return pluginRuntimeCatalog, nil return pluginRuntimeCatalog, nil
} }

View File

@@ -1435,6 +1435,7 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
coreConfig.MaxLeaseTTL = base.MaxLeaseTTL coreConfig.MaxLeaseTTL = base.MaxLeaseTTL
coreConfig.CacheSize = base.CacheSize coreConfig.CacheSize = base.CacheSize
coreConfig.PluginDirectory = base.PluginDirectory coreConfig.PluginDirectory = base.PluginDirectory
coreConfig.PluginTmpdir = base.PluginTmpdir
coreConfig.Seal = base.Seal coreConfig.Seal = base.Seal
coreConfig.UnwrapSeal = base.UnwrapSeal coreConfig.UnwrapSeal = base.UnwrapSeal
coreConfig.DevToken = base.DevToken coreConfig.DevToken = base.DevToken

View File

@@ -130,6 +130,16 @@ to specify where the configuration is.
allowed to be loaded. Vault must have permission to read files in this allowed to be loaded. Vault must have permission to read files in this
directory to successfully load plugins, and the value cannot be a symbolic link. directory to successfully load plugins, and the value cannot be a symbolic link.
- `plugin_tmpdir` `(string: "")` - A directory that Vault can create temporary
files in to support Unix socket communication with containerized plugins. If
not set, Vault will use the system's default directory for temporary files.
Generally not necessary unless you are using
[containerized plugins](/vault/docs/plugins/containerized-plugins) and Vault
does not share a temporary folder with other processes, such as if using
systemd's [PrivateTmp](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#PrivateTmp=)
setting. This can also be specified via the `VAULT_PLUGIN_TMPDIR` environment
variable.
@include 'plugin-file-permissions-check.mdx' @include 'plugin-file-permissions-check.mdx'
- `plugin_file_uid` `(integer: 0)` Uid of the plugin directories and plugin binaries if they - `plugin_file_uid` `(integer: 0)` Uid of the plugin directories and plugin binaries if they