500 lines · 15.4 KB · Go
RawDownload
// 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 (	"bytes"	"context"	"io"	"io/ioutil"	"net/http"	"net/url"	"os"	"testing"	"time" 	"github.com/minio/minio/internal/auth"	iampolicy "github.com/minio/pkg/iam/policy") type nullReader struct{} func (r *nullReader) Read(b []byte) (int, error) {	return len(b), nil} // Test get request auth type.func TestGetRequestAuthType(t *testing.T) {	type testCase struct {		req   *http.Request		authT authType	}	nopCloser := ioutil.NopCloser(io.LimitReader(&nullReader{}, 1024))	testCases := []testCase{		// Test case - 1		// Check for generic signature v4 header.		{			req: &http.Request{				URL: &url.URL{					Host:   "127.0.0.1:9000",					Scheme: httpScheme,					Path:   SlashSeparator,				},				Header: http.Header{					"Authorization":        []string{"AWS4-HMAC-SHA256 <cred_string>"},					"X-Amz-Content-Sha256": []string{streamingContentSHA256},					"Content-Encoding":     []string{streamingContentEncoding},				},				Method: http.MethodPut,				Body:   nopCloser,			},			authT: authTypeStreamingSigned,		},		// Test case - 2		// Check for JWT header.		{			req: &http.Request{				URL: &url.URL{					Host:   "127.0.0.1:9000",					Scheme: httpScheme,					Path:   SlashSeparator,				},				Header: http.Header{					"Authorization": []string{"Bearer 12313123"},				},			},			authT: authTypeJWT,		},		// Test case - 3		// Empty authorization header.		{			req: &http.Request{				URL: &url.URL{					Host:   "127.0.0.1:9000",					Scheme: httpScheme,					Path:   SlashSeparator,				},				Header: http.Header{					"Authorization": []string{""},				},			},			authT: authTypeUnknown,		},		// Test case - 4		// Check for presigned.		{			req: &http.Request{				URL: &url.URL{					Host:     "127.0.0.1:9000",					Scheme:   httpScheme,					Path:     SlashSeparator,					RawQuery: "X-Amz-Credential=EXAMPLEINVALIDEXAMPL%2Fs3%2F20160314%2Fus-east-1",				},			},			authT: authTypePresigned,		},		// Test case - 5		// Check for post policy.		{			req: &http.Request{				URL: &url.URL{					Host:   "127.0.0.1:9000",					Scheme: httpScheme,					Path:   SlashSeparator,				},				Header: http.Header{					"Content-Type": []string{"multipart/form-data"},				},				Method: http.MethodPost,				Body:   nopCloser,			},			authT: authTypePostPolicy,		},	} 	// .. Tests all request auth type.	for i, testc := range testCases {		authT := getRequestAuthType(testc.req)		if authT != testc.authT {			t.Errorf("Test %d: Expected %d, got %d", i+1, testc.authT, authT)		}	}} // Test all s3 supported auth types.func TestS3SupportedAuthType(t *testing.T) {	type testCase struct {		authT authType		pass  bool	}	// List of all valid and invalid test cases.	testCases := []testCase{		// Test 1 - supported s3 type anonymous.		{			authT: authTypeAnonymous,			pass:  true,		},		// Test 2 - supported s3 type presigned.		{			authT: authTypePresigned,			pass:  true,		},		// Test 3 - supported s3 type signed.		{			authT: authTypeSigned,			pass:  true,		},		// Test 4 - supported s3 type with post policy.		{			authT: authTypePostPolicy,			pass:  true,		},		// Test 5 - supported s3 type with streaming signed.		{			authT: authTypeStreamingSigned,			pass:  true,		},		// Test 6 - supported s3 type with signature v2.		{			authT: authTypeSignedV2,			pass:  true,		},		// Test 7 - supported s3 type with presign v2.		{			authT: authTypePresignedV2,			pass:  true,		},		// Test 8 - JWT is not supported s3 type.		{			authT: authTypeJWT,			pass:  false,		},		// Test 9 - unknown auth header is not supported s3 type.		{			authT: authTypeUnknown,			pass:  false,		},		// Test 10 - some new auth type is not supported s3 type.		{			authT: authType(9),			pass:  false,		},	}	// Validate all the test cases.	for i, tt := range testCases {		ok := isSupportedS3AuthType(tt.authT)		if ok != tt.pass {			t.Errorf("Test %d:, Expected %t, got %t", i+1, tt.pass, ok)		}	}} func TestIsRequestPresignedSignatureV2(t *testing.T) {	testCases := []struct {		inputQueryKey   string		inputQueryValue string		expectedResult  bool	}{		// Test case - 1.		// Test case with query key "AWSAccessKeyId" set.		{"", "", false},		// Test case - 2.		{"AWSAccessKeyId", "", true},		// Test case - 3.		{"X-Amz-Content-Sha256", "", false},	} 	for i, testCase := range testCases {		// creating an input HTTP request.		// Only the query parameters 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)		}		q := inputReq.URL.Query()		q.Add(testCase.inputQueryKey, testCase.inputQueryValue)		inputReq.URL.RawQuery = q.Encode()		inputReq.ParseForm() 		actualResult := isRequestPresignedSignatureV2(inputReq)		if testCase.expectedResult != actualResult {			t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult)		}	}} // TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature verision v4 detection.func TestIsRequestPresignedSignatureV4(t *testing.T) {	testCases := []struct {		inputQueryKey   string		inputQueryValue string		expectedResult  bool	}{		// Test case - 1.		// Test case with query key ""X-Amz-Credential" set.		{"", "", false},		// Test case - 2.		{"X-Amz-Credential", "", true},		// Test case - 3.		{"X-Amz-Content-Sha256", "", false},	} 	for i, testCase := range testCases {		// creating an input HTTP request.		// Only the query parameters 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)		}		q := inputReq.URL.Query()		q.Add(testCase.inputQueryKey, testCase.inputQueryValue)		inputReq.URL.RawQuery = q.Encode()		inputReq.ParseForm() 		actualResult := isRequestPresignedSignatureV4(inputReq)		if testCase.expectedResult != actualResult {			t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult)		}	}} // Provides a fully populated http request instance, fails otherwise.func mustNewRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {	req, err := newTestRequest(method, urlStr, contentLength, body)	if err != nil {		t.Fatalf("Unable to initialize new http request %s", err)	}	return req} // This is similar to mustNewRequest but additionally the request// is signed with AWS Signature V4, fails if not able to do so.func mustNewSignedRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {	req := mustNewRequest(method, urlStr, contentLength, body, t)	cred := globalActiveCred	if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {		t.Fatalf("Unable to inititalized new signed http request %s", err)	}	return req} // This is similar to mustNewRequest but additionally the request// is signed with AWS Signature V2, fails if not able to do so.func mustNewSignedV2Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {	req := mustNewRequest(method, urlStr, contentLength, body, t)	cred := globalActiveCred	if err := signRequestV2(req, cred.AccessKey, cred.SecretKey); err != nil {		t.Fatalf("Unable to inititalized new signed http request %s", err)	}	return req} // This is similar to mustNewRequest but additionally the request// is presigned with AWS Signature V2, fails if not able to do so.func mustNewPresignedV2Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {	req := mustNewRequest(method, urlStr, contentLength, body, t)	cred := globalActiveCred	if err := preSignV2(req, cred.AccessKey, cred.SecretKey, time.Now().Add(10*time.Minute).Unix()); err != nil {		t.Fatalf("Unable to inititalized new signed http request %s", err)	}	return req} // This is similar to mustNewRequest but additionally the request// is presigned with AWS Signature V4, fails if not able to do so.func mustNewPresignedRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {	req := mustNewRequest(method, urlStr, contentLength, body, t)	cred := globalActiveCred	if err := preSignV4(req, cred.AccessKey, cred.SecretKey, time.Now().Add(10*time.Minute).Unix()); err != nil {		t.Fatalf("Unable to inititalized new signed http request %s", err)	}	return req} func mustNewSignedShortMD5Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {	req := mustNewRequest(method, urlStr, contentLength, body, t)	req.Header.Set("Content-Md5", "invalid-digest")	cred := globalActiveCred	if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {		t.Fatalf("Unable to initialized new signed http request %s", err)	}	return req} func mustNewSignedEmptyMD5Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {	req := mustNewRequest(method, urlStr, contentLength, body, t)	req.Header.Set("Content-Md5", "")	cred := globalActiveCred	if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {		t.Fatalf("Unable to initialized new signed http request %s", err)	}	return req} func mustNewSignedBadMD5Request(method string, urlStr string, contentLength int64,	body io.ReadSeeker, t *testing.T,) *http.Request {	req := mustNewRequest(method, urlStr, contentLength, body, t)	req.Header.Set("Content-Md5", "YWFhYWFhYWFhYWFhYWFhCg==")	cred := globalActiveCred	if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {		t.Fatalf("Unable to initialized new signed http request %s", err)	}	return req} // Tests is requested authenticated function, tests replies for s3 errors.func TestIsReqAuthenticated(t *testing.T) {	objLayer, fsDir, err := prepareFS()	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, cancel := context.WithCancel(context.Background())	defer cancel() 	initConfigSubsystem(ctx, objLayer) 	globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second) 	creds, err := auth.CreateCredentials("myuser", "mypassword")	if err != nil {		t.Fatalf("unable create credential, %s", err)	} 	globalActiveCred = creds 	// List of test cases for validating http request authentication.	testCases := []struct {		req     *http.Request		s3Error APIErrorCode	}{		// When request is unsigned, access denied is returned.		{mustNewRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrAccessDenied},		// Empty Content-Md5 header.		{mustNewSignedEmptyMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrInvalidDigest},		// Short Content-Md5 header.		{mustNewSignedShortMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrInvalidDigest},		// When request is properly signed, but has bad Content-MD5 header.		{mustNewSignedBadMD5Request(http.MethodPut, "http://127.0.0.1:9000/", 5, bytes.NewReader([]byte("hello")), t), ErrBadDigest},		// When request is properly signed, error is none.		{mustNewSignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrNone},	} 	// Validates all testcases.	for i, testCase := range testCases {		s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region, serviceS3)		if s3Error != testCase.s3Error {			if _, err := ioutil.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {				t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)			}		}	}} func TestCheckAdminRequestAuthType(t *testing.T) {	objLayer, fsDir, err := prepareFS()	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)	} 	creds, err := auth.CreateCredentials("myuser", "mypassword")	if err != nil {		t.Fatalf("unable create credential, %s", err)	} 	globalActiveCred = creds	testCases := []struct {		Request *http.Request		ErrCode APIErrorCode	}{		{Request: mustNewRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},		{Request: mustNewSignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrNone},		{Request: mustNewSignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},		{Request: mustNewPresignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},		{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},	}	ctx := context.Background()	for i, testCase := range testCases {		if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {			t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)		}	}} func TestValidateAdminSignature(t *testing.T) {	ctx, cancel := context.WithCancel(context.Background())	defer cancel() 	objLayer, fsDir, err := prepareFS()	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() 	initConfigSubsystem(ctx, objLayer) 	globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second) 	creds, err := auth.CreateCredentials("admin", "mypassword")	if err != nil {		t.Fatalf("unable create credential, %s", err)	}	globalActiveCred = creds 	testCases := []struct {		AccessKey string		SecretKey string		ErrCode   APIErrorCode	}{		{"", "", ErrInvalidAccessKeyID},		{"admin", "", ErrSignatureDoesNotMatch},		{"admin", "wrongpassword", ErrSignatureDoesNotMatch},		{"wronguser", "mypassword", ErrInvalidAccessKeyID},		{"", "mypassword", ErrInvalidAccessKeyID},		{"admin", "mypassword", ErrNone},	} 	for i, testCase := range testCases {		req := mustNewRequest(http.MethodGet, "http://localhost:9000/", 0, nil, t)		if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil {			t.Fatalf("Unable to inititalized new signed http request %s", err)		}		_, _, _, s3Error := validateAdminSignature(ctx, req, globalMinioDefaultRegion)		if s3Error != testCase.ErrCode {			t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i+1, testCase.ErrCode, s3Error)		}	}}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t