github.com/hashicorp/vault/sdk@v0.11.0/helper/pluginutil/run_config.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package pluginutil 5 6 import ( 7 "context" 8 "crypto/sha256" 9 "crypto/tls" 10 "fmt" 11 "os" 12 "os/exec" 13 "strconv" 14 "strings" 15 16 log "github.com/hashicorp/go-hclog" 17 "github.com/hashicorp/go-plugin" 18 "github.com/hashicorp/go-secure-stdlib/plugincontainer" 19 "github.com/hashicorp/vault/sdk/helper/consts" 20 "github.com/hashicorp/vault/sdk/helper/pluginruntimeutil" 21 ) 22 23 const ( 24 // Labels for plugin container ownership 25 labelVaultPID = "com.hashicorp.vault.pid" 26 labelVaultClusterID = "com.hashicorp.vault.cluster.id" 27 labelVaultPluginName = "com.hashicorp.vault.plugin.name" 28 labelVaultPluginVersion = "com.hashicorp.vault.plugin.version" 29 labelVaultPluginType = "com.hashicorp.vault.plugin.type" 30 ) 31 32 type PluginClientConfig struct { 33 Name string 34 PluginType consts.PluginType 35 Version string 36 PluginSets map[int]plugin.PluginSet 37 HandshakeConfig plugin.HandshakeConfig 38 Logger log.Logger 39 IsMetadataMode bool 40 AutoMTLS bool 41 MLock bool 42 Wrapper RunnerUtil 43 } 44 45 type runConfig struct { 46 // Provided by PluginRunner 47 command string 48 image string 49 imageTag string 50 args []string 51 sha256 []byte 52 53 // Initialized with what's in PluginRunner.Env, but can be added to 54 env []string 55 56 runtimeConfig *pluginruntimeutil.PluginRuntimeConfig 57 58 PluginClientConfig 59 tmpdir string 60 } 61 62 func (rc runConfig) mlockEnabled() bool { 63 return rc.MLock || (rc.Wrapper != nil && rc.Wrapper.MlockEnabled()) 64 } 65 66 func (rc runConfig) generateCmd(ctx context.Context) (cmd *exec.Cmd, clientTLSConfig *tls.Config, err error) { 67 cmd = exec.Command(rc.command, rc.args...) 68 env := rc.env 69 70 // Add the mlock setting to the ENV of the plugin 71 if rc.mlockEnabled() { 72 env = append(env, fmt.Sprintf("%s=%s", PluginMlockEnabled, "true")) 73 } 74 version, err := rc.Wrapper.VaultVersion(ctx) 75 if err != nil { 76 return nil, nil, err 77 } 78 env = append(env, fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version)) 79 80 if rc.IsMetadataMode { 81 rc.Logger = rc.Logger.With("metadata", "true") 82 } 83 metadataEnv := fmt.Sprintf("%s=%t", PluginMetadataModeEnv, rc.IsMetadataMode) 84 env = append(env, metadataEnv) 85 86 automtlsEnv := fmt.Sprintf("%s=%t", PluginAutoMTLSEnv, rc.AutoMTLS) 87 env = append(env, automtlsEnv) 88 89 if !rc.AutoMTLS && !rc.IsMetadataMode { 90 // Get a CA TLS Certificate 91 certBytes, key, err := generateCert() 92 if err != nil { 93 return nil, nil, err 94 } 95 96 // Use CA to sign a client cert and return a configured TLS config 97 clientTLSConfig, err = createClientTLSConfig(certBytes, key) 98 if err != nil { 99 return nil, nil, err 100 } 101 102 // Use CA to sign a server cert and wrap the values in a response wrapped 103 // token. 104 wrapToken, err := wrapServerConfig(ctx, rc.Wrapper, certBytes, key) 105 if err != nil { 106 return nil, nil, err 107 } 108 109 // Add the response wrap token to the ENV of the plugin 110 env = append(env, fmt.Sprintf("%s=%s", PluginUnwrapTokenEnv, wrapToken)) 111 } 112 113 if rc.image == "" { 114 // go-plugin has always overridden user-provided env vars with the OS 115 // (Vault process) env vars, but we want plugins to be able to override 116 // the Vault process env. We don't want to make a breaking change in 117 // go-plugin so always set SkipHostEnv and replicate the legacy behavior 118 // ourselves if user opts in. 119 if legacy, _ := strconv.ParseBool(os.Getenv(PluginUseLegacyEnvLayering)); legacy { 120 // Env vars are layered as follows, with later entries overriding 121 // earlier entries if there are duplicate keys: 122 // 1. Env specified at plugin registration 123 // 2. Env from Vault SDK 124 // 3. Env from Vault process (OS) 125 // 4. Env from go-plugin 126 cmd.Env = append(env, os.Environ()...) 127 } else { 128 // Env vars are layered as follows, with later entries overriding 129 // earlier entries if there are duplicate keys: 130 // 1. Env from Vault process (OS) 131 // 2. Env specified at plugin registration 132 // 3. Env from Vault SDK 133 // 4. Env from go-plugin 134 cmd.Env = append(os.Environ(), env...) 135 } 136 } else { 137 // Containerized plugins do not inherit any env vars from Vault. 138 cmd.Env = env 139 } 140 141 return cmd, clientTLSConfig, nil 142 } 143 144 func (rc runConfig) makeConfig(ctx context.Context) (*plugin.ClientConfig, error) { 145 cmd, clientTLSConfig, err := rc.generateCmd(ctx) 146 if err != nil { 147 return nil, err 148 } 149 150 clientConfig := &plugin.ClientConfig{ 151 HandshakeConfig: rc.HandshakeConfig, 152 VersionedPlugins: rc.PluginSets, 153 TLSConfig: clientTLSConfig, 154 Logger: rc.Logger, 155 AllowedProtocols: []plugin.Protocol{ 156 plugin.ProtocolNetRPC, 157 plugin.ProtocolGRPC, 158 }, 159 AutoMTLS: rc.AutoMTLS, 160 SkipHostEnv: true, 161 } 162 if rc.image == "" { 163 clientConfig.Cmd = cmd 164 clientConfig.SecureConfig = &plugin.SecureConfig{ 165 Checksum: rc.sha256, 166 Hash: sha256.New(), 167 } 168 } else { 169 containerCfg, err := rc.containerConfig(ctx, cmd.Env) 170 if err != nil { 171 return nil, err 172 } 173 clientConfig.RunnerFunc = containerCfg.NewContainerRunner 174 clientConfig.UnixSocketConfig = &plugin.UnixSocketConfig{ 175 Group: strconv.Itoa(containerCfg.GroupAdd), 176 TempDir: rc.tmpdir, 177 } 178 clientConfig.GRPCBrokerMultiplex = true 179 } 180 return clientConfig, nil 181 } 182 183 func (rc runConfig) containerConfig(ctx context.Context, env []string) (*plugincontainer.Config, error) { 184 clusterID, err := rc.Wrapper.ClusterID(ctx) 185 if err != nil { 186 return nil, err 187 } 188 cfg := &plugincontainer.Config{ 189 Image: rc.image, 190 Tag: rc.imageTag, 191 SHA256: fmt.Sprintf("%x", rc.sha256), 192 193 Env: env, 194 GroupAdd: os.Getegid(), 195 Runtime: consts.DefaultContainerPluginOCIRuntime, 196 CapIPCLock: rc.mlockEnabled(), 197 Labels: map[string]string{ 198 labelVaultPID: strconv.Itoa(os.Getpid()), 199 labelVaultClusterID: clusterID, 200 labelVaultPluginName: rc.PluginClientConfig.Name, 201 labelVaultPluginType: rc.PluginClientConfig.PluginType.String(), 202 labelVaultPluginVersion: rc.PluginClientConfig.Version, 203 }, 204 } 205 206 // Use rc.command and rc.args directly instead of cmd.Path and cmd.Args, as 207 // exec.Command may mutate the provided command. 208 if rc.command != "" { 209 cfg.Entrypoint = []string{rc.command} 210 } 211 if len(rc.args) > 0 { 212 cfg.Args = rc.args 213 } 214 if rc.runtimeConfig != nil { 215 cfg.CgroupParent = rc.runtimeConfig.CgroupParent 216 cfg.NanoCpus = rc.runtimeConfig.CPU 217 cfg.Memory = rc.runtimeConfig.Memory 218 if rc.runtimeConfig.OCIRuntime != "" { 219 cfg.Runtime = rc.runtimeConfig.OCIRuntime 220 } 221 if rc.runtimeConfig.Rootless { 222 cfg.Rootless = true 223 } 224 } 225 226 return cfg, nil 227 } 228 229 func (rc runConfig) run(ctx context.Context) (*plugin.Client, error) { 230 clientConfig, err := rc.makeConfig(ctx) 231 if err != nil { 232 return nil, err 233 } 234 235 client := plugin.NewClient(clientConfig) 236 return client, nil 237 } 238 239 type RunOpt func(*runConfig) 240 241 func Env(env ...string) RunOpt { 242 return func(rc *runConfig) { 243 rc.env = append(rc.env, env...) 244 } 245 } 246 247 func Runner(wrapper RunnerUtil) RunOpt { 248 return func(rc *runConfig) { 249 rc.Wrapper = wrapper 250 } 251 } 252 253 func PluginSets(pluginSets map[int]plugin.PluginSet) RunOpt { 254 return func(rc *runConfig) { 255 rc.PluginSets = pluginSets 256 } 257 } 258 259 func HandshakeConfig(hs plugin.HandshakeConfig) RunOpt { 260 return func(rc *runConfig) { 261 rc.HandshakeConfig = hs 262 } 263 } 264 265 func Logger(logger log.Logger) RunOpt { 266 return func(rc *runConfig) { 267 rc.Logger = logger 268 } 269 } 270 271 func MetadataMode(isMetadataMode bool) RunOpt { 272 return func(rc *runConfig) { 273 rc.IsMetadataMode = isMetadataMode 274 } 275 } 276 277 func AutoMTLS(autoMTLS bool) RunOpt { 278 return func(rc *runConfig) { 279 rc.AutoMTLS = autoMTLS 280 } 281 } 282 283 func MLock(mlock bool) RunOpt { 284 return func(rc *runConfig) { 285 rc.MLock = mlock 286 } 287 } 288 289 func (r *PluginRunner) RunConfig(ctx context.Context, opts ...RunOpt) (*plugin.Client, error) { 290 var image, imageTag string 291 if r.OCIImage != "" { 292 image = r.OCIImage 293 imageTag = strings.TrimPrefix(r.Version, "v") 294 } 295 rc := runConfig{ 296 command: r.Command, 297 image: image, 298 imageTag: imageTag, 299 args: r.Args, 300 sha256: r.Sha256, 301 env: r.Env, 302 runtimeConfig: r.RuntimeConfig, 303 tmpdir: r.Tmpdir, 304 PluginClientConfig: PluginClientConfig{ 305 Name: r.Name, 306 PluginType: r.Type, 307 Version: r.Version, 308 }, 309 } 310 311 for _, opt := range opts { 312 opt(&rc) 313 } 314 315 return rc.run(ctx) 316 }