istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/security/authn/policy_applier_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package authn
    16  
    17  import (
    18  	"reflect"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/davecgh/go-spew/spew"
    23  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    24  	route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    25  	envoy_jwt "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/jwt_authn/v3"
    26  	hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
    27  	tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
    28  	"github.com/google/go-cmp/cmp"
    29  	"google.golang.org/protobuf/proto"
    30  	"google.golang.org/protobuf/testing/protocmp"
    31  	"google.golang.org/protobuf/types/known/durationpb"
    32  	"google.golang.org/protobuf/types/known/emptypb"
    33  
    34  	authn_alpha "istio.io/api/authentication/v1alpha1"
    35  	authn_filter "istio.io/api/envoy/config/filter/http/authn/v2alpha1"
    36  	"istio.io/api/security/v1beta1"
    37  	type_beta "istio.io/api/type/v1beta1"
    38  	"istio.io/istio/pilot/pkg/features"
    39  	"istio.io/istio/pilot/pkg/model"
    40  	"istio.io/istio/pilot/pkg/model/test"
    41  	"istio.io/istio/pilot/pkg/util/protoconv"
    42  	"istio.io/istio/pkg/config"
    43  	"istio.io/istio/pkg/config/host"
    44  	"istio.io/istio/pkg/jwt"
    45  	protovalue "istio.io/istio/pkg/proto"
    46  	istiotest "istio.io/istio/pkg/test"
    47  	"istio.io/istio/pkg/test/util/assert"
    48  )
    49  
    50  func TestJwtFilter(t *testing.T) {
    51  	ms, err := test.StartNewServer()
    52  	if err != nil {
    53  		t.Fatal("failed to start a mock server")
    54  	}
    55  
    56  	jwksURI := ms.URL + "/oauth2/v3/certs"
    57  
    58  	cases := []struct {
    59  		name          string
    60  		in            []*config.Config
    61  		jwksFetchMode jwt.JwksFetchMode
    62  		expected      *hcm.HttpFilter
    63  	}{
    64  		{
    65  			name:     "No policy",
    66  			in:       []*config.Config{},
    67  			expected: nil,
    68  		},
    69  		{
    70  			name: "Empty policy",
    71  			in: []*config.Config{
    72  				{
    73  					Spec: &v1beta1.RequestAuthentication{},
    74  				},
    75  			},
    76  			expected: nil,
    77  		},
    78  		{
    79  			name: "Single JWT policy",
    80  			in: []*config.Config{
    81  				{
    82  					Spec: &v1beta1.RequestAuthentication{
    83  						JwtRules: []*v1beta1.JWTRule{
    84  							{
    85  								Issuer:  "https://secret.foo.com",
    86  								JwksUri: jwksURI,
    87  							},
    88  						},
    89  					},
    90  				},
    91  			},
    92  			expected: &hcm.HttpFilter{
    93  				Name: "envoy.filters.http.jwt_authn",
    94  				ConfigType: &hcm.HttpFilter_TypedConfig{
    95  					TypedConfig: protoconv.MessageToAny(
    96  						&envoy_jwt.JwtAuthentication{
    97  							Rules: []*envoy_jwt.RequirementRule{
    98  								{
    99  									Match: &route.RouteMatch{
   100  										PathSpecifier: &route.RouteMatch_Prefix{
   101  											Prefix: "/",
   102  										},
   103  									},
   104  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   105  										Requires: &envoy_jwt.JwtRequirement{
   106  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   107  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   108  													Requirements: []*envoy_jwt.JwtRequirement{
   109  														{
   110  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   111  																ProviderName: "origins-0",
   112  															},
   113  														},
   114  														{
   115  															RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   116  																AllowMissing: &emptypb.Empty{},
   117  															},
   118  														},
   119  													},
   120  												},
   121  											},
   122  										},
   123  									},
   124  								},
   125  							},
   126  							Providers: map[string]*envoy_jwt.JwtProvider{
   127  								"origins-0": {
   128  									Issuer: "https://secret.foo.com",
   129  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
   130  										LocalJwks: &core.DataSource{
   131  											Specifier: &core.DataSource_InlineString{
   132  												InlineString: test.JwtPubKey1,
   133  											},
   134  										},
   135  									},
   136  									Forward:           false,
   137  									PayloadInMetadata: "https://secret.foo.com",
   138  								},
   139  							},
   140  							BypassCorsPreflight: true,
   141  						}),
   142  				},
   143  			},
   144  		},
   145  		{
   146  			name: "JWT policy with Mesh cluster as issuer and remote jwks mode Hybrid",
   147  			in: []*config.Config{
   148  				{
   149  					Spec: &v1beta1.RequestAuthentication{
   150  						JwtRules: []*v1beta1.JWTRule{
   151  							{
   152  								Issuer:  "mesh cluster",
   153  								JwksUri: "http://jwt-token-issuer.mesh:7443/jwks",
   154  							},
   155  						},
   156  					},
   157  				},
   158  			},
   159  			jwksFetchMode: jwt.Hybrid,
   160  			expected: &hcm.HttpFilter{
   161  				Name: "envoy.filters.http.jwt_authn",
   162  				ConfigType: &hcm.HttpFilter_TypedConfig{
   163  					TypedConfig: protoconv.MessageToAny(
   164  						&envoy_jwt.JwtAuthentication{
   165  							Rules: []*envoy_jwt.RequirementRule{
   166  								{
   167  									Match: &route.RouteMatch{
   168  										PathSpecifier: &route.RouteMatch_Prefix{
   169  											Prefix: "/",
   170  										},
   171  									},
   172  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   173  										Requires: &envoy_jwt.JwtRequirement{
   174  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   175  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   176  													Requirements: []*envoy_jwt.JwtRequirement{
   177  														{
   178  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   179  																ProviderName: "origins-0",
   180  															},
   181  														},
   182  														{
   183  															RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   184  																AllowMissing: &emptypb.Empty{},
   185  															},
   186  														},
   187  													},
   188  												},
   189  											},
   190  										},
   191  									},
   192  								},
   193  							},
   194  							Providers: map[string]*envoy_jwt.JwtProvider{
   195  								"origins-0": {
   196  									Issuer: "mesh cluster",
   197  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_RemoteJwks{
   198  										RemoteJwks: &envoy_jwt.RemoteJwks{
   199  											HttpUri: &core.HttpUri{
   200  												Uri: "http://jwt-token-issuer.mesh:7443/jwks",
   201  												HttpUpstreamType: &core.HttpUri_Cluster{
   202  													Cluster: "outbound|7443||jwt-token-issuer.mesh.svc.cluster.local",
   203  												},
   204  												Timeout: &durationpb.Duration{Seconds: 5},
   205  											},
   206  											CacheDuration: &durationpb.Duration{Seconds: 5 * 60},
   207  										},
   208  									},
   209  									Forward:           false,
   210  									PayloadInMetadata: "mesh cluster",
   211  								},
   212  							},
   213  							BypassCorsPreflight: true,
   214  						}),
   215  				},
   216  			},
   217  		},
   218  		{
   219  			name: "JWT policy with Mesh cluster as issuer and remote jwks mode Envoy",
   220  			in: []*config.Config{
   221  				{
   222  					Spec: &v1beta1.RequestAuthentication{
   223  						JwtRules: []*v1beta1.JWTRule{
   224  							{
   225  								Issuer:  "mesh cluster",
   226  								JwksUri: "http://jwt-token-issuer.mesh:7443/jwks",
   227  							},
   228  						},
   229  					},
   230  				},
   231  			},
   232  			jwksFetchMode: jwt.Envoy,
   233  			expected: &hcm.HttpFilter{
   234  				Name: "envoy.filters.http.jwt_authn",
   235  				ConfigType: &hcm.HttpFilter_TypedConfig{
   236  					TypedConfig: protoconv.MessageToAny(
   237  						&envoy_jwt.JwtAuthentication{
   238  							Rules: []*envoy_jwt.RequirementRule{
   239  								{
   240  									Match: &route.RouteMatch{
   241  										PathSpecifier: &route.RouteMatch_Prefix{
   242  											Prefix: "/",
   243  										},
   244  									},
   245  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   246  										Requires: &envoy_jwt.JwtRequirement{
   247  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   248  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   249  													Requirements: []*envoy_jwt.JwtRequirement{
   250  														{
   251  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   252  																ProviderName: "origins-0",
   253  															},
   254  														},
   255  														{
   256  															RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   257  																AllowMissing: &emptypb.Empty{},
   258  															},
   259  														},
   260  													},
   261  												},
   262  											},
   263  										},
   264  									},
   265  								},
   266  							},
   267  							Providers: map[string]*envoy_jwt.JwtProvider{
   268  								"origins-0": {
   269  									Issuer: "mesh cluster",
   270  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_RemoteJwks{
   271  										RemoteJwks: &envoy_jwt.RemoteJwks{
   272  											HttpUri: &core.HttpUri{
   273  												Uri: "http://jwt-token-issuer.mesh:7443/jwks",
   274  												HttpUpstreamType: &core.HttpUri_Cluster{
   275  													Cluster: "outbound|7443||jwt-token-issuer.mesh.svc.cluster.local",
   276  												},
   277  												Timeout: &durationpb.Duration{Seconds: 5},
   278  											},
   279  											CacheDuration: &durationpb.Duration{Seconds: 5 * 60},
   280  										},
   281  									},
   282  									Forward:           false,
   283  									PayloadInMetadata: "mesh cluster",
   284  								},
   285  							},
   286  							BypassCorsPreflight: true,
   287  						}),
   288  				},
   289  			},
   290  		},
   291  		{
   292  			name: "JWT policy with non Mesh cluster as issuer and remote jwks mode Hybrid",
   293  			in: []*config.Config{
   294  				{
   295  					Spec: &v1beta1.RequestAuthentication{
   296  						JwtRules: []*v1beta1.JWTRule{
   297  							{
   298  								Issuer:  "invalid|7443|",
   299  								JwksUri: jwksURI,
   300  							},
   301  						},
   302  					},
   303  				},
   304  			},
   305  			jwksFetchMode: jwt.Hybrid,
   306  			expected: &hcm.HttpFilter{
   307  				Name: "envoy.filters.http.jwt_authn",
   308  				ConfigType: &hcm.HttpFilter_TypedConfig{
   309  					TypedConfig: protoconv.MessageToAny(
   310  						&envoy_jwt.JwtAuthentication{
   311  							Rules: []*envoy_jwt.RequirementRule{
   312  								{
   313  									Match: &route.RouteMatch{
   314  										PathSpecifier: &route.RouteMatch_Prefix{
   315  											Prefix: "/",
   316  										},
   317  									},
   318  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   319  										Requires: &envoy_jwt.JwtRequirement{
   320  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   321  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   322  													Requirements: []*envoy_jwt.JwtRequirement{
   323  														{
   324  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   325  																ProviderName: "origins-0",
   326  															},
   327  														},
   328  														{
   329  															RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   330  																AllowMissing: &emptypb.Empty{},
   331  															},
   332  														},
   333  													},
   334  												},
   335  											},
   336  										},
   337  									},
   338  								},
   339  							},
   340  							Providers: map[string]*envoy_jwt.JwtProvider{
   341  								"origins-0": {
   342  									Issuer: "invalid|7443|",
   343  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
   344  										LocalJwks: &core.DataSource{
   345  											Specifier: &core.DataSource_InlineString{
   346  												InlineString: test.JwtPubKey2,
   347  											},
   348  										},
   349  									},
   350  									Forward:           false,
   351  									PayloadInMetadata: "invalid|7443|",
   352  								},
   353  							},
   354  							BypassCorsPreflight: true,
   355  						}),
   356  				},
   357  			},
   358  		},
   359  		{
   360  			name: "JWT policy with non Mesh cluster as issuer and remote jwks mode Envoy",
   361  			in: []*config.Config{
   362  				{
   363  					Spec: &v1beta1.RequestAuthentication{
   364  						JwtRules: []*v1beta1.JWTRule{
   365  							{
   366  								Issuer:  "invalid|7443|",
   367  								JwksUri: "http://invalid-issuer.com:7443/jwks",
   368  							},
   369  						},
   370  					},
   371  				},
   372  			},
   373  			jwksFetchMode: jwt.Envoy,
   374  			expected: &hcm.HttpFilter{
   375  				Name: "envoy.filters.http.jwt_authn",
   376  				ConfigType: &hcm.HttpFilter_TypedConfig{
   377  					TypedConfig: protoconv.MessageToAny(
   378  						&envoy_jwt.JwtAuthentication{
   379  							Rules: []*envoy_jwt.RequirementRule{
   380  								{
   381  									Match: &route.RouteMatch{
   382  										PathSpecifier: &route.RouteMatch_Prefix{
   383  											Prefix: "/",
   384  										},
   385  									},
   386  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   387  										Requires: &envoy_jwt.JwtRequirement{
   388  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   389  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   390  													Requirements: []*envoy_jwt.JwtRequirement{
   391  														{
   392  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   393  																ProviderName: "origins-0",
   394  															},
   395  														},
   396  														{
   397  															RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   398  																AllowMissing: &emptypb.Empty{},
   399  															},
   400  														},
   401  													},
   402  												},
   403  											},
   404  										},
   405  									},
   406  								},
   407  							},
   408  							Providers: map[string]*envoy_jwt.JwtProvider{
   409  								"origins-0": {
   410  									Issuer: "invalid|7443|",
   411  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_RemoteJwks{
   412  										RemoteJwks: &envoy_jwt.RemoteJwks{
   413  											HttpUri: &core.HttpUri{
   414  												Uri: "http://invalid-issuer.com:7443/jwks",
   415  												HttpUpstreamType: &core.HttpUri_Cluster{
   416  													Cluster: "outbound|7443||invalid-issuer.com",
   417  												},
   418  												Timeout: &durationpb.Duration{Seconds: 5},
   419  											},
   420  											CacheDuration: &durationpb.Duration{Seconds: 5 * 60},
   421  										},
   422  									},
   423  									Forward:           false,
   424  									PayloadInMetadata: "invalid|7443|",
   425  								},
   426  							},
   427  							BypassCorsPreflight: true,
   428  						}),
   429  				},
   430  			},
   431  		},
   432  		{
   433  			name: "Multi JWTs policy",
   434  			in: []*config.Config{
   435  				{
   436  					Spec: &v1beta1.RequestAuthentication{
   437  						JwtRules: []*v1beta1.JWTRule{
   438  							{
   439  								Issuer:  "https://secret.foo.com",
   440  								JwksUri: jwksURI,
   441  							},
   442  						},
   443  					},
   444  				},
   445  				{
   446  					Spec: &v1beta1.RequestAuthentication{},
   447  				},
   448  				{
   449  					Spec: &v1beta1.RequestAuthentication{
   450  						JwtRules: []*v1beta1.JWTRule{
   451  							{
   452  								Issuer: "https://secret.bar.com",
   453  								Jwks:   "jwks-inline-data",
   454  							},
   455  						},
   456  					},
   457  				},
   458  			},
   459  			expected: &hcm.HttpFilter{
   460  				Name: "envoy.filters.http.jwt_authn",
   461  				ConfigType: &hcm.HttpFilter_TypedConfig{
   462  					TypedConfig: protoconv.MessageToAny(
   463  						&envoy_jwt.JwtAuthentication{
   464  							Rules: []*envoy_jwt.RequirementRule{
   465  								{
   466  									Match: &route.RouteMatch{
   467  										PathSpecifier: &route.RouteMatch_Prefix{
   468  											Prefix: "/",
   469  										},
   470  									},
   471  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   472  										Requires: &envoy_jwt.JwtRequirement{
   473  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   474  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   475  													Requirements: []*envoy_jwt.JwtRequirement{
   476  														{
   477  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   478  																ProviderName: "origins-0",
   479  															},
   480  														},
   481  														{
   482  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   483  																ProviderName: "origins-1",
   484  															},
   485  														},
   486  														{
   487  															RequiresType: &envoy_jwt.JwtRequirement_RequiresAll{
   488  																RequiresAll: &envoy_jwt.JwtRequirementAndList{
   489  																	Requirements: []*envoy_jwt.JwtRequirement{
   490  																		{
   491  																			RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   492  																				RequiresAny: &envoy_jwt.JwtRequirementOrList{
   493  																					Requirements: []*envoy_jwt.JwtRequirement{
   494  																						{
   495  																							RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   496  																								ProviderName: "origins-0",
   497  																							},
   498  																						},
   499  																						{
   500  																							RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   501  																								AllowMissing: &emptypb.Empty{},
   502  																							},
   503  																						},
   504  																					},
   505  																				},
   506  																			},
   507  																		},
   508  																		{
   509  																			RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   510  																				RequiresAny: &envoy_jwt.JwtRequirementOrList{
   511  																					Requirements: []*envoy_jwt.JwtRequirement{
   512  																						{
   513  																							RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   514  																								ProviderName: "origins-1",
   515  																							},
   516  																						},
   517  																						{
   518  																							RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   519  																								AllowMissing: &emptypb.Empty{},
   520  																							},
   521  																						},
   522  																					},
   523  																				},
   524  																			},
   525  																		},
   526  																	},
   527  																},
   528  															},
   529  														},
   530  													},
   531  												},
   532  											},
   533  										},
   534  									},
   535  								},
   536  							},
   537  							Providers: map[string]*envoy_jwt.JwtProvider{
   538  								"origins-0": {
   539  									Issuer: "https://secret.bar.com",
   540  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
   541  										LocalJwks: &core.DataSource{
   542  											Specifier: &core.DataSource_InlineString{
   543  												InlineString: "jwks-inline-data",
   544  											},
   545  										},
   546  									},
   547  									Forward:           false,
   548  									PayloadInMetadata: "https://secret.bar.com",
   549  								},
   550  								"origins-1": {
   551  									Issuer: "https://secret.foo.com",
   552  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
   553  										LocalJwks: &core.DataSource{
   554  											Specifier: &core.DataSource_InlineString{
   555  												InlineString: test.JwtPubKey1,
   556  											},
   557  										},
   558  									},
   559  									Forward:           false,
   560  									PayloadInMetadata: "https://secret.foo.com",
   561  								},
   562  							},
   563  							BypassCorsPreflight: true,
   564  						}),
   565  				},
   566  			},
   567  		},
   568  		{
   569  			name: "JWT policy with inline Jwks",
   570  			in: []*config.Config{
   571  				{
   572  					Spec: &v1beta1.RequestAuthentication{
   573  						JwtRules: []*v1beta1.JWTRule{
   574  							{
   575  								Issuer: "https://secret.foo.com",
   576  								Jwks:   "inline-jwks-data",
   577  							},
   578  						},
   579  					},
   580  				},
   581  			},
   582  			expected: &hcm.HttpFilter{
   583  				Name: "envoy.filters.http.jwt_authn",
   584  				ConfigType: &hcm.HttpFilter_TypedConfig{
   585  					TypedConfig: protoconv.MessageToAny(
   586  						&envoy_jwt.JwtAuthentication{
   587  							Rules: []*envoy_jwt.RequirementRule{
   588  								{
   589  									Match: &route.RouteMatch{
   590  										PathSpecifier: &route.RouteMatch_Prefix{
   591  											Prefix: "/",
   592  										},
   593  									},
   594  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   595  										Requires: &envoy_jwt.JwtRequirement{
   596  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   597  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   598  													Requirements: []*envoy_jwt.JwtRequirement{
   599  														{
   600  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   601  																ProviderName: "origins-0",
   602  															},
   603  														},
   604  														{
   605  															RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   606  																AllowMissing: &emptypb.Empty{},
   607  															},
   608  														},
   609  													},
   610  												},
   611  											},
   612  										},
   613  									},
   614  								},
   615  							},
   616  							Providers: map[string]*envoy_jwt.JwtProvider{
   617  								"origins-0": {
   618  									Issuer: "https://secret.foo.com",
   619  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
   620  										LocalJwks: &core.DataSource{
   621  											Specifier: &core.DataSource_InlineString{
   622  												InlineString: "inline-jwks-data",
   623  											},
   624  										},
   625  									},
   626  									Forward:           false,
   627  									PayloadInMetadata: "https://secret.foo.com",
   628  								},
   629  							},
   630  							BypassCorsPreflight: true,
   631  						}),
   632  				},
   633  			},
   634  		},
   635  		{
   636  			name: "JWT policy with bad Jwks URI",
   637  			in: []*config.Config{
   638  				{
   639  					Spec: &v1beta1.RequestAuthentication{
   640  						JwtRules: []*v1beta1.JWTRule{
   641  							{
   642  								Issuer:  "https://secret.foo.com",
   643  								JwksUri: "http://site.not.exist",
   644  							},
   645  						},
   646  					},
   647  				},
   648  			},
   649  			expected: &hcm.HttpFilter{
   650  				Name: "envoy.filters.http.jwt_authn",
   651  				ConfigType: &hcm.HttpFilter_TypedConfig{
   652  					TypedConfig: protoconv.MessageToAny(
   653  						&envoy_jwt.JwtAuthentication{
   654  							Rules: []*envoy_jwt.RequirementRule{
   655  								{
   656  									Match: &route.RouteMatch{
   657  										PathSpecifier: &route.RouteMatch_Prefix{
   658  											Prefix: "/",
   659  										},
   660  									},
   661  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   662  										Requires: &envoy_jwt.JwtRequirement{
   663  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   664  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   665  													Requirements: []*envoy_jwt.JwtRequirement{
   666  														{
   667  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   668  																ProviderName: "origins-0",
   669  															},
   670  														},
   671  														{
   672  															RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   673  																AllowMissing: &emptypb.Empty{},
   674  															},
   675  														},
   676  													},
   677  												},
   678  											},
   679  										},
   680  									},
   681  								},
   682  							},
   683  							Providers: map[string]*envoy_jwt.JwtProvider{
   684  								"origins-0": {
   685  									Issuer: "https://secret.foo.com",
   686  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
   687  										LocalJwks: &core.DataSource{
   688  											Specifier: &core.DataSource_InlineString{
   689  												InlineString: model.FakeJwks,
   690  											},
   691  										},
   692  									},
   693  									Forward:           false,
   694  									PayloadInMetadata: "https://secret.foo.com",
   695  								},
   696  							},
   697  							BypassCorsPreflight: true,
   698  						}),
   699  				},
   700  			},
   701  		},
   702  		{
   703  			name: "Forward original token",
   704  			in: []*config.Config{
   705  				{
   706  					Spec: &v1beta1.RequestAuthentication{
   707  						JwtRules: []*v1beta1.JWTRule{
   708  							{
   709  								Issuer:               "https://secret.foo.com",
   710  								JwksUri:              jwksURI,
   711  								ForwardOriginalToken: true,
   712  							},
   713  						},
   714  					},
   715  				},
   716  			},
   717  			expected: &hcm.HttpFilter{
   718  				Name: "envoy.filters.http.jwt_authn",
   719  				ConfigType: &hcm.HttpFilter_TypedConfig{
   720  					TypedConfig: protoconv.MessageToAny(
   721  						&envoy_jwt.JwtAuthentication{
   722  							Rules: []*envoy_jwt.RequirementRule{
   723  								{
   724  									Match: &route.RouteMatch{
   725  										PathSpecifier: &route.RouteMatch_Prefix{
   726  											Prefix: "/",
   727  										},
   728  									},
   729  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   730  										Requires: &envoy_jwt.JwtRequirement{
   731  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   732  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   733  													Requirements: []*envoy_jwt.JwtRequirement{
   734  														{
   735  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   736  																ProviderName: "origins-0",
   737  															},
   738  														},
   739  														{
   740  															RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   741  																AllowMissing: &emptypb.Empty{},
   742  															},
   743  														},
   744  													},
   745  												},
   746  											},
   747  										},
   748  									},
   749  								},
   750  							},
   751  							Providers: map[string]*envoy_jwt.JwtProvider{
   752  								"origins-0": {
   753  									Issuer: "https://secret.foo.com",
   754  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
   755  										LocalJwks: &core.DataSource{
   756  											Specifier: &core.DataSource_InlineString{
   757  												InlineString: test.JwtPubKey1,
   758  											},
   759  										},
   760  									},
   761  									Forward:           true,
   762  									PayloadInMetadata: "https://secret.foo.com",
   763  								},
   764  							},
   765  							BypassCorsPreflight: true,
   766  						}),
   767  				},
   768  			},
   769  		},
   770  		{
   771  			name: "Output payload",
   772  			in: []*config.Config{
   773  				{
   774  					Spec: &v1beta1.RequestAuthentication{
   775  						JwtRules: []*v1beta1.JWTRule{
   776  							{
   777  								Issuer:                "https://secret.foo.com",
   778  								JwksUri:               jwksURI,
   779  								ForwardOriginalToken:  true,
   780  								OutputPayloadToHeader: "x-foo",
   781  							},
   782  						},
   783  					},
   784  				},
   785  			},
   786  			expected: &hcm.HttpFilter{
   787  				Name: "envoy.filters.http.jwt_authn",
   788  				ConfigType: &hcm.HttpFilter_TypedConfig{
   789  					TypedConfig: protoconv.MessageToAny(
   790  						&envoy_jwt.JwtAuthentication{
   791  							Rules: []*envoy_jwt.RequirementRule{
   792  								{
   793  									Match: &route.RouteMatch{
   794  										PathSpecifier: &route.RouteMatch_Prefix{
   795  											Prefix: "/",
   796  										},
   797  									},
   798  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   799  										Requires: &envoy_jwt.JwtRequirement{
   800  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   801  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   802  													Requirements: []*envoy_jwt.JwtRequirement{
   803  														{
   804  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   805  																ProviderName: "origins-0",
   806  															},
   807  														},
   808  														{
   809  															RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   810  																AllowMissing: &emptypb.Empty{},
   811  															},
   812  														},
   813  													},
   814  												},
   815  											},
   816  										},
   817  									},
   818  								},
   819  							},
   820  							Providers: map[string]*envoy_jwt.JwtProvider{
   821  								"origins-0": {
   822  									Issuer: "https://secret.foo.com",
   823  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
   824  										LocalJwks: &core.DataSource{
   825  											Specifier: &core.DataSource_InlineString{
   826  												InlineString: test.JwtPubKey1,
   827  											},
   828  										},
   829  									},
   830  									Forward:              true,
   831  									ForwardPayloadHeader: "x-foo",
   832  									PayloadInMetadata:    "https://secret.foo.com",
   833  								},
   834  							},
   835  							BypassCorsPreflight: true,
   836  						}),
   837  				},
   838  			},
   839  		},
   840  		{
   841  			name: "Output claim to header",
   842  			in: []*config.Config{
   843  				{
   844  					Spec: &v1beta1.RequestAuthentication{
   845  						JwtRules: []*v1beta1.JWTRule{
   846  							{
   847  								Issuer:               "https://secret.foo.com",
   848  								JwksUri:              jwksURI,
   849  								ForwardOriginalToken: true,
   850  								OutputClaimToHeaders: []*v1beta1.ClaimToHeader{
   851  									{Header: "x-jwt-key1", Claim: "value1"},
   852  									{Header: "x-jwt-key2", Claim: "value2"},
   853  								},
   854  							},
   855  						},
   856  					},
   857  				},
   858  			},
   859  			expected: &hcm.HttpFilter{
   860  				Name: "envoy.filters.http.jwt_authn",
   861  				ConfigType: &hcm.HttpFilter_TypedConfig{
   862  					TypedConfig: protoconv.MessageToAny(
   863  						&envoy_jwt.JwtAuthentication{
   864  							Rules: []*envoy_jwt.RequirementRule{
   865  								{
   866  									Match: &route.RouteMatch{
   867  										PathSpecifier: &route.RouteMatch_Prefix{
   868  											Prefix: "/",
   869  										},
   870  									},
   871  									RequirementType: &envoy_jwt.RequirementRule_Requires{
   872  										Requires: &envoy_jwt.JwtRequirement{
   873  											RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   874  												RequiresAny: &envoy_jwt.JwtRequirementOrList{
   875  													Requirements: []*envoy_jwt.JwtRequirement{
   876  														{
   877  															RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   878  																ProviderName: "origins-0",
   879  															},
   880  														},
   881  														{
   882  															RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   883  																AllowMissing: &emptypb.Empty{},
   884  															},
   885  														},
   886  													},
   887  												},
   888  											},
   889  										},
   890  									},
   891  								},
   892  							},
   893  							Providers: map[string]*envoy_jwt.JwtProvider{
   894  								"origins-0": {
   895  									Issuer: "https://secret.foo.com",
   896  									JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
   897  										LocalJwks: &core.DataSource{
   898  											Specifier: &core.DataSource_InlineString{
   899  												InlineString: test.JwtPubKey1,
   900  											},
   901  										},
   902  									},
   903  									Forward: true,
   904  									ClaimToHeaders: []*envoy_jwt.JwtClaimToHeader{
   905  										{HeaderName: "x-jwt-key1", ClaimName: "value1"},
   906  										{HeaderName: "x-jwt-key2", ClaimName: "value2"},
   907  									},
   908  									PayloadInMetadata: "https://secret.foo.com",
   909  								},
   910  							},
   911  							BypassCorsPreflight: true,
   912  						}),
   913  				},
   914  			},
   915  		},
   916  	}
   917  	push := model.NewPushContext()
   918  	push.JwtKeyResolver = model.NewJwksResolver(
   919  		model.JwtPubKeyEvictionDuration, model.JwtPubKeyRefreshInterval,
   920  		model.JwtPubKeyRefreshIntervalOnFailure, 10*time.Millisecond)
   921  
   922  	defer push.JwtKeyResolver.Close()
   923  
   924  	push.ServiceIndex.HostnameAndNamespace[host.Name("jwt-token-issuer.mesh")] = map[string]*model.Service{}
   925  	push.ServiceIndex.HostnameAndNamespace[host.Name("jwt-token-issuer.mesh")]["mesh"] = &model.Service{
   926  		Hostname: "jwt-token-issuer.mesh.svc.cluster.local",
   927  	}
   928  	for _, c := range cases {
   929  		t.Run(c.name, func(t *testing.T) {
   930  			istiotest.SetForTest(t, &features.JwksFetchMode, c.jwksFetchMode)
   931  			if got := newPolicyApplier("root-namespace", c.in, nil, push).JwtFilter(false, false); !reflect.DeepEqual(c.expected, got) {
   932  				t.Errorf("got:\n%s\nwanted:\n%s", spew.Sdump(got), spew.Sdump(c.expected))
   933  			}
   934  		})
   935  	}
   936  }
   937  
   938  func TestConvertToEnvoyJwtConfig(t *testing.T) {
   939  	ms, err := test.StartNewServer()
   940  	if err != nil {
   941  		t.Fatal("failed to start a mock server")
   942  	}
   943  
   944  	jwksURI := ms.URL + "/oauth2/v3/certs"
   945  
   946  	cases := []struct {
   947  		name     string
   948  		in       []*v1beta1.JWTRule
   949  		expected *envoy_jwt.JwtAuthentication
   950  	}{
   951  		{
   952  			name:     "No rule",
   953  			in:       []*v1beta1.JWTRule{},
   954  			expected: nil,
   955  		},
   956  		{
   957  			name: "Single JWT rule",
   958  			in: []*v1beta1.JWTRule{
   959  				{
   960  					Issuer:  "https://secret.foo.com",
   961  					JwksUri: jwksURI,
   962  				},
   963  			},
   964  			expected: &envoy_jwt.JwtAuthentication{
   965  				Rules: []*envoy_jwt.RequirementRule{
   966  					{
   967  						Match: &route.RouteMatch{
   968  							PathSpecifier: &route.RouteMatch_Prefix{
   969  								Prefix: "/",
   970  							},
   971  						},
   972  						RequirementType: &envoy_jwt.RequirementRule_Requires{
   973  							Requires: &envoy_jwt.JwtRequirement{
   974  								RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
   975  									RequiresAny: &envoy_jwt.JwtRequirementOrList{
   976  										Requirements: []*envoy_jwt.JwtRequirement{
   977  											{
   978  												RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
   979  													ProviderName: "origins-0",
   980  												},
   981  											},
   982  											{
   983  												RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
   984  													AllowMissing: &emptypb.Empty{},
   985  												},
   986  											},
   987  										},
   988  									},
   989  								},
   990  							},
   991  						},
   992  					},
   993  				},
   994  				Providers: map[string]*envoy_jwt.JwtProvider{
   995  					"origins-0": {
   996  						Issuer: "https://secret.foo.com",
   997  						JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
   998  							LocalJwks: &core.DataSource{
   999  								Specifier: &core.DataSource_InlineString{
  1000  									InlineString: test.JwtPubKey1,
  1001  								},
  1002  							},
  1003  						},
  1004  						Forward:           false,
  1005  						PayloadInMetadata: "https://secret.foo.com",
  1006  					},
  1007  				},
  1008  				BypassCorsPreflight: true,
  1009  			},
  1010  		},
  1011  		{
  1012  			name: "Multiple JWT rule",
  1013  			in: []*v1beta1.JWTRule{
  1014  				{
  1015  					Issuer:  "https://secret.foo.com",
  1016  					JwksUri: jwksURI,
  1017  				},
  1018  				{
  1019  					Issuer: "https://secret.bar.com",
  1020  					Jwks:   "jwks-inline-data",
  1021  				},
  1022  			},
  1023  			expected: &envoy_jwt.JwtAuthentication{
  1024  				Rules: []*envoy_jwt.RequirementRule{
  1025  					{
  1026  						Match: &route.RouteMatch{
  1027  							PathSpecifier: &route.RouteMatch_Prefix{
  1028  								Prefix: "/",
  1029  							},
  1030  						},
  1031  						RequirementType: &envoy_jwt.RequirementRule_Requires{
  1032  							Requires: &envoy_jwt.JwtRequirement{
  1033  								RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
  1034  									RequiresAny: &envoy_jwt.JwtRequirementOrList{
  1035  										Requirements: []*envoy_jwt.JwtRequirement{
  1036  											{
  1037  												RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
  1038  													ProviderName: "origins-0",
  1039  												},
  1040  											},
  1041  											{
  1042  												RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
  1043  													ProviderName: "origins-1",
  1044  												},
  1045  											},
  1046  											{
  1047  												RequiresType: &envoy_jwt.JwtRequirement_RequiresAll{
  1048  													RequiresAll: &envoy_jwt.JwtRequirementAndList{
  1049  														Requirements: []*envoy_jwt.JwtRequirement{
  1050  															{
  1051  																RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
  1052  																	RequiresAny: &envoy_jwt.JwtRequirementOrList{
  1053  																		Requirements: []*envoy_jwt.JwtRequirement{
  1054  																			{
  1055  																				RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
  1056  																					ProviderName: "origins-0",
  1057  																				},
  1058  																			},
  1059  																			{
  1060  																				RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
  1061  																					AllowMissing: &emptypb.Empty{},
  1062  																				},
  1063  																			},
  1064  																		},
  1065  																	},
  1066  																},
  1067  															},
  1068  															{
  1069  																RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
  1070  																	RequiresAny: &envoy_jwt.JwtRequirementOrList{
  1071  																		Requirements: []*envoy_jwt.JwtRequirement{
  1072  																			{
  1073  																				RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
  1074  																					ProviderName: "origins-1",
  1075  																				},
  1076  																			},
  1077  																			{
  1078  																				RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
  1079  																					AllowMissing: &emptypb.Empty{},
  1080  																				},
  1081  																			},
  1082  																		},
  1083  																	},
  1084  																},
  1085  															},
  1086  														},
  1087  													},
  1088  												},
  1089  											},
  1090  										},
  1091  									},
  1092  								},
  1093  							},
  1094  						},
  1095  					},
  1096  				},
  1097  				Providers: map[string]*envoy_jwt.JwtProvider{
  1098  					"origins-0": {
  1099  						Issuer: "https://secret.foo.com",
  1100  						JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
  1101  							LocalJwks: &core.DataSource{
  1102  								Specifier: &core.DataSource_InlineString{
  1103  									InlineString: test.JwtPubKey1,
  1104  								},
  1105  							},
  1106  						},
  1107  						Forward:           false,
  1108  						PayloadInMetadata: "https://secret.foo.com",
  1109  					},
  1110  					"origins-1": {
  1111  						Issuer: "https://secret.bar.com",
  1112  						JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
  1113  							LocalJwks: &core.DataSource{
  1114  								Specifier: &core.DataSource_InlineString{
  1115  									InlineString: "jwks-inline-data",
  1116  								},
  1117  							},
  1118  						},
  1119  						Forward:           false,
  1120  						PayloadInMetadata: "https://secret.bar.com",
  1121  					},
  1122  				},
  1123  				BypassCorsPreflight: true,
  1124  			},
  1125  		},
  1126  		{
  1127  			name: "Empty Jwks URI",
  1128  			in: []*v1beta1.JWTRule{
  1129  				{
  1130  					Issuer: "https://secret.foo.com",
  1131  				},
  1132  			},
  1133  			expected: &envoy_jwt.JwtAuthentication{
  1134  				Rules: []*envoy_jwt.RequirementRule{
  1135  					{
  1136  						Match: &route.RouteMatch{
  1137  							PathSpecifier: &route.RouteMatch_Prefix{
  1138  								Prefix: "/",
  1139  							},
  1140  						},
  1141  						RequirementType: &envoy_jwt.RequirementRule_Requires{
  1142  							Requires: &envoy_jwt.JwtRequirement{
  1143  								RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
  1144  									RequiresAny: &envoy_jwt.JwtRequirementOrList{
  1145  										Requirements: []*envoy_jwt.JwtRequirement{
  1146  											{
  1147  												RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
  1148  													ProviderName: "origins-0",
  1149  												},
  1150  											},
  1151  											{
  1152  												RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
  1153  													AllowMissing: &emptypb.Empty{},
  1154  												},
  1155  											},
  1156  										},
  1157  									},
  1158  								},
  1159  							},
  1160  						},
  1161  					},
  1162  				},
  1163  				Providers: map[string]*envoy_jwt.JwtProvider{
  1164  					"origins-0": {
  1165  						Issuer: "https://secret.foo.com",
  1166  						JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
  1167  							LocalJwks: &core.DataSource{
  1168  								Specifier: &core.DataSource_InlineString{
  1169  									InlineString: model.FakeJwks,
  1170  								},
  1171  							},
  1172  						},
  1173  						Forward:           false,
  1174  						PayloadInMetadata: "https://secret.foo.com",
  1175  					},
  1176  				},
  1177  				BypassCorsPreflight: true,
  1178  			},
  1179  		},
  1180  		{
  1181  			name: "Unreachable Jwks URI",
  1182  			in: []*v1beta1.JWTRule{
  1183  				{
  1184  					Issuer:  "https://secret.foo.com",
  1185  					JwksUri: "http://site.not.exist",
  1186  				},
  1187  			},
  1188  			expected: &envoy_jwt.JwtAuthentication{
  1189  				Rules: []*envoy_jwt.RequirementRule{
  1190  					{
  1191  						Match: &route.RouteMatch{
  1192  							PathSpecifier: &route.RouteMatch_Prefix{
  1193  								Prefix: "/",
  1194  							},
  1195  						},
  1196  						RequirementType: &envoy_jwt.RequirementRule_Requires{
  1197  							Requires: &envoy_jwt.JwtRequirement{
  1198  								RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{
  1199  									RequiresAny: &envoy_jwt.JwtRequirementOrList{
  1200  										Requirements: []*envoy_jwt.JwtRequirement{
  1201  											{
  1202  												RequiresType: &envoy_jwt.JwtRequirement_ProviderName{
  1203  													ProviderName: "origins-0",
  1204  												},
  1205  											},
  1206  											{
  1207  												RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{
  1208  													AllowMissing: &emptypb.Empty{},
  1209  												},
  1210  											},
  1211  										},
  1212  									},
  1213  								},
  1214  							},
  1215  						},
  1216  					},
  1217  				},
  1218  				Providers: map[string]*envoy_jwt.JwtProvider{
  1219  					"origins-0": {
  1220  						Issuer: "https://secret.foo.com",
  1221  						JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{
  1222  							LocalJwks: &core.DataSource{
  1223  								Specifier: &core.DataSource_InlineString{
  1224  									InlineString: model.FakeJwks,
  1225  								},
  1226  							},
  1227  						},
  1228  						Forward:           false,
  1229  						PayloadInMetadata: "https://secret.foo.com",
  1230  					},
  1231  				},
  1232  				BypassCorsPreflight: true,
  1233  			},
  1234  		},
  1235  	}
  1236  
  1237  	push := &model.PushContext{}
  1238  	push.JwtKeyResolver = model.NewJwksResolver(
  1239  		model.JwtPubKeyEvictionDuration, model.JwtPubKeyRefreshInterval,
  1240  		model.JwtPubKeyRefreshIntervalOnFailure, 10*time.Millisecond)
  1241  	defer push.JwtKeyResolver.Close()
  1242  
  1243  	for _, c := range cases {
  1244  		t.Run(c.name, func(t *testing.T) {
  1245  			if got := convertToEnvoyJwtConfig(c.in, push, false, false); !reflect.DeepEqual(c.expected, got) {
  1246  				t.Errorf("got:\n%s\nwanted:\n%s\n", spew.Sdump(got), spew.Sdump(c.expected))
  1247  			}
  1248  		})
  1249  	}
  1250  }
  1251  
  1252  func humanReadableAuthnFilterDump(filter *hcm.HttpFilter) string {
  1253  	if filter == nil {
  1254  		return "<nil>"
  1255  	}
  1256  	config := &authn_filter.FilterConfig{}
  1257  	filter.GetTypedConfig().UnmarshalTo(config)
  1258  	return spew.Sdump(config)
  1259  }
  1260  
  1261  func TestAuthnFilterConfig(t *testing.T) {
  1262  	ms, err := test.StartNewServer()
  1263  	if err != nil {
  1264  		t.Fatal("failed to start a mock server")
  1265  	}
  1266  	jwksURI := ms.URL + "/oauth2/v3/certs"
  1267  
  1268  	cases := []struct {
  1269  		name       string
  1270  		forSidecar bool
  1271  		jwtIn      []*config.Config
  1272  		peerIn     []*config.Config
  1273  		expected   *hcm.HttpFilter
  1274  	}{
  1275  		{
  1276  			name:     "no-policy",
  1277  			expected: nil,
  1278  		},
  1279  		{
  1280  			name: "beta-jwt",
  1281  			jwtIn: []*config.Config{
  1282  				{
  1283  					Spec: &v1beta1.RequestAuthentication{
  1284  						JwtRules: []*v1beta1.JWTRule{
  1285  							{
  1286  								Issuer:  "https://secret.foo.com",
  1287  								JwksUri: jwksURI,
  1288  							},
  1289  						},
  1290  					},
  1291  				},
  1292  			},
  1293  			expected: &hcm.HttpFilter{
  1294  				Name: "istio_authn",
  1295  				ConfigType: &hcm.HttpFilter_TypedConfig{
  1296  					TypedConfig: protoconv.MessageToAny(&authn_filter.FilterConfig{
  1297  						SkipValidateTrustDomain: true,
  1298  						Policy: &authn_alpha.Policy{
  1299  							Origins: []*authn_alpha.OriginAuthenticationMethod{
  1300  								{
  1301  									Jwt: &authn_alpha.Jwt{
  1302  										Issuer: "https://secret.foo.com",
  1303  									},
  1304  								},
  1305  							},
  1306  							OriginIsOptional: true,
  1307  							PrincipalBinding: authn_alpha.PrincipalBinding_USE_ORIGIN,
  1308  						},
  1309  					}),
  1310  				},
  1311  			},
  1312  		},
  1313  		{
  1314  			name:       "beta-jwt-for-sidecar",
  1315  			forSidecar: true,
  1316  			jwtIn: []*config.Config{
  1317  				{
  1318  					Spec: &v1beta1.RequestAuthentication{
  1319  						JwtRules: []*v1beta1.JWTRule{
  1320  							{
  1321  								Issuer:  "https://secret.foo.com",
  1322  								JwksUri: jwksURI,
  1323  							},
  1324  						},
  1325  					},
  1326  				},
  1327  			},
  1328  			expected: &hcm.HttpFilter{
  1329  				Name: "istio_authn",
  1330  				ConfigType: &hcm.HttpFilter_TypedConfig{
  1331  					TypedConfig: protoconv.MessageToAny(&authn_filter.FilterConfig{
  1332  						SkipValidateTrustDomain: true,
  1333  						DisableClearRouteCache:  true,
  1334  						Policy: &authn_alpha.Policy{
  1335  							Origins: []*authn_alpha.OriginAuthenticationMethod{
  1336  								{
  1337  									Jwt: &authn_alpha.Jwt{
  1338  										Issuer: "https://secret.foo.com",
  1339  									},
  1340  								},
  1341  							},
  1342  							OriginIsOptional: true,
  1343  							PrincipalBinding: authn_alpha.PrincipalBinding_USE_ORIGIN,
  1344  						},
  1345  					}),
  1346  				},
  1347  			},
  1348  		},
  1349  		{
  1350  			name: "multi-beta-jwt",
  1351  			jwtIn: []*config.Config{
  1352  				{
  1353  					Spec: &v1beta1.RequestAuthentication{
  1354  						JwtRules: []*v1beta1.JWTRule{
  1355  							{
  1356  								Issuer:  "https://secret.bar.com",
  1357  								JwksUri: jwksURI,
  1358  							},
  1359  						},
  1360  					},
  1361  				},
  1362  				{
  1363  					Spec: &v1beta1.RequestAuthentication{},
  1364  				},
  1365  				{
  1366  					Spec: &v1beta1.RequestAuthentication{
  1367  						JwtRules: []*v1beta1.JWTRule{
  1368  							{
  1369  								Issuer: "https://secret.foo.com",
  1370  								Jwks:   "jwks-inline-data",
  1371  							},
  1372  						},
  1373  					},
  1374  				},
  1375  			},
  1376  			expected: &hcm.HttpFilter{
  1377  				Name: "istio_authn",
  1378  				ConfigType: &hcm.HttpFilter_TypedConfig{
  1379  					TypedConfig: protoconv.MessageToAny(&authn_filter.FilterConfig{
  1380  						SkipValidateTrustDomain: true,
  1381  						Policy: &authn_alpha.Policy{
  1382  							Origins: []*authn_alpha.OriginAuthenticationMethod{
  1383  								{
  1384  									Jwt: &authn_alpha.Jwt{
  1385  										Issuer: "https://secret.bar.com",
  1386  									},
  1387  								},
  1388  								{
  1389  									Jwt: &authn_alpha.Jwt{
  1390  										Issuer: "https://secret.foo.com",
  1391  									},
  1392  								},
  1393  							},
  1394  							OriginIsOptional: true,
  1395  							PrincipalBinding: authn_alpha.PrincipalBinding_USE_ORIGIN,
  1396  						},
  1397  					}),
  1398  				},
  1399  			},
  1400  		},
  1401  		{
  1402  			name: "multi-beta-jwt-sort-by-issuer-again",
  1403  			jwtIn: []*config.Config{
  1404  				{
  1405  					Spec: &v1beta1.RequestAuthentication{
  1406  						JwtRules: []*v1beta1.JWTRule{
  1407  							{
  1408  								Issuer:  "https://secret.foo.com",
  1409  								JwksUri: jwksURI,
  1410  							},
  1411  						},
  1412  					},
  1413  				},
  1414  				{
  1415  					Spec: &v1beta1.RequestAuthentication{},
  1416  				},
  1417  				{
  1418  					Spec: &v1beta1.RequestAuthentication{
  1419  						JwtRules: []*v1beta1.JWTRule{
  1420  							{
  1421  								Issuer: "https://secret.bar.com",
  1422  								Jwks:   "jwks-inline-data",
  1423  							},
  1424  						},
  1425  					},
  1426  				},
  1427  			},
  1428  			expected: &hcm.HttpFilter{
  1429  				Name: "istio_authn",
  1430  				ConfigType: &hcm.HttpFilter_TypedConfig{
  1431  					TypedConfig: protoconv.MessageToAny(&authn_filter.FilterConfig{
  1432  						SkipValidateTrustDomain: true,
  1433  						Policy: &authn_alpha.Policy{
  1434  							Origins: []*authn_alpha.OriginAuthenticationMethod{
  1435  								{
  1436  									Jwt: &authn_alpha.Jwt{
  1437  										Issuer: "https://secret.bar.com",
  1438  									},
  1439  								},
  1440  								{
  1441  									Jwt: &authn_alpha.Jwt{
  1442  										Issuer: "https://secret.foo.com",
  1443  									},
  1444  								},
  1445  							},
  1446  							OriginIsOptional: true,
  1447  							PrincipalBinding: authn_alpha.PrincipalBinding_USE_ORIGIN,
  1448  						},
  1449  					}),
  1450  				},
  1451  			},
  1452  		},
  1453  		{
  1454  			name: "beta-mtls",
  1455  			peerIn: []*config.Config{
  1456  				{
  1457  					Spec: &v1beta1.PeerAuthentication{
  1458  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1459  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  1460  						},
  1461  					},
  1462  				},
  1463  			},
  1464  			expected: nil,
  1465  		},
  1466  		{
  1467  			name: "beta-mtls-disable",
  1468  			peerIn: []*config.Config{
  1469  				{
  1470  					Spec: &v1beta1.PeerAuthentication{
  1471  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1472  							Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE,
  1473  						},
  1474  					},
  1475  				},
  1476  			},
  1477  			expected: nil,
  1478  		},
  1479  		{
  1480  			name: "beta-mtls-skip-trust-domain",
  1481  			peerIn: []*config.Config{
  1482  				{
  1483  					Spec: &v1beta1.PeerAuthentication{
  1484  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1485  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  1486  						},
  1487  					},
  1488  				},
  1489  			},
  1490  			expected: nil,
  1491  		},
  1492  	}
  1493  	for _, c := range cases {
  1494  		t.Run(c.name, func(t *testing.T) {
  1495  			got := newPolicyApplier("root-namespace", c.jwtIn, c.peerIn, &model.PushContext{}).AuthNFilter(c.forSidecar)
  1496  			if !reflect.DeepEqual(c.expected, got) {
  1497  				t.Errorf("got:\n%v\nwanted:\n%v\n", humanReadableAuthnFilterDump(got), humanReadableAuthnFilterDump(c.expected))
  1498  			}
  1499  		})
  1500  	}
  1501  }
  1502  
  1503  func TestInboundMTLSSettings(t *testing.T) {
  1504  	now := time.Now()
  1505  	tlsContext := &tls.DownstreamTlsContext{
  1506  		CommonTlsContext: &tls.CommonTlsContext{
  1507  			TlsCertificateSdsSecretConfigs: []*tls.SdsSecretConfig{
  1508  				{
  1509  					Name: "default",
  1510  					SdsConfig: &core.ConfigSource{
  1511  						ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{
  1512  							ApiConfigSource: &core.ApiConfigSource{
  1513  								ApiType:                   core.ApiConfigSource_GRPC,
  1514  								SetNodeOnFirstMessageOnly: true,
  1515  								TransportApiVersion:       core.ApiVersion_V3,
  1516  								GrpcServices: []*core.GrpcService{
  1517  									{
  1518  										TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
  1519  											EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"},
  1520  										},
  1521  									},
  1522  								},
  1523  							},
  1524  						},
  1525  						InitialFetchTimeout: durationpb.New(time.Second * 0),
  1526  						ResourceApiVersion:  core.ApiVersion_V3,
  1527  					},
  1528  				},
  1529  			},
  1530  			ValidationContextType: &tls.CommonTlsContext_CombinedValidationContext{
  1531  				CombinedValidationContext: &tls.CommonTlsContext_CombinedCertificateValidationContext{
  1532  					DefaultValidationContext: &tls.CertificateValidationContext{},
  1533  					ValidationContextSdsSecretConfig: &tls.SdsSecretConfig{
  1534  						Name: "ROOTCA",
  1535  						SdsConfig: &core.ConfigSource{
  1536  							ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{
  1537  								ApiConfigSource: &core.ApiConfigSource{
  1538  									ApiType:                   core.ApiConfigSource_GRPC,
  1539  									SetNodeOnFirstMessageOnly: true,
  1540  									TransportApiVersion:       core.ApiVersion_V3,
  1541  									GrpcServices: []*core.GrpcService{
  1542  										{
  1543  											TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
  1544  												EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"},
  1545  											},
  1546  										},
  1547  									},
  1548  								},
  1549  							},
  1550  							InitialFetchTimeout: durationpb.New(time.Second * 0),
  1551  							ResourceApiVersion:  core.ApiVersion_V3,
  1552  						},
  1553  					},
  1554  				},
  1555  			},
  1556  			AlpnProtocols: []string{"istio-peer-exchange", "h2", "http/1.1"},
  1557  			TlsParams: &tls.TlsParameters{
  1558  				TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2,
  1559  				TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3,
  1560  				CipherSuites: []string{
  1561  					"ECDHE-ECDSA-AES256-GCM-SHA384",
  1562  					"ECDHE-RSA-AES256-GCM-SHA384",
  1563  					"ECDHE-ECDSA-AES128-GCM-SHA256",
  1564  					"ECDHE-RSA-AES128-GCM-SHA256",
  1565  					"AES256-GCM-SHA384",
  1566  					"AES128-GCM-SHA256",
  1567  				},
  1568  			},
  1569  		},
  1570  		RequireClientCertificate: protovalue.BoolTrue,
  1571  	}
  1572  	tlsContextHTTP := proto.Clone(tlsContext).(*tls.DownstreamTlsContext)
  1573  	tlsContextHTTP.CommonTlsContext.AlpnProtocols = []string{"h2", "http/1.1"}
  1574  
  1575  	expectedStrict := MTLSSettings{
  1576  		Port: 8080,
  1577  		Mode: model.MTLSStrict,
  1578  		TCP:  tlsContext,
  1579  		HTTP: tlsContextHTTP,
  1580  	}
  1581  	expectedPermissive := MTLSSettings{
  1582  		Port: 8080,
  1583  		Mode: model.MTLSPermissive,
  1584  		TCP:  tlsContext,
  1585  		HTTP: tlsContextHTTP,
  1586  	}
  1587  
  1588  	cases := []struct {
  1589  		name         string
  1590  		peerPolicies []*config.Config
  1591  		expected     MTLSSettings
  1592  	}{
  1593  		{
  1594  			name:     "No policy - behave as permissive",
  1595  			expected: expectedPermissive,
  1596  		},
  1597  		{
  1598  			name: "Single policy - disable mode",
  1599  			peerPolicies: []*config.Config{
  1600  				{
  1601  					Spec: &v1beta1.PeerAuthentication{
  1602  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1603  							Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE,
  1604  						},
  1605  					},
  1606  				},
  1607  			},
  1608  			expected: MTLSSettings{Port: 8080, Mode: model.MTLSDisable},
  1609  		},
  1610  		{
  1611  			name: "Single policy - permissive mode",
  1612  			peerPolicies: []*config.Config{
  1613  				{
  1614  					Spec: &v1beta1.PeerAuthentication{
  1615  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1616  							Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE,
  1617  						},
  1618  					},
  1619  				},
  1620  			},
  1621  			expected: expectedPermissive,
  1622  		},
  1623  		{
  1624  			name: "Single policy - strict mode",
  1625  			peerPolicies: []*config.Config{
  1626  				{
  1627  					Spec: &v1beta1.PeerAuthentication{
  1628  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1629  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  1630  						},
  1631  					},
  1632  				},
  1633  			},
  1634  			expected: expectedStrict,
  1635  		},
  1636  		{
  1637  			name: "Multiple policies resolved to STRICT",
  1638  			peerPolicies: []*config.Config{
  1639  				{
  1640  					Meta: config.Meta{
  1641  						Name:              "now",
  1642  						Namespace:         "my-ns",
  1643  						CreationTimestamp: now,
  1644  					},
  1645  					Spec: &v1beta1.PeerAuthentication{
  1646  						Selector: &type_beta.WorkloadSelector{
  1647  							MatchLabels: map[string]string{
  1648  								"app": "foo",
  1649  							},
  1650  						},
  1651  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1652  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  1653  						},
  1654  					},
  1655  				},
  1656  				{
  1657  					Meta: config.Meta{
  1658  						Name:              "later",
  1659  						Namespace:         "my-ns",
  1660  						CreationTimestamp: now.Add(time.Second),
  1661  					},
  1662  					Spec: &v1beta1.PeerAuthentication{
  1663  						Selector: &type_beta.WorkloadSelector{
  1664  							MatchLabels: map[string]string{
  1665  								"app": "foo",
  1666  							},
  1667  						},
  1668  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1669  							Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE,
  1670  						},
  1671  					},
  1672  				},
  1673  			},
  1674  			expected: expectedStrict,
  1675  		},
  1676  		{
  1677  			name: "Multiple policies resolved to PERMISSIVE",
  1678  			peerPolicies: []*config.Config{
  1679  				{
  1680  					Meta: config.Meta{
  1681  						Name:              "now",
  1682  						Namespace:         "my-ns",
  1683  						CreationTimestamp: now,
  1684  					},
  1685  					Spec: &v1beta1.PeerAuthentication{
  1686  						Selector: &type_beta.WorkloadSelector{
  1687  							MatchLabels: map[string]string{
  1688  								"app": "foo",
  1689  							},
  1690  						},
  1691  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1692  							Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE,
  1693  						},
  1694  					},
  1695  				},
  1696  				{
  1697  					Meta: config.Meta{
  1698  						Name:              "earlier",
  1699  						Namespace:         "my-ns",
  1700  						CreationTimestamp: now.Add(time.Second * -1),
  1701  					},
  1702  					Spec: &v1beta1.PeerAuthentication{
  1703  						Selector: &type_beta.WorkloadSelector{
  1704  							MatchLabels: map[string]string{
  1705  								"app": "foo",
  1706  							},
  1707  						},
  1708  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1709  							Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE,
  1710  						},
  1711  					},
  1712  				},
  1713  			},
  1714  			expected: expectedPermissive,
  1715  		},
  1716  		{
  1717  			name: "Port level hit",
  1718  			peerPolicies: []*config.Config{
  1719  				{
  1720  					Spec: &v1beta1.PeerAuthentication{
  1721  						Selector: &type_beta.WorkloadSelector{
  1722  							MatchLabels: map[string]string{
  1723  								"app": "foo",
  1724  							},
  1725  						},
  1726  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1727  							Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE,
  1728  						},
  1729  						PortLevelMtls: map[uint32]*v1beta1.PeerAuthentication_MutualTLS{
  1730  							8080: {
  1731  								Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  1732  							},
  1733  						},
  1734  					},
  1735  				},
  1736  			},
  1737  			expected: expectedStrict,
  1738  		},
  1739  		{
  1740  			name: "Port level miss",
  1741  			peerPolicies: []*config.Config{
  1742  				{
  1743  					Spec: &v1beta1.PeerAuthentication{
  1744  						Selector: &type_beta.WorkloadSelector{
  1745  							MatchLabels: map[string]string{
  1746  								"app": "foo",
  1747  							},
  1748  						},
  1749  						PortLevelMtls: map[uint32]*v1beta1.PeerAuthentication_MutualTLS{
  1750  							7070: {
  1751  								Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  1752  							},
  1753  						},
  1754  					},
  1755  				},
  1756  			},
  1757  			expected: expectedPermissive,
  1758  		},
  1759  	}
  1760  
  1761  	testNode := &model.Proxy{
  1762  		Labels: map[string]string{
  1763  			"app": "foo",
  1764  		},
  1765  		Metadata: &model.NodeMetadata{},
  1766  	}
  1767  	for _, tc := range cases {
  1768  		t.Run(tc.name, func(t *testing.T) {
  1769  			got := newPolicyApplier("root-namespace", nil, tc.peerPolicies, &model.PushContext{}).InboundMTLSSettings(
  1770  				8080,
  1771  				testNode,
  1772  				[]string{},
  1773  				NoOverride,
  1774  			)
  1775  			if diff := cmp.Diff(tc.expected, got, protocmp.Transform()); diff != "" {
  1776  				t.Errorf("unexpected filter chains: %v", diff)
  1777  			}
  1778  		})
  1779  	}
  1780  }
  1781  
  1782  func TestComposePeerAuthentication(t *testing.T) {
  1783  	now := time.Now()
  1784  	tests := []struct {
  1785  		name    string
  1786  		configs []*config.Config
  1787  		want    MergedPeerAuthentication
  1788  	}{
  1789  		{
  1790  			name:    "no config",
  1791  			configs: []*config.Config{},
  1792  			want: MergedPeerAuthentication{
  1793  				Mode: model.MTLSPermissive,
  1794  			},
  1795  		},
  1796  		{
  1797  			name: "mesh only",
  1798  			configs: []*config.Config{
  1799  				{
  1800  					Meta: config.Meta{
  1801  						Name:      "default",
  1802  						Namespace: "root-namespace",
  1803  					},
  1804  					Spec: &v1beta1.PeerAuthentication{
  1805  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1806  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  1807  						},
  1808  					},
  1809  				},
  1810  			},
  1811  			want: MergedPeerAuthentication{
  1812  				Mode: model.MTLSStrict,
  1813  			},
  1814  		},
  1815  		{
  1816  			name: "mesh vs namespace",
  1817  			configs: []*config.Config{
  1818  				{
  1819  					Meta: config.Meta{
  1820  						Name:      "default",
  1821  						Namespace: "root-namespace",
  1822  					},
  1823  					Spec: &v1beta1.PeerAuthentication{
  1824  						Selector: &type_beta.WorkloadSelector{
  1825  							MatchLabels: map[string]string{},
  1826  						},
  1827  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1828  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  1829  						},
  1830  					},
  1831  				},
  1832  				{
  1833  					Meta: config.Meta{
  1834  						Name:      "default",
  1835  						Namespace: "my-ns",
  1836  					},
  1837  					Spec: &v1beta1.PeerAuthentication{
  1838  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1839  							Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE,
  1840  						},
  1841  					},
  1842  				},
  1843  			},
  1844  			want: MergedPeerAuthentication{
  1845  				Mode: model.MTLSPermissive,
  1846  			},
  1847  		},
  1848  		{
  1849  			name: "ignore non-emptypb selector in root namespace",
  1850  			configs: []*config.Config{
  1851  				{
  1852  					Meta: config.Meta{
  1853  						Name:      "default",
  1854  						Namespace: "root-namespace",
  1855  					},
  1856  					Spec: &v1beta1.PeerAuthentication{
  1857  						Selector: &type_beta.WorkloadSelector{
  1858  							MatchLabels: map[string]string{
  1859  								"app": "foo",
  1860  							},
  1861  						},
  1862  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1863  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  1864  						},
  1865  					},
  1866  				},
  1867  			},
  1868  			want: MergedPeerAuthentication{
  1869  				Mode: model.MTLSPermissive,
  1870  			},
  1871  		},
  1872  		{
  1873  			name: "workload vs namespace config",
  1874  			configs: []*config.Config{
  1875  				{
  1876  					Meta: config.Meta{
  1877  						Name:      "default",
  1878  						Namespace: "my-ns",
  1879  					},
  1880  					Spec: &v1beta1.PeerAuthentication{},
  1881  				},
  1882  				{
  1883  					Meta: config.Meta{
  1884  						Name:      "foo",
  1885  						Namespace: "my-ns",
  1886  					},
  1887  					Spec: &v1beta1.PeerAuthentication{
  1888  						Selector: &type_beta.WorkloadSelector{
  1889  							MatchLabels: map[string]string{
  1890  								"app": "foo",
  1891  							},
  1892  						},
  1893  					},
  1894  				},
  1895  			},
  1896  			want: MergedPeerAuthentication{
  1897  				Mode: model.MTLSPermissive,
  1898  			},
  1899  		},
  1900  		{
  1901  			name: "workload vs mesh config",
  1902  			configs: []*config.Config{
  1903  				{
  1904  					Meta: config.Meta{
  1905  						Name:      "default",
  1906  						Namespace: "my-ns",
  1907  					},
  1908  					Spec: &v1beta1.PeerAuthentication{
  1909  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1910  							Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE,
  1911  						},
  1912  					},
  1913  				},
  1914  				{
  1915  					Meta: config.Meta{
  1916  						Name:      "default",
  1917  						Namespace: "root-namespace",
  1918  					},
  1919  					Spec: &v1beta1.PeerAuthentication{
  1920  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1921  							Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE,
  1922  						},
  1923  					},
  1924  				},
  1925  			},
  1926  			want: MergedPeerAuthentication{
  1927  				Mode: model.MTLSPermissive,
  1928  			},
  1929  		},
  1930  		{
  1931  			name: "multiple mesh policy",
  1932  			configs: []*config.Config{
  1933  				{
  1934  					Meta: config.Meta{
  1935  						Name:              "now",
  1936  						Namespace:         "root-namespace",
  1937  						CreationTimestamp: now,
  1938  					},
  1939  					Spec: &v1beta1.PeerAuthentication{
  1940  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1941  							Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE,
  1942  						},
  1943  					},
  1944  				},
  1945  				{
  1946  					Meta: config.Meta{
  1947  						Name:              "second ago",
  1948  						Namespace:         "root-namespace",
  1949  						CreationTimestamp: now.Add(time.Second * -1),
  1950  					},
  1951  					Spec: &v1beta1.PeerAuthentication{
  1952  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1953  							Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE,
  1954  						},
  1955  					},
  1956  				},
  1957  				{
  1958  					Meta: config.Meta{
  1959  						Name:              "second later",
  1960  						Namespace:         "root-namespace",
  1961  						CreationTimestamp: now.Add(time.Second * -1),
  1962  					},
  1963  					Spec: &v1beta1.PeerAuthentication{
  1964  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1965  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  1966  						},
  1967  					},
  1968  				},
  1969  			},
  1970  			want: MergedPeerAuthentication{
  1971  				Mode: model.MTLSPermissive,
  1972  			},
  1973  		},
  1974  		{
  1975  			name: "multiple namespace policy",
  1976  			configs: []*config.Config{
  1977  				{
  1978  					Meta: config.Meta{
  1979  						Name:              "now",
  1980  						Namespace:         "my-ns",
  1981  						CreationTimestamp: now,
  1982  					},
  1983  					Spec: &v1beta1.PeerAuthentication{
  1984  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1985  							Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE,
  1986  						},
  1987  					},
  1988  				},
  1989  				{
  1990  					Meta: config.Meta{
  1991  						Name:              "second ago",
  1992  						Namespace:         "my-ns",
  1993  						CreationTimestamp: now.Add(time.Second * -1),
  1994  					},
  1995  					Spec: &v1beta1.PeerAuthentication{
  1996  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  1997  							Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE,
  1998  						},
  1999  					},
  2000  				},
  2001  				{
  2002  					Meta: config.Meta{
  2003  						Name:              "second later",
  2004  						Namespace:         "my-ns",
  2005  						CreationTimestamp: now.Add(time.Second * -1),
  2006  					},
  2007  					Spec: &v1beta1.PeerAuthentication{
  2008  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  2009  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  2010  						},
  2011  					},
  2012  				},
  2013  			},
  2014  			want: MergedPeerAuthentication{
  2015  				Mode: model.MTLSPermissive,
  2016  			},
  2017  		},
  2018  		{
  2019  			name: "multiple workload policy",
  2020  			configs: []*config.Config{
  2021  				{
  2022  					Meta: config.Meta{
  2023  						Name:              "now",
  2024  						Namespace:         "my-ns",
  2025  						CreationTimestamp: now,
  2026  					},
  2027  					Spec: &v1beta1.PeerAuthentication{
  2028  						Selector: &type_beta.WorkloadSelector{
  2029  							MatchLabels: map[string]string{
  2030  								"app": "foo",
  2031  							},
  2032  						},
  2033  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  2034  							Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE,
  2035  						},
  2036  					},
  2037  				},
  2038  				{
  2039  					Meta: config.Meta{
  2040  						Name:              "second ago",
  2041  						Namespace:         "my-ns",
  2042  						CreationTimestamp: now.Add(time.Second * -1),
  2043  					},
  2044  					Spec: &v1beta1.PeerAuthentication{
  2045  						Selector: &type_beta.WorkloadSelector{
  2046  							MatchLabels: map[string]string{
  2047  								"app": "foo",
  2048  							},
  2049  						},
  2050  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  2051  							Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE,
  2052  						},
  2053  					},
  2054  				},
  2055  				{
  2056  					Meta: config.Meta{
  2057  						Name:              "second later",
  2058  						Namespace:         "my-ns",
  2059  						CreationTimestamp: now.Add(time.Second * -1),
  2060  					},
  2061  					Spec: &v1beta1.PeerAuthentication{
  2062  						Selector: &type_beta.WorkloadSelector{
  2063  							MatchLabels: map[string]string{
  2064  								"stage": "prod",
  2065  							},
  2066  						},
  2067  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  2068  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  2069  						},
  2070  					},
  2071  				},
  2072  			},
  2073  			want: MergedPeerAuthentication{
  2074  				Mode: model.MTLSPermissive,
  2075  			},
  2076  		},
  2077  		{
  2078  			name: "inheritance: default mesh",
  2079  			configs: []*config.Config{
  2080  				{
  2081  					Meta: config.Meta{
  2082  						Name:      "default",
  2083  						Namespace: "root-namespace",
  2084  					},
  2085  					Spec: &v1beta1.PeerAuthentication{
  2086  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  2087  							Mode: v1beta1.PeerAuthentication_MutualTLS_UNSET,
  2088  						},
  2089  					},
  2090  				},
  2091  			},
  2092  			want: MergedPeerAuthentication{
  2093  				Mode: model.MTLSPermissive,
  2094  			},
  2095  		},
  2096  		{
  2097  			name: "inheritance: mesh to workload",
  2098  			configs: []*config.Config{
  2099  				{
  2100  					Meta: config.Meta{
  2101  						Name:      "default",
  2102  						Namespace: "root-namespace",
  2103  					},
  2104  					Spec: &v1beta1.PeerAuthentication{
  2105  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  2106  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  2107  						},
  2108  					},
  2109  				},
  2110  				{
  2111  					Meta: config.Meta{
  2112  						Name:      "foo",
  2113  						Namespace: "my-ns",
  2114  					},
  2115  					Spec: &v1beta1.PeerAuthentication{
  2116  						Selector: &type_beta.WorkloadSelector{
  2117  							MatchLabels: map[string]string{
  2118  								"app": "foo",
  2119  							},
  2120  						},
  2121  					},
  2122  				},
  2123  			},
  2124  			want: MergedPeerAuthentication{
  2125  				Mode: model.MTLSStrict,
  2126  			},
  2127  		},
  2128  		{
  2129  			name: "inheritance: namespace to workload",
  2130  			configs: []*config.Config{
  2131  				{
  2132  					Meta: config.Meta{
  2133  						Name:      "default",
  2134  						Namespace: "my-ns",
  2135  					},
  2136  					Spec: &v1beta1.PeerAuthentication{
  2137  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  2138  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  2139  						},
  2140  					},
  2141  				},
  2142  				{
  2143  					Meta: config.Meta{
  2144  						Name:      "foo",
  2145  						Namespace: "my-ns",
  2146  					},
  2147  					Spec: &v1beta1.PeerAuthentication{
  2148  						Selector: &type_beta.WorkloadSelector{
  2149  							MatchLabels: map[string]string{
  2150  								"app": "foo",
  2151  							},
  2152  						},
  2153  					},
  2154  				},
  2155  			},
  2156  			want: MergedPeerAuthentication{
  2157  				Mode: model.MTLSStrict,
  2158  			},
  2159  		},
  2160  		{
  2161  			name: "inheritance: mesh to namespace to workload",
  2162  			configs: []*config.Config{
  2163  				{
  2164  					Meta: config.Meta{
  2165  						Name:      "default",
  2166  						Namespace: "root-namespace",
  2167  					},
  2168  					Spec: &v1beta1.PeerAuthentication{
  2169  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  2170  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  2171  						},
  2172  					},
  2173  				},
  2174  				{
  2175  					Meta: config.Meta{
  2176  						Name:      "default",
  2177  						Namespace: "my-ns",
  2178  					},
  2179  					Spec: &v1beta1.PeerAuthentication{
  2180  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  2181  							Mode: v1beta1.PeerAuthentication_MutualTLS_UNSET,
  2182  						},
  2183  					},
  2184  				},
  2185  				{
  2186  					Meta: config.Meta{
  2187  						Name:      "foo",
  2188  						Namespace: "my-ns",
  2189  					},
  2190  					Spec: &v1beta1.PeerAuthentication{
  2191  						Selector: &type_beta.WorkloadSelector{
  2192  							MatchLabels: map[string]string{
  2193  								"app": "foo",
  2194  							},
  2195  						},
  2196  					},
  2197  				},
  2198  			},
  2199  			want: MergedPeerAuthentication{
  2200  				Mode: model.MTLSStrict,
  2201  			},
  2202  		},
  2203  		{
  2204  			name: "port level",
  2205  			configs: []*config.Config{
  2206  				{
  2207  					Meta: config.Meta{
  2208  						Name:      "default",
  2209  						Namespace: "root-namespace",
  2210  					},
  2211  					Spec: &v1beta1.PeerAuthentication{
  2212  						Mtls: &v1beta1.PeerAuthentication_MutualTLS{
  2213  							Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT,
  2214  						},
  2215  					},
  2216  				},
  2217  				{
  2218  					Meta: config.Meta{
  2219  						Name:      "foo",
  2220  						Namespace: "my-ns",
  2221  					},
  2222  					Spec: &v1beta1.PeerAuthentication{
  2223  						Selector: &type_beta.WorkloadSelector{
  2224  							MatchLabels: map[string]string{
  2225  								"app": "foo",
  2226  							},
  2227  						},
  2228  						PortLevelMtls: map[uint32]*v1beta1.PeerAuthentication_MutualTLS{
  2229  							80: {
  2230  								Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE,
  2231  							},
  2232  							90: {
  2233  								Mode: v1beta1.PeerAuthentication_MutualTLS_UNSET,
  2234  							},
  2235  							100: {},
  2236  						},
  2237  					},
  2238  				},
  2239  			},
  2240  			want: MergedPeerAuthentication{
  2241  				Mode: model.MTLSStrict,
  2242  				PerPort: map[uint32]model.MutualTLSMode{
  2243  					80:  model.MTLSDisable,
  2244  					90:  model.MTLSStrict,
  2245  					100: model.MTLSStrict,
  2246  				},
  2247  			},
  2248  		},
  2249  	}
  2250  	for _, tt := range tests {
  2251  		t.Run(tt.name, func(t *testing.T) {
  2252  			got := ComposePeerAuthentication("root-namespace", tt.configs)
  2253  			assert.Equal(t, got, tt.want)
  2254  		})
  2255  	}
  2256  }