Skip to content

Commit

Permalink
api: add override mechanism for SOAP Header.Cookie
Browse files Browse the repository at this point in the history
Sessions are consumed by API endpoints via one of:
- HTTP Cookie (vim25 for example)
- HTTP Header (vmodl2 /rest and /api)
- SOAP Header (pbm, vslm, sms for example)

The soap.Client had always set the SOAP Header cookie regardless if consumed by an API endpoint.
Clients must now opt-in to this behavior if they need it, such as pbm and vslm.

This also allows clients to change the name of soap.Header.Cookie (default is still "vcSessionCookie")

vcsim: add override mechanism for session mapping

On the simulator side, API endpoints can specify where a session is sourced from.
Default is still the "vmware_soap_session" HTTP Cookie.
The pbm simulator now specifies the "vcSessionCookie" SOAP Header, behaving as real pbm/VC does.

Signed-off-by: Doug MacEachern <dougm@broadcom.com>
  • Loading branch information
dougm committed Dec 6, 2024
1 parent ab30b51 commit 1918984
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 35 deletions.
1 change: 1 addition & 0 deletions pbm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type Client struct {

func NewClient(ctx context.Context, c *vim25.Client) (*Client, error) {
sc := c.Client.NewServiceClient(Path, Namespace)
sc.Cookie = sc.SessionCookie // vcSessionCookie soap.Header

req := types.PbmRetrieveServiceContent{
This: ServiceInstance,
Expand Down
25 changes: 25 additions & 0 deletions pbm/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,28 @@ func TestSupportsEncryption(t *testing.T) {
})
})
}

