Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support customisation of ADFS server lookup cookie #699

Merged
merged 8 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changes/v2.26.0/699-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Added `Client.CustomAdfsCookie` and configuration function `WithSamlAdfsAndCookie` that might be
beneficial for ADFS server lookup query when using SAML auth [GH-699]
5 changes: 5 additions & 0 deletions govcd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ type Client struct {
// ID is used as Relaying Party Trust identifier.
CustomAdfsRptId string

// CustomAdfsCookie
// Placeholder {{.Org}} (if specified) will be replaced with real org
// E.g "sso-preferred=yes; sso_redirect_org={{.Org}}"
CustomAdfsCookie string

// UserAgent to send for API queries. Standard format is described as:
// "User-Agent: <product> / <product-version> <comment>"
UserAgent string
Expand Down
18 changes: 18 additions & 0 deletions govcd/api_vcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,24 @@ func WithSamlAdfs(useSaml bool, customAdfsRptId string) VCDClientOption {
}
}

// WithSamlAdfsAndCookie 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
// Additionall customAdfsCookie can be specified to impact how the code looks up ADFS SAML provider
// from VCD
func WithSamlAdfsAndCookie(useSaml bool, customAdfsRptId, customAdfsCookie string) VCDClientOption {
return func(vcdClient *VCDClient) error {
vcdClient.Client.UseSamlAdfs = useSaml
vcdClient.Client.CustomAdfsRptId = customAdfsRptId
vcdClient.Client.CustomAdfsCookie = customAdfsCookie
return nil
}
}

