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 }