fix(authorization): subject wildcard domain rule not matching (#4187)

This fixes an issue where the subject wildcard domain rules (those containing {user} and {group}) are not considered matches even though they may be once a user authenticates.

Fixes #4186
pull/4167/head^2
James Elliott 2022-10-18 19:14:34 +11:00 committed by GitHub
parent bcdbedee11
commit a4edf21320
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 30 additions and 18 deletions

View File

@ -9,7 +9,7 @@ import (
) )
// NewAccessControlDomain creates a new SubjectObjectMatcher that matches the domain as a basic string. // NewAccessControlDomain creates a new SubjectObjectMatcher that matches the domain as a basic string.
func NewAccessControlDomain(domain string) AccessControlDomain { func NewAccessControlDomain(domain string) (subjcets bool, rule AccessControlDomain) {
m := &AccessControlDomainMatcher{} m := &AccessControlDomainMatcher{}
domain = strings.ToLower(domain) domain = strings.ToLower(domain)
@ -19,15 +19,15 @@ func NewAccessControlDomain(domain string) AccessControlDomain {
m.Name = domain[1:] m.Name = domain[1:]
case strings.HasPrefix(domain, "{user}"): case strings.HasPrefix(domain, "{user}"):
m.UserWildcard = true m.UserWildcard = true
m.Name = domain[7:] m.Name = domain[6:]
case strings.HasPrefix(domain, "{group}"): case strings.HasPrefix(domain, "{group}"):
m.GroupWildcard = true m.GroupWildcard = true
m.Name = domain[8:] m.Name = domain[7:]
default: default:
m.Name = domain m.Name = domain
} }
return AccessControlDomain{m} return m.UserWildcard || m.GroupWildcard, AccessControlDomain{m}
} }
// NewAccessControlDomainRegex creates a new SubjectObjectMatcher that matches the domain either in a basic way or // NewAccessControlDomainRegex creates a new SubjectObjectMatcher that matches the domain either in a basic way or
@ -65,11 +65,19 @@ func (m AccessControlDomainMatcher) IsMatch(domain string, subject Subject) (mat
case m.Wildcard: case m.Wildcard:
return strings.HasSuffix(domain, m.Name) return strings.HasSuffix(domain, m.Name)
case m.UserWildcard: case m.UserWildcard:
return domain == fmt.Sprintf("%s.%s", subject.Username, m.Name) if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
case m.GroupWildcard: return true
prefix, suffix := domainToPrefixSuffix(domain) }
return suffix == m.Name && utils.IsStringInSliceFold(prefix, subject.Groups) return domain == fmt.Sprintf("%s%s", subject.Username, m.Name)
case m.GroupWildcard:
if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
return true
}
i := strings.Index(domain, ".")
return domain[i:] == m.Name && utils.IsStringInSliceFold(domain[:i], subject.Groups)
default: default:
return strings.EqualFold(domain, m.Name) return strings.EqualFold(domain, m.Name)
} }

View File

@ -22,7 +22,6 @@ func NewAccessControlRules(config schema.AccessControlConfiguration) (rules []*A
func NewAccessControlRule(pos int, rule schema.ACLRule, networksMap map[string][]*net.IPNet, networksCacheMap map[string]*net.IPNet) *AccessControlRule { func NewAccessControlRule(pos int, rule schema.ACLRule, networksMap map[string][]*net.IPNet, networksCacheMap map[string]*net.IPNet) *AccessControlRule {
r := &AccessControlRule{ r := &AccessControlRule{
Position: pos, Position: pos,
Domains: schemaDomainsToACL(rule.Domains),
Methods: schemaMethodsToACL(rule.Methods), Methods: schemaMethodsToACL(rule.Methods),
Networks: schemaNetworksToACL(rule.Networks, networksMap, networksCacheMap), Networks: schemaNetworksToACL(rule.Networks, networksMap, networksCacheMap),
Subjects: schemaSubjectsToACL(rule.Subjects), Subjects: schemaSubjectsToACL(rule.Subjects),
@ -33,6 +32,7 @@ func NewAccessControlRule(pos int, rule schema.ACLRule, networksMap map[string][
r.HasSubjects = true r.HasSubjects = true
} }
ruleAddDomain(rule.Domains, r)
ruleAddDomainRegex(rule.DomainsRegex, r) ruleAddDomainRegex(rule.DomainsRegex, r)
ruleAddResources(rule.Resources, r) ruleAddResources(rule.Resources, r)

View File

@ -151,17 +151,17 @@ func (s *AuthorizerSuite) TestShouldCheckDynamicDomainRules() {
WithDefaultPolicy(deny). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"{user}.example.com"}, Domains: []string{"{user}.example.com"},
Policy: bypass, Policy: oneFactor,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"{group}.example.com"}, Domains: []string{"{group}.example.com"},
Policy: bypass, Policy: oneFactor,
}). }).
Build() Build()
tester.CheckAuthorizations(s.T(), UserWithGroups, "https://john.example.com/", "GET", Bypass) tester.CheckAuthorizations(s.T(), UserWithGroups, "https://john.example.com/", "GET", OneFactor)
tester.CheckAuthorizations(s.T(), UserWithGroups, "https://dev.example.com/", "GET", Bypass) tester.CheckAuthorizations(s.T(), UserWithGroups, "https://dev.example.com/", "GET", OneFactor)
tester.CheckAuthorizations(s.T(), UserWithGroups, "https://admins.example.com/", "GET", Bypass) tester.CheckAuthorizations(s.T(), UserWithGroups, "https://admins.example.com/", "GET", OneFactor)
tester.CheckAuthorizations(s.T(), UserWithGroups, "https://othergroup.example.com/", "GET", Denied) tester.CheckAuthorizations(s.T(), UserWithGroups, "https://othergroup.example.com/", "GET", Denied)
} }

View File

@ -70,12 +70,16 @@ func schemaSubjectToACLSubject(subjectRule string) (subject SubjectMatcher) {
return nil return nil
} }
func schemaDomainsToACL(domainRules []string) (domains []AccessControlDomain) { func ruleAddDomain(domainRules []string, rule *AccessControlRule) {
for _, domainRule := range domainRules { for _, domainRule := range domainRules {
domains = append(domains, NewAccessControlDomain(domainRule)) subjects, r := NewAccessControlDomain(domainRule)
}
return domains rule.Domains = append(rule.Domains, r)
if !rule.HasSubjects && subjects {
rule.HasSubjects = true
}
}
} }
func ruleAddDomainRegex(exps []regexp.Regexp, rule *AccessControlRule) { func ruleAddDomainRegex(exps []regexp.Regexp, rule *AccessControlRule) {