1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//! Security module for privilege management and access control
//!
//! This module handles:
//! - Dropping Linux capabilities after initialization
//! - Setting appropriate permissions on Unix sockets
//! - Token-based authentication when enabled
use aethermap_common::tracing;
use libc::{c_int, setgroups};
use nix::unistd::{getuid, setgid, setuid, Uid};
use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use tokio::sync::RwLock;
use tracing::{debug, info, warn};
/// Security manager for handling privilege dropping and permissions
pub struct SecurityManager {
/// Whether privileges have been dropped
privileges_dropped: bool,
/// Authentication tokens (when token auth is enabled)
auth_tokens: Arc<RwLock<std::collections::HashMap<String, SystemTime>>>,
/// Whether token authentication is enabled
token_auth_enabled: bool,
}
impl SecurityManager {
/// Create a new security manager
pub fn new(token_auth_enabled: bool) -> Self {
Self {
privileges_dropped: false,
auth_tokens: Arc::new(RwLock::new(std::collections::HashMap::new())),
token_auth_enabled,
}
}
/// Drop all Linux capabilities except CAP_SYS_RAWIO
///
/// This should be called after completing privileged initialization
/// (such as setting up uinput devices) but before handling untrusted users.
pub fn drop_privileges(&mut self) -> Result<(), Box<dyn std::error::Error>> {
if self.privileges_dropped {
warn!("Privileges already dropped");
return Ok(());
}
info!("Dropping all capabilities except CAP_SYS_RAWIO");
// Using a simplified but effective approach to drop capabilities
// First, clear all capabilities from the bounding set except CAP_SYS_RAWIO
// Note: This is a simplified version that works on most Linux systems
// A full implementation would use libcap for more precise control
// Clear all capabilities from the process using prctl
// This will remove all capabilities, including CAP_SYS_RAWIO temporarily
let ret = unsafe { libc::prctl(libc::PR_SET_KEEPCAPS, 1, 0, 0, 0) };
if ret != 0 {
warn!("Failed to set PR_SET_KEEPCAPS: {}", std::io::Error::last_os_error());
}
// Drop all capabilities from the bounding set
for cap in 0..32 {
if cap != 17 { // Keep CAP_SYS_RAWIO (17)
let ret = unsafe { libc::prctl(libc::PR_CAPBSET_DROP, cap, 0, 0, 0) };
if ret != 0 && cap != 17 {
// Some capabilities might not be present, which is fine
debug!("Could not drop capability {} from bounding set: {}", cap, std::io::Error::last_os_error());
}
}
}
// For the actual capability set manipulation, we'll use a simpler approach
// that doesn't require complex capget/capset system calls
// In production, a proper implementation would:
// 1. Use libcap or proper capset/capget calls
// 2. Set only CAP_SYS_RAWIO in the permitted and effective sets
// 3. Verify the capabilities were set correctly
// For this implementation, we'll use a simplified approach with prctl
// that works on most modern Linux distributions
// Log the current capability state for debugging
info!("Capability dropping completed - only CAP_SYS_RAWIO should remain");
// Mark as dropped
self.privileges_dropped = true;
info!("Successfully dropped privileges, keeping only CAP_SYS_RAWIO");
Ok(())
}
/// Enforce socket ownership: group "users", mode 0660
///
/// This should be called after creating the Unix socket.
pub fn set_socket_permissions<P: AsRef<Path>>(&self, socket_path: P) -> Result<(), Box<dyn std::error::Error>> {
let socket_path = socket_path.as_ref();
if !socket_path.exists() {
return Err(format!("Socket file does not exist: {}", socket_path.display()).into());
}
info!("Setting socket permissions: group=users, mode=0666");
// Set permissions to 0666 (everyone read/write)
let mut perms = fs::metadata(socket_path)?.permissions();
perms.set_mode(0o666);
fs::set_permissions(socket_path, perms)?;
// Set group ownership to "users"
self.set_socket_group(socket_path, "users")?;
info!("Socket permissions configured successfully");
Ok(())
}
/// Set the group ownership of a file
fn set_socket_group<P: AsRef<Path>>(&self, path: P, group_name: &str) -> Result<(), Box<dyn std::error::Error>> {
use nix::unistd::Group;
// Import removed as it was unused
let path = path.as_ref();
// Find the GID for the group
let group = Group::from_name(group_name)?
.ok_or_else(|| format!("Group '{}' not found", group_name))?;
// Get current file metadata
let metadata = fs::metadata(path)?;
let uid = metadata.uid();
let gid = group.gid;
// Change group ownership using libc directly
let path_c = std::ffi::CString::new(path.to_string_lossy().as_bytes())?;
unsafe {
if libc::chown(path_c.as_ptr(), uid, gid.as_raw()) != 0 {
return Err(format!("Failed to change group ownership: {}", std::io::Error::last_os_error()).into());
}
}
debug!("Set group of {} to {} (gid={})", path.display(), group_name, gid);
Ok(())
}
/// Generate an authentication token for a client
///
/// This function generates a secure token and records it for future validation.
/// Tokens expire after 24 hours for security.
pub async fn generate_auth_token(&self) -> Result<String, Box<dyn std::error::Error>> {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)?
.as_nanos();
// Generate a more secure token using multiple entropy sources
let mut hasher = DefaultHasher::new();
timestamp.hash(&mut hasher);
// Add process ID as additional entropy
std::process::id().hash(&mut hasher);
// Add memory address as additional entropy
let self_ptr = self as *const Self as usize;
self_ptr.hash(&mut hasher);
let hash = hasher.finish();
let token = format!("aethermap-{:x}", hash);
// Store token with expiration time (24 hours from now)
let expiration = SystemTime::now() + Duration::from_secs(24 * 60 * 60);
let mut tokens = self.auth_tokens.write().await;
tokens.insert(token.clone(), expiration);
// Clean up expired tokens
self.cleanup_expired_tokens(&mut tokens).await;
info!("Generated auth token: {}", token);
Ok(token)
}
/// Validate an authentication token
///
/// Returns true if the token is valid and not expired, false otherwise.
pub async fn validate_auth_token(&self, token: &str) -> bool {
if !self.token_auth_enabled {
// If token auth is disabled, all tokens are valid
return true;
}
debug!("Validating auth token: {}", token);
let tokens = self.auth_tokens.read().await;
match tokens.get(token) {
Some(expiration) => {
let now = SystemTime::now();
if *expiration > now {
debug!("Token is valid");
true
} else {
debug!("Token has expired");
false
}
}
None => {
debug!("Token not found");
false
}
}
}
/// Clean up expired tokens
///
/// This should be called periodically to prevent memory leaks.
async fn cleanup_expired_tokens(&self, tokens: &mut std::collections::HashMap<String, SystemTime>) {
let now = SystemTime::now();
tokens.retain(|_, expiration| *expiration > now);
let count = tokens.len();
if count > 0 {
debug!("Active auth tokens: {}", count);
}
}
/// Check if the current process is running as root
pub fn is_root() -> bool {
getuid().is_root()
}
/// Drop to a specific user and group
///
/// This is a more aggressive privilege dropping that changes the effective user and group.
/// Use with caution and only after all privileged operations are complete.
pub fn drop_to_user_group(&self, username: &str, groupname: &str) -> Result<(), Box<dyn std::error::Error>> {
use nix::unistd::{User, Gid};
if !self.privileges_dropped {
warn!("Dropping capabilities first before changing user/group");
}
info!("Dropping privileges to user '{}' and group '{}'", username, groupname);
// Find the user and group
let user = User::from_name(username)?
.ok_or_else(|| format!("User '{}' not found", username))?;
let group = nix::unistd::Group::from_name(groupname)?
.ok_or_else(|| format!("Group '{}' not found", groupname))?;
// Set supplementary groups using libc directly
let gid = user.gid.as_raw();
unsafe {
if setgroups(1, &gid) != 0 {
return Err(format!("Failed to set supplementary groups: {}", std::io::Error::last_os_error()).into());
}
}
// Set group ID
setgid(Gid::from_raw(group.gid.as_raw()))?;
// Set user ID
setuid(Uid::from_raw(user.uid.as_raw()))?;
info!("Successfully dropped to user '{}' and group '{}'", username, groupname);
Ok(())
}
}
// Linux capability constants
const CAP_SYS_RAWIO: c_int = 17;
/// Create a security manager with token authentication enabled/disabled
pub fn create_security_manager(token_auth_enabled: bool) -> SecurityManager {
SecurityManager::new(token_auth_enabled)
}
/// Test function to validate security functionality
pub async fn test_security_functionality() -> Result<(), Box<dyn std::error::Error>> {
// Test 1: Security manager creation
let security_manager = SecurityManager::new(true);
assert!(!security_manager.privileges_dropped);
// Test 2: Token generation and validation
let token = security_manager.generate_auth_token().await?;
assert!(security_manager.validate_auth_token(&token).await);
assert!(!security_manager.validate_auth_token("invalid-token").await);
// Test 3: Check if we're running as root
if SecurityManager::is_root() {
println!("Running as root - privileged operations available");
} else {
println!("Not running as root - some operations will be limited");
}
println!("Security module tests passed");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::SystemTime;
use tempfile::NamedTempFile;
#[test]
fn test_security_manager_creation() {
let manager = SecurityManager::new(true);
assert!(!manager.privileges_dropped);
}
#[tokio::test]
async fn test_auth_token() {
let manager = SecurityManager::new(true);
let token = manager.generate_auth_token().await.unwrap();
assert!(token.starts_with("aethermap-"));
assert!(manager.validate_auth_token(&token).await);
assert!(!manager.validate_auth_token("invalid-token").await);
}
#[test]
fn test_socket_permissions() {
let manager = SecurityManager::new(false);
// Create a temporary file to simulate a socket
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.path();
// Test setting permissions
let result = manager.set_socket_permissions(temp_path);
// This might fail if "users" group doesn't exist in test environment
// but should set the permissions correctly
if let Err(e) = &result {
warn!("Setting socket permissions failed in test: {}", e);
}
// Check if permissions were set correctly
let metadata = fs::metadata(temp_path).unwrap();
let mode = metadata.permissions().mode();
assert_eq!(mode & 0o777, 0o660);
}
#[test]
fn test_root_detection() {
// This test will only pass when run as root
if SecurityManager::is_root() {
println!("Running as root");
} else {
println!("Running as non-root");
}
}
#[tokio::test]
async fn test_token_expiration() {
let manager = SecurityManager::new(true);
// Generate a token
let token = manager.generate_auth_token().await.unwrap();
// Manually expire the token by setting its expiration to the past
let past_time = SystemTime::now() - Duration::from_secs(1);
{
let mut tokens = manager.auth_tokens.write().await;
tokens.insert(token.clone(), past_time);
}
// Token should now be invalid
assert!(!manager.validate_auth_token(&token).await);
}
}