Authorization Policy Bypass Due to Case Insensitive Host Comparison
Description
Istio is an open source platform for providing a uniform way to integrate microservices, manage traffic flow across microservices, enforce policies and aggregate telemetry data. According to RFC 4343, Istio authorization policy should compare the hostname in the HTTP Host header in a case insensitive way, but currently the comparison is case sensitive. The proxy will route the request hostname in a case-insensitive way which means the authorization policy could be bypassed. As an example, the user may have an authorization policy that rejects request with hostname "httpbin.foo" for some source IPs, but the attacker can bypass this by sending the request with hostname "Httpbin.Foo". Patches are available in Istio 1.11.1, Istio 1.10.4 and Istio 1.9.8. As a work around a Lua filter may be written to normalize Host header before the authorization check. This is similar to the Path normalization presented in the Security Best Practices guide.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
istio.io/istioGo | < 1.9.8 | 1.9.8 |
istio.io/istioGo | >= 1.10.0, < 1.10.4 | 1.10.4 |
istio.io/istioGo | >= 1.11.0, < 1.11.1 | 1.11.1 |
Affected products
1Patches
326 files changed · +235 −20
istio.deps+1 −1 modified@@ -4,6 +4,6 @@ "name": "PROXY_REPO_SHA", "repoName": "proxy", "file": "", - "lastStableSHA": "1d095b02cf3be490e5331512e5865d86144436a5" + "lastStableSHA": "5b4dedce4f16569316fccb7a45e4144c4771648c" } ]
pilot/pkg/security/authz/builder/testdata/all-fields-out.yaml+18 −6 modified@@ -10,29 +10,41 @@ typedConfig: - orRules: rules: - header: - exactMatch: exact.com name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)exact\.com - header: name: :authority - suffixMatch: .suffix.com + safeRegexMatch: + googleRe2: {} + regex: (?i).*\.suffix\.com - header: name: :authority - prefixMatch: prefix. + safeRegexMatch: + googleRe2: {} + regex: (?i)prefix\..* - header: name: :authority presentMatch: true - notRule: orRules: rules: - header: - exactMatch: not-exact.com name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)not-exact\.com - header: name: :authority - suffixMatch: .not-suffix.com + safeRegexMatch: + googleRe2: {} + regex: (?i).*\.not-suffix\.com - header: name: :authority - prefixMatch: not-prefix. + safeRegexMatch: + googleRe2: {} + regex: (?i)not-prefix\..* - header: name: :authority presentMatch: true
pilot/pkg/security/authz/builder/testdata/multiple-policies-out.yaml+6 −2 modified@@ -42,11 +42,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: google.com name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)google\.com - header: - exactMatch: httpbin.org name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)httpbin\.org principals: - andIds: ids:
pilot/pkg/security/authz/builder/testdata/single-policy-out.yaml+24 −8 modified@@ -10,11 +10,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[0]-to[0]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[0\]-host\[1\] - header: - exactMatch: rule[0]-to[0]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[0\]-host\[2\] - orRules: rules: - header: @@ -48,11 +52,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[0]-to[1]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[1\]-host\[1\] - header: - exactMatch: rule[0]-to[1]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[1\]-host\[2\] - orRules: rules: - header: @@ -270,11 +278,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[1]-to[0]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[0\]-host\[1\] - header: - exactMatch: rule[1]-to[0]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[0\]-host\[2\] - orRules: rules: - header: @@ -300,11 +312,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[1]-to[1]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[1\]-host\[1\] - header: - exactMatch: rule[1]-to[1]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[1\]-host\[2\] - orRules: rules: - header:
pilot/pkg/security/authz/matcher/header.go+31 −0 modified@@ -15,6 +15,7 @@ package matcher import ( + "regexp" "strings" routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" @@ -55,6 +56,36 @@ func HeaderMatcher(k, v string) *routepb.HeaderMatcher { } } +// HostMatcher creates a host matcher for a host. +func HostMatcher(k, v string) *routepb.HeaderMatcher { + var regex string + if v == "*" { + return &routepb.HeaderMatcher{ + Name: k, + HeaderMatchSpecifier: &routepb.HeaderMatcher_PresentMatch{ + PresentMatch: true, + }, + } + } else if strings.HasPrefix(v, "*") { + regex = `.*` + regexp.QuoteMeta(v[1:]) + } else if strings.HasSuffix(v, "*") { + regex = regexp.QuoteMeta(v[:len(v)-1]) + `.*` + } else { + regex = regexp.QuoteMeta(v) + } + return &routepb.HeaderMatcher{ + Name: k, + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i)` + regex, + }, + }, + } +} + // PathMatcher creates a path matcher for a path. func PathMatcher(path string) *matcherpb.PathMatcher { return &matcherpb.PathMatcher{
pilot/pkg/security/authz/matcher/header_test.go+83 −0 modified@@ -15,6 +15,7 @@ package matcher import ( + "regexp" "testing" routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" @@ -62,6 +63,88 @@ func TestHeaderMatcher(t *testing.T) { } } +func TestHostMatcher(t *testing.T) { + testCases := []struct { + Name string + K string + V string + Expect *routepb.HeaderMatcher + }{ + { + Name: "present match", + K: ":authority", + V: "*", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_PresentMatch{PresentMatch: true}, + }, + }, + { + Name: "prefix match", + K: ":authority", + V: "*.example.com", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i).*\.example\.com`, + }, + }, + }, + }, + { + Name: "suffix match", + K: ":authority", + V: "example.*", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i)example\..*`, + }, + }, + }, + }, + { + Name: "exact match", + K: ":authority", + V: "example.com", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i)example\.com`, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + actual := HostMatcher(tc.K, tc.V) + if re := actual.GetSafeRegexMatch().GetRegex(); re != "" { + _, err := regexp.Compile(re) + if err != nil { + t.Errorf("failed to compile regex %s: %v", re, err) + } + } + if !cmp.Equal(tc.Expect, actual, protocmp.Transform()) { + t.Errorf("expecting %v, but got %v", tc.Expect, actual) + } + }) + } +} + func TestPathMatcher(t *testing.T) { testCases := []struct { Name string
pilot/pkg/security/authz/model/generator.go+1 −1 modified@@ -246,7 +246,7 @@ func (hostGenerator) permission(key, value string, forTCP bool) (*rbacpb.Permiss return nil, fmt.Errorf("%q is HTTP only", key) } - m := matcher.HeaderMatcher(hostHeader, value) + m := matcher.HostMatcher(hostHeader, value) return permissionHeader(m), nil }
pilot/pkg/security/authz/model/generator_test.go+3 −1 modified@@ -251,7 +251,9 @@ func TestGenerator(t *testing.T) { value: "foo", want: yamlPermission(t, ` header: - exactMatch: foo + safeRegexMatch: + googleRe2: {} + regex: (?i)foo name: :authority`), }, {
pkg/bootstrap/testdata/all_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/auth_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/authsds_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/default_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/running_golden.json+1 −0 modified@@ -18,6 +18,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/runningsds_golden.json+1 −0 modified@@ -18,6 +18,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/stats_inclusion_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/tracing_datadog_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/tracing_lightstep_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/tracing_opencensusagent_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/tracing_stackdriver_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/tracing_tls_custom_sni_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/tracing_tls_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/tracing_zipkin_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
pkg/bootstrap/testdata/xdsproxy_golden.json+1 −0 modified@@ -13,6 +13,7 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "re2.max_program_size.error_level": 1024 } },
tests/integration/security/authorization_test.go+45 −0 modified@@ -524,6 +524,51 @@ func TestAuthorization_IngressGateway(t *testing.T) { IP string WantCode int }{ + { + Name: "case-insensitive-deny deny.company.com", + Host: "deny.company.com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny DENY.COMPANY.COM", + Host: "DENY.COMPANY.COM", + WantCode: 403, + }, + { + Name: "case-insensitive-deny Deny.Company.Com", + Host: "Deny.Company.Com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny deny.suffix.company.com", + Host: "deny.suffix.company.com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny DENY.SUFFIX.COMPANY.COM", + Host: "DENY.SUFFIX.COMPANY.COM", + WantCode: 403, + }, + { + Name: "case-insensitive-deny Deny.Suffix.Company.Com", + Host: "Deny.Suffix.Company.Com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny prefix.company.com", + Host: "prefix.company.com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny PREFIX.COMPANY.COM", + Host: "PREFIX.COMPANY.COM", + WantCode: 403, + }, + { + Name: "case-insensitive-deny Prefix.Company.Com", + Host: "Prefix.Company.Com", + WantCode: 403, + }, { Name: "allow www.company.com", Host: "www.company.com",
tests/integration/security/testdata/authz/v1beta1-ingress-gateway.yaml.tmpl+4 −1 modified@@ -1,7 +1,7 @@ # The following policy denies access to "internal.company.com" and path "/private", # denies access from 172.17.72.46 or 192.168.4.0/23 to "remoteipblocks.company.com", # denies access from anything but 172.23.240.0/22 to "notremoteipblocks.company.com", -# and denies access to "remoteipattr.company.com" when the remote ip is 10.242.5.7 or +# and denies access to "remoteipattr.company.com" when the remote ip is 10.242.5.7 or # in the network 10.124.99.0/24. apiVersion: security.istio.io/v1beta1 @@ -15,6 +15,9 @@ spec: matchLabels: app: istio-ingressgateway rules: + - to: + - operation: + hosts: ["deny.company.com", "*.suffix.company.com", "prefix.company.*"] - to: - operation: hosts: ["internal.company.com"]
tools/packaging/common/envoy_bootstrap.json+4 −0 modified@@ -28,6 +28,10 @@ "static_layer": { "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.strict_1xx_and_204_response_headers": false, + "envoy.reloadable_features.http_reject_path_with_fragment": false, + {{- if eq (or .config.ProxyMetadata.HTTP_STRIP_FRAGMENT_FROM_PATH_UNSAFE_IF_DISABLED "") "false" }} + "envoy.reloadable_features.http_strip_fragment_from_path_unsafe_if_disabled": false, + {{- end }} "re2.max_program_size.error_level": 1024 } },
28 files changed · +237 −21
istio.deps+1 −1 modified@@ -4,6 +4,6 @@ "name": "PROXY_REPO_SHA", "repoName": "proxy", "file": "", - "lastStableSHA": "494a674e70543a319ad4865482c125581f5746bf" + "lastStableSHA": "9a976952980250ed356ebdd4f94e219e28a041dd" } ]
pilot/pkg/security/authz/builder/testdata/http/allow-full-rule-out.yaml+18 −6 modified@@ -10,29 +10,41 @@ typedConfig: - orRules: rules: - header: - exactMatch: exact.com name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)exact\.com - header: name: :authority - suffixMatch: .suffix.com + safeRegexMatch: + googleRe2: {} + regex: (?i).*\.suffix\.com - header: name: :authority - prefixMatch: prefix. + safeRegexMatch: + googleRe2: {} + regex: (?i)prefix\..* - header: name: :authority presentMatch: true - notRule: orRules: rules: - header: - exactMatch: not-exact.com name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)not-exact\.com - header: name: :authority - suffixMatch: .not-suffix.com + safeRegexMatch: + googleRe2: {} + regex: (?i).*\.not-suffix\.com - header: name: :authority - prefixMatch: not-prefix. + safeRegexMatch: + googleRe2: {} + regex: (?i)not-prefix\..* - header: name: :authority presentMatch: true
pilot/pkg/security/authz/builder/testdata/http/multiple-policies-out.yaml+6 −2 modified@@ -42,11 +42,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: google.com name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)google\.com - header: - exactMatch: httpbin.org name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)httpbin\.org principals: - andIds: ids:
pilot/pkg/security/authz/builder/testdata/http/single-policy-out.yaml+24 −8 modified@@ -10,11 +10,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[0]-to[0]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[0\]-host\[1\] - header: - exactMatch: rule[0]-to[0]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[0\]-host\[2\] - orRules: rules: - header: @@ -48,11 +52,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[0]-to[1]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[1\]-host\[1\] - header: - exactMatch: rule[0]-to[1]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[1\]-host\[2\] - orRules: rules: - header: @@ -238,11 +246,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[1]-to[0]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[0\]-host\[1\] - header: - exactMatch: rule[1]-to[0]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[0\]-host\[2\] - orRules: rules: - header: @@ -268,11 +280,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[1]-to[1]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[1\]-host\[1\] - header: - exactMatch: rule[1]-to[1]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[1\]-host\[2\] - orRules: rules: - header:
pilot/pkg/security/authz/matcher/header.go+31 −0 modified@@ -15,6 +15,7 @@ package matcher import ( + "regexp" "strings" routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" @@ -55,6 +56,36 @@ func HeaderMatcher(k, v string) *routepb.HeaderMatcher { } } +// HostMatcher creates a host matcher for a host. +func HostMatcher(k, v string) *routepb.HeaderMatcher { + var regex string + if v == "*" { + return &routepb.HeaderMatcher{ + Name: k, + HeaderMatchSpecifier: &routepb.HeaderMatcher_PresentMatch{ + PresentMatch: true, + }, + } + } else if strings.HasPrefix(v, "*") { + regex = `.*` + regexp.QuoteMeta(v[1:]) + } else if strings.HasSuffix(v, "*") { + regex = regexp.QuoteMeta(v[:len(v)-1]) + `.*` + } else { + regex = regexp.QuoteMeta(v) + } + return &routepb.HeaderMatcher{ + Name: k, + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i)` + regex, + }, + }, + } +} + // PathMatcher creates a path matcher for a path. func PathMatcher(path string) *matcherpb.PathMatcher { return &matcherpb.PathMatcher{
pilot/pkg/security/authz/matcher/header_test.go+83 −0 modified@@ -15,6 +15,7 @@ package matcher import ( + "regexp" "testing" routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" @@ -62,6 +63,88 @@ func TestHeaderMatcher(t *testing.T) { } } +func TestHostMatcher(t *testing.T) { + testCases := []struct { + Name string + K string + V string + Expect *routepb.HeaderMatcher + }{ + { + Name: "present match", + K: ":authority", + V: "*", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_PresentMatch{PresentMatch: true}, + }, + }, + { + Name: "prefix match", + K: ":authority", + V: "*.example.com", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i).*\.example\.com`, + }, + }, + }, + }, + { + Name: "suffix match", + K: ":authority", + V: "example.*", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i)example\..*`, + }, + }, + }, + }, + { + Name: "exact match", + K: ":authority", + V: "example.com", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i)example\.com`, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + actual := HostMatcher(tc.K, tc.V) + if re := actual.GetSafeRegexMatch().GetRegex(); re != "" { + _, err := regexp.Compile(re) + if err != nil { + t.Errorf("failed to compile regex %s: %v", re, err) + } + } + if !cmp.Equal(tc.Expect, actual, protocmp.Transform()) { + t.Errorf("expecting %v, but got %v", tc.Expect, actual) + } + }) + } +} + func TestPathMatcher(t *testing.T) { testCases := []struct { Name string
pilot/pkg/security/authz/model/generator.go+1 −1 modified@@ -222,7 +222,7 @@ func (hostGenerator) permission(key, value string, forTCP bool) (*rbacpb.Permiss return nil, fmt.Errorf("%q is HTTP only", key) } - m := matcher.HeaderMatcher(hostHeader, value) + m := matcher.HostMatcher(hostHeader, value) return permissionHeader(m), nil }
pilot/pkg/security/authz/model/generator_test.go+3 −1 modified@@ -243,7 +243,9 @@ func TestGenerator(t *testing.T) { value: "foo", want: yamlPermission(t, ` header: - exactMatch: foo + safeRegexMatch: + googleRe2: {} + regex: (?i)foo name: :authority`), }, {
pkg/bootstrap/testdata/all_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/auth_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/authsds_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/default_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/metrics_no_statsd_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/running_golden.json+1 −0 modified@@ -19,6 +19,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/runningsds_golden.json+1 −0 modified@@ -19,6 +19,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/stats_inclusion_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/tracing_datadog_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/tracing_lightstep_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/tracing_opencensusagent_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/tracing_stackdriver_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/tracing_tls_custom_sni_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/tracing_tls_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/tracing_zipkin_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
pkg/bootstrap/testdata/xdsproxy_golden.json+1 −0 modified@@ -14,6 +14,7 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, "envoy.reloadable_features.new_tcp_connection_pool": false }
tests/integration/pilot/common/routing.go+1 −1 modified@@ -323,7 +323,7 @@ spec: echo.ValidatorFunc( func(response echoclient.ParsedResponses, _ error) error { return response.Check(func(_ int, response *echoclient.ParsedResponse) error { - return ExpectString(response.URL, "/new/path?key=value#hash", "URL") + return ExpectString(response.URL, "/new/path?key=value", "URL") }) })), },
tests/integration/security/authorization_test.go+45 −0 modified@@ -539,6 +539,51 @@ func TestAuthorization_IngressGateway(t *testing.T) { IP string WantCode int }{ + { + Name: "case-insensitive-deny deny.company.com", + Host: "deny.company.com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny DENY.COMPANY.COM", + Host: "DENY.COMPANY.COM", + WantCode: 403, + }, + { + Name: "case-insensitive-deny Deny.Company.Com", + Host: "Deny.Company.Com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny deny.suffix.company.com", + Host: "deny.suffix.company.com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny DENY.SUFFIX.COMPANY.COM", + Host: "DENY.SUFFIX.COMPANY.COM", + WantCode: 403, + }, + { + Name: "case-insensitive-deny Deny.Suffix.Company.Com", + Host: "Deny.Suffix.Company.Com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny prefix.company.com", + Host: "prefix.company.com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny PREFIX.COMPANY.COM", + Host: "PREFIX.COMPANY.COM", + WantCode: 403, + }, + { + Name: "case-insensitive-deny Prefix.Company.Com", + Host: "Prefix.Company.Com", + WantCode: 403, + }, { Name: "allow www.company.com", Host: "www.company.com",
tests/integration/security/testdata/authz/v1beta1-ingress-gateway.yaml.tmpl+4 −1 modified@@ -1,7 +1,7 @@ # The following policy denies access to "internal.company.com" and path "/private", # denies access from 172.17.72.46 or 192.168.4.0/23 to "remoteipblocks.company.com", # denies access from anything but 172.23.240.0/22 to "notremoteipblocks.company.com", -# and denies access to "remoteipattr.company.com" when the remote ip is 10.242.5.7 or +# and denies access to "remoteipattr.company.com" when the remote ip is 10.242.5.7 or # in the network 10.124.99.0/24. apiVersion: security.istio.io/v1beta1 @@ -15,6 +15,9 @@ spec: matchLabels: app: istio-ingressgateway rules: + - to: + - operation: + hosts: ["deny.company.com", "*.suffix.company.com", "prefix.company.*"] - to: - operation: hosts: ["internal.company.com"]
tools/packaging/common/envoy_bootstrap.json+4 −0 modified@@ -29,7 +29,11 @@ "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": true, "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": false, "re2.max_program_size.error_level": 1024, + "envoy.reloadable_features.http_reject_path_with_fragment": false, "envoy.reloadable_features.treat_host_like_authority": false, + {{- if eq (or .config.ProxyMetadata.HTTP_STRIP_FRAGMENT_FROM_PATH_UNSAFE_IF_DISABLED "") "false" }} + "envoy.reloadable_features.http_strip_fragment_from_path_unsafe_if_disabled": false, + {{- end }} "envoy.reloadable_features.new_tcp_connection_pool": false } },
76ed51413dddrelease 1.10: use case-insensitive match for host field in authz (#74)
9 files changed · +215 −19
pilot/pkg/security/authz/builder/testdata/http/allow-full-rule-out.yaml+18 −6 modified@@ -10,29 +10,41 @@ typedConfig: - orRules: rules: - header: - exactMatch: exact.com name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)exact\.com - header: name: :authority - suffixMatch: .suffix.com + safeRegexMatch: + googleRe2: {} + regex: (?i).*\.suffix\.com - header: name: :authority - prefixMatch: prefix. + safeRegexMatch: + googleRe2: {} + regex: (?i)prefix\..* - header: name: :authority presentMatch: true - notRule: orRules: rules: - header: - exactMatch: not-exact.com name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)not-exact\.com - header: name: :authority - suffixMatch: .not-suffix.com + safeRegexMatch: + googleRe2: {} + regex: (?i).*\.not-suffix\.com - header: name: :authority - prefixMatch: not-prefix. + safeRegexMatch: + googleRe2: {} + regex: (?i)not-prefix\..* - header: name: :authority presentMatch: true
pilot/pkg/security/authz/builder/testdata/http/multiple-policies-out.yaml+6 −2 modified@@ -42,11 +42,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: google.com name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)google\.com - header: - exactMatch: httpbin.org name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)httpbin\.org principals: - andIds: ids:
pilot/pkg/security/authz/builder/testdata/http/single-policy-out.yaml+24 −8 modified@@ -10,11 +10,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[0]-to[0]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[0\]-host\[1\] - header: - exactMatch: rule[0]-to[0]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[0\]-host\[2\] - orRules: rules: - header: @@ -48,11 +52,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[0]-to[1]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[1\]-host\[1\] - header: - exactMatch: rule[0]-to[1]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[0\]-to\[1\]-host\[2\] - orRules: rules: - header: @@ -270,11 +278,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[1]-to[0]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[0\]-host\[1\] - header: - exactMatch: rule[1]-to[0]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[0\]-host\[2\] - orRules: rules: - header: @@ -300,11 +312,15 @@ typedConfig: - orRules: rules: - header: - exactMatch: rule[1]-to[1]-host[1] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[1\]-host\[1\] - header: - exactMatch: rule[1]-to[1]-host[2] name: :authority + safeRegexMatch: + googleRe2: {} + regex: (?i)rule\[1\]-to\[1\]-host\[2\] - orRules: rules: - header:
pilot/pkg/security/authz/matcher/header.go+31 −0 modified@@ -15,6 +15,7 @@ package matcher import ( + "regexp" "strings" routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" @@ -55,6 +56,36 @@ func HeaderMatcher(k, v string) *routepb.HeaderMatcher { } } +// HostMatcher creates a host matcher for a host. +func HostMatcher(k, v string) *routepb.HeaderMatcher { + var regex string + if v == "*" { + return &routepb.HeaderMatcher{ + Name: k, + HeaderMatchSpecifier: &routepb.HeaderMatcher_PresentMatch{ + PresentMatch: true, + }, + } + } else if strings.HasPrefix(v, "*") { + regex = `.*` + regexp.QuoteMeta(v[1:]) + } else if strings.HasSuffix(v, "*") { + regex = regexp.QuoteMeta(v[:len(v)-1]) + `.*` + } else { + regex = regexp.QuoteMeta(v) + } + return &routepb.HeaderMatcher{ + Name: k, + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i)` + regex, + }, + }, + } +} + // PathMatcher creates a path matcher for a path. func PathMatcher(path string) *matcherpb.PathMatcher { return &matcherpb.PathMatcher{
pilot/pkg/security/authz/matcher/header_test.go+83 −0 modified@@ -15,6 +15,7 @@ package matcher import ( + "regexp" "testing" routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" @@ -62,6 +63,88 @@ func TestHeaderMatcher(t *testing.T) { } } +func TestHostMatcher(t *testing.T) { + testCases := []struct { + Name string + K string + V string + Expect *routepb.HeaderMatcher + }{ + { + Name: "present match", + K: ":authority", + V: "*", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_PresentMatch{PresentMatch: true}, + }, + }, + { + Name: "prefix match", + K: ":authority", + V: "*.example.com", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i).*\.example\.com`, + }, + }, + }, + }, + { + Name: "suffix match", + K: ":authority", + V: "example.*", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i)example\..*`, + }, + }, + }, + }, + { + Name: "exact match", + K: ":authority", + V: "example.com", + Expect: &routepb.HeaderMatcher{ + Name: ":authority", + HeaderMatchSpecifier: &routepb.HeaderMatcher_SafeRegexMatch{ + SafeRegexMatch: &matcherpb.RegexMatcher{ + EngineType: &matcherpb.RegexMatcher_GoogleRe2{ + GoogleRe2: &matcherpb.RegexMatcher_GoogleRE2{}, + }, + Regex: `(?i)example\.com`, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + actual := HostMatcher(tc.K, tc.V) + if re := actual.GetSafeRegexMatch().GetRegex(); re != "" { + _, err := regexp.Compile(re) + if err != nil { + t.Errorf("failed to compile regex %s: %v", re, err) + } + } + if !cmp.Equal(tc.Expect, actual, protocmp.Transform()) { + t.Errorf("expecting %v, but got %v", tc.Expect, actual) + } + }) + } +} + func TestPathMatcher(t *testing.T) { testCases := []struct { Name string
pilot/pkg/security/authz/model/generator.go+1 −1 modified@@ -232,7 +232,7 @@ func (hostGenerator) permission(key, value string, forTCP bool) (*rbacpb.Permiss return nil, fmt.Errorf("%q is HTTP only", key) } - m := matcher.HeaderMatcher(hostHeader, value) + m := matcher.HostMatcher(hostHeader, value) return permissionHeader(m), nil }
pilot/pkg/security/authz/model/generator_test.go+3 −1 modified@@ -251,7 +251,9 @@ func TestGenerator(t *testing.T) { value: "foo", want: yamlPermission(t, ` header: - exactMatch: foo + safeRegexMatch: + googleRe2: {} + regex: (?i)foo name: :authority`), }, {
tests/integration/security/authorization_test.go+45 −0 modified@@ -517,6 +517,51 @@ func TestAuthorization_IngressGateway(t *testing.T) { IP string WantCode int }{ + { + Name: "case-insensitive-deny deny.company.com", + Host: "deny.company.com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny DENY.COMPANY.COM", + Host: "DENY.COMPANY.COM", + WantCode: 403, + }, + { + Name: "case-insensitive-deny Deny.Company.Com", + Host: "Deny.Company.Com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny deny.suffix.company.com", + Host: "deny.suffix.company.com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny DENY.SUFFIX.COMPANY.COM", + Host: "DENY.SUFFIX.COMPANY.COM", + WantCode: 403, + }, + { + Name: "case-insensitive-deny Deny.Suffix.Company.Com", + Host: "Deny.Suffix.Company.Com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny prefix.company.com", + Host: "prefix.company.com", + WantCode: 403, + }, + { + Name: "case-insensitive-deny PREFIX.COMPANY.COM", + Host: "PREFIX.COMPANY.COM", + WantCode: 403, + }, + { + Name: "case-insensitive-deny Prefix.Company.Com", + Host: "Prefix.Company.Com", + WantCode: 403, + }, { Name: "allow www.company.com", Host: "www.company.com",
tests/integration/security/testdata/authz/v1beta1-ingress-gateway.yaml.tmpl+4 −1 modified@@ -1,7 +1,7 @@ # The following policy denies access to "internal.company.com" and path "/private", # denies access from 172.17.72.46 or 192.168.4.0/23 to "remoteipblocks.company.com", # denies access from anything but 172.23.240.0/22 to "notremoteipblocks.company.com", -# and denies access to "remoteipattr.company.com" when the remote ip is 10.242.5.7 or +# and denies access to "remoteipattr.company.com" when the remote ip is 10.242.5.7 or # in the network 10.124.99.0/24. apiVersion: security.istio.io/v1beta1 @@ -15,6 +15,9 @@ spec: matchLabels: app: istio-ingressgateway rules: + - to: + - operation: + hosts: ["deny.company.com", "*.suffix.company.com", "prefix.company.*"] - to: - operation: hosts: ["internal.company.com"]
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-7774-7vr3-cc8jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-39155ghsaADVISORY
- datatracker.ietf.org/doc/html/rfc4343ghsax_refsource_MISCWEB
- github.com/istio/istio/commit/084b417a486dbe9b9024d4812877016a484572b1ghsaWEB
- github.com/istio/istio/commit/76ed51413ddd2a7fa253a368ab20a9cec5fb1cbeghsaWEB
- github.com/istio/istio/commit/90b00bdf891e6c770cb3235c14a9b1fda96cc7c5ghsaWEB
- github.com/istio/istio/security/advisories/GHSA-7774-7vr3-cc8jghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.