From 9fdea05804b93b4996237f33b470a01461b2cf86 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Sun, 13 Aug 2017 13:46:47 -0700 Subject: [PATCH] agent: Adding ACL block configuration --- command/agent/config-test-fixtures/basic.hcl | 6 ++ command/agent/config.go | 59 ++++++++++++++++++++ command/agent/config_parse.go | 59 ++++++++++++++++++++ command/agent/config_parse_test.go | 6 ++ command/agent/config_test.go | 13 +++++ 5 files changed, 143 insertions(+) diff --git a/command/agent/config-test-fixtures/basic.hcl b/command/agent/config-test-fixtures/basic.hcl index 00ffdb10e..9f0b16d7e 100644 --- a/command/agent/config-test-fixtures/basic.hcl +++ b/command/agent/config-test-fixtures/basic.hcl @@ -63,6 +63,7 @@ client { } server { enabled = true + authoritative_region = "foobar" bootstrap_expect = 5 data_dir = "/tmp/data" protocol_version = 3 @@ -82,6 +83,11 @@ server { rejoin_after_leave = true encrypt = "abc" } +acl { + enabled = true + token_ttl = "30s" + policy_ttl = "30s" +} telemetry { statsite_address = "127.0.0.1:1234" statsd_address = "127.0.0.1:2345" diff --git a/command/agent/config.go b/command/agent/config.go index 024c0b74b..f4aa50aa3 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -67,6 +67,9 @@ type Config struct { // Server has our server related settings Server *ServerConfig `mapstructure:"server"` + // ACL has our acl related settings + ACL *ACLConfig `mapstructure:"acl"` + // Telemetry is used to configure sending telemetry Telemetry *Telemetry `mapstructure:"telemetry"` @@ -228,11 +231,35 @@ type ClientConfig struct { NoHostUUID *bool `mapstructure:"no_host_uuid"` } +// ACLConfig is configuration specific to the ACL system +type ACLConfig struct { + // Enabled controls if we are enforce and manage ACLs + Enabled bool `mapstructure:"enabled"` + + // TokenTTL controls how long we cache ACL tokens. This controls + // how stale they can be when we are enforcing policies. Defaults + // to "30s". Reducing this impacts performance by forcing more + // frequent resolution. + TokenTTL string `mapstructure:"token_ttl"` + tokenTTL time.Duration `mapstructure:"-"` + + // PolicyTTL controls how long we cache ACL policies. This controls + // how stale they can be when we are enforcing policies. Defaults + // to "30s". Reducing this impacts performance by forcing more + // frequent resolution. + PolicyTTL string `mapstructure:"policy_ttl"` + policyTTL time.Duration `mapstructure:"-"` +} + // ServerConfig is configuration specific to the server mode type ServerConfig struct { // Enabled controls if we are a server Enabled bool `mapstructure:"enabled"` + // AuthoritativeRegion is used to control which region is treated as + // the source of truth for global tokens and ACL policies. + AuthoritativeRegion string `mapstructure:"authoritative_region"` + // BootstrapExpect tries to automatically bootstrap the Consul cluster, // by withholding peers until enough servers join. BootstrapExpect int `mapstructure:"bootstrap_expect"` @@ -565,6 +592,11 @@ func DefaultConfig() *Config { RetryInterval: "30s", RetryMaxAttempts: 0, }, + ACL: &ACLConfig{ + Enabled: false, + TokenTTL: "30s", + PolicyTTL: "30s", + }, SyslogFacility: "LOCAL0", Telemetry: &Telemetry{ CollectionInterval: "1s", @@ -676,6 +708,14 @@ func (c *Config) Merge(b *Config) *Config { result.Server = result.Server.Merge(b.Server) } + // Apply the acl config + if result.ACL == nil && b.ACL != nil { + server := *b.ACL + result.ACL = &server + } else if b.ACL != nil { + result.ACL = result.ACL.Merge(b.ACL) + } + // Apply the ports config if result.Ports == nil && b.Ports != nil { ports := *b.Ports @@ -902,6 +942,22 @@ func isTooManyColons(err error) bool { return err != nil && strings.Contains(err.Error(), tooManyColons) } +// Merge is used to merge two ACL configs together +func (a *ACLConfig) Merge(b *ACLConfig) *ACLConfig { + result := *a + + if b.Enabled { + result.Enabled = true + } + if b.TokenTTL != "" { + result.TokenTTL = b.TokenTTL + } + if b.PolicyTTL != "" { + result.PolicyTTL = b.PolicyTTL + } + return &result +} + // Merge is used to merge two server configs together func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig { result := *a @@ -909,6 +965,9 @@ func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig { if b.Enabled { result.Enabled = true } + if b.AuthoritativeRegion != "" { + result.AuthoritativeRegion = b.AuthoritativeRegion + } if b.BootstrapExpect > 0 { result.BootstrapExpect = b.BootstrapExpect } diff --git a/command/agent/config_parse.go b/command/agent/config_parse.go index de1401cb5..b0dfefb89 100644 --- a/command/agent/config_parse.go +++ b/command/agent/config_parse.go @@ -96,6 +96,7 @@ func parseConfig(result *Config, list *ast.ObjectList) error { "vault", "tls", "http_api_response_headers", + "acl", } if err := checkHCLKeys(list, valid); err != nil { return multierror.Prefix(err, "config:") @@ -118,6 +119,7 @@ func parseConfig(result *Config, list *ast.ObjectList) error { delete(m, "vault") delete(m, "tls") delete(m, "http_api_response_headers") + delete(m, "acl") // Decode the rest if err := mapstructure.WeakDecode(m, result); err != nil { @@ -159,6 +161,13 @@ func parseConfig(result *Config, list *ast.ObjectList) error { } } + // Parse ACL config + if o := list.Filter("acl"); len(o.Items) > 0 { + if err := parseACL(&result.ACL, o); err != nil { + return multierror.Prefix(err, "acl ->") + } + } + // Parse telemetry config if o := list.Filter("telemetry"); len(o.Items) > 0 { if err := parseTelemetry(&result.Telemetry, o); err != nil { @@ -514,6 +523,7 @@ func parseServer(result **ServerConfig, list *ast.ObjectList) error { "retry_interval", "rejoin_after_leave", "encrypt", + "authoritative_region", } if err := checkHCLKeys(listVal, valid); err != nil { return err @@ -541,6 +551,55 @@ func parseServer(result **ServerConfig, list *ast.ObjectList) error { return nil } +func parseACL(result **ACLConfig, list *ast.ObjectList) error { + list = list.Elem() + if len(list.Items) > 1 { + return fmt.Errorf("only one 'acl' block allowed") + } + + // Get our server object + obj := list.Items[0] + + // Value should be an object + var listVal *ast.ObjectList + if ot, ok := obj.Val.(*ast.ObjectType); ok { + listVal = ot.List + } else { + return fmt.Errorf("acl value: should be an object") + } + + // Check for invalid keys + valid := []string{ + "enabled", + "token_ttl", + "policy_ttl", + } + if err := checkHCLKeys(listVal, valid); err != nil { + return err + } + + var m map[string]interface{} + if err := hcl.DecodeObject(&m, listVal); err != nil { + return err + } + + var config ACLConfig + dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: mapstructure.StringToTimeDurationHookFunc(), + WeaklyTypedInput: true, + Result: &config, + }) + if err != nil { + return err + } + if err := dec.Decode(m); err != nil { + return err + } + + *result = &config + return nil +} + func parseTelemetry(result **Telemetry, list *ast.ObjectList) error { list = list.Elem() if len(list.Items) > 1 { diff --git a/command/agent/config_parse_test.go b/command/agent/config_parse_test.go index a677f62c7..aa69f09f0 100644 --- a/command/agent/config_parse_test.go +++ b/command/agent/config_parse_test.go @@ -84,6 +84,7 @@ func TestConfig_Parse(t *testing.T) { }, Server: &ServerConfig{ Enabled: true, + AuthoritativeRegion: "foobar", BootstrapExpect: 5, DataDir: "/tmp/data", ProtocolVersion: 3, @@ -103,6 +104,11 @@ func TestConfig_Parse(t *testing.T) { RetryMaxAttempts: 3, EncryptKey: "abc", }, + ACL: &ACLConfig{ + Enabled: true, + TokenTTL: "30s", + PolicyTTL: "30s", + }, Telemetry: &Telemetry{ StatsiteAddr: "127.0.0.1:1234", StatsdAddr: "127.0.0.1:2345", diff --git a/command/agent/config_test.go b/command/agent/config_test.go index cdfa36baa..878f89f6e 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -27,6 +27,7 @@ func TestConfig_Merge(t *testing.T) { Telemetry: &Telemetry{}, Client: &ClientConfig{}, Server: &ServerConfig{}, + ACL: &ACLConfig{}, Ports: &Ports{}, Addresses: &Addresses{}, AdvertiseAddrs: &AdvertiseAddrs{}, @@ -91,6 +92,7 @@ func TestConfig_Merge(t *testing.T) { }, Server: &ServerConfig{ Enabled: false, + AuthoritativeRegion: "global", BootstrapExpect: 1, DataDir: "/tmp/data1", ProtocolVersion: 1, @@ -100,6 +102,11 @@ func TestConfig_Merge(t *testing.T) { MinHeartbeatTTL: 30 * time.Second, MaxHeartbeatsPerSecond: 30.0, }, + ACL: &ACLConfig{ + Enabled: true, + TokenTTL: "60s", + PolicyTTL: "60s", + }, Ports: &Ports{ HTTP: 4646, RPC: 4647, @@ -223,6 +230,7 @@ func TestConfig_Merge(t *testing.T) { }, Server: &ServerConfig{ Enabled: true, + AuthoritativeRegion: "global2", BootstrapExpect: 2, DataDir: "/tmp/data2", ProtocolVersion: 2, @@ -238,6 +246,11 @@ func TestConfig_Merge(t *testing.T) { RetryInterval: "10s", retryInterval: time.Second * 10, }, + ACL: &ACLConfig{ + Enabled: true, + TokenTTL: "20s", + PolicyTTL: "20s", + }, Ports: &Ports{ HTTP: 20000, RPC: 21000,