Skip to main content

bacnet_server/
policy.rs

1//! One place for the server's authorization decisions (#524).
2//!
3//! [`ServerPolicy`] answers every "may this peer do this?" question the
4//! server asks of an application: the covered mutations, LifeSafetyOperation
5//! and both AuditNotification services. Each method has a documented
6//! default, so a policy overrides only what it decides.
7//!
8//! What stays outside it, and why:
9//! - [`MutationPolicy::DenyAll`](crate::mutation::MutationPolicy::DenyAll)
10//!   is a static hardening switch on [`ServerConfig`](crate::server::ServerConfig).
11//!   It wins over any policy, which isn't asked, and its denials are counted
12//!   apart.
13//! - DeviceCommunicationControl's password, source restriction and rate
14//!   limit are static settings, not callbacks.
15//! - `on_reinitialize` carries out a restart rather than deciding on one,
16//!   and runs after the server's own checks.
17//! - `on_time_sync` observes a change it can't refuse.
18//! - Routing control belongs to the router (`bacnet-network`'s
19//!   `ControlAuthorizer`), not to the server.
20//!
21//! ## Who sent it
22//!
23//! Every context carries the claimed source addresses, the ingress
24//! [`TransportProvenance`], and
25//! from those a `principal()`: the [`ScPrincipal`] of the leaf certificate
26//! when the request came over a verified direct BACnet/SC connection from
27//! the peer that originated it, and `None` otherwise. B/IP, B/IPv6, MS/TP
28//! and Ethernet have no principal. Neither does a request a hub relays: the
29//! hub authenticated its own TLS client, and the origin's certificate never
30//! reaches this node, so the policy can't tell one hub member from another.
31//!
32//! A routed request (one with an SNET/SADR source) has no principal either,
33//! even over a direct connection: the connection authenticates the router
34//! that forwarded it, not the device behind it. `forwarding_peer_principal()`
35//! returns that router's principal, for a policy that trusts a router with
36//! everything it forwards. A source VMAC, UUID or routed SNET/SADR is a
37//! claim, never a principal.
38//!
39//! So `principal()` means the direct SC peer that sent the NPDU without an
40//! SNET. A router that doesn't follow the standard, or an application
41//! gateway that re-originates requests, shows its own principal for what it
42//! relays: trust a principal as far as everything its holder may relay.
43//!
44//! SC mutual TLS authenticates a connection; it doesn't authorize a
45//! service. Without a policy, any peer a CA admits may use every service
46//! the server's defaults allow.
47//!
48//! ## Calling rules
49//!
50//! Every method must be fast, nonblocking and free of side effects. The
51//! server calls them on its dispatch tasks, sometimes with the object
52//! database locked (each method says when); calling back into the server
53//! from one can deadlock. Calls may run concurrently. Returning `false`, or
54//! panicking, denies, and the request changes nothing.
55//!
56//! ```
57//! use bacnet_server::mutation::MutationAuthorizationContext;
58//! use bacnet_server::ServerPolicy;
59//! use bacnet_transport::port::ScPrincipal;
60//!
61//! /// Writes only from the operator workstation's certificate.
62//! struct Site {
63//!     operator: ScPrincipal,
64//! }
65//!
66//! impl ServerPolicy for Site {
67//!     fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
68//!         context.principal() == Some(self.operator)
69//!     }
70//! }
71//! ```
72//!
73//! [`FnPolicy`] builds one from closures instead, keeping the default of
74//! every decision it isn't given:
75//!
76//! ```
77//! use bacnet_server::policy::FnPolicy;
78//! use bacnet_server::server::BACnetServer;
79//!
80//! // Remote writes from verified direct SC peers only; LifeSafetyOperation
81//! // from anyone. Audit notifications keep denying.
82//! let builder = BACnetServer::bip_builder().policy(
83//!     FnPolicy::new()
84//!         .mutation(|context| context.principal().is_some())
85//!         .life_safety_operation(|_| true),
86//! );
87//! ```
88
89use crate::audit_notification::{
90    AuditNotificationAuthorizationContext, UnconfirmedAuditNotificationAuthorizationContext,
91};
92use crate::life_safety::LifeSafetyOperationAuthorizationContext;
93use crate::mutation::MutationAuthorizationContext;
94use bacnet_encoding::npdu::NpduAddress;
95pub use bacnet_transport::port::ScPrincipal;
96use bacnet_transport::port::TransportProvenance;
97use std::sync::Arc;
98
99/// The originating peer's principal: the direct-SC leaf, unless the request
100/// was routed, when the connection only authenticates the forwarding router.
101pub(crate) fn originating_principal(
102    provenance: TransportProvenance,
103    source_network: Option<&NpduAddress>,
104) -> Option<ScPrincipal> {
105    source_network
106        .is_none()
107        .then(|| provenance.principal())
108        .flatten()
109}
110
111/// The server's authorization decisions, each with a documented default.
112///
113/// See the [module documentation](self) for what it covers, the principal
114/// each context carries and the calling rules. Install one with
115/// `policy(..)` on a server builder, or in
116/// [`ServerConfig::policy`](crate::server::ServerConfig::policy).
117///
118/// Without any policy installed, the server behaves as these defaults do,
119/// except that it allows a covered mutation without decoding it for
120/// authorization first; so does a policy whose
121/// [`decides_mutations`](Self::decides_mutations) is `false`.
122///
123/// `Arc<P>` and `Box<P>` are policies when `P` is, so one policy can be
124/// shared or boxed.
125pub trait ServerPolicy: Send + Sync + 'static {
126    /// Whether [`authorize_mutation`](Self::authorize_mutation) decides
127    /// anything (default: `true`).
128    ///
129    /// When `false`, the server never asks it: each covered mutation is
130    /// allowed without being decoded for authorization first, and counted as
131    /// allowed before the handler runs, exactly as with no policy. A policy
132    /// that only decides LifeSafetyOperation or Audit receipt returns `false`
133    /// so that mutations keep that path. The server may read it more than
134    /// once for one request, so it must give the same answer for the
135    /// policy's lifetime.
136    fn decides_mutations(&self) -> bool {
137        true
138    }
139
140    /// Whether to carry out one covered mutation (default: allow).
141    ///
142    /// Asked after DeviceCommunicationControl's checks, request admission,
143    /// service decoding and element validation, and before any database
144    /// change, COV or event work, or Audit record. A denial writes no Audit
145    /// record; it is counted in `BACnetServer::mutation_decision_counters`
146    /// and answered SERVICES / SERVICE_REQUEST_DENIED.
147    /// [`MutationPolicy::DenyAll`](crate::mutation::MutationPolicy::DenyAll)
148    /// denies without asking.
149    ///
150    /// WritePropertyMultiple asks once per decoded, validated element, in
151    /// wire order, just before that element's write, with the database
152    /// write-locked. A denial, or a malformed later element, leaves the
153    /// elements before it written. An element that an object saves before
154    /// serving it (a Notification Forwarder's or Notification Class's list,
155    /// an Access Rights object's rules, Enable or Accompaniment, an Audit
156    /// Log's Log_Enable or Buffer_Size) is asked about earlier, under a
157    /// database read guard, before the save is staged off the write lock
158    /// (#1321). It is still asked once and in wire order among such
159    /// elements, so the policy can be asked about an element the request
160    /// never reaches. The other confirmed services ask once, for the whole
161    /// decoded request.
162    ///
163    /// An inbound WriteGroup asks once per Channel it would write, with no
164    /// database guard held, no invoke ID and the unconfirmed service in the
165    /// context. A denial skips that Channel silently (nothing answers an
166    /// unconfirmed request) and writes no Audit record; the other Channels
167    /// are decided on their own (#1319).
168    fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
169        let _ = context;
170        true
171    }
172
173    /// Whether to carry out a LifeSafetyOperation (default: deny).
174    ///
175    /// Asked after the request is validated and its object found, with no
176    /// database guard held. A denial is answered SERVICES /
177    /// SERVICE_REQUEST_DENIED before the object changes.
178    fn authorize_life_safety_operation(
179        &self,
180        context: &LifeSafetyOperationAuthorizationContext,
181    ) -> bool {
182        let _ = context;
183        false
184    }
185
186    /// Whether to store a ConfirmedAuditNotification in the configured
187    /// Audit Log sink (default: deny).
188    ///
189    /// Asked after the request is decoded and checked against completed
190    /// receipts, with no database guard held. A denial is answered
191    /// SERVICES / SERVICE_REQUEST_DENIED and stores nothing.
192    fn authorize_audit_notification(
193        &self,
194        context: &AuditNotificationAuthorizationContext,
195    ) -> bool {
196        let _ = context;
197        false
198    }
199
200    /// Whether to store an UnconfirmedAuditNotification in the configured
201    /// Audit Log sink (default: deny).
202    ///
203    /// Asked after the request is decoded, with no database guard held. A
204    /// denial drops the request silently and stores nothing.
205    fn authorize_unconfirmed_audit_notification(
206        &self,
207        context: &UnconfirmedAuditNotificationAuthorizationContext,
208    ) -> bool {
209        let _ = context;
210        false
211    }
212}
213
214type Decide<C> = Arc<dyn Fn(&C) -> bool + Send + Sync>;
215
216/// A [`ServerPolicy`] made of closures, one per decision. A decision with no
217/// closure keeps the trait's default; with no mutation closure it doesn't
218/// decide mutations at all ([`ServerPolicy::decides_mutations`]).
219///
220/// Each closure follows the calling rules of the method it stands for.
221#[derive(Clone, Default)]
222pub struct FnPolicy {
223    mutation: Option<Decide<MutationAuthorizationContext>>,
224    life_safety_operation: Option<Decide<LifeSafetyOperationAuthorizationContext>>,
225    audit_notification: Option<Decide<AuditNotificationAuthorizationContext>>,
226    unconfirmed_audit_notification:
227        Option<Decide<UnconfirmedAuditNotificationAuthorizationContext>>,
228}
229
230impl FnPolicy {
231    /// A policy that decides as the defaults do, until given closures.
232    pub fn new() -> Self {
233        Self::default()
234    }
235
236    /// Decide mutations with `decide`; see [`ServerPolicy::authorize_mutation`].
237    pub fn mutation(
238        mut self,
239        decide: impl Fn(&MutationAuthorizationContext) -> bool + Send + Sync + 'static,
240    ) -> Self {
241        self.mutation = Some(Arc::new(decide));
242        self
243    }
244
245    /// Decide LifeSafetyOperation with `decide`; see
246    /// [`ServerPolicy::authorize_life_safety_operation`].
247    pub fn life_safety_operation(
248        mut self,
249        decide: impl Fn(&LifeSafetyOperationAuthorizationContext) -> bool + Send + Sync + 'static,
250    ) -> Self {
251        self.life_safety_operation = Some(Arc::new(decide));
252        self
253    }
254
255    /// Decide ConfirmedAuditNotification with `decide`; see
256    /// [`ServerPolicy::authorize_audit_notification`].
257    pub fn audit_notification(
258        mut self,
259        decide: impl Fn(&AuditNotificationAuthorizationContext) -> bool + Send + Sync + 'static,
260    ) -> Self {
261        self.audit_notification = Some(Arc::new(decide));
262        self
263    }
264
265    /// Decide UnconfirmedAuditNotification with `decide`; see
266    /// [`ServerPolicy::authorize_unconfirmed_audit_notification`].
267    pub fn unconfirmed_audit_notification(
268        mut self,
269        decide: impl Fn(&UnconfirmedAuditNotificationAuthorizationContext) -> bool
270            + Send
271            + Sync
272            + 'static,
273    ) -> Self {
274        self.unconfirmed_audit_notification = Some(Arc::new(decide));
275        self
276    }
277}
278
279impl<P: ServerPolicy + ?Sized> ServerPolicy for Arc<P> {
280    fn decides_mutations(&self) -> bool {
281        (**self).decides_mutations()
282    }
283    fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
284        (**self).authorize_mutation(context)
285    }
286    fn authorize_life_safety_operation(
287        &self,
288        context: &LifeSafetyOperationAuthorizationContext,
289    ) -> bool {
290        (**self).authorize_life_safety_operation(context)
291    }
292    fn authorize_audit_notification(
293        &self,
294        context: &AuditNotificationAuthorizationContext,
295    ) -> bool {
296        (**self).authorize_audit_notification(context)
297    }
298    fn authorize_unconfirmed_audit_notification(
299        &self,
300        context: &UnconfirmedAuditNotificationAuthorizationContext,
301    ) -> bool {
302        (**self).authorize_unconfirmed_audit_notification(context)
303    }
304}
305
306impl<P: ServerPolicy + ?Sized> ServerPolicy for Box<P> {
307    fn decides_mutations(&self) -> bool {
308        (**self).decides_mutations()
309    }
310    fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
311        (**self).authorize_mutation(context)
312    }
313    fn authorize_life_safety_operation(
314        &self,
315        context: &LifeSafetyOperationAuthorizationContext,
316    ) -> bool {
317        (**self).authorize_life_safety_operation(context)
318    }
319    fn authorize_audit_notification(
320        &self,
321        context: &AuditNotificationAuthorizationContext,
322    ) -> bool {
323        (**self).authorize_audit_notification(context)
324    }
325    fn authorize_unconfirmed_audit_notification(
326        &self,
327        context: &UnconfirmedAuditNotificationAuthorizationContext,
328    ) -> bool {
329        (**self).authorize_unconfirmed_audit_notification(context)
330    }
331}
332
333impl std::fmt::Debug for FnPolicy {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        f.debug_struct("FnPolicy")
336            .field("mutation", &self.mutation.is_some())
337            .field(
338                "life_safety_operation",
339                &self.life_safety_operation.is_some(),
340            )
341            .field("audit_notification", &self.audit_notification.is_some())
342            .field(
343                "unconfirmed_audit_notification",
344                &self.unconfirmed_audit_notification.is_some(),
345            )
346            .finish()
347    }
348}
349
350impl ServerPolicy for FnPolicy {
351    /// Only when it was given a mutation closure.
352    fn decides_mutations(&self) -> bool {
353        self.mutation.is_some()
354    }
355
356    fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
357        self.mutation.as_ref().is_none_or(|decide| decide(context))
358    }
359
360    fn authorize_life_safety_operation(
361        &self,
362        context: &LifeSafetyOperationAuthorizationContext,
363    ) -> bool {
364        self.life_safety_operation
365            .as_ref()
366            .is_some_and(|decide| decide(context))
367    }
368
369    fn authorize_audit_notification(
370        &self,
371        context: &AuditNotificationAuthorizationContext,
372    ) -> bool {
373        self.audit_notification
374            .as_ref()
375            .is_some_and(|decide| decide(context))
376    }
377
378    fn authorize_unconfirmed_audit_notification(
379        &self,
380        context: &UnconfirmedAuditNotificationAuthorizationContext,
381    ) -> bool {
382        self.unconfirmed_audit_notification
383            .as_ref()
384            .is_some_and(|decide| decide(context))
385    }
386}
387
388#[cfg(test)]
389#[path = "policy_tests.rs"]
390mod tests;