peer.rs (8861B)
1 //! Platform-bounded local-admin peer authorization. 2 3 use core::fmt; 4 use std::error::Error; 5 6 /// Host support level for local-admin peer authorization. 7 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 8 pub enum AdminPeerAuthorizationSupport { 9 /// Linux requires kernel-reported `SO_PEERCRED` credentials. 10 LinuxSoPeerCredRequired, 11 /// macOS v1 relies only on owner-restricted filesystem permissions. 12 MacOsFilesystemOwnerPermissionsOnly, 13 /// The local-admin transport is not supported on this platform in v1. 14 Unsupported, 15 } 16 17 impl AdminPeerAuthorizationSupport { 18 /// Returns the exact v1 authorization support for the compilation target. 19 #[must_use] 20 pub const fn current() -> Self { 21 #[cfg(target_os = "linux")] 22 { 23 Self::LinuxSoPeerCredRequired 24 } 25 #[cfg(target_os = "macos")] 26 { 27 Self::MacOsFilesystemOwnerPermissionsOnly 28 } 29 #[cfg(not(any(target_os = "linux", target_os = "macos")))] 30 { 31 Self::Unsupported 32 } 33 } 34 } 35 36 /// A safe configuration failure for local-admin peer authorization. 37 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 38 pub enum AdminPeerAuthorizationPolicyError { 39 InvalidAdminGroupId, 40 AdminGroupUnsupported, 41 } 42 43 impl fmt::Display for AdminPeerAuthorizationPolicyError { 44 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 45 formatter.write_str("admin peer authorization policy is invalid") 46 } 47 } 48 49 impl Error for AdminPeerAuthorizationPolicyError {} 50 51 /// One immutable policy shared by Unix-socket permissions and peer admission. 52 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 53 pub struct AdminPeerAuthorizationPolicy { 54 admin_gid: Option<u32>, 55 } 56 57 impl AdminPeerAuthorizationPolicy { 58 /// Restricts access to the daemon's effective user identity. 59 #[must_use] 60 pub const fn owner_only() -> Self { 61 Self { admin_gid: None } 62 } 63 64 /// Allows the configured Linux admin group in addition to the daemon user. 65 pub fn with_admin_gid(admin_gid: u32) -> Result<Self, AdminPeerAuthorizationPolicyError> { 66 if AdminPeerAuthorizationSupport::current() 67 != AdminPeerAuthorizationSupport::LinuxSoPeerCredRequired 68 { 69 return Err(AdminPeerAuthorizationPolicyError::AdminGroupUnsupported); 70 } 71 if admin_gid == u32::MAX { 72 return Err(AdminPeerAuthorizationPolicyError::InvalidAdminGroupId); 73 } 74 Ok(Self { 75 admin_gid: Some(admin_gid), 76 }) 77 } 78 79 /// Returns the host support contract represented by this policy. 80 #[must_use] 81 pub const fn support(self) -> AdminPeerAuthorizationSupport { 82 AdminPeerAuthorizationSupport::current() 83 } 84 85 /// Returns the configured Linux admin group, when present. 86 #[must_use] 87 pub const fn admin_gid(self) -> Option<u32> { 88 self.admin_gid 89 } 90 } 91 92 impl Default for AdminPeerAuthorizationPolicy { 93 fn default() -> Self { 94 Self::owner_only() 95 } 96 } 97 98 #[cfg(target_os = "linux")] 99 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 100 pub(crate) enum PeerAuthorizationFailure { 101 CredentialsUnavailable { kind: std::io::ErrorKind }, 102 Denied, 103 } 104 105 #[cfg(target_os = "macos")] 106 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 107 pub(crate) enum PeerAuthorizationFailure {} 108 109 #[cfg(any(target_os = "linux", target_os = "macos"))] 110 #[derive(Clone, Copy, Debug)] 111 pub(crate) struct PeerAuthorizer { 112 policy: AdminPeerAuthorizationPolicy, 113 daemon_euid: u32, 114 } 115 116 #[cfg(any(target_os = "linux", target_os = "macos"))] 117 impl PeerAuthorizer { 118 pub(crate) const fn new(policy: AdminPeerAuthorizationPolicy, daemon_euid: u32) -> Self { 119 Self { 120 policy, 121 daemon_euid, 122 } 123 } 124 125 #[cfg(target_os = "linux")] 126 pub(crate) fn authorize( 127 self, 128 stream: &tokio::net::UnixStream, 129 ) -> Result<(), PeerAuthorizationFailure> { 130 self.authorize_linux_result( 131 stream 132 .peer_cred() 133 .map(|credentials| (credentials.uid(), credentials.gid())) 134 .map_err(|error| error.kind()), 135 ) 136 } 137 138 #[cfg(target_os = "linux")] 139 fn authorize_linux_result( 140 self, 141 credentials: Result<(u32, u32), std::io::ErrorKind>, 142 ) -> Result<(), PeerAuthorizationFailure> { 143 let (peer_uid, peer_gid) = credentials 144 .map_err(|kind| PeerAuthorizationFailure::CredentialsUnavailable { kind })?; 145 self.authorize_linux_credentials(peer_uid, peer_gid) 146 } 147 148 #[cfg(target_os = "linux")] 149 fn authorize_linux_credentials( 150 self, 151 peer_uid: u32, 152 peer_gid: u32, 153 ) -> Result<(), PeerAuthorizationFailure> { 154 if peer_uid == self.daemon_euid || self.policy.admin_gid == Some(peer_gid) { 155 Ok(()) 156 } else { 157 Err(PeerAuthorizationFailure::Denied) 158 } 159 } 160 161 #[cfg(target_os = "macos")] 162 pub(crate) const fn authorize( 163 self, 164 _stream: &tokio::net::UnixStream, 165 ) -> Result<(), PeerAuthorizationFailure> { 166 let _ = self.policy; 167 let _ = self.daemon_euid; 168 Ok(()) 169 } 170 } 171 172 #[cfg(test)] 173 mod tests { 174 use super::*; 175 176 #[test] 177 fn support_inventory_is_exact_for_the_compilation_target() { 178 #[cfg(target_os = "linux")] 179 assert_eq!( 180 AdminPeerAuthorizationSupport::current(), 181 AdminPeerAuthorizationSupport::LinuxSoPeerCredRequired 182 ); 183 #[cfg(target_os = "macos")] 184 assert_eq!( 185 AdminPeerAuthorizationSupport::current(), 186 AdminPeerAuthorizationSupport::MacOsFilesystemOwnerPermissionsOnly 187 ); 188 #[cfg(not(any(target_os = "linux", target_os = "macos")))] 189 assert_eq!( 190 AdminPeerAuthorizationSupport::current(), 191 AdminPeerAuthorizationSupport::Unsupported 192 ); 193 } 194 195 #[test] 196 fn owner_policy_is_stable_and_group_policy_is_platform_bounded() { 197 let owner = AdminPeerAuthorizationPolicy::owner_only(); 198 assert_eq!(AdminPeerAuthorizationPolicy::default(), owner); 199 assert_eq!(owner.admin_gid(), None); 200 assert_eq!(owner.support(), AdminPeerAuthorizationSupport::current()); 201 assert_eq!( 202 AdminPeerAuthorizationPolicy::with_admin_gid(u32::MAX), 203 Err(if cfg!(target_os = "linux") { 204 AdminPeerAuthorizationPolicyError::InvalidAdminGroupId 205 } else { 206 AdminPeerAuthorizationPolicyError::AdminGroupUnsupported 207 }) 208 ); 209 let error = AdminPeerAuthorizationPolicyError::InvalidAdminGroupId; 210 assert!(!error.to_string().is_empty()); 211 assert!(error.source().is_none()); 212 #[cfg(target_os = "linux")] 213 assert_eq!( 214 AdminPeerAuthorizationPolicy::with_admin_gid(42) 215 .expect("valid Linux admin group") 216 .admin_gid(), 217 Some(42) 218 ); 219 #[cfg(not(target_os = "linux"))] 220 assert_eq!( 221 AdminPeerAuthorizationPolicy::with_admin_gid(42), 222 Err(AdminPeerAuthorizationPolicyError::AdminGroupUnsupported) 223 ); 224 } 225 226 #[cfg(target_os = "linux")] 227 #[tokio::test] 228 async fn linux_process_credentials_allow_uid_or_gid_and_deny_otherwise() { 229 let (peer, observed) = tokio::net::UnixStream::pair().expect("Unix stream pair"); 230 let credentials = peer.peer_cred().expect("Linux SO_PEERCRED"); 231 let different_uid = different_id(credentials.uid()); 232 let different_gid = different_id(credentials.gid()); 233 234 PeerAuthorizer::new( 235 AdminPeerAuthorizationPolicy::owner_only(), 236 credentials.uid(), 237 ) 238 .authorize(&observed) 239 .expect("matching daemon euid"); 240 PeerAuthorizer::new( 241 AdminPeerAuthorizationPolicy::with_admin_gid(credentials.gid()) 242 .expect("peer group policy"), 243 different_uid, 244 ) 245 .authorize(&observed) 246 .expect("matching configured admin gid"); 247 assert_eq!( 248 PeerAuthorizer::new( 249 AdminPeerAuthorizationPolicy::with_admin_gid(different_gid) 250 .expect("different group policy"), 251 different_uid, 252 ) 253 .authorize(&observed), 254 Err(PeerAuthorizationFailure::Denied) 255 ); 256 assert_eq!( 257 PeerAuthorizer::new( 258 AdminPeerAuthorizationPolicy::owner_only(), 259 credentials.uid(), 260 ) 261 .authorize_linux_result(Err(std::io::ErrorKind::PermissionDenied)), 262 Err(PeerAuthorizationFailure::CredentialsUnavailable { 263 kind: std::io::ErrorKind::PermissionDenied, 264 }) 265 ); 266 } 267 268 #[cfg(target_os = "linux")] 269 fn different_id(id: u32) -> u32 { 270 if id == 0 { 1 } else { 0 } 271 } 272 }