mirror of https://github.com/k3s-io/k3s
210 lines
6.5 KiB
Go
210 lines
6.5 KiB
Go
/*
|
|
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)
|
|
}
|