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:
+85
-17
@@ -472,8 +472,20 @@ type ReceiverConfig struct {
|
||||
OverrideProperties map[zfsprop.Property]string
|
||||
|
||||
BandwidthLimit bandwidthlimit.Config
|
||||
|
||||
PlaceholderEncryption PlaceholderCreationEncryptionProperty
|
||||
}
|
||||
|
||||
//go:generate enumer -type=PlaceholderCreationEncryptionProperty -transform=kebab -trimprefix=PlaceholderCreationEncryptionProperty
|
||||
type PlaceholderCreationEncryptionProperty int
|
||||
|
||||
// Note: the constant names, transformed through enumer, are part of the config format!
|
||||
const (
|
||||
PlaceholderCreationEncryptionPropertyUnspecified PlaceholderCreationEncryptionProperty = 1 << iota
|
||||
PlaceholderCreationEncryptionPropertyInherit
|
||||
PlaceholderCreationEncryptionPropertyOff
|
||||
)
|
||||
|
||||
func (c *ReceiverConfig) copyIn() {
|
||||
c.RootWithoutClientComponent = c.RootWithoutClientComponent.Copy()
|
||||
|
||||
@@ -513,6 +525,10 @@ func (c *ReceiverConfig) Validate() error {
|
||||
return errors.Wrap(err, "`BandwidthLimit` field invalid")
|
||||
}
|
||||
|
||||
if !c.PlaceholderEncryption.IsAPlaceholderCreationEncryptionProperty() {
|
||||
return errors.Errorf("`PlaceholderEncryption` field is invalid")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -525,6 +541,8 @@ type Receiver struct {
|
||||
bwLimit bandwidthlimit.Wrapper
|
||||
|
||||
recvParentCreationMtx *chainlock.L
|
||||
|
||||
Test_OverrideClientIdentityFunc func() string // for use by platformtest
|
||||
}
|
||||
|
||||
func NewReceiver(config ReceiverConfig) *Receiver {
|
||||
@@ -562,9 +580,15 @@ func (s *Receiver) clientRootFromCtx(ctx context.Context) *zfs.DatasetPath {
|
||||
return s.conf.RootWithoutClientComponent.Copy()
|
||||
}
|
||||
|
||||
clientIdentity, ok := ctx.Value(ClientIdentityKey).(string)
|
||||
if !ok {
|
||||
panic("ClientIdentityKey context value must be set")
|
||||
var clientIdentity string
|
||||
if s.Test_OverrideClientIdentityFunc != nil {
|
||||
clientIdentity = s.Test_OverrideClientIdentityFunc()
|
||||
} else {
|
||||
var ok bool
|
||||
clientIdentity, ok = ctx.Value(ClientIdentityKey).(string) // no shadow
|
||||
if !ok {
|
||||
panic("ClientIdentityKey context value must be set")
|
||||
}
|
||||
}
|
||||
|
||||
clientRoot, err := clientRoot(s.conf.RootWithoutClientComponent, clientIdentity)
|
||||
@@ -605,7 +629,8 @@ func (s *Receiver) ListFilesystems(ctx context.Context, req *pdu.ListFilesystemR
|
||||
if rphs, err := zfs.ZFSGetFilesystemPlaceholderState(ctx, s.conf.RootWithoutClientComponent); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot determine whether root_fs exists")
|
||||
} else if !rphs.FSExists {
|
||||
return nil, errors.New("root_fs does not exist")
|
||||
getLogger(ctx).WithField("root_fs", s.conf.RootWithoutClientComponent).Error("root_fs does not exist")
|
||||
return nil, errors.Errorf("root_fs does not exist")
|
||||
}
|
||||
|
||||
root := s.clientRootFromCtx(ctx)
|
||||
@@ -714,6 +739,33 @@ func (s *Receiver) SendDry(ctx context.Context, r *pdu.SendReq) (*pdu.SendRes, e
|
||||
return nil, fmt.Errorf("receiver does not implement SendDry()")
|
||||
}
|
||||
|
||||
func (s *Receiver) receive_GetPlaceholderCreationEncryptionValue(client_root, path *zfs.DatasetPath) (zfs.FilesystemPlaceholderCreateEncryptionValue, error) {
|
||||
if !s.conf.PlaceholderEncryption.IsAPlaceholderCreationEncryptionProperty() {
|
||||
panic(s.conf.PlaceholderEncryption)
|
||||
}
|
||||
|
||||
if client_root.Equal(path) && s.conf.PlaceholderEncryption == PlaceholderCreationEncryptionPropertyUnspecified {
|
||||
// If our Receiver is configured to append a client component to s.conf.RootWithoutClientComponent
|
||||
// then that dataset is always going to be a placeholder.
|
||||
// We don't want to burden users with the concept of placeholders if their `filesystems` filter on the sender
|
||||
// doesn't introduce any gaps.
|
||||
// Since the dataset hierarchy up to and including that client component dataset is still fully controlled by us,
|
||||
// using `inherit` is going to make it work in all expected use cases.
|
||||
return zfs.FilesystemPlaceholderCreateEncryptionInherit, nil
|
||||
}
|
||||
|
||||
switch s.conf.PlaceholderEncryption {
|
||||
case PlaceholderCreationEncryptionPropertyUnspecified:
|
||||
return 0, fmt.Errorf("placeholder filesystem encryption handling is unspecified in receiver config")
|
||||
case PlaceholderCreationEncryptionPropertyInherit:
|
||||
return zfs.FilesystemPlaceholderCreateEncryptionInherit, nil
|
||||
case PlaceholderCreationEncryptionPropertyOff:
|
||||
return zfs.FilesystemPlaceholderCreateEncryptionOff, nil
|
||||
default:
|
||||
panic(s.conf.PlaceholderEncryption)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Receiver) Receive(ctx context.Context, req *pdu.ReceiveReq, receive io.ReadCloser) (*pdu.ReceiveRes, error) {
|
||||
defer trace.WithSpanFromStackUpdateCtx(&ctx)()
|
||||
|
||||
@@ -754,15 +806,18 @@ func (s *Receiver) Receive(ctx context.Context, req *pdu.ReceiveReq, receive io.
|
||||
if v.Path.Equal(lp) {
|
||||
return false
|
||||
}
|
||||
|
||||
l := getLogger(ctx).
|
||||
WithField("placeholder_fs", v.Path.ToString()).
|
||||
WithField("receive_fs", lp.ToString())
|
||||
|
||||
ph, err := zfs.ZFSGetFilesystemPlaceholderState(ctx, v.Path)
|
||||
getLogger(ctx).
|
||||
WithField("fs", v.Path.ToString()).
|
||||
WithField("placeholder_state", fmt.Sprintf("%#v", ph)).
|
||||
l.WithField("placeholder_state", fmt.Sprintf("%#v", ph)).
|
||||
WithField("err", fmt.Sprintf("%s", err)).
|
||||
WithField("errType", fmt.Sprintf("%T", err)).
|
||||
Debug("placeholder state for filesystem")
|
||||
Debug("get placeholder state for filesystem")
|
||||
if err != nil {
|
||||
visitErr = err
|
||||
visitErr = errors.Wrapf(err, "cannot get placeholder state of %s", v.Path.ToString())
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -773,21 +828,34 @@ func (s *Receiver) Receive(ctx context.Context, req *pdu.ReceiveReq, receive io.
|
||||
} else {
|
||||
visitErr = fmt.Errorf("root_fs %q does not exist", s.conf.RootWithoutClientComponent.ToString())
|
||||
}
|
||||
getLogger(ctx).WithError(visitErr).Error("placeholders are only created automatically below root_fs")
|
||||
l.WithError(visitErr).Error("placeholders are only created automatically below root_fs")
|
||||
return false
|
||||
}
|
||||
l := getLogger(ctx).WithField("placeholder_fs", v.Path)
|
||||
l.Debug("create placeholder filesystem")
|
||||
err := zfs.ZFSCreatePlaceholderFilesystem(ctx, v.Path, v.Parent.Path)
|
||||
|
||||
// compute the value lazily so that users who don't rely on
|
||||
// placeholders can use the default value PlaceholderCreationEncryptionPropertyUnspecified
|
||||
placeholderEncryption, err := s.receive_GetPlaceholderCreationEncryptionValue(root, v.Path)
|
||||
if err != nil {
|
||||
l.WithError(err).Error("cannot create placeholder filesystem")
|
||||
visitErr = err
|
||||
l.WithError(err).Error("cannot create placeholder filesystem") // logger already contains path
|
||||
visitErr = errors.Wrapf(err, "cannot create placeholder filesystem %s", v.Path.ToString())
|
||||
return false
|
||||
}
|
||||
|
||||
l := l.WithField("encryption", placeholderEncryption)
|
||||
|
||||
l.Debug("creating placeholder filesystem")
|
||||
err = zfs.ZFSCreatePlaceholderFilesystem(ctx, v.Path, v.Parent.Path, placeholderEncryption)
|
||||
if err != nil {
|
||||
l.WithError(err).Error("cannot create placeholder filesystem") // logger already contains path
|
||||
visitErr = errors.Wrapf(err, "cannot create placeholder filesystem %s", v.Path.ToString())
|
||||
return false
|
||||
}
|
||||
l.Info("created placeholder filesystem")
|
||||
return true
|
||||
} else {
|
||||
l.Debug("filesystem exists")
|
||||
return true // leave this fs as is
|
||||
}
|
||||
getLogger(ctx).WithField("filesystem", v.Path.ToString()).Debug("exists")
|
||||
return true // leave this fs as is
|
||||
})
|
||||
}()
|
||||
getLogger(ctx).WithField("visitErr", visitErr).Debug("complete tree-walk")
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Code generated by "enumer -type=PlaceholderCreationEncryptionProperty -transform=kebab -trimprefix=PlaceholderCreationEncryptionProperty"; DO NOT EDIT.
|
||||
|
||||
//
|
||||
package endpoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
_PlaceholderCreationEncryptionPropertyName_0 = "unspecifiedinherit"
|
||||
_PlaceholderCreationEncryptionPropertyName_1 = "off"
|
||||
)
|
||||
|
||||
var (
|
||||
_PlaceholderCreationEncryptionPropertyIndex_0 = [...]uint8{0, 11, 18}
|
||||
_PlaceholderCreationEncryptionPropertyIndex_1 = [...]uint8{0, 3}
|
||||
)
|
||||
|
||||
func (i PlaceholderCreationEncryptionProperty) String() string {
|
||||
switch {
|
||||
case 1 <= i && i <= 2:
|
||||
i -= 1
|
||||
return _PlaceholderCreationEncryptionPropertyName_0[_PlaceholderCreationEncryptionPropertyIndex_0[i]:_PlaceholderCreationEncryptionPropertyIndex_0[i+1]]
|
||||
case i == 4:
|
||||
return _PlaceholderCreationEncryptionPropertyName_1
|
||||
default:
|
||||
return fmt.Sprintf("PlaceholderCreationEncryptionProperty(%d)", i)
|
||||
}
|
||||
}
|
||||
|
||||
var _PlaceholderCreationEncryptionPropertyValues = []PlaceholderCreationEncryptionProperty{1, 2, 4}
|
||||
|
||||
var _PlaceholderCreationEncryptionPropertyNameToValueMap = map[string]PlaceholderCreationEncryptionProperty{
|
||||
_PlaceholderCreationEncryptionPropertyName_0[0:11]: 1,
|
||||
_PlaceholderCreationEncryptionPropertyName_0[11:18]: 2,
|
||||
_PlaceholderCreationEncryptionPropertyName_1[0:3]: 4,
|
||||
}
|
||||
|
||||
// PlaceholderCreationEncryptionPropertyString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func PlaceholderCreationEncryptionPropertyString(s string) (PlaceholderCreationEncryptionProperty, error) {
|
||||
if val, ok := _PlaceholderCreationEncryptionPropertyNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to PlaceholderCreationEncryptionProperty values", s)
|
||||
}
|
||||
|
||||
// PlaceholderCreationEncryptionPropertyValues returns all values of the enum
|
||||
func PlaceholderCreationEncryptionPropertyValues() []PlaceholderCreationEncryptionProperty {
|
||||
return _PlaceholderCreationEncryptionPropertyValues
|
||||
}
|
||||
|
||||
// IsAPlaceholderCreationEncryptionProperty returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i PlaceholderCreationEncryptionProperty) IsAPlaceholderCreationEncryptionProperty() bool {
|
||||
for _, v := range _PlaceholderCreationEncryptionPropertyValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user