2021-04-18 15:41:13 -04:00
// 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/>.
2016-08-01 23:54:11 -04:00
2016-08-18 19:23:42 -04:00
package cmd
2016-08-01 23:54:11 -04:00
import (
2021-10-12 16:18:02 -04:00
"context"
2016-08-01 23:54:11 -04:00
"net/http"
2021-10-12 16:18:02 -04:00
"os"
2016-08-01 23:54:11 -04:00
"testing"
2021-11-12 00:03:02 -05:00
"time"
2019-10-01 18:07:20 -04:00
2021-10-12 16:18:02 -04:00
"github.com/minio/madmin-go"
"github.com/minio/minio/internal/auth"
2021-06-01 17:59:40 -04:00
xhttp "github.com/minio/minio/internal/http"
2016-08-01 23:54:11 -04:00
)
2021-10-12 16:18:02 -04:00
func TestCheckValid ( t * testing . T ) {
2021-11-12 00:03:02 -05:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2021-10-12 16:18:02 -04:00
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 )
}
newAllSubsystems ( )
2021-11-17 16:42:08 -05:00
initConfigSubsystem ( ctx , objLayer )
2021-10-12 16:18:02 -04:00
2021-11-29 17:38:57 -05:00
globalIAMSys . Init ( ctx , objLayer , globalEtcdClient , globalNotificationSys , 2 * time . Second )
2021-10-12 16:18:02 -04:00
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 )
}
2021-12-23 12:21:21 -05:00
globalIAMSys . CreateUser ( ctx , ucreds . AccessKey , madmin . AddOrUpdateUserReq {
2021-10-12 16:18:02 -04:00
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 )
}
}
2016-09-30 17:32:13 -04:00
// TestSkipContentSha256Cksum - Test validate the logic which decides whether
// to skip checksum validation based on the request header.
2016-08-01 23:54:11 -04:00
func TestSkipContentSha256Cksum ( t * testing . T ) {
testCases := [ ] struct {
inputHeaderKey string
inputHeaderValue string
inputQueryKey string
inputQueryValue string
expectedResult bool
} {
// Test case - 1.
2018-01-09 02:19:50 -05:00
// Test case with "X-Amz-Content-Sha256" header set, but to empty value but we can't skip.
2016-08-01 23:54:11 -04:00
{ "X-Amz-Content-Sha256" , "" , "" , "" , false } ,
2018-01-09 02:19:50 -05:00
2016-08-01 23:54:11 -04:00
// Test case - 2.
2018-01-09 02:19:50 -05:00
// Test case with "X-Amz-Content-Sha256" not set so we can skip.
{ "" , "" , "" , "" , true } ,
// Test case - 3.
2016-08-01 23:54:11 -04:00
// 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.
2018-01-09 02:19:50 -05:00
{ "X-Amz-Content-Sha256" , unsignedPayload , "X-Amz-Credential" , "" , true } ,
2016-08-01 23:54:11 -04:00
// Test case - 4.
2018-01-09 02:19:50 -05:00
// 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.
2016-08-01 23:54:11 -04:00
// "X-Amz-Content-Sha256" not set and PreSigned Signature v4 not enabled, sha256 checksum calculation is not skipped.
{ "" , "" , "X-Amz-Credential" , "" , true } ,
2018-01-09 02:19:50 -05:00
// Test case - 8.
// "X-Amz-Content-Sha256" has a proper value cannot skip.
{ "X-Amz-Content-Sha256" , "somevalue" , "" , "" , false } ,
2016-08-01 23:54:11 -04:00
}
for i , testCase := range testCases {
// creating an input HTTP request.
// Only the headers are relevant for this particular test.
2020-07-20 15:52:49 -04:00
inputReq , err := http . NewRequest ( http . MethodGet , "http://example.com" , nil )
2016-08-01 23:54:11 -04:00
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 )
2018-01-09 02:19:50 -05:00
if testCase . inputHeaderKey != "" {
q . Add ( testCase . inputHeaderKey , testCase . inputHeaderValue )
}
2016-08-01 23:54:11 -04:00
inputReq . URL . RawQuery = q . Encode ( )
2018-01-09 02:19:50 -05:00
} else {
if testCase . inputHeaderKey != "" {
inputReq . Header . Set ( testCase . inputHeaderKey , testCase . inputHeaderValue )
}
2016-08-01 23:54:11 -04:00
}
2021-08-08 01:43:01 -04:00
inputReq . ParseForm ( )
2016-08-01 23:54:11 -04:00
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
} {
2017-05-15 21:17:02 -04:00
{ "" , "" , true } ,
2017-01-18 15:24:34 -05:00
{ globalMinioDefaultRegion , "" , true } ,
{ globalMinioDefaultRegion , "US" , true } ,
2016-08-01 23:54:11 -04:00
{ "us-west-1" , "US" , false } ,
{ "us-west-1" , "us-west-1" , true } ,
2016-11-06 14:47:16 -05:00
// "US" was old naming convention for 'us-east-1'.
{ "US" , "US" , true } ,
2016-08-01 23:54:11 -04:00
}
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 ) {
2017-04-05 18:08:33 -04:00
signedHeaders := [ ] string { "host" , "x-amz-content-sha256" , "x-amz-date" , "transfer-encoding" }
2016-08-01 23:54:11 -04:00
// 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.
2019-04-09 14:39:42 -04:00
expectedHost := "play.min.io:9000"
2016-08-01 23:54:11 -04:00
expectedContentSha256 := "1234abcd"
2017-03-18 14:28:41 -04:00
expectedTime := UTCNow ( ) . Format ( iso8601Format )
2017-04-05 18:08:33 -04:00
expectedTransferEncoding := "gzip"
2017-04-05 20:00:24 -04:00
expectedExpect := "100-continue"
2017-04-05 18:08:33 -04:00
2020-07-20 15:52:49 -04:00
r , err := http . NewRequest ( http . MethodGet , "http://play.min.io:9000" , nil )
2017-04-05 18:08:33 -04:00
if err != nil {
t . Fatal ( "Unable to create http.Request :" , err )
}
r . TransferEncoding = [ ] string { expectedTransferEncoding }
2016-08-01 23:54:11 -04:00
// Creating input http header.
2017-04-05 18:08:33 -04:00
inputHeader := r . Header
2017-04-05 20:00:24 -04:00
inputHeader . Set ( "x-amz-content-sha256" , expectedContentSha256 )
inputHeader . Set ( "x-amz-date" , expectedTime )
2016-08-01 23:54:11 -04:00
// calling the function being tested.
2017-04-05 18:08:33 -04:00
extractedSignedHeaders , errCode := extractSignedHeaders ( signedHeaders , r )
2016-08-09 12:13:15 -04:00
if errCode != ErrNone {
t . Fatalf ( "Expected the APIErrorCode to be %d, but got %d" , ErrNone , errCode )
}
2019-05-22 00:00:02 -04:00
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
2020-12-22 12:19:32 -05:00
inputQuery . Add ( "x-amz-server-side-encryption" , xhttp . AmzEncryptionAES )
2019-05-22 00:00:02 -04:00
r . URL . RawQuery = inputQuery . Encode ( )
2021-08-08 01:43:01 -04:00
r . ParseForm ( )
2019-05-22 00:00:02 -04:00
_ , errCode = extractSignedHeaders ( signedHeaders , r )
if errCode != ErrNone {
t . Fatalf ( "Expected the APIErrorCode to be %d, but got %d" , ErrNone , errCode )
}
2016-08-01 23:54:11 -04:00
// "x-amz-content-sha256" header value from the extracted result.
2017-04-05 20:00:24 -04:00
extractedContentSha256 := extractedSignedHeaders . Get ( "x-amz-content-sha256" )
2016-08-01 23:54:11 -04:00
// "host" header value from the extracted result.
2017-04-05 20:00:24 -04:00
extractedHost := extractedSignedHeaders . Get ( "host" )
2016-08-01 23:54:11 -04:00
// "x-amz-date" header from the extracted result.
2017-04-05 20:00:24 -04:00
extractedDate := extractedSignedHeaders . Get ( "x-amz-date" )
2016-08-01 23:54:11 -04:00
// extracted `expect` header.
2017-04-05 20:00:24 -04:00
extractedExpect := extractedSignedHeaders . Get ( "expect" )
2017-04-05 18:08:33 -04:00
2017-04-05 20:00:24 -04:00
extractedTransferEncoding := extractedSignedHeaders . Get ( "transfer-encoding" )
2017-04-05 18:08:33 -04:00
2017-04-05 20:00:24 -04:00
if expectedHost != extractedHost {
2016-08-09 12:13:15 -04:00
t . Errorf ( "host header mismatch: expected `%s`, got `%s`" , expectedHost , extractedHost )
}
2016-08-01 23:54:11 -04:00
// assert the result with the expected value.
2017-04-05 20:00:24 -04:00
if expectedContentSha256 != extractedContentSha256 {
2016-08-01 23:54:11 -04:00
t . Errorf ( "x-amz-content-sha256 header mismatch: expected `%s`, got `%s`" , expectedContentSha256 , extractedContentSha256 )
}
2017-04-05 20:00:24 -04:00
if expectedTime != extractedDate {
2016-08-01 23:54:11 -04:00
t . Errorf ( "x-amz-date header mismatch: expected `%s`, got `%s`" , expectedTime , extractedDate )
}
2017-04-05 18:08:33 -04:00
if extractedTransferEncoding != expectedTransferEncoding {
t . Errorf ( "transfer-encoding mismatch: expected %s, got %s" , expectedTransferEncoding , extractedTransferEncoding )
}
2016-08-09 12:13:15 -04:00
2016-08-01 23:54:11 -04:00
// Since the list of signed headers value contained `expect`, the default value of `100-continue` will be added to extracted signed headers.
2017-04-05 20:00:24 -04:00
if extractedExpect != expectedExpect {
t . Errorf ( "expect header incorrect value: expected `%s`, got `%s`" , expectedExpect , extractedExpect )
2016-08-01 23:54:11 -04:00
}
2016-08-09 12:13:15 -04:00
2017-04-05 20:00:24 -04:00
// case where the headers don't contain the one of the signed header in the signed headers list.
signedHeaders = append ( signedHeaders , "X-Amz-Credential" )
2016-08-09 12:13:15 -04:00
// expected to fail with `ErrUnsignedHeaders`.
2017-04-05 18:08:33 -04:00
_ , errCode = extractSignedHeaders ( signedHeaders , r )
2016-08-09 12:13:15 -04:00
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.
2017-04-05 20:00:24 -04:00
signedHeaders = signedHeaders [ 2 : 5 ]
2016-08-09 12:13:15 -04:00
// expected to fail with `ErrUnsignedHeaders`.
2017-04-05 18:08:33 -04:00
_ , errCode = extractSignedHeaders ( signedHeaders , r )
2016-08-09 12:13:15 -04:00
if errCode != ErrUnsignedHeaders {
t . Fatalf ( "Expected the APIErrorCode to %d, but got %d" , ErrUnsignedHeaders , errCode )
}
}
2016-11-04 16:52:22 -04:00
// TestSignV4TrimAll - tests the logic of TrimAll() function
func TestSignV4TrimAll ( t * testing . T ) {
2016-11-03 19:41:25 -04:00
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" } ,
2016-11-04 16:52:22 -04:00
{ " \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" } ,
2016-11-03 19:41:25 -04:00
}
// Tests generated values from url encoded name.
for i , testCase := range testCases {
result := signV4TrimAll ( testCase . inputStr )
if testCase . result != result {
2016-11-04 16:52:22 -04:00
t . Errorf ( "Test %d: Expected signV4TrimAll result to be \"%s\", but found it to be \"%s\" instead" , i + 1 , testCase . result , result )
2016-11-03 19:41:25 -04:00
}
}
}
2017-04-10 12:58:08 -04:00
// Test getContentSha256Cksum
func TestGetContentSha256Cksum ( t * testing . T ) {
testCases := [ ] struct {
h string // header SHA256
q string // query SHA256
expected string // expected SHA256
} {
{ "shastring" , "" , "shastring" } ,
2018-01-09 02:19:50 -05:00
{ emptySHA256 , "" , emptySHA256 } ,
2017-04-10 12:58:08 -04:00
{ "" , "" , emptySHA256 } ,
{ "" , "X-Amz-Credential=random" , unsignedPayload } ,
2018-01-09 02:19:50 -05:00
{ "" , "X-Amz-Credential=random&X-Amz-Content-Sha256=" + unsignedPayload , unsignedPayload } ,
2017-04-10 12:58:08 -04:00
{ "" , "X-Amz-Credential=random&X-Amz-Content-Sha256=shastring" , "shastring" } ,
}
for i , testCase := range testCases {
2020-07-20 15:52:49 -04:00
r , err := http . NewRequest ( http . MethodGet , "http://localhost/?" + testCase . q , nil )
2017-04-10 12:58:08 -04:00
if err != nil {
t . Fatal ( err )
}
if testCase . h != "" {
r . Header . Set ( "x-amz-content-sha256" , testCase . h )
}
2021-08-08 01:43:01 -04:00
r . ParseForm ( )
2019-02-27 20:46:55 -05:00
got := getContentSha256Cksum ( r , serviceS3 )
2017-04-10 12:58:08 -04:00
if got != testCase . expected {
t . Errorf ( "Test %d: got:%s expected:%s" , i + 1 , got , testCase . expected )
}
}
}