mirror of
https://github.com/hashicorp/vault.git
synced 2025-09-01 12:01:10 +02:00
* upgrade hcl dependency on api pkg
This upgrades the hcl dependency for the API pkg,
and adapts its usage so users of our API pkg are
not affected. There's no good way of communicating
a warning via a library call so we don't.
The tokenHelper which is used by all Vault CLI
commands in order to create the Vault client, as
well as directly used by the login and server
commands, is implemented on the api pkg, so this
upgrade also affects all of those commands. Seems
like this was only moved to the api pkg because
the Terraform provider uses it, and I thought
creating a full copy of all those files back under
command would be too much spaghetti.
Also leaving some TODOs to make next deprecation
steps easier.
* upgrade hcl dependency in vault and sdk pkgs
* upgrade hcl dependency in vault and sdk pkgs
* add CLI warnings to commands that take a config
- vault agent (unit test on CMD warning)
- vault proxy (unit test on CMD warning)
- vault server (no test for the warning)
- vault operator diagnose (no tests at all, uses the
same function as vault server
* ignore duplicates on ParseKMSes function
* Extend policy parsing functions and warn on policy store
* Add warning on policy fmt with duplicate attributes
* Add warnings when creating/updating policy with duplicate HCL attrs
* Add log warning when switchedGetPolicy finds duplicate attrs
Following operations can trigger this warning when they run into a policy
with duplicate attributes:
* replication filtered path namespaces invalidation
* policy read API
* building an ACL (for many different purposes like most authZ operations)
* looking up DR token policies
* creating a token with named policies
* when caching the policies for all namespaces during unseal
* Print log warnings when token inline policy has duplicate attrs
No unit tests on these as new test infra would have to be built on all.
Operations affected, which will now print a log warning when the retrieved
token has an inline policy with duplicate attributes:
* capabilities endpoints in sys mount
* handing events under a subscription with a token with duplicate
attrs in inline policies
* token used to create another token has duplicate attrs in inline
policies (sudo check)
* all uses of fetchACLTokenEntryAndEntity when the request uses a
token with inline policies with duplicate attrs. Almost all reqs
are subject to this
* when tokens are created with inline policies (unclear exactly how that
can happen)
* add changelog and deprecation notice
* add missing copywrite notice
* fix copy-paste mistake
good thing it was covered by unit tests
* Fix manual parsing of telemetry field in SharedConfig
This commit in the hcl library was not in the
v1.0.1-vault-5 version we're using but is
included in v1.0.1-vault-7:
e80118accb
This thing of reusing when parsing means that
our approach of manually re-parsing fields
on top of fields that have already been parsed
by the hcl annotation causes strings (maybe
more?) to concatenate.
Fix that by removing annotation. There's
actually more occurrences of this thing of
automatically parsing something that is also
manually parsing. In some places we could
just remove the boilerplate manual parsing, in
others we better remove the auto parsing, but
I don't wanna pull at that thread right now. I
just checked that all places at least fully
overwrite the automatically parsed field
instead of reusing it as the target of the
decode call. The only exception is the AOP
field on ent but that doesn't have maps or
slices, so I think it's fine.
An alternative approach would be to ensure
that the auto-parsed value is discarded,
like the current parseCache function does
note how it's template not templates
* Fix linter complaints
* Update command/base_predict.go
Co-authored-by: Mike Palmiotto <mike.palmiotto@hashicorp.com>
* address review
* remove copywrite headers
* re-add copywrite headers
* make fmt
* Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
* Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
* Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
* undo changes to deprecation.mdx
* remove deprecation doc
* fix conflict with changes from main
---------
Co-authored-by: Mike Palmiotto <mike.palmiotto@hashicorp.com>
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
1422 lines
39 KiB
Go
1422 lines
39 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package config
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
ctconfig "github.com/hashicorp/consul-template/config"
|
|
ctsignals "github.com/hashicorp/consul-template/signals"
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
"github.com/hashicorp/hcl"
|
|
"github.com/hashicorp/hcl/hcl/ast"
|
|
"github.com/hashicorp/vault/command/agentproxyshared"
|
|
"github.com/hashicorp/vault/helper/namespace"
|
|
"github.com/hashicorp/vault/helper/random"
|
|
"github.com/hashicorp/vault/internalshared/configutil"
|
|
"github.com/hashicorp/vault/sdk/helper/pointerutil"
|
|
"github.com/mitchellh/mapstructure"
|
|
"k8s.io/utils/strings/slices"
|
|
)
|
|
|
|
// Config is the configuration for Vault Agent.
|
|
type Config struct {
|
|
*configutil.SharedConfig `hcl:"-"`
|
|
|
|
AutoAuth *AutoAuth `hcl:"auto_auth"`
|
|
ExitAfterAuth bool `hcl:"exit_after_auth"`
|
|
Cache *Cache `hcl:"cache"`
|
|
APIProxy *APIProxy `hcl:"api_proxy"`
|
|
Vault *Vault `hcl:"vault"`
|
|
TemplateConfig *TemplateConfig `hcl:"template_config"`
|
|
Templates []*ctconfig.TemplateConfig `hcl:"templates"`
|
|
DisableIdleConns []string `hcl:"disable_idle_connections"`
|
|
DisableIdleConnsAPIProxy bool `hcl:"-"`
|
|
DisableIdleConnsTemplating bool `hcl:"-"`
|
|
DisableIdleConnsAutoAuth bool `hcl:"-"`
|
|
DisableKeepAlives []string `hcl:"disable_keep_alives"`
|
|
DisableKeepAlivesAPIProxy bool `hcl:"-"`
|
|
DisableKeepAlivesTemplating bool `hcl:"-"`
|
|
DisableKeepAlivesAutoAuth bool `hcl:"-"`
|
|
Exec *ExecConfig `hcl:"exec,optional"`
|
|
EnvTemplates []*ctconfig.TemplateConfig `hcl:"env_template,optional"`
|
|
}
|
|
|
|
const (
|
|
DisableIdleConnsEnv = "VAULT_AGENT_DISABLE_IDLE_CONNECTIONS"
|
|
DisableKeepAlivesEnv = "VAULT_AGENT_DISABLE_KEEP_ALIVES"
|
|
|
|
DefaultTemplateConfigMaxConnsPerHost = 10
|
|
)
|
|
|
|
func (c *Config) Prune() {
|
|
for _, l := range c.Listeners {
|
|
l.RawConfig = nil
|
|
l.Profiling.UnusedKeys = nil
|
|
l.Telemetry.UnusedKeys = nil
|
|
l.CustomResponseHeaders = nil
|
|
}
|
|
c.FoundKeys = nil
|
|
c.UnusedKeys = nil
|
|
c.SharedConfig.FoundKeys = nil
|
|
c.SharedConfig.UnusedKeys = nil
|
|
if c.Telemetry != nil {
|
|
c.Telemetry.FoundKeys = nil
|
|
c.Telemetry.UnusedKeys = nil
|
|
}
|
|
}
|
|
|
|
type Retry struct {
|
|
NumRetries int `hcl:"num_retries"`
|
|
}
|
|
|
|
// Vault contains configuration for connecting to Vault servers
|
|
type Vault struct {
|
|
Address string `hcl:"address"`
|
|
CACert string `hcl:"ca_cert"`
|
|
CAPath string `hcl:"ca_path"`
|
|
TLSSkipVerify bool `hcl:"-"`
|
|
TLSSkipVerifyRaw interface{} `hcl:"tls_skip_verify"`
|
|
ClientCert string `hcl:"client_cert"`
|
|
ClientKey string `hcl:"client_key"`
|
|
TLSServerName string `hcl:"tls_server_name"`
|
|
Namespace string `hcl:"namespace"`
|
|
Retry *Retry `hcl:"retry"`
|
|
}
|
|
|
|
// transportDialer is an interface that allows passing a custom dialer function
|
|
// to an HTTP client's transport config
|
|
type transportDialer interface {
|
|
// Dial is intended to match https://pkg.go.dev/net#Dialer.Dial
|
|
Dial(network, address string) (net.Conn, error)
|
|
|
|
// DialContext is intended to match https://pkg.go.dev/net#Dialer.DialContext
|
|
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
|
}
|
|
|
|
// APIProxy contains any configuration needed for proxy mode
|
|
type APIProxy struct {
|
|
UseAutoAuthTokenRaw interface{} `hcl:"use_auto_auth_token"`
|
|
UseAutoAuthToken bool `hcl:"-"`
|
|
ForceAutoAuthToken bool `hcl:"-"`
|
|
EnforceConsistency string `hcl:"enforce_consistency"`
|
|
WhenInconsistent string `hcl:"when_inconsistent"`
|
|
}
|
|
|
|
// Cache contains any configuration needed for Cache mode
|
|
type Cache struct {
|
|
UseAutoAuthTokenRaw interface{} `hcl:"use_auto_auth_token"`
|
|
UseAutoAuthToken bool `hcl:"-"`
|
|
ForceAutoAuthToken bool `hcl:"-"`
|
|
EnforceConsistency string `hcl:"enforce_consistency"`
|
|
WhenInconsistent string `hcl:"when_inconsistent"`
|
|
Persist *agentproxyshared.PersistConfig `hcl:"persist"`
|
|
InProcDialer transportDialer `hcl:"-"`
|
|
}
|
|
|
|
// AutoAuth is the configured authentication method and sinks
|
|
type AutoAuth struct {
|
|
Method *Method `hcl:"-"`
|
|
Sinks []*Sink `hcl:"sinks"`
|
|
|
|
EnableReauthOnNewCredentials bool `hcl:"enable_reauth_on_new_credentials"`
|
|
}
|
|
|
|
// Method represents the configuration for the authentication backend
|
|
type Method struct {
|
|
Type string
|
|
MountPath string `hcl:"mount_path"`
|
|
WrapTTLRaw interface{} `hcl:"wrap_ttl"`
|
|
WrapTTL time.Duration `hcl:"-"`
|
|
MinBackoffRaw interface{} `hcl:"min_backoff"`
|
|
MinBackoff time.Duration `hcl:"-"`
|
|
MaxBackoffRaw interface{} `hcl:"max_backoff"`
|
|
MaxBackoff time.Duration `hcl:"-"`
|
|
Namespace string `hcl:"namespace"`
|
|
ExitOnError bool `hcl:"exit_on_err"`
|
|
Config map[string]interface{}
|
|
}
|
|
|
|
// Sink defines a location to write the authenticated token
|
|
type Sink struct {
|
|
Type string
|
|
WrapTTLRaw interface{} `hcl:"wrap_ttl"`
|
|
WrapTTL time.Duration `hcl:"-"`
|
|
DHType string `hcl:"dh_type"`
|
|
DeriveKey bool `hcl:"derive_key"`
|
|
DHPath string `hcl:"dh_path"`
|
|
AAD string `hcl:"aad"`
|
|
AADEnvVar string `hcl:"aad_env_var"`
|
|
Config map[string]interface{}
|
|
}
|
|
|
|
// TemplateConfig defines global behaviors around template
|
|
type TemplateConfig struct {
|
|
ExitOnRetryFailure bool `hcl:"exit_on_retry_failure"`
|
|
StaticSecretRenderIntRaw interface{} `hcl:"static_secret_render_interval"`
|
|
StaticSecretRenderInt time.Duration `hcl:"-"`
|
|
MaxConnectionsPerHostRaw interface{} `hcl:"max_connections_per_host"`
|
|
MaxConnectionsPerHost int `hcl:"-"`
|
|
LeaseRenewalThreshold *float64 `hcl:"lease_renewal_threshold"`
|
|
}
|
|
|
|
type ExecConfig struct {
|
|
Command []string `hcl:"command,attr" mapstructure:"command"`
|
|
RestartOnSecretChanges string `hcl:"restart_on_secret_changes,optional" mapstructure:"restart_on_secret_changes"`
|
|
RestartStopSignal os.Signal `hcl:"-" mapstructure:"restart_stop_signal"`
|
|
ChildProcessStdout string `mapstructure:"child_process_stdout"`
|
|
ChildProcessStderr string `mapstructure:"child_process_stderr"`
|
|
}
|
|
|
|
func NewConfig() *Config {
|
|
return &Config{
|
|
SharedConfig: new(configutil.SharedConfig),
|
|
TemplateConfig: &TemplateConfig{
|
|
MaxConnectionsPerHost: DefaultTemplateConfigMaxConnsPerHost,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Merge merges two Agent configurations.
|
|
func (c *Config) Merge(c2 *Config) *Config {
|
|
if c2 == nil {
|
|
return c
|
|
}
|
|
|
|
result := NewConfig()
|
|
|
|
result.SharedConfig = c.SharedConfig
|
|
if c2.SharedConfig != nil {
|
|
result.SharedConfig = c.SharedConfig.Merge(c2.SharedConfig)
|
|
}
|
|
|
|
result.AutoAuth = c.AutoAuth
|
|
if c2.AutoAuth != nil {
|
|
result.AutoAuth = c2.AutoAuth
|
|
}
|
|
|
|
result.Cache = c.Cache
|
|
if c2.Cache != nil {
|
|
result.Cache = c2.Cache
|
|
}
|
|
|
|
result.APIProxy = c.APIProxy
|
|
if c2.APIProxy != nil {
|
|
result.APIProxy = c2.APIProxy
|
|
}
|
|
|
|
result.DisableMlock = c.DisableMlock
|
|
if c2.DisableMlock {
|
|
result.DisableMlock = c2.DisableMlock
|
|
}
|
|
|
|
// For these, ignore the non-specific one and overwrite them all
|
|
result.DisableIdleConnsAutoAuth = c.DisableIdleConnsAutoAuth
|
|
if c2.DisableIdleConnsAutoAuth {
|
|
result.DisableIdleConnsAutoAuth = c2.DisableIdleConnsAutoAuth
|
|
}
|
|
|
|
result.DisableIdleConnsAPIProxy = c.DisableIdleConnsAPIProxy
|
|
if c2.DisableIdleConnsAPIProxy {
|
|
result.DisableIdleConnsAPIProxy = c2.DisableIdleConnsAPIProxy
|
|
}
|
|
|
|
result.DisableIdleConnsTemplating = c.DisableIdleConnsTemplating
|
|
if c2.DisableIdleConnsTemplating {
|
|
result.DisableIdleConnsTemplating = c2.DisableIdleConnsTemplating
|
|
}
|
|
|
|
result.DisableKeepAlivesAutoAuth = c.DisableKeepAlivesAutoAuth
|
|
if c2.DisableKeepAlivesAutoAuth {
|
|
result.DisableKeepAlivesAutoAuth = c2.DisableKeepAlivesAutoAuth
|
|
}
|
|
|
|
result.DisableKeepAlivesAPIProxy = c.DisableKeepAlivesAPIProxy
|
|
if c2.DisableKeepAlivesAPIProxy {
|
|
result.DisableKeepAlivesAPIProxy = c2.DisableKeepAlivesAPIProxy
|
|
}
|
|
|
|
result.DisableKeepAlivesTemplating = c.DisableKeepAlivesTemplating
|
|
if c2.DisableKeepAlivesTemplating {
|
|
result.DisableKeepAlivesTemplating = c2.DisableKeepAlivesTemplating
|
|
}
|
|
|
|
// Instead of checking if TemplateConfig is not nil, we compare against the default value
|
|
// that is set in NewConfig to determine if a TemplateConfig was specified in the config
|
|
defaultConfig := TemplateConfig{
|
|
MaxConnectionsPerHost: DefaultTemplateConfigMaxConnsPerHost,
|
|
}
|
|
result.TemplateConfig = c.TemplateConfig
|
|
if *c2.TemplateConfig != defaultConfig {
|
|
result.TemplateConfig = c2.TemplateConfig
|
|
}
|
|
|
|
for _, l := range c.Templates {
|
|
result.Templates = append(result.Templates, l)
|
|
}
|
|
for _, l := range c2.Templates {
|
|
result.Templates = append(result.Templates, l)
|
|
}
|
|
|
|
result.ExitAfterAuth = c.ExitAfterAuth
|
|
if c2.ExitAfterAuth {
|
|
result.ExitAfterAuth = c2.ExitAfterAuth
|
|
}
|
|
|
|
result.Vault = c.Vault
|
|
if c2.Vault != nil {
|
|
result.Vault = c2.Vault
|
|
}
|
|
|
|
result.PidFile = c.PidFile
|
|
if c2.PidFile != "" {
|
|
result.PidFile = c2.PidFile
|
|
}
|
|
|
|
result.Exec = c.Exec
|
|
if c2.Exec != nil {
|
|
result.Exec = c2.Exec
|
|
}
|
|
|
|
for _, envTmpl := range c.EnvTemplates {
|
|
result.EnvTemplates = append(result.EnvTemplates, envTmpl)
|
|
}
|
|
|
|
for _, envTmpl := range c2.EnvTemplates {
|
|
result.EnvTemplates = append(result.EnvTemplates, envTmpl)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// IsDefaultListerDefined returns true if a default listener has been defined
|
|
// in this config
|
|
func (c *Config) IsDefaultListerDefined() bool {
|
|
for _, l := range c.Listeners {
|
|
if l.Role != "metrics_only" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ValidateConfig validates an Agent configuration after it has been fully merged together, to
|
|
// ensure that required combinations of configs are there
|
|
func (c *Config) ValidateConfig() error {
|
|
if c.APIProxy != nil && c.Cache != nil {
|
|
if c.Cache.UseAutoAuthTokenRaw != nil {
|
|
if c.APIProxy.UseAutoAuthTokenRaw != nil {
|
|
return fmt.Errorf("use_auto_auth_token defined in both api_proxy and cache config. Please remove this configuration from the cache block")
|
|
} else {
|
|
c.APIProxy.ForceAutoAuthToken = c.Cache.ForceAutoAuthToken
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.Cache != nil {
|
|
if len(c.Listeners) < 1 && len(c.Templates) < 1 && len(c.EnvTemplates) < 1 {
|
|
return fmt.Errorf("enabling the cache requires at least 1 template or 1 listener to be defined")
|
|
}
|
|
|
|
if c.Cache.UseAutoAuthToken {
|
|
if c.AutoAuth == nil {
|
|
return fmt.Errorf("cache.use_auto_auth_token is true but auto_auth not configured")
|
|
}
|
|
if c.AutoAuth != nil && c.AutoAuth.Method != nil && c.AutoAuth.Method.WrapTTL > 0 {
|
|
return fmt.Errorf("cache.use_auto_auth_token is true and auto_auth uses wrapping")
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.APIProxy != nil {
|
|
if len(c.Listeners) < 1 {
|
|
return fmt.Errorf("configuring the api_proxy requires at least 1 listener to be defined")
|
|
}
|
|
|
|
if c.APIProxy.UseAutoAuthToken {
|
|
if c.AutoAuth == nil {
|
|
return fmt.Errorf("api_proxy.use_auto_auth_token is true but auto_auth not configured")
|
|
}
|
|
if c.AutoAuth != nil && c.AutoAuth.Method != nil && c.AutoAuth.Method.WrapTTL > 0 {
|
|
return fmt.Errorf("api_proxy.use_auto_auth_token is true and auto_auth uses wrapping")
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.AutoAuth != nil {
|
|
if len(c.AutoAuth.Sinks) == 0 &&
|
|
(c.APIProxy == nil || !c.APIProxy.UseAutoAuthToken) &&
|
|
len(c.Templates) == 0 &&
|
|
len(c.EnvTemplates) == 0 {
|
|
return fmt.Errorf("auto_auth requires at least one sink or at least one template or api_proxy.use_auto_auth_token=true")
|
|
}
|
|
}
|
|
|
|
if c.AutoAuth == nil && c.Cache == nil && len(c.Listeners) == 0 {
|
|
return fmt.Errorf("no auto_auth, cache, or listener block found in config")
|
|
}
|
|
|
|
return c.validateEnvTemplateConfig()
|
|
}
|
|
|
|
func (c *Config) validateEnvTemplateConfig() error {
|
|
// if we are not in env-template mode, exit early
|
|
if c.Exec == nil && len(c.EnvTemplates) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if c.Exec == nil {
|
|
return fmt.Errorf("a top-level 'exec' element must be specified with 'env_template' entries")
|
|
}
|
|
|
|
if len(c.EnvTemplates) == 0 {
|
|
return fmt.Errorf("must specify at least one 'env_template' element with a top-level 'exec' element")
|
|
}
|
|
|
|
if c.APIProxy != nil {
|
|
return fmt.Errorf("'api_proxy' cannot be specified with 'env_template' entries")
|
|
}
|
|
|
|
if len(c.Templates) > 0 {
|
|
return fmt.Errorf("'template' cannot be specified with 'env_template' entries")
|
|
}
|
|
|
|
if len(c.Exec.Command) == 0 {
|
|
return fmt.Errorf("'exec' requires a non-empty 'command' field")
|
|
}
|
|
|
|
if !slices.Contains([]string{"always", "never"}, c.Exec.RestartOnSecretChanges) {
|
|
return fmt.Errorf("'exec.restart_on_secret_changes' unexpected value: %q", c.Exec.RestartOnSecretChanges)
|
|
}
|
|
|
|
uniqueKeys := make(map[string]struct{})
|
|
|
|
for _, template := range c.EnvTemplates {
|
|
// Required:
|
|
// - the key (environment variable name)
|
|
// - either "contents" or "source"
|
|
// Optional / permitted:
|
|
// - error_on_missing_key
|
|
// - error_fatal
|
|
// - left_delimiter
|
|
// - right_delimiter
|
|
// - ExtFuncMap
|
|
// - function_denylist / function_blacklist
|
|
|
|
if template.MapToEnvironmentVariable == nil {
|
|
return fmt.Errorf("env_template: an environment variable name is required")
|
|
}
|
|
|
|
key := *template.MapToEnvironmentVariable
|
|
|
|
if _, exists := uniqueKeys[key]; exists {
|
|
return fmt.Errorf("env_template: duplicate environment variable name: %q", key)
|
|
}
|
|
|
|
uniqueKeys[key] = struct{}{}
|
|
|
|
if template.Contents == nil && template.Source == nil {
|
|
return fmt.Errorf("env_template[%s]: either 'contents' or 'source' must be specified", key)
|
|
}
|
|
|
|
if template.Contents != nil && template.Source != nil {
|
|
return fmt.Errorf("env_template[%s]: 'contents' and 'source' cannot be specified together", key)
|
|
}
|
|
|
|
if template.Backup != nil {
|
|
return fmt.Errorf("env_template[%s]: 'backup' is not allowed", key)
|
|
}
|
|
|
|
if template.Command != nil {
|
|
return fmt.Errorf("env_template[%s]: 'command' is not allowed", key)
|
|
}
|
|
|
|
if template.CommandTimeout != nil {
|
|
return fmt.Errorf("env_template[%s]: 'command_timeout' is not allowed", key)
|
|
}
|
|
|
|
if template.CreateDestDirs != nil {
|
|
return fmt.Errorf("env_template[%s]: 'create_dest_dirs' is not allowed", key)
|
|
}
|
|
|
|
if template.Destination != nil {
|
|
return fmt.Errorf("env_template[%s]: 'destination' is not allowed", key)
|
|
}
|
|
|
|
if template.Exec != nil {
|
|
return fmt.Errorf("env_template[%s]: 'exec' is not allowed", key)
|
|
}
|
|
|
|
if template.Perms != nil {
|
|
return fmt.Errorf("env_template[%s]: 'perms' is not allowed", key)
|
|
}
|
|
|
|
if template.User != nil {
|
|
return fmt.Errorf("env_template[%s]: 'user' is not allowed", key)
|
|
}
|
|
|
|
if template.Uid != nil {
|
|
return fmt.Errorf("env_template[%s]: 'uid' is not allowed", key)
|
|
}
|
|
|
|
if template.Group != nil {
|
|
return fmt.Errorf("env_template[%s]: 'group' is not allowed", key)
|
|
}
|
|
|
|
if template.Gid != nil {
|
|
return fmt.Errorf("env_template[%s]: 'gid' is not allowed", key)
|
|
}
|
|
|
|
if template.Wait != nil {
|
|
return fmt.Errorf("env_template[%s]: 'wait' is not allowed", key)
|
|
}
|
|
|
|
if template.SandboxPath != nil {
|
|
return fmt.Errorf("env_template[%s]: 'sandbox_path' is not allowed", key)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadConfig loads the configuration at the given path, regardless if
|
|
// it's a file or directory.
|
|
func LoadConfig(path string) (*Config, error) {
|
|
cfg, _, err := LoadConfigCheckDuplicates(path)
|
|
return cfg, err
|
|
}
|
|
|
|
// LoadConfigCheckDuplicates is the same as the above but adds the ability to check if the HCL config file has
|
|
// duplicate attributes.
|
|
// TODO (HCL_DUP_KEYS_DEPRECATION): keep only LoadConfig once deprecation is complete
|
|
func LoadConfigCheckDuplicates(path string) (cfg *Config, duplicate bool, err error) {
|
|
fi, err := os.Stat(path)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
if fi.IsDir() {
|
|
return LoadConfigDirCheckDuplicates(path)
|
|
}
|
|
return LoadConfigFileCheckDuplicates(path)
|
|
}
|
|
|
|
// LoadConfigDir loads the configuration at the given path if it's a directory
|
|
func LoadConfigDir(dir string) (*Config, error) {
|
|
cfg, _, err := LoadConfigDirCheckDuplicates(dir)
|
|
return cfg, err
|
|
}
|
|
|
|
// LoadConfigDirCheckDuplicates is the same as the above but adds the ability to check if the HCL config file has
|
|
// duplicate attributes.
|
|
// TODO (HCL_DUP_KEYS_DEPRECATION): keep only LoadConfigDir once deprecation is complete
|
|
func LoadConfigDirCheckDuplicates(dir string) (cfg *Config, duplicate bool, err error) {
|
|
f, err := os.Open(dir)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
defer f.Close()
|
|
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if !fi.IsDir() {
|
|
return nil, false, fmt.Errorf("configuration path must be a directory: %q", dir)
|
|
}
|
|
|
|
var files []string
|
|
err = nil
|
|
for err != io.EOF {
|
|
var fis []os.FileInfo
|
|
fis, err = f.Readdir(128)
|
|
if err != nil && err != io.EOF {
|
|
return nil, false, err
|
|
}
|
|
|
|
for _, fi := range fis {
|
|
// Ignore directories
|
|
if fi.IsDir() {
|
|
continue
|
|
}
|
|
|
|
// Only care about files that are valid to load.
|
|
name := fi.Name()
|
|
skip := true
|
|
if strings.HasSuffix(name, ".hcl") {
|
|
skip = false
|
|
} else if strings.HasSuffix(name, ".json") {
|
|
skip = false
|
|
}
|
|
if skip || isTemporaryFile(name) {
|
|
continue
|
|
}
|
|
|
|
path := filepath.Join(dir, name)
|
|
files = append(files, path)
|
|
}
|
|
}
|
|
|
|
result := NewConfig()
|
|
for _, f := range files {
|
|
config, dup, err := LoadConfigFileCheckDuplicates(f)
|
|
if err != nil {
|
|
return nil, duplicate, fmt.Errorf("error loading %q: %w", f, err)
|
|
}
|
|
duplicate = duplicate || dup
|
|
|
|
if result == nil {
|
|
result = config
|
|
} else {
|
|
result = result.Merge(config)
|
|
}
|
|
}
|
|
|
|
return result, duplicate, nil
|
|
}
|
|
|
|
// isTemporaryFile returns true or false depending on whether the
|
|
// provided file name is a temporary file for the following editors:
|
|
// emacs or vim.
|
|
func isTemporaryFile(name string) bool {
|
|
return strings.HasSuffix(name, "~") || // vim
|
|
strings.HasPrefix(name, ".#") || // emacs
|
|
(strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) // emacs
|
|
}
|
|
|
|
// LoadConfigFile loads the configuration at the given path if it's a file
|
|
func LoadConfigFile(path string) (*Config, error) {
|
|
cfg, _, err := LoadConfigFileCheckDuplicates(path)
|
|
return cfg, err
|
|
}
|
|
|
|
// LoadConfigFileCheckDuplicates is the same as the above but adds the ability to check if the HCL config file has
|
|
// duplicate attributes.
|
|
// TODO (HCL_DUP_KEYS_DEPRECATION): keep only LoadConfigFile once deprecation is complete
|
|
func LoadConfigFileCheckDuplicates(path string) (cfg *Config, duplicate bool, err error) {
|
|
fi, err := os.Stat(path)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
if fi.IsDir() {
|
|
return nil, false, fmt.Errorf("location is a directory, not a file")
|
|
}
|
|
|
|
// Read the file
|
|
d, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
// Parse!
|
|
obj, duplicate, err := random.ParseAndCheckForDuplicateHclAttributes(string(d))
|
|
if err != nil {
|
|
return nil, duplicate, err
|
|
}
|
|
|
|
// Attribute
|
|
ast.Walk(obj, func(n ast.Node) (ast.Node, bool) {
|
|
if k, ok := n.(*ast.ObjectKey); ok {
|
|
k.Token.Pos.Filename = path
|
|
}
|
|
return n, true
|
|
})
|
|
|
|
// Start building the result
|
|
result := NewConfig()
|
|
if err := hcl.DecodeObject(result, obj); err != nil {
|
|
return nil, duplicate, err
|
|
}
|
|
|
|
sharedConfig, dup, err := configutil.ParseConfigCheckDuplicate(string(d))
|
|
if err != nil {
|
|
return nil, duplicate, err
|
|
}
|
|
duplicate = duplicate || dup
|
|
|
|
// Pruning custom headers for Agent for now
|
|
for _, ln := range sharedConfig.Listeners {
|
|
ln.CustomResponseHeaders = nil
|
|
}
|
|
|
|
result.SharedConfig = sharedConfig
|
|
|
|
list, ok := obj.Node.(*ast.ObjectList)
|
|
if !ok {
|
|
return nil, duplicate, fmt.Errorf("error parsing: file doesn't contain a root object")
|
|
}
|
|
|
|
if err := parseAutoAuth(result, list); err != nil {
|
|
return nil, duplicate, fmt.Errorf("error parsing 'auto_auth': %w", err)
|
|
}
|
|
|
|
if err := parseCache(result, list); err != nil {
|
|
return nil, duplicate, fmt.Errorf("error parsing 'cache':%w", err)
|
|
}
|
|
|
|
if err := parseAPIProxy(result, list); err != nil {
|
|
return nil, duplicate, fmt.Errorf("error parsing 'api_proxy':%w", err)
|
|
}
|
|
|
|
if err := parseTemplateConfig(result, list); err != nil {
|
|
return nil, duplicate, fmt.Errorf("error parsing 'template_config': %w", err)
|
|
}
|
|
|
|
if err := parseTemplates(result, list); err != nil {
|
|
return nil, duplicate, fmt.Errorf("error parsing 'template': %w", err)
|
|
}
|
|
|
|
if err := parseExec(result, list); err != nil {
|
|
return nil, duplicate, fmt.Errorf("error parsing 'exec': %w", err)
|
|
}
|
|
|
|
if err := parseEnvTemplates(result, list); err != nil {
|
|
return nil, duplicate, fmt.Errorf("error parsing 'env_template': %w", err)
|
|
}
|
|
|
|
if result.Cache != nil && result.APIProxy == nil && (result.Cache.UseAutoAuthToken || result.Cache.ForceAutoAuthToken) {
|
|
result.APIProxy = &APIProxy{
|
|
UseAutoAuthToken: result.Cache.UseAutoAuthToken,
|
|
ForceAutoAuthToken: result.Cache.ForceAutoAuthToken,
|
|
}
|
|
}
|
|
|
|
err = parseVault(result, list)
|
|
if err != nil {
|
|
return nil, duplicate, fmt.Errorf("error parsing 'vault':%w", err)
|
|
}
|
|
|
|
if result.Vault != nil {
|
|
// Set defaults
|
|
if result.Vault.Retry == nil {
|
|
result.Vault.Retry = &Retry{}
|
|
}
|
|
switch result.Vault.Retry.NumRetries {
|
|
case 0:
|
|
result.Vault.Retry.NumRetries = ctconfig.DefaultRetryAttempts
|
|
case -1:
|
|
result.Vault.Retry.NumRetries = 0
|
|
}
|
|
}
|
|
|
|
if disableIdleConnsEnv := os.Getenv(DisableIdleConnsEnv); disableIdleConnsEnv != "" {
|
|
result.DisableIdleConns, err = parseutil.ParseCommaStringSlice(strings.ToLower(disableIdleConnsEnv))
|
|
if err != nil {
|
|
return nil, duplicate, fmt.Errorf("error parsing environment variable %s: %v", DisableIdleConnsEnv, err)
|
|
}
|
|
}
|
|
|
|
for _, subsystem := range result.DisableIdleConns {
|
|
switch subsystem {
|
|
case "auto-auth":
|
|
result.DisableIdleConnsAutoAuth = true
|
|
case "caching", "proxying":
|
|
result.DisableIdleConnsAPIProxy = true
|
|
case "templating":
|
|
result.DisableIdleConnsTemplating = true
|
|
case "":
|
|
continue
|
|
default:
|
|
return nil, duplicate, fmt.Errorf("unknown disable_idle_connections value: %s", subsystem)
|
|
}
|
|
}
|
|
|
|
if disableKeepAlivesEnv := os.Getenv(DisableKeepAlivesEnv); disableKeepAlivesEnv != "" {
|
|
result.DisableKeepAlives, err = parseutil.ParseCommaStringSlice(strings.ToLower(disableKeepAlivesEnv))
|
|
if err != nil {
|
|
return nil, duplicate, fmt.Errorf("error parsing environment variable %s: %v", DisableKeepAlivesEnv, err)
|
|
}
|
|
}
|
|
|
|
for _, subsystem := range result.DisableKeepAlives {
|
|
switch subsystem {
|
|
case "auto-auth":
|
|
result.DisableKeepAlivesAutoAuth = true
|
|
case "caching", "proxying":
|
|
result.DisableKeepAlivesAPIProxy = true
|
|
case "templating":
|
|
result.DisableKeepAlivesTemplating = true
|
|
case "":
|
|
continue
|
|
default:
|
|
return nil, duplicate, fmt.Errorf("unknown disable_keep_alives value: %s", subsystem)
|
|
}
|
|
}
|
|
|
|
return result, duplicate, nil
|
|
}
|
|
|
|
func parseVault(result *Config, list *ast.ObjectList) error {
|
|
name := "vault"
|
|
|
|
vaultList := list.Filter(name)
|
|
if len(vaultList.Items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(vaultList.Items) > 1 {
|
|
return fmt.Errorf("one and only one %q block is required", name)
|
|
}
|
|
|
|
item := vaultList.Items[0]
|
|
|
|
var v Vault
|
|
err := hcl.DecodeObject(&v, item.Val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if v.Address != "" {
|
|
v.Address = configutil.NormalizeAddr(v.Address)
|
|
}
|
|
|
|
if v.TLSSkipVerifyRaw != nil {
|
|
v.TLSSkipVerify, err = parseutil.ParseBool(v.TLSSkipVerifyRaw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
result.Vault = &v
|
|
|
|
subs, ok := item.Val.(*ast.ObjectType)
|
|
if !ok {
|
|
return fmt.Errorf("could not parse %q as an object", name)
|
|
}
|
|
|
|
if err := parseRetry(result, subs.List); err != nil {
|
|
return fmt.Errorf("error parsing 'retry': %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseRetry(result *Config, list *ast.ObjectList) error {
|
|
name := "retry"
|
|
|
|
retryList := list.Filter(name)
|
|
if len(retryList.Items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(retryList.Items) > 1 {
|
|
return fmt.Errorf("one and only one %q block is required", name)
|
|
}
|
|
|
|
item := retryList.Items[0]
|
|
|
|
var r Retry
|
|
err := hcl.DecodeObject(&r, item.Val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
result.Vault.Retry = &r
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseAPIProxy(result *Config, list *ast.ObjectList) error {
|
|
name := "api_proxy"
|
|
|
|
apiProxyList := list.Filter(name)
|
|
if len(apiProxyList.Items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(apiProxyList.Items) > 1 {
|
|
return fmt.Errorf("one and only one %q block is required", name)
|
|
}
|
|
|
|
item := apiProxyList.Items[0]
|
|
|
|
var apiProxy APIProxy
|
|
err := hcl.DecodeObject(&apiProxy, item.Val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if apiProxy.UseAutoAuthTokenRaw != nil {
|
|
apiProxy.UseAutoAuthToken, err = parseutil.ParseBool(apiProxy.UseAutoAuthTokenRaw)
|
|
if err != nil {
|
|
// Could be a value of "force" instead of "true"/"false"
|
|
switch apiProxy.UseAutoAuthTokenRaw.(type) {
|
|
case string:
|
|
v := apiProxy.UseAutoAuthTokenRaw.(string)
|
|
|
|
if !strings.EqualFold(v, "force") {
|
|
return fmt.Errorf("value of 'use_auto_auth_token' can be either true/false/force, %q is an invalid option", apiProxy.UseAutoAuthTokenRaw)
|
|
}
|
|
apiProxy.UseAutoAuthToken = true
|
|
apiProxy.ForceAutoAuthToken = true
|
|
|
|
default:
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
result.APIProxy = &apiProxy
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseCache(result *Config, list *ast.ObjectList) error {
|
|
name := "cache"
|
|
|
|
cacheList := list.Filter(name)
|
|
if len(cacheList.Items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(cacheList.Items) > 1 {
|
|
return fmt.Errorf("one and only one %q block is required", name)
|
|
}
|
|
|
|
item := cacheList.Items[0]
|
|
|
|
var c Cache
|
|
err := hcl.DecodeObject(&c, item.Val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if c.UseAutoAuthTokenRaw != nil {
|
|
c.UseAutoAuthToken, err = parseutil.ParseBool(c.UseAutoAuthTokenRaw)
|
|
if err != nil {
|
|
// Could be a value of "force" instead of "true"/"false"
|
|
switch c.UseAutoAuthTokenRaw.(type) {
|
|
case string:
|
|
v := c.UseAutoAuthTokenRaw.(string)
|
|
|
|
if !strings.EqualFold(v, "force") {
|
|
return fmt.Errorf("value of 'use_auto_auth_token' can be either true/false/force, %q is an invalid option", c.UseAutoAuthTokenRaw)
|
|
}
|
|
c.UseAutoAuthToken = true
|
|
c.ForceAutoAuthToken = true
|
|
|
|
default:
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
result.Cache = &c
|
|
|
|
subs, ok := item.Val.(*ast.ObjectType)
|
|
if !ok {
|
|
return fmt.Errorf("could not parse %q as an object", name)
|
|
}
|
|
subList := subs.List
|
|
if err := parsePersist(result, subList); err != nil {
|
|
return fmt.Errorf("error parsing persist: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parsePersist(result *Config, list *ast.ObjectList) error {
|
|
name := "persist"
|
|
|
|
persistList := list.Filter(name)
|
|
if len(persistList.Items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(persistList.Items) > 1 {
|
|
return fmt.Errorf("only one %q block is required", name)
|
|
}
|
|
|
|
item := persistList.Items[0]
|
|
|
|
var p agentproxyshared.PersistConfig
|
|
err := hcl.DecodeObject(&p, item.Val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if p.Type == "" {
|
|
if len(item.Keys) == 1 {
|
|
p.Type = strings.ToLower(item.Keys[0].Token.Value().(string))
|
|
}
|
|
if p.Type == "" {
|
|
return errors.New("persist type must be specified")
|
|
}
|
|
}
|
|
|
|
result.Cache.Persist = &p
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseAutoAuth(result *Config, list *ast.ObjectList) error {
|
|
name := "auto_auth"
|
|
|
|
autoAuthList := list.Filter(name)
|
|
if len(autoAuthList.Items) == 0 {
|
|
return nil
|
|
}
|
|
if len(autoAuthList.Items) > 1 {
|
|
return fmt.Errorf("at most one %q block is allowed", name)
|
|
}
|
|
|
|
// Get our item
|
|
item := autoAuthList.Items[0]
|
|
|
|
var a AutoAuth
|
|
if err := hcl.DecodeObject(&a, item.Val); err != nil {
|
|
return err
|
|
}
|
|
|
|
result.AutoAuth = &a
|
|
|
|
subs, ok := item.Val.(*ast.ObjectType)
|
|
if !ok {
|
|
return fmt.Errorf("could not parse %q as an object", name)
|
|
}
|
|
subList := subs.List
|
|
|
|
if err := parseMethod(result, subList); err != nil {
|
|
return fmt.Errorf("error parsing 'method': %w", err)
|
|
}
|
|
if a.Method == nil {
|
|
return fmt.Errorf("no 'method' block found")
|
|
}
|
|
|
|
if err := parseSinks(result, subList); err != nil {
|
|
return fmt.Errorf("error parsing 'sink' stanzas: %w", err)
|
|
}
|
|
|
|
if result.AutoAuth.Method.WrapTTL > 0 {
|
|
if len(result.AutoAuth.Sinks) != 1 {
|
|
return fmt.Errorf("error parsing auto_auth: wrapping enabled on auth method and 0 or many sinks defined")
|
|
}
|
|
|
|
if result.AutoAuth.Sinks[0].WrapTTL > 0 {
|
|
return fmt.Errorf("error parsing auto_auth: wrapping enabled both on auth method and sink")
|
|
}
|
|
}
|
|
|
|
if result.AutoAuth.Method.MaxBackoffRaw != nil {
|
|
var err error
|
|
if result.AutoAuth.Method.MaxBackoff, err = parseutil.ParseDurationSecond(result.AutoAuth.Method.MaxBackoffRaw); err != nil {
|
|
return err
|
|
}
|
|
result.AutoAuth.Method.MaxBackoffRaw = nil
|
|
}
|
|
|
|
if result.AutoAuth.Method.MinBackoffRaw != nil {
|
|
var err error
|
|
if result.AutoAuth.Method.MinBackoff, err = parseutil.ParseDurationSecond(result.AutoAuth.Method.MinBackoffRaw); err != nil {
|
|
return err
|
|
}
|
|
result.AutoAuth.Method.MinBackoffRaw = nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseMethod(result *Config, list *ast.ObjectList) error {
|
|
name := "method"
|
|
|
|
methodList := list.Filter(name)
|
|
if len(methodList.Items) != 1 {
|
|
return fmt.Errorf("one and only one %q block is required", name)
|
|
}
|
|
|
|
// Get our item
|
|
item := methodList.Items[0]
|
|
|
|
var m Method
|
|
if err := hcl.DecodeObject(&m, item.Val); err != nil {
|
|
return err
|
|
}
|
|
|
|
if m.Type == "" {
|
|
if len(item.Keys) == 1 {
|
|
m.Type = strings.ToLower(item.Keys[0].Token.Value().(string))
|
|
}
|
|
if m.Type == "" {
|
|
return errors.New("method type must be specified")
|
|
}
|
|
}
|
|
|
|
// Default to Vault's default
|
|
if m.MountPath == "" {
|
|
m.MountPath = fmt.Sprintf("auth/%s", m.Type)
|
|
}
|
|
// Standardize on no trailing slash
|
|
m.MountPath = strings.TrimSuffix(m.MountPath, "/")
|
|
|
|
if m.WrapTTLRaw != nil {
|
|
var err error
|
|
if m.WrapTTL, err = parseutil.ParseDurationSecond(m.WrapTTLRaw); err != nil {
|
|
return err
|
|
}
|
|
m.WrapTTLRaw = nil
|
|
}
|
|
|
|
// Canonicalize namespace path if provided
|
|
m.Namespace = namespace.Canonicalize(m.Namespace)
|
|
|
|
// Normalize any configuration addresses
|
|
if len(m.Config) > 0 {
|
|
var err error
|
|
for k, v := range m.Config {
|
|
vStr, ok := v.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
m.Config[k], err = normalizeAutoAuthMethod(m.Type, k, vStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
result.AutoAuth.Method = &m
|
|
return nil
|
|
}
|
|
|
|
// autoAuthMethodKeys maps an auto-auth method type to its associated
|
|
// configuration whose values are URLs, IP addresses, or host:port style
|
|
// addresses. All auto-auth types must have an entry in this map, otherwise our
|
|
// normalization check will fail when parsing the storage entry config.
|
|
// Auto-auth method types which don't contain such keys should include an empty
|
|
// array.
|
|
var autoAuthMethodKeys = map[string][]string{
|
|
"alicloud": {""},
|
|
"approle": {""},
|
|
"aws": {""},
|
|
"azure": {"resource"},
|
|
"cert": {""},
|
|
"cf": {""},
|
|
"gcp": {"service_account"},
|
|
"jwt": {""},
|
|
"ldap": {""},
|
|
"kerberos": {""},
|
|
"kubernetes": {""},
|
|
"oci": {""},
|
|
"token_file": {""},
|
|
}
|
|
|
|
// normalizeAutoAuthMethod takes a storage name, a configuration key
|
|
// and its associated value and will normalize any URLs, IP addresses, or
|
|
// host:port style addresses.
|
|
func normalizeAutoAuthMethod(method string, key string, value string) (string, error) {
|
|
keys, ok := autoAuthMethodKeys[method]
|
|
if !ok {
|
|
return "", fmt.Errorf("unknown auto-auth method type %s", method)
|
|
}
|
|
|
|
if slices.Contains(keys, key) {
|
|
return configutil.NormalizeAddr(value), nil
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
func parseSinks(result *Config, list *ast.ObjectList) error {
|
|
name := "sink"
|
|
|
|
sinkList := list.Filter(name)
|
|
if len(sinkList.Items) < 1 {
|
|
return nil
|
|
}
|
|
|
|
var ts []*Sink
|
|
|
|
for _, item := range sinkList.Items {
|
|
var s Sink
|
|
if err := hcl.DecodeObject(&s, item.Val); err != nil {
|
|
return err
|
|
}
|
|
|
|
if s.Type == "" {
|
|
if len(item.Keys) == 1 {
|
|
s.Type = strings.ToLower(item.Keys[0].Token.Value().(string))
|
|
}
|
|
if s.Type == "" {
|
|
return errors.New("sink type must be specified")
|
|
}
|
|
}
|
|
|
|
if s.WrapTTLRaw != nil {
|
|
var err error
|
|
if s.WrapTTL, err = parseutil.ParseDurationSecond(s.WrapTTLRaw); err != nil {
|
|
return multierror.Prefix(err, fmt.Sprintf("sink.%s", s.Type))
|
|
}
|
|
s.WrapTTLRaw = nil
|
|
}
|
|
|
|
switch s.DHType {
|
|
case "":
|
|
case "curve25519":
|
|
default:
|
|
return multierror.Prefix(errors.New("invalid value for 'dh_type'"), fmt.Sprintf("sink.%s", s.Type))
|
|
}
|
|
|
|
if s.AADEnvVar != "" {
|
|
s.AAD = os.Getenv(s.AADEnvVar)
|
|
s.AADEnvVar = ""
|
|
}
|
|
|
|
switch {
|
|
case s.DHPath == "" && s.DHType == "":
|
|
if s.AAD != "" {
|
|
return multierror.Prefix(errors.New("specifying AAD data without 'dh_type' does not make sense"), fmt.Sprintf("sink.%s", s.Type))
|
|
}
|
|
if s.DeriveKey {
|
|
return multierror.Prefix(errors.New("specifying 'derive_key' data without 'dh_type' does not make sense"), fmt.Sprintf("sink.%s", s.Type))
|
|
}
|
|
case s.DHPath != "" && s.DHType != "":
|
|
default:
|
|
return multierror.Prefix(errors.New("'dh_type' and 'dh_path' must be specified together"), fmt.Sprintf("sink.%s", s.Type))
|
|
}
|
|
|
|
ts = append(ts, &s)
|
|
}
|
|
|
|
result.AutoAuth.Sinks = ts
|
|
return nil
|
|
}
|
|
|
|
func parseTemplateConfig(result *Config, list *ast.ObjectList) error {
|
|
name := "template_config"
|
|
|
|
templateConfigList := list.Filter(name)
|
|
if len(templateConfigList.Items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(templateConfigList.Items) > 1 {
|
|
return fmt.Errorf("at most one %q block is allowed", name)
|
|
}
|
|
|
|
// Get our item
|
|
item := templateConfigList.Items[0]
|
|
|
|
var cfg TemplateConfig
|
|
if err := hcl.DecodeObject(&cfg, item.Val); err != nil {
|
|
return err
|
|
}
|
|
|
|
result.TemplateConfig = &cfg
|
|
|
|
if result.TemplateConfig.StaticSecretRenderIntRaw != nil {
|
|
var err error
|
|
if result.TemplateConfig.StaticSecretRenderInt, err = parseutil.ParseDurationSecond(result.TemplateConfig.StaticSecretRenderIntRaw); err != nil {
|
|
return err
|
|
}
|
|
result.TemplateConfig.StaticSecretRenderIntRaw = nil
|
|
}
|
|
|
|
if result.TemplateConfig.MaxConnectionsPerHostRaw != nil {
|
|
var err error
|
|
if result.TemplateConfig.MaxConnectionsPerHost, err = parseutil.SafeParseInt(result.TemplateConfig.MaxConnectionsPerHostRaw); err != nil {
|
|
return err
|
|
}
|
|
|
|
result.TemplateConfig.MaxConnectionsPerHostRaw = nil
|
|
} else {
|
|
result.TemplateConfig.MaxConnectionsPerHost = DefaultTemplateConfigMaxConnsPerHost
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseTemplates(result *Config, list *ast.ObjectList) error {
|
|
name := "template"
|
|
|
|
templateList := list.Filter(name)
|
|
if len(templateList.Items) < 1 {
|
|
return nil
|
|
}
|
|
|
|
var tcs []*ctconfig.TemplateConfig
|
|
|
|
for _, item := range templateList.Items {
|
|
var shadow interface{}
|
|
if err := hcl.DecodeObject(&shadow, item.Val); err != nil {
|
|
return fmt.Errorf("error decoding config: %s", err)
|
|
}
|
|
|
|
// Convert to a map and flatten the keys we want to flatten
|
|
parsed, ok := shadow.(map[string]interface{})
|
|
if !ok {
|
|
return errors.New("error converting config")
|
|
}
|
|
|
|
// flatten the wait or exec fields. The initial "wait" or "exec" value, if given, is a
|
|
// []map[string]interface{}, but we need it to be map[string]interface{}.
|
|
// Consul Template has a method flattenKeys that walks all of parsed and
|
|
// flattens every key. For Vault Agent, we only care about the wait input.
|
|
// Only one wait/exec stanza is supported, however Consul Template does not error
|
|
// with multiple instead it flattens them down, with last value winning.
|
|
// Here we take the last element of the parsed["wait"] or parsed["exec"] slice to keep
|
|
// consistency with Consul Template behavior.
|
|
wait, ok := parsed["wait"].([]map[string]interface{})
|
|
if ok {
|
|
parsed["wait"] = wait[len(wait)-1]
|
|
}
|
|
|
|
exec, ok := parsed["exec"].([]map[string]interface{})
|
|
if ok {
|
|
parsed["exec"] = exec[len(exec)-1]
|
|
}
|
|
|
|
var tc ctconfig.TemplateConfig
|
|
|
|
// Use mapstructure to populate the basic config fields
|
|
var md mapstructure.Metadata
|
|
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
|
ctconfig.StringToFileModeFunc(),
|
|
ctconfig.StringToWaitDurationHookFunc(),
|
|
mapstructure.StringToSliceHookFunc(","),
|
|
mapstructure.StringToTimeDurationHookFunc(),
|
|
),
|
|
ErrorUnused: true,
|
|
Metadata: &md,
|
|
Result: &tc,
|
|
})
|
|
if err != nil {
|
|
return errors.New("mapstructure decoder creation failed")
|
|
}
|
|
if err := decoder.Decode(parsed); err != nil {
|
|
return err
|
|
}
|
|
tcs = append(tcs, &tc)
|
|
}
|
|
result.Templates = tcs
|
|
return nil
|
|
}
|
|
|
|
func parseExec(result *Config, list *ast.ObjectList) error {
|
|
name := "exec"
|
|
|
|
execList := list.Filter(name)
|
|
if len(execList.Items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(execList.Items) > 1 {
|
|
return fmt.Errorf("at most one %q block is allowed", name)
|
|
}
|
|
|
|
item := execList.Items[0]
|
|
var shadow interface{}
|
|
if err := hcl.DecodeObject(&shadow, item.Val); err != nil {
|
|
return fmt.Errorf("error decoding config: %s", err)
|
|
}
|
|
|
|
parsed, ok := shadow.(map[string]interface{})
|
|
if !ok {
|
|
return errors.New("error converting config")
|
|
}
|
|
|
|
var execConfig ExecConfig
|
|
var md mapstructure.Metadata
|
|
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
|
ctconfig.StringToFileModeFunc(),
|
|
ctconfig.StringToWaitDurationHookFunc(),
|
|
mapstructure.StringToSliceHookFunc(","),
|
|
mapstructure.StringToTimeDurationHookFunc(),
|
|
ctsignals.StringToSignalFunc(),
|
|
),
|
|
ErrorUnused: true,
|
|
Metadata: &md,
|
|
Result: &execConfig,
|
|
})
|
|
if err != nil {
|
|
return errors.New("mapstructure decoder creation failed")
|
|
}
|
|
if err := decoder.Decode(parsed); err != nil {
|
|
return err
|
|
}
|
|
|
|
// if the user does not specify a restart signal, default to SIGTERM
|
|
if execConfig.RestartStopSignal == nil {
|
|
execConfig.RestartStopSignal = syscall.SIGTERM
|
|
}
|
|
|
|
if execConfig.RestartOnSecretChanges == "" {
|
|
execConfig.RestartOnSecretChanges = "always"
|
|
}
|
|
|
|
result.Exec = &execConfig
|
|
return nil
|
|
}
|
|
|
|
func parseEnvTemplates(result *Config, list *ast.ObjectList) error {
|
|
name := "env_template"
|
|
|
|
envTemplateList := list.Filter(name)
|
|
|
|
if len(envTemplateList.Items) < 1 {
|
|
return nil
|
|
}
|
|
|
|
envTemplates := make([]*ctconfig.TemplateConfig, 0, len(envTemplateList.Items))
|
|
|
|
for _, item := range envTemplateList.Items {
|
|
var shadow interface{}
|
|
if err := hcl.DecodeObject(&shadow, item.Val); err != nil {
|
|
return fmt.Errorf("error decoding config: %s", err)
|
|
}
|
|
|
|
// Convert to a map and flatten the keys we want to flatten
|
|
parsed, ok := shadow.(map[string]any)
|
|
if !ok {
|
|
return errors.New("error converting config")
|
|
}
|
|
|
|
var templateConfig ctconfig.TemplateConfig
|
|
var md mapstructure.Metadata
|
|
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
|
ctconfig.StringToFileModeFunc(),
|
|
ctconfig.StringToWaitDurationHookFunc(),
|
|
mapstructure.StringToSliceHookFunc(","),
|
|
mapstructure.StringToTimeDurationHookFunc(),
|
|
ctsignals.StringToSignalFunc(),
|
|
),
|
|
ErrorUnused: true,
|
|
Metadata: &md,
|
|
Result: &templateConfig,
|
|
})
|
|
if err != nil {
|
|
return errors.New("mapstructure decoder creation failed")
|
|
}
|
|
if err := decoder.Decode(parsed); err != nil {
|
|
return err
|
|
}
|
|
|
|
// parse the keys in the item for the environment variable name
|
|
if numberOfKeys := len(item.Keys); numberOfKeys != 1 {
|
|
return fmt.Errorf("expected one and only one environment variable name, got %d", numberOfKeys)
|
|
}
|
|
|
|
// hcl parses this with extra quotes if quoted in config file
|
|
environmentVariableName := strings.Trim(item.Keys[0].Token.Text, `"`)
|
|
|
|
templateConfig.MapToEnvironmentVariable = pointerutil.StringPtr(environmentVariableName)
|
|
|
|
envTemplates = append(envTemplates, &templateConfig)
|
|
}
|
|
|
|
result.EnvTemplates = envTemplates
|
|
return nil
|
|
}
|