ic_backup/policy/restore_safety/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 consistency::ApplicationFenceState,
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 before_start = request.wire().method() == IcManagementMethodRecord::StartCanister;
113 let lifecycle_matches = observation.targets().iter().all(|target| {
114 if before_start && target.target != binding.target() {
115 target.state != CanisterStatusType::Stopping
116 } else {
117 target.state == CanisterStatusType::Stopped
118 }
119 });
120 if !lifecycle_matches {
121 return Err(RestoreSafetyError::TargetNotStopped);
122 }
123 if before_start
124 && observation
125 .targets()
126 .iter()
127 .any(|target| target.restored_acceptance.is_none())
128 {
129 return Err(RestoreSafetyError::RestoredAcceptanceRequired);
130 }
131 validate_safety(requirement, observation.safety(), before_start)?;
132 Ok(RestoreSafetyView {
133 request: digest,
134 requirement: requirement.digest(),
135 observation,
136 })
137}
138fn validate_safety(
139 requirement: &RestoreSafetyRequirementRecord,
140 evidence: &RestoreSafetyEvidence,
141 before_start: bool,
142) -> Result<(), RestoreSafetyError> {
143 match (requirement.safety(), evidence) {
144 (_, RestoreSafetyEvidence::Unresolved(_)) => {
145 return Err(RestoreSafetyError::UnresolvedExternalObligations);
146 }
147 (
148 RestoreSafetyLaneRecord::NoIrreversibleEffects,
149 RestoreSafetyEvidence::NoIrreversibleEffects(_),
150 ) => {}
151 (
152 RestoreSafetyLaneRecord::ApplicationFenced,
153 RestoreSafetyEvidence::ApplicationFenced(fence),
154 ) => {
155 let original = requirement
156 .expected_fence()
157 .ok_or(RestoreSafetyError::FenceMismatch)?;
158 if fence.binding.identity != original.identity {
159 return Err(RestoreSafetyError::FenceMismatch);
160 }
161 if fence.binding.membership_revision != original.membership_revision {
162 return Err(RestoreSafetyError::MembershipRevisionMismatch);
163 }
164 if fence.binding.external_obligations_revision != original.external_obligations_revision
165 {
166 return Err(RestoreSafetyError::ExternalObligationsRevisionMismatch);
167 }
168 if fence.state != ApplicationFenceState::Active {
169 return Err(RestoreSafetyError::FenceNotActive);
170 }
171 if before_start && fence.controlled_execution.is_none() {
172 return Err(RestoreSafetyError::ControlledExecutionRequired);
173 }
174 }
175 _ => return Err(RestoreSafetyError::SafetyLaneMismatch),
176 }
177 Ok(())
178}
179#[derive(Debug, Eq, Error, PartialEq)]
181pub enum RestoreSafetyError {
182 #[error("restore safety request mismatch")]
184 RequestMismatch,
185 #[error("restore safety observed {0} mismatch")]
187 ContextMismatch(&'static str),
188 #[error("restore safety inventory mismatch")]
190 InventoryMismatch,
191 #[error("restore safety selection mismatch")]
193 SelectionMismatch,
194 #[error("restore safety source plan mismatch")]
196 SourcePlanMismatch,
197 #[error("restore safety source artifacts mismatch")]
199 SourceArtifactsMismatch,
200 #[error("restore safety evidence lane mismatch")]
202 SafetyLaneMismatch,
203 #[error("restore safety external obligations unresolved")]
205 UnresolvedExternalObligations,
206 #[error("restore safety target is not stopped")]
208 TargetNotStopped,
209 #[error("restore safety restored-state acceptance required")]
211 RestoredAcceptanceRequired,
212 #[error("restore safety fence identity mismatch")]
214 FenceMismatch,
215 #[error("restore safety membership revision mismatch")]
217 MembershipRevisionMismatch,
218 #[error("restore safety external-obligations revision mismatch")]
220 ExternalObligationsRevisionMismatch,
221 #[error("restore safety fence not active")]
223 FenceNotActive,
224 #[error("restore safety controlled execution evidence required")]
226 ControlledExecutionRequired,
227 #[error("restore safety reports {reported} observations above ceiling {limit}")]
229 ObservationLimitExceeded {
230 limit: u32,
232 reported: u32,
234 },
235}
236
237#[cfg(test)]
238mod tests;