apiserver/pkg/server/egressselector/config_test.go

551 lines
15 KiB
Go

/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package egressselector
import (
"fmt"
"io/ioutil"
"os"
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/apis/apiserver"
)
func strptr(s string) *string {
return &s
}
func TestReadEgressSelectorConfiguration(t *testing.T) {
testcases := []struct {
name string
contents string
createFile bool
expectedResult *apiserver.EgressSelectorConfiguration
expectedError *string
}{
{
name: "empty",
createFile: true,
contents: ``,
expectedResult: nil,
expectedError: strptr("invalid service configuration object \"\""),
},
{
name: "absent",
createFile: false,
contents: ``,
expectedResult: nil,
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]"),
},
{
name: "v1beta1",
createFile: true,
contents: `
apiVersion: apiserver.k8s.io/v1beta1
kind: EgressSelectorConfiguration
egressSelections:
- name: "cluster"
connection:
proxyProtocol: "HTTPConnect"
transport:
tcp:
url: "https://127.0.0.1:8131"
tlsConfig:
caBundle: "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt"
clientKey: "/etc/srv/kubernetes/pki/konnectivity-server/client.key"
clientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt"
- name: "controlplane"
connection:
proxyProtocol: "HTTPConnect"
transport:
tcp:
url: "https://127.0.0.1:8132"
tlsConfig:
caBundle: "/etc/srv/kubernetes/pki/konnectivity-server-master/ca.crt"
clientKey: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.key"
clientCert: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.crt"
- name: "etcd"
connection:
proxyProtocol: "Direct"
`,
expectedResult: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "cluster",
Connection: apiserver.Connection{
ProxyProtocol: "HTTPConnect",
Transport: &apiserver.Transport{
TCP: &apiserver.TCPTransport{
URL: "https://127.0.0.1:8131",
TLSConfig: &apiserver.TLSConfig{
CABundle: "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt",
ClientKey: "/etc/srv/kubernetes/pki/konnectivity-server/client.key",
ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt",
},
},
},
},
},
{
Name: "controlplane",
Connection: apiserver.Connection{
ProxyProtocol: "HTTPConnect",
Transport: &apiserver.Transport{
TCP: &apiserver.TCPTransport{
URL: "https://127.0.0.1:8132",
TLSConfig: &apiserver.TLSConfig{
CABundle: "/etc/srv/kubernetes/pki/konnectivity-server-master/ca.crt",
ClientKey: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.key",
ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.crt",
},
},
},
},
},
{
Name: "etcd",
Connection: apiserver.Connection{
ProxyProtocol: "Direct",
},
},
},
},
expectedError: nil,
},
{
name: "v1beta1 using deprecated 'master' type",
createFile: true,
contents: `
apiVersion: apiserver.k8s.io/v1beta1
kind: EgressSelectorConfiguration
egressSelections:
- name: "cluster"
connection:
proxyProtocol: "HTTPConnect"
transport:
tcp:
url: "https://127.0.0.1:8131"
tlsConfig:
caBundle: "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt"
clientKey: "/etc/srv/kubernetes/pki/konnectivity-server/client.key"
clientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt"
- name: "master"
connection:
proxyProtocol: "HTTPConnect"
transport:
tcp:
url: "https://127.0.0.1:8132"
tlsConfig:
caBundle: "/etc/srv/kubernetes/pki/konnectivity-server-master/ca.crt"
clientKey: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.key"
clientCert: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.crt"
- name: "etcd"
connection:
proxyProtocol: "Direct"
`,
expectedResult: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "cluster",
Connection: apiserver.Connection{
ProxyProtocol: "HTTPConnect",
Transport: &apiserver.Transport{
TCP: &apiserver.TCPTransport{
URL: "https://127.0.0.1:8131",
TLSConfig: &apiserver.TLSConfig{
CABundle: "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt",
ClientKey: "/etc/srv/kubernetes/pki/konnectivity-server/client.key",
ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt",
},
},
},
},
},
{
Name: "controlplane",
Connection: apiserver.Connection{
ProxyProtocol: "HTTPConnect",
Transport: &apiserver.Transport{
TCP: &apiserver.TCPTransport{
URL: "https://127.0.0.1:8132",
TLSConfig: &apiserver.TLSConfig{
CABundle: "/etc/srv/kubernetes/pki/konnectivity-server-master/ca.crt",
ClientKey: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.key",
ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.crt",
},
},
},
},
},
{
Name: "etcd",
Connection: apiserver.Connection{
ProxyProtocol: "Direct",
},
},
},
},
expectedError: nil,
},
{
name: "wrong_type",
createFile: true,
contents: `
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
addonmanager.kubernetes.io/mode: Reconcile
k8s-app: konnectivity-agent
namespace: kube-system
name: proxy-agent
spec:
selector:
matchLabels:
k8s-app: konnectivity-agent
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
k8s-app: proxy-agent
spec:
priorityClassName: system-cluster-critical
# Necessary to reboot node
hostPID: true
volumes:
- name: pki
hostPath:
path: /etc/srv/kubernetes/pki/konnectivity-agent
containers:
- image: registry.k8s.io/proxy-agent:v0.0.3
name: proxy-agent
command: ["/proxy-agent"]
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"]
securityContext:
capabilities:
add: ["SYS_BOOT"]
env:
- name: wrong-type
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: kube-system
valueFrom:
fieldRef:
fieldPath: metadata.namespace
resources:
limits:
cpu: 50m
memory: 30Mi
volumeMounts:
- name: pki
mountPath: /etc/srv/kubernetes/pki/konnectivity-agent
`,
expectedResult: nil,
expectedError: strptr("invalid service configuration object \"DaemonSet\""),
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
proxyConfig := fmt.Sprintf("test-egress-selector-config-%s", tc.name)
if tc.createFile {
f, err := ioutil.TempFile("", proxyConfig)
if err != nil {
t.Fatal(err)
}
defer os.Remove(f.Name())
if err := ioutil.WriteFile(f.Name(), []byte(tc.contents), os.FileMode(0755)); err != nil {
t.Fatal(err)
}
proxyConfig = f.Name()
}
config, err := ReadEgressSelectorConfiguration(proxyConfig)
if err == nil && tc.expectedError != nil {
t.Errorf("calling ReadEgressSelectorConfiguration expected error: %s, did not get it", *tc.expectedError)
}
if err != nil && tc.expectedError == nil {
t.Errorf("unexpected error calling ReadEgressSelectorConfiguration got: %#v", err)
}
if err != nil && tc.expectedError != nil && err.Error() != *tc.expectedError {
t.Errorf("calling ReadEgressSelectorConfiguration expected error: %s, got %#v", *tc.expectedError, err)
}
if !reflect.DeepEqual(config, tc.expectedResult) {
t.Errorf("problem with configuration returned from ReadEgressSelectorConfiguration expected: %#v, got: %#v", tc.expectedResult, config)
}
})
}
}
func TestValidateEgressSelectorConfiguration(t *testing.T) {
testcases := []struct {
name string
expectError bool
contents *apiserver.EgressSelectorConfiguration
}{
{
name: "direct-valid",
expectError: false,
contents: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "controlplane",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolDirect,
},
},
},
},
},
{
name: "direct-invalid-transport",
expectError: true,
contents: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "controlplane",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolDirect,
Transport: &apiserver.Transport{},
},
},
},
},
},
{
name: "httpconnect-no-https",
expectError: false,
contents: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "cluster",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolHTTPConnect,
Transport: &apiserver.Transport{
TCP: &apiserver.TCPTransport{
URL: "http://127.0.0.1:8131",
},
},
},
},
},
},
},
{
name: "httpconnect-https-no-cert-error",
expectError: true,
contents: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "cluster",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolHTTPConnect,
Transport: &apiserver.Transport{
TCP: &apiserver.TCPTransport{
URL: "https://127.0.0.1:8131",
},
},
},
},
},
},
},
{
name: "httpconnect-tcp-uds-both-set",
expectError: true,
contents: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "cluster",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolHTTPConnect,
Transport: &apiserver.Transport{
TCP: &apiserver.TCPTransport{
URL: "http://127.0.0.1:8131",
},
UDS: &apiserver.UDSTransport{
UDSName: "/etc/srv/kubernetes/konnectivity/konnectivity-server.socket",
},
},
},
},
},
},
},
{
name: "httpconnect-uds",
expectError: false,
contents: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "cluster",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolHTTPConnect,
Transport: &apiserver.Transport{
UDS: &apiserver.UDSTransport{
UDSName: "/etc/srv/kubernetes/konnectivity/konnectivity-server.socket",
},
},
},
},
},
},
},
{
name: "grpc-https-invalid",
expectError: true,
contents: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "cluster",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolGRPC,
Transport: &apiserver.Transport{
TCP: &apiserver.TCPTransport{
URL: "http://127.0.0.1:8131",
TLSConfig: &apiserver.TLSConfig{
CABundle: "",
ClientKey: "",
ClientCert: "",
},
},
},
},
},
},
},
},
{
name: "grpc-uds",
expectError: false,
contents: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "cluster",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolGRPC,
Transport: &apiserver.Transport{
UDS: &apiserver.UDSTransport{
UDSName: "/etc/srv/kubernetes/konnectivity/konnectivity-server.socket",
},
},
},
},
},
},
},
{
name: "invalid egress selection name",
expectError: true,
contents: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "invalid",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolDirect,
},
},
},
},
},
{
name: "duplicate egress selections configured",
expectError: true,
contents: &apiserver.EgressSelectorConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "",
APIVersion: "",
},
EgressSelections: []apiserver.EgressSelection{
{
Name: "controlplane",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolDirect,
},
},
{
Name: "controlplane",
Connection: apiserver.Connection{
ProxyProtocol: apiserver.ProtocolDirect,
},
},
},
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
errs := ValidateEgressSelectorConfiguration(tc.contents)
if tc.expectError == false && len(errs) != 0 {
t.Errorf("Calling ValidateEgressSelectorConfiguration expected no error, got %v", errs)
} else if tc.expectError == true && len(errs) == 0 {
t.Errorf("Calling ValidateEgressSelectorConfiguration expected error, got no error")
}
})
}
}