github.com/vmware/govmomi@v0.37.2/sts/client_test.go (about)

     1  /*
     2  Copyright (c) 2018-2023 VMware, Inc. All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package sts_test
    18  
    19  import (
    20  	"context"
    21  	"crypto/tls"
    22  	"encoding/base64"
    23  	"encoding/pem"
    24  	"log"
    25  	"net/url"
    26  	"os"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/stretchr/testify/require"
    31  
    32  	lsim "github.com/vmware/govmomi/lookup/simulator"
    33  	"github.com/vmware/govmomi/session"
    34  	"github.com/vmware/govmomi/simulator"
    35  	"github.com/vmware/govmomi/ssoadmin"
    36  	_ "github.com/vmware/govmomi/ssoadmin/simulator"
    37  	"github.com/vmware/govmomi/ssoadmin/types"
    38  	"github.com/vmware/govmomi/sts"
    39  	_ "github.com/vmware/govmomi/sts/simulator"
    40  	"github.com/vmware/govmomi/vapi/rest"
    41  	"github.com/vmware/govmomi/vim25"
    42  	"github.com/vmware/govmomi/vim25/methods"
    43  	"github.com/vmware/govmomi/vim25/soap"
    44  )
    45  
    46  // The following can help debug signature mismatch:
    47  // % vi /usr/lib/vmware-sso/vmware-sts/conf/logging.properties
    48  // # turn up logging for dsig:
    49  // org.jcp.xml.dsig.internal.level = FINE
    50  // com.sun.org.apache.xml.internal.security.level = FINE
    51  // # restart the STS service:
    52  // % service-control --stop vmware-stsd
    53  // % service-control --start vmware-stsd
    54  // % tail -f /var/log/vmware/sso/vmware-rest-idm-http.log
    55  
    56  // solutionUserCreate ensures that solution user "govmomi-test" exists for uses with the tests that follow.
    57  func solutionUserCreate(ctx context.Context, info *url.Userinfo, stsClient *sts.Client, vc *vim25.Client) error {
    58  	s, err := stsClient.Issue(ctx, sts.TokenRequest{Userinfo: info})
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	admin, err := ssoadmin.NewClient(ctx, vc)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	header := soap.Header{Security: s}
    69  	if err = admin.Login(stsClient.WithHeader(ctx, header)); err != nil {
    70  		return err
    71  	}
    72  
    73  	defer func() {
    74  		if err := admin.Logout(ctx); err != nil {
    75  			log.Printf("user logout error: %v", err)
    76  		}
    77  	}()
    78  
    79  	id := types.PrincipalId{
    80  		Name:   "govmomi-test",
    81  		Domain: admin.Domain,
    82  	}
    83  
    84  	user, err := admin.FindSolutionUser(ctx, id.Name)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	if user == nil {
    90  		block, _ := pem.Decode([]byte(sts.LocalhostCert))
    91  		details := types.AdminSolutionDetails{
    92  			Certificate: base64.StdEncoding.EncodeToString(block.Bytes),
    93  			Description: "govmomi test solution user",
    94  		}
    95  
    96  		if err = admin.CreateSolutionUser(ctx, id.Name, details); err != nil {
    97  			return err
    98  		}
    99  	}
   100  
   101  	if _, err = admin.GrantWSTrustRole(ctx, id, types.RoleActAsUser); err != nil {
   102  		return err
   103  	}
   104  
   105  	_, err = admin.SetRole(ctx, id, types.RoleAdministrator)
   106  	return err
   107  }
   108  
   109  func solutionUserCert() *tls.Certificate {
   110  	cert, err := tls.X509KeyPair(sts.LocalhostCert, sts.LocalhostKey)
   111  	if err != nil {
   112  		panic(err)
   113  	}
   114  	return &cert
   115  }
   116  
   117  func TestIssueHOK(t *testing.T) {
   118  	ctx := context.Background()
   119  	url := os.Getenv("GOVC_TEST_URL")
   120  	if url == "" {
   121  		t.SkipNow()
   122  	}
   123  
   124  	u, err := soap.ParseURL(url)
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  
   129  	c, err := vim25.NewClient(ctx, soap.NewClient(u, true))
   130  	if err != nil {
   131  		log.Fatal(err)
   132  	}
   133  	_ = c.UseServiceVersion()
   134  
   135  	if !c.IsVC() {
   136  		t.SkipNow()
   137  	}
   138  
   139  	stsClient, err := sts.NewClient(ctx, c)
   140  	if err != nil {
   141  		t.Fatal(err)
   142  	}
   143  
   144  	if err = solutionUserCreate(ctx, u.User, stsClient, c); err != nil {
   145  		t.Fatal(err)
   146  	}
   147  
   148  	req := sts.TokenRequest{
   149  		Certificate: solutionUserCert(),
   150  		Delegatable: true,
   151  	}
   152  
   153  	s, err := stsClient.Issue(ctx, req)
   154  	if err != nil {
   155  		t.Fatal(err)
   156  	}
   157  
   158  	header := soap.Header{Security: s}
   159  
   160  	err = session.NewManager(c).LoginByToken(c.WithHeader(ctx, header))
   161  	if err != nil {
   162  		t.Fatal(err)
   163  	}
   164  
   165  	now, err := methods.GetCurrentTime(ctx, c)
   166  	if err != nil {
   167  		t.Fatal(err)
   168  	}
   169  
   170  	log.Printf("current time=%s", now)
   171  
   172  	rc := rest.NewClient(c)
   173  	err = rc.LoginByToken(rc.WithSigner(ctx, s))
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  }
   178  
   179  func TestIssueTokenByToken(t *testing.T) {
   180  	ctx := context.Background()
   181  	url := os.Getenv("GOVC_TEST_URL")
   182  	if url == "" {
   183  		t.SkipNow()
   184  	}
   185  
   186  	u, err := soap.ParseURL(url)
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  
   191  	vc1, err := vim25.NewClient(ctx, soap.NewClient(u, true))
   192  	if err != nil {
   193  		log.Fatal(err)
   194  	}
   195  	_ = vc1.UseServiceVersion()
   196  
   197  	if !vc1.IsVC() {
   198  		t.SkipNow()
   199  	}
   200  
   201  	vc2, err := vim25.NewClient(ctx, soap.NewClient(u, true))
   202  	if err != nil {
   203  		log.Fatal(err)
   204  	}
   205  	_ = vc2.UseServiceVersion()
   206  
   207  	sts1, err := sts.NewClient(ctx, vc1)
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  
   212  	if err = solutionUserCreate(ctx, u.User, sts1, vc1); err != nil {
   213  		t.Fatal(err)
   214  	}
   215  
   216  	sts2, err := sts.NewClient(ctx, vc2)
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  
   221  	req1 := sts.TokenRequest{
   222  		Certificate: solutionUserCert(),
   223  		Delegatable: true,
   224  	}
   225  
   226  	signer1, err := sts1.Issue(ctx, req1)
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  
   231  	req2 := signer1.NewRequest()
   232  	// use Assertion header instead of BinarySecurityToken
   233  	req2.Certificate = &tls.Certificate{PrivateKey: req1.Certificate.PrivateKey}
   234  
   235  	signer2, err := sts2.Issue(ctx, req2)
   236  	if err != nil {
   237  		t.Fatal(err)
   238  	}
   239  
   240  	header := soap.Header{Security: signer2}
   241  
   242  	err = session.NewManager(vc2).LoginByToken(vc2.WithHeader(ctx, header))
   243  	if err != nil {
   244  		t.Fatal(err)
   245  	}
   246  }
   247  
   248  func TestIssueBearer(t *testing.T) {
   249  	ctx := context.Background()
   250  	url := os.Getenv("GOVC_TEST_URL")
   251  	if url == "" {
   252  		t.SkipNow()
   253  	}
   254  
   255  	u, err := soap.ParseURL(url)
   256  	if err != nil {
   257  		t.Fatal(err)
   258  	}
   259  
   260  	c, err := vim25.NewClient(ctx, soap.NewClient(u, true))
   261  	if err != nil {
   262  		log.Fatal(err)
   263  	}
   264  	_ = c.UseServiceVersion()
   265  
   266  	if !c.IsVC() {
   267  		t.SkipNow()
   268  	}
   269  
   270  	stsClient, err := sts.NewClient(ctx, c)
   271  	if err != nil {
   272  		t.Fatal(err)
   273  	}
   274  
   275  	// Test that either Certificate or Userinfo is set.
   276  	_, err = stsClient.Issue(ctx, sts.TokenRequest{})
   277  	if err == nil {
   278  		t.Error("expected error")
   279  	}
   280  
   281  	req := sts.TokenRequest{
   282  		Userinfo: u.User,
   283  	}
   284  
   285  	s, err := stsClient.Issue(ctx, req)
   286  	if err != nil {
   287  		t.Fatal(err)
   288  	}
   289  
   290  	header := soap.Header{Security: s}
   291  
   292  	err = session.NewManager(c).LoginByToken(c.WithHeader(ctx, header))
   293  	if err != nil {
   294  		t.Fatal(err)
   295  	}
   296  
   297  	now, err := methods.GetCurrentTime(ctx, c)
   298  	if err != nil {
   299  		t.Fatal(err)
   300  	}
   301  
   302  	log.Printf("current time=%s", now)
   303  }
   304  
   305  func TestIssueActAs(t *testing.T) {
   306  	ctx := context.Background()
   307  	url := os.Getenv("GOVC_TEST_URL")
   308  	if url == "" {
   309  		t.SkipNow()
   310  	}
   311  
   312  	u, err := soap.ParseURL(url)
   313  	if err != nil {
   314  		t.Fatal(err)
   315  	}
   316  
   317  	c, err := vim25.NewClient(ctx, soap.NewClient(u, true))
   318  	if err != nil {
   319  		log.Fatal(err)
   320  	}
   321  	_ = c.UseServiceVersion()
   322  
   323  	if !c.IsVC() {
   324  		t.SkipNow()
   325  	}
   326  
   327  	stsClient, err := sts.NewClient(ctx, c)
   328  	if err != nil {
   329  		t.Fatal(err)
   330  	}
   331  
   332  	if err = solutionUserCreate(ctx, u.User, stsClient, c); err != nil {
   333  		t.Fatal(err)
   334  	}
   335  
   336  	req := sts.TokenRequest{
   337  		Delegatable: true,
   338  		Renewable:   true,
   339  		Userinfo:    u.User,
   340  	}
   341  
   342  	s, err := stsClient.Issue(ctx, req)
   343  	if err != nil {
   344  		t.Fatal(err)
   345  	}
   346  
   347  	req = sts.TokenRequest{
   348  		Lifetime:    24 * time.Hour,
   349  		Token:       s.Token,
   350  		ActAs:       true,
   351  		Delegatable: true,
   352  		Renewable:   true,
   353  		Certificate: solutionUserCert(),
   354  	}
   355  
   356  	s, err = stsClient.Issue(ctx, req)
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  
   361  	header := soap.Header{Security: s}
   362  
   363  	err = session.NewManager(c).LoginByToken(c.WithHeader(ctx, header))
   364  	if err != nil {
   365  		t.Fatal(err)
   366  	}
   367  
   368  	now, err := methods.GetCurrentTime(ctx, c)
   369  	if err != nil {
   370  		t.Fatal(err)
   371  	}
   372  
   373  	t.Logf("current time=%s", now)
   374  
   375  	duration := s.Lifetime.Expires.Sub(s.Lifetime.Created)
   376  	if duration < req.Lifetime {
   377  		req.Lifetime = 24 * time.Hour
   378  		req.Token = s.Token
   379  		log.Printf("extending lifetime from %s", s.Lifetime.Expires.Sub(s.Lifetime.Created))
   380  		s, err = stsClient.Renew(ctx, req)
   381  		if err != nil {
   382  			t.Fatal(err)
   383  		}
   384  	} else {
   385  		t.Errorf("duration=%s", duration)
   386  	}
   387  
   388  	t.Logf("expires in %s", s.Lifetime.Expires.Sub(s.Lifetime.Created))
   389  }
   390  
   391  func TestNewClient(t *testing.T) {
   392  	t.Run("Happy path client creation", func(t *testing.T) {
   393  		simulator.Test(func(ctx context.Context, client *vim25.Client) {
   394  			_, err := sts.NewClient(ctx, client)
   395  			require.NoError(t, err)
   396  		})
   397  	})
   398  
   399  	t.Run("STS client should work with Envoy sidecar even when lookup service is down", func(t *testing.T) {
   400  		model := simulator.VPX()
   401  
   402  		model.Create()
   403  		simulator.Test(func(ctx context.Context, client *vim25.Client) {
   404  			lsim.BreakLookupServiceURLs()
   405  			// Map Envoy sidecar on the same port as the vcsim client.
   406  			os.Setenv("GOVMOMI_ENVOY_SIDECAR_PORT", client.Client.URL().Port())
   407  			os.Setenv("GOVMOMI_ENVOY_SIDECAR_HOST", client.Client.URL().Hostname())
   408  
   409  			c, err := sts.NewClient(ctx, client)
   410  			require.NoError(t, err)
   411  
   412  			req := sts.TokenRequest{
   413  				Userinfo: url.UserPassword("foo", "bar"),
   414  			}
   415  
   416  			_, err = c.Issue(ctx, req)
   417  			require.NoError(t, err)
   418  		}, model)
   419  	})
   420  }