mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
				
	
	
		
			344 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			344 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright (c) 2015-2021 MinIO, Inc.
 | |
| //
 | |
| // This file is part of MinIO Object Storage stack
 | |
| //
 | |
| // This program is free software: you can redistribute it and/or modify
 | |
| // it under the terms of the GNU Affero General Public License as published by
 | |
| // the Free Software Foundation, either version 3 of the License, or
 | |
| // (at your option) any later version.
 | |
| //
 | |
| // This program is distributed in the hope that it will be useful
 | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| // GNU Affero General Public License for more details.
 | |
| //
 | |
| // You should have received a copy of the GNU Affero General Public License
 | |
| // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| package cmd
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/minio/madmin-go"
 | |
| 	"github.com/minio/minio/internal/auth"
 | |
| 	xhttp "github.com/minio/minio/internal/http"
 | |
| )
 | |
| 
 | |
| func TestCheckValid(t *testing.T) {
 | |
| 	ctx, cancel := context.WithCancel(context.Background())
 | |
| 	defer cancel()
 | |
| 
 | |
| 	objLayer, fsDir, err := prepareFS(ctx)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	defer os.RemoveAll(fsDir)
 | |
| 	if err = newTestConfig(globalMinioDefaultRegion, objLayer); err != nil {
 | |
| 		t.Fatalf("unable initialize config file, %s", err)
 | |
| 	}
 | |
| 
 | |
| 	initAllSubsystems(ctx)
 | |
| 	initConfigSubsystem(ctx, objLayer)
 | |
| 
 | |
| 	globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
 | |
| 
 | |
| 	req, err := newTestRequest(http.MethodGet, "http://example.com:9000/bucket/object", 0, nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	if err = signRequestV4(req, globalActiveCred.AccessKey, globalActiveCred.SecretKey); err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	_, owner, s3Err := checkKeyValid(req, globalActiveCred.AccessKey)
 | |
| 	if s3Err != ErrNone {
 | |
| 		t.Fatalf("Unexpected failure with %v", errorCodes.ToAPIErr(s3Err))
 | |
| 	}
 | |
| 
 | |
| 	if !owner {
 | |
| 		t.Fatalf("Expected owner to be 'true', found %t", owner)
 | |
| 	}
 | |
| 
 | |
| 	_, _, s3Err = checkKeyValid(req, "does-not-exist")
 | |
| 	if s3Err != ErrInvalidAccessKeyID {
 | |
| 		t.Fatalf("Expected error 'ErrInvalidAccessKeyID', found %v", s3Err)
 | |
| 	}
 | |
| 
 | |
| 	ucreds, err := auth.CreateCredentials("myuser1", "mypassword1")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("unable create credential, %s", err)
 | |
| 	}
 | |
| 
 | |
| 	globalIAMSys.CreateUser(ctx, ucreds.AccessKey, madmin.AddOrUpdateUserReq{
 | |
| 		SecretKey: ucreds.SecretKey,
 | |
| 		Status:    madmin.AccountEnabled,
 | |
| 	})
 | |
| 
 | |
| 	_, owner, s3Err = checkKeyValid(req, ucreds.AccessKey)
 | |
| 	if s3Err != ErrNone {
 | |
| 		t.Fatalf("Unexpected failure with %v", errorCodes.ToAPIErr(s3Err))
 | |
| 	}
 | |
| 
 | |
| 	if owner {
 | |
| 		t.Fatalf("Expected owner to be 'false', found %t", owner)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestSkipContentSha256Cksum - Test validate the logic which decides whether
 | |
| // to skip checksum validation based on the request header.
 | |
| func TestSkipContentSha256Cksum(t *testing.T) {
 | |
| 	testCases := []struct {
 | |
| 		inputHeaderKey   string
 | |
| 		inputHeaderValue string
 | |
| 
 | |
| 		inputQueryKey   string
 | |
| 		inputQueryValue string
 | |
| 
 | |
| 		expectedResult bool
 | |
| 	}{
 | |
| 		// Test case - 1.
 | |
| 		// Test case with "X-Amz-Content-Sha256" header set, but to empty value but we can't skip.
 | |
| 		{"X-Amz-Content-Sha256", "", "", "", false},
 | |
| 
 | |
| 		// Test case - 2.
 | |
| 		// Test case with "X-Amz-Content-Sha256" not set so we can skip.
 | |
| 		{"", "", "", "", true},
 | |
| 
 | |
| 		// Test case - 3.
 | |
| 		// Test case with "X-Amz-Content-Sha256" header set to  "UNSIGNED-PAYLOAD"
 | |
| 		// When "X-Amz-Content-Sha256" header is set to  "UNSIGNED-PAYLOAD", validation of content sha256 has to be skipped.
 | |
| 		{"X-Amz-Content-Sha256", unsignedPayload, "X-Amz-Credential", "", true},
 | |
| 
 | |
| 		// Test case - 4.
 | |
| 		// Enabling PreSigned Signature v4, but X-Amz-Content-Sha256 not set has to be skipped.
 | |
| 		{"", "", "X-Amz-Credential", "", true},
 | |
| 
 | |
| 		// Test case - 5.
 | |
| 		// Enabling PreSigned Signature v4, but X-Amz-Content-Sha256 set and its not UNSIGNED-PAYLOAD, we shouldn't skip.
 | |
| 		{"X-Amz-Content-Sha256", "somevalue", "X-Amz-Credential", "", false},
 | |
| 
 | |
| 		// Test case - 6.
 | |
| 		// Test case with "X-Amz-Content-Sha256" header set to  "UNSIGNED-PAYLOAD" and its not presigned, we should skip.
 | |
| 		{"X-Amz-Content-Sha256", unsignedPayload, "", "", true},
 | |
| 
 | |
| 		// Test case - 7.
 | |
| 		// "X-Amz-Content-Sha256" not set and  PreSigned Signature v4 not enabled, sha256 checksum calculation is not skipped.
 | |
| 		{"", "", "X-Amz-Credential", "", true},
 | |
| 
 | |
| 		// Test case - 8.
 | |
| 		// "X-Amz-Content-Sha256" has a proper value cannot skip.
 | |
| 		{"X-Amz-Content-Sha256", "somevalue", "", "", false},
 | |
| 	}
 | |
| 
 | |
| 	for i, testCase := range testCases {
 | |
| 		// creating an input HTTP request.
 | |
| 		// Only the headers are relevant for this particular test.
 | |
| 		inputReq, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("Error initializing input HTTP request: %v", err)
 | |
| 		}
 | |
| 		if testCase.inputQueryKey != "" {
 | |
| 			q := inputReq.URL.Query()
 | |
| 			q.Add(testCase.inputQueryKey, testCase.inputQueryValue)
 | |
| 			if testCase.inputHeaderKey != "" {
 | |
| 				q.Add(testCase.inputHeaderKey, testCase.inputHeaderValue)
 | |
| 			}
 | |
| 			inputReq.URL.RawQuery = q.Encode()
 | |
| 		} else {
 | |
| 			if testCase.inputHeaderKey != "" {
 | |
| 				inputReq.Header.Set(testCase.inputHeaderKey, testCase.inputHeaderValue)
 | |
| 			}
 | |
| 		}
 | |
| 		inputReq.ParseForm()
 | |
| 
 | |
| 		actualResult := skipContentSha256Cksum(inputReq)
 | |
| 		if testCase.expectedResult != actualResult {
 | |
| 			t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestIsValidRegion - Tests validate the comparison logic for asserting whether the region from http request is valid.
 | |
| func TestIsValidRegion(t *testing.T) {
 | |
| 	testCases := []struct {
 | |
| 		inputReqRegion  string
 | |
| 		inputConfRegion string
 | |
| 
 | |
| 		expectedResult bool
 | |
| 	}{
 | |
| 		{"", "", true},
 | |
| 		{globalMinioDefaultRegion, "", true},
 | |
| 		{globalMinioDefaultRegion, "US", true},
 | |
| 		{"us-west-1", "US", false},
 | |
| 		{"us-west-1", "us-west-1", true},
 | |
| 		// "US" was old naming convention for 'us-east-1'.
 | |
| 		{"US", "US", true},
 | |
| 	}
 | |
| 
 | |
| 	for i, testCase := range testCases {
 | |
| 		actualResult := isValidRegion(testCase.inputReqRegion, testCase.inputConfRegion)
 | |
| 		if testCase.expectedResult != actualResult {
 | |
| 			t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestExtractSignedHeaders - Tests validate extraction of signed headers using list of signed header keys.
 | |
| func TestExtractSignedHeaders(t *testing.T) {
 | |
| 	signedHeaders := []string{"host", "x-amz-content-sha256", "x-amz-date", "transfer-encoding"}
 | |
| 
 | |
| 	// 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.
 | |
| 	signedHeaders = append(signedHeaders, "expect")
 | |
| 	// expected header values.
 | |
| 	expectedHost := "play.min.io:9000"
 | |
| 	expectedContentSha256 := "1234abcd"
 | |
| 	expectedTime := UTCNow().Format(iso8601Format)
 | |
| 	expectedTransferEncoding := "gzip"
 | |
| 	expectedExpect := "100-continue"
 | |
| 
 | |
| 	r, err := http.NewRequest(http.MethodGet, "http://play.min.io:9000", nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatal("Unable to create http.Request :", err)
 | |
| 	}
 | |
| 	r.TransferEncoding = []string{expectedTransferEncoding}
 | |
| 
 | |
| 	// Creating input http header.
 | |
| 	inputHeader := r.Header
 | |
| 	inputHeader.Set("x-amz-content-sha256", expectedContentSha256)
 | |
| 	inputHeader.Set("x-amz-date", expectedTime)
 | |
| 	// calling the function being tested.
 | |
| 	extractedSignedHeaders, errCode := extractSignedHeaders(signedHeaders, r)
 | |
| 	if errCode != ErrNone {
 | |
| 		t.Fatalf("Expected the APIErrorCode to be %d, but got %d", ErrNone, errCode)
 | |
| 	}
 | |
| 
 | |
| 	inputQuery := r.URL.Query()
 | |
| 	// case where some headers need to get from request query
 | |
| 	signedHeaders = append(signedHeaders, "x-amz-server-side-encryption")
 | |
| 	// expect to fail with `ErrUnsignedHeaders` because couldn't find some header
 | |
| 	_, errCode = extractSignedHeaders(signedHeaders, r)
 | |
| 	if errCode != ErrUnsignedHeaders {
 | |
| 		t.Fatalf("Expected the APIErrorCode to %d, but got %d", ErrUnsignedHeaders, errCode)
 | |
| 	}
 | |
| 	// set headers value through Get parameter
 | |
| 	inputQuery.Add("x-amz-server-side-encryption", xhttp.AmzEncryptionAES)
 | |
| 	r.URL.RawQuery = inputQuery.Encode()
 | |
| 	r.ParseForm()
 | |
| 	_, errCode = extractSignedHeaders(signedHeaders, r)
 | |
| 	if errCode != ErrNone {
 | |
| 		t.Fatalf("Expected the APIErrorCode to be %d, but got %d", ErrNone, errCode)
 | |
| 	}
 | |
| 
 | |
| 	// "x-amz-content-sha256" header value from the extracted result.
 | |
| 	extractedContentSha256 := extractedSignedHeaders.Get("x-amz-content-sha256")
 | |
| 	// "host" header value from the extracted result.
 | |
| 	extractedHost := extractedSignedHeaders.Get("host")
 | |
| 	//  "x-amz-date" header from the extracted result.
 | |
| 	extractedDate := extractedSignedHeaders.Get("x-amz-date")
 | |
| 	// extracted `expect` header.
 | |
| 	extractedExpect := extractedSignedHeaders.Get("expect")
 | |
| 
 | |
| 	extractedTransferEncoding := extractedSignedHeaders.Get("transfer-encoding")
 | |
| 
 | |
| 	if expectedHost != extractedHost {
 | |
| 		t.Errorf("host header mismatch: expected `%s`, got `%s`", expectedHost, extractedHost)
 | |
| 	}
 | |
| 	// assert the result with the expected value.
 | |
| 	if expectedContentSha256 != extractedContentSha256 {
 | |
| 		t.Errorf("x-amz-content-sha256 header mismatch: expected `%s`, got `%s`", expectedContentSha256, extractedContentSha256)
 | |
| 	}
 | |
| 	if expectedTime != extractedDate {
 | |
| 		t.Errorf("x-amz-date header mismatch: expected `%s`, got `%s`", expectedTime, extractedDate)
 | |
| 	}
 | |
| 	if extractedTransferEncoding != expectedTransferEncoding {
 | |
| 		t.Errorf("transfer-encoding mismatch: expected %s, got %s", expectedTransferEncoding, extractedTransferEncoding)
 | |
| 	}
 | |
| 
 | |
| 	// Since the list of signed headers value contained `expect`, the default value of `100-continue` will be added to extracted signed headers.
 | |
| 	if extractedExpect != expectedExpect {
 | |
| 		t.Errorf("expect header incorrect value: expected `%s`, got `%s`", expectedExpect, extractedExpect)
 | |
| 	}
 | |
| 
 | |
| 	// case where the headers don't contain the one of the signed header in the signed headers list.
 | |
| 	signedHeaders = append(signedHeaders, "X-Amz-Credential")
 | |
| 	// expected to fail with `ErrUnsignedHeaders`.
 | |
| 	_, errCode = extractSignedHeaders(signedHeaders, r)
 | |
| 	if errCode != ErrUnsignedHeaders {
 | |
| 		t.Fatalf("Expected the APIErrorCode to %d, but got %d", ErrUnsignedHeaders, errCode)
 | |
| 	}
 | |
| 
 | |
| 	// case where the list of signed headers doesn't contain the host field.
 | |
| 	signedHeaders = signedHeaders[2:5]
 | |
| 	// expected to fail with `ErrUnsignedHeaders`.
 | |
| 	_, errCode = extractSignedHeaders(signedHeaders, r)
 | |
| 	if errCode != ErrUnsignedHeaders {
 | |
| 		t.Fatalf("Expected the APIErrorCode to %d, but got %d", ErrUnsignedHeaders, errCode)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestSignV4TrimAll - tests the logic of TrimAll() function
 | |
| func TestSignV4TrimAll(t *testing.T) {
 | |
| 	testCases := []struct {
 | |
| 		// Input.
 | |
| 		inputStr string
 | |
| 		// Expected result.
 | |
| 		result string
 | |
| 	}{
 | |
| 		{"本語", "本語"},
 | |
| 		{" abc ", "abc"},
 | |
| 		{" a b ", "a b"},
 | |
| 		{"a b ", "a b"},
 | |
| 		{"a  b", "a b"},
 | |
| 		{"a   b", "a b"},
 | |
| 		{"   a   b  c   ", "a b c"},
 | |
| 		{"a \t b  c   ", "a b c"},
 | |
| 		{"\"a \t b  c   ", "\"a b c"},
 | |
| 		{" \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"},
 | |
| 	}
 | |
| 
 | |
| 	// Tests generated values from url encoded name.
 | |
| 	for i, testCase := range testCases {
 | |
| 		result := signV4TrimAll(testCase.inputStr)
 | |
| 		if testCase.result != result {
 | |
| 			t.Errorf("Test %d: Expected signV4TrimAll result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Test getContentSha256Cksum
 | |
| func TestGetContentSha256Cksum(t *testing.T) {
 | |
| 	testCases := []struct {
 | |
| 		h        string // header SHA256
 | |
| 		q        string // query SHA256
 | |
| 		expected string // expected SHA256
 | |
| 	}{
 | |
| 		{"shastring", "", "shastring"},
 | |
| 		{emptySHA256, "", emptySHA256},
 | |
| 		{"", "", emptySHA256},
 | |
| 		{"", "X-Amz-Credential=random", unsignedPayload},
 | |
| 		{"", "X-Amz-Credential=random&X-Amz-Content-Sha256=" + unsignedPayload, unsignedPayload},
 | |
| 		{"", "X-Amz-Credential=random&X-Amz-Content-Sha256=shastring", "shastring"},
 | |
| 	}
 | |
| 
 | |
| 	for i, testCase := range testCases {
 | |
| 		r, err := http.NewRequest(http.MethodGet, "http://localhost/?"+testCase.q, nil)
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if testCase.h != "" {
 | |
| 			r.Header.Set("x-amz-content-sha256", testCase.h)
 | |
| 		}
 | |
| 		r.ParseForm()
 | |
| 		got := getContentSha256Cksum(r, serviceS3)
 | |
| 		if got != testCase.expected {
 | |
| 			t.Errorf("Test %d: got:%s expected:%s", i+1, got, testCase.expected)
 | |
| 		}
 | |
| 	}
 | |
| }
 |