storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/auth-handler_test.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2016, 2017 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "bytes" 21 "context" 22 "io" 23 "io/ioutil" 24 "net/http" 25 "net/url" 26 "os" 27 "testing" 28 "time" 29 30 "storj.io/minio/pkg/auth" 31 iampolicy "storj.io/minio/pkg/iam/policy" 32 ) 33 34 // Test get request auth type. 35 func TestGetRequestAuthType(t *testing.T) { 36 type testCase struct { 37 req *http.Request 38 authT authType 39 } 40 testCases := []testCase{ 41 // Test case - 1 42 // Check for generic signature v4 header. 43 { 44 req: &http.Request{ 45 URL: &url.URL{ 46 Host: "127.0.0.1:9000", 47 Scheme: httpScheme, 48 Path: SlashSeparator, 49 }, 50 Header: http.Header{ 51 "Authorization": []string{"AWS4-HMAC-SHA256 <cred_string>"}, 52 "X-Amz-Content-Sha256": []string{streamingContentSHA256}, 53 "Content-Encoding": []string{streamingContentEncoding}, 54 }, 55 Method: http.MethodPut, 56 }, 57 authT: authTypeStreamingSigned, 58 }, 59 // Test case - 2 60 // Check for JWT header. 61 { 62 req: &http.Request{ 63 URL: &url.URL{ 64 Host: "127.0.0.1:9000", 65 Scheme: httpScheme, 66 Path: SlashSeparator, 67 }, 68 Header: http.Header{ 69 "Authorization": []string{"Bearer 12313123"}, 70 }, 71 }, 72 authT: authTypeJWT, 73 }, 74 // Test case - 3 75 // Empty authorization header. 76 { 77 req: &http.Request{ 78 URL: &url.URL{ 79 Host: "127.0.0.1:9000", 80 Scheme: httpScheme, 81 Path: SlashSeparator, 82 }, 83 Header: http.Header{ 84 "Authorization": []string{""}, 85 }, 86 }, 87 authT: authTypeUnknown, 88 }, 89 // Test case - 4 90 // Check for presigned. 91 { 92 req: &http.Request{ 93 URL: &url.URL{ 94 Host: "127.0.0.1:9000", 95 Scheme: httpScheme, 96 Path: SlashSeparator, 97 RawQuery: "X-Amz-Credential=EXAMPLEINVALIDEXAMPL%2Fs3%2F20160314%2Fus-east-1", 98 }, 99 }, 100 authT: authTypePresigned, 101 }, 102 // Test case - 5 103 // Check for post policy. 104 { 105 req: &http.Request{ 106 URL: &url.URL{ 107 Host: "127.0.0.1:9000", 108 Scheme: httpScheme, 109 Path: SlashSeparator, 110 }, 111 Header: http.Header{ 112 "Content-Type": []string{"multipart/form-data"}, 113 }, 114 Method: http.MethodPost, 115 }, 116 authT: authTypePostPolicy, 117 }, 118 } 119 120 // .. Tests all request auth type. 121 for i, testc := range testCases { 122 authT := getRequestAuthType(testc.req) 123 if authT != testc.authT { 124 t.Errorf("Test %d: Expected %d, got %d", i+1, testc.authT, authT) 125 } 126 } 127 } 128 129 // Test all s3 supported auth types. 130 func TestS3SupportedAuthType(t *testing.T) { 131 type testCase struct { 132 authT authType 133 pass bool 134 } 135 // List of all valid and invalid test cases. 136 testCases := []testCase{ 137 // Test 1 - supported s3 type anonymous. 138 { 139 authT: authTypeAnonymous, 140 pass: true, 141 }, 142 // Test 2 - supported s3 type presigned. 143 { 144 authT: authTypePresigned, 145 pass: true, 146 }, 147 // Test 3 - supported s3 type signed. 148 { 149 authT: authTypeSigned, 150 pass: true, 151 }, 152 // Test 4 - supported s3 type with post policy. 153 { 154 authT: authTypePostPolicy, 155 pass: true, 156 }, 157 // Test 5 - supported s3 type with streaming signed. 158 { 159 authT: authTypeStreamingSigned, 160 pass: true, 161 }, 162 // Test 6 - supported s3 type with signature v2. 163 { 164 authT: authTypeSignedV2, 165 pass: true, 166 }, 167 // Test 7 - supported s3 type with presign v2. 168 { 169 authT: authTypePresignedV2, 170 pass: true, 171 }, 172 // Test 8 - JWT is not supported s3 type. 173 { 174 authT: authTypeJWT, 175 pass: false, 176 }, 177 // Test 9 - unknown auth header is not supported s3 type. 178 { 179 authT: authTypeUnknown, 180 pass: false, 181 }, 182 // Test 10 - some new auth type is not supported s3 type. 183 { 184 authT: authType(9), 185 pass: false, 186 }, 187 } 188 // Validate all the test cases. 189 for i, tt := range testCases { 190 ok := isSupportedS3AuthType(tt.authT) 191 if ok != tt.pass { 192 t.Errorf("Test %d:, Expected %t, got %t", i+1, tt.pass, ok) 193 } 194 } 195 } 196 197 func TestIsRequestPresignedSignatureV2(t *testing.T) { 198 testCases := []struct { 199 inputQueryKey string 200 inputQueryValue string 201 expectedResult bool 202 }{ 203 // Test case - 1. 204 // Test case with query key "AWSAccessKeyId" set. 205 {"", "", false}, 206 // Test case - 2. 207 {"AWSAccessKeyId", "", true}, 208 // Test case - 3. 209 {"X-Amz-Content-Sha256", "", false}, 210 } 211 212 for i, testCase := range testCases { 213 // creating an input HTTP request. 214 // Only the query parameters are relevant for this particular test. 215 inputReq, err := http.NewRequest(http.MethodGet, "http://example.com", nil) 216 if err != nil { 217 t.Fatalf("Error initializing input HTTP request: %v", err) 218 } 219 q := inputReq.URL.Query() 220 q.Add(testCase.inputQueryKey, testCase.inputQueryValue) 221 inputReq.URL.RawQuery = q.Encode() 222 223 actualResult := isRequestPresignedSignatureV2(inputReq) 224 if testCase.expectedResult != actualResult { 225 t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult) 226 } 227 } 228 } 229 230 // TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature verision v4 detection. 231 func TestIsRequestPresignedSignatureV4(t *testing.T) { 232 testCases := []struct { 233 inputQueryKey string 234 inputQueryValue string 235 expectedResult bool 236 }{ 237 // Test case - 1. 238 // Test case with query key ""X-Amz-Credential" set. 239 {"", "", false}, 240 // Test case - 2. 241 {"X-Amz-Credential", "", true}, 242 // Test case - 3. 243 {"X-Amz-Content-Sha256", "", false}, 244 } 245 246 for i, testCase := range testCases { 247 // creating an input HTTP request. 248 // Only the query parameters are relevant for this particular test. 249 inputReq, err := http.NewRequest(http.MethodGet, "http://example.com", nil) 250 if err != nil { 251 t.Fatalf("Error initializing input HTTP request: %v", err) 252 } 253 q := inputReq.URL.Query() 254 q.Add(testCase.inputQueryKey, testCase.inputQueryValue) 255 inputReq.URL.RawQuery = q.Encode() 256 257 actualResult := isRequestPresignedSignatureV4(inputReq) 258 if testCase.expectedResult != actualResult { 259 t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult) 260 } 261 } 262 } 263 264 // Provides a fully populated http request instance, fails otherwise. 265 func mustNewRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request { 266 req, err := newTestRequest(method, urlStr, contentLength, body) 267 if err != nil { 268 t.Fatalf("Unable to initialize new http request %s", err) 269 } 270 return req 271 } 272 273 // This is similar to mustNewRequest but additionally the request 274 // is signed with AWS Signature V4, fails if not able to do so. 275 func mustNewSignedRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request { 276 req := mustNewRequest(method, urlStr, contentLength, body, t) 277 cred := globalActiveCred 278 if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil { 279 t.Fatalf("Unable to inititalized new signed http request %s", err) 280 } 281 return req 282 } 283 284 // This is similar to mustNewRequest but additionally the request 285 // is signed with AWS Signature V2, fails if not able to do so. 286 func mustNewSignedV2Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request { 287 req := mustNewRequest(method, urlStr, contentLength, body, t) 288 cred := globalActiveCred 289 if err := signRequestV2(req, cred.AccessKey, cred.SecretKey); err != nil { 290 t.Fatalf("Unable to inititalized new signed http request %s", err) 291 } 292 return req 293 } 294 295 // This is similar to mustNewRequest but additionally the request 296 // is presigned with AWS Signature V2, fails if not able to do so. 297 func mustNewPresignedV2Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request { 298 req := mustNewRequest(method, urlStr, contentLength, body, t) 299 cred := globalActiveCred 300 if err := preSignV2(req, cred.AccessKey, cred.SecretKey, time.Now().Add(10*time.Minute).Unix()); err != nil { 301 t.Fatalf("Unable to inititalized new signed http request %s", err) 302 } 303 return req 304 } 305 306 // This is similar to mustNewRequest but additionally the request 307 // is presigned with AWS Signature V4, fails if not able to do so. 308 func mustNewPresignedRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request { 309 req := mustNewRequest(method, urlStr, contentLength, body, t) 310 cred := globalActiveCred 311 if err := preSignV4(req, cred.AccessKey, cred.SecretKey, time.Now().Add(10*time.Minute).Unix()); err != nil { 312 t.Fatalf("Unable to inititalized new signed http request %s", err) 313 } 314 return req 315 } 316 317 func mustNewSignedShortMD5Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request { 318 req := mustNewRequest(method, urlStr, contentLength, body, t) 319 req.Header.Set("Content-Md5", "invalid-digest") 320 cred := globalActiveCred 321 if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil { 322 t.Fatalf("Unable to initialized new signed http request %s", err) 323 } 324 return req 325 } 326 327 func mustNewSignedEmptyMD5Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request { 328 req := mustNewRequest(method, urlStr, contentLength, body, t) 329 req.Header.Set("Content-Md5", "") 330 cred := globalActiveCred 331 if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil { 332 t.Fatalf("Unable to initialized new signed http request %s", err) 333 } 334 return req 335 } 336 337 func mustNewSignedBadMD5Request(method string, urlStr string, contentLength int64, 338 body io.ReadSeeker, t *testing.T) *http.Request { 339 req := mustNewRequest(method, urlStr, contentLength, body, t) 340 req.Header.Set("Content-Md5", "YWFhYWFhYWFhYWFhYWFhCg==") 341 cred := globalActiveCred 342 if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil { 343 t.Fatalf("Unable to initialized new signed http request %s", err) 344 } 345 return req 346 } 347 348 // Tests is requested authenticated function, tests replies for s3 errors. 349 func TestIsReqAuthenticated(t *testing.T) { 350 objLayer, fsDir, err := prepareFS() 351 if err != nil { 352 t.Fatal(err) 353 } 354 defer os.RemoveAll(fsDir) 355 if err = newTestConfig(globalMinioDefaultRegion, objLayer); err != nil { 356 t.Fatalf("unable initialize config file, %s", err) 357 } 358 359 creds, err := auth.CreateCredentials("myuser", "mypassword") 360 if err != nil { 361 t.Fatalf("unable create credential, %s", err) 362 } 363 364 globalActiveCred = creds 365 366 // List of test cases for validating http request authentication. 367 testCases := []struct { 368 req *http.Request 369 s3Error APIErrorCode 370 }{ 371 // When request is unsigned, access denied is returned. 372 {mustNewRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrAccessDenied}, 373 // Empty Content-Md5 header. 374 {mustNewSignedEmptyMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrInvalidDigest}, 375 // Short Content-Md5 header. 376 {mustNewSignedShortMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrInvalidDigest}, 377 // When request is properly signed, but has bad Content-MD5 header. 378 {mustNewSignedBadMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrBadDigest}, 379 // When request is properly signed, error is none. 380 {mustNewSignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrNone}, 381 } 382 383 ctx := context.Background() 384 // Validates all testcases. 385 for i, testCase := range testCases { 386 s3Error := isReqAuthenticated(ctx, testCase.req, globalServerRegion, serviceS3) 387 if s3Error != testCase.s3Error { 388 if _, err := ioutil.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error { 389 t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, ToAPIError(ctx, err).Code) 390 } 391 } 392 } 393 } 394 395 func TestCheckAdminRequestAuthType(t *testing.T) { 396 objLayer, fsDir, err := prepareFS() 397 if err != nil { 398 t.Fatal(err) 399 } 400 defer os.RemoveAll(fsDir) 401 402 if err = newTestConfig(globalMinioDefaultRegion, objLayer); err != nil { 403 t.Fatalf("unable initialize config file, %s", err) 404 } 405 406 creds, err := auth.CreateCredentials("myuser", "mypassword") 407 if err != nil { 408 t.Fatalf("unable create credential, %s", err) 409 } 410 411 globalActiveCred = creds 412 testCases := []struct { 413 Request *http.Request 414 ErrCode APIErrorCode 415 }{ 416 {Request: mustNewRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied}, 417 {Request: mustNewSignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrNone}, 418 {Request: mustNewSignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied}, 419 {Request: mustNewPresignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied}, 420 {Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied}, 421 } 422 ctx := context.Background() 423 for i, testCase := range testCases { 424 if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalServerRegion); s3Error != testCase.ErrCode { 425 t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error) 426 } 427 } 428 } 429 430 func TestValidateAdminSignature(t *testing.T) { 431 432 ctx := context.Background() 433 434 objLayer, fsDir, err := prepareFS() 435 if err != nil { 436 t.Fatal(err) 437 } 438 defer os.RemoveAll(fsDir) 439 440 if err = newTestConfig(globalMinioDefaultRegion, objLayer); err != nil { 441 t.Fatalf("unable initialize config file, %s", err) 442 } 443 444 creds, err := auth.CreateCredentials("admin", "mypassword") 445 if err != nil { 446 t.Fatalf("unable create credential, %s", err) 447 } 448 globalActiveCred = creds 449 450 testCases := []struct { 451 AccessKey string 452 SecretKey string 453 ErrCode APIErrorCode 454 }{ 455 {"", "", ErrInvalidAccessKeyID}, 456 {"admin", "", ErrSignatureDoesNotMatch}, 457 {"admin", "wrongpassword", ErrSignatureDoesNotMatch}, 458 {"wronguser", "mypassword", ErrInvalidAccessKeyID}, 459 {"", "mypassword", ErrInvalidAccessKeyID}, 460 {"admin", "mypassword", ErrNone}, 461 } 462 463 for i, testCase := range testCases { 464 req := mustNewRequest(http.MethodGet, "http://localhost:9000/", 0, nil, t) 465 if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil { 466 t.Fatalf("Unable to inititalized new signed http request %s", err) 467 } 468 _, _, _, s3Error := validateAdminSignature(ctx, req, globalMinioDefaultRegion) 469 if s3Error != testCase.ErrCode { 470 t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i+1, testCase.ErrCode, s3Error) 471 } 472 } 473 }