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 <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
This commit is contained in:
Tom Caputi
2017-10-13 13:09:04 -04:00
committed by Brian Behlendorf
parent fe8a7982ca
commit d9c460a0b6
17 changed files with 507 additions and 93 deletions
+72 -21
View 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);
}
}
+4
View 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));
+43 -27
View 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);