Skip to content

Commit 7472985

Browse files
Merge branch 'improvement/CLDSRV-650' into w/9.1/improvement/CLDSRV-650
2 parents bc7e924 + 6d0c57c commit 7472985

7 files changed

Lines changed: 450 additions & 25 deletions

File tree

lib/Config.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -872,14 +872,26 @@ class Config extends EventEmitter {
872872
}
873873
}
874874

875-
this.port = 8000;
876875
if (config.port !== undefined) {
877876
assert(Number.isInteger(config.port) && config.port > 0,
878877
'bad config: port must be a positive integer');
879-
this.port = config.port;
880878
}
881879

880+
if (config.internalPort !== undefined) {
881+
assert(Number.isInteger(config.internalPort) && config.internalPort > 0,
882+
'bad config: internalPort must be a positive integer');
883+
}
884+
885+
this.port = config.port;
882886
this.listenOn = this._parseEndpoints(config.listenOn, 'listenOn');
887+
this.internalPort = config.internalPort;
888+
this.internalListenOn = this._parseEndpoints(config.internalListenOn, 'internalListenOn');
889+
890+
if (this.listenOn.length || (!this.internalPort && !this.internalListenOn.length)) {
891+
// default to 8000 unless running in "internal" API mode only (i.e. internalPort/ListenOn)
892+
// i.e. when either no port config is specified, or only listenOn is specified.
893+
this.port ||= 8000;
894+
}
883895

