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

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