Compare commits
2 Commits
developmen
...
improvemen
Author | SHA1 | Date |
---|---|---|
naren-scality | 18cc1ae62f | |
Rahul Padigela | f114c1be59 |
|
@ -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',
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue