repo/daemon/
mount_auth.rs1use std::path::Path;
10
11use objects::error::HeddleError;
12
13use super::mount_proto::MountDaemonRequest;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum MountClientAuth {
18 SameUid,
20 Unauthenticated,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct MountAuthDenied {
27 pub code: &'static str,
28 pub message: String,
29}
30
31impl MountAuthDenied {
32 pub fn unauthorized(message: impl Into<String>) -> Self {
33 Self {
34 code: super::mount_proto::ERR_UNAUTHORIZED,
35 message: message.into(),
36 }
37 }
38}
39
40impl From<MountAuthDenied> for HeddleError {
41 fn from(denied: MountAuthDenied) -> Self {
42 HeddleError::Config(format!("{}: {}", denied.code, denied.message))
43 }
44}
45
46pub fn trusted_mount_path(
48 auth: MountClientAuth,
49 supplied: &Path,
50) -> Result<&Path, MountAuthDenied> {
51 match auth {
52 MountClientAuth::SameUid => Ok(supplied),
53 MountClientAuth::Unauthenticated => Err(MountAuthDenied::unauthorized(
54 "refusing client-supplied mount_path over an unauthenticated transport; same-uid UDS is required",
55 )),
56 }
57}
58
59pub fn authorize_mount_request(
62 auth: MountClientAuth,
63 request: &MountDaemonRequest,
64) -> Result<(), MountAuthDenied> {
65 match auth {
66 MountClientAuth::SameUid => Ok(()),
67 MountClientAuth::Unauthenticated => Err(MountAuthDenied::unauthorized(format!(
68 "refusing {verb} over an unauthenticated transport; same-uid UDS is required",
69 verb = request_verb(request)
70 ))),
71 }
72}
73
74fn request_verb(request: &MountDaemonRequest) -> &'static str {
75 match request {
76 MountDaemonRequest::Mount { .. } => "mount",
77 MountDaemonRequest::Unmount { .. } => "unmount",
78 MountDaemonRequest::ListMounts {} => "list_mounts",
79 MountDaemonRequest::Health {} => "health",
80 MountDaemonRequest::Shutdown {} => "shutdown",
81 MountDaemonRequest::Unknown => "unknown",
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use std::path::{Path, PathBuf};
88
89 use super::{MountClientAuth, authorize_mount_request, trusted_mount_path};
90 use crate::daemon::MountDaemonRequest;
91
92 fn mount_request() -> MountDaemonRequest {
93 MountDaemonRequest::Mount {
94 thread_id: "agent-7".to_string(),
95 mount_path: PathBuf::from("/tmp/evil"),
96 repo_root: PathBuf::from("/tmp/repo"),
97 }
98 }
99
100 #[test]
101 fn unauthenticated_tcp_must_not_honor_client_mount_path() {
102 let denied = trusted_mount_path(MountClientAuth::Unauthenticated, Path::new("/tmp/evil"));
103 assert!(
104 denied.is_err(),
105 "localhost TCP must not honor a client-supplied mount_path"
106 );
107 }
108
109 #[test]
110 fn same_uid_peer_may_supply_mount_path() {
111 let path = Path::new("/tmp/ok");
112 let accepted = trusted_mount_path(MountClientAuth::SameUid, path)
113 .expect("same-uid UDS may honor mount_path");
114 assert_eq!(accepted, path);
115 }
116
117 #[test]
118 fn unauthenticated_tcp_must_not_drive_mount_or_unmount() {
119 let mount = authorize_mount_request(MountClientAuth::Unauthenticated, &mount_request());
120 assert!(mount.is_err(), "unauthenticated mount must fail closed");
121
122 let unmount = authorize_mount_request(
123 MountClientAuth::Unauthenticated,
124 &MountDaemonRequest::Unmount {
125 thread_id: "agent-7".to_string(),
126 },
127 );
128 assert!(unmount.is_err(), "unauthenticated unmount must fail closed");
129 }
130
131 #[test]
132 fn same_uid_peer_is_authorized() {
133 authorize_mount_request(MountClientAuth::SameUid, &mount_request())
134 .expect("same-uid mount is allowed");
135 }
136}