use std::{
collections::{BTreeMap, BTreeSet},
fmt,
time::Duration,
};
use polyc_state::{
error::StateError,
id::{Audience, OperationFamily},
};
use crate::wire::CallContextVersion;
pub const STATE_AUDIENCE: &str = "polychrome.state";
#[must_use]
pub fn state_audience() -> Audience {
Audience::new(STATE_AUDIENCE)
}
pub fn check_call_context_version(declared: CallContextVersion) -> Result<(), StateError> {
if declared == CallContextVersion::CURRENT {
return Ok(());
}
Err(StateError::Malformed {
field: "protocol_version".to_owned(),
reason: format!("this listener speaks {}", CallContextVersion::CURRENT),
})
}
pub fn check_audience(
declared: &Audience,
served: &Audience,
family: &OperationFamily,
) -> Result<(), StateError> {
if declared == served {
return Ok(());
}
Err(StateError::Denied {
family: family.clone(),
})
}
pub fn check_transport_deadline(
remaining: Option<Duration>,
family: &OperationFamily,
) -> Result<(), StateError> {
if remaining.is_some_and(|left| left.is_zero()) {
return Err(StateError::DeadlineExpired {
family: family.clone(),
overrun: Duration::ZERO,
});
}
Ok(())
}
pub fn check_not_draining(draining: bool) -> Result<(), connectrpc::ConnectError> {
if draining {
return Err(connectrpc::ConnectError::unavailable(
"this listener is draining and is not admitting new calls",
));
}
Ok(())
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs, clippy::unwrap_used)]
use super::*;
use polyc_state::{conformance::family, error::RetryClass};
fn family_id() -> OperationFamily {
OperationFamily::new(family::FAMILY)
}
#[test]
fn the_current_version_is_admitted_and_every_other_one_is_not() {
assert!(check_call_context_version(CallContextVersion::CURRENT).is_ok());
let error = check_call_context_version(CallContextVersion::new(1)).unwrap_err();
assert!(
matches!(error, StateError::Malformed { ref field, .. } if field == "protocol_version")
);
assert_eq!(error.retry_class(), RetryClass::Terminal);
}
#[test]
fn a_foreign_audience_is_denied_and_names_nothing_else() {
let served = Audience::new(family::AUDIENCE);
assert!(check_audience(&served, &served, &family_id()).is_ok());
let error =
check_audience(&Audience::new("somebody-else"), &served, &family_id()).unwrap_err();
assert_eq!(
error,
StateError::Denied {
family: family_id()
}
);
assert_eq!(error.retry_class(), RetryClass::Terminal);
assert!(
!error.to_string().contains("somebody-else"),
"a denial must not echo what the caller asked for"
);
}
#[test]
fn a_spent_transport_budget_expires_ambiguously() {
assert!(check_transport_deadline(None, &family_id()).is_ok());
assert!(check_transport_deadline(Some(Duration::from_millis(1)), &family_id()).is_ok());
let error = check_transport_deadline(Some(Duration::ZERO), &family_id()).unwrap_err();
assert!(matches!(error, StateError::DeadlineExpired { .. }));
assert!(error.is_ambiguous() && error.is_retry_safe());
}
#[test]
fn a_draining_listener_refuses_a_new_call() {
assert!(check_not_draining(false).is_ok());
let error = check_not_draining(true).unwrap_err();
assert_eq!(error.code, connectrpc::ErrorCode::Unavailable);
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PeerIdentity {
Anonymous,
Workload(String),
}
impl PeerIdentity {
#[must_use]
pub fn from_verified_leaf(leaf: Option<&[u8]>) -> Self {
leaf.map_or(Self::Anonymous, |bytes| {
use sha2::Digest as _;
let digest = sha2::Sha256::digest(bytes);
Self::Workload(hex::encode(digest))
})
}
#[must_use]
pub fn workload(identity: impl Into<String>) -> Self {
Self::Workload(identity.into())
}
#[must_use]
pub const fn is_anonymous(&self) -> bool {
matches!(self, Self::Anonymous)
}
}
impl fmt::Display for PeerIdentity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Anonymous => f.write_str("anonymous"),
Self::Workload(identity) => f.write_str(identity),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AudienceBinding {
permitted: BTreeMap<PeerIdentity, BTreeSet<Audience>>,
}
impl AudienceBinding {
#[must_use]
pub const fn new() -> Self {
Self {
permitted: BTreeMap::new(),
}
}
#[must_use]
pub fn allow(mut self, identity: PeerIdentity, audience: Audience) -> Self {
self.permitted.entry(identity).or_default().insert(audience);
self
}
#[must_use]
pub fn permits(&self, identity: &PeerIdentity, audience: &Audience) -> bool {
self.permitted
.get(identity)
.is_some_and(|audiences| audiences.contains(audience))
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.permitted.is_empty()
}
}
pub fn check_audience_binding(
identity: &PeerIdentity,
declared: &Audience,
served: &Audience,
binding: &AudienceBinding,
family: &OperationFamily,
) -> Result<(), StateError> {
check_audience(declared, served, family)?;
if binding.permits(identity, declared) {
return Ok(());
}
Err(StateError::Denied {
family: family.clone(),
})
}
#[cfg(test)]
mod binding_tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs, clippy::unwrap_used)]
use super::*;
use polyc_state::{error::RetryClass, journal};
fn family_id() -> OperationFamily {
journal::family()
}
fn served() -> Audience {
state_audience()
}
#[test]
fn a_verified_leaf_yields_a_stable_workload_identity() {
let one = PeerIdentity::from_verified_leaf(Some(b"leaf-bytes"));
assert_eq!(one, PeerIdentity::from_verified_leaf(Some(b"leaf-bytes")));
assert_ne!(one, PeerIdentity::from_verified_leaf(Some(b"other-bytes")));
assert!(!one.is_anonymous());
assert_eq!(one.to_string().len(), 64, "a sha-256 digest, hex encoded");
let none = PeerIdentity::from_verified_leaf(None);
assert!(none.is_anonymous());
assert_eq!(none.to_string(), "anonymous");
}
#[test]
fn a_new_binding_permits_nothing_including_anonymity() {
let binding = AudienceBinding::new();
assert!(binding.is_empty());
for identity in [
PeerIdentity::Anonymous,
PeerIdentity::workload("control-plane"),
] {
let error =
check_audience_binding(&identity, &served(), &served(), &binding, &family_id())
.unwrap_err();
assert_eq!(
error,
StateError::Denied {
family: family_id()
},
"{identity}"
);
assert_eq!(error.retry_class(), RetryClass::Terminal);
}
}
#[test]
fn a_correctly_addressed_call_from_the_wrong_workload_is_denied() {
let binding =
AudienceBinding::new().allow(PeerIdentity::workload("control-plane"), served());
assert!(
check_audience_binding(
&PeerIdentity::workload("control-plane"),
&served(),
&served(),
&binding,
&family_id()
)
.is_ok(),
"the bound workload asks for the audience it is bound to"
);
let error = check_audience_binding(
&PeerIdentity::workload("projector"),
&served(),
&served(),
&binding,
&family_id(),
)
.unwrap_err();
assert_eq!(
error,
StateError::Denied {
family: family_id()
}
);
assert!(
!error.to_string().contains("projector"),
"a denial must not echo who was refused"
);
}
#[test]
fn a_binding_does_not_generalize_across_audiences() {
let other = Audience::new("forensics");
let binding = AudienceBinding::new().allow(PeerIdentity::workload("control-plane"), other);
assert!(
check_audience_binding(
&PeerIdentity::workload("control-plane"),
&served(),
&served(),
&binding,
&family_id()
)
.is_err()
);
}
#[test]
fn a_bound_workload_asking_the_wrong_surface_is_still_denied() {
let elsewhere = Audience::new("somewhere-else");
let binding = AudienceBinding::new()
.allow(PeerIdentity::workload("control-plane"), elsewhere.clone());
assert!(
check_audience_binding(
&PeerIdentity::workload("control-plane"),
&elsewhere,
&served(),
&binding,
&family_id()
)
.is_err()
);
}
#[test]
fn anonymity_is_permitted_only_when_a_composition_names_it() {
let binding = AudienceBinding::new().allow(PeerIdentity::Anonymous, served());
assert!(
check_audience_binding(
&PeerIdentity::Anonymous,
&served(),
&served(),
&binding,
&family_id()
)
.is_ok()
);
assert!(binding.permits(&PeerIdentity::Anonymous, &served()));
assert!(!binding.permits(&PeerIdentity::workload("x"), &served()));
}
}