diff --git a/migrations/20200512172301-rename-backup-key-to-password.js b/migrations/20200512172301-rename-backup-key-to-password.js new file mode 100644 index 000000000..676df7a88 --- /dev/null +++ b/migrations/20200512172301-rename-backup-key-to-password.js @@ -0,0 +1,20 @@ +'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.key) { + backupConfig.password = backupConfig.key; + } else { + delete backupConfig.key; + } + + db.runSql('UPDATE settings SET value=? WHERE name="backup_config"', [ JSON.stringify(backupConfig) ], callback); + }); +}; + +exports.down = function(db, callback) { + callback(); +}; diff --git a/src/backups.js b/src/backups.js index ce63bd66e..e4caad10c 100644 --- a/src/backups.js +++ b/src/backups.js @@ -102,13 +102,13 @@ function api(provider) { } function injectPrivateFields(newConfig, currentConfig) { - if (newConfig.key === exports.SECRET_PLACEHOLDER) newConfig.key = currentConfig.key; + if (newConfig.password === exports.SECRET_PLACEHOLDER) newConfig.password = currentConfig.password; if (newConfig.provider === currentConfig.provider) api(newConfig.provider).injectPrivateFields(newConfig, currentConfig); } function removePrivateFields(backupConfig) { assert.strictEqual(typeof backupConfig, 'object'); - if (backupConfig.key) backupConfig.key = exports.SECRET_PLACEHOLDER; + if (backupConfig.password) backupConfig.password = exports.SECRET_PLACEHOLDER; return api(backupConfig.provider).removePrivateFields(backupConfig); } @@ -124,9 +124,9 @@ function testConfig(backupConfig, callback) { // remember to adjust the cron ensureBackup task interval accordingly if (backupConfig.intervalSecs < 6 * 60 * 60) return callback(new BoxError(BoxError.BAD_FIELD, 'Interval must be atleast 6 hours', { field: 'interval' })); - if ('key' in backupConfig) { - if (typeof backupConfig.key !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'key must be a string', { field: 'key' })); - if (backupConfig.key.length < 8) return callback(new BoxError(BoxError.BAD_FIELD, 'key must be atleast 8 characters', { field: 'key' })); + if ('password' in backupConfig) { + if (typeof backupConfig.password !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'password must be a string', { field: 'password' })); + if (backupConfig.password.length < 8) return callback(new BoxError(BoxError.BAD_FIELD, 'password must be atleast 8 characters', { field: 'password' })); } api(backupConfig.provider).testConfig(backupConfig, callback); @@ -186,7 +186,7 @@ function getBackupFilePath(backupConfig, backupId, format) { assert.strictEqual(typeof format, 'string'); if (format === 'tgz') { - const fileType = backupConfig.key ? '.tar.gz.enc' : '.tar.gz'; + const fileType = backupConfig.password ? '.tar.gz.enc' : '.tar.gz'; return path.join(backupConfig.prefix || backupConfig.backupFolder || '', backupId+fileType); } else { return path.join(backupConfig.prefix || backupConfig.backupFolder || '', backupId); @@ -407,7 +407,7 @@ function tarPack(dataLayout, key, callback) { } function aesKeyFromPassword(password) { - assert(password === null || typeof password === 'string'); + assert(typeof password === 'undefined' || typeof password === 'string'); if (!password) return null; @@ -423,12 +423,12 @@ function sync(backupConfig, backupId, dataLayout, progressCallback, callback) { // the number here has to take into account the s3.upload partSize (which is 10MB). So 20=200MB const concurrency = backupConfig.syncConcurrency || (backupConfig.provider === 's3' ? 20 : 10); - const aesKey = aesKeyFromPassword(backupConfig.key); + const aesKey = aesKeyFromPassword(backupConfig.password); syncer.sync(dataLayout, function processTask(task, iteratorCallback) { debug('sync: processing task: %j', task); // the empty task.path is special to signify the directory - const destPath = task.path && backupConfig.key ? encryptFilePath(task.path, aesKey) : task.path; + const destPath = task.path && backupConfig.password ? encryptFilePath(task.path, aesKey) : task.path; const backupFilePath = path.join(getBackupFilePath(backupConfig, backupId, backupConfig.format), destPath); if (task.operation === 'removedir') { @@ -547,7 +547,7 @@ function upload(backupId, format, dataLayoutString, progressCallback, callback) if (error) return callback(error); if (format === 'tgz') { - const aesKey = aesKeyFromPassword(backupConfig.key); + const aesKey = aesKeyFromPassword(backupConfig.password); async.retry({ times: 5, interval: 20000 }, function (retryCallback) { retryCallback = once(retryCallback); // protect again upload() erroring much later after tar stream error @@ -662,11 +662,11 @@ function downloadDir(backupConfig, backupFilePath, dataLayout, progressCallback, debug(`downloadDir: ${backupFilePath} to ${dataLayout.toString()}`); - const aesKey = aesKeyFromPassword(backupConfig.key); + const aesKey = aesKeyFromPassword(backupConfig.password); function downloadFile(entry, done) { let relativePath = path.relative(backupFilePath, entry.fullPath); - if (backupConfig.key) { + if (backupConfig.password) { relativePath = decryptFilePath(relativePath, aesKey); if (!relativePath) return done(new BoxError(BoxError.CRYPTO_ERROR, 'Unable to decrypt file')); } @@ -727,7 +727,7 @@ function download(backupConfig, backupId, format, dataLayout, progressCallback, const backupFilePath = getBackupFilePath(backupConfig, backupId, format); if (format === 'tgz') { - const aesKey = aesKeyFromPassword(backupConfig.key); + const aesKey = aesKeyFromPassword(backupConfig.password); async.retry({ times: 5, interval: 20000 }, function (retryCallback) { api(backupConfig.provider).download(backupConfig, backupFilePath, function (error, sourceStream) { if (error) return retryCallback(error); diff --git a/src/routes/apps.js b/src/routes/apps.js index fc43483ac..9288971d7 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -428,7 +428,7 @@ function importApp(req, res, next) { if (req.body.backupConfig) { if (typeof backupConfig.provider !== 'string') return next(new HttpError(400, 'provider is required')); - if ('key' in backupConfig && typeof backupConfig.key !== 'string') return next(new HttpError(400, 'key must be a string')); + if ('password' in backupConfig && typeof backupConfig.password !== 'string') return next(new HttpError(400, 'password must be a string')); if ('acceptSelfSignedCerts' in backupConfig && typeof backupConfig.acceptSelfSignedCerts !== 'boolean') return next(new HttpError(400, 'format must be a boolean')); // testing backup config can take sometime diff --git a/src/routes/provision.js b/src/routes/provision.js index 29ec0d483..cb19a6a1c 100644 --- a/src/routes/provision.js +++ b/src/routes/provision.js @@ -98,7 +98,7 @@ function restore(req, res, next) { var backupConfig = req.body.backupConfig; if (typeof backupConfig.provider !== 'string') return next(new HttpError(400, 'provider is required')); - if ('key' in backupConfig && typeof backupConfig.key !== 'string') return next(new HttpError(400, 'key must be a string')); + if ('password' in backupConfig && typeof backupConfig.password !== 'string') return next(new HttpError(400, 'password must be a string')); if (typeof backupConfig.format !== 'string') return next(new HttpError(400, 'format must be a string')); if ('acceptSelfSignedCerts' in backupConfig && typeof backupConfig.acceptSelfSignedCerts !== 'boolean') return next(new HttpError(400, 'format must be a boolean')); diff --git a/src/routes/settings.js b/src/routes/settings.js index 64c9ba961..984abeba1 100644 --- a/src/routes/settings.js +++ b/src/routes/settings.js @@ -99,7 +99,7 @@ function setBackupConfig(req, res, next) { if (typeof req.body.provider !== 'string') return next(new HttpError(400, 'provider is required')); if (typeof req.body.retentionSecs !== 'number') return next(new HttpError(400, 'retentionSecs is required')); if (typeof req.body.intervalSecs !== 'number') return next(new HttpError(400, 'intervalSecs is required')); - if ('key' in req.body && typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string')); + if ('password' in req.body && typeof req.body.password !== 'string') return next(new HttpError(400, 'password must be a string')); if ('syncConcurrency' in req.body) { if (typeof req.body.syncConcurrency !== 'number') return next(new HttpError(400, 'syncConcurrency must be a positive integer')); if (req.body.syncConcurrency < 1) return next(new HttpError(400, 'syncConcurrency must be a positive integer')); diff --git a/src/settings.js b/src/settings.js index 315952da1..f06f8ddfd 100644 --- a/src/settings.js +++ b/src/settings.js @@ -142,7 +142,6 @@ let gDefaults = (function () { result[exports.CLOUDRON_TOKEN_KEY] = ''; result[exports.BACKUP_CONFIG_KEY] = { provider: 'filesystem', - key: '', backupFolder: '/var/backups', format: 'tgz', retentionSecs: 2 * 24 * 60 * 60, // 2 days @@ -386,7 +385,7 @@ function getBackupConfig(callback) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.BACKUP_CONFIG_KEY]); if (error) return callback(error); - callback(null, JSON.parse(value)); // provider, token, key, region, prefix, bucket + callback(null, JSON.parse(value)); // provider, token, password, region, prefix, bucket }); } diff --git a/src/test/backups-test.js b/src/test/backups-test.js index 982b86d70..7dc3bd507 100644 --- a/src/test/backups-test.js +++ b/src/test/backups-test.js @@ -78,7 +78,7 @@ describe('backups', function () { database._clear, settings.setBackupConfig.bind(null, { provider: 'filesystem', - key: 'secretenckey', + password: 'supersecret', backupFolder: BACKUP_DIR, retentionSecs: 1, format: 'tgz'