mirror of https://github.com/digint/btrbk
btrbk: add openssl_enc encryption for raw targets; add system_urandom()
Example: Manually create a key: # KEYFILE=/some/secure/place/btrbk.key # dd if=/dev/urandom bs=1 count=32 | od -x -A n | tr -d "[:space:]" > $KEYFILE btrbk.conf: volume /mnt/btr_pool incremental no raw_target_encrypt openssl_enc openssl_ciphername aes-256-cbc openssl_iv_size 16 # NOTE: set to "no" if no IV is needed by the selected cipher openssl_keyfile /some/secure/place/btrbk.key subvolume home target raw ssh://cloud.example.com/backuppull/204/head
parent
251c2fb2a1
commit
de7628ac7c
|
@ -9,6 +9,7 @@ btrbk-current
|
|||
* Add "--preserve-snapshots" and "--preserve-backups" options.
|
||||
* Change raw backup format (sidecar file instead of uuid in file).
|
||||
* Honor target_preserve for raw targets (delete raw targets).
|
||||
* Add symmetric encryption for raw targets (close #157).
|
||||
* Do not run in "perl taint mode" by default: remove "perl -T" in
|
||||
hashbang; hardcode $PATH only if taint mode is enabled.
|
||||
* Remove "duration" column from transaction_log/transaction_syslog.
|
||||
|
|
83
btrbk
83
btrbk
|
@ -62,7 +62,7 @@ my $glob_match = qr/[0-9a-zA-Z_@\+\-\.\/\*]+/; # file_match plus '*'
|
|||
my $uuid_match = qr/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/;
|
||||
my $timestamp_postfix_match = qr/\.(?<YYYY>[0-9]{4})(?<MM>[0-9]{2})(?<DD>[0-9]{2})(T(?<hh>[0-9]{2})(?<mm>[0-9]{2})((?<ss>[0-9]{2})(?<zz>(Z|[+-][0-9]{4})))?)?(_(?<NN>[0-9]+))?/; # matches "YYYYMMDD[Thhmm[ss+0000]][_NN]"
|
||||
my $raw_postfix_match_DEPRECATED = qr/--(?<received_uuid>$uuid_match)(\@(?<parent_uuid>$uuid_match))?\.btrfs?(\.(?<compress>($compress_format_alt)))?(\.(?<encrypt>gpg))?(\.(?<split>split))?(\.(?<incomplete>part))?/; # matches ".btrfs_<received_uuid>[@<parent_uuid>][.gz|bz2|xz][.gpg][.split][.part]"
|
||||
my $raw_postfix_match = qr/\.btrfs(\.($compress_format_alt))?(\.gpg)?/; # matches ".btrfs[.gz|bz2|xz][.gpg]"
|
||||
my $raw_postfix_match = qr/\.btrfs(\.($compress_format_alt))?(\.(gpg|encrypted))?/; # matches ".btrfs[.gz|bz2|xz][.gpg|encrypted]"
|
||||
|
||||
my $group_match = qr/[a-zA-Z0-9_:-]+/;
|
||||
my $ssh_cipher_match = qr/[a-z0-9][a-z0-9@.-]+/;
|
||||
|
@ -105,11 +105,14 @@ my %config_options = (
|
|||
raw_target_compress => { default => undef, accept => [ "no", (keys %compression) ] },
|
||||
raw_target_compress_level => { default => "default", accept => [ "default" ], accept_numeric => 1 },
|
||||
raw_target_compress_threads => { default => "default", accept => [ "default" ], accept_numeric => 1 },
|
||||
raw_target_encrypt => { default => undef, accept => [ "no", "gpg" ] },
|
||||
raw_target_encrypt => { default => undef, accept => [ "no", "gpg", "openssl_enc" ] },
|
||||
raw_target_block_size => { default => "128K", accept_regexp => qr/^[0-9]+(kB|k|K|KiB|MB|M|MiB)?$/ },
|
||||
raw_target_split => { default => undef, accept => [ "no" ], accept_regexp => qr/^[0-9]+([kmgtpezyKMGTPEZY][bB]?)?$/ },
|
||||
gpg_keyring => { default => undef, accept_file => { absolute => 1 } },
|
||||
gpg_recipient => { default => undef, accept_regexp => qr/^[0-9a-zA-Z_@\+\-\.]+$/ },
|
||||
openssl_ciphername => { default => "aes-256-cbc", accept_regexp => qr/^[0-9a-zA-Z\-]+$/ },
|
||||
openssl_iv_size => { default => undef, accept => [ "no", accept_numeric => 1 ] },
|
||||
openssl_keyfile => { default => undef, accept_file => { absolute => 1 } },
|
||||
|
||||
group => { default => undef, accept_regexp => qr/^$group_match(\s*,\s*$group_match)*$/, split => qr/\s*,\s*/ },
|
||||
|
||||
|
@ -231,6 +234,8 @@ my %raw_info_sort = (
|
|||
compress => 9,
|
||||
split => 10,
|
||||
encrypt => 11,
|
||||
cipher => 12,
|
||||
iv => 13,
|
||||
);
|
||||
|
||||
my %url_cache; # map URL to btr_tree node
|
||||
|
@ -1379,6 +1384,14 @@ sub btrfs_send_to_file($$$;$$)
|
|||
$target_filename .= '.' . $compression{$compress->{key}}->{format};
|
||||
push @cmd_pipe, { compress => $compress }; # does nothing if already compressed by rsh_compress_out
|
||||
}
|
||||
if($encrypt) {
|
||||
$target_filename .= ($encrypt->{type} eq "gpg") ? '.gpg' : '.encrypted';
|
||||
}
|
||||
|
||||
# NOTE: $ret_vol_received must always be set when function returns!
|
||||
my $vol_received = vinfo_child($target, $target_filename);
|
||||
$$ret_vol_received = $vol_received if(ref $ret_vol_received);
|
||||
|
||||
if($encrypt) {
|
||||
$raw_info{encrypt} = $encrypt->{type};
|
||||
|
||||
|
@ -1393,7 +1406,6 @@ sub btrfs_send_to_file($$$;$$)
|
|||
# generation faster; however sometimes write operations are not
|
||||
# desired. This option can be used to achieve that with the cost
|
||||
# of slower random generation.
|
||||
$target_filename .= '.gpg';
|
||||
my @gpg_options = ( '--batch', '--no-tty', '--no-random-seed-file', '--trust-model', 'always' );
|
||||
push @gpg_options, ( '--compress-algo', 'none' ) if($compress); # NOTE: if --compress-algo is not set, gpg might still compress according to OpenPGP standard.
|
||||
push(@gpg_options, ( '--no-default-keyring', '--keyring', $encrypt->{keyring} )) if($encrypt->{keyring});
|
||||
|
@ -1404,6 +1416,35 @@ sub btrfs_send_to_file($$$;$$)
|
|||
compressed_ok => ($compress ? 1 : 0),
|
||||
};
|
||||
}
|
||||
elsif($encrypt->{type} eq "openssl_enc") {
|
||||
# encrypt using "openssl enc"
|
||||
$raw_info{cipher} = $encrypt->{ciphername};
|
||||
|
||||
# NOTE: iv is always generated locally!
|
||||
my $iv_size = $encrypt->{iv_size};
|
||||
my $iv;
|
||||
if($iv_size) {
|
||||
INFO "Generating iv for openssl encryption (cipher=$encrypt->{ciphername})";
|
||||
$iv = system_urandom($iv_size, 'hex');
|
||||
unless($iv) {
|
||||
ERROR "Failed generate IV for openssl_enc: $source->{PRINT}";
|
||||
return undef;
|
||||
}
|
||||
$raw_info{iv} = $iv;
|
||||
}
|
||||
|
||||
my @openssl_options = (
|
||||
'-' . $encrypt->{ciphername},
|
||||
'-K $(cat ' . $encrypt->{keyfile} . ')',
|
||||
);
|
||||
push @openssl_options, ('-iv', $iv) if($iv);
|
||||
|
||||
push @cmd_pipe, {
|
||||
cmd => [ 'openssl', 'enc', '-e', @openssl_options ],
|
||||
name => 'openssl_enc',
|
||||
compressed_ok => ($compress ? 1 : 0),
|
||||
};
|
||||
}
|
||||
else {
|
||||
die "Usupported encryption type (raw_target_encrypt)";
|
||||
}
|
||||
|
@ -1442,9 +1483,6 @@ sub btrfs_send_to_file($$$;$$)
|
|||
};
|
||||
}
|
||||
|
||||
my $vol_received = vinfo_child($target, $target_filename);
|
||||
$$ret_vol_received = $vol_received if(ref $ret_vol_received);
|
||||
|
||||
$raw_info{FILE} = $target_filename;
|
||||
$raw_info{RECEIVED_PARENT_UUID} = $parent_uuid if($parent_uuid);
|
||||
# disabled for now, as its not very useful and might leak information:
|
||||
|
@ -1786,6 +1824,36 @@ sub system_write_raw_info($$)
|
|||
}
|
||||
|
||||
|
||||
sub system_urandom($;$) {
|
||||
my $size = shift;
|
||||
my $format = shift || 'hex';
|
||||
die unless(($size > 0) && ($size <= 256)); # sanity check
|
||||
|
||||
unless(open(URANDOM, '<', '/dev/urandom')) {
|
||||
ERROR "Failed to open /dev/urandom: $!";
|
||||
return undef;
|
||||
}
|
||||
binmode URANDOM;
|
||||
my $rand;
|
||||
my $rlen = read(URANDOM, $rand, $size);
|
||||
close(FILE);
|
||||
unless(defined($rand) && ($rlen == $size)) {
|
||||
ERROR "Failed to read from /dev/urandom: $!";
|
||||
return undef;
|
||||
}
|
||||
|
||||
if($format eq 'hex') {
|
||||
my $hex = unpack('H*', $rand);
|
||||
die unless(length($hex) == ($size * 2)); # paranoia check
|
||||
return $hex;
|
||||
}
|
||||
elsif($format eq 'bin') {
|
||||
return $rand;
|
||||
}
|
||||
die "unsupported format";
|
||||
}
|
||||
|
||||
|
||||
sub btr_tree($$)
|
||||
{
|
||||
my $vol = shift;
|
||||
|
@ -2781,6 +2849,9 @@ sub config_encrypt_hash($$)
|
|||
type => $encrypt_type,
|
||||
keyring => config_key($config, "gpg_keyring"),
|
||||
recipient => config_key($config, "gpg_recipient"),
|
||||
iv_size => config_key($config, "openssl_iv_size"),
|
||||
ciphername => config_key($config, "openssl_ciphername"),
|
||||
keyfile => config_key($config, "openssl_keyfile"),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -428,18 +428,37 @@ raw_target_compress_level default|<number>
|
|||
.PP
|
||||
raw_target_compress_threads default|<number>
|
||||
.PP
|
||||
raw_target_encrypt gpg|no
|
||||
.PP
|
||||
raw_target_split <size>|no
|
||||
.PP
|
||||
raw_target_block_size <number> (defaults to 128K)
|
||||
.PP
|
||||
raw_target_encrypt gpg|openssl_enc|no
|
||||
.RE
|
||||
.PD
|
||||
.PP
|
||||
Additional options for "raw_target_encrypt gpg":
|
||||
.PP
|
||||
.RS 4
|
||||
gpg_keyring <file>
|
||||
.PD 0
|
||||
.PP
|
||||
gpg_recipient <name>
|
||||
.RE
|
||||
.PD
|
||||
.PP
|
||||
Additional options for "raw_target_encrypt openssl_enc" (\fIvery
|
||||
experimental\fR):
|
||||
.PP
|
||||
.RS 4
|
||||
openssl_ciphername <name> (defaults to "aes-256-cbc")
|
||||
.PD 0
|
||||
.PP
|
||||
openssl_iv_size <size-in-bytes>|no (depends on selected cipher)
|
||||
.PP
|
||||
openssl_keyfile <file>|no
|
||||
.RE
|
||||
.PD
|
||||
.PP
|
||||
Raw backups consist of two files: the main data file containing the
|
||||
btrfs send stream, and a sidecar file ".info" containing metadata:
|
||||
.RS 4
|
||||
|
|
Loading…
Reference in New Issue