Skip to main content

agent_vault/core/
manifest.rs

1use std::path::Path;
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::VaultError;
6
7#[derive(Debug, Serialize, Deserialize)]
8pub struct Manifest {
9    pub version: u32,
10    #[serde(default)]
11    pub owners: Vec<Owner>,
12    #[serde(default)]
13    pub agents: Vec<AgentEntry>,
14    #[serde(default)]
15    pub groups: Vec<Group>,
16}
17
18#[derive(Debug, Serialize, Deserialize)]
19pub struct Owner {
20    pub name: String,
21    pub public_key: String,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct AgentEntry {
26    pub name: String,
27    #[serde(default)]
28    pub groups: Vec<String>,
29}
30
31#[derive(Debug, Serialize, Deserialize)]
32pub struct Group {
33    pub name: String,
34    #[serde(default)]
35    pub secrets: Vec<String>,
36}
37
38impl Manifest {
39    pub fn new(owner_name: &str) -> Self {
40        Self {
41            version: 1,
42            owners: vec![Owner {
43                name: owner_name.to_string(),
44                public_key: "owner.pub".to_string(),
45            }],
46            agents: vec![],
47            groups: vec![],
48        }
49    }
50
51    pub fn load(path: &Path) -> Result<Self, VaultError> {
52        let contents = std::fs::read_to_string(path)?;
53        let manifest: Manifest = serde_yaml::from_str(&contents)?;
54        Ok(manifest)
55    }
56
57    pub fn save(&self, path: &Path) -> Result<(), VaultError> {
58        let yaml = serde_yaml::to_string(self)?;
59        std::fs::write(path, yaml)?;
60        Ok(())
61    }
62
63    /// Add an agent with no group access.
64    pub fn add_agent(&mut self, name: &str) -> Result<(), VaultError> {
65        if self.agents.iter().any(|a| a.name == name) {
66            return Err(VaultError::AgentExists(name.to_string()));
67        }
68        self.agents.push(AgentEntry {
69            name: name.to_string(),
70            groups: vec![],
71        });
72        Ok(())
73    }
74
75    /// Remove an agent, returning the groups they belonged to.
76    pub fn remove_agent(&mut self, name: &str) -> Result<Vec<String>, VaultError> {
77        let idx = self
78            .agents
79            .iter()
80            .position(|a| a.name == name)
81            .ok_or_else(|| VaultError::AgentNotFound(name.to_string()))?;
82        let agent = self.agents.remove(idx);
83        Ok(agent.groups)
84    }
85
86    /// Grant an agent access to a group.
87    pub fn grant(&mut self, agent_name: &str, group_name: &str) -> Result<(), VaultError> {
88        // Ensure group exists
89        if !self.groups.iter().any(|g| g.name == group_name) {
90            return Err(VaultError::GroupNotFound(group_name.to_string()));
91        }
92        let agent = self
93            .agents
94            .iter_mut()
95            .find(|a| a.name == agent_name)
96            .ok_or_else(|| VaultError::AgentNotFound(agent_name.to_string()))?;
97        if !agent.groups.contains(&group_name.to_string()) {
98            agent.groups.push(group_name.to_string());
99        }
100        Ok(())
101    }
102
103    /// Revoke an agent's access to a group.
104    pub fn revoke(&mut self, agent_name: &str, group_name: &str) -> Result<(), VaultError> {
105        let agent = self
106            .agents
107            .iter_mut()
108            .find(|a| a.name == agent_name)
109            .ok_or_else(|| VaultError::AgentNotFound(agent_name.to_string()))?;
110        agent.groups.retain(|g| g != group_name);
111        Ok(())
112    }
113
114    /// Ensure a group exists, creating it if necessary.
115    pub fn ensure_group(&mut self, group_name: &str) {
116        if !self.groups.iter().any(|g| g.name == group_name) {
117            self.groups.push(Group {
118                name: group_name.to_string(),
119                secrets: vec![],
120            });
121        }
122    }
123
124    /// Add a secret path to a group.
125    pub fn add_secret_to_group(&mut self, group_name: &str, secret_path: &str) {
126        self.ensure_group(group_name);
127        let group = self.groups.iter_mut().find(|g| g.name == group_name).unwrap();
128        if !group.secrets.contains(&secret_path.to_string()) {
129            group.secrets.push(secret_path.to_string());
130        }
131    }
132
133    /// Get all agent names that have access to a group.
134    pub fn agents_in_group(&self, group_name: &str) -> Vec<String> {
135        self.agents
136            .iter()
137            .filter(|a| a.groups.contains(&group_name.to_string()))
138            .map(|a| a.name.clone())
139            .collect()
140    }
141
142    /// Get all secret paths in a group.
143    pub fn secrets_in_group(&self, group_name: &str) -> Vec<String> {
144        self.groups
145            .iter()
146            .find(|g| g.name == group_name)
147            .map(|g| g.secrets.clone())
148            .unwrap_or_default()
149    }
150
151    /// Get all groups an agent belongs to.
152    pub fn agent_groups(&self, agent_name: &str) -> Option<Vec<String>> {
153        self.agents
154            .iter()
155            .find(|a| a.name == agent_name)
156            .map(|a| a.groups.clone())
157    }
158
159    /// Get all agent names that are authorized for a specific secret path.
160    pub fn authorized_agents_for_secret(&self, secret_path: &str) -> Vec<String> {
161        // Find which group(s) contain this secret
162        let groups: Vec<&str> = self
163            .groups
164            .iter()
165            .filter(|g| g.secrets.contains(&secret_path.to_string()))
166            .map(|g| g.name.as_str())
167            .collect();
168
169        // Collect agents that belong to any of those groups
170        let mut agents: Vec<String> = self
171            .agents
172            .iter()
173            .filter(|a| a.groups.iter().any(|ag| groups.contains(&ag.as_str())))
174            .map(|a| a.name.clone())
175            .collect();
176        agents.sort();
177        agents.dedup();
178        agents
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_manifest_operations() {
188        let mut m = Manifest::new("alice");
189        m.add_agent("bot1").unwrap();
190        m.ensure_group("stripe");
191        m.add_secret_to_group("stripe", "stripe/api-key");
192        m.grant("bot1", "stripe").unwrap();
193
194        assert_eq!(m.agents_in_group("stripe"), vec!["bot1"]);
195        assert_eq!(
196            m.authorized_agents_for_secret("stripe/api-key"),
197            vec!["bot1"]
198        );
199
200        m.revoke("bot1", "stripe").unwrap();
201        assert!(m.agents_in_group("stripe").is_empty());
202    }
203}