Skip to main content

amiss_wire/controls/
execution_constraint.rs

1use crate::de::{self, Error, ErrorKind, Obj, fail};
2use crate::digest::{Digest, hj};
3use crate::json::Value;
4use crate::model::{ObjectFormat, Oid, RepoPathText, RepositoryIdentity};
5
6use super::{decode_digest, decode_repo_path, decode_repository, root};
7
8const EXECUTION_CONSTRAINT_SCHEMA: &str = "amiss/scanner-execution-constraint";
9const ACTION_BOOTSTRAP_CONTRACT: &str = "amiss-action-bootstrap";
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum ConstraintPlatform {
13    LinuxX8664,
14    LinuxAarch64,
15    MacosX8664,
16    MacosAarch64,
17    WindowsX8664,
18    WindowsAarch64,
19}
20
21impl ConstraintPlatform {
22    #[must_use]
23    pub const fn as_str(self) -> &'static str {
24        match self {
25            Self::LinuxX8664 => "linux-x86_64",
26            Self::LinuxAarch64 => "linux-aarch64",
27            Self::MacosX8664 => "macos-x86_64",
28            Self::MacosAarch64 => "macos-aarch64",
29            Self::WindowsX8664 => "windows-x86_64",
30            Self::WindowsAarch64 => "windows-aarch64",
31        }
32    }
33
34    /// # Errors
35    ///
36    /// A value outside the closed six-platform table.
37    pub fn decode(path: &str, value: Value) -> Result<Self, Error> {
38        match de::string(path, value)?.as_str() {
39            "linux-x86_64" => Ok(Self::LinuxX8664),
40            "linux-aarch64" => Ok(Self::LinuxAarch64),
41            "macos-x86_64" => Ok(Self::MacosX8664),
42            "macos-aarch64" => Ok(Self::MacosAarch64),
43            "windows-x86_64" => Ok(Self::WindowsX8664),
44            "windows-aarch64" => Ok(Self::WindowsAarch64),
45            _ => fail(path, ErrorKind::InvalidValue),
46        }
47    }
48}
49
50/// The externally protected allow-list entry for one scanner action tree,
51/// release manifest, bootstrap contract, and required provider status name.
52#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct ExecutionConstraintDescriptor {
54    pub digest: Digest,
55    pub action_repository: RepositoryIdentity,
56    pub action_object_format: ObjectFormat,
57    pub action_commit_oid: Oid,
58    pub action_tree_oid: Oid,
59    pub manifest_path: RepoPathText,
60    pub release_manifest_digest: Digest,
61    pub selected_platform: ConstraintPlatform,
62    pub required_status_name: String,
63    pub bootstrap_digest: Digest,
64}
65
66fn decode_status_name(path: &str, value: Value) -> Result<String, Error> {
67    let raw = de::string(path, value)?;
68    let bytes = raw.as_bytes();
69    let interior = |byte: &u8| {
70        byte.is_ascii_alphanumeric() || matches!(byte, b' ' | b'.' | b'_' | b'/' | b'-')
71    };
72    let edge =
73        |byte: &u8| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'/' | b'-');
74    let valid = match (bytes.first(), bytes.last()) {
75        (Some(first), Some(last)) => {
76            bytes.len() <= 160
77                && first.is_ascii_alphanumeric()
78                && (bytes.len() == 1 || edge(last))
79                && bytes.iter().all(interior)
80        }
81        _ => false,
82    };
83    if valid {
84        Ok(raw)
85    } else {
86        fail(path, ErrorKind::InvalidValue)
87    }
88}
89
90impl ExecutionConstraintDescriptor {
91    /// # Errors
92    ///
93    /// Fails on strict-JSON defects, schema-shape violations, and invalid
94    /// grammar values.
95    pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
96        let value = root(bytes)?;
97        let digest = hj(EXECUTION_CONSTRAINT_SCHEMA, &value);
98        let mut obj = Obj::new("$", value)?;
99        de::const_str(
100            &obj.field("schema"),
101            obj.take("schema")?,
102            EXECUTION_CONSTRAINT_SCHEMA,
103        )?;
104        let action_repository = decode_repository(
105            &obj.field("action_repository"),
106            obj.take("action_repository")?,
107        )?;
108        let format_path = obj.field("action_object_format");
109        let action_object_format =
110            match de::string(&format_path, obj.take("action_object_format")?)?.as_str() {
111                "sha1" => ObjectFormat::Sha1,
112                "sha256" => ObjectFormat::Sha256,
113                _ => return fail(&format_path, ErrorKind::InvalidValue),
114            };
115        let commit_path = obj.field("action_commit_oid");
116        let action_commit_oid = Oid::new(
117            action_object_format,
118            de::string(&commit_path, obj.take("action_commit_oid")?)?,
119        )
120        .ok_or_else(|| Error::new(&commit_path, ErrorKind::InvalidValue))?;
121        let tree_path = obj.field("action_tree_oid");
122        let action_tree_oid = Oid::new(
123            action_object_format,
124            de::string(&tree_path, obj.take("action_tree_oid")?)?,
125        )
126        .ok_or_else(|| Error::new(&tree_path, ErrorKind::InvalidValue))?;
127        let manifest_path =
128            decode_repo_path(&obj.field("manifest_path"), obj.take("manifest_path")?)?;
129        let release_manifest_digest = decode_digest(
130            &obj.field("release_manifest_digest"),
131            obj.take("release_manifest_digest")?,
132        )?;
133        let selected_platform = ConstraintPlatform::decode(
134            &obj.field("selected_platform"),
135            obj.take("selected_platform")?,
136        )?;
137        let required_status_name = decode_status_name(
138            &obj.field("required_status_name"),
139            obj.take("required_status_name")?,
140        )?;
141        de::const_str(
142            &obj.field("bootstrap_contract"),
143            obj.take("bootstrap_contract")?,
144            ACTION_BOOTSTRAP_CONTRACT,
145        )?;
146        let bootstrap_digest = decode_digest(
147            &obj.field("bootstrap_digest"),
148            obj.take("bootstrap_digest")?,
149        )?;
150        obj.finish()?;
151        Ok(Self {
152            digest,
153            action_repository,
154            action_object_format,
155            action_commit_oid,
156            action_tree_oid,
157            manifest_path,
158            release_manifest_digest,
159            selected_platform,
160            required_status_name,
161            bootstrap_digest,
162        })
163    }
164}