github.com/vmware/govmomi@v0.51.0/lookup/client.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package lookup
     6  
     7  import (
     8  	"context"
     9  	"crypto/x509"
    10  	"encoding/base64"
    11  	"errors"
    12  	"fmt"
    13  	"log"
    14  	"net/url"
    15  
    16  	"github.com/vmware/govmomi/internal"
    17  	"github.com/vmware/govmomi/lookup/methods"
    18  	"github.com/vmware/govmomi/lookup/types"
    19  	"github.com/vmware/govmomi/object"
    20  	"github.com/vmware/govmomi/vim25"
    21  	"github.com/vmware/govmomi/vim25/soap"
    22  	vim "github.com/vmware/govmomi/vim25/types"
    23  )
    24  
    25  const (
    26  	Namespace = "lookup"
    27  	Version   = "2.0"
    28  	Path      = "/lookupservice" + vim25.Path
    29  )
    30  
    31  var (
    32  	ServiceInstance = vim.ManagedObjectReference{
    33  		Type:  "LookupServiceInstance",
    34  		Value: "ServiceInstance",
    35  	}
    36  )
    37  
    38  // Client is a soap.Client targeting the SSO Lookup Service API endpoint.
    39  type Client struct {
    40  	*soap.Client
    41  
    42  	RoundTripper soap.RoundTripper
    43  
    44  	ServiceContent types.LookupServiceContent
    45  
    46  	// Rewrite when true changes EndpointURL Host to the VC connection's Host
    47  	Rewrite bool
    48  }
    49  
    50  // NewClient returns a client targeting the SSO Lookup Service API endpoint.
    51  func NewClient(ctx context.Context, c *vim25.Client) (*Client, error) {
    52  	path := &url.URL{Path: Path}
    53  	// PSC may be external, attempt to derive from sts.uri if not using envoy sidecar
    54  	if !internal.UsingEnvoySidecar(c) && c.ServiceContent.Setting != nil {
    55  		m := object.NewOptionManager(c, *c.ServiceContent.Setting)
    56  		opts, err := m.Query(ctx, "config.vpxd.sso.sts.uri")
    57  		if err == nil && len(opts) == 1 {
    58  			u, err := url.Parse(opts[0].GetOptionValue().Value.(string))
    59  			if err == nil {
    60  				path.Scheme = u.Scheme
    61  				path.Host = u.Host
    62  			}
    63  		}
    64  	}
    65  
    66  	// 1st try: use the URL from OptionManager as-is, continue to 2nd try on DNS error
    67  	// 2nd try: use the URL from OptionManager, changing Host to vim25.Client's Host
    68  	var attempts []error
    69  
    70  	for _, rewrite := range []bool{false, true} {
    71  		if rewrite {
    72  			path.Host = c.URL().Host
    73  		}
    74  
    75  		sc := c.Client.NewServiceClient(path.String(), Namespace)
    76  		sc.Version = Version
    77  		client := &Client{Client: sc, RoundTripper: sc, Rewrite: rewrite}
    78  
    79  		req := types.RetrieveServiceContent{
    80  			This: ServiceInstance,
    81  		}
    82  
    83  		res, err := methods.RetrieveServiceContent(ctx, client, &req)
    84  		if err != nil {
    85  			attempts = append(attempts, err)
    86  			continue
    87  		}
    88  
    89  		client.ServiceContent = res.Returnval
    90  
    91  		return client, nil
    92  	}
    93  
    94  	return nil, errors.Join(attempts...)
    95  }
    96  
    97  // RoundTrip dispatches to the RoundTripper field.
    98  func (c *Client) RoundTrip(ctx context.Context, req, res soap.HasFault) error {
    99  	// Drop any operationID header, not used by lookup service
   100  	ctx = context.WithValue(ctx, vim.ID{}, "")
   101  	return c.RoundTripper.RoundTrip(ctx, req, res)
   102  }
   103  
   104  func (c *Client) List(ctx context.Context, filter *types.LookupServiceRegistrationFilter) ([]types.LookupServiceRegistrationInfo, error) {
   105  	req := types.List{
   106  		This:           *c.ServiceContent.ServiceRegistration,
   107  		FilterCriteria: filter,
   108  	}
   109  
   110  	res, err := methods.List(ctx, c, &req)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	return res.Returnval, nil
   115  }
   116  
   117  func (c *Client) SiteID(ctx context.Context) (string, error) {
   118  	req := types.GetSiteId{
   119  		This: *c.ServiceContent.ServiceRegistration,
   120  	}
   121  
   122  	res, err := methods.GetSiteId(ctx, c, &req)
   123  	if err != nil {
   124  		return "", err
   125  	}
   126  	return res.Returnval, nil
   127  }
   128  
   129  // EndpointURL uses the Lookup Service to find the endpoint URL and thumbprint for the given filter.
   130  // If the endpoint is found, its TLS certificate is also added to the vim25.Client's trusted host thumbprints.
   131  // If the Lookup Service is not available, the given path is returned as the default.
   132  func EndpointURL(ctx context.Context, c *vim25.Client, path string, filter *types.LookupServiceRegistrationFilter) string {
   133  	// Services running on vCenter can bypass lookup service.
   134  	if useSidecar := internal.UsingEnvoySidecar(c); useSidecar {
   135  		return fmt.Sprintf("http://%s%s", c.URL().Host, path)
   136  	}
   137  	if lu, err := NewClient(ctx, c); err == nil {
   138  		info, _ := lu.List(ctx, filter)
   139  		if len(info) != 0 && len(info[0].ServiceEndpoints) != 0 {
   140  			endpoint := &info[0].ServiceEndpoints[0]
   141  			path = endpoint.Url
   142  
   143  			if u, err := url.Parse(path); err == nil {
   144  				if lu.Rewrite {
   145  					u.Host = c.URL().Host
   146  					path = u.String()
   147  				} else {
   148  					// Set thumbprint only for endpoints on hosts outside this vCenter.
   149  					// Platform Services may live on multiple hosts.
   150  					if c.URL().Host != u.Host && c.Thumbprint(u.Host) == "" {
   151  						c.SetThumbprint(u.Host, endpointThumbprint(endpoint))
   152  					}
   153  				}
   154  			}
   155  		}
   156  	}
   157  	return path
   158  }
   159  
   160  // endpointThumbprint converts the base64 encoded endpoint certificate to a SHA1 thumbprint.
   161  func endpointThumbprint(endpoint *types.LookupServiceRegistrationEndpoint) string {
   162  	if len(endpoint.SslTrust) == 0 {
   163  		return ""
   164  	}
   165  	enc := endpoint.SslTrust[0]
   166  
   167  	b, err := base64.StdEncoding.DecodeString(enc)
   168  	if err != nil {
   169  		log.Printf("base64.Decode(%q): %s", enc, err)
   170  		return ""
   171  	}
   172  
   173  	cert, err := x509.ParseCertificate(b)
   174  	if err != nil {
   175  		log.Printf("x509.ParseCertificate(%q): %s", enc, err)
   176  		return ""
   177  	}
   178  
   179  	return soap.ThumbprintSHA1(cert)
   180  }