Skip to content

Commit 07ab62a

Browse files
committed
CORS
Signed-off-by: jackyalbo <jacky.albo@gmail.com>
1 parent 6d65c1b commit 07ab62a

23 files changed

+393
-76
lines changed

config.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ config.BUFFERS_MEM_LIMIT_MIN = 32 * 1024 * 1024; // just some workable minimum s
6464
config.BUFFERS_MEM_LIMIT_MAX = 4 * 1024 * 1024 * 1024;
6565
config.BUFFERS_MEM_LIMIT = Math.min(
6666
config.BUFFERS_MEM_LIMIT_MAX,
67-
Math.max(Math.floor(config.CONTAINER_MEM_LIMIT / 4), config.BUFFERS_MEM_LIMIT_MIN, )
67+
Math.max(Math.floor(config.CONTAINER_MEM_LIMIT / 4), config.BUFFERS_MEM_LIMIT_MIN)
6868
);
6969

7070
////////////////////////
@@ -152,9 +152,8 @@ config.ENDPOINT_HTTP_SERVER_REQUEST_TIMEOUT = 300 * 1000;
152152
config.ENDPOINT_HTTP_SERVER_KEEPALIVE_TIMEOUT = 5 * 1000;
153153
config.ENDPOINT_HTTP_MAX_REQUESTS_PER_SOCKET = 0; // 0 = no limit
154154

155-
// For now we enable fixed CORS for all buckets
156-
// but this should become a setting per bucket which is configurable
157-
// with the s3 put-bucket-cors api.
155+
// For now we enable fixed CORS only for sts
156+
// for S3 per bucket is configurabl with the s3 put-bucket-cors api.
158157
// note that browsers do not really allow origin=* with credentials,
159158
// but we just allow both from our side for simplicity.
160159
config.S3_CORS_ENABLED = true;
@@ -502,6 +501,7 @@ config.LOG_COLOR_ENABLED = process.env.NOOBAA_LOG_COLOR ? process.env.NOOBAA_LOG
502501

503502
// TEST Mode
504503
config.test_mode = false;
504+
config.allow_anonymous_access_in_test = false; // used for emulating ACL='public-read' for ceph-s3 tests
505505

