Blame vendor/github.com/vmware/govmomi/session/cache/session.go

Packit Service 4d2de5
/*
Packit Service 4d2de5
Copyright (c) 2020 VMware, Inc. All Rights Reserved.
Packit Service 4d2de5
Packit Service 4d2de5
Licensed under the Apache License, Version 2.0 (the "License");
Packit Service 4d2de5
you may not use this file except in compliance with the License.
Packit Service 4d2de5
You may obtain a copy of the License at
Packit Service 4d2de5
Packit Service 4d2de5
    http://www.apache.org/licenses/LICENSE-2.0
Packit Service 4d2de5
Packit Service 4d2de5
Unless required by applicable law or agreed to in writing, software
Packit Service 4d2de5
distributed under the License is distributed on an "AS IS" BASIS,
Packit Service 4d2de5
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Packit Service 4d2de5
See the License for the specific language governing permissions and
Packit Service 4d2de5
limitations under the License.
Packit Service 4d2de5
*/
Packit Service 4d2de5
Packit Service 4d2de5
package cache
Packit Service 4d2de5
Packit Service 4d2de5
import (
Packit Service 4d2de5
	"context"
Packit Service 4d2de5
	"crypto/sha1"
Packit Service 4d2de5
	"encoding/json"
Packit Service 4d2de5
	"fmt"
Packit Service 4d2de5
	"io/ioutil"
Packit Service 4d2de5
	"net/url"
Packit Service 4d2de5
	"os"
Packit Service 4d2de5
	"os/user"
Packit Service 4d2de5
	"path/filepath"
Packit Service 4d2de5
Packit Service 4d2de5
	"github.com/vmware/govmomi/session"
Packit Service 4d2de5
	"github.com/vmware/govmomi/vapi/rest"
Packit Service 4d2de5
	"github.com/vmware/govmomi/vim25"
Packit Service 4d2de5
	"github.com/vmware/govmomi/vim25/soap"
Packit Service 4d2de5
	"github.com/vmware/govmomi/vim25/types"
Packit Service 4d2de5
)
Packit Service 4d2de5
Packit Service 4d2de5
// Client interface to support client session caching
Packit Service 4d2de5
type Client interface {
Packit Service 4d2de5
	json.Marshaler
Packit Service 4d2de5
	json.Unmarshaler
Packit Service 4d2de5
Packit Service 4d2de5
	Valid() bool
Packit Service 4d2de5
	Path() string
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// Session provides methods to cache authenticated vim25.Client and rest.Client sessions.
Packit Service 4d2de5
// Use of session cache avoids the expense of creating and deleting vSphere sessions.
Packit Service 4d2de5
// It also helps avoid the problem of "leaking sessions", as Session.Login will only
Packit Service 4d2de5
// create a new authenticated session if the cached session does not exist or is invalid.
Packit Service 4d2de5
// By default, username/password authentication is used to create new sessions.
Packit Service 4d2de5
// The Session.Login{SOAP,REST} fields can be set to use other methods,
Packit Service 4d2de5
// such as SAML token authentication (see govc session.login for example).
Packit Service 4d2de5
type Session struct {
Packit Service 4d2de5
	URL         *url.URL // URL of a vCenter or ESXi instance
Packit Service 4d2de5
	DirSOAP     string   // DirSOAP cache directory. Defaults to "$HOME/.govmomi/sessions"
Packit Service 4d2de5
	DirREST     string   // DirREST cache directory. Defaults to "$HOME/.govmomi/rest_sessions"
Packit Service 4d2de5
	Insecure    bool     // Insecure param for soap.NewClient (tls.Config.InsecureSkipVerify)
Packit Service 4d2de5
	Passthrough bool     // Passthrough disables caching when set to true
Packit Service 4d2de5
Packit Service 4d2de5
	LoginSOAP func(context.Context, *vim25.Client) error // LoginSOAP defaults to session.Manager.Login()
Packit Service 4d2de5
	LoginREST func(context.Context, *rest.Client) error  // LoginREST defaults to rest.Client.Login()
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
var (
Packit Service 4d2de5
	home = os.Getenv("GOVMOMI_HOME")
Packit Service 4d2de5
)
Packit Service 4d2de5
Packit Service 4d2de5
func init() {
Packit Service 4d2de5
	if home == "" {
Packit Service 4d2de5
		dir, err := os.UserHomeDir()
Packit Service 4d2de5
		if err != nil {
Packit Service 4d2de5
			dir = os.Getenv("HOME")
Packit Service 4d2de5
		}
Packit Service 4d2de5
		home = filepath.Join(dir, ".govmomi")
Packit Service 4d2de5
	}
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// Endpoint returns a copy of the Session.URL with Password, Query and Fragment removed.
Packit Service 4d2de5
func (s *Session) Endpoint() *url.URL {
Packit Service 4d2de5
	if s.URL == nil {
Packit Service 4d2de5
		return nil
Packit Service 4d2de5
	}
Packit Service 4d2de5
	p := &url.URL{
Packit Service 4d2de5
		Scheme: s.URL.Scheme,
Packit Service 4d2de5
		Host:   s.URL.Host,
Packit Service 4d2de5
		Path:   s.URL.Path,
Packit Service 4d2de5
	}
Packit Service 4d2de5
	if u := s.URL.User; u != nil {
Packit Service 4d2de5
		p.User = url.User(u.Username()) // Remove password
Packit Service 4d2de5
	}
Packit Service 4d2de5
	return p
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// key is a digest of the URL scheme + username + host + Client.Path()
Packit Service 4d2de5
func (s *Session) key(path string) string {
Packit Service 4d2de5
	p := s.Endpoint()
Packit Service 4d2de5
	p.Path = path
Packit Service 4d2de5
Packit Service 4d2de5
	// Key session file off of full URI and insecure setting.
Packit Service 4d2de5
	// Hash key to get a predictable, canonical format.
Packit Service 4d2de5
	key := fmt.Sprintf("%s#insecure=%t", p.String(), s.Insecure)
Packit Service 4d2de5
	return fmt.Sprintf("%040x", sha1.Sum([]byte(key)))
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
func (s *Session) file(p string) string {
Packit Service 4d2de5
	dir := ""
Packit Service 4d2de5
Packit Service 4d2de5
	switch p {
Packit Service 4d2de5
	case rest.Path:
Packit Service 4d2de5
		dir = s.DirREST
Packit Service 4d2de5
		if dir == "" {
Packit Service 4d2de5
			dir = filepath.Join(home, "rest_sessions")
Packit Service 4d2de5
		}
Packit Service 4d2de5
	default:
Packit Service 4d2de5
		dir = s.DirSOAP
Packit Service 4d2de5
		if dir == "" {
Packit Service 4d2de5
			dir = filepath.Join(home, "sessions")
Packit Service 4d2de5
		}
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return filepath.Join(dir, s.key(p))
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// Save a Client in the file cache.
Packit Service 4d2de5
// Session will not be saved if Session.Passthrough is true.
Packit Service 4d2de5
func (s *Session) Save(c Client) error {
Packit Service 4d2de5
	if s.Passthrough {
Packit Service 4d2de5
		return nil
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	p := s.file(c.Path())
Packit Service 4d2de5
Packit Service 4d2de5
	err := os.MkdirAll(filepath.Dir(p), 0700)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY, 0600)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	err = json.NewEncoder(f).Encode(c)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		_ = f.Close()
Packit Service 4d2de5
		return err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return f.Close()
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
func (s *Session) get(c Client) (bool, error) {
Packit Service 4d2de5
	f, err := os.Open(s.file(c.Path()))
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		if os.IsNotExist(err) {
Packit Service 4d2de5
			return false, nil
Packit Service 4d2de5
		}
Packit Service 4d2de5
Packit Service 4d2de5
		return false, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	dec := json.NewDecoder(f)
Packit Service 4d2de5
	err = dec.Decode(c)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		_ = f.Close()
Packit Service 4d2de5
		return false, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return c.Valid(), f.Close()
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
func localTicket(ctx context.Context, m *session.Manager) (*url.Userinfo, error) {
Packit Service 4d2de5
	name := os.Getenv("USER")
Packit Service 4d2de5
	u, err := user.Current()
Packit Service 4d2de5
	if err == nil {
Packit Service 4d2de5
		name = u.Username
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	ticket, err := m.AcquireLocalTicket(ctx, name)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	password, err := ioutil.ReadFile(ticket.PasswordFilePath)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return url.UserPassword(ticket.UserName, string(password)), nil
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
func (s *Session) loginSOAP(ctx context.Context, c *vim25.Client) error {
Packit Service 4d2de5
	m := session.NewManager(c)
Packit Service 4d2de5
	u := s.URL.User
Packit Service 4d2de5
	name := u.Username()
Packit Service 4d2de5
Packit Service 4d2de5
	if name == "" && !c.IsVC() {
Packit Service 4d2de5
		// If no username is provided, try to acquire a local ticket.
Packit Service 4d2de5
		// When invoked remotely, ESX returns an InvalidRequestFault.
Packit Service 4d2de5
		// So, rather than return an error here, fallthrough to Login() with the original User to
Packit Service 4d2de5
		// to avoid what would be a confusing error message.
Packit Service 4d2de5
		luser, lerr := localTicket(ctx, m)
Packit Service 4d2de5
		if lerr == nil {
Packit Service 4d2de5
			// We are running directly on an ESX or Workstation host and can use the ticket with Login()
Packit Service 4d2de5
			u = luser
Packit Service 4d2de5
			name = u.Username()
Packit Service 4d2de5
		}
Packit Service 4d2de5
	}
Packit Service 4d2de5
	if name == "" {
Packit Service 4d2de5
		// ServiceContent does not require authentication
Packit Service 4d2de5
		return nil
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return m.Login(ctx, u)
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
func (s *Session) loginREST(ctx context.Context, c *rest.Client) error {
Packit Service 4d2de5
	return c.Login(ctx, s.URL.User)
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
func soapSessionValid(ctx context.Context, client *vim25.Client) (bool, error) {
Packit Service 4d2de5
	m := session.NewManager(client)
Packit Service 4d2de5
	u, err := m.UserSession(ctx)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		if soap.IsSoapFault(err) {
Packit Service 4d2de5
			fault := soap.ToSoapFault(err).VimFault()
Packit Service 4d2de5
			// If the PropertyCollector is not found, the saved session for this URL is not valid
Packit Service 4d2de5
			if _, ok := fault.(types.ManagedObjectNotFound); ok {
Packit Service 4d2de5
				return false, nil
Packit Service 4d2de5
			}
Packit Service 4d2de5
		}
Packit Service 4d2de5
Packit Service 4d2de5
		return false, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return u != nil, nil
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
func restSessionValid(ctx context.Context, client *rest.Client) (bool, error) {
Packit Service 4d2de5
	s, err := client.Session(ctx)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return false, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
	return s != nil, nil
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// Load a Client from the file cache.
Packit Service 4d2de5
// Returns false if no cache exists or is invalid.
Packit Service 4d2de5
// An error is returned if the file cannot be opened or is not json encoded.
Packit Service 4d2de5
// After loading the Client from the file:
Packit Service 4d2de5
// Returns true if the session is still valid, false otherwise indicating the client requires authentication.
Packit Service 4d2de5
// An error is returned if the session ID cannot be validated.
Packit Service 4d2de5
// Returns false if Session.Passthrough is true.
Packit Service 4d2de5
func (s *Session) Load(ctx context.Context, c Client, config func(*soap.Client) error) (bool, error) {
Packit Service 4d2de5
	if s.Passthrough {
Packit Service 4d2de5
		return false, nil
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	ok, err := s.get(c)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return false, err
Packit Service 4d2de5
Packit Service 4d2de5
	}
Packit Service 4d2de5
	if !ok {
Packit Service 4d2de5
		return false, nil
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	switch client := c.(type) {
Packit Service 4d2de5
	case *vim25.Client:
Packit Service 4d2de5
		if config != nil {
Packit Service 4d2de5
			if err := config(client.Client); err != nil {
Packit Service 4d2de5
				return false, err
Packit Service 4d2de5
			}
Packit Service 4d2de5
		}
Packit Service 4d2de5
		return soapSessionValid(ctx, client)
Packit Service 4d2de5
	case *rest.Client:
Packit Service 4d2de5
		if config != nil {
Packit Service 4d2de5
			if err := config(client.Client); err != nil {
Packit Service 4d2de5
				return false, err
Packit Service 4d2de5
			}
Packit Service 4d2de5
		}
Packit Service 4d2de5
		return restSessionValid(ctx, client)
Packit Service 4d2de5
	default:
Packit Service 4d2de5
		panic(fmt.Sprintf("unsupported client type=%T", client))
Packit Service 4d2de5
	}
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// Login returns a cached session via Load() if valid.
Packit Service 4d2de5
// Otherwise, creates a new authenticated session and saves to the cache.
Packit Service 4d2de5
// The config func can be used to apply soap.Client configuration, such as TLS settings.
Packit Service 4d2de5
// When Session.Passthrough is true, Login will always create a new session.
Packit Service 4d2de5
func (s *Session) Login(ctx context.Context, c Client, config func(*soap.Client) error) error {
Packit Service 4d2de5
	ok, err := s.Load(ctx, c, config)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return err
Packit Service 4d2de5
	}
Packit Service 4d2de5
	if ok {
Packit Service 4d2de5
		return nil
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	sc := soap.NewClient(s.URL, s.Insecure)
Packit Service 4d2de5
Packit Service 4d2de5
	if config != nil {
Packit Service 4d2de5
		err = config(sc)
Packit Service 4d2de5
		if err != nil {
Packit Service 4d2de5
			return err
Packit Service 4d2de5
		}
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	switch client := c.(type) {
Packit Service 4d2de5
	case *vim25.Client:
Packit Service 4d2de5
		vc, err := vim25.NewClient(ctx, sc)
Packit Service 4d2de5
		if err != nil {
Packit Service 4d2de5
			return err
Packit Service 4d2de5
		}
Packit Service 4d2de5
Packit Service 4d2de5
		login := s.loginSOAP
Packit Service 4d2de5
		if s.LoginSOAP != nil {
Packit Service 4d2de5
			login = s.LoginSOAP
Packit Service 4d2de5
		}
Packit Service 4d2de5
		if err = login(ctx, vc); err != nil {
Packit Service 4d2de5
			return err
Packit Service 4d2de5
		}
Packit Service 4d2de5
Packit Service 4d2de5
		*client = *vc
Packit Service 4d2de5
		c = client
Packit Service 4d2de5
	case *rest.Client:
Packit Service 4d2de5
		client.Client = sc.NewServiceClient(rest.Path, "")
Packit Service 4d2de5
Packit Service 4d2de5
		login := s.loginREST
Packit Service 4d2de5
		if s.LoginREST != nil {
Packit Service 4d2de5
			login = s.LoginREST
Packit Service 4d2de5
		}
Packit Service 4d2de5
		if err = login(ctx, client); err != nil {
Packit Service 4d2de5
			return err
Packit Service 4d2de5
		}
Packit Service 4d2de5
Packit Service 4d2de5
		c = client
Packit Service 4d2de5
	default:
Packit Service 4d2de5
		panic(fmt.Sprintf("unsupported client type=%T", client))
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return s.Save(c)
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// Login calls the Logout method for the given Client if Session.Passthrough is true.
Packit Service 4d2de5
// Otherwise returns nil.
Packit Service 4d2de5
func (s *Session) Logout(ctx context.Context, c Client) error {
Packit Service 4d2de5
	if s.Passthrough {
Packit Service 4d2de5
		switch client := c.(type) {
Packit Service 4d2de5
		case *vim25.Client:
Packit Service 4d2de5
			return session.NewManager(client).Logout(ctx)
Packit Service 4d2de5
		case *rest.Client:
Packit Service 4d2de5
			return client.Logout(ctx)
Packit Service 4d2de5
		default:
Packit Service 4d2de5
			panic(fmt.Sprintf("unsupported client type=%T", client))
Packit Service 4d2de5
		}
Packit Service 4d2de5
	}
Packit Service 4d2de5
	return nil
Packit Service 4d2de5
}