fix encrypt-on-receive with placeholders

fixes https://github.com/zrepl/zrepl/issues/504

Problem:
  plain send + recv with root_fs encrypted + placeholders causes plain recvs
  whereas user would expect encrypt-on-recv
Reason:
  We create placeholder filesytems with -o encryption=off.
  Thus, children received below those placeholders won't inherit
  encryption of root_fs.
Fix:
  We'll have three values for `recv.placeholders.encryption: unspecified (default) | off | inherit`.
  When we create a placeholder, we will fail the operation if  `recv.placeholders.encryption = unspecified`.
  The exception is if the placeholder filesystem is to encode the client identity ($root_fs/$client_identity) in a pull job.
  Those are created in `inherit` mode if the config field is `unspecified` so that users who don't need
  placeholders are not bothered by these details.

Future Work:
  Automatically warn existing users of encrypt-on-recv about the problem
  if they are affected.
  The problem that I hit during implementation of this is that the
  `encryption` prop's `source` doesn't quite behave like other props:
  `source` is `default` for `encryption=off` and `-` when `encryption=on`.
  Hence, we can't use `source` to distinguish the following 2x2 cases:
  (1) placeholder created with explicit -o encryption=off
  (2) placeholder created without specifying -o encryption
  with
  (A) an encrypted parent at creation time
  (B) an unencrypted parent at creation time
This commit is contained in:
Christian Schwarz
2021-11-21 21:16:37 +01:00
parent c1e2c9826f
commit fb6a9be954
11 changed files with 587 additions and 55 deletions
+52 -5
View File
@@ -80,7 +80,15 @@ func ZFSGetFilesystemPlaceholderState(ctx context.Context, p *DatasetPath) (stat
return state, nil
}
func ZFSCreatePlaceholderFilesystem(ctx context.Context, fs *DatasetPath, parent *DatasetPath) (err error) {
//go:generate enumer -type=FilesystemPlaceholderCreateEncryptionValue -trimprefix=FilesystemPlaceholderCreateEncryption
type FilesystemPlaceholderCreateEncryptionValue int
const (
FilesystemPlaceholderCreateEncryptionInherit FilesystemPlaceholderCreateEncryptionValue = 1 << iota
FilesystemPlaceholderCreateEncryptionOff
)
func ZFSCreatePlaceholderFilesystem(ctx context.Context, fs *DatasetPath, parent *DatasetPath, encryption FilesystemPlaceholderCreateEncryptionValue) (err error) {
if fs.Length() == 1 {
return fmt.Errorf("cannot create %q: pools cannot be created with zfs create", fs.ToString())
}
@@ -90,11 +98,19 @@ func ZFSCreatePlaceholderFilesystem(ctx context.Context, fs *DatasetPath, parent
"-o", fmt.Sprintf("%s=%s", PlaceholderPropertyName, placeholderPropertyOn),
"-o", "mountpoint=none",
}
if parentEncrypted, err := ZFSGetEncryptionEnabled(ctx, parent.ToString()); err != nil {
return errors.Wrap(err, "cannot determine encryption support")
} else if parentEncrypted {
cmdline = append(cmdline, "-o", "encryption=off")
if !encryption.IsAFilesystemPlaceholderCreateEncryptionValue() {
panic(encryption)
}
switch encryption {
case FilesystemPlaceholderCreateEncryptionInherit:
// no-op
case FilesystemPlaceholderCreateEncryptionOff:
cmdline = append(cmdline, "-o", "encryption=off")
default:
panic(encryption)
}
cmdline = append(cmdline, fs.ToString())
cmd := zfscmd.CommandContext(ctx, ZFS_BINARY, cmdline...)
@@ -148,3 +164,34 @@ func ZFSMigrateHashBasedPlaceholderToCurrent(ctx context.Context, fs *DatasetPat
}
return &report, nil
}
func ZFSListPlaceholderFilesystemsWithAdditionalProps(ctx context.Context, root string, additionalProps []string) (map[string]*ZFSProperties, error) {
props := []string{PlaceholderPropertyName}
if len(additionalProps) > 0 {
props = append(props, additionalProps...)
}
propsByFS, err := zfsGetRecursive(ctx, root, -1, []string{"filesystem", "volume"}, props, SourceAny)
if err != nil {
return nil, errors.Wrapf(err, "cannot get placeholder filesystems under %q", root)
}
filtered := make(map[string]*ZFSProperties)
for fs, props := range propsByFS {
details := props.GetDetails(PlaceholderPropertyName)
if details.Source != SourceLocal {
continue
}
fsp, err := NewDatasetPath(fs)
if err != nil {
return nil, errors.Wrapf(err, "zfs get returned invalid dataset path %q", fs)
}
if !isLocalPlaceholderPropertyValuePlaceholder(fsp, details.Value) {
continue
}
filtered[fs] = props
}
return filtered, nil
}