github.com/avenga/couper@v1.12.2/accesscontrol/jwt_test.go (about) 1 package accesscontrol_test 2 3 import ( 4 "context" 5 "crypto/rand" 6 "crypto/rsa" 7 "crypto/x509" 8 "encoding/pem" 9 "fmt" 10 "net/http" 11 "net/http/httptest" 12 "reflect" 13 "regexp" 14 "strings" 15 "testing" 16 "time" 17 18 "github.com/golang-jwt/jwt/v4" 19 "github.com/hashicorp/hcl/v2" 20 "github.com/hashicorp/hcl/v2/hcltest" 21 "github.com/sirupsen/logrus" 22 "github.com/zclconf/go-cty/cty" 23 24 ac "github.com/avenga/couper/accesscontrol" 25 acjwt "github.com/avenga/couper/accesscontrol/jwt" 26 "github.com/avenga/couper/cache" 27 "github.com/avenga/couper/config/configload" 28 "github.com/avenga/couper/config/reader" 29 "github.com/avenga/couper/config/request" 30 "github.com/avenga/couper/config/runtime" 31 "github.com/avenga/couper/errors" 32 "github.com/avenga/couper/eval" 33 "github.com/avenga/couper/internal/test" 34 ) 35 36 func Test_JWT_NewJWT_RSA(t *testing.T) { 37 helper := test.New(t) 38 39 type fields struct { 40 algorithm string 41 claims hcl.Expression 42 claimsRequired []string 43 pubKey []byte 44 pubKeyPath string 45 } 46 47 privKey, err := rsa.GenerateKey(rand.Reader, 2048) 48 helper.Must(err) 49 50 bytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey) 51 helper.Must(err) 52 53 pubKeyBytesPKIX := pem.EncodeToMemory(&pem.Block{ 54 Type: "PUBLIC KEY", 55 Bytes: bytes, 56 }) 57 pubKeyBytesPKCS1 := pem.EncodeToMemory(&pem.Block{ 58 Type: "RSA PUBLIC KEY", 59 Bytes: x509.MarshalPKCS1PublicKey(&privKey.PublicKey), 60 }) 61 privKeyBytes := pem.EncodeToMemory(&pem.Block{ 62 Type: "RSA PRIVATE KEY", 63 Bytes: x509.MarshalPKCS1PrivateKey(privKey), 64 }) 65 // created using 66 // openssl req -new -newkey rsa:1024 -days 100000 -nodes -x509 67 certBytes := []byte(`-----BEGIN CERTIFICATE----- 68 MIICaDCCAdGgAwIBAgIUZe+V/eBcYEaoORX8mfsyR8LqY/kwDQYJKoZIhvcNAQEL 69 BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 70 GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMTA0MTIxMzI1MzRaGA8yMjk1 71 MDEyNjEzMjUzNFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx 72 ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0B 73 AQEFAAOBjQAwgYkCgYEA2m79uRP+f/L6YgCuQoAiY6Qs5pccKR4DNfb+vQOsO+xx 74 ZxWrY3RLSLOYKCBybHClz0JLT61duq7yfOl+03lYE6wTdy5XN1PGoijITj3cA6g1 75 Eah6/CirrDVqEVIng+5lsw/Qws1gOOkHaCdfkL85Trm4AWqppgFgIc/wafHZjekC 76 AwEAAaNTMFEwHQYDVR0OBBYEFCAUN20ma8sVaz1KZttyofv6tDZdMB8GA1UdIwQY 77 MBaAFCAUN20ma8sVaz1KZttyofv6tDZdMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI 78 hvcNAQELBQADgYEADyu05JNvWly50lvUksx85QwEMb7oZha6aov/9eslJnHD10Zu 79 QolLGgj3tz4NbDEitq+zKMr0uTHvP1Vyu1mXAflcpYcJA4ZmuB3Oj39e0U0gnmr/ 80 1T2dX1uHaAWl3pCmkRH1Dmpsx2sHllN/yizHpve2rrVpM9ZMXEdPxnzNNFE= 81 -----END CERTIFICATE-----`) 82 83 for _, signingMethod := range []jwt.SigningMethod{ 84 jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512, 85 } { 86 alg := signingMethod.Alg() 87 tests := []struct { 88 name string 89 fields fields 90 wantErr string 91 }{ 92 {"missing key-file path", fields{}, "configuration error: jwt key: read error: required: configured attribute or file"}, 93 {"missing key-file", fields{pubKeyPath: "./not-there.file"}, "not-there.file: no such file or directory"}, 94 {"PKIX", fields{ 95 algorithm: alg, 96 pubKey: pubKeyBytesPKIX, 97 }, ""}, 98 {"PKCS1", fields{ 99 algorithm: alg, 100 pubKey: pubKeyBytesPKCS1, 101 }, ""}, 102 {"Cert", fields{ 103 algorithm: alg, 104 pubKey: certBytes, 105 }, ""}, 106 {"Priv", fields{ 107 algorithm: alg, 108 pubKey: privKeyBytes, 109 }, "key is not a valid RSA public key"}, 110 } 111 112 for _, tt := range tests { 113 t.Run(fmt.Sprintf("%v / %s", signingMethod, tt.name), func(subT *testing.T) { 114 key, rerr := reader.ReadFromAttrFile("jwt key", string(tt.fields.pubKey), tt.fields.pubKeyPath) 115 if rerr != nil { 116 logErr := rerr.(errors.GoError) 117 if tt.wantErr != "" && !strings.HasSuffix(logErr.LogError(), tt.wantErr) { 118 subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, logErr.LogError()) 119 } else if tt.wantErr == "" { 120 subT.Fatal(logErr.LogError()) 121 } 122 return 123 } 124 125 j, jerr := ac.NewJWT(&ac.JWTOptions{ 126 Algorithm: tt.fields.algorithm, 127 Claims: tt.fields.claims, 128 ClaimsRequired: tt.fields.claimsRequired, 129 Name: "test_ac", 130 Key: key, 131 Source: ac.NewJWTSource("", "Authorization", nil), 132 }) 133 if jerr != nil { 134 if tt.wantErr != jerr.Error() { 135 subT.Errorf("error: %v, want: %v", jerr.Error(), tt.wantErr) 136 } 137 } else if tt.wantErr != "" { 138 subT.Errorf("error expected: %v", tt.wantErr) 139 } 140 if tt.wantErr == "" && j == nil { 141 subT.Errorf("JWT struct expected") 142 } 143 }) 144 } 145 } 146 } 147 148 func Test_JWT_Validate(t *testing.T) { 149 log, _ := test.NewLogger() 150 type fields struct { 151 algorithm acjwt.Algorithm 152 claims map[string]string 153 claimsRequired []string 154 source ac.JWTSource 155 pubKey []byte 156 } 157 158 for _, signingMethod := range []jwt.SigningMethod{ 159 jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512, 160 jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512, 161 } { 162 163 pubKeyBytes, privKey := newRSAKeyPair() 164 165 tok := jwt.NewWithClaims(signingMethod, jwt.MapClaims{ 166 "aud": "peter", 167 "test123": "value123", 168 }) 169 var token string 170 var tokenErr error 171 172 algo := acjwt.NewAlgorithm(signingMethod.Alg()) 173 174 if algo.IsHMAC() { 175 pubKeyBytes = []byte("mySecretK3y") 176 token, tokenErr = tok.SignedString(pubKeyBytes) 177 } else { 178 token, tokenErr = tok.SignedString(privKey) 179 } 180 181 if tokenErr != nil { 182 t.Error(tokenErr) 183 } 184 185 tests := []struct { 186 name string 187 fields fields 188 req *http.Request 189 wantErrKind string 190 }{ 191 {"src: header /w no authorization header", fields{ 192 algorithm: algo, 193 source: ac.NewJWTSource("", "Authorization", nil), 194 pubKey: pubKeyBytes, 195 }, httptest.NewRequest(http.MethodGet, "/", nil), "jwt_token_missing"}, 196 {"src: header /w different auth-scheme", fields{ 197 algorithm: algo, 198 source: ac.NewJWTSource("", "Authorization", nil), 199 pubKey: pubKeyBytes, 200 }, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "Basic qbqnb"), "jwt_token_missing"}, 201 {"src: header /w empty bearer", fields{ 202 algorithm: algo, 203 source: ac.NewJWTSource("", "Authorization", nil), 204 pubKey: pubKeyBytes, 205 }, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR"), "jwt_token_missing"}, 206 {"src: header /w valid bearer", fields{ 207 algorithm: algo, 208 source: ac.NewJWTSource("", "Authorization", nil), 209 pubKey: pubKeyBytes, 210 }, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token), ""}, 211 {"src: header /w no cookie", fields{ 212 algorithm: algo, 213 source: ac.NewJWTSource("token", "", nil), 214 pubKey: pubKeyBytes, 215 }, httptest.NewRequest(http.MethodGet, "/", nil), "jwt_token_missing"}, 216 {"src: header /w empty cookie", fields{ 217 algorithm: algo, 218 source: ac.NewJWTSource("token", "", nil), 219 pubKey: pubKeyBytes, 220 }, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "token", ""), "jwt_token_missing"}, 221 {"src: header /w valid cookie", fields{ 222 algorithm: algo, 223 source: ac.NewJWTSource("token", "", nil), 224 pubKey: pubKeyBytes, 225 }, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "token", token), ""}, 226 {"src: header /w valid bearer & claims", fields{ 227 algorithm: algo, 228 claims: map[string]string{ 229 "aud": "peter", 230 "test123": "value123", 231 }, 232 claimsRequired: []string{"aud"}, 233 source: ac.NewJWTSource("", "Authorization", nil), 234 pubKey: pubKeyBytes, 235 }, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), ""}, 236 {"src: header /w valid bearer & wrong audience", fields{ 237 algorithm: algo, 238 claims: map[string]string{ 239 "aud": "paul", 240 "test123": "value123", 241 }, 242 claimsRequired: []string{"aud"}, 243 source: ac.NewJWTSource("", "Authorization", nil), 244 pubKey: pubKeyBytes, 245 }, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), "jwt_token_invalid"}, 246 {"src: header /w valid bearer & w/o claims", fields{ 247 algorithm: algo, 248 claims: map[string]string{ 249 "aud": "peter", 250 "cptn": "hook", 251 }, 252 source: ac.NewJWTSource("", "Authorization", nil), 253 pubKey: pubKeyBytes, 254 }, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), "jwt_token_invalid"}, 255 {"src: header /w valid bearer & w/o required claims", fields{ 256 algorithm: algo, 257 claims: map[string]string{ 258 "aud": "peter", 259 }, 260 claimsRequired: []string{"exp"}, 261 source: ac.NewJWTSource("", "Authorization", nil), 262 pubKey: pubKeyBytes, 263 }, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), "jwt_token_invalid"}, 264 { 265 "token_value number", 266 fields{ 267 algorithm: algo, 268 source: ac.NewJWTSource("", "", hcltest.MockExprLiteral(cty.NumberIntVal(42))), 269 pubKey: pubKeyBytes, 270 }, 271 setContext(httptest.NewRequest(http.MethodGet, "/", nil)), 272 "jwt_token_invalid", 273 }, 274 { 275 "token_value string", 276 fields{ 277 algorithm: algo, 278 claims: map[string]string{"aud": "peter", "test123": "value123"}, 279 claimsRequired: []string{"aud", "test123"}, 280 source: ac.NewJWTSource("", "", hcltest.MockExprLiteral(cty.StringVal(token))), 281 pubKey: pubKeyBytes, 282 }, 283 setContext(httptest.NewRequest(http.MethodGet, "/", nil)), 284 "", 285 }, 286 } 287 for _, tt := range tests { 288 t.Run(fmt.Sprintf("%v_%s", signingMethod, tt.name), func(subT *testing.T) { 289 claimValMap := make(map[string]cty.Value) 290 for k, v := range tt.fields.claims { 291 claimValMap[k] = cty.StringVal(v) 292 } 293 j, err := ac.NewJWT(&ac.JWTOptions{ 294 Algorithm: tt.fields.algorithm.String(), 295 Claims: hcl.StaticExpr(cty.ObjectVal(claimValMap), hcl.Range{}), 296 ClaimsRequired: tt.fields.claimsRequired, 297 Name: "test_ac", 298 Source: tt.fields.source, 299 Key: tt.fields.pubKey, 300 }) 301 if err != nil { 302 subT.Error(err) 303 return 304 } 305 306 tt.req = tt.req.WithContext(context.WithValue(context.Background(), request.LogEntry, log.WithContext(context.Background()))) 307 308 errKind := "" 309 err = j.Validate(tt.req) 310 if err != nil { 311 cErr := err.(*errors.Error) 312 errKind = cErr.Kinds()[0] 313 } 314 if errKind != tt.wantErrKind { 315 subT.Errorf("Validate() error kind does not match; want: %q, got: %q", tt.wantErrKind, errKind) 316 } 317 318 if tt.wantErrKind == "" && tt.fields.claims != nil { 319 acMap := tt.req.Context().Value(request.AccessControls).(map[string]interface{}) 320 if claims, ok := acMap["test_ac"]; !ok { 321 subT.Errorf("Expected a configured access control name within request context") 322 } else { 323 claimsMap := claims.(map[string]interface{}) 324 for k, v := range tt.fields.claims { 325 if claimsMap[k] != v { 326 subT.Errorf("Claim does not match: %q want: %v, got: %v", k, v, claimsMap[k]) 327 } 328 } 329 } 330 331 } 332 }) 333 } 334 } 335 } 336 337 func Test_JWT_yields_permissions(t *testing.T) { 338 log, hook := test.NewLogger() 339 signingMethod := jwt.SigningMethodHS256 340 algo := acjwt.NewAlgorithm(signingMethod.Alg()) 341 342 rolesMap := map[string][]string{ 343 "admin": {"foo", "bar", "baz"}, 344 "user1": {"foo"}, 345 "user2": {"bar"}, 346 "*": {"default"}, 347 } 348 permissionsMap := map[string][]string{ 349 "baz": {"blubb"}, 350 } 351 var noGrantedPermissions []string 352 353 tests := []struct { 354 name string 355 permissionsClaim string 356 permissionsValue interface{} 357 rolesClaim string 358 rolesValue interface{} 359 expWarning string 360 expGrantedPerms []string 361 }{ 362 { 363 "no permissions, no roles", 364 "scp", 365 nil, 366 "roles", 367 nil, 368 "", 369 noGrantedPermissions, 370 }, 371 { 372 "permissions: space-separated list", 373 "scp", 374 "foo bar", 375 "", 376 nil, 377 "", 378 []string{"foo", "bar"}, 379 }, 380 { 381 "permissions: space-separated list, multiple", 382 "scp", 383 "foo bar foo", 384 "", 385 nil, 386 "", 387 []string{"foo", "bar"}, 388 }, 389 { 390 "permissions: list of string", 391 "scoop", 392 []string{"foo", "bar"}, 393 "", 394 nil, 395 "", 396 []string{"foo", "bar"}, 397 }, 398 { 399 "permissions: list of string, multiple", 400 "scoop", 401 []string{"foo", "bar", "bar"}, 402 "", 403 nil, 404 "", 405 []string{"foo", "bar"}, 406 }, 407 { 408 "permissions: warn: boolean", 409 "scope", 410 true, 411 "", 412 nil, 413 "invalid permissions claim value type, ignoring claim, value true", 414 noGrantedPermissions, 415 }, 416 { 417 "permissions: warn: number", 418 "scope", 419 1.23, 420 "", 421 nil, 422 "invalid permissions claim value type, ignoring claim, value 1.23", 423 noGrantedPermissions, 424 }, 425 { 426 "permissions: warn: list of bool", 427 "scope", 428 []bool{true, false}, 429 "", 430 nil, 431 "invalid permissions claim value type, ignoring claim, value []interface {}{true, false}", 432 noGrantedPermissions, 433 }, 434 { 435 "permissions: warn: list of number", 436 "scope", 437 []int{1, 2}, 438 "", 439 nil, 440 "invalid permissions claim value type, ignoring claim, value []interface {}{1, 2}", 441 noGrantedPermissions, 442 }, 443 { 444 "permissions: warn: mixed list", 445 "scope", 446 []interface{}{"eins", 2}, 447 "", 448 nil, 449 `invalid permissions claim value type, ignoring claim, value []interface {}{"eins", 2}`, 450 noGrantedPermissions, 451 }, 452 { 453 "permissions: warn: object", 454 "scope", 455 map[string]interface{}{"foo": 1, "bar": 1}, 456 "", 457 nil, 458 `invalid permissions claim value type, ignoring claim, value map[string]interface {}{"bar":1, "foo":1}`, 459 noGrantedPermissions, 460 }, 461 { 462 "roles: single string, permission mapped", 463 "", 464 nil, 465 "roles", 466 "admin", 467 "", 468 []string{"foo", "bar", "baz", "default", "blubb"}, 469 }, 470 { 471 "roles: space-separated list", 472 "", 473 nil, 474 "roles", 475 "user1 user2", 476 "", 477 []string{"foo", "bar", "default"}, 478 }, 479 { 480 "roles: space-separated list, multiple", 481 "", 482 nil, 483 "roles", 484 "user1 user2 user1", 485 "", 486 []string{"foo", "bar", "default"}, 487 }, 488 { 489 "roles: list of string", 490 "", 491 nil, 492 "rollen", 493 []string{"user1", "user2"}, 494 "", 495 []string{"foo", "bar", "default"}, 496 }, 497 { 498 "roles: list of string, multiple", 499 "", 500 nil, 501 "rollen", 502 []string{"user1", "user2", "user2"}, 503 "", 504 []string{"foo", "bar", "default"}, 505 }, 506 { 507 "roles: list of string, no additional 1, permission mapped", 508 "", 509 nil, 510 "rollen", 511 []string{"admin", "user1"}, 512 "", 513 []string{"foo", "bar", "baz", "default", "blubb"}, 514 }, 515 { 516 "roles: list of string, no additional 2, permission mapped", 517 "", 518 nil, 519 "rollen", 520 []string{"admin", "user2"}, 521 "", 522 []string{"foo", "bar", "baz", "default", "blubb"}, 523 }, 524 { 525 "roles: warn: boolean", 526 "", 527 nil, 528 "roles", 529 true, 530 "invalid roles claim value type, ignoring claim, value true", 531 []string{"default"}, 532 }, 533 { 534 "roles: warn: number", 535 "", 536 nil, 537 "roles", 538 1.23, 539 "invalid roles claim value type, ignoring claim, value 1.23", 540 []string{"default"}, 541 }, 542 { 543 "roles: warn: list of bool", 544 "", 545 nil, 546 "roles", 547 []bool{true, false}, 548 "invalid roles claim value type, ignoring claim, value []interface {}{true, false}", 549 []string{"default"}, 550 }, 551 { 552 "roles: warn: list of number", 553 "", 554 nil, 555 "roles", 556 []int{1, 2}, 557 "invalid roles claim value type, ignoring claim, value []interface {}{1, 2}", 558 []string{"default"}, 559 }, 560 { 561 "roles: warn: mixed list", 562 "", 563 nil, 564 "roles", 565 []interface{}{"user1", 2}, 566 `invalid roles claim value type, ignoring claim, value []interface {}{"user1", 2}`, 567 []string{"default"}, 568 }, 569 { 570 "roles: warn: object", 571 "", 572 nil, 573 "roles", 574 map[string]interface{}{"foo": 1, "bar": 1}, 575 `invalid roles claim value type, ignoring claim, value map[string]interface {}{"bar":1, "foo":1}`, 576 []string{"default"}, 577 }, 578 { 579 "combi 1", 580 "scope", 581 "foo foo", 582 "roles", 583 []string{"user2"}, 584 "", 585 []string{"foo", "bar", "default"}, 586 }, 587 { 588 "combi 2, permission mapped", 589 "scope", 590 []string{"foo", "bar"}, 591 "roles", 592 "admin", 593 "", 594 []string{"foo", "bar", "baz", "default", "blubb"}, 595 }, 596 } 597 for _, tt := range tests { 598 t.Run(tt.name, func(subT *testing.T) { 599 hook.Reset() 600 claims := jwt.MapClaims{} 601 if tt.permissionsClaim != "" && tt.permissionsValue != nil { 602 claims[tt.permissionsClaim] = tt.permissionsValue 603 } 604 if tt.rolesClaim != "" && tt.rolesValue != nil { 605 claims[tt.rolesClaim] = tt.rolesValue 606 } 607 tok := jwt.NewWithClaims(signingMethod, claims) 608 pubKeyBytes := []byte("mySecretK3y") 609 token, tokenErr := tok.SignedString(pubKeyBytes) 610 if tokenErr != nil { 611 subT.Error(tokenErr) 612 } 613 614 source := ac.NewJWTSource("", "Authorization", nil) 615 j, err := ac.NewJWT(&ac.JWTOptions{ 616 Algorithm: algo.String(), 617 Name: "test_ac", 618 PermissionsClaim: tt.permissionsClaim, 619 PermissionsMap: permissionsMap, 620 RolesClaim: tt.rolesClaim, 621 RolesMap: rolesMap, 622 Source: source, 623 Key: pubKeyBytes, 624 }) 625 if err != nil { 626 subT.Fatal(err) 627 } 628 629 req := setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token) 630 req = req.WithContext(context.WithValue(context.Background(), request.LogEntry, log.WithContext(context.Background()))) 631 632 if err = j.Validate(req); err != nil { 633 subT.Errorf("Unexpected error = %v", err) 634 return 635 } 636 637 grantedPermissionsList, ok := req.Context().Value(request.GrantedPermissions).([]string) 638 if !ok { 639 subT.Errorf("Expected granted permissions within request context") 640 } else { 641 if !reflect.DeepEqual(tt.expGrantedPerms, grantedPermissionsList) { 642 subT.Errorf("Granted permissions do not match, want: %#v, got: %#v", tt.expGrantedPerms, grantedPermissionsList) 643 } 644 } 645 646 entries := hook.AllEntries() 647 if tt.expWarning == "" { 648 if len(entries) > 0 { 649 subT.Errorf("Expected no log messages, got: %d", len(entries)) 650 } 651 return 652 } 653 if len(entries) != 1 { 654 subT.Errorf("Expected one log message: got: %d", len(entries)) 655 return 656 } 657 entry := entries[0] 658 if entry.Level != logrus.WarnLevel { 659 subT.Errorf("Expected warning, got: %v", entry.Level) 660 return 661 } 662 if entry.Message != tt.expWarning { 663 subT.Errorf("Warning mismatch,\n\twant: %s,\n\tgot: %s", tt.expWarning, entry.Message) 664 } 665 }) 666 } 667 } 668 669 func TestJwtConfig(t *testing.T) { 670 671 const backendURL = "http://blackhole.webpagetest.org/" 672 673 tests := []struct { 674 name string 675 hcl string 676 error string 677 }{ 678 { 679 "missing both signature_algorithm/jwks_url", 680 ` 681 server "test" {} 682 definitions { 683 jwt "myac" { 684 } 685 } 686 `, 687 "configuration error: myac: signature_algorithm or jwks_url attribute required", 688 }, 689 { 690 "signature_algorithm, missing key/key_file", 691 ` 692 server "test" {} 693 definitions { 694 jwt "myac" { 695 signature_algorithm = "HS256" 696 header = "..." 697 } 698 } 699 `, 700 "configuration error: myac: jwt key: read error: required: configured attribute or file", 701 }, 702 { 703 "signature_algorithm, both key and key_file", 704 ` 705 server "test" {} 706 definitions { 707 jwt "myac" { 708 signature_algorithm = "HS256" 709 header = "..." 710 key = "..." 711 key_file = "testdata/secret.txt" 712 } 713 } 714 `, 715 "configuration error: myac: jwt key: read error: configured attribute and file", 716 }, 717 { 718 "signature_algorithm, both roles_map and roles_map_file", 719 ` 720 server "test" {} 721 definitions { 722 jwt "myac" { 723 signature_algorithm = "HS256" 724 header = "..." 725 key = "..." 726 roles_map = {} 727 roles_map_file = "testdata/map.json" 728 } 729 } 730 `, 731 "configuration error: myac: jwt roles map: read error: configured attribute and file", 732 }, 733 { 734 "signature_algorithm, roles_map_file not found", 735 ` 736 server "test" {} 737 definitions { 738 jwt "myac" { 739 signature_algorithm = "HS256" 740 header = "..." 741 key = "..." 742 roles_map_file = "file_not_found" 743 } 744 } 745 `, 746 "configuration error: myac: roles map: read error: open .*/testdata/file_not_found: no such file or directory", 747 }, 748 { 749 "signature_algorithm, both permissions_map and permissions_map_file", 750 ` 751 server "test" {} 752 definitions { 753 jwt "myac" { 754 signature_algorithm = "HS256" 755 header = "..." 756 key = "..." 757 permissions_map = {} 758 permissions_map_file = "testdata/map.json" 759 } 760 } 761 `, 762 "configuration error: myac: jwt permissions map: read error: configured attribute and file", 763 }, 764 { 765 "signature_algorithm, permissions_map_file not found", 766 ` 767 server "test" {} 768 definitions { 769 jwt "myac" { 770 signature_algorithm = "HS256" 771 header = "..." 772 key = "..." 773 permissions_map_file = "file_not_found" 774 } 775 } 776 `, 777 "configuration error: myac: permissions map: read error: open .*/accesscontrol/file_not_found: no such file or directory", 778 }, 779 { 780 "ok: signature_algorithm + key (default: header = Authorization)", 781 ` 782 server "test" {} 783 definitions { 784 jwt "myac" { 785 signature_algorithm = "HS256" 786 key = "..." 787 } 788 } 789 `, 790 "", 791 }, 792 { 793 "ok: signature_algorithm + key + header", 794 ` 795 server "test" {} 796 definitions { 797 jwt "myac" { 798 signature_algorithm = "HS256" 799 header = "..." 800 key = "..." 801 } 802 } 803 `, 804 "", 805 }, 806 { 807 "ok: signature_algorithm + key + cookie", 808 ` 809 server "test" {} 810 definitions { 811 jwt "myac" { 812 signature_algorithm = "HS256" 813 cookie = "..." 814 key = "..." 815 } 816 } 817 `, 818 "", 819 }, 820 { 821 "ok: signature_algorithm + key + token_value", 822 ` 823 server "test" {} 824 definitions { 825 jwt "myac" { 826 signature_algorithm = "HS256" 827 token_value = env.TOKEN 828 key = "..." 829 } 830 } 831 `, 832 "", 833 }, 834 { 835 "token_value + header", 836 ` 837 server "test" {} 838 definitions { 839 jwt "myac" { 840 signature_algorithm = "HS256" 841 token_value = env.TOKEN 842 header = "..." 843 key = "..." 844 } 845 } 846 `, 847 "configuration error: myac: token source is invalid", 848 }, 849 { 850 "token_value + cookie", 851 ` 852 server "test" {} 853 definitions { 854 jwt "myac" { 855 signature_algorithm = "HS256" 856 token_value = env.TOKEN 857 cookie = "..." 858 key = "..." 859 } 860 } 861 `, 862 "configuration error: myac: token source is invalid", 863 }, 864 { 865 "cookie + header", 866 ` 867 server "test" {} 868 definitions { 869 jwt "myac" { 870 signature_algorithm = "HS256" 871 cookie = "..." 872 header = "..." 873 key = "..." 874 } 875 } 876 `, 877 "configuration error: myac: token source is invalid", 878 }, 879 { 880 "ok: signature_algorithm + key_file", 881 ` 882 server "test" {} 883 definitions { 884 jwt "myac" { 885 signature_algorithm = "HS256" 886 header = "..." 887 key_file = "testdata/secret.txt" 888 } 889 } 890 `, 891 "", 892 }, 893 { 894 "ok: jwks_url", 895 ` 896 server "test" {} 897 definitions { 898 jwt "myac" { 899 jwks_url = "file:jwk/testdata/jwks.json", 900 header = "..." 901 } 902 } 903 `, 904 "", 905 }, 906 { 907 "jwks_url file not found", 908 ` 909 server "test" {} 910 definitions { 911 jwt "myac" { 912 jwks_url = "file:file_not_found", 913 header = "..." 914 } 915 } 916 `, 917 "configuration error: myac: jwks_url: read error: open .*/accesscontrol/file_not_found: no such file or directory", 918 }, 919 { 920 "signature_algorithm + jwks_url", 921 ` 922 server "test" {} 923 definitions { 924 jwt "myac" { 925 signature_algorithm = "HS256" 926 jwks_url = "` + backendURL + `" 927 header = "..." 928 } 929 } 930 `, 931 "configuration error: myac: signature_algorithm cannot be used together with jwks_url", 932 }, 933 { 934 "key + jwks_url", 935 ` 936 server "test" {} 937 definitions { 938 jwt "myac" { 939 key = "..." 940 jwks_url = "` + backendURL + `" 941 header = "..." 942 } 943 } 944 `, 945 "configuration error: myac: key cannot be used together with jwks_url", 946 }, 947 { 948 "key_file + jwks_url", 949 ` 950 server "test" {} 951 definitions { 952 jwt "myac" { 953 key_file = "..." 954 jwks_url = "` + backendURL + `" 955 header = "..." 956 } 957 } 958 `, 959 "configuration error: myac: key_file cannot be used together with jwks_url", 960 }, 961 { 962 "backend reference, missing jwks_url", 963 ` 964 server "test" {} 965 definitions { 966 jwt "myac" { 967 backend = "foo" 968 header = "..." 969 signature_algorithm = "asdf" 970 } 971 backend "foo" {} 972 } 973 `, 974 "configuration error: myac: backend is obsolete without jwks_url attribute", 975 }, 976 } 977 978 log, hook := test.NewLogger() 979 helper := test.New(t) 980 981 for _, tt := range tests { 982 t.Run(tt.name, func(subT *testing.T) { 983 hook.Reset() 984 985 conf, err := configload.LoadBytes([]byte(tt.hcl), "couper.hcl") 986 if conf != nil { 987 tmpStoreCh := make(chan struct{}) 988 defer close(tmpStoreCh) 989 990 ctx, cancel := context.WithCancel(conf.Context) 991 conf.Context = ctx 992 defer cancel() 993 994 logger := log.WithContext(ctx) 995 996 _, err = runtime.NewServerConfiguration(conf, logger, cache.New(logger, tmpStoreCh)) 997 } 998 999 var errMsg, expectedError string 1000 if err != nil { 1001 if _, ok := err.(errors.GoError); ok { 1002 errMsg = err.(errors.GoError).LogError() 1003 } else { 1004 errMsg = err.Error() 1005 } 1006 } 1007 1008 if tt.error == "" && errMsg == "" { 1009 return 1010 } 1011 1012 time.Sleep(time.Second / 2) // sync routine start 1013 1014 for _, e := range hook.AllEntries() { 1015 if e.Level != logrus.ErrorLevel { 1016 continue 1017 } 1018 errMsg = e.Message 1019 break 1020 } 1021 1022 re, err := regexp.Compile(expectedError) 1023 helper.Must(err) 1024 if !re.MatchString(errMsg) { 1025 subT.Errorf("%q: Unexpected configuration error:\n\tWant: %q\n\tGot: %q", tt.name, expectedError, errMsg) 1026 } 1027 }) 1028 } 1029 } 1030 1031 func newRSAKeyPair() (pubKeyBytes []byte, privKey *rsa.PrivateKey) { 1032 privKey, err := rsa.GenerateKey(rand.Reader, 2048) 1033 if err != nil { 1034 panic(err) 1035 } 1036 if e := privKey.Validate(); e != nil { 1037 panic(e) 1038 } 1039 1040 pubKeyBytes = pem.EncodeToMemory(&pem.Block{ 1041 Type: "RSA PUBLIC KEY", 1042 Bytes: x509.MarshalPKCS1PublicKey(&privKey.PublicKey), 1043 }) 1044 return 1045 } 1046 1047 func setCookieAndHeader(req *http.Request, key, value string) *http.Request { 1048 req.Header.Set(key, value) 1049 req.Header.Set("Cookie", key+"="+value) 1050 return req 1051 } 1052 1053 func setContext(req *http.Request) *http.Request { 1054 evalCtx := eval.ContextFromRequest(req) 1055 *req = *req.WithContext(evalCtx.WithClientRequest(req)) 1056 return req 1057 }