gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/internal/xds/rbac/rbac_engine_test.go (about)

     1  /*
     2   * Copyright 2021 gRPC authors.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package rbac
    18  
    19  import (
    20  	"context"
    21  	"crypto/x509/pkix"
    22  	"net"
    23  	"net/url"
    24  	"testing"
    25  
    26  	"gitee.com/ks-custle/core-gm/x509"
    27  
    28  	tls "gitee.com/ks-custle/core-gm/gmtls"
    29  
    30  	v3corepb "gitee.com/ks-custle/core-gm/go-control-plane/envoy/config/core/v3"
    31  	v3rbacpb "gitee.com/ks-custle/core-gm/go-control-plane/envoy/config/rbac/v3"
    32  	v3routepb "gitee.com/ks-custle/core-gm/go-control-plane/envoy/config/route/v3"
    33  	v3matcherpb "gitee.com/ks-custle/core-gm/go-control-plane/envoy/type/matcher/v3"
    34  	v3typepb "gitee.com/ks-custle/core-gm/go-control-plane/envoy/type/v3"
    35  	grpc "gitee.com/ks-custle/core-gm/grpc"
    36  	"gitee.com/ks-custle/core-gm/grpc/codes"
    37  	"gitee.com/ks-custle/core-gm/grpc/credentials"
    38  	"gitee.com/ks-custle/core-gm/grpc/internal/grpctest"
    39  	"gitee.com/ks-custle/core-gm/grpc/metadata"
    40  	"gitee.com/ks-custle/core-gm/grpc/peer"
    41  	"gitee.com/ks-custle/core-gm/grpc/status"
    42  	wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
    43  )
    44  
    45  type s struct {
    46  	grpctest.Tester
    47  }
    48  
    49  func Test(t *testing.T) {
    50  	grpctest.RunSubTests(t, s{})
    51  }
    52  
    53  type addr struct {
    54  	ipAddress string
    55  }
    56  
    57  func (addr) Network() string   { return "" }
    58  func (a *addr) String() string { return a.ipAddress }
    59  
    60  // TestNewChainEngine tests the construction of the ChainEngine. Due to some
    61  // types of RBAC configuration being logically wrong and returning an error
    62  // rather than successfully constructing the RBAC Engine, this test tests both
    63  // RBAC Configurations deemed successful and also RBAC Configurations that will
    64  // raise errors.
    65  func (s) TestNewChainEngine(t *testing.T) {
    66  	tests := []struct {
    67  		name     string
    68  		policies []*v3rbacpb.RBAC
    69  		wantErr  bool
    70  	}{
    71  		{
    72  			name: "SuccessCaseAnyMatchSingular",
    73  			policies: []*v3rbacpb.RBAC{
    74  				{
    75  					Action: v3rbacpb.RBAC_ALLOW,
    76  					Policies: map[string]*v3rbacpb.Policy{
    77  						"anyone": {
    78  							Permissions: []*v3rbacpb.Permission{
    79  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
    80  							},
    81  							Principals: []*v3rbacpb.Principal{
    82  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
    83  							},
    84  						},
    85  					},
    86  				},
    87  			},
    88  		},
    89  		{
    90  			name: "SuccessCaseAnyMatchMultiple",
    91  			policies: []*v3rbacpb.RBAC{
    92  				{
    93  					Action: v3rbacpb.RBAC_ALLOW,
    94  					Policies: map[string]*v3rbacpb.Policy{
    95  						"anyone": {
    96  							Permissions: []*v3rbacpb.Permission{
    97  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
    98  							},
    99  							Principals: []*v3rbacpb.Principal{
   100  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   101  							},
   102  						},
   103  					},
   104  				},
   105  				{
   106  					Action: v3rbacpb.RBAC_DENY,
   107  					Policies: map[string]*v3rbacpb.Policy{
   108  						"anyone": {
   109  							Permissions: []*v3rbacpb.Permission{
   110  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   111  							},
   112  							Principals: []*v3rbacpb.Principal{
   113  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   114  							},
   115  						},
   116  					},
   117  				},
   118  			},
   119  		},
   120  		{
   121  			name: "SuccessCaseSimplePolicySingular",
   122  			policies: []*v3rbacpb.RBAC{
   123  				{
   124  					Action: v3rbacpb.RBAC_ALLOW,
   125  					Policies: map[string]*v3rbacpb.Policy{
   126  						"localhost-fan": {
   127  							Permissions: []*v3rbacpb.Permission{
   128  								{Rule: &v3rbacpb.Permission_DestinationPort{DestinationPort: 8080}},
   129  								{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}},
   130  							},
   131  							Principals: []*v3rbacpb.Principal{
   132  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   133  							},
   134  						},
   135  					},
   136  				},
   137  			},
   138  		},
   139  		// SuccessCaseSimplePolicyTwoPolicies tests the construction of the
   140  		// chained engines in the case where there are two policies in a list,
   141  		// one with an allow policy and one with a deny policy. A situation
   142  		// where two policies (allow and deny) is a very common use case for
   143  		// this API, and should successfully build.
   144  		{
   145  			name: "SuccessCaseSimplePolicyTwoPolicies",
   146  			policies: []*v3rbacpb.RBAC{
   147  				{
   148  					Action: v3rbacpb.RBAC_ALLOW,
   149  					Policies: map[string]*v3rbacpb.Policy{
   150  						"localhost-fan": {
   151  							Permissions: []*v3rbacpb.Permission{
   152  								{Rule: &v3rbacpb.Permission_DestinationPort{DestinationPort: 8080}},
   153  								{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}},
   154  							},
   155  							Principals: []*v3rbacpb.Principal{
   156  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   157  							},
   158  						},
   159  					},
   160  				},
   161  				{
   162  					Action: v3rbacpb.RBAC_DENY,
   163  					Policies: map[string]*v3rbacpb.Policy{
   164  						"localhost-fan": {
   165  							Permissions: []*v3rbacpb.Permission{
   166  								{Rule: &v3rbacpb.Permission_DestinationPort{DestinationPort: 8080}},
   167  								{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}},
   168  							},
   169  							Principals: []*v3rbacpb.Principal{
   170  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   171  							},
   172  						},
   173  					},
   174  				},
   175  			},
   176  		},
   177  		{
   178  			name: "SuccessCaseEnvoyExampleSingular",
   179  			policies: []*v3rbacpb.RBAC{
   180  				{
   181  					Action: v3rbacpb.RBAC_ALLOW,
   182  					Policies: map[string]*v3rbacpb.Policy{
   183  						"service-admin": {
   184  							Permissions: []*v3rbacpb.Permission{
   185  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   186  							},
   187  							Principals: []*v3rbacpb.Principal{
   188  								{Identifier: &v3rbacpb.Principal_Authenticated_{Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "cluster.local/ns/default/sa/admin"}}}}},
   189  								{Identifier: &v3rbacpb.Principal_Authenticated_{Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "cluster.local/ns/default/sa/superuser"}}}}},
   190  							},
   191  						},
   192  						"product-viewer": {
   193  							Permissions: []*v3rbacpb.Permission{
   194  								{Rule: &v3rbacpb.Permission_AndRules{AndRules: &v3rbacpb.Permission_Set{
   195  									Rules: []*v3rbacpb.Permission{
   196  										{Rule: &v3rbacpb.Permission_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "GET"}}}},
   197  										{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/products"}}}}}},
   198  										{Rule: &v3rbacpb.Permission_OrRules{OrRules: &v3rbacpb.Permission_Set{
   199  											Rules: []*v3rbacpb.Permission{
   200  												{Rule: &v3rbacpb.Permission_DestinationPort{DestinationPort: 80}},
   201  												{Rule: &v3rbacpb.Permission_DestinationPort{DestinationPort: 443}},
   202  											},
   203  										},
   204  										},
   205  										},
   206  									},
   207  								},
   208  								},
   209  								},
   210  							},
   211  							Principals: []*v3rbacpb.Principal{
   212  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   213  							},
   214  						},
   215  					},
   216  				},
   217  			},
   218  		},
   219  		{
   220  			name: "SourceIpMatcherSuccessSingular",
   221  			policies: []*v3rbacpb.RBAC{
   222  				{
   223  					Action: v3rbacpb.RBAC_ALLOW,
   224  					Policies: map[string]*v3rbacpb.Policy{
   225  						"certain-source-ip": {
   226  							Permissions: []*v3rbacpb.Permission{
   227  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   228  							},
   229  							Principals: []*v3rbacpb.Principal{
   230  								{Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}},
   231  							},
   232  						},
   233  					},
   234  				},
   235  			},
   236  		},
   237  		{
   238  			name: "SourceIpMatcherFailureSingular",
   239  			policies: []*v3rbacpb.RBAC{
   240  				{
   241  					Action: v3rbacpb.RBAC_ALLOW,
   242  					Policies: map[string]*v3rbacpb.Policy{
   243  						"certain-source-ip": {
   244  							Permissions: []*v3rbacpb.Permission{
   245  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   246  							},
   247  							Principals: []*v3rbacpb.Principal{
   248  								{Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "not a correct address", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}},
   249  							},
   250  						},
   251  					},
   252  				},
   253  			},
   254  			wantErr: true,
   255  		},
   256  		{
   257  			name: "DestinationIpMatcherSuccess",
   258  			policies: []*v3rbacpb.RBAC{
   259  				{
   260  					Action: v3rbacpb.RBAC_ALLOW,
   261  					Policies: map[string]*v3rbacpb.Policy{
   262  						"certain-destination-ip": {
   263  							Permissions: []*v3rbacpb.Permission{
   264  								{Rule: &v3rbacpb.Permission_DestinationIp{DestinationIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}},
   265  							},
   266  							Principals: []*v3rbacpb.Principal{
   267  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   268  							},
   269  						},
   270  					},
   271  				},
   272  			},
   273  		},
   274  		{
   275  			name: "DestinationIpMatcherFailure",
   276  			policies: []*v3rbacpb.RBAC{
   277  				{
   278  					Action: v3rbacpb.RBAC_ALLOW,
   279  					Policies: map[string]*v3rbacpb.Policy{
   280  						"certain-destination-ip": {
   281  							Permissions: []*v3rbacpb.Permission{
   282  								{Rule: &v3rbacpb.Permission_DestinationIp{DestinationIp: &v3corepb.CidrRange{AddressPrefix: "not a correct address", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}},
   283  							},
   284  							Principals: []*v3rbacpb.Principal{
   285  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   286  							},
   287  						},
   288  					},
   289  				},
   290  			},
   291  			wantErr: true,
   292  		},
   293  		{
   294  			name: "MatcherToNotPolicy",
   295  			policies: []*v3rbacpb.RBAC{
   296  				{
   297  					Action: v3rbacpb.RBAC_ALLOW,
   298  					Policies: map[string]*v3rbacpb.Policy{
   299  						"not-secret-content": {
   300  							Permissions: []*v3rbacpb.Permission{
   301  								{Rule: &v3rbacpb.Permission_NotRule{NotRule: &v3rbacpb.Permission{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/secret-content"}}}}}}}},
   302  							},
   303  							Principals: []*v3rbacpb.Principal{
   304  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   305  							},
   306  						},
   307  					},
   308  				},
   309  			},
   310  		},
   311  		{
   312  			name: "MatcherToNotPrinicipal",
   313  			policies: []*v3rbacpb.RBAC{
   314  				{
   315  					Action: v3rbacpb.RBAC_ALLOW,
   316  					Policies: map[string]*v3rbacpb.Policy{
   317  						"not-from-certain-ip": {
   318  							Permissions: []*v3rbacpb.Permission{
   319  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   320  							},
   321  							Principals: []*v3rbacpb.Principal{
   322  								{Identifier: &v3rbacpb.Principal_NotId{NotId: &v3rbacpb.Principal{Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}}},
   323  							},
   324  						},
   325  					},
   326  				},
   327  			},
   328  		},
   329  		// PrinicpalProductViewer tests the construction of a chained engine
   330  		// with a policy that allows any downstream to send a GET request on a
   331  		// certain path.
   332  		{
   333  			name: "PrincipalProductViewer",
   334  			policies: []*v3rbacpb.RBAC{
   335  				{
   336  					Action: v3rbacpb.RBAC_ALLOW,
   337  					Policies: map[string]*v3rbacpb.Policy{
   338  						"product-viewer": {
   339  							Permissions: []*v3rbacpb.Permission{
   340  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   341  							},
   342  							Principals: []*v3rbacpb.Principal{
   343  								{
   344  									Identifier: &v3rbacpb.Principal_AndIds{AndIds: &v3rbacpb.Principal_Set{Ids: []*v3rbacpb.Principal{
   345  										{Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "GET"}}}},
   346  										{Identifier: &v3rbacpb.Principal_OrIds{OrIds: &v3rbacpb.Principal_Set{
   347  											Ids: []*v3rbacpb.Principal{
   348  												{Identifier: &v3rbacpb.Principal_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/books"}}}}}},
   349  												{Identifier: &v3rbacpb.Principal_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/cars"}}}}}},
   350  											},
   351  										}}},
   352  									}}},
   353  								},
   354  							},
   355  						},
   356  					},
   357  				},
   358  			},
   359  		},
   360  		// Certain Headers tests the construction of a chained engine with a
   361  		// policy that allows any downstream to send an HTTP request with
   362  		// certain headers.
   363  		{
   364  			name: "CertainHeaders",
   365  			policies: []*v3rbacpb.RBAC{
   366  				{
   367  					Policies: map[string]*v3rbacpb.Policy{
   368  						"certain-headers": {
   369  							Permissions: []*v3rbacpb.Permission{
   370  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   371  							},
   372  							Principals: []*v3rbacpb.Principal{
   373  								{
   374  									Identifier: &v3rbacpb.Principal_OrIds{OrIds: &v3rbacpb.Principal_Set{Ids: []*v3rbacpb.Principal{
   375  										{Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "GET"}}}},
   376  										{Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SafeRegexMatch{SafeRegexMatch: &v3matcherpb.RegexMatcher{Regex: "GET"}}}}},
   377  										{Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_RangeMatch{RangeMatch: &v3typepb.Int64Range{
   378  											Start: 0,
   379  											End:   64,
   380  										}}}}},
   381  										{Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PresentMatch{PresentMatch: true}}}},
   382  										{Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{PrefixMatch: "GET"}}}},
   383  										{Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SuffixMatch{SuffixMatch: "GET"}}}},
   384  										{Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ContainsMatch{ContainsMatch: "GET"}}}},
   385  									}}},
   386  								},
   387  							},
   388  						},
   389  					},
   390  				},
   391  			},
   392  		},
   393  		{
   394  			name: "LogAction",
   395  			policies: []*v3rbacpb.RBAC{
   396  				{
   397  					Action: v3rbacpb.RBAC_LOG,
   398  					Policies: map[string]*v3rbacpb.Policy{
   399  						"anyone": {
   400  							Permissions: []*v3rbacpb.Permission{
   401  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   402  							},
   403  							Principals: []*v3rbacpb.Principal{
   404  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   405  							},
   406  						},
   407  					},
   408  				},
   409  			},
   410  			wantErr: true,
   411  		},
   412  		{
   413  			name: "ActionNotSpecified",
   414  			policies: []*v3rbacpb.RBAC{
   415  				{
   416  					Policies: map[string]*v3rbacpb.Policy{
   417  						"anyone": {
   418  							Permissions: []*v3rbacpb.Permission{
   419  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   420  							},
   421  							Principals: []*v3rbacpb.Principal{
   422  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   423  							},
   424  						},
   425  					},
   426  				},
   427  			},
   428  		},
   429  	}
   430  	for _, test := range tests {
   431  		t.Run(test.name, func(t *testing.T) {
   432  			if _, err := NewChainEngine(test.policies); (err != nil) != test.wantErr {
   433  				t.Fatalf("NewChainEngine(%+v) returned err: %v, wantErr: %v", test.policies, err, test.wantErr)
   434  			}
   435  		})
   436  	}
   437  }
   438  
   439  // TestChainEngine tests the chain of RBAC Engines by configuring the chain of
   440  // engines in a certain way in different scenarios. After configuring the chain
   441  // of engines in a certain way, this test pings the chain of engines with
   442  // different types of data representing incoming RPC's (piped into a context),
   443  // and verifies that it works as expected.
   444  func (s) TestChainEngine(t *testing.T) {
   445  	defer func(gc func(ctx context.Context) net.Conn) {
   446  		getConnection = gc
   447  	}(getConnection)
   448  	tests := []struct {
   449  		name        string
   450  		rbacConfigs []*v3rbacpb.RBAC
   451  		rbacQueries []struct {
   452  			rpcData        *rpcData
   453  			wantStatusCode codes.Code
   454  		}
   455  	}{
   456  		// SuccessCaseAnyMatch tests a single RBAC Engine instantiated with
   457  		// a config with a policy with any rules for both permissions and
   458  		// principals, meaning that any data about incoming RPC's that the RBAC
   459  		// Engine is queried with should match that policy.
   460  		{
   461  			name: "SuccessCaseAnyMatch",
   462  			rbacConfigs: []*v3rbacpb.RBAC{
   463  				{
   464  					Policies: map[string]*v3rbacpb.Policy{
   465  						"anyone": {
   466  							Permissions: []*v3rbacpb.Permission{
   467  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   468  							},
   469  							Principals: []*v3rbacpb.Principal{
   470  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   471  							},
   472  						},
   473  					},
   474  				},
   475  			},
   476  			rbacQueries: []struct {
   477  				rpcData        *rpcData
   478  				wantStatusCode codes.Code
   479  			}{
   480  				{
   481  					rpcData: &rpcData{
   482  						fullMethod: "some method",
   483  						peerInfo: &peer.Peer{
   484  							Addr: &addr{ipAddress: "0.0.0.0"},
   485  						},
   486  					},
   487  					wantStatusCode: codes.OK,
   488  				},
   489  			},
   490  		},
   491  		// SuccessCaseSimplePolicy is a test that tests a single policy
   492  		// that only allows an rpc to proceed if the rpc is calling with a certain
   493  		// path.
   494  		{
   495  			name: "SuccessCaseSimplePolicy",
   496  			rbacConfigs: []*v3rbacpb.RBAC{
   497  				{
   498  					Policies: map[string]*v3rbacpb.Policy{
   499  						"localhost-fan": {
   500  							Permissions: []*v3rbacpb.Permission{
   501  								{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}},
   502  							},
   503  							Principals: []*v3rbacpb.Principal{
   504  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   505  							},
   506  						},
   507  					},
   508  				},
   509  			},
   510  			rbacQueries: []struct {
   511  				rpcData        *rpcData
   512  				wantStatusCode codes.Code
   513  			}{
   514  				// This RPC should match with the local host fan policy. Thus,
   515  				// this RPC should be allowed to proceed.
   516  				{
   517  					rpcData: &rpcData{
   518  						fullMethod: "localhost-fan-page",
   519  						peerInfo: &peer.Peer{
   520  							Addr: &addr{ipAddress: "0.0.0.0"},
   521  						},
   522  					},
   523  					wantStatusCode: codes.OK,
   524  				},
   525  
   526  				// This RPC shouldn't match with the local host fan policy. Thus,
   527  				// this rpc shouldn't be allowed to proceed.
   528  				{
   529  					rpcData: &rpcData{
   530  						peerInfo: &peer.Peer{
   531  							Addr: &addr{ipAddress: "0.0.0.0"},
   532  						},
   533  					},
   534  					wantStatusCode: codes.PermissionDenied,
   535  				},
   536  			},
   537  		},
   538  		// SuccessCaseEnvoyExample is a test based on the example provided
   539  		// in the EnvoyProxy docs. The RBAC Config contains two policies,
   540  		// service admin and product viewer, that provides an example of a real
   541  		// RBAC Config that might be configured for a given for a given backend
   542  		// service.
   543  		{
   544  			name: "SuccessCaseEnvoyExample",
   545  			rbacConfigs: []*v3rbacpb.RBAC{
   546  				{
   547  					Policies: map[string]*v3rbacpb.Policy{
   548  						"service-admin": {
   549  							Permissions: []*v3rbacpb.Permission{
   550  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   551  							},
   552  							Principals: []*v3rbacpb.Principal{
   553  								{Identifier: &v3rbacpb.Principal_Authenticated_{Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "//cluster.local/ns/default/sa/admin"}}}}},
   554  								{Identifier: &v3rbacpb.Principal_Authenticated_{Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "//cluster.local/ns/default/sa/superuser"}}}}},
   555  							},
   556  						},
   557  						"product-viewer": {
   558  							Permissions: []*v3rbacpb.Permission{
   559  								{
   560  									Rule: &v3rbacpb.Permission_AndRules{AndRules: &v3rbacpb.Permission_Set{
   561  										Rules: []*v3rbacpb.Permission{
   562  											{Rule: &v3rbacpb.Permission_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "GET"}}}},
   563  											{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/products"}}}}}},
   564  										},
   565  									},
   566  									},
   567  								},
   568  							},
   569  							Principals: []*v3rbacpb.Principal{
   570  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   571  							},
   572  						},
   573  					},
   574  				},
   575  			},
   576  			rbacQueries: []struct {
   577  				rpcData        *rpcData
   578  				wantStatusCode codes.Code
   579  			}{
   580  				// This incoming RPC Call should match with the service admin
   581  				// policy.
   582  				{
   583  					rpcData: &rpcData{
   584  						fullMethod: "some method",
   585  						peerInfo: &peer.Peer{
   586  							Addr: &addr{ipAddress: "0.0.0.0"},
   587  							AuthInfo: credentials.TLSInfo{
   588  								State: tls.ConnectionState{
   589  									PeerCertificates: []*x509.Certificate{
   590  										{
   591  											URIs: []*url.URL{
   592  												{
   593  													Host: "cluster.local",
   594  													Path: "/ns/default/sa/admin",
   595  												},
   596  											},
   597  										},
   598  									},
   599  								},
   600  							},
   601  						},
   602  					},
   603  					wantStatusCode: codes.OK,
   604  				},
   605  				// These incoming RPC calls should not match any policy.
   606  				{
   607  					rpcData: &rpcData{
   608  						peerInfo: &peer.Peer{
   609  							Addr: &addr{ipAddress: "0.0.0.0"},
   610  						},
   611  					},
   612  					wantStatusCode: codes.PermissionDenied,
   613  				},
   614  				{
   615  					rpcData: &rpcData{
   616  						fullMethod: "get-product-list",
   617  						peerInfo: &peer.Peer{
   618  							Addr: &addr{ipAddress: "0.0.0.0"},
   619  						},
   620  					},
   621  					wantStatusCode: codes.PermissionDenied,
   622  				},
   623  				{
   624  					rpcData: &rpcData{
   625  						peerInfo: &peer.Peer{
   626  							Addr: &addr{ipAddress: "0.0.0.0"},
   627  							AuthInfo: credentials.TLSInfo{
   628  								State: tls.ConnectionState{
   629  									PeerCertificates: []*x509.Certificate{
   630  										{
   631  											Subject: pkix.Name{
   632  												CommonName: "localhost",
   633  											},
   634  										},
   635  									},
   636  								},
   637  							},
   638  						},
   639  					},
   640  					wantStatusCode: codes.PermissionDenied,
   641  				},
   642  			},
   643  		},
   644  		{
   645  			name: "NotMatcher",
   646  			rbacConfigs: []*v3rbacpb.RBAC{
   647  				{
   648  					Policies: map[string]*v3rbacpb.Policy{
   649  						"not-secret-content": {
   650  							Permissions: []*v3rbacpb.Permission{
   651  								{
   652  									Rule: &v3rbacpb.Permission_NotRule{
   653  										NotRule: &v3rbacpb.Permission{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/secret-content"}}}}}},
   654  									},
   655  								},
   656  							},
   657  							Principals: []*v3rbacpb.Principal{
   658  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   659  							},
   660  						},
   661  					},
   662  				},
   663  			},
   664  			rbacQueries: []struct {
   665  				rpcData        *rpcData
   666  				wantStatusCode codes.Code
   667  			}{
   668  				// This incoming RPC Call should match with the not-secret-content policy.
   669  				{
   670  					rpcData: &rpcData{
   671  						fullMethod: "/regular-content",
   672  						peerInfo: &peer.Peer{
   673  							Addr: &addr{ipAddress: "0.0.0.0"},
   674  						},
   675  					},
   676  					wantStatusCode: codes.OK,
   677  				},
   678  				// This incoming RPC Call shouldn't match with the not-secret-content-policy.
   679  				{
   680  					rpcData: &rpcData{
   681  						fullMethod: "/secret-content",
   682  						peerInfo: &peer.Peer{
   683  							Addr: &addr{ipAddress: "0.0.0.0"},
   684  						},
   685  					},
   686  					wantStatusCode: codes.PermissionDenied,
   687  				},
   688  			},
   689  		},
   690  		{
   691  			name: "DirectRemoteIpMatcher",
   692  			rbacConfigs: []*v3rbacpb.RBAC{
   693  				{
   694  					Policies: map[string]*v3rbacpb.Policy{
   695  						"certain-direct-remote-ip": {
   696  							Permissions: []*v3rbacpb.Permission{
   697  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   698  							},
   699  							Principals: []*v3rbacpb.Principal{
   700  								{Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}},
   701  							},
   702  						},
   703  					},
   704  				},
   705  			},
   706  			rbacQueries: []struct {
   707  				rpcData        *rpcData
   708  				wantStatusCode codes.Code
   709  			}{
   710  				// This incoming RPC Call should match with the certain-direct-remote-ip policy.
   711  				{
   712  					rpcData: &rpcData{
   713  						peerInfo: &peer.Peer{
   714  							Addr: &addr{ipAddress: "0.0.0.0"},
   715  						},
   716  					},
   717  					wantStatusCode: codes.OK,
   718  				},
   719  				// This incoming RPC Call shouldn't match with the certain-direct-remote-ip policy.
   720  				{
   721  					rpcData: &rpcData{
   722  						peerInfo: &peer.Peer{
   723  							Addr: &addr{ipAddress: "10.0.0.0"},
   724  						},
   725  					},
   726  					wantStatusCode: codes.PermissionDenied,
   727  				},
   728  			},
   729  		},
   730  		// This test tests a RBAC policy configured with a remote-ip policy.
   731  		// This should be logically equivalent to configuring a Engine with a
   732  		// direct-remote-ip policy, as per A41 - "allow equating RBAC's
   733  		// direct_remote_ip and remote_ip."
   734  		{
   735  			name: "RemoteIpMatcher",
   736  			rbacConfigs: []*v3rbacpb.RBAC{
   737  				{
   738  					Policies: map[string]*v3rbacpb.Policy{
   739  						"certain-remote-ip": {
   740  							Permissions: []*v3rbacpb.Permission{
   741  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   742  							},
   743  							Principals: []*v3rbacpb.Principal{
   744  								{Identifier: &v3rbacpb.Principal_RemoteIp{RemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}},
   745  							},
   746  						},
   747  					},
   748  				},
   749  			},
   750  			rbacQueries: []struct {
   751  				rpcData        *rpcData
   752  				wantStatusCode codes.Code
   753  			}{
   754  				// This incoming RPC Call should match with the certain-remote-ip policy.
   755  				{
   756  					rpcData: &rpcData{
   757  						peerInfo: &peer.Peer{
   758  							Addr: &addr{ipAddress: "0.0.0.0"},
   759  						},
   760  					},
   761  					wantStatusCode: codes.OK,
   762  				},
   763  				// This incoming RPC Call shouldn't match with the certain-remote-ip policy.
   764  				{
   765  					rpcData: &rpcData{
   766  						peerInfo: &peer.Peer{
   767  							Addr: &addr{ipAddress: "10.0.0.0"},
   768  						},
   769  					},
   770  					wantStatusCode: codes.PermissionDenied,
   771  				},
   772  			},
   773  		},
   774  		{
   775  			name: "DestinationIpMatcher",
   776  			rbacConfigs: []*v3rbacpb.RBAC{
   777  				{
   778  					Policies: map[string]*v3rbacpb.Policy{
   779  						"certain-destination-ip": {
   780  							Permissions: []*v3rbacpb.Permission{
   781  								{Rule: &v3rbacpb.Permission_DestinationIp{DestinationIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}},
   782  							},
   783  							Principals: []*v3rbacpb.Principal{
   784  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   785  							},
   786  						},
   787  					},
   788  				},
   789  			},
   790  			rbacQueries: []struct {
   791  				rpcData        *rpcData
   792  				wantStatusCode codes.Code
   793  			}{
   794  				// This incoming RPC Call shouldn't match with the
   795  				// certain-destination-ip policy, as the test listens on local
   796  				// host.
   797  				{
   798  					rpcData: &rpcData{
   799  						peerInfo: &peer.Peer{
   800  							Addr: &addr{ipAddress: "10.0.0.0"},
   801  						},
   802  					},
   803  					wantStatusCode: codes.PermissionDenied,
   804  				},
   805  			},
   806  		},
   807  		// AllowAndDenyPolicy tests a policy with an allow (on path) and
   808  		// deny (on port) policy chained together. This represents how a user
   809  		// configured interceptor would use this, and also is a potential
   810  		// configuration for a dynamic xds interceptor.
   811  		{
   812  			name: "AllowAndDenyPolicy",
   813  			rbacConfigs: []*v3rbacpb.RBAC{
   814  				{
   815  					Policies: map[string]*v3rbacpb.Policy{
   816  						"certain-source-ip": {
   817  							Permissions: []*v3rbacpb.Permission{
   818  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   819  							},
   820  							Principals: []*v3rbacpb.Principal{
   821  								{Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}},
   822  							},
   823  						},
   824  					},
   825  					Action: v3rbacpb.RBAC_ALLOW,
   826  				},
   827  				{
   828  					Policies: map[string]*v3rbacpb.Policy{
   829  						"localhost-fan": {
   830  							Permissions: []*v3rbacpb.Permission{
   831  								{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}},
   832  							},
   833  							Principals: []*v3rbacpb.Principal{
   834  								{Identifier: &v3rbacpb.Principal_Any{Any: true}},
   835  							},
   836  						},
   837  					},
   838  					Action: v3rbacpb.RBAC_DENY,
   839  				},
   840  			},
   841  			rbacQueries: []struct {
   842  				rpcData        *rpcData
   843  				wantStatusCode codes.Code
   844  			}{
   845  				// This RPC should match with the allow policy, and shouldn't
   846  				// match with the deny and thus should be allowed to proceed.
   847  				{
   848  					rpcData: &rpcData{
   849  						peerInfo: &peer.Peer{
   850  							Addr: &addr{ipAddress: "0.0.0.0"},
   851  						},
   852  					},
   853  					wantStatusCode: codes.OK,
   854  				},
   855  				// This RPC should match with both the allow policy and deny policy
   856  				// and thus shouldn't be allowed to proceed as matched with deny.
   857  				{
   858  					rpcData: &rpcData{
   859  						fullMethod: "localhost-fan-page",
   860  						peerInfo: &peer.Peer{
   861  							Addr: &addr{ipAddress: "0.0.0.0"},
   862  						},
   863  					},
   864  					wantStatusCode: codes.PermissionDenied,
   865  				},
   866  				// This RPC shouldn't match with either policy, and thus
   867  				// shouldn't be allowed to proceed as didn't match with allow.
   868  				{
   869  					rpcData: &rpcData{
   870  						peerInfo: &peer.Peer{
   871  							Addr: &addr{ipAddress: "10.0.0.0"},
   872  						},
   873  					},
   874  					wantStatusCode: codes.PermissionDenied,
   875  				},
   876  				// This RPC shouldn't match with allow, match with deny, and
   877  				// thus shouldn't be allowed to proceed.
   878  				{
   879  					rpcData: &rpcData{
   880  						fullMethod: "localhost-fan-page",
   881  						peerInfo: &peer.Peer{
   882  							Addr: &addr{ipAddress: "10.0.0.0"},
   883  						},
   884  					},
   885  					wantStatusCode: codes.PermissionDenied,
   886  				},
   887  			},
   888  		},
   889  		// This test tests that when there are no SANs or Subject's
   890  		// distinguished name in incoming RPC's, that authenticated matchers
   891  		// match against the empty string.
   892  		{
   893  			name: "default-matching-no-credentials",
   894  			rbacConfigs: []*v3rbacpb.RBAC{
   895  				{
   896  					Policies: map[string]*v3rbacpb.Policy{
   897  						"service-admin": {
   898  							Permissions: []*v3rbacpb.Permission{
   899  								{Rule: &v3rbacpb.Permission_Any{Any: true}},
   900  							},
   901  							Principals: []*v3rbacpb.Principal{
   902  								{Identifier: &v3rbacpb.Principal_Authenticated_{Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: ""}}}}},
   903  							},
   904  						},
   905  					},
   906  				},
   907  			},
   908  			rbacQueries: []struct {
   909  				rpcData        *rpcData
   910  				wantStatusCode codes.Code
   911  			}{
   912  				// This incoming RPC Call should match with the service admin
   913  				// policy. No authentication info is provided, so the
   914  				// authenticated matcher should match to the string matcher on
   915  				// the empty string, matching to the service-admin policy.
   916  				{
   917  					rpcData: &rpcData{
   918  						fullMethod: "some method",
   919  						peerInfo: &peer.Peer{
   920  							Addr: &addr{ipAddress: "0.0.0.0"},
   921  							AuthInfo: credentials.TLSInfo{
   922  								State: tls.ConnectionState{
   923  									PeerCertificates: []*x509.Certificate{
   924  										{
   925  											URIs: []*url.URL{
   926  												{
   927  													Host: "cluster.local",
   928  													Path: "/ns/default/sa/admin",
   929  												},
   930  											},
   931  										},
   932  									},
   933  								},
   934  							},
   935  						},
   936  					},
   937  					wantStatusCode: codes.OK,
   938  				},
   939  			},
   940  		},
   941  	}
   942  
   943  	for _, test := range tests {
   944  		t.Run(test.name, func(t *testing.T) {
   945  			// Instantiate the chainedRBACEngine with different configurations that are
   946  			// interesting to test and to query.
   947  			cre, err := NewChainEngine(test.rbacConfigs)
   948  			if err != nil {
   949  				t.Fatalf("Error constructing RBAC Engine: %v", err)
   950  			}
   951  			// Query the created chain of RBAC Engines with different args to see
   952  			// if the chain of RBAC Engines configured as such works as intended.
   953  			for _, data := range test.rbacQueries {
   954  				func() {
   955  					// Construct the context with three data points that have enough
   956  					// information to represent incoming RPC's. This will be how a
   957  					// user uses this API. A user will have to put MD, PeerInfo, and
   958  					// the connection the RPC is sent on in the context.
   959  					ctx := metadata.NewIncomingContext(context.Background(), data.rpcData.md)
   960  
   961  					// Make a TCP connection with a certain destination port. The
   962  					// address/port of this connection will be used to populate the
   963  					// destination ip/port in RPCData struct. This represents what
   964  					// the user of ChainEngine will have to place into
   965  					// context, as this is only way to get destination ip and port.
   966  					lis, err := net.Listen("tcp", "localhost:0")
   967  					if err != nil {
   968  						t.Fatalf("Error listening: %v", err)
   969  					}
   970  					defer lis.Close()
   971  					connCh := make(chan net.Conn, 1)
   972  					go func() {
   973  						conn, err := lis.Accept()
   974  						if err != nil {
   975  							t.Errorf("Error accepting connection: %v", err)
   976  							return
   977  						}
   978  						connCh <- conn
   979  					}()
   980  					_, err = net.Dial("tcp", lis.Addr().String())
   981  					if err != nil {
   982  						t.Fatalf("Error dialing: %v", err)
   983  					}
   984  					conn := <-connCh
   985  					defer conn.Close()
   986  					getConnection = func(context.Context) net.Conn {
   987  						return conn
   988  					}
   989  					ctx = peer.NewContext(ctx, data.rpcData.peerInfo)
   990  					stream := &ServerTransportStreamWithMethod{
   991  						method: data.rpcData.fullMethod,
   992  					}
   993  
   994  					ctx = grpc.NewContextWithServerTransportStream(ctx, stream)
   995  					err = cre.IsAuthorized(ctx)
   996  					if gotCode := status.Code(err); gotCode != data.wantStatusCode {
   997  						t.Fatalf("IsAuthorized(%+v, %+v) returned (%+v), want(%+v)", ctx, data.rpcData.fullMethod, gotCode, data.wantStatusCode)
   998  					}
   999  				}()
  1000  			}
  1001  		})
  1002  	}
  1003  }
  1004  
  1005  type ServerTransportStreamWithMethod struct {
  1006  	method string
  1007  }
  1008  
  1009  func (sts *ServerTransportStreamWithMethod) Method() string {
  1010  	return sts.method
  1011  }
  1012  
  1013  func (sts *ServerTransportStreamWithMethod) SetHeader(md metadata.MD) error {
  1014  	return nil
  1015  }
  1016  
  1017  func (sts *ServerTransportStreamWithMethod) SendHeader(md metadata.MD) error {
  1018  	return nil
  1019  }
  1020  
  1021  func (sts *ServerTransportStreamWithMethod) SetTrailer(md metadata.MD) error {
  1022  	return nil
  1023  }