506506
// On Premise NVA params
507507
config.on_premise = {
@@ -753,11 +753,11 @@ config.NSFS_BUF_POOL_MEM_LIMIT_XS = Math.min(Math.floor(config.NSFS_MAX_MEM_SIZE
753753
config.NSFS_BUF_POOL_MEM_LIMIT_S = Math.min(Math.floor(config.NSFS_MAX_MEM_SIZE_S / config.NSFS_BUF_SIZE_S),
754754
config.NSFS_WANTED_BUFFERS_NUMBER) * config.NSFS_BUF_SIZE_S;
755755
// Semaphore size will give 90% of remainning memory to large buffer size, 10% to medium
756-
config.NSFS_BUF_POOL_MEM_LIMIT_M = range_utils.align_down((config.BUFFERS_MEM_LIMIT -
757-
config.NSFS_BUF_POOL_MEM_LIMIT_S - config.NSFS_BUF_POOL_MEM_LIMIT_XS) * 0.1,
756+
config.NSFS_BUF_POOL_MEM_LIMIT_M = range_utils.align_down(
757+
(config.BUFFERS_MEM_LIMIT - config.NSFS_BUF_POOL_MEM_LIMIT_S - config.NSFS_BUF_POOL_MEM_LIMIT_XS) * 0.1,
758758
config.NSFS_BUF_SIZE_M);
759-
config.NSFS_BUF_POOL_MEM_LIMIT_L = range_utils.align_down((config.BUFFERS_MEM_LIMIT -
760-
config.NSFS_BUF_POOL_MEM_LIMIT_S - config.NSFS_BUF_POOL_MEM_LIMIT_XS) * 0.9,
759+
config.NSFS_BUF_POOL_MEM_LIMIT_L = range_utils.align_down(
760+
(config.BUFFERS_MEM_LIMIT - config.NSFS_BUF_POOL_MEM_LIMIT_S - config.NSFS_BUF_POOL_MEM_LIMIT_XS) * 0.9,
761761
config.NSFS_BUF_SIZE_L);
762762

763763
config.NSFS_BUF_WARMUP_SPARSE_FILE_READS = true;

src/api/bucket_api.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ module.exports = {
343343
notifications: {
344344
type: 'array',
345345
items: {
346-
$ref: 'common_api#/definitions/bucket_notification'
346+
$ref: 'common_api#/definitions/bucket_notification'
347347
}
348348
}
349349
}
@@ -890,6 +890,65 @@ module.exports = {
890890
system: ['admin', 'user']
891891
}
892892
},
893+
894+
get_bucket_cors: {
895+
method: 'GET',
896+
params: {
897+
type: 'object',
898+
required: ['name'],
899+
properties: {
900+
name: {
901+
$ref: 'common_api#/definitions/bucket_name'
902+
},
903+
},
904+
},
905+
reply: {
906+
type: 'object',
907+
properties: {
908+
cors: {
909+
$ref: 'common_api#/definitions/bucket_cors_configuration'
910+
}
911+
}
912+
},
913+
auth: {
914+
system: ['admin', 'user']
915+
}
916+
},
917+
918+
put_bucket_cors: {
919+
method: 'PUT',
920+
params: {
921+
type: 'object',
922+
required: ['name', 'cors_rules'],
923+
properties: {
924+
name: {
925+
$ref: 'common_api#/definitions/bucket_name'
926+
},
927+
cors_rules: {
928+
$ref: 'common_api#/definitions/bucket_cors_configuration'
929+
},
930+
},
931+
},
932+
auth: {
933+
system: ['admin', 'user']
934+
}
935+
},
936+
937+
delete_bucket_cors: {
938+
method: 'DELETE',
939+
params: {
940+
type: 'object',
941+
required: ['name'],
942+
properties: {
943+
name: {
944+
$ref: 'common_api#/definitions/bucket_name'
945+
},
946+
},
947+
},
948+
auth: {
949+
system: ['admin', 'user']
950+
}
951+
},
893952
},
894953

895954
definitions: {
@@ -1200,6 +1259,9 @@ module.exports = {
12001259
items: {
12011260
$ref: 'common_api#/definitions/bucket_notification'
12021261
}
1262+
},
1263+
cors_configuration_rules: {
1264+
$ref: 'common_api#/definitions/bucket_cors_configuration',
12031265
}
12041266
}
12051267
},

src/api/common_api.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,50 @@ module.exports = {
271271
}
272272
},
273273

274+
bucket_cors_configuration: {
275+
type: 'array',
276+
items: {
277+
$ref: '#/definitions/bucket_cors_rule'
278+
}
279+
},
280+
281+
bucket_cors_rule: {
282+
type: 'object',
283+
required: ['allowed_methods', 'allowed_origins'],
284+
properties: {
285+
id: {
286+
type: 'string'
287+
},
288+
allowed_methods: {
289+
type: 'array',
290+
items: {
291+
type: 'string'
292+
}
293+
},
294+
allowed_origins: {
295+
type: 'array',
296+
items: {
297+
type: 'string'
298+
}
299+
},
300+
allowed_headers: {
301+
type: 'array',
302+
items: {
303+
type: 'string'
304+
}
305+
},
306+
expose_headers: {
307+
type: 'array',
308+
items: {
309+
type: 'string'
310+
}
311+
},
312+
max_age_seconds: {
313+
type: 'integer'
314+
},
315+
}
316+
},
317+
274318
bucket_policy_principal: {
275319
anyOf: [{
276320
wrapper: SensitiveString

src/endpoint/s3/ops/s3_delete_bucket_cors.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
* http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketDELETEcors.html
66
*/
77
async function delete_bucket_cors(req) {
8-
await req.object_sdk.read_bucket({ name: req.params.bucket });
9-
// TODO S3 delete_bucket_cors not implemented
8+
await req.object_sdk.delete_bucket_cors({
9+
name: req.params.bucket,
10+
});
1011
}
1112

1213
module.exports = {

src/endpoint/s3/ops/s3_get_bucket_cors.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
/* Copyright (C) 2016 NooBaa */
22
'use strict';
33

4+
const S3Error = require('../s3_errors').S3Error;
5+
46
/**
57
* http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETcors.html
68
*/
79
async function get_bucket_cors(req) {
8-
await req.object_sdk.read_bucket({ name: req.params.bucket });
9-
return {
10-
CORSConfiguration: ''
11-
};
10+
const reply = await req.object_sdk.get_bucket_cors({ name: req.params.bucket });
11+
if (!reply.cors.length) throw new S3Error(S3Error.NoSuchCORSConfiguration);
12+
const cors_rules = reply.cors.map(rule => {
13+
const new_rule = [];
14+
new_rule.push(...rule.allowed_methods.map(m => ({ AllowedMethod: m })));
15+
new_rule.push(...rule.allowed_origins.map(o => ({ AllowedOrigin: o })));
16+
if (rule.allowed_headers) new_rule.push(...rule.allowed_headers.map(h => ({ AllowedHeader: h })));
17+
if (rule.expose_headers) new_rule.push(...rule.expose_headers.map(e => ({ ExposeHeader: e })));
18+
if (rule.id) new_rule.push({ ID: rule.id });
19+
if (rule.max_age_seconds) new_rule.push({ MaxAgeSeconds: rule.max_age_seconds });
20+
return { CORSRule: new_rule };
21+
});
22+
return { CORSConfiguration: cors_rules.length ? cors_rules : '' };
1223
}
1324

1425
module.exports = {

src/endpoint/s3/ops/s3_put_bucket.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,26 @@
22
'use strict';
33
const config = require('../../../../config');
44

5+
56
/**
67
* http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUT.html
78
*/
89
async function put_bucket(req, res) {
910
const lock_enabled = config.WORM_ENABLED ? req.headers['x-amz-bucket-object-lock-enabled'] &&
1011
req.headers['x-amz-bucket-object-lock-enabled'].toUpperCase() === 'TRUE' : undefined;
1112
await req.object_sdk.create_bucket({ name: req.params.bucket, lock_enabled: lock_enabled });
13+
if (config.allow_anonymous_access_in_test && req.headers['x-amz-acl'] === 'public-read') { // For now we will enable only for tests
14+
const policy = {
15+
Version: '2012-10-17',
16+
Statement: [{
17+
Effect: 'Allow',
18+
Principal: { AWS: ["*"] },
19+
Action: ['s3:GetObject', 's3:ListBucket'],
20+
Resource: ['arn:aws:s3:::*']
21+
}]
22+
};
23+
await req.object_sdk.put_bucket_policy({ name: req.params.bucket, policy });
24+
}
1225
res.setHeader('Location', '/' + req.params.bucket);
1326
}
1427

src/endpoint/s3/ops/s3_put_bucket_cors.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
/* Copyright (C) 2016 NooBaa */
22
'use strict';
33

4-
const S3Error = require('../s3_errors').S3Error;
4+
const _ = require('lodash');
55

66
/**
77
* http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTcors.html
88
*/
99
async function put_bucket_cors(req) {
10-
await req.object_sdk.read_bucket({ name: req.params.bucket });
11-
// TODO S3 put_bucket_cors not implemented
12-
throw new S3Error(S3Error.NotImplemented);
10+
const cors_rules = req.body.CORSConfiguration.CORSRule.map(rule =>
11+
_.omitBy({
12+
allowed_headers: rule.AllowedHeader,
13+
allowed_methods: rule.AllowedMethod,
14+
allowed_origins: rule.AllowedOrigin,
15+
expose_headers: rule.ExposeHeader,
16+
id: rule.ID,
17+
max_age_seconds: rule.MaxAgeSeconds,
18+
}, _.isUndefined)
19+
);
20+
await req.object_sdk.put_bucket_cors({
21+
name: req.params.bucket,
22+
cors_rules
23+
});
1324
}
1425

1526
module.exports = {

src/endpoint/s3/s3_errors.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,11 @@ S3Error.NoSuchLifecycleConfiguration = Object.freeze({
339339
message: 'The lifecycle configuration does not exist.',
340340
http_code: 404,
341341
});
342+
S3Error.NoSuchCORSConfiguration = Object.freeze({
343+
code: 'NoSuchCORSConfiguration',
344+
message: 'The specified bucket does not have a CORS configuration.',
345+
http_code: 404,
346+
});
342347
S3Error.NoSuchUpload = Object.freeze({
343348
code: 'NoSuchUpload',
344349
message: 'The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.',

src/endpoint/s3/s3_rest.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,6 @@ async function handle_request(req, res) {
8383

8484
http_utils.validate_server_ip_whitelist(req);
8585
http_utils.set_amz_headers(req, res);
86-
http_utils.set_cors_headers_s3(req, res);
87-
88-
if (req.method === 'OPTIONS') {
89-
dbg.log1('OPTIONS!');
90-
res.statusCode = 200;
91-
res.end();
92-
return;
93-
}
9486

9587
const headers_options = {
9688
ErrorClass: S3Error,
@@ -115,6 +107,18 @@ async function handle_request(req, res) {
115107
}
116108

117109
const op_name = parse_op_name(req);
110+
const cors = req.params.bucket && await req.object_sdk.read_bucket_sdk_cors_info(req.params.bucket);
111+
112+
http_utils.set_cors_headers_s3(req, res, cors);
113+
114+
if (req.method === 'OPTIONS') {
115+
dbg.log1('OPTIONS!');
116+
const error_code = req.headers.origin && req.headers['access-control-request-method'] ? 403 : 400;
117+
const res_headers = res.getHeaders(); // We will check if we found a matching rule - if no we will return error_code
118+
res.statusCode = res_headers['access-control-allow-origin'] && res_headers['access-control-allow-methods'] ? 200 : error_code;
119+
res.end();
120+
return;
121+
}
118122
const op = s3_ops[op_name];
119123
if (!op || !op.handler) {
120124
dbg.error('S3 NotImplemented', op_name, req.method, req.originalUrl);

src/sdk/bucketspace_fs.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,47 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
697697
}
698698
}
699699

700+
////////////////////
701+
// BUCKET CORS //
702+
////////////////////
703+
704+
async put_bucket_cors(params) {
705+
try {
706+
const { name, cors_rules } = params;
707+
dbg.log0('BucketSpaceFS.put_bucket_cors: Bucket name', name, ", cors configuration ", cors_rules);
708+
const bucket = await this.config_fs.get_bucket_by_name(name);
709+
bucket.cors_configuration_rules = cors_rules;
710+
await this.config_fs.update_bucket_config_file(bucket);
711+
} catch (error) {
712+
throw translate_error_codes(error, entity_enum.BUCKET);
713+
}
714+
}
715+
716+
async delete_bucket_cors(params) {
717+
try {
718+
const { name } = params;
719+
dbg.log0('BucketSpaceFS.delete_bucket_cors: Bucket name', name);
720+
const bucket = await this.config_fs.get_bucket_by_name(name);
721+
delete bucket.cors_configuration_rules;
722+
await this.config_fs.update_bucket_config_file(bucket);
723+
} catch (err) {
724+
throw translate_error_codes(err, entity_enum.BUCKET);
725+
}
726+
}
727+
728+
async get_bucket_cors(params) {
729+
try {
730+
const { name } = params;
731+
dbg.log0('BucketSpaceFS.get_bucket_cors: Bucket name', name);
732+
const bucket = await this.config_fs.get_bucket_by_name(name);
733+
return {
734+
cors: bucket.cors_configuration_rules || [],
735+
};
736+
} catch (error) {
737+
throw translate_error_codes(error, entity_enum.BUCKET);
738+
}
739+
}
740+
700741
/////////////////////////
701742
// DEFAULT OBJECT LOCK //
702743
/////////////////////////

0 commit comments

Comments
 (0)