Skip to main content

secure_exec_kernel/
user.rs

1#[derive(Debug, Clone, PartialEq, Eq)]
2pub struct ProcessIdentity {
3    pub uid: u32,
4    pub gid: u32,
5    pub euid: u32,
6    pub egid: u32,
7    pub supplementary_gids: Vec<u32>,
8}
9
10impl Default for ProcessIdentity {
11    fn default() -> Self {
12        Self {
13            uid: 1000,
14            gid: 1000,
15            euid: 1000,
16            egid: 1000,
17            supplementary_gids: vec![1000],
18        }
19    }
20}
21
22#[derive(Debug, Clone, Default, PartialEq, Eq)]
23pub struct UserConfig {
24    pub uid: Option<u32>,
25    pub gid: Option<u32>,
26    pub euid: Option<u32>,
27    pub egid: Option<u32>,
28    pub username: Option<String>,
29    pub homedir: Option<String>,
30    pub shell: Option<String>,
31    pub gecos: Option<String>,
32    pub group_name: Option<String>,
33    /// Supplementary groups are VM configuration, not guest-mutable state.
34    /// The primary gid is always injected and duplicate gids are dropped.
35    pub supplementary_gids: Vec<u32>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct UserManager {
40    pub uid: u32,
41    pub gid: u32,
42    pub euid: u32,
43    pub egid: u32,
44    pub username: String,
45    pub homedir: String,
46    pub shell: String,
47    pub gecos: String,
48    pub group_name: String,
49    pub supplementary_gids: Vec<u32>,
50}
51
52impl Default for UserManager {
53    fn default() -> Self {
54        Self::from_config(UserConfig::default())
55    }
56}
57
58impl UserManager {
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    pub fn from_config(config: UserConfig) -> Self {
64        let uid = config.uid.unwrap_or(1000);
65        let gid = config.gid.unwrap_or(1000);
66        let username = config.username.unwrap_or_else(|| String::from("agentos"));
67        let supplementary_gids = normalize_supplementary_gids(gid, config.supplementary_gids);
68
69        Self {
70            uid,
71            gid,
72            euid: config.euid.unwrap_or(uid),
73            egid: config.egid.unwrap_or(gid),
74            username: username.clone(),
75            homedir: config
76                .homedir
77                .unwrap_or_else(|| String::from("/home/agentos")),
78            shell: config.shell.unwrap_or_else(|| String::from("/bin/sh")),
79            gecos: config.gecos.unwrap_or_default(),
80            group_name: config.group_name.unwrap_or(username),
81            supplementary_gids,
82        }
83    }
84
85    pub fn identity(&self) -> ProcessIdentity {
86        ProcessIdentity {
87            uid: self.uid,
88            gid: self.gid,
89            euid: self.euid,
90            egid: self.egid,
91            supplementary_gids: self.supplementary_gids.clone(),
92        }
93    }
94
95    pub fn getgroups(&self) -> Vec<u32> {
96        self.supplementary_gids.clone()
97    }
98
99    pub fn getpwuid(&self, uid: u32) -> Option<String> {
100        if uid == self.uid {
101            return Some(format!(
102                "{}:x:{}:{}:{}:{}:{}",
103                self.username, self.uid, self.gid, self.gecos, self.homedir, self.shell
104            ));
105        }
106
107        None
108    }
109
110    pub fn getgrgid(&self, gid: u32) -> Option<String> {
111        if gid == self.gid {
112            return Some(format!(
113                "{}:x:{}:{}",
114                self.group_name, self.gid, self.username
115            ));
116        }
117
118        if self.supplementary_gids.contains(&gid) {
119            // Supplementary group names are synthetic because only numeric
120            // secondary group ids are configured for the VM.
121            let group_name = format!("group{gid}");
122            return Some(format!("{group_name}:x:{gid}:{}", self.username));
123        }
124
125        None
126    }
127}
128
129fn normalize_supplementary_gids(primary_gid: u32, supplementary_gids: Vec<u32>) -> Vec<u32> {
130    let mut normalized = Vec::with_capacity(supplementary_gids.len() + 1);
131    normalized.push(primary_gid);
132    for gid in supplementary_gids {
133        if !normalized.contains(&gid) {
134            normalized.push(gid);
135        }
136    }
137    normalized
138}