From: John Mulligan Date: Wed, 6 Aug 2025 17:32:50 +0000 (-0400) Subject: vfs_ceph_new: add keybridge and ceph fscrypt support X-Git-Tag: tdb-1.4.15~44 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=29f4d89cb6467da98ddf45a09d9995c4467a67bc;p=thirdparty%2Fsamba.git vfs_ceph_new: add keybridge and ceph fscrypt support Add support for CephFS's new fscrypt feature. Fetch the key material using the new keybridge varlink local RPC API. Adds the following configuration parameters for the vfs_ceph_new module: ``` ceph_new:keybridge socket = unix:/run/keybridge.sock ceph_new:keybridge scope = mem ceph_new:keybridge name = test ceph_new:keybridge kind = B64 ceph_new:fscrypt = keybridge ``` Where the various keybridge parameters configure what keybridge server to use and what key to fetch. The `ceph_new:fscrypt` parameter defaults to 'disabled' and can be set to 'keybridge'. An enum is used here in case we ever need to support something other than keybridge in the future. Pair-Programmed-With: Shachar Sharon Signed-off-by: Shachar Sharon Signed-off-by: John Mulligan Reviewed-by: Gunther Deschner Reviewed-by: Anoop C S --- diff --git a/source3/modules/vfs_ceph_new.c b/source3/modules/vfs_ceph_new.c index 5e9d6add4cc..4a91f55b612 100644 --- a/source3/modules/vfs_ceph_new.c +++ b/source3/modules/vfs_ceph_new.c @@ -39,6 +39,11 @@ #include "smbprofile.h" #include "modules/posixacl_xattr.h" #include "lib/util/tevent_unix.h" +#include "lib/util/base64.h" +#if HAVE_CEPH_FSCRYPT +#include +#include "modules/varlink_keybridge.h" +#endif #undef DBGC_CLASS #define DBGC_CLASS DBGC_VFS @@ -93,12 +98,38 @@ static const struct enum_list enum_vfs_cephfs_proxy_vals[] = { {-1, NULL} }; +enum vfs_cephfs_fscrypt_mode { + /* no fscrypt */ + VFS_CEPHFS_FSCRYPT_DISABLED, + /* enable fscrypt, get keys from keybridge */ + VFS_CEPHFS_FSCRYPT_KEYBRIDGE +}; + +static const struct enum_list enum_vfs_cephfs_fscrypt_vals[] = { + {VFS_CEPHFS_FSCRYPT_DISABLED, "disabled"}, + {VFS_CEPHFS_FSCRYPT_DISABLED, "none"}, + {VFS_CEPHFS_FSCRYPT_KEYBRIDGE, "keybridge"}, +}; + +struct vfs_ceph_fscrypt_key { + /* TODO: need a key type or expected length to validate the data? */ + DATA_BLOB blob; +}; + +#define FS_KEY_DATA(kp) (char *)((kp)->blob.data) +#define FS_KEY_LEN(kp) ((kp)->blob.length) + #define CEPH_FN(_name) typeof(_name) *_name ## _fn struct vfs_ceph_config { #ifdef HAVE_CEPH_ASYNCIO struct tevent_threaded_context *tctx; #endif +#if HAVE_CEPH_FSCRYPT + struct varlink_keybridge_config *kbc; + struct vfs_ceph_fscrypt_key *fskey; +#endif + enum vfs_cephfs_fscrypt_mode fscrypt; const char *conf_file; const char *user_id; const char *fsname; @@ -165,6 +196,10 @@ struct vfs_ceph_config { #ifdef HAVE_CEPH_ASYNCIO CEPH_FN(ceph_ll_nonblocking_readv_writev); #endif +#if HAVE_CEPH_FSCRYPT + CEPH_FN(ceph_add_fscrypt_key); + CEPH_FN(ceph_ll_set_fscrypt_policy_v2); +#endif }; /* @@ -220,7 +255,7 @@ static bool cephmount_cache_change_ref(struct cephmount_cached *entry, int n) entry->count += n; DBG_DEBUG("[CEPH] updated mount cache entry: count=%" PRId32 - "change=%+d cookie=%s\n", + " change=%+d cookie=%s\n", entry->count, n, entry->cookie); @@ -464,6 +499,10 @@ static bool vfs_cephfs_load_lib(struct vfs_ceph_config *config) #ifdef HAVE_CEPH_ASYNCIO CHECK_CEPH_FN(libhandle, ceph_ll_nonblocking_readv_writev); #endif +#if HAVE_CEPH_FSCRYPT + CHECK_CEPH_FN(libhandle, ceph_add_fscrypt_key); + CHECK_CEPH_FN(libhandle, ceph_ll_set_fscrypt_policy_v2); +#endif config->libhandle = libhandle; @@ -481,6 +520,139 @@ static int vfs_ceph_config_destructor(struct vfs_ceph_config *config) return 0; } +#if HAVE_CEPH_FSCRYPT +static bool parse_keybridge_config(int snum, + const char *module_name, + struct vfs_ceph_config *config) +{ + const char *tmp = NULL; + struct varlink_keybridge_config *kbc = NULL; + const char *socket = lp_parm_const_string(snum, + module_name, + "keybridge socket", + NULL); + if (socket == NULL) { + /* no keybridge socket means no keybridge */ + return false; + } + kbc = talloc_zero(config, struct varlink_keybridge_config); + if (kbc == NULL) { + DBG_ERR("talloc_zero failed\n"); + goto fail; + } + kbc->path = talloc_strdup(kbc, socket); + if (kbc->path == NULL) { + DBG_ERR("talloc_strdup failed\n"); + goto fail; + } + DBG_INFO("[CEPH] keybridge path = [%s]\n", kbc->path); + + tmp = lp_parm_const_string(snum, module_name, "keybridge scope", NULL); + if (tmp == NULL || strlen(tmp) == 0) { + DBG_ERR("[CEPH] '%s:keybridge scope' must be set if keybridge " + "socket is set\n", + module_name); + goto fail; + } + kbc->scope = talloc_strdup(kbc, tmp); + if (kbc->scope == NULL) { + DBG_ERR("[CEPH] talloc_strdup failed\n"); + goto fail; + } + DBG_INFO("[CEPH] keybridge scope = [%s]\n", kbc->scope); + + tmp = lp_parm_const_string(snum, module_name, "keybridge name", NULL); + if (tmp == NULL || strlen(tmp) == 0) { + DBG_ERR("[CEPH] '%s:keybridge name' must be set if keybridge " + "socket is set\n", + module_name); + goto fail; + } + kbc->name = talloc_strdup(kbc, tmp); + if (kbc->name == NULL) { + DBG_ERR("talloc_strdup failed\n"); + goto fail; + } + DBG_INFO("[CEPH] keybridge name = [%s]\n", kbc->name); + + tmp = lp_parm_const_string(snum, module_name, "keybridge kind", NULL); + if (tmp == NULL || strcmp(tmp, "B64") == 0) { + /* default to B64 or chosen */ + kbc->kind = VARLINK_KEYBRIDGE_KIND_B64; + } else if (tmp && strcmp(tmp, "VALUE") == 0) { + kbc->kind = VARLINK_KEYBRIDGE_KIND_VALUE; + } else { + DBG_ERR("[CEPH] invalid keybridge kind: [%s]\n", tmp); + goto fail; + } + DBG_INFO("[CEPH] keybridge set: kind=%d\n", kbc->kind); + + config->kbc = kbc; + return true; +fail: + TALLOC_FREE(kbc); + return false; +} + +static void extract_keybridge_key(struct vfs_ceph_config *config, + struct varlink_keybridge_result *result) +{ + config->fskey = talloc_zero(config, struct vfs_ceph_fscrypt_key); + if (config->fskey == NULL) { + DBG_ERR("[CEPH] failed to allocate fskey\n"); + return; + } + + /* TODO: error checking? expected key length? */ + if (result->kind == VARLINK_KEYBRIDGE_KIND_B64) { + config->fskey->blob = base64_decode_data_blob_talloc( + config->fskey, result->data); + } else if (result->kind == VARLINK_KEYBRIDGE_KIND_VALUE) { + /* TODO: loses the trailing null. does this even make + * sense at all for fscrypt? */ + config->fskey->blob = data_blob_talloc(config->fskey, + result->data, + strlen(result->data)); + } else { + DBG_ERR("[CEPH] invalid keybridge: kind=%d\n", result->kind); + TALLOC_FREE(config->fskey); + } +} + +static void fetch_keybridge_config(struct vfs_ceph_config *config) +{ + struct varlink_keybridge_result *result = NULL; + bool ok; + + ok = varlink_keybridge_entry_get(config, config->kbc, &result); + if (!ok) { + DBG_ERR("[CEPH] failed to get keybridge entry\n"); + return; + } + switch (result->status) { + case VARLINK_KEYBRIDGE_STATUS_OK: + DBG_INFO("[CEPH] got value from keybridge\n"); + DEBUG(100, ("keybridge data value: %s\n", result->data)); + extract_keybridge_key(config, result); + break; + case VARLINK_KEYBRIDGE_STATUS_FAILURE: + DBG_ERR("[CEPH] failed to get keybridge: varlink failure\n"); + break; + case VARLINK_KEYBRIDGE_STATUS_ERROR: + DBG_ERR("[CEPH] got error from keybridge server: %s\n", + result->data); + break; + default: + DBG_ERR("[CEPH] invalid varlink keybridge status value: %d\n", + result->status); + break; + } + if (result) { + talloc_free(result); + } +} +#endif /* HAVE_CEPH_FSCRYPT */ + static bool vfs_ceph_load_config(struct vfs_handle_struct *handle, struct vfs_ceph_config **config) { @@ -516,6 +688,27 @@ static bool vfs_ceph_load_config(struct vfs_handle_struct *handle, DBG_ERR("[CEPH] value for proxy: mode unknown\n"); return false; } + config_tmp->fscrypt = lp_parm_enum(snum, + module_name, + "fscrypt", + enum_vfs_cephfs_fscrypt_vals, + VFS_CEPHFS_FSCRYPT_DISABLED); + if (config_tmp->fscrypt == -1) { + DBG_ERR("[CEPH] value for fscrypt: unknown\n"); + return false; + } +#if HAVE_CEPH_FSCRYPT + if (config_tmp->fscrypt == VFS_CEPHFS_FSCRYPT_KEYBRIDGE) { + if (parse_keybridge_config(snum, module_name, config_tmp)) { + fetch_keybridge_config(config_tmp); + } + } +#else + if (config_tmp->fscrypt == VFS_CEPHFS_FSCRYPT_KEYBRIDGE) { + DBG_ERR("[CEPH] fscrypt support configured but" + " not enabled during build, ignoring\n"); + } +#endif ok = vfs_cephfs_load_lib(config_tmp); if (!ok) { @@ -537,6 +730,10 @@ done: is sure to try and execute them. These stubs are used to prevent this possibility. */ +#if HAVE_CEPH_FSCRYPT +static int vfs_ceph_setup_fscrypt(struct vfs_handle_struct *handle); +#endif /* HAVE_CEPH_FSCRYPT */ + static int vfs_ceph_connect(struct vfs_handle_struct *handle, const char *service, const char *user) { @@ -582,6 +779,15 @@ connect_ok: SNUM(handle->conn), cookie); +#if HAVE_CEPH_FSCRYPT + if (config->fscrypt != VFS_CEPHFS_FSCRYPT_DISABLED) { + DBG_INFO("[CEPH] fscrypt is enabled\n"); + ret = vfs_ceph_setup_fscrypt(handle); + DBG_INFO("[CEPH] vfs_ceph_setup_fscrypt: %s\n", + (ret == 0) ? "ok" : "error"); + } +#endif + /* * Unless we have an async implementation of getxattrat turn this off. */ @@ -2101,6 +2307,84 @@ static void vfs_ceph_iput(const struct vfs_handle_struct *handle, } } +#if HAVE_CEPH_FSCRYPT +/* Fscrypt */ +/* + * struct ceph_fscrypt_key_identifier isn't exported so we cannot know its + * exact internals. However, the first FSCRYPT_KEY_IDENTIFIER_SIZE bytes will + * be copied into policy_v2. Following the foot-steps of nfs-ganesha. + */ +struct vfs_ceph_fscrypt_key_identifier { + char raw[256]; +}; + +static void populate_policy(const struct vfs_ceph_fscrypt_key_identifier *kid, + struct fscrypt_policy_v2 *policy) +{ + memset(policy, 0, sizeof(*policy)); + policy->version = 2; + policy->contents_encryption_mode = FSCRYPT_MODE_AES_256_XTS; + policy->filenames_encryption_mode = FSCRYPT_MODE_AES_256_CTS; + policy->flags = FSCRYPT_POLICY_FLAGS_PAD_32; + memcpy(policy->master_key_identifier, + kid->raw, + FSCRYPT_KEY_IDENTIFIER_SIZE); +} + +static int vfs_ceph_setup_fscrypt(struct vfs_handle_struct *handle) +{ + struct vfs_ceph_fscrypt_key_identifier kid = {}; + struct fscrypt_policy_v2 policy = {}; + struct vfs_ceph_iref iref = {}; + struct vfs_ceph_config *config = NULL; + const struct security_unix_token *utok = NULL; + void *kid_raw = kid.raw; + int ret = -1; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct vfs_ceph_config, + return ret); + + if (config->fskey == NULL) { + DBG_ERR("[CEPH] no fs key configured\n"); + return ret; + } + DBG_INFO("[CEPH] setting up fscrypt for cephfs\n"); + + ret = vfs_ceph_iget(handle, handle->conn->connectpath, 0, &iref); + if (ret != 0) { + DBG_ERR("[CEPH] iget root failed: errno=%d\n", errno); + return -1; + } + utok = get_current_utok(handle->conn); + ret = config->ceph_add_fscrypt_key_fn(config->mount, + FS_KEY_DATA(config->fskey), + FS_KEY_LEN(config->fskey), + kid_raw, + utok->uid); + if (ret < 0) { + DBG_ERR("[CEPH] ceph_add_fscrypt_key error: ret=%d\n", ret); + goto out; + } + populate_policy(&kid, &policy); + + ret = config->ceph_ll_set_fscrypt_policy_v2_fn(config->mount, + iref.inode, + &policy); + DBG_DEBUG("[CEPH] ceph_ll_set_fscrypt_policy_v2 ret=%d\n", ret); + if (ret < 0) { + DBG_ERR("[CEPH] ceph_ll_set_fscrypt_policy_v2 error: ret=%d\n", + ret); + goto out; + } + ret = 0; +out: + vfs_ceph_iput(handle, &iref); + return ret; +} +#endif /* HAVE_CEPH_FSCRYPT */ + /* Disk operations */ static uint64_t vfs_ceph_disk_free(struct vfs_handle_struct *handle,