Compare commits

...

2 Commits

Author SHA1 Message Date
naren-scality 18cc1ae62f improvement: allow sse header for encrypted buckets 2019-11-16 21:46:47 -08:00
Rahul Padigela f114c1be59 improvement: allow sse header for encrypted buckets 2019-09-25 17:00:39 -07:00
5 changed files with 295 additions and 1 deletions

View File

@ -97,7 +97,6 @@ const constants = {
], ],
// Headers supported by AWS that we do not currently support. // Headers supported by AWS that we do not currently support.
unsupportedHeaders: [ unsupportedHeaders: [
'x-amz-server-side-encryption',
'x-amz-server-side-encryption-customer-algorithm', 'x-amz-server-side-encryption-customer-algorithm',
'x-amz-server-side-encryption-aws-kms-key-id', 'x-amz-server-side-encryption-aws-kms-key-id',
'x-amz-server-side-encryption-context', 'x-amz-server-side-encryption-context',

View File

@ -45,6 +45,11 @@ function initiateMultipartUpload(authInfo, request, log, callback) {
log.debug('processing request', { method: 'initiateMultipartUpload' }); log.debug('processing request', { method: 'initiateMultipartUpload' });
const bucketName = request.bucketName; const bucketName = request.bucketName;
const objectKey = request.objectKey; const objectKey = request.objectKey;
const sseHeader =
request.headers['x-amz-server-side-encryption'];
const invalidSSEError
= errors.InvalidArgument.customizeDescription('The encryption method '
+ 'specified is not supported');
// Note that we are using the string set forth in constants.js // Note that we are using the string set forth in constants.js
// to split components in the storage // to split components in the storage
// of each MPU. AWS does not restrict characters in object keys so // of each MPU. AWS does not restrict characters in object keys so
@ -160,6 +165,13 @@ function initiateMultipartUpload(authInfo, request, log, callback) {
const serverSideEncryption = const serverSideEncryption =
destinationBucket.getServerSideEncryption(); destinationBucket.getServerSideEncryption();
let cipherBundle = null; let cipherBundle = null;
if ((!serverSideEncryption && sseHeader) ||
(sseHeader === 'AES256' &&
serverSideEncryption.algorithm !== sseHeader)) {
// x-amz-server-side-encryption is allowed only if bucket
// encryption is enabled and if the value is AES256
return callback(invalidSSEError);
}
if (serverSideEncryption) { if (serverSideEncryption) {
cipherBundle = { cipherBundle = {
algorithm: serverSideEncryption.algorithm, algorithm: serverSideEncryption.algorithm,

View File

@ -213,6 +213,11 @@ function objectCopy(authInfo, request, sourceBucket,
}; };
const websiteRedirectHeader = const websiteRedirectHeader =
request.headers['x-amz-website-redirect-location']; request.headers['x-amz-website-redirect-location'];
const sseHeader =
request.headers['x-amz-server-side-encryption'];
const invalidSSEError
= errors.InvalidArgument.customizeDescription('The encryption method '
+ 'specified is not supported');
if (!validateWebsiteHeader(websiteRedirectHeader)) { if (!validateWebsiteHeader(websiteRedirectHeader)) {
const err = errors.InvalidRedirectLocation; const err = errors.InvalidRedirectLocation;
@ -326,6 +331,14 @@ function objectCopy(authInfo, request, sourceBucket,
const destLocationConstraintName = const destLocationConstraintName =
storeMetadataParams.dataStoreName; storeMetadataParams.dataStoreName;
if ((!serverSideEncryption && sseHeader) ||
(sseHeader === 'AES256' &&
serverSideEncryption.algorithm !== sseHeader)) {
// x-amz-server-side-encryption is allowed only if bucket
// encryption is enabled and if the value is AES256
return next(invalidSSEError);
}
// skip if source and dest and location constraint the same and // skip if source and dest and location constraint the same and
// versioning is not enabled // versioning is not enabled
// still send along serverSideEncryption info so algo // still send along serverSideEncryption info so algo

View File

@ -33,6 +33,7 @@ const versionIdUtils = versioning.VersionID;
*/ */
function objectPut(authInfo, request, streamingV4Params, log, callback) { function objectPut(authInfo, request, streamingV4Params, log, callback) {
log.debug('processing request', { method: 'objectPut' }); log.debug('processing request', { method: 'objectPut' });
const { headers } = request;
if (!aclUtils.checkGrantHeaderValidity(request.headers)) { if (!aclUtils.checkGrantHeaderValidity(request.headers)) {
log.trace('invalid acl header'); log.trace('invalid acl header');
return callback(errors.InvalidArgument); return callback(errors.InvalidArgument);
@ -41,6 +42,9 @@ function objectPut(authInfo, request, streamingV4Params, log, callback) {
if (queryContainsVersionId instanceof Error) { if (queryContainsVersionId instanceof Error) {
return callback(queryContainsVersionId); return callback(queryContainsVersionId);
} }
const sseHeader = headers['x-amz-server-side-encryption'];
const invalidSSEError = errors.InvalidArgument.customizeDescription(
'The encryption method specified is not supported');
const bucketName = request.bucketName; const bucketName = request.bucketName;
const objectKey = request.objectKey; const objectKey = request.objectKey;
const requestType = 'objectPut'; const requestType = 'objectPut';
@ -73,6 +77,13 @@ function objectPut(authInfo, request, streamingV4Params, log, callback) {
}, },
function createCipherBundle(next) { function createCipherBundle(next) {
const serverSideEncryption = bucket.getServerSideEncryption(); const serverSideEncryption = bucket.getServerSideEncryption();
if ((!serverSideEncryption && sseHeader) ||
(sseHeader === 'AES256' &&
serverSideEncryption.algorithm !== sseHeader)) {
// x-amz-server-side-encryption is allowed only if bucket
// encryption is enabled and if the value is AES256
return next(invalidSSEError);
}
if (serverSideEncryption) { if (serverSideEncryption) {
return kms.createCipherBundle( return kms.createCipherBundle(
serverSideEncryption, log, next); serverSideEncryption, log, next);

View File

@ -0,0 +1,259 @@
const AWS = require('aws-sdk');
const uuid4 = require('uuid/v4');
const config = require('../config.json');
const { auth } = require('arsenal');
const http = require('http');
const https = require('https');
const assert = require('assert');
const logger = { info: msg => process.stdout.write(`${msg}\n`) };
const async = require('async');
function _createBucket(name, encrypt, done) {
const { transport, ipAddress, accessKey, secretKey } = config;
const verbose = false;
const options = {
host: ipAddress,
port: 8000,
method: 'PUT',
path: `/${name}/`,
rejectUnauthorized: false,
};
if (encrypt) {
Object.assign(options, {
headers: {
'x-amz-scal-server-side-encryption': 'AES256',
},
});
}
logger.info(`Creating encrypted bucket ${name}`);
const client = transport === 'https' ? https : http;
const request = client.request(options, response => {
if (verbose) {
logger.info('response status code', {
statusCode: response.statusCode,
});
logger.info('response headers', { headers: response.headers });
}
const body = [];
response.setEncoding('utf8');
response.on('data', chunk => body.push(chunk));
response.on('end', () => {
if (response.statusCode >= 200 && response.statusCode < 300) {
logger.info('Success', {
statusCode: response.statusCode,
body: verbose ? body.join('') : undefined,
});
done(null);
} else {
done({
statusCode: response.statusCode,
body: body.join(''),
});
}
});
});
auth.client.generateV4Headers(request, '', accessKey, secretKey, 's3');
if (verbose) {
logger.info('request headers', { headers: request._headers });
}
request.end();
}
function _buildS3() {
const { transport, ipAddress, accessKey, secretKey } = config;
AWS.config.update({
endpoint: `${transport}://${ipAddress}:8000`,
accessKeyId: accessKey,
secretAccessKey: secretKey,
sslEnabled: transport === 'https',
s3ForcePathStyle: true,
});
return new AWS.S3();
}
const s3 = _buildS3();
function _putObject(bucketName, objectName, encrypt, cb) {
const params = {
Bucket: bucketName,
Key: objectName,
Body: 'I am the best content ever',
};
if (encrypt) {
Object.assign(params, {
ServerSideEncryption: 'AES256',
});
}
s3.putObject(params, err => {
cb(err);
});
}
function _copyObject(sourceBucket, sourceObject, targetBucket, targetObject,
encrypt, cb) {
const params = {
Bucket: targetBucket,
CopySource: `/${sourceBucket}/${sourceObject}`,
Key: targetObject,
};
if (encrypt) {
Object.assign(params, {
ServerSideEncryption: 'AES256',
});
}
s3.copyObject(params, err => {
cb(err);
});
}
function _initiateMultipartUpload(bucketName, objectName, encrypt, cb) {
const params = {
Bucket: bucketName,
Key: objectName,
};
if (encrypt) {
Object.assign(params, {
ServerSideEncryption: 'AES256',
});
}
s3.createMultipartUpload(params, err => {
cb(err);
});
}
describe('KMIP backed server-side encryption', () => {
let bucketName;
let objectName;
const encryptionErrorMessage
= 'The encryption method specified is not supported';
beforeEach(() => {
bucketName = uuid4();
objectName = uuid4();
});
it('should create an encrypted bucket', done => {
_createBucket(bucketName, true, err => {
assert.equal(err, null, 'Expected success, ' +
`got error ${JSON.stringify(err)}`);
done();
});
});
it('should create an encrypted bucket and upload an object', done => {
async.waterfall([
next => _createBucket(bucketName, true, err => next(err)),
next => _putObject(bucketName, objectName, false, err => next(err)),
], err => {
assert.equal(err, null, 'Expected success, ' +
`got error ${JSON.stringify(err)}`);
done();
});
});
it('should allow object PUT with SSE header in encrypted bucket', done => {
async.waterfall([
next => _createBucket(bucketName, true, err => next(err)),
next => _putObject(bucketName, objectName, true, err => next(err)),
], err => {
assert.equal(err, null, 'Expected success, ' +
`got error ${JSON.stringify(err)}`);
done();
});
});
it('should not allow object PUT with SSE header ' +
'in bucket with no SSE', done => {
async.waterfall([
next => _createBucket(bucketName, false, err => {
assert.equal(err, null, 'Expected success, ' +
`got error ${JSON.stringify(err)}`);
return next();
}),
next => _putObject(bucketName, objectName, true, err => next(err)),
], err => {
assert.strictEqual(err.statusCode, 400);
assert.strictEqual(err.message, encryptionErrorMessage);
done();
});
});
it('should allow object copy with SSE header in encrypted bucket', done => {
async.waterfall([
next => _createBucket(bucketName, false, err => next(err)),
next => _putObject(bucketName, objectName, false, err => next(err)),
next => _createBucket(`${bucketName}2`, true, err => next(err)),
next => _copyObject(bucketName, objectName, `${bucketName}2`,
`${objectName}2`, true, err => next(err)),
], err => {
assert.equal(err, null, 'Expected success, ' +
`got error ${JSON.stringify(err)}`);
done();
});
});
it('should not allow object copy with SSE header ' +
'in bucket with no SSE', done => {
async.waterfall([
next => _createBucket(bucketName, false, err => {
assert.equal(err, null, 'Expected success, ' +
`got error ${JSON.stringify(err)}`);
return next();
}),
next => _putObject(bucketName, objectName, false, err => {
assert.equal(err, null, 'Expected success, ' +
`got error ${JSON.stringify(err)}`);
return next();
}),
next => _createBucket(`${bucketName}2`, false, err => {
assert.equal(err, null, 'Expected success, ' +
`got error ${JSON.stringify(err)}`);
return next();
}),
next => _copyObject(bucketName, objectName, `${bucketName}2`,
`${objectName}2`, true, err => next(err)),
], err => {
assert.strictEqual(err.statusCode, 400);
assert.strictEqual(err.message, encryptionErrorMessage);
done();
});
});
it('should allow creating mpu with SSE header ' +
'in encrypted bucket', done => {
async.waterfall([
next => _createBucket(bucketName, true, err => next(err)),
next => _initiateMultipartUpload(bucketName, objectName,
true, err => next(err)),
], err => {
assert.equal(err, null, 'Expected success, ' +
`got error ${JSON.stringify(err)}`);
done();
});
});
it('should not allow mpu creation with SSE header ' +
'in bucket with no SSE', done => {
async.waterfall([
next => _createBucket(bucketName, false, err => {
assert.equal(err, null, 'Expected success, ' +
`got error ${JSON.stringify(err)}`);
return next();
}),
next => _initiateMultipartUpload(bucketName, objectName,
true, err => next(err)),
], err => {
assert.strictEqual(err.statusCode, 400);
assert.strictEqual(err.message, encryptionErrorMessage);
done();
});
});
});