diff --git a/govc/session/login.go b/govc/session/login.go index 2d0bbecad..87e720780 100644 --- a/govc/session/login.go +++ b/govc/session/login.go @@ -215,6 +215,13 @@ func (cmd *login) loginByToken(ctx context.Context, c *vim25.Client) error { }, } + // something behind the LoginByToken scene requires a version from /sdk/vimServiceVersions.xml + // in the SOAPAction header. For example, if vim25.Version is "7.0" but the service version is "6.3", + // LoginByToken fails with: 'VersionMismatchFaultCode: Unsupported version URI "urn:vim25/7.0"' + if c.Version == vim25.Version { + _ = c.UseServiceVersion() + } + return session.NewManager(c).LoginByToken(c.WithHeader(ctx, header)) } diff --git a/sts/client.go b/sts/client.go index bcd346b76..d98c56095 100644 --- a/sts/client.go +++ b/sts/client.go @@ -71,6 +71,9 @@ func (c *Client) RoundTrip(ctx context.Context, req, res soap.HasFault) error { // TokenRequest parameters for issuing a SAML token. // At least one of Userinfo or Certificate must be specified. +// When `TokenRequest.Certificate` is set, the `tls.Certificate.PrivateKey` field must be set as it is required to sign the request. +// When the `tls.Certificate.Certificate` field is not set, the request Assertion header is set to that of the TokenRequest.Token. +// Otherwise `tls.Certificate.Certificate` is used as the BinarySecurityToken in the request. type TokenRequest struct { Userinfo *url.Userinfo // Userinfo when set issues a Bearer token Certificate *tls.Certificate // Certificate when set issues a HoK token diff --git a/sts/client_test.go b/sts/client_test.go index 030140396..8196e50ca 100644 --- a/sts/client_test.go +++ b/sts/client_test.go @@ -122,6 +122,7 @@ func TestIssueHOK(t *testing.T) { if err != nil { log.Fatal(err) } + _ = c.UseServiceVersion() if !c.IsVC() { t.SkipNow() @@ -161,6 +162,75 @@ func TestIssueHOK(t *testing.T) { log.Printf("current time=%s", now) } +func TestIssueTokenByToken(t *testing.T) { + ctx := context.Background() + url := os.Getenv("GOVC_TEST_URL") + if url == "" { + t.SkipNow() + } + + u, err := soap.ParseURL(url) + if err != nil { + t.Fatal(err) + } + + vc1, err := vim25.NewClient(ctx, soap.NewClient(u, true)) + if err != nil { + log.Fatal(err) + } + _ = vc1.UseServiceVersion() + + if !vc1.IsVC() { + t.SkipNow() + } + + vc2, err := vim25.NewClient(ctx, soap.NewClient(u, true)) + if err != nil { + log.Fatal(err) + } + _ = vc2.UseServiceVersion() + + sts1, err := NewClient(ctx, vc1) + if err != nil { + t.Fatal(err) + } + + if err = solutionUserCreate(ctx, u.User, sts1, vc1); err != nil { + t.Fatal(err) + } + + sts2, err := NewClient(ctx, vc2) + if err != nil { + t.Fatal(err) + } + + req1 := TokenRequest{ + Certificate: solutionUserCert(), + Delegatable: true, + } + + signer1, err := sts1.Issue(ctx, req1) + if err != nil { + t.Fatal(err) + } + + req2 := signer1.NewRequest() + // use Assertion header instead of BinarySecurityToken + req2.Certificate = &tls.Certificate{PrivateKey: req1.Certificate.PrivateKey} + + signer2, err := sts2.Issue(ctx, req2) + if err != nil { + t.Fatal(err) + } + + header := soap.Header{Security: signer2} + + err = session.NewManager(vc2).LoginByToken(vc2.WithHeader(ctx, header)) + if err != nil { + t.Fatal(err) + } +} + func TestIssueBearer(t *testing.T) { ctx := context.Background() url := os.Getenv("GOVC_TEST_URL") @@ -177,6 +247,7 @@ func TestIssueBearer(t *testing.T) { if err != nil { log.Fatal(err) } + _ = c.UseServiceVersion() if !c.IsVC() { t.SkipNow() @@ -233,6 +304,7 @@ func TestIssueActAs(t *testing.T) { if err != nil { log.Fatal(err) } + _ = c.UseServiceVersion() if !c.IsVC() { t.SkipNow() diff --git a/sts/signer.go b/sts/signer.go index 78cf20461..c6bf49d92 100644 --- a/sts/signer.go +++ b/sts/signer.go @@ -128,20 +128,28 @@ func (s *Signer) Sign(env soap.Envelope) ([]byte, error) { req := x.RequestSecurityToken() c14n = req.C14N() body = req.String() - id := newID() - info.SecurityTokenReference = &internal.SecurityTokenReference{ - Reference: &internal.SecurityReference{ - URI: "#" + id, - ValueType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3", - }, - } - - header.BinarySecurityToken = &internal.BinarySecurityToken{ - EncodingType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary", - ValueType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3", - ID: id, - Value: base64.StdEncoding.EncodeToString(s.Certificate.Certificate[0]), + if len(s.Certificate.Certificate) == 0 { + header.Assertion = s.Token + if err := s.setTokenReference(&info); err != nil { + return nil, err + } + } else { + id := newID() + + header.BinarySecurityToken = &internal.BinarySecurityToken{ + EncodingType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary", + ValueType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3", + ID: id, + Value: base64.StdEncoding.EncodeToString(s.Certificate.Certificate[0]), + } + + info.SecurityTokenReference = &internal.SecurityTokenReference{ + Reference: &internal.SecurityReference{ + URI: "#" + id, + ValueType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3", + }, + } } } // When requesting HoK token for interactive user, request will have both priv. key and username/password.