oasis_runtime_sdk/modules/rofl/
policy.rs

1use 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
25/// Maximum depth when evaluating an endorsement policy.
26pub const MAX_ENDORSEMENT_POLICY_DEPTH: usize = 4;
27
28/// Per-application ROFL policy.
29#[derive(Clone, Debug, PartialEq, Eq, Default, cbor::Encode, cbor::Decode)]
30pub struct AppAuthPolicy {
31    /// Quote policy.
32    pub quotes: QuotePolicy,
33    /// The set of allowed enclave identities.
34    pub enclaves: Vec<EnclaveIdentity>,
35    /// The set of allowed endorsements.
36    pub endorsements: Vec<Box<AllowedEndorsement>>,
37    /// Gas fee payment policy.
38    pub fees: FeePolicy,
39    /// Maximum number of future epochs for which one can register.
40    pub max_expiration: EpochTime,
41}
42
43impl AppAuthPolicy {
44    /// Validate the application policy for basic correctness.
45    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/// An allowed endorsement policy.
89#[derive(Clone, Debug, PartialEq, Eq, cbor::Encode, cbor::Decode)]
90#[cbor(no_default)]
91pub enum AllowedEndorsement {
92    /// Any node can endorse the enclave.
93    #[cbor(rename = "any", as_struct)]
94    Any,
95    /// Compute node for the current runtime can endorse the enclave.
96    #[cbor(rename = "role_compute", as_struct)]
97    ComputeRole,
98    /// Observer node for the current runtime can endorse the enclave.
99    #[cbor(rename = "role_observer", as_struct)]
100    ObserverRole,
101    /// Registered node from a specific entity can endorse the enclave.
102    #[cbor(rename = "entity")]
103    Entity(PublicKey),
104    /// Specific node can endorse the enclave.
105    #[cbor(rename = "node")]
106    Node(PublicKey),
107    /// Any node from a specific provider can endorse the enclave.
108    #[cbor(rename = "provider")]
109    Provider(Address),
110    /// Any provider instance where the given address is currently the admin.
111    #[cbor(rename = "provider_instance_admin")]
112    ProviderInstanceAdmin(Address),
113
114    /// Evaluate all of the child endorsement policies and allow in case all accept the node.
115    #[cbor(rename = "and")]
116    And(Vec<Box<AllowedEndorsement>>),
117    /// Evaluate all of the child endorsement policies and allow in case any accepts the node.
118    #[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/// Endorsement policy operator.
143#[derive(Copy, Clone, Debug)]
144pub enum EndorsementPolicyOperator {
145    And,
146    Or,
147}
148
149/// Gas fee payment policy.
150#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
151#[repr(u8)]
152pub enum FeePolicy {
153    /// Application enclave pays the gas fees.
154    InstancePays = 1,
155    /// Endorsing node pays the gas fees.
156    #[default]
157    EndorsingNodePays = 2,
158}
159
160/// An evaluator of an endorsement policy which decides whether the node that endorsed the given
161/// enclave is acceptable accoording to a policy.
162pub trait EndorsementPolicyEvaluator {
163    /// Verify the given endorsed TEE capability against an endorsement policy.
164    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        // Attempt to resolve the node that endorsed the enclave. It may be that the node is not
173        // even registered in the consensus layer which may be acceptable for some policies.
174        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            // Ensure node is not expired.
185            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    /// Verify multiple endorsement policy atoms.
207    #[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    /// Verify a single endorsement policy atom.
263    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
295/// An endorsement policy evaluator which implements support for basic endorsement atoms.
296pub 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        // Ensure node is registered for this runtime.
308        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                // Any node is allowed.
316                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 a specific entity is required, it may be registered for any runtime.
330                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        // If nothing matched, this node is not allowed to register.
343        Err(Error::NodeNotAllowed)
344    }
345}