VYPR
High severityNVD Advisory· Published Aug 24, 2021· Updated Aug 4, 2024

Authorization Policy Bypass Due to Case Insensitive Host Comparison

CVE-2021-39155

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.

PackageAffected versionsPatched versions
istio.io/istioGo
< 1.9.81.9.8
istio.io/istioGo
>= 1.10.0, < 1.10.41.10.4
istio.io/istioGo
>= 1.11.0, < 1.11.11.11.1

Affected products

1

Patches

3
084b417a486d

[1.9] Security fixes (#80)

https://github.com/istio/istioOliver LiuAug 10, 2021via ghsa
26 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
                   }
               },
    
90b00bdf891e

[1.11] Security fixes (#77)

https://github.com/istio/istioOliver LiuAug 10, 2021via ghsa
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
                   }
               },
    
76ed51413ddd

release 1.10: use case-insensitive match for host field in authz (#74)

https://github.com/istio/istioYangmin ZhuAug 6, 2021via ghsa
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

News mentions

0

No linked articles in our index yet.