|
|
|
@ -19,6 +19,7 @@ package cmd |
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"context" |
|
|
|
|
"crypto/rand" |
|
|
|
|
"encoding/binary" |
|
|
|
|
"encoding/json" |
|
|
|
|
"encoding/xml" |
|
|
|
@ -28,6 +29,7 @@ import ( |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"github.com/minio/minio-go/v7/pkg/tags" |
|
|
|
|
"github.com/minio/minio/cmd/crypto" |
|
|
|
|
"github.com/minio/minio/cmd/logger" |
|
|
|
|
bucketsse "github.com/minio/minio/pkg/bucket/encryption" |
|
|
|
|
"github.com/minio/minio/pkg/bucket/lifecycle" |
|
|
|
@ -37,6 +39,7 @@ import ( |
|
|
|
|
"github.com/minio/minio/pkg/bucket/versioning" |
|
|
|
|
"github.com/minio/minio/pkg/event" |
|
|
|
|
"github.com/minio/minio/pkg/madmin" |
|
|
|
|
"github.com/minio/sio" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
const ( |
|
|
|
@ -74,6 +77,7 @@ type BucketMetadata struct { |
|
|
|
|
QuotaConfigJSON []byte |
|
|
|
|
ReplicationConfigXML []byte |
|
|
|
|
BucketTargetsConfigJSON []byte |
|
|
|
|
BucketTargetsConfigMetaJSON []byte |
|
|
|
|
|
|
|
|
|
// Unexported fields. Must be updated atomically.
|
|
|
|
|
policyConfig *policy.Policy |
|
|
|
@ -86,6 +90,7 @@ type BucketMetadata struct { |
|
|
|
|
quotaConfig *madmin.BucketQuota |
|
|
|
|
replicationConfig *replication.Config |
|
|
|
|
bucketTargetConfig *madmin.BucketTargets |
|
|
|
|
bucketTargetConfigMeta map[string]string |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// newBucketMetadata creates BucketMetadata with the supplied name and Created to Now.
|
|
|
|
@ -101,6 +106,7 @@ func newBucketMetadata(name string) BucketMetadata { |
|
|
|
|
XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/", |
|
|
|
|
}, |
|
|
|
|
bucketTargetConfig: &madmin.BucketTargets{}, |
|
|
|
|
bucketTargetConfigMeta: make(map[string]string), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -140,16 +146,16 @@ func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string) |
|
|
|
|
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) { |
|
|
|
|
b := newBucketMetadata(bucket) |
|
|
|
|
err := b.Load(ctx, objectAPI, b.Name) |
|
|
|
|
if err == nil { |
|
|
|
|
return b, b.convertLegacyConfigs(ctx, objectAPI) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !errors.Is(err, errConfigNotFound) { |
|
|
|
|
if err != nil && !errors.Is(err, errConfigNotFound) { |
|
|
|
|
return b, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
|
|
|
|
return b, b.convertLegacyConfigs(ctx, objectAPI) |
|
|
|
|
if err := b.convertLegacyConfigs(ctx, objectAPI); err != nil { |
|
|
|
|
return b, err |
|
|
|
|
} |
|
|
|
|
// migrate unencrypted remote targets
|
|
|
|
|
return b, b.migrateTargetConfig(ctx, objectAPI) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// parseAllConfigs will parse all configs and populate the private fields.
|
|
|
|
@ -234,7 +240,8 @@ func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLa |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(b.BucketTargetsConfigJSON) != 0 { |
|
|
|
|
if err = json.Unmarshal(b.BucketTargetsConfigJSON, b.bucketTargetConfig); err != nil { |
|
|
|
|
b.bucketTargetConfig, err = parseBucketTargetConfig(b.Name, b.BucketTargetsConfigJSON, b.BucketTargetsConfigMetaJSON) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
@ -354,6 +361,7 @@ func (b *BucketMetadata) Save(ctx context.Context, api ObjectLayer) error { |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
configFile := path.Join(bucketConfigPrefix, b.Name, bucketMetadataFile) |
|
|
|
|
return saveConfig(ctx, api, configFile, data) |
|
|
|
|
} |
|
|
|
@ -373,3 +381,76 @@ func deleteBucketMetadata(ctx context.Context, obj objectDeleter, bucket string) |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// migrate config for remote targets by encrypting data if currently unencrypted and kms is configured.
|
|
|
|
|
func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI ObjectLayer) error { |
|
|
|
|
var err error |
|
|
|
|
// early return if no targets or already encrypted
|
|
|
|
|
if len(b.BucketTargetsConfigJSON) == 0 || GlobalKMS == nil || len(b.BucketTargetsConfigMetaJSON) != 0 { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
encBytes, metaBytes, err := encryptBucketMetadata(b.Name, b.BucketTargetsConfigJSON, crypto.Context{b.Name: b.Name, bucketTargetsFile: bucketTargetsFile}) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
b.BucketTargetsConfigJSON = encBytes |
|
|
|
|
b.BucketTargetsConfigMetaJSON = metaBytes |
|
|
|
|
return b.Save(ctx, objectAPI) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// encrypt bucket metadata if kms is configured.
|
|
|
|
|
func encryptBucketMetadata(bucket string, input []byte, kmsContext crypto.Context) (output, metabytes []byte, err error) { |
|
|
|
|
var sealedKey crypto.SealedKey |
|
|
|
|
if GlobalKMS == nil { |
|
|
|
|
output = input |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
var ( |
|
|
|
|
key [32]byte |
|
|
|
|
encKey []byte |
|
|
|
|
) |
|
|
|
|
metadata := make(map[string]string) |
|
|
|
|
key, encKey, err = GlobalKMS.GenerateKey(GlobalKMS.DefaultKeyID(), kmsContext) |
|
|
|
|
if err != nil { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
outbuf := bytes.NewBuffer(nil) |
|
|
|
|
objectKey := crypto.GenerateKey(key, rand.Reader) |
|
|
|
|
sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "") |
|
|
|
|
crypto.S3.CreateMetadata(metadata, GlobalKMS.DefaultKeyID(), encKey, sealedKey) |
|
|
|
|
_, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20}) |
|
|
|
|
if err != nil { |
|
|
|
|
return output, metabytes, err |
|
|
|
|
} |
|
|
|
|
metabytes, err = json.Marshal(metadata) |
|
|
|
|
if err != nil { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
return outbuf.Bytes(), metabytes, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// decrypt bucket metadata if kms is configured.
|
|
|
|
|
func decryptBucketMetadata(input []byte, bucket string, meta map[string]string, kmsContext crypto.Context) ([]byte, error) { |
|
|
|
|
if GlobalKMS == nil { |
|
|
|
|
return nil, errKMSNotConfigured |
|
|
|
|
} |
|
|
|
|
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(meta) |
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
extKey, err := GlobalKMS.UnsealKey(keyID, kmsKey, kmsContext) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
var objectKey crypto.ObjectKey |
|
|
|
|
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, ""); err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
outbuf := bytes.NewBuffer(nil) |
|
|
|
|
_, err = sio.Decrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20}) |
|
|
|
|
|
|
|
|
|
return outbuf.Bytes(), err |
|
|
|
|
} |
|
|
|
|