From: Sebastien Buisson Date: Fri, 24 Jan 2025 15:31:37 +0000 (+0100) Subject: LU-18694 sec: nodemap local root user capabilities X-Git-Tag: 2.16.53~14 X-Git-Url: https://git.whamcloud.com/?a=commitdiff_plain;h=f7495bc57f4873db2760fbe6a2c36e3ce8ef546d;p=fs%2Flustre-release.git LU-18694 sec: nodemap local root user capabilities Add a new 'local_admin' rbac role, on by default. The purpose of this new role is to keep capabilities for root even if it is mapped or offset. This allows to have root mapped to a non-privileged storage id while still being able to perform 'admin-like' tasks thanks to capabilities, such as changing file permissions or file ownership. Note that setquota and changing project id is also impacted by the local_admin role. When enabled, root on the client that gets mapped on file system side is still able to interact with those. Be aware that if root is squashed, then capabilities are dropped as for any other regular user. New test sanity-sec test_64h exercises the local_admin role. Signed-off-by: Sebastien Buisson Change-Id: I5832b21106b2829134a596c2aacf04839be856e9 Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/57966 Tested-by: jenkins Tested-by: Maloo Reviewed-by: Andreas Dilger Reviewed-by: Marc Vef Reviewed-by: Oleg Drokin --- diff --git a/lustre/doc/lctl-nodemap-modify.8 b/lustre/doc/lctl-nodemap-modify.8 index 0fcdc2a..ac6fe61 100644 --- a/lustre/doc/lctl-nodemap-modify.8 +++ b/lustre/doc/lctl-nodemap-modify.8 @@ -110,6 +110,9 @@ as these operations only need read access to fscrypt metadata. .br - ignore_root_prjquota, so that project quota is not enforced for the root user. .br +- local_admin, to allow mapped root to perform admin operations on files, unless +it is squashed. +.br - quota_ops, to allow quota modifications. .br - server_upcall, to define which identity upcall to use. If set, identity upcall diff --git a/lustre/include/lustre_nodemap.h b/lustre/include/lustre_nodemap.h index bf6adbd..122693f 100644 --- a/lustre/include/lustre_nodemap.h +++ b/lustre/include/lustre_nodemap.h @@ -35,6 +35,7 @@ static const struct nodemap_rbac_name { { NODEMAP_RBAC_SERVER_UPCALL, "server_upcall" }, { NODEMAP_RBAC_IGN_ROOT_PRJQUOTA, "ignore_root_prjquota" }, { NODEMAP_RBAC_HSM_OPS, "hsm_ops" }, + { NODEMAP_RBAC_LOCAL_ADMIN, "local_admin" }, }; struct nodemap_pde { @@ -253,4 +254,32 @@ static inline int nodemap_process_idx_pages(void *config, int nodemap_get_config_req(struct obd_device *mgs_obd, struct ptlrpc_request *req); + +/* Return true if id corresponds to local root */ +static inline bool is_local_root(__u32 id, struct lu_nodemap *nodemap) +{ + /* Plain root is also local root */ + if (id == 0) + return true; + + /* id is not mapped root (0): + * just a regular mapped user + */ + if (id != nodemap_map_id(nodemap, NODEMAP_UID, + NODEMAP_CLIENT_TO_FS, 0)) + return false; + + /* id is mapped root, but root is squashed: + * not considered a local admin + */ + if (id == nodemap_map_id(nodemap, NODEMAP_UID, NODEMAP_CLIENT_TO_FS, + nodemap->nm_squash_uid)) + return false; + + /* id is mapped root and not squashed: + * rely on the local_admin rbac role + */ + return nodemap->nmf_rbac & NODEMAP_RBAC_LOCAL_ADMIN; +} + #endif /* _LUSTRE_NODEMAP_H */ diff --git a/lustre/include/md_object.h b/lustre/include/md_object.h index 476b86c..24f61be 100644 --- a/lustre/include/md_object.h +++ b/lustre/include/md_object.h @@ -690,6 +690,7 @@ struct lu_ucred { int uc_rbac_server_upcall:1; int uc_rbac_ignore_root_prjquota:1; int uc_rbac_hsm_ops:1; + int uc_rbac_local_admin:1; }; struct lu_ucred *lu_ucred(const struct lu_env *env); diff --git a/lustre/include/uapi/linux/lustre/lustre_idl.h b/lustre/include/uapi/linux/lustre/lustre_idl.h index 57fe42a..abe7932 100644 --- a/lustre/include/uapi/linux/lustre/lustre_idl.h +++ b/lustre/include/uapi/linux/lustre/lustre_idl.h @@ -3833,6 +3833,7 @@ enum nodemap_rbac_roles { NODEMAP_RBAC_SERVER_UPCALL = 0x00000040, NODEMAP_RBAC_IGN_ROOT_PRJQUOTA = 0x00000080, NODEMAP_RBAC_HSM_OPS = 0x00000100, + NODEMAP_RBAC_LOCAL_ADMIN = 0x00000200, NODEMAP_RBAC_NONE = (__u32)~(NODEMAP_RBAC_FILE_PERMS | NODEMAP_RBAC_DNE_OPS | NODEMAP_RBAC_QUOTA_OPS | @@ -3841,7 +3842,8 @@ enum nodemap_rbac_roles { NODEMAP_RBAC_FSCRYPT_ADMIN | NODEMAP_RBAC_SERVER_UPCALL | NODEMAP_RBAC_IGN_ROOT_PRJQUOTA | - NODEMAP_RBAC_HSM_OPS), + NODEMAP_RBAC_HSM_OPS | + NODEMAP_RBAC_LOCAL_ADMIN), NODEMAP_RBAC_ALL = 0xFFFFFFFF, /* future caps ON by default */ }; diff --git a/lustre/mdt/mdt_coordinator.c b/lustre/mdt/mdt_coordinator.c index 48c88ce..a1d113c 100644 --- a/lustre/mdt/mdt_coordinator.c +++ b/lustre/mdt/mdt_coordinator.c @@ -1084,6 +1084,7 @@ int hsm_init_ucred(struct lu_ucred *uc) uc->uc_rbac_server_upcall = 1; uc->uc_rbac_ignore_root_prjquota = 1; uc->uc_rbac_hsm_ops = 1; + uc->uc_rbac_local_admin = 1; RETURN(0); } diff --git a/lustre/mdt/mdt_handler.c b/lustre/mdt/mdt_handler.c index 91f7acb..b8b3af0 100644 --- a/lustre/mdt/mdt_handler.c +++ b/lustre/mdt/mdt_handler.c @@ -7267,6 +7267,7 @@ static int mdt_ctxt_add_dirty_flag(struct lu_env *env, mdt_ucred(info)->uc_rbac_server_upcall = 1; mdt_ucred(info)->uc_rbac_ignore_root_prjquota = 1; mdt_ucred(info)->uc_rbac_hsm_ops = 1; + mdt_ucred(info)->uc_rbac_local_admin = 1; rc = mdt_add_dirty_flag(info, mfd->mfd_object, &info->mti_attr); lu_context_exit(&ses); diff --git a/lustre/mdt/mdt_lib.c b/lustre/mdt/mdt_lib.c index d4cb5a9..fafb607 100644 --- a/lustre/mdt/mdt_lib.c +++ b/lustre/mdt/mdt_lib.c @@ -188,6 +188,7 @@ static void ucred_set_rbac_roles(struct mdt_thread_info *info, uc->uc_rbac_ignore_root_prjquota = !!(rbac & NODEMAP_RBAC_IGN_ROOT_PRJQUOTA); uc->uc_rbac_hsm_ops = !!(rbac & NODEMAP_RBAC_HSM_OPS); + uc->uc_rbac_local_admin = !!(rbac & NODEMAP_RBAC_LOCAL_ADMIN); } static int new_init_ucred(struct mdt_thread_info *info, ucred_init_type_t type, @@ -352,7 +353,7 @@ static int new_init_ucred(struct mdt_thread_info *info, ucred_init_type_t type, mdt_root_squash(info, &peernid); - if (ucred->uc_fsuid) { + if (!is_local_root(ucred->uc_fsuid, nodemap)) { if (!cap_issubset(ucred->uc_cap, mdt->mdt_enable_cap_mask)) CDEBUG(D_SEC, "%s: drop capabilities %llx for NID %s\n", mdt_obd_name(mdt), @@ -571,7 +572,7 @@ static int old_init_ucred_common(struct mdt_thread_info *info, mdt_root_squash(info, &mdt_info_req(info)->rq_peer.nid); - if (uc->uc_fsuid) { + if (!is_local_root(uc->uc_fsuid, nodemap)) { if (!cap_issubset(uc->uc_cap, mdt->mdt_enable_cap_mask)) CDEBUG(D_SEC, "%s: drop capabilities %llx for NID %s\n", mdt_obd_name(mdt), diff --git a/lustre/mdt/mdt_restripe.c b/lustre/mdt/mdt_restripe.c index f1211a0..bdb8610 100644 --- a/lustre/mdt/mdt_restripe.c +++ b/lustre/mdt/mdt_restripe.c @@ -910,6 +910,7 @@ int mdt_restriper_start(struct mdt_device *mdt) uc->uc_rbac_server_upcall = 1; uc->uc_rbac_ignore_root_prjquota = 1; uc->uc_rbac_hsm_ops = 1; + uc->uc_rbac_local_admin = 1; task = kthread_create(mdt_restriper_main, info, "mdt_restriper_%03d", mdt_seq_site(mdt)->ss_node_id); diff --git a/lustre/obdecho/echo_client.c b/lustre/obdecho/echo_client.c index bd1e6b3..df4dfec 100644 --- a/lustre/obdecho/echo_client.c +++ b/lustre/obdecho/echo_client.c @@ -1824,6 +1824,7 @@ static void echo_ucred_init(struct lu_env *env) ucred->uc_rbac_server_upcall = 1; ucred->uc_rbac_ignore_root_prjquota = 1; ucred->uc_rbac_hsm_ops = 1; + ucred->uc_rbac_local_admin = 1; } static void echo_ucred_fini(struct lu_env *env) diff --git a/lustre/ptlrpc/nodemap_handler.c b/lustre/ptlrpc/nodemap_handler.c index 4f4d4a6..3cd6eef 100644 --- a/lustre/ptlrpc/nodemap_handler.c +++ b/lustre/ptlrpc/nodemap_handler.c @@ -1836,7 +1836,7 @@ EXPORT_SYMBOL(nodemap_set_squash_projid); * * If nodemap is not active, always allow. * For user and group quota, allow if the nodemap allows root access, unless - * root is mapped. + * root does not have local admin role. * For project quota, allow if project id is not squashed or deny_unknown * is not set. * @@ -1854,7 +1854,10 @@ bool nodemap_can_setquota(struct lu_nodemap *nodemap, __u32 qc_type, __u32 id) !(nodemap->nmf_rbac & NODEMAP_RBAC_QUOTA_OPS)) return false; - if (nodemap_map_id(nodemap, NODEMAP_UID, NODEMAP_CLIENT_TO_FS, 0) != 0) + /* deny if local root has not local admin role */ + if (!is_local_root(nodemap_map_id(nodemap, NODEMAP_UID, + NODEMAP_CLIENT_TO_FS, 0), + nodemap)) return false; if (qc_type == PRJQUOTA) { diff --git a/lustre/ptlrpc/wiretest.c b/lustre/ptlrpc/wiretest.c index f5510e4..7a82095 100644 --- a/lustre/ptlrpc/wiretest.c +++ b/lustre/ptlrpc/wiretest.c @@ -6548,7 +6548,9 @@ void lustre_assert_wire_constants(void) (unsigned)NODEMAP_RBAC_IGN_ROOT_PRJQUOTA); LASSERTF(NODEMAP_RBAC_HSM_OPS == 0x00000100UL, "found 0x%.8xUL\n", (unsigned)NODEMAP_RBAC_HSM_OPS); - LASSERTF(NODEMAP_RBAC_NONE == 0xfffffe00UL, "found 0x%.8xUL\n", + LASSERTF(NODEMAP_RBAC_LOCAL_ADMIN == 0x00000200UL, "found 0x%.8xUL\n", + (unsigned)NODEMAP_RBAC_LOCAL_ADMIN); + LASSERTF(NODEMAP_RBAC_NONE == 0xfffffc00UL, "found 0x%.8xUL\n", (unsigned)NODEMAP_RBAC_NONE); LASSERTF(NODEMAP_RBAC_ALL == 0xffffffffUL, "found 0x%.8xUL\n", (unsigned)NODEMAP_RBAC_ALL); diff --git a/lustre/tests/sanity-sec.sh b/lustre/tests/sanity-sec.sh index bf2a516..fe730d7 100755 --- a/lustre/tests/sanity-sec.sh +++ b/lustre/tests/sanity-sec.sh @@ -6159,6 +6159,7 @@ run_test 63 "fid2path with encrypted files" test_64a() { local testfile=$DIR/$tdir/$tfile local srv_uc="" + local local_admin="" local rbac (( MDS1_VERSION >= $(version_code 2.15.54) )) || @@ -6167,6 +6168,9 @@ test_64a() { (( MDS1_VERSION >= $(version_code 2.16.50) )) && srv_uc="server_upcall" + (( MDS1_VERSION >= $(version_code 2.16.52) )) && + local_admin="local_admin" + stack_trap cleanup_local_client_nodemap EXIT mkdir -p $DIR/$tdir || error "mkdir $DIR/$tdir failed" setup_local_client_nodemap "c0" 1 1 @@ -6180,6 +6184,7 @@ test_64a() { chlg_ops \ fscrypt_admin \ $srv_uc \ + $local_admin \ ; do [[ "$rbac" =~ "$role" ]] || @@ -6671,6 +6676,114 @@ test_64g() { } run_test 64g "Nodemap enforces server_upcall RBAC role" +test_64h() { + local testfile=$DIR/$tdir/$tfile + local offset_start=100000 + local offset_limit=200000 + local projid=1001 + local srv_uc="" + local rbac + local fid + + (( MDS1_VERSION >= $(version_code 2.15.54) )) || + skip "Need MDS >= 2.15.54 for role-based controls" + + (( MDS1_VERSION >= $(version_code 2.16.50) )) && + srv_uc="server_upcall" + + do_nodes $(comma_list $(all_mdts_nodes)) \ + $LCTL set_param mdt.*.identity_upcall=NONE + + stack_trap \ + "$LFS setquota -p $((projid+offset_start)) --delete $DIR/$tdir" EXIT + stack_trap cleanup_local_client_nodemap EXIT + mkdir -p $DIR/$tdir || error "mkdir $DIR/$tdir failed" + chmod 777 $DIR/$tdir + $LFS project -p $((projid+offset_start)) -s $DIR/$tdir + $LFS setquota -p $((projid+offset_start)) -b 1G -B 1G $DIR/$tdir + $LFS project -d $DIR/$tdir + $LFS quota -aph $DIR/$tdir + setup_local_client_nodemap "c0" 1 1 + + # skip test if server does not support local_admin rbac role + rbac=$(do_facet mds $LCTL get_param -n nodemap.c0.rbac) + [[ "$rbac" =~ "local_admin" ]] || + skip "server does not support 'local_admin' rbac role" + + # Let's offset ids. Even root is offset. + do_facet mgs $LCTL nodemap_add_offset --name c0 \ + --offset $offset_start --limit $offset_limit || + error "cannot set offset for c0" + + rbac="file_perms,quota_ops" + [ -z "$srv_uc" ] || rbac="$rbac,$srv_uc" + do_facet mgs $LCTL nodemap_modify --name c0 --property rbac \ + --value $rbac || + error "setting rbac $rbac failed (1)" + wait_nm_sync c0 rbac + + $RUNAS touch $testfile + + # Without local_admin, root capabilities are dropped + chmod o+x $testfile && error "root chmod should fail (1)" + # and setquota/lfs project is not permitted + $LFS setquota -p $projid -b 4G -B 4G $DIR/$tdir && + error "setquota should fail (1)" + $LFS project -p $((projid+1)) -s $DIR/$tdir && + error "setting projid should fail (1)" + + rbac="file_perms,quota_ops,local_admin" + [ -z "$srv_uc" ] || rbac="$rbac,$srv_uc" + do_facet mgs $LCTL nodemap_modify --name c0 \ + --property rbac --value $rbac || + error "setting rbac $rbac failed (2)" + wait_nm_sync c0 rbac + # squash root by setting admin=0 + do_facet mgs $LCTL nodemap_modify --name c0 \ + --property admin --value 0 + wait_nm_sync c0 admin_nodemap + + # Even with local_admin, capabilities are dropped if root is squashed + chmod o+x $testfile && error "root chmod should fail (2)" + # and setquota/lfs project is not permitted + $LFS setquota -p $projid -b 4G -B 4G $DIR/$tdir && + error "setquota should fail (2)" + $LFS project -p $((projid+1)) -s $DIR/$tdir && + error "setting projid should fail (2)" + + do_facet mgs $LCTL nodemap_modify --name c0 \ + --property admin --value 1 + wait_nm_sync c0 admin_nodemap + + # with local_admin and admin=1, capabilities are kept + chmod o+x $testfile || error "root chmod failed (1)" + # and setquota/lfs project is permitted + $LFS setquota -p $projid -b 4G -B 4G $DIR/$tdir || + error "setquota failed (1)" + $LFS project -p $((projid+1)) -s $DIR/$tdir || + error "setting projid failed (1)" + + # remove offset and local_admin but keep admin, so that root + # on client is root on file system side + do_facet mgs $LCTL nodemap_del_offset --name c0 || + error "cannot del offset for c0" + rbac="file_perms,quota_ops" + [ -z "$srv_uc" ] || rbac="$rbac,$srv_uc" + do_facet mgs $LCTL nodemap_modify --name c0 --property rbac \ + --value $rbac || + error "setting rbac $rbac failed (3)" + wait_nm_sync c0 rbac + + # as root, capabilities are kept even without local_admin + chmod g+x $testfile || error "root chmod failed (2)" + # and setquota/lfs project is permitted + $LFS setquota -p $((projid+offset_start)) -b 3G -B 3G $DIR/$tdir || + error "setquota failed (2)" + $LFS project -p $((projid+offset_start)) -s $DIR/$tdir || + error "setting projid failed (2)" +} +run_test 64h "Nodemap enforces local_admin RBAC roles" + look_for_files() { local pattern=$1 local neg=$2 diff --git a/lustre/utils/wirecheck.c b/lustre/utils/wirecheck.c index 2a4d85e..9c0a80b 100644 --- a/lustre/utils/wirecheck.c +++ b/lustre/utils/wirecheck.c @@ -3097,6 +3097,7 @@ static void check_nodemap_key(void) CHECK_VALUE_X(NODEMAP_RBAC_SERVER_UPCALL); CHECK_VALUE_X(NODEMAP_RBAC_IGN_ROOT_PRJQUOTA); CHECK_VALUE_X(NODEMAP_RBAC_HSM_OPS); + CHECK_VALUE_X(NODEMAP_RBAC_LOCAL_ADMIN); CHECK_VALUE_X(NODEMAP_RBAC_NONE); CHECK_VALUE_X(NODEMAP_RBAC_ALL); } diff --git a/lustre/utils/wiretest.c b/lustre/utils/wiretest.c index 1d57383..f644558 100644 --- a/lustre/utils/wiretest.c +++ b/lustre/utils/wiretest.c @@ -6591,7 +6591,9 @@ void lustre_assert_wire_constants(void) (unsigned)NODEMAP_RBAC_IGN_ROOT_PRJQUOTA); LASSERTF(NODEMAP_RBAC_HSM_OPS == 0x00000100UL, "found 0x%.8xUL\n", (unsigned)NODEMAP_RBAC_HSM_OPS); - LASSERTF(NODEMAP_RBAC_NONE == 0xfffffe00UL, "found 0x%.8xUL\n", + LASSERTF(NODEMAP_RBAC_LOCAL_ADMIN == 0x00000200UL, "found 0x%.8xUL\n", + (unsigned)NODEMAP_RBAC_LOCAL_ADMIN); + LASSERTF(NODEMAP_RBAC_NONE == 0xfffffc00UL, "found 0x%.8xUL\n", (unsigned)NODEMAP_RBAC_NONE); LASSERTF(NODEMAP_RBAC_ALL == 0xffffffffUL, "found 0x%.8xUL\n", (unsigned)NODEMAP_RBAC_ALL);