add OpenBSD TA constraints and localhost TLS examples
Some checks failed
ci / rust (push) Has been cancelled
ci / docker (push) Has been cancelled
ci / audit (push) Has been cancelled

This commit is contained in:
2026-09-20 11:28:01 +08:00
parent 4975128945
commit 09b4c40b9f
31 changed files with 4878 additions and 47 deletions

View File

@@ -21,6 +21,7 @@ pub struct Config {
pub mode: String,
pub anchors: Vec<Anchor>,
pub ta_refresh_policy: String,
pub ta_constraints_enabled: bool,
pub max_ca_depth: usize,
pub http_timeout_secs: u64,
pub http_root_cert: Vec<PathBuf>,
@@ -51,6 +52,7 @@ impl Default for Config {
mode: String::new(),
anchors: Vec::new(),
ta_refresh_policy: "allow-valid-cache".into(),
ta_constraints_enabled: true,
max_ca_depth: 64,
http_timeout_secs: 300,
http_root_cert: Vec::new(),
@@ -270,17 +272,13 @@ impl Config {
&std::fs::read(ta).map_err(|e| format!("read TA: {e}"))?,
)?;
}
let constraints = anchor
.constraints
.clone()
.unwrap_or_else(|| anchor.tal.with_extension("constraints"));
if anchor.constraints.is_some() || constraints.exists() {
crate::ta_constraints::TaConstraintsByTal::load_for_tals(
&[anchor.input(anchor.ta.as_deref().unwrap_or(&anchor.tal))],
&[format!("{}={}", anchor.id, constraints.display())],
)?;
}
}
let inputs: Vec<_> = self
.anchors
.iter()
.map(|anchor| anchor.input(anchor.ta.as_deref().unwrap_or(&anchor.tal)))
.collect();
self.load_constraints(&inputs)?;
for path in &self.http_root_cert {
reqwest::Certificate::from_pem(
&std::fs::read(path).map_err(|e| format!("read HTTP root: {e}"))?,
@@ -323,6 +321,29 @@ impl Config {
Ok(())
}
/// Use the same policy selection for config inspection and actual validation.
/// Explicit disable takes precedence over paths and adjacent-file discovery.
pub(crate) fn load_constraints(
&self,
inputs: &[crate::scheduler::types::TalInputSpec],
) -> Result<crate::ta_constraints::TaConstraintsByTal, String> {
if !self.ta_constraints_enabled {
return Ok(crate::ta_constraints::TaConstraintsByTal::default());
}
let overrides: Vec<String> = self
.anchors
.iter()
.filter(|anchor| inputs.iter().any(|input| input.tal_id == anchor.id))
.filter_map(|anchor| {
anchor
.constraints
.as_ref()
.map(|path| format!("{}={}", anchor.id, path.display()))
})
.collect();
crate::ta_constraints::TaConstraintsByTal::load_for_tals(inputs, &overrides)
}
pub(crate) fn child_config(
&self,
out: &Path,
@@ -389,3 +410,62 @@ fn collect_sources(
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constraints_selection_disable_override_and_reload() {
let dir = tempfile::tempdir().unwrap();
let tal = dir.path().join("test.tal");
let adjacent = tal.with_extension("constraints");
std::fs::write(&adjacent, "allow 192.0.2.0/24\n").unwrap();
let mut config = Config {
anchors: vec![Anchor {
id: "test".into(),
tal,
ta: None,
constraints: None,
}],
..Default::default()
};
let inputs = vec![config.anchors[0].input(&config.anchors[0].tal)];
assert!(
config
.load_constraints(&inputs)
.unwrap()
.for_tal("test")
.is_some()
);
// Read fresh policy on each invocation, even with unchanged TAL/state.
std::fs::write(&adjacent, "invalid policy").unwrap();
assert!(config.load_constraints(&inputs).is_err());
let explicit = dir.path().join("custom.constraints");
std::fs::write(&explicit, "deny 192.0.2.0/24\n").unwrap();
config.anchors[0].constraints = Some(explicit.clone());
assert_eq!(
config
.load_constraints(&inputs)
.unwrap()
.for_tal("test")
.unwrap()
.source(),
explicit
);
std::fs::remove_file(explicit).unwrap();
assert!(config.load_constraints(&inputs).is_err());
config.ta_constraints_enabled = false;
assert!(config.load_constraints(&inputs).unwrap().is_empty());
config.anchors[0].constraints = None;
assert!(config.load_constraints(&inputs).unwrap().is_empty());
let child: Config = toml::from_str(
&config
.child_config(dir.path(), dir.path(), "delta")
.unwrap(),
)
.unwrap();
assert!(!child.ta_constraints_enabled);
assert!(child.load_constraints(&inputs).unwrap().is_empty());
}
}

View File

@@ -90,18 +90,15 @@ fn execute(config: &Config, run_id: &str) -> Result<i32, String> {
observer.finish(false);
return Err("all trust anchors failed".into());
}
let overrides: Vec<String> = config
.anchors
let constraints = config.load_constraints(&inputs)?;
let applied_constraints: Vec<Value> = inputs
.iter()
.filter(|a| inputs.iter().any(|i| i.tal_id == a.id))
.filter_map(|a| {
a.constraints
.as_ref()
.map(|p| format!("{}={}", a.id, p.display()))
.filter_map(|input| {
constraints
.for_tal(&input.tal_id)
.map(|policy| json!({"id": input.tal_id, "path": policy.source()}))
})
.collect();
let constraints =
crate::ta_constraints::TaConstraintsByTal::load_for_tals(&inputs, &overrides)?;
// All runtime artifacts are first written under an isolated attempt directory.
let staging = tempfile::Builder::new()
.prefix(".validation-")
@@ -148,6 +145,8 @@ fn execute(config: &Config, run_id: &str) -> Result<i32, String> {
} else {
"success"
};
summary["taConstraintsEnabled"] = json!(config.ta_constraints_enabled);
summary["taConstraintsApplied"] = json!(applied_constraints);
summary["resultStatus"] = json!(status);
summary["taFailedCount"] = json!(failed);
summary["taCacheFallbackCount"] = json!(cached);