github.com/hernad/nomad@v1.6.112/command/agent/acl_endpoint_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package agent 5 6 import ( 7 "bytes" 8 "fmt" 9 "net/http" 10 "net/http/httptest" 11 "net/url" 12 "testing" 13 "time" 14 15 "github.com/golang-jwt/jwt/v5" 16 capOIDC "github.com/hashicorp/cap/oidc" 17 "github.com/hernad/nomad/ci" 18 "github.com/hernad/nomad/helper/uuid" 19 "github.com/hernad/nomad/nomad/mock" 20 "github.com/hernad/nomad/nomad/structs" 21 "github.com/shoenig/test/must" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 ) 25 26 func TestHTTP_ACLPolicyList(t *testing.T) { 27 ci.Parallel(t) 28 httpACLTest(t, nil, func(s *TestAgent) { 29 p1 := mock.ACLPolicy() 30 p2 := mock.ACLPolicy() 31 p3 := mock.ACLPolicy() 32 args := structs.ACLPolicyUpsertRequest{ 33 Policies: []*structs.ACLPolicy{p1, p2, p3}, 34 WriteRequest: structs.WriteRequest{ 35 Region: "global", 36 AuthToken: s.RootToken.SecretID, 37 }, 38 } 39 var resp structs.GenericResponse 40 if err := s.Agent.RPC("ACL.UpsertPolicies", &args, &resp); err != nil { 41 t.Fatalf("err: %v", err) 42 } 43 44 // Make the HTTP request 45 req, err := http.NewRequest("GET", "/v1/acl/policies", nil) 46 if err != nil { 47 t.Fatalf("err: %v", err) 48 } 49 respW := httptest.NewRecorder() 50 setToken(req, s.RootToken) 51 52 // Make the request 53 obj, err := s.Server.ACLPoliciesRequest(respW, req) 54 if err != nil { 55 t.Fatalf("err: %v", err) 56 } 57 58 // Check for the index 59 if respW.Result().Header.Get("X-Nomad-Index") == "" { 60 t.Fatalf("missing index") 61 } 62 if respW.Result().Header.Get("X-Nomad-KnownLeader") != "true" { 63 t.Fatalf("missing known leader") 64 } 65 if respW.Result().Header.Get("X-Nomad-LastContact") == "" { 66 t.Fatalf("missing last contact") 67 } 68 69 // Check the output 70 n := obj.([]*structs.ACLPolicyListStub) 71 if len(n) != 3 { 72 t.Fatalf("bad: %#v", n) 73 } 74 }) 75 } 76 77 func TestHTTP_ACLPolicyQuery(t *testing.T) { 78 ci.Parallel(t) 79 httpACLTest(t, nil, func(s *TestAgent) { 80 p1 := mock.ACLPolicy() 81 args := structs.ACLPolicyUpsertRequest{ 82 Policies: []*structs.ACLPolicy{p1}, 83 WriteRequest: structs.WriteRequest{ 84 Region: "global", 85 AuthToken: s.RootToken.SecretID, 86 }, 87 } 88 var resp structs.GenericResponse 89 if err := s.Agent.RPC("ACL.UpsertPolicies", &args, &resp); err != nil { 90 t.Fatalf("err: %v", err) 91 } 92 93 // Make the HTTP request 94 req, err := http.NewRequest("GET", "/v1/acl/policy/"+p1.Name, nil) 95 if err != nil { 96 t.Fatalf("err: %v", err) 97 } 98 respW := httptest.NewRecorder() 99 setToken(req, s.RootToken) 100 101 // Make the request 102 obj, err := s.Server.ACLPolicySpecificRequest(respW, req) 103 if err != nil { 104 t.Fatalf("err: %v", err) 105 } 106 107 // Check for the index 108 if respW.Result().Header.Get("X-Nomad-Index") == "" { 109 t.Fatalf("missing index") 110 } 111 if respW.Result().Header.Get("X-Nomad-KnownLeader") != "true" { 112 t.Fatalf("missing known leader") 113 } 114 if respW.Result().Header.Get("X-Nomad-LastContact") == "" { 115 t.Fatalf("missing last contact") 116 } 117 118 // Check the output 119 n := obj.(*structs.ACLPolicy) 120 if n.Name != p1.Name { 121 t.Fatalf("bad: %#v", n) 122 } 123 }) 124 } 125 126 func TestHTTP_ACLPolicyCreate(t *testing.T) { 127 ci.Parallel(t) 128 httpACLTest(t, nil, func(s *TestAgent) { 129 // Make the HTTP request 130 p1 := mock.ACLPolicy() 131 buf := encodeReq(p1) 132 req, err := http.NewRequest("PUT", "/v1/acl/policy/"+p1.Name, buf) 133 must.NoError(t, err) 134 135 respW := httptest.NewRecorder() 136 setToken(req, s.RootToken) 137 138 // Make the request 139 obj, err := s.Server.ACLPolicySpecificRequest(respW, req) 140 must.NoError(t, err) 141 must.Nil(t, obj) 142 143 // Check for the index 144 must.StrNotEqFold(t, "", respW.Result().Header.Get("X-Nomad-Index")) 145 146 // Check policy was created 147 state := s.Agent.server.State() 148 out, err := state.ACLPolicyByName(nil, p1.Name) 149 must.NoError(t, err) 150 must.NotNil(t, out) 151 152 p1.CreateIndex, p1.ModifyIndex = out.CreateIndex, out.ModifyIndex 153 must.Eq(t, p1.Name, out.Name) 154 must.Eq(t, p1, out) 155 156 // Create a policy that is invalid. This ensures we call the validation 157 // func in the RPC handler, also that the correct code and error is 158 // returned. 159 aclPolicy2 := mock.ACLPolicy() 160 aclPolicy2.Rules = "invalid" 161 162 aclPolicy2Req, err := http.NewRequest(http.MethodPut, "/v1/acl/policy/"+aclPolicy2.Name, encodeReq(aclPolicy2)) 163 must.NoError(t, err) 164 165 respW = httptest.NewRecorder() 166 setToken(aclPolicy2Req, s.RootToken) 167 168 // Make the request 169 aclPolicy2Obj, err := s.Server.ACLPolicySpecificRequest(respW, aclPolicy2Req) 170 must.ErrorContains(t, err, "400") 171 must.ErrorContains(t, err, "failed to parse rules") 172 must.Nil(t, aclPolicy2Obj) 173 }) 174 } 175 176 func TestHTTP_ACLPolicyDelete(t *testing.T) { 177 ci.Parallel(t) 178 httpACLTest(t, nil, func(s *TestAgent) { 179 p1 := mock.ACLPolicy() 180 args := structs.ACLPolicyUpsertRequest{ 181 Policies: []*structs.ACLPolicy{p1}, 182 WriteRequest: structs.WriteRequest{ 183 Region: "global", 184 AuthToken: s.RootToken.SecretID, 185 }, 186 } 187 var resp structs.GenericResponse 188 if err := s.Agent.RPC("ACL.UpsertPolicies", &args, &resp); err != nil { 189 t.Fatalf("err: %v", err) 190 } 191 192 // Make the HTTP request 193 req, err := http.NewRequest("DELETE", "/v1/acl/policy/"+p1.Name, nil) 194 if err != nil { 195 t.Fatalf("err: %v", err) 196 } 197 respW := httptest.NewRecorder() 198 setToken(req, s.RootToken) 199 200 // Make the request 201 obj, err := s.Server.ACLPolicySpecificRequest(respW, req) 202 assert.Nil(t, err) 203 assert.Nil(t, obj) 204 205 // Check for the index 206 if respW.Result().Header.Get("X-Nomad-Index") == "" { 207 t.Fatalf("missing index") 208 } 209 210 // Check policy was created 211 state := s.Agent.server.State() 212 out, err := state.ACLPolicyByName(nil, p1.Name) 213 assert.Nil(t, err) 214 assert.Nil(t, out) 215 }) 216 } 217 218 func TestHTTP_ACLTokenBootstrap(t *testing.T) { 219 ci.Parallel(t) 220 conf := func(c *Config) { 221 c.ACL.Enabled = true 222 c.ACL.PolicyTTL = 0 // Special flag to disable auto-bootstrap 223 } 224 httpTest(t, conf, func(s *TestAgent) { 225 // Make the HTTP request 226 req, err := http.NewRequest("PUT", "/v1/acl/bootstrap", nil) 227 if err != nil { 228 t.Fatalf("err: %v", err) 229 } 230 respW := httptest.NewRecorder() 231 232 // Make the request 233 obj, err := s.Server.ACLTokenBootstrap(respW, req) 234 if err != nil { 235 t.Fatalf("err: %v", err) 236 } 237 238 // Check for the index 239 if respW.Result().Header.Get("X-Nomad-Index") == "" { 240 t.Fatalf("missing index") 241 } 242 243 // Check the output 244 n := obj.(*structs.ACLToken) 245 assert.NotNil(t, n) 246 assert.Equal(t, "Bootstrap Token", n.Name) 247 }) 248 } 249 250 func TestHTTP_ACLTokenBootstrapOperator(t *testing.T) { 251 ci.Parallel(t) 252 conf := func(c *Config) { 253 c.ACL.Enabled = true 254 c.ACL.PolicyTTL = 0 // Special flag to disable auto-bootstrap 255 } 256 httpTest(t, conf, func(s *TestAgent) { 257 // Provide token 258 args := structs.ACLTokenBootstrapRequest{ 259 BootstrapSecret: "2b778dd9-f5f1-6f29-b4b4-9a5fa948757a", 260 } 261 262 buf := encodeReq(args) 263 264 // Make the HTTP request 265 req, err := http.NewRequest("PUT", "/v1/acl/bootstrap", buf) 266 if err != nil { 267 t.Fatalf("err: %v", err) 268 } 269 270 // Since we're not actually writing this HTTP request, we have 271 // to manually set ContentLength 272 req.ContentLength = -1 273 274 respW := httptest.NewRecorder() 275 // Make the request 276 obj, err := s.Server.ACLTokenBootstrap(respW, req) 277 if err != nil { 278 t.Fatalf("err: %v", err) 279 } 280 281 // Check for the index 282 if respW.Result().Header.Get("X-Nomad-Index") == "" { 283 t.Fatalf("missing index") 284 } 285 286 // Check the output 287 n := obj.(*structs.ACLToken) 288 assert.NotNil(t, n) 289 assert.Equal(t, args.BootstrapSecret, n.SecretID) 290 }) 291 } 292 293 func TestHTTP_ACLTokenList(t *testing.T) { 294 ci.Parallel(t) 295 httpACLTest(t, nil, func(s *TestAgent) { 296 p1 := mock.ACLToken() 297 p1.AccessorID = "" 298 p2 := mock.ACLToken() 299 p2.AccessorID = "" 300 p3 := mock.ACLToken() 301 p3.AccessorID = "" 302 args := structs.ACLTokenUpsertRequest{ 303 Tokens: []*structs.ACLToken{p1, p2, p3}, 304 WriteRequest: structs.WriteRequest{ 305 Region: "global", 306 AuthToken: s.RootToken.SecretID, 307 }, 308 } 309 var resp structs.ACLTokenUpsertResponse 310 if err := s.Agent.RPC("ACL.UpsertTokens", &args, &resp); err != nil { 311 t.Fatalf("err: %v", err) 312 } 313 314 // Make the HTTP request 315 req, err := http.NewRequest("GET", "/v1/acl/tokens", nil) 316 if err != nil { 317 t.Fatalf("err: %v", err) 318 } 319 respW := httptest.NewRecorder() 320 setToken(req, s.RootToken) 321 322 // Make the request 323 obj, err := s.Server.ACLTokensRequest(respW, req) 324 if err != nil { 325 t.Fatalf("err: %v", err) 326 } 327 328 // Check for the index 329 if respW.Result().Header.Get("X-Nomad-Index") == "" { 330 t.Fatalf("missing index") 331 } 332 if respW.Result().Header.Get("X-Nomad-KnownLeader") != "true" { 333 t.Fatalf("missing known leader") 334 } 335 if respW.Result().Header.Get("X-Nomad-LastContact") == "" { 336 t.Fatalf("missing last contact") 337 } 338 339 // Check the output (includes bootstrap token) 340 n := obj.([]*structs.ACLTokenListStub) 341 if len(n) != 4 { 342 t.Fatalf("bad: %#v", n) 343 } 344 }) 345 } 346 347 func TestHTTP_ACLTokenQuery(t *testing.T) { 348 ci.Parallel(t) 349 httpACLTest(t, nil, func(s *TestAgent) { 350 p1 := mock.ACLToken() 351 p1.AccessorID = "" 352 args := structs.ACLTokenUpsertRequest{ 353 Tokens: []*structs.ACLToken{p1}, 354 WriteRequest: structs.WriteRequest{ 355 Region: "global", 356 AuthToken: s.RootToken.SecretID, 357 }, 358 } 359 var resp structs.ACLTokenUpsertResponse 360 if err := s.Agent.RPC("ACL.UpsertTokens", &args, &resp); err != nil { 361 t.Fatalf("err: %v", err) 362 } 363 out := resp.Tokens[0] 364 365 // Make the HTTP request 366 req, err := http.NewRequest("GET", "/v1/acl/token/"+out.AccessorID, nil) 367 if err != nil { 368 t.Fatalf("err: %v", err) 369 } 370 respW := httptest.NewRecorder() 371 setToken(req, s.RootToken) 372 373 // Make the request 374 obj, err := s.Server.ACLTokenSpecificRequest(respW, req) 375 if err != nil { 376 t.Fatalf("err: %v", err) 377 } 378 379 // Check for the index 380 if respW.Result().Header.Get("X-Nomad-Index") == "" { 381 t.Fatalf("missing index") 382 } 383 if respW.Result().Header.Get("X-Nomad-KnownLeader") != "true" { 384 t.Fatalf("missing known leader") 385 } 386 if respW.Result().Header.Get("X-Nomad-LastContact") == "" { 387 t.Fatalf("missing last contact") 388 } 389 390 // Check the output 391 n := obj.(*structs.ACLToken) 392 assert.Equal(t, out, n) 393 }) 394 } 395 396 func TestHTTP_ACLTokenSelf(t *testing.T) { 397 ci.Parallel(t) 398 httpACLTest(t, nil, func(s *TestAgent) { 399 p1 := mock.ACLToken() 400 p1.AccessorID = "" 401 args := structs.ACLTokenUpsertRequest{ 402 Tokens: []*structs.ACLToken{p1}, 403 WriteRequest: structs.WriteRequest{ 404 Region: "global", 405 AuthToken: s.RootToken.SecretID, 406 }, 407 } 408 var resp structs.ACLTokenUpsertResponse 409 if err := s.Agent.RPC("ACL.UpsertTokens", &args, &resp); err != nil { 410 t.Fatalf("err: %v", err) 411 } 412 out := resp.Tokens[0] 413 414 // Make the HTTP request 415 req, err := http.NewRequest("GET", "/v1/acl/token/self", nil) 416 if err != nil { 417 t.Fatalf("err: %v", err) 418 } 419 respW := httptest.NewRecorder() 420 setToken(req, out) 421 422 // Make the request 423 obj, err := s.Server.ACLTokenSpecificRequest(respW, req) 424 if err != nil { 425 t.Fatalf("err: %v", err) 426 } 427 428 // Check for the index 429 if respW.Result().Header.Get("X-Nomad-Index") == "" { 430 t.Fatalf("missing index") 431 } 432 if respW.Result().Header.Get("X-Nomad-KnownLeader") != "true" { 433 t.Fatalf("missing known leader") 434 } 435 if respW.Result().Header.Get("X-Nomad-LastContact") == "" { 436 t.Fatalf("missing last contact") 437 } 438 439 // Check the output 440 n := obj.(*structs.ACLToken) 441 assert.Equal(t, out, n) 442 }) 443 } 444 445 func TestHTTP_ACLTokenCreate(t *testing.T) { 446 ci.Parallel(t) 447 httpACLTest(t, nil, func(s *TestAgent) { 448 // Make the HTTP request 449 p1 := mock.ACLToken() 450 p1.AccessorID = "" 451 buf := encodeReq(p1) 452 req, err := http.NewRequest("PUT", "/v1/acl/token", buf) 453 if err != nil { 454 t.Fatalf("err: %v", err) 455 } 456 respW := httptest.NewRecorder() 457 setToken(req, s.RootToken) 458 459 // Make the request 460 obj, err := s.Server.ACLTokenSpecificRequest(respW, req) 461 assert.Nil(t, err) 462 assert.NotNil(t, obj) 463 outTK := obj.(*structs.ACLToken) 464 465 // Check for the index 466 if respW.Result().Header.Get("X-Nomad-Index") == "" { 467 t.Fatalf("missing index") 468 } 469 470 // Check token was created 471 state := s.Agent.server.State() 472 out, err := state.ACLTokenByAccessorID(nil, outTK.AccessorID) 473 assert.Nil(t, err) 474 assert.NotNil(t, out) 475 assert.Equal(t, outTK, out) 476 }) 477 } 478 479 func TestHTTP_ACLTokenCreateExpirationTTL(t *testing.T) { 480 ci.Parallel(t) 481 httpACLTest(t, nil, func(s *TestAgent) { 482 483 // Generate an example token which has an expiration TTL in string 484 // format. 485 aclToken := ` 486 { 487 "Name": "Readonly token", 488 "Type": "client", 489 "Policies": ["readonly"], 490 "ExpirationTTL": "10h", 491 "Global": false 492 }` 493 494 req, err := http.NewRequest("PUT", "/v1/acl/token", bytes.NewReader([]byte(aclToken))) 495 must.NoError(t, err) 496 497 respW := httptest.NewRecorder() 498 setToken(req, s.RootToken) 499 500 // Make the request. 501 obj, err := s.Server.ACLTokenSpecificRequest(respW, req) 502 must.NoError(t, err) 503 must.NotNil(t, obj) 504 505 // Ensure the returned token includes expiration. 506 createdTokenResp := obj.(*structs.ACLToken) 507 must.Eq(t, "10h0m0s", createdTokenResp.ExpirationTTL.String()) 508 must.False(t, createdTokenResp.CreateTime.IsZero()) 509 510 // Check for the index. 511 must.StrNotEqFold(t, "", respW.Result().Header.Get("X-Nomad-Index")) 512 513 // Check token was created and stored properly within state. 514 out, err := s.Agent.server.State().ACLTokenByAccessorID(nil, createdTokenResp.AccessorID) 515 must.NoError(t, err) 516 must.NotNil(t, out) 517 must.Eq(t, createdTokenResp, out) 518 }) 519 } 520 521 func TestHTTP_ACLTokenDelete(t *testing.T) { 522 ci.Parallel(t) 523 httpACLTest(t, nil, func(s *TestAgent) { 524 p1 := mock.ACLToken() 525 p1.AccessorID = "" 526 args := structs.ACLTokenUpsertRequest{ 527 Tokens: []*structs.ACLToken{p1}, 528 WriteRequest: structs.WriteRequest{ 529 Region: "global", 530 AuthToken: s.RootToken.SecretID, 531 }, 532 } 533 var resp structs.ACLTokenUpsertResponse 534 if err := s.Agent.RPC("ACL.UpsertTokens", &args, &resp); err != nil { 535 t.Fatalf("err: %v", err) 536 } 537 ID := resp.Tokens[0].AccessorID 538 539 // Make the HTTP request 540 req, err := http.NewRequest("DELETE", "/v1/acl/token/"+ID, nil) 541 if err != nil { 542 t.Fatalf("err: %v", err) 543 } 544 respW := httptest.NewRecorder() 545 setToken(req, s.RootToken) 546 547 // Make the request 548 obj, err := s.Server.ACLTokenSpecificRequest(respW, req) 549 assert.Nil(t, err) 550 assert.Nil(t, obj) 551 552 // Check for the index 553 if respW.Result().Header.Get("X-Nomad-Index") == "" { 554 t.Fatalf("missing index") 555 } 556 557 // Check token was created 558 state := s.Agent.server.State() 559 out, err := state.ACLTokenByAccessorID(nil, ID) 560 assert.Nil(t, err) 561 assert.Nil(t, out) 562 }) 563 } 564 565 func TestHTTP_OneTimeToken(t *testing.T) { 566 ci.Parallel(t) 567 httpACLTest(t, nil, func(s *TestAgent) { 568 569 // Setup the ACL token 570 571 p1 := mock.ACLToken() 572 p1.AccessorID = "" 573 args := structs.ACLTokenUpsertRequest{ 574 Tokens: []*structs.ACLToken{p1}, 575 WriteRequest: structs.WriteRequest{ 576 Region: "global", 577 AuthToken: s.RootToken.SecretID, 578 }, 579 } 580 var resp structs.ACLTokenUpsertResponse 581 err := s.Agent.RPC("ACL.UpsertTokens", &args, &resp) 582 require.NoError(t, err) 583 aclID := resp.Tokens[0].AccessorID 584 aclSecret := resp.Tokens[0].SecretID 585 586 // Make a HTTP request to get a one-time token 587 588 req, err := http.NewRequest("POST", "/v1/acl/token/onetime", nil) 589 require.NoError(t, err) 590 req.Header.Set("X-Nomad-Token", aclSecret) 591 respW := httptest.NewRecorder() 592 593 obj, err := s.Server.UpsertOneTimeToken(respW, req) 594 require.NoError(t, err) 595 require.NotNil(t, obj) 596 597 ott := obj.(structs.OneTimeTokenUpsertResponse) 598 require.Equal(t, aclID, ott.OneTimeToken.AccessorID) 599 require.NotEqual(t, "", ott.OneTimeToken.OneTimeSecretID) 600 601 // Make a HTTP request to exchange that token 602 603 buf := encodeReq(structs.OneTimeTokenExchangeRequest{ 604 OneTimeSecretID: ott.OneTimeToken.OneTimeSecretID}) 605 req, err = http.NewRequest("POST", "/v1/acl/token/onetime/exchange", buf) 606 require.NoError(t, err) 607 respW = httptest.NewRecorder() 608 609 obj, err = s.Server.ExchangeOneTimeToken(respW, req) 610 require.NoError(t, err) 611 require.NotNil(t, obj) 612 613 token := obj.(structs.OneTimeTokenExchangeResponse) 614 require.Equal(t, aclID, token.Token.AccessorID) 615 require.Equal(t, aclSecret, token.Token.SecretID) 616 617 // Making the same request a second time should return an error 618 619 buf = encodeReq(structs.OneTimeTokenExchangeRequest{ 620 OneTimeSecretID: ott.OneTimeToken.OneTimeSecretID}) 621 req, err = http.NewRequest("POST", "/v1/acl/token/onetime/exchange", buf) 622 require.NoError(t, err) 623 respW = httptest.NewRecorder() 624 625 obj, err = s.Server.ExchangeOneTimeToken(respW, req) 626 require.EqualError(t, err, structs.ErrPermissionDenied.Error()) 627 }) 628 } 629 630 func TestHTTPServer_ACLRoleListRequest(t *testing.T) { 631 ci.Parallel(t) 632 633 testCases := []struct { 634 name string 635 testFn func(srv *TestAgent) 636 }{ 637 { 638 name: "no auth token set", 639 testFn: func(srv *TestAgent) { 640 641 // Build the HTTP request. 642 req, err := http.NewRequest(http.MethodGet, "/v1/acl/roles", nil) 643 require.NoError(t, err) 644 respW := httptest.NewRecorder() 645 646 // Send the HTTP request. 647 obj, err := srv.Server.ACLRoleListRequest(respW, req) 648 require.NoError(t, err) 649 require.Empty(t, obj) 650 }, 651 }, 652 { 653 name: "invalid method", 654 testFn: func(srv *TestAgent) { 655 656 // Build the HTTP request. 657 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/roles", nil) 658 require.NoError(t, err) 659 respW := httptest.NewRecorder() 660 661 // Ensure we have a token set. 662 setToken(req, srv.RootToken) 663 664 // Send the HTTP request. 665 obj, err := srv.Server.ACLRoleListRequest(respW, req) 666 require.Error(t, err) 667 require.ErrorContains(t, err, "Invalid method") 668 require.Nil(t, obj) 669 }, 670 }, 671 { 672 name: "no roles in state", 673 testFn: func(srv *TestAgent) { 674 675 // Build the HTTP request. 676 req, err := http.NewRequest(http.MethodGet, "/v1/acl/roles", nil) 677 require.NoError(t, err) 678 respW := httptest.NewRecorder() 679 680 // Ensure we have a token set. 681 setToken(req, srv.RootToken) 682 683 // Send the HTTP request. 684 obj, err := srv.Server.ACLRoleListRequest(respW, req) 685 require.NoError(t, err) 686 require.Empty(t, obj.([]*structs.ACLRoleListStub)) 687 }, 688 }, 689 { 690 name: "roles in state", 691 testFn: func(srv *TestAgent) { 692 693 // Create the policies our ACL roles wants to link to. 694 policy1 := mock.ACLPolicy() 695 policy1.Name = "mocked-test-policy-1" 696 policy2 := mock.ACLPolicy() 697 policy2.Name = "mocked-test-policy-2" 698 699 require.NoError(t, srv.server.State().UpsertACLPolicies( 700 structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2})) 701 702 // Create two ACL roles and put these directly into state. 703 aclRoles := []*structs.ACLRole{mock.ACLRole(), mock.ACLRole()} 704 require.NoError(t, srv.server.State().UpsertACLRoles(structs.MsgTypeTestSetup, 20, aclRoles, false)) 705 706 // Build the HTTP request. 707 req, err := http.NewRequest(http.MethodGet, "/v1/acl/roles", nil) 708 require.NoError(t, err) 709 respW := httptest.NewRecorder() 710 711 // Ensure we have a token set. 712 setToken(req, srv.RootToken) 713 714 // Send the HTTP request. 715 obj, err := srv.Server.ACLRoleListRequest(respW, req) 716 require.NoError(t, err) 717 require.Len(t, obj.([]*structs.ACLRoleListStub), 2) 718 }, 719 }, 720 { 721 name: "roles in state using prefix", 722 testFn: func(srv *TestAgent) { 723 724 // Create the policies our ACL roles wants to link to. 725 policy1 := mock.ACLPolicy() 726 policy1.Name = "mocked-test-policy-1" 727 policy2 := mock.ACLPolicy() 728 policy2.Name = "mocked-test-policy-2" 729 730 require.NoError(t, srv.server.State().UpsertACLPolicies( 731 structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2})) 732 733 // Create two ACL roles and put these directly into state, one 734 // using a custom prefix. 735 aclRoles := []*structs.ACLRole{mock.ACLRole(), mock.ACLRole()} 736 aclRoles[1].ID = "badger-badger-badger-" + uuid.Generate() 737 require.NoError(t, srv.server.State().UpsertACLRoles(structs.MsgTypeTestSetup, 20, aclRoles, false)) 738 739 // Build the HTTP request. 740 req, err := http.NewRequest(http.MethodGet, "/v1/acl/roles?prefix=badger-badger-badger", nil) 741 require.NoError(t, err) 742 respW := httptest.NewRecorder() 743 744 // Ensure we have a token set. 745 setToken(req, srv.RootToken) 746 747 // Send the HTTP request. 748 obj, err := srv.Server.ACLRoleListRequest(respW, req) 749 require.NoError(t, err) 750 require.Len(t, obj.([]*structs.ACLRoleListStub), 1) 751 require.Contains(t, obj.([]*structs.ACLRoleListStub)[0].ID, "badger-badger-badger") 752 }, 753 }, 754 } 755 756 for _, tc := range testCases { 757 t.Run(tc.name, func(t *testing.T) { 758 httpACLTest(t, nil, tc.testFn) 759 }) 760 } 761 } 762 763 func TestHTTPServer_ACLRoleRequest(t *testing.T) { 764 ci.Parallel(t) 765 766 testCases := []struct { 767 name string 768 testFn func(srv *TestAgent) 769 }{ 770 { 771 name: "no auth token set", 772 testFn: func(srv *TestAgent) { 773 774 // Create a mock role to use in the request body. 775 mockACLRole := mock.ACLRole() 776 mockACLRole.ID = "" 777 778 // Build the HTTP request. 779 req, err := http.NewRequest(http.MethodPut, "/v1/acl/role", encodeReq(mockACLRole)) 780 require.NoError(t, err) 781 respW := httptest.NewRecorder() 782 783 // Send the HTTP request. 784 obj, err := srv.Server.ACLRoleRequest(respW, req) 785 require.Error(t, err) 786 require.ErrorContains(t, err, "Permission denied") 787 require.Nil(t, obj) 788 }, 789 }, 790 { 791 name: "invalid method", 792 testFn: func(srv *TestAgent) { 793 794 // Build the HTTP request. 795 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/role", nil) 796 require.NoError(t, err) 797 respW := httptest.NewRecorder() 798 799 // Ensure we have a token set. 800 setToken(req, srv.RootToken) 801 802 // Send the HTTP request. 803 obj, err := srv.Server.ACLRoleRequest(respW, req) 804 require.Error(t, err) 805 require.ErrorContains(t, err, "Invalid method") 806 require.Nil(t, obj) 807 }, 808 }, 809 { 810 name: "successful upsert", 811 testFn: func(srv *TestAgent) { 812 813 // Create the policies our ACL roles wants to link to. 814 policy1 := mock.ACLPolicy() 815 policy1.Name = "mocked-test-policy-1" 816 policy2 := mock.ACLPolicy() 817 policy2.Name = "mocked-test-policy-2" 818 819 require.NoError(t, srv.server.State().UpsertACLPolicies( 820 structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2})) 821 822 // Create a mock role to use in the request body. 823 mockACLRole := mock.ACLRole() 824 mockACLRole.ID = "" 825 826 // Build the HTTP request. 827 req, err := http.NewRequest(http.MethodPut, "/v1/acl/role", encodeReq(mockACLRole)) 828 require.NoError(t, err) 829 respW := httptest.NewRecorder() 830 831 // Ensure we have a token set. 832 setToken(req, srv.RootToken) 833 834 // Send the HTTP request. 835 obj, err := srv.Server.ACLRoleRequest(respW, req) 836 require.NoError(t, err) 837 require.NotNil(t, obj) 838 require.Equal(t, obj.(*structs.ACLRole).Hash, mockACLRole.Hash) 839 }, 840 }, 841 } 842 843 for _, tc := range testCases { 844 t.Run(tc.name, func(t *testing.T) { 845 httpACLTest(t, nil, tc.testFn) 846 }) 847 } 848 } 849 850 func TestHTTPServer_ACLRoleSpecificRequest(t *testing.T) { 851 ci.Parallel(t) 852 853 testCases := []struct { 854 name string 855 testFn func(srv *TestAgent) 856 }{ 857 { 858 name: "invalid URI", 859 testFn: func(srv *TestAgent) { 860 861 // Build the HTTP request. 862 req, err := http.NewRequest(http.MethodGet, "/v1/acl/role/name/this/is/will/not/work", nil) 863 require.NoError(t, err) 864 respW := httptest.NewRecorder() 865 866 // Send the HTTP request. 867 obj, err := srv.Server.ACLRoleSpecificRequest(respW, req) 868 require.Error(t, err) 869 require.ErrorContains(t, err, "invalid URI") 870 require.Nil(t, obj) 871 }, 872 }, 873 { 874 name: "invalid role name lookalike URI", 875 testFn: func(srv *TestAgent) { 876 877 // Build the HTTP request. 878 req, err := http.NewRequest(http.MethodGet, "/v1/acl/role/foobar/rolename", nil) 879 require.NoError(t, err) 880 respW := httptest.NewRecorder() 881 882 // Send the HTTP request. 883 obj, err := srv.Server.ACLRoleSpecificRequest(respW, req) 884 require.Error(t, err) 885 require.ErrorContains(t, err, "invalid URI") 886 require.Nil(t, obj) 887 }, 888 }, 889 { 890 name: "missing role name", 891 testFn: func(srv *TestAgent) { 892 893 // Build the HTTP request. 894 req, err := http.NewRequest(http.MethodGet, "/v1/acl/role/name/", nil) 895 require.NoError(t, err) 896 respW := httptest.NewRecorder() 897 898 // Send the HTTP request. 899 obj, err := srv.Server.ACLRoleSpecificRequest(respW, req) 900 require.Error(t, err) 901 require.ErrorContains(t, err, "missing ACL role name") 902 require.Nil(t, obj) 903 }, 904 }, 905 { 906 name: "missing role ID", 907 testFn: func(srv *TestAgent) { 908 909 // Build the HTTP request. 910 req, err := http.NewRequest(http.MethodGet, "/v1/acl/role/", nil) 911 require.NoError(t, err) 912 respW := httptest.NewRecorder() 913 914 // Send the HTTP request. 915 obj, err := srv.Server.ACLRoleSpecificRequest(respW, req) 916 require.Error(t, err) 917 require.ErrorContains(t, err, "missing ACL role ID") 918 require.Nil(t, obj) 919 }, 920 }, 921 { 922 name: "role name incorrect method", 923 testFn: func(srv *TestAgent) { 924 925 // Build the HTTP request. 926 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/role/name/foobar", nil) 927 require.NoError(t, err) 928 respW := httptest.NewRecorder() 929 930 // Send the HTTP request. 931 obj, err := srv.Server.ACLRoleSpecificRequest(respW, req) 932 require.Error(t, err) 933 require.ErrorContains(t, err, "Invalid method") 934 require.Nil(t, obj) 935 }, 936 }, 937 { 938 name: "role ID incorrect method", 939 testFn: func(srv *TestAgent) { 940 941 // Build the HTTP request. 942 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/role/foobar", nil) 943 require.NoError(t, err) 944 respW := httptest.NewRecorder() 945 946 // Send the HTTP request. 947 obj, err := srv.Server.ACLRoleSpecificRequest(respW, req) 948 require.Error(t, err) 949 require.ErrorContains(t, err, "Invalid method") 950 require.Nil(t, obj) 951 }, 952 }, 953 { 954 name: "get role by name", 955 testFn: func(srv *TestAgent) { 956 957 // Create the policies our ACL roles wants to link to. 958 policy1 := mock.ACLPolicy() 959 policy1.Name = "mocked-test-policy-1" 960 policy2 := mock.ACLPolicy() 961 policy2.Name = "mocked-test-policy-2" 962 963 require.NoError(t, srv.server.State().UpsertACLPolicies( 964 structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2})) 965 966 // Create a mock role and put directly into state. 967 mockACLRole := mock.ACLRole() 968 require.NoError(t, srv.server.State().UpsertACLRoles( 969 structs.MsgTypeTestSetup, 20, []*structs.ACLRole{mockACLRole}, false)) 970 971 url := fmt.Sprintf("/v1/acl/role/name/%s", mockACLRole.Name) 972 973 // Build the HTTP request. 974 req, err := http.NewRequest(http.MethodGet, url, nil) 975 require.NoError(t, err) 976 respW := httptest.NewRecorder() 977 978 // Ensure we have a token set. 979 setToken(req, srv.RootToken) 980 981 // Send the HTTP request. 982 obj, err := srv.Server.ACLRoleSpecificRequest(respW, req) 983 require.NoError(t, err) 984 require.Equal(t, obj.(*structs.ACLRole).Hash, mockACLRole.Hash) 985 }, 986 }, 987 { 988 name: "get, update, and delete role by ID", 989 testFn: func(srv *TestAgent) { 990 991 // Create the policies our ACL roles wants to link to. 992 policy1 := mock.ACLPolicy() 993 policy1.Name = "mocked-test-policy-1" 994 policy2 := mock.ACLPolicy() 995 policy2.Name = "mocked-test-policy-2" 996 997 require.NoError(t, srv.server.State().UpsertACLPolicies( 998 structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2})) 999 1000 // Create a mock role and put directly into state. 1001 mockACLRole := mock.ACLRole() 1002 require.NoError(t, srv.server.State().UpsertACLRoles( 1003 structs.MsgTypeTestSetup, 20, []*structs.ACLRole{mockACLRole}, false)) 1004 1005 url := fmt.Sprintf("/v1/acl/role/%s", mockACLRole.ID) 1006 1007 // Build the HTTP request to read the role using its ID. 1008 req, err := http.NewRequest(http.MethodGet, url, nil) 1009 require.NoError(t, err) 1010 respW := httptest.NewRecorder() 1011 1012 // Ensure we have a token set. 1013 setToken(req, srv.RootToken) 1014 1015 // Send the HTTP request. 1016 obj, err := srv.Server.ACLRoleSpecificRequest(respW, req) 1017 require.NoError(t, err) 1018 require.Equal(t, obj.(*structs.ACLRole).Hash, mockACLRole.Hash) 1019 1020 // Update the role policy list and make the request via the 1021 // HTTP API. 1022 mockACLRole.Policies = []*structs.ACLRolePolicyLink{{Name: "mocked-test-policy-1"}} 1023 1024 req, err = http.NewRequest(http.MethodPost, url, encodeReq(mockACLRole)) 1025 require.NoError(t, err) 1026 respW = httptest.NewRecorder() 1027 1028 // Ensure we have a token set. 1029 setToken(req, srv.RootToken) 1030 1031 // Send the HTTP request. 1032 obj, err = srv.Server.ACLRoleSpecificRequest(respW, req) 1033 require.NoError(t, err) 1034 require.Equal(t, obj.(*structs.ACLRole).Policies, mockACLRole.Policies) 1035 1036 // Delete the ACL role using its ID. 1037 req, err = http.NewRequest(http.MethodDelete, url, nil) 1038 require.NoError(t, err) 1039 respW = httptest.NewRecorder() 1040 1041 // Ensure we have a token set. 1042 setToken(req, srv.RootToken) 1043 1044 // Send the HTTP request. 1045 obj, err = srv.Server.ACLRoleSpecificRequest(respW, req) 1046 require.NoError(t, err) 1047 require.Nil(t, obj) 1048 1049 // Ensure the ACL role is no longer stored within state. 1050 aclRole, err := srv.server.State().GetACLRoleByID(nil, mockACLRole.ID) 1051 require.NoError(t, err) 1052 require.Nil(t, aclRole) 1053 }, 1054 }, 1055 } 1056 1057 for _, tc := range testCases { 1058 t.Run(tc.name, func(t *testing.T) { 1059 httpACLTest(t, nil, tc.testFn) 1060 }) 1061 } 1062 } 1063 1064 func TestHTTPServer_ACLAuthMethodListRequest(t *testing.T) { 1065 ci.Parallel(t) 1066 1067 testCases := []struct { 1068 name string 1069 testFn func(srv *TestAgent) 1070 }{ 1071 { 1072 name: "no auth token set", 1073 testFn: func(srv *TestAgent) { 1074 1075 // Build the HTTP request. 1076 req, err := http.NewRequest(http.MethodGet, "/v1/acl/auth-methods", nil) 1077 must.NoError(t, err) 1078 respW := httptest.NewRecorder() 1079 1080 // Send the HTTP request. 1081 obj, err := srv.Server.ACLAuthMethodListRequest(respW, req) 1082 must.NoError(t, err) 1083 must.Len(t, 0, obj.([]*structs.ACLAuthMethodStub)) 1084 }, 1085 }, 1086 { 1087 name: "invalid method", 1088 testFn: func(srv *TestAgent) { 1089 1090 // Build the HTTP request. 1091 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/auth-methods", nil) 1092 must.NoError(t, err) 1093 respW := httptest.NewRecorder() 1094 1095 // Ensure we have a token set. 1096 setToken(req, srv.RootToken) 1097 1098 // Send the HTTP request. 1099 obj, err := srv.Server.ACLAuthMethodListRequest(respW, req) 1100 must.Error(t, err) 1101 must.StrContains(t, err.Error(), "Invalid method") 1102 must.Nil(t, obj) 1103 }, 1104 }, 1105 { 1106 name: "no auth-methods in state", 1107 testFn: func(srv *TestAgent) { 1108 1109 // Build the HTTP request. 1110 req, err := http.NewRequest(http.MethodGet, "/v1/acl/auth-methods", nil) 1111 must.NoError(t, err) 1112 respW := httptest.NewRecorder() 1113 1114 // Ensure we have a token set. 1115 setToken(req, srv.RootToken) 1116 1117 // Send the HTTP request. 1118 obj, err := srv.Server.ACLAuthMethodListRequest(respW, req) 1119 must.NoError(t, err) 1120 must.Len(t, 0, obj.([]*structs.ACLAuthMethodStub)) 1121 }, 1122 }, 1123 { 1124 name: "auth-methods in state", 1125 testFn: func(srv *TestAgent) { 1126 1127 // Upsert two auth-methods into state. 1128 must.NoError(t, srv.server.State().UpsertACLAuthMethods( 1129 10, []*structs.ACLAuthMethod{mock.ACLOIDCAuthMethod(), mock.ACLOIDCAuthMethod()})) 1130 1131 // Build the HTTP request. 1132 req, err := http.NewRequest(http.MethodGet, "/v1/acl/auth-methods", nil) 1133 must.NoError(t, err) 1134 respW := httptest.NewRecorder() 1135 1136 // Ensure we have a token set. 1137 setToken(req, srv.RootToken) 1138 1139 // Send the HTTP request. 1140 obj, err := srv.Server.ACLAuthMethodListRequest(respW, req) 1141 must.NoError(t, err) 1142 must.Len(t, 2, obj.([]*structs.ACLAuthMethodStub)) 1143 }, 1144 }, 1145 } 1146 1147 for _, tc := range testCases { 1148 t.Run(tc.name, func(t *testing.T) { 1149 httpACLTest(t, nil, tc.testFn) 1150 }) 1151 } 1152 } 1153 1154 func TestHTTPServer_ACLAuthMethodRequest(t *testing.T) { 1155 ci.Parallel(t) 1156 1157 testCases := []struct { 1158 name string 1159 testFn func(srv *TestAgent) 1160 }{ 1161 { 1162 name: "no auth token set", 1163 testFn: func(srv *TestAgent) { 1164 1165 // Create a mock role to use in the request body. 1166 mockACLRole := mock.ACLRole() 1167 mockACLRole.ID = "" 1168 1169 // Build the HTTP request. 1170 req, err := http.NewRequest(http.MethodPut, "/v1/acl/auth-method", encodeReq(mockACLRole)) 1171 must.NoError(t, err) 1172 respW := httptest.NewRecorder() 1173 1174 // Send the HTTP request. 1175 obj, err := srv.Server.ACLAuthMethodRequest(respW, req) 1176 must.Error(t, err) 1177 must.StrContains(t, err.Error(), "Permission denied") 1178 must.Nil(t, obj) 1179 }, 1180 }, 1181 { 1182 name: "invalid method", 1183 testFn: func(srv *TestAgent) { 1184 1185 // Build the HTTP request. 1186 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/auth-method", nil) 1187 must.NoError(t, err) 1188 respW := httptest.NewRecorder() 1189 1190 // Ensure we have a token set. 1191 setToken(req, srv.RootToken) 1192 1193 // Send the HTTP request. 1194 obj, err := srv.Server.ACLAuthMethodRequest(respW, req) 1195 must.Error(t, err) 1196 must.StrContains(t, err.Error(), "Invalid method") 1197 must.Nil(t, obj) 1198 }, 1199 }, 1200 { 1201 name: "successful upsert", 1202 testFn: func(srv *TestAgent) { 1203 1204 // Create a mock auth-method to use in the request body. 1205 mockACLAuthMethod := mock.ACLOIDCAuthMethod() 1206 1207 // Build the HTTP request. 1208 req, err := http.NewRequest(http.MethodPut, "/v1/acl/auth-method", encodeReq(mockACLAuthMethod)) 1209 must.NoError(t, err) 1210 respW := httptest.NewRecorder() 1211 1212 // Ensure we have a token set. 1213 setToken(req, srv.RootToken) 1214 1215 // Send the HTTP request. 1216 obj, err := srv.Server.ACLAuthMethodRequest(respW, req) 1217 must.NoError(t, err) 1218 1219 result := obj.(*structs.ACLAuthMethod) 1220 must.Eq(t, result, mockACLAuthMethod) 1221 }, 1222 }, 1223 } 1224 1225 for _, tc := range testCases { 1226 t.Run(tc.name, func(t *testing.T) { 1227 httpACLTest(t, nil, tc.testFn) 1228 }) 1229 } 1230 } 1231 1232 func TestHTTPServer_ACLAuthMethodSpecificRequest(t *testing.T) { 1233 ci.Parallel(t) 1234 1235 testCases := []struct { 1236 name string 1237 testFn func(srv *TestAgent) 1238 }{ 1239 { 1240 name: "missing auth-method name", 1241 testFn: func(srv *TestAgent) { 1242 1243 // Build the HTTP request. 1244 req, err := http.NewRequest(http.MethodGet, "/v1/acl/auth-method/", nil) 1245 must.NoError(t, err) 1246 respW := httptest.NewRecorder() 1247 1248 // Send the HTTP request. 1249 obj, err := srv.Server.ACLAuthMethodSpecificRequest(respW, req) 1250 must.Error(t, err) 1251 must.StrContains(t, err.Error(), "missing ACL auth-method name") 1252 must.Nil(t, obj) 1253 }, 1254 }, 1255 { 1256 name: "incorrect method", 1257 testFn: func(srv *TestAgent) { 1258 1259 // Build the HTTP request. 1260 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/auth-method/foobar", nil) 1261 must.NoError(t, err) 1262 respW := httptest.NewRecorder() 1263 1264 // Send the HTTP request. 1265 obj, err := srv.Server.ACLAuthMethodSpecificRequest(respW, req) 1266 must.Error(t, err) 1267 must.StrContains(t, err.Error(), "Invalid method") 1268 must.Nil(t, obj) 1269 }, 1270 }, 1271 { 1272 name: "get auth-method", 1273 testFn: func(srv *TestAgent) { 1274 1275 // Create a mock auth-method and put directly into state. 1276 mockACLAuthMethod := mock.ACLOIDCAuthMethod() 1277 must.NoError(t, srv.server.State().UpsertACLAuthMethods( 1278 20, []*structs.ACLAuthMethod{mockACLAuthMethod})) 1279 1280 authMethodURL := "/v1/acl/auth-method/" + mockACLAuthMethod.Name 1281 1282 // Build the HTTP request. 1283 req, err := http.NewRequest(http.MethodGet, authMethodURL, nil) 1284 must.NoError(t, err) 1285 respW := httptest.NewRecorder() 1286 1287 // Ensure we have a token set. 1288 setToken(req, srv.RootToken) 1289 1290 // Send the HTTP request. 1291 obj, err := srv.Server.ACLAuthMethodSpecificRequest(respW, req) 1292 must.NoError(t, err) 1293 must.Eq(t, obj.(*structs.ACLAuthMethod).Hash, mockACLAuthMethod.Hash) 1294 }, 1295 }, 1296 { 1297 name: "get, update, and delete auth-method", 1298 testFn: func(srv *TestAgent) { 1299 1300 // Create a mock auth-method and put directly into state. 1301 mockACLAuthMethod := mock.ACLOIDCAuthMethod() 1302 must.NoError(t, srv.server.State().UpsertACLAuthMethods( 1303 20, []*structs.ACLAuthMethod{mockACLAuthMethod})) 1304 1305 authMethodURL := "/v1/acl/auth-method/" + mockACLAuthMethod.Name 1306 1307 // Build the HTTP request to read the auth-method. 1308 req, err := http.NewRequest(http.MethodGet, authMethodURL, nil) 1309 must.NoError(t, err) 1310 respW := httptest.NewRecorder() 1311 1312 // Ensure we have a token set. 1313 setToken(req, srv.RootToken) 1314 1315 // Send the HTTP request. 1316 obj, err := srv.Server.ACLAuthMethodSpecificRequest(respW, req) 1317 must.NoError(t, err) 1318 must.Eq(t, obj.(*structs.ACLAuthMethod).Hash, mockACLAuthMethod.Hash) 1319 1320 // Update the auth-method and make the request via the HTTP 1321 // API. 1322 mockACLAuthMethod.MaxTokenTTL = 3600 * time.Hour 1323 mockACLAuthMethod.SetHash() 1324 1325 req, err = http.NewRequest(http.MethodPost, authMethodURL, encodeReq(mockACLAuthMethod)) 1326 must.NoError(t, err) 1327 respW = httptest.NewRecorder() 1328 1329 // Ensure we have a token set. 1330 setToken(req, srv.RootToken) 1331 1332 // Send the HTTP request. 1333 _, err = srv.Server.ACLAuthMethodSpecificRequest(respW, req) 1334 must.NoError(t, err) 1335 1336 // Delete the ACL auth-method. 1337 req, err = http.NewRequest(http.MethodDelete, authMethodURL, nil) 1338 must.NoError(t, err) 1339 respW = httptest.NewRecorder() 1340 1341 // Ensure we have a token set. 1342 setToken(req, srv.RootToken) 1343 1344 // Send the HTTP request. 1345 obj, err = srv.Server.ACLAuthMethodSpecificRequest(respW, req) 1346 must.NoError(t, err) 1347 must.Nil(t, obj) 1348 1349 // Ensure the ACL auth-method is no longer stored within state. 1350 aclAuthMethod, err := srv.server.State().GetACLAuthMethodByName(nil, mockACLAuthMethod.Name) 1351 must.NoError(t, err) 1352 must.Nil(t, aclAuthMethod) 1353 }, 1354 }, 1355 } 1356 1357 for _, tc := range testCases { 1358 t.Run(tc.name, func(t *testing.T) { 1359 cb := func(c *Config) { c.NomadConfig.ACLTokenMaxExpirationTTL = 3600 * time.Hour } 1360 httpACLTest(t, cb, tc.testFn) 1361 }) 1362 } 1363 } 1364 1365 func TestHTTPServer_ACLBindingRuleListRequest(t *testing.T) { 1366 ci.Parallel(t) 1367 1368 testCases := []struct { 1369 name string 1370 testFn func(srv *TestAgent) 1371 }{ 1372 { 1373 name: "no auth token set", 1374 testFn: func(srv *TestAgent) { 1375 1376 // Build the HTTP request. 1377 req, err := http.NewRequest(http.MethodGet, "/v1/acl/binding-rules", nil) 1378 must.NoError(t, err) 1379 respW := httptest.NewRecorder() 1380 1381 // Send the HTTP request. 1382 obj, err := srv.Server.ACLBindingRuleListRequest(respW, req) 1383 must.EqError(t, err, "Permission denied") 1384 must.Nil(t, obj) 1385 }, 1386 }, 1387 { 1388 name: "invalid method", 1389 testFn: func(srv *TestAgent) { 1390 1391 // Build the HTTP request. 1392 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/binding-rules", nil) 1393 must.NoError(t, err) 1394 respW := httptest.NewRecorder() 1395 1396 // Ensure we have a token set. 1397 setToken(req, srv.RootToken) 1398 1399 // Send the HTTP request. 1400 obj, err := srv.Server.ACLBindingRuleListRequest(respW, req) 1401 must.EqError(t, err, "Invalid method") 1402 must.Nil(t, obj) 1403 }, 1404 }, 1405 { 1406 name: "no binding rules in state", 1407 testFn: func(srv *TestAgent) { 1408 1409 // Build the HTTP request. 1410 req, err := http.NewRequest(http.MethodGet, "/v1/acl/binding-rules", nil) 1411 must.NoError(t, err) 1412 respW := httptest.NewRecorder() 1413 1414 // Ensure we have a token set. 1415 setToken(req, srv.RootToken) 1416 1417 // Send the HTTP request. 1418 obj, err := srv.Server.ACLBindingRuleListRequest(respW, req) 1419 must.NoError(t, err) 1420 must.Len(t, 0, obj.([]*structs.ACLBindingRuleListStub)) 1421 }, 1422 }, 1423 { 1424 name: "binding rules in state", 1425 testFn: func(srv *TestAgent) { 1426 1427 // Upsert two binding rules into state. 1428 must.NoError(t, srv.server.State().UpsertACLBindingRules( 1429 10, []*structs.ACLBindingRule{mock.ACLBindingRule(), mock.ACLBindingRule()}, true)) 1430 1431 // Build the HTTP request. 1432 req, err := http.NewRequest(http.MethodGet, "/v1/acl/binding-rules", nil) 1433 must.NoError(t, err) 1434 respW := httptest.NewRecorder() 1435 1436 // Ensure we have a token set. 1437 setToken(req, srv.RootToken) 1438 1439 // Send the HTTP request. 1440 obj, err := srv.Server.ACLBindingRuleListRequest(respW, req) 1441 must.NoError(t, err) 1442 must.Len(t, 2, obj.([]*structs.ACLBindingRuleListStub)) 1443 }, 1444 }, 1445 } 1446 1447 for _, tc := range testCases { 1448 t.Run(tc.name, func(t *testing.T) { 1449 httpACLTest(t, nil, tc.testFn) 1450 }) 1451 } 1452 } 1453 1454 func TestHTTPServer_ACLBindingRuleRequest(t *testing.T) { 1455 ci.Parallel(t) 1456 1457 testCases := []struct { 1458 name string 1459 testFn func(srv *TestAgent) 1460 }{ 1461 { 1462 name: "no auth token set", 1463 testFn: func(srv *TestAgent) { 1464 1465 // Create a mock binding rule to use in the request body. 1466 mockACLBindingRule := mock.ACLBindingRule() 1467 mockACLBindingRule.ID = "" 1468 1469 // Build the HTTP request. 1470 req, err := http.NewRequest(http.MethodPut, "/v1/acl/binding-rule", encodeReq(mockACLBindingRule)) 1471 must.NoError(t, err) 1472 respW := httptest.NewRecorder() 1473 1474 // Send the HTTP request. 1475 obj, err := srv.Server.ACLBindingRuleRequest(respW, req) 1476 must.Error(t, err) 1477 must.StrContains(t, err.Error(), "Permission denied") 1478 must.Nil(t, obj) 1479 }, 1480 }, 1481 { 1482 name: "invalid method", 1483 testFn: func(srv *TestAgent) { 1484 1485 // Build the HTTP request. 1486 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/binding-rule", nil) 1487 must.NoError(t, err) 1488 respW := httptest.NewRecorder() 1489 1490 // Ensure we have a token set. 1491 setToken(req, srv.RootToken) 1492 1493 // Send the HTTP request. 1494 obj, err := srv.Server.ACLBindingRuleRequest(respW, req) 1495 must.Error(t, err) 1496 must.StrContains(t, err.Error(), "Invalid method") 1497 must.Nil(t, obj) 1498 }, 1499 }, 1500 { 1501 name: "successful upsert", 1502 testFn: func(srv *TestAgent) { 1503 1504 // Upsert the auth method that the binding rule will associate 1505 // with. 1506 mockACLAuthMethod := mock.ACLOIDCAuthMethod() 1507 must.NoError(t, srv.server.State().UpsertACLAuthMethods( 1508 10, []*structs.ACLAuthMethod{mockACLAuthMethod})) 1509 1510 // Create a mock binding rule to use in the request body. 1511 mockACLBindingRule := mock.ACLBindingRule() 1512 mockACLBindingRule.AuthMethod = mockACLAuthMethod.Name 1513 mockACLBindingRule.ID = "" 1514 1515 // Build the HTTP request. 1516 req, err := http.NewRequest(http.MethodPut, "/v1/acl/binding-rule", encodeReq(mockACLBindingRule)) 1517 must.NoError(t, err) 1518 respW := httptest.NewRecorder() 1519 1520 // Ensure we have a token set. 1521 setToken(req, srv.RootToken) 1522 1523 // Send the HTTP request. 1524 obj, err := srv.Server.ACLBindingRuleRequest(respW, req) 1525 must.NoError(t, err) 1526 1527 result := obj.(*structs.ACLBindingRule) 1528 must.Eq(t, mockACLBindingRule.Selector, result.Selector) 1529 must.Eq(t, mockACLBindingRule.AuthMethod, result.AuthMethod) 1530 must.Eq(t, mockACLBindingRule.Description, result.Description) 1531 }, 1532 }, 1533 } 1534 1535 for _, tc := range testCases { 1536 t.Run(tc.name, func(t *testing.T) { 1537 httpACLTest(t, nil, tc.testFn) 1538 }) 1539 } 1540 } 1541 1542 func TestHTTPServer_ACLBindingRuleSpecificRequest(t *testing.T) { 1543 ci.Parallel(t) 1544 1545 testCases := []struct { 1546 name string 1547 testFn func(srv *TestAgent) 1548 }{ 1549 { 1550 name: "missing binding rule ID", 1551 testFn: func(srv *TestAgent) { 1552 1553 // Build the HTTP request. 1554 req, err := http.NewRequest(http.MethodGet, "/v1/acl/binding-rule/", nil) 1555 must.NoError(t, err) 1556 respW := httptest.NewRecorder() 1557 1558 // Send the HTTP request. 1559 obj, err := srv.Server.ACLBindingRuleSpecificRequest(respW, req) 1560 must.EqError(t, err, "missing ACL binding rule ID") 1561 must.Nil(t, obj) 1562 }, 1563 }, 1564 { 1565 name: "incorrect method", 1566 testFn: func(srv *TestAgent) { 1567 1568 // Build the HTTP request. 1569 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/binding-rule/foobar", nil) 1570 must.NoError(t, err) 1571 respW := httptest.NewRecorder() 1572 1573 // Send the HTTP request. 1574 obj, err := srv.Server.ACLBindingRuleSpecificRequest(respW, req) 1575 must.EqError(t, err, "Invalid method") 1576 must.Nil(t, obj) 1577 }, 1578 }, 1579 { 1580 name: "get binding rule", 1581 testFn: func(srv *TestAgent) { 1582 1583 // Upsert a binding rule into state. 1584 aclBindingRules := []*structs.ACLBindingRule{mock.ACLBindingRule()} 1585 must.NoError(t, srv.server.State().UpsertACLBindingRules(10, aclBindingRules, true)) 1586 1587 url := "/v1/acl/binding-rule/" + aclBindingRules[0].ID 1588 1589 // Build the HTTP request. 1590 req, err := http.NewRequest(http.MethodGet, url, nil) 1591 must.NoError(t, err) 1592 respW := httptest.NewRecorder() 1593 1594 // Ensure we have a token set. 1595 setToken(req, srv.RootToken) 1596 1597 // Send the HTTP request. 1598 obj, err := srv.Server.ACLBindingRuleSpecificRequest(respW, req) 1599 must.NoError(t, err) 1600 1601 result := obj.(*structs.ACLBindingRule) 1602 must.Eq(t, aclBindingRules[0].ID, result.ID) 1603 must.Eq(t, aclBindingRules[0].Selector, result.Selector) 1604 must.Eq(t, aclBindingRules[0].AuthMethod, result.AuthMethod) 1605 must.Eq(t, aclBindingRules[0].Description, result.Description) 1606 }, 1607 }, 1608 { 1609 name: "get, update, and delete binding rule", 1610 testFn: func(srv *TestAgent) { 1611 1612 // Upsert the auth method that the binding rule will associate 1613 // with. 1614 mockACLAuthMethod := mock.ACLOIDCAuthMethod() 1615 must.NoError(t, srv.server.State().UpsertACLAuthMethods( 1616 10, []*structs.ACLAuthMethod{mockACLAuthMethod})) 1617 1618 // Upsert a binding rule into state. 1619 mockedAclBindingRule := mock.ACLBindingRule() 1620 mockedAclBindingRule.AuthMethod = mockACLAuthMethod.Name 1621 1622 must.NoError(t, srv.server.State().UpsertACLBindingRules( 1623 10, []*structs.ACLBindingRule{mockedAclBindingRule}, true)) 1624 1625 url := "/v1/acl/binding-rule/" + mockedAclBindingRule.ID 1626 1627 // Build the HTTP request to read the binding rule. 1628 req, err := http.NewRequest(http.MethodGet, url, nil) 1629 must.NoError(t, err) 1630 respW := httptest.NewRecorder() 1631 1632 // Ensure we have a token set. 1633 setToken(req, srv.RootToken) 1634 1635 // Send the HTTP request. 1636 obj, err := srv.Server.ACLBindingRuleSpecificRequest(respW, req) 1637 must.NoError(t, err) 1638 1639 result := obj.(*structs.ACLBindingRule) 1640 must.Eq(t, mockedAclBindingRule.ID, result.ID) 1641 must.Eq(t, mockedAclBindingRule.Selector, result.Selector) 1642 must.Eq(t, mockedAclBindingRule.AuthMethod, result.AuthMethod) 1643 must.Eq(t, mockedAclBindingRule.Description, result.Description) 1644 1645 // Update the binding rule and make the request via the HTTP 1646 // API. 1647 result.Description = "new description" 1648 1649 req, err = http.NewRequest(http.MethodPost, url, encodeReq(result)) 1650 must.NoError(t, err) 1651 respW = httptest.NewRecorder() 1652 1653 // Ensure we have a token set. 1654 setToken(req, srv.RootToken) 1655 1656 // Send the HTTP request. 1657 _, err = srv.Server.ACLBindingRuleSpecificRequest(respW, req) 1658 must.NoError(t, err) 1659 1660 // Delete the ACL binding rule. 1661 req, err = http.NewRequest(http.MethodDelete, url, nil) 1662 must.NoError(t, err) 1663 respW = httptest.NewRecorder() 1664 1665 // Ensure we have a token set. 1666 setToken(req, srv.RootToken) 1667 1668 // Send the HTTP request. 1669 obj, err = srv.Server.ACLBindingRuleSpecificRequest(respW, req) 1670 must.NoError(t, err) 1671 must.Nil(t, obj) 1672 1673 // Ensure the ACL binding rule is no longer stored within 1674 // state. 1675 aclBindingRule, err := srv.server.State().GetACLBindingRule(nil, mockedAclBindingRule.ID) 1676 must.NoError(t, err) 1677 must.Nil(t, aclBindingRule) 1678 }, 1679 }, 1680 } 1681 1682 for _, tc := range testCases { 1683 t.Run(tc.name, func(t *testing.T) { 1684 cb := func(c *Config) { c.NomadConfig.ACLTokenMaxExpirationTTL = 3600 * time.Hour } 1685 httpACLTest(t, cb, tc.testFn) 1686 }) 1687 } 1688 } 1689 1690 func TestHTTPServer_ACLOIDCAuthURLRequest(t *testing.T) { 1691 ci.Parallel(t) 1692 1693 testCases := []struct { 1694 name string 1695 testFn func(srv *TestAgent) 1696 }{ 1697 { 1698 name: "incorrect method", 1699 testFn: func(testAgent *TestAgent) { 1700 1701 // Build the HTTP request. 1702 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/oidc/auth-url", nil) 1703 must.NoError(t, err) 1704 respW := httptest.NewRecorder() 1705 1706 // Send the HTTP request. 1707 obj, err := testAgent.Server.ACLOIDCAuthURLRequest(respW, req) 1708 must.Error(t, err) 1709 must.StrContains(t, err.Error(), "Invalid method") 1710 must.Nil(t, obj) 1711 }, 1712 }, 1713 { 1714 name: "success", 1715 testFn: func(testAgent *TestAgent) { 1716 1717 // Set up the test OIDC provider. 1718 oidcTestProvider := capOIDC.StartTestProvider(t) 1719 defer oidcTestProvider.Stop() 1720 1721 // Generate and upsert an ACL auth method for use. Certain values must be 1722 // taken from the cap OIDC provider just like real world use. 1723 mockedAuthMethod := mock.ACLOIDCAuthMethod() 1724 mockedAuthMethod.Config.AllowedRedirectURIs = []string{"http://127.0.0.1:4649/oidc/callback"} 1725 mockedAuthMethod.Config.OIDCDiscoveryURL = oidcTestProvider.Addr() 1726 mockedAuthMethod.Config.SigningAlgs = []string{"ES256"} 1727 mockedAuthMethod.Config.DiscoveryCaPem = []string{oidcTestProvider.CACert()} 1728 1729 must.NoError(t, testAgent.server.State().UpsertACLAuthMethods( 1730 10, []*structs.ACLAuthMethod{mockedAuthMethod})) 1731 1732 // Generate the request body. 1733 requestBody := structs.ACLOIDCAuthURLRequest{ 1734 AuthMethodName: mockedAuthMethod.Name, 1735 RedirectURI: mockedAuthMethod.Config.AllowedRedirectURIs[0], 1736 ClientNonce: "fpSPuaodKevKfDU3IeXa", 1737 WriteRequest: structs.WriteRequest{ 1738 Region: "global", 1739 }, 1740 } 1741 1742 // Build the HTTP request. 1743 req, err := http.NewRequest(http.MethodPost, "/v1/acl/oidc/auth-url", encodeReq(&requestBody)) 1744 must.NoError(t, err) 1745 respW := httptest.NewRecorder() 1746 1747 // Send the HTTP request. 1748 obj, err := testAgent.Server.ACLOIDCAuthURLRequest(respW, req) 1749 must.NoError(t, err) 1750 1751 // The response URL comes encoded, so decode this and check we have each 1752 // component we expect. 1753 escapedURL, err := url.PathUnescape(obj.(structs.ACLOIDCAuthURLResponse).AuthURL) 1754 must.NoError(t, err) 1755 must.StrContains(t, escapedURL, "/authorize?client_id=mock") 1756 must.StrContains(t, escapedURL, "&nonce=fpSPuaodKevKfDU3IeXa") 1757 must.StrContains(t, escapedURL, "&redirect_uri=http://127.0.0.1:4649/oidc/callback") 1758 must.StrContains(t, escapedURL, "&response_type=code") 1759 must.StrContains(t, escapedURL, "&scope=openid") 1760 must.StrContains(t, escapedURL, "&state=st_") 1761 }, 1762 }, 1763 } 1764 1765 for _, tc := range testCases { 1766 t.Run(tc.name, func(t *testing.T) { 1767 httpACLTest(t, nil, tc.testFn) 1768 }) 1769 } 1770 } 1771 1772 func TestHTTPServer_ACLOIDCCompleteAuthRequest(t *testing.T) { 1773 ci.Parallel(t) 1774 1775 testCases := []struct { 1776 name string 1777 testFn func(srv *TestAgent) 1778 }{ 1779 { 1780 name: "incorrect method", 1781 testFn: func(testAgent *TestAgent) { 1782 1783 // Build the HTTP request. 1784 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/oidc/complete-auth", nil) 1785 must.NoError(t, err) 1786 respW := httptest.NewRecorder() 1787 1788 // Send the HTTP request. 1789 obj, err := testAgent.Server.ACLOIDCCompleteAuthRequest(respW, req) 1790 must.Error(t, err) 1791 must.StrContains(t, err.Error(), "Invalid method") 1792 must.Nil(t, obj) 1793 }, 1794 }, 1795 { 1796 name: "success", 1797 testFn: func(testAgent *TestAgent) { 1798 1799 // Set up the test OIDC provider. 1800 oidcTestProvider := capOIDC.StartTestProvider(t) 1801 defer oidcTestProvider.Stop() 1802 oidcTestProvider.SetAllowedRedirectURIs([]string{"http://127.0.0.1:4649/oidc/callback"}) 1803 1804 // Generate and upsert an ACL auth method for use. Certain values must be 1805 // taken from the cap OIDC provider just like real world use. 1806 mockedAuthMethod := mock.ACLOIDCAuthMethod() 1807 mockedAuthMethod.Config.BoundAudiences = []string{"mock"} 1808 mockedAuthMethod.Config.AllowedRedirectURIs = []string{"http://127.0.0.1:4649/oidc/callback"} 1809 mockedAuthMethod.Config.OIDCDiscoveryURL = oidcTestProvider.Addr() 1810 mockedAuthMethod.Config.SigningAlgs = []string{"ES256"} 1811 mockedAuthMethod.Config.DiscoveryCaPem = []string{oidcTestProvider.CACert()} 1812 mockedAuthMethod.Config.ClaimMappings = map[string]string{} 1813 mockedAuthMethod.Config.ListClaimMappings = map[string]string{ 1814 "http://nomad.internal/roles": "roles", 1815 "http://nomad.internal/policies": "policies", 1816 } 1817 1818 must.NoError(t, testAgent.server.State().UpsertACLAuthMethods( 1819 10, []*structs.ACLAuthMethod{mockedAuthMethod})) 1820 1821 // Set our custom data and some expected values, so we can make the RPC and 1822 // use the test provider. 1823 oidcTestProvider.SetExpectedAuthNonce("fpSPuaodKevKfDU3IeXa") 1824 oidcTestProvider.SetExpectedAuthCode("codeABC") 1825 oidcTestProvider.SetCustomAudience("mock") 1826 oidcTestProvider.SetExpectedState("st_someweirdstateid") 1827 oidcTestProvider.SetCustomClaims(map[string]interface{}{ 1828 "azp": "mock", 1829 "http://nomad.internal/policies": []string{"engineering"}, 1830 "http://nomad.internal/roles": []string{"engineering"}, 1831 }) 1832 1833 // Generate the request body. 1834 requestBody := structs.ACLOIDCCompleteAuthRequest{ 1835 AuthMethodName: mockedAuthMethod.Name, 1836 ClientNonce: "fpSPuaodKevKfDU3IeXa", 1837 State: "st_someweirdstateid", 1838 Code: "codeABC", 1839 RedirectURI: mockedAuthMethod.Config.AllowedRedirectURIs[0], 1840 WriteRequest: structs.WriteRequest{ 1841 Region: "global", 1842 }, 1843 } 1844 1845 // Build the HTTP request. 1846 req, err := http.NewRequest(http.MethodPost, "/v1/acl/oidc/complete-auth", encodeReq(&requestBody)) 1847 must.NoError(t, err) 1848 respW := httptest.NewRecorder() 1849 1850 // Send the HTTP request. 1851 _, err = testAgent.Server.ACLOIDCCompleteAuthRequest(respW, req) 1852 must.ErrorContains(t, err, "no role or policy bindings matched") 1853 1854 // Upsert an ACL policy and role, so that we can reference this within our 1855 // OIDC claims. 1856 mockACLPolicy := mock.ACLPolicy() 1857 must.NoError(t, testAgent.server.State().UpsertACLPolicies( 1858 structs.MsgTypeTestSetup, 20, []*structs.ACLPolicy{mockACLPolicy})) 1859 1860 mockACLRole := mock.ACLRole() 1861 mockACLRole.Policies = []*structs.ACLRolePolicyLink{{Name: mockACLPolicy.Name}} 1862 must.NoError(t, testAgent.server.State().UpsertACLRoles( 1863 structs.MsgTypeTestSetup, 30, []*structs.ACLRole{mockACLRole}, true)) 1864 1865 // Generate and upsert two binding rules, so we can test both ACL Policy 1866 // and Role claim mapping. 1867 mockBindingRule1 := mock.ACLBindingRule() 1868 mockBindingRule1.AuthMethod = mockedAuthMethod.Name 1869 mockBindingRule1.BindType = structs.ACLBindingRuleBindTypePolicy 1870 mockBindingRule1.Selector = "engineering in list.policies" 1871 mockBindingRule1.BindName = mockACLPolicy.Name 1872 1873 mockBindingRule2 := mock.ACLBindingRule() 1874 mockBindingRule2.AuthMethod = mockedAuthMethod.Name 1875 mockBindingRule2.BindName = mockACLRole.Name 1876 1877 must.NoError(t, testAgent.server.State().UpsertACLBindingRules( 1878 40, []*structs.ACLBindingRule{mockBindingRule1, mockBindingRule2}, true)) 1879 1880 // Build the HTTP request. 1881 req, err = http.NewRequest(http.MethodPost, "/v1/acl/oidc/complete-auth", encodeReq(&requestBody)) 1882 must.NoError(t, err) 1883 respW = httptest.NewRecorder() 1884 1885 // Send the HTTP request. 1886 obj, err := testAgent.Server.ACLOIDCCompleteAuthRequest(respW, req) 1887 must.NoError(t, err) 1888 1889 aclTokenResp, ok := obj.(*structs.ACLToken) 1890 must.True(t, ok) 1891 must.NotNil(t, aclTokenResp) 1892 must.Len(t, 1, aclTokenResp.Policies) 1893 must.Eq(t, mockACLPolicy.Name, aclTokenResp.Policies[0]) 1894 must.Len(t, 1, aclTokenResp.Roles) 1895 must.Eq(t, mockACLRole.Name, aclTokenResp.Roles[0].Name) 1896 must.Eq(t, mockACLRole.ID, aclTokenResp.Roles[0].ID) 1897 }, 1898 }, 1899 } 1900 1901 for _, tc := range testCases { 1902 t.Run(tc.name, func(t *testing.T) { 1903 httpACLTest(t, nil, tc.testFn) 1904 }) 1905 } 1906 } 1907 1908 func TestHTTPServer_ACLLoginRequest(t *testing.T) { 1909 ci.Parallel(t) 1910 1911 testCases := []struct { 1912 name string 1913 testFn func(srv *TestAgent) 1914 }{ 1915 { 1916 name: "incorrect method", 1917 testFn: func(testAgent *TestAgent) { 1918 1919 // Build the HTTP request. 1920 req, err := http.NewRequest(http.MethodConnect, "/v1/acl/login", nil) 1921 must.NoError(t, err) 1922 respW := httptest.NewRecorder() 1923 1924 // Send the HTTP request. 1925 obj, err := testAgent.Server.ACLOIDCCompleteAuthRequest(respW, req) 1926 must.Error(t, err) 1927 must.StrContains(t, err.Error(), "Invalid method") 1928 must.Nil(t, obj) 1929 }, 1930 }, 1931 { 1932 name: "success", 1933 testFn: func(testAgent *TestAgent) { 1934 1935 // Generate a sample JWT 1936 iat := time.Now().Unix() 1937 nbf := time.Now().Unix() 1938 exp := time.Now().Add(time.Hour).Unix() 1939 claims := jwt.MapClaims{ 1940 "iss": "nomad test suite", 1941 "iat": iat, 1942 "nbf": nbf, 1943 "exp": exp, 1944 "aud": "engineering", 1945 "http://nomad.internal/policies": []string{"engineering"}, 1946 "http://nomad.internal/roles": []string{"engineering"}, 1947 } 1948 1949 token, pubKey, err := mock.SampleJWTokenWithKeys(claims, nil) 1950 must.NoError(t, err) 1951 1952 // Generate and upsert a JWT ACL auth method for use. 1953 mockedAuthMethod := mock.ACLJWTAuthMethod() 1954 mockedAuthMethod.Config.BoundAudiences = []string{"engineering"} 1955 mockedAuthMethod.Config.JWTValidationPubKeys = []string{pubKey} 1956 mockedAuthMethod.Config.BoundIssuer = []string{"nomad test suite"} 1957 mockedAuthMethod.Config.ExpirationLeeway = time.Duration(3600) 1958 mockedAuthMethod.Config.ClockSkewLeeway = time.Duration(3600) 1959 mockedAuthMethod.Config.ClaimMappings = map[string]string{} 1960 mockedAuthMethod.Config.ListClaimMappings = map[string]string{ 1961 "http://nomad.internal/roles": "roles", 1962 "http://nomad.internal/policies": "policies", 1963 } 1964 1965 must.NoError(t, testAgent.server.State().UpsertACLAuthMethods( 1966 10, []*structs.ACLAuthMethod{mockedAuthMethod})) 1967 1968 // Generate the request body. 1969 requestBody := structs.ACLLoginRequest{ 1970 AuthMethodName: mockedAuthMethod.Name, 1971 LoginToken: token, 1972 WriteRequest: structs.WriteRequest{ 1973 Region: "global", 1974 }, 1975 } 1976 1977 // Build the HTTP request. 1978 req, err := http.NewRequest(http.MethodPost, "/v1/acl/login", encodeReq(&requestBody)) 1979 must.NoError(t, err) 1980 respW := httptest.NewRecorder() 1981 1982 // Send the HTTP request. 1983 _, err = testAgent.Server.ACLLoginRequest(respW, req) 1984 must.ErrorContains(t, err, "no role or policy bindings matched") 1985 1986 // Upsert an ACL policy and role, so that we can reference this within our 1987 // OIDC claims. 1988 mockACLPolicy := mock.ACLPolicy() 1989 must.NoError(t, testAgent.server.State().UpsertACLPolicies( 1990 structs.MsgTypeTestSetup, 20, []*structs.ACLPolicy{mockACLPolicy})) 1991 1992 mockACLRole := mock.ACLRole() 1993 mockACLRole.Policies = []*structs.ACLRolePolicyLink{{Name: mockACLPolicy.Name}} 1994 must.NoError(t, testAgent.server.State().UpsertACLRoles( 1995 structs.MsgTypeTestSetup, 30, []*structs.ACLRole{mockACLRole}, true)) 1996 1997 // Generate and upsert two binding rules, so we can test both ACL Policy 1998 // and Role claim mapping. 1999 mockBindingRule1 := mock.ACLBindingRule() 2000 mockBindingRule1.AuthMethod = mockedAuthMethod.Name 2001 mockBindingRule1.BindType = structs.ACLBindingRuleBindTypePolicy 2002 mockBindingRule1.Selector = "engineering in list.policies" 2003 mockBindingRule1.BindName = mockACLPolicy.Name 2004 2005 mockBindingRule2 := mock.ACLBindingRule() 2006 mockBindingRule2.AuthMethod = mockedAuthMethod.Name 2007 mockBindingRule2.BindName = mockACLRole.Name 2008 2009 must.NoError(t, testAgent.server.State().UpsertACLBindingRules( 2010 40, []*structs.ACLBindingRule{mockBindingRule1, mockBindingRule2}, true)) 2011 2012 // Build the HTTP request. 2013 req, err = http.NewRequest(http.MethodPost, "/v1/acl/login", encodeReq(&requestBody)) 2014 must.NoError(t, err) 2015 respW = httptest.NewRecorder() 2016 2017 // Send the HTTP request. 2018 obj, err := testAgent.Server.ACLLoginRequest(respW, req) 2019 must.NoError(t, err) 2020 2021 aclTokenResp, ok := obj.(*structs.ACLToken) 2022 must.True(t, ok) 2023 must.NotNil(t, aclTokenResp) 2024 must.Len(t, 1, aclTokenResp.Policies) 2025 must.Eq(t, mockACLPolicy.Name, aclTokenResp.Policies[0]) 2026 must.Len(t, 1, aclTokenResp.Roles) 2027 must.Eq(t, mockACLRole.Name, aclTokenResp.Roles[0].Name) 2028 must.Eq(t, mockACLRole.ID, aclTokenResp.Roles[0].ID) 2029 }, 2030 }, 2031 } 2032 2033 for _, tc := range testCases { 2034 t.Run(tc.name, func(t *testing.T) { 2035 httpACLTest(t, nil, tc.testFn) 2036 }) 2037 } 2038 }