client.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*
  2. Copyright (c) 2018 VMware, Inc. All Rights Reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package sts
  14. import (
  15. "context"
  16. "crypto/tls"
  17. "errors"
  18. "net/url"
  19. "time"
  20. "github.com/vmware/govmomi/lookup"
  21. "github.com/vmware/govmomi/lookup/types"
  22. "github.com/vmware/govmomi/sts/internal"
  23. "github.com/vmware/govmomi/vim25"
  24. "github.com/vmware/govmomi/vim25/soap"
  25. )
  26. const (
  27. Namespace = "oasis:names:tc:SAML:2.0:assertion"
  28. Path = "/sts/STSService"
  29. )
  30. // Client is a soap.Client targeting the STS (Secure Token Service) API endpoint.
  31. type Client struct {
  32. *soap.Client
  33. }
  34. // NewClient returns a client targeting the STS API endpoint.
  35. // The Client.URL will be set to that of the Lookup Service's endpoint registration,
  36. // as the SSO endpoint can be external to vCenter. If the Lookup Service is not available,
  37. // URL defaults to Path on the vim25.Client.URL.Host.
  38. func NewClient(ctx context.Context, c *vim25.Client) (*Client, error) {
  39. filter := &types.LookupServiceRegistrationFilter{
  40. ServiceType: &types.LookupServiceRegistrationServiceType{
  41. Product: "com.vmware.cis",
  42. Type: "sso:sts",
  43. },
  44. EndpointType: &types.LookupServiceRegistrationEndpointType{
  45. Protocol: "wsTrust",
  46. Type: "com.vmware.cis.cs.identity.sso",
  47. },
  48. }
  49. url := lookup.EndpointURL(ctx, c, Path, filter)
  50. sc := c.Client.NewServiceClient(url, Namespace)
  51. return &Client{sc}, nil
  52. }
  53. // TokenRequest parameters for issuing a SAML token.
  54. // At least one of Userinfo or Certificate must be specified.
  55. type TokenRequest struct {
  56. Userinfo *url.Userinfo // Userinfo when set issues a Bearer token
  57. Certificate *tls.Certificate // Certificate when set issues a HoK token
  58. Lifetime time.Duration // Lifetime is the token's lifetime, defaults to 10m
  59. Renewable bool // Renewable allows the issued token to be renewed
  60. Delegatable bool // Delegatable allows the issued token to be delegated (e.g. for use with ActAs)
  61. ActAs bool // ActAs allows to request an ActAs token based on the passed Token.
  62. Token string // Token for Renew request or Issue request ActAs identity or to be exchanged.
  63. KeyType string // KeyType for requested token (if not set will be decucted from Userinfo and Certificate options)
  64. KeyID string // KeyID used for signing the requests
  65. }
  66. func (c *Client) newRequest(req TokenRequest, kind string, s *Signer) (internal.RequestSecurityToken, error) {
  67. if req.Lifetime == 0 {
  68. req.Lifetime = 5 * time.Minute
  69. }
  70. created := time.Now().UTC()
  71. rst := internal.RequestSecurityToken{
  72. TokenType: c.Namespace,
  73. RequestType: "http://docs.oasis-open.org/ws-sx/ws-trust/200512/" + kind,
  74. SignatureAlgorithm: internal.SHA256,
  75. Lifetime: &internal.Lifetime{
  76. Created: created.Format(internal.Time),
  77. Expires: created.Add(req.Lifetime).Format(internal.Time),
  78. },
  79. Renewing: &internal.Renewing{
  80. Allow: req.Renewable,
  81. // /wst:RequestSecurityToken/wst:Renewing/@OK
  82. // "It NOT RECOMMENDED to use this as it can leave you open to certain types of security attacks.
  83. // Issuers MAY restrict the period after expiration during which time the token can be renewed.
  84. // This window is governed by the issuer's policy."
  85. OK: false,
  86. },
  87. Delegatable: req.Delegatable,
  88. KeyType: req.KeyType,
  89. }
  90. if req.KeyType == "" {
  91. // Deduce KeyType based on Certificate nad Userinfo.
  92. if req.Certificate == nil {
  93. if req.Userinfo == nil {
  94. return rst, errors.New("one of TokenRequest Certificate or Userinfo is required")
  95. }
  96. rst.KeyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer"
  97. } else {
  98. rst.KeyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey"
  99. // For HOK KeyID is required.
  100. if req.KeyID == "" {
  101. req.KeyID = newID()
  102. }
  103. }
  104. }
  105. if req.KeyID != "" {
  106. rst.UseKey = &internal.UseKey{Sig: req.KeyID}
  107. s.keyID = rst.UseKey.Sig
  108. }
  109. return rst, nil
  110. }
  111. func (s *Signer) setLifetime(lifetime *internal.Lifetime) error {
  112. var err error
  113. if lifetime != nil {
  114. s.Lifetime.Created, err = time.Parse(internal.Time, lifetime.Created)
  115. if err == nil {
  116. s.Lifetime.Expires, err = time.Parse(internal.Time, lifetime.Expires)
  117. }
  118. }
  119. return err
  120. }
  121. // Issue is used to request a security token.
  122. // The returned Signer can be used to sign SOAP requests, such as the SessionManager LoginByToken method and the RequestSecurityToken method itself.
  123. // One of TokenRequest Certificate or Userinfo is required, with Certificate taking precedence.
  124. // When Certificate is set, a Holder-of-Key token will be requested. Otherwise, a Bearer token is requested with the Userinfo credentials.
  125. // See: http://docs.oasis-open.org/ws-sx/ws-trust/v1.4/errata01/os/ws-trust-1.4-errata01-os-complete.html#_Toc325658937
  126. func (c *Client) Issue(ctx context.Context, req TokenRequest) (*Signer, error) {
  127. s := &Signer{
  128. Certificate: req.Certificate,
  129. keyID: req.KeyID,
  130. Token: req.Token,
  131. user: req.Userinfo,
  132. }
  133. rst, err := c.newRequest(req, "Issue", s)
  134. if err != nil {
  135. return nil, err
  136. }
  137. if req.ActAs {
  138. rst.ActAs = &internal.Target{
  139. Token: req.Token,
  140. }
  141. }
  142. header := soap.Header{
  143. Security: s,
  144. Action: rst.Action(),
  145. }
  146. res, err := internal.Issue(c.WithHeader(ctx, header), c, &rst)
  147. if err != nil {
  148. return nil, err
  149. }
  150. s.Token = res.RequestSecurityTokenResponse.RequestedSecurityToken.Assertion
  151. return s, s.setLifetime(res.RequestSecurityTokenResponse.Lifetime)
  152. }
  153. // Renew is used to request a security token renewal.
  154. func (c *Client) Renew(ctx context.Context, req TokenRequest) (*Signer, error) {
  155. s := &Signer{
  156. Certificate: req.Certificate,
  157. }
  158. rst, err := c.newRequest(req, "Renew", s)
  159. if err != nil {
  160. return nil, err
  161. }
  162. if req.Token == "" {
  163. return nil, errors.New("TokenRequest Token is required")
  164. }
  165. rst.RenewTarget = &internal.Target{Token: req.Token}
  166. header := soap.Header{
  167. Security: s,
  168. Action: rst.Action(),
  169. }
  170. res, err := internal.Renew(c.WithHeader(ctx, header), c, &rst)
  171. if err != nil {
  172. return nil, err
  173. }
  174. s.Token = res.RequestedSecurityToken.Assertion
  175. return s, s.setLifetime(res.Lifetime)
  176. }