Skip to content

Commit b0f47f2

Browse files
knrcdgn
authored andcommitted
MAISTRA-224: Support for authorisation regex matching on request headers
Co-authored-by: Daniel Grimm <[email protected]>
1 parent 1343d77 commit b0f47f2

File tree

6 files changed

+89
-2
lines changed

6 files changed

+89
-2
lines changed

pilot/pkg/security/authz/model/matcher/header.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"strings"
1919

2020
route "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
21+
matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher"
2122
)
2223

2324
// HeaderMatcher converts a key, value string pair to a corresponding HeaderMatcher.
@@ -53,3 +54,16 @@ func HeaderMatcher(k, v string) *route.HeaderMatcher {
5354
},
5455
}
5556
}
57+
58+
// HeaderMatcherRegex converts a key, value string pair to a corresponding SafeRegex HeaderMatcher.
59+
func HeaderMatcherRegex(k, v string) *route.HeaderMatcher {
60+
return &route.HeaderMatcher{
61+
Name: k,
62+
HeaderMatchSpecifier: &route.HeaderMatcher_SafeRegexMatch{
63+
SafeRegexMatch: &matcher.RegexMatcher{
64+
EngineType: &matcher.RegexMatcher_GoogleRe2{GoogleRe2: &matcher.RegexMatcher_GoogleRE2{}},
65+
Regex: v,
66+
},
67+
},
68+
}
69+
}

