Compare commits

...

14 Commits

9 changed files with 408 additions and 7 deletions

View File

@ -93,7 +93,6 @@ const constants = {
'logging', 'logging',
'metrics', 'metrics',
'notification', 'notification',
'object-lock',
'policyStatus', 'policyStatus',
'publicAccessBlock', 'publicAccessBlock',
'requestPayment', 'requestPayment',

View File

@ -12,6 +12,7 @@ const bucketGetVersioning = require('./bucketGetVersioning');
const bucketGetWebsite = require('./bucketGetWebsite'); const bucketGetWebsite = require('./bucketGetWebsite');
const bucketGetLocation = require('./bucketGetLocation'); const bucketGetLocation = require('./bucketGetLocation');
const bucketGetLifecycle = require('./bucketGetLifecycle'); const bucketGetLifecycle = require('./bucketGetLifecycle');
const bucketGetObjectLock = require('./bucketGetObjectLock');
const bucketGetPolicy = require('./bucketGetPolicy'); const bucketGetPolicy = require('./bucketGetPolicy');
const bucketHead = require('./bucketHead'); const bucketHead = require('./bucketHead');
const { bucketPut } = require('./bucketPut'); const { bucketPut } = require('./bucketPut');
@ -22,6 +23,7 @@ const bucketPutWebsite = require('./bucketPutWebsite');
const bucketPutReplication = require('./bucketPutReplication'); const bucketPutReplication = require('./bucketPutReplication');
const bucketPutLifecycle = require('./bucketPutLifecycle'); const bucketPutLifecycle = require('./bucketPutLifecycle');
const bucketPutPolicy = require('./bucketPutPolicy'); const bucketPutPolicy = require('./bucketPutPolicy');
const bucketPutObjectLock = require('./bucketPutObjectLock');
const bucketGetReplication = require('./bucketGetReplication'); const bucketGetReplication = require('./bucketGetReplication');
const bucketDeleteReplication = require('./bucketDeleteReplication'); const bucketDeleteReplication = require('./bucketDeleteReplication');
const corsPreflight = require('./corsPreflight'); const corsPreflight = require('./corsPreflight');
@ -172,6 +174,7 @@ const api = {
bucketGetVersioning, bucketGetVersioning,
bucketGetWebsite, bucketGetWebsite,
bucketGetLocation, bucketGetLocation,
bucketGetObjectLock,
bucketHead, bucketHead,
bucketPut, bucketPut,
bucketPutACL, bucketPutACL,
@ -187,6 +190,7 @@ const api = {
bucketPutPolicy, bucketPutPolicy,
bucketGetPolicy, bucketGetPolicy,
bucketDeletePolicy, bucketDeletePolicy,
bucketPutObjectLock,
corsPreflight, corsPreflight,
completeMultipartUpload, completeMultipartUpload,
initiateMultipartUpload, initiateMultipartUpload,

View File

@ -178,7 +178,7 @@ function createBucket(authInfo, bucketName, headers,
// when a bucket is created with object lock // when a bucket is created with object lock
const versioningConfiguration = { const versioningConfiguration = {
Status: 'Enabled', Status: 'Enabled',
MfaDelete: 'Disabled', MFADelete: 'Disabled',
}; };
bucket.setVersioningConfiguration(versioningConfiguration); bucket.setVersioningConfiguration(versioningConfiguration);
} }

View File

@ -0,0 +1,68 @@
const { errors } = require('arsenal');
const { metadataValidateBucket } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const ObjectLockConfiguration =
require('arsenal').models.ObjectLockConfiguration;
// Format of the xml response:
/**
* <ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
* <ObjectLockEnabled>string</ObjectLockEnabled>
* <Rule>
* <DefaultRetention>
* <Mode>string</Mode>
* <Days>integer</Days>
* <Years>integer</Years>
* </DefaultRetention>
* </Rule>
* </ObjectLockConfiguration>
*/
/**
* bucketGetObjectLock - Return object lock configuration for the bucket
* @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
* @param {object} request - http request object
* @param {object} log - Werelogs logger
* @param {function} callback - callback to respond to http request
* @return {undefined}
*/
function bucketGetObjectLock(authInfo, request, log, callback) {
log.debug('processing request', { method: 'bucketGetObjectLock' });
const { bucketName, headers, method } = request;
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketGetObjectLock',
};
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) {
log.debug('error processing request', {
error: err,
method: 'bucketGetObjectLock',
});
return callback(err, null, corsHeaders);
}
const objectLockEnabled = bucket._objectLockEnabled;
const objectLockConfig = bucket._objectLockConfiguration ?
bucket._objectLockConfiguration :
{};
if (!objectLockEnabled) {
log.debug('error processing request', {
error: errors.ObjectLockConfigurationNotFoundError,
method: 'bucketGetObjectLock',
});
return callback(errors.ObjectLockConfigurationNotFoundError, null,
corsHeaders);
}
const xml = ObjectLockConfiguration.getConfigXML(objectLockConfig);
pushMetric('getBucketObjectLock', log, {
authInfo,
bucket: bucketName,
});
return callback(null, xml, corsHeaders);
});
}
module.exports = bucketGetObjectLock;

