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