mirror of https://github.com/minio/minio.git
Enable replication of SSE-C objects (#19107)
If site replication enabled across sites, replicate the SSE-C objects as well. These objects could be read from target sites using the same client encryption keys. Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
This commit is contained in:
parent
d87f91720b
commit
468a9fae83
6
Makefile
6
Makefile
|
@ -107,6 +107,12 @@ test-site-replication-oidc: install-race ## verify automatic site replication
|
||||||
test-site-replication-minio: install-race ## verify automatic site replication
|
test-site-replication-minio: install-race ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
|
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
|
||||||
|
@echo "Running tests for automatic site replication of SSE-C objects"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication.sh)
|
||||||
|
@echo "Running tests for automatic site replication of SSE-C objects with SSE-KMS enabled for bucket"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-sse-kms-object-replication.sh)
|
||||||
|
@echo "Running tests for automatic site replication of SSE-C objects with compression enabled for site"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication-with-compression.sh)
|
||||||
|
|
||||||
verify: ## verify minio various setups
|
verify: ## verify minio various setups
|
||||||
@echo "Verifying build with race"
|
@echo "Verifying build with race"
|
||||||
|
|
|
@ -1218,11 +1218,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
reader io.Reader
|
reader io.Reader
|
||||||
keyID string
|
keyID string
|
||||||
|
|
|
@ -51,6 +51,8 @@ import (
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/tinylib/msgp/msgp"
|
"github.com/tinylib/msgp/msgp"
|
||||||
"github.com/zeebo/xxh3"
|
"github.com/zeebo/xxh3"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -76,11 +78,6 @@ const (
|
||||||
ReplicationWorkerMultiplier = 1.5
|
ReplicationWorkerMultiplier = 1.5
|
||||||
)
|
)
|
||||||
|
|
||||||
func isReplicationEnabled(ctx context.Context, bucketName string) bool {
|
|
||||||
rc, _ := getReplicationConfig(ctx, bucketName)
|
|
||||||
return rc != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// gets replication config associated to a given bucket name.
|
// gets replication config associated to a given bucket name.
|
||||||
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
||||||
rCfg, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucketName)
|
rCfg, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucketName)
|
||||||
|
@ -764,13 +761,20 @@ func (m caseInsensitiveMap) Lookup(key string) (string, bool) {
|
||||||
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts minio.PutObjectOptions, err error) {
|
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts minio.PutObjectOptions, err error) {
|
||||||
meta := make(map[string]string)
|
meta := make(map[string]string)
|
||||||
for k, v := range objInfo.UserDefined {
|
for k, v := range objInfo.UserDefined {
|
||||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
// In case of SSE-C objects copy the allowed internal headers as well
|
||||||
continue
|
if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
||||||
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isStandardHeader(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if isStandardHeader(k) {
|
if slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
||||||
continue
|
meta[validSSEReplicationHeaders[k]] = v
|
||||||
|
} else {
|
||||||
|
meta[k] = v
|
||||||
}
|
}
|
||||||
meta[k] = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) {
|
if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) {
|
||||||
|
@ -1166,9 +1170,10 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
||||||
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||||
|
|
||||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
||||||
VersionID: ri.VersionID,
|
VersionID: ri.VersionID,
|
||||||
Versioned: versioned,
|
Versioned: versioned,
|
||||||
VersionSuspended: versionSuspended,
|
VersionSuspended: versionSuspended,
|
||||||
|
ReplicationRequest: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||||
|
@ -1322,11 +1327,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||||
versioned := globalBucketVersioningSys.PrefixEnabled(bucket, object)
|
versioned := globalBucketVersioningSys.PrefixEnabled(bucket, object)
|
||||||
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||||
|
|
||||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{},
|
||||||
VersionID: ri.VersionID,
|
ObjectOptions{
|
||||||
Versioned: versioned,
|
VersionID: ri.VersionID,
|
||||||
VersionSuspended: versionSuspended,
|
Versioned: versioned,
|
||||||
})
|
VersionSuspended: versionSuspended,
|
||||||
|
ReplicationRequest: true,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||||
objInfo := ri.ToObjectInfo()
|
objInfo := ri.ToObjectInfo()
|
||||||
|
@ -1344,6 +1351,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||||
defer gr.Close()
|
defer gr.Close()
|
||||||
|
|
||||||
objInfo := gr.ObjInfo
|
objInfo := gr.ObjInfo
|
||||||
|
|
||||||
// make sure we have the latest metadata for metrics calculation
|
// make sure we have the latest metadata for metrics calculation
|
||||||
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
||||||
|
|
||||||
|
@ -1367,6 +1375,11 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the encrypted size for SSE-C objects
|
||||||
|
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
||||||
|
size = objInfo.Size
|
||||||
|
}
|
||||||
|
|
||||||
if tgt.Bucket == "" {
|
if tgt.Bucket == "" {
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate object %s(%s) to %s, target bucket is missing", objInfo.Name, objInfo.VersionID, tgt.EndpointURL()))
|
logger.LogIf(ctx, fmt.Errorf("unable to replicate object %s(%s) to %s, target bucket is missing", objInfo.Name, objInfo.VersionID, tgt.EndpointURL()))
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
|
@ -1599,21 +1612,35 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
||||||
pInfo minio.ObjectPart
|
pInfo minio.ObjectPart
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var objectSize int64
|
||||||
for _, partInfo := range objInfo.Parts {
|
for _, partInfo := range objInfo.Parts {
|
||||||
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
|
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
||||||
|
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.Size), partInfo.Size, "", "", partInfo.ActualSize)
|
||||||
|
} else {
|
||||||
|
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cHeader := http.Header{}
|
||||||
|
cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true")
|
||||||
popts := minio.PutObjectPartOptions{
|
popts := minio.PutObjectPartOptions{
|
||||||
SSE: opts.ServerSideEncryption,
|
SSE: opts.ServerSideEncryption,
|
||||||
|
CustomHeader: cHeader,
|
||||||
}
|
}
|
||||||
|
|
||||||
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.ActualSize, popts)
|
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
||||||
|
objectSize += partInfo.Size
|
||||||
|
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.Size, popts)
|
||||||
|
} else {
|
||||||
|
objectSize += partInfo.ActualSize
|
||||||
|
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.ActualSize, popts)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if pInfo.Size != partInfo.ActualSize {
|
if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) && pInfo.Size != partInfo.ActualSize {
|
||||||
return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.ActualSize)
|
return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.ActualSize)
|
||||||
}
|
}
|
||||||
uploadedParts = append(uploadedParts, minio.CompletePart{
|
uploadedParts = append(uploadedParts, minio.CompletePart{
|
||||||
|
@ -1624,6 +1651,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
||||||
cctx, ccancel := context.WithTimeout(ctx, 10*time.Minute)
|
cctx, ccancel := context.WithTimeout(ctx, 10*time.Minute)
|
||||||
defer ccancel()
|
defer ccancel()
|
||||||
_, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{
|
_, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{
|
||||||
|
UserMetadata: map[string]string{validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"]},
|
||||||
Internal: minio.AdvancedPutOptions{
|
Internal: minio.AdvancedPutOptions{
|
||||||
SourceMTime: objInfo.ModTime,
|
SourceMTime: objInfo.ModTime,
|
||||||
// always set this to distinguish between `mc mirror` replication and serverside
|
// always set this to distinguish between `mc mirror` replication and serverside
|
||||||
|
|
|
@ -1255,7 +1255,11 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the consolidated actual size.
|
// Save the consolidated actual size.
|
||||||
fi.Metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(objectActualSize, 10)
|
if opts.ReplicationRequest {
|
||||||
|
fi.Metadata[ReservedMetadataPrefix+"actual-size"] = opts.UserDefined["X-Minio-Internal-Actual-Object-Size"]
|
||||||
|
} else {
|
||||||
|
fi.Metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(objectActualSize, 10)
|
||||||
|
}
|
||||||
|
|
||||||
if opts.DataMovement {
|
if opts.DataMovement {
|
||||||
fi.SetDataMov()
|
fi.SetDataMov()
|
||||||
|
|
|
@ -245,6 +245,11 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
|
||||||
}, toObjectErr(errMethodNotAllowed, bucket, object)
|
}, toObjectErr(errMethodNotAllowed, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set NoDecryption for SSE-C objects and if replication request
|
||||||
|
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) && opts.ReplicationRequest {
|
||||||
|
opts.NoDecryption = true
|
||||||
|
}
|
||||||
|
|
||||||
if objInfo.IsRemote() {
|
if objInfo.IsRemote() {
|
||||||
gr, err := getTransitionedObjectReader(ctx, bucket, object, rs, h, objInfo, opts)
|
gr, err := getTransitionedObjectReader(ctx, bucket, object, rs, h, objInfo, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -33,6 +33,8 @@ import (
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/grid"
|
"github.com/minio/minio/internal/grid"
|
||||||
xnet "github.com/minio/pkg/v2/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/amztime"
|
"github.com/minio/minio/internal/amztime"
|
||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
|
@ -73,6 +75,9 @@ const (
|
||||||
// and must not set by clients
|
// and must not set by clients
|
||||||
func containsReservedMetadata(header http.Header) bool {
|
func containsReservedMetadata(header http.Header) bool {
|
||||||
for key := range header {
|
for key := range header {
|
||||||
|
if slices.Contains(maps.Keys(validSSEReplicationHeaders), key) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if stringsHasPrefixFold(key, ReservedMetadataPrefix) {
|
if stringsHasPrefixFold(key, ReservedMetadataPrefix) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,15 +108,15 @@ var containsReservedMetadataTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{crypto.MetaIV: []string{"iv"}},
|
header: http.Header{crypto.MetaIV: []string{"iv"}},
|
||||||
shouldFail: true,
|
shouldFail: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{crypto.MetaAlgorithm: []string{crypto.InsecureSealAlgorithm}},
|
header: http.Header{crypto.MetaAlgorithm: []string{crypto.InsecureSealAlgorithm}},
|
||||||
shouldFail: true,
|
shouldFail: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{crypto.MetaSealedKeySSEC: []string{"mac"}},
|
header: http.Header{crypto.MetaSealedKeySSEC: []string{"mac"}},
|
||||||
shouldFail: true,
|
shouldFail: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{ReservedMetadataPrefix + "Key": []string{"value"}},
|
header: http.Header{ReservedMetadataPrefix + "Key": []string{"value"}},
|
||||||
|
|
|
@ -33,6 +33,8 @@ import (
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/mcontext"
|
"github.com/minio/minio/internal/mcontext"
|
||||||
xnet "github.com/minio/pkg/v2/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -82,6 +84,31 @@ var supportedHeaders = []string{
|
||||||
xhttp.AmzObjectTagging,
|
xhttp.AmzObjectTagging,
|
||||||
"expires",
|
"expires",
|
||||||
xhttp.AmzBucketReplicationStatus,
|
xhttp.AmzBucketReplicationStatus,
|
||||||
|
"X-Minio-Replication-Server-Side-Encryption-Sealed-Key",
|
||||||
|
"X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm",
|
||||||
|
"X-Minio-Replication-Server-Side-Encryption-Iv",
|
||||||
|
"X-Minio-Replication-Encrypted-Multipart",
|
||||||
|
"X-Minio-Replication-Actual-Object-Size",
|
||||||
|
// Add more supported headers here.
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapping of internal headers to allowed replication headers
|
||||||
|
var validSSEReplicationHeaders = map[string]string{
|
||||||
|
"X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "X-Minio-Replication-Server-Side-Encryption-Sealed-Key",
|
||||||
|
"X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm",
|
||||||
|
"X-Minio-Internal-Server-Side-Encryption-Iv": "X-Minio-Replication-Server-Side-Encryption-Iv",
|
||||||
|
"X-Minio-Internal-Encrypted-Multipart": "X-Minio-Replication-Encrypted-Multipart",
|
||||||
|
"X-Minio-Internal-Actual-Object-Size": "X-Minio-Replication-Actual-Object-Size",
|
||||||
|
// Add more supported headers here.
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapping of replication headers to internal headers
|
||||||
|
var replicationToInternalHeaders = map[string]string{
|
||||||
|
"X-Minio-Replication-Server-Side-Encryption-Sealed-Key": "X-Minio-Internal-Server-Side-Encryption-Sealed-Key",
|
||||||
|
"X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm": "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm",
|
||||||
|
"X-Minio-Replication-Server-Side-Encryption-Iv": "X-Minio-Internal-Server-Side-Encryption-Iv",
|
||||||
|
"X-Minio-Replication-Encrypted-Multipart": "X-Minio-Internal-Encrypted-Multipart",
|
||||||
|
"X-Minio-Replication-Actual-Object-Size": "X-Minio-Internal-Actual-Object-Size",
|
||||||
// Add more supported headers here.
|
// Add more supported headers here.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +205,11 @@ func extractMetadataFromMime(ctx context.Context, v textproto.MIMEHeader, m map[
|
||||||
for _, supportedHeader := range supportedHeaders {
|
for _, supportedHeader := range supportedHeaders {
|
||||||
value, ok := nv[http.CanonicalHeaderKey(supportedHeader)]
|
value, ok := nv[http.CanonicalHeaderKey(supportedHeader)]
|
||||||
if ok {
|
if ok {
|
||||||
m[supportedHeader] = strings.Join(value, ",")
|
if slices.Contains(maps.Keys(replicationToInternalHeaders), supportedHeader) {
|
||||||
|
m[replicationToInternalHeaders[supportedHeader]] = strings.Join(value, ",")
|
||||||
|
} else {
|
||||||
|
m[supportedHeader] = strings.Join(value, ",")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -480,7 +480,7 @@ func completeMultipartOpts(ctx context.Context, r *http.Request, bucket, object
|
||||||
}
|
}
|
||||||
opts.MTime = mtime
|
opts.MTime = mtime
|
||||||
opts.UserDefined = make(map[string]string)
|
opts.UserDefined = make(map[string]string)
|
||||||
|
opts.UserDefined[ReservedMetadataPrefix+"Actual-Object-Size"] = r.Header.Get(xhttp.MinIOReplicationActualObjectSize)
|
||||||
// Transfer SSEC key in opts.EncryptFn
|
// Transfer SSEC key in opts.EncryptFn
|
||||||
if crypto.SSEC.IsRequested(r.Header) {
|
if crypto.SSEC.IsRequested(r.Header) {
|
||||||
key, err := ParseSSECustomerRequest(r)
|
key, err := ParseSSECustomerRequest(r)
|
||||||
|
@ -491,5 +491,8 @@ func completeMultipartOpts(ctx context.Context, r *http.Request, bucket, object
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, ok := r.Header[xhttp.MinIOSourceReplicationRequest]; ok {
|
||||||
|
opts.ReplicationRequest = true
|
||||||
|
}
|
||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2234,8 +2234,8 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
if crypto.SSEC.IsRequested(r.Header) && isCompressible(r.Header, object) {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, crypto.ErrIncompatibleEncryptionWithCompression), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2649,10 +2649,6 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
||||||
return errInvalidEncryptionParameters
|
return errInvalidEncryptionParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
|
||||||
return errInvalidEncryptionParametersSSEC
|
|
||||||
}
|
|
||||||
|
|
||||||
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -116,14 +116,29 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
if crypto.SSEC.IsRequested(r.Header) && isCompressible(r.Header, object) {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, crypto.ErrIncompatibleEncryptionWithCompression), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil {
|
_, sourceReplReq := r.Header[xhttp.MinIOSourceReplicationRequest]
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
ssecRepHeaders := []string{
|
||||||
return
|
"X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm",
|
||||||
|
"X-Minio-Replication-Server-Side-Encryption-Sealed-Key",
|
||||||
|
"X-Minio-Replication-Server-Side-Encryption-Iv",
|
||||||
|
}
|
||||||
|
ssecRep := false
|
||||||
|
for _, header := range ssecRepHeaders {
|
||||||
|
if val := r.Header.Get(header); val != "" {
|
||||||
|
ssecRep = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !(ssecRep && sourceReplReq) {
|
||||||
|
if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Set this for multipart only operations, we need to differentiate during
|
// Set this for multipart only operations, we need to differentiate during
|
||||||
// decryption if the file was actually multipart or not.
|
// decryption if the file was actually multipart or not.
|
||||||
|
@ -757,9 +772,10 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||||
pReader := NewPutObjReader(hashReader)
|
pReader := NewPutObjReader(hashReader)
|
||||||
|
|
||||||
_, isEncrypted := crypto.IsEncrypted(mi.UserDefined)
|
_, isEncrypted := crypto.IsEncrypted(mi.UserDefined)
|
||||||
|
_, replicationStatus := mi.UserDefined[xhttp.AmzBucketReplicationStatus]
|
||||||
var objectEncryptionKey crypto.ObjectKey
|
var objectEncryptionKey crypto.ObjectKey
|
||||||
if isEncrypted {
|
if isEncrypted {
|
||||||
if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(mi.UserDefined) {
|
if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(mi.UserDefined) && !replicationStatus {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrSSEMultipartEncrypted), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrSSEMultipartEncrypted), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -779,52 +795,55 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculating object encryption key
|
_, sourceReplReq := r.Header[xhttp.MinIOSourceReplicationRequest]
|
||||||
key, err = decryptObjectMeta(key, bucket, object, mi.UserDefined)
|
if !(sourceReplReq && crypto.SSEC.IsEncrypted(mi.UserDefined)) {
|
||||||
if err != nil {
|
// Calculating object encryption key
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
key, err = decryptObjectMeta(key, bucket, object, mi.UserDefined)
|
||||||
return
|
if err != nil {
|
||||||
}
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
copy(objectEncryptionKey[:], key)
|
return
|
||||||
|
}
|
||||||
|
copy(objectEncryptionKey[:], key)
|
||||||
|
|
||||||
partEncryptionKey := objectEncryptionKey.DerivePartKey(uint32(partID))
|
partEncryptionKey := objectEncryptionKey.DerivePartKey(uint32(partID))
|
||||||
in := io.Reader(hashReader)
|
in := io.Reader(hashReader)
|
||||||
if size > encryptBufferThreshold {
|
if size > encryptBufferThreshold {
|
||||||
// The encryption reads in blocks of 64KB.
|
// The encryption reads in blocks of 64KB.
|
||||||
// We add a buffer on bigger files to reduce the number of syscalls upstream.
|
// We add a buffer on bigger files to reduce the number of syscalls upstream.
|
||||||
in = bufio.NewReaderSize(hashReader, encryptBufferSize)
|
in = bufio.NewReaderSize(hashReader, encryptBufferSize)
|
||||||
}
|
}
|
||||||
reader, err = sio.EncryptReader(in, sio.Config{Key: partEncryptionKey[:], CipherSuites: fips.DARECiphers()})
|
reader, err = sio.EncryptReader(in, sio.Config{Key: partEncryptionKey[:], CipherSuites: fips.DARECiphers()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wantSize := int64(-1)
|
wantSize := int64(-1)
|
||||||
if size >= 0 {
|
if size >= 0 {
|
||||||
info := ObjectInfo{Size: size}
|
info := ObjectInfo{Size: size}
|
||||||
wantSize = info.EncryptedSize()
|
wantSize = info.EncryptedSize()
|
||||||
}
|
}
|
||||||
// do not try to verify encrypted content
|
// do not try to verify encrypted content
|
||||||
hashReader, err = hash.NewReader(ctx, etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
hashReader, err = hash.NewReader(ctx, etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := hashReader.AddChecksum(r, true); err != nil {
|
if err := hashReader.AddChecksum(r, true); err != nil {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if idxCb != nil {
|
if idxCb != nil {
|
||||||
idxCb = compressionIndexEncrypter(objectEncryptionKey, idxCb)
|
idxCb = compressionIndexEncrypter(objectEncryptionKey, idxCb)
|
||||||
|
}
|
||||||
|
opts.EncryptFn = metadataEncrypter(objectEncryptionKey)
|
||||||
}
|
}
|
||||||
opts.EncryptFn = metadataEncrypter(objectEncryptionKey)
|
|
||||||
}
|
}
|
||||||
opts.IndexCB = idxCb
|
opts.IndexCB = idxCb
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,10 @@ trap 'catch $LINENO' ERR
|
||||||
catch() {
|
catch() {
|
||||||
if [ $# -ne 0 ]; then
|
if [ $# -ne 0 ]; then
|
||||||
echo "error on line $1"
|
echo "error on line $1"
|
||||||
echo "$site server logs ========="
|
echo "server logs ========="
|
||||||
cat "/tmp/${site}_1.log"
|
cat "/tmp/sitea_1.log"
|
||||||
echo "==========================="
|
echo "==========================="
|
||||||
cat "/tmp/${site}_2.log"
|
cat "/tmp/sitea_2.log"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Cleaning up instances of MinIO"
|
echo "Cleaning up instances of MinIO"
|
||||||
|
@ -42,32 +42,34 @@ if [ ! -f ./mc ]; then
|
||||||
chmod +x mc
|
chmod +x mc
|
||||||
fi
|
fi
|
||||||
|
|
||||||
minio server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
minio server --address ":9001" "https://localhost:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||||
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
|
"https://localhost:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
|
||||||
minio server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
minio server --address ":9002" "https://localhost:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||||
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
|
"https://localhost:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
|
||||||
|
|
||||||
export MC_HOST_sitea=http://minio:minio123@127.0.0.1:9001
|
sleep 60
|
||||||
|
|
||||||
./mc mb sitea/delissue
|
export MC_HOST_sitea=https://minio:minio123@localhost:9001
|
||||||
|
|
||||||
./mc version enable sitea/delissue
|
./mc mb sitea/delissue --insecure
|
||||||
|
|
||||||
echo hello | ./mc pipe sitea/delissue/hello
|
./mc version enable sitea/delissue --insecure
|
||||||
|
|
||||||
./mc version suspend sitea/delissue
|
echo hello | ./mc pipe sitea/delissue/hello --insecure
|
||||||
|
|
||||||
./mc rm sitea/delissue/hello
|
./mc version suspend sitea/delissue --insecure
|
||||||
|
|
||||||
./mc version enable sitea/delissue
|
./mc rm sitea/delissue/hello --insecure
|
||||||
|
|
||||||
echo hello | ./mc pipe sitea/delissue/hello
|
./mc version enable sitea/delissue --insecure
|
||||||
|
|
||||||
./mc version suspend sitea/delissue
|
echo hello | ./mc pipe sitea/delissue/hello --insecure
|
||||||
|
|
||||||
./mc rm sitea/delissue/hello
|
./mc version suspend sitea/delissue --insecure
|
||||||
|
|
||||||
count=$(./mc ls --versions sitea/delissue | wc -l)
|
./mc rm sitea/delissue/hello --insecure
|
||||||
|
|
||||||
|
count=$(./mc ls --versions sitea/delissue --insecure | wc -l)
|
||||||
|
|
||||||
if [ ${count} -ne 3 ]; then
|
if [ ${count} -ne 3 ]; then
|
||||||
echo "BUG: expected number of versions to be '3' found ${count}"
|
echo "BUG: expected number of versions to be '3' found ${count}"
|
||||||
|
@ -76,6 +78,6 @@ if [ ${count} -ne 3 ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "SUCCESS:"
|
echo "SUCCESS:"
|
||||||
./mc ls --versions sitea/delissue
|
./mc ls --versions sitea/delissue --insecure
|
||||||
|
|
||||||
catch
|
catch
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# shellcheck disable=SC2120
|
||||||
|
exit_1() {
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
echo "minio1 ============"
|
||||||
|
cat /tmp/minio1_1.log
|
||||||
|
echo "minio2 ============"
|
||||||
|
cat /tmp/minio2_1.log
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo -n "Cleaning up instances of MinIO ..."
|
||||||
|
pkill -9 minio || sudo pkill -9 minio
|
||||||
|
pkill -9 kes || sudo pkill -9 kes
|
||||||
|
rm -rf ${PWD}/keys
|
||||||
|
rm -rf /tmp/minio{1,2}
|
||||||
|
echo "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
export MINIO_BROWSER=off
|
||||||
|
export MINIO_ROOT_USER="minio"
|
||||||
|
export MINIO_ROOT_PASSWORD="minio123"
|
||||||
|
|
||||||
|
# Create certificates for TLS enabled MinIO
|
||||||
|
echo -n "Setup certs for MinIO instances ..."
|
||||||
|
wget -O certgen https://github.com/minio/certgen/releases/latest/download/certgen-linux-amd64 && chmod +x certgen
|
||||||
|
./certgen --host localhost
|
||||||
|
mkdir -p ~/.minio/certs
|
||||||
|
mv public.crt ~/.minio/certs || sudo mv public.crt ~/.minio/certs
|
||||||
|
mv private.key ~/.minio/certs || sudo mv private.key ~/.minio/certs
|
||||||
|
echo "done"
|
||||||
|
|
||||||
|
# Start MinIO instances
|
||||||
|
echo -n "Starting MinIO instances ..."
|
||||||
|
CI=on MINIO_KMS_SECRET_KEY=minio-default-key:IyqsU3kMFloCNup4BsZtf/rmfHVcTgznO2F25CkEH1g= MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 minio server --address ":9001" --console-address ":10000" /tmp/minio1/{1...4}/disk{1...4} /tmp/minio1/{5...8}/disk{1...4} >/tmp/minio1_1.log 2>&1 &
|
||||||
|
CI=on MINIO_KMS_SECRET_KEY=minio-default-key:IyqsU3kMFloCNup4BsZtf/rmfHVcTgznO2F25CkEH1g= MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 minio server --address ":9002" --console-address ":11000" /tmp/minio2/{1...4}/disk{1...4} /tmp/minio2/{5...8}/disk{1...4} >/tmp/minio2_1.log 2>&1 &
|
||||||
|
echo "done"
|
||||||
|
|
||||||
|
if [ ! -f ./mc ]; then
|
||||||
|
echo -n "Downloading MinIO client ..."
|
||||||
|
wget -O mc https://dl.min.io/client/mc/release/linux-amd64/mc &&
|
||||||
|
chmod +x mc
|
||||||
|
echo "done"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
export MC_HOST_minio1=https://minio:minio123@localhost:9001
|
||||||
|
export MC_HOST_minio2=https://minio:minio123@localhost:9002
|
||||||
|
|
||||||
|
# Prepare data for tests
|
||||||
|
echo -n "Preparing test data ..."
|
||||||
|
mkdir -p /tmp/data
|
||||||
|
echo "Hello from encrypted world" >/tmp/data/encrypted
|
||||||
|
touch /tmp/data/mpartobj
|
||||||
|
shred -s 500M /tmp/data/mpartobj
|
||||||
|
touch /tmp/data/defpartsize
|
||||||
|
shred -s 500M /tmp/data/defpartsize
|
||||||
|
touch /tmp/data/custpartsize
|
||||||
|
shred -s 500M /tmp/data/custpartsize
|
||||||
|
echo "done"
|
||||||
|
|
||||||
|
# Add replication site
|
||||||
|
./mc admin replicate add minio1 minio2 --insecure
|
||||||
|
# sleep for replication to complete
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
# Create bucket in source cluster
|
||||||
|
echo "Create bucket in source MinIO instance"
|
||||||
|
./mc mb minio1/test-bucket --insecure
|
||||||
|
|
||||||
|
# Enable SSE KMS for the bucket
|
||||||
|
./mc encrypt set sse-kms minio-default-key minio1/test-bucket --insecure
|
||||||
|
|
||||||
|
# Load objects to source site
|
||||||
|
echo "Loading objects to source MinIO instance"
|
||||||
|
./mc cp /tmp/data/encrypted minio1/test-bucket --insecure
|
||||||
|
./mc cp /tmp/data/mpartobj minio1/test-bucket --encrypt-key "minio1/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure
|
||||||
|
./mc cp /tmp/data/defpartsize minio1/test-bucket --insecure
|
||||||
|
./mc put /tmp/data/custpartsize minio1/test-bucket --insecure --part-size 50MiB
|
||||||
|
sleep 120
|
||||||
|
|
||||||
|
# List the objects from source site
|
||||||
|
echo "Objects from source instance"
|
||||||
|
./mc ls minio1/test-bucket --insecure
|
||||||
|
count1=$(./mc ls minio1/test-bucket/encrypted --insecure | wc -l)
|
||||||
|
if [ "${count1}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/encrypted not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
count2=$(./mc ls minio1/test-bucket/mpartobj --insecure | wc -l)
|
||||||
|
if [ "${count2}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/mpartobj not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
count3=$(./mc ls minio1/test-bucket/defpartsize --insecure | wc -l)
|
||||||
|
if [ "${count3}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/defpartsize not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
count4=$(./mc ls minio1/test-bucket/custpartsize --insecure | wc -l)
|
||||||
|
if [ "${count4}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/custpartsize not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# List the objects from replicated site
|
||||||
|
echo "Objects from replicated instance"
|
||||||
|
./mc ls minio2/test-bucket --insecure
|
||||||
|
repcount1=$(./mc ls minio2/test-bucket/encrypted --insecure | wc -l)
|
||||||
|
if [ "${repcount1}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/encrypted not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
repcount2=$(./mc ls minio2/test-bucket/mpartobj --insecure | wc -l)
|
||||||
|
if [ "${repcount2}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/mpartobj not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
repcount3=$(./mc ls minio2/test-bucket/defpartsize --insecure | wc -l)
|
||||||
|
if [ "${repcount3}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/defpartsize not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
repcount4=$(./mc ls minio2/test-bucket/custpartsize --insecure | wc -l)
|
||||||
|
if [ "${repcount4}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/custpartsize not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stat the objects from source site
|
||||||
|
echo "Stat minio1/test-bucket/encrypted"
|
||||||
|
./mc stat minio1/test-bucket/encrypted --insecure --json
|
||||||
|
stat_out1=$(./mc stat minio1/test-bucket/encrypted --insecure --json)
|
||||||
|
src_obj1_algo=$(echo "${stat_out1}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||||
|
src_obj1_keyid=$(echo "${stat_out1}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||||
|
echo "Stat minio1/test-bucket/defpartsize"
|
||||||
|
./mc stat minio1/test-bucket/defpartsize --insecure --json
|
||||||
|
stat_out2=$(./mc stat minio1/test-bucket/defpartsize --insecure --json)
|
||||||
|
src_obj2_algo=$(echo "${stat_out2}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||||
|
src_obj2_keyid=$(echo "${stat_out2}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||||
|
echo "Stat minio1/test-bucket/custpartsize"
|
||||||
|
./mc stat minio1/test-bucket/custpartsize --insecure --json
|
||||||
|
stat_out3=$(./mc stat minio1/test-bucket/custpartsize --insecure --json)
|
||||||
|
src_obj3_algo=$(echo "${stat_out3}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||||
|
src_obj3_keyid=$(echo "${stat_out3}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||||
|
echo "Stat minio1/test-bucket/mpartobj"
|
||||||
|
./mc stat minio1/test-bucket/mpartobj --encrypt-key "minio1/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out4=$(./mc stat minio1/test-bucket/mpartobj --encrypt-key "minio1/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
src_obj4_etag=$(echo "${stat_out4}" | jq '.etag')
|
||||||
|
src_obj4_size=$(echo "${stat_out4}" | jq '.size')
|
||||||
|
src_obj4_md5=$(echo "${stat_out4}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
|
||||||
|
# Stat the objects from replicated site
|
||||||
|
echo "Stat minio2/test-bucket/encrypted"
|
||||||
|
./mc stat minio2/test-bucket/encrypted --insecure --json
|
||||||
|
stat_out1_rep=$(./mc stat minio2/test-bucket/encrypted --insecure --json)
|
||||||
|
rep_obj1_algo=$(echo "${stat_out1_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||||
|
rep_obj1_keyid=$(echo "${stat_out1_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||||
|
echo "Stat minio2/test-bucket/defpartsize"
|
||||||
|
./mc stat minio2/test-bucket/defpartsize --insecure --json
|
||||||
|
stat_out2_rep=$(./mc stat minio2/test-bucket/defpartsize --insecure --json)
|
||||||
|
rep_obj2_algo=$(echo "${stat_out2_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||||
|
rep_obj2_keyid=$(echo "${stat_out2_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||||
|
echo "Stat minio2/test-bucket/custpartsize"
|
||||||
|
./mc stat minio2/test-bucket/custpartsize --insecure --json
|
||||||
|
stat_out3_rep=$(./mc stat minio2/test-bucket/custpartsize --insecure --json)
|
||||||
|
rep_obj3_algo=$(echo "${stat_out3_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||||
|
rep_obj3_keyid=$(echo "${stat_out3_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||||
|
echo "Stat minio2/test-bucket/mpartobj"
|
||||||
|
./mc stat minio2/test-bucket/mpartobj --encrypt-key "minio2/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out4_rep=$(./mc stat minio2/test-bucket/mpartobj --encrypt-key "minio2/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
rep_obj4_etag=$(echo "${stat_out4}" | jq '.etag')
|
||||||
|
rep_obj4_size=$(echo "${stat_out4}" | jq '.size')
|
||||||
|
rep_obj4_md5=$(echo "${stat_out4}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
|
||||||
|
# Check the algo and keyId of replicated objects
|
||||||
|
if [ "${rep_obj1_algo}" != "${src_obj1_algo}" ]; then
|
||||||
|
echo "BUG: Algorithm: '${rep_obj1_algo}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_algo}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj1_keyid}" != "${src_obj1_keyid}" ]; then
|
||||||
|
echo "BUG: KeyId: '${rep_obj1_keyid}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_keyid}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj2_algo}" != "${src_obj2_algo}" ]; then
|
||||||
|
echo "BUG: Algorithm: '${rep_obj2_algo}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_algo}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj2_keyid}" != "${src_obj2_keyid}" ]; then
|
||||||
|
echo "BUG: KeyId: '${rep_obj2_keyid}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_keyid}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj3_algo}" != "${src_obj3_algo}" ]; then
|
||||||
|
echo "BUG: Algorithm: '${rep_obj3_algo}' of replicated object: 'minio2/test-bucket/custpartsize' doesn't match with source value: '${src_obj3_algo}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj3_keyid}" != "${src_obj3_keyid}" ]; then
|
||||||
|
echo "BUG: KeyId: '${rep_obj3_keyid}' of replicated object: 'minio2/test-bucket/custpartsize' doesn't match with source value: '${src_obj3_keyid}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check the etag, size and md5 of replicated SSEC object
|
||||||
|
if [ "${rep_obj4_etag}" != "${src_obj4_etag}" ]; then
|
||||||
|
echo "BUG: Etag: '${rep_obj4_etag}' of replicated object: 'minio2/test-bucket/mpartobj' doesn't match with source value: '${src_obj4_etag}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj4_size}" != "${src_obj4_size}" ]; then
|
||||||
|
echo "BUG: Size: '${rep_obj4_size}' of replicated object: 'minio2/test-bucket/mpartobj' doesn't match with source value: '${src_obj4_size}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${src_obj4_md5}" != "${rep_obj4_md5}" ]; then
|
||||||
|
echo "BUG: MD5 checksum of object 'minio2/test-bucket/mpartobj' doesn't match with source. Expected: '${src_obj4_md5}', Found: '${rep_obj4_md5}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check content of replicated objects
|
||||||
|
./mc cat minio2/test-bucket/encrypted --insecure
|
||||||
|
./mc cat minio2/test-bucket/mpartobj --encrypt-key "minio2/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure >/dev/null || exit_1
|
||||||
|
./mc cat minio2/test-bucket/defpartsize --insecure >/dev/null || exit_1
|
||||||
|
./mc cat minio2/test-bucket/custpartsize --insecure >/dev/null || exit_1
|
||||||
|
|
||||||
|
cleanup
|
|
@ -0,0 +1,193 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# shellcheck disable=SC2120
|
||||||
|
exit_1() {
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
echo "minio1 ============"
|
||||||
|
cat /tmp/minio1_1.log
|
||||||
|
echo "minio2 ============"
|
||||||
|
cat /tmp/minio2_1.log
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo -n "Cleaning up instances of MinIO ..."
|
||||||
|
pkill minio || sudo pkill minio
|
||||||
|
pkill -9 minio || sudo pkill -9 minio
|
||||||
|
rm -rf /tmp/minio{1,2}
|
||||||
|
echo "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
export MINIO_BROWSER=off
|
||||||
|
export MINIO_ROOT_USER="minio"
|
||||||
|
export MINIO_ROOT_PASSWORD="minio123"
|
||||||
|
|
||||||
|
# Create certificates for TLS enabled MinIO
|
||||||
|
echo -n "Setup certs for MinIO instances ..."
|
||||||
|
wget -O certgen https://github.com/minio/certgen/releases/latest/download/certgen-linux-amd64 && chmod +x certgen
|
||||||
|
./certgen --host localhost
|
||||||
|
mkdir -p ~/.minio/certs
|
||||||
|
mv public.crt ~/.minio/certs || sudo mv public.crt ~/.minio/certs
|
||||||
|
mv private.key ~/.minio/certs || sudo mv private.key ~/.minio/certs
|
||||||
|
echo "done"
|
||||||
|
|
||||||
|
# Start MinIO instances
|
||||||
|
echo -n "Starting MinIO instances ..."
|
||||||
|
minio server --address ":9001" --console-address ":10000" /tmp/minio1/{1...4}/disk{1...4} /tmp/minio1/{5...8}/disk{1...4} >/tmp/minio1_1.log 2>&1 &
|
||||||
|
minio server --address ":9002" --console-address ":11000" /tmp/minio2/{1...4}/disk{1...4} /tmp/minio2/{5...8}/disk{1...4} >/tmp/minio2_1.log 2>&1 &
|
||||||
|
echo "done"
|
||||||
|
|
||||||
|
if [ ! -f ./mc ]; then
|
||||||
|
echo -n "Downloading MinIO client ..."
|
||||||
|
wget -O mc https://dl.min.io/client/mc/release/linux-amd64/mc &&
|
||||||
|
chmod +x mc
|
||||||
|
echo "done"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
export MC_HOST_minio1=https://minio:minio123@localhost:9001
|
||||||
|
export MC_HOST_minio2=https://minio:minio123@localhost:9002
|
||||||
|
|
||||||
|
# Prepare data for tests
|
||||||
|
echo -n "Preparing test data ..."
|
||||||
|
mkdir -p /tmp/data
|
||||||
|
echo "Hello world" >/tmp/data/plainfile
|
||||||
|
echo "Hello from encrypted world" >/tmp/data/encrypted
|
||||||
|
touch /tmp/data/defpartsize
|
||||||
|
shred -s 500M /tmp/data/defpartsize
|
||||||
|
touch /tmp/data/mpartobj.txt
|
||||||
|
shred -s 500M /tmp/data/mpartobj.txt
|
||||||
|
echo "done"
|
||||||
|
|
||||||
|
# Enable compression for site minio1
|
||||||
|
./mc admin config set minio1 compression enable=on extensions=".txt" --insecure
|
||||||
|
./mc admin config set minio1 compression allow_encryption=on --insecure
|
||||||
|
|
||||||
|
# Create bucket in source cluster
|
||||||
|
echo "Create bucket in source MinIO instance"
|
||||||
|
./mc mb minio1/test-bucket --insecure
|
||||||
|
|
||||||
|
# Load objects to source site
|
||||||
|
echo "Loading objects to source MinIO instance"
|
||||||
|
./mc cp /tmp/data/plainfile minio1/test-bucket --insecure
|
||||||
|
./mc cp /tmp/data/encrypted minio1/test-bucket --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure
|
||||||
|
./mc cp /tmp/data/defpartsize minio1/test-bucket --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure
|
||||||
|
|
||||||
|
# Below should fail as compression and SSEC used at the same time
|
||||||
|
RESULT=$({ ./mc put /tmp/data/mpartobj.txt minio1/test-bucket --encrypt-key "minio1/test-bucket/mpartobj.txt=iliketobecrazybutnotsomuchreally" --insecure; } 2>&1)
|
||||||
|
if [[ ${RESULT} != *"Server side encryption specified with SSE-C with compression not allowed"* ]]; then
|
||||||
|
echo "BUG: Loading an SSE-C object to site with compression should fail. Succeeded though."
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add replication site
|
||||||
|
./mc admin replicate add minio1 minio2 --insecure
|
||||||
|
# sleep for replication to complete
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
# List the objects from source site
|
||||||
|
echo "Objects from source instance"
|
||||||
|
./mc ls minio1/test-bucket --insecure
|
||||||
|
count1=$(./mc ls minio1/test-bucket/plainfile --insecure | wc -l)
|
||||||
|
if [ "${count1}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/plainfile not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
count2=$(./mc ls minio1/test-bucket/encrypted --insecure | wc -l)
|
||||||
|
if [ "${count2}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/encrypted not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
count3=$(./mc ls minio1/test-bucket/defpartsize --insecure | wc -l)
|
||||||
|
if [ "${count3}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/defpartsize not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
sleep 120
|
||||||
|
|
||||||
|
# List the objects from replicated site
|
||||||
|
echo "Objects from replicated instance"
|
||||||
|
./mc ls minio2/test-bucket --insecure
|
||||||
|
repcount1=$(./mc ls minio2/test-bucket/plainfile --insecure | wc -l)
|
||||||
|
if [ "${repcount1}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/plainfile not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
repcount2=$(./mc ls minio2/test-bucket/encrypted --insecure | wc -l)
|
||||||
|
if [ "${repcount2}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/encrypted not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
repcount3=$(./mc ls minio2/test-bucket/defpartsize --insecure | wc -l)
|
||||||
|
if [ "${repcount3}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/defpartsize not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stat the SSEC objects from source site
|
||||||
|
echo "Stat minio1/test-bucket/encrypted"
|
||||||
|
./mc stat minio1/test-bucket/encrypted --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out1=$(./mc stat minio1/test-bucket/encrypted --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
src_obj1_etag=$(echo "${stat_out1}" | jq '.etag')
|
||||||
|
src_obj1_size=$(echo "${stat_out1}" | jq '.size')
|
||||||
|
src_obj1_md5=$(echo "${stat_out1}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
echo "Stat minio1/test-bucket/defpartsize"
|
||||||
|
./mc stat minio1/test-bucket/defpartsize --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out2=$(./mc stat minio1/test-bucket/defpartsize --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
src_obj2_etag=$(echo "${stat_out2}" | jq '.etag')
|
||||||
|
src_obj2_size=$(echo "${stat_out2}" | jq '.size')
|
||||||
|
src_obj2_md5=$(echo "${stat_out2}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
|
||||||
|
# Stat the SSEC objects from replicated site
|
||||||
|
echo "Stat minio2/test-bucket/encrypted"
|
||||||
|
./mc stat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out1_rep=$(./mc stat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
rep_obj1_etag=$(echo "${stat_out1_rep}" | jq '.etag')
|
||||||
|
rep_obj1_size=$(echo "${stat_out1_rep}" | jq '.size')
|
||||||
|
rep_obj1_md5=$(echo "${stat_out1_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
echo "Stat minio2/test-bucket/defpartsize"
|
||||||
|
./mc stat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out2_rep=$(./mc stat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
rep_obj2_etag=$(echo "${stat_out2_rep}" | jq '.etag')
|
||||||
|
rep_obj2_size=$(echo "${stat_out2_rep}" | jq '.size')
|
||||||
|
rep_obj2_md5=$(echo "${stat_out2_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
|
||||||
|
# Check the etag and size of replicated SSEC objects
|
||||||
|
if [ "${rep_obj1_etag}" != "${src_obj1_etag}" ]; then
|
||||||
|
echo "BUG: Etag: '${rep_obj1_etag}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_etag}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj1_size}" != "${src_obj1_size}" ]; then
|
||||||
|
echo "BUG: Size: '${rep_obj1_size}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_size}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj2_etag}" != "${src_obj2_etag}" ]; then
|
||||||
|
echo "BUG: Etag: '${rep_obj2_etag}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_etag}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj2_size}" != "${src_obj2_size}" ]; then
|
||||||
|
echo "BUG: Size: '${rep_obj2_size}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_size}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check content of replicated SSEC objects
|
||||||
|
./mc cat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure
|
||||||
|
./mc cat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure >/dev/null || exit_1
|
||||||
|
|
||||||
|
# Check the MD5 checksums of encrypted objects from source and target
|
||||||
|
if [ "${src_obj1_md5}" != "${rep_obj1_md5}" ]; then
|
||||||
|
echo "BUG: MD5 checksum of object 'minio2/test-bucket/encrypted' doesn't match with source. Expected: '${src_obj1_md5}', Found: '${rep_obj1_md5}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${src_obj2_md5}" != "${rep_obj2_md5}" ]; then
|
||||||
|
echo "BUG: MD5 checksum of object 'minio2/test-bucket/defpartsize' doesn't match with source. Expected: '${src_obj2_md5}', Found: '${rep_obj2_md5}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup
|
|
@ -0,0 +1,219 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# shellcheck disable=SC2120
|
||||||
|
exit_1() {
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
echo "minio1 ============"
|
||||||
|
cat /tmp/minio1_1.log
|
||||||
|
echo "minio2 ============"
|
||||||
|
cat /tmp/minio2_1.log
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo -n "Cleaning up instances of MinIO ..."
|
||||||
|
pkill minio || sudo pkill minio
|
||||||
|
pkill -9 minio || sudo pkill -9 minio
|
||||||
|
rm -rf /tmp/minio{1,2}
|
||||||
|
echo "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
export MINIO_BROWSER=off
|
||||||
|
export MINIO_ROOT_USER="minio"
|
||||||
|
export MINIO_ROOT_PASSWORD="minio123"
|
||||||
|
|
||||||
|
# Create certificates for TLS enabled MinIO
|
||||||
|
echo -n "Setup certs for MinIO instances ..."
|
||||||
|
wget -O certgen https://github.com/minio/certgen/releases/latest/download/certgen-linux-amd64 && chmod +x certgen
|
||||||
|
./certgen --host localhost
|
||||||
|
mkdir -p ~/.minio/certs
|
||||||
|
mv public.crt ~/.minio/certs || sudo mv public.crt ~/.minio/certs
|
||||||
|
mv private.key ~/.minio/certs || sudo mv private.key ~/.minio/certs
|
||||||
|
echo "done"
|
||||||
|
|
||||||
|
# Start MinIO instances
|
||||||
|
echo -n "Starting MinIO instances ..."
|
||||||
|
minio server --address ":9001" --console-address ":10000" /tmp/minio1/{1...4}/disk{1...4} /tmp/minio1/{5...8}/disk{1...4} >/tmp/minio1_1.log 2>&1 &
|
||||||
|
minio server --address ":9002" --console-address ":11000" /tmp/minio2/{1...4}/disk{1...4} /tmp/minio2/{5...8}/disk{1...4} >/tmp/minio2_1.log 2>&1 &
|
||||||
|
echo "done"
|
||||||
|
|
||||||
|
if [ ! -f ./mc ]; then
|
||||||
|
echo -n "Downloading MinIO client ..."
|
||||||
|
wget -O mc https://dl.min.io/client/mc/release/linux-amd64/mc &&
|
||||||
|
chmod +x mc
|
||||||
|
echo "done"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
export MC_HOST_minio1=https://minio:minio123@localhost:9001
|
||||||
|
export MC_HOST_minio2=https://minio:minio123@localhost:9002
|
||||||
|
|
||||||
|
# Prepare data for tests
|
||||||
|
echo -n "Preparing test data ..."
|
||||||
|
mkdir -p /tmp/data
|
||||||
|
echo "Hello world" >/tmp/data/plainfile
|
||||||
|
echo "Hello from encrypted world" >/tmp/data/encrypted
|
||||||
|
touch /tmp/data/defpartsize
|
||||||
|
shred -s 500M /tmp/data/defpartsize
|
||||||
|
touch /tmp/data/custpartsize
|
||||||
|
shred -s 500M /tmp/data/custpartsize
|
||||||
|
echo "done"
|
||||||
|
|
||||||
|
# Add replication site
|
||||||
|
./mc admin replicate add minio1 minio2 --insecure
|
||||||
|
# sleep for replication to complete
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
# Create bucket in source cluster
|
||||||
|
echo "Create bucket in source MinIO instance"
|
||||||
|
./mc mb minio1/test-bucket --insecure
|
||||||
|
|
||||||
|
# Load objects to source site
|
||||||
|
echo "Loading objects to source MinIO instance"
|
||||||
|
./mc cp /tmp/data/plainfile minio1/test-bucket --insecure
|
||||||
|
./mc cp /tmp/data/encrypted minio1/test-bucket --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure
|
||||||
|
./mc cp /tmp/data/defpartsize minio1/test-bucket --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure
|
||||||
|
./mc put /tmp/data/custpartsize minio1/test-bucket --encrypt-key "minio1/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure --part-size 50MiB
|
||||||
|
sleep 120
|
||||||
|
|
||||||
|
# List the objects from source site
|
||||||
|
echo "Objects from source instance"
|
||||||
|
./mc ls minio1/test-bucket --insecure
|
||||||
|
count1=$(./mc ls minio1/test-bucket/plainfile --insecure | wc -l)
|
||||||
|
if [ "${count1}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/plainfile not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
count2=$(./mc ls minio1/test-bucket/encrypted --insecure | wc -l)
|
||||||
|
if [ "${count2}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/encrypted not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
count3=$(./mc ls minio1/test-bucket/defpartsize --insecure | wc -l)
|
||||||
|
if [ "${count3}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/defpartsize not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
count4=$(./mc ls minio1/test-bucket/custpartsize --insecure | wc -l)
|
||||||
|
if [ "${count4}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/custpartsize not found"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# List the objects from replicated site
|
||||||
|
echo "Objects from replicated instance"
|
||||||
|
./mc ls minio2/test-bucket --insecure
|
||||||
|
repcount1=$(./mc ls minio2/test-bucket/plainfile --insecure | wc -l)
|
||||||
|
if [ "${repcount1}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/plainfile not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
repcount2=$(./mc ls minio2/test-bucket/encrypted --insecure | wc -l)
|
||||||
|
if [ "${repcount2}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/encrypted not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
repcount3=$(./mc ls minio2/test-bucket/defpartsize --insecure | wc -l)
|
||||||
|
if [ "${repcount3}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/defpartsize not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
repcount4=$(./mc ls minio2/test-bucket/custpartsize --insecure | wc -l)
|
||||||
|
if [ "${repcount4}" -ne 1 ]; then
|
||||||
|
echo "BUG: object test-bucket/custpartsize not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stat the SSEC objects from source site
|
||||||
|
echo "Stat minio1/test-bucket/encrypted"
|
||||||
|
./mc stat minio1/test-bucket/encrypted --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out1=$(./mc stat minio1/test-bucket/encrypted --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
src_obj1_etag=$(echo "${stat_out1}" | jq '.etag')
|
||||||
|
src_obj1_size=$(echo "${stat_out1}" | jq '.size')
|
||||||
|
src_obj1_md5=$(echo "${stat_out1}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
echo "Stat minio1/test-bucket/defpartsize"
|
||||||
|
./mc stat minio1/test-bucket/defpartsize --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out2=$(./mc stat minio1/test-bucket/defpartsize --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
src_obj2_etag=$(echo "${stat_out2}" | jq '.etag')
|
||||||
|
src_obj2_size=$(echo "${stat_out2}" | jq '.size')
|
||||||
|
src_obj2_md5=$(echo "${stat_out2}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
echo "Stat minio1/test-bucket/custpartsize"
|
||||||
|
./mc stat minio1/test-bucket/custpartsize --encrypt-key "minio1/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out3=$(./mc stat minio1/test-bucket/custpartsize --encrypt-key "minio1/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
src_obj3_etag=$(echo "${stat_out3}" | jq '.etag')
|
||||||
|
src_obj3_size=$(echo "${stat_out3}" | jq '.size')
|
||||||
|
src_obj3_md5=$(echo "${stat_out3}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
|
||||||
|
# Stat the SSEC objects from replicated site
|
||||||
|
echo "Stat minio2/test-bucket/encrypted"
|
||||||
|
./mc stat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out1_rep=$(./mc stat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
rep_obj1_etag=$(echo "${stat_out1_rep}" | jq '.etag')
|
||||||
|
rep_obj1_size=$(echo "${stat_out1_rep}" | jq '.size')
|
||||||
|
rep_obj1_md5=$(echo "${stat_out1_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
echo "Stat minio2/test-bucket/defpartsize"
|
||||||
|
./mc stat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out2_rep=$(./mc stat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
rep_obj2_etag=$(echo "${stat_out2_rep}" | jq '.etag')
|
||||||
|
rep_obj2_size=$(echo "${stat_out2_rep}" | jq '.size')
|
||||||
|
rep_obj2_md5=$(echo "${stat_out2_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
echo "Stat minio2/test-bucket/custpartsize"
|
||||||
|
./mc stat minio2/test-bucket/custpartsize --encrypt-key "minio2/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||||
|
stat_out3_rep=$(./mc stat minio2/test-bucket/custpartsize --encrypt-key "minio2/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||||
|
rep_obj3_etag=$(echo "${stat_out3_rep}" | jq '.etag')
|
||||||
|
rep_obj3_size=$(echo "${stat_out3_rep}" | jq '.size')
|
||||||
|
rep_obj3_md5=$(echo "${stat_out3_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||||
|
|
||||||
|
# Check the etag and size of replicated SSEC objects
|
||||||
|
if [ "${rep_obj1_etag}" != "${src_obj1_etag}" ]; then
|
||||||
|
echo "BUG: Etag: '${rep_obj1_etag}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_etag}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj1_size}" != "${src_obj1_size}" ]; then
|
||||||
|
echo "BUG: Size: '${rep_obj1_size}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_size}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj2_etag}" != "${src_obj2_etag}" ]; then
|
||||||
|
echo "BUG: Etag: '${rep_obj2_etag}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_etag}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj2_size}" != "${src_obj2_size}" ]; then
|
||||||
|
echo "BUG: Size: '${rep_obj2_size}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_size}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj3_etag}" != "${src_obj3_etag}" ]; then
|
||||||
|
echo "BUG: Etag: '${rep_obj3_etag}' of replicated object: 'minio2/test-bucket/custpartsize' doesn't match with source value: '${src_obj3_etag}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${rep_obj3_size}" != "${src_obj3_size}" ]; then
|
||||||
|
echo "BUG: Size: '${rep_obj3_size}' of replicated object: 'minio2/test-bucket/custpartsize' doesn't match with source value: '${src_obj3_size}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check content of replicated SSEC objects
|
||||||
|
./mc cat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure
|
||||||
|
./mc cat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure >/dev/null || exit_1
|
||||||
|
./mc cat minio2/test-bucket/custpartsize --encrypt-key "minio2/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure >/dev/null || exit_1
|
||||||
|
|
||||||
|
# Check the MD5 checksums of encrypted objects from source and target
|
||||||
|
if [ "${src_obj1_md5}" != "${rep_obj1_md5}" ]; then
|
||||||
|
echo "BUG: MD5 checksum of object 'minio2/test-bucket/encrypted' doesn't match with source. Expected: '${src_obj1_md5}', Found: '${rep_obj1_md5}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${src_obj2_md5}" != "${rep_obj2_md5}" ]; then
|
||||||
|
echo "BUG: MD5 checksum of object 'minio2/test-bucket/defpartsize' doesn't match with source. Expected: '${src_obj2_md5}', Found: '${rep_obj2_md5}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${src_obj3_md5}" != "${rep_obj3_md5}" ]; then
|
||||||
|
echo "BUG: MD5 checksum of object 'minio2/test-bucket/custpartsize' doesn't match with source. Expected: '${src_obj3_md5}', Found: '${rep_obj3_md5}'"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup
|
|
@ -220,9 +220,6 @@ func (c Config) GetDestination() Destination {
|
||||||
|
|
||||||
// Replicate returns true if the object should be replicated.
|
// Replicate returns true if the object should be replicated.
|
||||||
func (c Config) Replicate(obj ObjectOpts) bool {
|
func (c Config) Replicate(obj ObjectOpts) bool {
|
||||||
if obj.SSEC {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, rule := range c.FilterActionableRules(obj) {
|
for _, rule := range c.FilterActionableRules(obj) {
|
||||||
if rule.Status == Disabled {
|
if rule.Status == Disabled {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -250,12 +250,12 @@ func TestReplicate(t *testing.T) {
|
||||||
{ObjectOpts{Name: "c1test"}, cfgs[0], true}, // 2. valid ObjectOpts passing empty Filter
|
{ObjectOpts{Name: "c1test"}, cfgs[0], true}, // 2. valid ObjectOpts passing empty Filter
|
||||||
{ObjectOpts{Name: "c1test", VersionID: "vid"}, cfgs[0], true}, // 3. valid ObjectOpts passing empty Filter
|
{ObjectOpts{Name: "c1test", VersionID: "vid"}, cfgs[0], true}, // 3. valid ObjectOpts passing empty Filter
|
||||||
|
|
||||||
{ObjectOpts{Name: "c1test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 4. DeleteMarker version replication valid case - matches DeleteMarkerReplication status
|
{ObjectOpts{Name: "c1test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 4. DeleteMarker version replication valid case - matches DeleteMarkerReplication status
|
||||||
{ObjectOpts{Name: "c1test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[0], true}, // 5. permanent delete of version, matches DeleteReplication status - valid case
|
{ObjectOpts{Name: "c1test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[0], true}, // 5. permanent delete of version, matches DeleteReplication status - valid case
|
||||||
{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 6. permanent delete of version, matches DeleteReplication status
|
{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 6. permanent delete of version, matches DeleteReplication status
|
||||||
{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], false}, // 7. permanent delete of version, disqualified by SSE-C
|
{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 7. permanent delete of version
|
||||||
{ObjectOpts{Name: "c1test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], false}, // 8. setting DeleteMarker on SSE-C encrypted object, disqualified by SSE-C
|
{ObjectOpts{Name: "c1test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 8. setting DeleteMarker on SSE-C encrypted object
|
||||||
{ObjectOpts{Name: "c1test", SSEC: true}, cfgs[0], false}, // 9. replication of SSE-C encrypted object, disqualified
|
{ObjectOpts{Name: "c1test", SSEC: true}, cfgs[0], true}, // 9. replication of SSE-C encrypted object
|
||||||
|
|
||||||
// using config 2 - no filters, only replication of object, metadata enabled
|
// using config 2 - no filters, only replication of object, metadata enabled
|
||||||
{ObjectOpts{Name: "c2test"}, cfgs[1], true}, // 10. valid ObjectOpts passing empty Filter
|
{ObjectOpts{Name: "c2test"}, cfgs[1], true}, // 10. valid ObjectOpts passing empty Filter
|
||||||
|
@ -264,7 +264,7 @@ func TestReplicate(t *testing.T) {
|
||||||
{ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 13. permanent delete of DeleteMarker version, disallowed by DeleteReplication status
|
{ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 13. permanent delete of DeleteMarker version, disallowed by DeleteReplication status
|
||||||
{ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 14. permanent delete of version, disqualified by SSE-C & DeleteReplication status
|
{ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 14. permanent delete of version, disqualified by SSE-C & DeleteReplication status
|
||||||
{ObjectOpts{Name: "c2test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 15. setting DeleteMarker on SSE-C encrypted object, disqualified by SSE-C & DeleteMarkerReplication status
|
{ObjectOpts{Name: "c2test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 15. setting DeleteMarker on SSE-C encrypted object, disqualified by SSE-C & DeleteMarkerReplication status
|
||||||
{ObjectOpts{Name: "c2test", SSEC: true}, cfgs[1], false}, // 16. replication of SSE-C encrypted object, disqualified by default
|
{ObjectOpts{Name: "c2test", SSEC: true}, cfgs[1], true}, // 16. replication of SSE-C encrypted object
|
||||||
// using config 2 - has more than one rule with overlapping prefixes
|
// using config 2 - has more than one rule with overlapping prefixes
|
||||||
{ObjectOpts{Name: "xy/c3test", UserTags: "k1=v1"}, cfgs[2], true}, // 17. matches rule 1 for replication of content/metadata
|
{ObjectOpts{Name: "xy/c3test", UserTags: "k1=v1"}, cfgs[2], true}, // 17. matches rule 1 for replication of content/metadata
|
||||||
{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1"}, cfgs[2], true}, // 18. matches rule 1 for replication of content/metadata
|
{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1"}, cfgs[2], true}, // 18. matches rule 1 for replication of content/metadata
|
||||||
|
|
|
@ -82,6 +82,8 @@ var (
|
||||||
// ErrIncompatibleEncryptionMethod indicates that both SSE-C headers and SSE-S3 headers were specified, and are incompatible
|
// ErrIncompatibleEncryptionMethod indicates that both SSE-C headers and SSE-S3 headers were specified, and are incompatible
|
||||||
// The client needs to remove the SSE-S3 header or the SSE-C headers
|
// The client needs to remove the SSE-S3 header or the SSE-C headers
|
||||||
ErrIncompatibleEncryptionMethod = Errorf("Server side encryption specified with both SSE-C and SSE-S3 headers")
|
ErrIncompatibleEncryptionMethod = Errorf("Server side encryption specified with both SSE-C and SSE-S3 headers")
|
||||||
|
// ErrIncompatibleEncryptionWithCompression indicates that both data compression and SSE-C not allowed at the same time
|
||||||
|
ErrIncompatibleEncryptionWithCompression = Errorf("Server side encryption specified with SSE-C with compression not allowed")
|
||||||
|
|
||||||
// ErrInvalidEncryptionKeyID returns error when KMS key id contains invalid characters
|
// ErrInvalidEncryptionKeyID returns error when KMS key id contains invalid characters
|
||||||
ErrInvalidEncryptionKeyID = Errorf("KMS KeyID contains unsupported characters")
|
ErrInvalidEncryptionKeyID = Errorf("KMS KeyID contains unsupported characters")
|
||||||
|
|
|
@ -233,6 +233,9 @@ const (
|
||||||
// Header indicates a Tag operation was performed on one/more peers successfully, though the
|
// Header indicates a Tag operation was performed on one/more peers successfully, though the
|
||||||
// current cluster does not have the object yet. This is in a site/bucket replication scenario.
|
// current cluster does not have the object yet. This is in a site/bucket replication scenario.
|
||||||
MinIOTaggingProxied = "X-Minio-Tagging-Proxied"
|
MinIOTaggingProxied = "X-Minio-Tagging-Proxied"
|
||||||
|
// Header indicates the actual replicated object size
|
||||||
|
// In case of SSEC objects getting replicated (multipart) actual size would be needed at target
|
||||||
|
MinIOReplicationActualObjectSize = "X-Minio-Replication-Actual-Object-Size"
|
||||||
|
|
||||||
// predicted date/time of transition
|
// predicted date/time of transition
|
||||||
MinIOTransition = "X-Minio-Transition"
|
MinIOTransition = "X-Minio-Transition"
|
||||||
|
|
Loading…
Reference in New Issue