diff --git a/README.md b/README.md index 62dff92..7930c01 100644 --- a/README.md +++ b/README.md @@ -370,6 +370,21 @@ zfs-autobackup will re-evaluate this on every run: As soon as a snapshot doesn't Snapshots on the source that still have to be send to the target wont be destroyed off course. (If the target still wants them, according to the target schedule) +## How zfs-autobackup handles encryption + +In normal operation datasets are transferred unaltered: + +* Source datasets that are encrypted will be send over as such and stay encrypted at the target side. (In ZFS this is called raw-mode) You dont need keys at the target side if you dont want to access the data. +* Source datasets that are plain will stay that way on the target. Even if the specified target-path IS encrypted. + +### Decrypting/encrypting + +If you want to alter the encryption-state of a dataset you have several options: + +* If you want to decrypt encrypted datasets before sending them, you should use the `--decrypt` option. Datasets will then be stored plain at the target. +* If you want to encrypt plain datasets when they are received, you should use the `--encrypt` option. Datasets will then be stored encrypted at the target. (Datasets that are already encrypted will still be sent over unaltered!) You are responsible for creating the target-path with encryption enabled. +* If you also want re-encrypt encrypted datasets with the target-side encryption you can use both options. + ## Tips * Use ```--debug``` if something goes wrong and you want to see the commands that are executed. This will also stop at the first error. diff --git a/tests/test_encryption.py b/tests/test_encryption.py index 432d1ea..ce4f4d8 100644 --- a/tests/test_encryption.py +++ b/tests/test_encryption.py @@ -1,8 +1,116 @@ -import unittest +from zfs_autobackup.CmdPipe import CmdPipe +from basetest import * +import time -class MyTestCase(unittest.TestCase): - def test_something(self): - self.assertEqual(True, False) -if __name__ == '__main__': - unittest.main() +class TestZfsEncryption(unittest2.TestCase): + + def setUp(self): + prepare_zpools() + + def prepare_encrypted_dataset(self, key, path, unload_key=False): + + # create encrypted source dataset + shelltest("echo {} > /tmp/zfstest.key".format(key)) + shelltest("zfs create -o keylocation=file:///tmp/zfstest.key -o keyformat=passphrase -o encryption=on {}".format(path)) + + if unload_key: + shelltest("zfs unmount {}".format(path)) + shelltest("zfs unload-key {}".format(path)) + + # r=shelltest("dd if=/dev/zero of=/test_source1/fs1/enc1/data.txt bs=200000 count=1") + + def test_raw(self): + """send encrypted data unaltered (standard operation)""" + + self.prepare_encrypted_dataset("11111111", "test_source1/fs1/encryptedsource") + self.prepare_encrypted_dataset("11111111", "test_source1/fs1/encryptedsourcekeyless", unload_key=True) # raw mode shouldn't need a key + self.prepare_encrypted_dataset("22222222", "test_target1/encryptedtarget") + + with patch('time.strftime', return_value="20101111000000"): + self.assertFalse(ZfsAutobackup("test test_target1 --verbose --no-progress --allow-empty".split(" ")).run()) + self.assertFalse(ZfsAutobackup("test test_target1/encryptedtarget --verbose --no-progress --no-snapshot".split(" ")).run()) + + with patch('time.strftime', return_value="20101111000001"): + self.assertFalse(ZfsAutobackup("test test_target1 --verbose --no-progress --allow-empty".split(" ")).run()) + self.assertFalse(ZfsAutobackup("test test_target1/encryptedtarget --verbose --no-progress --no-snapshot".split(" ")).run()) + + r = shelltest("zfs get -r -t filesystem encryptionroot test_target1") + self.assertMultiLineEqual(r,""" +NAME PROPERTY VALUE SOURCE +test_target1 encryptionroot - - +test_target1/encryptedtarget encryptionroot test_target1/encryptedtarget - +test_target1/encryptedtarget/test_source1 encryptionroot test_target1/encryptedtarget - +test_target1/encryptedtarget/test_source1/fs1 encryptionroot - - +test_target1/encryptedtarget/test_source1/fs1/encryptedsource encryptionroot test_target1/encryptedtarget/test_source1/fs1/encryptedsource - +test_target1/encryptedtarget/test_source1/fs1/encryptedsourcekeyless encryptionroot test_target1/encryptedtarget/test_source1/fs1/encryptedsourcekeyless - +test_target1/encryptedtarget/test_source1/fs1/sub encryptionroot - - +test_target1/encryptedtarget/test_source2 encryptionroot test_target1/encryptedtarget - +test_target1/encryptedtarget/test_source2/fs2 encryptionroot test_target1/encryptedtarget - +test_target1/encryptedtarget/test_source2/fs2/sub encryptionroot - - +test_target1/test_source1 encryptionroot - - +test_target1/test_source1/fs1 encryptionroot - - +test_target1/test_source1/fs1/encryptedsource encryptionroot test_target1/test_source1/fs1/encryptedsource - +test_target1/test_source1/fs1/encryptedsourcekeyless encryptionroot test_target1/test_source1/fs1/encryptedsourcekeyless - +test_target1/test_source1/fs1/sub encryptionroot - - +test_target1/test_source2 encryptionroot - - +test_target1/test_source2/fs2 encryptionroot - - +test_target1/test_source2/fs2/sub encryptionroot - - +""") + + def test_decrypt(self): + """decrypt data and store unencrypted (--decrypt)""" + + self.prepare_encrypted_dataset("11111111", "test_source1/fs1/encryptedsource") + self.prepare_encrypted_dataset("22222222", "test_target1/encryptedtarget") + + with patch('time.strftime', return_value="20101111000000"): + self.assertFalse(ZfsAutobackup("test test_target1 --verbose --no-progress --decrypt".split(" ")).run()) + self.assertFalse(ZfsAutobackup("test test_target1/encryptedtarget --verbose --no-progress --decrypt".split(" ")).run()) + + r = shelltest("zfs get -r -t filesystem encryptionroot test_target1") + self.assertEqual(r, """ +NAME PROPERTY VALUE SOURCE +test_target1 encryptionroot - - +test_target1/encryptedtarget encryptionroot test_target1/encryptedtarget - +test_target1/encryptedtarget/test_source1 encryptionroot test_target1/encryptedtarget - +test_target1/encryptedtarget/test_source1/fs1 encryptionroot - - +test_target1/encryptedtarget/test_source1/fs1/encryptedsource encryptionroot - - +test_target1/encryptedtarget/test_source1/fs1/sub encryptionroot - - +test_target1/encryptedtarget/test_source2 encryptionroot test_target1/encryptedtarget - +test_target1/encryptedtarget/test_source2/fs2 encryptionroot test_target1/encryptedtarget - +test_target1/encryptedtarget/test_source2/fs2/sub encryptionroot - - +test_target1/test_source1 encryptionroot - - +test_target1/test_source1/fs1 encryptionroot - - +test_target1/test_source1/fs1/encryptedsource encryptionroot - - +test_target1/test_source1/fs1/sub encryptionroot - - +test_target1/test_source2 encryptionroot - - +test_target1/test_source2/fs2 encryptionroot - - +test_target1/test_source2/fs2/sub encryptionroot - - +""") + + def test_encrypt(self): + """send normal data set and store encrypted on the other side (--encrypt) issue #60 """ + + self.prepare_encrypted_dataset("11111111", "test_source1/fs1/encryptedsource") + self.prepare_encrypted_dataset("22222222", "test_target1/encryptedtarget") + + with patch('time.strftime', return_value="20101111000000"): + self.assertFalse(ZfsAutobackup("test test_target1 --verbose --no-progress --encrypt".split(" ")).run()) + self.assertFalse(ZfsAutobackup("test test_target1/encryptedtarget --verbose --no-progress --encrypt".split(" ")).run()) + + r = shelltest("zfs get encryption -H -o value test_target1/test_source1/fs1/encryptedsource test_target1/encryptedtarget/test_source1/fs1/encryptedsource") + self.assertNotIn("off",r) + + + def test_reencrypt(self): + """decrypt data and reencrypt on the otherside (--decrypt --encrypt) """ + + # create encrypted target dataset + shelltest("echo 12345678 > /tmp/zfstest.key") + shelltest("zfs create -o keylocation=file:///tmp/zfstest.key -o keyformat=passphrase -o encryption=on test_target1/enc1") + + with patch('time.strftime', return_value="20101111000000"): + self.assertFalse(ZfsAutobackup("test test_target1/enc1 --allow-empty --verbose --no-progress".split(" ")).run()) + r = shelltest("zfs get encryption -H -o value test_target1/enc1/test_source1/fs1") + self.assertNotIn("off",r) diff --git a/tests/test_zfsautobackup.py b/tests/test_zfsautobackup.py index 34f79c2..587d576 100644 --- a/tests/test_zfsautobackup.py +++ b/tests/test_zfsautobackup.py @@ -880,35 +880,6 @@ test_target1/test_source2/fs2/sub test_target1/test_source2/fs2/sub@test-20101111000003 """) - ########################### -# TODO: - - def test_encrypted(self): - - # create encrypted dataset - shelltest("echo 12345678 > /tmp/zfstest.key") - shelltest("zfs create -o keylocation=file:///tmp/zfstest.key -o keyformat=passphrase -o encryption=on test_source1/fs1/enc1") - r=shelltest("dd if=/dev/zero of=/test_source1/fs1/enc1/data.txt bs=200000 count=1") - - with patch('time.strftime', return_value="20101111000000"): - self.assertFalse(ZfsAutobackup("test test_target1 --allow-empty --verbose --no-progress".split(" ")).run()) - # self.skipTest("todo: later when travis supports zfs 0.8") - r = shelltest("zfs get encryption -H -o value test_target1/test_source1/fs1/enc1") - self.assertNotIn("off",r) - - def test_decrypted(self): - - # create encrypted dataset - shelltest("echo 12345678 > /tmp/zfstest.key") - shelltest("zfs create -o keylocation=file:///tmp/zfstest.key -o keyformat=passphrase -o encryption=on test_source1/fs1/enc1") - r=shelltest("dd if=/dev/zero of=/test_source1/fs1/enc1/data.txt bs=200000 count=1") - - with patch('time.strftime', return_value="20101111000000"): - self.assertFalse(ZfsAutobackup("test test_target1 --decrypt --allow-empty --no-progress".split(" ")).run()) - # self.skipTest("todo: later when travis supports zfs 0.8") - r = shelltest("zfs get encryption -H -o value test_target1/test_source1/fs1/enc1") - self.assertIn("off",r) - def test_progress(self): diff --git a/zfs_autobackup/ZfsAutobackup.py b/zfs_autobackup/ZfsAutobackup.py index 4932e31..d353a48 100644 --- a/zfs_autobackup/ZfsAutobackup.py +++ b/zfs_autobackup/ZfsAutobackup.py @@ -93,7 +93,10 @@ class ZfsAutobackup: help=argparse.SUPPRESS) parser.add_argument('--decrypt', action='store_true', - help='Decrypt data before sending it over') + help='Decrypt data before sending it over.') + + parser.add_argument('--encrypt', action='store_true', + help='Encrypt data after receiving it.') parser.add_argument('--test', action='store_true', help='dont change anything, just show what would be done (still does all read-only '