mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
Translate storage access denied error to S3 Access Denied response (#3015)
This commit is contained in:
parent
8876e0a80a
commit
41f9ab1c69
@ -64,6 +64,7 @@ const (
|
|||||||
ErrInvalidCopySource
|
ErrInvalidCopySource
|
||||||
ErrInvalidCopyDest
|
ErrInvalidCopyDest
|
||||||
ErrInvalidPolicyDocument
|
ErrInvalidPolicyDocument
|
||||||
|
ErrInvalidObjectState
|
||||||
ErrMalformedXML
|
ErrMalformedXML
|
||||||
ErrMissingContentLength
|
ErrMissingContentLength
|
||||||
ErrMissingContentMD5
|
ErrMissingContentMD5
|
||||||
@ -307,6 +308,11 @@ var errorCodeResponse = map[APIErrorCode]APIError{
|
|||||||
Description: "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
|
Description: "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrInvalidObjectState: {
|
||||||
|
Code: "InvalidObjectState",
|
||||||
|
Description: "The operation is not valid for the current state of the object.",
|
||||||
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
|
},
|
||||||
ErrAuthorizationHeaderMalformed: {
|
ErrAuthorizationHeaderMalformed: {
|
||||||
Code: "AuthorizationHeaderMalformed",
|
Code: "AuthorizationHeaderMalformed",
|
||||||
Description: "The authorization header is malformed; the region is wrong; expecting 'us-east-1'.",
|
Description: "The authorization header is malformed; the region is wrong; expecting 'us-east-1'.",
|
||||||
@ -587,6 +593,8 @@ func toAPIErrorCode(err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrIncompleteBody
|
apiErr = ErrIncompleteBody
|
||||||
case ObjectExistsAsDirectory:
|
case ObjectExistsAsDirectory:
|
||||||
apiErr = ErrObjectExistsAsDirectory
|
apiErr = ErrObjectExistsAsDirectory
|
||||||
|
case PrefixAccessDenied:
|
||||||
|
apiErr = ErrAccessDenied
|
||||||
case BucketNameInvalid:
|
case BucketNameInvalid:
|
||||||
apiErr = ErrInvalidBucketName
|
apiErr = ErrInvalidBucketName
|
||||||
case BucketNotFound:
|
case BucketNotFound:
|
||||||
|
@ -20,6 +20,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -175,9 +177,121 @@ func testGetObject(obj ObjectLayer, instanceType string, t TestErrHandler) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrapper for calling GetObject with permission denied expected
|
||||||
|
func TestGetObjectPermissionDenied(t *testing.T) {
|
||||||
|
// Windows doesn't support Chmod under golang
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
ExecObjectLayerDiskAlteredTest(t, testGetObjectPermissionDenied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetObject when we are allowed to access some dirs and objects
|
||||||
|
func testGetObjectPermissionDenied(obj ObjectLayer, instanceType string, disks []string, t *testing.T) {
|
||||||
|
// Setup for the tests.
|
||||||
|
bucketName := getRandomBucketName()
|
||||||
|
// create bucket.
|
||||||
|
err := obj.MakeBucket(bucketName)
|
||||||
|
// Stop the test if creation of the bucket fails.
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesData := []struct {
|
||||||
|
byteData []byte
|
||||||
|
}{
|
||||||
|
{generateBytesData(6 * 1024 * 1024)},
|
||||||
|
}
|
||||||
|
// set of inputs for uploading the objects before tests for downloading is done.
|
||||||
|
putObjectInputs := []struct {
|
||||||
|
bucketName string
|
||||||
|
objectName string
|
||||||
|
contentLength int64
|
||||||
|
textData []byte
|
||||||
|
metaData map[string]string
|
||||||
|
}{
|
||||||
|
{bucketName, "test-object1", int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)},
|
||||||
|
{bucketName, "test-object2", int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)},
|
||||||
|
{bucketName, "dir/test-object3", int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)},
|
||||||
|
}
|
||||||
|
sha256sum := ""
|
||||||
|
// iterate through the above set of inputs and upkoad the object.
|
||||||
|
for i, input := range putObjectInputs {
|
||||||
|
// uploading the object.
|
||||||
|
_, err = obj.PutObject(input.bucketName, input.objectName, input.contentLength, bytes.NewBuffer(input.textData), input.metaData, sha256sum)
|
||||||
|
// if object upload fails stop the test.
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set of empty buffers used to fill GetObject data.
|
||||||
|
buffers := []*bytes.Buffer{
|
||||||
|
new(bytes.Buffer),
|
||||||
|
}
|
||||||
|
|
||||||
|
// test cases with set of inputs
|
||||||
|
testCases := []struct {
|
||||||
|
bucketName string
|
||||||
|
objectName string
|
||||||
|
chmodPath string
|
||||||
|
startOffset int64
|
||||||
|
length int64
|
||||||
|
// data obtained/fetched from GetObject.
|
||||||
|
getObjectData *bytes.Buffer
|
||||||
|
// writer which governs the write into the `getObjectData`.
|
||||||
|
writer io.Writer
|
||||||
|
// flag indicating whether the test for given ase should pass.
|
||||||
|
shouldPass bool
|
||||||
|
// expected Result.
|
||||||
|
expectedData []byte
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
// Test 1 - chmod 000 bucket/test-object1
|
||||||
|
{bucketName, "test-object1", "test-object1", 0, int64(len(bytesData[0].byteData)), buffers[0], buffers[0], false, bytesData[0].byteData, PrefixAccessDenied{Bucket: bucketName, Object: "test-object1"}},
|
||||||
|
// Test 2 - chmod 000 bucket/dir/
|
||||||
|
{bucketName, "dir/test-object2", "dir", 0, int64(len(bytesData[0].byteData)), buffers[0], buffers[0], false, bytesData[0].byteData, PrefixAccessDenied{Bucket: bucketName, Object: "dir/test-object2"}},
|
||||||
|
// Test 3 - chmod 000 bucket/
|
||||||
|
{bucketName, "test-object3", "", 0, int64(len(bytesData[0].byteData)), buffers[0], buffers[0], false, bytesData[0].byteData, PrefixAccessDenied{Bucket: bucketName, Object: "test-object3"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, testCase := range testCases {
|
||||||
|
for _, d := range disks {
|
||||||
|
err = os.Chmod(d+"/"+testCase.bucketName+"/"+testCase.chmodPath, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %d, Unable to chmod: %v", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = obj.GetObject(testCase.bucketName, testCase.objectName, testCase.startOffset, testCase.length, testCase.writer)
|
||||||
|
if err != nil && testCase.shouldPass {
|
||||||
|
t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, err.Error())
|
||||||
|
}
|
||||||
|
if err == nil && !testCase.shouldPass {
|
||||||
|
t.Errorf("Test %d: %s: Expected to fail with <ERROR> \"%s\", but passed instead.", i+1, instanceType, testCase.err.Error())
|
||||||
|
}
|
||||||
|
// Failed as expected, but does it fail for the expected reason.
|
||||||
|
if err != nil && !testCase.shouldPass {
|
||||||
|
if !strings.Contains(err.Error(), testCase.err.Error()) {
|
||||||
|
t.Errorf("Test %d: %s: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead.", i+1, instanceType, testCase.err.Error(), err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Since there are cases for which GetObject fails, this is
|
||||||
|
// necessary. Test passes as expected, but the output values
|
||||||
|
// are verified for correctness here.
|
||||||
|
if err == nil && testCase.shouldPass {
|
||||||
|
if !bytes.Equal(testCase.expectedData, testCase.getObjectData.Bytes()) {
|
||||||
|
t.Errorf("Test %d: %s: Data Mismatch: Expected data and the fetched data from GetObject doesn't match.", i+1, instanceType)
|
||||||
|
}
|
||||||
|
// empty the buffer so that it can be used to further cases.
|
||||||
|
testCase.getObjectData.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Wrapper for calling GetObject tests for both XL multiple disks and single node setup.
|
// Wrapper for calling GetObject tests for both XL multiple disks and single node setup.
|
||||||
func TestGetObjectDiskNotFound(t *testing.T) {
|
func TestGetObjectDiskNotFound(t *testing.T) {
|
||||||
ExecObjectLayerDiskNotFoundTest(t, testGetObjectDiskNotFound)
|
ExecObjectLayerDiskAlteredTest(t, testGetObjectDiskNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectLayer.GetObject is called with series of cases for valid and erroneous inputs and the result is validated.
|
// ObjectLayer.GetObject is called with series of cases for valid and erroneous inputs and the result is validated.
|
||||||
|
@ -160,7 +160,7 @@ func testObjectAPIIsUploadIDExists(obj ObjectLayer, instanceType string, t TestE
|
|||||||
// Wrapper for calling TestPutObjectPartDiskNotFound tests for both XL
|
// Wrapper for calling TestPutObjectPartDiskNotFound tests for both XL
|
||||||
// write quorum.
|
// write quorum.
|
||||||
func TestPutObjectPartDiskNotFound(t *testing.T) {
|
func TestPutObjectPartDiskNotFound(t *testing.T) {
|
||||||
ExecObjectLayerDiskNotFoundTest(t, testPutObjectPartDiskNotFound)
|
ExecObjectLayerDiskAlteredTest(t, testPutObjectPartDiskNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testPutObjectPartDiskNotFound - Tests validate PutObjectPart behavior when disks go offline.
|
// testPutObjectPartDiskNotFound - Tests validate PutObjectPart behavior when disks go offline.
|
||||||
@ -1274,7 +1274,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
|
|||||||
|
|
||||||
// Wrapper for calling TestListObjectPartsDiskNotFound tests for both XL multiple disks and single node setup.
|
// Wrapper for calling TestListObjectPartsDiskNotFound tests for both XL multiple disks and single node setup.
|
||||||
func TestListObjectPartsDiskNotFound(t *testing.T) {
|
func TestListObjectPartsDiskNotFound(t *testing.T) {
|
||||||
ExecObjectLayerDiskNotFoundTest(t, testListObjectPartsDiskNotFound)
|
ExecObjectLayerDiskAlteredTest(t, testListObjectPartsDiskNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testListObjectParts - Tests validate listing of object parts when disks go offline.
|
// testListObjectParts - Tests validate listing of object parts when disks go offline.
|
||||||
|
@ -185,7 +185,7 @@ func testObjectAPIPutObject(obj ObjectLayer, instanceType string, t TestErrHandl
|
|||||||
// Wrapper for calling PutObject tests for both XL multiple disks case
|
// Wrapper for calling PutObject tests for both XL multiple disks case
|
||||||
// when quorum is not available.
|
// when quorum is not available.
|
||||||
func TestObjectAPIPutObjectDiskNotFound(t *testing.T) {
|
func TestObjectAPIPutObjectDiskNotFound(t *testing.T) {
|
||||||
ExecObjectLayerDiskNotFoundTest(t, testObjectAPIPutObjectDiskNotFOund)
|
ExecObjectLayerDiskAlteredTest(t, testObjectAPIPutObjectDiskNotFOund)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests validate correctness of PutObject.
|
// Tests validate correctness of PutObject.
|
||||||
|
@ -46,6 +46,13 @@ func toObjectErr(err error, params ...string) error {
|
|||||||
}
|
}
|
||||||
case errDiskFull:
|
case errDiskFull:
|
||||||
err = StorageFull{}
|
err = StorageFull{}
|
||||||
|
case errFileAccessDenied:
|
||||||
|
if len(params) >= 2 {
|
||||||
|
err = PrefixAccessDenied{
|
||||||
|
Bucket: params[0],
|
||||||
|
Object: params[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
case errIsNotRegular, errFileAccessDenied:
|
case errIsNotRegular, errFileAccessDenied:
|
||||||
if len(params) >= 2 {
|
if len(params) >= 2 {
|
||||||
err = ObjectExistsAsDirectory{
|
err = ObjectExistsAsDirectory{
|
||||||
@ -145,6 +152,13 @@ func (e ObjectExistsAsDirectory) Error() string {
|
|||||||
return "Object exists on : " + e.Bucket + " as directory " + e.Object
|
return "Object exists on : " + e.Bucket + " as directory " + e.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//PrefixAccessDenied object access is denied.
|
||||||
|
type PrefixAccessDenied GenericError
|
||||||
|
|
||||||
|
func (e PrefixAccessDenied) Error() string {
|
||||||
|
return "Prefix access is denied: " + e.Bucket + "/" + e.Object
|
||||||
|
}
|
||||||
|
|
||||||
// BucketExists bucket exists.
|
// BucketExists bucket exists.
|
||||||
type BucketExists GenericError
|
type BucketExists GenericError
|
||||||
|
|
||||||
|
@ -115,6 +115,9 @@ func readDir(dirPath string) (entries []string, err error) {
|
|||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil, errFileNotFound
|
return nil, errFileNotFound
|
||||||
}
|
}
|
||||||
|
if os.IsPermission(err) {
|
||||||
|
return nil, errFileAccessDenied
|
||||||
|
}
|
||||||
|
|
||||||
// File path cannot be verified since one of the parents is a file.
|
// File path cannot be verified since one of the parents is a file.
|
||||||
if strings.Contains(err.Error(), "not a directory") {
|
if strings.Contains(err.Error(), "not a directory") {
|
||||||
|
@ -1792,9 +1792,9 @@ func ExecObjectLayerTest(t TestErrHandler, objTest objTestType) {
|
|||||||
defer removeRoots(append(fsDirs, fsDir))
|
defer removeRoots(append(fsDirs, fsDir))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecObjectLayerDiskNotFoundTest - executes object layer tests while deleting
|
// ExecObjectLayerDiskAlteredTest - executes object layer tests while altering
|
||||||
// disks in between tests. Creates XL ObjectLayer instance and runs test for XL layer.
|
// disks in between tests. Creates XL ObjectLayer instance and runs test for XL layer.
|
||||||
func ExecObjectLayerDiskNotFoundTest(t *testing.T, objTest objTestDiskNotFoundType) {
|
func ExecObjectLayerDiskAlteredTest(t *testing.T, objTest objTestDiskNotFoundType) {
|
||||||
objLayer, fsDirs, err := prepareXL()
|
objLayer, fsDirs, err := prepareXL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Initialization of object layer failed for XL setup: %s", err)
|
t.Fatalf("Initialization of object layer failed for XL setup: %s", err)
|
||||||
|
Loading…
Reference in New Issue
Block a user