Skip to main content

routing/bedrock/
with_policy_check.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::capability_source::CapabilitySource;
6use crate::component_instance::{ComponentInstanceInterface, WeakExtendedInstanceInterface};
7use crate::error::{ComponentInstanceError, RoutingError};
8use crate::policy::GlobalPolicyChecker;
9use async_trait::async_trait;
10use moniker::ExtendedMoniker;
11use router_error::RouterError;
12#[cfg(not(target_os = "fuchsia"))]
13use runtime_capabilities::Capability;
14use runtime_capabilities::{CapabilityBound, Data, Request, Routable, Router, WeakInstanceToken};
15
16/// If the metadata for a route contains a Data::Uint64 value under this key with a value greater
17/// than 0, then no policy checks will be performed. This behavior is limited to non-fuchsia
18/// builds, and is exclusively used when performing routes from an offer declaration. This is
19/// necessary because we don't know the ultimate target of the route, and thus routes that are
20/// otherwise valid could fail due to policy checks.
21///
22/// Consider a policy that allows a component `/core/session_manager/session:session/my_cool_app`
23/// to access `fuchsia.kernel.VmexResource`. If we attempt to validate that route from the offer
24/// placed on `session_manager`, we'd have to fill in `session_manager` for the target of the route
25/// in the route request and follow the route to its source from there. If this policy check were
26/// applied on this route it would fail the route, as `session` manager is not allowed to access
27/// `fuchsia.kernel.VmexResource`. The route is valid though, because the offer on
28/// `session_manager` doesn't grant the session manager program access to the restricted
29/// capability.
30///
31/// To be able to properly support this scenario, we need to selectively disable policy checks when
32/// routing from offer declarations.
33pub const SKIP_POLICY_CHECKS: &'static str = "skip_policy_checks";
34
35pub trait WithPolicyCheck {
36    /// Returns a router that ensures the capability request is allowed by the
37    /// policy in [`GlobalPolicyChecker`].
38    fn with_policy_check<C: ComponentInstanceInterface + 'static>(
39        self,
40        capability_source: CapabilitySource,
41        policy_checker: GlobalPolicyChecker,
42    ) -> Self;
43}
44
45impl<T: CapabilityBound> WithPolicyCheck for Router<T> {
46    fn with_policy_check<C: ComponentInstanceInterface + 'static>(
47        self,
48        capability_source: CapabilitySource,
49        policy_checker: GlobalPolicyChecker,
50    ) -> Self {
51        Self::new(PolicyCheckRouter::<C, T>::new(capability_source, policy_checker, self))
52    }
53}
54
55pub struct PolicyCheckRouter<C: ComponentInstanceInterface + 'static, T: CapabilityBound> {
56    capability_source: CapabilitySource,
57    policy_checker: GlobalPolicyChecker,
58    router: Router<T>,
59    _phantom_data: std::marker::PhantomData<C>,
60}
61
62impl<C: ComponentInstanceInterface + 'static, T: CapabilityBound> PolicyCheckRouter<C, T> {
63    pub fn new(
64        capability_source: CapabilitySource,
65        policy_checker: GlobalPolicyChecker,
66        router: Router<T>,
67    ) -> Self {
68        Self {
69            capability_source,
70            policy_checker,
71            router,
72            _phantom_data: std::marker::PhantomData::<C>,
73        }
74    }
75
76    fn check_policy(
77        &self,
78        _request: &Option<Request>,
79        target_token: WeakInstanceToken,
80    ) -> Result<(), RouterError> {
81        #[cfg(not(target_os = "fuchsia"))]
82        if let Some(Capability::Data(runtime_capabilities::Data::Uint64(num))) = _request
83            .as_ref()
84            .ok_or_else(|| RouterError::InvalidArgs)?
85            .metadata
86            .get(&cm_types::Name::new(SKIP_POLICY_CHECKS).unwrap())
87        {
88            if num > 0 {
89                return Ok(());
90            }
91        }
92        let target = target_token
93            .inner
94            .as_any()
95            .downcast_ref::<WeakExtendedInstanceInterface<C>>()
96            .ok_or(RouterError::Unknown)?;
97        let ExtendedMoniker::ComponentInstance(moniker) = target.extended_moniker() else {
98            return Err(RoutingError::from(
99                ComponentInstanceError::ComponentManagerInstanceUnexpected {},
100            )
101            .into());
102        };
103        match self.policy_checker.can_route_capability(&self.capability_source, &moniker) {
104            Ok(()) => Ok(()),
105            Err(policy_error) => Err(RoutingError::PolicyError(policy_error).into()),
106        }
107    }
108}
109
110#[async_trait]
111impl<C: ComponentInstanceInterface + 'static, T: CapabilityBound> Routable<T>
112    for PolicyCheckRouter<C, T>
113{
114    async fn route(
115        &self,
116        request: Option<Request>,
117        target_token: WeakInstanceToken,
118    ) -> Result<Option<T>, RouterError> {
119        self.check_policy(&request, target_token.clone())?;
120        self.router.route(request, target_token).await
121    }
122
123    async fn route_debug(
124        &self,
125        request: Option<Request>,
126        target_token: WeakInstanceToken,
127    ) -> Result<Data, RouterError> {
128        self.check_policy(&request, target_token.clone())?;
129        self.router.route_debug(request, target_token).await
130    }
131}