884896
this.metricsPort = 8002;
885897
if (config.metricsPort !== undefined) {

lib/api/apiUtils/authorization/permissionChecks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ function processBucketPolicy(requestType, bucket, canonicalID, arn, bucketOwner,
358358
request, aclPermission, results, actionImplicitDenies) {
359359
const bucketPolicy = bucket.getBucketPolicy();
360360
let processedResult = results[requestType];
361-
if (!bucketPolicy) {
361+
if (!bucketPolicy || request?.bypassUserBucketPolicies) {
362362
processedResult = actionImplicitDenies[requestType] === false && aclPermission;
363363
} else {
364364
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType, canonicalID, arn,

lib/server.js

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ const {
2626

2727
const HttpAgent = require('agentkeepalive');
2828
const QuotaService = require('./utilization/instance');
29-
const routes = arsenal.s3routes.routes;
3029
const { parseLC, MultipleBackendGateway } = arsenal.storage.data;
3130
const websiteEndpoints = _config.websiteEndpoints;
3231
let client = dataWrapper.client;
@@ -62,9 +61,11 @@ class S3Server {
6261
/**
6362
* This represents our S3 connector.
6463
* @constructor
64+
* @param {Object} config - Configuration object
6565
* @param {Worker} [worker=null] - Track the worker when using cluster
6666
*/
67-
constructor(worker) {
67+
constructor(config, worker) {
68+
this.config = config;
6869
this.worker = worker;
6970
this.cluster = true;
7071
this.servers = [];
@@ -93,11 +94,29 @@ class S3Server {
9394
this.started = false;
9495
}
9596

97+
/**
98+
* Route requests on 'internal s3' port
99+
* Same as routeRequest, but ignoring user's bucket policy. This should be used only for
100+
* backbeat and other internal/system processes that must not be affected by user's bucket
101+
* policy.
102+
* Note that this is not a temporary measure: eventually this should be improved to better
103+
* identify and isolate internal/system calls vs user calls, so that "system" bucket policies
104+
* may be applied to system requests, and the existing "user" bucket policies apply only to
105+
* user requests.
106+
* @param {http.IncomingMessage} req - http request object
107+
* @param {http.ServerResponse} res - http response object
108+
* @returns {undefined}
109+
*/
110+
internalRouteRequest(req, res) {
111+
req.bypassUserBucketPolicies = true; // eslint-disable-line no-param-reassign
112+
return this.routeRequest(req, res);
113+
}
114+
96115
/**
97116
* Route requests on 's3' port
98117
* @param {http.IncomingMessage} req - http request object
99118
* @param {http.ServerResponse} res - http response object
100-
* @returns {void}
119+
* @returns {undefined}
101120
*/
102121
routeRequest(req, res) {
103122
monitoringClient.httpActiveRequests.inc();
@@ -144,21 +163,21 @@ class S3Server {
144163
dataRetrievalParams: {
145164
client,
146165
implName,
147-
config: _config,
166+
config: this.config,
148167
kms,
149168
metadata,
150169
locStorageCheckFn: locationStorageCheck,
151170
vault,
152171
},
153172
};
154-
routes(req, res, params, logger, _config);
173+
arsenal.s3routes.routes(req, res, params, logger, this.config);
155174
}
156175

157176
/**
158177
* Route requests on 'admin' port
159178
* @param {http.IncomingMessage} req - http request object
160179
* @param {http.ServerResponse} res - http response object
161-
* @returns {void}
180+
* @returns {undefined}
162181
*/
163182
routeAdminRequest(req, res) {
164183
// use proxied hostname if needed
@@ -205,16 +224,16 @@ class S3Server {
205224
* @param {http.RequestListener} listener - Callback to handle requests
206225
* @param {number} port - Port to listen on
207226
* @param {string | undefined} ipAddress - IpAddress to listen on
208-
* @returns {void}
227+
* @returns {undefined}
209228
*/
210229
_startServer(listener, port, ipAddress) {
211230
// Todo: http.globalAgent.maxSockets, http.globalAgent.maxFreeSockets
212231
let server;
213-
if (_config.https) {
232+
if (this.config.https) {
214233
server = https.createServer({
215-
cert: _config.https.cert,
216-
key: _config.https.key,
217-
ca: _config.https.ca,
234+
cert: this.config.https.cert,
235+
key: this.config.https.key,
236+
ca: this.config.https.ca,
218237
ciphers: arsenal.https.ciphers.ciphers,
219238
dhparam: arsenal.https.dhparam.dhparam,
220239
rejectUnauthorized: true,
@@ -305,21 +324,30 @@ class S3Server {
305324
}
306325

307326
// Start API server(s)
308-
if (_config.listenOn.length > 0) {
309-
_config.listenOn.forEach(item => {
327+
if (this.config.listenOn.length > 0) {
328+
this.config.listenOn.forEach(item => {
310329
this._startServer(this.routeRequest, item.port, item.ip);
311330
});
312-
} else {
313-
this._startServer(this.routeRequest, _config.port);
331+
} else if (this.config.port) {
332+
this._startServer(this.routeRequest, this.config.port);
333+
}
334+
335+
// Start internal API server(s)
336+
if (this.config.internalListenOn.length > 0) {
337+
this.config.internalListenOn.forEach(item => {
338+
this._startServer(this.internalRouteRequest, item.port, item.ip);
339+
});
340+
} else if (this.config.internalPort) {
341+
this._startServer(this.internalRouteRequest, this.config.internalPort);
314342
}
315343

316344
// Start metrics server(s)
317-
if (_config.metricsListenOn.length > 0) {
318-
_config.metricsListenOn.forEach(item => {
345+
if (this.config.metricsListenOn.length > 0) {
346+
this.config.metricsListenOn.forEach(item => {
319347
this._startServer(this.routeAdminRequest, item.port, item.ip);
320348
});
321349
} else {
322-
this._startServer(this.routeAdminRequest, _config.metricsPort);
350+
this._startServer(this.routeAdminRequest, this.config.metricsPort);
323351
}
324352

325353
// Start quota service health checks
@@ -353,7 +381,7 @@ function main() {
353381
this.cluster = workers > 1;
354382
if (!this.cluster) {
355383
process.env.REPORT_TOKEN = _config.reportToken;
356-
const server = new S3Server();
384+
const server = new S3Server(_config);
357385
server.initiateStartup(logger.newRequestLogger());
358386
}
359387
if (this.cluster && cluster.isMaster) {
@@ -399,10 +427,11 @@ function main() {
399427
});
400428
}
401429
if (this.cluster && cluster.isWorker) {
402-
const server = new S3Server(cluster.worker);
430+
const server = new S3Server(_config, cluster.worker);
403431
server.initiateStartup(logger.newRequestLogger());
404432
}
405433
monitoringClient.collectDefaultMetrics({ timeout: 5000 });
406434
}
407435

408436
module.exports = main;
437+
module.exports.S3Server = S3Server;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zenko/cloudserver",
3-
"version": "9.0.11",
3+
"version": "9.0.12",
44
"description": "Zenko CloudServer, an open-source Node.js implementation of a server handling the Amazon S3 protocol",
55
"main": "index.js",
66
"engines": {

tests/unit/Config.js

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const {
1212
const { ValidLifecycleRules: supportedLifecycleRules } = require('arsenal').models;
1313

1414
describe('Config', () => {
15+
const defaultConfig = JSON.parse(fs.readFileSync('config.json'), { encoding: 'utf-8' });
16+
1517
const envToRestore = [];
1618
const setEnv = (key, value) => {
1719
if (key in process.env) {
@@ -446,7 +448,6 @@ describe('Config', () => {
446448
});
447449

448450
describe('multiObjectDeleteEnableOptimizations', () => {
449-
const defaultConfig = JSON.parse(fs.readFileSync('config.json'), { encoding: 'utf-8' });
450451
let sandbox;
451452
let readFileStub;
452453

@@ -679,4 +680,134 @@ describe('Config', () => {
679680
assert.deepStrictEqual(parsedRules, rules);
680681
});
681682
});
683+
684+
describe('port and internalPort configuration', () => {
685+
// same as `defaultConfig` (i.e. config.json) but without the port settings
686+
const baseConfig = (() => {
687+
const config = { ...defaultConfig };
688+
delete config.port;
689+
delete config.listenOn;
690+
delete config.internalPort;
691+
delete config.internalListenOn;
692+
delete config.metricsPort;
693+
delete config.metricsListenOn;
694+
return config;
695+
})();
696+
697+
let sandbox;
698+
let readFileStub;
699+
700+
beforeEach(() => {
701+
sandbox = sinon.createSandbox();
702+
readFileStub = sandbox.stub(fs, 'readFileSync');
703+
readFileStub.callThrough();
704+
});
705+
706+
afterEach(() => {
707+
sandbox.restore();
708+
});
709+
710+
it('should throw an error for negative port number', () => {
711+
const testConfig = { ...baseConfig, port: -1 };
712+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(testConfig));
713+
714+
assert.throws(() => new ConfigObject(), /bad config: port must be a positive integer/);
715+
});
716+
717+
it('should throw an error for non-integer port number', () => {
718+
const testConfig = { ...baseConfig, port: 8000.5 };
719+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(testConfig));
720+
721+
assert.throws(() => new ConfigObject(), /bad config: port must be a positive integer/);
722+
});
723+
724+
it('should throw an error for negative internalPort number', () => {
725+
const testConfig = { ...baseConfig, internalPort: -1 };
726+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(testConfig));
727+
728+
assert.throws(() => new ConfigObject(), /bad config: internalPort must be a positive integer/);
729+
});
730+
731+
it('should throw an error for non-integer internalPort number', () => {
732+
const testConfig = { ...baseConfig, internalPort: 9000.5 };
733+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(testConfig));
734+
735+
assert.throws(() => new ConfigObject(), /bad config: internalPort must be a positive integer/);
736+
});
737+
738+
it('should accept a valid port number', () => {
739+
const testConfig = { ...baseConfig, port: 8765 };
740+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(testConfig));
741+
742+
const config = new ConfigObject();
743+
assert.strictEqual(config.port, 8765);
744+
assert.strictEqual(config.internalPort, undefined);
745+
});
746+
747+
it('should accept a valid internalPort number', () => {
748+
const testConfig = { ...baseConfig, internalPort: 9000 };
749+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(testConfig));
750+
751+
const config = new ConfigObject();
752+
assert.strictEqual(config.port, undefined);
753+
assert.strictEqual(config.internalPort, 9000);
754+
});
755+
756+
it('should accept both port and internalPort when provided', () => {
757+
const testConfig = { ...baseConfig, port: 8000, internalPort: 9000 };
758+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(testConfig));
759+
760+
const config = new ConfigObject();
761+
assert.strictEqual(config.port, 8000);
762+
assert.strictEqual(config.internalPort, 9000);
763+
});
764+
765+
it('should use default port 8000 if no port or internalPort specified', () => {
766+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(baseConfig));
767+
768+
const config = new ConfigObject();
769+
assert.strictEqual(config.port, 8000);
770+
assert.strictEqual(config.internalPort, undefined);
771+
});
772+
773+
it('should default port if listenOn is provided', () => {
774+
const testConfig = {
775+
...baseConfig,
776+
listenOn: ['127.0.0.1:9000'],
777+
};
778+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(testConfig));
779+
780+
const config = new ConfigObject();
781+
assert.strictEqual(config.port, 8000);
782+
assert.strictEqual(config.internalPort, undefined);
783+
assert.deepEqual(config.listenOn, [{ ip: '127.0.0.1', port: 9000 }]);
784+
});
785+
786+
it('should not default port if internalListenOn is provided', () => {
787+
const testConfig = {
788+
...baseConfig,
789+
internalListenOn: ['127.0.0.1:9000'],
790+
};
791+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(testConfig));
792+
793+
const config = new ConfigObject();
794+
assert.strictEqual(config.port, undefined);
795+
assert.strictEqual(config.internalPort, undefined);
796+
assert.deepEqual(config.internalListenOn, [{ ip: '127.0.0.1', port: 9000 }]);
797+
});
798+
799+
it('should properly handle listenOn and internalListenOn configuration', () => {
800+
const testConfig = {
801+
...baseConfig,
802+
listenOn: ['0.0.0.0:8000'],
803+
internalListenOn: ['127.0.0.1:9000'],
804+
};
805+
readFileStub.withArgs(sinon.match(/config.json$/)).returns(JSON.stringify(testConfig));
806+
807+
const config = new ConfigObject();
808+
assert.strictEqual(config.port, 8000);
809+
assert.deepEqual(config.listenOn, [{ ip: '0.0.0.0', port: 8000 }]);
810+
assert.deepEqual(config.internalListenOn, [{ ip: '127.0.0.1', port: 9000 }]);
811+
});
812+
});
682813
});

0 commit comments

Comments
 (0)