123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- /*
- Copyright (c) 2018 VMware, Inc. All Rights Reserved.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package sts
- import (
- "context"
- "crypto/tls"
- "errors"
- "net/url"
- "time"
- "github.com/vmware/govmomi/lookup"
- "github.com/vmware/govmomi/lookup/types"
- "github.com/vmware/govmomi/sts/internal"
- "github.com/vmware/govmomi/vim25"
- "github.com/vmware/govmomi/vim25/soap"
- )
- const (
- Namespace = "oasis:names:tc:SAML:2.0:assertion"
- Path = "/sts/STSService"
- )
- // Client is a soap.Client targeting the STS (Secure Token Service) API endpoint.
- type Client struct {
- *soap.Client
- }
- // NewClient returns a client targeting the STS API endpoint.
- // The Client.URL will be set to that of the Lookup Service's endpoint registration,
- // as the SSO endpoint can be external to vCenter. If the Lookup Service is not available,
- // URL defaults to Path on the vim25.Client.URL.Host.
- func NewClient(ctx context.Context, c *vim25.Client) (*Client, error) {
- filter := &types.LookupServiceRegistrationFilter{
- ServiceType: &types.LookupServiceRegistrationServiceType{
- Product: "com.vmware.cis",
- Type: "sso:sts",
- },
- EndpointType: &types.LookupServiceRegistrationEndpointType{
- Protocol: "wsTrust",
- Type: "com.vmware.cis.cs.identity.sso",
- },
- }
- url := lookup.EndpointURL(ctx, c, Path, filter)
- sc := c.Client.NewServiceClient(url, Namespace)
- return &Client{sc}, nil
- }
- // TokenRequest parameters for issuing a SAML token.
- // At least one of Userinfo or Certificate must be specified.
- type TokenRequest struct {
- Userinfo *url.Userinfo // Userinfo when set issues a Bearer token
- Certificate *tls.Certificate // Certificate when set issues a HoK token
- Lifetime time.Duration // Lifetime is the token's lifetime, defaults to 10m
- Renewable bool // Renewable allows the issued token to be renewed
- Delegatable bool // Delegatable allows the issued token to be delegated (e.g. for use with ActAs)
- ActAs bool // ActAs allows to request an ActAs token based on the passed Token.
- Token string // Token for Renew request or Issue request ActAs identity or to be exchanged.
- KeyType string // KeyType for requested token (if not set will be decucted from Userinfo and Certificate options)
- KeyID string // KeyID used for signing the requests
- }
- func (c *Client) newRequest(req TokenRequest, kind string, s *Signer) (internal.RequestSecurityToken, error) {
- if req.Lifetime == 0 {
- req.Lifetime = 5 * time.Minute
- }
- created := time.Now().UTC()
- rst := internal.RequestSecurityToken{
- TokenType: c.Namespace,
- RequestType: "http://docs.oasis-open.org/ws-sx/ws-trust/200512/" + kind,
- SignatureAlgorithm: internal.SHA256,
- Lifetime: &internal.Lifetime{
- Created: created.Format(internal.Time),
- Expires: created.Add(req.Lifetime).Format(internal.Time),
- },
- Renewing: &internal.Renewing{
- Allow: req.Renewable,
- // /wst:RequestSecurityToken/wst:Renewing/@OK
- // "It NOT RECOMMENDED to use this as it can leave you open to certain types of security attacks.
- // Issuers MAY restrict the period after expiration during which time the token can be renewed.
- // This window is governed by the issuer's policy."
- OK: false,
- },
- Delegatable: req.Delegatable,
- KeyType: req.KeyType,
- }
- if req.KeyType == "" {
- // Deduce KeyType based on Certificate nad Userinfo.
- if req.Certificate == nil {
- if req.Userinfo == nil {
- return rst, errors.New("one of TokenRequest Certificate or Userinfo is required")
- }
- rst.KeyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer"
- } else {
- rst.KeyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey"
- // For HOK KeyID is required.
- if req.KeyID == "" {
- req.KeyID = newID()
- }
- }
- }
- if req.KeyID != "" {
- rst.UseKey = &internal.UseKey{Sig: req.KeyID}
- s.keyID = rst.UseKey.Sig
- }
- return rst, nil
- }
- func (s *Signer) setLifetime(lifetime *internal.Lifetime) error {
- var err error
- if lifetime != nil {
- s.Lifetime.Created, err = time.Parse(internal.Time, lifetime.Created)
- if err == nil {
- s.Lifetime.Expires, err = time.Parse(internal.Time, lifetime.Expires)
- }
- }
- return err
- }
- // Issue is used to request a security token.
- // The returned Signer can be used to sign SOAP requests, such as the SessionManager LoginByToken method and the RequestSecurityToken method itself.
- // One of TokenRequest Certificate or Userinfo is required, with Certificate taking precedence.
- // When Certificate is set, a Holder-of-Key token will be requested. Otherwise, a Bearer token is requested with the Userinfo credentials.
- // See: http://docs.oasis-open.org/ws-sx/ws-trust/v1.4/errata01/os/ws-trust-1.4-errata01-os-complete.html#_Toc325658937
- func (c *Client) Issue(ctx context.Context, req TokenRequest) (*Signer, error) {
- s := &Signer{
- Certificate: req.Certificate,
- keyID: req.KeyID,
- Token: req.Token,
- user: req.Userinfo,
- }
- rst, err := c.newRequest(req, "Issue", s)
- if err != nil {
- return nil, err
- }
- if req.ActAs {
- rst.ActAs = &internal.Target{
- Token: req.Token,
- }
- }
- header := soap.Header{
- Security: s,
- Action: rst.Action(),
- }
- res, err := internal.Issue(c.WithHeader(ctx, header), c, &rst)
- if err != nil {
- return nil, err
- }
- s.Token = res.RequestSecurityTokenResponse.RequestedSecurityToken.Assertion
- return s, s.setLifetime(res.RequestSecurityTokenResponse.Lifetime)
- }
- // Renew is used to request a security token renewal.
- func (c *Client) Renew(ctx context.Context, req TokenRequest) (*Signer, error) {
- s := &Signer{
- Certificate: req.Certificate,
- }
- rst, err := c.newRequest(req, "Renew", s)
- if err != nil {
- return nil, err
- }
- if req.Token == "" {
- return nil, errors.New("TokenRequest Token is required")
- }
- rst.RenewTarget = &internal.Target{Token: req.Token}
- header := soap.Header{
- Security: s,
- Action: rst.Action(),
- }
- res, err := internal.Renew(c.WithHeader(ctx, header), c, &rst)
- if err != nil {
- return nil, err
- }
- s.Token = res.RequestedSecurityToken.Assertion
- return s, s.setLifetime(res.Lifetime)
- }
|