// WithHttpUserAgent allows to specify HTTP user-agent which can be useful for statistics tracking.
// By default User-Agent is set to "go-vcloud-director". It can be unset by supplying an empty value.
func WithHttpUserAgent(userAgent string) VCDClientOption {
Expand Down
4 changes: 4 additions & 0 deletions govcd/saml_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ func getSamlAdfsServer(vcdCli *VCDClient, org string) (string, error) {
// "?service=tenant:my-org"
req := vcdCli.Client.NewRequestWitNotEncodedParams(
nil, map[string]string{"service": "tenant:" + org}, http.MethodGet, *loginURL, nil)
if vcdCli.Client.CustomAdfsCookie != "" {
cookie := strings.ReplaceAll(vcdCli.Client.CustomAdfsCookie, "{{.Org}}", org)
req.Header.Add("Cookie", cookie)
}
httpResponse, err := checkResp(vcdCli.Client.Http.Do(req))
if err != nil {
return "", fmt.Errorf("SAML - ADFS server query failed: %s", err)
Expand Down
44 changes: 40 additions & 4 deletions govcd/saml_auth_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestSamlAdfsAuthenticate(t *testing.T) {
defer adfsServer.Close()

// Spawn mock vCD instance just enough to cover login details
vcdServer := spawnVcdServer(t, adfsServerHost, "my-org")
vcdServer := spawnVcdServer(t, adfsServerHost, "my-org", "")
vcdServerHost := vcdServer.URL
defer vcdServer.Close()

Expand All @@ -61,12 +61,40 @@ func TestSamlAdfsAuthenticate(t *testing.T) {
}
}

func TestSamlAdfsAuthenticateWithCookie(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", "sso-preferred=yes; sso_redirect_org=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, WithSamlAdfsAndCookie(true, "", "sso-preferred=yes; sso_redirect_org={{.Org}}"))
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 {
func spawnVcdServer(t *testing.T, adfsServerHost, org, expectCookie 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("/login/"+org+"/saml/login/alias/vcd", mockServer.getVcdAdfsRedirectHandler(adfsServerHost, expectCookie))
mux.HandleFunc("/api/sessions", mockServer.vCDLoginHandler)
mux.HandleFunc("/api/versions", mockServer.vCDApiVersionHandler)
mux.HandleFunc("/api/org", mockServer.vCDApiOrgHandler)
Expand Down Expand Up @@ -137,12 +165,20 @@ func (mockServer *samlMockServer) vCDSamlMetadataHandler(w http.ResponseWriter,
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) {
func (mockServer *samlMockServer) getVcdAdfsRedirectHandler(adfsServerHost, expectCookie 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
}

if expectCookie != "" {
cookie := r.Header.Get("Cookie")
if cookie != expectCookie {
w.WriteHeader(500)
}
}

headers := w.Header()
locationHeaderPayload := goldenString(mockServer.t, "RESP_HEADER_login_my-org_saml_login_alias_vcd", "", false)
headers.Add("Location", adfsServerHost+locationHeaderPayload)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">REPLACED</a:To>
<o:Security s:mustUnderstand="1"
xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="_0">
<u:Created>REPLACED</u:Created>
<u:Expires>REPLACED</u:Expires>
</u:Timestamp>
<o:UsernameToken>
<o:Username>fakeUser</o:Username>
<o:Password o:Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">fakePass</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body>
<trust:RequestSecurityToken xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<a:EndpointReference>
<a:Address>https://192.168.1.109/cloud/org/my-org/saml/metadata/alias/vcd</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<trust:KeySize>0</trust:KeySize>
<trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
<i:RequestDisplayToken xml:lang="en"
xmlns:i="http://schemas.xmlsoap.org/ws/2005/05/identity" />
<trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
<trust:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</trust:TokenType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal</a:Action><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><u:Timestamp u:Id="_0"><u:Created>2020-04-27T06:05:53.281Z</u:Created><u:Expires>2020-04-27T06:10:53.281Z</u:Expires></u:Timestamp></o:Security></s:Header><s:Body><trust:RequestSecurityTokenResponseCollection xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512"><trust:RequestSecurityTokenResponse><trust:Lifetime><wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2020-04-27T06:05:53.281Z</wsu:Created><wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2020-04-27T07:05:53.281Z</wsu:Expires></trust:Lifetime><wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"><wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing"><wsa:Address>https://192.168.1.109/cloud/org/my-org/saml/metadata/alias/vcd</wsa:Address></wsa:EndpointReference></wsp:AppliesTo><trust:RequestedSecurityToken><EncryptedAssertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion"><xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/><KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><e:EncryptedKey xmlns:e="http://www.w3.org/2001/04/xmlenc#"><e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/></e:EncryptionMethod><KeyInfo><ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509IssuerSerial><ds:X509IssuerName>CN=vCloud Director organization Certificate</ds:X509IssuerName><ds:X509SerialNumber>1066064898</ds:X509SerialNumber></ds:X509IssuerSerial></ds:X509Data></KeyInfo><e:CipherData><e:CipherValue>******</e:CipherValue></e:CipherData></e:EncryptedKey></KeyInfo><xenc:CipherData><xenc:CipherValue>******</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData></EncryptedAssertion></trust:RequestedSecurityToken><i:RequestedDisplayToken xmlns:i="http://schemas.xmlsoap.org/ws/2005/05/identity"><i:DisplayToken xml:lang="en"><i:DisplayClaim Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"><i:DisplayTag>E-Mail Address</i:DisplayTag><i:Description>The e-mail address of the user</i:Description><i:DisplayValue>test@test-forest.net</i:DisplayValue></i:DisplayClaim><i:DisplayClaim Uri="groups"><i:DisplayValue>Domain Users</i:DisplayValue></i:DisplayClaim><i:DisplayClaim Uri="groups"><i:DisplayValue>VCDUsers</i:DisplayValue></i:DisplayClaim></i:DisplayToken></i:RequestedDisplayToken><trust:RequestedAttachedReference><SecurityTokenReference b:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:b="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"><KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID">_83958fe2-1b12-4706-a6e5-af2143616c23</KeyIdentifier></SecurityTokenReference></trust:RequestedAttachedReference><trust:RequestedUnattachedReference><SecurityTokenReference b:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:b="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"><KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID">_83958fe2-1b12-4706-a6e5-af2143616c23</KeyIdentifier></SecurityTokenReference></trust:RequestedUnattachedReference><trust:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</trust:TokenType><trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType><trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType></trust:RequestSecurityTokenResponse></trust:RequestSecurityTokenResponseCollection></s:Body></s:Envelope>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OrgList xmlns="http://www.vmware.com/vcloud/v1.5" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:common="http://schemas.dmtf.org/wbem/wscim/1/common" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vmw="http://www.vmware.com/schema/ovf" xmlns:ovfenv="http://schemas.dmtf.org/ovf/environment/1" xmlns:vmext="http://www.vmware.com/vcloud/extension/v1.5" xmlns:ns9="http://www.vmware.com/vcloud/versions" href="https://192.168.1.109/api/org/" type="application/vnd.vmware.vcloud.orgList+xml">
<Org href="https://192.168.1.109/api/org/c196c6f0-5c31-4929-a626-b29b2c9ff5ab" name="my-org" type="application/vnd.vmware.vcloud.org+xml"/>
</OrgList>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Session xmlns="http://www.vmware.com/vcloud/v1.5" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:common="http://schemas.dmtf.org/wbem/wscim/1/common" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vmw="http://www.vmware.com/schema/ovf" xmlns:ovfenv="http://schemas.dmtf.org/ovf/environment/1" xmlns:vmext="http://www.vmware.com/vcloud/extension/v1.5" xmlns:ns9="http://www.vmware.com/vcloud/versions" locationId="c196c6f0-5c31-4929-a626-b29b2c9ff5ab@cb33a646-6652-4628-95b0-24bd981783b6" org="my-org" roles="Organization Administrator" user="test@test-forest.net" userId="urn:vcloud:user:8fd38079-b31c-4dfd-99f2-7073b3f7ec90" href="https://192.168.1.109/api/session" type="application/vnd.vmware.vcloud.session+xml">
<Link rel="down" href="https://192.168.1.109/api/org/" type="application/vnd.vmware.vcloud.orgList+xml"/>
<Link rel="remove" href="https://192.168.1.109/api/session"/>
<Link rel="down" href="https://192.168.1.109/api/admin/" type="application/vnd.vmware.admin.vcloud+xml"/>
<Link rel="down" href="https://192.168.1.109/api/org/c196c6f0-5c31-4929-a626-b29b2c9ff5ab" name="my-org" type="application/vnd.vmware.vcloud.org+xml"/>
<Link rel="down" href="https://192.168.1.109/api/query" type="application/vnd.vmware.vcloud.query.queryList+xml"/>
<Link rel="entityResolver" href="https://192.168.1.109/api/entity/" type="application/vnd.vmware.vcloud.entity+xml"/>
<Link rel="down:extensibility" href="https://192.168.1.109/api/extensibility" type="application/vnd.vmware.vcloud.apiextensibility+xml"/>
<Link rel="nsx" href="https://192.168.1.109/network" type="application/xml"/>
<Link rel="openapi" href="https://192.168.1.109/cloudapi" type="application/json"/>
<AuthorizedLocations>
<Location>
<LocationId>c196c6f0-5c31-4929-a626-b29b2c9ff5ab@cb33a646-6652-4628-95b0-24bd981783b6</LocationId>
<SiteName>192.168.1.109</SiteName>
<OrgName>my-org</OrgName>
<RestApiEndpoint>https://192.168.1.109</RestApiEndpoint>
<UIEndpoint>https://192.168.1.109</UIEndpoint>
<AuthContext>my-org</AuthContext>
</Location>
</AuthorizedLocations>
</Session>
Loading
Loading