From 85907f6947e78d7d267dce33d2168b1e45a63f9f Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Wed, 20 Mar 2019 10:32:23 -0700 Subject: [PATCH] vSphere: add token auth support for tags client SAML auth support for the vCenter rest API endpoint came to govmomi a bit after Zone support came to vSphere Cloud Provider. Fixes #75511 --- .../providers/vsphere/vclib/connection.go | 41 +++++++++++++------ .../providers/vsphere/vsphere.go | 16 ++++++-- .../providers/vsphere/vsphere_test.go | 11 ++++- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/pkg/cloudprovider/providers/vsphere/vclib/connection.go b/pkg/cloudprovider/providers/vsphere/vclib/connection.go index 54eaba161fc..f0c7f5cb74b 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/connection.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/connection.go @@ -86,34 +86,27 @@ 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) - connection.credentialsLock.Lock() - defer connection.credentialsLock.Unlock() - +// Signer returns an sts.Signer for use with SAML token auth if connection is configured for such. +// Returns nil if username/password auth is configured for the connection. +func (connection *VSphereConnection) Signer(ctx context.Context, client *vim25.Client) (*sts.Signer, error) { // 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 { - klog.V(3).Infof("SessionManager.Login with username '%s'", connection.Username) - return m.Login(ctx, neturl.UserPassword(connection.Username, connection.Password)) + return nil, nil } - klog.V(3).Infof("SessionManager.LoginByToken with certificate '%s'", connection.Username) - cert, err := tls.X509KeyPair([]byte(connection.Username), []byte(connection.Password)) if err != nil { klog.Errorf("Failed to load X509 key pair. err: %+v", err) - return err + return nil, err } tokens, err := sts.NewClient(ctx, client) if err != nil { klog.Errorf("Failed to create STS client. err: %+v", err) - return err + return nil, err } req := sts.TokenRequest{ @@ -123,9 +116,31 @@ func (connection *VSphereConnection) login(ctx context.Context, client *vim25.Cl signer, err := tokens.Issue(ctx, req) if err != nil { klog.Errorf("Failed to issue SAML token. err: %+v", err) + return nil, err + } + + return signer, 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) + connection.credentialsLock.Lock() + defer connection.credentialsLock.Unlock() + + signer, err := connection.Signer(ctx, client) + if err != nil { return err } + if signer == nil { + klog.V(3).Infof("SessionManager.Login with username %q", connection.Username) + return m.Login(ctx, neturl.UserPassword(connection.Username, connection.Password)) + } + + klog.V(3).Infof("SessionManager.LoginByToken with certificate %q", connection.Username) + header := soap.Header{Security: signer} return m.LoginByToken(client.WithHeader(ctx, header)) diff --git a/pkg/cloudprovider/providers/vsphere/vsphere.go b/pkg/cloudprovider/providers/vsphere/vsphere.go index 96918fc3732..ff72c36d3c7 100644 --- a/pkg/cloudprovider/providers/vsphere/vsphere.go +++ b/pkg/cloudprovider/providers/vsphere/vsphere.go @@ -38,7 +38,7 @@ import ( "github.com/vmware/govmomi/vapi/tags" "github.com/vmware/govmomi/vim25/mo" vmwaretypes "github.com/vmware/govmomi/vim25/types" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" k8stypes "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" @@ -1432,10 +1432,20 @@ func (vs *VSphere) NodeManager() (nodeManager *NodeManager) { func withTagsClient(ctx context.Context, connection *vclib.VSphereConnection, f func(c *rest.Client) error) error { c := rest.NewClient(connection.Client) - user := url.UserPassword(connection.Username, connection.Password) - if err := c.Login(ctx, user); err != nil { + signer, err := connection.Signer(ctx, connection.Client) + if err != nil { return err } + if signer == nil { + user := url.UserPassword(connection.Username, connection.Password) + err = c.Login(ctx, user) + } else { + err = c.LoginByToken(c.WithSigner(ctx, signer)) + } + if err != nil { + return err + } + defer func() { if err := c.Logout(ctx); err != nil { klog.Errorf("failed to logout: %v", err) diff --git a/pkg/cloudprovider/providers/vsphere/vsphere_test.go b/pkg/cloudprovider/providers/vsphere/vsphere_test.go index 9f9db640b13..be7ea73f914 100644 --- a/pkg/cloudprovider/providers/vsphere/vsphere_test.go +++ b/pkg/cloudprovider/providers/vsphere/vsphere_test.go @@ -344,6 +344,10 @@ func TestZones(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 { @@ -382,8 +386,11 @@ func TestZones(t *testing.T) { // Tag manager instance m := tags.NewManager(rest.NewClient(vsi.conn.Client)) - user := url.UserPassword(vsi.conn.Username, vsi.conn.Password) - if err = m.Login(ctx, user); err != nil { + signer, err := vsi.conn.Signer(ctx, vsi.conn.Client) + if err != nil { + t.Fatal(err) + } + if err = m.LoginByToken(m.WithSigner(ctx, signer)); err != nil { t.Fatal(err) }