github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/config/identity/openid/jwt_test.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package openid 19 20 import ( 21 "bytes" 22 "context" 23 "encoding/base64" 24 "encoding/json" 25 "fmt" 26 "io" 27 "net/http" 28 "net/http/httptest" 29 "net/url" 30 "sync" 31 "testing" 32 "time" 33 34 jwtgo "github.com/golang-jwt/jwt/v4" 35 "github.com/minio/minio/internal/arn" 36 "github.com/minio/minio/internal/config" 37 jwtm "github.com/minio/minio/internal/jwt" 38 xnet "github.com/minio/pkg/v2/net" 39 ) 40 41 func TestUpdateClaimsExpiry(t *testing.T) { 42 testCases := []struct { 43 exp interface{} 44 dsecs string 45 expectedFailure bool 46 }{ 47 {"", "", true}, 48 {"-1", "0", true}, 49 {"-1", "900", true}, 50 {"1574812326", "900", false}, 51 {1574812326, "900", false}, 52 {int64(1574812326), "900", false}, 53 {int(1574812326), "900", false}, 54 {uint(1574812326), "900", false}, 55 {uint64(1574812326), "900", false}, 56 {json.Number("1574812326"), "900", false}, 57 {1574812326.000, "900", false}, 58 {time.Duration(3) * time.Minute, "900", false}, 59 } 60 61 for _, testCase := range testCases { 62 testCase := testCase 63 t.Run("", func(t *testing.T) { 64 claims := map[string]interface{}{} 65 claims["exp"] = testCase.exp 66 err := updateClaimsExpiry(testCase.dsecs, claims) 67 if err != nil && !testCase.expectedFailure { 68 t.Errorf("Expected success, got failure %s", err) 69 } 70 if err == nil && testCase.expectedFailure { 71 t.Error("Expected failure, got success") 72 } 73 }) 74 } 75 } 76 77 func initJWKSServer() *httptest.Server { 78 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 79 const jsonkey = `{"keys": 80 [ 81 {"kty":"RSA", 82 "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", 83 "e":"AQAB", 84 "alg":"RS256", 85 "kid":"2011-04-29"} 86 ] 87 }` 88 w.Write([]byte(jsonkey)) 89 })) 90 return server 91 } 92 93 func TestJWTHMACType(t *testing.T) { 94 server := initJWKSServer() 95 defer server.Close() 96 97 jwt := &jwtgo.Token{ 98 Method: jwtgo.SigningMethodHS256, 99 Claims: jwtgo.StandardClaims{ 100 ExpiresAt: 253428928061, 101 Audience: "76b95ae5-33ef-4283-97b7-d2a85dc2d8f4", 102 }, 103 Header: map[string]interface{}{ 104 "typ": "JWT", 105 "alg": jwtgo.SigningMethodHS256.Alg(), 106 "kid": "76b95ae5-33ef-4283-97b7-d2a85dc2d8f4", 107 }, 108 } 109 110 token, err := jwt.SignedString([]byte("WNGvKVyyNmXq0TraSvjaDN9CtpFgx35IXtGEffMCPR0")) 111 if err != nil { 112 t.Fatal(err) 113 } 114 fmt.Println(token) 115 116 u1, err := xnet.ParseHTTPURL(server.URL) 117 if err != nil { 118 t.Fatal(err) 119 } 120 121 pubKeys := publicKeys{ 122 RWMutex: &sync.RWMutex{}, 123 pkMap: map[string]interface{}{}, 124 } 125 pubKeys.add("76b95ae5-33ef-4283-97b7-d2a85dc2d8f4", []byte("WNGvKVyyNmXq0TraSvjaDN9CtpFgx35IXtGEffMCPR0")) 126 127 if len(pubKeys.pkMap) != 1 { 128 t.Fatalf("Expected 1 keys, got %d", len(pubKeys.pkMap)) 129 } 130 131 provider := providerCfg{ 132 ClientID: "76b95ae5-33ef-4283-97b7-d2a85dc2d8f4", 133 ClientSecret: "WNGvKVyyNmXq0TraSvjaDN9CtpFgx35IXtGEffMCPR0", 134 } 135 provider.JWKS.URL = u1 136 cfg := Config{ 137 Enabled: true, 138 pubKeys: pubKeys, 139 arnProviderCfgsMap: map[arn.ARN]*providerCfg{ 140 DummyRoleARN: &provider, 141 }, 142 ProviderCfgs: map[string]*providerCfg{ 143 "1": &provider, 144 }, 145 closeRespFn: func(rc io.ReadCloser) { 146 rc.Close() 147 }, 148 } 149 150 var claims jwtgo.MapClaims 151 if err = cfg.Validate(context.Background(), DummyRoleARN, token, "", "", claims); err != nil { 152 t.Fatal(err) 153 } 154 } 155 156 func TestJWT(t *testing.T) { 157 const jsonkey = `{"keys": 158 [ 159 {"kty":"RSA", 160 "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", 161 "e":"AQAB", 162 "alg":"RS256", 163 "kid":"2011-04-29"} 164 ] 165 }` 166 167 pubKeys := publicKeys{ 168 RWMutex: &sync.RWMutex{}, 169 pkMap: map[string]interface{}{}, 170 } 171 err := pubKeys.parseAndAdd(bytes.NewBuffer([]byte(jsonkey))) 172 if err != nil { 173 t.Fatal("Error loading pubkeys:", err) 174 } 175 if len(pubKeys.pkMap) != 1 { 176 t.Fatalf("Expected 1 keys, got %d", len(pubKeys.pkMap)) 177 } 178 179 u1, err := xnet.ParseHTTPURL("http://127.0.0.1:8443") 180 if err != nil { 181 t.Fatal(err) 182 } 183 184 provider := providerCfg{} 185 provider.JWKS.URL = u1 186 cfg := Config{ 187 Enabled: true, 188 pubKeys: pubKeys, 189 arnProviderCfgsMap: map[arn.ARN]*providerCfg{ 190 DummyRoleARN: &provider, 191 }, 192 ProviderCfgs: map[string]*providerCfg{ 193 "1": &provider, 194 }, 195 } 196 197 u, err := url.Parse("http://127.0.0.1:8443/?Token=invalid") 198 if err != nil { 199 t.Fatal(err) 200 } 201 202 var claims jwtgo.MapClaims 203 if err = cfg.Validate(context.Background(), DummyRoleARN, u.Query().Get("Token"), "", "", claims); err == nil { 204 t.Fatal(err) 205 } 206 } 207 208 func TestDefaultExpiryDuration(t *testing.T) { 209 testCases := []struct { 210 reqURL string 211 duration time.Duration 212 expectErr bool 213 }{ 214 { 215 reqURL: "http://127.0.0.1:8443/?Token=xxxxx", 216 duration: time.Duration(60) * time.Minute, 217 }, 218 { 219 reqURL: "http://127.0.0.1:8443/?DurationSeconds=9s", 220 expectErr: true, 221 }, 222 { 223 reqURL: "http://127.0.0.1:8443/?DurationSeconds=31536001", 224 expectErr: true, 225 }, 226 { 227 reqURL: "http://127.0.0.1:8443/?DurationSeconds=800", 228 expectErr: true, 229 }, 230 { 231 reqURL: "http://127.0.0.1:8443/?DurationSeconds=901", 232 duration: time.Duration(901) * time.Second, 233 }, 234 } 235 236 for i, testCase := range testCases { 237 u, err := url.Parse(testCase.reqURL) 238 if err != nil { 239 t.Fatal(err) 240 } 241 d, err := GetDefaultExpiration(u.Query().Get("DurationSeconds")) 242 gotErr := (err != nil) 243 if testCase.expectErr != gotErr { 244 t.Errorf("Test %d: Expected %v, got %v with error %s", i+1, testCase.expectErr, gotErr, err) 245 } 246 if d != testCase.duration { 247 t.Errorf("Test %d: Expected duration %d, got %d", i+1, testCase.duration, d) 248 } 249 } 250 } 251 252 func TestExpCorrect(t *testing.T) { 253 signKey, _ := base64.StdEncoding.DecodeString("NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyHkHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=") 254 255 claimsMap := jwtm.NewMapClaims() 256 claimsMap.SetExpiry(time.Now().Add(time.Minute)) 257 claimsMap.SetAccessKey("test-access") 258 if err := updateClaimsExpiry("3600", claimsMap.MapClaims); err != nil { 259 t.Error(err) 260 } 261 // Build simple token with updated expiration claim 262 token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS256, claimsMap) 263 tokenString, err := token.SignedString(signKey) 264 if err != nil { 265 t.Error(err) 266 } 267 268 // Parse token to be sure it is valid 269 err = jwtm.ParseWithClaims(tokenString, claimsMap, func(*jwtm.MapClaims) ([]byte, error) { 270 return signKey, nil 271 }) 272 if err != nil { 273 t.Error(err) 274 } 275 } 276 277 func TestKeycloakProviderInitialization(t *testing.T) { 278 testConfig := providerCfg{ 279 DiscoveryDoc: DiscoveryDoc{ 280 TokenEndpoint: "http://keycloak.test/token/endpoint", 281 }, 282 } 283 testKvs := config.KVS{} 284 testKvs.Set(Vendor, "keycloak") 285 testKvs.Set(KeyCloakRealm, "TestRealm") 286 testKvs.Set(KeyCloakAdminURL, "http://keycloak.test/auth/admin") 287 cfgGet := func(param string) string { 288 return testKvs.Get(param) 289 } 290 291 if testConfig.provider != nil { 292 t.Errorf("Empty config cannot have any provider!") 293 } 294 295 if err := testConfig.initializeProvider(cfgGet, http.DefaultTransport); err != nil { 296 t.Error(err) 297 } 298 299 if testConfig.provider == nil { 300 t.Errorf("keycloak provider must be initialized!") 301 } 302 }