oasis_runtime_sdk/modules/rofl/
policy.rs1use std::collections::BTreeMap;
2
3use impl_trait_for_tuples::impl_for_tuples;
4
5use oasis_core_runtime::consensus::registry::EndorsedCapabilityTEE;
6
7use crate::{
8 core::{
9 common::{
10 crypto::signature::PublicKey,
11 sgx::{EnclaveIdentity, QuotePolicy},
12 },
13 consensus::{
14 beacon::EpochTime,
15 registry::{Node, RolesMask},
16 state::registry::ImmutableState as RegistryImmutableState,
17 },
18 },
19 types::address::Address,
20 Context, Runtime,
21};
22
23use super::{error::Error, Config};
24
25pub const MAX_ENDORSEMENT_POLICY_DEPTH: usize = 4;
27
28#[derive(Clone, Debug, PartialEq, Eq, Default, cbor::Encode, cbor::Decode)]
30pub struct AppAuthPolicy {
31 pub quotes: QuotePolicy,
33 pub enclaves: Vec<EnclaveIdentity>,
35 pub endorsements: Vec<Box<AllowedEndorsement>>,
37 pub fees: FeePolicy,
39 pub max_expiration: EpochTime,
41}
42
43impl AppAuthPolicy {
44 pub fn validate<Cfg: Config>(&self) -> Result<(), Error> {
46 let atom_count = Self::count_endorsement_atoms(&self.endorsements, 1)?;
47 if atom_count > Cfg::MAX_ENDORSEMENT_POLICY_ATOMS {
48 return Err(Error::EndorsementPolicyTooManyAtoms(
49 Cfg::MAX_ENDORSEMENT_POLICY_ATOMS,
50 ));
51 }
52
53 if self.max_expiration > Cfg::MAX_POLICY_EXPIRATION_EPOCHS {
54 return Err(Error::PolicyMaxExpirationTooHigh(
55 Cfg::MAX_POLICY_EXPIRATION_EPOCHS,
56 ));
57 }
58
59 Ok(())
60 }
61
62 fn count_endorsement_atoms(
63 endorsements: &[Box<AllowedEndorsement>],
64 depth: usize,
65 ) -> Result<usize, Error> {
66 if depth > MAX_ENDORSEMENT_POLICY_DEPTH {
67 return Err(Error::EndorsementPolicyTooDeep(
68 MAX_ENDORSEMENT_POLICY_DEPTH,
69 ));
70 }
71
72 let mut atom_count = endorsements.len();
73 for atom in endorsements {
74 match &**atom {
75 AllowedEndorsement::And(atoms) | AllowedEndorsement::Or(atoms) => {
76 let child_atoms =
77 Self::count_endorsement_atoms(atoms, depth.saturating_add(1))?;
78 atom_count = atom_count.saturating_add(child_atoms);
79 }
80 _ => continue,
81 }
82 }
83
84 Ok(atom_count)
85 }
86}
87
88#[derive(Clone, Debug, PartialEq, Eq, cbor::Encode, cbor::Decode)]
90#[cbor(no_default)]
91pub enum AllowedEndorsement {
92 #[cbor(rename = "any", as_struct)]
94 Any,
95 #[cbor(rename = "role_compute", as_struct)]
97 ComputeRole,
98 #[cbor(rename = "role_observer", as_struct)]
100 ObserverRole,
101 #[cbor(rename = "entity")]
103 Entity(PublicKey),
104 #[cbor(rename = "node")]
106 Node(PublicKey),
107 #[cbor(rename = "provider")]
109 Provider(Address),
110 #[cbor(rename = "provider_instance_admin")]
112 ProviderInstanceAdmin(Address),
113
114 #[cbor(rename = "and")]
116 And(Vec<Box<AllowedEndorsement>>),
117 #[cbor(rename = "or")]
119 Or(Vec<Box<AllowedEndorsement>>),
120}
121
122impl cbor::Encode for Box<AllowedEndorsement> {
123 fn is_empty(&self) -> bool {
124 AllowedEndorsement::is_empty(self)
125 }
126
127 fn into_cbor_value(self) -> cbor::Value {
128 AllowedEndorsement::into_cbor_value(*self)
129 }
130}
131
132impl cbor::Decode for Box<AllowedEndorsement> {
133 fn try_default() -> Result<Self, cbor::DecodeError> {
134 AllowedEndorsement::try_default().map(Box::new)
135 }
136
137 fn try_from_cbor_value(value: cbor::Value) -> Result<Self, cbor::DecodeError> {
138 AllowedEndorsement::try_from_cbor_value(value).map(Box::new)
139 }
140}
141
142#[derive(Copy, Clone, Debug)]
144pub enum EndorsementPolicyOperator {
145 And,
146 Or,
147}
148
149#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
151#[repr(u8)]
152pub enum FeePolicy {
153 InstancePays = 1,
155 #[default]
157 EndorsingNodePays = 2,
158}
159
160pub trait EndorsementPolicyEvaluator {
163 fn verify<C: Context>(
165 ctx: &C,
166 policy: &[Box<AllowedEndorsement>],
167 ect: &EndorsedCapabilityTEE,
168 metadata: &BTreeMap<String, String>,
169 ) -> Result<Option<Node>, Error> {
170 let endorsing_node_id = ect.node_endorsement.public_key;
171
172 let endorsing_node = || -> Result<Option<Node>, Error> {
175 let registry = RegistryImmutableState::new(ctx.consensus_state());
176 let node = registry
177 .node(&endorsing_node_id)
178 .map_err(|_| Error::UnknownNode)?;
179 let node = if let Some(node) = node {
180 node
181 } else {
182 return Ok(None);
183 };
184 if node.expiration < ctx.epoch() {
186 return Ok(None);
187 }
188
189 Ok(Some(node))
190 }()?;
191
192 Self::verify_atoms(
193 ctx,
194 EndorsementPolicyOperator::Or,
195 policy,
196 ect,
197 endorsing_node_id,
198 &endorsing_node,
199 metadata,
200 MAX_ENDORSEMENT_POLICY_DEPTH,
201 )?;
202
203 Ok(endorsing_node)
204 }
205
206 #[allow(clippy::too_many_arguments)]
208 fn verify_atoms<C: Context>(
209 ctx: &C,
210 op: EndorsementPolicyOperator,
211 policy: &[Box<AllowedEndorsement>],
212 ect: &EndorsedCapabilityTEE,
213 endorsing_node_id: PublicKey,
214 endorsing_node: &Option<Node>,
215 metadata: &BTreeMap<String, String>,
216 max_depth: usize,
217 ) -> Result<(), Error> {
218 if max_depth == 0 {
219 return Err(Error::NodeNotAllowed);
220 }
221
222 for atom in policy {
223 let result = match &**atom {
224 AllowedEndorsement::And(children) => Self::verify_atoms(
225 ctx,
226 EndorsementPolicyOperator::And,
227 children,
228 ect,
229 endorsing_node_id,
230 endorsing_node,
231 metadata,
232 max_depth.saturating_sub(1),
233 ),
234 AllowedEndorsement::Or(children) => Self::verify_atoms(
235 ctx,
236 EndorsementPolicyOperator::Or,
237 children,
238 ect,
239 endorsing_node_id,
240 endorsing_node,
241 metadata,
242 max_depth.saturating_sub(1),
243 ),
244 atom => {
245 Self::verify_atom(ctx, atom, ect, endorsing_node_id, endorsing_node, metadata)
246 }
247 };
248
249 match (op, result) {
250 (EndorsementPolicyOperator::Or, Ok(_)) => return Ok(()),
251 (EndorsementPolicyOperator::And, Err(err)) => return Err(err),
252 _ => continue,
253 }
254 }
255
256 match op {
257 EndorsementPolicyOperator::Or => Err(Error::NodeNotAllowed),
258 EndorsementPolicyOperator::And => Ok(()),
259 }
260 }
261
262 fn verify_atom<C: Context>(
264 ctx: &C,
265 policy: &AllowedEndorsement,
266 ect: &EndorsedCapabilityTEE,
267 endorsing_node_id: PublicKey,
268 endorsing_node: &Option<Node>,
269 metadata: &BTreeMap<String, String>,
270 ) -> Result<(), Error>;
271}
272
273#[impl_for_tuples(30)]
274impl EndorsementPolicyEvaluator for Tuple {
275 fn verify_atom<C: Context>(
276 ctx: &C,
277 policy: &AllowedEndorsement,
278 ect: &EndorsedCapabilityTEE,
279 endorsing_node_id: PublicKey,
280 endorsing_node: &Option<Node>,
281 metadata: &BTreeMap<String, String>,
282 ) -> Result<(), Error> {
283 for_tuples!(
284 #(
285 if Tuple::verify_atom(ctx, policy, ect, endorsing_node_id, endorsing_node, metadata).is_ok() {
286 return Ok(());
287 }
288 )*
289 );
290
291 Err(Error::NodeNotAllowed)
292 }
293}
294
295pub struct BasicEndorsementPolicyEvaluator;
297
298impl EndorsementPolicyEvaluator for BasicEndorsementPolicyEvaluator {
299 fn verify_atom<C: Context>(
300 ctx: &C,
301 policy: &AllowedEndorsement,
302 _ect: &EndorsedCapabilityTEE,
303 endorsing_node_id: PublicKey,
304 endorsing_node: &Option<Node>,
305 _metadata: &BTreeMap<String, String>,
306 ) -> Result<(), Error> {
307 let has_runtime = |node: &Node| -> bool {
309 let version = &<C::Runtime as Runtime>::VERSION;
310 node.get_runtime(ctx.runtime_id(), version).is_some()
311 };
312
313 match (policy, endorsing_node) {
314 (AllowedEndorsement::Any, _) => {
315 return Ok(());
317 }
318 (AllowedEndorsement::ComputeRole, Some(node)) => {
319 if node.has_roles(RolesMask::ROLE_COMPUTE_WORKER) && has_runtime(node) {
320 return Ok(());
321 }
322 }
323 (AllowedEndorsement::ObserverRole, Some(node)) => {
324 if node.has_roles(RolesMask::ROLE_OBSERVER) && has_runtime(node) {
325 return Ok(());
326 }
327 }
328 (AllowedEndorsement::Entity(entity_id), Some(node)) => {
329 if &node.entity_id == entity_id {
331 return Ok(());
332 }
333 }
334 (AllowedEndorsement::Node(node_id), _) => {
335 if endorsing_node_id == *node_id {
336 return Ok(());
337 }
338 }
339 _ => {}
340 }
341
342 Err(Error::NodeNotAllowed)
344 }
345}