func TestClientCookie(t *testing.T) {
simulator.Test(func(ctx context.Context, c *vim25.Client) {
pbmc, err := pbm.NewClient(ctx, c)
assert.NoError(t, err)
assert.NotNil(t, pbmc)

// Using default / expected Header.Cookie.XMLName = vcSessionCookie
ok, err := pbmc.SupportsEncryption(ctx, pbmsim.DefaultEncryptionProfileID)
assert.NoError(t, err)
assert.True(t, ok)

// Using invalid Header.Cookie.XMLName = myCookie
myCookie := c.SessionCookie()
myCookie.XMLName.Local = "myCookie"

pbmc.Client.Cookie = func() *soap.HeaderElement {
return myCookie
}

ok, err = pbmc.SupportsEncryption(ctx, pbmsim.DefaultEncryptionProfileID)
assert.EqualError(t, err, "ServerFaultCode: NotAuthenticated")
assert.False(t, ok)
})
}
1 change: 1 addition & 0 deletions pbm/simulator/simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func New() *simulator.Registry {
r := simulator.NewRegistry()
r.Namespace = pbm.Namespace
r.Path = pbm.Path
r.Cookie = simulator.SOAPCookie

r.Put(&ServiceInstance{
ManagedObjectReference: pbm.ServiceInstance,
Expand Down
1 change: 1 addition & 0 deletions simulator/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ type Registry struct {
Namespace string
Path string
Handler func(*Context, *Method) (mo.Reference, types.BaseMethodFault)
Cookie func(*Context) string

tagManager tagManager
}
Expand Down
25 changes: 21 additions & 4 deletions simulator/session_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,12 +329,29 @@ type Context struct {
Map *Registry
}

func SOAPCookie(ctx *Context) string {
if cookie := ctx.Header.Cookie; cookie != nil {
return cookie.Value
}
return ""
}

func HTTPCookie(ctx *Context) string {
if cookie, err := ctx.req.Cookie(soap.SessionCookieName); err == nil {
return cookie.Value
}
return ""
}

// mapSession maps an HTTP cookie to a Session.
func (c *Context) mapSession() {
if cookie, err := c.req.Cookie(soap.SessionCookieName); err == nil {
if val, ok := c.svc.sm.getSession(cookie.Value); ok {
c.SetSession(val, false)
}
cookie := c.Map.Cookie
if cookie == nil {
cookie = HTTPCookie
}

if val, ok := c.svc.sm.getSession(cookie(c)); ok {
c.SetSession(val, false)
}
}

Expand Down
4 changes: 2 additions & 2 deletions simulator/simulator.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2017-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2017-2024 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.
Expand Down Expand Up @@ -497,7 +497,6 @@ func (s *Service) ServeSDK(w http.ResponseWriter, r *http.Request) {
Map: s.sdk[r.URL.Path],
Context: context.Background(),
}
ctx.Map.WithLock(ctx, s.sm, ctx.mapSession)

var res soap.HasFault
var soapBody interface{}
Expand All @@ -511,6 +510,7 @@ func (s *Service) ServeSDK(w http.ResponseWriter, r *http.Request) {
// Redirect any Fetch method calls to the PropertyCollector singleton
method.This = ctx.Map.content().PropertyCollector
}
ctx.Map.WithLock(ctx, s.sm, ctx.mapSession)
res = s.call(ctx, method)
}

Expand Down
4 changes: 2 additions & 2 deletions ssoadmin/client.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
Copyright (c) 2018-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2018-2024 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
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,
Expand Down
4 changes: 2 additions & 2 deletions sts/client.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
Copyright (c) 2018-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2018-2024 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
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,
Expand Down
32 changes: 20 additions & 12 deletions vim25/soap/client.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2014-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2014-2024 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.
Expand Down Expand Up @@ -86,7 +86,11 @@ type Client struct {
Types types.Func `json:"types"`
UserAgent string `json:"userAgent"`

cookie string
// Cookie returns a value for the SOAP Header.Cookie.
// This SOAP request header is used for authentication by
// API endpoints such as pbm, vslm and sms.
// When nil, no SOAP Header.Cookie is set.
Cookie func() *HeaderElement
insecureCookies bool

useJSON bool
Expand Down Expand Up @@ -210,6 +214,16 @@ func (c *Client) NewServiceClient(path string, namespace string) *Client {
return c.newServiceClientWithTransport(path, namespace, c.t)
}

// SessionCookie returns a SessionCookie with value of the vmware_soap_session http.Cookie.
func (c *Client) SessionCookie() *HeaderElement {
for _, cookie := range c.Jar.Cookies(c.URL()) {
if cookie.Name == SessionCookieName {
return &HeaderElement{Value: cookie.Value}
}
}
return nil
}

func (c *Client) newServiceClientWithTransport(path string, namespace string, t *http.Transport) *Client {
vc := c.URL()
u, err := url.Parse(path)
Expand All @@ -234,14 +248,6 @@ func (c *Client) newServiceClientWithTransport(path string, namespace string, t
// Copy the cookies
client.Client.Jar.SetCookies(u, c.Client.Jar.Cookies(u))

// Set SOAP Header cookie
for _, cookie := range client.Jar.Cookies(u) {
if cookie.Name == SessionCookieName {
client.cookie = cookie.Value
break
}
}

// Copy any query params (e.g. GOVMOMI_TUNNEL_PROXY_PORT used in testing)
client.u.RawQuery = vc.RawQuery

Expand Down Expand Up @@ -718,8 +724,10 @@ func (c *Client) soapRoundTrip(ctx context.Context, reqBody, resBody HasFault) e
h.ID = id
}

h.Cookie = c.cookie
if h.Cookie != "" || h.ID != "" || h.Security != nil {
if c.Cookie != nil {
h.Cookie = c.Cookie()
}
if h.Cookie != nil || h.ID != "" || h.Security != nil {
reqEnv.Header = &h // XML marshal header only if a field is set
}

Expand Down
14 changes: 9 additions & 5 deletions vim25/soap/json_client.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2023-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2023-2024 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.
Expand Down Expand Up @@ -71,8 +71,10 @@ func (c *Client) invoke(ctx context.Context, this types.ManagedObjectReference,
return err
}

if len(c.cookie) != 0 {
req.Header.Add(sessionHeader, c.cookie)
if c.Cookie != nil {
if cookie := c.Cookie(); cookie != nil {
req.Header.Add(sessionHeader, cookie.Value)
}
}

result, err := getSOAPResultPtr(res)
Expand Down Expand Up @@ -156,8 +158,10 @@ func isError(statusCode int) bool {
// session header.
func (c *Client) checkForSessionHeader(resp *http.Response) {
sessionKey := resp.Header.Get(sessionHeader)
if len(sessionKey) > 0 {
c.cookie = sessionKey
if sessionKey != "" {
c.Cookie = func() *HeaderElement {
return &HeaderElement{Value: sessionKey}
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions vim25/soap/json_client_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2023-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2023-2024 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.
Expand Down Expand Up @@ -206,7 +206,9 @@ func TestFullRequestCycle(t *testing.T) {
c := NewClient(addr, true)
c.Namespace = "urn:vim25"
c.Version = "8.0.0.1"
c.cookie = "(original)"
c.Cookie = func() *HeaderElement {
return &HeaderElement{Value: "(original)"}
}
c.UseJSON(true)

c.Transport = &mockHTTP{
Expand Down
18 changes: 12 additions & 6 deletions vim25/soap/soap.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
Copyright (c) 2014-2018 VMware, Inc. All Rights Reserved.
Copyright (c) 2014-2024 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
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,
Expand All @@ -21,12 +21,18 @@ import (
"github.com/vmware/govmomi/vim25/xml"
)

// HeaderElement allows changing the default XMLName (e.g. Cookie's default of vcSessionCookie)
type HeaderElement struct {
XMLName xml.Name
Value string `xml:",chardata"`
}

// Header includes optional soap Header fields.
type Header struct {
Action string `xml:"-"` // Action is the 'SOAPAction' HTTP header value. Defaults to "Client.Namespace/Client.Version".
Cookie string `xml:"vcSessionCookie,omitempty"` // Cookie is a vCenter session cookie that can be used with other SDK endpoints (e.g. pbm).
ID string `xml:"operationID,omitempty"` // ID is the operationID used by ESX/vCenter logging for correlation.
Security interface{} `xml:",omitempty"` // Security is used for SAML token authentication and request signing.
Action string `xml:"-"` // Action is the 'SOAPAction' HTTP header value. Defaults to "Client.Namespace/Client.Version".
Cookie *HeaderElement `xml:"vcSessionCookie,omitempty"` // Cookie is a vCenter session cookie that can be used with other SDK endpoints (e.g. pbm, vslm).
ID string `xml:"operationID,omitempty"` // ID is the operationID used by ESX/vCenter logging for correlation.
Security interface{} `xml:",omitempty"` // Security is used for SAML token authentication and request signing.
}

type Envelope struct {
Expand Down
2 changes: 2 additions & 0 deletions vslm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type Client struct {

func NewClient(ctx context.Context, c *vim25.Client) (*Client, error) {
sc := c.Client.NewServiceClient(Path, Namespace)
sc.Cookie = sc.SessionCookie // vcSessionCookie soap.Header

req := types.RetrieveContent{
This: ServiceInstance,
}
Expand Down

0 comments on commit 1918984

Please sign in to comment.