View File

@ -0,0 +1,82 @@
const { waterfall } = require('async');
const arsenal = require('arsenal');
const errors = arsenal.errors;
const ObjectLockConfiguration = arsenal.models.ObjectLockConfiguration;
const parseXML = require('../utilities/parseXML');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const metadata = require('../metadata/wrapper');
const { metadataValidateBucket } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
/**
* Bucket Put Object Lock - Create or update bucket object lock configuration
* @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
* @param {object} request - http request object
* @param {object} log - Werelogs logger
* @param {function} callback - callback to server
* @return {undefined}
*/
function bucketPutObjectLock(authInfo, request, log, callback) {
log.debug('processing request', { method: 'bucketPutObjectLock' });
const bucketName = request.bucketName;
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketPutObjectLock',
};
return waterfall([
next => parseXML(request.post, log, next),
(parsedXml, next) => {
const lockConfigClass = new ObjectLockConfiguration(parsedXml);
// if there was an error getting object lock configuration,
// returned configObj will contain 'error' key
process.nextTick(() => {
const configObj = lockConfigClass.
getValidatedObjectLockConfiguration();
return next(configObj.error || null, configObj);
});
},
(objectLockConfig, next) => metadataValidateBucket(metadataValParams,
log, (err, bucket) => {
if (err) {
return next(err, bucket);
}
return next(null, bucket, objectLockConfig);
}),
(bucket, objectLockConfig, next) => {
const isObjectLockEnabled = bucket.isObjectLockEnabled();
process.nextTick(() => {
if (!isObjectLockEnabled) {
return next(errors.InvalidBucketState.customizeDescription(
'Object Lock configuration cannot be enabled on ' +
'existing buckets'), bucket);
}
return next(null, bucket, objectLockConfig);
});
},
(bucket, objectLockConfig, next) => {
bucket.setObjectLockConfiguration(objectLockConfig);
metadata.updateBucket(bucket.getName(), bucket, log, err =>
next(err, bucket));
},
], (err, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket);
if (err) {
log.trace('error processing request', { error: err,
method: 'bucketPutObjectLock' });
return callback(err, corsHeaders);
}
pushMetric('putBucketObjectLock', log, {
authInfo,
bucket: bucketName,
});
return callback(null, corsHeaders);
});
}
module.exports = bucketPutObjectLock;

View File

@ -80,3 +80,17 @@ this._lifecycleConfiguration = lifecycleConfiguration || null;
### Usage ### Usage
Used to store the bucket lifecycle configuration info Used to store the bucket lifecycle configuration info
## Model version 7
### Properties Added
```javascript
this._objectLockEnabled = objectLockEnabled || false;
this._objectLockConfiguration = objectLockConfiguration || null;
```
### Usage
Used to determine whether object lock capabilities are enabled on a bucket and
to store the object lock configuration of the bucket

View File

