github.com/vmware/govmomi@v0.43.0/simulator/simulator.go (about) 1 /* 2 Copyright (c) 2017-2023 VMware, Inc. All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package simulator 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/tls" 23 "crypto/x509" 24 "encoding/base64" 25 "encoding/json" 26 "encoding/pem" 27 "fmt" 28 "io" 29 "log" 30 "net" 31 "net/http" 32 "net/url" 33 "os" 34 "path" 35 "reflect" 36 "sort" 37 "strconv" 38 "strings" 39 40 "github.com/google/uuid" 41 42 "github.com/vmware/govmomi/find" 43 "github.com/vmware/govmomi/object" 44 "github.com/vmware/govmomi/simulator/internal" 45 "github.com/vmware/govmomi/vim25" 46 "github.com/vmware/govmomi/vim25/mo" 47 "github.com/vmware/govmomi/vim25/soap" 48 "github.com/vmware/govmomi/vim25/types" 49 "github.com/vmware/govmomi/vim25/xml" 50 ) 51 52 var ( 53 // Trace when set to true, writes SOAP traffic to stderr 54 Trace = false 55 56 // TraceFile is the output file when Trace = true 57 TraceFile = os.Stderr 58 59 // DefaultLogin for authentication 60 DefaultLogin = url.UserPassword("user", "pass") 61 ) 62 63 // Method encapsulates a decoded SOAP client request 64 type Method struct { 65 Name string 66 This types.ManagedObjectReference 67 Header soap.Header 68 Body types.AnyType 69 } 70 71 // Service decodes incoming requests and dispatches to a Handler 72 type Service struct { 73 client *vim25.Client 74 sm *SessionManager 75 sdk map[string]*Registry 76 funcs []handleFunc 77 delay *DelayConfig 78 79 readAll func(io.Reader) ([]byte, error) 80 81 Listen *url.URL 82 TLS *tls.Config 83 ServeMux *http.ServeMux 84 // RegisterEndpoints will initialize any endpoints added via RegisterEndpoint 85 RegisterEndpoints bool 86 } 87 88 // Server provides a simulator Service over HTTP 89 type Server struct { 90 *internal.Server 91 URL *url.URL 92 Tunnel int 93 94 caFile string 95 } 96 97 // New returns an initialized simulator Service instance 98 func New(instance *ServiceInstance) *Service { 99 s := &Service{ 100 readAll: io.ReadAll, 101 sm: Map.SessionManager(), 102 sdk: make(map[string]*Registry), 103 } 104 105 s.client, _ = vim25.NewClient(context.Background(), s) 106 107 return s 108 } 109 110 type serverFaultBody struct { 111 Reason *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"` 112 } 113 114 func (b *serverFaultBody) Fault() *soap.Fault { return b.Reason } 115 116 func serverFault(msg string) soap.HasFault { 117 return &serverFaultBody{Reason: Fault(msg, &types.InvalidRequest{})} 118 } 119 120 // Fault wraps the given message and fault in a soap.Fault 121 func Fault(msg string, fault types.BaseMethodFault) *soap.Fault { 122 f := &soap.Fault{ 123 Code: "ServerFaultCode", 124 String: msg, 125 } 126 127 f.Detail.Fault = fault 128 129 return f 130 } 131 132 func tracef(format string, v ...interface{}) { 133 if Trace { 134 log.Printf(format, v...) 135 } 136 } 137 138 func (s *Service) call(ctx *Context, method *Method) soap.HasFault { 139 handler := ctx.Map.Get(method.This) 140 session := ctx.Session 141 ctx.Caller = &method.This 142 143 if ctx.Map.Handler != nil { 144 h, fault := ctx.Map.Handler(ctx, method) 145 if fault != nil { 146 return &serverFaultBody{Reason: Fault("", fault)} 147 } 148 if h != nil { 149 handler = h 150 } 151 } 152 153 if session == nil { 154 switch method.Name { 155 case "RetrieveServiceContent", "PbmRetrieveServiceContent", "Fetch", "List", "Login", "LoginByToken", "LoginExtensionByCertificate", "RetrieveProperties", "RetrievePropertiesEx", "CloneSession": 156 // ok for now, TODO: authz 157 default: 158 fault := &types.NotAuthenticated{ 159 NoPermission: types.NoPermission{ 160 Object: &method.This, 161 PrivilegeId: "System.View", 162 }, 163 } 164 return &serverFaultBody{Reason: Fault("", fault)} 165 } 166 } else { 167 // Prefer the Session.Registry, ServiceContent.PropertyCollector filter field for example is per-session 168 if h := session.Get(method.This); h != nil { 169 handler = h 170 } 171 } 172 173 if handler == nil { 174 msg := fmt.Sprintf("managed object not found: %s", method.This) 175 log.Print(msg) 176 fault := &types.ManagedObjectNotFound{Obj: method.This} 177 return &serverFaultBody{Reason: Fault(msg, fault)} 178 } 179 180 // Lowercase methods can't be accessed outside their package 181 name := strings.Title(method.Name) 182 183 if strings.HasSuffix(name, vTaskSuffix) { 184 // Make golint happy renaming "Foo_Task" -> "FooTask" 185 name = name[:len(name)-len(vTaskSuffix)] + sTaskSuffix 186 } 187 188 m := reflect.ValueOf(handler).MethodByName(name) 189 if !m.IsValid() { 190 msg := fmt.Sprintf("%s does not implement: %s", method.This, method.Name) 191 log.Print(msg) 192 fault := &types.MethodNotFound{Receiver: method.This, Method: method.Name} 193 return &serverFaultBody{Reason: Fault(msg, fault)} 194 } 195 196 if e, ok := handler.(mo.Entity); ok { 197 for _, dm := range e.Entity().DisabledMethod { 198 if name == dm { 199 msg := fmt.Sprintf("%s method is disabled: %s", method.This, method.Name) 200 fault := &types.MethodDisabled{} 201 return &serverFaultBody{Reason: Fault(msg, fault)} 202 } 203 } 204 } 205 206 // We have a valid call. Introduce a delay if requested 207 if s.delay != nil { 208 s.delay.delay(method.Name) 209 } 210 211 var args, res []reflect.Value 212 if m.Type().NumIn() == 2 { 213 args = append(args, reflect.ValueOf(ctx)) 214 } 215 args = append(args, reflect.ValueOf(method.Body)) 216 ctx.Map.WithLock(ctx, handler, func() { 217 res = m.Call(args) 218 }) 219 220 return res[0].Interface().(soap.HasFault) 221 } 222 223 // internalSession is the session for use by the in-memory client (Service.RoundTrip) 224 var internalSession = &Session{ 225 UserSession: types.UserSession{ 226 Key: uuid.New().String(), 227 }, 228 Registry: NewRegistry(), 229 } 230 231 // RoundTrip implements the soap.RoundTripper interface in process. 232 // Rather than encode/decode SOAP over HTTP, this implementation uses reflection. 233 func (s *Service) RoundTrip(ctx context.Context, request, response soap.HasFault) error { 234 field := func(r soap.HasFault, name string) reflect.Value { 235 return reflect.ValueOf(r).Elem().FieldByName(name) 236 } 237 238 // Every struct passed to soap.RoundTrip has "Req" and "Res" fields 239 req := field(request, "Req") 240 241 // Every request has a "This" field. 242 this := req.Elem().FieldByName("This") 243 // Copy request body 244 body := reflect.New(req.Type().Elem()) 245 deepCopy(req.Interface(), body.Interface()) 246 247 method := &Method{ 248 Name: req.Elem().Type().Name(), 249 This: this.Interface().(types.ManagedObjectReference), 250 Body: body.Interface(), 251 } 252 253 res := s.call(&Context{ 254 Map: Map, 255 Context: ctx, 256 Session: internalSession, 257 }, method) 258 259 if err := res.Fault(); err != nil { 260 return soap.WrapSoapFault(err) 261 } 262 263 field(response, "Res").Set(field(res, "Res")) 264 265 return nil 266 } 267 268 // soapEnvelope is a copy of soap.Envelope, with namespace changed to "soapenv", 269 // and additional namespace attributes required by some client libraries. 270 // Go still has issues decoding with such a namespace, but encoding is ok. 271 type soapEnvelope struct { 272 XMLName xml.Name `xml:"soapenv:Envelope"` 273 Enc string `xml:"xmlns:soapenc,attr"` 274 Env string `xml:"xmlns:soapenv,attr"` 275 XSD string `xml:"xmlns:xsd,attr"` 276 XSI string `xml:"xmlns:xsi,attr"` 277 Body interface{} `xml:"soapenv:Body"` 278 } 279 280 type faultDetail struct { 281 Fault types.AnyType 282 } 283 284 // soapFault is a copy of soap.Fault, with the same changes as soapEnvelope 285 type soapFault struct { 286 XMLName xml.Name `xml:"soapenv:Fault"` 287 Code string `xml:"faultcode"` 288 String string `xml:"faultstring"` 289 Detail struct { 290 Fault *faultDetail 291 } `xml:"detail"` 292 } 293 294 // MarshalXML renames the start element from "Fault" to "${Type}Fault" 295 func (d *faultDetail) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 296 kind := reflect.TypeOf(d.Fault).Elem().Name() 297 start.Name.Local = kind + "Fault" 298 start.Attr = append(start.Attr, 299 xml.Attr{ 300 Name: xml.Name{Local: "xmlns"}, 301 Value: "urn:" + vim25.Namespace, 302 }, 303 xml.Attr{ 304 Name: xml.Name{Local: "xsi:type"}, 305 Value: kind, 306 }) 307 return e.EncodeElement(d.Fault, start) 308 } 309 310 // response sets xml.Name.Space when encoding Body. 311 // Note that namespace is intentionally omitted in the vim25/methods/methods.go Body.Res field tags. 312 type response struct { 313 Namespace string 314 Body soap.HasFault 315 } 316 317 func (r *response) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 318 val := reflect.ValueOf(r.Body).Elem().FieldByName("Res") 319 if !val.IsValid() { 320 return fmt.Errorf("%T: invalid response type (missing 'Res' field)", r.Body) 321 } 322 if val.IsNil() { 323 return fmt.Errorf("%T: invalid response (nil 'Res' field)", r.Body) 324 } 325 res := xml.StartElement{ 326 Name: xml.Name{ 327 Space: "urn:" + r.Namespace, 328 Local: val.Elem().Type().Name(), 329 }, 330 } 331 if err := e.EncodeToken(start); err != nil { 332 return err 333 } 334 if err := e.EncodeElement(val.Interface(), res); err != nil { 335 return err 336 } 337 return e.EncodeToken(start.End()) 338 } 339 340 // About generates some info about the simulator. 341 func (s *Service) About(w http.ResponseWriter, r *http.Request) { 342 var about struct { 343 Methods []string `json:"methods"` 344 Types []string `json:"types"` 345 } 346 347 seen := make(map[string]bool) 348 349 f := reflect.TypeOf((*soap.HasFault)(nil)).Elem() 350 351 for _, sdk := range s.sdk { 352 for _, obj := range sdk.objects { 353 kind := obj.Reference().Type 354 if seen[kind] { 355 continue 356 } 357 seen[kind] = true 358 359 about.Types = append(about.Types, kind) 360 361 t := reflect.TypeOf(obj) 362 for i := 0; i < t.NumMethod(); i++ { 363 m := t.Method(i) 364 if seen[m.Name] { 365 continue 366 } 367 seen[m.Name] = true 368 369 in := m.Type.NumIn() 370 if in < 2 || in > 3 { // at least 2 params (receiver and request), optionally a 3rd param (context) 371 continue 372 } 373 if m.Type.NumOut() != 1 || m.Type.Out(0) != f { // all methods return soap.HasFault 374 continue 375 } 376 377 about.Methods = append(about.Methods, strings.Replace(m.Name, "Task", "_Task", 1)) 378 } 379 } 380 } 381 382 sort.Strings(about.Methods) 383 sort.Strings(about.Types) 384 385 w.Header().Set("Content-Type", "application/json") 386 enc := json.NewEncoder(w) 387 enc.SetIndent("", " ") 388 _ = enc.Encode(&about) 389 } 390 391 var endpoints []func(*Service, *Registry) 392 393 // RegisterEndpoint funcs are called after the Server is initialized if Service.RegisterEndpoints=true. 394 // Such a func would typically register a SOAP endpoint via Service.RegisterSDK or REST endpoint via Service.Handle 395 func RegisterEndpoint(endpoint func(*Service, *Registry)) { 396 endpoints = append(endpoints, endpoint) 397 } 398 399 // Handle registers the handler for the given pattern with Service.ServeMux. 400 func (s *Service) Handle(pattern string, handler http.Handler) { 401 s.ServeMux.Handle(pattern, handler) 402 // Not ideal, but avoids having to add yet another registration mechanism 403 // so we can optionally use vapi/simulator internally. 404 if m, ok := handler.(tagManager); ok { 405 s.sdk[vim25.Path].tagManager = m 406 } 407 } 408 409 type muxHandleFunc interface { 410 HandleFunc(string, func(http.ResponseWriter, *http.Request)) 411 } 412 413 type handleFunc struct { 414 pattern string 415 handler func(http.ResponseWriter, *http.Request) 416 } 417 418 // HandleFunc dispatches to http.ServeMux.HandleFunc after all endpoints have been registered. 419 // This allows dispatching to an endpoint's HandleFunc impl, such as vapi/simulator for example. 420 func (s *Service) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { 421 s.funcs = append(s.funcs, handleFunc{pattern, handler}) 422 } 423 424 // RegisterSDK adds an HTTP handler for the Registry's Path and Namespace. 425 // If r.Path is already registered, r's objects are added to the existing Registry. 426 // An optional set of aliases can be provided to register the same handler for 427 // multiple paths. 428 func (s *Service) RegisterSDK(r *Registry, alias ...string) { 429 if existing, ok := s.sdk[r.Path]; ok { 430 for id, obj := range r.objects { 431 existing.objects[id] = obj 432 } 433 return 434 } 435 436 if s.ServeMux == nil { 437 s.ServeMux = http.NewServeMux() 438 } 439 440 s.sdk[r.Path] = r 441 s.ServeMux.HandleFunc(r.Path, s.ServeSDK) 442 443 for _, p := range alias { 444 s.sdk[p] = r 445 s.ServeMux.HandleFunc(p, s.ServeSDK) 446 } 447 } 448 449 // StatusSDK can be used to simulate an /sdk HTTP response code other than 200. 450 // The value of StatusSDK is restored to http.StatusOK after 1 response. 451 // This can be useful to test vim25.Retry() for example. 452 var StatusSDK = http.StatusOK 453 454 // ServeSDK implements the http.Handler interface 455 func (s *Service) ServeSDK(w http.ResponseWriter, r *http.Request) { 456 if r.Method != http.MethodPost { 457 w.WriteHeader(http.StatusMethodNotAllowed) 458 return 459 } 460 461 if StatusSDK != http.StatusOK { 462 w.WriteHeader(StatusSDK) 463 StatusSDK = http.StatusOK // reset 464 return 465 } 466 467 body, err := s.readAll(r.Body) 468 _ = r.Body.Close() 469 if err != nil { 470 log.Printf("error reading body: %s", err) 471 w.WriteHeader(http.StatusBadRequest) 472 return 473 } 474 475 if Trace { 476 fmt.Fprintf(TraceFile, "Request: %s\n", string(body)) 477 } 478 479 ctx := &Context{ 480 req: r, 481 res: w, 482 svc: s, 483 484 Map: s.sdk[r.URL.Path], 485 Context: context.Background(), 486 } 487 ctx.Map.WithLock(ctx, s.sm, ctx.mapSession) 488 489 var res soap.HasFault 490 var soapBody interface{} 491 492 method, err := UnmarshalBody(ctx.Map.typeFunc, body) 493 if err != nil { 494 res = serverFault(err.Error()) 495 } else { 496 ctx.Header = method.Header 497 if method.Name == "Fetch" { 498 // Redirect any Fetch method calls to the PropertyCollector singleton 499 method.This = ctx.Map.content().PropertyCollector 500 } 501 res = s.call(ctx, method) 502 } 503 504 if f := res.Fault(); f != nil { 505 w.WriteHeader(http.StatusInternalServerError) 506 507 // the generated method/*Body structs use the '*soap.Fault' type, 508 // so we need our own Body type to use the modified '*soapFault' type. 509 soapBody = struct { 510 Fault *soapFault 511 }{ 512 &soapFault{ 513 Code: f.Code, 514 String: f.String, 515 Detail: struct { 516 Fault *faultDetail 517 }{&faultDetail{f.Detail.Fault}}, 518 }, 519 } 520 } else { 521 w.WriteHeader(http.StatusOK) 522 523 soapBody = &response{ctx.Map.Namespace, res} 524 } 525 526 var out bytes.Buffer 527 528 fmt.Fprint(&out, xml.Header) 529 e := xml.NewEncoder(&out) 530 err = e.Encode(&soapEnvelope{ 531 Enc: "http://schemas.xmlsoap.org/soap/encoding/", 532 Env: "http://schemas.xmlsoap.org/soap/envelope/", 533 XSD: "http://www.w3.org/2001/XMLSchema", 534 XSI: "http://www.w3.org/2001/XMLSchema-instance", 535 Body: soapBody, 536 }) 537 if err == nil { 538 err = e.Flush() 539 } 540 541 if err != nil { 542 log.Printf("error encoding %s response: %s", method.Name, err) 543 return 544 } 545 546 if Trace { 547 fmt.Fprintf(TraceFile, "Response: %s\n", out.String()) 548 } 549 550 _, _ = w.Write(out.Bytes()) 551 } 552 553 func (s *Service) findDatastore(query url.Values) (*Datastore, error) { 554 ctx := context.Background() 555 556 finder := find.NewFinder(s.client, false) 557 dc, err := finder.DatacenterOrDefault(ctx, query.Get("dcPath")) 558 if err != nil { 559 return nil, err 560 } 561 562 finder.SetDatacenter(dc) 563 564 ds, err := finder.DatastoreOrDefault(ctx, query.Get("dsName")) 565 if err != nil { 566 return nil, err 567 } 568 569 return Map.Get(ds.Reference()).(*Datastore), nil 570 } 571 572 const folderPrefix = "/folder/" 573 574 // ServeDatastore handler for Datastore access via /folder path. 575 func (s *Service) ServeDatastore(w http.ResponseWriter, r *http.Request) { 576 ds, ferr := s.findDatastore(r.URL.Query()) 577 if ferr != nil { 578 log.Printf("failed to locate datastore with query params: %s", r.URL.RawQuery) 579 w.WriteHeader(http.StatusNotFound) 580 return 581 } 582 583 r.URL.Path = strings.TrimPrefix(r.URL.Path, folderPrefix) 584 p := path.Join(ds.Info.GetDatastoreInfo().Url, r.URL.Path) 585 586 switch r.Method { 587 case http.MethodPost: 588 _, err := os.Stat(p) 589 if err == nil { 590 // File exists 591 w.WriteHeader(http.StatusConflict) 592 return 593 } 594 595 // File does not exist, fallthrough to create via PUT logic 596 fallthrough 597 case http.MethodPut: 598 dir := path.Dir(p) 599 _ = os.MkdirAll(dir, 0700) 600 601 f, err := os.Create(p) 602 if err != nil { 603 log.Printf("failed to %s '%s': %s", r.Method, p, err) 604 w.WriteHeader(http.StatusInternalServerError) 605 return 606 } 607 defer f.Close() 608 609 _, _ = io.Copy(f, r.Body) 610 default: 611 fs := http.FileServer(http.Dir(ds.Info.GetDatastoreInfo().Url)) 612 613 fs.ServeHTTP(w, r) 614 } 615 } 616 617 // ServiceVersions handler for the /sdk/vimServiceVersions.xml path. 618 func (s *Service) ServiceVersions(w http.ResponseWriter, r *http.Request) { 619 const versions = xml.Header + `<namespaces version="1.0"> 620 <namespace> 621 <name>urn:vim25</name> 622 <version>%s</version> 623 <priorVersions> 624 <version>6.0</version> 625 <version>5.5</version> 626 </priorVersions> 627 </namespace> 628 </namespaces> 629 ` 630 fmt.Fprintf(w, versions, s.client.ServiceContent.About.ApiVersion) 631 } 632 633 // ServiceVersionsVsan handler for the /sdk/vsanServiceVersions.xml path. 634 func (s *Service) ServiceVersionsVsan(w http.ResponseWriter, r *http.Request) { 635 const versions = xml.Header + `<namespaces version="1.0"> 636 <namespace> 637 <name>urn:vsan</name> 638 <version>%s</version> 639 <priorVersions> 640 <version>6.7</version> 641 <version>6.6</version> 642 </priorVersions> 643 </namespace> 644 </namespaces> 645 ` 646 fmt.Fprintf(w, versions, s.client.ServiceContent.About.ApiVersion) 647 } 648 649 // defaultIP returns addr.IP if specified, otherwise attempts to find a non-loopback ipv4 IP 650 func defaultIP(addr *net.TCPAddr) string { 651 if !addr.IP.IsUnspecified() { 652 return addr.IP.String() 653 } 654 655 nics, err := net.Interfaces() 656 if err != nil { 657 return addr.IP.String() 658 } 659 660 for _, nic := range nics { 661 if nic.Name == "docker0" || strings.HasPrefix(nic.Name, "vmnet") { 662 continue 663 } 664 addrs, aerr := nic.Addrs() 665 if aerr != nil { 666 continue 667 } 668 for _, addr := range addrs { 669 if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() { 670 if ip.IP.To4() != nil { 671 return ip.IP.String() 672 } 673 } 674 } 675 } 676 677 return addr.IP.String() 678 } 679 680 // NewServer returns an http Server instance for the given service 681 func (s *Service) NewServer() *Server { 682 s.RegisterSDK(Map, Map.Path+"/vimService") 683 684 mux := s.ServeMux 685 mux.HandleFunc(Map.Path+"/vimServiceVersions.xml", s.ServiceVersions) 686 mux.HandleFunc(Map.Path+"/vsanServiceVersions.xml", s.ServiceVersionsVsan) 687 mux.HandleFunc(folderPrefix, s.ServeDatastore) 688 mux.HandleFunc(guestPrefix, ServeGuest) 689 mux.HandleFunc(nfcPrefix, ServeNFC) 690 mux.HandleFunc("/about", s.About) 691 692 if s.Listen == nil { 693 s.Listen = new(url.URL) 694 } 695 ts := internal.NewUnstartedServer(mux, s.Listen.Host) 696 addr := ts.Listener.Addr().(*net.TCPAddr) 697 port := strconv.Itoa(addr.Port) 698 u := &url.URL{ 699 Scheme: "http", 700 Host: net.JoinHostPort(defaultIP(addr), port), 701 Path: Map.Path, 702 } 703 if s.TLS != nil { 704 u.Scheme += "s" 705 } 706 707 // Redirect clients to this http server, rather than HostSystem.Name 708 Map.SessionManager().ServiceHostName = u.Host 709 710 // Add vcsim config to OptionManager for use by SDK handlers (see lookup/simulator for example) 711 m := Map.OptionManager() 712 for i := range m.Setting { 713 setting := m.Setting[i].GetOptionValue() 714 715 if strings.HasSuffix(setting.Key, ".uri") { 716 // Rewrite any URIs with vcsim's host:port 717 endpoint, err := url.Parse(setting.Value.(string)) 718 if err == nil { 719 endpoint.Scheme = u.Scheme 720 endpoint.Host = u.Host 721 setting.Value = endpoint.String() 722 } 723 } 724 } 725 m.Setting = append(m.Setting, 726 &types.OptionValue{ 727 Key: "vcsim.server.url", 728 Value: u.String(), 729 }, 730 ) 731 732 u.User = s.Listen.User 733 if u.User == nil { 734 u.User = DefaultLogin 735 } 736 s.Listen = u 737 738 if s.RegisterEndpoints { 739 for i := range endpoints { 740 endpoints[i](s, Map) 741 } 742 } 743 744 for _, f := range s.funcs { 745 pattern := &url.URL{Path: f.pattern} 746 endpoint, _ := s.ServeMux.Handler(&http.Request{URL: pattern}) 747 748 if mux, ok := endpoint.(muxHandleFunc); ok { 749 mux.HandleFunc(f.pattern, f.handler) // e.g. vapi/simulator 750 } else { 751 s.ServeMux.HandleFunc(f.pattern, f.handler) 752 } 753 } 754 755 if s.TLS != nil { 756 ts.TLS = s.TLS 757 ts.TLS.ClientAuth = tls.RequestClientCert // Used by SessionManager.LoginExtensionByCertificate 758 Map.SessionManager().TLSCert = func() string { 759 return base64.StdEncoding.EncodeToString(ts.TLS.Certificates[0].Certificate[0]) 760 } 761 ts.StartTLS() 762 } else { 763 ts.Start() 764 } 765 766 return &Server{ 767 Server: ts, 768 URL: u, 769 } 770 } 771 772 // Certificate returns the TLS certificate for the Server if started with TLS enabled. 773 // This method will panic if TLS is not enabled for the server. 774 func (s *Server) Certificate() *x509.Certificate { 775 // By default httptest.StartTLS uses http/internal.LocalhostCert, which we can access here: 776 cert, _ := x509.ParseCertificate(s.TLS.Certificates[0].Certificate[0]) 777 return cert 778 } 779 780 // CertificateInfo returns Server.Certificate() as object.HostCertificateInfo 781 func (s *Server) CertificateInfo() *object.HostCertificateInfo { 782 info := new(object.HostCertificateInfo) 783 info.FromCertificate(s.Certificate()) 784 return info 785 } 786 787 // CertificateFile returns a file name, where the file contains the PEM encoded Server.Certificate. 788 // The temporary file is removed when Server.Close() is called. 789 func (s *Server) CertificateFile() (string, error) { 790 if s.caFile != "" { 791 return s.caFile, nil 792 } 793 794 f, err := os.CreateTemp("", "vcsim-") 795 if err != nil { 796 return "", err 797 } 798 defer f.Close() 799 800 s.caFile = f.Name() 801 cert := s.Certificate() 802 return s.caFile, pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) 803 } 804 805 // proxy tunnels SDK requests 806 func (s *Server) proxy(w http.ResponseWriter, r *http.Request) { 807 if r.Method != http.MethodConnect { 808 http.Error(w, "", http.StatusMethodNotAllowed) 809 return 810 } 811 812 dst, err := net.Dial("tcp", s.URL.Host) 813 if err != nil { 814 http.Error(w, err.Error(), http.StatusBadGateway) 815 return 816 } 817 w.WriteHeader(http.StatusOK) 818 819 src, _, err := w.(http.Hijacker).Hijack() 820 if err != nil { 821 http.Error(w, err.Error(), http.StatusBadRequest) 822 return 823 } 824 825 go io.Copy(src, dst) 826 go func() { 827 _, _ = io.Copy(dst, src) 828 _ = dst.Close() 829 _ = src.Close() 830 }() 831 } 832 833 // StartTunnel runs an HTTP proxy for tunneling SDK requests that require TLS client certificate authentication. 834 func (s *Server) StartTunnel() error { 835 tunnel := &http.Server{ 836 Addr: fmt.Sprintf("%s:%d", s.URL.Hostname(), s.Tunnel), 837 Handler: http.HandlerFunc(s.proxy), 838 } 839 840 l, err := net.Listen("tcp", tunnel.Addr) 841 if err != nil { 842 return err 843 } 844 845 if s.Tunnel == 0 { 846 s.Tunnel = l.Addr().(*net.TCPAddr).Port 847 } 848 849 // Set client proxy port (defaults to vCenter host port 80 in real life) 850 q := s.URL.Query() 851 q.Set("GOVMOMI_TUNNEL_PROXY_PORT", strconv.Itoa(s.Tunnel)) 852 s.URL.RawQuery = q.Encode() 853 854 go tunnel.Serve(l) 855 856 return nil 857 } 858 859 // Close shuts down the server and blocks until all outstanding 860 // requests on this server have completed. 861 func (s *Server) Close() { 862 s.Server.Close() 863 if s.caFile != "" { 864 _ = os.Remove(s.caFile) 865 } 866 } 867 868 var ( 869 vim25MapType = types.TypeFunc() 870 ) 871 872 func defaultMapType(name string) (reflect.Type, bool) { 873 typ, ok := vim25MapType(name) 874 if !ok { 875 // See TestIssue945, in which case Go does not resolve the namespace and name == "ns1:TraversalSpec" 876 // Without this hack, the SelectSet would be all nil's 877 kind := strings.SplitN(name, ":", 2) 878 if len(kind) == 2 { 879 typ, ok = vim25MapType(kind[1]) 880 } 881 } 882 return typ, ok 883 } 884 885 // Element can be used to defer decoding of an XML node. 886 type Element struct { 887 start xml.StartElement 888 inner struct { 889 Content string `xml:",innerxml"` 890 } 891 typeFunc func(string) (reflect.Type, bool) 892 } 893 894 func (e *Element) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 895 e.start = start 896 897 return d.DecodeElement(&e.inner, &start) 898 } 899 900 func (e *Element) decoder() *xml.Decoder { 901 decoder := xml.NewDecoder(strings.NewReader(e.inner.Content)) 902 decoder.TypeFunc = e.typeFunc // required to decode interface types 903 return decoder 904 } 905 906 func (e *Element) Decode(val interface{}) error { 907 return e.decoder().DecodeElement(val, &e.start) 908 } 909 910 // UnmarshalBody extracts the Body from a soap.Envelope and unmarshals to the corresponding govmomi type 911 func UnmarshalBody(typeFunc func(string) (reflect.Type, bool), data []byte) (*Method, error) { 912 body := &Element{typeFunc: typeFunc} 913 req := soap.Envelope{ 914 Header: &soap.Header{ 915 Security: new(Element), 916 }, 917 Body: body, 918 } 919 920 err := xml.Unmarshal(data, &req) 921 if err != nil { 922 return nil, fmt.Errorf("xml.Unmarshal: %s", err) 923 } 924 925 var start xml.StartElement 926 var ok bool 927 decoder := body.decoder() 928 929 for { 930 tok, derr := decoder.Token() 931 if derr != nil { 932 return nil, fmt.Errorf("decoding: %s", derr) 933 } 934 if start, ok = tok.(xml.StartElement); ok { 935 break 936 } 937 } 938 939 if !ok { 940 return nil, fmt.Errorf("decoding: method token not found") 941 } 942 943 kind := start.Name.Local 944 rtype, ok := typeFunc(kind) 945 if !ok { 946 return nil, fmt.Errorf("no vmomi type defined for '%s'", kind) 947 } 948 949 val := reflect.New(rtype).Interface() 950 951 err = decoder.DecodeElement(val, &start) 952 if err != nil { 953 return nil, fmt.Errorf("decoding %s: %s", kind, err) 954 } 955 956 method := &Method{Name: kind, Header: *req.Header, Body: val} 957 958 field := reflect.ValueOf(val).Elem().FieldByName("This") 959 960 method.This = field.Interface().(types.ManagedObjectReference) 961 962 return method, nil 963 } 964 965 func newInvalidStateFault(format string, args ...any) *types.InvalidState { 966 msg := fmt.Sprintf(format, args...) 967 return &types.InvalidState{ 968 VimFault: types.VimFault{ 969 MethodFault: types.MethodFault{ 970 FaultCause: &types.LocalizedMethodFault{ 971 Fault: &types.SystemErrorFault{ 972 Reason: msg, 973 }, 974 LocalizedMessage: msg, 975 }, 976 }, 977 }, 978 } 979 }