Merge "Adding support for encrypted backups."

This commit is contained in:
Jenkins 2013-06-18 20:58:55 +00:00 committed by Gerrit Code Review
commit 0bdf7b3928
8 changed files with 173 additions and 23 deletions

View File

@ -72,6 +72,8 @@ storage_strategy = SwiftStorage
storage_namespace = reddwarf.guestagent.strategies.storage.swift storage_namespace = reddwarf.guestagent.strategies.storage.swift
backup_swift_container = database_backups backup_swift_container = database_backups
backup_use_gzip_compression = True backup_use_gzip_compression = True
backup_use_openssl_encryption = True
backup_aes_cbc_key = "default_aes_cbc_key"
backup_use_snet = False backup_use_snet = False
backup_chunk_size = 65536 backup_chunk_size = 65536
backup_segment_max_size = 2147483648 backup_segment_max_size = 2147483648

View File

@ -140,6 +140,10 @@ common_opts = [
cfg.StrOpt('backup_swift_container', default='database_backups'), cfg.StrOpt('backup_swift_container', default='database_backups'),
cfg.BoolOpt('backup_use_gzip_compression', default=True, cfg.BoolOpt('backup_use_gzip_compression', default=True,
help='Compress backups using gzip.'), help='Compress backups using gzip.'),
cfg.BoolOpt('backup_use_openssl_encryption', default=True,
help='Encrypt backups using openssl.'),
cfg.StrOpt('backup_aes_cbc_key', default='default_aes_cbc_key',
help='default openssl aes_cbc key.'),
cfg.BoolOpt('backup_use_snet', default=False, cfg.BoolOpt('backup_use_snet', default=False,
help='Send backup files over snet.'), help='Send backup files over snet.'),
cfg.IntOpt('backup_chunk_size', default=2 ** 16, cfg.IntOpt('backup_chunk_size', default=2 ** 16,

View File

@ -30,6 +30,9 @@ CHUNK_SIZE = CONF.backup_chunk_size
MAX_FILE_SIZE = CONF.backup_segment_max_size MAX_FILE_SIZE = CONF.backup_segment_max_size
BACKUP_CONTAINER = CONF.backup_swift_container BACKUP_CONTAINER = CONF.backup_swift_container
BACKUP_USE_GZIP = CONF.backup_use_gzip_compression BACKUP_USE_GZIP = CONF.backup_use_gzip_compression
BACKUP_USE_OPENSSL = CONF.backup_use_openssl_encryption
BACKUP_ENCRYPT_KEY = CONF.backup_aes_cbc_key
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -49,6 +52,8 @@ class BackupRunner(Strategy):
# The actual system call to run the backup # The actual system call to run the backup
cmd = None cmd = None
is_zipped = BACKUP_USE_GZIP is_zipped = BACKUP_USE_GZIP
is_encrypted = BACKUP_USE_OPENSSL
encrypt_key = BACKUP_ENCRYPT_KEY
def __init__(self, filename, **kwargs): def __init__(self, filename, **kwargs):
self.filename = filename self.filename = filename
@ -119,6 +124,15 @@ class BackupRunner(Strategy):
def zip_manifest(self): def zip_manifest(self):
return '.gz' if self.is_zipped else '' return '.gz' if self.is_zipped else ''
@property
def encrypt_cmd(self):
return (' | openssl enc -aes-256-cbc -salt -pass pass:%s' %
self.encrypt_key) if self.is_encrypted else ''
@property
def encrypt_manifest(self):
return '.enc' if self.is_encrypted else ''
def read(self, chunk_size): def read(self, chunk_size):
"""Wrap self.process.stdout.read to allow for segmentation.""" """Wrap self.process.stdout.read to allow for segmentation."""
if self.end_of_segment: if self.end_of_segment:

View File

@ -32,12 +32,12 @@ class MySQLDump(base.BackupRunner):
' --opt'\ ' --opt'\
' --password=%(password)s'\ ' --password=%(password)s'\
' -u %(user)s' ' -u %(user)s'
return cmd + self.zip_cmd return cmd + self.zip_cmd + self.encrypt_cmd
@property @property
def manifest(self): def manifest(self):
manifest = '%s' + self.zip_manifest manifest = '%s' % self.filename
return manifest % self.filename return manifest + self.zip_manifest + self.encrypt_manifest
class InnoBackupEx(base.BackupRunner): class InnoBackupEx(base.BackupRunner):
@ -49,9 +49,9 @@ class InnoBackupEx(base.BackupRunner):
cmd = 'sudo innobackupex'\ cmd = 'sudo innobackupex'\
' --stream=xbstream'\ ' --stream=xbstream'\
' /var/lib/mysql 2>/tmp/innobackupex.log' ' /var/lib/mysql 2>/tmp/innobackupex.log'
return cmd + self.zip_cmd return cmd + self.zip_cmd + self.encrypt_cmd
@property @property
def manifest(self): def manifest(self):
manifest = '%s.xbstream' + self.zip_manifest manifest = '%s.xbstream' % self.filename
return manifest % self.filename return manifest + self.zip_manifest + self.encrypt_manifest

View File

@ -27,6 +27,9 @@ import glob
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
CHUNK_SIZE = CONF.backup_chunk_size CHUNK_SIZE = CONF.backup_chunk_size
BACKUP_USE_GZIP = CONF.backup_use_gzip_compression
BACKUP_USE_OPENSSL = CONF.backup_use_openssl_encryption
BACKUP_DECRYPT_KEY = CONF.backup_aes_cbc_key
RESET_ROOT_RETRY_TIMEOUT = 100 RESET_ROOT_RETRY_TIMEOUT = 100
RESET_ROOT_SLEEP_INTERVAL = 10 RESET_ROOT_SLEEP_INTERVAL = 10
RESET_ROOT_MYSQL_COMMAND = """ RESET_ROOT_MYSQL_COMMAND = """
@ -78,13 +81,20 @@ class RestoreRunner(Strategy):
# The backup format type # The backup format type
restore_type = None restore_type = None
# Decryption Parameters
is_zipped = BACKUP_USE_GZIP
is_encrypted = BACKUP_USE_OPENSSL
decrypt_key = BACKUP_DECRYPT_KEY
def __init__(self, restore_stream, **kwargs): def __init__(self, restore_stream, **kwargs):
self.restore_stream = restore_stream self.restore_stream = restore_stream
self.restore_location = kwargs.get('restore_location', self.restore_location = kwargs.get('restore_location',
'/var/lib/mysql') '/var/lib/mysql')
self.restore_cmd = self.restore_cmd % kwargs self.restore_cmd = (self.decrypt_cmd +
self.prepare_cmd = self.prepare_cmd % kwargs \ self.unzip_cmd +
if hasattr(self, 'prepare_cmd') else None (self.base_restore_cmd % kwargs))
self.prepare_cmd = self.base_prepare_cmd % kwargs \
if hasattr(self, 'base_prepare_cmd') else None
super(RestoreRunner, self).__init__() super(RestoreRunner, self).__init__()
def __enter__(self): def __enter__(self):
@ -175,3 +185,15 @@ class RestoreRunner(Strategy):
filelist = glob.glob(self.restore_location + "/ib_logfile*") filelist = glob.glob(self.restore_location + "/ib_logfile*")
for f in filelist: for f in filelist:
os.unlink(f) os.unlink(f)
@property
def decrypt_cmd(self):
if self.is_encrypted:
return ('openssl enc -d -aes-256-cbc -salt -pass pass:%s | '
% self.decrypt_key)
else:
return ''
@property
def unzip_cmd(self):
return 'gzip -d -c | ' if self.is_zipped else ''

View File

@ -25,10 +25,9 @@ LOG = logging.getLogger(__name__)
class MySQLDump(base.RestoreRunner): class MySQLDump(base.RestoreRunner):
""" Implementation of Restore Strategy for MySQLDump """ """ Implementation of Restore Strategy for MySQLDump """
__strategy_name__ = 'mysqldump' __strategy_name__ = 'mysqldump'
is_zipped = True base_restore_cmd = ('mysql '
restore_cmd = ('mysql ' '--password=%(password)s '
'--password=%(password)s ' '-u %(user)s')
'-u %(user)s')
def _pre_restore(self): def _pre_restore(self):
pass pass
@ -40,11 +39,10 @@ class MySQLDump(base.RestoreRunner):
class InnoBackupEx(base.RestoreRunner): class InnoBackupEx(base.RestoreRunner):
""" Implementation of Restore Strategy for InnoBackupEx """ """ Implementation of Restore Strategy for InnoBackupEx """
__strategy_name__ = 'innobackupex' __strategy_name__ = 'innobackupex'
is_zipped = True base_restore_cmd = 'sudo xbstream -x -C %(restore_location)s'
restore_cmd = 'sudo xbstream -x -C %(restore_location)s' base_prepare_cmd = ('sudo innobackupex --apply-log %(restore_location)s'
prepare_cmd = ('sudo innobackupex --apply-log %(restore_location)s ' ' --defaults-file=%(restore_location)s/backup-my.cnf'
'--defaults-file=%(restore_location)s/backup-my.cnf ' ' --ibbackup xtrabackup 2>/tmp/innoprepare.log')
'--ibbackup xtrabackup 2>/tmp/innoprepare.log')
def _pre_restore(self): def _pre_restore(self):
app = dbaas.MySqlApp(dbaas.MySqlAppStatus.get()) app = dbaas.MySqlApp(dbaas.MySqlAppStatus.get())

View File

@ -21,7 +21,6 @@ from reddwarf.common import utils
from eventlet.green import subprocess from eventlet.green import subprocess
import zlib import zlib
UNZIPPER = zlib.decompressobj(16 + zlib.MAX_WBITS)
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -107,7 +106,6 @@ class SwiftDownloadStream(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.process = None self.process = None
self.pid = None self.pid = None
self.is_zipped = kwargs.get('is_zipped', False)
self.cmd = self.cmd % kwargs self.cmd = self.cmd % kwargs
def __enter__(self): def __enter__(self):
@ -128,9 +126,7 @@ class SwiftDownloadStream(object):
pass pass
def read(self, *args, **kwargs): def read(self, *args, **kwargs):
if not self.is_zipped: return self.process.stdout.read(*args, **kwargs)
return self.process.stdout.read(*args, **kwargs)
return UNZIPPER.decompress(self.process.stdout.read(*args, **kwargs))
def run(self): def run(self):
self.process = subprocess.Popen(self.cmd, shell=True, self.process = subprocess.Popen(self.cmd, shell=True,

View File

@ -0,0 +1,114 @@
# Copyright 2012 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License
import reddwarf.guestagent.strategies.backup.base as backupBase
import reddwarf.guestagent.strategies.restore.base as restoreBase
import testtools
from reddwarf.common import utils
BACKUP_XTRA_CLS = "reddwarf.guestagent.strategies.backup.impl.InnoBackupEx"
RESTORE_XTRA_CLS = "reddwarf.guestagent.strategies.restore.impl.InnoBackupEx"
BACKUP_SQLDUMP_CLS = "reddwarf.guestagent.strategies.backup.impl.MySQLDump"
RESTORE_SQLDUMP_CLS = "reddwarf.guestagent.strategies.restore.impl.MySQLDump"
PIPE = " | "
ZIP = "gzip"
UNZIP = "gzip -d -c"
ENCRYPT = "openssl enc -aes-256-cbc -salt -pass pass:default_aes_cbc_key"
DECRYPT = "openssl enc -d -aes-256-cbc -salt -pass pass:default_aes_cbc_key"
XTRA_BACKUP = "sudo innobackupex --stream=xbstream /var/lib/mysql 2>/" \
"tmp/innobackupex.log"
SQLDUMP_BACKUP = "/usr/bin/mysqldump --all-databases --opt " \
"--password=password -u user"
XTRA_RESTORE = "sudo xbstream -x -C /var/lib/mysql"
SQLDUMP_RESTORE = "mysql --password=password -u user"
PREPARE = "sudo innobackupex --apply-log /var/lib/mysql " \
"--defaults-file=/var/lib/mysql/backup-my.cnf " \
"--ibbackup xtrabackup 2>/tmp/innoprepare.log"
CRYPTO_KEY = "default_aes_cbc_key"
class GuestAgentBackupTest(testtools.TestCase):
def test_backup_decrypted_xtrabackup_command(self):
backupBase.BackupRunner.is_zipped = True
backupBase.BackupRunner.is_encrypted = False
RunnerClass = utils.import_class(BACKUP_XTRA_CLS)
bkup = RunnerClass(12345, user="user", password="password")
self.assertEqual(bkup.command, XTRA_BACKUP + PIPE + ZIP)
self.assertEqual(bkup.manifest, "12345.xbstream.gz")
def test_backup_encrypted_xtrabackup_command(self):
backupBase.BackupRunner.is_zipped = True
backupBase.BackupRunner.is_encrypted = True
backupBase.BackupRunner.encrypt_key = CRYPTO_KEY
RunnerClass = utils.import_class(BACKUP_XTRA_CLS)
bkup = RunnerClass(12345, user="user", password="password")
self.assertEqual(bkup.command,
XTRA_BACKUP + PIPE + ZIP + PIPE + ENCRYPT)
self.assertEqual(bkup.manifest, "12345.xbstream.gz.enc")
def test_backup_decrypted_mysqldump_command(self):
backupBase.BackupRunner.is_zipped = True
backupBase.BackupRunner.is_encrypted = False
RunnerClass = utils.import_class(BACKUP_SQLDUMP_CLS)
bkup = RunnerClass(12345, user="user", password="password")
self.assertEqual(bkup.command, SQLDUMP_BACKUP + PIPE + ZIP)
self.assertEqual(bkup.manifest, "12345.gz")
def test_backup_encrypted_mysqldump_command(self):
backupBase.BackupRunner.is_zipped = True
backupBase.BackupRunner.is_encrypted = True
backupBase.BackupRunner.encrypt_key = CRYPTO_KEY
RunnerClass = utils.import_class(BACKUP_SQLDUMP_CLS)
bkup = RunnerClass(12345, user="user", password="password")
self.assertEqual(bkup.command,
SQLDUMP_BACKUP + PIPE + ZIP + PIPE + ENCRYPT)
self.assertEqual(bkup.manifest, "12345.gz.enc")
def test_restore_decrypted_xtrabackup_command(self):
restoreBase.RestoreRunner.is_zipped = True
restoreBase.RestoreRunner.is_encrypted = False
RunnerClass = utils.import_class(RESTORE_XTRA_CLS)
restr = RunnerClass(None, restore_location="/var/lib/mysql")
self.assertEqual(restr.restore_cmd, UNZIP + PIPE + XTRA_RESTORE)
self.assertEqual(restr.prepare_cmd, PREPARE)
def test_restore_encrypted_xtrabackup_command(self):
restoreBase.RestoreRunner.is_zipped = True
restoreBase.RestoreRunner.is_encrypted = True
restoreBase.RestoreRunner.decrypt_key = CRYPTO_KEY
RunnerClass = utils.import_class(RESTORE_XTRA_CLS)
restr = RunnerClass(None, restore_location="/var/lib/mysql")
self.assertEqual(restr.restore_cmd,
DECRYPT + PIPE + UNZIP + PIPE + XTRA_RESTORE)
self.assertEqual(restr.prepare_cmd, PREPARE)
def test_restore_decrypted_mysqldump_command(self):
restoreBase.RestoreRunner.is_zipped = True
restoreBase.RestoreRunner.is_encrypted = False
RunnerClass = utils.import_class(RESTORE_SQLDUMP_CLS)
restr = RunnerClass(None, restore_location="/var/lib/mysql",
user="user", password="password")
self.assertEqual(restr.restore_cmd, UNZIP + PIPE + SQLDUMP_RESTORE)
self.assertIsNone(restr.prepare_cmd)
def test_restore_encrypted_mysqldump_command(self):
restoreBase.RestoreRunner.is_zipped = True
restoreBase.RestoreRunner.is_encrypted = True
restoreBase.RestoreRunner.decrypt_key = CRYPTO_KEY
RunnerClass = utils.import_class(RESTORE_SQLDUMP_CLS)
restr = RunnerClass(None, restore_location="/var/lib/mysql",
user="user", password="password")
self.assertEqual(restr.restore_cmd,
DECRYPT + PIPE + UNZIP + PIPE + SQLDUMP_RESTORE)
self.assertIsNone(restr.prepare_cmd)