Merge pull request #64758 from pivotal-k8s/64222-vcp-ca-cert

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Setup TLS with CA Cert for vsphere cloud provider

- Extend config to take a path to a CA Certificate
- Use the CA Cert when establishing a connection with the SOAP client

Testing
We provide certs and keys for tests as fixtures, `vclib/fixtures`.
Those were created (and can be regenerated) using `vclib/fixtures/createCerts.sh`.

At the moment it's possible to configure a CA path and at the same time allow insecure
communication between vsphere cloud provider and vcenter. This may
change in the future; we might opt for overwriting the insecure
communication if a CA is configured / log and transparently pass the
arguments to the vcenter command / other. To be discussed.

At the moment the CA is a global level configuration. In other
words, all vcenter servers need to use certificates signed by the same
CA. There might be use cases for different CA per vcenter server; to be
discussed.





**What this PR does / why we need it**:
This PR adds the option of configuring a trusted CA for the communication between the vsphere cloud provider and the vcenter control plane.

**Which issue(s) this PR fixes**:
Fixes #64222

**Special notes for your reviewer**:

**Release note**:

```release-note
- Can configure the vsphere cloud provider with a trusted Root-CA
```
This commit is contained in:
Kubernetes Submit Queue 2018-06-30 03:29:24 -07:00 committed by GitHub
commit 64243d4806
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 792 additions and 4 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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

View File

@ -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
}

View File

@ -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"],
)

View File

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@ -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-----

View File

@ -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 "$@"

View File

@ -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")
}

View File

@ -0,0 +1 @@
this is some invalid content

View File

@ -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-----

View File

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@ -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-----

View File

@ -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,

View File

@ -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,
)
}
}
}
}
}