diff --git a/pkg/cloudprovider/providers/vsphere/BUILD b/pkg/cloudprovider/providers/vsphere/BUILD index d6309bd52a9..8c0be034f9c 100644 --- a/pkg/cloudprovider/providers/vsphere/BUILD +++ b/pkg/cloudprovider/providers/vsphere/BUILD @@ -45,6 +45,7 @@ go_test( deps = [ "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers/vsphere/vclib:go_default_library", + "//pkg/cloudprovider/providers/vsphere/vclib/fixtures:go_default_library", "//pkg/controller:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/cloudprovider/providers/vsphere/vclib/BUILD b/pkg/cloudprovider/providers/vsphere/vclib/BUILD index 9bb59c3ab16..dddd496c59d 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/BUILD +++ b/pkg/cloudprovider/providers/vsphere/vclib/BUILD @@ -52,6 +52,7 @@ filegroup( srcs = [ ":package-srcs", "//pkg/cloudprovider/providers/vsphere/vclib/diskmanagers:all-srcs", + "//pkg/cloudprovider/providers/vsphere/vclib/fixtures:all-srcs", ], tags = ["automanaged"], ) @@ -59,6 +60,7 @@ filegroup( go_test( name = "go_default_test", srcs = [ + "connection_test.go", "datacenter_test.go", "datastore_test.go", "folder_test.go", @@ -67,6 +69,7 @@ go_test( ], embed = [":go_default_library"], deps = [ + "//pkg/cloudprovider/providers/vsphere/vclib/fixtures:go_default_library", "//vendor/github.com/vmware/govmomi:go_default_library", "//vendor/github.com/vmware/govmomi/object:go_default_library", "//vendor/github.com/vmware/govmomi/simulator:go_default_library", diff --git a/pkg/cloudprovider/providers/vsphere/vclib/connection.go b/pkg/cloudprovider/providers/vsphere/vclib/connection.go index 91f0e8a963a..63d2cdbdff1 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/connection.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/connection.go @@ -38,6 +38,8 @@ type VSphereConnection struct { Password string Hostname string Port string + CACert string + Thumbprint string Insecure bool RoundTripperCount uint credentialsLock sync.Mutex @@ -130,6 +132,16 @@ func (connection *VSphereConnection) login(ctx context.Context, client *vim25.Cl // Logout calls SessionManager.Logout for the given connection. func (connection *VSphereConnection) Logout(ctx context.Context) { m := session.NewManager(connection.Client) + + hasActiveSession, err := m.SessionIsActive(ctx) + if err != nil { + glog.Errorf("Logout failed: %s", err) + return + } + if !hasActiveSession { + glog.Errorf("No active session, cannot logout") + return + } if err := m.Logout(ctx); err != nil { glog.Errorf("Logout failed: %s", err) } @@ -144,11 +156,22 @@ func (connection *VSphereConnection) NewClient(ctx context.Context) (*vim25.Clie } sc := soap.NewClient(url, connection.Insecure) + + if ca := connection.CACert; ca != "" { + if err := sc.SetRootCAs(ca); err != nil { + return nil, err + } + } + + tpHost := connection.Hostname + ":" + connection.Port + sc.SetThumbprint(tpHost, connection.Thumbprint) + client, err := vim25.NewClient(ctx, sc) if err != nil { glog.Errorf("Failed to create new client. err: %+v", err) return nil, err } + err = connection.login(ctx, client) if err != nil { return nil, err diff --git a/pkg/cloudprovider/providers/vsphere/vclib/connection_test.go b/pkg/cloudprovider/providers/vsphere/vclib/connection_test.go new file mode 100644 index 00000000000..b5569d92ae1 --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/vclib/connection_test.go @@ -0,0 +1,224 @@ +/* +Copyright 2018 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 vclib_test + +import ( + "context" + "crypto/sha1" + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib" + "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib/fixtures" +) + +func createTestServer( + t *testing.T, + caCertPath string, + serverCertPath string, + serverKeyPath string, + handler http.HandlerFunc, +) (*httptest.Server, string) { + caCertPEM, err := ioutil.ReadFile(caCertPath) + if err != nil { + t.Fatalf("Could not read ca cert from file") + } + + serverCert, err := tls.LoadX509KeyPair(serverCertPath, serverKeyPath) + if err != nil { + t.Fatalf("Could not load server cert and server key from files: %#v", err) + } + + certPool := x509.NewCertPool() + if ok := certPool.AppendCertsFromPEM(caCertPEM); !ok { + t.Fatalf("Cannot add CA to CAPool") + } + + server := httptest.NewUnstartedServer(http.HandlerFunc(handler)) + server.TLS = &tls.Config{ + Certificates: []tls.Certificate{ + serverCert, + }, + RootCAs: certPool, + } + + // calculate the leaf certificate's fingerprint + if len(server.TLS.Certificates) < 1 || len(server.TLS.Certificates[0].Certificate) < 1 { + t.Fatal("Expected server.TLS.Certificates not to be empty") + } + x509LeafCert := server.TLS.Certificates[0].Certificate[0] + var tpString string + for i, b := range sha1.Sum(x509LeafCert) { + if i > 0 { + tpString += ":" + } + tpString += fmt.Sprintf("%02X", b) + } + + return server, tpString +} + +func TestWithValidCaCert(t *testing.T) { + handler, verifyConnectionWasMade := getRequestVerifier(t) + + server, _ := createTestServer(t, fixtures.CaCertPath, fixtures.ServerCertPath, fixtures.ServerKeyPath, handler) + server.StartTLS() + u := mustParseUrl(t, server.URL) + + connection := &vclib.VSphereConnection{ + Hostname: u.Hostname(), + Port: u.Port(), + CACert: fixtures.CaCertPath, + } + + // Ignoring error here, because we only care about the TLS connection + connection.NewClient(context.Background()) + + verifyConnectionWasMade() +} + +func TestWithVerificationWithWrongThumbprint(t *testing.T) { + handler, _ := getRequestVerifier(t) + + server, _ := createTestServer(t, fixtures.CaCertPath, fixtures.ServerCertPath, fixtures.ServerKeyPath, handler) + server.StartTLS() + u := mustParseUrl(t, server.URL) + + connection := &vclib.VSphereConnection{ + Hostname: u.Hostname(), + Port: u.Port(), + Thumbprint: "obviously wrong", + } + + _, err := connection.NewClient(context.Background()) + + if msg := err.Error(); !strings.Contains(msg, "thumbprint does not match") { + t.Fatalf("Expected wrong thumbprint error, got '%s'", msg) + } +} + +func TestWithVerificationWithoutCaCertOrThumbprint(t *testing.T) { + handler, _ := getRequestVerifier(t) + + server, _ := createTestServer(t, fixtures.CaCertPath, fixtures.ServerCertPath, fixtures.ServerKeyPath, handler) + server.StartTLS() + u := mustParseUrl(t, server.URL) + + connection := &vclib.VSphereConnection{ + Hostname: u.Hostname(), + Port: u.Port(), + } + + _, err := connection.NewClient(context.Background()) + + verifyWrappedX509UnkownAuthorityErr(t, err) +} + +func TestWithValidThumbprint(t *testing.T) { + handler, verifyConnectionWasMade := getRequestVerifier(t) + + server, thumbprint := + createTestServer(t, fixtures.CaCertPath, fixtures.ServerCertPath, fixtures.ServerKeyPath, handler) + server.StartTLS() + u := mustParseUrl(t, server.URL) + + connection := &vclib.VSphereConnection{ + Hostname: u.Hostname(), + Port: u.Port(), + Thumbprint: thumbprint, + } + + // Ignoring error here, because we only care about the TLS connection + connection.NewClient(context.Background()) + + verifyConnectionWasMade() +} + +func TestWithInvalidCaCertPath(t *testing.T) { + connection := &vclib.VSphereConnection{ + Hostname: "should-not-matter", + Port: "should-not-matter", + CACert: "invalid-path", + } + + _, err := connection.NewClient(context.Background()) + if _, ok := err.(*os.PathError); !ok { + t.Fatalf("Expected an os.PathError, got: '%s' (%#v)", err.Error(), err) + } +} + +func TestInvalidCaCert(t *testing.T) { + t.Skip("Waiting for https://github.com/vmware/govmomi/pull/1154") + + connection := &vclib.VSphereConnection{ + Hostname: "should-not-matter", + Port: "should-not-matter", + CACert: fixtures.InvalidCertPath, + } + + _, err := connection.NewClient(context.Background()) + + if msg := err.Error(); !strings.Contains(msg, "invalid certificate") { + t.Fatalf("Expected invalid certificate error, got '%s'", msg) + } +} + +func verifyWrappedX509UnkownAuthorityErr(t *testing.T, err error) { + urlErr, ok := err.(*url.Error) + if !ok { + t.Fatalf("Expected to receive an url.Error, got '%s' (%#v)", err.Error(), err) + } + x509Err, ok := urlErr.Err.(x509.UnknownAuthorityError) + if !ok { + t.Fatalf("Expected to receive a wrapped x509.UnknownAuthorityError, got: '%s' (%#v)", urlErr.Error(), urlErr) + } + if msg := x509Err.Error(); msg != "x509: certificate signed by unknown authority" { + t.Fatalf("Expected 'signed by unknown authority' error, got: '%s'", msg) + } +} + +func getRequestVerifier(t *testing.T) (http.HandlerFunc, func()) { + gotRequest := false + + handler := func(w http.ResponseWriter, r *http.Request) { + gotRequest = true + } + + checker := func() { + if !gotRequest { + t.Fatalf("Never saw a request, maybe TLS connection could not be established?") + } + } + + return handler, checker +} + +func mustParseUrl(t *testing.T, i string) *url.URL { + u, err := url.Parse(i) + if err != nil { + t.Fatalf("Cannot parse URL: %v", err) + } + return u +} diff --git a/pkg/cloudprovider/providers/vsphere/vclib/fixtures/BUILD b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/BUILD new file mode 100644 index 00000000000..026ee7bd229 --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/BUILD @@ -0,0 +1,26 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fixtures.go"], + data = glob([ + "*.pem", + "*.key", + ]), + importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib/fixtures", + visibility = ["//visibility:public"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/cloudprovider/providers/vsphere/vclib/fixtures/ca.key b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/ca.key new file mode 100644 index 00000000000..84b4d3266df --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/ca.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/pkg/cloudprovider/providers/vsphere/vclib/fixtures/ca.pem b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/ca.pem new file mode 100644 index 00000000000..9e03ae78762 --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/ca.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE/jCCAuYCCQDRJ2qPhdmG0DANBgkqhkiG9w0BAQsFADBAMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAoMCkFjbWUsIEluYy4xDzANBgNVBAMMBnNv +bWVDQTAgFw0xODA2MDgxMzM5MjFaGA8yMjE4MDQyMTEzMzkyMVowQDELMAkGA1UE +BhMCVVMxCzAJBgNVBAgMAkNBMRMwEQYDVQQKDApBY21lLCBJbmMuMQ8wDQYDVQQD +DAZzb21lQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDgIovAI/Ax +QhVoaG9nEuZcTMN+UgtVoYJlFEFt9i5x/KzKVP8ko8yUqzc5E1VbVx2JbXP9lSUC +U356qrjRZJVAmotR5eW2x9nB6Z0WZ/dIBYi72/3AjmaEtAkyHZc8o0gsIGGsRP8A +0tU9s5slQW8Zq+lH1dVdNewtS+4JH6hKkO9BjKdYonl0DCopoXHcYIQUCbR02dLO +WJCvMoU4TQNQzEVAfku3YRgsevJr4rhm1htfmcpf75P1HmlmzSemqZNBL+sh9+/a +FPz20p2o/P8wK3nHCaOwA7a6uLk75ZotQiR/wF1+ZUC6wT/m/anfHcwfStRdo0+D +sWouPVydtzAeqq3c+ZWX1Vkg2Q9ucceK5rbUY2oMBn8b+8/z+GVfAh4Tx3pg+xI+ +bg0QfQq77KTRMQvpCQYUxhinILDUE1gZd37O9/XGNm0atxWIeF6zG6/vWcZ0ls1K +LvPCRhfJ1IoI2eMn46rKKnm5QL4ObJ2pwlNAtxlbk5s194Hw8vPpS5WJ0x9+Hx68 +TXcvrRxLnBnJlaF6syoH4j+5ES3TmCKQK2UlU6iyG4tLRCNsr8g1gJIUEmO3TGvB +NvDXdJCwSGS1Bh2pMdoMFaLq6H6lxj2awXWNkn4YAQ9hwvMrejb+QTqDT1NndAN4 +/1sQNOWUv6YizgkaJYY0L94aMZ/LICd+YQIDAQABMA0GCSqGSIb3DQEBCwUAA4IC +AQBYBRH/q3gB4gEiOAUl9HbnoUb7MznZ0uQTH7fUYqr66ceZkg9w1McbwiAeZAaY +qQWwr3u4A8/Bg8csE2yQTsXeA33FP3Q6obyuYn4q7e++4+9SLkbSSQfbB67pGUK5 +/pal6ULrLGzs69fbL1tOaA/VKQJndg3N9cftyiIUWTzHDop8SLmIobWVRtPQHf00 +oKq8loakyluQdxQxnGdl7vMXwSpSpIH84TOdy2JN90MzVLgOz55sb/wRYfhClNFD ++1sb2V4nL2w1kXaO2UVPzk7qpG5FE54JPvvN67Ec4JjMSnGo8l3dJ9jGEmgBIML3 +l1onrti2HStSs1vR4Ax0xok08okRlrGA4FqQiSx853T5uLa/JLmWfLKg9ixR4ZV+ +dF+2ZrFwDLZUr4VeaDd2v2mQFBNLvdZrqp1OZ4B/1+H5S8ucb+oVhGqzDkEvRCc+ +WYpNxx7kpwZPTLmMYTXXKdTWfpgz9GL0LSkY8d1rxLwHxtV8EzAkV+zIWix4h/IE +0FG4WvhrttMCu8ulZhGGoVqy7gdb4+ViWnUYNuCCjIcRJj7SeZaDawBASa/jZwik +Hxrwn0osGUqEUBmvjDdXJpTaKCr2GFOvhCM2pG6AXa14b5hS2DgbX+NZYcScYtVC +vn2HMDjnIEF4uOfDJU5eLok4jli5+VwzOQ7hOHs3DIm4+g== +-----END CERTIFICATE----- diff --git a/pkg/cloudprovider/providers/vsphere/vclib/fixtures/createCerts.sh b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/createCerts.sh new file mode 100755 index 00000000000..913c7f9e400 --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/createCerts.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +# Copyright 2018 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. + +set -eu + +readonly VALID_DAYS='73000' +readonly RSA_KEY_SIZE='4096' + +createKey() { + openssl genrsa \ + -out "$1" \ + "$RSA_KEY_SIZE" +} + +createCaCert() { + openssl req \ + -x509 \ + -subj "$( getSubj 'someCA' )" \ + -new \ + -nodes \ + -key "$2" \ + -sha256 \ + -days "$VALID_DAYS" \ + -out "$1" +} + +createCSR() { + openssl req \ + -new \ + -sha256 \ + -key "$2" \ + -subj "$( getSubj 'localhost' )" \ + -reqexts SAN \ + -config <( getSANConfig ) \ + -out "$1" +} + +signCSR() { + openssl x509 \ + -req \ + -in "$2" \ + -CA "$3" \ + -CAkey "$4" \ + -CAcreateserial \ + -days "$VALID_DAYS" \ + -sha256 \ + -extfile <( getSAN ) \ + -out "$1" +} + +getSubj() { + local cn="${1:-someRandomCN}" + echo "/C=US/ST=CA/O=Acme, Inc./CN=${cn}" +} + +getSAN() { + printf "subjectAltName=DNS:localhost,IP:127.0.0.1" +} + +getSANConfig() { + cat /etc/ssl/openssl.cnf + printf '\n[SAN]\n' + getSAN +} + +main() { + local caCertPath="./ca.pem" + local caKeyPath="./ca.key" + local serverCsrPath="./server.csr" + local serverCertPath="./server.pem" + local serverKeyPath="./server.key" + + createKey "$caKeyPath" + createCaCert "$caCertPath" "$caKeyPath" + createKey "$serverKeyPath" + createCSR "$serverCsrPath" "$serverKeyPath" + signCSR "$serverCertPath" "$serverCsrPath" "$caCertPath" "$caKeyPath" +} + +main "$@" diff --git a/pkg/cloudprovider/providers/vsphere/vclib/fixtures/fixtures.go b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/fixtures.go new file mode 100644 index 00000000000..610dbf9e10c --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/fixtures.go @@ -0,0 +1,65 @@ +/* +Copyright 2018 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 fixtures + +import ( + "os" + "path/filepath" + "runtime" + "strings" +) + +var ( + // CaCertPath is the filepath to a certificate that can be used as a CA + // certificate. + CaCertPath string + // ServerCertPath is the filepath to a leaf certifiacte signed by the CA at + // `CaCertPath`. + ServerCertPath string + // ServerKeyPath is the filepath to the private key for the ceritifiacte at + // `ServerCertPath`. + ServerKeyPath string + // InvalidCertPath is the filepath to an invalid certificate. + InvalidCertPath string +) + +func init() { + _, thisFile, _, ok := runtime.Caller(0) + if !ok { + panic("Cannot get path to the fixtures") + } + + fixturesDir := filepath.Dir(thisFile) + + cwd, err := os.Getwd() + if err != nil { + panic("Cannot get CWD: " + err.Error()) + } + + // When tests run in a bazel sandbox `runtime.Caller()` + // returns a relative path, when run with plain `go test` the path + // returned is absolute. To make those fixtures work in both those cases, + // we prepend the CWD iff the CWD is not yet part of the path to the fixtures. + if !strings.HasPrefix(fixturesDir, cwd) { + fixturesDir = filepath.Join(cwd, fixturesDir) + } + + CaCertPath = filepath.Join(fixturesDir, "ca.pem") + ServerCertPath = filepath.Join(fixturesDir, "server.pem") + ServerKeyPath = filepath.Join(fixturesDir, "server.key") + InvalidCertPath = filepath.Join(fixturesDir, "invalid.pem") +} diff --git a/pkg/cloudprovider/providers/vsphere/vclib/fixtures/invalid.pem b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/invalid.pem new file mode 100644 index 00000000000..253d11958ad --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/invalid.pem @@ -0,0 +1 @@ +this is some invalid content diff --git a/pkg/cloudprovider/providers/vsphere/vclib/fixtures/server.csr b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/server.csr new file mode 100644 index 00000000000..fb91630515b --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/server.csr @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEtTCCAp0CAQAwQzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRMwEQYDVQQK +DApBY21lLCBJbmMuMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCVkk5HMKNvMXVJoJcUfKK252UT6rdnlsaFLZOlcbp3 +otqiq3A2jhQLeL5Ocyd22s/ak2RX9liK+ynV8fP3YWoUBP5elhwbykubiIvSTRS5 +85Z0s9NfzscImMpnivt+bOy3KOoriy/0jfJ7WMqLRUTUEusXUpW8QT/U9cK6DrwQ +E/9oXTr669yvqjyFsxjOB0pLOFFib0LeQZxrA2h+oAP8qT/Of6kyTgGWjLhSC1cV +eCPZsSeZUT61FbIu/b5M42WYuddoFbf8y9m0oLeYizYob7poE25jw91bNa8y2nfS +v+JuCcfO4wq29cnldGFNpJPhBhc1sbBvVshXXKWdfzN1c8RCS5hNANy1phAJ7RFe +3Uj0WneBVBHHJMz7Qh61uxTST1W8HBDTuaBTxGKTcPFWd9u4lj/BEScRFOSC/qiO +1HCKzOsYhjnHfql5GzfQKpEy/e4m2oL8VTqcJBsfHCyxDIH+6Y3ovttymxAUPJ14 +r3mG9FDLq1va/+8xzDswyjmRIVQeOgvllzgM5vCKqz6nsXtLRYgkwHMk5yOaAIzO +BnsmZztsyaubjcYvM5pUsiO49VWk6ntiAn+WpF/sreFlesx1peQKbTVovwvn137d +V92Oncce+ZikKHxtz4qOz+dH1Fz7Ykor8fXcsfdbkKvwWdz8U/pOBu+83CxBXTWA +bwIDAQABoC0wKwYJKoZIhvcNAQkOMR4wHDAaBgNVHREEEzARgglsb2NhbGhvc3SH +BH8AAAEwDQYJKoZIhvcNAQELBQADggIBADgJfI3xRKlOInZQjg+afz+L477IiFmP +Pf0qwO/EqBkCmbDbmvXpXi/y9Ffh6bMx2naN873nW3k1uVG2W0O4Bl7di9PkmRxY +ktcWY+CaxDT5+Y3LmrqICgrZmELTuV5G8xX2/7bpdEtY4sWpoOeOun+CeGTCeUGx +sGxOWrhydYwrkowupPthYreIIBBPHWl2gEw/m+Y7aJZGtKnDD9eCbF6RxmXRWHDu +0Ly+F3veXbht9LjKPFsgfsogo33Nl8+W1LCActKNY7NMDdGkc+RqaTyxhYEwomui +N1NDOW1qHqSyp2RC13cXokfLL58WGXS6PpNhSln9u4ZG9a+TY+vw1qC//1CyTicY +ylyEn2qfqTSG3W7T/u6ZTL0MpMjFv8VigpffJcFDjq6lVH8LyTniSXdCREy78jAo +8O/2tzJtWrar8bbeN7KCwVcJVaK15a1GWZmo5Ei33U/2Tm+UyRbWL8eISO2Hs3WM +90aFPaHfqKpiPsJrnnOm270lZclgqEtpsyuLsAClqxytCYPw4zTa6WOfDJtmVUrT +1fvMjqwzvs7jbNrgfkwSxXiABwTMQQWeAtuSO+zZH4Ms10qyANoh4FFi/oS3dRKQ +0kdu7AsJqnou9q9HWq1WCTqMcyNE0KPHuo4xhtOlWoGbsugTs7XBml30D7bKJVfG +PazsY1b0/cx7 +-----END CERTIFICATE REQUEST----- diff --git a/pkg/cloudprovider/providers/vsphere/vclib/fixtures/server.key b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/server.key new file mode 100644 index 00000000000..643d5463874 --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/server.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/pkg/cloudprovider/providers/vsphere/vclib/fixtures/server.pem b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/server.pem new file mode 100644 index 00000000000..dbebde6a803 --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/vclib/fixtures/server.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFJjCCAw6gAwIBAgIJAOcEAbv8NslfMA0GCSqGSIb3DQEBCwUAMEAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTETMBEGA1UECgwKQWNtZSwgSW5jLjEPMA0GA1UE +AwwGc29tZUNBMCAXDTE4MDYwODEzMzkyNFoYDzIyMTgwNDIxMTMzOTI0WjBDMQsw +CQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEzARBgNVBAoMCkFjbWUsIEluYy4xEjAQ +BgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AJWSTkcwo28xdUmglxR8orbnZRPqt2eWxoUtk6Vxunei2qKrcDaOFAt4vk5zJ3ba +z9qTZFf2WIr7KdXx8/dhahQE/l6WHBvKS5uIi9JNFLnzlnSz01/OxwiYymeK+35s +7Lco6iuLL/SN8ntYyotFRNQS6xdSlbxBP9T1wroOvBAT/2hdOvrr3K+qPIWzGM4H +Sks4UWJvQt5BnGsDaH6gA/ypP85/qTJOAZaMuFILVxV4I9mxJ5lRPrUVsi79vkzj +ZZi512gVt/zL2bSgt5iLNihvumgTbmPD3Vs1rzLad9K/4m4Jx87jCrb1yeV0YU2k +k+EGFzWxsG9WyFdcpZ1/M3VzxEJLmE0A3LWmEAntEV7dSPRad4FUEcckzPtCHrW7 +FNJPVbwcENO5oFPEYpNw8VZ327iWP8ERJxEU5IL+qI7UcIrM6xiGOcd+qXkbN9Aq +kTL97ibagvxVOpwkGx8cLLEMgf7pjei+23KbEBQ8nXiveYb0UMurW9r/7zHMOzDK +OZEhVB46C+WXOAzm8IqrPqexe0tFiCTAcyTnI5oAjM4GeyZnO2zJq5uNxi8zmlSy +I7j1VaTqe2ICf5akX+yt4WV6zHWl5AptNWi/C+fXft1X3Y6dxx75mKQofG3Pio7P +50fUXPtiSivx9dyx91uQq/BZ3PxT+k4G77zcLEFdNYBvAgMBAAGjHjAcMBoGA1Ud +EQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAgEABL8kffi7 +48qSD+/l/UwCYdmqta1vAbOkvLnPtfXe1XlDpJipNuPxUBc8nNTemtrbg0erNJnC +jQHodqmdKBJJOdaEKTwAGp5pYvvjlU3WasmhfJy+QwOWgeqjJcTUo3+DEaHRls16 +AZXlsp3hB6z0gzR/qzUuZwpMbL477JpuZtAcwLYeVvLG8bQRyWyEy8JgGDoYSn8s +Z16s+r6AX+cnL/2GHkZ+oc3iuXJbnac4xfWTKDiYnyzK6RWRnoyro7X0jiPz6XX3 +wyoWzB1uMSCXscrW6ZcKyKqz75lySLuwGxOMhX4nGOoYHY0ZtrYn5WK2ZAJxsQnn +8QcjPB0nq37U7ifk1uebmuXe99iqyKnWaLvlcpe+HnO5pVxFkSQEf7Zh+hEnRDkN +IBzLFnqwDS1ug/oQ1aSvc8oBh2ylKDJuGtPNqGKibNJyb2diXO/aEUOKRUKPAxKa +dbKsc4Y1bhZNN3/MICMoyghwAOiuwUQMR5uhxTkQmZUwNrPFa+eW6GvyoYLFUsZs +hZfWLNGD5mLADElxs0HF7F9Zk6pSocTDXba4d4lfxsq88SyZZ7PbjJYFRfLQPzd1 +CfvpRPqolEmZo1Y5Q644PELYiJRKpBxmX5GtC5j5eaUD9XdGKvXsGhb0m0gW75rq +iUnnLkZt2ya1cDJDiCnJjo7r5KxMo0XXFDc= +-----END CERTIFICATE----- diff --git a/pkg/cloudprovider/providers/vsphere/vsphere.go b/pkg/cloudprovider/providers/vsphere/vsphere.go index 80b9258edaf..421b2bd302c 100644 --- a/pkg/cloudprovider/providers/vsphere/vsphere.go +++ b/pkg/cloudprovider/providers/vsphere/vsphere.go @@ -103,6 +103,8 @@ type VirtualCenterConfig struct { Datacenters string `gcfg:"datacenters"` // Soap round tripper count (retries = RoundTripper - 1) RoundTripperCount uint `gcfg:"soap-roundtrip-count"` + // Thumbprint of the VCenter's certificate thumbprint + Thumbprint string `gcfg:"thumbprint"` } // Structure that represents the content of vsphere.conf file. @@ -121,6 +123,11 @@ type VSphereConfig struct { VCenterPort string `gcfg:"port"` // True if vCenter uses self-signed cert. InsecureFlag bool `gcfg:"insecure-flag"` + // Specifies the path to a CA certificate in PEM format. Optional; if not + // configured, the system's CA certificates will be used. + CAFile string `gcfg:"ca-file"` + // Thumbprint of the VCenter's certificate thumbprint + Thumbprint string `gcfg:"thumbprint"` // Datacenter in which VMs are located. // Deprecated. Use "datacenters" instead. Datacenter string `gcfg:"datacenter"` @@ -334,6 +341,7 @@ func populateVsphereInstanceMap(cfg *VSphereConfig) (map[string]*VSphereInstance VCenterPort: cfg.Global.VCenterPort, Datacenters: cfg.Global.Datacenter, RoundTripperCount: cfg.Global.RoundTripperCount, + Thumbprint: cfg.Global.Thumbprint, } // Note: If secrets info is provided username and password will be populated @@ -345,7 +353,10 @@ func populateVsphereInstanceMap(cfg *VSphereConfig) (map[string]*VSphereInstance Insecure: cfg.Global.InsecureFlag, RoundTripperCount: vcConfig.RoundTripperCount, Port: vcConfig.VCenterPort, + CACert: cfg.Global.CAFile, + Thumbprint: cfg.Global.Thumbprint, } + vsphereIns := VSphereInstance{ conn: &vSphereConn, cfg: &vcConfig, @@ -417,6 +428,8 @@ func populateVsphereInstanceMap(cfg *VSphereConfig) (map[string]*VSphereInstance Insecure: cfg.Global.InsecureFlag, RoundTripperCount: vcConfig.RoundTripperCount, Port: vcConfig.VCenterPort, + CACert: cfg.Global.CAFile, + Thumbprint: vcConfig.Thumbprint, } vsphereIns := VSphereInstance{ conn: &vSphereConn, diff --git a/pkg/cloudprovider/providers/vsphere/vsphere_test.go b/pkg/cloudprovider/providers/vsphere/vsphere_test.go index 0533405b190..87ae5af823f 100644 --- a/pkg/cloudprovider/providers/vsphere/vsphere_test.go +++ b/pkg/cloudprovider/providers/vsphere/vsphere_test.go @@ -19,6 +19,8 @@ package vsphere import ( "context" "crypto/tls" + "crypto/x509" + "io/ioutil" "log" "os" "strconv" @@ -33,6 +35,7 @@ import ( "k8s.io/apimachinery/pkg/util/rand" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib" + "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib/fixtures" ) // localhostCert was generated from crypto/tls/generate_cert.go with the following command: @@ -90,7 +93,15 @@ func configFromEnv() (cfg VSphereConfig, ok bool) { } // configFromSim starts a vcsim instance and returns config for use against the vcsim instance. +// The vcsim instance is configured with an empty tls.Config. func configFromSim() (VSphereConfig, func()) { + return configFromSimWithTLS(new(tls.Config), true) +} + +// configFromSimWithTLS starts a vcsim instance and returns config for use against the vcsim instance. +// The vcsim instance is configured with a tls.Config. The returned client +// config can be configured to allow/decline insecure connections. +func configFromSimWithTLS(tlsConfig *tls.Config, insecureAllowed bool) (VSphereConfig, func()) { var cfg VSphereConfig model := simulator.VPX() @@ -99,7 +110,7 @@ func configFromSim() (VSphereConfig, func()) { log.Fatal(err) } - model.Service.TLS = new(tls.Config) + model.Service.TLS = tlsConfig s := model.Service.NewServer() // STS simulator @@ -109,7 +120,8 @@ func configFromSim() (VSphereConfig, func()) { // Lookup Service simulator model.Service.RegisterSDK(lookup.New()) - cfg.Global.InsecureFlag = true + cfg.Global.InsecureFlag = insecureAllowed + cfg.Global.VCenterIP = s.URL.Hostname() cfg.Global.VCenterPort = s.URL.Port() cfg.Global.User = s.URL.User.Username() @@ -160,6 +172,7 @@ insecure-flag = true datacenter = us-west vm-uuid = 1234 vm-name = vmname +ca-file = /some/path/to/a/ca.pem `)) if err != nil { t.Fatalf("Should succeed when a valid config is provided: %s", err) @@ -180,6 +193,10 @@ vm-name = vmname if cfg.Global.VMName != "vmname" { t.Errorf("incorrect vm-name: %s", cfg.Global.VMName) } + + if cfg.Global.CAFile != "/some/path/to/a/ca.pem" { + t.Errorf("incorrect ca-file: %s", cfg.Global.CAFile) + } } func TestNewVSphere(t *testing.T) { @@ -250,6 +267,53 @@ func TestVSphereLoginByToken(t *testing.T) { vcInstance.conn.Logout(ctx) } +func TestVSphereLoginWithCaCert(t *testing.T) { + caCertPEM, err := ioutil.ReadFile(fixtures.CaCertPath) + if err != nil { + t.Fatalf("Could not read ca cert from file") + } + + serverCert, err := tls.LoadX509KeyPair(fixtures.ServerCertPath, fixtures.ServerKeyPath) + if err != nil { + t.Fatalf("Could not load server cert and server key from files: %#v", err) + } + + certPool := x509.NewCertPool() + if ok := certPool.AppendCertsFromPEM(caCertPEM); !ok { + t.Fatalf("Cannot add CA to CAPool") + } + + tlsConfig := tls.Config{ + Certificates: []tls.Certificate{serverCert}, + RootCAs: certPool, + } + + cfg, cleanup := configFromSimWithTLS(&tlsConfig, false) + defer cleanup() + + cfg.Global.CAFile = fixtures.CaCertPath + + // Create vSphere configuration object + vs, err := newControllerNode(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate vSphere: %s", err) + } + + ctx := context.Background() + + // Create vSphere client + vcInstance, ok := vs.vsphereInstanceMap[cfg.Global.VCenterIP] + if !ok { + t.Fatalf("Couldn't get vSphere instance: %s", cfg.Global.VCenterIP) + } + + err = vcInstance.conn.Connect(ctx) + if err != nil { + t.Errorf("Failed to connect to vSphere: %s", err) + } + vcInstance.conn.Logout(ctx) +} + func TestZones(t *testing.T) { cfg := VSphereConfig{} cfg.Global.Datacenter = "myDatacenter" @@ -366,6 +430,7 @@ func TestSecretVSphereConfig(t *testing.T) { expectedUsername string expectedPassword string expectedError error + expectedThumbprints map[string]string }{ { testName: "Username and password with old configuration", @@ -535,6 +600,69 @@ func TestSecretVSphereConfig(t *testing.T) { expectedIsSecretProvided: true, expectedError: nil, }, + { + testName: "virtual centers with a thumbprint", + conf: `[Global] + server = global + user = user + password = password + datacenter = us-west + thumbprint = "thumbprint:global" + working-dir = kubernetes + `, + expectedUsername: username, + expectedPassword: password, + expectedError: nil, + expectedThumbprints: map[string]string{ + "global": "thumbprint:global", + }, + }, + { + testName: "Multiple virtual centers with different thumbprints", + conf: `[Global] + user = user + password = password + datacenter = us-west + [VirtualCenter "0.0.0.0"] + thumbprint = thumbprint:0 + [VirtualCenter "no_thumbprint"] + [VirtualCenter "1.1.1.1"] + thumbprint = thumbprint:1 + [Workspace] + server = 0.0.0.0 + datacenter = us-west + folder = kubernetes + `, + expectedUsername: username, + expectedPassword: password, + expectedError: nil, + expectedThumbprints: map[string]string{ + "0.0.0.0": "thumbprint:0", + "1.1.1.1": "thumbprint:1", + }, + }, + { + testName: "Multiple virtual centers use the global CA cert", + conf: `[Global] + user = user + password = password + datacenter = us-west + ca-file = /some/path/to/my/trusted/ca.pem + [VirtualCenter "0.0.0.0"] + user = user + password = password + [VirtualCenter "1.1.1.1"] + user = user + password = password + [Workspace] + server = 0.0.0.0 + datacenter = us-west + folder = kubernetes + `, + expectedUsername: username, + expectedPassword: password, + expectedError: nil, + }, } for _, testcase := range testcases { @@ -564,9 +692,31 @@ func TestSecretVSphereConfig(t *testing.T) { t.Fatalf("Expected password %s doesn't match actual password %s in config %s. error: %s", testcase.expectedPassword, vsInstance.conn.Password, testcase.conf, err) } - } } - + // Check, if all the expected thumbprints are configured + for instanceName, expectedThumbprint := range testcase.expectedThumbprints { + instanceConfig, ok := vs.vsphereInstanceMap[instanceName] + if !ok { + t.Fatalf("Could not find configuration for instance %s", instanceName) + } + if actualThumbprint := instanceConfig.conn.Thumbprint; actualThumbprint != expectedThumbprint { + t.Fatalf( + "Expected thumbprint for instance '%s' to be '%s', got '%s'", + instanceName, expectedThumbprint, actualThumbprint, + ) + } + } + // Check, if all all connections are configured with the global CA certificate + if expectedCaPath := cfg.Global.CAFile; expectedCaPath != "" { + for name, instance := range vs.vsphereInstanceMap { + if actualCaPath := instance.conn.CACert; actualCaPath != expectedCaPath { + t.Fatalf( + "Expected CA certificate path for instance '%s' to be the globally configured one ('%s'), got '%s'", + name, expectedCaPath, actualCaPath, + ) + } + } + } } }