diff --git a/.travis.yml b/.travis.yml index acf4caf8a..124a6cf66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: go go: - - "1.12.x" + - "1.14.x" sudo: false script: - - env GO111MODULE=on make + - make after_success: - echo "Build Successful!" diff --git a/CHANGELOG.md b/CHANGELOG.md index d7c51a8a0..3ec2c967c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ * Append log files by default instead of overwriting. `GOVCD_LOG_OVERWRITE=true` environment variable can set to overwrite log file on every initialization [#307](https://github.com/vmware/go-vcloud-director/pull/307) +* Add configuration option `WithSamlAdfs` to `NewVCDClient()` to support SAML authentication using + Active Directory Federations Services (ADFS) as IdP using WS-TRUST auth endpoint + "/adfs/services/trust/13/usernamemixed" + [#304](https://github.com/vmware/go-vcloud-director/pull/304) + ## 2.7.0 (April 10,2020) diff --git a/Makefile b/Makefile index 26ceb47b8..2c8c0ec6f 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ testnsxv: # any common errors. vet: @echo "==> Running Go Vet" - @cd govcd && go vet ; if [ $$? -ne 0 ] ; then echo "vet error!" ; exit 1 ; fi && cd - + @go vet ./... ; if [ $$? -ne 0 ] ; then echo "vet error!" ; exit 1 ; fi # static runs the source code static analysis tool `staticcheck` static: fmtcheck diff --git a/README.md b/README.md index a5619eadc..4a6c50253 100644 --- a/README.md +++ b/README.md @@ -114,25 +114,35 @@ func main() { ## Authentication -You can authenticate to the vCD in three ways: +You can authenticate to the vCD in four ways: * With a System Administration user and password (`administrator@system`) * With an Organization user and password (`tenant-admin@org-name`) -* With an authorization token - -For the first two methods, you use: +For the above two methods, you use: ```go err := vcdClient.Authenticate(User, Password, Org) // or resp, err := vcdClient.GetAuthResponse(User, Password, Org) ``` -For the token, you use: - +* With an authorization token ```go err := vcdClient.SetToken(Org, govcd.AuthorizationHeader, Token) ``` +The file `scripts/get_token.sh` provides a handy method of extracting the token +(`x-vcloud-authorization` value) for future use. + +* SAML user and password (works with ADFS as IdP using WS-TRUST endpoint + "/adfs/services/trust/13/usernamemixed"). One must pass `govcd.WithSamlAdfs(true,customAdfsRptId)` + and username must be formatted so that ADFS understands it ('user@contoso.com' or + 'contoso.com\user') You can find usage example in + [samples/saml_auth_adfs](/samples/saml_auth_adfs). +```go +vcdCli := govcd.NewVCDClient(*vcdURL, true, govcd.WithSamlAdfs(true, customAdfsRptId)) +err = vcdCli.Authenticate(username, password, org) +``` -The file `scripts/get_token.sh` provides a handy method of extracting the token (`x-vcloud-authorization` value) for future use. - +More information about inner workings of SAML auth flow in this codebase can be found in +`saml_auth.go:authorizeSamlAdfs(...)`. Additionaly this flow is documented in [vCD +documentation](https://code.vmware.com/docs/10000/vcloud-api-programming-guide-for-service-providers/GUID-335CFC35-7AD8-40E5-91BE-53971937A2BB.html). \ No newline at end of file diff --git a/TESTING.md b/TESTING.md index ddc4f50cd..c8269ae31 100644 --- a/TESTING.md +++ b/TESTING.md @@ -309,6 +309,16 @@ func (vcd *TestVCD) Test_ComposeVApp(check *checks.C) { } ``` +# Golden test files + +In some tests (especially unit) there is a need for data samples (Golden files). There are a few +helpers in `api_vcd_test_unit.go` - `goldenString` and `goldenBytes`. These helpers are here to +unify data storage naming formats. All files will be stored in `test-resources/golden/`. File name +will be formatted as `t.Name() + "custompart" + ".golden"` (e.g. +"TestSamlAdfsAuthenticate_custompart.golden"). These functions allow to update existing data by +supplying actual data and setting `update=true`. As an example `TestSamlAdfsAuthenticate` test uses +golden data. + # Environment variables and corresponding flags While running tests, the following environment variables can be used: @@ -335,6 +345,22 @@ While running tests, the following environment variables can be used: When both the environment variable and the command line option are possible, the environment variable gets evaluated first. +# SAML auth testing with Active Directory Federation Services (ADFS) as Identity Provider (IdP) + +This package supports SAML authentication with ADFS. It can be achieved by supplying +`WithSamlAdfs()` function to `NewVCDClient`. Testing framework also supports SAML auth and there are +a few ways to test it: +* There is a unit test `TestSamlAdfsAuthenticate` which spawns mock servers and does not require + ADFS or SAML being configured. It tests the flow based on mock endpoints. +* Using regular `user` and `password` to supply SAML credentials and `true` for `useSamlAdfs` + variable (optionally one can override Relaying Party Trust ID with variable `customAdfsRptId`). + That way all tests would run using SAML authentication flow. +* Using `samlUser`, `samlPassword` and optionally `samlCustomRptId` variables will enable + `Test_SamlAdfsAuth` test run. Test_SamlAdfsAuth will test and compare VDC retrieved using main + authentication credentials vs the one retrieved using specific SAML credentials. + +All these tests can run in combination. + # Final Words Be careful about using our tests as these tests run on a real vcd. If you don't have 1 gb of ram and 2 vcpus available then you should not be running tests that deploy your vm/change memory and cpu. However everything created will be removed at the end of testing. diff --git a/govcd/api.go b/govcd/api.go index 5c28172aa..6c8cacdd6 100644 --- a/govcd/api.go +++ b/govcd/api.go @@ -14,7 +14,6 @@ import ( "net/http" "net/url" "os" - "reflect" "strings" "time" @@ -36,10 +35,20 @@ type Client struct { // This must be >0 to avoid instant timeout errors. MaxRetryTimeout int + // UseSamlAdfs specifies if SAML auth is used for authenticating vCD instead of local login. + // The following conditions must be met so that authentication SAML authentication works: + // * SAML IdP (Identity Provider) is Active Directory Federation Service (ADFS) + // * Authentication endpoint "/adfs/services/trust/13/usernamemixed" must be enabled on ADFS + // server + UseSamlAdfs bool + // CustomAdfsRptId allows to set custom Relaying Party Trust identifier. By default vCD Entity + // ID is used as Relaying Party Trust identifier. + CustomAdfsRptId string + supportedVersions SupportedVersions // Versions from /api/versions endpoint } -// The header key used by default to set the authorization token. +// AuthorizationHeader header key used by default to set the authorization token. const AuthorizationHeader = "X-Vcloud-Authorization" // General purpose error to be used whenever an entity is not found from a "GET" request @@ -147,6 +156,12 @@ func (cli *Client) NewRequestWitNotEncodedParams(params map[string]string, notEn // * body - request body // * apiVersion - provided Api version overrides default Api version value used in request parameter func (cli *Client) NewRequestWitNotEncodedParamsWithApiVersion(params map[string]string, notEncodedParams map[string]string, method string, reqUrl url.URL, body io.Reader, apiVersion string) *http.Request { + return cli.newRequest(params, notEncodedParams, method, reqUrl, body, apiVersion, nil) +} + +// newRequest is the parent of many "specific" "NewRequest" functions. +// Note. It is kept private to avoid breaking public API on every new field addition. +func (cli *Client) newRequest(params map[string]string, notEncodedParams map[string]string, method string, reqUrl url.URL, body io.Reader, apiVersion string, additionalHeader http.Header) *http.Request { reqValues := url.Values{} // Build up our request parameters @@ -163,6 +178,14 @@ func (cli *Client) NewRequestWitNotEncodedParamsWithApiVersion(params map[string } } + // If the body contains data - try to read all contents for logging and re-create another + // io.Reader with all contents to use it down the line + var readBody []byte + if body != nil { + readBody, _ = ioutil.ReadAll(body) + body = bytes.NewReader(readBody) + } + // Build the request, no point in checking for errors here as we're just // passing a string version of an url.URL struct and http.NewRequest returns // error only if can't process an url.ParseRequestURI(). @@ -171,39 +194,38 @@ func (cli *Client) NewRequestWitNotEncodedParamsWithApiVersion(params map[string if cli.VCDAuthHeader != "" && cli.VCDToken != "" { // Add the authorization header req.Header.Add(cli.VCDAuthHeader, cli.VCDToken) + } + if (cli.VCDAuthHeader != "" && cli.VCDToken != "") || + (additionalHeader != nil && additionalHeader.Get("Authorization") != "") { // Add the Accept header for VCD req.Header.Add("Accept", "application/*+xml;version="+apiVersion) } + // Merge in additional headers before logging if any where specified in additionalHeader + // paramter + if additionalHeader != nil && len(additionalHeader) > 0 { + for headerName, headerValueSlice := range additionalHeader { + for _, singleHeaderValue := range headerValueSlice { + req.Header.Add(headerName, singleHeaderValue) + } + } + } + // Avoids passing data if the logging of requests is disabled if util.LogHttpRequest { - // Makes a safe copy of the request body, and passes it - // to the processing function. payload := "" if req.ContentLength > 0 { - // We try to convert body to a *bytes.Buffer - var ibody interface{} = body - bbody, ok := ibody.(*bytes.Buffer) - // If the inner object is a bytes.Buffer, we get a safe copy of the data. - // If it is really just an io.Reader, we don't, as the copy would empty the reader - if ok { - payload = bbody.String() - } else { - // With this content, we'll know that the payload is not really empty, but - // it was unavailable due to the body type. - payload = fmt.Sprintf("", reflect.TypeOf(body)) - } + payload = string(readBody) } util.ProcessRequestOutput(util.FuncNameCallStack(), method, reqUrl.String(), payload, req) - debugShowRequest(req, payload) } + return req } -// NewRequest creates a new HTTP request and applies necessary auth headers if -// set. +// NewRequest creates a new HTTP request and applies necessary auth headers if set. func (cli *Client) NewRequest(params map[string]string, method string, reqUrl url.URL, body io.Reader) *http.Request { return cli.NewRequestWitNotEncodedParams(params, nil, method, reqUrl, body) } @@ -237,9 +259,13 @@ func decodeBody(resp *http.Response, out interface{}) error { } debugShowResponse(resp, body) - // Unmarshal the XML. - if err = xml.Unmarshal(body, &out); err != nil { - return err + + // only attempty to unmarshal if body is not empty + if len(body) > 0 { + // Unmarshal the XML. + if err = xml.Unmarshal(body, &out); err != nil { + return err + } } return nil @@ -266,7 +292,8 @@ func checkRespWithErrType(resp *http.Response, err, errType error) (*http.Respon http.StatusOK, // 200 http.StatusCreated, // 201 http.StatusAccepted, // 202 - http.StatusNoContent: // 204 + http.StatusNoContent, // 204 + http.StatusFound: // 302 return resp, nil // Invalid request, parse the XML error returned and return it. case diff --git a/govcd/api_vcd.go b/govcd/api_vcd.go index 9f9c66fe8..83addcd4a 100644 --- a/govcd/api_vcd.go +++ b/govcd/api_vcd.go @@ -117,7 +117,7 @@ func NewVCDClient(vcdEndpoint url.URL, insecure bool, options ...VCDClientOption return vcdClient } -// Authenticate is an helper function that performs a login in vCloud Director. +// Authenticate is a helper function that performs a login in vCloud Director. func (vcdCli *VCDClient) Authenticate(username, password, org string) error { _, err := vcdCli.GetAuthResponse(username, password, org) return err @@ -132,11 +132,24 @@ func (vcdCli *VCDClient) GetAuthResponse(username, password, org string) (*http. if err != nil { return nil, fmt.Errorf("error finding LoginUrl: %s", err) } - // Authorize - resp, err := vcdCli.vcdAuthorize(username, password, org) - if err != nil { - return nil, fmt.Errorf("error authorizing: %s", err) + + // Choose correct auth mechanism based on what type of authentication is used. The end result + // for each of the below functions is to set authorization token vcdCli.Client.VCDToken. + var resp *http.Response + switch { + case vcdCli.Client.UseSamlAdfs: + err = vcdCli.authorizeSamlAdfs(username, password, org, vcdCli.Client.CustomAdfsRptId) + if err != nil { + return nil, fmt.Errorf("error authorizing SAML: %s", err) + } + default: + // Authorize + resp, err = vcdCli.vcdAuthorize(username, password, org) + if err != nil { + return nil, fmt.Errorf("error authorizing: %s", err) + } } + return resp, nil } @@ -217,3 +230,18 @@ func WithHttpTimeout(timeout int64) VCDClientOption { return nil } } + +// WithSamlAdfs specifies if SAML auth is used for authenticating to vCD instead of local login. +// The following conditions must be met so that SAML authentication works: +// * SAML IdP (Identity Provider) is Active Directory Federation Service (ADFS) +// * WS-Trust authentication endpoint "/adfs/services/trust/13/usernamemixed" must be enabled on +// ADFS server +// By default vCD SAML Entity ID will be used as Relaying Party Trust Identifier unless +// customAdfsRptId is specified +func WithSamlAdfs(useSaml bool, customAdfsRptId string) VCDClientOption { + return func(vcdClient *VCDClient) error { + vcdClient.Client.UseSamlAdfs = useSaml + vcdClient.Client.CustomAdfsRptId = customAdfsRptId + return nil + } +} diff --git a/govcd/api_vcd_test.go b/govcd/api_vcd_test.go index 5387dac79..d21fcf6eb 100644 --- a/govcd/api_vcd_test.go +++ b/govcd/api_vcd_test.go @@ -1,4 +1,4 @@ -// +build api functional catalog vapp gateway network org query extnetwork task vm vdc system disk lb lbAppRule lbAppProfile lbServerPool lbServiceMonitor lbVirtualServer user search nsxv ALL +// +build api functional catalog vapp gateway network org query extnetwork task vm vdc system disk lb lbAppRule lbAppProfile lbServerPool lbServiceMonitor lbVirtualServer user search nsxv auth ALL /* * Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. @@ -28,6 +28,21 @@ import ( "gopkg.in/yaml.v2" ) +func init() { + testingTags["api"] = "api_vcd_test.go" + + // To list the flags when we run "go test -tags functional -vcd-help", the flag name must start with "vcd" + // They will all appear alongside the native flags when we use an invalid one + setBoolFlag(&vcdHelp, "vcd-help", "VCD_HELP", "Show vcd flags") + setBoolFlag(&enableDebug, "vcd-debug", "GOVCD_DEBUG", "enables debug output") + setBoolFlag(&testVerbose, "vcd-verbose", "GOVCD_TEST_VERBOSE", "enables verbose output") + setBoolFlag(&skipVappCreation, "vcd-skip-vapp-creation", "GOVCD_SKIP_VAPP_CREATION", "Skips vApp creation") + setBoolFlag(&ignoreCleanupFile, "vcd-ignore-cleanup-file", "GOVCD_IGNORE_CLEANUP_FILE", "Does not process previous cleanup file") + setBoolFlag(&debugShowRequestEnabled, "vcd-show-request", "GOVCD_SHOW_REQ", "Shows API request") + setBoolFlag(&debugShowResponseEnabled, "vcd-show-response", "GOVCD_SHOW_RESP", "Shows API response") + +} + const ( // Names for entities created by the tests TestCreateOrg = "TestCreateOrg" @@ -83,9 +98,26 @@ const ( // specifies type TestConfig struct { Provider struct { - User string `yaml:"user"` - Password string `yaml:"password"` - Token string `yaml:"token"` + User string `yaml:"user"` + Password string `yaml:"password"` + Token string `yaml:"token"` + + // UseSamlAdfs specifies if SAML auth is used for authenticating vCD instead of local login. + // The above `User` and `Password` will be used to authenticate against ADFS IdP when true. + UseSamlAdfs bool `yaml:"useSamlAdfs"` + + // CustomAdfsRptId allows to set custom Relaying Party Trust identifier if needed. Only has + // effect if `UseSamlAdfs` is true. + CustomAdfsRptId string `yaml:"customAdfsRptId"` + + // The variables `SamlUser`, `SamlPassword` and `SamlCustomRptId` are optional and are + // related to an additional test run specifically with SAML user/password. It is useful in + // case local user is used for test run (defined by above 'User', 'Password' variables). + // SamlUser takes ADFS friendly format ('contoso.com\username' or 'username@contoso.com') + SamlUser string `yaml:"samlUser,omitempty"` + SamlPassword string `yaml:"samlPassword,omitempty"` + SamlCustomRptId string `yaml:"samlCustomRptId,omitempty"` + Url string `yaml:"url"` SysOrg string `yaml:"sysOrg"` MaxRetryTimeout int `yaml:"maxRetryTimeout,omitempty"` @@ -369,6 +401,10 @@ func GetTestVCDFromYaml(testConfig TestConfig, options ...VCDClientOption) (*VCD options = append(options, WithHttpTimeout(testConfig.Provider.HttpTimeout)) } + if testConfig.Provider.UseSamlAdfs { + options = append(options, WithSamlAdfs(true, testConfig.Provider.CustomAdfsRptId)) + } + return NewVCDClient(*configUrl, true, options...), nil } @@ -439,6 +475,9 @@ func (vcd *TestVCD) SetUpSuite(check *C) { } else { err = vcd.client.Authenticate(config.Provider.User, config.Provider.Password, config.Provider.SysOrg) } + if config.Provider.UseSamlAdfs { + authenticationMode = "SAML password" + } if err != nil { panic(err) } @@ -1469,18 +1508,3 @@ func setTestEnv() { _ = os.Setenv("GOVCD_SHOW_RESP", "1") } } - -func init() { - testingTags["api"] = "api_vcd_test.go" - - // To list the flags when we run "go test -tags functional -vcd-help", the flag name must start with "vcd" - // They will all appear alongside the native flags when we use an invalid one - setBoolFlag(&vcdHelp, "vcd-help", "VCD_HELP", "Show vcd flags") - setBoolFlag(&enableDebug, "vcd-debug", "GOVCD_DEBUG", "enables debug output") - setBoolFlag(&testVerbose, "vcd-verbose", "GOVCD_TEST_VERBOSE", "enables verbose output") - setBoolFlag(&skipVappCreation, "vcd-skip-vapp-creation", "GOVCD_SKIP_VAPP_CREATION", "Skips vApp creation") - setBoolFlag(&ignoreCleanupFile, "vcd-ignore-cleanup-file", "GOVCD_IGNORE_CLEANUP_FILE", "Does not process previous cleanup file") - setBoolFlag(&debugShowRequestEnabled, "vcd-show-request", "GOVCD_SHOW_REQ", "Shows API request") - setBoolFlag(&debugShowResponseEnabled, "vcd-show-response", "GOVCD_SHOW_RESP", "Shows API response") - -} diff --git a/govcd/api_vcd_test_unit.go b/govcd/api_vcd_test_unit.go new file mode 100644 index 000000000..abaa9029b --- /dev/null +++ b/govcd/api_vcd_test_unit.go @@ -0,0 +1,43 @@ +// +build unit ALL + +package govcd + +import ( + "io/ioutil" + "os" + "testing" +) + +// goldenString is a test helper to manage Golden files. It supports `update` parameter which may be +// useful for writing such files (manual or automated way). +func goldenString(t *testing.T, goldenFile string, actual string, update bool) string { + t.Helper() + + goldenPath := "../test-resources/golden/" + t.Name() + "_" + goldenFile + ".golden" + + f, err := os.OpenFile(goldenPath, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + t.Fatalf("unable to find golden file '%s': %s", goldenPath, err) + } + defer f.Close() + + if update { + _, err := f.WriteString(actual) + if err != nil { + t.Fatalf("error writing to file %s: %s", goldenPath, err) + } + + return actual + } + + content, err := ioutil.ReadAll(f) + if err != nil { + t.Fatalf("error opening file %s: %s", goldenPath, err) + } + return string(content) +} + +// goldenBytes wraps goldenString and returns []byte +func goldenBytes(t *testing.T, goldenFile string, actual []byte, update bool) []byte { + return []byte(goldenString(t, goldenFile, string(actual), update)) +} diff --git a/govcd/api_vcd_versions.go b/govcd/api_vcd_versions.go index 94d8251d6..df96efecd 100644 --- a/govcd/api_vcd_versions.go +++ b/govcd/api_vcd_versions.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "sort" + "strings" semver "github.com/hashicorp/go-version" @@ -122,6 +123,13 @@ func (cli *Client) vcdFetchSupportedVersions() error { cli.supportedVersions = *suppVersions + // Log all supported API versions in one line to help identify vCD version from logs + allApiVersions := make([]string, len(cli.supportedVersions.VersionInfos)) + for versionIndex, version := range cli.supportedVersions.VersionInfos { + allApiVersions[versionIndex] = version.Version + } + util.Logger.Printf("[DEBUG] supported API versions : %s", strings.Join(allApiVersions, ",")) + return err } diff --git a/govcd/saml_auth.go b/govcd/saml_auth.go new file mode 100644 index 000000000..9397ff887 --- /dev/null +++ b/govcd/saml_auth.go @@ -0,0 +1,321 @@ +/* + * Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "github.com/vmware/go-vcloud-director/v2/types/v56" + "github.com/vmware/go-vcloud-director/v2/util" +) + +/* +This file implements SAML authentication flow using Microsoft Active Directory Federation Services +(ADFS). It adds support to authenticate to Cloud Director using SAML authentication (by applying +WithSamlAdfs() configuration option to NewVCDClient function). The identity provider (IdP) must be +Active Directory Federation Services (ADFS) and "/adfs/services/trust/13/usernamemixed" endpoint +must be enabled to make it work. Furthermore username must be supplied in ADFS friendly format - +test@contoso.com' or 'contoso.com\test'. + +It works by finding ADFS login endpoint for vCD by querying vCD SAML redirect endpoint +for specific Org and then submits authentication request to "/adfs/services/trust/13/usernamemixed" +endpoint of ADFS server. Using ADFS response it constructs a SIGN token which vCD accepts for the +"/api/sessions". After first initial "login" it grabs the regular X-Vcloud-Authorization token and +uses it for further requests. +More information in vCD documentation: +https://code.vmware.com/docs/10000/vcloud-api-programming-guide-for-service-providers/GUID-335CFC35-7AD8-40E5-91BE-53971937A2BB.html + +There is a working code example in /samples/saml_auth_adfs directory how to setup client using SAML +auth. +*/ + +// authorizeSamlAdfs is the main entry point for SAML authentication on ADFS endpoint +// "/adfs/services/trust/13/usernamemixed" +// Input parameters: +// user - username for authentication to ADFS server (e.g. 'test@contoso.com' or +// 'contoso.com\test') +// pass - password for authentication to ADFS server +// org - Org to authenticate to +// override_rpt_id - override relaying party trust ID. If it is empty - vCD Entity ID will be used +// as relaying party trust ID +// +// The general concept is to get a SIGN token from ADFS IdP (Identity Provider) and exchange it with +// regular vCD token for further operations. It is documented in +// https://code.vmware.com/docs/10000/vcloud-api-programming-guide-for-service-providers/GUID-335CFC35-7AD8-40E5-91BE-53971937A2BB.html +// This is achieved with the following steps: +// 1 - Lookup vCD Entity ID to use for ADFS authentication or use custom value if overrideRptId +// field is provided +// 2 - Find ADFS server name by querying vCD SAML URL which responds with HTTP redirect (302) +// 3 - Authenticate to ADFS server using vCD SAML Entity ID or custom value if overrideRptId is +// specified Relying Party Trust Identifier +// 4 - Process received ciphers from ADFS server (gzip and base64 encode) so that data can be used +// as SIGN token in vCD +// 5 - Authenticate to vCD using SIGN token in order to receive back regular +// X-Vcloud-Authorization token +// 6 - Set the received X-Vcloud-Authorization for further usage +func (vcdCli *VCDClient) authorizeSamlAdfs(user, pass, org, overrideRptId string) error { + // Step 1 - find SAML entity ID configured in vCD metadata URL unless overrideRptId is provided + // Example URL: url.Scheme + "://" + url.Host + "/cloud/org/" + org + "/saml/metadata/alias/vcd" + samlEntityId := overrideRptId + var err error + if overrideRptId == "" { + samlEntityId, err = getSamlEntityId(vcdCli, org) + if err != nil { + return fmt.Errorf("SAML - error getting vCD SAML Entity ID: %s", err) + } + } + + // Step 2 - find ADFS server used for SAML by calling vCD SAML endpoint and hoping for a + // redirect to ADFS server. Example URL: + // url.Scheme + "://" + url.Host + "/login/my-org/saml/login/alias/vcd?service=tenant:" + org + adfsAuthEndPoint, err := getSamlAdfsServer(vcdCli, org) + if err != nil { + return fmt.Errorf("SAML - error getting IdP (ADFS): %s", err) + } + + // Step 3 - authenticate to ADFS to receive SIGN token which can be used for vCD authentication + signToken, err := getSamlAuthToken(vcdCli, user, pass, samlEntityId, adfsAuthEndPoint, org) + if err != nil { + return fmt.Errorf("SAML - could not get auth token from IdP (ADFS). Did you specify "+ + "username in ADFS format ('user@contoso.com' or 'contoso.com\\user')? : %s", err) + } + + // Step 4 - gzip and base64 encode SIGN token so that vCD can understand it + base64GzippedSignToken, err := gzipAndBase64Encode(signToken) + if err != nil { + return fmt.Errorf("SAML - error encoding SIGN token: %s", err) + } + util.Logger.Printf("[DEBUG] SAML got SIGN token from IdP '%s' for entity with ID '%s'", + adfsAuthEndPoint, samlEntityId) + + // Step 5 - authenticate to vCD with SIGN token and receive vCD regular token in exchange + accessToken, err := authorizeSignToken(vcdCli, base64GzippedSignToken, org) + if err != nil { + return fmt.Errorf("SAML - error submitting SIGN token to vCD: %s", err) + } + + // Step 6 - set regular vCD auth token X-Vcloud-Authorization + err = vcdCli.SetToken(org, AuthorizationHeader, accessToken) + if err != nil { + return fmt.Errorf("error during token-based authentication: %s", err) + } + + return nil +} + +// getSamlAdfsServer finds out Active Directory Federation Service (ADFS) server to use +// for SAML authentication +// It works by temporarily patching existing http.Client behavior to avoid automatically +// following HTTP redirects and searches for Location header after the request to vCD SAML redirect +// address. The URL to search redirect location is: +// url.Scheme + "://" + url.Host + "/login/my-org/saml/login/alias/vcd?service=tenant:" + org +// +// Concurrency note. This function temporarily patches `vcdCli.Client.Http` therefore http.Client +// would not follow redirects during this time. It is however safe as vCDClient is not expected to +// use `http.Client` in any other place before authentication occurs. +func getSamlAdfsServer(vcdCli *VCDClient, org string) (string, error) { + url := vcdCli.Client.VCDHREF + + // Backup existing http.Client redirect behavior so that it does not follow HTTP redirects + // automatically and restore it right after this function by using defer. A new http.Client + // could be spawned here, but the existing one is re-used on purpose to inherit all other + // settings used for client (timeouts, etc). + backupRedirectChecker := vcdCli.Client.Http.CheckRedirect + + defer func() { + vcdCli.Client.Http.CheckRedirect = backupRedirectChecker + }() + + // Patch http client to avoid following redirects + vcdCli.Client.Http.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + + // Construct SAML login URL which should return a redirect to ADFS server + loginURLString := url.Scheme + "://" + url.Host + "/login/" + org + "/saml/login/alias/vcd" + loginURL, err := url.Parse(loginURLString) + if err != nil { + return "", fmt.Errorf("unable to parse login URL '%s': %s", loginURLString, err) + } + util.Logger.Printf("[DEBUG] SAML looking up IdP (ADFS) host redirect in: %s", loginURL.String()) + + // Make a request to URL adding unencoded query parameters in the format: + // "?service=tenant:my-org" + req := vcdCli.Client.NewRequestWitNotEncodedParams( + nil, map[string]string{"service": "tenant:" + org}, http.MethodGet, *loginURL, nil) + httpResponse, err := checkResp(vcdCli.Client.Http.Do(req)) + if err != nil { + return "", fmt.Errorf("SAML - ADFS server query failed: %s", err) + } + + err = decodeBody(httpResponse, nil) + if err != nil { + return "", fmt.Errorf("SAML - error decoding body: %s", err) + } + + // httpResponse.Location() returns an error if no 'Location' header is present + adfsEndpoint, err := httpResponse.Location() + if err != nil { + return "", fmt.Errorf("SAML GET request for '%s' did not return HTTP redirect. "+ + "Is SAML configured? Got error: %s", loginURL, err) + } + + authEndPoint := adfsEndpoint.Scheme + "://" + adfsEndpoint.Host + "/adfs/services/trust/13/usernamemixed" + util.Logger.Printf("[DEBUG] SAML got IdP login endpoint: %s", authEndPoint) + + return authEndPoint, nil +} + +// getSamlEntityId attempts to load vCD hosted SAML metadata from URL: +// url.Scheme + "://" + url.Host + "/cloud/org/" + org + "/saml/metadata/alias/vcd" +// Returns an error if Entity ID is empty +// Sample response body can be found in saml_auth_unit_test.go +func getSamlEntityId(vcdCli *VCDClient, org string) (string, error) { + url := vcdCli.Client.VCDHREF + samlMetadataUrl := url.Scheme + "://" + url.Host + "/cloud/org/" + org + "/saml/metadata/alias/vcd" + + metadata := types.VcdSamlMetadata{} + errString := fmt.Sprintf("SAML - unable to load metadata from URL %s: %%s", samlMetadataUrl) + _, err := vcdCli.Client.ExecuteRequest(samlMetadataUrl, http.MethodGet, "", errString, nil, &metadata) + if err != nil { + return "", err + } + + samlEntityId := metadata.EntityID + util.Logger.Printf("[DEBUG] SAML got entity ID: %s", samlEntityId) + + if samlEntityId == "" { + return "", errors.New("SAML - got empty entity ID") + } + + return samlEntityId, nil +} + +// getSamlAuthToken generates a token request payload using function +// getSamlTokenRequestBody. This request is submitted to ADFS server endpoint +// "/adfs/services/trust/13/usernamemixed" and `RequestedSecurityTokenTxt` is expected in response +// Sample response body can be found in saml_auth_unit_test.go +func getSamlAuthToken(vcdCli *VCDClient, user, pass, samlEntityId, authEndpoint, org string) (string, error) { + requestBody := getSamlTokenRequestBody(user, pass, samlEntityId, authEndpoint) + samlTokenRequestBody := strings.NewReader(requestBody) + tokenRequestResponse := types.AdfsAuthResponseEnvelope{} + + // Post to ADFS endpoint "/adfs/services/trust/13/usernamemixed" + authEndpointUrl, err := url.Parse(authEndpoint) + if err != nil { + return "", fmt.Errorf("SAML - error parsing authentication endpoint %s: %s", authEndpoint, err) + } + req := vcdCli.Client.NewRequest(nil, http.MethodPost, *authEndpointUrl, samlTokenRequestBody) + req.Header.Add("Content-Type", types.SoapXML) + resp, err := vcdCli.Client.Http.Do(req) + resp, err = checkRespWithErrType(resp, err, &types.AdfsAuthErrorEnvelope{}) + if err != nil { + return "", fmt.Errorf("SAML - ADFS token request query failed for RPT ID ('%s'): %s", + samlEntityId, err) + } + + err = decodeBody(resp, &tokenRequestResponse) + if err != nil { + return "", fmt.Errorf("SAML - error decoding ADFS token request response: %s", err) + } + + tokenString := tokenRequestResponse.Body.RequestSecurityTokenResponseCollection.RequestSecurityTokenResponse.RequestedSecurityTokenTxt.Text + + return tokenString, nil +} + +// authorizeSignToken submits a SIGN token received from ADFS server and gets regular vCD +// "X-Vcloud-Authorization" token in exchange +// Sample response body can be found in saml_auth_unit_test.go +func authorizeSignToken(vcdCli *VCDClient, base64GzippedSignToken, org string) (string, error) { + url, err := url.Parse(vcdCli.Client.VCDHREF.Scheme + "://" + vcdCli.Client.VCDHREF.Host + "/api/sessions") + if err != nil { + return "", fmt.Errorf("SAML error - could not parse URL for posting SIGN token: %s", err) + } + + signHeader := http.Header{} + signHeader.Add("Authorization", `SIGN token="`+base64GzippedSignToken+`",org="`+org+`"`) + + req := vcdCli.Client.newRequest(nil, nil, http.MethodPost, *url, nil, vcdCli.Client.APIVersion, signHeader) + resp, err := checkResp(vcdCli.Client.Http.Do(req)) + if err != nil { + return "", fmt.Errorf("SAML - error submitting SIGN token for authentication to %s: %s", req.URL.String(), err) + } + err = decodeBody(resp, nil) + if err != nil { + return "", fmt.Errorf("SAML - error decoding body SIGN token auth response: %s", err) + } + + accessToken := resp.Header.Get("X-Vcloud-Authorization") + util.Logger.Printf("[DEBUG] SAML - setting access token for further requests") + return accessToken, nil +} + +// getSamlTokenRequestBody returns a SAML Token request body which is accepted by ADFS server +// endpoint "/adfs/services/trust/13/usernamemixed". +// The payload is not configured as a struct and unmarshalled because Go's unmarshalling changes +// structure so that ADFS does not accept the payload +func getSamlTokenRequestBody(user, password, samlEntityIdReference, adfsAuthEndpoint string) string { + return ` + + http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue + + http://www.w3.org/2005/08/addressing/anonymous + + ` + adfsAuthEndpoint + ` + + + ` + time.Now().Format(time.RFC3339) + ` + ` + time.Now().Add(1*time.Minute).Format(time.RFC3339) + ` + + + ` + user + ` + ` + password + ` + + + + + + + + ` + samlEntityIdReference + ` + + + 0 + http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer + + http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue + http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0 + + +` +} + +// gzipAndBase64Encode accepts a string, gzips it and encodes in base64 +func gzipAndBase64Encode(text string) (string, error) { + var gzipBuffer bytes.Buffer + gz := gzip.NewWriter(&gzipBuffer) + if _, err := gz.Write([]byte(text)); err != nil { + return "", fmt.Errorf("error writing to gzip buffer: %s", err) + } + if err := gz.Close(); err != nil { + return "", fmt.Errorf("error closing gzip buffer: %s", err) + } + base64GzippedToken := base64.StdEncoding.EncodeToString(gzipBuffer.Bytes()) + + return base64GzippedToken, nil +} diff --git a/govcd/saml_auth_test.go b/govcd/saml_auth_test.go new file mode 100644 index 000000000..8e80a75f2 --- /dev/null +++ b/govcd/saml_auth_test.go @@ -0,0 +1,71 @@ +// +build auth functional ALL + +/* + * Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + . "gopkg.in/check.v1" +) + +// Test_SamlAdfsAuth checks if SAML ADFS login works using WS-TRUST endpoint +// "/adfs/services/trust/13/usernamemixed". +// Credential variables must be specified in test configuration for it to work +// The steps of this test are: +// * Query object using test framework vCD connection +// * Create a new client with SAML authentication using specified org and query the same object +// using it to make sure access is granted +// * Compare results to ensure that it worked as it should +// +// Note. This test requires real environment setup to work. Unit testing is also available in +// `saml_auth_unit_test.go` +func (vcd *TestVCD) Test_SamlAdfsAuth(check *C) { + cfg := vcd.config + if cfg.Provider.SamlUser == "" || cfg.Provider.SamlPassword == "" || cfg.VCD.Org == "" { + check.Skip("Skipping test because no Org, SamlUser, SamlPassword and was specified") + } + + // Get vDC details using existing vCD client + org, err := vcd.client.GetOrgByName(cfg.VCD.Org) + check.Assert(err, IsNil) + + vdc, err := org.GetVDCByName(cfg.VCD.Vdc, true) + check.Assert(err, IsNil) + + // Get new vCD session and client using specifically SAML credentials + samlVcdCli := NewVCDClient(vcd.client.Client.VCDHREF, true, + WithSamlAdfs(true, cfg.Provider.SamlCustomRptId)) + err = samlVcdCli.Authenticate(cfg.Provider.SamlUser, cfg.Provider.SamlPassword, cfg.VCD.Org) + check.Assert(err, IsNil) + + samlOrg, err := vcd.client.GetOrgByName(cfg.VCD.Org) + check.Assert(err, IsNil) + + samlVdc, err := samlOrg.GetVDCByName(cfg.VCD.Vdc, true) + check.Assert(err, IsNil) + + check.Assert(samlVdc, DeepEquals, vdc) + + // If SamlCustomRptId was not specified - try to feed VCD entity ID manually (this is usually + // done automatically, but doing it to test this path is not broken) + if cfg.Provider.SamlCustomRptId == "" { + samlEntityId, err := getSamlEntityId(vcd.client, cfg.VCD.Org) + check.Assert(err, IsNil) + + samlCustomRptVcdCli := NewVCDClient(vcd.client.Client.VCDHREF, true, + WithSamlAdfs(true, samlEntityId)) + err = samlCustomRptVcdCli.Authenticate(cfg.Provider.SamlUser, cfg.Provider.SamlPassword, cfg.VCD.Org) + check.Assert(err, IsNil) + + samlCustomRptOrg, err := vcd.client.GetOrgByName(cfg.VCD.Org) + check.Assert(err, IsNil) + + samlCustomRptVdc, err := samlCustomRptOrg.GetVDCByName(cfg.VCD.Vdc, true) + check.Assert(err, IsNil) + + check.Assert(samlCustomRptVdc, DeepEquals, samlVdc) + } + +} diff --git a/govcd/saml_auth_unit_test.go b/govcd/saml_auth_unit_test.go new file mode 100644 index 000000000..d40a9f002 --- /dev/null +++ b/govcd/saml_auth_unit_test.go @@ -0,0 +1,194 @@ +// +build unit ALL + +package govcd + +import ( + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "net/url" + "os" + "regexp" + "testing" +) + +// testVcdMockAuthToken is the expected vcdCli.Client.VCDToken value after `Authentication()` +// function passes mock SAML authentication process +const testVcdMockAuthToken = "e3b02b30b8ff4e87ac38db785b0172b5" + +// samlMockServer struct allows to attach HTTP handlers to use additional variables (like +// *testing.T) inside those handlers +type samlMockServer struct { + t *testing.T +} + +// TestSamlAdfsAuthenticate is a unit test using mock vCD and ADFS server endpoint to follow +// complete SAML auth flow. The `testVcdMockAuthToken` is expected as an outcome token because +// mock servers return static responses. +// +// Note. A test using real infrastructure is defined in `saml_auth_test.go` +func TestSamlAdfsAuthenticate(t *testing.T) { + // Spawn mock ADFS server + adfsServer := testSpawnAdfsServer(t) + adfsServerHost := adfsServer.URL + defer adfsServer.Close() + + // Spawn mock vCD instance just enough to cover login details + vcdServer := spawnVcdServer(t, adfsServerHost, "my-org") + vcdServerHost := vcdServer.URL + defer vcdServer.Close() + + // Setup vCD client pointing to mock API + vcdUrl, err := url.Parse(vcdServerHost + "/api") + if err != nil { + t.Errorf("got errors: %s", err) + } + vcdCli := NewVCDClient(*vcdUrl, true, WithSamlAdfs(true, "")) + err = vcdCli.Authenticate("fakeUser", "fakePass", "my-org") + if err != nil { + t.Errorf("got errors: %s", err) + } + + // After authentication + if vcdCli.Client.VCDToken != testVcdMockAuthToken { + t.Errorf("received token does not match specified one") + } +} + +// spawnVcdServer establishes a mock vCD server with endpoints required to satisfy authentication +func spawnVcdServer(t *testing.T, adfsServerHost, org string) *httptest.Server { + mockServer := samlMockServer{t} + mux := http.NewServeMux() + mux.HandleFunc("/cloud/org/"+org+"/saml/metadata/alias/vcd", mockServer.vCDSamlMetadataHandler) + mux.HandleFunc("/login/"+org+"/saml/login/alias/vcd", mockServer.getVcdAdfsRedirectHandler(adfsServerHost)) + mux.HandleFunc("/api/sessions", mockServer.vCDLoginHandler) + mux.HandleFunc("/api/versions", mockServer.vCDApiVersionHandler) + mux.HandleFunc("/api/org", mockServer.vCDApiOrgHandler) + + server := httptest.NewTLSServer(mux) + if os.Getenv("GOVCD_DEBUG") != "" { + log.Printf("vCD mock server now listening on %s...\n", server.URL) + } + return server +} + +// vcdLoginHandler serves mock "/api/sessions" +func (mockServer *samlMockServer) vCDLoginHandler(w http.ResponseWriter, r *http.Request) { + // We expect POST method and not anything else + if r.Method != http.MethodPost { + w.WriteHeader(500) + return + } + + expectedHeader := goldenString(mockServer.t, "REQ_api_sessions", "", false) + if r.Header.Get("Authorization") != expectedHeader { + w.WriteHeader(500) + return + } + + headers := w.Header() + headers.Add("X-Vcloud-Authorization", testVcdMockAuthToken) + + resp := goldenBytes(mockServer.t, "RESP_api_sessions", []byte{}, false) + _, err := w.Write(resp) + if err != nil { + panic(err) + } +} + +// vCDApiVersionHandler server mock "/api/versions" +func (mockServer *samlMockServer) vCDApiVersionHandler(w http.ResponseWriter, r *http.Request) { + // We expect GET method and not anything else + if r.Method != http.MethodGet { + w.WriteHeader(500) + return + } + + resp := goldenBytes(mockServer.t, "RESP_api_versions", []byte{}, false) + _, err := w.Write(resp) + if err != nil { + panic(err) + } +} + +// vCDApiOrgHandler serves mock "/api/org" +func (mockServer *samlMockServer) vCDApiOrgHandler(w http.ResponseWriter, r *http.Request) { + // We expect GET method and not anything else + if r.Method != http.MethodGet { + w.WriteHeader(500) + return + } + + resp := goldenBytes(mockServer.t, "RESP_api_org", []byte{}, false) + _, err := w.Write(resp) + if err != nil { + panic(err) + } +} + +// vCDSamlMetadataHandler serves mock "/cloud/org/" + org + "/saml/metadata/alias/vcd" +func (mockServer *samlMockServer) vCDSamlMetadataHandler(w http.ResponseWriter, r *http.Request) { + re := goldenBytes(mockServer.t, "RESP_cloud_org_my-org_saml_metadata_alias_vcd", []byte{}, false) + _, _ = w.Write(re) +} +func (mockServer *samlMockServer) getVcdAdfsRedirectHandler(adfsServerHost string) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(500) + return + } + headers := w.Header() + locationHeaderPayload := goldenString(mockServer.t, "RESP_HEADER_login_my-org_saml_login_alias_vcd", "", false) + headers.Add("Location", adfsServerHost+locationHeaderPayload) + + w.WriteHeader(http.StatusFound) + } +} + +// testSpawnAdfsServer spawns mock HTTPS server to server ADFS auth endpoint +// "/adfs/services/trust/13/usernamemixed" +func testSpawnAdfsServer(t *testing.T) *httptest.Server { + mockServer := samlMockServer{t} + mux := http.NewServeMux() + mux.HandleFunc("/adfs/services/trust/13/usernamemixed", mockServer.adfsSamlAuthHandler) + server := httptest.NewTLSServer(mux) + if os.Getenv("GOVCD_DEBUG") != "" { + log.Printf("ADFS mock server now listening on %s...\n", server.URL) + } + return server +} + +// adfsSamlAuthHandler checks that POST request with expected payload is sent and serves response +// sample ADFS response +func (mockServer *samlMockServer) adfsSamlAuthHandler(w http.ResponseWriter, r *http.Request) { + // it must be POST method and not anything else + if r.Method != http.MethodPost { + w.WriteHeader(500) + return + } + + // Replace known dynamic strings to 'REPLACED' string + gotBody, _ := ioutil.ReadAll(r.Body) + gotBodyString := string(gotBody) + re := regexp.MustCompile(`().*()`) + gotBodyString = re.ReplaceAllString(gotBodyString, `${1}REPLACED${2}`) + + re2 := regexp.MustCompile(`().*()`) + gotBodyString = re2.ReplaceAllString(gotBodyString, `${1}REPLACED${2}`) + + re3 := regexp.MustCompile(`().*()`) + gotBodyString = re3.ReplaceAllString(gotBodyString, `${1}REPLACED${2}`) + + expectedBody := goldenString(mockServer.t, "REQ_adfs_services_trust_13_usernamemixed", gotBodyString, false) + if gotBodyString != expectedBody { + w.WriteHeader(500) + return + } + + resp := goldenBytes(mockServer.t, "RESP_adfs_services_trust_13_usernamemixed", []byte(""), false) + _, err := w.Write(resp) + if err != nil { + panic(err) + } +} diff --git a/govcd/sample_govcd_test_config.json b/govcd/sample_govcd_test_config.json index 38f97b1ba..8c6703fc7 100644 --- a/govcd/sample_govcd_test_config.json +++ b/govcd/sample_govcd_test_config.json @@ -1,70 +1,83 @@ { - "comment": "see sample_govcd_test_config.yaml for fields description", - "provider": { - "user": "someuser", - "password": "somepassword", + "comment": "see sample_govcd_test_config.yaml for fields description", + "provider": { + "user": "someuser", + "password": "somepassword", "//": "If token is provided, username and password are ignored", "token": "an_auth_token", - "url": "https://11.111.1.111/api", - "sysOrg": "System", - "//": "(Optional) In some cases the vCloud Director SDK must wait. It can be", - "//": "customized using the below var. If it is not set SDK assumes a default value.", - "maxRetryTimeout": 60, - "httpTimeout" : 600 - }, - "vcd": { - "org": "myorg", - "vdc": "myvdc", - "provider_vdc": { - "name": "myprovidervdc", - "storage_profile": "mystorageprofile", - "network_pool": "mynetworkpool" - }, - "catalog": { - "name": "mycat", - "//": "One item in the catalog. It will be used to compose test vApps. Some tests", - "//": "rely on it being Photon OS. It is is not Photon OS - the tests will be skipped", - "catalogItem": "myitem", - "description": "my cat for loading", - "catalogItemDescription": "my item to create vapps" - }, - "network": { - "network1": "mynet", - "network2": "mynet2" - }, - "storageProfile": { - "storageProfile1": "Development", - "storageProfile2": "*" - }, - "edgeGateway": "myedgegw", - "externalIp": "10.150.10.10", - "externalNetmask": "255.255.224.0", - "internalIp": "192.168.1.10", - "internalNetmask": "255.255.255.0", - "externalNetwork": "myexternalnet", + "//": "If useSamlAdfs is true - client will try to authenticate against ADFS using SAML.", + "useSamlAdfs": false, + "//": "customAdfsRptId allows to specify custom Relaying Party Trust Identifier. By default", + "//": "client will use vCD SAML Entity ID", + "customAdfsRptId": "", + + "//": "The 3 fields below allow to set SAML credentials for tests that specifically use it.", + "//": "May be useful when local user credentials are used by default.", + "//": "Such credentials will authenticate to Org specified in vcd.org parameter.", + "samlUser": "", + "samlPassword": "", + "samlCustomRptId": "", + + "url": "https://11.111.1.111/api", + "sysOrg": "System", + "//": "(Optional) In some cases the vCloud Director SDK must wait. It can be", + "//": "customized using the below var. If it is not set SDK assumes a default value.", + "maxRetryTimeout": 60, + "httpTimeout": 600 + }, + "vcd": { + "org": "myorg", + "vdc": "myvdc", + "provider_vdc": { + "name": "myprovidervdc", + "storage_profile": "mystorageprofile", + "network_pool": "mynetworkpool" + }, + "catalog": { + "name": "mycat", + "//": "One item in the catalog. It will be used to compose test vApps. Some tests", + "//": "rely on it being Photon OS. It is is not Photon OS - the tests will be skipped", + "catalogItem": "myitem", + "description": "my cat for loading", + "catalogItemDescription": "my item to create vapps" + }, + "network": { + "network1": "mynet", + "network2": "mynet2" + }, + "storageProfile": { + "storageProfile1": "Development", + "storageProfile2": "*" + }, + "edgeGateway": "myedgegw", + "externalIp": "10.150.10.10", + "externalNetmask": "255.255.224.0", + "internalIp": "192.168.1.10", + "internalNetmask": "255.255.255.0", + "externalNetwork": "myexternalnet", "//": "A port group name for creating an external network", - "externalNetworkPortGroup": "ForTestingPG", + "externalNetworkPortGroup": "ForTestingPG", "externalNetworkPortGroupType": "NETWORK", - "vimServer": "vc9", - "disk": { - "size": 1048576, - "sizeForUpdate": 1048576 - } - }, - "logging": { - "enabled": true, - "logFileName": "go-vcloud-director.log", - "logHttpRequests": true, - "skipResponseTags": "SupportedVersions,VAppTemplate", - "apiLogFunctions": "FindVAppByName", - "logHttpResponses": true - }, + "vimServer": "vc9", + "disk": { + "size": 1048576, + "sizeForUpdate": 1048576 + } + }, + "logging": { + "enabled": true, + "logFileName": "go-vcloud-director.log", + "logHttpRequests": true, + "skipResponseTags": "SupportedVersions,VAppTemplate", + "apiLogFunctions": "FindVAppByName", + "logHttpResponses": true + }, "ova": { "ovaPath": "../test-resources/test_vapp_template.ova", "ovaChunkedPath": "../test-resources/template_with_custom_chunk_size.ova" }, "media": { "mediaPath": "../test-resources/test.iso", - "mediaName": "uploadedMediaName", + "mediaName": "uploadedMediaName" } } diff --git a/govcd/sample_govcd_test_config.yaml b/govcd/sample_govcd_test_config.yaml index 4a8349f23..3241e70bf 100644 --- a/govcd/sample_govcd_test_config.yaml +++ b/govcd/sample_govcd_test_config.yaml @@ -13,6 +13,18 @@ provider: password: somepassword # If token is provided, username and password are ignored token: an_auth_token + # If useSamlAdfs is true - client will try to authenticate against ADFS using SAML. + useSamlAdfs: false + # customAdfsRptId allows to specify custom Relaying Party Trust Identifier. By default client + # will use vCD SAML Entity ID + # customAdfsRptId: "" + + # The 3 fields below allow to set SAML credentials for tests that specifically use it. + # May be useful when local user credentials are used by default. Such credentials will + # authenticate to Org specified in vcd.org parameter. + # samlUser: test@test-forest.net + # samlPassword: XXX + # samlCustomRptId: "my-optional-custom-relaying-party-trust-id" # # The vCD address, in the format https://vCD_IP/api # or https://vCD_host_name/api diff --git a/samples/discover.go b/samples/discover/discover.go similarity index 100% rename from samples/discover.go rename to samples/discover/discover.go diff --git a/samples/sample_config.json b/samples/discover/sample_config.json similarity index 100% rename from samples/sample_config.json rename to samples/discover/sample_config.json diff --git a/samples/sample_config.yaml b/samples/discover/sample_config.yaml similarity index 100% rename from samples/sample_config.yaml rename to samples/discover/sample_config.yaml diff --git a/samples/saml_auth_adfs/README.md b/samples/saml_auth_adfs/README.md new file mode 100644 index 000000000..c4f6b8217 --- /dev/null +++ b/samples/saml_auth_adfs/README.md @@ -0,0 +1,26 @@ +# SAML authentication with ADFS as IdP +This is an example how to use Active Directory Federation Services as SAML IdP for vCD. +`main()` function has an example how to setup vCD client with SAML auth. On successful login it will +list Edge Gateways. +To run this command please supply parameters as per below example: +``` +go build -o auth +./auth --username test@test-forest.net --password my-password --org my-org --endpoint https://_YOUR_HOSTNAME_/api +``` + +Results should look similar to: +``` +Found 1 Edge Gateways +my-edge-gw +``` + + +## More details +Main trick for making SAML with ADFS work is to use configuration option function +`WithSamlAdfs(useSaml bool, customAdfsRptId string)` in `govcd.NewVCDClient()`. +At the moment ADFS WS-TRUST endpoint "/adfs/services/trust/13/usernamemixed" is the only one +supported and it must be enabled on ADFS server to work properly. + +## Troubleshooting +Environment variable `GOVCD_LOG=1` can be used to enable API call logging. It should log all API +calls (including the ones to ADFS server) with obfuscated credentials to aid troubleshooting. \ No newline at end of file diff --git a/samples/saml_auth_adfs/main.go b/samples/saml_auth_adfs/main.go new file mode 100644 index 000000000..249b39072 --- /dev/null +++ b/samples/saml_auth_adfs/main.go @@ -0,0 +1,77 @@ +/* + * Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ +package main + +import ( + "flag" + "fmt" + "net/url" + "os" + + "github.com/vmware/go-vcloud-director/v2/govcd" +) + +var ( + username string + password string + org string + apiEndpoint string + customAdfsRptId string +) + +func init() { + flag.StringVar(&username, "username", "", "Username") + flag.StringVar(&password, "password", "", "Password") + flag.StringVar(&org, "org", "System", "Org name. Default is 'System'") + flag.StringVar(&apiEndpoint, "endpoint", "", "API endpoint (e.g. 'https://hostname/api')") + flag.StringVar(&customAdfsRptId, "rpt", "", "Custom Relaying party trust ID. Default is vCD SAML Entity ID") +} + +// Usage: +// # go build -o auth +// # ./auth --username test@test-forest.net --password asdasd --org my-org --endpoint https://192.168.1.160/api +func main() { + flag.Parse() + + if username == "" || password == "" || org == "" || apiEndpoint == "" { + fmt.Printf("At least 'username', 'password', 'org' and 'endpoint' must be specified\n") + os.Exit(1) + } + + vcdURL, err := url.Parse(apiEndpoint) + if err != nil { + fmt.Printf("Error parsing supplied endpoint %s: %s", apiEndpoint, err) + os.Exit(2) + } + + // Create VCD client allowing insecure TLS connection and using SAML auth. + // WithSamlAdfs() allows SAML authentication when vCD uses Microsoft Active Directory + // Federation Services (ADFS) as SAML IdP. The code below allows to authenticate ADFS using + // WS-TRUST endpoint "/adfs/services/trust/13/usernamemixed" + // Input parameters: + // user - username for authentication against ADFS server (e.g. 'test@test-forest.net' or 'test-forest.net\test') + // password - password for authentication against ADFS server + // org - Org to authenticate to. Can be 'System'. + // customAdfsRptId - override relaying party trust ID. If it is empty - vCD Entity ID will be used + // as Relaying Party Trust ID. + vcdCli := govcd.NewVCDClient(*vcdURL, true, govcd.WithSamlAdfs(true, customAdfsRptId)) + err = vcdCli.Authenticate(username, password, org) + if err != nil { + + fmt.Println(err) + os.Exit(3) + } + + // To prove authentication worked - just fetch all edge gateways and dump them on the screen + edgeGatewayResults, err := vcdCli.Query(map[string]string{"type": "edgeGateway"}) + if err != nil { + fmt.Printf("Error retrieving Edge Gateways: %s\n", err) + os.Exit(4) + } + + fmt.Printf("Found %d Edge Gateways\n", len(edgeGatewayResults.Results.EdgeGatewayRecord)) + for _, v := range edgeGatewayResults.Results.EdgeGatewayRecord { + fmt.Println(v.Name) + } +} diff --git a/test-resources/golden/TestSamlAdfsAuthenticate_REQ_adfs_services_trust_13_usernamemixed.golden b/test-resources/golden/TestSamlAdfsAuthenticate_REQ_adfs_services_trust_13_usernamemixed.golden new file mode 100644 index 000000000..b2cacca69 --- /dev/null +++ b/test-resources/golden/TestSamlAdfsAuthenticate_REQ_adfs_services_trust_13_usernamemixed.golden @@ -0,0 +1,37 @@ + + + http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue + + http://www.w3.org/2005/08/addressing/anonymous + + REPLACED + + + REPLACED + REPLACED + + + fakeUser + fakePass + + + + + + + + https://192.168.1.109/cloud/org/my-org/saml/metadata/alias/vcd + + + 0 + http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer + + http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue + http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0 + + + \ No newline at end of file diff --git a/test-resources/golden/TestSamlAdfsAuthenticate_REQ_api_sessions.golden b/test-resources/golden/TestSamlAdfsAuthenticate_REQ_api_sessions.golden new file mode 100644 index 000000000..f0853ce94 --- /dev/null +++ b/test-resources/golden/TestSamlAdfsAuthenticate_REQ_api_sessions.golden @@ -0,0 +1 @@ +SIGN token="H4sIAAAAAAAA/5xTTY+bMBD9K4jeKiV20l20sRxLEdnDaru5bFX16jUTsIRtZJsS+usrPhdCD6jcPPZ7b95jhj5rYevCQ3JyDqyXRgc3lWt3DEurieFOOqK5Ake8IO+nt+9kv8WED49DRm+gBRlpztzz4EddwDHMvC8IQlVVbatvW2NTtMd4h/ADuqkctPjynIMC7cNOkTREK1B3ktLoN/CZSYJTnhorfaZWkHBw+8doIz5EiBh9hfpFX83g/J9ojPChQSdOpk0P8On5FerewhrXU+z/NW8d3xgOxUal110RMnqWKTi/kmlmxGV81ySAli2NsTCaOPLrER/af9sZTdbG1ENfnCvBvoOVPL8rXrgCFl+Ov+PclElwlhaENzYwNuVa/uHtTMbNuF2l4B4oWsIHxk7gUqoPsGyHowhHD0+HpxEyu78jGppDE7eMojEEILEsMrBdfTj95HkJ7Gv7NSlOq5/nnms+M1PydqKnbyeFucTiYlbqhZY7ySharjr7GwAA///dRZE6/wMAAA==",org="my-org" \ No newline at end of file diff --git a/test-resources/golden/TestSamlAdfsAuthenticate_RESP_HEADER_login_my-org_saml_login_alias_vcd.golden b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_HEADER_login_my-org_saml_login_alias_vcd.golden new file mode 100644 index 000000000..f76f0b95f --- /dev/null +++ b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_HEADER_login_my-org_saml_login_alias_vcd.golden @@ -0,0 +1 @@ +/adfs/ls/?SAMLRequest=lZJBT8MwDIXv%2FIoq9zZpt3UjWjsNJsQkEBMtHLhlqdsFtcmI0wL%2Fnm5lYlyQOFmW7M9P73m%2B%2BGhqrwOLyuiEhAEjHmhpCqWrhDzlN%2F6MLNKLOYqmjvZ82bqdfoS3FtB5S0Swrt%2B7NhrbBmwGtlMSnh7vErJzbo%2Bc0vAyCsJ4FoRByC6prE1bUGMr2nz6h3Lg0ix7oKJWAmknC%2BKterjSwh0VnTjvSvsxq2IWaybKKnD9kF8a25dAg6OiKJHWSIl3Y6yEo9CElKJGIN56lRBRFmxaxFs2epXAKgC5VbvxbBqqSdUfXeNGIKoOfpYQW1hrdEK7hEQsYj4b%2B9E0ZzFnEz4ZBaMJeyHexhpnpKmvlB5ca63mRqBCrkUDyJ3k2fL%2BjkcB49thCPltnm%2F8zUOWE%2B%2F55H50cL%2FPQyMf%2FP6btf8%2BTNIhHn5UbM8JfwPEKUCS%2FieuBpwohBM%2Fmc3puYD0u%2F39LukX&RelayState=aHR0cHM6Ly8xOTIuMTY4LjEuMTA5L3RlbmFudC9teS1vcmc%3D&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=EXL0%2BO1aLhXKAMCKTaqduTW5tWsg94ANZ8hC60MtT4kwitvFUQ7VsQT3qtPj8MFbz0tvN9lX79R0yRwMPilP0zb50uuaVpaJy7qUpHiPyBa5HHA2xG2beyNjlUmC%2BOJSBjfx3k6YMkEzRqfKY6KD%2BKxSMsnSJuazBrWdzihoe4dMgWDS5Dpl2YOC0Ychc1huqedCD2WlE4QRfmtXq0oXlydPVSIYCtHXF1pwYq1j9%2B2q0oK9%2BEEoha0mCMWD74t5hei0kVJldFTcSXx0kgqPi6Rih7aP8%2BlKxnUFu4%2Bo7u9n9Oh8SLV3Tz%2Ba9A9cq4OxdCzyQCOwPRYs3GCb8iIB8g%3D%3D \ No newline at end of file diff --git a/test-resources/golden/TestSamlAdfsAuthenticate_RESP_adfs_services_trust_13_usernamemixed.golden b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_adfs_services_trust_13_usernamemixed.golden new file mode 100644 index 000000000..72237ca30 --- /dev/null +++ b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_adfs_services_trust_13_usernamemixed.golden @@ -0,0 +1 @@ +http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal2020-04-27T06:05:53.281Z2020-04-27T06:10:53.281Z2020-04-27T06:05:53.281Z2020-04-27T07:05:53.281Zhttps://192.168.1.109/cloud/org/my-org/saml/metadata/alias/vcdCN=vCloud Director organization Certificate1066064898************E-Mail AddressThe e-mail address of the usertest@test-forest.netDomain UsersVCDUsers_83958fe2-1b12-4706-a6e5-af2143616c23_83958fe2-1b12-4706-a6e5-af2143616c23http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issuehttp://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer \ No newline at end of file diff --git a/test-resources/golden/TestSamlAdfsAuthenticate_RESP_api_org.golden b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_api_org.golden new file mode 100644 index 000000000..54afd6591 --- /dev/null +++ b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_api_org.golden @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test-resources/golden/TestSamlAdfsAuthenticate_RESP_api_sessions.golden b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_api_sessions.golden new file mode 100644 index 000000000..04c156778 --- /dev/null +++ b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_api_sessions.golden @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + c196c6f0-5c31-4929-a626-b29b2c9ff5ab@cb33a646-6652-4628-95b0-24bd981783b6 + 192.168.1.109 + my-org + https://192.168.1.109 + https://192.168.1.109 + my-org + + + \ No newline at end of file diff --git a/test-resources/golden/TestSamlAdfsAuthenticate_RESP_api_versions.golden b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_api_versions.golden new file mode 100644 index 000000000..3539e9ffb --- /dev/null +++ b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_api_versions.golden @@ -0,0 +1,1045 @@ + + + + 20.0 + https://192.168.1.109/api/sessions + + + 21.0 + https://192.168.1.109/api/sessions + + + 22.0 + https://192.168.1.109/api/sessions + + + 23.0 + https://192.168.1.109/api/sessions + + + 24.0 + https://192.168.1.109/api/sessions + + + 25.0 + https://192.168.1.109/api/sessions + + + 26.0 + https://192.168.1.109/api/sessions + + + 27.0 + https://192.168.1.109/api/sessions + + + 28.0 + https://192.168.1.109/api/sessions + + + 29.0 + https://192.168.1.109/api/sessions + + + 30.0 + https://192.168.1.109/api/sessions + + + 31.0 + https://192.168.1.109/api/sessions + + + 5.5 + https://192.168.1.109/api/sessions + + application/vnd.vmware.vcloud.error+xml + ErrorType + http://192.168.1.109/api/v1.5/schema/common.xsd + + + application/vnd.vmware.vcloud.controlAccess+xml + ControlAccessParamsType + http://192.168.1.109/api/v1.5/schema/common.xsd + + + application/vnd.vmware.vcloud.owner+xml + OwnerType + http://192.168.1.109/api/v1.5/schema/common.xsd + + + application/vnd.vmware.vcloud.query.references+xml + ReferencesType + http://192.168.1.109/api/v1.5/schema/common.xsd + + + application/vnd.vmware.admin.fileUploadParams+xml + FileUploadParamsType + http://192.168.1.109/api/v1.5/schema/common.xsd + + + application/vnd.vmware.vcloud.fileUploadSocket+xml + FileUploadSocketType + http://192.168.1.109/api/v1.5/schema/common.xsd + + + application/vnd.vmware.vcloud.apiextensibility+xml + ApiExtensibilityType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.vcloud.service+xml + ServiceType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.vcloud.apidefinition+xml + ApiDefinitionType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.vcloud.filedescriptor+xml + FileDescriptorType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.vcloud.media+xml + MediaType + http://192.168.1.109/api/v1.5/schema/media.xsd + + + application/vnd.vmware.vcloud.cloneMediaParams+xml + CloneMediaParamsType + http://192.168.1.109/api/v1.5/schema/media.xsd + + + application/vnd.vmware.vcloud.vms+xml + VmsType + http://192.168.1.109/api/v1.5/schema/vms.xsd + + + application/vnd.vmware.vcloud.supportedSystemsInfo+xml + SupportedOperatingSystemsInfoType + http://192.168.1.109/api/v1.5/schema/vms.xsd + + + application/vnd.vmware.vcloud.catalog+xml + CatalogType + http://192.168.1.109/api/v1.5/schema/catalog.xsd + + + application/vnd.vmware.admin.publishCatalogParams+xml + PublishCatalogParamsType + http://192.168.1.109/api/v1.5/schema/catalog.xsd + + + application/vnd.vmware.vcloud.task+xml + TaskType + http://192.168.1.109/api/v1.5/schema/task.xsd + + + application/vnd.vmware.admin.taskOperationList+xml + TaskOperationListType + http://192.168.1.109/api/v1.5/schema/task.xsd + + + application/vnd.vmware.vcloud.vAppTemplate+xml + VAppTemplateType + http://192.168.1.109/api/v1.5/schema/vAppTemplate.xsd + + + application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml + UploadVAppTemplateParamsType + http://192.168.1.109/api/v1.5/schema/vAppTemplate.xsd + + + application/vnd.vmware.vcloud.cloneVAppTemplateParams+xml + CloneVAppTemplateParamsType + http://192.168.1.109/api/v1.5/schema/vAppTemplate.xsd + + + application/vnd.vmware.vcloud.customizationSection+xml + CustomizationSectionType + http://192.168.1.109/api/v1.5/schema/vAppTemplate.xsd + + + application/vnd.vmware.admin.vmwNetworkPool.services+xml + VendorServicesType + http://192.168.1.109/api/v1.5/schema/vendorServices.xsd + + + application/vnd.vmware.vcloud.entity+xml + EntityType + http://192.168.1.109/api/v1.5/schema/entity.xsd + + + application/vnd.vmware.vcloud.entity.reference+xml + EntityReferenceType + http://192.168.1.109/api/v1.5/schema/entity.xsd + + + application/vnd.vmware.vcloud.network+xml + NetworkType + http://192.168.1.109/api/v1.5/schema/network.xsd + + + application/vnd.vmware.vcloud.orgNetwork+xml + OrgNetworkType + http://192.168.1.109/api/v1.5/schema/network.xsd + + + application/vnd.vmware.vcloud.vAppNetwork+xml + VAppNetworkType + http://192.168.1.109/api/v1.5/schema/network.xsd + + + application/vnd.vmware.vcloud.allocatedNetworkAddress+xml + AllocatedIpAddressesType + http://192.168.1.109/api/v1.5/schema/network.xsd + + + application/vnd.vmware.vcloud.subAllocations+xml + SubAllocationsType + http://192.168.1.109/api/v1.5/schema/network.xsd + + + application/vnd.vmware.vcloud.orgVdcNetwork+xml + OrgVdcNetworkType + http://192.168.1.109/api/v1.5/schema/network.xsd + + + application/vnd.vmware.admin.edgeGateway+xml + GatewayType + http://192.168.1.109/api/v1.5/schema/network.xsd + + + application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml + GatewayFeaturesType + http://192.168.1.109/api/v1.5/schema/network.xsd + + + application/vnd.vmware.vcloud.session+xml + SessionType + http://192.168.1.109/api/v1.5/schema/session.xsd + + + application/vnd.vmware.vcloud.disk+xml + DiskType + http://192.168.1.109/api/v1.5/schema/disk.xsd + + + application/vnd.vmware.vcloud.diskCreateParams+xml + DiskCreateParamsType + http://192.168.1.109/api/v1.5/schema/disk.xsd + + + application/vnd.vmware.vcloud.diskAttachOrDetachParams+xml + DiskAttachOrDetachParamsType + http://192.168.1.109/api/v1.5/schema/disk.xsd + + + application/vnd.vmware.vcloud.vdc+xml + VdcType + http://192.168.1.109/api/v1.5/schema/vdc.xsd + + + application/vnd.vmware.vcloud.screenTicket+xml + ScreenTicketType + http://192.168.1.109/api/v1.5/schema/screenTicket.xsd + + + application/vnd.vmware.vcloud.productSections+xml + ProductSectionListType + http://192.168.1.109/api/v1.5/schema/productSectionList.xsd + + + application/vnd.vmware.vcloud.catalogItem+xml + CatalogItemType + http://192.168.1.109/api/v1.5/schema/catalogItem.xsd + + + application/vnd.vmware.vcloud.tasksList+xml + TasksListType + http://192.168.1.109/api/v1.5/schema/tasksList.xsd + + + application/vnd.vmware.vcloud.orgList+xml + OrgListType + http://192.168.1.109/api/v1.5/schema/organizationList.xsd + + + application/vnd.vmware.vcloud.org+xml + OrgType + http://192.168.1.109/api/v1.5/schema/organization.xsd + + + application/vnd.vmware.vcloud.vm+xml + VmType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.vmCapabilitiesSection+xml + VmCapabilitiesType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.vApp+xml + VAppType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.rasdItemsList+xml + RasdItemsListType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.networkConfigSection+xml + NetworkConfigSectionType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.leaseSettingsSection+xml + LeaseSettingsSectionType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.networkConnectionSection+xml + NetworkConnectionSectionType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.runtimeInfoSection+xml + RuntimeInfoSectionType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.guestCustomizationSection+xml + GuestCustomizationSectionType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.snapshot+xml + SnapshotType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.snapshotSection+xml + SnapshotSectionType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.composeVAppParams+xml + ComposeVAppParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.recomposeVAppParams+xml + RecomposeVAppParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.registerVAppParams+xml + RegisterVAppParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml + InstantiateVAppTemplateParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.instantiateOvfParams+xml + InstantiateOvfParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.cloneVAppParams+xml + CloneVAppParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.deployVAppParams+xml + DeployVAppParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.undeployVAppParams+xml + UndeployVAppParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.mediaInsertOrEjectParams+xml + MediaInsertOrEjectParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.captureVAppParams+xml + CaptureVAppParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.vmPendingQuestion+xml + VmPendingQuestionType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.vmPendingAnswer+xml + VmQuestionAnswerType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.relocateVmParams+xml + RelocateParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.createSnapshotParams+xml + CreateSnapshotParamsType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vm.complianceResult+xml + ComplianceResultType + http://192.168.1.109/api/v1.5/schema/vApp.xsd + + + application/vnd.vmware.vcloud.vdcStorageProfile+xml + VdcStorageProfileType + http://192.168.1.109/api/v1.5/schema/vdcStorageProfile.xsd + + + application/vnd.vmware.admin.certificateUpdateParams+xml + CertificateUpdateParamsType + http://192.168.1.109/api/v1.5/schema/upload.xsd + + + application/vnd.vmware.admin.certificateUploadSocketType+xml + CertificateUploadSocketType + http://192.168.1.109/api/v1.5/schema/upload.xsd + + + application/vnd.vmware.admin.keystoreUpdateParams+xml + KeystoreUpdateParamsType + http://192.168.1.109/api/v1.5/schema/upload.xsd + + + application/vnd.vmware.admin.keystoreUploadSocketType+xml + KeystoreUploadSocketType + http://192.168.1.109/api/v1.5/schema/upload.xsd + + + application/vnd.vmware.admin.sspiKeytabUpdateParams+xml + SspiKeytabUpdateParamsType + http://192.168.1.109/api/v1.5/schema/upload.xsd + + + application/vnd.vmware.admin.sspiKeytabUploadSocketType+xml + SspiKeytabUploadSocketType + http://192.168.1.109/api/v1.5/schema/upload.xsd + + + application/vnd.vmware.admin.trustStoreUpdateParams+xml + TrustStoreUpdateParamsType + http://192.168.1.109/api/v1.5/schema/upload.xsd + + + application/vnd.vmware.admin.trustStoreUploadSocketType+xml + TrustStoreUploadSocketType + http://192.168.1.109/api/v1.5/schema/upload.xsd + + + application/vnd.vmware.admin.event+xml + EventType + http://192.168.1.109/api/v1.5/schema/event.xsd + + + application/vnd.vmware.admin.providervdc+xml + ProviderVdcType + http://192.168.1.109/api/v1.5/schema/providerVdc.xsd + + + application/vnd.vmware.admin.createVdcParams+xml + CreateVdcParamsType + http://192.168.1.109/api/v1.5/schema/providerVdc.xsd + + + application/vnd.vmware.admin.vdc+xml + AdminVdcType + http://192.168.1.109/api/v1.5/schema/providerVdc.xsd + + + application/vnd.vmware.admin.vdcReferences+xml + VdcReferencesType + http://192.168.1.109/api/v1.5/schema/providerVdc.xsd + + + application/vnd.vmware.admin.pvdcStorageProfile+xml + ProviderVdcStorageProfileType + http://192.168.1.109/api/v1.5/schema/providerVdc.xsd + + + application/vnd.vmware.vcloud.vdcStorageProfileParams+xml + VdcStorageProfileParamsType + http://192.168.1.109/api/v1.5/schema/providerVdc.xsd + + + application/vnd.vmware.admin.vdcStorageProfile+xml + AdminVdcStorageProfileType + http://192.168.1.109/api/v1.5/schema/providerVdc.xsd + + + application/vnd.vmware.admin.updateVdcStorageProfiles+xml + UpdateVdcStorageProfilesType + http://192.168.1.109/api/v1.5/schema/providerVdc.xsd + + + application/vnd.vmware.admin.user+xml + UserType + http://192.168.1.109/api/v1.5/schema/user.xsd + + + application/vnd.vmware.admin.group+xml + GroupType + http://192.168.1.109/api/v1.5/schema/user.xsd + + + application/vnd.vmware.admin.right+xml + RightType + http://192.168.1.109/api/v1.5/schema/user.xsd + + + application/vnd.vmware.admin.role+xml + RoleType + http://192.168.1.109/api/v1.5/schema/user.xsd + + + application/vnd.vmware.admin.vcloud+xml + VCloudType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.organization+xml + AdminOrgType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.vAppTemplateLeaseSettings+xml + OrgVAppTemplateLeaseSettingsType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.orgSettings+xml + OrgSettingsType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.organizationGeneralSettings+xml + OrgGeneralSettingsType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.vAppLeaseSettings+xml + OrgLeaseSettingsType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.organizationFederationSettings+xml + OrgFederationSettingsType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.organizationLdapSettings+xml + OrgLdapSettingsType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.organizationEmailSettings+xml + OrgEmailSettingsType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.organizationPasswordPolicySettings+xml + OrgPasswordPolicySettingsType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.catalog+xml + AdminCatalogType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.guestPersonalizationSettings+xml + OrgGuestPersonalizationSettingsType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.operationLimitsSettings+xml + OrgOperationLimitsSettingsType + http://192.168.1.109/api/v1.5/schema/vCloudEntities.xsd + + + application/vnd.vmware.admin.systemSettings+xml + SystemSettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.generalSettings+xml + GeneralSettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.amqpSettings+xml + AmqpSettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.amqpSettingsTest+xml + AmqpSettingsTestType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.notificationsSettings+xml + NotificationsSettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.blockingTaskSettings+xml + BlockingTaskSettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.systemPasswordPolicySettings+xml + SystemPasswordPolicySettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.ldapSettings+xml + LdapSettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.brandingSettings+xml + BrandingSettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.licenseSettings+xml + LicenseType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.emailSettings+xml + EmailSettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.kerberosSettings+xml + KerberosSettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.lookupServiceSettings+xml + LookupServiceSettingsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.lookupServiceParams+xml + LookupServiceParamsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.vcTrustStoreUpdateParams+xml + VcTrustStoreUpdateParamsType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.vcTrustStoreUploadSocket+xml + VcTrustStoreUploadSocketType + http://192.168.1.109/api/v1.5/schema/settings.xsd + + + application/vnd.vmware.admin.extensionServices+xml + ExtensionServicesType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.service+xml + AdminServiceType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.apiFilter+xml + ApiFilterType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.apiFilters+xml + ApiFiltersType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.apiDefinition+xml + AdminApiDefinitionType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.apiDefinitions+xml + AdminApiDefinitionsType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.fileDescriptor+xml + AdminFileDescriptorType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.serviceLink+xml + AdminServiceLinkType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.bundleUploadParams+xml + BundleUploadParamsType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.bundleUploadSocket+xml + BundleUploadSocketType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.aclAccess+xml + AclAccessType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.aclRule+xml + AclRuleType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.resourceClassAction+xml + ResourceClassActionType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.resourceClass+xml + ResourceClassType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.serviceResource+xml + ServiceResourceType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.authorizationCheckParams+xml + AuthorizationCheckParamsType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.authorizationCheckResponse+xml + AuthorizationCheckResponseType + http://192.168.1.109/api/v1.5/schema/services.xsd + + + application/vnd.vmware.admin.vmwExtension+xml + VMWExtensionType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.prepareHostParams+xml + PrepareHostParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.registerVimServerParams+xml + RegisterVimServerParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwvirtualcenter+xml + VimServerType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwVimServerReferences+xml + VMWVimServerReferencesType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vshieldmanager+xml + ShieldManagerType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmsObjectRefsList+xml + VmObjectRefsListType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmObjectRef+xml + VmObjectRefType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.importVmAsVAppParams+xml + ImportVmAsVAppParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.importVmIntoExistingVAppParams+xml + ImportVmIntoExistingVAppParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.importVmAsVAppTemplateParams+xml + ImportVmAsVAppTemplateParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.importMediaParams+xml + ImportMediaParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.host+xml + HostType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vimObjectRef+xml + VimObjectRefType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vimObjectRefs+xml + VimObjectRefsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwprovidervdc+xml + VMWProviderVdcType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.createProviderVdcParams+xml + VMWProviderVdcParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwProviderVdcReferences+xml + VMWProviderVdcReferencesType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwPvdcStorageProfile+xml + VMWProviderVdcStorageProfileType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwexternalnet+xml + VMWExternalNetworkType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwExternalNetworkReferences+xml + VMWExternalNetworkReferencesType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwNetworkPoolReferences+xml + VMWNetworkPoolReferencesType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwNetworkPool+xml + VMWNetworkPoolType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.portGroupPool+xml + PortGroupPoolType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vlanPool+xml + VlanPoolType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vxlanPool+xml + VxlanPoolType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vxlanPool+xml + VdsContextType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwHostReferences+xml + VMWHostReferencesType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.resourcePoolList+xml + ResourcePoolListType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.licensingReport+xml + LicensingReportType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.licensingReportList+xml + LicensingReportListType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.datastore+xml + DatastoreType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwStorageProfiles+xml + VMWStorageProfilesType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwProviderVdcResourcePoolSet+xml + VMWProviderVdcResourcePoolSetType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vmwProviderVdcResourcePool+xml + VMWProviderVdcResourcePoolType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.resourcePoolSetUpdateParams+xml + UpdateResourcePoolSetParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.OrganizationVdcResourcePoolSet+xml + OrganizationResourcePoolSetType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.strandedItemVimObjects+xml + StrandedItemVimObjectType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.strandedItemVimObjects+xml + StrandedItemVimObjectsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.strandedItem+xml + StrandedItemType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.updateProviderVdcStorageProfiles+xml + UpdateProviderVdcStorageProfilesParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.providerVdcMergeParams+xml + ProviderVdcMergeParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.vSphereWebClientUrl+xml + VSphereWebClientUrlType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.updateRightsParams+xml + UpdateRightsParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.rights+xml + RightRefsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.entityReferences+xml + EntityReferencesType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.userEntityRights+xml + UserEntityRightsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.migrateVmParams+xml + MigrateParamsType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + application/vnd.vmware.admin.blockingTask+xml + BlockingTaskType + http://192.168.1.109/api/v1.5/schema/taskExtensionRequest.xsd + + + application/vnd.vmware.admin.blockingTaskOperationParams+xml + BlockingTaskOperationParamsType + http://192.168.1.109/api/v1.5/schema/taskExtensionRequest.xsd + + + application/vnd.vmware.admin.blockingTaskUpdateProgressOperationParams+xml + BlockingTaskUpdateProgressParamsType + http://192.168.1.109/api/v1.5/schema/taskExtensionRequest.xsd + + + application/vnd.vmware.vcloud.rasdItem+xml + RASD_Type + http://192.168.1.109/api/v1.5/schema/master.xsd + + + application/vnd.vmware.vcloud.startupSection+xml + StartupSection_Type + http://schemas.dmtf.org/ovf/envelope/1/dsp8023_1.1.0.xsd + + + application/vnd.vmware.vcloud.virtualHardwareSection+xml + VirtualHardwareSection_Type + http://schemas.dmtf.org/ovf/envelope/1/dsp8023_1.1.0.xsd + + + application/vnd.vmware.vcloud.operatingSystemSection+xml + OperatingSystemSection_Type + http://schemas.dmtf.org/ovf/envelope/1/dsp8023_1.1.0.xsd + + + application/vnd.vmware.vcloud.networkSection+xml + NetworkSection_Type + http://schemas.dmtf.org/ovf/envelope/1/dsp8023_1.1.0.xsd + + + application/vnd.vmware.vcloud.vAppNetwork+xml + VAppNetworkType + http://192.168.1.109/api/v1.5/schema/master.xsd + + + application/vnd.vmware.vcloud.network+xml + NetworkType + http://192.168.1.109/api/v1.5/schema/master.xsd + + + application/vnd.vmware.vcloud.orgNetwork+xml + OrgNetworkType + http://192.168.1.109/api/v1.5/schema/master.xsd + + + application/vnd.vmware.admin.vmwexternalnet+xml + VMWExternalNetworkType + http://192.168.1.109/api/v1.5/schema/vmwextensions.xsd + + + \ No newline at end of file diff --git a/test-resources/golden/TestSamlAdfsAuthenticate_RESP_cloud_org_my-org_saml_metadata_alias_vcd.golden b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_cloud_org_my-org_saml_metadata_alias_vcd.golden new file mode 100644 index 000000000..181a9a8e2 --- /dev/null +++ b/test-resources/golden/TestSamlAdfsAuthenticate_RESP_cloud_org_my-org_saml_metadata_alias_vcd.golden @@ -0,0 +1,28 @@ + + MIIC4jCCAcqgAwIBAgIEP4rcAjANBgkqhkiG9w0BAQUFADAzMTEwLwYDVQQDEyh2Q2xvdWQgRGly + ZWN0b3Igb3JnYW5pemF0aW9uIENlcnRpZmljYXRlMB4XDTIwMDMxOTA3MjkwOFoXDTIxMDMxOTA3 + MjkwOFowMzExMC8GA1UEAxModkNsb3VkIERpcmVjdG9yIG9yZ2FuaXphdGlvbiBDZXJ0aWZpY2F0 + ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKvB9rOzZOUW5AK3TAH9h3p3oFzVOljB + XSNvOz/OKEL7kVafnPUdxfqJvoZhtTxPOQ9VC9m9t+2sumyXiWCHaOgB/xNWGjzCJci1xFk6YD7j + y3J1XoQ+JHnL93QJZQK9didH1sjJ7XvtjFA5+1DJyHdTb5CuBH3/Qekyrok3a5ZnwujbwtwGL2NN + GLjQhEIkioJ67ge/jQWvF5BtthsKh3Jy9SZvMK/cR/s5LfrHHvVu7/ftELlRmfTcBBV2HaZ0lu1H + QSFvop1pgkD/UIkiqiuI/CdpJwVHoh5AILwOKXHnj1iMqMM+zgRUSFT3LitUM0nsYMypr5ubXbl5 + kpfxlGsCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAcQRO4lzuS6ec3SX3Vt0EzdKOw7pcsRpHXxbE + +TlgBlGge0JDDoliaf3Y5QGVjdvMYPn7iwBNHN+DkhRB/5CvgszzhKbyV/FEx+ulnII0Qw03aWVK + h8L5iPS/1qfBOc67tSKuEuQfXoSSDmJbb3bNmXz1FDh9URAUhoI8wJxYa8SQxiTpaof+WlZ7pRVW + z9peoenDOMVGcW41gpGA/uXE3PbH66Z5nJTxJvrpFkMtXyu+RBfWHkhQFi9FMWYS9viW+wg+JCqH + 0febOWgCGPqmZ2uUDSMcoSnlYnNdpcv1QXr0NtrKIZt4aXePRmoS7Lxjh671TcznlB7jNCqz+Koh + 5Q==MIIC4jCCAcqgAwIBAgIEP4rcAjANBgkqhkiG9w0BAQUFADAzMTEwLwYDVQQDEyh2Q2xvdWQgRGly + ZWN0b3Igb3JnYW5pemF0aW9uIENlcnRpZmljYXRlMB4XDTIwMDMxOTA3MjkwOFoXDTIxMDMxOTA3 + MjkwOFowMzExMC8GA1UEAxModkNsb3VkIERpcmVjdG9yIG9yZ2FuaXphdGlvbiBDZXJ0aWZpY2F0 + ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKvB9rOzZOUW5AK3TAH9h3p3oFzVOljB + XSNvOz/OKEL7kVafnPUdxfqJvoZhtTxPOQ9VC9m9t+2sumyXiWCHaOgB/xNWGjzCJci1xFk6YD7j + y3J1XoQ+JHnL93QJZQK9didH1sjJ7XvtjFA5+1DJyHdTb5CuBH3/Qekyrok3a5ZnwujbwtwGL2NN + GLjQhEIkioJ67ge/jQWvF5BtthsKh3Jy9SZvMK/cR/s5LfrHHvVu7/ftELlRmfTcBBV2HaZ0lu1H + QSFvop1pgkD/UIkiqiuI/CdpJwVHoh5AILwOKXHnj1iMqMM+zgRUSFT3LitUM0nsYMypr5ubXbl5 + kpfxlGsCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAcQRO4lzuS6ec3SX3Vt0EzdKOw7pcsRpHXxbE + +TlgBlGge0JDDoliaf3Y5QGVjdvMYPn7iwBNHN+DkhRB/5CvgszzhKbyV/FEx+ulnII0Qw03aWVK + h8L5iPS/1qfBOc67tSKuEuQfXoSSDmJbb3bNmXz1FDh9URAUhoI8wJxYa8SQxiTpaof+WlZ7pRVW + z9peoenDOMVGcW41gpGA/uXE3PbH66Z5nJTxJvrpFkMtXyu+RBfWHkhQFi9FMWYS9viW+wg+JCqH + 0febOWgCGPqmZ2uUDSMcoSnlYnNdpcv1QXr0NtrKIZt4aXePRmoS7Lxjh671TcznlB7jNCqz+Koh + 5Q==urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:2.0:nameid-format:transienturn:oasis:names:tc:SAML:2.0:nameid-format:persistenturn:oasis:names:tc:SAML:1.1:nameid-format:unspecifiedurn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName \ No newline at end of file diff --git a/types/v56/constants.go b/types/v56/constants.go index 50b11bab3..50724cf26 100644 --- a/types/v56/constants.go +++ b/types/v56/constants.go @@ -20,6 +20,8 @@ const ( Version511 = "5.11" // Version is the default version number Version = Version511 + // SoapXML mime type + SoapXML = "application/soap+xml" ) const ( diff --git a/types/v56/saml.go b/types/v56/saml.go new file mode 100644 index 000000000..6fe5fc39b --- /dev/null +++ b/types/v56/saml.go @@ -0,0 +1,74 @@ +/* + * Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package types + +import ( + "encoding/xml" + "fmt" +) + +// VcdSamlMetadata helps to marshal vCD SAML Metadata endpoint response +// https://1.1.1.1/cloud/org/my-org/saml/metadata/alias/vcd +// +// Note. This structure is not complete and has many more fields. +type VcdSamlMetadata struct { + XMLName xml.Name `xml:"EntityDescriptor"` + Text string `xml:",chardata"` + ID string `xml:"ID,attr"` + // EntityID is the configured vCD Entity ID which is used in ADFS authentication request + EntityID string `xml:"entityID,attr"` +} + +// AdfsAuthErrorEnvelope helps to parse ADFS authentication error with help of Error() method +// +// Note. This structure is not complete and has many more fields. +type AdfsAuthErrorEnvelope struct { + XMLName xml.Name `xml:"Envelope"` + Body struct { + Text string `xml:",chardata"` + Fault struct { + Text string `xml:",chardata"` + Code struct { + Text string `xml:",chardata"` + Value string `xml:"Value"` + Subcode struct { + Text string `xml:",chardata"` + Value struct { + Text string `xml:",chardata"` + A string `xml:"a,attr"` + } `xml:"Value"` + } `xml:"Subcode"` + } `xml:"Code"` + Reason struct { + Chardata string `xml:",chardata"` + Text struct { + Text string `xml:",chardata"` + Lang string `xml:"lang,attr"` + } `xml:"Text"` + } `xml:"Reason"` + } `xml:"Fault"` + } `xml:"Body"` +} + +// Error satisfies Go's default `error` interface for AdfsAuthErrorEnvelope and formats +// error for humand readable output +func (samlErr AdfsAuthErrorEnvelope) Error() string { + return fmt.Sprintf("SAML request got error: %s", samlErr.Body.Fault.Reason.Text) +} + +// AdfsAuthResponseEnvelope helps to marshal ADFS reponse to authentication request. +// +// Note. This structure is not complete and has many more fields. +type AdfsAuthResponseEnvelope struct { + XMLName xml.Name `xml:"Envelope"` + Body struct { + RequestSecurityTokenResponseCollection struct { + RequestSecurityTokenResponse struct { + // RequestedSecurityTokenTxt returns data which is accepted by vCD as a SIGN token + RequestedSecurityTokenTxt InnerXML `xml:"RequestedSecurityToken"` + } `xml:"RequestSecurityTokenResponse"` + } `xml:"RequestSecurityTokenResponseCollection"` + } `xml:"Body"` +} diff --git a/types/v56/types.go b/types/v56/types.go index 85d4e858f..c9c403e9e 100644 --- a/types/v56/types.go +++ b/types/v56/types.go @@ -1432,7 +1432,7 @@ type MediaSettings struct { // CpuResourceMhz from VM/VmSpecSection struct type CpuResourceMhz struct { - Configured int64 `xml:"Configured` // The amount of resource configured on the virtual machine. + Configured int64 `xml:"Configured"` // The amount of resource configured on the virtual machine. Reservation *int64 `xml:"Reservation,omitempty"` // The amount of reservation of this resource on the underlying virtualization infrastructure. Limit *int64 `xml:"Limit,omitempty"` // The limit for how much of this resource can be consumed on the underlying virtualization infrastructure. This is only valid when the resource allocation is not unlimited. SharesLevel string `xml:"SharesLevel,omitempty"` // Pre-determined relative priorities according to which the non-reserved portion of this resource is made available to the virtualized workload. diff --git a/util/logging.go b/util/logging.go index a6a02d3fd..0913c5626 100644 --- a/util/logging.go +++ b/util/logging.go @@ -150,13 +150,36 @@ func SetLog() { } } -// Hides passwords that may be used in a request +// hidePasswords hides passwords that may be used in a request func hidePasswords(in string, onScreen bool) string { if !onScreen && LogPasswords { return in } - re := regexp.MustCompile(`("[^\"]*[Pp]assword"\s*:\s*)"[^\"]+"`) - return re.ReplaceAllString(in, `${1}"********"`) + var out string + re1 := regexp.MustCompile(`("[^\"]*[Pp]assword"\s*:\s*)"[^\"]+"`) + out = re1.ReplaceAllString(in, `${1}"********"`) + + // Replace password in ADFS SAML request + re2 := regexp.MustCompile(`(\s*)(.*)()`) + out = re2.ReplaceAllString(out, `${1}******${3}`) + return out +} + +// hideTokens hides SAML auth response token +func hideTokens(in string, onScreen bool) string { + if !onScreen && LogPasswords { + return in + } + var out string + // Filters out the below: + // Token data between + re1 := regexp.MustCompile(`(.*)(.*)(.*)`) + out = re1.ReplaceAllString(in, `${1}******${3}`) + // Token data between + re2 := regexp.MustCompile(`(.*)(.*)(.*)`) + out = re2.ReplaceAllString(out, `${1}******${3}`) + + return out } // Determines whether a string is likely to contain binary data @@ -193,6 +216,19 @@ func SanitizedHeader(inputHeader http.Header) http.Header { } var sanitizedHeader = make(http.Header) for key, value := range inputHeader { + // Explicitly mask only token in SIGN token so that other details are not obfuscated + // Header format: SIGN token="`+base64GzippedSignToken+`",org="`+org+`" + if (key == "authorization" || key == "Authorization") && len(value) == 1 && + strings.HasPrefix(value[0], "SIGN") && !LogPasswords { + + re := regexp.MustCompile(`(SIGN token=")([^"]*)(.*)`) + out := re.ReplaceAllString(value[0], `${1}********${3}"`) + + Logger.Printf("\t%s: %s\n", key, out) + // Do not perform any post processing on this header + continue + } + for _, sk := range sensitiveKeys { if strings.EqualFold(sk, key) { value = []string{"********"} @@ -293,9 +329,9 @@ func ProcessResponseOutput(caller string, resp *http.Response, result string) { dataSize := len(result) outTextSize := len(outText) if outTextSize != dataSize { - Logger.Printf("Response text: [%d -> %d] %s\n", dataSize, outTextSize, outText) + Logger.Printf("Response text: [%d -> %d] %s\n", dataSize, outTextSize, hideTokens(outText, false)) } else { - Logger.Printf("Response text: [%d] %s\n", dataSize, outText) + Logger.Printf("Response text: [%d] %s\n", dataSize, hideTokens(outText, false)) } }