mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
Co-authored-by: Michael Schurter <mschurter@hashicorp.com> Co-authored-by: Tim Gross <tgross@hashicorp.com>
212 lines
5.9 KiB
Go
212 lines
5.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package taskrunner
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/hashicorp/consul-template/renderer"
|
|
"github.com/hashicorp/go-envparse"
|
|
log "github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/nomad/client/allocrunner/interfaces"
|
|
ti "github.com/hashicorp/nomad/client/allocrunner/taskrunner/interfaces"
|
|
"github.com/hashicorp/nomad/client/allocrunner/taskrunner/secrets"
|
|
"github.com/hashicorp/nomad/client/allocrunner/taskrunner/template"
|
|
"github.com/hashicorp/nomad/client/commonplugins"
|
|
"github.com/hashicorp/nomad/client/config"
|
|
"github.com/hashicorp/nomad/client/taskenv"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
type TemplateProvider interface {
|
|
BuildTemplate() *structs.Template
|
|
}
|
|
|
|
type PluginProvider interface {
|
|
Fetch(context.Context) (map[string]string, error)
|
|
}
|
|
|
|
type secretsHookConfig struct {
|
|
// logger is used to log
|
|
logger log.Logger
|
|
|
|
// lifecycle is used to interact with the task's lifecycle
|
|
lifecycle ti.TaskLifecycle
|
|
|
|
// events is used to emit events
|
|
events ti.EventEmitter
|
|
|
|
// clientConfig is the Nomad Client configuration
|
|
clientConfig *config.Config
|
|
|
|
// envBuilder is the environment variable builder for the task.
|
|
envBuilder *taskenv.Builder
|
|
|
|
// nomadNamespace is the job's Nomad namespace
|
|
nomadNamespace string
|
|
}
|
|
|
|
type secretsHook struct {
|
|
// logger is used to log
|
|
logger log.Logger
|
|
|
|
// lifecycle is used to interact with the task's lifecycle
|
|
lifecycle ti.TaskLifecycle
|
|
|
|
// events is used to emit events
|
|
events ti.EventEmitter
|
|
|
|
// clientConfig is the Nomad Client configuration
|
|
clientConfig *config.Config
|
|
|
|
// envBuilder is the environment variable builder for the task
|
|
envBuilder *taskenv.Builder
|
|
|
|
// nomadNamespace is the job's Nomad namespace
|
|
nomadNamespace string
|
|
|
|
// secrets to be fetched and populated for interpolation
|
|
secrets []*structs.Secret
|
|
}
|
|
|
|
func newSecretsHook(conf *secretsHookConfig, secrets []*structs.Secret) *secretsHook {
|
|
return &secretsHook{
|
|
logger: conf.logger,
|
|
lifecycle: conf.lifecycle,
|
|
events: conf.events,
|
|
clientConfig: conf.clientConfig,
|
|
envBuilder: conf.envBuilder,
|
|
nomadNamespace: conf.nomadNamespace,
|
|
secrets: secrets,
|
|
}
|
|
}
|
|
|
|
func (h *secretsHook) Name() string {
|
|
return "secrets"
|
|
}
|
|
|
|
func (h *secretsHook) Prestart(ctx context.Context, req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error {
|
|
tmplProvider, pluginProvider, err := h.buildSecretProviders(req.TaskDir.SecretsDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
templates := []*structs.Template{}
|
|
for _, p := range tmplProvider {
|
|
templates = append(templates, p.BuildTemplate())
|
|
}
|
|
|
|
vaultCluster := req.Task.GetVaultClusterName()
|
|
vaultConfig := h.clientConfig.GetVaultConfigs(h.logger)[vaultCluster]
|
|
|
|
mu := &sync.Mutex{}
|
|
contents := []byte{}
|
|
unblock := make(chan struct{})
|
|
tm, err := template.NewTaskTemplateManager(&template.TaskTemplateManagerConfig{
|
|
UnblockCh: unblock,
|
|
Lifecycle: h.lifecycle,
|
|
Events: h.events,
|
|
Templates: templates,
|
|
ClientConfig: h.clientConfig,
|
|
VaultToken: req.VaultToken,
|
|
VaultConfig: vaultConfig,
|
|
VaultNamespace: req.Alloc.Job.VaultNamespace,
|
|
TaskDir: req.TaskDir.Dir,
|
|
EnvBuilder: h.envBuilder,
|
|
MaxTemplateEventRate: template.DefaultMaxTemplateEventRate,
|
|
NomadNamespace: h.nomadNamespace,
|
|
NomadToken: req.NomadToken,
|
|
TaskID: req.Alloc.ID + "-" + req.Task.Name,
|
|
Logger: h.logger,
|
|
|
|
// This RenderFunc is used to keep any secret data from being written to disk.
|
|
RenderFunc: func(ri *renderer.RenderInput) (*renderer.RenderResult, error) {
|
|
// This RenderFunc is called by a single goroutine synchronously, but we
|
|
// lock the append in the event this behavior changes without us knowing.
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
contents = append(contents, ri.Contents...)
|
|
return &renderer.RenderResult{
|
|
DidRender: true,
|
|
WouldRender: true,
|
|
Contents: ri.Contents,
|
|
}, nil
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go tm.Run()
|
|
|
|
// Safeguard against the template manager continuing to run.
|
|
defer tm.Stop()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case <-unblock:
|
|
}
|
|
|
|
// Set secrets from templates
|
|
m, err := envparse.Parse(bytes.NewBuffer(contents))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h.envBuilder.SetSecrets(m)
|
|
|
|
// Set secrets from plugin providers
|
|
for _, p := range pluginProvider {
|
|
vars, err := p.Fetch(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h.envBuilder.SetSecrets(vars)
|
|
}
|
|
|
|
resp.Done = true
|
|
return nil
|
|
}
|
|
|
|
func (h *secretsHook) buildSecretProviders(secretDir string) ([]TemplateProvider, []PluginProvider, error) {
|
|
// Any configuration errors will be found when calling the secret providers constructor,
|
|
// so use a multierror to collect all errors and return them to the user at the same time.
|
|
tmplProvider, pluginProvider, mErr := []TemplateProvider{}, []PluginProvider{}, new(multierror.Error)
|
|
|
|
for idx, s := range h.secrets {
|
|
if s == nil {
|
|
continue
|
|
}
|
|
|
|
tmplFile := fmt.Sprintf("temp-%d", idx)
|
|
switch s.Provider {
|
|
case secrets.SecretProviderNomad:
|
|
if p, err := secrets.NewNomadProvider(s, secretDir, tmplFile, h.nomadNamespace); err != nil {
|
|
multierror.Append(mErr, err)
|
|
} else {
|
|
tmplProvider = append(tmplProvider, p)
|
|
}
|
|
case secrets.SecretProviderVault:
|
|
if p, err := secrets.NewVaultProvider(s, secretDir, tmplFile); err != nil {
|
|
multierror.Append(mErr, err)
|
|
} else {
|
|
tmplProvider = append(tmplProvider, p)
|
|
}
|
|
default:
|
|
plug, err := commonplugins.NewExternalSecretsPlugin(h.clientConfig.CommonPluginDir, s.Provider, s.Env)
|
|
if err != nil {
|
|
multierror.Append(mErr, err)
|
|
continue
|
|
}
|
|
pluginProvider = append(pluginProvider, secrets.NewExternalPluginProvider(plug, s.Name, s.Path))
|
|
}
|
|
}
|
|
|
|
return tmplProvider, pluginProvider, mErr.ErrorOrNil()
|
|
}
|