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 }