allow to restrict the env vars passed to plugins
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
9a7a3b00dc
commit
5c938e46b7
10 changed files with 90 additions and 13 deletions
|
@ -477,7 +477,7 @@ The configuration file contains the following sections:
|
||||||
<details><summary><font size=4>Plugins</font></summary>
|
<details><summary><font size=4>Plugins</font></summary>
|
||||||
|
|
||||||
- **plugins**, list of external plugins. :warning: Please note that the plugin system is experimental, the configuration parameters and interfaces may change in a backward incompatible way in future. Each plugin is configured using a struct with the following fields:
|
- **plugins**, list of external plugins. :warning: Please note that the plugin system is experimental, the configuration parameters and interfaces may change in a backward incompatible way in future. Each plugin is configured using a struct with the following fields:
|
||||||
- `type`, string. Defines the plugin type. Supported types: `notifier`, `kms`, `auth`, `metadata`.
|
- `type`, string. Defines the plugin type. Supported types: `notifier`, `kms`, `auth`, `metadata`, `eventsearcher`, `ipfilter`.
|
||||||
- `notifier_options`, struct. Defines the options for notifier plugins.
|
- `notifier_options`, struct. Defines the options for notifier plugins.
|
||||||
- `fs_events`, list of strings. Defines the filesystem events that will be notified to this plugin.
|
- `fs_events`, list of strings. Defines the filesystem events that will be notified to this plugin.
|
||||||
- `provider_events`, list of strings. Defines the provider events that will be notified to this plugin.
|
- `provider_events`, list of strings. Defines the provider events that will be notified to this plugin.
|
||||||
|
@ -494,6 +494,8 @@ The configuration file contains the following sections:
|
||||||
- `args`, list of strings. Optional arguments to pass to the plugin executable.
|
- `args`, list of strings. Optional arguments to pass to the plugin executable.
|
||||||
- `sha256sum`, string. SHA256 checksum for the plugin executable. If not empty it will be used to verify the integrity of the executable.
|
- `sha256sum`, string. SHA256 checksum for the plugin executable. If not empty it will be used to verify the integrity of the executable.
|
||||||
- `auto_mtls`, boolean. If enabled the client and the server automatically negotiate mutual TLS for transport authentication. This ensures that only the original client will be allowed to connect to the server, and all other connections will be rejected. The client will also refuse to connect to any server that isn't the original instance started by the client.
|
- `auto_mtls`, boolean. If enabled the client and the server automatically negotiate mutual TLS for transport authentication. This ensures that only the original client will be allowed to connect to the server, and all other connections will be rejected. The client will also refuse to connect to any server that isn't the original instance started by the client.
|
||||||
|
- `env_prefix`, string. Defines the prefix for env vars to pass from the SFTPGo process environment to the plugin. Set to `none` to not pass any environment variable, set to `*` to pass all environment variables. If empty, the prefix is returned as the plugin name in uppercase with `-` replaced with `_` and a trailing `_`. For example if the plugin name is `sftpgo-plugin-eventsearch` the prefix will be `SFTPGO_PLUGIN_EVENTSEARCH_`
|
||||||
|
- `env_vars`, list of strings. Additional environment variable names to pass from the SFTPGo process environment to the plugin.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
|
@ -1046,6 +1046,18 @@ func getPluginsFromEnv(idx int) {
|
||||||
isSet = true
|
isSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
envPrefix, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_PLUGINS__%v__ENV_PREFIX", idx))
|
||||||
|
if ok {
|
||||||
|
pluginConfig.EnvPrefix = envPrefix
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
envVars, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_PLUGINS__%v__ENV_VARS", idx))
|
||||||
|
if ok {
|
||||||
|
pluginConfig.EnvVars = envVars
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
if isSet {
|
if isSet {
|
||||||
if len(globalConf.PluginsConfig) > idx {
|
if len(globalConf.PluginsConfig) > idx {
|
||||||
globalConf.PluginsConfig[idx] = pluginConfig
|
globalConf.PluginsConfig[idx] = pluginConfig
|
||||||
|
|
|
@ -693,6 +693,9 @@ func TestPluginsFromEnv(t *testing.T) {
|
||||||
os.Setenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__SCHEME", kms.SchemeAWS)
|
os.Setenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__SCHEME", kms.SchemeAWS)
|
||||||
os.Setenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__ENCRYPTED_STATUS", kms.SecretStatusAWS)
|
os.Setenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__ENCRYPTED_STATUS", kms.SecretStatusAWS)
|
||||||
os.Setenv("SFTPGO_PLUGINS__0__AUTH_OPTIONS__SCOPE", "14")
|
os.Setenv("SFTPGO_PLUGINS__0__AUTH_OPTIONS__SCOPE", "14")
|
||||||
|
os.Setenv("SFTPGO_PLUGINS__0__ENV_PREFIX", "prefix_")
|
||||||
|
os.Setenv("SFTPGO_PLUGINS__0__ENV_VARS", "a, b")
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
os.Unsetenv("SFTPGO_PLUGINS__0__TYPE")
|
os.Unsetenv("SFTPGO_PLUGINS__0__TYPE")
|
||||||
os.Unsetenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__FS_EVENTS")
|
os.Unsetenv("SFTPGO_PLUGINS__0__NOTIFIER_OPTIONS__FS_EVENTS")
|
||||||
|
@ -708,6 +711,8 @@ func TestPluginsFromEnv(t *testing.T) {
|
||||||
os.Unsetenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__SCHEME")
|
os.Unsetenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__SCHEME")
|
||||||
os.Unsetenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__ENCRYPTED_STATUS")
|
os.Unsetenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__ENCRYPTED_STATUS")
|
||||||
os.Unsetenv("SFTPGO_PLUGINS__0__AUTH_OPTIONS__SCOPE")
|
os.Unsetenv("SFTPGO_PLUGINS__0__AUTH_OPTIONS__SCOPE")
|
||||||
|
os.Unsetenv("SFTPGO_PLUGINS__0__ENV_PREFIX")
|
||||||
|
os.Unsetenv("SFTPGO_PLUGINS__0__ENV_VARS")
|
||||||
})
|
})
|
||||||
|
|
||||||
err := config.LoadConfig(configDir, "")
|
err := config.LoadConfig(configDir, "")
|
||||||
|
@ -739,6 +744,10 @@ func TestPluginsFromEnv(t *testing.T) {
|
||||||
require.Equal(t, kms.SchemeAWS, pluginConf.KMSOptions.Scheme)
|
require.Equal(t, kms.SchemeAWS, pluginConf.KMSOptions.Scheme)
|
||||||
require.Equal(t, kms.SecretStatusAWS, pluginConf.KMSOptions.EncryptedStatus)
|
require.Equal(t, kms.SecretStatusAWS, pluginConf.KMSOptions.EncryptedStatus)
|
||||||
require.Equal(t, 14, pluginConf.AuthOptions.Scope)
|
require.Equal(t, 14, pluginConf.AuthOptions.Scope)
|
||||||
|
require.Equal(t, "prefix_", pluginConf.EnvPrefix)
|
||||||
|
require.Len(t, pluginConf.EnvVars, 2)
|
||||||
|
assert.Equal(t, "a", pluginConf.EnvVars[0])
|
||||||
|
assert.Equal(t, "b", pluginConf.EnvVars[1])
|
||||||
|
|
||||||
cfg := make(map[string]any)
|
cfg := make(map[string]any)
|
||||||
cfg["plugins"] = pluginConf
|
cfg["plugins"] = pluginConf
|
||||||
|
@ -754,6 +763,8 @@ func TestPluginsFromEnv(t *testing.T) {
|
||||||
os.Setenv("SFTPGO_PLUGINS__0__AUTO_MTLS", "0")
|
os.Setenv("SFTPGO_PLUGINS__0__AUTO_MTLS", "0")
|
||||||
os.Setenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__SCHEME", kms.SchemeVaultTransit)
|
os.Setenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__SCHEME", kms.SchemeVaultTransit)
|
||||||
os.Setenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__ENCRYPTED_STATUS", kms.SecretStatusVaultTransit)
|
os.Setenv("SFTPGO_PLUGINS__0__KMS_OPTIONS__ENCRYPTED_STATUS", kms.SecretStatusVaultTransit)
|
||||||
|
os.Setenv("SFTPGO_PLUGINS__0__ENV_PREFIX", "")
|
||||||
|
os.Setenv("SFTPGO_PLUGINS__0__ENV_VARS", "")
|
||||||
err = config.LoadConfig(configDir, confName)
|
err = config.LoadConfig(configDir, confName)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
pluginsConf = config.GetPluginsConfig()
|
pluginsConf = config.GetPluginsConfig()
|
||||||
|
@ -778,6 +789,8 @@ func TestPluginsFromEnv(t *testing.T) {
|
||||||
require.Equal(t, kms.SchemeVaultTransit, pluginConf.KMSOptions.Scheme)
|
require.Equal(t, kms.SchemeVaultTransit, pluginConf.KMSOptions.Scheme)
|
||||||
require.Equal(t, kms.SecretStatusVaultTransit, pluginConf.KMSOptions.EncryptedStatus)
|
require.Equal(t, kms.SecretStatusVaultTransit, pluginConf.KMSOptions.EncryptedStatus)
|
||||||
require.Equal(t, 14, pluginConf.AuthOptions.Scope)
|
require.Equal(t, 14, pluginConf.AuthOptions.Scope)
|
||||||
|
assert.Empty(t, pluginConf.EnvPrefix)
|
||||||
|
assert.Len(t, pluginConf.EnvVars, 0)
|
||||||
|
|
||||||
err = os.Remove(configFilePath)
|
err = os.Remove(configFilePath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -17,7 +17,6 @@ package plugin
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
@ -119,7 +118,8 @@ func (p *authPlugin) initialize() error {
|
||||||
client := plugin.NewClient(&plugin.ClientConfig{
|
client := plugin.NewClient(&plugin.ClientConfig{
|
||||||
HandshakeConfig: auth.Handshake,
|
HandshakeConfig: auth.Handshake,
|
||||||
Plugins: auth.PluginMap,
|
Plugins: auth.PluginMap,
|
||||||
Cmd: exec.Command(p.config.Cmd, p.config.Args...),
|
Cmd: p.config.getCommand(),
|
||||||
|
SkipHostEnv: true,
|
||||||
AllowedProtocols: []plugin.Protocol{
|
AllowedProtocols: []plugin.Protocol{
|
||||||
plugin.ProtocolGRPC,
|
plugin.ProtocolGRPC,
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,6 @@ package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
@ -60,7 +59,8 @@ func (p *ipFilterPlugin) initialize() error {
|
||||||
client := plugin.NewClient(&plugin.ClientConfig{
|
client := plugin.NewClient(&plugin.ClientConfig{
|
||||||
HandshakeConfig: ipfilter.Handshake,
|
HandshakeConfig: ipfilter.Handshake,
|
||||||
Plugins: ipfilter.PluginMap,
|
Plugins: ipfilter.PluginMap,
|
||||||
Cmd: exec.Command(p.config.Cmd, p.config.Args...),
|
Cmd: p.config.getCommand(),
|
||||||
|
SkipHostEnv: true,
|
||||||
AllowedProtocols: []plugin.Protocol{
|
AllowedProtocols: []plugin.Protocol{
|
||||||
plugin.ProtocolGRPC,
|
plugin.ProtocolGRPC,
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,6 @@ package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
|
@ -81,7 +80,8 @@ func (p *kmsPlugin) initialize() error {
|
||||||
client := plugin.NewClient(&plugin.ClientConfig{
|
client := plugin.NewClient(&plugin.ClientConfig{
|
||||||
HandshakeConfig: kmsplugin.Handshake,
|
HandshakeConfig: kmsplugin.Handshake,
|
||||||
Plugins: kmsplugin.PluginMap,
|
Plugins: kmsplugin.PluginMap,
|
||||||
Cmd: exec.Command(p.config.Cmd, p.config.Args...),
|
Cmd: p.config.getCommand(),
|
||||||
|
SkipHostEnv: true,
|
||||||
AllowedProtocols: []plugin.Protocol{
|
AllowedProtocols: []plugin.Protocol{
|
||||||
plugin.ProtocolGRPC,
|
plugin.ProtocolGRPC,
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,6 @@ package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
@ -60,7 +59,8 @@ func (p *metadataPlugin) initialize() error {
|
||||||
client := plugin.NewClient(&plugin.ClientConfig{
|
client := plugin.NewClient(&plugin.ClientConfig{
|
||||||
HandshakeConfig: metadata.Handshake,
|
HandshakeConfig: metadata.Handshake,
|
||||||
Plugins: metadata.PluginMap,
|
Plugins: metadata.PluginMap,
|
||||||
Cmd: exec.Command(p.config.Cmd, p.config.Args...),
|
Cmd: p.config.getCommand(),
|
||||||
|
SkipHostEnv: true,
|
||||||
AllowedProtocols: []plugin.Protocol{
|
AllowedProtocols: []plugin.Protocol{
|
||||||
plugin.ProtocolGRPC,
|
plugin.ProtocolGRPC,
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,6 @@ package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -168,7 +167,8 @@ func (p *notifierPlugin) initialize() error {
|
||||||
client := plugin.NewClient(&plugin.ClientConfig{
|
client := plugin.NewClient(&plugin.ClientConfig{
|
||||||
HandshakeConfig: notifier.Handshake,
|
HandshakeConfig: notifier.Handshake,
|
||||||
Plugins: notifier.PluginMap,
|
Plugins: notifier.PluginMap,
|
||||||
Cmd: exec.Command(p.config.Cmd, p.config.Args...),
|
Cmd: p.config.getCommand(),
|
||||||
|
SkipHostEnv: true,
|
||||||
AllowedProtocols: []plugin.Protocol{
|
AllowedProtocols: []plugin.Protocol{
|
||||||
plugin.ProtocolGRPC,
|
plugin.ProtocolGRPC,
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,6 +21,10 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
@ -81,6 +85,16 @@ type Config struct {
|
||||||
// rejected. The client will also refuse to connect to any server that isn't
|
// rejected. The client will also refuse to connect to any server that isn't
|
||||||
// the original instance started by the client.
|
// the original instance started by the client.
|
||||||
AutoMTLS bool `json:"auto_mtls" mapstructure:"auto_mtls"`
|
AutoMTLS bool `json:"auto_mtls" mapstructure:"auto_mtls"`
|
||||||
|
// EnvPrefix defines the prefix for env vars to pass from the SFTPGo process
|
||||||
|
// environment to the plugin. Set to "none" to not pass any environment
|
||||||
|
// variable, set to "*" to pass all environment variables. If empty, the
|
||||||
|
// prefix is returned as the plugin name in uppercase with "-" replaced with
|
||||||
|
// "_" and a trailing "_". For example if the plugin name is
|
||||||
|
// sftpgo-plugin-eventsearch the prefix will be SFTPGO_PLUGIN_EVENTSEARCH_
|
||||||
|
EnvPrefix string `json:"env_prefix" mapstructure:"env_prefix"`
|
||||||
|
// Additional environment variable names to pass from the SFTPGo process
|
||||||
|
// environment to the plugin.
|
||||||
|
EnvVars []string `json:"env_vars" mapstructure:"env_vars"`
|
||||||
// unique identifier for kms plugins
|
// unique identifier for kms plugins
|
||||||
kmsID int
|
kmsID int
|
||||||
}
|
}
|
||||||
|
@ -99,6 +113,42 @@ func (c *Config) getSecureConfig() (*plugin.SecureConfig, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) getEnvVarPrefix() string {
|
||||||
|
if c.EnvPrefix == "none" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if c.EnvPrefix != "" {
|
||||||
|
return c.EnvPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := strings.ToUpper(filepath.Base(c.Cmd)) + "_"
|
||||||
|
return strings.ReplaceAll(prefix, "-", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) getCommand() *exec.Cmd {
|
||||||
|
cmd := exec.Command(c.Cmd, c.Args...)
|
||||||
|
cmd.Env = []string{}
|
||||||
|
|
||||||
|
if envVarPrefix := c.getEnvVarPrefix(); envVarPrefix != "" {
|
||||||
|
if envVarPrefix == "*" {
|
||||||
|
logger.Debug(logSender, "", "sharing all the environment variables with plugin %q", c.Cmd)
|
||||||
|
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
logger.Debug(logSender, "", "adding env vars with prefix %q for plugin %q", envVarPrefix, c.Cmd)
|
||||||
|
for _, val := range os.Environ() {
|
||||||
|
if strings.HasPrefix(val, envVarPrefix) {
|
||||||
|
cmd.Env = append(cmd.Env, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Debug(logSender, "", "additional env vars for plugin %q: %+v", c.Cmd, c.EnvVars)
|
||||||
|
for _, key := range c.EnvVars {
|
||||||
|
cmd.Env = append(cmd.Env, os.Getenv(key))
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) newKMSPluginSecretProvider(base kms.BaseSecret, url, masterKey string) kms.SecretProvider {
|
func (c *Config) newKMSPluginSecretProvider(base kms.BaseSecret, url, masterKey string) kms.SecretProvider {
|
||||||
return &kmsPluginSecretProvider{
|
return &kmsPluginSecretProvider{
|
||||||
BaseSecret: base,
|
BaseSecret: base,
|
||||||
|
|
|
@ -16,7 +16,6 @@ package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
@ -60,7 +59,8 @@ func (p *searcherPlugin) initialize() error {
|
||||||
client := plugin.NewClient(&plugin.ClientConfig{
|
client := plugin.NewClient(&plugin.ClientConfig{
|
||||||
HandshakeConfig: eventsearcher.Handshake,
|
HandshakeConfig: eventsearcher.Handshake,
|
||||||
Plugins: eventsearcher.PluginMap,
|
Plugins: eventsearcher.PluginMap,
|
||||||
Cmd: exec.Command(p.config.Cmd, p.config.Args...),
|
Cmd: p.config.getCommand(),
|
||||||
|
SkipHostEnv: true,
|
||||||
AllowedProtocols: []plugin.Protocol{
|
AllowedProtocols: []plugin.Protocol{
|
||||||
plugin.ProtocolGRPC,
|
plugin.ProtocolGRPC,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue