storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/signature-v4-utils_test.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2015, 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  	"net/http"
    21  	"testing"
    22  
    23  	xhttp "storj.io/minio/cmd/http"
    24  )
    25  
    26  // TestSkipContentSha256Cksum - Test validate the logic which decides whether
    27  // to skip checksum validation based on the request header.
    28  func TestSkipContentSha256Cksum(t *testing.T) {
    29  	testCases := []struct {
    30  		inputHeaderKey   string
    31  		inputHeaderValue string
    32  
    33  		inputQueryKey   string
    34  		inputQueryValue string
    35  
    36  		expectedResult bool
    37  	}{
    38  		// Test case - 1.
    39  		// Test case with "X-Amz-Content-Sha256" header set, but to empty value but we can't skip.
    40  		{"X-Amz-Content-Sha256", "", "", "", false},
    41  
    42  		// Test case - 2.
    43  		// Test case with "X-Amz-Content-Sha256" not set so we can skip.
    44  		{"", "", "", "", true},
    45  
    46  		// Test case - 3.
    47  		// Test case with "X-Amz-Content-Sha256" header set to  "UNSIGNED-PAYLOAD"
    48  		// When "X-Amz-Content-Sha256" header is set to  "UNSIGNED-PAYLOAD", validation of content sha256 has to be skipped.
    49  		{"X-Amz-Content-Sha256", unsignedPayload, "X-Amz-Credential", "", true},
    50  
    51  		// Test case - 4.
    52  		// Enabling PreSigned Signature v4, but X-Amz-Content-Sha256 not set has to be skipped.
    53  		{"", "", "X-Amz-Credential", "", true},
    54  
    55  		// Test case - 5.
    56  		// Enabling PreSigned Signature v4, but X-Amz-Content-Sha256 set and its not UNSIGNED-PAYLOAD, we shouldn't skip.
    57  		{"X-Amz-Content-Sha256", "somevalue", "X-Amz-Credential", "", false},
    58  
    59  		// Test case - 6.
    60  		// Test case with "X-Amz-Content-Sha256" header set to  "UNSIGNED-PAYLOAD" and its not presigned, we should skip.
    61  		{"X-Amz-Content-Sha256", unsignedPayload, "", "", true},
    62  
    63  		// Test case - 7.
    64  		// "X-Amz-Content-Sha256" not set and  PreSigned Signature v4 not enabled, sha256 checksum calculation is not skipped.
    65  		{"", "", "X-Amz-Credential", "", true},
    66  
    67  		// Test case - 8.
    68  		// "X-Amz-Content-Sha256" has a proper value cannot skip.
    69  		{"X-Amz-Content-Sha256", "somevalue", "", "", false},
    70  	}
    71  
    72  	for i, testCase := range testCases {
    73  		// creating an input HTTP request.
    74  		// Only the headers are relevant for this particular test.
    75  		inputReq, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
    76  		if err != nil {
    77  			t.Fatalf("Error initializing input HTTP request: %v", err)
    78  		}
    79  		if testCase.inputQueryKey != "" {
    80  			q := inputReq.URL.Query()
    81  			q.Add(testCase.inputQueryKey, testCase.inputQueryValue)
    82  			if testCase.inputHeaderKey != "" {
    83  				q.Add(testCase.inputHeaderKey, testCase.inputHeaderValue)
    84  			}
    85  			inputReq.URL.RawQuery = q.Encode()
    86  		} else {
    87  			if testCase.inputHeaderKey != "" {
    88  				inputReq.Header.Set(testCase.inputHeaderKey, testCase.inputHeaderValue)
    89  			}
    90  		}
    91  
    92  		actualResult := skipContentSha256Cksum(inputReq)
    93  		if testCase.expectedResult != actualResult {
    94  			t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult)
    95  		}
    96  	}
    97  }
    98  
    99  // TestIsValidRegion - Tests validate the comparison logic for asserting whether the region from http request is valid.
   100  func TestIsValidRegion(t *testing.T) {
   101  	testCases := []struct {
   102  		inputReqRegion  string
   103  		inputConfRegion string
   104  
   105  		expectedResult bool
   106  	}{
   107  
   108  		{"", "", true},
   109  		{globalMinioDefaultRegion, "", true},
   110  		{globalMinioDefaultRegion, "US", true},
   111  		{"us-west-1", "US", false},
   112  		{"us-west-1", "us-west-1", true},
   113  		// "US" was old naming convention for 'us-east-1'.
   114  		{"US", "US", true},
   115  	}
   116  
   117  	for i, testCase := range testCases {
   118  		actualResult := isValidRegion(testCase.inputReqRegion, testCase.inputConfRegion)
   119  		if testCase.expectedResult != actualResult {
   120  			t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult)
   121  		}
   122  	}
   123  }
   124  
   125  // TestExtractSignedHeaders - Tests validate extraction of signed headers using list of signed header keys.
   126  func TestExtractSignedHeaders(t *testing.T) {
   127  	signedHeaders := []string{"host", "x-amz-content-sha256", "x-amz-date", "transfer-encoding"}
   128  
   129  	// If the `expect` key exists in the signed headers then golang server would have stripped out the value, expecting the `expect` header set to `100-continue` in the result.
   130  	signedHeaders = append(signedHeaders, "expect")
   131  	// expected header values.
   132  	expectedHost := "play.min.io:9000"
   133  	expectedContentSha256 := "1234abcd"
   134  	expectedTime := UTCNow().Format(iso8601Format)
   135  	expectedTransferEncoding := "gzip"
   136  	expectedExpect := "100-continue"
   137  
   138  	r, err := http.NewRequest(http.MethodGet, "http://play.min.io:9000", nil)
   139  	if err != nil {
   140  		t.Fatal("Unable to create http.Request :", err)
   141  	}
   142  	r.TransferEncoding = []string{expectedTransferEncoding}
   143  
   144  	// Creating input http header.
   145  	inputHeader := r.Header
   146  	inputHeader.Set("x-amz-content-sha256", expectedContentSha256)
   147  	inputHeader.Set("x-amz-date", expectedTime)
   148  	// calling the function being tested.
   149  	extractedSignedHeaders, errCode := extractSignedHeaders(signedHeaders, r)
   150  	if errCode != ErrNone {
   151  		t.Fatalf("Expected the APIErrorCode to be %d, but got %d", ErrNone, errCode)
   152  	}
   153  
   154  	inputQuery := r.URL.Query()
   155  	// case where some headers need to get from request query
   156  	signedHeaders = append(signedHeaders, "x-amz-server-side-encryption")
   157  	// expect to fail with `ErrUnsignedHeaders` because couldn't find some header
   158  	_, errCode = extractSignedHeaders(signedHeaders, r)
   159  	if errCode != ErrUnsignedHeaders {
   160  		t.Fatalf("Expected the APIErrorCode to %d, but got %d", ErrUnsignedHeaders, errCode)
   161  	}
   162  	// set headers value through Get parameter
   163  	inputQuery.Add("x-amz-server-side-encryption", xhttp.AmzEncryptionAES)
   164  	r.URL.RawQuery = inputQuery.Encode()
   165  	_, errCode = extractSignedHeaders(signedHeaders, r)
   166  	if errCode != ErrNone {
   167  		t.Fatalf("Expected the APIErrorCode to be %d, but got %d", ErrNone, errCode)
   168  	}
   169  
   170  	// "x-amz-content-sha256" header value from the extracted result.
   171  	extractedContentSha256 := extractedSignedHeaders.Get("x-amz-content-sha256")
   172  	// "host" header value from the extracted result.
   173  	extractedHost := extractedSignedHeaders.Get("host")
   174  	//  "x-amz-date" header from the extracted result.
   175  	extractedDate := extractedSignedHeaders.Get("x-amz-date")
   176  	// extracted `expect` header.
   177  	extractedExpect := extractedSignedHeaders.Get("expect")
   178  
   179  	extractedTransferEncoding := extractedSignedHeaders.Get("transfer-encoding")
   180  
   181  	if expectedHost != extractedHost {
   182  		t.Errorf("host header mismatch: expected `%s`, got `%s`", expectedHost, extractedHost)
   183  	}
   184  	// assert the result with the expected value.
   185  	if expectedContentSha256 != extractedContentSha256 {
   186  		t.Errorf("x-amz-content-sha256 header mismatch: expected `%s`, got `%s`", expectedContentSha256, extractedContentSha256)
   187  	}
   188  	if expectedTime != extractedDate {
   189  		t.Errorf("x-amz-date header mismatch: expected `%s`, got `%s`", expectedTime, extractedDate)
   190  	}
   191  	if extractedTransferEncoding != expectedTransferEncoding {
   192  		t.Errorf("transfer-encoding mismatch: expected %s, got %s", expectedTransferEncoding, extractedTransferEncoding)
   193  	}
   194  
   195  	// Since the list of signed headers value contained `expect`, the default value of `100-continue` will be added to extracted signed headers.
   196  	if extractedExpect != expectedExpect {
   197  		t.Errorf("expect header incorrect value: expected `%s`, got `%s`", expectedExpect, extractedExpect)
   198  	}
   199  
   200  	// case where the headers don't contain the one of the signed header in the signed headers list.
   201  	signedHeaders = append(signedHeaders, "X-Amz-Credential")
   202  	// expected to fail with `ErrUnsignedHeaders`.
   203  	_, errCode = extractSignedHeaders(signedHeaders, r)
   204  	if errCode != ErrUnsignedHeaders {
   205  		t.Fatalf("Expected the APIErrorCode to %d, but got %d", ErrUnsignedHeaders, errCode)
   206  	}
   207  
   208  	// case where the list of signed headers doesn't contain the host field.
   209  	signedHeaders = signedHeaders[2:5]
   210  	// expected to fail with `ErrUnsignedHeaders`.
   211  	_, errCode = extractSignedHeaders(signedHeaders, r)
   212  	if errCode != ErrUnsignedHeaders {
   213  		t.Fatalf("Expected the APIErrorCode to %d, but got %d", ErrUnsignedHeaders, errCode)
   214  	}
   215  }
   216  
   217  // TestSignV4TrimAll - tests the logic of TrimAll() function
   218  func TestSignV4TrimAll(t *testing.T) {
   219  	testCases := []struct {
   220  		// Input.
   221  		inputStr string
   222  		// Expected result.
   223  		result string
   224  	}{
   225  		{"本語", "本語"},
   226  		{" abc ", "abc"},
   227  		{" a b ", "a b"},
   228  		{"a b ", "a b"},
   229  		{"a  b", "a b"},
   230  		{"a   b", "a b"},
   231  		{"   a   b  c   ", "a b c"},
   232  		{"a \t b  c   ", "a b c"},
   233  		{"\"a \t b  c   ", "\"a b c"},
   234  		{" \t\n\u000b\r\fa \t\n\u000b\r\f b \t\n\u000b\r\f c \t\n\u000b\r\f", "a b c"},
   235  	}
   236  
   237  	// Tests generated values from url encoded name.
   238  	for i, testCase := range testCases {
   239  		result := signV4TrimAll(testCase.inputStr)
   240  		if testCase.result != result {
   241  			t.Errorf("Test %d: Expected signV4TrimAll result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result)
   242  		}
   243  	}
   244  }
   245  
   246  // Test getContentSha256Cksum
   247  func TestGetContentSha256Cksum(t *testing.T) {
   248  	testCases := []struct {
   249  		h        string // header SHA256
   250  		q        string // query SHA256
   251  		expected string // expected SHA256
   252  	}{
   253  		{"shastring", "", "shastring"},
   254  		{emptySHA256, "", emptySHA256},
   255  		{"", "", emptySHA256},
   256  		{"", "X-Amz-Credential=random", unsignedPayload},
   257  		{"", "X-Amz-Credential=random&X-Amz-Content-Sha256=" + unsignedPayload, unsignedPayload},
   258  		{"", "X-Amz-Credential=random&X-Amz-Content-Sha256=shastring", "shastring"},
   259  	}
   260  
   261  	for i, testCase := range testCases {
   262  		r, err := http.NewRequest(http.MethodGet, "http://localhost/?"+testCase.q, nil)
   263  		if err != nil {
   264  			t.Fatal(err)
   265  		}
   266  		if testCase.h != "" {
   267  			r.Header.Set("x-amz-content-sha256", testCase.h)
   268  		}
   269  		got := getContentSha256Cksum(r, serviceS3)
   270  		if got != testCase.expected {
   271  			t.Errorf("Test %d: got:%s expected:%s", i+1, got, testCase.expected)
   272  		}
   273  	}
   274  }