k8s.io/apiserver@v0.31.1/pkg/server/egressselector/config_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package egressselector
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"reflect"
    23  	"testing"
    24  
    25  	utiltesting "k8s.io/client-go/util/testing"
    26  
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apiserver/pkg/apis/apiserver"
    29  )
    30  
    31  func strptr(s string) *string {
    32  	return &s
    33  }
    34  
    35  func TestReadEgressSelectorConfiguration(t *testing.T) {
    36  	testcases := []struct {
    37  		name           string
    38  		contents       string
    39  		createFile     bool
    40  		expectedResult *apiserver.EgressSelectorConfiguration
    41  		expectedError  *string
    42  	}{
    43  		{
    44  			name:           "empty",
    45  			createFile:     true,
    46  			contents:       ``,
    47  			expectedResult: nil,
    48  			expectedError:  strptr("invalid service configuration object \"\""),
    49  		},
    50  		{
    51  			name:           "absent",
    52  			createFile:     false,
    53  			contents:       ``,
    54  			expectedResult: nil,
    55  			expectedError:  strptr("unable to read egress selector configuration from \"test-egress-selector-config-absent\" [open test-egress-selector-config-absent: no such file or directory]"),
    56  		},
    57  		{
    58  			name:       "v1beta1",
    59  			createFile: true,
    60  			contents: `
    61  apiVersion: apiserver.k8s.io/v1beta1
    62  kind: EgressSelectorConfiguration
    63  egressSelections:
    64  - name: "cluster"
    65    connection:
    66      proxyProtocol: "HTTPConnect"
    67      transport:
    68        tcp:
    69          url: "https://127.0.0.1:8131"
    70          tlsConfig:
    71            caBundle: "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt"
    72            clientKey: "/etc/srv/kubernetes/pki/konnectivity-server/client.key"
    73            clientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt"
    74  - name: "controlplane"
    75    connection:
    76      proxyProtocol: "HTTPConnect"
    77      transport:
    78        tcp:
    79          url: "https://127.0.0.1:8132"
    80          tlsConfig:
    81            caBundle: "/etc/srv/kubernetes/pki/konnectivity-server-master/ca.crt"
    82            clientKey: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.key"
    83            clientCert: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.crt"
    84  - name: "etcd"
    85    connection:
    86      proxyProtocol: "Direct"
    87  `,
    88  			expectedResult: &apiserver.EgressSelectorConfiguration{
    89  				TypeMeta: metav1.TypeMeta{
    90  					Kind:       "",
    91  					APIVersion: "",
    92  				},
    93  				EgressSelections: []apiserver.EgressSelection{
    94  					{
    95  						Name: "cluster",
    96  						Connection: apiserver.Connection{
    97  							ProxyProtocol: "HTTPConnect",
    98  							Transport: &apiserver.Transport{
    99  								TCP: &apiserver.TCPTransport{
   100  									URL: "https://127.0.0.1:8131",
   101  
   102  									TLSConfig: &apiserver.TLSConfig{
   103  										CABundle:   "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt",
   104  										ClientKey:  "/etc/srv/kubernetes/pki/konnectivity-server/client.key",
   105  										ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt",
   106  									},
   107  								},
   108  							},
   109  						},
   110  					},
   111  					{
   112  						Name: "controlplane",
   113  						Connection: apiserver.Connection{
   114  							ProxyProtocol: "HTTPConnect",
   115  							Transport: &apiserver.Transport{
   116  								TCP: &apiserver.TCPTransport{
   117  									URL: "https://127.0.0.1:8132",
   118  									TLSConfig: &apiserver.TLSConfig{
   119  										CABundle:   "/etc/srv/kubernetes/pki/konnectivity-server-master/ca.crt",
   120  										ClientKey:  "/etc/srv/kubernetes/pki/konnectivity-server-master/client.key",
   121  										ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.crt",
   122  									},
   123  								},
   124  							},
   125  						},
   126  					},
   127  					{
   128  						Name: "etcd",
   129  						Connection: apiserver.Connection{
   130  							ProxyProtocol: "Direct",
   131  						},
   132  					},
   133  				},
   134  			},
   135  			expectedError: nil,
   136  		},
   137  		{
   138  			name:       "v1beta1 using deprecated 'master' type",
   139  			createFile: true,
   140  			contents: `
   141  apiVersion: apiserver.k8s.io/v1beta1
   142  kind: EgressSelectorConfiguration
   143  egressSelections:
   144  - name: "cluster"
   145    connection:
   146      proxyProtocol: "HTTPConnect"
   147      transport:
   148        tcp:
   149          url: "https://127.0.0.1:8131"
   150          tlsConfig:
   151            caBundle: "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt"
   152            clientKey: "/etc/srv/kubernetes/pki/konnectivity-server/client.key"
   153            clientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt"
   154  - name: "master"
   155    connection:
   156      proxyProtocol: "HTTPConnect"
   157      transport:
   158        tcp:
   159          url: "https://127.0.0.1:8132"
   160          tlsConfig:
   161            caBundle: "/etc/srv/kubernetes/pki/konnectivity-server-master/ca.crt"
   162            clientKey: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.key"
   163            clientCert: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.crt"
   164  - name: "etcd"
   165    connection:
   166      proxyProtocol: "Direct"
   167  `,
   168  			expectedResult: &apiserver.EgressSelectorConfiguration{
   169  				TypeMeta: metav1.TypeMeta{
   170  					Kind:       "",
   171  					APIVersion: "",
   172  				},
   173  				EgressSelections: []apiserver.EgressSelection{
   174  					{
   175  						Name: "cluster",
   176  						Connection: apiserver.Connection{
   177  							ProxyProtocol: "HTTPConnect",
   178  							Transport: &apiserver.Transport{
   179  								TCP: &apiserver.TCPTransport{
   180  									URL: "https://127.0.0.1:8131",
   181  
   182  									TLSConfig: &apiserver.TLSConfig{
   183  										CABundle:   "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt",
   184  										ClientKey:  "/etc/srv/kubernetes/pki/konnectivity-server/client.key",
   185  										ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt",
   186  									},
   187  								},
   188  							},
   189  						},
   190  					},
   191  					{
   192  						Name: "controlplane",
   193  						Connection: apiserver.Connection{
   194  							ProxyProtocol: "HTTPConnect",
   195  							Transport: &apiserver.Transport{
   196  								TCP: &apiserver.TCPTransport{
   197  									URL: "https://127.0.0.1:8132",
   198  									TLSConfig: &apiserver.TLSConfig{
   199  										CABundle:   "/etc/srv/kubernetes/pki/konnectivity-server-master/ca.crt",
   200  										ClientKey:  "/etc/srv/kubernetes/pki/konnectivity-server-master/client.key",
   201  										ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.crt",
   202  									},
   203  								},
   204  							},
   205  						},
   206  					},
   207  					{
   208  						Name: "etcd",
   209  						Connection: apiserver.Connection{
   210  							ProxyProtocol: "Direct",
   211  						},
   212  					},
   213  				},
   214  			},
   215  			expectedError: nil,
   216  		},
   217  		{
   218  			name:       "wrong_type",
   219  			createFile: true,
   220  			contents: `
   221  apiVersion: apps/v1
   222  kind: DaemonSet
   223  metadata:
   224    labels:
   225      addonmanager.kubernetes.io/mode: Reconcile
   226      k8s-app: konnectivity-agent
   227    namespace: kube-system
   228    name: proxy-agent
   229  spec:
   230    selector:
   231      matchLabels:
   232        k8s-app: konnectivity-agent
   233    updateStrategy:
   234      type: RollingUpdate
   235    template:
   236      metadata:
   237        labels:
   238          k8s-app: proxy-agent
   239      spec:
   240        priorityClassName: system-cluster-critical
   241        # Necessary to reboot node
   242        hostPID: true
   243        volumes:
   244          - name: pki
   245            hostPath:
   246              path: /etc/srv/kubernetes/pki/konnectivity-agent
   247        containers:
   248          - image: registry.k8s.io/proxy-agent:v0.0.3
   249            name: proxy-agent
   250            command: ["/proxy-agent"]
   251            args: ["--caCert=/etc/srv/kubernetes/pki/proxy-agent/ca.crt", "--agentCert=/etc/srv/kubernetes/pki/proxy-agent/client.crt", "--agentKey=/etc/srv/kubernetes/pki/proxy-agent/client.key", "--proxyServerHost=127.0.0.1", "--proxyServerPort=8132"]
   252            securityContext:
   253              capabilities:
   254                add: ["SYS_BOOT"]
   255            env:
   256              - name: wrong-type
   257                valueFrom:
   258                  fieldRef:
   259                    fieldPath: metadata.name
   260              - name: kube-system
   261                valueFrom:
   262                  fieldRef:
   263                    fieldPath: metadata.namespace
   264            resources:
   265              limits:
   266                cpu: 50m
   267                memory: 30Mi
   268            volumeMounts:
   269              - name: pki
   270                mountPath: /etc/srv/kubernetes/pki/konnectivity-agent
   271  `,
   272  			expectedResult: nil,
   273  			expectedError:  strptr("invalid service configuration object \"DaemonSet\""),
   274  		},
   275  	}
   276  
   277  	for _, tc := range testcases {
   278  		t.Run(tc.name, func(t *testing.T) {
   279  			proxyConfig := fmt.Sprintf("test-egress-selector-config-%s", tc.name)
   280  			if tc.createFile {
   281  				f, err := os.CreateTemp("", proxyConfig)
   282  				if err != nil {
   283  					t.Fatal(err)
   284  				}
   285  				defer utiltesting.CloseAndRemove(t, f)
   286  				if err := os.WriteFile(f.Name(), []byte(tc.contents), os.FileMode(0755)); err != nil {
   287  					t.Fatal(err)
   288  				}
   289  				proxyConfig = f.Name()
   290  			}
   291  			config, err := ReadEgressSelectorConfiguration(proxyConfig)
   292  			if err == nil && tc.expectedError != nil {
   293  				t.Errorf("calling ReadEgressSelectorConfiguration expected error: %s, did not get it", *tc.expectedError)
   294  			}
   295  			if err != nil && tc.expectedError == nil {
   296  				t.Errorf("unexpected error calling ReadEgressSelectorConfiguration got: %#v", err)
   297  			}
   298  			if err != nil && tc.expectedError != nil && err.Error() != *tc.expectedError {
   299  				t.Errorf("calling ReadEgressSelectorConfiguration expected error: %s, got %#v", *tc.expectedError, err)
   300  			}
   301  			if !reflect.DeepEqual(config, tc.expectedResult) {
   302  				t.Errorf("problem with configuration returned from ReadEgressSelectorConfiguration expected: %#v, got: %#v", tc.expectedResult, config)
   303  			}
   304  		})
   305  	}
   306  }
   307  
   308  func TestValidateEgressSelectorConfiguration(t *testing.T) {
   309  	testcases := []struct {
   310  		name        string
   311  		expectError bool
   312  		contents    *apiserver.EgressSelectorConfiguration
   313  	}{
   314  		{
   315  			name:        "direct-valid",
   316  			expectError: false,
   317  			contents: &apiserver.EgressSelectorConfiguration{
   318  				TypeMeta: metav1.TypeMeta{
   319  					Kind:       "",
   320  					APIVersion: "",
   321  				},
   322  				EgressSelections: []apiserver.EgressSelection{
   323  					{
   324  						Name: "controlplane",
   325  						Connection: apiserver.Connection{
   326  							ProxyProtocol: apiserver.ProtocolDirect,
   327  						},
   328  					},
   329  				},
   330  			},
   331  		},
   332  		{
   333  			name:        "direct-invalid-transport",
   334  			expectError: true,
   335  			contents: &apiserver.EgressSelectorConfiguration{
   336  				TypeMeta: metav1.TypeMeta{
   337  					Kind:       "",
   338  					APIVersion: "",
   339  				},
   340  				EgressSelections: []apiserver.EgressSelection{
   341  					{
   342  						Name: "controlplane",
   343  						Connection: apiserver.Connection{
   344  							ProxyProtocol: apiserver.ProtocolDirect,
   345  							Transport:     &apiserver.Transport{},
   346  						},
   347  					},
   348  				},
   349  			},
   350  		},
   351  		{
   352  			name:        "httpconnect-no-https",
   353  			expectError: false,
   354  			contents: &apiserver.EgressSelectorConfiguration{
   355  				TypeMeta: metav1.TypeMeta{
   356  					Kind:       "",
   357  					APIVersion: "",
   358  				},
   359  				EgressSelections: []apiserver.EgressSelection{
   360  					{
   361  						Name: "cluster",
   362  						Connection: apiserver.Connection{
   363  							ProxyProtocol: apiserver.ProtocolHTTPConnect,
   364  							Transport: &apiserver.Transport{
   365  								TCP: &apiserver.TCPTransport{
   366  									URL: "http://127.0.0.1:8131",
   367  								},
   368  							},
   369  						},
   370  					},
   371  				},
   372  			},
   373  		},
   374  		{
   375  			name:        "httpconnect-https-no-cert-error",
   376  			expectError: true,
   377  			contents: &apiserver.EgressSelectorConfiguration{
   378  				TypeMeta: metav1.TypeMeta{
   379  					Kind:       "",
   380  					APIVersion: "",
   381  				},
   382  				EgressSelections: []apiserver.EgressSelection{
   383  					{
   384  						Name: "cluster",
   385  						Connection: apiserver.Connection{
   386  							ProxyProtocol: apiserver.ProtocolHTTPConnect,
   387  							Transport: &apiserver.Transport{
   388  								TCP: &apiserver.TCPTransport{
   389  									URL: "https://127.0.0.1:8131",
   390  								},
   391  							},
   392  						},
   393  					},
   394  				},
   395  			},
   396  		},
   397  		{
   398  			name:        "httpconnect-tcp-uds-both-set",
   399  			expectError: true,
   400  			contents: &apiserver.EgressSelectorConfiguration{
   401  				TypeMeta: metav1.TypeMeta{
   402  					Kind:       "",
   403  					APIVersion: "",
   404  				},
   405  				EgressSelections: []apiserver.EgressSelection{
   406  					{
   407  						Name: "cluster",
   408  						Connection: apiserver.Connection{
   409  							ProxyProtocol: apiserver.ProtocolHTTPConnect,
   410  							Transport: &apiserver.Transport{
   411  								TCP: &apiserver.TCPTransport{
   412  									URL: "http://127.0.0.1:8131",
   413  								},
   414  								UDS: &apiserver.UDSTransport{
   415  									UDSName: "/etc/srv/kubernetes/konnectivity/konnectivity-server.socket",
   416  								},
   417  							},
   418  						},
   419  					},
   420  				},
   421  			},
   422  		},
   423  		{
   424  			name:        "httpconnect-uds",
   425  			expectError: false,
   426  			contents: &apiserver.EgressSelectorConfiguration{
   427  				TypeMeta: metav1.TypeMeta{
   428  					Kind:       "",
   429  					APIVersion: "",
   430  				},
   431  				EgressSelections: []apiserver.EgressSelection{
   432  					{
   433  						Name: "cluster",
   434  						Connection: apiserver.Connection{
   435  							ProxyProtocol: apiserver.ProtocolHTTPConnect,
   436  							Transport: &apiserver.Transport{
   437  								UDS: &apiserver.UDSTransport{
   438  									UDSName: "/etc/srv/kubernetes/konnectivity/konnectivity-server.socket",
   439  								},
   440  							},
   441  						},
   442  					},
   443  				},
   444  			},
   445  		},
   446  		{
   447  			name:        "grpc-https-invalid",
   448  			expectError: true,
   449  			contents: &apiserver.EgressSelectorConfiguration{
   450  				TypeMeta: metav1.TypeMeta{
   451  					Kind:       "",
   452  					APIVersion: "",
   453  				},
   454  				EgressSelections: []apiserver.EgressSelection{
   455  					{
   456  						Name: "cluster",
   457  						Connection: apiserver.Connection{
   458  							ProxyProtocol: apiserver.ProtocolGRPC,
   459  							Transport: &apiserver.Transport{
   460  								TCP: &apiserver.TCPTransport{
   461  									URL: "http://127.0.0.1:8131",
   462  									TLSConfig: &apiserver.TLSConfig{
   463  										CABundle:   "",
   464  										ClientKey:  "",
   465  										ClientCert: "",
   466  									},
   467  								},
   468  							},
   469  						},
   470  					},
   471  				},
   472  			},
   473  		},
   474  		{
   475  			name:        "grpc-uds",
   476  			expectError: false,
   477  			contents: &apiserver.EgressSelectorConfiguration{
   478  				TypeMeta: metav1.TypeMeta{
   479  					Kind:       "",
   480  					APIVersion: "",
   481  				},
   482  				EgressSelections: []apiserver.EgressSelection{
   483  					{
   484  						Name: "cluster",
   485  						Connection: apiserver.Connection{
   486  							ProxyProtocol: apiserver.ProtocolGRPC,
   487  							Transport: &apiserver.Transport{
   488  								UDS: &apiserver.UDSTransport{
   489  									UDSName: "/etc/srv/kubernetes/konnectivity/konnectivity-server.socket",
   490  								},
   491  							},
   492  						},
   493  					},
   494  				},
   495  			},
   496  		},
   497  		{
   498  			name:        "invalid egress selection name",
   499  			expectError: true,
   500  			contents: &apiserver.EgressSelectorConfiguration{
   501  				TypeMeta: metav1.TypeMeta{
   502  					Kind:       "",
   503  					APIVersion: "",
   504  				},
   505  				EgressSelections: []apiserver.EgressSelection{
   506  					{
   507  						Name: "invalid",
   508  						Connection: apiserver.Connection{
   509  							ProxyProtocol: apiserver.ProtocolDirect,
   510  						},
   511  					},
   512  				},
   513  			},
   514  		},
   515  		{
   516  			name:        "duplicate egress selections configured",
   517  			expectError: true,
   518  			contents: &apiserver.EgressSelectorConfiguration{
   519  				TypeMeta: metav1.TypeMeta{
   520  					Kind:       "",
   521  					APIVersion: "",
   522  				},
   523  				EgressSelections: []apiserver.EgressSelection{
   524  					{
   525  						Name: "controlplane",
   526  						Connection: apiserver.Connection{
   527  							ProxyProtocol: apiserver.ProtocolDirect,
   528  						},
   529  					},
   530  					{
   531  						Name: "controlplane",
   532  						Connection: apiserver.Connection{
   533  							ProxyProtocol: apiserver.ProtocolDirect,
   534  						},
   535  					},
   536  				},
   537  			},
   538  		},
   539  	}
   540  
   541  	for _, tc := range testcases {
   542  		t.Run(tc.name, func(t *testing.T) {
   543  			errs := ValidateEgressSelectorConfiguration(tc.contents)
   544  			if !tc.expectError && len(errs) != 0 {
   545  				t.Errorf("Calling ValidateEgressSelectorConfiguration expected no error, got %v", errs)
   546  			} else if tc.expectError && len(errs) == 0 {
   547  				t.Errorf("Calling ValidateEgressSelectorConfiguration expected error, got no error")
   548  			}
   549  		})
   550  	}
   551  }