istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/util/jwtutil_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package util 16 17 import ( 18 "fmt" 19 "reflect" 20 "testing" 21 "time" 22 ) 23 24 var ( 25 // thirdPartyJwt is generated in a testing K8s cluster, using the "istio-token" projected volume. 26 // Token is issued at 2020-04-04 22:13:54 Pacific Daylight time. 27 // Expiration time is 2020-04-05 10:13:54 Pacific Daylight time. 28 thirdPartyJwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9." + 29 "eyJhdWQiOlsieW9uZ2dhbmdsLWlzdGlvLTQuc3ZjLmlkLmdvb2ciXSwiZXhwIjoxNTg2MTA2ODM0LCJpYXQiOjE1" + 30 "ODYwNjM2MzQsImlzcyI6Imh0dHBzOi8vY29udGFpbmVyLmdvb2dsZWFwaXMuY29tL3YxL3Byb2plY3RzL3lvbmdn" + 31 "YW5nbC1pc3Rpby00L2xvY2F0aW9ucy91cy1jZW50cmFsMS1hL2NsdXN0ZXJzL2NsdXN0ZXItMyIsImt1YmVybmV0" + 32 "ZXMuaW8iOnsibmFtZXNwYWNlIjoiZm9vIiwicG9kIjp7Im5hbWUiOiJodHRwYmluLTY0Nzc2YmY3OGQtanFsNWIi" + 33 "LCJ1aWQiOiI5YWQ3NTcxYi03NjBhLTExZWEtODllNy00MjAxMGE4MDAxYzEifSwic2VydmljZWFjY291bnQiOnsi" + 34 "bmFtZSI6Imh0dHBiaW4iLCJ1aWQiOiI5OWY2NWY1MC03NjBhLTExZWEtODllNy00MjAxMGE4MDAxYzEifX0sIm5i" + 35 "ZiI6MTU4NjA2MzYzNCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmZvbzpodHRwYmluIn0.XWSCdarBR0cx" + 36 "MlVV5X9pvgI9m0lyO-17B45aBKJBIQjvjluKXqxnCuIeD3X2ItLkCUzmKUa3ftTjUUov1MJ89MdBngNfUP7IfwnD" + 37 "2dBl7Jtju0-Ks7aTFOkgtoMYqNnQ1VSDTAOfNpdZVUnsR_oY8obXSQR_H4uMcaNOGED2RX5HLBWFlvymtn4JXuyg" + 38 "_rpOrJ8dv-snrmO3LT9y-zaUnZqSceDC8skzStrJIRvsRkO8GEcoQd5VwDn-UVgOcqWb-S-vgSjdtwBsnGPXsh_I" + 39 "NZCq3ftr0Qu8-IxsIjpMjhLmAGTH1bR324aqLTYhAXp6fk06Pe3T9stCY5acSeadKA" 40 // firstPartyJwt is generated in a testing K8s cluster. It is the default service account JWT. 41 // No expiration time. 42 firstPartyJwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9." + 43 "eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9u" + 44 "YW1lc3BhY2UiOiJmb28iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiaHR0cGJp" + 45 "bi10b2tlbi14cWRncCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUi" + 46 "OiJodHRwYmluIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiOTlm" + 47 "NjVmNTAtNzYwYS0xMWVhLTg5ZTctNDIwMTBhODAwMWMxIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmZv" + 48 "bzpodHRwYmluIn0.4kIl9TRjXEw6DfhtR-LdpxsAlYjJgC6Ly1DY_rqYY4h0haxcXB3kYZ3b2He-3fqOBryz524W" + 49 "KkZscZgvs5L-sApmvlqdUG61TMAl7josB0x4IMHm1NS995LNEaXiI4driffwfopvqc_z3lVKfbF9j-mBgnCepxz3" + 50 "UyWo5irFa3qcwbOUB9kuuUNGBdtbFBN5yIYLpfa9E-MtTX_zJ9fQ9j2pi8Z4ljii0tEmPmRxokHkmG_xNJjUkxKU" + 51 "WZf4bLDdCEjVFyshNae-FdxiUVyeyYorTYzwZZYQch9MJeedg4keKKUOvCCJUlKixd2qAe-H7r15RPmo4AU5O5YL" + 52 "65xiNg" 53 54 // oneAudString includes one `aud` claim "abc" of type string. 55 oneAudString = "header.eyJhdWQiOiJhYmMiLCJleHAiOjQ3MzI5OTQ4MDEsImlhdCI6MTU3OTM5NDgwMSwiaXNzIjoidGVzdC1pc3N1ZXItMUBpc3Rpby5pbyIsInN1YiI6InN1Yi0xIn0.signature" // nolint: lll 56 57 // twoAudList includes two `aud` claims ["abc", "xyz"] of type []string. 58 twoAudList = "header.eyJhdWQiOlsiYWJjIiwieHl6Il0sImV4cCI6NDczMjk5NDgwMSwiaWF0IjoxNTc5Mzk0ODAxLCJpc3MiOiJ0ZXN0LWlzc3Vlci0xQGlzdGlvLmlvIiwic3ViIjoic3ViLTEifQ.signature" // nolint: lll 59 60 // A JWT encoded payload that has the padding stripped and contains an underscore character from the base64url alphabet 61 base64UrlEncodedPaddingStrippedPart = "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwiYXVkIjoiSm9lIEFyZG_DsWV6In0" 62 ) 63 64 func TestGetExp(t *testing.T) { 65 testCases := map[string]struct { 66 jwt string 67 expectedExp time.Time 68 expectedErr error 69 }{ 70 "jwt with expiration time": { 71 jwt: thirdPartyJwt, 72 expectedExp: time.Date(2020, time.April, 5, 10, 13, 54, 0, time.FixedZone("PDT", -int((7*time.Hour).Seconds()))), 73 expectedErr: nil, 74 }, 75 "jwt with no expiration time": { 76 jwt: firstPartyJwt, 77 expectedExp: time.Time{}, 78 expectedErr: nil, 79 }, 80 "invalid jwt": { 81 jwt: "invalid-section1.invalid-section2.invalid-section3", 82 expectedExp: time.Time{}, 83 expectedErr: fmt.Errorf("failed to decode the JWT claims"), 84 }, 85 } 86 87 for id, tc := range testCases { 88 t.Run(id, func(t *testing.T) { 89 exp, err := GetExp(tc.jwt) 90 if err != nil && tc.expectedErr == nil || err == nil && tc.expectedErr != nil { 91 t.Errorf("%s: Got error \"%v\", expected error \"%v\"", id, err, tc.expectedErr) 92 } else if err != nil && tc.expectedErr != nil && err.Error() != tc.expectedErr.Error() { 93 t.Errorf("%s: Got error \"%v\", expected error \"%v\"", id, err, tc.expectedErr) 94 } else if err == nil && exp.Sub(tc.expectedExp) != time.Duration(0) { 95 t.Errorf("%s: Got expiration time: %s, expected expiration time: %s", 96 id, exp.String(), tc.expectedExp.String()) 97 } 98 }) 99 } 100 } 101 102 func TestGetAud(t *testing.T) { 103 testCases := map[string]struct { 104 jwt string 105 aud []string 106 }{ 107 "no audience": { 108 jwt: firstPartyJwt, 109 }, 110 "one audience string": { 111 jwt: oneAudString, 112 aud: []string{"abc"}, 113 }, 114 "one audience list": { 115 jwt: thirdPartyJwt, 116 aud: []string{"yonggangl-istio-4.svc.id.goog"}, 117 }, 118 "two audiences list": { 119 jwt: twoAudList, 120 aud: []string{"abc", "xyz"}, 121 }, 122 } 123 124 for id, tc := range testCases { 125 t.Run(id, func(t *testing.T) { 126 if got, _ := GetAud(tc.jwt); !reflect.DeepEqual(tc.aud, got) { 127 t.Errorf("want audience %v but got %v", tc.aud, got) 128 } 129 }) 130 } 131 } 132 133 func Test3p(t *testing.T) { 134 for _, s := range []string{thirdPartyJwt, "InvalidToken"} { 135 if IsK8SUnbound(s) { 136 t.Error("Expecting bound token, detected unbound ", s) 137 } 138 } 139 for _, s := range []string{firstPartyJwt, ".bnVsbM."} { 140 if !IsK8SUnbound(s) { 141 t.Error("Expecting unbound, detected bound ", s) 142 } 143 } 144 } 145 146 func TestBase64UrlPartDecoding(t *testing.T) { 147 payloadBytes, err := DecodeJwtPart(base64UrlEncodedPaddingStrippedPart) 148 if err != nil { 149 t.Error("Expected DecodeJwtPart success, got failure", err) 150 } 151 if payloadBytes == nil { 152 t.Error("Expected DecodeJwtPart to return non-nil, got nil") 153 } 154 155 expectedAud := "Joe ArdoƱez" 156 if got, _ := GetAud("header." + base64UrlEncodedPaddingStrippedPart + ".signature"); len(got) != 1 || expectedAud != got[0] { 157 t.Errorf("want audience %v but got %v", expectedAud, got) 158 } 159 }