d7b311ce55
The client ACL cache was not accounting for tokens which included ACL role links. This change modifies the behaviour to resolve role links to policies. It will also now store ACL roles within the cache for quick lookup. The cache TTL is configurable in the same manner as policies or tokens. Another small fix is included that takes into account the ACL token expiry time. This was not included, which meant tokens with expiry could be used past the expiry time, until they were GC'd.
289 lines
9.6 KiB
Go
289 lines
9.6 KiB
Go
package agent
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/hashicorp/hcl"
|
|
client "github.com/hashicorp/nomad/client/config"
|
|
"github.com/hashicorp/nomad/helper"
|
|
"github.com/hashicorp/nomad/nomad/structs/config"
|
|
)
|
|
|
|
// ParseConfigFile returns an agent.Config from parsed from a file.
|
|
func ParseConfigFile(path string) (*Config, error) {
|
|
// slurp
|
|
var buf bytes.Buffer
|
|
path, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
if _, err := io.Copy(&buf, f); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// parse
|
|
c := &Config{
|
|
Client: &ClientConfig{
|
|
ServerJoin: &ServerJoin{},
|
|
TemplateConfig: &client.ClientTemplateConfig{
|
|
Wait: &client.WaitConfig{},
|
|
WaitBounds: &client.WaitConfig{},
|
|
ConsulRetry: &client.RetryConfig{},
|
|
VaultRetry: &client.RetryConfig{},
|
|
NomadRetry: &client.RetryConfig{},
|
|
},
|
|
},
|
|
Server: &ServerConfig{
|
|
PlanRejectionTracker: &PlanRejectionTracker{},
|
|
ServerJoin: &ServerJoin{},
|
|
},
|
|
ACL: &ACLConfig{},
|
|
Audit: &config.AuditConfig{},
|
|
Consul: &config.ConsulConfig{},
|
|
Autopilot: &config.AutopilotConfig{},
|
|
Telemetry: &Telemetry{},
|
|
Vault: &config.VaultConfig{},
|
|
}
|
|
|
|
err = hcl.Decode(c, buf.String())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode HCL file %s: %w", path, err)
|
|
}
|
|
|
|
// convert strings to time.Durations
|
|
tds := []durationConversionMap{
|
|
{"gc_interval", &c.Client.GCInterval, &c.Client.GCIntervalHCL, nil},
|
|
{"acl.token_ttl", &c.ACL.TokenTTL, &c.ACL.TokenTTLHCL, nil},
|
|
{"acl.policy_ttl", &c.ACL.PolicyTTL, &c.ACL.PolicyTTLHCL, nil},
|
|
{"acl.policy_ttl", &c.ACL.RoleTTL, &c.ACL.RoleTTLHCL, nil},
|
|
{"acl.token_min_expiration_ttl", &c.ACL.TokenMinExpirationTTL, &c.ACL.TokenMinExpirationTTLHCL, nil},
|
|
{"acl.token_max_expiration_ttl", &c.ACL.TokenMaxExpirationTTL, &c.ACL.TokenMaxExpirationTTLHCL, nil},
|
|
{"client.server_join.retry_interval", &c.Client.ServerJoin.RetryInterval, &c.Client.ServerJoin.RetryIntervalHCL, nil},
|
|
{"server.heartbeat_grace", &c.Server.HeartbeatGrace, &c.Server.HeartbeatGraceHCL, nil},
|
|
{"server.min_heartbeat_ttl", &c.Server.MinHeartbeatTTL, &c.Server.MinHeartbeatTTLHCL, nil},
|
|
{"server.failover_heartbeat_ttl", &c.Server.FailoverHeartbeatTTL, &c.Server.FailoverHeartbeatTTLHCL, nil},
|
|
{"server.plan_rejection_tracker.node_window", &c.Server.PlanRejectionTracker.NodeWindow, &c.Server.PlanRejectionTracker.NodeWindowHCL, nil},
|
|
{"server.retry_interval", &c.Server.RetryInterval, &c.Server.RetryIntervalHCL, nil},
|
|
{"server.server_join.retry_interval", &c.Server.ServerJoin.RetryInterval, &c.Server.ServerJoin.RetryIntervalHCL, nil},
|
|
{"consul.timeout", &c.Consul.Timeout, &c.Consul.TimeoutHCL, nil},
|
|
{"autopilot.server_stabilization_time", &c.Autopilot.ServerStabilizationTime, &c.Autopilot.ServerStabilizationTimeHCL, nil},
|
|
{"autopilot.last_contact_threshold", &c.Autopilot.LastContactThreshold, &c.Autopilot.LastContactThresholdHCL, nil},
|
|
{"telemetry.collection_interval", &c.Telemetry.collectionInterval, &c.Telemetry.CollectionInterval, nil},
|
|
{"client.template.block_query_wait", nil, &c.Client.TemplateConfig.BlockQueryWaitTimeHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.BlockQueryWaitTime = d
|
|
},
|
|
},
|
|
{"client.template.max_stale", nil, &c.Client.TemplateConfig.MaxStaleHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.MaxStale = d
|
|
}},
|
|
{"client.template.wait.min", nil, &c.Client.TemplateConfig.Wait.MinHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.Wait.Min = d
|
|
},
|
|
},
|
|
{"client.template.wait.max", nil, &c.Client.TemplateConfig.Wait.MaxHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.Wait.Max = d
|
|
},
|
|
},
|
|
{"client.template.wait_bounds.min", nil, &c.Client.TemplateConfig.WaitBounds.MinHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.WaitBounds.Min = d
|
|
},
|
|
},
|
|
{"client.template.wait_bounds.max", nil, &c.Client.TemplateConfig.WaitBounds.MaxHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.WaitBounds.Max = d
|
|
},
|
|
},
|
|
{"client.template.consul_retry.backoff", nil, &c.Client.TemplateConfig.ConsulRetry.BackoffHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.ConsulRetry.Backoff = d
|
|
},
|
|
},
|
|
{"client.template.consul_retry.max_backoff", nil, &c.Client.TemplateConfig.ConsulRetry.MaxBackoffHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.ConsulRetry.MaxBackoff = d
|
|
},
|
|
},
|
|
{"client.template.vault_retry.backoff", nil, &c.Client.TemplateConfig.VaultRetry.BackoffHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.VaultRetry.Backoff = d
|
|
},
|
|
},
|
|
{"client.template.vault_retry.max_backoff", nil, &c.Client.TemplateConfig.VaultRetry.MaxBackoffHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.VaultRetry.MaxBackoff = d
|
|
},
|
|
},
|
|
{"client.template.nomad_retry.backoff", nil, &c.Client.TemplateConfig.NomadRetry.BackoffHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.NomadRetry.Backoff = d
|
|
},
|
|
},
|
|
{"client.template.nomad_retry.max_backoff", nil, &c.Client.TemplateConfig.NomadRetry.MaxBackoffHCL,
|
|
func(d *time.Duration) {
|
|
c.Client.TemplateConfig.NomadRetry.MaxBackoff = d
|
|
},
|
|
},
|
|
}
|
|
|
|
// Add enterprise audit sinks for time.Duration parsing
|
|
for i, sink := range c.Audit.Sinks {
|
|
tds = append(tds, durationConversionMap{
|
|
fmt.Sprintf("audit.sink.%d", i), &sink.RotateDuration, &sink.RotateDurationHCL, nil})
|
|
}
|
|
|
|
// convert strings to time.Durations
|
|
err = convertDurations(tds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// report unexpected keys
|
|
err = extraKeys(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Set client template config or its members to nil if not set.
|
|
finalizeClientTemplateConfig(c)
|
|
|
|
return c, nil
|
|
}
|
|
|
|
// durationConversionMap holds args for one duration conversion
|
|
type durationConversionMap struct {
|
|
targetFieldPath string
|
|
targetField *time.Duration
|
|
sourceField *string
|
|
setFunc func(*time.Duration)
|
|
}
|
|
|
|
// convertDurations parses the duration strings specified in the config files
|
|
// into time.Durations
|
|
func convertDurations(xs []durationConversionMap) error {
|
|
for _, x := range xs {
|
|
// if targetField is not a pointer itself, use the field map.
|
|
if x.targetField != nil && x.sourceField != nil && "" != *x.sourceField {
|
|
d, err := time.ParseDuration(*x.sourceField)
|
|
if err != nil {
|
|
return fmt.Errorf("%s can't parse time duration %s", x.targetFieldPath, *x.sourceField)
|
|
}
|
|
|
|
*x.targetField = d
|
|
} else if x.setFunc != nil && x.sourceField != nil && "" != *x.sourceField {
|
|
// if targetField is a pointer itself, use the setFunc closure.
|
|
d, err := time.ParseDuration(*x.sourceField)
|
|
if err != nil {
|
|
return fmt.Errorf("%s can't parse time duration %s", x.targetFieldPath, *x.sourceField)
|
|
}
|
|
x.setFunc(&d)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func extraKeys(c *Config) error {
|
|
// hcl leaves behind extra keys when parsing JSON. These keys
|
|
// are kept on the top level, taken from slices or the keys of
|
|
// structs contained in slices. Clean up before looking for
|
|
// extra keys.
|
|
for range c.HTTPAPIResponseHeaders {
|
|
helper.RemoveEqualFold(&c.ExtraKeysHCL, "http_api_response_headers")
|
|
}
|
|
|
|
for _, p := range c.Plugins {
|
|
helper.RemoveEqualFold(&c.ExtraKeysHCL, p.Name)
|
|
helper.RemoveEqualFold(&c.ExtraKeysHCL, "config")
|
|
helper.RemoveEqualFold(&c.ExtraKeysHCL, "plugin")
|
|
}
|
|
|
|
for _, k := range []string{"options", "meta", "chroot_env", "servers", "server_join"} {
|
|
helper.RemoveEqualFold(&c.ExtraKeysHCL, k)
|
|
helper.RemoveEqualFold(&c.ExtraKeysHCL, "client")
|
|
}
|
|
|
|
// stats is an unused key, continue to silently ignore it
|
|
helper.RemoveEqualFold(&c.Client.ExtraKeysHCL, "stats")
|
|
|
|
// Remove HostVolume extra keys
|
|
for _, hv := range c.Client.HostVolumes {
|
|
helper.RemoveEqualFold(&c.Client.ExtraKeysHCL, hv.Name)
|
|
helper.RemoveEqualFold(&c.Client.ExtraKeysHCL, "host_volume")
|
|
}
|
|
|
|
// Remove HostNetwork extra keys
|
|
for _, hn := range c.Client.HostNetworks {
|
|
helper.RemoveEqualFold(&c.Client.ExtraKeysHCL, hn.Name)
|
|
helper.RemoveEqualFold(&c.Client.ExtraKeysHCL, "host_network")
|
|
}
|
|
|
|
// Remove AuditConfig extra keys
|
|
for _, f := range c.Audit.Filters {
|
|
helper.RemoveEqualFold(&c.Audit.ExtraKeysHCL, f.Name)
|
|
helper.RemoveEqualFold(&c.Audit.ExtraKeysHCL, "filter")
|
|
}
|
|
|
|
for _, s := range c.Audit.Sinks {
|
|
helper.RemoveEqualFold(&c.Audit.ExtraKeysHCL, s.Name)
|
|
helper.RemoveEqualFold(&c.Audit.ExtraKeysHCL, "sink")
|
|
}
|
|
|
|
for _, k := range []string{"enabled_schedulers", "start_join", "retry_join", "server_join"} {
|
|
helper.RemoveEqualFold(&c.ExtraKeysHCL, k)
|
|
helper.RemoveEqualFold(&c.ExtraKeysHCL, "server")
|
|
}
|
|
|
|
for _, k := range []string{"datadog_tags"} {
|
|
helper.RemoveEqualFold(&c.ExtraKeysHCL, k)
|
|
helper.RemoveEqualFold(&c.ExtraKeysHCL, "telemetry")
|
|
}
|
|
|
|
return helper.UnusedKeys(c)
|
|
}
|
|
|
|
// hcl.Decode will error if the ClientTemplateConfig isn't initialized with empty
|
|
// structs, however downstream code expect nils if the struct only contains fields
|
|
// with the zero value for its type. This function nils out type members that are
|
|
// structs where all the member fields are just the zero value for its type.
|
|
func finalizeClientTemplateConfig(config *Config) {
|
|
if config.Client.TemplateConfig.Wait.IsEmpty() {
|
|
config.Client.TemplateConfig.Wait = nil
|
|
}
|
|
|
|
if config.Client.TemplateConfig.WaitBounds.IsEmpty() {
|
|
config.Client.TemplateConfig.WaitBounds = nil
|
|
}
|
|
|
|
if config.Client.TemplateConfig.ConsulRetry.IsEmpty() {
|
|
config.Client.TemplateConfig.ConsulRetry = nil
|
|
}
|
|
|
|
if config.Client.TemplateConfig.VaultRetry.IsEmpty() {
|
|
config.Client.TemplateConfig.VaultRetry = nil
|
|
}
|
|
|
|
if config.Client.TemplateConfig.NomadRetry.IsEmpty() {
|
|
config.Client.TemplateConfig.NomadRetry = nil
|
|
}
|
|
|
|
if config.Client.TemplateConfig.IsEmpty() {
|
|
config.Client.TemplateConfig = nil
|
|
}
|
|
}
|