| 83e0677b Andy McCall ↶ |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
use super::*; |
| 9 |
use crate::policy::{Policy, Require, Rule}; |
| 10 |
use crate::protocol::SignedPolicy; |
| 11 |
|
| 12 |
|
| 13 |
const POLICY_LIFETIME_MS: u64 = 365 * 24 * 60 * 60 * 1000; |
| 14 |
|
| 15 |
impl Repo { |
| 16 |
|
| 17 |
pub fn current_policy(&self) -> Result<Option<(ObjectRef, SignedPolicy)>> { |
| 18 |
let Some(r) = self.view()?.policy else { return Ok(None) }; |
| 19 |
let policy = Policy::decode(&self.store.get_typed(&r, ObjectType::Policy)?)?; |
| 20 |
Ok(Some((r, SignedPolicy { policy, sigs: self.store.signatures(&r)? }))) |
| 21 |
} |
| 22 |
|
| 23 |
|
| 24 |
pub(crate) fn set_policy(&self, sp: &SignedPolicy, command: &str) -> Result<ObjectRef> { |
| 25 |
let r = self.store.put(ObjectType::Policy, &sp.policy.encode())?; |
| 26 |
for sig in &sp.sigs { |
| 27 |
self.store.put_signature(&r, sig)?; |
| 28 |
} |
| 29 |
for id in &sp.policy.identities { |
| 30 |
self.store.put_identity(id)?; |
| 31 |
} |
| 32 |
let (op_ref, op) = self.head_op()?; |
| 33 |
if op.view.policy != Some(r) { |
| 34 |
let mut view = op.view.clone(); |
| 35 |
view.policy = Some(r); |
| 36 |
self.record_op(&[op_ref], command, view)?; |
| 37 |
} |
| 38 |
Ok(r) |
| 39 |
} |
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
pub fn check_policy(&self, sp: &SignedPolicy, trusted: Option<&Policy>) -> std::result::Result<(), String> { |
| 44 |
if sp.policy.repo != self.id().map_err(|e| e.to_string())? { |
| 45 |
return Err("policy is for a different repository".into()); |
| 46 |
} |
| 47 |
if let Some(t) = trusted { |
| 48 |
if sp.policy.serial <= t.serial { |
| 49 |
return Err(format!("policy serial {} is not greater than the current {}", sp.policy.serial, t.serial)); |
| 50 |
} |
| 51 |
} |
| 52 |
sp.policy.verify_signatures(&sp.sigs, trusted.unwrap_or(&sp.policy))?; |
| 53 |
Ok(()) |
| 54 |
} |
| 55 |
|
| 56 |
fn sign_policy(&self, policy: Policy) -> Result<SignedPolicy> { |
| 57 |
let Some((key, sk)) = self.secret_key()? else { |
| 58 |
return fail("no secret key: run `oxy identity create` first"); |
| 59 |
}; |
| 60 |
if !policy.roots.contains(&key) { |
| 61 |
return fail("your key is not a root key of this policy"); |
| 62 |
} |
| 63 |
if policy.root_threshold > 1 { |
| 64 |
return fail(format!("this policy needs {} root signatures; multi-signer updates are not implemented yet", policy.root_threshold)); |
| 65 |
} |
| 66 |
let sig = sk.sign(Domain::Policy, &key, &policy.hash()); |
| 67 |
Ok(SignedPolicy { policy, sigs: vec![sig] }) |
| 68 |
} |
| 69 |
|
| 70 |
|
| 71 |
pub fn policy_init(&self) -> Result<SignedPolicy> { |
| 72 |
if self.current_policy()?.is_some() { |
| 73 |
return fail("this repository already has a policy"); |
| 74 |
} |
| 75 |
let Some((key, _)) = self.secret_key()? else { |
| 76 |
return fail("no secret key: run `oxy identity create` first"); |
| 77 |
}; |
| 78 |
let identity = self.identity(&key)?; |
| 79 |
let policy = Policy::single_root(self.id()?, identity, Hlc::now().wall_ms + POLICY_LIFETIME_MS); |
| 80 |
let sp = self.sign_policy(policy)?; |
| 81 |
self.set_policy(&sp, "oxy policy init")?; |
| 82 |
Ok(sp) |
| 83 |
} |
| 84 |
|
| 85 |
fn next_policy(&self) -> Result<Policy> { |
| 86 |
let Some((_, sp)) = self.current_policy()? else { |
| 87 |
return fail("this repository has no policy; run `oxy policy init`"); |
| 88 |
}; |
| 89 |
let mut p = sp.policy; |
| 90 |
p.serial += 1; |
| 91 |
p.expires = Hlc::now().wall_ms + POLICY_LIFETIME_MS; |
| 92 |
Ok(p) |
| 93 |
} |
| 94 |
|
| 95 |
|
| 96 |
pub fn policy_grant(&self, identity: Identity, pattern: Option<&str>) -> Result<SignedPolicy> { |
| 97 |
let mut p = self.next_policy()?; |
| 98 |
let key = identity.hash(); |
| 99 |
let pattern = pattern.unwrap_or("*"); |
| 100 |
p.revoked.retain(|k| k != &key); |
| 101 |
match p.rules.iter_mut().find(|r| r.pattern == pattern) { |
| 102 |
Some(rule) => { |
| 103 |
if !rule.keys.contains(&key) { |
| 104 |
rule.keys.push(key); |
| 105 |
rule.keys.sort(); |
| 106 |
} |
| 107 |
} |
| 108 |
None => p.rules.push(Rule { pattern: pattern.into(), keys: vec![key], threshold: 1, require: Require::Any, allow_conflicts: true }), |
| 109 |
} |
| 110 |
if p.identity(&key).is_none() { |
| 111 |
p.identities.push(identity); |
| 112 |
} |
| 113 |
let sp = self.sign_policy(p)?; |
| 114 |
self.set_policy(&sp, &format!("oxy policy grant {}", key.short(12)))?; |
| 115 |
Ok(sp) |
| 116 |
} |
| 117 |
|
| 118 |
|
| 119 |
pub fn policy_revoke(&self, key: &KeyId) -> Result<SignedPolicy> { |
| 120 |
let mut p = self.next_policy()?; |
| 121 |
if p.roots.contains(key) { |
| 122 |
return fail("cannot revoke a root key"); |
| 123 |
} |
| 124 |
for r in &mut p.rules { |
| 125 |
r.keys.retain(|k| k != key); |
| 126 |
} |
| 127 |
p.rules.retain(|r| !r.keys.is_empty()); |
| 128 |
if !p.revoked.contains(key) { |
| 129 |
p.revoked.push(*key); |
| 130 |
p.revoked.sort(); |
| 131 |
} |
| 132 |
let sp = self.sign_policy(p)?; |
| 133 |
self.set_policy(&sp, &format!("oxy policy revoke {}", key.short(12)))?; |
| 134 |
Ok(sp) |
| 135 |
} |
| 136 |
|
| 137 |
|
| 138 |
|
| 139 |
|
| 140 |
pub fn change_allowed(&self, hash: &ChangeHash, policy: &Policy, reference: &str) -> std::result::Result<(), String> { |
| 141 |
let rule = policy.rule_for(reference).ok_or_else(|| format!("no policy rule matches {reference:?}"))?; |
| 142 |
match self.authenticate_change(hash).map_err(|e| e.to_string())? { |
| 143 |
SignatureStatus::Valid { key, sealed, .. } => { |
| 144 |
if policy.is_revoked(&key) { |
| 145 |
return Err(format!("change {} is signed by revoked key {}", hash.short(12), key.short(12))); |
| 146 |
} |
| 147 |
if !rule.keys.contains(&key) { |
| 148 |
return Err(format!("change {} is signed by {}, which may not update {reference:?}", hash.short(12), key.short(12))); |
| 149 |
} |
| 150 |
match (rule.require, sealed) { |
| 151 |
(Require::Signed, true) => Err(format!("{reference:?} requires individually signed changes; {} is only sealed", hash.short(12))), |
| 152 |
(Require::Sealed, false) => Err(format!("{reference:?} requires sealed changes; {} is individually signed", hash.short(12))), |
| 153 |
_ => Ok(()), |
| 154 |
} |
| 155 |
} |
| 156 |
SignatureStatus::Unsigned => Err(format!("change {} is not signed or sealed", hash.short(12))), |
| 157 |
SignatureStatus::Invalid(e) => Err(format!("change {}: {e}", hash.short(12))), |
| 158 |
} |
| 159 |
} |
| 160 |
|
| 161 |
|
| 162 |
pub(crate) fn state_allowed(&self, old_heads: &[ChangeHash], new_heads: &[ChangeHash], policy: &Policy, reference: &str) -> std::result::Result<(), String> { |
| 163 |
let added = self.negotiate(old_heads, new_heads).map_err(|e| e.to_string())?; |
| 164 |
for h in &added { |
| 165 |
self.change_allowed(h, policy, reference)?; |
| 166 |
} |
| 167 |
if let Some(rule) = policy.rule_for(reference) { |
| 168 |
if !rule.allow_conflicts && !added.is_empty() { |
| 169 |
let st = self.load_state(new_heads).map_err(|e| e.to_string())?; |
| 170 |
if st.materialize().is_conflicted() { |
| 171 |
return Err(format!("{reference:?} does not allow conflicted states")); |
| 172 |
} |
| 173 |
} |
| 174 |
} |
| 175 |
Ok(()) |
| 176 |
} |
| 177 |
} |