Spaces:
Running
Running
Commit Β·
4b8758b
1
Parent(s): 9aa9297
Deploy from markdownfs@1a8d486
Browse files- src/auth/session.rs +23 -4
- src/db.rs +6 -0
- src/fs/mod.rs +3 -0
- src/server/perms.rs +3 -1
- src/server/routes_fs.rs +1 -0
- tests/permissions/main.rs +1 -2
src/auth/session.rs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
use super::perms::{check_permission, Access};
|
| 2 |
-
use super::{Gid, Uid, ROOT_UID};
|
| 3 |
use crate::fs::inode::Inode;
|
| 4 |
|
| 5 |
/// Context for the user an agent is acting on behalf of.
|
|
@@ -105,10 +105,29 @@ impl Session {
|
|
| 105 |
}
|
| 106 |
}
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
/// Whether either the principal or (if delegating) the delegate is root.
|
| 109 |
-
///
|
| 110 |
-
/// This is used for checks like "is this session effectively root?" β answer: only if
|
| 111 |
-
/// there's no delegate constraining it.
|
| 112 |
pub fn is_effectively_root(&self) -> bool {
|
| 113 |
if self.uid != ROOT_UID {
|
| 114 |
return false;
|
|
|
|
| 1 |
use super::perms::{check_permission, Access};
|
| 2 |
+
use super::{Gid, Uid, ROOT_UID, WHEEL_GID};
|
| 3 |
use crate::fs::inode::Inode;
|
| 4 |
|
| 5 |
/// Context for the user an agent is acting on behalf of.
|
|
|
|
| 105 |
}
|
| 106 |
}
|
| 107 |
|
| 108 |
+
/// Whether the principal is a wheel-group member (admin-equivalent).
|
| 109 |
+
pub fn is_wheel(&self) -> bool {
|
| 110 |
+
self.groups.contains(&WHEEL_GID)
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
/// Whether the session may bypass per-file Read checks (i.e., see every
|
| 114 |
+
/// directory entry and read every file). True for literal uid=0 or any
|
| 115 |
+
/// wheel-group member. Privileged write ops (chmod-across-owner,
|
| 116 |
+
/// chown-uid) still go through the stricter `is_effectively_root` check.
|
| 117 |
+
/// When delegating, the delegate must also be admin-equivalent.
|
| 118 |
+
pub fn can_read_anywhere(&self) -> bool {
|
| 119 |
+
let principal_admin = self.uid == ROOT_UID || self.is_wheel();
|
| 120 |
+
if !principal_admin {
|
| 121 |
+
return false;
|
| 122 |
+
}
|
| 123 |
+
match &self.delegate {
|
| 124 |
+
Some(d) => d.uid == ROOT_UID || d.groups.contains(&WHEEL_GID),
|
| 125 |
+
None => true,
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
/// Whether either the principal or (if delegating) the delegate is root.
|
| 130 |
+
/// Strict: only literal uid=0 qualifies. Used by privileged write ops.
|
|
|
|
|
|
|
| 131 |
pub fn is_effectively_root(&self) -> bool {
|
| 132 |
if self.uid != ROOT_UID {
|
| 133 |
return false;
|
src/db.rs
CHANGED
|
@@ -683,7 +683,13 @@ impl MarkdownDb {
|
|
| 683 |
.map(|u| u.groups[0])
|
| 684 |
.unwrap_or(0);
|
| 685 |
let home = format!("/home/{name}");
|
|
|
|
| 686 |
let _ = guard.fs.mkdir_p(&home, uid, gid);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 687 |
// Lock the home dir down: only the owner can list/enter it.
|
| 688 |
let _ = guard.fs.chmod(&home, 0o700);
|
| 689 |
drop(guard);
|
|
|
|
| 683 |
.map(|u| u.groups[0])
|
| 684 |
.unwrap_or(0);
|
| 685 |
let home = format!("/home/{name}");
|
| 686 |
+
let already_exists = guard.fs.stat(&home).is_ok();
|
| 687 |
let _ = guard.fs.mkdir_p(&home, uid, gid);
|
| 688 |
+
// If the home directory was orphaned by a prior user with the same
|
| 689 |
+
// name (and a now-stale uid), reclaim it for the new account.
|
| 690 |
+
if already_exists {
|
| 691 |
+
let _ = guard.fs.chown(&home, uid, gid);
|
| 692 |
+
}
|
| 693 |
// Lock the home dir down: only the owner can list/enter it.
|
| 694 |
let _ = guard.fs.chmod(&home, 0o700);
|
| 695 |
drop(guard);
|
src/fs/mod.rs
CHANGED
|
@@ -1250,6 +1250,9 @@ impl VirtualFs {
|
|
| 1250 |
Some(s) => s,
|
| 1251 |
None => return true,
|
| 1252 |
};
|
|
|
|
|
|
|
|
|
|
| 1253 |
let inode = match self.get_inode(id) {
|
| 1254 |
Ok(i) => i,
|
| 1255 |
Err(_) => return false,
|
|
|
|
| 1250 |
Some(s) => s,
|
| 1251 |
None => return true,
|
| 1252 |
};
|
| 1253 |
+
if session.can_read_anywhere() {
|
| 1254 |
+
return self.get_inode(id).is_ok();
|
| 1255 |
+
}
|
| 1256 |
let inode = match self.get_inode(id) {
|
| 1257 |
Ok(i) => i,
|
| 1258 |
Err(_) => return false,
|
src/server/perms.rs
CHANGED
|
@@ -23,7 +23,9 @@ pub async fn require_perm(
|
|
| 23 |
path: &str,
|
| 24 |
access: Access,
|
| 25 |
) -> Result<bool, VfsError> {
|
| 26 |
-
if session.is_effectively_root()
|
|
|
|
|
|
|
| 27 |
return Ok(db.stat(path).await.is_ok());
|
| 28 |
}
|
| 29 |
match db.stat(path).await {
|
|
|
|
| 23 |
path: &str,
|
| 24 |
access: Access,
|
| 25 |
) -> Result<bool, VfsError> {
|
| 26 |
+
if session.is_effectively_root()
|
| 27 |
+
|| (matches!(access, Access::Read | Access::Execute) && session.can_read_anywhere())
|
| 28 |
+
{
|
| 29 |
return Ok(db.stat(path).await.is_ok());
|
| 30 |
}
|
| 31 |
match db.stat(path).await {
|
src/server/routes_fs.rs
CHANGED
|
@@ -86,6 +86,7 @@ async fn get_fs_root(State(state): State<AppState>, headers: HeaderMap) -> impl
|
|
| 86 |
.into_iter()
|
| 87 |
.filter(|e| {
|
| 88 |
session.is_effectively_root()
|
|
|
|
| 89 |
|| session.has_permission_bits(e.mode, e.uid, e.gid, Access::Read)
|
| 90 |
})
|
| 91 |
.collect();
|
|
|
|
| 86 |
.into_iter()
|
| 87 |
.filter(|e| {
|
| 88 |
session.is_effectively_root()
|
| 89 |
+
|| session.can_read_anywhere()
|
| 90 |
|| session.has_permission_bits(e.mode, e.uid, e.gid, Access::Read)
|
| 91 |
})
|
| 92 |
.collect();
|
tests/permissions/main.rs
CHANGED
|
@@ -55,7 +55,7 @@ fn try_run(line: &str, fs: &mut VirtualFs, session: &mut Session) -> Result<Stri
|
|
| 55 |
///
|
| 56 |
/// Users:
|
| 57 |
/// - root (uid=0) β superuser
|
| 58 |
-
/// - alice (uid=1) β member of: alice, engineering
|
| 59 |
/// - bob (uid=2) β member of: bob, engineering
|
| 60 |
/// - carol (uid=3) β member of: carol, finance
|
| 61 |
/// - agent-x (uid=4) β agent, member of: agent-x, engineering
|
|
@@ -75,7 +75,6 @@ fn setup() -> (VirtualFs, Session, Session, Session, Session, Session) {
|
|
| 75 |
|
| 76 |
// Group memberships
|
| 77 |
run("usermod -aG engineering alice", &mut fs, &mut root);
|
| 78 |
-
run("usermod -aG wheel alice", &mut fs, &mut root);
|
| 79 |
run("usermod -aG engineering bob", &mut fs, &mut root);
|
| 80 |
run("usermod -aG finance carol", &mut fs, &mut root);
|
| 81 |
run("usermod -aG engineering agent-x", &mut fs, &mut root);
|
|
|
|
| 55 |
///
|
| 56 |
/// Users:
|
| 57 |
/// - root (uid=0) β superuser
|
| 58 |
+
/// - alice (uid=1) β member of: alice, engineering (regular user; wheel = admin)
|
| 59 |
/// - bob (uid=2) β member of: bob, engineering
|
| 60 |
/// - carol (uid=3) β member of: carol, finance
|
| 61 |
/// - agent-x (uid=4) β agent, member of: agent-x, engineering
|
|
|
|
| 75 |
|
| 76 |
// Group memberships
|
| 77 |
run("usermod -aG engineering alice", &mut fs, &mut root);
|
|
|
|
| 78 |
run("usermod -aG engineering bob", &mut fs, &mut root);
|
| 79 |
run("usermod -aG finance carol", &mut fs, &mut root);
|
| 80 |
run("usermod -aG engineering agent-x", &mut fs, &mut root);
|