pilot/pkg/security/authz/model/model.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ const (
3636
RBACTCPFilterStatPrefix = "tcp."
3737

3838
// attributes that could be used in both ServiceRoleBinding and ServiceRole.
39-
attrRequestHeader = "request.headers" // header name is surrounded by brackets, e.g. "request.headers[User-Agent]".
39+
attrRequestHeader = "request.headers" // header name is surrounded by brackets, e.g. "request.headers[User-Agent]".
40+
attrRequestRegexHeader = "request.regex.headers" // header name is surrounded by brackets, e.g. "request.headers.regex[User-Agent]".
4041

4142
// attributes that could be used in a ServiceRoleBinding property.
4243
attrSrcIP = "source.ip" // supports both single ip and cidr, e.g. "10.1.2.3" or "10.1.0.0/16".

pilot/pkg/security/authz/model/permission.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func (permission *Permission) ValidateForTCP(forTCP bool) error {
112112
}
113113
for _, constraint := range permission.Constraints {
114114
for k := range constraint {
115-
if strings.HasPrefix(k, attrRequestHeader) {
115+
if strings.HasPrefix(k, attrRequestHeader) || strings.HasPrefix(k, attrRequestRegexHeader) {
116116
return fmt.Errorf("constraint(%v)", constraint)
117117
}
118118
}
@@ -207,6 +207,7 @@ func isSupportedPermission(key string) bool {
207207
case key == attrDestPort:
208208
case key == pathHeader || key == methodHeader || key == hostHeader:
209209
case strings.HasPrefix(key, attrRequestHeader):
210+
case strings.HasPrefix(key, attrRequestRegexHeader):
210211
case key == attrConnSNI:
211212
case strings.HasPrefix(key, "experimental.envoy.filters.") && isKeyBinary(key):
212213
default:
@@ -252,6 +253,16 @@ func (permission *Permission) forKeyValues(key string, values []string) *envoy_r
252253
m := matcher.HeaderMatcher(header, v)
253254
return permissionHeader(m), nil
254255
}
256+
case strings.HasPrefix(key, attrRequestRegexHeader):
257+
header, err := extractNameInBrackets(strings.TrimPrefix(key, attrRequestRegexHeader))
258+
if err != nil {
259+
rbacLog.Errorf("ignored invalid %s: %v", attrRequestRegexHeader, err)
260+
return nil
261+
}
262+
converter = func(v string) (*envoy_rbac.Permission, error) {
263+
m := matcher.HeaderMatcherRegex(header, v)
264+
return permissionHeader(m), nil
265+
}
255266
case key == attrConnSNI:
256267
converter = func(v string) (*envoy_rbac.Permission, error) {
257268
m := matcher.StringMatcher(v, permission.v1beta1)

pilot/pkg/security/authz/model/permission_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,34 @@ func TestPermission_Generate(t *testing.T) {
499499
exactMatch: tag-2
500500
name: X-tag`,
501501
},
502+
{
503+
name: "permission with constraint attrRequestRegexHeader",
504+
permission: &Permission{
505+
Constraints: []KeyValues{
506+
{
507+
fmt.Sprintf("%s[%s]", attrRequestRegexHeader, "X-id"): []string{"id-[0-9]"},
508+
fmt.Sprintf("%s[%s]", attrRequestRegexHeader, "X-tag"): []string{"tag-[0-9]"},
509+
},
510+
},
511+
},
512+
wantYAML: `
513+
andRules:
514+
rules:
515+
- orRules:
516+
rules:
517+
- header:
518+
name: X-id
519+
safeRegexMatch:
520+
googleRe2: {}
521+
regex: id-[0-9]
522+
- orRules:
523+
rules:
524+
- header:
525+
name: X-tag
526+
safeRegexMatch:
527+
googleRe2: {}
528+
regex: tag-[0-9]`,
529+
},
502530
{
503531
name: "permission with constraint attrConnSNI",
504532
permission: &Permission{

pilot/pkg/security/authz/model/principal.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ func isSupportedPrincipal(key string) bool {
184184
case attrSrcPrincipal == key:
185185
case found(key, []string{attrRequestPrincipal, attrRequestAudiences, attrRequestPresenter, attrSrcUser}):
186186
case strings.HasPrefix(key, attrRequestHeader):
187+
case strings.HasPrefix(key, attrRequestRegexHeader):
187188
case strings.HasPrefix(key, attrRequestClaims):
188189
default:
189190
return false
@@ -255,6 +256,14 @@ func (principal *Principal) forKeyValue(key, value string, forTCPFilter bool) *e
255256
}
256257
m := matcher.HeaderMatcher(header, value)
257258
return principalHeader(m)
259+
case strings.HasPrefix(key, attrRequestRegexHeader):
260+
header, err := extractNameInBrackets(strings.TrimPrefix(key, attrRequestRegexHeader))
261+
if err != nil {
262+
rbacLog.Errorf("ignored invalid %s: %v", attrRequestRegexHeader, err)
263+
return nil
264+
}
265+
m := matcher.HeaderMatcherRegex(header, value)
266+
return principalHeader(m)
258267
case strings.HasPrefix(key, attrRequestClaims):
259268
claim, err := extractNameInBrackets(strings.TrimPrefix(key, attrRequestClaims))
260269
if err != nil {

pilot/pkg/security/authz/model/principal_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,30 @@ func TestPrincipal_Generate(t *testing.T) {
657657
exactMatch: tag-2
658658
name: X-tag`,
659659
},
660+
{
661+
name: "principal with property attrRequestHeader",
662+
principal: &Principal{
663+
Properties: []KeyValues{
664+
{
665+
fmt.Sprintf("%s[%s]", attrRequestRegexHeader, "X-id"): []string{"id-[0-9]"},
666+
fmt.Sprintf("%s[%s]", attrRequestRegexHeader, "X-tag"): []string{"tag-[0-9]"},
667+
},
668+
},
669+
},
670+
wantYAML: `
671+
andIds:
672+
ids:
673+
- header:
674+
name: X-id
675+
safeRegexMatch:
676+
googleRe2: {}
677+
regex: id-[0-9]
678+
- header:
679+
name: X-tag
680+
safeRegexMatch:
681+
googleRe2: {}
682+
regex: tag-[0-9]`,
683+
},
660684
{
661685
name: "principal with property attrRequestClaims",
662686
principal: &Principal{

0 commit comments

Comments
 (0)