Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,9 @@
},
"kmip": {
"providerName": "thales"
},
"apiBodySizeLimits": {
"multiObjectDelete": 2097152,
Comment thread
SylvainSenechal marked this conversation as resolved.
"bucketPutPolicy": 20480
}
}
9 changes: 6 additions & 3 deletions constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,15 @@ const constants = {
oneMegaBytes: 1024 * 1024,
halfMegaBytes: 512 * 1024,

// Some apis may need a custom body length limit :
apisLengthLimits: {
// Some apis may need a custom body length limit
// Caution : Users will be able to override these values in config.json
defaultApiBodySizeLimits: {
// Multi Objects Delete request can be large : up to 1000 keys of 1024 bytes is
// already 1mb, with the other fields it could reach 2mb
'multiObjectDelete': 2 * 1024 * 1024,
// AWS sets the maximum size for bucket policies to 20 KB
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/add-bucket-policy.html
'bucketPutPolicy': 20 * 1024,
},

// hex digest of sha256 hash of empty string:
Expand Down Expand Up @@ -266,5 +270,4 @@ const constants = {
onlyOwnerAllowed: ['bucketDeletePolicy', 'bucketGetPolicy', 'bucketPutPolicy'],
};


module.exports = constants;
19 changes: 19 additions & 0 deletions lib/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -1737,6 +1737,25 @@ class Config extends EventEmitter {
}

this.supportedLifecycleRules = parseSupportedLifecycleRules(config.supportedLifecycleRules);

this.apiBodySizeLimits = { ...constants.defaultApiBodySizeLimits };
Comment thread
francoisferrand marked this conversation as resolved.
if (config.apiBodySizeLimits) {
assert(typeof config.apiBodySizeLimits === 'object' &&
!Array.isArray(config.apiBodySizeLimits),
'bad config: apiBodySizeLimits must be an object');

for (const [apiKey, limit] of Object.entries(config.apiBodySizeLimits)) {
// Only allow modifications of predefined APIs from constants
assert(Object.hasOwn(constants.defaultApiBodySizeLimits, apiKey),
`bad config: apiBodySizeLimits for "${apiKey}" cannot be configured. ` +
`Valid APIs are: ${Object.keys(constants.defaultApiBodySizeLimits).join(', ')}`);

assert(Number.isInteger(limit) && limit > 0,
`bad config: apiBodySizeLimits for "${apiKey}" must be a positive integer`);
this.apiBodySizeLimits[apiKey] = limit;
}
}

return config;
}

Expand Down
17 changes: 9 additions & 8 deletions lib/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const { tagConditionKeyAuth } = require('./apiUtils/authorization/tagConditionKe
const { isRequesterASessionUser } = require('./apiUtils/authorization/permissionChecks');
const checkHttpHeadersSize = require('./apiUtils/object/checkHttpHeadersSize');
const constants = require('../../constants');
const { config } = require('../Config.js');

const monitoringMap = policies.actionMaps.actionMonitoringMapS3;

Expand Down Expand Up @@ -219,15 +220,15 @@ const api = {
// issue 100 Continue to the client
writeContinue(request, response);

const defaultMaxPostLength = request.method === 'POST' ?
const defaultMaxBodyLength = request.method === 'POST' ?
constants.oneMegaBytes : constants.halfMegaBytes;
const MAX_POST_LENGTH = constants.apisLengthLimits[apiMethod] || defaultMaxPostLength;
const MAX_BODY_LENGTH = config.apiBodySizeLimits[apiMethod] || defaultMaxBodyLength;
const post = [];
let postLength = 0;
let bodyLength = 0;
request.on('data', chunk => {
postLength += chunk.length;
bodyLength += chunk.length;
// Sanity check on post length
if (postLength <= MAX_POST_LENGTH) {
if (bodyLength <= MAX_BODY_LENGTH) {
post.push(chunk);
}
});
Expand All @@ -240,13 +241,13 @@ const api = {
});

request.on('end', () => {
if (postLength > MAX_POST_LENGTH) {
if (bodyLength > MAX_BODY_LENGTH) {
log.error('body length is too long for request type',
{ postLength });
{ bodyLength });
return next(errors.InvalidRequest);
}
// Convert array of post buffers into one string
request.post = Buffer.concat(post, postLength).toString();
request.post = Buffer.concat(post, bodyLength).toString();
return next(null, userInfo, authorizationResults, streamingV4Params, infos);
});
return undefined;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenko/cloudserver",
"version": "9.0.22",
"version": "9.1.0-preview.1",
"description": "Zenko CloudServer, an open-source Node.js implementation of a server handling the Amazon S3 protocol",
"main": "index.js",
"engines": {
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {
const {
LOCATION_NAME_DMF,
} = require('../constants');
const constants = require('../../constants');

const { ValidLifecycleRules: supportedLifecycleRules } = require('arsenal').models;

Expand Down Expand Up @@ -889,4 +890,47 @@ describe('Config', () => {
assert.strictEqual(config.instanceId.length, 6);
});
});

describe('apisLengthLimits configuration', () => {
let sandbox;
let readFileStub;

beforeEach(() => {
sandbox = sinon.createSandbox();
readFileStub = sandbox.stub(fs, 'readFileSync');
readFileStub.callThrough();
});

afterEach(() => {
sandbox.restore();
});

it('should use default API and overwrite when config is provided', () => {
const multiObjectDeleteSize = 42;
const modifiedConfig = {
...defaultConfig,
apiBodySizeLimits: { 'multiObjectDelete': multiObjectDeleteSize },
};
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(modifiedConfig));
const config = new ConfigObject();

assert.deepStrictEqual(config.apiBodySizeLimits, {
'multiObjectDelete': multiObjectDeleteSize, // Configured: overwrites default
'bucketPutPolicy': constants.defaultApiBodySizeLimits['bucketPutPolicy'], // Not configured: default
});
});

it('should fail if a user tries to modify a non-existent API', () => {
const modifiedConfig = {
...defaultConfig,
apiBodySizeLimits: { 'anApiNotSetInConstants.js': 42 },
};
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(modifiedConfig));

assert.throws(
() => new ConfigObject(),
/bad config: apiBodySizeLimits for "anApiNotSetInConstants.js" cannot be configured/
);
});
});
});
Loading