alien_core/
sandbox_identity.rs1use alien_error::{AlienError, Context, IntoAlienError};
4
5use crate::error::{ErrorData, Result};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct ExecIdentity {
10 pub uid: u32,
12 pub gid: u32,
14}
15
16pub struct ImageUser<'a> {
18 pub user: &'a str,
20 pub passwd: &'a str,
22 pub group: &'a str,
24}
25
26impl ImageUser<'_> {
27 pub fn exec_identity(
31 &self,
32 default_uid: u32,
33 command_uid: Option<u32>,
34 ) -> Result<ExecIdentity> {
35 let invalid = |reason: String| ErrorData::SandboxImageUserInvalid {
36 user: self.user.to_string(),
37 reason,
38 };
39 let parse_id = |value: &str| -> Result<u32> {
40 let id = value
41 .parse::<u32>()
42 .into_alien_error()
43 .context(invalid(format!("'{value}' is not a numeric uid/gid")))?;
44 if id == u32::MAX {
45 return Err(AlienError::new(invalid(
46 "uid/gid -1 is reserved".to_string(),
47 )));
48 }
49 Ok(id)
50 };
51 let (user, group) = self
52 .user
53 .split_once(':')
54 .map_or((self.user, None), |(u, g)| (u, Some(g)));
55 let numeric_uid = if user.is_empty() {
56 Some(0)
57 } else if user.bytes().all(|c| c.is_ascii_digit()) {
58 Some(parse_id(user)?)
59 } else {
60 None
61 };
62 let account = self.passwd.lines().find_map(|line| {
63 let fields: Vec<_> = line.split(':').collect();
64 (fields.len() >= 4
65 && numeric_uid.map_or_else(
66 || fields[0] == user,
67 |uid| fields[2].parse::<u32>() == Ok(uid),
68 ))
69 .then_some(fields)
70 });
71 let uid = match numeric_uid {
72 Some(uid) => uid,
73 None => parse_id(
74 account.as_ref().ok_or_else(|| {
75 AlienError::new(invalid(format!("user '{user}' is absent from /etc/passwd")))
76 })?[2],
77 )?,
78 };
79 if uid == 0 {
80 let uid = command_uid.unwrap_or(default_uid);
81 if uid == 0 || uid == u32::MAX {
82 return Err(AlienError::new(invalid(
83 "command uid must be non-root and cannot be -1".to_string(),
84 )));
85 }
86 return Ok(ExecIdentity { uid, gid: uid });
87 }
88 if command_uid.is_some_and(|declared| declared != uid) {
89 return Err(AlienError::new(invalid(format!(
90 "privilegedSupervisor.commandUid {} disagrees with image USER uid {uid}",
91 command_uid.unwrap_or_default()
92 ))));
93 }
94 let gid = match group {
95 Some(group) if !group.is_empty() && group.bytes().all(|c| c.is_ascii_digit()) => {
96 parse_id(group)?
97 }
98 Some(group) => {
99 let entry = self
100 .group
101 .lines()
102 .find_map(|line| {
103 let fields: Vec<_> = line.split(':').collect();
104 (fields.len() >= 3 && fields[0] == group).then_some(fields)
105 })
106 .ok_or_else(|| {
107 AlienError::new(invalid(format!(
108 "group '{group}' is absent from /etc/group"
109 )))
110 })?;
111 parse_id(entry[2])?
112 }
113 None => match account {
114 Some(account) => parse_id(account[3])?,
115 None => 0,
117 },
118 };
119 if gid == 0 {
120 return Err(AlienError::new(invalid(
121 "the root group is not permitted for sandbox commands; declare USER with a non-root primary group".to_string(),
122 )));
123 }
124 Ok(ExecIdentity { uid, gid })
125 }
126}