@ -0,0 +1,123 @@
const assert = require('assert');
const { bucketPut } = require('../../../lib/api/bucketPut');
const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../helpers');
const bucketGetObjectLock = require('../../../lib/api/bucketGetObjectLock');
const bucketPutObjectLock = require('../../../lib/api/bucketPutObjectLock');
const ObjectLockConfiguration =
require('arsenal').models.ObjectLockConfiguration;
const log = new DummyRequestLogger();
const authInfo = makeAuthInfo('accessKey1');
const bucketName = 'bucketname';
const bucketPutReq = {
bucketName,
headers: {
host: `${bucketName}.s3.amazonaws.com`,
},
url: '/',
};
const testBucketPutReqWithObjLock = {
bucketName,
headers: {
host: `${bucketName}.s3.amazonaws.com`,
'x-amz-bucket-object-lock-enabled': true
},
url: '/',
};
function getObjectLockConfigRequest(bucketName, xml) {
const request = {
bucketName,
headers: {
host: `${bucketName}.s3.amazonaws.com`,
'x-amz-bucket-object-lock-enabled': true,
},
url: '/?object-lock',
};
if (xml) {
request.post = xml;
}
return request;
}
function getObjectLockXml(mode, type, time) {
const xml = {
link: 'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">',
objLockConfigOpen: '<ObjectLockConfiguration ',
objLockConfigClose: '</ObjectLockConfiguration>',
objectLockEnabled: '<ObjectLockEnabled>Enabled</ObjectLockEnabled>',
ruleOpen: '<Rule><DefaultRetention>',
ruleClose: '</DefaultRetention></Rule>',
}
const retentionMode = `<Mode>${mode}</Mode>`;
const retentionTime = `<${type}>${time}</${type}>`;
let xmlStr = '<?xml version="1.0" encoding="UTF-8"?>' +
xml.objLockConfigOpen +
xml.link +
xml.objectLockEnabled;
// object lock is enabled and object lock configuration is set
if (arguments.length === 3) {
xmlStr += xml.ruleOpen +
retentionMode +
retentionTime +
xml.ruleClose;
}
xmlStr += xml.objLockConfigClose;
return xmlStr;
};
describe('getBucketObjectLock API', () => {
before(done => bucketPut(authInfo, bucketPutReq, log, done));
after(cleanup);
it('should return ObjectLockConfigurationNotFoundError error if ' +
'object lock is not enabled on the bucket', done => {
const objectLockRequest = getObjectLockConfigRequest(bucketName);
bucketGetObjectLock(authInfo, objectLockRequest, log, err => {
assert.strictEqual(err.ObjectLockConfigurationNotFoundError, true);
done();
});
});
});
describe('bucketGetObjectLock API', () => {
before(cleanup);
beforeEach(done => bucketPut(authInfo, testBucketPutReqWithObjLock, log, done));
afterEach(cleanup);
it('should return config without \'rule\' if object lock configuration ' +
'not set on the bucket', done => {
const objectLockRequest = getObjectLockConfigRequest(bucketName);
bucketGetObjectLock(authInfo, objectLockRequest, log, (err, res) => {
assert.ifError(err);
const expectedXml = getObjectLockXml();
assert.equal(expectedXml, res);
done();
});
});
describe('after object lock configuration has been put', () => {
beforeEach(done => {
const xml = getObjectLockXml('COMPLIANCE', 'Days', 90);
const objectLockRequest = getObjectLockConfigRequest(bucketName, xml);
bucketPutObjectLock(authInfo, objectLockRequest, log, err => {
assert.ifError(err);
done();
});
});
it('should return object lock configuration XML', done => {
const getRequest = getObjectLockConfigRequest(bucketName);
bucketGetObjectLock(authInfo, getRequest, log, (err, res) => {
assert.ifError(err);
const expectedXml = getObjectLockXml('COMPLIANCE', 'Days', 90);
assert.strictEqual(expectedXml, res);
done();
});
});
});
});

View File

@ -0,0 +1,84 @@
const assert = require('assert');
const { bucketPut } = require('../../../lib/api/bucketPut');
const bucketPutObjectLock = require('../../../lib/api/bucketPutObjectLock');
const { cleanup,
DummyRequestLogger,
makeAuthInfo,
} = require('../helpers');
const metadata = require('../../../lib/metadata/wrapper');
const log = new DummyRequestLogger();
const authInfo = makeAuthInfo('accessKey1');
const bucketName = 'bucketputobjectlockbucket';
const bucketPutRequest = {
bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/',
};
const objectLockXml = '<ObjectLockConfiguration ' +
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
'<ObjectLockEnabled>Enabled</ObjectLockEnabled>' +
'<Rule><DefaultRetention>' +
'<Mode>GOVERNANCE</Mode>' +
'<Days>1</Days>' +
'</DefaultRetention></Rule>' +
'</ObjectLockConfiguration>';
const putObjLockRequest = {
bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
post: objectLockXml,
};
const expectedObjectLockConfig = {
rule: {
mode: 'GOVERNANCE',
days: 1,
},
};
describe('putBucketObjectLock API', () => {
before(() => cleanup());
describe('without Object Lock enabled on bucket', () => {
beforeEach(done => bucketPut(authInfo, bucketPutRequest, log, done));
afterEach(() => cleanup());
it('should return InvalidBucketState error', done => {
bucketPutObjectLock(authInfo, putObjLockRequest, log, err => {
assert.strictEqual(err.InvalidBucketState, true);
done();
});
});
});
describe('with Object Lock enabled on bucket', () => {
const bucketObjLockRequest = Object.assign({}, bucketPutRequest,
{ headers: { 'x-amz-bucket-object-lock-enabled': true } });
beforeEach(done => bucketPut(authInfo, bucketObjLockRequest, log, done));
afterEach(() => cleanup());
it('should update a bucket\'s metadata with object lock config', done => {
bucketPutObjectLock(authInfo, putObjLockRequest, log, err => {
if (err) {
process.stdout.write(`Err putting lifecycle config ${err}`);
return done(err);
}
return metadata.getBucket(bucketName, log, (err, bucket) => {
if (err) {
process.stdout.write(`Err retrieving bucket MD ${err}`);
return done(err);
}
const bucketObjectLockConfig = bucket.
getObjectLockConfiguration();
assert.deepStrictEqual(
bucketObjectLockConfig, expectedObjectLockConfig);
return done();
});
});
});
});
});

View File

