]> granicus.if.org Git - zfs/commitdiff
Added encryption support for zfs recv -o / -x
authorTom Caputi <tcaputi@datto.com>
Fri, 13 Oct 2017 17:09:04 +0000 (13:09 -0400)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Wed, 15 Aug 2018 16:48:49 +0000 (09:48 -0700)
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 <lundman@lundman.net>
Reviewed by: Matthew Ahrens <mahrens@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Richard Elling <Richard.Elling@RichardElling.com>
Signed-off-by: Tom Caputi <tcaputi@datto.com>
Closes #7650

17 files changed:
contrib/pyzfs/libzfs_core/_libzfs_core.py
contrib/pyzfs/libzfs_core/bindings/libzfs_core.py
include/libzfs.h
include/libzfs_core.h
include/sys/dmu_send.h
lib/libzfs/libzfs_crypto.c
lib/libzfs/libzfs_dataset.c
lib/libzfs/libzfs_pool.c
lib/libzfs/libzfs_sendrecv.c
lib/libzfs_core/libzfs_core.c
man/man8/zfs.8
module/zfs/dmu_send.c
module/zfs/dsl_crypt.c
module/zfs/zfs_ioctl.c
tests/runfiles/linux.run
tests/zfs-tests/tests/functional/rsend/Makefile.am
tests/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh [new file with mode: 0755]

index 1e38a3f323b4c271f73d27098e6ea2a471dfa5fd..ffc93081284e3afefc69de22b8c21081d14cf175 100644 (file)
@@ -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)
index a67a01ee72196cd519f7bf23c2caa52688b09849..55899b5565d15937f6054a5513ccc8774eb89685 100644 (file)
@@ -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 **);
index c0c0f3c3cf67c9c31d34ad80126306fa58dc89d1..5c157bbf3a02677445218a56925f7664a52d457b 100644 (file)
@@ -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 *);
index 4ca9b254c26b94d1e1d3aed4dc48698c98b781b1..c22cbf18e2cc974b28a47fcea516ea471debab20 100644 (file)
@@ -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 **);
 
index c0b2aafdb428dda31c7bc765501d44257f553db9..396710470f5fdb405433b3db5f9cecd07a675924 100644 (file)
@@ -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);
index 0956466e200882ec14400ac3189c8adba04c6cca..3318a6bd2e1188b8c098bacb3f6d6772953563fd 100644 (file)
@@ -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);
index 7f7dd15940d941228fc3024d0988219d72756dfb..85b4c55310de70ec55de351f6b1a0e78a0064225 100644 (file)
@@ -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));
        }
index d19ca77140ecf55ad02a0acd59e377cc2b9205aa..0785c3170012fa3eadf9be43db8fb9fe1e087004 100644 (file)
@@ -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;
index 9a4f373a1efb0db20d6bdfc9e8b015cb0cf81029..b5c91ec20a60009a3d7d31afa0dc23b21aaa118f 100644 (file)
@@ -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
index ab2b705193217b6150736b12f2201e4063579b34..3a0b02690d1f8bac6df4baf4b26b01336a93819f 100644 (file)
@@ -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));
 }
 
 /*
index f1eed207f268c329882464d95f9a787d02f364fa..e356fc196e94fbc05847680e740810b85622be35 100644 (file)
@@ -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.
index ded0860875f0bded806c44961a76581033df53fc..80a4843b3ed4f486ca8746f05c0cf2486d8e1b74 100644 (file)
@@ -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);
        }
 }
 
index 9bdfc8d91a1ece766716be78a5de95e138c590a2..38e6123d32d30cd11576616586e5dfb5d894d4ed 100644 (file)
@@ -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));
index 74a58fb8908ebcbac81a81bc1296f147bec2ac7b..6a5846bfea7363a3987c6dd3b85ab35ab69ecd39 100644 (file)
@@ -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);
index c3e3887a8820b1ed2e688daf469f26ea7f70ee21..5e0dd0832e25aafda523e0947b35fc1060a71571 100644 (file)
@@ -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]
index 79e01d190fa62021f6fe4ab6496623524da18931..d65a9144e5891bb492e2812748256691b8d46aef 100644 (file)
@@ -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 (executable)
index 0000000..a216f1c
--- /dev/null
@@ -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"