istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/peer_authentication_simulation_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 core_test
    16  
    17  import (
    18  	"testing"
    19  
    20  	"istio.io/istio/pilot/pkg/model"
    21  	"istio.io/istio/pilot/pkg/simulation"
    22  	"istio.io/istio/pilot/test/xds"
    23  )
    24  
    25  // TestPeerAuthenticationPassthrough tests the PeerAuthentication policy applies correctly on the passthrough filter chain,
    26  // including both global configuration and port level configuration.
    27  func TestPeerAuthenticationPassthrough(t *testing.T) {
    28  	paStrict := `
    29  apiVersion: security.istio.io/v1beta1
    30  kind: PeerAuthentication
    31  metadata:
    32   name: default
    33  spec:
    34   selector:
    35     matchLabels:
    36       app: foo
    37   mtls:
    38     mode: STRICT
    39  ---`
    40  	paDisable := `
    41  apiVersion: security.istio.io/v1beta1
    42  kind: PeerAuthentication
    43  metadata:
    44   name: default
    45  spec:
    46   selector:
    47     matchLabels:
    48       app: foo
    49   mtls:
    50     mode: DISABLE
    51  ---`
    52  	paPermissive := `
    53  apiVersion: security.istio.io/v1beta1
    54  kind: PeerAuthentication
    55  metadata:
    56   name: default
    57  spec:
    58   selector:
    59     matchLabels:
    60       app: foo
    61   mtls:
    62     mode: PERMISSIVE
    63  ---`
    64  	paStrictWithDisableOnPort9000 := `
    65  apiVersion: security.istio.io/v1beta1
    66  kind: PeerAuthentication
    67  metadata:
    68   name: default
    69  spec:
    70   selector:
    71     matchLabels:
    72       app: foo
    73   mtls:
    74     mode: STRICT
    75   portLevelMtls:
    76     9000:
    77       mode: DISABLE
    78  ---`
    79  	paDisableWithStrictOnPort9000 := `
    80  apiVersion: security.istio.io/v1beta1
    81  kind: PeerAuthentication
    82  metadata:
    83   name: default
    84  spec:
    85   selector:
    86     matchLabels:
    87       app: foo
    88   mtls:
    89     mode: DISABLE
    90   portLevelMtls:
    91     9000:
    92       mode: STRICT
    93  ---`
    94  	paDisableWithPermissiveOnPort9000 := `
    95  apiVersion: security.istio.io/v1beta1
    96  kind: PeerAuthentication
    97  metadata:
    98    name: default
    99  spec:
   100    selector:
   101      matchLabels:
   102        app: foo
   103    mtls:
   104      mode: DISABLE
   105    portLevelMtls:
   106      9000:
   107        mode: PERMISSIVE
   108  ---`
   109  	sePort8000 := `
   110  apiVersion: networking.istio.io/v1alpha3
   111  kind: ServiceEntry
   112  metadata:
   113   name: se
   114  spec:
   115   hosts:
   116   - foo.bar
   117   endpoints:
   118   - address: 1.1.1.1
   119   location: MESH_INTERNAL
   120   resolution: STATIC
   121   ports:
   122   - name: http
   123     number: 8000
   124     protocol: HTTP
   125  ---`
   126  	mkCall := func(port int, tls simulation.TLSMode) simulation.Call {
   127  		r := simulation.Call{Protocol: simulation.HTTP, Port: port, CallMode: simulation.CallModeInbound, TLS: tls}
   128  		if tls == simulation.MTLS {
   129  			r.Alpn = "istio"
   130  		}
   131  		return r
   132  	}
   133  	cases := []struct {
   134  		name   string
   135  		config string
   136  		calls  []simulation.Expect
   137  	}{
   138  		{
   139  			name:   "global disable",
   140  			config: paDisable,
   141  			calls: []simulation.Expect{
   142  				{
   143  					Name:   "mtls",
   144  					Call:   mkCall(8000, simulation.MTLS),
   145  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   146  				},
   147  				{
   148  					Name:   "plaintext",
   149  					Call:   mkCall(8000, simulation.Plaintext),
   150  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   151  				},
   152  			},
   153  		},
   154  		{
   155  			name:   "global strict",
   156  			config: paStrict,
   157  			calls: []simulation.Expect{
   158  				{
   159  					Name:   "plaintext",
   160  					Call:   mkCall(8000, simulation.Plaintext),
   161  					Result: simulation.Result{Error: simulation.ErrNoFilterChain},
   162  				},
   163  				{
   164  					Name:   "mtls",
   165  					Call:   mkCall(8000, simulation.MTLS),
   166  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   167  				},
   168  			},
   169  		},
   170  		{
   171  			name:   "global permissive",
   172  			config: paPermissive,
   173  			calls: []simulation.Expect{
   174  				{
   175  					Name:   "plaintext",
   176  					Call:   mkCall(8000, simulation.Plaintext),
   177  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   178  				},
   179  				{
   180  					Name:   "mtls",
   181  					Call:   mkCall(8000, simulation.MTLS),
   182  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   183  				},
   184  			},
   185  		},
   186  		{
   187  			name:   "global disable and port 9000 strict",
   188  			config: paDisableWithStrictOnPort9000,
   189  			calls: []simulation.Expect{
   190  				{
   191  					Name:   "plaintext on port 8000",
   192  					Call:   mkCall(8000, simulation.Plaintext),
   193  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   194  				},
   195  				{
   196  					Name:   "mtls on port 8000",
   197  					Call:   mkCall(8000, simulation.MTLS),
   198  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   199  				},
   200  				{
   201  					Name:   "plaintext port 9000",
   202  					Call:   mkCall(9000, simulation.Plaintext),
   203  					Result: simulation.Result{Error: simulation.ErrNoFilterChain},
   204  				},
   205  				{
   206  					Name:   "mtls port 9000",
   207  					Call:   mkCall(9000, simulation.MTLS),
   208  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   209  				},
   210  			},
   211  		},
   212  		{
   213  			name:   "global disable and port 9000 strict not in service",
   214  			config: paDisableWithStrictOnPort9000 + sePort8000,
   215  			calls: []simulation.Expect{
   216  				{
   217  					Name:   "plaintext on port 8000",
   218  					Call:   mkCall(8000, simulation.Plaintext),
   219  					Result: simulation.Result{ClusterMatched: "inbound|8000||"},
   220  				},
   221  				{
   222  					Name: "mtls on port 8000",
   223  					Call: mkCall(8000, simulation.MTLS),
   224  					// This will send an mTLS request to plaintext HTTP port, which is expected to fail
   225  					Result: simulation.Result{Error: simulation.ErrProtocolError},
   226  				},
   227  				{
   228  					Name:   "plaintext port 9000",
   229  					Call:   mkCall(9000, simulation.Plaintext),
   230  					Result: simulation.Result{Error: simulation.ErrNoFilterChain},
   231  				},
   232  				{
   233  					Name:   "mtls port 9000",
   234  					Call:   mkCall(9000, simulation.MTLS),
   235  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   236  				},
   237  			},
   238  		},
   239  		{
   240  			name:   "global strict and port 9000 plaintext",
   241  			config: paStrictWithDisableOnPort9000,
   242  			calls: []simulation.Expect{
   243  				{
   244  					Name:   "plaintext on port 8000",
   245  					Call:   mkCall(8000, simulation.Plaintext),
   246  					Result: simulation.Result{Error: simulation.ErrNoFilterChain},
   247  				},
   248  				{
   249  					Name:   "mtls on port 8000",
   250  					Call:   mkCall(8000, simulation.MTLS),
   251  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   252  				},
   253  				{
   254  					Name:   "plaintext port 9000",
   255  					Call:   mkCall(9000, simulation.Plaintext),
   256  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   257  				},
   258  				{
   259  					Name:   "mtls port 9000",
   260  					Call:   mkCall(9000, simulation.MTLS),
   261  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   262  				},
   263  			},
   264  		},
   265  		{
   266  			name:   "global strict and port 9000 plaintext not in service",
   267  			config: paStrictWithDisableOnPort9000 + sePort8000,
   268  			calls: []simulation.Expect{
   269  				{
   270  					Name:   "plaintext on port 8000",
   271  					Call:   mkCall(8000, simulation.Plaintext),
   272  					Result: simulation.Result{Error: simulation.ErrNoFilterChain},
   273  				},
   274  				{
   275  					Name:   "mtls on port 8000",
   276  					Call:   mkCall(8000, simulation.MTLS),
   277  					Result: simulation.Result{ClusterMatched: "inbound|8000||"},
   278  				},
   279  				{
   280  					Name:   "plaintext port 9000",
   281  					Call:   mkCall(9000, simulation.Plaintext),
   282  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   283  				},
   284  				{
   285  					Name:   "mtls port 9000",
   286  					Call:   mkCall(9000, simulation.MTLS),
   287  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   288  				},
   289  			},
   290  		},
   291  		{
   292  			name:   "global plaintext and port 9000 permissive",
   293  			config: paDisableWithPermissiveOnPort9000,
   294  			calls: []simulation.Expect{
   295  				{
   296  					Name:   "plaintext on port 8000",
   297  					Call:   mkCall(8000, simulation.Plaintext),
   298  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   299  				},
   300  				{
   301  					Name:   "mtls on port 8000",
   302  					Call:   mkCall(8000, simulation.MTLS),
   303  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   304  				},
   305  				{
   306  					Name:   "plaintext port 9000",
   307  					Call:   mkCall(9000, simulation.Plaintext),
   308  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   309  				},
   310  				{
   311  					Name:   "mtls port 9000",
   312  					Call:   mkCall(9000, simulation.MTLS),
   313  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   314  				},
   315  			},
   316  		},
   317  		{
   318  			name:   "global plaintext and port 9000 permissive not in service",
   319  			config: paDisableWithPermissiveOnPort9000 + sePort8000,
   320  			calls: []simulation.Expect{
   321  				{
   322  					Name:   "plaintext on port 8000",
   323  					Call:   mkCall(8000, simulation.Plaintext),
   324  					Result: simulation.Result{ClusterMatched: "inbound|8000||"},
   325  				},
   326  				{
   327  					Name: "mtls on port 8000",
   328  					Call: mkCall(8000, simulation.MTLS),
   329  					// We match the plaintext HTTP filter chain, which is a protocol error (as expected)
   330  					Result: simulation.Result{Error: simulation.ErrProtocolError},
   331  				},
   332  				{
   333  					Name:   "plaintext port 9000",
   334  					Call:   mkCall(9000, simulation.Plaintext),
   335  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   336  				},
   337  				{
   338  					Name:   "mtls port 9000",
   339  					Call:   mkCall(9000, simulation.MTLS),
   340  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   341  				},
   342  			},
   343  		},
   344  	}
   345  	proxy := &model.Proxy{
   346  		Labels:   map[string]string{"app": "foo"},
   347  		Metadata: &model.NodeMetadata{Labels: map[string]string{"app": "foo"}},
   348  	}
   349  	for _, tt := range cases {
   350  		runSimulationTest(t, proxy, xds.FakeOptions{}, simulationTest{
   351  			name:   tt.name,
   352  			config: tt.config,
   353  			calls:  tt.calls,
   354  		})
   355  	}
   356  }
   357  
   358  // TestPeerAuthenticationWithSidecar tests the PeerAuthentication policy applies correctly to filter chain generated from
   359  // either the service or sidecar resource.
   360  func TestPeerAuthenticationWithSidecar(t *testing.T) {
   361  	pa := `
   362  apiVersion: security.istio.io/v1beta1
   363  kind: PeerAuthentication
   364  metadata:
   365    name: default
   366  spec:
   367    selector:
   368      matchLabels:
   369        app: foo
   370    mtls:
   371      mode: STRICT
   372    portLevelMtls:
   373      9090:
   374        mode: DISABLE
   375  ---`
   376  	sidecar := `
   377  apiVersion: networking.istio.io/v1alpha3
   378  kind: Sidecar
   379  metadata:
   380    labels:
   381      app: foo
   382    name: sidecar
   383  spec:
   384    ingress:
   385    - defaultEndpoint: 127.0.0.1:8080
   386      port:
   387        name: tls
   388        number: 8080
   389        protocol: TCP
   390    - defaultEndpoint: 127.0.0.1:9090
   391      port:
   392        name: plaintext
   393        number: 9090
   394        protocol: TCP
   395    egress:
   396    - hosts:
   397      - "*/*"
   398    workloadSelector:
   399      labels:
   400        app: foo
   401  ---`
   402  	partialSidecar := `
   403  apiVersion: networking.istio.io/v1alpha3
   404  kind: Sidecar
   405  metadata:
   406    labels:
   407      app: foo
   408    name: sidecar
   409  spec:
   410    ingress:
   411    - defaultEndpoint: 127.0.0.1:8080
   412      port:
   413        name: tls
   414        number: 8080
   415        protocol: TCP
   416    egress:
   417    - hosts:
   418      - "*/*"
   419    workloadSelector:
   420      labels:
   421        app: foo
   422  ---`
   423  	instancePorts := `
   424  apiVersion: networking.istio.io/v1alpha3
   425  kind: ServiceEntry
   426  metadata:
   427    name: se
   428  spec:
   429    hosts:
   430    - foo.bar
   431    endpoints:
   432    - address: 1.1.1.1
   433      labels:
   434        app: foo
   435    location: MESH_INTERNAL
   436    resolution: STATIC
   437    ports:
   438    - name: tls
   439      number: 8080
   440      protocol: TCP
   441    - name: plaintext
   442      number: 9090
   443      protocol: TCP
   444  ---`
   445  	instanceNoPorts := `
   446  apiVersion: networking.istio.io/v1alpha3
   447  kind: ServiceEntry
   448  metadata:
   449    name: se
   450  spec:
   451    hosts:
   452    - foo.bar
   453    endpoints:
   454    - address: 1.1.1.1
   455      labels:
   456        app: foo
   457    location: MESH_INTERNAL
   458    resolution: STATIC
   459    ports:
   460    - name: random
   461      number: 5050
   462      protocol: TCP
   463  ---`
   464  	mkCall := func(port int, tls simulation.TLSMode) simulation.Call {
   465  		return simulation.Call{Protocol: simulation.TCP, Port: port, CallMode: simulation.CallModeInbound, TLS: tls}
   466  	}
   467  	cases := []struct {
   468  		name   string
   469  		config string
   470  		calls  []simulation.Expect
   471  	}{
   472  		{
   473  			name:   "service, no sidecar",
   474  			config: pa + instancePorts,
   475  			calls: []simulation.Expect{
   476  				{
   477  					Name:   "plaintext on tls port",
   478  					Call:   mkCall(8080, simulation.Plaintext),
   479  					Result: simulation.Result{Error: simulation.ErrNoFilterChain},
   480  				},
   481  				{
   482  					Name:   "tls on tls port",
   483  					Call:   mkCall(8080, simulation.MTLS),
   484  					Result: simulation.Result{ClusterMatched: "inbound|8080||"},
   485  				},
   486  				{
   487  					Name:   "plaintext on plaintext port",
   488  					Call:   mkCall(9090, simulation.Plaintext),
   489  					Result: simulation.Result{ClusterMatched: "inbound|9090||"},
   490  				},
   491  				{
   492  					Name: "tls on plaintext port",
   493  					Call: mkCall(9090, simulation.MTLS),
   494  					// TLS is fine here; we are not sniffing TLS at all so anything is allowed
   495  					Result: simulation.Result{ClusterMatched: "inbound|9090||"},
   496  				},
   497  			},
   498  		},
   499  		{
   500  			name:   "service, full sidecar",
   501  			config: pa + sidecar + instancePorts,
   502  			calls: []simulation.Expect{
   503  				{
   504  					Name:   "plaintext on tls port",
   505  					Call:   mkCall(8080, simulation.Plaintext),
   506  					Result: simulation.Result{Error: simulation.ErrNoFilterChain},
   507  				},
   508  				{
   509  					Name:   "tls on tls port",
   510  					Call:   mkCall(8080, simulation.MTLS),
   511  					Result: simulation.Result{ClusterMatched: "inbound|8080||"},
   512  				},
   513  				{
   514  					Name:   "plaintext on plaintext port",
   515  					Call:   mkCall(9090, simulation.Plaintext),
   516  					Result: simulation.Result{ClusterMatched: "inbound|9090||"},
   517  				},
   518  				{
   519  					Name: "tls on plaintext port",
   520  					Call: mkCall(9090, simulation.MTLS),
   521  					// TLS is fine here; we are not sniffing TLS at all so anything is allowed
   522  					Result: simulation.Result{ClusterMatched: "inbound|9090||"},
   523  				},
   524  			},
   525  		},
   526  		{
   527  			name:   "no service, no sidecar",
   528  			config: pa + instanceNoPorts,
   529  			calls: []simulation.Expect{
   530  				{
   531  					Name:   "plaintext on tls port",
   532  					Call:   mkCall(8080, simulation.Plaintext),
   533  					Result: simulation.Result{Error: simulation.ErrNoFilterChain},
   534  				},
   535  				{
   536  					Name: "tls on tls port",
   537  					Call: mkCall(8080, simulation.MTLS),
   538  					// no ports defined, so we will passthrough
   539  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   540  				},
   541  				{
   542  					Name: "plaintext on plaintext port",
   543  					Call: mkCall(9090, simulation.Plaintext),
   544  					// no ports defined, so we will passthrough
   545  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   546  				},
   547  				{
   548  					Name: "tls on plaintext port",
   549  					Call: mkCall(9090, simulation.MTLS),
   550  					// no ports defined, so we will passthrough
   551  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   552  				},
   553  			},
   554  		},
   555  		{
   556  			name:   "no service, full sidecar",
   557  			config: pa + sidecar + instanceNoPorts,
   558  			calls: []simulation.Expect{
   559  				{
   560  					Name:   "plaintext on tls port",
   561  					Call:   mkCall(8080, simulation.Plaintext),
   562  					Result: simulation.Result{Error: simulation.ErrNoFilterChain},
   563  				},
   564  				{
   565  					Name:   "tls on tls port",
   566  					Call:   mkCall(8080, simulation.MTLS),
   567  					Result: simulation.Result{ClusterMatched: "inbound|8080||"},
   568  				},
   569  				{
   570  					Name:   "plaintext on plaintext port",
   571  					Call:   mkCall(9090, simulation.Plaintext),
   572  					Result: simulation.Result{ClusterMatched: "inbound|9090||"},
   573  				},
   574  				{
   575  					Name: "tls on plaintext port",
   576  					Call: mkCall(9090, simulation.MTLS),
   577  					// TLS is fine here; we are not sniffing TLS at all so anything is allowed
   578  					Result: simulation.Result{ClusterMatched: "inbound|9090||"},
   579  				},
   580  			},
   581  		},
   582  		{
   583  			name:   "service, partial sidecar",
   584  			config: pa + partialSidecar + instancePorts,
   585  			calls: []simulation.Expect{
   586  				{
   587  					Name:   "plaintext on tls port",
   588  					Call:   mkCall(8080, simulation.Plaintext),
   589  					Result: simulation.Result{Error: simulation.ErrNoFilterChain},
   590  				},
   591  				{
   592  					Name:   "tls on tls port",
   593  					Call:   mkCall(8080, simulation.MTLS),
   594  					Result: simulation.Result{ClusterMatched: "inbound|8080||"},
   595  				},
   596  				// Despite being defined in the Service, we get no filter chain since its not in Sidecar
   597  				{
   598  					Name: "plaintext on plaintext port",
   599  					Call: mkCall(9090, simulation.Plaintext),
   600  					// port 9090 not defined in partialSidecar and will use plain text, plaintext request should pass.
   601  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   602  				},
   603  				{
   604  					Name: "tls on plaintext port",
   605  					Call: mkCall(9090, simulation.MTLS),
   606  					// no ports defined, so we will passthrough
   607  					Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"},
   608  				},
   609  			},
   610  		},
   611  	}
   612  	proxy := &model.Proxy{
   613  		Labels:   map[string]string{"app": "foo"},
   614  		Metadata: &model.NodeMetadata{Labels: map[string]string{"app": "foo"}},
   615  	}
   616  	for _, tt := range cases {
   617  		runSimulationTest(t, proxy, xds.FakeOptions{}, simulationTest{
   618  			name:   tt.name,
   619  			config: tt.config,
   620  			calls:  tt.calls,
   621  		})
   622  	}
   623  }