github.com/vmware/govmomi@v0.37.1/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 }