use rand::RngCore;
#[derive(Debug, Clone, Default)]
pub struct AttestationPolicy {
pub expected_audience: Option<String>,
pub expected_nonce: Option<String>,
pub expected_image_digest: Option<String>,
pub expected_measurement: Option<String>,
pub allow_debug: bool,
pub max_age_secs: Option<u64>,
}
impl AttestationPolicy {
pub const MAX_AGE_CEILING_SECS: u64 = 3600;
pub fn production() -> Self {
Self {
allow_debug: false,
max_age_secs: Some(600),
..Self::default()
}
}
pub fn with_audience(mut self, aud: impl Into<String>) -> Self {
self.expected_audience = Some(aud.into());
self
}
pub fn with_nonce(mut self, nonce: impl Into<String>) -> Self {
self.expected_nonce = Some(nonce.into());
self
}
pub fn with_image_digest(mut self, digest: impl Into<String>) -> Self {
self.expected_image_digest = Some(digest.into());
self
}
pub fn with_measurement(mut self, measurement: impl Into<String>) -> Self {
self.expected_measurement = Some(measurement.into());
self
}
pub fn is_workload_bound(&self) -> bool {
self.expected_audience.is_some() || self.expected_image_digest.is_some()
}
pub fn from_custom_config(
config: &std::collections::HashMap<String, String>,
nonce: &str,
) -> Self {
let mut policy = Self::production().with_nonce(nonce);
if let Some(v) = config.get("tee_expected_audience") {
policy.expected_audience = Some(v.clone());
}
if let Some(v) = config.get("tee_expected_image_digest") {
policy.expected_image_digest = Some(v.clone());
}
if let Some(v) = config.get("tee_expected_measurement") {
policy.expected_measurement = Some(v.clone());
}
if let Some(v) = config.get("tee_allow_debug") {
if v.eq_ignore_ascii_case("true") {
if cfg!(feature = "testing") {
policy.allow_debug = true;
tracing::warn!(
"tee_allow_debug=true honoured: accepting debug-mode TEEs \
(testing build only)"
);
} else {
tracing::warn!(
"tee_allow_debug=true requested via deployment config but refused: \
debug-mode TEEs are only accepted in a `testing` build"
);
}
}
}
if let Some(v) = config.get("tee_max_age_secs") {
if let Ok(secs) = v.parse::<u64>() {
if secs > Self::MAX_AGE_CEILING_SECS {
tracing::warn!(
requested_secs = secs,
ceiling_secs = Self::MAX_AGE_CEILING_SECS,
"tee_max_age_secs exceeds the freshness ceiling; clamping"
);
}
policy.max_age_secs = Some(secs.min(Self::MAX_AGE_CEILING_SECS));
}
}
policy
}
}
pub fn fresh_nonce() -> String {
let mut bytes = [0_u8; 16];
rand::thread_rng().fill_bytes(&mut bytes);
hex::encode(bytes)
}
pub(crate) fn now_unix() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}