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.
This commit is contained in:
Matt Keeler 2020-06-18 11:16:57 -04:00 committed by Matt Keeler
parent ea8fbdc68f
commit b0fcf86140
4 changed files with 130 additions and 110 deletions

View File

@ -4594,8 +4594,9 @@ func TestAutoConfig_Integration(t *testing.T) {
connect { enabled = true }
auto_encrypt { allow_tls = true }
auto_config {
authorizer {
authorization {
enabled = true
static {
claim_mappings = {
consul_node_name = "node"
}
@ -4609,6 +4610,7 @@ func TestAutoConfig_Integration(t *testing.T) {
jwt_validation_pub_keys = ["` + strings.ReplaceAll(pub, "\n", "\\n") + `"]
}
}
}
`
srv := StartTestAgent(t, TestAgent{Name: "TestAgent-Server", HCL: hclConfig})

View File

@ -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,
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

View File

@ -695,11 +695,15 @@ type AutoConfigRaw struct {
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"`
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"`

View File

@ -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
static {
jwks_url = "https://fake.uri.local"
oidc_discovery_url = "https://fake.uri.local"
}
}
}
`},
json: []string{`
{
"auto_config": {
"authorizer": {
"authorization": {
"enabled": true,
"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
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,
"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,8 +4033,9 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
},
hcl: []string{`
auto_config {
authorizer {
authorization {
enabled = true
static {
jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"]
claim_assertions = [
"value.node == ${node}"
@ -4036,12 +4045,14 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
}
}
}
}
`},
json: []string{`
{
"auto_config": {
"authorizer": {
"authorization": {
"enabled": true,
"static": {
"jwt_validation_pub_keys": ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"],
"claim_assertions": [
"value.node == ${node}"
@ -4051,6 +4062,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
}
}
}
}
}`},
patch: func(rt *RuntimeConfig) {
rt.AutoConfig.Authorizer.Enabled = true
@ -4304,8 +4316,9 @@ func TestFullConfig(t *testing.T) {
"dns_sans": ["6zdaWg9J"],
"ip_sans": ["198.18.99.99"],
"server_addresses": ["198.18.100.1"],
"authorizer": {
"authorization": {
"enabled": true,
"static": {
"allow_reuse": true,
"claim_mappings": {
"node": "node"
@ -4318,6 +4331,7 @@ func TestFullConfig(t *testing.T) {
"claim_assertions": ["value.node == \"${node}\""],
"jwt_validation_pub_keys": ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"]
}
}
},
"autopilot": {
"cleanup_dead_servers": true,
@ -4962,8 +4976,9 @@ func TestFullConfig(t *testing.T) {
dns_sans = ["6zdaWg9J"]
ip_sans = ["198.18.99.99"]
server_addresses = ["198.18.100.1"]
authorizer = {
authorization = {
enabled = true
static {
allow_reuse = true
claim_mappings = {
node = "node"
@ -4977,6 +4992,7 @@ func TestFullConfig(t *testing.T) {
jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERVchfCZng4mmdvQz1+sJHRN40snC\nYt8NjYOnbnScEXMkyoUmASr88gb7jaVAVt3RYASAbgBjB2Z+EUizWkx5Tg==\n-----END PUBLIC KEY-----"]
}
}
}
autopilot = {
cleanup_dead_servers = true
disable_upgrade_migration = true
@ -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",
},
},
},