Commit f417a49b authored by Girish Ramakrishnan's avatar Girish Ramakrishnan

Add encryptionVersion to backups

this will identify the old style backups and warn user that a restore
doesn't work anymore
parent 66fd713d
......@@ -1931,4 +1931,5 @@
* graphs: fix issue where large number of apps would crash the box code (query param limit exceeded)
* backups: fix various security issues in encypted backups (thanks @mehdi)
* graphs: add app graphs
* older encrypted backups cannot be used in this version
'use strict';
exports.up = function(db, callback) {
db.runSql('ALTER TABLE backups ADD COLUMN encryptionVersion INTEGER', function (error) {
if (error) return callback(error);
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.encryption) return callback(null);
// mark old encrypted backups as v1
db.runSql('UPDATE backups SET encryptionVersion=1', callback);
});
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE backups DROP COLUMN encryptionVersion', function (error) {
if (error) console.error(error);
callback(error);
});
};
......@@ -121,7 +121,8 @@ CREATE TABLE IF NOT EXISTS appEnvVars(
CREATE TABLE IF NOT EXISTS backups(
id VARCHAR(128) NOT NULL,
creationTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
version VARCHAR(128) NOT NULL, /* app version or box version */
packageVersion VARCHAR(128) NOT NULL, /* app version or box version */
encryptionVersion INTEGER, /* when null, unencrypted backup */
type VARCHAR(16) NOT NULL, /* 'box' or 'app' */
dependsOn TEXT, /* comma separate list of objects this backup depends on */
state VARCHAR(16) NOT NULL,
......
......@@ -1480,7 +1480,8 @@ function restore(app, backupId, auditSource, callback) {
func(function (error, backupInfo) {
if (error) return callback(error);
if (!backupInfo.manifest) callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Could not get restore manifest'));
if (!backupInfo.manifest) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Could not get restore manifest'));
if (backupInfo.encryptionVersion === 1) return callback(new BoxError(BoxError.BAD_FIELD, 'This encrypted backup was created with an older Cloudron version and has to be restored using the CLI tool'));
// re-validate because this new box version may not accept old configs
error = checkManifestConstraints(backupInfo.manifest);
......@@ -1603,7 +1604,8 @@ function clone(app, data, user, auditSource, callback) {
backups.get(backupId, function (error, backupInfo) {
if (error) return callback(error);
if (!backupInfo.manifest) callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Could not get restore config'));
if (!backupInfo.manifest) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Could not get restore config'));
if (backupInfo.encryptionVersion === 1) return callback(new BoxError(BoxError.BAD_FIELD, 'This encrypted backup was created with an older Cloudron version and cannot be cloned'));
const manifest = backupInfo.manifest, appStoreId = app.appStoreId;
......
......@@ -6,7 +6,7 @@ var assert = require('assert'),
safe = require('safetydance'),
util = require('util');
var BACKUPS_FIELDS = [ 'id', 'creationTime', 'packageVersion', 'type', 'dependsOn', 'state', 'manifestJson', 'format', 'preserveSecs' ];
var BACKUPS_FIELDS = [ 'id', 'creationTime', 'packageVersion', 'type', 'dependsOn', 'state', 'manifestJson', 'format', 'preserveSecs', 'encryptionVersion' ];
exports = module.exports = {
add: add,
......@@ -106,6 +106,7 @@ function get(id, callback) {
function add(id, data, callback) {
assert(data && typeof data === 'object');
assert.strictEqual(typeof id, 'string');
assert(data.encryptionVersion === null || typeof data.encryptionVersion === 'number');
assert.strictEqual(typeof data.packageVersion, 'string');
assert(data.type === exports.BACKUP_TYPE_APP || data.type === exports.BACKUP_TYPE_BOX);
assert(util.isArray(data.dependsOn));
......@@ -116,8 +117,8 @@ function add(id, data, callback) {
var creationTime = data.creationTime || new Date(); // allow tests to set the time
var manifestJson = JSON.stringify(data.manifest);
database.query('INSERT INTO backups (id, packageVersion, type, creationTime, state, dependsOn, manifestJson, format) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[ id, data.packageVersion, data.type, creationTime, exports.BACKUP_STATE_NORMAL, data.dependsOn.join(','), manifestJson, data.format ],
database.query('INSERT INTO backups (id, encryptionVersion, packageVersion, type, creationTime, state, dependsOn, manifestJson, format) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
[ id, data.encryptionVersion, data.packageVersion, data.type, creationTime, exports.BACKUP_STATE_NORMAL, data.dependsOn.join(','), manifestJson, data.format ],
function (error) {
if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS));
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
......
......@@ -941,7 +941,15 @@ function rotateBoxBackup(backupConfig, tag, appBackupIds, progressCallback, call
debug(`Rotating box backup to id ${backupId}`);
backupdb.add(backupId, { packageVersion: constants.VERSION, type: backupdb.BACKUP_TYPE_BOX, dependsOn: appBackupIds, manifest: null, format: format }, function (error) {
const data = {
encryptionVersion: backupConfig.encryption ? 2 : null,
packageVersion: constants.VERSION,
type: backupdb.BACKUP_TYPE_BOX,
dependsOn: appBackupIds,
manifest: null,
format: format
};
backupdb.add(backupId, data, function (error) {
if (error) return callback(error);
var copy = api(backupConfig.provider).copy(backupConfig, getBackupFilePath(backupConfig, 'snapshot/box', format), getBackupFilePath(backupConfig, backupId, format));
......@@ -1023,7 +1031,16 @@ function rotateAppBackup(backupConfig, app, tag, options, progressCallback, call
debug(`Rotating app backup of ${app.id} to id ${backupId}`);
backupdb.add(backupId, { packageVersion: manifest.version, type: backupdb.BACKUP_TYPE_APP, dependsOn: [ ], manifest: manifest, format: format }, function (error) {
const data = {
encryptionVersion: backupConfig.encryption ? 2 : null,
packageVersion: manifest.version,
type: backupdb.BACKUP_TYPE_APP,
dependsOn: [ ],
manifest,
format: format
};
backupdb.add(backupId, data, function (error) {
if (error) return callback(error);
var copy = api(backupConfig.provider).copy(backupConfig, getBackupFilePath(backupConfig, `snapshot/app_${app.id}`, format), getBackupFilePath(backupConfig, backupId, format));
......
......@@ -95,6 +95,7 @@ describe('backups', function () {
describe('cleanup', function () {
var BACKUP_0 = {
id: 'backup-box-0',
encryptionVersion: null,
packageVersion: '1.0.0',
type: backupdb.BACKUP_TYPE_BOX,
dependsOn: [ 'backup-app-00', 'backup-app-01' ],
......@@ -104,6 +105,7 @@ describe('backups', function () {
var BACKUP_0_APP_0 = {
id: 'backup-app-00',
encryptionVersion: null,
packageVersion: '1.0.0',
type: backupdb.BACKUP_TYPE_APP,
dependsOn: [],
......@@ -113,6 +115,7 @@ describe('backups', function () {
var BACKUP_0_APP_1 = {
id: 'backup-app-01',
encryptionVersion: null,
packageVersion: '1.0.0',
type: backupdb.BACKUP_TYPE_APP,
dependsOn: [],
......@@ -122,6 +125,7 @@ describe('backups', function () {
var BACKUP_1 = {
id: 'backup-box-1',
encryptionVersion: null,
packageVersion: '1.0.0',
type: backupdb.BACKUP_TYPE_BOX,
dependsOn: [ 'backup-app-10', 'backup-app-11' ],
......@@ -131,6 +135,7 @@ describe('backups', function () {
var BACKUP_1_APP_0 = {
id: 'backup-app-10',
encryptionVersion: null,
packageVersion: '1.0.0',
type: backupdb.BACKUP_TYPE_APP,
dependsOn: [],
......@@ -140,6 +145,7 @@ describe('backups', function () {
var BACKUP_1_APP_1 = {
id: 'backup-app-11',
encryptionVersion: null,
packageVersion: '1.0.0',
type: backupdb.BACKUP_TYPE_APP,
dependsOn: [],
......
......@@ -1324,6 +1324,7 @@ describe('database', function () {
it('add succeeds', function (done) {
var backup = {
id: 'backup-box',
encryptionVersion: 2,
packageVersion: '1.0.0',
type: backupdb.BACKUP_TYPE_BOX,
dependsOn: [ 'dep1' ],
......@@ -1340,6 +1341,7 @@ describe('database', function () {
it('get succeeds', function (done) {
backupdb.get('backup-box', function (error, result) {
expect(error).to.be(null);
expect(result.encryptionVersion).to.be(2);
expect(result.packageVersion).to.be('1.0.0');
expect(result.type).to.be(backupdb.BACKUP_TYPE_BOX);
expect(result.creationTime).to.be.a(Date);
......@@ -1365,6 +1367,7 @@ describe('database', function () {
expect(results.length).to.be(1);
expect(results[0].id).to.be('backup-box');
expect(results[0].encryptionVersion).to.be(2);
expect(results[0].packageVersion).to.be('1.0.0');
expect(results[0].dependsOn).to.eql(['dep1']);
expect(results[0].manifest).to.eql(null);
......@@ -1390,6 +1393,7 @@ describe('database', function () {
it('add app succeeds', function (done) {
var backup = {
id: 'app_appid_123',
encryptionVersion: null,
packageVersion: '1.0.0',
type: backupdb.BACKUP_TYPE_APP,
dependsOn: [ ],
......@@ -1406,6 +1410,7 @@ describe('database', function () {
it('get succeeds', function (done) {
backupdb.get('app_appid_123', function (error, result) {
expect(error).to.be(null);
expect(result.encryptionVersion).to.be(null);
expect(result.packageVersion).to.be('1.0.0');
expect(result.type).to.be(backupdb.BACKUP_TYPE_APP);
expect(result.creationTime).to.be.a(Date);
......@@ -1422,6 +1427,7 @@ describe('database', function () {
expect(results.length).to.be(1);
expect(results[0].id).to.be('app_appid_123');
expect(results[0].encryptionVersion).to.be(null);
expect(results[0].packageVersion).to.be('1.0.0');
expect(results[0].dependsOn).to.eql([]);
expect(results[0].manifest).to.eql({ foo: 'bar' });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment