[#604] Add MFADelete tests with reworked mfa.Storage implementation
All checks were successful
/ DCO (pull_request) Successful in 36s
/ Builds (pull_request) Successful in 1m38s
/ Vulncheck (pull_request) Successful in 1m37s
/ OCI image (pull_request) Successful in 2m16s
/ Lint (pull_request) Successful in 2m40s
/ Tests (pull_request) Successful in 1m26s
/ Vulncheck (push) Successful in 1m18s
/ Builds (push) Successful in 1m14s
/ OCI image (push) Successful in 2m0s
/ Lint (push) Successful in 2m14s
/ Tests (push) Successful in 1m35s
All checks were successful
/ DCO (pull_request) Successful in 36s
/ Builds (pull_request) Successful in 1m38s
/ Vulncheck (pull_request) Successful in 1m37s
/ OCI image (pull_request) Successful in 2m16s
/ Lint (pull_request) Successful in 2m40s
/ Tests (pull_request) Successful in 1m26s
/ Vulncheck (push) Successful in 1m18s
/ Builds (push) Successful in 1m14s
/ OCI image (push) Successful in 2m0s
/ Lint (push) Successful in 2m14s
/ Tests (push) Successful in 1m35s
Signed-off-by: Alex Vanin <a.vanin@yadro.com> Signed-off-by: Pavel Pogodaev <p.pogodaev@yadro.com>
This commit is contained in:
parent
0fc56cbfce
commit
7d6e20fdad
17 changed files with 440 additions and 233 deletions
|
@ -10,12 +10,15 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-mfa/mfa"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -106,6 +109,27 @@ func TestForceDeleteBucket(t *testing.T) {
|
|||
deleteBucketForce(t, hc, bktName, http.StatusNoContent, "true")
|
||||
}
|
||||
|
||||
func TestForceDeleteBucketWithMFADelete(t *testing.T) {
|
||||
hc := prepareHandlerContext(t)
|
||||
|
||||
bktName, objName, deviceName := "bucket-for-removal", "object-to-delete", "device"
|
||||
bktInfo := createTestBucket(hc, bktName)
|
||||
key := createMFADevice(hc, bktName, deviceName)
|
||||
putBucketVersioningMFADelete(hc, bktName, "Suspended", "Enabled", generateMFAHeader(key, deviceName))
|
||||
putObject(hc, bktName, objName)
|
||||
|
||||
nodeVersion, err := hc.tree.GetUnversioned(hc.context, bktInfo, objName)
|
||||
require.NoError(t, err)
|
||||
var addr oid.Address
|
||||
addr.SetContainer(bktInfo.CID)
|
||||
addr.SetObject(nodeVersion.OID)
|
||||
|
||||
hc.owner = bktInfo.Owner
|
||||
|
||||
// force delete bucket fails when MFA Delete enabled
|
||||
deleteBucketForce(t, hc, bktName, http.StatusConflict, "true")
|
||||
}
|
||||
|
||||
func TestDeleteMultipleObjectCheckUniqueness(t *testing.T) {
|
||||
hc := prepareHandlerContext(t)
|
||||
|
||||
|
@ -124,7 +148,7 @@ func TestDeleteObjectsError(t *testing.T) {
|
|||
|
||||
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||
bktInfo := createTestBucket(hc, bktName)
|
||||
putBucketVersioning(t, hc, bktName, true, "")
|
||||
putBucketVersioning(t, hc, bktName, "Enabled")
|
||||
|
||||
putObject(hc, bktName, objName)
|
||||
|
||||
|
@ -302,7 +326,7 @@ func TestDeleteMarkerSuspended(t *testing.T) {
|
|||
|
||||
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||
bktInfo, _ := createVersionedBucketAndObject(t, tc, bktName, objName)
|
||||
putBucketVersioning(t, tc, bktName, false, "")
|
||||
putBucketVersioning(t, tc, bktName, "Suspended")
|
||||
|
||||
t.Run("not create new delete marker if last version is delete marker", func(t *testing.T) {
|
||||
deleteMarkerVersion, isDeleteMarker := deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||
|
@ -350,7 +374,7 @@ func TestDeleteObjectCombined(t *testing.T) {
|
|||
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||
bktInfo, objInfo := createBucketAndObject(tc, bktName, objName)
|
||||
|
||||
putBucketVersioning(t, tc, bktName, true, "")
|
||||
putBucketVersioning(t, tc, bktName, "Enabled")
|
||||
|
||||
checkFound(t, tc, bktName, objName, emptyVersion)
|
||||
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||
|
@ -367,13 +391,13 @@ func TestDeleteObjectSuspended(t *testing.T) {
|
|||
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||
bktInfo, objInfo := createBucketAndObject(tc, bktName, objName)
|
||||
|
||||
putBucketVersioning(t, tc, bktName, true, "")
|
||||
putBucketVersioning(t, tc, bktName, "Enabled")
|
||||
|
||||
checkFound(t, tc, bktName, objName, emptyVersion)
|
||||
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||
|
||||
putBucketVersioning(t, tc, bktName, false, "")
|
||||
putBucketVersioning(t, tc, bktName, "Suspended")
|
||||
|
||||
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||
checkNotFound(t, tc, bktName, objName, objInfo.VersionID())
|
||||
|
@ -386,7 +410,7 @@ func TestDeleteMarkers(t *testing.T) {
|
|||
|
||||
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||
createTestBucket(tc, bktName)
|
||||
putBucketVersioning(t, tc, bktName, true, "")
|
||||
putBucketVersioning(t, tc, bktName, "Enabled")
|
||||
|
||||
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||
deleteObject(t, tc, bktName, objName, emptyVersion)
|
||||
|
@ -405,7 +429,7 @@ func TestGetHeadDeleteMarker(t *testing.T) {
|
|||
|
||||
bktName, objName := "bucket-for-removal", "object-to-delete"
|
||||
createTestBucket(hc, bktName)
|
||||
putBucketVersioning(t, hc, bktName, true, "")
|
||||
putBucketVersioning(t, hc, bktName, "Enabled")
|
||||
|
||||
putObject(hc, bktName, objName)
|
||||
|
||||
|
@ -473,13 +497,47 @@ func TestDeleteBucketByNotOwner(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDeleteObjectMFAEnabled(t *testing.T) {
|
||||
hc := prepareHandlerContext(t)
|
||||
|
||||
bktName := "bucket-name"
|
||||
deviceName := "mfa"
|
||||
serialNumber := "arn:aws:iam:::mfa/" + deviceName
|
||||
token := "123456"
|
||||
_ = createVersionedBucketMFAEnabled(hc, bktName, serialNumber+" "+token)
|
||||
objName := "object-name"
|
||||
deviceName := "device"
|
||||
|
||||
t.Run("versioned bucket", func(t *testing.T) {
|
||||
hc := prepareHandlerContext(t)
|
||||
bktInfo := createTestBucket(hc, bktName)
|
||||
key := createMFADevice(hc, bktName, deviceName)
|
||||
putBucketVersioningMFADelete(hc, bktName, "Enabled", "Enabled", generateMFAHeader(key, deviceName))
|
||||
objInfo := createTestObject(hc, bktInfo, objName, encryption.Params{})
|
||||
|
||||
ver := objInfo.VersionID()
|
||||
|
||||
// delete object without MFA with error
|
||||
deleteObjectErr(t, hc, bktName, objName, ver, apierr.GetAPIError(apierr.ErrMFAAuthNeeded))
|
||||
|
||||
// delete object with invalid MFA with error
|
||||
mfaHeader := generateMFAHeader(key, deviceName)
|
||||
deleteObjectWithMFAErr(hc, bktName, objName, ver, mfaHeader+"1", apierr.GetAPIError(apierr.ErrMFAAuthNeeded))
|
||||
|
||||
// delete object with MFA successfully
|
||||
deleteObjectWithMFA(hc, bktName, objName, ver, generateMFAHeader(key, deviceName))
|
||||
|
||||
// disable MFA and delete object successfully
|
||||
objInfo = createTestObject(hc, bktInfo, objName, encryption.Params{})
|
||||
|
||||
ver = objInfo.VersionID()
|
||||
|
||||
putBucketVersioningMFADelete(hc, bktName, "Enabled", "", generateMFAHeader(key, deviceName))
|
||||
deleteObjectWithMFA(hc, bktName, objName, ver, generateMFAHeader(key, deviceName))
|
||||
})
|
||||
|
||||
t.Run("versioned bucket without verId", func(t *testing.T) {
|
||||
hc := prepareHandlerContext(t)
|
||||
bktInfo := createTestBucket(hc, bktName)
|
||||
key := createMFADevice(hc, bktName, deviceName)
|
||||
putBucketVersioningMFADelete(hc, bktName, "Enabled", "Enabled", generateMFAHeader(key, deviceName))
|
||||
objInfo := createTestObject(hc, bktInfo, objName, encryption.Params{})
|
||||
|
||||
deleteObject(t, hc, bktName, objInfo.Name, "")
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemovalOnReplace(t *testing.T) {
|
||||
|
@ -547,34 +605,67 @@ func createVersionedBucketAndObject(_ *testing.T, tc *handlerContext, bktName, o
|
|||
|
||||
func createVersionedBucket(hc *handlerContext, bktName string) *data.BucketInfo {
|
||||
bktInfo := createTestBucket(hc, bktName)
|
||||
putBucketVersioning(hc.t, hc, bktName, true, "")
|
||||
putBucketVersioning(hc.t, hc, bktName, "Enabled")
|
||||
|
||||
return bktInfo
|
||||
}
|
||||
|
||||
func createVersionedBucketMFAEnabled(hc *handlerContext, bktName, mfa string) *data.BucketInfo {
|
||||
bktInfo := createTestBucket(hc, bktName)
|
||||
putBucketVersioning(hc.t, hc, bktName, true, mfa)
|
||||
func createMFADevice(hc *handlerContext, bktName, device string) *otp.Key {
|
||||
otpKey, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: bktName,
|
||||
AccountName: bktName,
|
||||
})
|
||||
require.NoError(hc.t, err)
|
||||
|
||||
return bktInfo
|
||||
err = hc.h.mfa.CreateMFADevice(hc.context, mfa.SecretDevice{
|
||||
Device: *mfa.NewDevice("", device, "/"),
|
||||
Key: otpKey,
|
||||
})
|
||||
require.NoError(hc.t, err)
|
||||
|
||||
return otpKey
|
||||
}
|
||||
|
||||
func putBucketVersioning(t *testing.T, tc *handlerContext, bktName string, enabled bool, mfa string) {
|
||||
cfg := &VersioningConfiguration{Status: "Suspended"}
|
||||
if enabled {
|
||||
func generateMFAHeader(key *otp.Key, device string) string {
|
||||
code, _ := totp.GenerateCode(key.Secret(), time.Now().UTC()) // error should never happen with otp.Key
|
||||
return "arn:aws:iam:::mfa/" + device + " " + code
|
||||
}
|
||||
|
||||
func putBucketVersioning(t *testing.T, hc *handlerContext, bktName string, status string) {
|
||||
w := putBucketVersioningBase(hc, bktName, status, "", "")
|
||||
assertStatus(t, w, http.StatusOK)
|
||||
}
|
||||
|
||||
func putBucketVersioningMFADelete(hc *handlerContext, bktName string, versioning string, mfa string, mfaHeader string) {
|
||||
w := putBucketVersioningBase(hc, bktName, versioning, mfa, mfaHeader)
|
||||
assertStatus(hc.t, w, http.StatusOK)
|
||||
}
|
||||
|
||||
func putBucketVersioningMFADeleteErr(hc *handlerContext, bktName string, versioning string, mfa string, mfaHeader string, err apierr.Error) {
|
||||
w := putBucketVersioningBase(hc, bktName, versioning, mfa, mfaHeader)
|
||||
assertS3Error(hc.t, w, err)
|
||||
}
|
||||
|
||||
func putBucketVersioningBase(tc *handlerContext, bktName string, versioning string, mfa string, mfaHeader string) *httptest.ResponseRecorder {
|
||||
cfg := &VersioningConfiguration{}
|
||||
switch versioning {
|
||||
case "Suspended":
|
||||
cfg.Status = "Suspended"
|
||||
case "Enabled":
|
||||
cfg.Status = "Enabled"
|
||||
}
|
||||
if len(mfa) > 0 {
|
||||
switch mfa {
|
||||
case "Enabled":
|
||||
cfg.MfaDelete = "Enabled"
|
||||
case "Disabled":
|
||||
cfg.MfaDelete = "Disabled"
|
||||
}
|
||||
w, r := prepareTestRequest(tc, bktName, "", cfg)
|
||||
|
||||
if len(mfa) > 0 {
|
||||
r.Header.Set(api.AmzMFA, mfa)
|
||||
if len(mfaHeader) > 0 {
|
||||
r.Header.Set(api.AmzMFA, mfaHeader)
|
||||
}
|
||||
|
||||
tc.Handler().PutBucketVersioningHandler(w, r)
|
||||
assertStatus(t, w, http.StatusOK)
|
||||
return w
|
||||
}
|
||||
|
||||
func getBucketVersioning(hc *handlerContext, bktName string) *VersioningConfiguration {
|
||||
|
@ -598,6 +689,37 @@ func deleteObject(t *testing.T, tc *handlerContext, bktName, objName, version st
|
|||
return w.Header().Get(api.AmzVersionID), w.Header().Get(api.AmzDeleteMarker) != ""
|
||||
}
|
||||
|
||||
func deleteObjectWithMFA(tc *handlerContext, bktName, objName, version, mfa string) (string, bool) {
|
||||
query := make(url.Values)
|
||||
query.Add(api.QueryVersionID, version)
|
||||
|
||||
w, r := prepareTestFullRequest(tc, bktName, objName, query, nil)
|
||||
r.Header.Set(api.AmzMFA, mfa)
|
||||
tc.Handler().DeleteObjectHandler(w, r)
|
||||
assertStatus(tc.t, w, http.StatusNoContent)
|
||||
|
||||
return w.Header().Get(api.AmzVersionID), w.Header().Get(api.AmzDeleteMarker) != ""
|
||||
}
|
||||
|
||||
func deleteObjectErr(t *testing.T, tc *handlerContext, bktName, objName, version string, err apierr.Error) {
|
||||
query := make(url.Values)
|
||||
query.Add(api.QueryVersionID, version)
|
||||
|
||||
w, r := prepareTestFullRequest(tc, bktName, objName, query, nil)
|
||||
tc.Handler().DeleteObjectHandler(w, r)
|
||||
assertS3Error(t, w, err)
|
||||
}
|
||||
|
||||
func deleteObjectWithMFAErr(tc *handlerContext, bktName, objName, version, mfa string, err apierr.Error) {
|
||||
query := make(url.Values)
|
||||
query.Add(api.QueryVersionID, version)
|
||||
|
||||
w, r := prepareTestFullRequest(tc, bktName, objName, query, nil)
|
||||
r.Header.Set(api.AmzMFA, mfa)
|
||||
tc.Handler().DeleteObjectHandler(w, r)
|
||||
assertS3Error(tc.t, w, err)
|
||||
}
|
||||
|
||||
func deleteObjects(t *testing.T, tc *handlerContext, bktName string, objVersions [][2]string) *DeleteObjectsResponse {
|
||||
w := deleteObjectsBase(tc, bktName, objVersions)
|
||||
|
||||
|
@ -696,6 +818,6 @@ func createSuspendedBucket(t *testing.T, tc *handlerContext, bktName string) *da
|
|||
createTestBucket(tc, bktName)
|
||||
bktInfo, err := tc.Layer().GetBucketInfo(tc.Context(), bktName)
|
||||
require.NoError(t, err)
|
||||
putBucketVersioning(t, tc, bktName, false, "")
|
||||
putBucketVersioning(t, tc, bktName, "Suspended")
|
||||
return bktInfo
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue