mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 10:51:29 +00:00
vSphere Cloud Provider: add SAML token authentication support
For now the config structs and validation are left as-is and the LoginByToken method is used if the username value is PEM encoded. In this case of username field configured with the public key, the password field is expected to be configured with the private key. In a follow-up PR we can look at collapsing the auth related fields into a common struct to avoid duplication of field merging and validation. And then add separate fields for the public and private keys. Fixes #63209
This commit is contained in:
parent
41a531317a
commit
83768d286c
@ -18,12 +18,15 @@ package vclib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/pem"
|
||||
"net"
|
||||
neturl "net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/vmware/govmomi/session"
|
||||
"github.com/vmware/govmomi/sts"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
)
|
||||
@ -78,6 +81,49 @@ func (connection *VSphereConnection) Connect(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// login calls SessionManager.LoginByToken if certificate and private key are configured,
|
||||
// otherwise calls SessionManager.Login with user and password.
|
||||
func (connection *VSphereConnection) login(ctx context.Context, client *vim25.Client) error {
|
||||
m := session.NewManager(client)
|
||||
|
||||
// TODO: Add separate fields for certificate and private-key.
|
||||
// For now we can leave the config structs and validation as-is and
|
||||
// decide to use LoginByToken if the username value is PEM encoded.
|
||||
b, _ := pem.Decode([]byte(connection.Username))
|
||||
if b == nil {
|
||||
glog.V(3).Infof("SessionManager.Login with username '%s'", connection.Username)
|
||||
return m.Login(ctx, neturl.UserPassword(connection.Username, connection.Password))
|
||||
}
|
||||
|
||||
glog.V(3).Infof("SessionManager.LoginByToken with certificate '%s'", connection.Username)
|
||||
|
||||
cert, err := tls.X509KeyPair([]byte(connection.Username), []byte(connection.Password))
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to load X509 key pair. err: %+v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
tokens, err := sts.NewClient(ctx, client)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to create STS client. err: %+v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
req := sts.TokenRequest{
|
||||
Certificate: &cert,
|
||||
}
|
||||
|
||||
signer, err := tokens.Issue(ctx, req)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to issue SAML token. err: %+v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
header := soap.Header{Security: signer}
|
||||
|
||||
return m.LoginByToken(client.WithHeader(ctx, header))
|
||||
}
|
||||
|
||||
// Logout calls SessionManager.Logout for the given connection.
|
||||
func (connection *VSphereConnection) Logout(ctx context.Context) {
|
||||
m := session.NewManager(connection.Client)
|
||||
@ -100,13 +146,16 @@ func (connection *VSphereConnection) NewClient(ctx context.Context) (*vim25.Clie
|
||||
glog.Errorf("Failed to create new client. err: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := session.NewManager(client)
|
||||
|
||||
err = m.Login(ctx, neturl.UserPassword(connection.Username, connection.Password))
|
||||
err = connection.login(ctx, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if glog.V(3) {
|
||||
s, err := session.NewManager(client).UserSession(ctx)
|
||||
if err == nil {
|
||||
glog.Infof("New session ID for '%s' = %s", s.UserName, s.Key)
|
||||
}
|
||||
}
|
||||
|
||||
if connection.RoundTripperCount == 0 {
|
||||
connection.RoundTripperCount = RoundTripperDefaultCount
|
||||
|
@ -25,13 +25,41 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
lookup "github.com/vmware/govmomi/lookup/simulator"
|
||||
"github.com/vmware/govmomi/simulator"
|
||||
"github.com/vmware/govmomi/simulator/vpx"
|
||||
sts "github.com/vmware/govmomi/sts/simulator"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib"
|
||||
)
|
||||
|
||||
// localhostCert was generated from crypto/tls/generate_cert.go with the following command:
|
||||
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||
var localhostCert = `-----BEGIN CERTIFICATE-----
|
||||
MIIBjzCCATmgAwIBAgIRAKpi2WmTcFrVjxrl5n5YDUEwDQYJKoZIhvcNAQELBQAw
|
||||
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
|
||||
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC
|
||||
QQC9fEbRszP3t14Gr4oahV7zFObBI4TfA5i7YnlMXeLinb7MnvT4bkfOJzE6zktn
|
||||
59zP7UiHs3l4YOuqrjiwM413AgMBAAGjaDBmMA4GA1UdDwEB/wQEAwICpDATBgNV
|
||||
HSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MC4GA1UdEQQnMCWCC2V4
|
||||
YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUA
|
||||
A0EAUsVE6KMnza/ZbodLlyeMzdo7EM/5nb5ywyOxgIOCf0OOLHsPS9ueGLQX9HEG
|
||||
//yjTXuhNcUugExIjM/AIwAZPQ==
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
// localhostKey is the private key for localhostCert.
|
||||
var localhostKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIBOwIBAAJBAL18RtGzM/e3XgavihqFXvMU5sEjhN8DmLtieUxd4uKdvsye9Phu
|
||||
R84nMTrOS2fn3M/tSIezeXhg66quOLAzjXcCAwEAAQJBAKcRxH9wuglYLBdI/0OT
|
||||
BLzfWPZCEw1vZmMR2FF1Fm8nkNOVDPleeVGTWoOEcYYlQbpTmkGSxJ6ya+hqRi6x
|
||||
goECIQDx3+X49fwpL6B5qpJIJMyZBSCuMhH4B7JevhGGFENi3wIhAMiNJN5Q3UkL
|
||||
IuSvv03kaPR5XVQ99/UeEetUgGvBcABpAiBJSBzVITIVCGkGc7d+RCf49KTCIklv
|
||||
bGWObufAR8Ni4QIgWpILjW8dkGg8GOUZ0zaNA6Nvt6TIv2UWGJ4v5PoV98kCIQDx
|
||||
rIiZs5QbKdycsv9gQJzwQAogC8o04X3Zz3dsoX+h4A==
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
|
||||
func configFromEnv() (cfg VSphereConfig, ok bool) {
|
||||
var InsecureFlag bool
|
||||
var err error
|
||||
@ -61,14 +89,9 @@ func configFromEnv() (cfg VSphereConfig, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// configFromEnvOrSim returns config from configFromEnv if set,
|
||||
// otherwise starts a vcsim instance and returns config for use against the vcsim instance.
|
||||
func configFromEnvOrSim() (VSphereConfig, func()) {
|
||||
cfg, ok := configFromEnv()
|
||||
if ok {
|
||||
return cfg, func() {}
|
||||
}
|
||||
|
||||
// configFromSim starts a vcsim instance and returns config for use against the vcsim instance.
|
||||
func configFromSim() (VSphereConfig, func()) {
|
||||
var cfg VSphereConfig
|
||||
model := simulator.VPX()
|
||||
|
||||
err := model.Create()
|
||||
@ -79,6 +102,13 @@ func configFromEnvOrSim() (VSphereConfig, func()) {
|
||||
model.Service.TLS = new(tls.Config)
|
||||
s := model.Service.NewServer()
|
||||
|
||||
// STS simulator
|
||||
path, handler := sts.New(s.URL, vpx.Setting)
|
||||
model.Service.ServeMux.Handle(path, handler)
|
||||
|
||||
// Lookup Service simulator
|
||||
model.Service.RegisterSDK(lookup.New())
|
||||
|
||||
cfg.Global.InsecureFlag = true
|
||||
cfg.Global.VCenterIP = s.URL.Hostname()
|
||||
cfg.Global.VCenterPort = s.URL.Port()
|
||||
@ -105,6 +135,15 @@ func configFromEnvOrSim() (VSphereConfig, func()) {
|
||||
}
|
||||
}
|
||||
|
||||
// configFromEnvOrSim returns config from configFromEnv if set, otherwise returns configFromSim.
|
||||
func configFromEnvOrSim() (VSphereConfig, func()) {
|
||||
cfg, ok := configFromEnv()
|
||||
if ok {
|
||||
return cfg, func() {}
|
||||
}
|
||||
return configFromSim()
|
||||
}
|
||||
|
||||
func TestReadConfig(t *testing.T) {
|
||||
_, err := readConfig(nil)
|
||||
if err == nil {
|
||||
@ -179,7 +218,36 @@ func TestVSphereLogin(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Failed to connect to vSphere: %s", err)
|
||||
}
|
||||
defer vcInstance.conn.Logout(ctx)
|
||||
vcInstance.conn.Logout(ctx)
|
||||
}
|
||||
|
||||
func TestVSphereLoginByToken(t *testing.T) {
|
||||
cfg, cleanup := configFromSim()
|
||||
defer cleanup()
|
||||
|
||||
// Configure for SAML token auth
|
||||
cfg.Global.User = localhostCert
|
||||
cfg.Global.Password = localhostKey
|
||||
|
||||
// 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) {
|
||||
|
Loading…
Reference in New Issue
Block a user