agent_vault/core/
manifest.rs1use 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 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 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 pub fn grant(&mut self, agent_name: &str, group_name: &str) -> Result<(), VaultError> {
88 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 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 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 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 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 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 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 pub fn authorized_agents_for_secret(&self, secret_path: &str) -> Vec<String> {
161 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 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}