diff options
author | Tom Caputi <[email protected]> | 2017-10-13 13:09:04 -0400 |
---|---|---|
committer | Brian Behlendorf <[email protected]> | 2018-08-15 09:48:49 -0700 |
commit | d9c460a0b659c044d4397b7405712f2c9450d3c4 (patch) | |
tree | 973b92b7f835540f5ee722b56ff838828fd53116 | |
parent | fe8a7982ca90c3c9b8a09ec33f032527d7034a7b (diff) |
Added encryption support for zfs recv -o / -x
One small integration that was absent from b52563 was
support for zfs recv -o / -x with regards to encryption
parameters. The main use cases of this are as follows:
* Receiving an unencrypted stream as encrypted without
needing to create a "dummy" encrypted parent so that
encryption can be inheritted.
* Allowing users to change their keylocation on receive,
so long as the receiving dataset is an encryption root.
* Allowing users to explicitly exclude or override the
encryption property from an unencrypted properties stream,
allowing it to be received as encrypted.
* Receiving a recursive heirarchy of unencrypted datasets,
encrypting the top-level one and forcing all children to
inherit the encryption.
Reviewed-by: Jorgen Lundman <[email protected]>
Reviewed by: Matthew Ahrens <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Richard Elling <[email protected]>
Signed-off-by: Tom Caputi <[email protected]>
Closes #7650
-rw-r--r-- | contrib/pyzfs/libzfs_core/_libzfs_core.py | 16 | ||||
-rw-r--r-- | contrib/pyzfs/libzfs_core/bindings/libzfs_core.py | 6 | ||||
-rw-r--r-- | include/libzfs.h | 2 | ||||
-rw-r--r-- | include/libzfs_core.h | 2 | ||||
-rw-r--r-- | include/sys/dmu_send.h | 6 | ||||
-rw-r--r-- | lib/libzfs/libzfs_crypto.c | 14 | ||||
-rw-r--r-- | lib/libzfs/libzfs_dataset.c | 4 | ||||
-rw-r--r-- | lib/libzfs/libzfs_pool.c | 2 | ||||
-rw-r--r-- | lib/libzfs/libzfs_sendrecv.c | 95 | ||||
-rw-r--r-- | lib/libzfs_core/libzfs_core.c | 53 | ||||
-rw-r--r-- | man/man8/zfs.8 | 28 | ||||
-rw-r--r-- | module/zfs/dmu_send.c | 93 | ||||
-rw-r--r-- | module/zfs/dsl_crypt.c | 4 | ||||
-rw-r--r-- | module/zfs/zfs_ioctl.c | 70 | ||||
-rw-r--r-- | tests/runfiles/linux.run | 5 | ||||
-rw-r--r-- | tests/zfs-tests/tests/functional/rsend/Makefile.am | 1 | ||||
-rwxr-xr-x | tests/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh | 199 |
17 files changed, 507 insertions, 93 deletions
diff --git a/contrib/pyzfs/libzfs_core/_libzfs_core.py b/contrib/pyzfs/libzfs_core/_libzfs_core.py index 1e38a3f32..ffc930812 100644 --- a/contrib/pyzfs/libzfs_core/_libzfs_core.py +++ b/contrib/pyzfs/libzfs_core/_libzfs_core.py @@ -1303,7 +1303,8 @@ def lzc_receive_one( @_uncommitted() def lzc_receive_with_cmdprops( snapname, fd, begin_record, force=False, resumable=False, raw=False, - origin=None, props=None, cmdprops=None, cleanup_fd=-1, action_handle=0 + origin=None, props=None, cmdprops=None, key=None, cleanup_fd=-1, + action_handle=0 ): ''' Like :func:`lzc_receive_one`, but allows the caller to pass an additional @@ -1333,6 +1334,8 @@ def lzc_receive_with_cmdprops( every other value is set locally as if the command "zfs set" was invoked immediately before the receive. :type cmdprops: dict of bytes : Any + :param key: raw bytes representing user's wrapping key + :type key: bytes :param int cleanup_fd: file descriptor used to set a cleanup-on-exit file descriptor. :param int action_handle: variable used to pass the handle for guid/ds @@ -1400,14 +1403,19 @@ def lzc_receive_with_cmdprops( props = {} if cmdprops is None: cmdprops = {} + if key is None: + key = bytes("") + else: + key = bytes(key) + nvlist = nvlist_in(props) cmdnvlist = nvlist_in(cmdprops) properrs = {} with nvlist_out(properrs) as c_errors: ret = _lib.lzc_receive_with_cmdprops( - snapname, nvlist, cmdnvlist, c_origin, force, resumable, raw, fd, - begin_record, cleanup_fd, c_read_bytes, c_errflags, - c_action_handle, c_errors) + snapname, nvlist, cmdnvlist, key, len(key), c_origin, + force, resumable, raw, fd, begin_record, cleanup_fd, c_read_bytes, + c_errflags, c_action_handle, c_errors) errors.lzc_receive_translate_errors( ret, snapname, fd, force, raw, False, False, origin, properrs) return (int(c_read_bytes[0]), action_handle) diff --git a/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py b/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py index a67a01ee7..55899b556 100644 --- a/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py +++ b/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py @@ -108,9 +108,9 @@ CDEF = """ int lzc_receive_resumable(const char *, nvlist_t *, const char *, boolean_t, boolean_t, int); int lzc_receive_with_cmdprops(const char *, nvlist_t *, nvlist_t *, - const char *, boolean_t, boolean_t, boolean_t, int, - const dmu_replay_record_t *, int, uint64_t *, uint64_t *, uint64_t *, - nvlist_t **); + uint8_t *, uint_t, const char *, boolean_t, boolean_t, + boolean_t, int, const dmu_replay_record_t *, int, uint64_t *, + uint64_t *, uint64_t *, nvlist_t **); int lzc_receive_with_header(const char *, nvlist_t *, const char *, boolean_t, boolean_t, boolean_t, int, const dmu_replay_record_t *); int lzc_release(nvlist_t *, nvlist_t **); diff --git a/include/libzfs.h b/include/libzfs.h index c0c0f3c3c..5c157bbf3 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -527,7 +527,7 @@ extern nvlist_t *zfs_get_clones_nvl(zfs_handle_t *); */ extern int zfs_crypto_get_encryption_root(zfs_handle_t *, boolean_t *, char *); extern int zfs_crypto_create(libzfs_handle_t *, char *, nvlist_t *, nvlist_t *, - uint8_t **, uint_t *); + boolean_t stdin_available, uint8_t **, uint_t *); extern int zfs_crypto_clone_check(libzfs_handle_t *, zfs_handle_t *, char *, nvlist_t *); extern int zfs_crypto_attempt_load_keys(libzfs_handle_t *, char *); diff --git a/include/libzfs_core.h b/include/libzfs_core.h index 4ca9b254c..c22cbf18e 100644 --- a/include/libzfs_core.h +++ b/include/libzfs_core.h @@ -93,7 +93,7 @@ int lzc_receive_one(const char *, nvlist_t *, const char *, boolean_t, boolean_t, boolean_t, int, const struct dmu_replay_record *, int, uint64_t *, uint64_t *, uint64_t *, nvlist_t **); int lzc_receive_with_cmdprops(const char *, nvlist_t *, nvlist_t *, - const char *, boolean_t, boolean_t, boolean_t, int, + uint8_t *, uint_t, const char *, boolean_t, boolean_t, boolean_t, int, const struct dmu_replay_record *, int, uint64_t *, uint64_t *, uint64_t *, nvlist_t **); diff --git a/include/sys/dmu_send.h b/include/sys/dmu_send.h index c0b2aafdb..396710470 100644 --- a/include/sys/dmu_send.h +++ b/include/sys/dmu_send.h @@ -30,6 +30,7 @@ #define _DMU_SEND_H #include <sys/inttypes.h> +#include <sys/dsl_crypt.h> #include <sys/spa.h> struct vnode; @@ -72,8 +73,9 @@ typedef struct dmu_recv_cookie { } dmu_recv_cookie_t; int dmu_recv_begin(char *tofs, char *tosnap, - struct dmu_replay_record *drr_begin, - boolean_t force, boolean_t resumable, char *origin, dmu_recv_cookie_t *drc); + struct dmu_replay_record *drr_begin, boolean_t force, boolean_t resumable, + nvlist_t *localprops, nvlist_t *hidden_args, char *origin, + dmu_recv_cookie_t *drc); int dmu_recv_stream(dmu_recv_cookie_t *drc, struct vnode *vp, offset_t *voffp, int cleanup_fd, uint64_t *action_handlep); int dmu_recv_end(dmu_recv_cookie_t *drc, void *owner); diff --git a/lib/libzfs/libzfs_crypto.c b/lib/libzfs/libzfs_crypto.c index 0956466e2..3318a6bd2 100644 --- a/lib/libzfs/libzfs_crypto.c +++ b/lib/libzfs/libzfs_crypto.c @@ -667,7 +667,8 @@ zfs_crypto_get_encryption_root(zfs_handle_t *zhp, boolean_t *is_encroot, int zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props, - nvlist_t *pool_props, uint8_t **wkeydata_out, uint_t *wkeylen_out) + nvlist_t *pool_props, boolean_t stdin_available, uint8_t **wkeydata_out, + uint_t *wkeylen_out) { int ret; char errbuf[1024]; @@ -808,6 +809,17 @@ zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props, * encryption root. Populate the encryption params. */ if (keylocation != NULL) { + /* + * 'zfs recv -o keylocation=prompt' won't work because stdin + * is being used by the send stream, so we disallow it. + */ + if (!stdin_available && strcmp(keylocation, "prompt") == 0) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "Cannot use " + "'prompt' keylocation because stdin is in use.")); + goto out; + } + ret = populate_create_encryption_params_nvlists(hdl, NULL, B_FALSE, keyformat, keylocation, props, &wkeydata, &wkeylen); diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index 7f7dd1594..85b4c5531 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -3731,8 +3731,8 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, } (void) parent_name(path, parent, sizeof (parent)); - if (zfs_crypto_create(hdl, parent, props, NULL, &wkeydata, - &wkeylen) != 0) { + if (zfs_crypto_create(hdl, parent, props, NULL, B_TRUE, + &wkeydata, &wkeylen) != 0) { nvlist_free(props); return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); } diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index d19ca7714..0785c3170 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -1224,7 +1224,7 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot, (nvlist_alloc(&zc_props, NV_UNIQUE_NAME, 0) != 0)) { goto create_failed; } - if (zfs_crypto_create(hdl, NULL, zc_fsprops, props, + if (zfs_crypto_create(hdl, NULL, zc_fsprops, props, B_TRUE, &wkeydata, &wkeylen) != 0) { zfs_error(hdl, EZFS_CRYPTOFAILED, msg); goto create_failed; diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index 9a4f373a1..b5c91ec20 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -3461,16 +3461,19 @@ recv_ecksum_set_aux(libzfs_handle_t *hdl, const char *target_snap, * oxprops: valid output override (-o) and excluded (-x) properties */ static int -zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned, - boolean_t recursive, boolean_t toplevel, nvlist_t *recvprops, - nvlist_t *cmdprops, nvlist_t *origprops, nvlist_t **oxprops, - const char *errbuf) +zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, + char *fsname, boolean_t zoned, boolean_t recursive, boolean_t newfs, + boolean_t raw, boolean_t toplevel, nvlist_t *recvprops, nvlist_t *cmdprops, + nvlist_t *origprops, nvlist_t **oxprops, uint8_t **wkeydata_out, + uint_t *wkeylen_out, const char *errbuf) { nvpair_t *nvp; nvlist_t *oprops, *voprops; zfs_handle_t *zhp = NULL; zpool_handle_t *zpool_hdl = NULL; + char *cp; int ret = 0; + char namebuf[ZFS_MAX_DATASET_NAME_LEN]; if (nvlist_empty(cmdprops)) return (0); /* No properties to override or exclude */ @@ -3478,6 +3481,33 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned, *oxprops = fnvlist_alloc(); oprops = fnvlist_alloc(); + strlcpy(namebuf, fsname, ZFS_MAX_DATASET_NAME_LEN); + + /* + * Get our dataset handle. The target dataset may not exist yet. + */ + if (zfs_dataset_exists(hdl, namebuf, ZFS_TYPE_DATASET)) { + zhp = zfs_open(hdl, namebuf, ZFS_TYPE_DATASET); + if (zhp == NULL) { + ret = -1; + goto error; + } + } + + /* open the zpool handle */ + cp = strchr(namebuf, '/'); + if (cp != NULL) + *cp = '\0'; + zpool_hdl = zpool_open(hdl, namebuf); + if (zpool_hdl == NULL) { + ret = -1; + goto error; + } + + /* restore namebuf to match fsname for later use */ + if (cp != NULL) + *cp = '/'; + /* * first iteration: process excluded (-x) properties now and gather * added (-o) properties to be later processed by zfs_valid_proplist() @@ -3509,6 +3539,17 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned, goto error; } + /* raw streams can't override encryption properties */ + if ((zfs_prop_encryption_key_param(prop) || + prop == ZFS_PROP_ENCRYPTION) && (raw || !newfs)) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "encryption property '%s' cannot " + "be set or excluded for raw or incremental " + "streams."), name); + ret = zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + switch (nvpair_type(nvp)) { case DATA_TYPE_BOOLEAN: /* -x property */ /* @@ -3559,6 +3600,21 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned, goto error; } + /* + * zfs_crypto_create() requires the parent name. Get it + * by truncating the fsname copy stored in namebuf. + */ + cp = strrchr(namebuf, '/'); + if (cp != NULL) + *cp = '\0'; + + if (!raw && zfs_crypto_create(hdl, namebuf, voprops, NULL, + B_FALSE, wkeydata_out, wkeylen_out) != 0) { + fnvlist_free(voprops); + ret = zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf); + goto error; + } + /* second pass: process "-o" properties */ fnvlist_merge(*oxprops, voprops); fnvlist_free(voprops); @@ -3572,6 +3628,10 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned, } error: + if (zhp != NULL) + zfs_close(zhp); + if (zpool_hdl != NULL) + zpool_close(zpool_hdl); fnvlist_free(oprops); return (ret); } @@ -3615,6 +3675,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, boolean_t toplevel = B_FALSE; boolean_t zoned = B_FALSE; boolean_t hastoken = B_FALSE; + uint8_t *wkeydata = NULL; + uint_t wkeylen = 0; begin_time = time(NULL); bzero(origin, MAXNAMELEN); @@ -4001,9 +4063,13 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, * locally set on the send side would not be received correctly. * We can infer encryption=off if the stream is not raw and * properties were included since the send side will only ever - * send the encryption property in a raw nvlist header. + * send the encryption property in a raw nvlist header. This + * check will be avoided if the user specifically overrides + * the encryption property on the command line. */ - if (!raw && rcvprops != NULL) { + if (!raw && rcvprops != NULL && + !nvlist_exists(cmdprops, + zfs_prop_to_name(ZFS_PROP_ENCRYPTION))) { uint64_t crypt; zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET); @@ -4053,13 +4119,15 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, err = zfs_error(hdl, EZFS_BADSTREAM, errbuf); goto out; } - if ((err = zfs_setup_cmdline_props(hdl, type, zoned, recursive, - toplevel, rcvprops, cmdprops, origprops, &oxprops, errbuf)) != 0) + if ((err = zfs_setup_cmdline_props(hdl, type, name, zoned, recursive, + stream_wantsnewfs, raw, toplevel, rcvprops, cmdprops, origprops, + &oxprops, &wkeydata, &wkeylen, errbuf)) != 0) goto out; - err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops, oxprops, - origin, flags->force, flags->resumable, raw, infd, drr_noswap, - cleanup_fd, &read_bytes, &errflags, action_handlep, &prop_errors); + err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops, + oxprops, wkeydata, wkeylen, origin, flags->force, flags->resumable, + raw, infd, drr_noswap, cleanup_fd, &read_bytes, &errflags, + action_handlep, &prop_errors); ioctl_errno = ioctl_err; prop_errflags = errflags; @@ -4346,6 +4414,11 @@ zfs_receive_checkprops(libzfs_handle_t *hdl, nvlist_t *props, if (prop == ZFS_PROP_ORIGIN) continue; + /* encryption params have their own verification later */ + if (prop == ZFS_PROP_ENCRYPTION || + zfs_prop_encryption_key_param(prop)) + continue; + /* * cannot override readonly, set-once and other specific * settable properties diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c index ab2b70519..3a0b02690 100644 --- a/lib/libzfs_core/libzfs_core.c +++ b/lib/libzfs_core/libzfs_core.c @@ -662,8 +662,9 @@ recv_read(int fd, void *buf, int ilen) */ static int recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops, - const char *origin, boolean_t force, boolean_t resumable, boolean_t raw, - int input_fd, const dmu_replay_record_t *begin_record, int cleanup_fd, + uint8_t *wkeydata, uint_t wkeylen, const char *origin, boolean_t force, + boolean_t resumable, boolean_t raw, int input_fd, + const dmu_replay_record_t *begin_record, int cleanup_fd, uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle, nvlist_t **errors) { @@ -703,7 +704,11 @@ recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops, drr = *begin_record; } - if (resumable || raw) { + /* + * Raw receives, resumable receives, and receives that include a + * wrapping key all use the new interface. + */ + if (resumable || raw || wkeydata != NULL) { nvlist_t *outnvl = NULL; nvlist_t *innvl = fnvlist_alloc(); @@ -715,6 +720,20 @@ recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops, if (localprops != NULL) fnvlist_add_nvlist(innvl, "localprops", localprops); + if (wkeydata != NULL) { + /* + * wkeydata must be placed in the special + * ZPOOL_HIDDEN_ARGS nvlist so that it + * will not be printed to the zpool history. + */ + nvlist_t *hidden_args = fnvlist_alloc(); + fnvlist_add_uint8_array(hidden_args, "wkeydata", + wkeydata, wkeylen); + fnvlist_add_nvlist(innvl, ZPOOL_HIDDEN_ARGS, + hidden_args); + nvlist_free(hidden_args); + } + if (origin != NULL && strlen(origin)) fnvlist_add_string(innvl, "origin", origin); @@ -846,8 +865,8 @@ int lzc_receive(const char *snapname, nvlist_t *props, const char *origin, boolean_t force, boolean_t raw, int fd) { - return (recv_impl(snapname, props, NULL, origin, force, B_FALSE, raw, - fd, NULL, -1, NULL, NULL, NULL, NULL)); + return (recv_impl(snapname, props, NULL, NULL, 0, origin, force, + B_FALSE, raw, fd, NULL, -1, NULL, NULL, NULL, NULL)); } /* @@ -860,8 +879,8 @@ int lzc_receive_resumable(const char *snapname, nvlist_t *props, const char *origin, boolean_t force, boolean_t raw, int fd) { - return (recv_impl(snapname, props, NULL, origin, force, B_TRUE, raw, - fd, NULL, -1, NULL, NULL, NULL, NULL)); + return (recv_impl(snapname, props, NULL, NULL, 0, origin, force, + B_TRUE, raw, fd, NULL, -1, NULL, NULL, NULL, NULL)); } /* @@ -883,8 +902,8 @@ lzc_receive_with_header(const char *snapname, nvlist_t *props, if (begin_record == NULL) return (EINVAL); - return (recv_impl(snapname, props, NULL, origin, force, resumable, raw, - fd, begin_record, -1, NULL, NULL, NULL, NULL)); + return (recv_impl(snapname, props, NULL, NULL, 0, origin, force, + resumable, raw, fd, begin_record, -1, NULL, NULL, NULL, NULL)); } /* @@ -913,9 +932,9 @@ int lzc_receive_one(const char *snapname, nvlist_t *props, uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle, nvlist_t **errors) { - return (recv_impl(snapname, props, NULL, origin, force, resumable, - raw, input_fd, begin_record, cleanup_fd, read_bytes, errflags, - action_handle, errors)); + return (recv_impl(snapname, props, NULL, NULL, 0, origin, force, + resumable, raw, input_fd, begin_record, cleanup_fd, read_bytes, + errflags, action_handle, errors)); } /* @@ -927,15 +946,15 @@ int lzc_receive_one(const char *snapname, nvlist_t *props, * this nvlist */ int lzc_receive_with_cmdprops(const char *snapname, nvlist_t *props, - nvlist_t *cmdprops, const char *origin, boolean_t force, - boolean_t resumable, boolean_t raw, int input_fd, + nvlist_t *cmdprops, uint8_t *wkeydata, uint_t wkeylen, const char *origin, + boolean_t force, boolean_t resumable, boolean_t raw, int input_fd, const dmu_replay_record_t *begin_record, int cleanup_fd, uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle, nvlist_t **errors) { - return (recv_impl(snapname, props, cmdprops, origin, force, resumable, - raw, input_fd, begin_record, cleanup_fd, read_bytes, errflags, - action_handle, errors)); + return (recv_impl(snapname, props, cmdprops, wkeydata, wkeylen, origin, + force, resumable, raw, input_fd, begin_record, cleanup_fd, + read_bytes, errflags, action_handle, errors)); } /* diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index f1eed207f..e356fc196 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -3912,6 +3912,34 @@ results if the same property is specified in multiple or .Fl x options. +.Pp +The +.Fl o +option may also be used to override encryption properties upon initial +receive. This allows unencrypted streams to be received as encrypted datasets. +To cause the received dataset (or root dataset of a recursive stream) to be +received as an encryption root, specify encryption properties in the same +manner as is required for +.Nm +.Cm create . +For instance: +.Bd -literal +# zfs send tank/test@snap1 | zfs recv -o encryption=on -o keyformat=passphrase -o keylocation=file:///path/to/keyfile +.Ed +.Pp +Note that +.Op Fl o Ar keylocation Ns = Ns Ar prompt +may not be specified here, since stdin is already being utilized for the send +stream. Once the receive has completed, you can use +.Nm +.Cm set +to change this setting after the fact. Similarly, you can receive a dataset as +an encrypted child by specifying +.Op Fl x Ar encryption +to force the property to be inherited. Overriding encryption properties (except +for +.Sy keylocation Ns ) +is not possible with raw send streams. .It Fl s If the receive is interrupted, save the partially received state, rather than deleting it. diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c index ded086087..80a4843b3 100644 --- a/module/zfs/dmu_send.c +++ b/module/zfs/dmu_send.c @@ -1526,18 +1526,17 @@ typedef struct dmu_recv_begin_arg { const char *drba_origin; dmu_recv_cookie_t *drba_cookie; cred_t *drba_cred; + dsl_crypto_params_t *drba_dcp; uint64_t drba_snapobj; } dmu_recv_begin_arg_t; static int recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds, - uint64_t fromguid) + uint64_t fromguid, uint64_t featureflags) { uint64_t val; int error; dsl_pool_t *dp = ds->ds_dir->dd_pool; - struct drr_begin *drrb = drba->drba_cookie->drc_drrb; - uint64_t featureflags = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo); boolean_t encrypted = ds->ds_dir->dd_crypto_obj != 0; boolean_t raw = (featureflags & DMU_BACKUP_FEATURE_RAW) != 0; @@ -1624,6 +1623,13 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds, if ((!encrypted && raw) || encrypted) return (SET_ERROR(EINVAL)); + if ((featureflags & DMU_BACKUP_FEATURE_RAW) == 0) { + error = dmu_objset_create_crypt_check( + ds->ds_dir->dd_parent, drba->drba_dcp); + if (error != 0) + return (error); + } + drba->drba_snapobj = 0; } @@ -1690,7 +1696,7 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx) !spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_LARGE_DNODE)) return (SET_ERROR(ENOTSUP)); - if ((featureflags & DMU_BACKUP_FEATURE_RAW)) { + if (featureflags & DMU_BACKUP_FEATURE_RAW) { /* raw receives require the encryption feature */ if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_ENCRYPTION)) return (SET_ERROR(ENOTSUP)); @@ -1708,7 +1714,8 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx) return (SET_ERROR(EINVAL)); } - error = recv_begin_check_existing_impl(drba, ds, fromguid); + error = recv_begin_check_existing_impl(drba, ds, fromguid, + featureflags); dsl_dataset_rele_flags(ds, dsflags, FTAG); } else if (error == ENOENT) { /* target fs does not exist; must be a full backup or clone */ @@ -1738,6 +1745,16 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx) if (error != 0) return (error); + if ((featureflags & DMU_BACKUP_FEATURE_RAW) == 0 && + drba->drba_origin == NULL) { + error = dmu_objset_create_crypt_check(ds->ds_dir, + drba->drba_dcp); + if (error != 0) { + dsl_dataset_rele_flags(ds, dsflags, FTAG); + return (error); + } + } + /* * Check filesystem and snapshot limits before receiving. We'll * recheck snapshot limits again at the end (we create the @@ -1801,15 +1818,27 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx) ds_hold_flags_t dsflags = 0; int error; uint64_t crflags = 0; - dsl_crypto_params_t *dcpp = NULL; - dsl_crypto_params_t dcp = { 0 }; + dsl_crypto_params_t dummy_dcp = { 0 }; + dsl_crypto_params_t *dcp = drba->drba_dcp; if (drrb->drr_flags & DRR_FLAG_CI_DATA) crflags |= DS_FLAG_CI_DATASET; - if ((featureflags & DMU_BACKUP_FEATURE_RAW) == 0) { + + if ((featureflags & DMU_BACKUP_FEATURE_RAW) == 0) dsflags |= DS_HOLD_FLAG_DECRYPT; - } else { - dcp.cp_cmd = DCP_CMD_RAW_RECV; + + /* + * Raw, non-incremental recvs always use a dummy dcp with + * the raw cmd set. Raw incremental recvs do not use a dcp + * since the encryption parameters are already set in stone. + */ + if (dcp == NULL && drba->drba_snapobj == 0 && + drba->drba_origin == NULL) { + ASSERT3P(dcp, ==, NULL); + dcp = &dummy_dcp; + + if (featureflags & DMU_BACKUP_FEATURE_RAW) + dcp->cp_cmd = DCP_CMD_RAW_RECV; } error = dsl_dataset_hold_flags(dp, tofs, dsflags, FTAG, &ds); @@ -1820,13 +1849,11 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx) if (drba->drba_snapobj != 0) { VERIFY0(dsl_dataset_hold_obj(dp, drba->drba_snapobj, FTAG, &snap)); - } else { - /* we use the dcp whenever we are not making a clone */ - dcpp = &dcp; + ASSERT3P(dcp, ==, NULL); } dsobj = dsl_dataset_create_sync(ds->ds_dir, recv_clone_name, - snap, crflags, drba->drba_cred, dcpp, tx); + snap, crflags, drba->drba_cred, dcp, tx); if (drba->drba_snapobj != 0) dsl_dataset_rele(snap, FTAG); dsl_dataset_rele_flags(ds, dsflags, FTAG); @@ -1840,19 +1867,18 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx) if (drba->drba_origin != NULL) { VERIFY0(dsl_dataset_hold(dp, drba->drba_origin, FTAG, &origin)); - } else { - /* we use the dcp whenever we are not making a clone */ - dcpp = &dcp; + ASSERT3P(dcp, ==, NULL); } /* Create new dataset. */ dsobj = dsl_dataset_create_sync(dd, strrchr(tofs, '/') + 1, - origin, crflags, drba->drba_cred, dcpp, tx); + origin, crflags, drba->drba_cred, dcp, tx); if (origin != NULL) dsl_dataset_rele(origin, FTAG); dsl_dir_rele(dd, FTAG); drba->drba_cookie->drc_newfs = B_TRUE; } + VERIFY0(dsl_dataset_own_obj(dp, dsobj, dsflags, dmu_recv_tag, &newds)); VERIFY0(dmu_objset_from_ds(newds, &os)); @@ -2103,7 +2129,8 @@ dmu_recv_resume_begin_sync(void *arg, dmu_tx_t *tx) */ int dmu_recv_begin(char *tofs, char *tosnap, dmu_replay_record_t *drr_begin, - boolean_t force, boolean_t resumable, char *origin, dmu_recv_cookie_t *drc) + boolean_t force, boolean_t resumable, nvlist_t *localprops, + nvlist_t *hidden_args, char *origin, dmu_recv_cookie_t *drc) { dmu_recv_begin_arg_t drba = { 0 }; @@ -2139,9 +2166,33 @@ dmu_recv_begin(char *tofs, char *tosnap, dmu_replay_record_t *drr_begin, dmu_recv_resume_begin_check, dmu_recv_resume_begin_sync, &drba, 5, ZFS_SPACE_CHECK_NORMAL)); } else { - return (dsl_sync_task(tofs, + int err; + + /* + * For non-raw, non-incremental, non-resuming receives the + * user can specify encryption parameters on the command line + * with "zfs recv -o". For these receives we create a dcp and + * pass it to the sync task. Creating the dcp will implicitly + * remove the encryption params from the localprops nvlist, + * which avoids errors when trying to set these normally + * read-only properties. Any other kind of receive that + * attempts to set these properties will fail as a result. + */ + if ((DMU_GET_FEATUREFLAGS(drc->drc_drrb->drr_versioninfo) & + DMU_BACKUP_FEATURE_RAW) == 0 && + origin == NULL && drc->drc_drrb->drr_fromguid == 0) { + err = dsl_crypto_params_create_nvlist(DCP_CMD_NONE, + localprops, hidden_args, &drba.drba_dcp); + if (err != 0) + return (err); + } + + err = dsl_sync_task(tofs, dmu_recv_begin_check, dmu_recv_begin_sync, - &drba, 5, ZFS_SPACE_CHECK_NORMAL)); + &drba, 5, ZFS_SPACE_CHECK_NORMAL); + dsl_crypto_params_free(drba.drba_dcp, !!err); + + return (err); } } diff --git a/module/zfs/dsl_crypt.c b/module/zfs/dsl_crypt.c index 9bdfc8d91..38e6123d3 100644 --- a/module/zfs/dsl_crypt.c +++ b/module/zfs/dsl_crypt.c @@ -1719,6 +1719,10 @@ dmu_objset_create_crypt_check(dsl_dir_t *parentdd, dsl_crypto_params_t *dcp) { int ret; uint64_t pcrypt, crypt; + dsl_crypto_params_t dummy_dcp = { 0 }; + + if (dcp == NULL) + dcp = &dummy_dcp; if (dcp->cp_cmd != DCP_CMD_NONE) return (SET_ERROR(EINVAL)); diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index 74a58fb89..6a5846bfe 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -4339,15 +4339,17 @@ static boolean_t zfs_ioc_recv_inject_err; */ static int zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops, - nvlist_t *localprops, boolean_t force, boolean_t resumable, int input_fd, - dmu_replay_record_t *begin_record, int cleanup_fd, uint64_t *read_bytes, - uint64_t *errflags, uint64_t *action_handle, nvlist_t **errors) + nvlist_t *localprops, nvlist_t *hidden_args, boolean_t force, + boolean_t resumable, int input_fd, dmu_replay_record_t *begin_record, + int cleanup_fd, uint64_t *read_bytes, uint64_t *errflags, + uint64_t *action_handle, nvlist_t **errors) { dmu_recv_cookie_t drc; int error = 0; int props_error = 0; offset_t off; - nvlist_t *delayprops = NULL; /* sent properties applied post-receive */ + nvlist_t *local_delayprops = NULL; + nvlist_t *recv_delayprops = NULL; nvlist_t *origprops = NULL; /* existing properties */ nvlist_t *origrecvd = NULL; /* existing received properties */ boolean_t first_recvd_props = B_FALSE; @@ -4361,8 +4363,8 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops, if (input_fp == NULL) return (SET_ERROR(EBADF)); - error = dmu_recv_begin(tofs, tosnap, - begin_record, force, resumable, origin, &drc); + error = dmu_recv_begin(tofs, tosnap, begin_record, force, + resumable, localprops, hidden_args, origin, &drc); if (error != 0) goto out; @@ -4379,8 +4381,8 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops, /* * If new received properties are supplied, they are to - * completely replace the existing received properties, so stash - * away the existing ones. + * completely replace the existing received properties, + * so stash away the existing ones. */ if (dsl_prop_get_received(tofs, &origrecvd) == 0) { nvlist_t *errlist = NULL; @@ -4427,7 +4429,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops, props_error = dsl_prop_set_hasrecvd(tofs); if (props_error == 0) { - delayprops = extract_delay_props(recvprops); + recv_delayprops = extract_delay_props(recvprops); (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED, recvprops, *errors); } @@ -4454,6 +4456,8 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops, fnvlist_add_nvpair(oprops, nvp); } } + + local_delayprops = extract_delay_props(oprops); (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_LOCAL, oprops, *errors); (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_INHERITED, @@ -4495,26 +4499,33 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops, } /* Set delayed properties now, after we're done receiving. */ - if (delayprops != NULL && error == 0) { + if (recv_delayprops != NULL && error == 0) { (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED, - delayprops, *errors); + recv_delayprops, *errors); + } + if (local_delayprops != NULL && error == 0) { + (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_LOCAL, + local_delayprops, *errors); } } - if (delayprops != NULL) { - /* - * Merge delayed props back in with initial props, in case - * we're DEBUG and zfs_ioc_recv_inject_err is set (which means - * we have to make sure clear_received_props() includes - * the delayed properties). - * - * Since zfs_ioc_recv_inject_err is only in DEBUG kernels, - * using ASSERT() will be just like a VERIFY. - */ - ASSERT(nvlist_merge(recvprops, delayprops, 0) == 0); - nvlist_free(delayprops); + /* + * Merge delayed props back in with initial props, in case + * we're DEBUG and zfs_ioc_recv_inject_err is set (which means + * we have to make sure clear_received_props() includes + * the delayed properties). + * + * Since zfs_ioc_recv_inject_err is only in DEBUG kernels, + * using ASSERT() will be just like a VERIFY. + */ + if (recv_delayprops != NULL) { + ASSERT(nvlist_merge(recvprops, recv_delayprops, 0) == 0); + nvlist_free(recv_delayprops); + } + if (local_delayprops != NULL) { + ASSERT(nvlist_merge(localprops, local_delayprops, 0) == 0); + nvlist_free(local_delayprops); } - *read_bytes = off - input_fp->f_offset; if (VOP_SEEK(input_fp->f_vnode, input_fp->f_offset, &off, NULL) == 0) @@ -4689,7 +4700,7 @@ zfs_ioc_recv(zfs_cmd_t *zc) begin_record.drr_u.drr_begin = zc->zc_begin_record; error = zfs_ioc_recv_impl(tofs, tosnap, origin, recvdprops, localprops, - zc->zc_guid, B_FALSE, zc->zc_cookie, &begin_record, + NULL, zc->zc_guid, B_FALSE, zc->zc_cookie, &begin_record, zc->zc_cleanup_fd, &zc->zc_cookie, &zc->zc_obj, &zc->zc_action_handle, &errors); nvlist_free(recvdprops); @@ -4743,6 +4754,7 @@ zfs_ioc_recv_new(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl) nvlist_t *errors = NULL; nvlist_t *recvprops = NULL; nvlist_t *localprops = NULL; + nvlist_t *hidden_args = NULL; char *snapname = NULL; char *origin = NULL; char *tosnap; @@ -4802,9 +4814,13 @@ zfs_ioc_recv_new(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl) if (error && error != ENOENT) return (error); + error = nvlist_lookup_nvlist(innvl, ZPOOL_HIDDEN_ARGS, &hidden_args); + if (error && error != ENOENT) + return (error); + error = zfs_ioc_recv_impl(tofs, tosnap, origin, recvprops, localprops, - force, resumable, input_fd, begin_record, cleanup_fd, &read_bytes, - &errflags, &action_handle, &errors); + hidden_args, force, resumable, input_fd, begin_record, cleanup_fd, + &read_bytes, &errflags, &action_handle, &errors); fnvlist_add_uint64(outnvl, "read_bytes", read_bytes); fnvlist_add_uint64(outnvl, "error_flags", errflags); diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index c3e3887a8..5e0dd0832 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -455,7 +455,7 @@ tests = ['zdb_001_neg', 'zfs_001_neg', 'zfs_allow_001_neg', 'zpool_offline_001_neg', 'zpool_online_001_neg', 'zpool_remove_001_neg', 'zpool_replace_001_neg', 'zpool_scrub_001_neg', 'zpool_set_001_neg', 'zpool_status_001_neg', 'zpool_upgrade_001_neg', 'arcstat_001_pos', - 'arc_summary_001_pos', 'arc_summary_002_neg', + 'arc_summary_001_pos', 'arc_summary_002_neg', 'arc_summary3_001_pos', 'dbufstat_001_pos'] user = tags = ['functional', 'cli_user', 'misc'] @@ -743,7 +743,8 @@ tests = ['rsend_001_pos', 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos', 'send-c_mixed_compression', 'send-c_stream_size_estimate', 'send-cD', 'send-c_embedded_blocks', 'send-c_resume', 'send-cpL_varied_recsize', 'send-c_recv_dedup', 'send_encrypted_files', 'send_encrypted_heirarchy', - 'send_freeobjects', 'send_realloc_dnode_size', 'send_hole_birth'] + 'send_encrypted_props', 'send_freeobjects', 'send_realloc_dnode_size', + 'send_hole_birth'] tags = ['functional', 'rsend'] [tests/functional/scrub_mirror] diff --git a/tests/zfs-tests/tests/functional/rsend/Makefile.am b/tests/zfs-tests/tests/functional/rsend/Makefile.am index 79e01d190..d65a9144e 100644 --- a/tests/zfs-tests/tests/functional/rsend/Makefile.am +++ b/tests/zfs-tests/tests/functional/rsend/Makefile.am @@ -23,6 +23,7 @@ dist_pkgdata_SCRIPTS = \ rsend_024_pos.ksh \ send_encrypted_files.ksh \ send_encrypted_heirarchy.ksh \ + send_encrypted_props.ksh \ send-cD.ksh \ send-c_embedded_blocks.ksh \ send-c_incremental.ksh \ diff --git a/tests/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh b/tests/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh new file mode 100755 index 000000000..a216f1c5f --- /dev/null +++ b/tests/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh @@ -0,0 +1,199 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2018 by Datto Inc. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib + +# +# DESCRIPTION: +# Verify that zfs properly handles encryption properties when receiving +# send streams. +# +# STRATEGY: +# 1. Create a few unencrypted and encrypted test datasets with some data +# 2. Take snapshots of these datasets in preparation for sending +# 3. Verify that 'zfs recv -o keylocation=prompt' fails +# 4. Verify that 'zfs recv -x <encryption prop>' fails on a raw send stream +# 5. Verify that encryption properties cannot be changed on incrementals +# 6. Verify that a simple send can be received as an encryption root +# 7. Verify that an unencrypted props send can be received as an +# encryption root +# 8. Verify that an unencrypted recursive send can be received as an +# encryption root +# 9. Verify that an unencrypted props send can be received as an +# encryption child +# 10. Verify that an unencrypted recursive send can be received as an +# encryption child +# + +verify_runnable "both" + +function cleanup +{ + destroy_dataset $TESTPOOL/recv "-r" + destroy_dataset $TESTPOOL/crypt "-r" + destroy_dataset $TESTPOOL/ds "-r" + [[ -f $sendfile ]] && log_must rm $sendfile + [[ -f $keyfile ]] && log_must rm $keyfile +} +log_onexit cleanup + +log_assert "'zfs recv' must properly handle encryption properties" + +typeset keyfile=/$TESTPOOL/pkey +typeset sendfile=/$TESTPOOL/sendfile +typeset snap=$TESTPOOL/ds@snap +typeset esnap=$TESTPOOL/crypt@snap1 +typeset esnap2=$TESTPOOL/crypt@snap2 + +log_must eval "echo 'password' > $keyfile" + +log_must zfs create $TESTPOOL/ds +log_must zfs create $TESTPOOL/ds/ds1 + +log_must zfs create -o encryption=on -o keyformat=passphrase \ + -o keylocation=file://$keyfile $TESTPOOL/crypt +log_must zfs create $TESTPOOL/crypt/ds1 +log_must zfs create -o keyformat=passphrase -o keylocation=file://$keyfile \ + $TESTPOOL/crypt/ds2 + +log_must mkfile 1M /$TESTPOOL/ds/$TESTFILE0 +log_must cp /$TESTPOOL/ds/$TESTFILE0 /$TESTPOOL/crypt/$TESTFILE0 +typeset cksum=$(md5sum /$TESTPOOL/ds/$TESTFILE0 | awk '{ print $1 }') + +log_must zfs snap -r $snap +log_must zfs snap -r $esnap +log_must zfs snap -r $esnap2 + +# Embedded data is incompatible with encrypted datasets, so we cannot +# allow embedded streams to be received. +log_note "Must not be able to receive an embedded stream as encrypted" +log_mustnot eval "zfs send -e $TESTPOOL/crypt/ds1 | zfs recv $TESTPOOL/recv" + +# We currently don't have an elegant and secure way to pass the passphrase +# in via prompt because the send stream itself is using stdin. +log_note "Must not be able to use 'keylocation=prompt' on receive" +log_must eval "zfs send $snap > $sendfile" +log_mustnot eval "zfs recv -o encryption=on -o keyformat=passphrase" \ + "$TESTPOOL/recv < $sendfile" +log_mustnot eval "zfs recv -o encryption=on -o keyformat=passphrase" \ + "-o keylocation=prompt $TESTPOOL/recv < $sendfile" + +# Raw sends do not have access to the decrypted data so we cannot override +# the encryption settings without losing the data. +log_note "Must not be able to disable encryption properties on raw send" +log_must eval "zfs send -w $esnap > $sendfile" +log_mustnot eval "zfs recv -x encryption $TESTPOOL/recv < $sendfile" +log_mustnot eval "zfs recv -x keyformat $TESTPOOL/recv < $sendfile" +log_mustnot eval "zfs recv -x pbkdf2iters $TESTPOOL/recv < $sendfile" + +# Encryption properties are set upon creating the dataset. Changing them +# afterwards requires using 'zfs change-key' or an update from a raw send. +log_note "Must not be able to change encryption properties on incrementals" +log_must eval "zfs send $esnap | zfs recv -o encryption=on" \ + "-o keyformat=passphrase -o keylocation=file://$keyfile $TESTPOOL/recv" +log_mustnot eval "zfs send -i $esnap $esnap2 |" \ + "zfs recv -o encryption=aes-128-ccm $TESTPOOL/recv" +log_mustnot eval "zfs send -i $esnap $esnap2 |" \ + "zfs recv -o keyformat=hex $TESTPOOL/recv" +log_mustnot eval "zfs send -i $esnap $esnap2 |" \ + "zfs recv -o pbkdf2iters=100k $TESTPOOL/recv" +log_must zfs destroy -r $TESTPOOL/recv + +# Test that we can receive a simple stream as an encryption root. +log_note "Must be able to receive stream as encryption root" +ds=$TESTPOOL/recv +log_must eval "zfs send $snap > $sendfile" +log_must eval "zfs recv -o encryption=on -o keyformat=passphrase" \ + "-o keylocation=file://$keyfile $ds < $sendfile" +log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm" +log_must test "$(get_prop 'encryptionroot' $ds)" == "$ds" +log_must test "$(get_prop 'keyformat' $ds)" == "passphrase" +log_must test "$(get_prop 'keylocation' $ds)" == "file://$keyfile" +log_must test "$(get_prop 'mounted' $ds)" == "yes" +recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }') +log_must test "$recv_cksum" == "$cksum" +log_must zfs destroy -r $ds + +# Test that we can override encryption properties on a properties stream +# of an unencrypted dataset, turning it into an encryption root. +log_note "Must be able to receive stream with props as encryption root" +ds=$TESTPOOL/recv +log_must eval "zfs send -p $snap > $sendfile" +log_must eval "zfs recv -o encryption=on -o keyformat=passphrase" \ + "-o keylocation=file://$keyfile $ds < $sendfile" +log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm" +log_must test "$(get_prop 'encryptionroot' $ds)" == "$ds" +log_must test "$(get_prop 'keyformat' $ds)" == "passphrase" +log_must test "$(get_prop 'keylocation' $ds)" == "file://$keyfile" +log_must test "$(get_prop 'mounted' $ds)" == "yes" +recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }') +log_must test "$recv_cksum" == "$cksum" +log_must zfs destroy -r $ds + +# Test that we can override encryption properties on a recursive stream +# of an unencrypted dataset, turning it into an encryption root. The root +# dataset of the stream should become an encryption root with all children +# inheriting from it. +log_note "Must be able to receive recursive stream as encryption root" +ds=$TESTPOOL/recv +log_must eval "zfs send -R $snap > $sendfile" +log_must eval "zfs recv -o encryption=on -o keyformat=passphrase" \ + "-o keylocation=file://$keyfile $ds < $sendfile" +log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm" +log_must test "$(get_prop 'encryptionroot' $ds)" == "$ds" +log_must test "$(get_prop 'keyformat' $ds)" == "passphrase" +log_must test "$(get_prop 'keylocation' $ds)" == "file://$keyfile" +log_must test "$(get_prop 'mounted' $ds)" == "yes" +recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }') +log_must test "$recv_cksum" == "$cksum" +log_must zfs destroy -r $ds + +# Test that we can override an unencrypted properties stream's encryption +# settings, receiving it as an encrypted child. +log_note "Must be able to receive stream with props to encrypted child" +ds=$TESTPOOL/crypt/recv +log_must eval "zfs send -p $snap > $sendfile" +log_must eval "zfs recv -x encryption $ds < $sendfile" +log_must test "$(get_prop 'encryptionroot' $ds)" == "$TESTPOOL/crypt" +log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm" +log_must test "$(get_prop 'keyformat' $ds)" == "passphrase" +log_must test "$(get_prop 'mounted' $ds)" == "yes" +recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }') +log_must test "$recv_cksum" == "$cksum" +log_must zfs destroy -r $ds + +# Test that we can override an unencrypted recursive stream's encryption +# settings, receiving all datasets as encrypted children. +log_note "Must be able to receive recursive stream to encrypted child" +ds=$TESTPOOL/crypt/recv +log_must eval "zfs send -R $snap > $sendfile" +log_must eval "zfs recv -x encryption $ds < $sendfile" +log_must test "$(get_prop 'encryptionroot' $ds)" == "$TESTPOOL/crypt" +log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm" +log_must test "$(get_prop 'keyformat' $ds)" == "passphrase" +log_must test "$(get_prop 'mounted' $ds)" == "yes" +recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }') +log_must test "$recv_cksum" == "$cksum" +log_must zfs destroy -r $ds + +# Check that we haven't printed the key to the zpool history log +log_mustnot eval "zpool history -i | grep -q 'wkeydata'" + +log_pass "'zfs recv' properly handles encryption properties" |