diff --git a/lib/Config.js b/lib/Config.js index bee83b4b1c..ef5fe1771d 100644 --- a/lib/Config.js +++ b/lib/Config.js @@ -1403,6 +1403,11 @@ class Config extends EventEmitter { this.kmsAWS = this._parseKmsAWS(config); + const globalEncryptionEnabled = config.globalEncryptionEnabled; + this.globalEncryptionEnabled = globalEncryptionEnabled || false; + assert(typeof this.globalEncryptionEnabled === 'boolean', + 'config.globalEncryptionEnabled must be a boolean'); + const defaultEncryptionKeyPerAccount = config.defaultEncryptionKeyPerAccount; this.defaultEncryptionKeyPerAccount = defaultEncryptionKeyPerAccount || false; assert(typeof this.defaultEncryptionKeyPerAccount === 'boolean', diff --git a/lib/api/apiUtils/bucket/bucketCreation.js b/lib/api/apiUtils/bucket/bucketCreation.js index 64c598465d..458cbd6375 100644 --- a/lib/api/apiUtils/bucket/bucketCreation.js +++ b/lib/api/apiUtils/bucket/bucketCreation.js @@ -11,6 +11,7 @@ const metadata = require('../../../metadata/wrapper'); const kms = require('../../../kms/wrapper'); const isLegacyAWSBehavior = require('../../../utilities/legacyAWSBehavior'); const { isServiceAccount } = require('../authorization/permissionChecks'); +const { config } = require('../../../Config'); const usersBucket = constants.usersBucket; const oldUsersBucket = constants.oldUsersBucket; @@ -248,7 +249,17 @@ function createBucket(authInfo, bucketName, headers, } const newBucketMD = results.prepareNewBucketMD; if (existingBucketMD === 'NoBucketYet') { - const sseConfig = parseBucketEncryptionHeaders(headers); + const bucketSseConfig = parseBucketEncryptionHeaders(headers); + + // Apply global SSE configuration when global encryption is enabled + // and no SSE settings were specified during bucket creation. + // Bucket-specific SSE headers override the default encryption. + const sseConfig = config.globalEncryptionEnabled && !bucketSseConfig.algorithm + ? { + algorithm: 'AES256', + mandatory: true, + } : bucketSseConfig; + return bucketLevelServerSideEncryption( bucket, sseConfig, log, (err, sseInfo) => { diff --git a/lib/api/bucketPutEncryption.js b/lib/api/bucketPutEncryption.js index 3c965643d7..e214407ed7 100644 --- a/lib/api/bucketPutEncryption.js +++ b/lib/api/bucketPutEncryption.js @@ -70,6 +70,11 @@ function bucketPutEncryption(authInfo, request, log, callback) { updatedConfig.configuredMasterKeyId = configuredMasterKeyId; } + const { isAccountEncryptionEnabled } = existingConfig; + if (isAccountEncryptionEnabled) { + updatedConfig.isAccountEncryptionEnabled = isAccountEncryptionEnabled; + } + return next(null, bucket, updatedConfig); }, (bucket, updatedConfig, next) => { diff --git a/tests/unit/api/bucketPut.js b/tests/unit/api/bucketPut.js index b69cec30c2..db9b766b9e 100644 --- a/tests/unit/api/bucketPut.js +++ b/tests/unit/api/bucketPut.js @@ -740,3 +740,160 @@ describe('bucketPut API with failed vault service', () => { }); }); +describe('bucketPut API with SSE Configurations', () => { + const initialGlobalEncryption = config.globalEncryptionEnabled; + + afterEach(() => { + config.globalEncryptionEnabled = initialGlobalEncryption; + cleanup(); + }); + + const createTestRequestWithSSE = (sseHeaders) => ({ + ...testRequest, + headers: { + ...testRequest.headers, + ...sseHeaders, + }, + }); + + it('should apply default AES256 SSE when global encryption is enabled and no SSE headers are provided', done => { + config.globalEncryptionEnabled = true; + const request = createTestRequestWithSSE({}); + + bucketPut(authInfo, request, log, err => { + assert.ifError(err); + return metadata.getBucket(bucketName, log, (err, md) => { + assert.ifError(err); + const sse = md.getServerSideEncryption(); + assert.strictEqual(sse.algorithm, 'AES256'); + assert.strictEqual(sse.mandatory, true); + done(); + }); + }); + }); + + it('should override global SSE with bucket-specific SSE headers when provided', done => { + config.globalEncryptionEnabled = true; + const request = createTestRequestWithSSE({ + 'x-amz-scal-server-side-encryption': 'aws:kms', + 'x-amz-scal-server-side-encryption-aws-kms-key-id': 'test-kms-key-id', + }); + + bucketPut(authInfo, request, log, err => { + assert.ifError(err); + return metadata.getBucket(bucketName, log, (err, md) => { + assert.ifError(err); + const sse = md.getServerSideEncryption(); + assert.strictEqual(sse.algorithm, 'aws:kms'); + assert.strictEqual(sse.mandatory, true); + assert.strictEqual(sse.configuredMasterKeyId, 'test-kms-key-id'); + done(); + }); + }); + }); + + it('should not apply global SSE when global encryption is disabled and no SSE headers are provided', done => { + config.globalEncryptionEnabled = false; + const request = createTestRequestWithSSE({}); + + bucketPut(authInfo, request, log, err => { + assert.ifError(err); + return metadata.getBucket(bucketName, log, (err, md) => { + assert.ifError(err); + const sse = md.getServerSideEncryption(); + assert(!sse); + done(); + }); + }); + }); + + it('should apply bucket-specific SSE headers when global encryption is disabled', done => { + config.globalEncryptionEnabled = false; + const request = createTestRequestWithSSE({ + 'x-amz-scal-server-side-encryption': 'AES256', + }); + + bucketPut(authInfo, request, log, err => { + assert.ifError(err); + return metadata.getBucket(bucketName, log, (err, md) => { + assert.ifError(err); + const sse = md.getServerSideEncryption(); + assert.strictEqual(sse.algorithm, 'AES256'); + assert.strictEqual(sse.mandatory, true); + done(); + }); + }); + }); + + it('should apply default AES256 SSE when global encryption is enabled and an invalid algorithm is set', done => { + config.globalEncryptionEnabled = true; + const request = createTestRequestWithSSE({ + 'x-amz-scal-server-side-encryption': 'INVALID_ALGO', + }); + + bucketPut(authInfo, request, log, err => { + assert.ifError(err); + metadata.getBucket(bucketName, log, (err, md) => { + assert.ifError(err); + const sse = md.getServerSideEncryption(); + assert.strictEqual(sse.algorithm, 'AES256'); + assert.strictEqual(sse.mandatory, true); + done(); + }); + }); + }); + + it('should prioritize bucket-specific SSE over global settings even if global is enabled', done => { + config.globalEncryptionEnabled = true; + const request = createTestRequestWithSSE({ + 'x-amz-scal-server-side-encryption': 'aws:kms', + 'x-amz-scal-server-side-encryption-aws-kms-key-id': 'another-kms-key-id', + }); + + bucketPut(authInfo, request, log, err => { + assert.ifError(err); + return metadata.getBucket(bucketName, log, (err, md) => { + assert.ifError(err); + const sse = md.getServerSideEncryption(); + assert.strictEqual(sse.algorithm, 'aws:kms'); + assert.strictEqual(sse.mandatory, true); + assert.strictEqual(sse.configuredMasterKeyId, 'another-kms-key-id'); + done(); + }); + }); + }); + + it('should apply default SSE if global encryption is enabled and no bucket-specific SSE is provided', done => { + config.globalEncryptionEnabled = true; + const request = createTestRequestWithSSE({}); + + bucketPut(authInfo, request, log, err => { + assert.ifError(err); + return metadata.getBucket(bucketName, log, (err, md) => { + assert.ifError(err); + const sse = md.getServerSideEncryption(); + assert.strictEqual(sse.algorithm, 'AES256'); + assert.strictEqual(sse.mandatory, true); + done(); + }); + }); + }); + + it('should not override bucket-specific SSE with global settings', done => { + config.globalEncryptionEnabled = true; + const request = createTestRequestWithSSE({ + 'x-amz-scal-server-side-encryption': 'aws:kms', + }); + + bucketPut(authInfo, request, log, err => { + assert.ifError(err); + return metadata.getBucket(bucketName, log, (err, md) => { + assert.ifError(err); + const sse = md.getServerSideEncryption(); + assert.strictEqual(sse.algorithm, 'aws:kms'); + assert.strictEqual(sse.mandatory, true); + done(); + }); + }); + }); +}); diff --git a/tests/unit/api/bucketPutEncryption.js b/tests/unit/api/bucketPutEncryption.js index 1b66f40462..4d42a625fa 100644 --- a/tests/unit/api/bucketPutEncryption.js +++ b/tests/unit/api/bucketPutEncryption.js @@ -360,6 +360,136 @@ describe('bucketPutEncryption API with account level encryption', () => { }); }); }); + + it('should keep isAccountEncryptionEnabled after AES256 encryption update', done => { + const post = templateSSEConfig({ algorithm: 'AES256' }); + const expectedSseInfo = { + cryptoScheme: 1, + algorithm: 'AES256', + mandatory: true, + masterKeyId: accountLevelMasterKeyId, + isAccountEncryptionEnabled: true, + }; + + bucketPutEncryption(authInfo, templateRequest(bucketName, { post }), log, err => { + assert.ifError(err); + return getSSEConfig(bucketName, log, (err, sseInfo) => { + assert.ifError(err); + assert.deepStrictEqual(sseInfo, expectedSseInfo); + const newConf = templateSSEConfig({ algorithm: 'AES256' }); + return bucketPutEncryption(authInfo, templateRequest(bucketName, { post: newConf }), log, + err => { + assert.ifError(err); + return getSSEConfig(bucketName, log, (err, updatedSSEInfo) => { + assert.deepStrictEqual(updatedSSEInfo, expectedSseInfo); + done(); + }); + } + ); + }); + }); + }); + + it('should keep isAccountEncryptionEnabled after switching from AES256 to aws:kms with keyId', done => { + const post = templateSSEConfig({ algorithm: 'AES256' }); + bucketPutEncryption(authInfo, templateRequest(bucketName, { post }), log, err => { + assert.ifError(err); + return getSSEConfig(bucketName, log, (err, sseInfo) => { + assert.ifError(err); + assert.deepStrictEqual(sseInfo, { + cryptoScheme: 1, + algorithm: 'AES256', + mandatory: true, + masterKeyId: accountLevelMasterKeyId, + isAccountEncryptionEnabled: true, + }); + const keyId = '12345'; + const newConf = templateSSEConfig({ algorithm: 'aws:kms', keyId }); + return bucketPutEncryption(authInfo, templateRequest(bucketName, { post: newConf }), log, + err => { + assert.ifError(err); + return getSSEConfig(bucketName, log, (err, updatedSSEInfo) => { + assert.deepStrictEqual(updatedSSEInfo, { + cryptoScheme: 1, + algorithm: 'aws:kms', + mandatory: true, + masterKeyId: accountLevelMasterKeyId, + configuredMasterKeyId: keyId, + isAccountEncryptionEnabled: true, + }); + done(); + }); + } + ); + }); + }); + }); + + it('should keep isAccountEncryptionEnabled after switching from aws:kms to AES256 encryption', done => { + const post = templateSSEConfig({ algorithm: 'aws:kms' }); + bucketPutEncryption(authInfo, templateRequest(bucketName, { post }), log, err => { + assert.ifError(err); + return getSSEConfig(bucketName, log, (err, sseInfo) => { + assert.ifError(err); + assert.deepStrictEqual(sseInfo, { + cryptoScheme: 1, + algorithm: 'aws:kms', + mandatory: true, + masterKeyId: accountLevelMasterKeyId, + isAccountEncryptionEnabled: true, + }); + const newConf = templateSSEConfig({ algorithm: 'AES256' }); + return bucketPutEncryption(authInfo, templateRequest(bucketName, { post: newConf }), log, + err => { + assert.ifError(err); + return getSSEConfig(bucketName, log, (err, updatedSSEInfo) => { + assert.deepStrictEqual(updatedSSEInfo, { + cryptoScheme: 1, + algorithm: 'AES256', + mandatory: true, + masterKeyId: accountLevelMasterKeyId, + isAccountEncryptionEnabled: true, + }); + done(); + }); + } + ); + }); + }); + }); + + it('should set isAccountEncryptionEnabled after switching from aws:kms with keyId to AES256', done => { + const keyId = '12345'; + const post = templateSSEConfig({ algorithm: 'aws:kms', keyId }); + bucketPutEncryption(authInfo, templateRequest(bucketName, { post }), log, err => { + assert.ifError(err); + return getSSEConfig(bucketName, log, (err, sseInfo) => { + assert.ifError(err); + assert.deepStrictEqual(sseInfo, { + cryptoScheme: 1, + algorithm: 'aws:kms', + mandatory: true, + configuredMasterKeyId: keyId, + }); + const newConf = templateSSEConfig({ algorithm: 'AES256' }); + return bucketPutEncryption(authInfo, templateRequest(bucketName, { post: newConf }), log, + err => { + assert.ifError(err); + return getSSEConfig(bucketName, log, (err, updatedSSEInfo) => { + assert.deepStrictEqual(updatedSSEInfo, { + cryptoScheme: 1, + algorithm: 'AES256', + mandatory: true, + masterKeyId: accountLevelMasterKeyId, + isAccountEncryptionEnabled: true, + }); + done(); + }); + } + ); + }); + }); + }); }); describe('bucketPutEncryption API with failed vault service', () => {