ic_backup/policy/restore_safety/
mod.rs1use super::restore_fence::{self, RestoreFenceMismatch};
4use crate::model::{
5 artifacts::ArtifactChecksumRecord,
6 ic_request::IcManagementMethodRecord,
7 restore_safety::{
8 RestoreFenceEvidence, RestoreSafetyEvidence, RestoreSafetyLaneRecord,
9 RestoreSafetyObservation, RestoreSafetyRequest, RestoreSafetyRequirementRecord,
10 TargetRestoreEvidence,
11 },
12};
13use ic_platform::management_canister_types::CanisterStatusType;
14use thiserror::Error;
15
16#[derive(Clone, Debug)]
18pub struct RestoreSafetyView<'a> {
19 request: ArtifactChecksumRecord,
20 requirement: ArtifactChecksumRecord,
21 observation: &'a RestoreSafetyObservation,
22}
23impl RestoreSafetyView<'_> {
24 #[must_use]
26 pub const fn request(&self) -> &ArtifactChecksumRecord {
27 &self.request
28 }
29 #[must_use]
31 pub const fn requirement(&self) -> &ArtifactChecksumRecord {
32 &self.requirement
33 }
34 #[must_use]
36 pub fn targets(&self) -> &[TargetRestoreEvidence] {
37 self.observation.targets()
38 }
39 #[must_use]
41 pub fn fence(&self) -> Option<&RestoreFenceEvidence> {
42 match self.observation.safety() {
43 RestoreSafetyEvidence::ApplicationFenced(fence) => Some(fence),
44 RestoreSafetyEvidence::NoIrreversibleEffects(_)
45 | RestoreSafetyEvidence::Unresolved(_) => None,
46 }
47 }
48 #[must_use]
50 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
51 self.observation.evidence()
52 }
53 #[must_use]
55 pub const fn remote_observations(&self) -> u32 {
56 self.observation.remote_observations()
57 }
58}
59pub fn validate<'a>(
76 request: &RestoreSafetyRequest<'_>,
77 observation: &'a RestoreSafetyObservation,
78) -> Result<RestoreSafetyView<'a>, RestoreSafetyError> {
79 let digest = request.digest();
80 let binding = request.binding();
81 super::observed_context::admit_request_context(
82 &digest,
83 observation.request(),
84 binding,
85 observation.context(),
86 RestoreSafetyError::RequestMismatch,
87 RestoreSafetyError::ContextMismatch,
88 )?;
89 if observation.inventory() != request.inventory() {
90 return Err(RestoreSafetyError::InventoryMismatch);
91 }
92 if !observation
93 .targets()
94 .iter()
95 .map(|target| &target.target)
96 .eq(request.selected_targets())
97 {
98 return Err(RestoreSafetyError::SelectionMismatch);
99 }
100 let requirement = request.requirement();
101 if observation.source_plan_intent().hash() != requirement.source_plan_intent() {
102 return Err(RestoreSafetyError::SourcePlanMismatch);
103 }
104 if observation.source_artifacts() != requirement.source_artifacts() {
105 return Err(RestoreSafetyError::SourceArtifactsMismatch);
106 }
107 super::observed_context::admit_limit(
108 request.max_remote_observations(),
109 observation.remote_observations(),
110 |limit, reported| RestoreSafetyError::ObservationLimitExceeded { limit, reported },
111 )?;
112 let method = request.wire().method();
113 validate_targets(method, binding.target(), observation.targets())?;
114 validate_safety(requirement, observation.safety(), method)?;
115 Ok(RestoreSafetyView {
116 request: digest,
117 requirement: requirement.digest(),
118 observation,
119 })
120}
121fn validate_targets(
124 method: IcManagementMethodRecord,
125 current_target: &str,
126 targets: &[TargetRestoreEvidence],
127) -> Result<(), RestoreSafetyError> {
128 if !targets.iter().all(|target| match method {
129 IcManagementMethodRecord::StartCanister if target.target != current_target => {
130 target.state != CanisterStatusType::Stopping
131 }
132 _ => target.state == CanisterStatusType::Stopped,
133 }) {
134 return Err(RestoreSafetyError::TargetNotStopped);
135 }
136 if method == IcManagementMethodRecord::StartCanister
137 && targets
138 .iter()
139 .any(|target| target.restored_acceptance.is_none())
140 {
141 return Err(RestoreSafetyError::RestoredAcceptanceRequired);
142 }
143 Ok(())
144}
145
146fn validate_safety(
147 requirement: &RestoreSafetyRequirementRecord,
148 evidence: &RestoreSafetyEvidence,
149 method: IcManagementMethodRecord,
150) -> Result<(), RestoreSafetyError> {
151 match (requirement.safety(), evidence) {
152 (_, RestoreSafetyEvidence::Unresolved(_)) => {
153 return Err(RestoreSafetyError::UnresolvedExternalObligations);
154 }
155 (
156 RestoreSafetyLaneRecord::NoIrreversibleEffects,
157 RestoreSafetyEvidence::NoIrreversibleEffects(_),
158 ) => {}
159 (
160 RestoreSafetyLaneRecord::ApplicationFenced,
161 RestoreSafetyEvidence::ApplicationFenced(fence),
162 ) => {
163 let original = requirement
164 .expected_fence()
165 .ok_or(RestoreSafetyError::FenceMismatch)?;
166 restore_fence::validate(original, fence).map_err(|mismatch| match mismatch {
167 RestoreFenceMismatch::Identity => RestoreSafetyError::FenceMismatch,
168 RestoreFenceMismatch::MembershipRevision => {
169 RestoreSafetyError::MembershipRevisionMismatch
170 }
171 RestoreFenceMismatch::ExternalObligationsRevision => {
172 RestoreSafetyError::ExternalObligationsRevisionMismatch
173 }
174 RestoreFenceMismatch::Inactive => RestoreSafetyError::FenceNotActive,
175 })?;
176 if method == IcManagementMethodRecord::StartCanister
177 && fence.controlled_execution.is_none()
178 {
179 return Err(RestoreSafetyError::ControlledExecutionRequired);
180 }
181 }
182 _ => return Err(RestoreSafetyError::SafetyLaneMismatch),
183 }
184 Ok(())
185}
186#[derive(Debug, Eq, Error, PartialEq)]
188pub enum RestoreSafetyError {
189 #[error("restore safety request mismatch")]
191 RequestMismatch,
192 #[error("restore safety observed {0} mismatch")]
194 ContextMismatch(&'static str),
195 #[error("restore safety inventory mismatch")]
197 InventoryMismatch,
198 #[error("restore safety selection mismatch")]
200 SelectionMismatch,
201 #[error("restore safety source plan mismatch")]
203 SourcePlanMismatch,
204 #[error("restore safety source artifacts mismatch")]
206 SourceArtifactsMismatch,
207 #[error("restore safety evidence lane mismatch")]
209 SafetyLaneMismatch,
210 #[error("restore safety external obligations unresolved")]
212 UnresolvedExternalObligations,
213 #[error("restore safety target is not stopped")]
215 TargetNotStopped,
216 #[error("restore safety restored-state acceptance required")]
218 RestoredAcceptanceRequired,
219 #[error("restore safety fence identity mismatch")]
221 FenceMismatch,
222 #[error("restore safety membership revision mismatch")]
224 MembershipRevisionMismatch,
225 #[error("restore safety external-obligations revision mismatch")]
227 ExternalObligationsRevisionMismatch,
228 #[error("restore safety fence not active")]
230 FenceNotActive,
231 #[error("restore safety controlled execution evidence required")]
233 ControlledExecutionRequired,
234 #[error("restore safety reports {reported} observations above ceiling {limit}")]
236 ObservationLimitExceeded {
237 limit: u32,
239 reported: u32,
241 },
242}
243
244#[cfg(test)]
245mod tests;