wascc_host/
authz.rs

1use crate::errors;
2use crate::{Host, Result, WasccEntity};
3use std::collections::HashMap;
4use std::sync::Arc;
5use std::sync::RwLock;
6use wascap::jwt::Token;
7use wascap::prelude::*;
8
9pub(crate) type ClaimsMap = Arc<RwLock<HashMap<String, Claims<wascap::jwt::Actor>>>>;
10
11/// An authorizer is responsible for determining whether an actor can be loaded as well as
12/// whether an actor can invoke another entity. For invocation checks, the authorizer is only ever invoked _after_
13/// an initial capability attestation check has been performed and _passed_. This has the net effect of making it
14/// impossible to override the base behavior of checking that an actor's embedded JWT contains the right
15/// capability attestations.
16pub trait Authorizer: Sync + Send {
17    /// This check is performed during the `add_actor` call, allowing the custom authorizer to do things
18    /// like verify a provenance chain, make external calls, etc.
19    fn can_load(&self, claims: &Claims<Actor>) -> bool;
20    /// This check will be performed for _every_ invocation that has passed the base capability check,
21    /// including the operation that occurs during `bind_actor`. Developers should be aware of this because
22    /// if `set_authorizer` is done _after_ actor binding, it could potentially allow an unauthorized binding.
23    fn can_invoke(&self, claims: &Claims<Actor>, target: &WasccEntity, operation: &str) -> bool;
24}
25
26pub(crate) struct DefaultAuthorizer {}
27
28impl DefaultAuthorizer {
29    pub fn new() -> impl Authorizer {
30        DefaultAuthorizer {}
31    }
32}
33
34impl Authorizer for DefaultAuthorizer {
35    fn can_load(&self, _claims: &Claims<Actor>) -> bool {
36        true
37    }
38
39    // This doesn't actually mean everyone can invoke everything. Remember that the host itself
40    // will _always_ enforce the claims check on an actor having the required capability
41    // attestation
42    fn can_invoke(&self, _claims: &Claims<Actor>, target: &WasccEntity, _operation: &str) -> bool {
43        match target {
44            WasccEntity::Actor(_a) => true,
45            WasccEntity::Capability { .. } => true,
46        }
47    }
48}
49
50pub(crate) fn get_all_claims(map: ClaimsMap) -> Vec<(String, Claims<wascap::jwt::Actor>)> {
51    map.read()
52        .unwrap()
53        .iter()
54        .map(|(pk, claims)| (pk.clone(), claims.clone()))
55        .collect()
56}
57
58// We don't (yet) support per-operation security constraints, but when we do, this
59// function will be ready to support that without breaking everyone else's calls
60pub(crate) fn can_invoke(
61    claims: &Claims<wascap::jwt::Actor>,
62    capability_id: &str,
63    _operation: &str,
64) -> bool {
65    // Edge case - deliver configuration to an actor directly,
66    // so "self invocation" needs to be authorized
67    if claims.subject == capability_id {
68        return true;
69    }
70    claims
71        .metadata
72        .as_ref()
73        .unwrap()
74        .caps
75        .as_ref()
76        .map_or(false, |caps| caps.contains(&capability_id.to_string()))
77}
78
79// Extract claims from the JWT embedded in the wasm module's custom section
80pub(crate) fn extract_claims(buf: &[u8]) -> Result<wascap::jwt::Token<wascap::jwt::Actor>> {
81    let token = wascap::wasm::extract_claims(buf)?;
82    match token {
83        Some(token) => {
84            let claims = token.claims.clone();
85            let caps = claims.metadata.as_ref().unwrap().caps.clone();
86            info!(
87                "Actor claims loaded for {} - {}",
88                &claims.subject,
89                caps.unwrap_or(vec![]).join(",")
90            );
91            Ok(token)
92        }
93        None => Err(errors::new(errors::ErrorKind::Authorization(
94            "No embedded JWT in actor module".to_string(),
95        ))),
96    }
97}
98
99pub(crate) fn enforce_validation(jwt: &str) -> Result<()> {
100    let v = validate_token::<wascap::jwt::Actor>(jwt)?;
101    if v.expired {
102        Err(errors::new(errors::ErrorKind::Authorization(
103            "Expired token".to_string(),
104        )))
105    } else if v.cannot_use_yet {
106        Err(errors::new(errors::ErrorKind::Authorization(format!(
107            "Module cannot be used before {}",
108            v.not_before_human
109        ))))
110    } else {
111        Ok(())
112    }
113}
114
115pub(crate) fn register_claims(
116    claims_map: ClaimsMap,
117    subject: &str,
118    claims: Claims<wascap::jwt::Actor>,
119) {
120    claims_map
121        .write()
122        .unwrap()
123        .insert(subject.to_string(), claims);
124}
125
126pub(crate) fn unregister_claims(claims_map: ClaimsMap, subject: &str) {
127    {
128        let mut lock = claims_map.write().unwrap();
129        let _ = lock.remove(subject);
130    }
131}
132
133impl Host {
134    pub(crate) fn check_auth(&self, token: &Token<wascap::jwt::Actor>) -> bool {
135        self.authorizer.read().unwrap().can_load(&token.claims)
136    }
137}