From b0fcf8614051e9c1b104cd000aac5072da999d0c Mon Sep 17 00:00:00 2001 From: Matt Keeler Date: Thu, 18 Jun 2020 11:16:57 -0400 Subject: [PATCH] Change auto config authorizer to allow for future extension The envisioned changes would allow extra settings to enable dynamically defined auth methods to be used instead of or in addition to the statically defined one in the configuration. --- agent/agent_test.go | 24 +++--- agent/config/builder.go | 56 +++++++------- agent/config/config.go | 20 +++-- agent/config/runtime_test.go | 140 +++++++++++++++++++---------------- 4 files changed, 130 insertions(+), 110 deletions(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index 8e270bd487..887447bb80 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -4594,19 +4594,21 @@ func TestAutoConfig_Integration(t *testing.T) { connect { enabled = true } auto_encrypt { allow_tls = true } auto_config { - authorizer { + authorization { enabled = true - claim_mappings = { - consul_node_name = "node" + static { + claim_mappings = { + consul_node_name = "node" + } + claim_assertions = [ + "value.node == \"${node}\"" + ] + bound_issuer = "consul" + bound_audiences = [ + "consul" + ] + jwt_validation_pub_keys = ["` + strings.ReplaceAll(pub, "\n", "\\n") + `"] } - claim_assertions = [ - "value.node == \"${node}\"" - ] - bound_issuer = "consul" - bound_audiences = [ - "consul" - ] - jwt_validation_pub_keys = ["` + strings.ReplaceAll(pub, "\n", "\\n") + `"] } } ` diff --git a/agent/config/builder.go b/agent/config/builder.go index f02834c306..b9217c1936 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -1937,41 +1937,41 @@ func (b *Builder) autoConfigVal(raw AutoConfigRaw) AutoConfig { val.IPSANs = append(val.IPSANs, ip) } - val.Authorizer = b.autoConfigAuthorizerVal(raw.Authorizer) + val.Authorizer = b.autoConfigAuthorizerVal(raw.Authorization) return val } -func (b *Builder) autoConfigAuthorizerVal(raw AutoConfigAuthorizerRaw) AutoConfigAuthorizer { +func (b *Builder) autoConfigAuthorizerVal(raw AutoConfigAuthorizationRaw) AutoConfigAuthorizer { + // Our config file syntax wraps the static authorizer configuration in a "static" stanza. However + // internally we do not support multiple configured authorization types so the RuntimeConfig just + // inlines the static one. While we can and probably should extend the authorization types in the + // future to support dynamic authorizers (ACL Auth Methods configured via normal APIs) its not + // needed right now so the configuration types will remain simplistic until they need to be otherwise. var val AutoConfigAuthorizer val.Enabled = b.boolValWithDefault(raw.Enabled, false) - val.ClaimAssertions = raw.ClaimAssertions - val.AllowReuse = b.boolValWithDefault(raw.AllowReuse, false) + val.ClaimAssertions = raw.Static.ClaimAssertions + val.AllowReuse = b.boolValWithDefault(raw.Static.AllowReuse, false) val.AuthMethod = structs.ACLAuthMethod{ - Name: "Auto Config Authorizer", - Type: "jwt", - // TODO (autoconf) - Configurable token TTL - MaxTokenTTL: 72 * time.Hour, + Name: "Auto Config Authorizer", + Type: "jwt", EnterpriseMeta: *structs.DefaultEnterpriseMeta(), Config: map[string]interface{}{ - "JWTSupportedAlgs": raw.JWTSupportedAlgs, - "BoundAudiences": raw.BoundAudiences, - "ClaimMappings": raw.ClaimMappings, - "ListClaimMappings": raw.ListClaimMappings, - "OIDCDiscoveryURL": b.stringVal(raw.OIDCDiscoveryURL), - "OIDCDiscoveryCACert": b.stringVal(raw.OIDCDiscoveryCACert), - "JWKSURL": b.stringVal(raw.JWKSURL), - "JWKSCACert": b.stringVal(raw.JWKSCACert), - "JWTValidationPubKeys": raw.JWTValidationPubKeys, - "BoundIssuer": b.stringVal(raw.BoundIssuer), - "ExpirationLeeway": b.durationVal("auto_config.authorizer.expiration_leeway", raw.ExpirationLeeway), - "NotBeforeLeeway": b.durationVal("auto_config.authorizer.not_before_leeway", raw.NotBeforeLeeway), - "ClockSkewLeeway": b.durationVal("auto_config.authorizer.clock_skew_leeway", raw.ClockSkewLeeway), + "JWTSupportedAlgs": raw.Static.JWTSupportedAlgs, + "BoundAudiences": raw.Static.BoundAudiences, + "ClaimMappings": raw.Static.ClaimMappings, + "ListClaimMappings": raw.Static.ListClaimMappings, + "OIDCDiscoveryURL": b.stringVal(raw.Static.OIDCDiscoveryURL), + "OIDCDiscoveryCACert": b.stringVal(raw.Static.OIDCDiscoveryCACert), + "JWKSURL": b.stringVal(raw.Static.JWKSURL), + "JWKSCACert": b.stringVal(raw.Static.JWKSCACert), + "JWTValidationPubKeys": raw.Static.JWTValidationPubKeys, + "BoundIssuer": b.stringVal(raw.Static.BoundIssuer), + "ExpirationLeeway": b.durationVal("auto_config.authorization.static.expiration_leeway", raw.Static.ExpirationLeeway), + "NotBeforeLeeway": b.durationVal("auto_config.authorization.static.not_before_leeway", raw.Static.NotBeforeLeeway), + "ClockSkewLeeway": b.durationVal("auto_config.authorization.static.clock_skew_leeway", raw.Static.ClockSkewLeeway), }, - // should be unnecessary as we aren't using the typical login process to create tokens but this is our - // desired mode regardless so if it ever did matter its probably better to be explicit. - TokenLocality: "local", } return val @@ -2018,7 +2018,7 @@ func (b *Builder) validateAutoConfigAuthorizer(rt RuntimeConfig) error { } // Auto Config Authorization is only supported on servers if !rt.ServerMode { - return fmt.Errorf("auto_config.authorizer.enabled cannot be set to true for client agents") + return fmt.Errorf("auto_config.authorization.enabled cannot be set to true for client agents") } // build out the validator to ensure that the given configuration was valid @@ -2026,7 +2026,7 @@ func (b *Builder) validateAutoConfigAuthorizer(rt RuntimeConfig) error { validator, err := ssoauth.NewValidator(null, &authz.AuthMethod) if err != nil { - return fmt.Errorf("auto_config.authorizer has invalid configuration: %v", err) + return fmt.Errorf("auto_config.authorization.static has invalid configuration: %v", err) } // create a blank identity for use to validate the claim assertions. @@ -2041,14 +2041,14 @@ func (b *Builder) validateAutoConfigAuthorizer(rt RuntimeConfig) error { // validate any HIL filled, err := libtempl.InterpolateHIL(raw, varMap, true) if err != nil { - return fmt.Errorf("auto_config.claim_assertion %q is invalid: %v", raw, err) + return fmt.Errorf("auto_config.authorization.static.claim_assertion %q is invalid: %v", raw, err) } // validate the bexpr syntax - note that for now all the keys mapped by the claim mappings // are not validateable due to them being put inside a map. Some bexpr updates to setup keys // from current map keys would probably be nice here. if _, err := bexpr.CreateEvaluatorForType(filled, nil, blankID.SelectableFields); err != nil { - return fmt.Errorf("auto_config.claim_assertion %q is invalid: %v", raw, err) + return fmt.Errorf("auto_config.authorization.static.claim_assertion %q is invalid: %v", raw, err) } } return nil diff --git a/agent/config/config.go b/agent/config/config.go index 3b2490cd7e..2ed81f6b92 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -689,17 +689,21 @@ type AuditSink struct { } type AutoConfigRaw struct { - Enabled *bool `json:"enabled,omitempty" hcl:"enabled" mapstructure:"enabled"` - IntroToken *string `json:"intro_token,omitempty" hcl:"intro_token" mapstructure:"intro_token"` - IntroTokenFile *string `json:"intro_token_file,omitempty" hcl:"intro_token_file" mapstructure:"intro_token_file"` - ServerAddresses []string `json:"server_addresses,omitempty" hcl:"server_addresses" mapstructure:"server_addresses"` - DNSSANs []string `json:"dns_sans,omitempty" hcl:"dns_sans" mapstructure:"dns_sans"` - IPSANs []string `json:"ip_sans,omitempty" hcl:"ip_sans" mapstructure:"ip_sans"` - Authorizer AutoConfigAuthorizerRaw `json:"authorizer,omitempty" hcl:"authorizer" mapstructure:"authorizer"` + Enabled *bool `json:"enabled,omitempty" hcl:"enabled" mapstructure:"enabled"` + IntroToken *string `json:"intro_token,omitempty" hcl:"intro_token" mapstructure:"intro_token"` + IntroTokenFile *string `json:"intro_token_file,omitempty" hcl:"intro_token_file" mapstructure:"intro_token_file"` + ServerAddresses []string `json:"server_addresses,omitempty" hcl:"server_addresses" mapstructure:"server_addresses"` + DNSSANs []string `json:"dns_sans,omitempty" hcl:"dns_sans" mapstructure:"dns_sans"` + IPSANs []string `json:"ip_sans,omitempty" hcl:"ip_sans" mapstructure:"ip_sans"` + Authorization AutoConfigAuthorizationRaw `json:"authorization,omitempty" hcl:"authorization" mapstructure:"authorization"` +} + +type AutoConfigAuthorizationRaw struct { + Enabled *bool `json:"enabled,omitempty" hcl:"enabled" mapstructure:"enabled"` + Static AutoConfigAuthorizerRaw `json:"static,omitempty" hcl:"static" mapstructure:"static"` } type AutoConfigAuthorizerRaw struct { - Enabled *bool `json:"enabled,omitempty" hcl:"enabled" mapstructure:"enabled"` ClaimAssertions []string `json:"claim_assertions,omitempty" hcl:"claim_assertions" mapstructure:"claim_assertions"` AllowReuse *bool `json:"allow_reuse,omitempty" hcl:"allow_reuse" mapstructure:"allow_reuse"` diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index bf1e37d9c9..de84f9b6b0 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -3918,7 +3918,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { }, hcl: []string{` auto_config { - authorizer { + authorization { enabled = true } } @@ -3926,12 +3926,12 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { json: []string{` { "auto_config": { - "authorizer": { + "authorization": { "enabled": true } } }`}, - err: "auto_config.authorizer.enabled cannot be set to true for client agents", + err: "auto_config.authorization.enabled cannot be set to true for client agents", }, { @@ -3942,7 +3942,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { }, hcl: []string{` auto_config { - authorizer { + authorization { enabled = true } } @@ -3950,12 +3950,12 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { json: []string{` { "auto_config": { - "authorizer": { + "authorization": { "enabled": true } } }`}, - err: `auto_config.authorizer has invalid configuration: exactly one of 'JWTValidationPubKeys', 'JWKSURL', or 'OIDCDiscoveryURL' must be set for type "jwt"`, + err: `auto_config.authorization.static has invalid configuration: exactly one of 'JWTValidationPubKeys', 'JWKSURL', or 'OIDCDiscoveryURL' must be set for type "jwt"`, }, { @@ -3966,24 +3966,28 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { }, hcl: []string{` auto_config { - authorizer { + authorization { enabled = true - jwks_url = "https://fake.uri.local" - oidc_discovery_url = "https://fake.uri.local" + static { + jwks_url = "https://fake.uri.local" + oidc_discovery_url = "https://fake.uri.local" + } } } `}, json: []string{` { "auto_config": { - "authorizer": { + "authorization": { "enabled": true, - "jwks_url": "https://fake.uri.local", - "oidc_discovery_url": "https://fake.uri.local" + "static": { + "jwks_url": "https://fake.uri.local", + "oidc_discovery_url": "https://fake.uri.local" + } } } }`}, - err: `auto_config.authorizer has invalid configuration: exactly one of 'JWTValidationPubKeys', 'JWKSURL', or 'OIDCDiscoveryURL' must be set for type "jwt"`, + err: `auto_config.authorization.static has invalid configuration: exactly one of 'JWTValidationPubKeys', 'JWKSURL', or 'OIDCDiscoveryURL' must be set for type "jwt"`, }, { @@ -3994,28 +3998,32 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { }, hcl: []string{` auto_config { - authorizer { + authorization { enabled = true - jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"] - claim_assertions = [ - "values.node == ${node}" - ] + static { + jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"] + claim_assertions = [ + "values.node == ${node}" + ] + } } } `}, json: []string{` { "auto_config": { - "authorizer": { + "authorization": { "enabled": true, - "jwt_validation_pub_keys": ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"], - "claim_assertions": [ - "values.node == ${node}" - ] + "static": { + "jwt_validation_pub_keys": ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"], + "claim_assertions": [ + "values.node == ${node}" + ] + } } } }`}, - err: `auto_config.claim_assertion "values.node == ${node}" is invalid: Selector "values" is not valid`, + err: `auto_config.authorization.static.claim_assertion "values.node == ${node}" is invalid: Selector "values" is not valid`, }, { desc: "auto config authorizer ok", @@ -4025,14 +4033,16 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { }, hcl: []string{` auto_config { - authorizer { + authorization { enabled = true - jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"] - claim_assertions = [ - "value.node == ${node}" - ] - claim_mappings = { - node = "node" + static { + jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"] + claim_assertions = [ + "value.node == ${node}" + ] + claim_mappings = { + node = "node" + } } } } @@ -4040,14 +4050,16 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { json: []string{` { "auto_config": { - "authorizer": { + "authorization": { "enabled": true, - "jwt_validation_pub_keys": ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"], - "claim_assertions": [ - "value.node == ${node}" - ], - "claim_mappings": { - "node": "node" + "static": { + "jwt_validation_pub_keys": ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"], + "claim_assertions": [ + "value.node == ${node}" + ], + "claim_mappings": { + "node": "node" + } } } } @@ -4304,19 +4316,21 @@ func TestFullConfig(t *testing.T) { "dns_sans": ["6zdaWg9J"], "ip_sans": ["198.18.99.99"], "server_addresses": ["198.18.100.1"], - "authorizer": { + "authorization": { "enabled": true, - "allow_reuse": true, - "claim_mappings": { - "node": "node" - }, - "list_claim_mappings": { - "foo": "bar" - }, - "bound_issuer": "consul", - "bound_audiences": ["consul-cluster-1"], - "claim_assertions": ["value.node == \"${node}\""], - "jwt_validation_pub_keys": ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"] + "static": { + "allow_reuse": true, + "claim_mappings": { + "node": "node" + }, + "list_claim_mappings": { + "foo": "bar" + }, + "bound_issuer": "consul", + "bound_audiences": ["consul-cluster-1"], + "claim_assertions": ["value.node == \"${node}\""], + "jwt_validation_pub_keys": ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"] + } } }, "autopilot": { @@ -4962,19 +4976,21 @@ func TestFullConfig(t *testing.T) { dns_sans = ["6zdaWg9J"] ip_sans = ["198.18.99.99"] server_addresses = ["198.18.100.1"] - authorizer = { + authorization = { enabled = true - allow_reuse = true - claim_mappings = { - node = "node" - } - list_claim_mappings = { - foo = "bar" + static { + allow_reuse = true + claim_mappings = { + node = "node" + } + list_claim_mappings = { + foo = "bar" + } + bound_issuer = "consul" + bound_audiences = ["consul-cluster-1"] + claim_assertions = ["value.node == \"${node}\""] + jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"] } - bound_issuer = "consul" - bound_audiences = ["consul-cluster-1"] - claim_assertions = ["value.node == \"${node}\""] - jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"] } } autopilot = { @@ -5828,7 +5844,6 @@ func TestFullConfig(t *testing.T) { AuthMethod: structs.ACLAuthMethod{ Name: "Auto Config Authorizer", Type: "jwt", - MaxTokenTTL: 72 * time.Hour, EnterpriseMeta: *structs.DefaultEnterpriseMeta(), Config: map[string]interface{}{ "JWTValidationPubKeys": []string{"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"}, @@ -5849,7 +5864,6 @@ func TestFullConfig(t *testing.T) { "ClockSkewLeeway": 0 * time.Second, "JWTSupportedAlgs": []string(nil), }, - TokenLocality: "local", }, }, },