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  }