use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaselineContract {
pub name: String,
pub description: String,
pub required_trust_profile: String,
pub access_level: AccessLevel,
pub secret_constraints: SecretConstraints,
pub target_constraints: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AccessLevel {
ReadOnly,
ReadWrite,
}
impl std::fmt::Display for AccessLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AccessLevel::ReadOnly => write!(f, "read_only"),
AccessLevel::ReadWrite => write!(f, "read_write"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecretConstraints {
pub allowed_secrets: Vec<String>,
pub required_secrets: Vec<String>,
}
pub fn read_only_contract() -> BaselineContract {
BaselineContract {
name: "read_only".to_string(),
description: "Read-only access: retrieve secrets, no vault modifications".to_string(),
required_trust_profile: "standard".to_string(),
access_level: AccessLevel::ReadOnly,
secret_constraints: SecretConstraints {
allowed_secrets: vec![],
required_secrets: vec![],
},
target_constraints: None,
}
}
pub fn ci_deploy_contract(targets: Vec<String>) -> BaselineContract {
BaselineContract {
name: "ci_deploy".to_string(),
description: "CI deployment: hardened env, target-restricted, read-only vault".to_string(),
required_trust_profile: "hardened".to_string(),
access_level: AccessLevel::ReadOnly,
secret_constraints: SecretConstraints {
allowed_secrets: vec![
"AWS_ACCOUNT_ID".to_string(),
"AWS_ACCESS_KEY_ID".to_string(),
"AWS_SECRET_ACCESS_KEY".to_string(),
"TERRAFORM_TOKEN".to_string(),
],
required_secrets: vec![
"AWS_ACCESS_KEY_ID".to_string(),
"AWS_SECRET_ACCESS_KEY".to_string(),
],
},
target_constraints: Some(targets),
}
}
pub fn ops_emergency_contract() -> BaselineContract {
BaselineContract {
name: "ops_emergency".to_string(),
description: "Operations emergency: hardened env, write access, strict targeting"
.to_string(),
required_trust_profile: "hardened".to_string(),
access_level: AccessLevel::ReadWrite,
secret_constraints: SecretConstraints {
allowed_secrets: vec![
"ADMIN_SSH_KEY".to_string(),
"SUDO_PASSWORD".to_string(),
"INCIDENT_TICKET_TOKEN".to_string(),
],
required_secrets: vec!["ADMIN_SSH_KEY".to_string()],
},
target_constraints: Some(vec!["ssh".to_string(), "sudo".to_string()]),
}
}
pub fn baseline_contracts() -> HashMap<&'static str, BaselineContract> {
let mut contracts = HashMap::new();
contracts.insert("read_only", read_only_contract());
contracts.insert(
"ci_deploy",
ci_deploy_contract(vec!["terraform".to_string(), "ansible".to_string()]),
);
contracts.insert("ops_emergency", ops_emergency_contract());
contracts
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_only_contract_no_restrictions() {
let contract = read_only_contract();
assert_eq!(contract.access_level.to_string(), "read_only");
assert!(contract.secret_constraints.allowed_secrets.is_empty());
assert!(contract.target_constraints.is_none());
}
#[test]
fn test_ci_deploy_contract_has_required_secrets() {
let contract = ci_deploy_contract(vec!["terraform".to_string()]);
assert_eq!(contract.access_level.to_string(), "read_only");
assert!(!contract.secret_constraints.required_secrets.is_empty());
assert!(contract.target_constraints.is_some());
assert_eq!(contract.required_trust_profile, "hardened");
}
#[test]
fn test_ops_emergency_contract_allows_write() {
let contract = ops_emergency_contract();
assert_eq!(contract.access_level.to_string(), "read_write");
assert_eq!(contract.required_trust_profile, "hardened");
let targets = contract.target_constraints.unwrap();
assert!(targets.contains(&"ssh".to_string()));
}
#[test]
fn test_baseline_contracts_registry_is_complete() {
let registry = baseline_contracts();
assert!(registry.contains_key("read_only"));
assert!(registry.contains_key("ci_deploy"));
assert!(registry.contains_key("ops_emergency"));
}
}