mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-08 07:37:01 +02:00
Implements running plugins in containers to give them some degree of isolation from the main Vault process and other plugins. It only supports running on Linux initially, where it is easiest to manage unix socket communication across the container boundary. Additionally * Adds -env arg to vault plugin register. * Don't return env from 'vault plugin info' Historically it's been omitted, and it could conceivably have secret information in it, so if we want to return it in the response, it should probably only be via explicit opt-in. Skipping for now though as it's not the main purpose of the commit.
236 lines
5.5 KiB
Go
236 lines
5.5 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package pluginutil
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
log "github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/go-plugin"
|
|
"github.com/hashicorp/go-plugin/runner"
|
|
"github.com/hashicorp/go-secure-stdlib/plugincontainer"
|
|
"github.com/hashicorp/go-secure-stdlib/plugincontainer/config"
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
)
|
|
|
|
type PluginClientConfig struct {
|
|
Name string
|
|
PluginType consts.PluginType
|
|
Version string
|
|
PluginSets map[int]plugin.PluginSet
|
|
HandshakeConfig plugin.HandshakeConfig
|
|
Logger log.Logger
|
|
IsMetadataMode bool
|
|
AutoMTLS bool
|
|
MLock bool
|
|
Wrapper RunnerUtil
|
|
}
|
|
|
|
type runConfig struct {
|
|
// Provided by PluginRunner
|
|
command string
|
|
image string
|
|
imageTag string
|
|
args []string
|
|
sha256 []byte
|
|
|
|
// Initialized with what's in PluginRunner.Env, but can be added to
|
|
env []string
|
|
|
|
PluginClientConfig
|
|
}
|
|
|
|
func overlayCmdSpec(base, cmd *exec.Cmd) {
|
|
if cmd.Path != "" {
|
|
base.Path = cmd.Path
|
|
}
|
|
if len(cmd.Args) > 0 {
|
|
base.Args = cmd.Args
|
|
}
|
|
if len(cmd.Env) > 0 {
|
|
base.Env = append(base.Env, cmd.Env...)
|
|
}
|
|
}
|
|
|
|
func (rc runConfig) makeConfig(ctx context.Context) (*plugin.ClientConfig, error) {
|
|
cmd := exec.Command(rc.command, rc.args...)
|
|
cmd.Env = append(cmd.Env, rc.env...)
|
|
|
|
// Add the mlock setting to the ENV of the plugin
|
|
if rc.MLock || (rc.Wrapper != nil && rc.Wrapper.MlockEnabled()) {
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", PluginMlockEnabled, "true"))
|
|
}
|
|
version, err := rc.Wrapper.VaultVersion(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version))
|
|
|
|
if rc.IsMetadataMode {
|
|
rc.Logger = rc.Logger.With("metadata", "true")
|
|
}
|
|
metadataEnv := fmt.Sprintf("%s=%t", PluginMetadataModeEnv, rc.IsMetadataMode)
|
|
cmd.Env = append(cmd.Env, metadataEnv)
|
|
|
|
automtlsEnv := fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, rc.AutoMTLS)
|
|
cmd.Env = append(cmd.Env, automtlsEnv)
|
|
|
|
var clientTLSConfig *tls.Config
|
|
if !rc.AutoMTLS && !rc.IsMetadataMode {
|
|
// Get a CA TLS Certificate
|
|
certBytes, key, err := generateCert()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Use CA to sign a client cert and return a configured TLS config
|
|
clientTLSConfig, err = createClientTLSConfig(certBytes, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Use CA to sign a server cert and wrap the values in a response wrapped
|
|
// token.
|
|
wrapToken, err := wrapServerConfig(ctx, rc.Wrapper, certBytes, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Add the response wrap token to the ENV of the plugin
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", PluginUnwrapTokenEnv, wrapToken))
|
|
}
|
|
|
|
clientConfig := &plugin.ClientConfig{
|
|
HandshakeConfig: rc.HandshakeConfig,
|
|
VersionedPlugins: rc.PluginSets,
|
|
TLSConfig: clientTLSConfig,
|
|
Logger: rc.Logger,
|
|
AllowedProtocols: []plugin.Protocol{
|
|
plugin.ProtocolNetRPC,
|
|
plugin.ProtocolGRPC,
|
|
},
|
|
AutoMTLS: rc.AutoMTLS,
|
|
}
|
|
if rc.image == "" {
|
|
clientConfig.Cmd = cmd
|
|
clientConfig.SecureConfig = &plugin.SecureConfig{
|
|
Checksum: rc.sha256,
|
|
Hash: sha256.New(),
|
|
}
|
|
} else {
|
|
clientConfig.SkipHostEnv = true
|
|
clientConfig.RunnerFunc = func(logger hclog.Logger, goPluginCmd *exec.Cmd, tmpDir string) (runner.Runner, error) {
|
|
overlayCmdSpec(goPluginCmd, cmd)
|
|
cfg := &config.ContainerConfig{
|
|
UnixSocketGroup: fmt.Sprintf("%d", os.Getgid()),
|
|
Image: rc.image,
|
|
Tag: rc.imageTag,
|
|
SHA256: fmt.Sprintf("%x", rc.sha256),
|
|
Labels: map[string]string{
|
|
"managed-by": "hashicorp.com/vault",
|
|
},
|
|
// TODO: More configurables.
|
|
// Defaulting to runsc will require installing gVisor in the GitHub runner.
|
|
// Runtime: "runsc",
|
|
// CgroupParent: "",
|
|
// NanoCpus: 100000000,
|
|
// Memory: 64 * 1024 * 1024,
|
|
// TODO: network
|
|
|
|
}
|
|
return plugincontainer.NewContainerRunner(logger, goPluginCmd, cfg, tmpDir)
|
|
}
|
|
}
|
|
return clientConfig, nil
|
|
}
|
|
|
|
func (rc runConfig) run(ctx context.Context) (*plugin.Client, error) {
|
|
clientConfig, err := rc.makeConfig(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client := plugin.NewClient(clientConfig)
|
|
return client, nil
|
|
}
|
|
|
|
type RunOpt func(*runConfig)
|
|
|
|
func Env(env ...string) RunOpt {
|
|
return func(rc *runConfig) {
|
|
rc.env = append(rc.env, env...)
|
|
}
|
|
}
|
|
|
|
func Runner(wrapper RunnerUtil) RunOpt {
|
|
return func(rc *runConfig) {
|
|
rc.Wrapper = wrapper
|
|
}
|
|
}
|
|
|
|
func PluginSets(pluginSets map[int]plugin.PluginSet) RunOpt {
|
|
return func(rc *runConfig) {
|
|
rc.PluginSets = pluginSets
|
|
}
|
|
}
|
|
|
|
func HandshakeConfig(hs plugin.HandshakeConfig) RunOpt {
|
|
return func(rc *runConfig) {
|
|
rc.HandshakeConfig = hs
|
|
}
|
|
}
|
|
|
|
func Logger(logger log.Logger) RunOpt {
|
|
return func(rc *runConfig) {
|
|
rc.Logger = logger
|
|
}
|
|
}
|
|
|
|
func MetadataMode(isMetadataMode bool) RunOpt {
|
|
return func(rc *runConfig) {
|
|
rc.IsMetadataMode = isMetadataMode
|
|
}
|
|
}
|
|
|
|
func AutoMTLS(autoMTLS bool) RunOpt {
|
|
return func(rc *runConfig) {
|
|
rc.AutoMTLS = autoMTLS
|
|
}
|
|
}
|
|
|
|
func MLock(mlock bool) RunOpt {
|
|
return func(rc *runConfig) {
|
|
rc.MLock = mlock
|
|
}
|
|
}
|
|
|
|
func (r *PluginRunner) RunConfig(ctx context.Context, opts ...RunOpt) (*plugin.Client, error) {
|
|
var image, imageTag string
|
|
if r.OCIImage != "" {
|
|
image = r.OCIImage
|
|
imageTag = strings.TrimPrefix(r.Version, "v")
|
|
}
|
|
rc := runConfig{
|
|
command: r.Command,
|
|
image: image,
|
|
imageTag: imageTag,
|
|
args: r.Args,
|
|
sha256: r.Sha256,
|
|
env: r.Env,
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(&rc)
|
|
}
|
|
|
|
return rc.run(ctx)
|
|
}
|