@ -210,9 +210,21 @@ arraybuffer.slice@0.0.6:
resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca"
integrity sha1-8zshWfBTKj8xB6JywMz70a0peco= integrity sha1-8zshWfBTKj8xB6JywMz70a0peco=
<<<<<<< HEAD
<<<<<<< HEAD
"arsenal@github:scality/Arsenal#842ded1":
version "7.5.0"
resolved "https://codeload.github.com/scality/Arsenal/tar.gz/842ded13094bf52c06844e8d870e1f9e8672143c"
=======
"arsenal@github:scality/Arsenal#c5e21e0":
version "7.5.0"
resolved "https://codeload.github.com/scality/Arsenal/tar.gz/c5e21e07191df97a136ce57e5620390a4e79ced3"
>>>>>>> 3d386bdeaffb7f0a4ebf2873dfa2185df6a04b30
=======
"arsenal@github:scality/Arsenal#f988270": "arsenal@github:scality/Arsenal#f988270":
version "7.5.0" version "7.5.0"
resolved "https://codeload.github.com/scality/Arsenal/tar.gz/f988270a0c557f0d663cae5a6d3dc5b840ccc4ff" resolved "https://codeload.github.com/scality/Arsenal/tar.gz/f988270a0c557f0d663cae5a6d3dc5b840ccc4ff"
>>>>>>> development/7.7
dependencies: dependencies:
"@hapi/joi" "^15.1.0" "@hapi/joi" "^15.1.0"
JSONStream "^1.0.0" JSONStream "^1.0.0"
@ -363,10 +375,18 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
<<<<<<< HEAD
<<<<<<< HEAD
aws-sdk@2.178.0:
version "2.178.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.178.0.tgz#e8257adde039cad2a1ac0abd278d172b6c39a9c7"
integrity sha1-6CV63eA5ytKhrAq9J40XK2w5qcc=
=======
aws-sdk@2.363.0: aws-sdk@2.363.0:
version "2.363.0" version "2.363.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.363.0.tgz#6d366a78d5b008fd927d6ff24815d39d78b54778" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.363.0.tgz#6d366a78d5b008fd927d6ff24815d39d78b54778"
integrity sha512-kQOfjzCEllH45OFN0z3fvhpSWDFWu19715A7TztHx6IEWKwwIEyd3b2XhTZtQLJrI1Giv7iGALwH46gybH9HJw== integrity sha512-kQOfjzCEllH45OFN0z3fvhpSWDFWu19715A7TztHx6IEWKwwIEyd3b2XhTZtQLJrI1Giv7iGALwH46gybH9HJw==
>>>>>>> development/7.7
dependencies: dependencies:
buffer "4.9.1" buffer "4.9.1"
events "1.1.1" events "1.1.1"
@ -379,9 +399,21 @@ aws-sdk@2.363.0:
xml2js "0.4.19" xml2js "0.4.19"
aws-sdk@^2.2.23: aws-sdk@^2.2.23:
<<<<<<< HEAD
version "2.669.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.669.0.tgz#7e8e7985120102da6bdbf40a18d8b9d692dea1ba"
integrity sha512-kuVcSRpDzvkgmeSmMX6Q32eTOb8UeihhUdavMrvUOP6fzSU19cNWS9HAIkYOi/jrEDK85cCZxXjxqE3JGZIGcw==
=======
aws-sdk@^2.178.0, aws-sdk@^2.2.23:
version "2.670.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.670.0.tgz#d54d18b9245df7b89bea96102e5bdebd99587701"
integrity sha512-hGRnZtp1wDUh6hZRBHO0Ki7thx/xbRlIEiTKlWes+f/0E1Nhm3KpelsBZ3L/Q6y1ragwkQd4Q720AmWEqemLyA==
>>>>>>> 3d386bdeaffb7f0a4ebf2873dfa2185df6a04b30
=======
version "2.671.0" version "2.671.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.671.0.tgz#2c6e164a0f540d6fc428c123f2994ac081663ff5" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.671.0.tgz#2c6e164a0f540d6fc428c123f2994ac081663ff5"
integrity sha512-i83+/TIOLlhAxvV2xVLz5+XGtNqJgQJwP/e8J49rzDkyMV6OE2FgxU8utujGrComrSJFpITqMFqug+ZfdHoLIQ== integrity sha512-i83+/TIOLlhAxvV2xVLz5+XGtNqJgQJwP/e8J49rzDkyMV6OE2FgxU8utujGrComrSJFpITqMFqug+ZfdHoLIQ==
>>>>>>> development/7.7
dependencies: dependencies:
buffer "4.9.1" buffer "4.9.1"
events "1.1.1" events "1.1.1"
@ -3705,11 +3737,6 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
uuid@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
integrity sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==
uuid@3.3.2: uuid@3.3.2:
version "3.3.2" version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"