blob: f653a49d5867b253e83d5fed9b63850db6c1c68d [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package route
import (
"reflect"
"testing"
)
import (
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
xdstype "github.com/envoyproxy/go-control-plane/envoy/type/v3"
wrappers "google.golang.org/protobuf/types/known/wrapperspb"
networking "istio.io/api/networking/v1alpha3"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/util"
authzmatcher "github.com/apache/dubbo-go-pixiu/pilot/pkg/security/authz/matcher"
authz "github.com/apache/dubbo-go-pixiu/pilot/pkg/security/authz/model"
"github.com/apache/dubbo-go-pixiu/pkg/config/labels"
)
func TestIsCatchAllMatch(t *testing.T) {
cases := []struct {
name string
match *networking.HTTPMatchRequest
want bool
}{
{
name: "catch all prefix",
match: &networking.HTTPMatchRequest{
Name: "catch-all",
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{
Prefix: "/",
},
},
},
want: true,
},
{
name: "specific prefix match",
match: &networking.HTTPMatchRequest{
Name: "specific match",
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{
Prefix: "/a",
},
},
},
want: false,
},
{
name: "uri regex catch all",
match: &networking.HTTPMatchRequest{
Name: "regex-catch-all",
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{
Regex: "*",
},
},
},
want: true,
},
{
name: "uri regex with headers",
match: &networking.HTTPMatchRequest{
Name: "regex with headers",
Headers: map[string]*networking.StringMatch{
"Authentication": {
MatchType: &networking.StringMatch_Regex{
Regex: "Bearer .+?\\..+?\\..+?",
},
},
},
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{
Regex: "*",
},
},
},
want: false,
},
{
name: "uri regex with query params",
match: &networking.HTTPMatchRequest{
Name: "regex with query params",
QueryParams: map[string]*networking.StringMatch{
"Authentication": {
MatchType: &networking.StringMatch_Regex{
Regex: "Bearer .+?\\..+?\\..+?",
},
},
},
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{
Regex: "*",
},
},
},
want: false,
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
match := isCatchAllMatch(tt.match)
if match != tt.want {
t.Errorf("Unexpected catchAllMatch want %v, got %v", tt.want, match)
}
})
}
}
func TestIsCatchAllRoute(t *testing.T) {
cases := []struct {
name string
route *route.Route
want bool
}{
{
name: "catch all prefix",
route: &route.Route{
Name: "catch-all",
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{
Prefix: "/",
},
},
},
want: true,
},
{
name: "catch all prefix >= 1.14",
route: &route.Route{
Name: "catch-all",
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_PathSeparatedPrefix{
PathSeparatedPrefix: "/",
},
},
},
want: true,
},
{
name: "catch all regex",
route: &route.Route{
Name: "catch-all",
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: &matcher.RegexMatcher_GoogleRe2{GoogleRe2: &matcher.RegexMatcher_GoogleRE2{}},
Regex: "*",
},
},
},
},
want: true,
},
{
name: "catch all prefix with headers",
route: &route.Route{
Name: "catch-all",
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{
Prefix: "/",
},
Headers: []*route.HeaderMatcher{
{
Name: "Authentication",
HeaderMatchSpecifier: &route.HeaderMatcher_ExactMatch{
ExactMatch: "test",
},
},
},
},
},
want: false,
},
{
name: "uri regex with headers",
route: &route.Route{
Name: "non-catch-all",
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
// nolint: staticcheck
EngineType: &matcher.RegexMatcher_GoogleRe2{},
Regex: "*",
},
},
Headers: []*route.HeaderMatcher{
{
Name: "Authentication",
HeaderMatchSpecifier: &route.HeaderMatcher_StringMatch{
StringMatch: &matcher.StringMatcher{
MatchPattern: &matcher.StringMatcher_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: util.RegexEngine,
Regex: "*",
},
},
},
},
},
},
},
},
want: false,
},
{
name: "uri regex with query params",
route: &route.Route{
Name: "non-catch-all",
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
// nolint: staticcheck
EngineType: &matcher.RegexMatcher_GoogleRe2{},
Regex: "*",
},
},
QueryParameters: []*route.QueryParameterMatcher{
{
Name: "Authentication",
QueryParameterMatchSpecifier: &route.QueryParameterMatcher_PresentMatch{
PresentMatch: true,
},
},
},
},
},
want: false,
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
catchall := isCatchAllRoute(tt.route)
if catchall != tt.want {
t.Errorf("Unexpected catchAllMatch want %v, got %v", tt.want, catchall)
}
})
}
}
func TestCatchAllMatch(t *testing.T) {
cases := []struct {
name string
http *networking.HTTPMatchRequest
match bool
}{
{
name: "catch all virtual service",
http: &networking.HTTPMatchRequest{
Name: "catch-all",
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{
Prefix: "/",
},
},
},
match: true,
},
{
name: "uri regex",
http: &networking.HTTPMatchRequest{
Name: "regex-catch-all",
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{
Regex: "*",
},
},
},
match: true,
},
{
name: "uri regex with query params",
http: &networking.HTTPMatchRequest{
Name: "regex-catch-all",
QueryParams: map[string]*networking.StringMatch{
"Authentication": {
MatchType: &networking.StringMatch_Regex{
Regex: "Bearer .+?\\..+?\\..+?",
},
},
},
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{
Regex: "*",
},
},
},
match: false,
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
match := isCatchAllMatch(tt.http)
if tt.match != match {
t.Errorf("Expected a match=%v but got match=%v", tt.match, match)
}
})
}
}
func TestTranslateCORSPolicy(t *testing.T) {
corsPolicy := &networking.CorsPolicy{
AllowOrigins: []*networking.StringMatch{
{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
{MatchType: &networking.StringMatch_Prefix{Prefix: "prefix"}},
{MatchType: &networking.StringMatch_Regex{Regex: "regex"}},
},
}
expectedCorsPolicy := &route.CorsPolicy{
AllowOriginStringMatch: []*matcher.StringMatcher{
{MatchPattern: &matcher.StringMatcher_Exact{Exact: "exact"}},
{MatchPattern: &matcher.StringMatcher_Prefix{Prefix: "prefix"}},
{
MatchPattern: &matcher.StringMatcher_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: util.RegexEngine,
Regex: "regex",
},
},
},
},
EnabledSpecifier: &route.CorsPolicy_FilterEnabled{
FilterEnabled: &core.RuntimeFractionalPercent{
DefaultValue: &xdstype.FractionalPercent{
Numerator: 100,
Denominator: xdstype.FractionalPercent_HUNDRED,
},
},
},
}
if got := translateCORSPolicy(corsPolicy); !reflect.DeepEqual(got, expectedCorsPolicy) {
t.Errorf("translateCORSPolicy() = \n%v, want \n%v", got, expectedCorsPolicy)
}
}
func TestMirrorPercent(t *testing.T) {
cases := []struct {
name string
route *networking.HTTPRoute
want *core.RuntimeFractionalPercent
}{
{
name: "zero mirror percent",
route: &networking.HTTPRoute{
Mirror: &networking.Destination{},
MirrorPercent: &wrappers.UInt32Value{Value: 0.0},
},
want: nil,
},
{
name: "mirror with no value given",
route: &networking.HTTPRoute{
Mirror: &networking.Destination{},
},
want: &core.RuntimeFractionalPercent{
DefaultValue: &xdstype.FractionalPercent{
Numerator: 100,
Denominator: xdstype.FractionalPercent_HUNDRED,
},
},
},
{
name: "mirror with actual percent",
route: &networking.HTTPRoute{
Mirror: &networking.Destination{},
MirrorPercent: &wrappers.UInt32Value{Value: 50},
},
want: &core.RuntimeFractionalPercent{
DefaultValue: &xdstype.FractionalPercent{
Numerator: 50,
Denominator: xdstype.FractionalPercent_HUNDRED,
},
},
},
{
name: "zero mirror percentage",
route: &networking.HTTPRoute{
Mirror: &networking.Destination{},
MirrorPercentage: &networking.Percent{Value: 0.0},
},
want: nil,
},
{
name: "mirrorpercentage with actual percent",
route: &networking.HTTPRoute{
Mirror: &networking.Destination{},
MirrorPercentage: &networking.Percent{Value: 50.0},
},
want: &core.RuntimeFractionalPercent{
DefaultValue: &xdstype.FractionalPercent{
Numerator: 500000,
Denominator: xdstype.FractionalPercent_MILLION,
},
},
},
{
name: "mirrorpercentage takes precedence when both are given",
route: &networking.HTTPRoute{
Mirror: &networking.Destination{},
MirrorPercent: &wrappers.UInt32Value{Value: 40},
MirrorPercentage: &networking.Percent{Value: 50.0},
},
want: &core.RuntimeFractionalPercent{
DefaultValue: &xdstype.FractionalPercent{
Numerator: 500000,
Denominator: xdstype.FractionalPercent_MILLION,
},
},
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
mp := mirrorPercent(tt.route)
if !reflect.DeepEqual(mp, tt.want) {
t.Errorf("Unexpected mirro percent want %v, got %v", tt.want, mp)
}
})
}
}
func TestSourceMatchHTTP(t *testing.T) {
type args struct {
match *networking.HTTPMatchRequest
proxyLabels labels.Instance
gatewayNames map[string]bool
proxyNamespace string
}
tests := []struct {
name string
args args
want bool
}{
{
"source namespace match",
args{
match: &networking.HTTPMatchRequest{
SourceNamespace: "foo",
},
proxyNamespace: "foo",
},
true,
},
{
"source namespace not match",
args{
match: &networking.HTTPMatchRequest{
SourceNamespace: "foo",
},
proxyNamespace: "bar",
},
false,
},
{
"source namespace not match when empty",
args{
match: &networking.HTTPMatchRequest{
SourceNamespace: "foo",
},
proxyNamespace: "",
},
false,
},
{
"source namespace any",
args{
match: &networking.HTTPMatchRequest{},
proxyNamespace: "bar",
},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := sourceMatchHTTP(tt.args.match, tt.args.proxyLabels, tt.args.gatewayNames, tt.args.proxyNamespace); got != tt.want {
t.Errorf("sourceMatchHTTP() = %v, want %v", got, tt.want)
}
})
}
}
func TestTranslateMetadataMatch(t *testing.T) {
cases := []struct {
name string
in *networking.StringMatch
want *matcher.MetadataMatcher
}{
{
name: "@request.auth.claims",
},
{
name: "@request.auth.claims-",
},
{
name: "request.auth.claims.",
},
{
name: "@request.auth.claims-",
},
{
name: "@request.auth.claims-abc",
},
{
name: "x-some-other-header",
},
{
name: "@request.auth.claims.key1",
in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
want: authz.MetadataMatcherForJWTClaims([]string{"key1"}, authzmatcher.StringMatcher("exact")),
},
{
name: "@request.auth.claims.key1.KEY2",
in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
want: authz.MetadataMatcherForJWTClaims([]string{"key1", "KEY2"}, authzmatcher.StringMatcher("exact")),
},
{
name: "@request.auth.claims.key1-key2",
in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
want: authz.MetadataMatcherForJWTClaims([]string{"key1-key2"}, authzmatcher.StringMatcher("exact")),
},
{
name: "@request.auth.claims.prefix",
in: &networking.StringMatch{MatchType: &networking.StringMatch_Prefix{Prefix: "prefix"}},
want: authz.MetadataMatcherForJWTClaims([]string{"prefix"}, authzmatcher.StringMatcher("prefix*")),
},
{
name: "@request.auth.claims.regex",
in: &networking.StringMatch{MatchType: &networking.StringMatch_Regex{Regex: ".+?\\..+?\\..+?"}},
want: authz.MetadataMatcherForJWTClaims([]string{"regex"}, authzmatcher.StringMatcherRegex(".+?\\..+?\\..+?")),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := translateMetadataMatch(tc.name, tc.in)
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("Unexpected metadata matcher want %v, got %v", tc.want, got)
}
})
}
}