add OpenBSD TA constraints and localhost TLS examples
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user