istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/authz_test.go (about)

     1  //go:build integ
     2  // +build integ
     3  
     4  // Copyright Istio Authors
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package security
    19  
    20  import (
    21  	"fmt"
    22  	"net/http"
    23  	"testing"
    24  
    25  	"istio.io/istio/pkg/config/protocol"
    26  	"istio.io/istio/pkg/http/headers"
    27  	"istio.io/istio/pkg/test/framework"
    28  	"istio.io/istio/pkg/test/framework/components/authz"
    29  	"istio.io/istio/pkg/test/framework/components/echo"
    30  	"istio.io/istio/pkg/test/framework/components/echo/check"
    31  	"istio.io/istio/pkg/test/framework/components/echo/common/ports"
    32  	"istio.io/istio/pkg/test/framework/components/echo/config"
    33  	"istio.io/istio/pkg/test/framework/components/echo/config/param"
    34  	"istio.io/istio/pkg/test/framework/components/echo/echotest"
    35  	"istio.io/istio/pkg/test/framework/components/echo/match"
    36  	"istio.io/istio/pkg/test/framework/components/istio"
    37  	"istio.io/istio/pkg/test/framework/components/istio/ingress"
    38  	"istio.io/istio/pkg/test/framework/components/namespace"
    39  	"istio.io/istio/pkg/test/framework/label"
    40  	"istio.io/istio/tests/common/jwt"
    41  )
    42  
    43  func TestAuthz_Principal(t *testing.T) {
    44  	framework.NewTest(t).
    45  		Run(func(t framework.TestContext) {
    46  			allowed := apps.Ns1.A
    47  			denied := apps.Ns2.A
    48  
    49  			from := allowed.Append(denied)
    50  			fromMatch := match.AnyServiceName(from.NamespacedNames())
    51  			toMatch := match.Not(fromMatch)
    52  			to := toMatch.GetServiceMatches(apps.Ns1.All)
    53  			fromAndTo := to.Instances().Append(from)
    54  
    55  			config.New(t).
    56  				Source(config.File("testdata/authz/mtls.yaml.tmpl")).
    57  				Source(config.File("testdata/authz/allow-principal.yaml.tmpl").WithParams(
    58  					param.Params{
    59  						"Allowed": allowed,
    60  					})).
    61  				BuildAll(nil, to).
    62  				Apply()
    63  
    64  			newTrafficTest(t, fromAndTo).
    65  				FromMatch(fromMatch).
    66  				ToMatch(toMatch).
    67  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
    68  					allow := allowValue(from.NamespacedName() == allowed.NamespacedName())
    69  
    70  					cases := []struct {
    71  						ports []echo.Port
    72  						path  string
    73  						allow allowValue
    74  					}{
    75  						{
    76  							ports: []echo.Port{ports.GRPC, ports.TCP},
    77  							allow: allow,
    78  						},
    79  						{
    80  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
    81  							path:  "/allow",
    82  							allow: allow,
    83  						},
    84  						{
    85  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
    86  							path:  "/allow?param=value",
    87  							allow: allow,
    88  						},
    89  						{
    90  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
    91  							path:  "/deny",
    92  							allow: false,
    93  						},
    94  						{
    95  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
    96  							path:  "/deny?param=value",
    97  							allow: false,
    98  						},
    99  					}
   100  
   101  					for _, c := range cases {
   102  						newAuthzTest().
   103  							From(from).
   104  							To(to).
   105  							Allow(c.allow).
   106  							Path(c.path).
   107  							BuildAndRunForPorts(t, c.ports...)
   108  					}
   109  				})
   110  		})
   111  }
   112  
   113  func TestAuthz_DenyPrincipal(t *testing.T) {
   114  	framework.NewTest(t).
   115  		Run(func(t framework.TestContext) {
   116  			allowed := apps.Ns1.A
   117  			denied := apps.Ns2.A
   118  
   119  			from := allowed.Append(denied)
   120  			fromMatch := match.AnyServiceName(from.NamespacedNames())
   121  			toMatch := match.Not(fromMatch)
   122  			to := toMatch.GetServiceMatches(apps.Ns1.All)
   123  			fromAndTo := to.Instances().Append(from)
   124  
   125  			config.New(t).
   126  				Source(config.File("testdata/authz/mtls.yaml.tmpl")).
   127  				Source(config.File("testdata/authz/deny-global.yaml.tmpl").WithParams(param.Params{
   128  					param.Namespace.String(): istio.ClaimSystemNamespaceOrFail(t, t),
   129  				})).
   130  				Source(config.File("testdata/authz/deny-principal.yaml.tmpl").WithParams(
   131  					param.Params{
   132  						"Denied": denied,
   133  					})).
   134  				BuildAll(nil, to).
   135  				Apply()
   136  
   137  			newTrafficTest(t, fromAndTo).
   138  				FromMatch(fromMatch).
   139  				ToMatch(toMatch).
   140  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   141  					allow := allowValue(from.NamespacedName() != denied.NamespacedName())
   142  
   143  					cases := []struct {
   144  						ports []echo.Port
   145  						path  string
   146  						allow allowValue
   147  					}{
   148  						{
   149  							ports: []echo.Port{ports.GRPC, ports.TCP},
   150  							allow: allow,
   151  						},
   152  						{
   153  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   154  							path:  "/deny",
   155  							allow: allow,
   156  						},
   157  						{
   158  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   159  							path:  "/deny?param=value",
   160  							allow: allow,
   161  						},
   162  						{
   163  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   164  							path:  "/deny/allow",
   165  							allow: true,
   166  						},
   167  						{
   168  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   169  							path:  "/deny/allow?param=value",
   170  							allow: true,
   171  						},
   172  						{
   173  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   174  							path:  "/allow",
   175  							allow: true,
   176  						},
   177  						{
   178  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   179  							path:  "/allow?param=value",
   180  							allow: true,
   181  						},
   182  						{
   183  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   184  							path:  "/global-deny",
   185  							allow: false,
   186  						},
   187  						{
   188  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   189  							path:  "/global-deny?param=value",
   190  							allow: false,
   191  						},
   192  						{
   193  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   194  							path:  "/global-deny/allow",
   195  							allow: true,
   196  						},
   197  						{
   198  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   199  							path:  "/global-deny/allow?param=value",
   200  							allow: true,
   201  						},
   202  					}
   203  
   204  					for _, c := range cases {
   205  						newAuthzTest().
   206  							From(from).
   207  							To(to).
   208  							Allow(c.allow).
   209  							Path(c.path).
   210  							BuildAndRunForPorts(t, c.ports...)
   211  					}
   212  				})
   213  		})
   214  }
   215  
   216  func TestAuthz_Namespace(t *testing.T) {
   217  	framework.NewTest(t).
   218  		Run(func(t framework.TestContext) {
   219  			// Allow anything from ns1. Any service in ns1 will work as the `from` (just using ns1.A)
   220  			allowed := apps.Ns1.A
   221  			denied := apps.Ns2.A
   222  
   223  			from := allowed.Append(denied)
   224  			fromMatch := match.AnyServiceName(from.NamespacedNames())
   225  			toMatch := match.Not(fromMatch)
   226  			to := toMatch.GetServiceMatches(apps.Ns1AndNs2)
   227  			fromAndTo := to.Instances().Append(from)
   228  
   229  			config.New(t).
   230  				Source(config.File("testdata/authz/mtls.yaml.tmpl")).
   231  				Source(config.File("testdata/authz/allow-namespace.yaml.tmpl").WithParams(
   232  					param.Params{
   233  						"Allowed": allowed,
   234  					})).
   235  				BuildAll(nil, to).
   236  				Apply()
   237  
   238  			newTrafficTest(t, fromAndTo).
   239  				FromMatch(fromMatch).
   240  				ToMatch(toMatch).
   241  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   242  					allow := allowValue(from.Config().Namespace.Name() == allowed.Config().Namespace.Name())
   243  
   244  					cases := []struct {
   245  						ports []echo.Port
   246  						path  string
   247  						allow allowValue
   248  					}{
   249  						{
   250  							ports: []echo.Port{ports.GRPC, ports.TCP},
   251  							allow: allow,
   252  						},
   253  						{
   254  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   255  							path:  "/allow",
   256  							allow: allow,
   257  						},
   258  						{
   259  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   260  							path:  "/allow?param=value",
   261  							allow: allow,
   262  						},
   263  						{
   264  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   265  							path:  "/deny",
   266  							allow: false,
   267  						},
   268  						{
   269  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   270  							path:  "/deny?param=value",
   271  							allow: false,
   272  						},
   273  					}
   274  
   275  					for _, c := range cases {
   276  						newAuthzTest().
   277  							From(from).
   278  							To(to).
   279  							Allow(c.allow).
   280  							Path(c.path).
   281  							BuildAndRunForPorts(t, c.ports...)
   282  					}
   283  				})
   284  		})
   285  }
   286  
   287  func TestAuthz_DenyNamespace(t *testing.T) {
   288  	framework.NewTest(t).
   289  		Run(func(t framework.TestContext) {
   290  			allowed := apps.Ns1.A
   291  			denied := apps.Ns2.A
   292  
   293  			from := allowed.Append(denied)
   294  			fromMatch := match.AnyServiceName(from.NamespacedNames())
   295  			toMatch := match.Not(fromMatch)
   296  			to := toMatch.GetServiceMatches(apps.Ns1AndNs2)
   297  			fromAndTo := to.Instances().Append(from)
   298  
   299  			config.New(t).
   300  				Source(config.File("testdata/authz/mtls.yaml.tmpl")).
   301  				Source(config.File("testdata/authz/deny-global.yaml.tmpl").WithParams(param.Params{
   302  					param.Namespace.String(): istio.ClaimSystemNamespaceOrFail(t, t),
   303  				})).
   304  				Source(config.File("testdata/authz/deny-namespace.yaml.tmpl").WithParams(
   305  					param.Params{
   306  						"Denied": denied,
   307  					})).
   308  				BuildAll(nil, to).
   309  				Apply()
   310  
   311  			newTrafficTest(t, fromAndTo).
   312  				FromMatch(fromMatch).
   313  				ToMatch(toMatch).
   314  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   315  					allow := allowValue(from.Config().Namespace.Name() == allowed.Config().Namespace.Name())
   316  
   317  					cases := []struct {
   318  						ports []echo.Port
   319  						path  string
   320  						allow allowValue
   321  					}{
   322  						{
   323  							ports: []echo.Port{ports.GRPC, ports.TCP},
   324  							allow: allow,
   325  						},
   326  						{
   327  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   328  							path:  "/deny",
   329  							allow: allow,
   330  						},
   331  						{
   332  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   333  							path:  "/deny?param=value",
   334  							allow: allow,
   335  						},
   336  						{
   337  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   338  							path:  "/deny/allow",
   339  							allow: true,
   340  						},
   341  						{
   342  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   343  							path:  "/deny/allow?param=value",
   344  							allow: true,
   345  						},
   346  						{
   347  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   348  							path:  "/allow",
   349  							allow: true,
   350  						},
   351  						{
   352  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   353  							path:  "/allow?param=value",
   354  							allow: true,
   355  						},
   356  						{
   357  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   358  							path:  "/global-deny",
   359  							allow: false,
   360  						},
   361  						{
   362  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   363  							path:  "/global-deny?param=value",
   364  							allow: false,
   365  						},
   366  						{
   367  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   368  							path:  "/global-deny/allow",
   369  							allow: true,
   370  						},
   371  						{
   372  							ports: []echo.Port{ports.HTTP, ports.HTTP2},
   373  							path:  "/global-deny/allow?param=value",
   374  							allow: true,
   375  						},
   376  					}
   377  
   378  					for _, c := range cases {
   379  						newAuthzTest().
   380  							From(from).
   381  							To(to).
   382  							Allow(c.allow).
   383  							Path(c.path).
   384  							BuildAndRunForPorts(t, c.ports...)
   385  					}
   386  				})
   387  		})
   388  }
   389  
   390  func TestAuthz_NotNamespace(t *testing.T) {
   391  	framework.NewTest(t).
   392  		Run(func(t framework.TestContext) {
   393  			allowed := apps.Ns1.A
   394  			denied := apps.Ns2.A
   395  
   396  			from := allowed.Append(denied)
   397  			fromMatch := match.AnyServiceName(from.NamespacedNames())
   398  			toMatch := match.Not(fromMatch)
   399  			to := toMatch.GetServiceMatches(apps.Ns1.All)
   400  			fromAndTo := to.Instances().Append(from)
   401  
   402  			config.New(t).
   403  				Source(config.File("testdata/authz/mtls.yaml.tmpl")).
   404  				Source(config.File("testdata/authz/not-namespace.yaml.tmpl").WithParams(
   405  					param.Params{
   406  						"Allowed": allowed,
   407  					})).
   408  				BuildAll(nil, to).
   409  				Apply()
   410  
   411  			newTrafficTest(t, fromAndTo).
   412  				FromMatch(fromMatch).
   413  				ToMatch(toMatch).
   414  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   415  					allow := allowValue(from.Config().Namespace.Name() == allowed.Config().Namespace.Name())
   416  
   417  					newAuthzTest().
   418  						From(from).
   419  						To(to).
   420  						Allow(allow).
   421  						BuildAndRunForPorts(t, ports.GRPC, ports.TCP, ports.HTTP, ports.HTTP2)
   422  				})
   423  		})
   424  }
   425  
   426  func TestAuthz_NotHost(t *testing.T) {
   427  	framework.NewTest(t).
   428  		Run(func(t framework.TestContext) {
   429  			from := apps.Ns1.A
   430  			fromMatch := match.AnyServiceName(from.NamespacedNames())
   431  			toMatch := match.Not(fromMatch)
   432  			to := toMatch.GetServiceMatches(apps.Ns1.All)
   433  			fromAndTo := to.Instances().Append(from)
   434  
   435  			config.New(t).
   436  				Source(config.File("testdata/authz/not-host.yaml.tmpl").WithParams(param.Params{
   437  					"GatewayIstioLabel": i.Settings().IngressGatewayIstioLabel,
   438  				})).
   439  				BuildAll(nil, to).
   440  				Apply()
   441  
   442  			newTrafficTest(t, fromAndTo).
   443  				FromMatch(fromMatch).
   444  				ToMatch(toMatch).
   445  				RunViaIngress(func(t framework.TestContext, from ingress.Instance, to echo.Target) {
   446  					cases := []struct {
   447  						host  string
   448  						allow allowValue
   449  					}{
   450  						{
   451  							host:  fmt.Sprintf("allow.%s.com", to.Config().Service),
   452  							allow: true,
   453  						},
   454  						{
   455  							host:  fmt.Sprintf("deny.%s.com", to.Config().Service),
   456  							allow: false,
   457  						},
   458  					}
   459  
   460  					for _, c := range cases {
   461  						c := c
   462  						testName := fmt.Sprintf("%s(%s)/http", c.host, c.allow)
   463  						t.NewSubTest(testName).Run(func(t framework.TestContext) {
   464  							wantCode := http.StatusOK
   465  							if !c.allow {
   466  								wantCode = http.StatusForbidden
   467  							}
   468  
   469  							opts := echo.CallOptions{
   470  								Port: echo.Port{
   471  									Protocol: protocol.HTTP,
   472  								},
   473  								HTTP: echo.HTTP{
   474  									Headers: headers.New().WithHost(c.host).Build(),
   475  								},
   476  								Check: check.And(check.NoError(), check.Status(wantCode)),
   477  							}
   478  							from.CallOrFail(t, opts)
   479  						})
   480  					}
   481  				})
   482  		})
   483  }
   484  
   485  func TestAuthz_NotMethod(t *testing.T) {
   486  	// NOTE: negative match for mtls is tested by TestAuthz_DenyPlaintext.
   487  	// Negative match for paths is tested by TestAuthz_DenyPrincipal, TestAuthz_DenyNamespace.
   488  	framework.NewTest(t).
   489  		Run(func(t framework.TestContext) {
   490  			from := apps.Ns1.A
   491  			fromMatch := match.AnyServiceName(from.NamespacedNames())
   492  			toMatch := match.Not(fromMatch)
   493  			to := toMatch.GetServiceMatches(apps.Ns1AndNs2)
   494  			fromAndTo := to.Instances().Append(from)
   495  
   496  			config.New(t).
   497  				Source(config.File("testdata/authz/not-method.yaml.tmpl")).
   498  				BuildAll(nil, to).
   499  				Apply()
   500  
   501  			newTrafficTest(t, fromAndTo).
   502  				FromMatch(fromMatch).
   503  				ToMatch(toMatch).
   504  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   505  					cases := []struct {
   506  						ports  []echo.Port
   507  						method string
   508  						allow  allowValue
   509  					}{
   510  						{
   511  							ports:  []echo.Port{ports.HTTP, ports.HTTP2},
   512  							method: "GET",
   513  							allow:  true,
   514  						},
   515  						{
   516  							ports:  []echo.Port{ports.HTTP, ports.HTTP2},
   517  							method: "PUT",
   518  							allow:  false,
   519  						},
   520  					}
   521  
   522  					for _, c := range cases {
   523  						newAuthzTest().
   524  							From(from).
   525  							To(to).
   526  							Allow(c.allow).
   527  							Method(c.method).
   528  							BuildAndRunForPorts(t, c.ports...)
   529  					}
   530  				})
   531  		})
   532  }
   533  
   534  func TestAuthz_NotPort(t *testing.T) {
   535  	framework.NewTest(t).
   536  		Run(func(t framework.TestContext) {
   537  			from := apps.Ns1.A
   538  			fromMatch := match.AnyServiceName(from.NamespacedNames())
   539  			toMatch := match.Not(fromMatch)
   540  			to := toMatch.GetServiceMatches(apps.Ns1AndNs2)
   541  			fromAndTo := to.Instances().Append(from)
   542  
   543  			config.New(t).
   544  				Source(config.File("testdata/authz/not-port.yaml.tmpl")).
   545  				BuildAll(nil, to).
   546  				Apply()
   547  
   548  			newTrafficTest(t, fromAndTo).
   549  				FromMatch(fromMatch).
   550  				ToMatch(toMatch).
   551  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   552  					cases := []struct {
   553  						ports []echo.Port
   554  						allow allowValue
   555  					}{
   556  						{
   557  							ports: []echo.Port{ports.HTTP},
   558  							allow: true,
   559  						},
   560  						{
   561  							ports: []echo.Port{ports.HTTP2},
   562  							allow: false,
   563  						},
   564  					}
   565  
   566  					for _, c := range cases {
   567  						newAuthzTest().
   568  							From(from).
   569  							To(to).
   570  							Allow(c.allow).
   571  							BuildAndRunForPorts(t, c.ports...)
   572  					}
   573  				})
   574  		})
   575  }
   576  
   577  func TestAuthz_DenyPlaintext(t *testing.T) {
   578  	framework.NewTest(t).
   579  		Run(func(t framework.TestContext) {
   580  			allowed := apps.Ns1.A
   581  			denied := apps.Ns2.A
   582  
   583  			newTrafficTest(t, apps.Ns1.All.Instances().Append(denied)).
   584  				Config(config.File("testdata/authz/plaintext.yaml.tmpl").WithParams(param.Params{
   585  					"Denied": denied,
   586  					// The namespaces for each resource are specified in the file. Use "" as the ns to apply to.
   587  					param.Namespace.String(): "",
   588  				})).
   589  				// Just test from A in each namespace to show the policy works.
   590  				FromMatch(match.AnyServiceName(allowed.Append(denied).NamespacedNames())).
   591  				ToMatch(match.Namespace(apps.Ns1.Namespace)).
   592  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   593  					allow := allowValue(from.Config().Namespace.Name() == allowed.Config().Namespace.Name())
   594  					newAuthzTest().
   595  						From(from).
   596  						To(to).
   597  						Allow(allow).
   598  						BuildAndRunForPorts(t, ports.GRPC, ports.TCP, ports.HTTP, ports.HTTP2)
   599  				})
   600  		})
   601  }
   602  
   603  func TestAuthz_JWT(t *testing.T) {
   604  	framework.NewTest(t).
   605  		Label(label.IPv4). // https://github.com/istio/istio/issues/35835
   606  		Run(func(t framework.TestContext) {
   607  			from := apps.Ns1.A
   608  			fromMatch := match.ServiceName(from.NamespacedName())
   609  			toMatch := match.Not(fromMatch)
   610  			to := toMatch.GetServiceMatches(apps.Ns1.All)
   611  			fromAndTo := to.Instances().Append(from)
   612  
   613  			config.New(t).
   614  				Source(config.File("testdata/authz/jwt.yaml.tmpl").WithNamespace(apps.Ns1.Namespace)).
   615  				BuildAll(nil, to).
   616  				Apply()
   617  
   618  			newTrafficTest(t, fromAndTo).
   619  				FromMatch(fromMatch).
   620  				ToMatch(toMatch).
   621  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   622  					cases := []struct {
   623  						prefix string
   624  						jwt    string
   625  						path   string
   626  						allow  allowValue
   627  					}{
   628  						{
   629  							prefix: "[No JWT]",
   630  							jwt:    "",
   631  							path:   "/token1",
   632  							allow:  false,
   633  						},
   634  						{
   635  							prefix: "[No JWT]",
   636  							jwt:    "",
   637  							path:   "/token2",
   638  							allow:  false,
   639  						},
   640  						{
   641  							prefix: "[Token1]",
   642  							jwt:    jwt.TokenIssuer1, path: "/token1", allow: true,
   643  						},
   644  						{
   645  							prefix: "[Token1]",
   646  							jwt:    jwt.TokenIssuer1,
   647  							path:   "/token2",
   648  							allow:  false,
   649  						},
   650  						{
   651  							prefix: "[Token2]",
   652  							jwt:    jwt.TokenIssuer2,
   653  							path:   "/token1",
   654  							allow:  false,
   655  						},
   656  						{
   657  							prefix: "[Token2]",
   658  							jwt:    jwt.TokenIssuer2,
   659  							path:   "/token2",
   660  							allow:  true,
   661  						},
   662  						{
   663  							prefix: "[Token3]",
   664  							jwt:    jwt.TokenIssuer1,
   665  							path:   "/token3",
   666  							allow:  false,
   667  						},
   668  						{
   669  							prefix: "[Token3]",
   670  							jwt:    jwt.TokenIssuer2,
   671  							path:   "/token3",
   672  							allow:  true,
   673  						},
   674  						{
   675  							prefix: "[Token1]",
   676  							jwt:    jwt.TokenIssuer1,
   677  							path:   "/tokenAny",
   678  							allow:  true,
   679  						},
   680  						{
   681  							prefix: "[Token2]",
   682  							jwt:    jwt.TokenIssuer2,
   683  							path:   "/tokenAny",
   684  							allow:  true,
   685  						},
   686  						{
   687  							prefix: "[PermissionToken1]",
   688  							jwt:    jwt.TokenIssuer1,
   689  							path:   "/permission",
   690  							allow:  false,
   691  						},
   692  						{
   693  							prefix: "[PermissionToken2]",
   694  							jwt:    jwt.TokenIssuer2,
   695  							path:   "/permission",
   696  							allow:  false,
   697  						},
   698  						{
   699  							prefix: "[PermissionTokenWithSpaceDelimitedScope]",
   700  							jwt:    jwt.TokenIssuer2WithSpaceDelimitedScope,
   701  							path:   "/permission",
   702  							allow:  true,
   703  						},
   704  						{
   705  							prefix: "[NestedToken1]",
   706  							jwt:    jwt.TokenIssuer1WithNestedClaims1,
   707  							path:   "/nested-key1",
   708  							allow:  true,
   709  						},
   710  						{
   711  							prefix: "[NestedToken2]",
   712  							jwt:    jwt.TokenIssuer1WithNestedClaims2,
   713  							path:   "/nested-key1",
   714  							allow:  false,
   715  						},
   716  						{
   717  							prefix: "[NestedToken1]",
   718  							jwt:    jwt.TokenIssuer1WithNestedClaims1,
   719  							path:   "/nested-key2",
   720  							allow:  false,
   721  						},
   722  						{
   723  							prefix: "[NestedToken2]",
   724  							jwt:    jwt.TokenIssuer1WithNestedClaims2,
   725  							path:   "/nested-key2",
   726  							allow:  true,
   727  						},
   728  						{
   729  							prefix: "[NestedToken1]",
   730  							jwt:    jwt.TokenIssuer1WithNestedClaims1,
   731  							path:   "/nested-2-key1",
   732  							allow:  true,
   733  						},
   734  						{
   735  							prefix: "[NestedToken2]",
   736  							jwt:    jwt.TokenIssuer1WithNestedClaims2,
   737  							path:   "/nested-2-key1",
   738  							allow:  false,
   739  						},
   740  						{
   741  							prefix: "[NestedToken1]",
   742  							jwt:    jwt.TokenIssuer1WithNestedClaims1,
   743  							path:   "/nested-non-exist",
   744  							allow:  false,
   745  						},
   746  						{
   747  							prefix: "[NestedToken2]",
   748  							jwt:    jwt.TokenIssuer1WithNestedClaims2,
   749  							path:   "/nested-non-exist",
   750  							allow:  false,
   751  						},
   752  						{
   753  							prefix: "[NoJWT]",
   754  							jwt:    "",
   755  							path:   "/tokenAny",
   756  							allow:  false,
   757  						},
   758  						{
   759  							prefix: "[JWTWithAud]",
   760  							jwt:    jwt.TokenIssuer1WithAud,
   761  							path:   "/audiences",
   762  							allow:  true,
   763  						},
   764  						{
   765  							prefix: "[JWTWithAudList]",
   766  							jwt:    jwt.TokenIssuer1WithAudList,
   767  							path:   "/audiences",
   768  							allow:  true,
   769  						},
   770  					}
   771  					for _, c := range cases {
   772  						h := headers.New().WithAuthz(c.jwt).Build()
   773  						newAuthzTest().
   774  							From(from).
   775  							To(to).
   776  							Allow(c.allow).
   777  							Prefix(c.prefix).
   778  							Path(c.path).
   779  							Headers(h).
   780  							BuildAndRunForPorts(t, ports.HTTP, ports.HTTP2)
   781  					}
   782  				})
   783  		})
   784  }
   785  
   786  func TestAuthz_WorkloadSelector(t *testing.T) {
   787  	framework.NewTest(t).
   788  		Run(func(t framework.TestContext) {
   789  			// Verify that the workload-specific path (/policy-<ns>-<svc>) works only on the selected workload.
   790  			t.NewSubTestf("single workload").
   791  				Run(func(t framework.TestContext) {
   792  					from := apps.Ns1.A
   793  					fromMatch := match.ServiceName(from.NamespacedName())
   794  					toMatch := match.Not(fromMatch)
   795  					to := toMatch.GetServiceMatches(apps.Ns1.All)
   796  					fromAndTo := to.Instances().Append(from)
   797  
   798  					config.New(t).
   799  						Source(config.File("testdata/authz/workload.yaml.tmpl")).
   800  						// Also define a bad workload selector for path /policy-<ns>-<svc>-bad.
   801  						Source(config.File("testdata/authz/workload-bad.yaml.tmpl")).
   802  						// Allow /policy-<ns>-all for all workloads.
   803  						Source(config.File("testdata/authz/workload-ns.yaml.tmpl").WithParams(param.Params{
   804  							param.Namespace.String(): apps.Ns1.Namespace,
   805  						})).
   806  						Source(config.File("testdata/authz/workload-ns.yaml.tmpl").WithParams(param.Params{
   807  							param.Namespace.String(): apps.Ns2.Namespace,
   808  						})).
   809  						// Allow /policy-istio-system-<svc> for all services in all namespaces. Just using ns1 to avoid
   810  						// creating duplicate resources.
   811  						Source(config.File("testdata/authz/workload-system-ns.yaml.tmpl").WithParams(param.Params{
   812  							param.Namespace.String(): istio.ClaimSystemNamespaceOrFail(t, t),
   813  						})).
   814  						BuildAll(nil, to).
   815  						Apply()
   816  
   817  					newTrafficTest(t, fromAndTo).
   818  						FromMatch(fromMatch).
   819  						ToMatch(toMatch).
   820  						Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   821  							type testCase struct {
   822  								path        string
   823  								allow       allowValue
   824  								updateLabel bool
   825  							}
   826  
   827  							cases := []testCase{
   828  								{
   829  									// Make sure the bad policy did not select this workload.
   830  									path:  fmt.Sprintf("/policy-%s-%s-bad", to.Config().Namespace.Prefix(), to.Config().Service),
   831  									allow: false,
   832  								},
   833  								{
   834  									// Make sure the bad policy select this workload.
   835  									// Skip vm
   836  									path:        fmt.Sprintf("/policy-%s-%s-bad", to.Config().Namespace.Prefix(), to.Config().Service),
   837  									allow:       true,
   838  									updateLabel: true,
   839  								},
   840  							}
   841  
   842  							// Make sure the namespace-wide policy was applied to this workload.
   843  							for _, ns := range []namespace.Instance{apps.Ns1.Namespace, apps.Ns2.Namespace} {
   844  								cases = append(cases,
   845  									testCase{
   846  										path:  fmt.Sprintf("/policy-%s-all", ns.Prefix()),
   847  										allow: ns.Name() == to.Config().Namespace.Name(),
   848  									})
   849  							}
   850  
   851  							// Make sure the workload-specific paths succeeds.
   852  							cases = append(cases,
   853  								testCase{
   854  									path:  fmt.Sprintf("/policy-%s-%s", to.Config().Namespace.Prefix(), to.Config().Service),
   855  									allow: true,
   856  								},
   857  								testCase{
   858  									path:  fmt.Sprintf("/policy-system-%s", to.Config().Service),
   859  									allow: true,
   860  								})
   861  
   862  							// The workload-specific paths should fail for another service (just add a single test case).
   863  							for _, svc := range apps.Ns1.All {
   864  								if svc.Config().Service != to.Config().Service {
   865  									cases = append(cases,
   866  										testCase{
   867  											path:  fmt.Sprintf("/policy-%s-%s", svc.Config().Namespace.Prefix(), svc.Config().Service),
   868  											allow: false,
   869  										},
   870  										testCase{
   871  											path:  fmt.Sprintf("/policy-system-%s", svc.Config().Service),
   872  											allow: false,
   873  										})
   874  									break
   875  								}
   876  							}
   877  
   878  							for _, c := range cases {
   879  								if c.updateLabel {
   880  									// skip updating pod labels for VM
   881  									if to.Config().DeployAsVM {
   882  										continue
   883  									}
   884  									for _, instance := range to.Instances() {
   885  										err := instance.UpdateWorkloadLabel(map[string]string{"foo": "bla"}, nil)
   886  										if err != nil {
   887  											t.Fatal(err)
   888  										}
   889  									}
   890  								}
   891  								newAuthzTest().
   892  									From(from).
   893  									To(to).
   894  									Allow(c.allow).
   895  									Path(c.path).
   896  									BuildForPorts(t, ports.HTTP, ports.HTTP2).
   897  									RunInSerial(t)
   898  
   899  								if c.updateLabel {
   900  									// skip updating pod labels for VM
   901  									if to.Config().DeployAsVM {
   902  										continue
   903  									}
   904  									for _, instance := range to.Instances() {
   905  										err := instance.UpdateWorkloadLabel(nil, []string{"foo"})
   906  										if err != nil {
   907  											t.Fatal(err)
   908  										}
   909  									}
   910  								}
   911  							}
   912  						})
   913  				})
   914  		})
   915  }
   916  
   917  func TestAuthz_PathPrecedence(t *testing.T) {
   918  	framework.NewTest(t).
   919  		Run(func(t framework.TestContext) {
   920  			from := apps.Ns1.A
   921  			fromMatch := match.ServiceName(from.NamespacedName())
   922  			toMatch := match.Not(fromMatch)
   923  			to := toMatch.GetServiceMatches(apps.Ns1.All)
   924  			fromAndTo := to.Instances().Append(from)
   925  
   926  			config.New(t).
   927  				Source(config.File("testdata/authz/path-precedence.yaml.tmpl")).
   928  				BuildAll(nil, to).
   929  				Apply()
   930  
   931  			newTrafficTest(t, fromAndTo).
   932  				FromMatch(fromMatch).
   933  				ToMatch(toMatch).
   934  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   935  					cases := []struct {
   936  						path  string
   937  						allow allowValue
   938  					}{
   939  						{
   940  							path:  "/allow/admin",
   941  							allow: false,
   942  						},
   943  						{
   944  							path:  "/allow/admin?param=value",
   945  							allow: false,
   946  						},
   947  						{
   948  							path:  "/allow",
   949  							allow: true,
   950  						},
   951  						{
   952  							path:  "/allow?param=value",
   953  							allow: true,
   954  						},
   955  					}
   956  
   957  					for _, c := range cases {
   958  						newAuthzTest().
   959  							From(from).
   960  							To(to).
   961  							Allow(c.allow).
   962  							Path(c.path).
   963  							BuildAndRunForPorts(t, ports.HTTP, ports.HTTP2)
   964  					}
   965  				})
   966  		})
   967  }
   968  
   969  func TestAuthz_PathTemplating(t *testing.T) {
   970  	framework.NewTest(t).
   971  		Run(func(t framework.TestContext) {
   972  			from := apps.Ns1.A
   973  			fromMatch := match.ServiceName(from.NamespacedName())
   974  			toMatch := match.Not(fromMatch)
   975  			to := toMatch.GetServiceMatches(apps.Ns1.All)
   976  			fromAndTo := to.Instances().Append(from)
   977  
   978  			config.New(t).
   979  				Source(config.File("testdata/authz/path-templating.yaml.tmpl")).
   980  				BuildAll(nil, to).
   981  				Apply()
   982  
   983  			newTrafficTest(t, fromAndTo).
   984  				FromMatch(fromMatch).
   985  				ToMatch(toMatch).
   986  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
   987  					cases := []struct {
   988  						path  string
   989  						allow allowValue
   990  					}{
   991  						// Test matches for `/allow/admin/{**}`
   992  						{
   993  							path:  "/allow/admin/",
   994  							allow: true,
   995  						},
   996  						{
   997  							// When `**` is the last segment and operator in the template, the path must have a trailing `/` to match
   998  							path:  "/allow/admin",
   999  							allow: false,
  1000  						},
  1001  						{
  1002  							path:  "/allow/user/",
  1003  							allow: false,
  1004  						},
  1005  						{
  1006  							path:  "/allow/admin?param=value",
  1007  							allow: false,
  1008  						},
  1009  						{
  1010  							path:  "/allow",
  1011  							allow: false,
  1012  						},
  1013  						{
  1014  							path:  "/allow/admin/temp",
  1015  							allow: true,
  1016  						},
  1017  						{
  1018  							path:  "/allow/admin/temp.txt",
  1019  							allow: true,
  1020  						},
  1021  						{
  1022  							path:  "/allow/admin/foo/temp.txt",
  1023  							allow: true,
  1024  						},
  1025  						// Test matches for `/foo/{*}/bar/{**}`
  1026  						{
  1027  							path:  "/foo/buzz/bar/bat.txt",
  1028  							allow: true,
  1029  						},
  1030  						{
  1031  							path:  "/foo/buzz/bar/bat.temp.txt",
  1032  							allow: true,
  1033  						},
  1034  						{
  1035  							path:  "/foo/buzz/bar/bat/fuzz.txt",
  1036  							allow: true,
  1037  						},
  1038  						{
  1039  							path:  "/foo/bar/bat.txt",
  1040  							allow: false,
  1041  						},
  1042  						{
  1043  							path:  "/foo//bar/bat.txt",
  1044  							allow: false,
  1045  						},
  1046  						{
  1047  							path:  "/foo/buzz/bar/bat",
  1048  							allow: true,
  1049  						},
  1050  						// Test matches for `/store/{**}/cart`
  1051  						{
  1052  							path:  "/store//cart",
  1053  							allow: true,
  1054  						},
  1055  						{
  1056  							path:  "/store/cart",
  1057  							allow: false,
  1058  						},
  1059  					}
  1060  
  1061  					for _, c := range cases {
  1062  						c := c
  1063  						testName := fmt.Sprintf("%s(%s)/http", c.path, c.allow)
  1064  						t.NewSubTest(testName).Run(func(t framework.TestContext) {
  1065  							newAuthzTest().
  1066  								From(from).
  1067  								To(to).
  1068  								Allow(c.allow).
  1069  								Path(c.path).
  1070  								BuildAndRunForPorts(t, ports.HTTP, ports.HTTP2)
  1071  						})
  1072  					}
  1073  				})
  1074  		})
  1075  }
  1076  
  1077  func TestAuthz_IngressGateway(t *testing.T) {
  1078  	framework.NewTest(t).
  1079  		Run(func(t framework.TestContext) {
  1080  			to := apps.Ns1.All
  1081  			config.New(t).
  1082  				Source(config.File("testdata/authz/ingress-gateway.yaml.tmpl").WithParams(param.Params{
  1083  					// The namespaces for each resource are specified in the file. Use "" as the ns to apply to.
  1084  					param.Namespace.String(): "",
  1085  					"GatewayIstioLabel":      i.Settings().IngressGatewayIstioLabel,
  1086  				})).
  1087  				BuildAll(nil, to).
  1088  				Apply()
  1089  			newTrafficTest(t, to.Instances()).
  1090  				RunViaIngress(func(t framework.TestContext, from ingress.Instance, to echo.Target) {
  1091  					host := func(fmtStr string) string {
  1092  						return fmt.Sprintf(fmtStr, to.Config().Service)
  1093  					}
  1094  					cases := []struct {
  1095  						host  string
  1096  						path  string
  1097  						ip    string
  1098  						allow allowValue
  1099  					}{
  1100  						{
  1101  							host:  host("deny.%s.com"),
  1102  							allow: false,
  1103  						},
  1104  						{
  1105  							host:  host("DENY.%s.COM"),
  1106  							allow: false,
  1107  						},
  1108  						{
  1109  							host:  host("Deny.%s.Com"),
  1110  							allow: false,
  1111  						},
  1112  						{
  1113  							host:  host("deny.suffix.%s.com"),
  1114  							allow: false,
  1115  						},
  1116  						{
  1117  							host:  host("DENY.SUFFIX.%s.COM"),
  1118  							allow: false,
  1119  						},
  1120  						{
  1121  							host:  host("Deny.Suffix.%s.Com"),
  1122  							allow: false,
  1123  						},
  1124  						{
  1125  							host:  host("prefix.%s.com"),
  1126  							allow: false,
  1127  						},
  1128  						{
  1129  							host:  host("PREFIX.%s.COM"),
  1130  							allow: false,
  1131  						},
  1132  						{
  1133  							host:  host("Prefix.%s.Com"),
  1134  							allow: false,
  1135  						},
  1136  						{
  1137  							host:  host("www.%s.com"),
  1138  							path:  "/",
  1139  							ip:    "172.16.0.1",
  1140  							allow: true,
  1141  						},
  1142  						{
  1143  							host:  host("www.%s.com"),
  1144  							path:  "/private",
  1145  							ip:    "172.16.0.1",
  1146  							allow: false,
  1147  						},
  1148  						{
  1149  							host:  host("www.%s.com"),
  1150  							path:  "/public",
  1151  							ip:    "172.16.0.1",
  1152  							allow: true,
  1153  						},
  1154  						{
  1155  							host:  host("internal.%s.com"),
  1156  							path:  "/",
  1157  							ip:    "172.16.0.1",
  1158  							allow: false,
  1159  						},
  1160  						{
  1161  							host:  host("internal.%s.com"),
  1162  							path:  "/private",
  1163  							ip:    "172.16.0.1",
  1164  							allow: false,
  1165  						},
  1166  						{
  1167  							host:  host("remoteipblocks.%s.com"),
  1168  							path:  "/",
  1169  							ip:    "172.17.72.46",
  1170  							allow: false,
  1171  						},
  1172  						{
  1173  							host:  host("remoteipblocks.%s.com"),
  1174  							path:  "/",
  1175  							ip:    "192.168.5.233",
  1176  							allow: false,
  1177  						},
  1178  						{
  1179  							host:  host("remoteipblocks.%s.com"),
  1180  							path:  "/",
  1181  							ip:    "10.4.5.6",
  1182  							allow: true,
  1183  						},
  1184  						{
  1185  							host:  host("notremoteipblocks.%s.com"),
  1186  							path:  "/",
  1187  							ip:    "10.2.3.4",
  1188  							allow: false,
  1189  						},
  1190  						{
  1191  							host:  host("notremoteipblocks.%s.com"),
  1192  							path:  "/",
  1193  							ip:    "172.23.242.188",
  1194  							allow: true,
  1195  						},
  1196  						{
  1197  							host:  host("remoteipattr.%s.com"),
  1198  							path:  "/",
  1199  							ip:    "10.242.5.7",
  1200  							allow: false,
  1201  						},
  1202  						{
  1203  							host:  host("remoteipattr.%s.com"),
  1204  							path:  "/",
  1205  							ip:    "10.124.99.10",
  1206  							allow: false,
  1207  						},
  1208  						{
  1209  							host:  host("remoteipattr.%s.com"),
  1210  							path:  "/",
  1211  							ip:    "10.4.5.6",
  1212  							allow: true,
  1213  						},
  1214  					}
  1215  
  1216  					for _, c := range cases {
  1217  						c := c
  1218  						testName := fmt.Sprintf("%s%s(%s)/http", c.host, c.path, c.allow)
  1219  						if len(c.ip) > 0 {
  1220  							testName = c.ip + "->" + testName
  1221  						}
  1222  						t.NewSubTest(testName).Run(func(t framework.TestContext) {
  1223  							wantCode := http.StatusOK
  1224  							if !c.allow {
  1225  								wantCode = http.StatusForbidden
  1226  							}
  1227  
  1228  							opts := echo.CallOptions{
  1229  								Port: echo.Port{
  1230  									Protocol: protocol.HTTP,
  1231  								},
  1232  								HTTP: echo.HTTP{
  1233  									Path:    c.path,
  1234  									Headers: headers.New().WithHost(c.host).WithXForwardedFor(c.ip).Build(),
  1235  								},
  1236  								Check: check.And(check.NoError(), check.Status(wantCode)),
  1237  							}
  1238  							from.CallOrFail(t, opts)
  1239  						})
  1240  					}
  1241  				})
  1242  		})
  1243  }
  1244  
  1245  func TestAuthz_EgressGateway(t *testing.T) {
  1246  	framework.NewTest(t).
  1247  		Label(label.IPv4). // https://github.com/istio/istio/issues/35835
  1248  		Run(func(t framework.TestContext) {
  1249  			allowed := apps.Ns1.A
  1250  			denied := apps.Ns2.A
  1251  
  1252  			from := allowed.Append(denied)
  1253  			fromMatch := match.AnyServiceName(from.NamespacedNames())
  1254  			toMatch := match.Not(fromMatch)
  1255  			to := toMatch.GetServiceMatches(apps.Ns1.All)
  1256  			fromAndTo := to.Instances().Append(from)
  1257  
  1258  			newTrafficTest(t, fromAndTo).
  1259  				FromMatch(fromMatch).
  1260  				ToMatch(toMatch).
  1261  				Config(config.File("testdata/authz/egress-gateway.yaml.tmpl").WithParams(param.Params{
  1262  					// The namespaces for each resource are specified in the file. Use "" as the ns to apply to.
  1263  					param.Namespace.String():        "",
  1264  					"EgressGatewayIstioLabel":       i.Settings().EgressGatewayIstioLabel,
  1265  					"EgressGatewayServiceName":      i.Settings().EgressGatewayServiceName,
  1266  					"EgressGatewayServiceNamespace": i.Settings().EgressGatewayServiceNamespace,
  1267  					"Allowed":                       allowed,
  1268  				})).
  1269  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
  1270  					allow := allowValue(from.NamespacedName() == allowed.Config().NamespacedName())
  1271  
  1272  					cases := []struct {
  1273  						host  string
  1274  						path  string
  1275  						token string
  1276  						allow allowValue
  1277  					}{
  1278  						{
  1279  							host:  "www.company.com",
  1280  							path:  "/allow",
  1281  							allow: true,
  1282  						},
  1283  						{
  1284  							host:  "www.company.com",
  1285  							path:  "/deny",
  1286  							allow: false,
  1287  						},
  1288  						{
  1289  							host:  fmt.Sprintf("%s-%s-only.com", allowed.Config().Service, allowed.Config().Namespace.Name()),
  1290  							path:  "/",
  1291  							allow: allow,
  1292  						},
  1293  						{
  1294  							host:  "jwt-only.com",
  1295  							path:  "/",
  1296  							token: jwt.TokenIssuer1,
  1297  							allow: true,
  1298  						},
  1299  						{
  1300  							host:  "jwt-only.com",
  1301  							path:  "/",
  1302  							token: jwt.TokenIssuer2,
  1303  							allow: false,
  1304  						},
  1305  						{
  1306  							host:  fmt.Sprintf("jwt-and-%s-%s-only.com", allowed.Config().Service, allowed.Config().Namespace.Name()),
  1307  							path:  "/",
  1308  							token: jwt.TokenIssuer1,
  1309  							allow: allow,
  1310  						},
  1311  						{
  1312  							path:  "/",
  1313  							host:  fmt.Sprintf("jwt-and-%s-%s-only.com", allowed.Config().Service, allowed.Config().Namespace.Name()),
  1314  							token: jwt.TokenIssuer2,
  1315  							allow: false,
  1316  						},
  1317  					}
  1318  
  1319  					for _, c := range cases {
  1320  						c := c
  1321  						testName := fmt.Sprintf("%s%s(%s)/http", c.host, c.path, c.allow)
  1322  						t.NewSubTest(testName).Run(func(t framework.TestContext) {
  1323  							wantCode := http.StatusOK
  1324  							body := "handled-by-egress-gateway"
  1325  							if !c.allow {
  1326  								wantCode = http.StatusForbidden
  1327  								body = "RBAC: access denied"
  1328  							}
  1329  
  1330  							opts := echo.CallOptions{
  1331  								// Use a fake IP address to bypass DNS lookup (which will fail). The host
  1332  								// header will be used for routing decisions.
  1333  								Address: "10.4.4.4",
  1334  								Port: echo.Port{
  1335  									Name:        ports.HTTP.Name,
  1336  									Protocol:    protocol.HTTP,
  1337  									ServicePort: 80,
  1338  								},
  1339  								HTTP: echo.HTTP{
  1340  									Path:    c.path,
  1341  									Headers: headers.New().WithHost(c.host).WithAuthz(c.token).Build(),
  1342  								},
  1343  								Check: check.And(check.NoErrorAndStatus(wantCode), check.BodyContains(body)),
  1344  							}
  1345  
  1346  							from.CallOrFail(t, opts)
  1347  						})
  1348  					}
  1349  				})
  1350  		})
  1351  }
  1352  
  1353  func TestAuthz_Conditions(t *testing.T) {
  1354  	framework.NewTest(t).
  1355  		Run(func(t framework.TestContext) {
  1356  			allowed := apps.Ns1.A
  1357  			denied := apps.Ns2.A
  1358  
  1359  			from := allowed.Append(denied)
  1360  			fromMatch := match.AnyServiceName(from.NamespacedNames())
  1361  			toMatch := match.Not(fromMatch)
  1362  			to := toMatch.GetServiceMatches(apps.Ns1.All)
  1363  			fromAndTo := to.Instances().Append(from)
  1364  
  1365  			config.New(t).
  1366  				Source(config.File("testdata/authz/mtls.yaml.tmpl")).
  1367  				Source(config.File("testdata/authz/conditions.yaml.tmpl").WithParams(param.Params{
  1368  					"Allowed": allowed,
  1369  					"Denied":  denied,
  1370  				})).
  1371  				BuildAll(nil, to).
  1372  				Apply()
  1373  
  1374  			newTrafficTest(t, fromAndTo).
  1375  				FromMatch(fromMatch).
  1376  				ToMatch(toMatch).
  1377  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
  1378  					allow := allowValue(from.NamespacedName() == allowed.Config().NamespacedName())
  1379  
  1380  					skipSourceIPTestsForMulticluster := func(t framework.TestContext) {
  1381  						t.Helper()
  1382  						if t.Clusters().IsMulticluster() {
  1383  							// TODO(nmittler): Needs to be documented as a limitation for multi-network.
  1384  							t.Skip("https://github.com/istio/istio/issues/37307: " +
  1385  								"Source IP-based authz tests are not supported in multi-network configurations " +
  1386  								"due to the fact that the origin source IP will be lost when traversing the " +
  1387  								"east-west gateway.")
  1388  						}
  1389  					}
  1390  
  1391  					cases := []struct {
  1392  						path    string
  1393  						headers http.Header
  1394  						allow   allowValue
  1395  						skipFn  func(t framework.TestContext)
  1396  					}{
  1397  						// Test headers.
  1398  						{
  1399  							path:    "/request-headers",
  1400  							headers: headers.New().With("x-foo", "foo").Build(),
  1401  							allow:   true,
  1402  						},
  1403  						{
  1404  							path:    "/request-headers",
  1405  							headers: headers.New().With("x-foo", "bar").Build(),
  1406  							allow:   false,
  1407  						},
  1408  						{
  1409  							path:  "/request-headers",
  1410  							allow: false,
  1411  						},
  1412  						{
  1413  							path:    "/request-headers-notValues",
  1414  							headers: headers.New().With("x-foo", "foo").Build(),
  1415  							allow:   true,
  1416  						},
  1417  						{
  1418  							path:    "/request-headers-notValues",
  1419  							headers: headers.New().With("x-foo", "bar").Build(),
  1420  							allow:   false,
  1421  						},
  1422  
  1423  						// Test source IP
  1424  						{
  1425  							path:   "/source-ip",
  1426  							allow:  allow,
  1427  							skipFn: skipSourceIPTestsForMulticluster,
  1428  						},
  1429  						{
  1430  							path:   "/source-ip-notValues",
  1431  							allow:  allow,
  1432  							skipFn: skipSourceIPTestsForMulticluster,
  1433  						},
  1434  
  1435  						// Test source namespace
  1436  						{
  1437  							path:  "/source-namespace",
  1438  							allow: allow,
  1439  						},
  1440  						{
  1441  							path:  "/source-namespace-notValues",
  1442  							allow: allow,
  1443  						},
  1444  
  1445  						// Test source principal
  1446  						{
  1447  							path:  "/source-principal",
  1448  							allow: allow,
  1449  						},
  1450  						{
  1451  							path:  "/source-principal-notValues",
  1452  							allow: allow,
  1453  						},
  1454  
  1455  						// Test destination IP
  1456  						{
  1457  							path:  "/destination-ip-good",
  1458  							allow: true,
  1459  						},
  1460  						{
  1461  							path:  "/destination-ip-bad",
  1462  							allow: false,
  1463  						},
  1464  						{
  1465  							path:  "/destination-ip-notValues",
  1466  							allow: false,
  1467  						},
  1468  
  1469  						// Test destination port
  1470  						{
  1471  							path:  "/destination-port-good",
  1472  							allow: true,
  1473  						},
  1474  						{
  1475  							path:  "/destination-port-bad",
  1476  							allow: false,
  1477  						},
  1478  						{
  1479  							path:  "/destination-port-notValues",
  1480  							allow: false,
  1481  						},
  1482  
  1483  						// Test SNI
  1484  						{
  1485  							path:  "/connection-sni-good",
  1486  							allow: true,
  1487  						},
  1488  						{
  1489  							path:  "/connection-sni-bad",
  1490  							allow: false,
  1491  						},
  1492  						{
  1493  							path:  "/connection-sni-notValues",
  1494  							allow: false,
  1495  						},
  1496  
  1497  						{
  1498  							path:  "/other",
  1499  							allow: false,
  1500  						},
  1501  					}
  1502  
  1503  					for _, c := range cases {
  1504  						c := c
  1505  						xfooHeader := ""
  1506  						if c.headers != nil {
  1507  							xfooHeader = "?x-foo=" + c.headers.Get("x-foo")
  1508  						}
  1509  						testName := fmt.Sprintf("%s%s(%s)/http", c.path, xfooHeader, c.allow)
  1510  						t.NewSubTest(testName).Run(func(t framework.TestContext) {
  1511  							if c.skipFn != nil {
  1512  								c.skipFn(t)
  1513  							}
  1514  
  1515  							newAuthzTest().
  1516  								From(from).
  1517  								To(to).
  1518  								PortName(ports.HTTP.Name).
  1519  								Path(c.path).
  1520  								Allow(c.allow).
  1521  								Headers(c.headers).
  1522  								BuildAndRun(t)
  1523  						})
  1524  					}
  1525  				})
  1526  		})
  1527  }
  1528  
  1529  func TestAuthz_PathNormalization(t *testing.T) {
  1530  	framework.NewTest(t).
  1531  		Run(func(t framework.TestContext) {
  1532  			from := apps.Ns1.A
  1533  			fromMatch := match.ServiceName(from.NamespacedName())
  1534  			toMatch := match.Not(fromMatch)
  1535  			to := toMatch.GetServiceMatches(apps.Ns1.All)
  1536  			fromAndTo := to.Instances().Append(from)
  1537  
  1538  			config.New(t).
  1539  				Source(config.File("testdata/authz/path-normalization.yaml.tmpl")).
  1540  				BuildAll(nil, to).
  1541  				Apply()
  1542  
  1543  			newTrafficTest(t, fromAndTo).
  1544  				FromMatch(fromMatch).
  1545  				ToMatch(toMatch).
  1546  				Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
  1547  					cases := []struct {
  1548  						path  string
  1549  						allow allowValue
  1550  					}{
  1551  						{
  1552  							path:  "/public",
  1553  							allow: true,
  1554  						},
  1555  						{
  1556  							path:  "/public/../public",
  1557  							allow: true,
  1558  						},
  1559  						{
  1560  							path:  "/private",
  1561  							allow: false,
  1562  						},
  1563  						{
  1564  							path:  "/public/../private",
  1565  							allow: false,
  1566  						},
  1567  						{
  1568  							path:  "/public/./../private",
  1569  							allow: false,
  1570  						},
  1571  						{
  1572  							path:  "/public/.././private",
  1573  							allow: false,
  1574  						},
  1575  						{
  1576  							path:  "/public/%2E%2E/private",
  1577  							allow: false,
  1578  						},
  1579  						{
  1580  							path:  "/public/%2e%2e/private",
  1581  							allow: false,
  1582  						},
  1583  						{
  1584  							path:  "/public/%2E/%2E%2E/private",
  1585  							allow: false,
  1586  						},
  1587  						{
  1588  							path:  "/public/%2e/%2e%2e/private",
  1589  							allow: false,
  1590  						},
  1591  						{
  1592  							path:  "/public/%2E%2E/%2E/private",
  1593  							allow: false,
  1594  						},
  1595  						{
  1596  							path:  "/public/%2e%2e/%2e/private",
  1597  							allow: false,
  1598  						},
  1599  					}
  1600  
  1601  					for _, c := range cases {
  1602  						c := c
  1603  						testName := fmt.Sprintf("%s(%s)/http", c.path, c.allow)
  1604  						t.NewSubTest(testName).Run(func(t framework.TestContext) {
  1605  							newAuthzTest().
  1606  								From(from).
  1607  								To(to).
  1608  								PortName(ports.HTTP.Name).
  1609  								Path(c.path).
  1610  								Allow(c.allow).
  1611  								BuildAndRun(t)
  1612  						})
  1613  					}
  1614  				})
  1615  		})
  1616  }
  1617  
  1618  func TestAuthz_CustomServer(t *testing.T) {
  1619  	framework.NewTest(t).
  1620  		Run(func(t framework.TestContext) {
  1621  			extAuthzHeaders := func(value string) http.Header {
  1622  				return headers.New().
  1623  					With(authz.XExtAuthz, value).
  1624  					With(authz.XExtAuthzAdditionalHeaderOverride, "should-be-override").
  1625  					Build()
  1626  			}
  1627  			allowHeaders := func() http.Header {
  1628  				return extAuthzHeaders(authz.XExtAuthzAllow)
  1629  			}
  1630  			denyHeaders := func() http.Header {
  1631  				return extAuthzHeaders("deny")
  1632  			}
  1633  
  1634  			allProviders := append(authzServer.Providers(), localAuthzServer.Providers()...)
  1635  			for _, provider := range allProviders {
  1636  				t.NewSubTest(provider.Name()).Run(func(t framework.TestContext) {
  1637  					// The ext-authz server is hard-coded to allow requests from any service account ending in
  1638  					// "/sa/a". Since the namespace is ignored, we use ns2.B for our denied app (rather than ns2.A).
  1639  					// We'll only need the service account for TCP, since we send headers in all other protocols
  1640  					// to control the server's behavior.
  1641  					allowed := apps.Ns1.A
  1642  					var denied echo.Instances
  1643  					if provider.IsProtocolSupported(protocol.TCP) {
  1644  						denied = apps.Ns2.B
  1645  					}
  1646  
  1647  					from := allowed.Append(denied)
  1648  					fromMatch := match.AnyServiceName(from.NamespacedNames())
  1649  					toMatch := match.And(match.Not(fromMatch), provider.MatchSupportedTargets())
  1650  					to := toMatch.GetServiceMatches(apps.Ns1.All)
  1651  					fromAndTo := to.Instances().Append(from)
  1652  
  1653  					config.New(t).
  1654  						Source(config.File("testdata/authz/custom-provider.yaml.tmpl").WithParams(param.Params{
  1655  							"Provider": provider,
  1656  						})).
  1657  						BuildAll(nil, to).
  1658  						Apply()
  1659  
  1660  					newTrafficTest(t, fromAndTo).
  1661  						FromMatch(fromMatch).
  1662  						ToMatch(toMatch).
  1663  						Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
  1664  							fromAllowed := from.NamespacedName() == allowed.NamespacedName()
  1665  
  1666  							authzPath := "/custom"
  1667  							cases := []struct {
  1668  								ports   []echo.Port
  1669  								path    string
  1670  								headers http.Header
  1671  								allow   allowValue
  1672  								skip    bool
  1673  							}{
  1674  								{
  1675  									ports: []echo.Port{ports.TCP},
  1676  									// For TCP, we rely on the hard-coded allowed service account.
  1677  									allow: allowValue(fromAllowed),
  1678  								},
  1679  								{
  1680  									ports:   []echo.Port{ports.HTTP, ports.HTTP2, ports.GRPC},
  1681  									path:    authzPath,
  1682  									headers: allowHeaders(),
  1683  									allow:   true,
  1684  									skip:    !fromAllowed,
  1685  								},
  1686  								{
  1687  									ports:   []echo.Port{ports.HTTP, ports.HTTP2, ports.GRPC},
  1688  									path:    authzPath,
  1689  									headers: denyHeaders(),
  1690  									allow:   false,
  1691  									skip:    !fromAllowed,
  1692  								},
  1693  								{
  1694  									ports:   []echo.Port{ports.HTTP, ports.HTTP2},
  1695  									path:    "/health",
  1696  									headers: extAuthzHeaders(authz.XExtAuthzAllow),
  1697  									allow:   true,
  1698  									skip:    !fromAllowed,
  1699  								},
  1700  								{
  1701  									ports:   []echo.Port{ports.HTTP, ports.HTTP2},
  1702  									path:    "/health",
  1703  									headers: extAuthzHeaders("deny"),
  1704  									allow:   true,
  1705  									skip:    !fromAllowed,
  1706  								},
  1707  							}
  1708  
  1709  							for _, c := range cases {
  1710  								c := c
  1711  								if c.skip {
  1712  									continue
  1713  								}
  1714  								tsts := newAuthzTest().
  1715  									From(from).
  1716  									To(to).
  1717  									Path(c.path).
  1718  									Headers(c.headers).
  1719  									Allow(c.allow).
  1720  									BuildForPorts(t, c.ports...).
  1721  									Filter(func(tst authzTest) bool {
  1722  										return provider.IsProtocolSupported(tst.opts.Port.Protocol)
  1723  									})
  1724  								for _, tst := range tsts {
  1725  									tst := tst
  1726  									params := ""
  1727  									if c.headers != nil {
  1728  										params = fmt.Sprintf("?%s=%s", authz.XExtAuthz, c.headers.Get(authz.XExtAuthz))
  1729  									}
  1730  									testName := fmt.Sprintf("%s%s(%s)/%s", c.path, params, c.allow, tst.opts.Port.Name)
  1731  									t.NewSubTest(testName).Run(func(t framework.TestContext) {
  1732  										if c.path == authzPath {
  1733  											tst.opts.Check = check.And(tst.opts.Check, provider.Check(tst.opts, c.allow.Bool()))
  1734  										}
  1735  
  1736  										tst.Run(t)
  1737  									})
  1738  								}
  1739  							}
  1740  						})
  1741  				})
  1742  			}
  1743  		})
  1744  }
  1745  
  1746  func newTrafficTest(t framework.TestContext, echos ...echo.Instances) *echotest.T {
  1747  	var all []echo.Instance
  1748  	for _, e := range echos {
  1749  		all = append(all, e...)
  1750  	}
  1751  
  1752  	return echotest.New(t, all).
  1753  		WithDefaultFilters(1, 1).
  1754  		FromMatch(match.And(
  1755  			match.NotNaked,
  1756  			match.NotProxylessGRPC)).
  1757  		ToMatch(match.And(
  1758  			match.NotNaked,
  1759  			match.NotProxylessGRPC)).
  1760  		ConditionallyTo(echotest.NoSelfCalls)
  1761  }
  1762  
  1763  type allowValue bool
  1764  
  1765  func (v allowValue) Bool() bool {
  1766  	return bool(v)
  1767  }
  1768  
  1769  func (v allowValue) String() string {
  1770  	if v {
  1771  		return "allow"
  1772  	}
  1773  	return "deny"
  1774  }
  1775  
  1776  type authzTest struct {
  1777  	from   echo.Instance
  1778  	opts   echo.CallOptions
  1779  	allow  allowValue
  1780  	prefix string
  1781  }
  1782  
  1783  func newAuthzTest() *authzTest {
  1784  	return &authzTest{}
  1785  }
  1786  
  1787  func (b *authzTest) Prefix(prefix string) *authzTest {
  1788  	b.prefix = prefix
  1789  	return b
  1790  }
  1791  
  1792  func (b *authzTest) From(from echo.Instance) *authzTest {
  1793  	b.from = from
  1794  	return b
  1795  }
  1796  
  1797  func (b *authzTest) To(to echo.Target) *authzTest {
  1798  	b.opts.To = to
  1799  	return b
  1800  }
  1801  
  1802  func (b *authzTest) PortName(portName string) *authzTest {
  1803  	b.opts.Port.Name = portName
  1804  	return b
  1805  }
  1806  
  1807  func (b *authzTest) Method(method string) *authzTest {
  1808  	b.opts.HTTP.Method = method
  1809  	return b
  1810  }
  1811  
  1812  func (b *authzTest) Path(path string) *authzTest {
  1813  	b.opts.HTTP.Path = path
  1814  	return b
  1815  }
  1816  
  1817  func (b *authzTest) Headers(headers http.Header) *authzTest {
  1818  	b.opts.HTTP.Headers = headers
  1819  	return b
  1820  }
  1821  
  1822  func (b *authzTest) Allow(allow allowValue) *authzTest {
  1823  	b.allow = allow
  1824  	return b
  1825  }
  1826  
  1827  func (b *authzTest) Build(t framework.TestContext) *authzTest {
  1828  	t.Helper()
  1829  	// Set check now, as FillDefaults requires it
  1830  	b.opts.Check = check.OK()
  1831  	// Fill in the defaults; we need this to get the dest protocol
  1832  	b.opts.FillDefaultsOrFail(t)
  1833  	if b.allow {
  1834  		b.opts.Check = check.And(check.OK(), check.ReachedTargetClusters(t))
  1835  	} else {
  1836  		b.opts.Check = check.Forbidden(b.opts.Port.Protocol)
  1837  	}
  1838  	return b
  1839  }
  1840  
  1841  func (b *authzTest) BuildForPorts(t framework.TestContext, ports ...echo.Port) authzTests {
  1842  	out := make(authzTests, 0, len(ports))
  1843  	for _, p := range ports {
  1844  		opts := b.opts.DeepCopy()
  1845  		opts.Port.Name = p.Name
  1846  
  1847  		tst := (&authzTest{
  1848  			prefix: b.prefix,
  1849  			from:   b.from,
  1850  			opts:   opts,
  1851  			allow:  b.allow,
  1852  		}).Build(t)
  1853  		out = append(out, *tst)
  1854  	}
  1855  	return out
  1856  }
  1857  
  1858  func (b *authzTest) BuildAndRunForPorts(t framework.TestContext, ports ...echo.Port) {
  1859  	tsts := b.BuildForPorts(t, ports...)
  1860  	tsts.RunAll(t)
  1861  }
  1862  
  1863  func (b *authzTest) Run(t framework.TestContext) {
  1864  	t.Helper()
  1865  	b.from.CallOrFail(t, b.opts)
  1866  }
  1867  
  1868  func (b *authzTest) BuildAndRun(t framework.TestContext) {
  1869  	t.Helper()
  1870  	b.Build(t).Run(t)
  1871  }
  1872  
  1873  type authzTests []authzTest
  1874  
  1875  func (tsts authzTests) checkValid() {
  1876  	path := tsts[0].opts.HTTP.Path
  1877  	allow := tsts[0].allow
  1878  	prefix := tsts[0].prefix
  1879  	for _, tst := range tsts {
  1880  		if tst.opts.HTTP.Path != path {
  1881  			panic("authz tests have different paths")
  1882  		}
  1883  		if tst.allow != allow {
  1884  			panic("authz tests have different allow")
  1885  		}
  1886  		if tst.prefix != prefix {
  1887  			panic("authz tests have different prefixes")
  1888  		}
  1889  	}
  1890  }
  1891  
  1892  func (tsts authzTests) Filter(keep func(authzTest) bool) authzTests {
  1893  	out := make(authzTests, 0, len(tsts))
  1894  	for _, tst := range tsts {
  1895  		if keep(tst) {
  1896  			out = append(out, tst)
  1897  		}
  1898  	}
  1899  	return out
  1900  }
  1901  
  1902  func (tsts authzTests) RunAll(t framework.TestContext) {
  1903  	t.Helper()
  1904  
  1905  	firstTest := tsts[0]
  1906  	if len(tsts) == 1 {
  1907  		// Testing a single port. Just run a single test.
  1908  		testName := fmt.Sprintf("%s%s(%s)/%s", firstTest.prefix, firstTest.opts.HTTP.Path, firstTest.allow, firstTest.opts.Port.Name)
  1909  		t.NewSubTest(testName).Run(func(t framework.TestContext) {
  1910  			firstTest.BuildAndRun(t)
  1911  		})
  1912  		return
  1913  	}
  1914  
  1915  	tsts.checkValid()
  1916  
  1917  	// Testing multiple ports...
  1918  	// Name outer test with constant info. Name inner test with port.
  1919  	outerTestName := fmt.Sprintf("%s%s(%s)", firstTest.prefix, firstTest.opts.HTTP.Path, firstTest.allow)
  1920  	t.NewSubTest(outerTestName).Run(func(t framework.TestContext) {
  1921  		for _, tst := range tsts {
  1922  			tst := tst
  1923  			t.NewSubTest(tst.opts.Port.Name).Run(func(t framework.TestContext) {
  1924  				tst.BuildAndRun(t)
  1925  			})
  1926  		}
  1927  	})
  1928  }
  1929  
  1930  func (tsts authzTests) RunInSerial(t framework.TestContext) {
  1931  	t.Helper()
  1932  
  1933  	firstTest := tsts[0]
  1934  	if len(tsts) == 1 {
  1935  		// Testing a single port. Just run a single test.
  1936  		testName := fmt.Sprintf("%s%s(%s)/%s", firstTest.prefix, firstTest.opts.HTTP.Path, firstTest.allow, firstTest.opts.Port.Name)
  1937  		t.NewSubTest(testName).Run(func(t framework.TestContext) {
  1938  			firstTest.BuildAndRun(t)
  1939  		})
  1940  		return
  1941  	}
  1942  
  1943  	tsts.checkValid()
  1944  
  1945  	// Testing multiple ports...
  1946  	// Name outer test with constant info. Name inner test with port.
  1947  	outerTestName := fmt.Sprintf("%s%s(%s)", firstTest.prefix, firstTest.opts.HTTP.Path, firstTest.allow)
  1948  	t.NewSubTest(outerTestName).Run(func(t framework.TestContext) {
  1949  		for _, tst := range tsts {
  1950  			tst := tst
  1951  			t.NewSubTest(tst.opts.Port.Name).Run(func(t framework.TestContext) {
  1952  				tst.BuildAndRun(t)
  1953  			})
  1954  		}
  1955  	})
  1956  }