Allow to enforce permissions at the server side

This commit is contained in:
Vitaliy Filippov
2025-08-24 16:20:19 +03:00
parent fb1c3e00f4
commit fbf14fb0cb
16 changed files with 225 additions and 27 deletions
-1
View File
@@ -12,7 +12,6 @@ void nfs_kv_procs(nfs_client_t *self);
int nfs3_fsstat_proc(void *opaque, rpc_op_t *rop);
int nfs3_fsinfo_proc(void *opaque, rpc_op_t *rop);
int nfs3_pathconf_proc(void *opaque, rpc_op_t *rop);
int nfs3_access_proc(void *opaque, rpc_op_t *rop);
int nfs3_null_proc(void *opaque, rpc_op_t *rop);
int nfs3_commit_proc(void *opaque, rpc_op_t *rop);
int mount3_mnt_proc(void *opaque, rpc_op_t *rop);
+1 -1
View File
@@ -182,7 +182,7 @@ void nfs_kv_procs(nfs_client_t *self)
{NFS_PROGRAM, NFS_V3, NFS3_GETATTR, kv_nfs3_getattr_proc, (xdrproc_t)xdr_GETATTR3args, sizeof(GETATTR3args), (xdrproc_t)xdr_GETATTR3res, sizeof(GETATTR3res), self},
{NFS_PROGRAM, NFS_V3, NFS3_SETATTR, kv_nfs3_setattr_proc, (xdrproc_t)xdr_SETATTR3args, sizeof(SETATTR3args), (xdrproc_t)xdr_SETATTR3res, sizeof(SETATTR3res), self},
{NFS_PROGRAM, NFS_V3, NFS3_LOOKUP, kv_nfs3_lookup_proc, (xdrproc_t)xdr_LOOKUP3args, sizeof(LOOKUP3args), (xdrproc_t)xdr_LOOKUP3res, sizeof(LOOKUP3res), self},
{NFS_PROGRAM, NFS_V3, NFS3_ACCESS, nfs3_access_proc, (xdrproc_t)xdr_ACCESS3args, sizeof(ACCESS3args), (xdrproc_t)xdr_ACCESS3res, sizeof(ACCESS3res), self},
{NFS_PROGRAM, NFS_V3, NFS3_ACCESS, kv_nfs3_access_proc, (xdrproc_t)xdr_ACCESS3args, sizeof(ACCESS3args), (xdrproc_t)xdr_ACCESS3res, sizeof(ACCESS3res), self},
{NFS_PROGRAM, NFS_V3, NFS3_READLINK, kv_nfs3_readlink_proc, (xdrproc_t)xdr_READLINK3args, sizeof(READLINK3args), (xdrproc_t)xdr_READLINK3res, sizeof(READLINK3res), self},
{NFS_PROGRAM, NFS_V3, NFS3_READ, kv_nfs3_read_proc, (xdrproc_t)xdr_READ3args, sizeof(READ3args), (xdrproc_t)xdr_READ3res, sizeof(READ3res), self},
{NFS_PROGRAM, NFS_V3, NFS3_WRITE, kv_nfs3_write_proc, (xdrproc_t)xdr_WRITE3args, sizeof(WRITE3args), (xdrproc_t)xdr_WRITE3res, sizeof(WRITE3res), self},
+3
View File
@@ -135,7 +135,10 @@ uint64_t align_shared_size(nfs_client_t *self, uint64_t size);
void nfs_do_rmw(nfs_rmw_t *rmw);
void nfs_move_inode_from(nfs_proxy_t *proxy, uint64_t ino, uint64_t shared_ino,
uint64_t shared_offset, std::function<void(int res, bool moved)> cb);
uint32_t kv_get_access(const authsys_parms & auth_sys, const json11::Json & attrs);
bool kv_is_accessible(const authsys_parms & auth_sys, const json11::Json & attrs, uint32_t access);
int kv_nfs3_access_proc(void *opaque, rpc_op_t *rop);
int kv_nfs3_getattr_proc(void *opaque, rpc_op_t *rop);
int kv_nfs3_setattr_proc(void *opaque, rpc_op_t *rop);
int kv_nfs3_lookup_proc(void *opaque, rpc_op_t *rop);
+19
View File
@@ -111,6 +111,7 @@ static void kv_continue_create(kv_create_state *st, int state)
else if (state == 3) goto resume_3;
else if (state == 4) goto resume_4;
else if (state == 5) goto resume_5;
else if (state == 6) goto resume_6;
if (st->self->parent->trace)
fprintf(stderr, "[%d] CREATE %ju/%s ATTRS %s\n", st->self->nfs_fd, st->dir_ino, st->filename.c_str(), json11::Json(st->attrobj).dump().c_str());
if (st->filename == "" || st->filename.find("/") != std::string::npos)
@@ -127,6 +128,24 @@ static void kv_continue_create(kv_create_state *st, int state)
if (st->rop->auth_sys.gid && st->attrobj.find("gid") == st->attrobj.end())
st->attrobj["gid"] = (uint64_t)st->rop->auth_sys.gid;
st->attrs = std::move(st->attrobj);
if (st->self->parent->enforce_perms)
{
// Check that the directory is actually a directory and is accessible
kv_read_inode(st->self->parent, st->dir_ino, [st](int res, const std::string & value, json11::Json attrs)
{
st->res = res == 0 ? (attrs["type"].string_value() == "dir"
? (kv_is_accessible(st->rop->auth_sys, attrs, ACCESS3_MODIFY) ? 0 : -EACCES) : -ENOTDIR) : res;
kv_continue_create(st, 6);
}, true/*allow_cache*/);
return;
resume_6:
if (st->res < 0)
{
auto cb = std::move(st->cb);
cb(st->res);
return;
}
}
resume_1:
// Generate inode ID
// Directories and special files don't need pool
+103
View File
@@ -65,6 +65,11 @@ int kv_nfs3_getattr_proc(void *opaque, rpc_op_t *rop)
{
if (self->parent->trace)
fprintf(stderr, "[%d] GETATTR %ju -> %s\n", self->nfs_fd, ino, value.c_str());
if (res == 0 && self->parent->enforce_perms &&
!kv_is_accessible(rop->auth_sys, attrs, ACCESS3_READ))
{
res = -EACCES;
}
if (res < 0)
{
*reply = (GETATTR3res){ .status = vitastor_nfs_map_err(-res) };
@@ -82,3 +87,101 @@ int kv_nfs3_getattr_proc(void *opaque, rpc_op_t *rop)
});
return 1;
}
uint32_t kv_get_access(const authsys_parms & auth_sys, const json11::Json & attrs)
{
uint32_t mode = attrs["mode"].is_null() ? (attrs["type"] == "dir" ? 0755 : 0644) : attrs["mode"].uint64_value();
uint32_t uid = attrs["uid"].uint64_value();
uint32_t gid = attrs["gid"].uint64_value();
uint32_t access = 0;
if (uid == auth_sys.uid)
{
access |= ((mode & (1 << 8)) ? ACCESS3_READ|ACCESS3_LOOKUP : 0);
access |= ((mode & (1 << 7)) ? ACCESS3_MODIFY|ACCESS3_EXTEND|ACCESS3_DELETE : 0);
access |= ((mode & (1 << 6)) ? ACCESS3_EXECUTE : 0);
}
for (uint32_t i = 0; i < auth_sys.gids.gids_len; i++)
{
if (gid == auth_sys.gids.gids_val[i])
gid = auth_sys.gid;
}
if (gid == auth_sys.gid)
{
access |= ((mode & (1 << 5)) ? ACCESS3_READ|ACCESS3_LOOKUP : 0);
access |= ((mode & (1 << 4)) ? ACCESS3_MODIFY|ACCESS3_EXTEND|ACCESS3_DELETE : 0);
access |= ((mode & (1 << 3)) ? ACCESS3_EXECUTE : 0);
}
access |= ((mode & (1 << 2)) ? ACCESS3_READ|ACCESS3_LOOKUP : 0);
access |= ((mode & (1 << 1)) ? ACCESS3_MODIFY|ACCESS3_EXTEND|ACCESS3_DELETE : 0);
access |= ((mode & (1 << 0)) ? ACCESS3_EXECUTE : 0);
return access;
}
bool kv_is_accessible(const authsys_parms & auth_sys, const json11::Json & attrs, uint32_t access)
{
uint32_t mode = attrs["mode"].is_null() ? (attrs["type"] == "dir" ? 0755 : 0644) : attrs["mode"].uint64_value();
uint32_t mask = 1 << (access == ACCESS3_EXECUTE ? 0
: (access == ACCESS3_MODIFY || access == ACCESS3_EXTEND || access == ACCESS3_DELETE ? 1 : 2));
if (mode & mask)
return true;
uint32_t uid = attrs["uid"].uint64_value();
if ((mode & (mask << 6)) && (uint32_t)uid == auth_sys.uid)
return true;
if ((mode & (mask << 3)))
{
uint32_t gid = attrs["gid"].uint64_value();
if (gid == auth_sys.gid)
return true;
for (uint32_t i = 0; i < auth_sys.gids.gids_len; i++)
if (gid == auth_sys.gids.gids_val[i])
return true;
}
return false;
}
int kv_nfs3_access_proc(void *opaque, rpc_op_t *rop)
{
nfs_client_t *self = (nfs_client_t*)opaque;
ACCESS3args *args = (ACCESS3args*)rop->request;
ACCESS3res *reply = (ACCESS3res*)rop->reply;
std::string fh = args->object;
auto ino = kv_fh_inode(fh);
if (self->parent->trace)
fprintf(stderr, "[%d] ACCESS %ju\n", self->nfs_fd, ino);
if (!kv_fh_valid(fh) || !ino)
{
*reply = (ACCESS3res){ .status = NFS3ERR_INVAL };
rpc_queue_reply(rop);
return 0;
}
kv_read_inode(self->parent, ino, [=](int res, const std::string & value, json11::Json attrs)
{
if (self->parent->trace)
fprintf(stderr, "[%d] ACCESS %ju -> %s\n", self->nfs_fd, ino, value.c_str());
if (res < 0)
{
*reply = (ACCESS3res){ .status = vitastor_nfs_map_err(-res) };
}
else
{
uint32_t actual = kv_get_access(rop->auth_sys, attrs);
if (args->access & actual)
{
*reply = (ACCESS3res){
.status = NFS3_OK,
.resok = (ACCESS3resok){
.access = actual,
},
};
}
else
{
*reply = (ACCESS3res){
.status = NFS3ERR_ACCES,
};
}
}
rpc_queue_reply(rop);
});
return 1;
}
+4 -2
View File
@@ -49,7 +49,8 @@ resume_0:
st->res2 = 0;
kv_read_inode(st->self->parent, st->ino, [st](int res, const std::string & value, json11::Json attrs)
{
st->res = res == 0 ? (attrs["type"].string_value() == "dir" ? -EISDIR : 0) : res;
st->res = (res != 0 ? res : (attrs["type"].string_value() == "dir" ? -EISDIR :
(st->self->parent->enforce_perms && !kv_is_accessible(st->rop->auth_sys, attrs, ACCESS3_READ) ? -EACCES : 0)));
st->ientry_text = value;
st->ientry = attrs;
if (!--st->wait)
@@ -60,7 +61,8 @@ resume_0:
// Check that the new directory exists
kv_read_inode(st->self->parent, st->dir_ino, [st](int res, const std::string & value, json11::Json attrs)
{
st->res2 = res == 0 ? (attrs["type"].string_value() == "dir" ? 0 : -ENOTDIR) : res;
st->res2 = (res != 0 ? res : (attrs["type"].string_value() != "dir" ? -ENOTDIR :
(st->self->parent->enforce_perms && !kv_is_accessible(st->rop->auth_sys, attrs, ACCESS3_MODIFY) ? -EACCES : 0)));
if (!--st->wait)
nfs_kv_continue_link(st, 1);
});
+7
View File
@@ -70,6 +70,13 @@ int kv_nfs3_lookup_proc(void *opaque, rpc_op_t *rop)
rpc_queue_reply(rop);
return;
}
else if (self->parent->enforce_perms && ientry["type"] != "link" &&
!kv_is_accessible(rop->auth_sys, ientry, ACCESS3_LOOKUP))
{
*reply = (LOOKUP3res){ .status = NFS3ERR_ACCES };
rpc_queue_reply(rop);
return;
}
*reply = (LOOKUP3res){
.status = NFS3_OK,
.resok = (LOOKUP3resok){
+14 -2
View File
@@ -66,6 +66,12 @@ resume_1:
cb(st->res < 0 ? st->res : -EINVAL);
return;
}
if (st->self->parent->enforce_perms && !kv_is_accessible(st->rop->auth_sys, st->ientry, ACCESS3_READ))
{
auto cb = std::move(st->cb);
cb(-EACCES);
return;
}
if (st->ientry["shared_ino"].uint64_value() != 0)
{
if (st->offset >= st->ientry["size"].uint64_value())
@@ -142,9 +148,9 @@ resume_2:
return;
}
}
else if (st->self->rdma_conn)
else if (st->self->rdma_conn || st->self->parent->enforce_perms)
{
// Take ientry from read_hack_cache for RDMA connections
// Take ientry from read_hack_cache for RDMA connections or for the permission check
{
auto rh_it = st->self->parent->kvfs->read_hack_cache.find(st->ino);
if (rh_it != st->self->parent->kvfs->read_hack_cache.end())
@@ -170,6 +176,12 @@ resume_4:
}
st->self->parent->kvfs->read_hack_cache[st->ino] = st->ientry;
}
if (st->self->parent->enforce_perms && !kv_is_accessible(st->rop->auth_sys, st->ientry, ACCESS3_READ))
{
auto cb = std::move(st->cb);
cb(-EACCES);
return;
}
}
st->aligned_offset = align_down(st->offset);
st->aligned_size = align_up(st->offset+st->size) - st->aligned_offset;
+10 -1
View File
@@ -94,7 +94,7 @@ static void nfs_kv_continue_readdir(nfs_kv_readdir_state *st, int state)
return;
}
// Add . and ..
if (st->cookie <= 1)
if (st->cookie <= 1 || st->self->parent->enforce_perms)
{
kv_read_inode(st->self->parent, st->dir_ino, [st](int res, const std::string & value, json11::Json ientry)
{
@@ -111,6 +111,15 @@ resume_1:
cb(st->res);
return;
}
if (st->self->parent->enforce_perms && !kv_is_accessible(st->rop->auth_sys, st->ientry, ACCESS3_READ))
{
auto cb = std::move(st->cb);
cb(-EACCES);
return;
}
}
if (st->cookie <= 1)
{
if (st->cookie == 0)
{
auto fh = kv_fh(st->dir_ino);
+24 -1
View File
@@ -44,6 +44,7 @@ static void nfs_kv_continue_delete(kv_del_state *st, int state)
else if (state == 5) goto resume_5;
else if (state == 6) goto resume_6;
else if (state == 7) goto resume_7;
else if (state == 8) goto resume_8;
else
{
fprintf(stderr, "BUG: invalid state in nfs_kv_continue_delete()");
@@ -123,6 +124,28 @@ resume_2:
cb(st->is_rmdir ? -ENOTDIR : -EISDIR);
return;
}
if (st->self->parent->enforce_perms)
{
// Check directory permission
kv_read_inode(st->self->parent, st->dir_ino, [st](int res, const std::string & value, json11::Json attrs)
{
st->res = (res != 0 ? res : (attrs["type"].string_value() != "dir" ? -ENOTDIR :
(st->self->parent->enforce_perms && (
!kv_is_accessible(st->rop->auth_sys, attrs, ACCESS3_DELETE) &&
(!(attrs["mode"].uint64_value() & 01000 /* directory sticky bit */) ||
st->ientry["uid"].uint64_value() != st->rop->auth_sys.uid)
) ? -EACCES : 0)));
nfs_kv_continue_delete(st, 8);
}, st->allow_cache);
return;
resume_8:
if (st->res < 0)
{
auto cb = std::move(st->cb);
cb(st->res);
return;
}
}
// (3) Delete direntry with CAS
st->self->parent->db->del(kv_direntry_key(st->dir_ino, st->filename), [st](int res)
{
@@ -157,7 +180,7 @@ resume_3:
}
if (st->is_rmdir)
{
// (4) Check if directory actually is not empty
// (4) Check if the directory actually is not empty
st->list_handle = st->self->parent->db->list_start(kv_direntry_key(st->ino, ""));
st->self->parent->db->list_next(st->list_handle, [st](int res, const std::string & key, const std::string & value)
{
+22 -2
View File
@@ -60,6 +60,7 @@ static void nfs_kv_continue_rename(nfs_kv_rename_state *st, int state)
else if (state == 10) goto resume_10;
else if (state == 11) goto resume_11;
else if (state == 12) goto resume_12;
else if (state == 13) goto resume_13;
else
{
fprintf(stderr, "BUG: invalid state in nfs_kv_continue_rename()");
@@ -154,12 +155,13 @@ resume_3:
}
}
}
else
if (!st->new_exists || st->self->parent->enforce_perms)
{
// Check that the new directory is actually a directory
kv_read_inode(st->self->parent, st->new_dir_ino, [st](int res, const std::string & value, json11::Json attrs)
{
st->res = res == 0 ? (attrs["type"].string_value() == "dir" ? 0 : -ENOTDIR) : res;
st->res = (res != 0 ? res : (attrs["type"].string_value() != "dir" ? -ENOTDIR :
(st->self->parent->enforce_perms && !kv_is_accessible(st->rop->auth_sys, attrs, ACCESS3_MODIFY) ? -EACCES : 0)));
nfs_kv_continue_rename(st, 4);
});
return;
@@ -171,6 +173,24 @@ resume_4:
return;
}
}
if (st->self->parent->enforce_perms)
{
// Check that the old directory is accessible
kv_read_inode(st->self->parent, st->old_dir_ino, [st](int res, const std::string & value, json11::Json attrs)
{
st->res = (res != 0 ? res : (attrs["type"].string_value() != "dir" ? -ENOTDIR :
(!kv_is_accessible(st->rop->auth_sys, attrs, ACCESS3_MODIFY) ? -EACCES : 0)));
nfs_kv_continue_rename(st, 13);
});
return;
resume_13:
if (st->res < 0)
{
auto cb = std::move(st->cb);
cb(st->res);
return;
}
}
// Write the new direntry
st->self->parent->db->set(kv_direntry_key(st->new_dir_ino, st->new_name), st->old_direntry_text, [st](int res)
{
+6
View File
@@ -53,6 +53,12 @@ resume_1:
cb(st->res);
return;
}
if (st->self->parent->enforce_perms && !kv_is_accessible(st->rop->auth_sys, st->ientry, ACCESS3_MODIFY))
{
auto cb = std::move(st->cb);
cb(-EACCES);
return;
}
if (st->ientry["type"].string_value() != "file" &&
st->ientry["type"].string_value() != "" &&
!st->set_attrs["size"].is_null())
+6
View File
@@ -786,6 +786,12 @@ resume_1:
cb(st->res == 0 ? -EINVAL : st->res);
return;
}
if (st->proxy->enforce_perms && !kv_is_accessible(st->rop->auth_sys, st->ientry, ACCESS3_MODIFY))
{
auto cb = std::move(st->cb);
cb(-EACCES);
return;
}
st->was_immediate = st->proxy->cli->get_immediate_commit(st->ino);
st->new_size = st->ientry["size"].uint64_value();
if (st->new_size < st->offset + st->size)
+2 -16
View File
@@ -16,12 +16,13 @@ nfsstat3 vitastor_nfs_map_err(int err)
}
return (err == EINVAL ? NFS3ERR_INVAL
: (err == ENOENT ? NFS3ERR_NOENT
: (err == EACCES ? NFS3ERR_ACCES
: (err == ENOSPC ? NFS3ERR_NOSPC
: (err == EEXIST ? NFS3ERR_EXIST
: (err == EISDIR ? NFS3ERR_ISDIR
: (err == ENOTDIR ? NFS3ERR_NOTDIR
: (err == ENOTEMPTY ? NFS3ERR_NOTEMPTY
: (err == EIO ? NFS3ERR_IO : (err ? NFS3ERR_IO : NFS3_OK)))))))));
: (err == EIO ? NFS3ERR_IO : (err ? NFS3ERR_IO : NFS3_OK))))))))));
}
int nfs3_null_proc(void *opaque, rpc_op_t *rop)
@@ -30,21 +31,6 @@ int nfs3_null_proc(void *opaque, rpc_op_t *rop)
return 0;
}
int nfs3_access_proc(void *opaque, rpc_op_t *rop)
{
//nfs_client_t *self = (nfs_client_t*)opaque;
ACCESS3args *args = (ACCESS3args*)rop->request;
ACCESS3res *reply = (ACCESS3res*)rop->reply;
*reply = (ACCESS3res){
.status = NFS3_OK,
.resok = (ACCESS3resok){
.access = args->access,
},
};
rpc_queue_reply(rop);
return 0;
}
int nfs3_commit_proc(void *opaque, rpc_op_t *rop)
{
nfs_client_t *self = (nfs_client_t*)opaque;
+2
View File
@@ -119,6 +119,7 @@ static const char* help_text =
" --nfspath <PATH> set NFS export path to <PATH> (default is /)\n"
" --pidfile <FILE> write process ID to the specified file\n"
" --logfile <FILE> log to the specified file\n"
" --enforce 1 enforce permissions at the server side (default is disabled)\n"
" --foreground 1 stay in foreground, do not daemonize\n"
"\n"
"NFS proxy is stateless if you use immediate_commit=all in your cluster and if\n"
@@ -206,6 +207,7 @@ void nfs_proxy_t::run(json11::Json cfg)
bind_address = "0.0.0.0";
default_pool = cfg["pool"].as_string();
portmap_enabled = !json_is_false(cfg["portmap"]);
enforce_perms = json_is_true(cfg["enforce"]);
nfs_port = cfg["port"].uint64_value() & 0xffff;
#ifdef WITH_RDMACM
nfs_rdma_port = cfg["nfs_rdma"].uint64_value() & 0xffff;
+2 -1
View File
@@ -33,7 +33,8 @@ public:
// FIXME: Maybe allow to create files in different pools?
std::string default_pool;
std::string export_root;
bool portmap_enabled;
bool enforce_perms = false;
bool portmap_enabled = false;
bool nfs_port_auto = false;
unsigned nfs_port = 0;
unsigned nfs_rdma_port = 0;