diff --git a/CHANGES b/CHANGES index 6e906ac56..37b49e778 100644 --- a/CHANGES +++ b/CHANGES @@ -1958,4 +1958,5 @@ [6.0.0] * better nginx config for higher loads * backups: add CIFS storage provider +* s3: use vhost style diff --git a/migrations/20200528003052-settings-backup-s3-path-style.js b/migrations/20200528003052-settings-backup-s3-path-style.js new file mode 100644 index 000000000..e2747c9e7 --- /dev/null +++ b/migrations/20200528003052-settings-backup-s3-path-style.js @@ -0,0 +1,18 @@ +'use strict'; + +exports.up = function(db, callback) { + db.all('SELECT value FROM settings WHERE name="backup_config"', function (error, results) { + if (error || results.length === 0) return callback(error); + + var backupConfig = JSON.parse(results[0].value); + if (backupConfig.provider !== 'minio' && backupConfig.provider !== 's3-v4-compat') return callback(); + + backupConfig.s3ForcePathStyle = true; // usually minio is self-hosted. s3 v4 compat, we don't know + + db.runSql('UPDATE settings SET value=? WHERE name="backup_config"', [ JSON.stringify(backupConfig) ], callback); + }); +}; + +exports.down = function(db, callback) { + callback(); +}; diff --git a/src/storage/s3.js b/src/storage/s3.js index a39c66145..c497d335e 100644 --- a/src/storage/s3.js +++ b/src/storage/s3.js @@ -54,7 +54,7 @@ function getS3Config(apiConfig, callback) { var credentials = { signatureVersion: apiConfig.signatureVersion || 'v4', - s3ForcePathStyle: true, // Force use path-style url (http://endpoint/bucket/path) instead of host-style (http://bucket.endpoint/path) + s3ForcePathStyle: false, // Use vhost style instead of path style - https://forums.aws.amazon.com/ann.jspa?annID=6776 accessKeyId: apiConfig.accessKeyId, secretAccessKey: apiConfig.secretAccessKey, region: apiConfig.region || 'us-east-1', @@ -70,8 +70,14 @@ function getS3Config(apiConfig, callback) { if (apiConfig.endpoint) credentials.endpoint = apiConfig.endpoint; - if (apiConfig.acceptSelfSignedCerts === true && credentials.endpoint && credentials.endpoint.startsWith('https://')) { - credentials.httpOptions.agent = new https.Agent({ rejectUnauthorized: false }); + if (apiConfig.s3ForcePathStyle === true) credentials.s3ForcePathStyle = true; + + // s3 endpoint names come from the SDK + const isHttps = (credentials.endpoint && credentials.endpoint.startsWith('https://')) || apiConfig.provider === 's3'; + if (isHttps) { // only set agent for https calls. otherwise, it crashes + if (apiConfig.acceptSelfSignedCerts || apiConfig.bucket.includes('.')) { + credentials.httpOptions.agent = new https.Agent({ rejectUnauthorized: false }); + } } callback(null, credentials); } @@ -419,12 +425,13 @@ function testConfig(apiConfig, callback) { // names must be lowercase and start with a letter or number. can contain dashes if (apiConfig.bucket.includes('_') || apiConfig.bucket.match(/[A-Z]/)) return callback(new BoxError(BoxError.BAD_FIELD, 'bucket name cannot contain "_" or capitals', { field: 'bucket' })); - if (apiConfig.bucket.includes('.')) return callback(new BoxError(BoxError.BAD_FIELD, 'Use of bucket names with "." is discouraged. Use the "S3 API Compatible" provider and accept self-signed certificate if you really need this', { field: 'bucket' })); - if (typeof apiConfig.prefix !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'prefix must be a string', { field: 'prefix' })); if ('signatureVersion' in apiConfig && typeof apiConfig.signatureVersion !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'signatureVersion must be a string', { field: 'signatureVersion' })); if ('endpoint' in apiConfig && typeof apiConfig.endpoint !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'endpoint must be a string', { field: 'endpoint' })); + if ('acceptSelfSignedCerts' in apiConfig && typeof apiConfig.acceptSelfSignedCerts !== 'boolean') return callback(new BoxError(BoxError.BAD_FIELD, 'acceptSelfSignedCerts must be a boolean', { field: 'acceptSelfSignedCerts' })); + if ('s3ForcePathStyle' in apiConfig && typeof apiConfig.s3ForcePathStyle !== 'boolean') return callback(new BoxError(BoxError.BAD_FIELD, 's3ForcePathStyle must be a boolean', { field: 's3ForcePathStyle' })); + // attempt to upload and delete a file with new credentials getS3Config(apiConfig, function (error, credentials) { if (error) return callback(error);