amiss_bootstrap/
constraint.rs1use std::fmt;
2
3use amiss_git::{GitResources, Repository};
4use amiss_wire::action::executable_platform;
5use amiss_wire::controls::{ExecutionConstraintDescriptor, ExecutionConstraintInput, GitMode};
6use amiss_wire::digest::{Digest, hb};
7use amiss_wire::model::{Oid, RepoPathText, RepositoryIdentity};
8
9use crate::build::{RELEASE_MANIFEST_DIGEST_PATH, RELEASE_MANIFEST_PATH};
10use crate::{
11 BOOTSTRAP_DOMAIN, Refusal, blob, load_release_manifest, resolve_action_tree, validate_release,
12};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub struct ConstraintError {
17 pub reason: &'static str,
19}
20
21impl fmt::Display for ConstraintError {
22 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
23 formatter.write_str(self.reason)
24 }
25}
26
27impl std::error::Error for ConstraintError {}
28
29pub fn derive_execution_constraint(
39 action: &Repository,
40 resources: &mut GitResources,
41 action_repository: &RepositoryIdentity,
42 action_commit_oid: &Oid,
43 required_status_name: &str,
44 bootstrap_bytes: &[u8],
45) -> Result<ExecutionConstraintDescriptor, ConstraintError> {
46 let platform = executable_platform(bootstrap_bytes).ok_or(ConstraintError {
47 reason: "bootstrap-platform-mismatch",
48 })?;
49 let tree =
50 resolve_action_tree(action, resources, action_commit_oid).map_err(constraint_error)?;
51 let manifest_path =
52 RepoPathText::new(RELEASE_MANIFEST_PATH.to_owned()).ok_or(ConstraintError {
53 reason: "execution-constraint-invalid",
54 })?;
55 let manifest = load_release_manifest(action, resources, &tree, &manifest_path)
56 .map_err(constraint_error)?;
57 let marker_path =
58 RepoPathText::new(RELEASE_MANIFEST_DIGEST_PATH.to_owned()).ok_or(ConstraintError {
59 reason: "execution-constraint-invalid",
60 })?;
61 let (marker, mode) = blob(action, resources, &tree, &marker_path).map_err(constraint_error)?;
62 if mode != GitMode::RegularFile {
63 return Err(ConstraintError {
64 reason: "manifest-digest-mismatch",
65 });
66 }
67 let marked_digest = parse_marker(&marker).ok_or(ConstraintError {
68 reason: "manifest-digest-mismatch",
69 })?;
70 if marked_digest != manifest.digest {
71 return Err(ConstraintError {
72 reason: "manifest-digest-mismatch",
73 });
74 }
75 let descriptor = ExecutionConstraintDescriptor::new(ExecutionConstraintInput {
76 action_repository: action_repository.clone(),
77 action_object_format: action.object_format(),
78 action_commit_oid: action_commit_oid.clone(),
79 action_tree_oid: tree.clone(),
80 manifest_path,
81 release_manifest_digest: manifest.digest,
82 selected_platform: platform,
83 required_status_name: required_status_name.to_owned(),
84 bootstrap_digest: hb(BOOTSTRAP_DOMAIN, bootstrap_bytes),
85 })
86 .map_err(|_defect| ConstraintError {
87 reason: "execution-constraint-invalid",
88 })?;
89 validate_release(action, resources, &tree, manifest, platform).map_err(constraint_error)?;
90 Ok(descriptor)
91}
92
93const fn constraint_error(refusal: Refusal) -> ConstraintError {
94 match refusal {
95 Refusal::Unavailable(reason) | Refusal::Tampered(reason) => ConstraintError { reason },
96 }
97}
98
99fn parse_marker(bytes: &[u8]) -> Option<Digest> {
100 bytes
101 .strip_suffix(b"\n")
102 .and_then(|text| std::str::from_utf8(text).ok())
103 .and_then(Digest::from_wire)
104}