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//! DeviceCommunicationControl and both AuditNotification services. Each
6//! method has a documented default, so a policy overrides only what it
7//! decides.
8//!
9//! What stays outside it, and why:
10//! - [`MutationPolicy::DenyAll`](crate::mutation::MutationPolicy::DenyAll)
11//!   is a static hardening switch on [`ServerConfig`](crate::server::ServerConfig).
12//!   It wins over any policy, which isn't asked, and its denials are counted
13//!   apart.
14//! - DeviceCommunicationControl's password, static
15//!   [`DccPolicy`](crate::server::DccPolicy), source restriction and
16//!   disable rate limit are settings, not callbacks. The policy is asked
17//!   after the password, `DccPolicy` and source checks, and before the rate
18//!   limit is charged.
19//! - `on_reinitialize` carries out a restart rather than deciding on one,
20//!   and runs after the server's own checks.
21//! - `on_time_sync` observes a change it can't refuse.
22//! - Routing control belongs to the router (`bacnet-network`'s
23//!   `ControlAuthorizer`), not to the server.
24//!
25//! ## Who sent it
26//!
27//! Every context carries the claimed source addresses, the ingress
28//! [`TransportProvenance`], and
29//! from those a `principal()`: the [`ScPrincipal`] of the leaf certificate
30//! when the request came over a verified direct BACnet/SC connection from
31//! the peer that originated it, and `None` otherwise. B/IP, B/IPv6, MS/TP
32//! and Ethernet have no principal. Neither does a request a hub relays: the
33//! hub authenticated its own TLS client, and the origin's certificate never
34//! reaches this node, so the policy can't tell one hub member from another.
35//!
36//! A routed request (one with an SNET/SADR source) has no principal either,
37//! even over a direct connection: the connection authenticates the router
38//! that forwarded it, not the device behind it. `forwarding_peer_principal()`
39//! returns that router's principal, for a policy that trusts a router with
40//! everything it forwards. A source VMAC, UUID or routed SNET/SADR is a
41//! claim, never a principal.
42//!
43//! With `sc-tls`, every context's `certificate()` returns the verified leaf
44//! certificate that `principal()` fingerprints, under the same rule, and
45//! `forwarding_peer_certificate()` the forwarding peer's. Its metadata gives
46//! the subject, issuer and Subject Alternative Names, so a rule can follow a
47//! name across certificate renewal instead of listing old and new
48//! fingerprints. Allow on a name found rather than deny on one missing, and
49//! never log a certificate's contents.
50//!
51//! So `principal()` means the direct SC peer that sent the NPDU without an
52//! SNET. A router that doesn't follow the standard, or an application
53//! gateway that re-originates requests, shows its own principal for what it
54//! relays: trust a principal as far as everything its holder may relay.
55//!
56//! SC mutual TLS authenticates a connection; it doesn't authorize a
57//! service. Without a policy, any peer a CA admits may use every service
58//! the server's defaults allow.
59//!
60//! ## Calling rules
61//!
62//! Every method should be quick and free of side effects. For a received
63//! request the server calls them on Tokio's blocking pool with no object
64//! database guard held (#2032; each method says when it asks), so a slow
65//! policy delays only its own request, but a call that never returns keeps
66//! the process from exiting. Calls may run concurrently. Returning `false`, or
67//! panicking, denies, and the request changes nothing.
68//!
69//! ```
70//! use bacnet_server::mutation::MutationAuthorizationContext;
71//! use bacnet_server::ServerPolicy;
72//! use bacnet_transport::port::ScPrincipal;
73//!
74//! /// Writes only from the operator workstation's certificate.
75//! struct Site {
76//!     operator: ScPrincipal,
77//! }
78//!
79//! impl ServerPolicy for Site {
80//!     fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
81//!         context.principal() == Some(self.operator)
82//!     }
83//! }
84//! ```
85//!
86//! [`FnPolicy`] builds one from closures instead, keeping the default of
87//! every decision it isn't given:
88//!
89//! ```
90//! use bacnet_server::policy::FnPolicy;
91//! use bacnet_server::server::BACnetServer;
92//!
93//! // Remote writes from verified direct SC peers only; LifeSafetyOperation
94//! // from anyone. Audit notifications keep denying.
95//! let builder = BACnetServer::bip_builder().policy(
96//!     FnPolicy::new()
97//!         .mutation(|context| context.principal().is_some())
98//!         .life_safety_operation(|_| true),
99//! );
100//! ```
101
102use crate::audit_notification::{
103    AuditNotificationAuthorizationContext, UnconfirmedAuditNotificationAuthorizationContext,
104};
105use crate::device_communication_control::DeviceCommunicationControlAuthorizationContext;
106use crate::life_safety::LifeSafetyOperationAuthorizationContext;
107use crate::mutation::MutationAuthorizationContext;
108use bacnet_encoding::npdu::NpduAddress;
109pub use bacnet_transport::port::ScPrincipal;
110use bacnet_transport::port::TransportProvenance;
111#[cfg(feature = "sc-tls")]
112pub use bacnet_transport::sc_tls::{ScCertificateMetadata, ScPeerCertificate, ScSubjectAltName};
113use std::sync::Arc;
114
115/// The originating peer's principal: the direct-SC leaf, unless the request
116/// was routed, when the connection only authenticates the forwarding router.
117pub(crate) fn originating_principal(
118    provenance: &TransportProvenance,
119    source_network: Option<&NpduAddress>,
120) -> Option<ScPrincipal> {
121    source_network
122        .is_none()
123        .then(|| provenance.principal())
124        .flatten()
125}
126
127/// The originating peer's verified leaf certificate, under the rule
128/// [`originating_principal`] applies (#1965).
129#[cfg(feature = "sc-tls")]
130pub(crate) fn originating_certificate<'a>(
131    provenance: &'a TransportProvenance,
132    source_network: Option<&NpduAddress>,
133) -> Option<&'a ScPeerCertificate> {
134    source_network
135        .is_none()
136        .then(|| provenance.leaf_certificate())
137        .flatten()
138}
139
140/// The `certificate()` and `forwarding_peer_certificate()` accessors every
141/// policy context has, for a context with `provenance` and
142/// `source_network` fields. With `sc-tls` only: no other build has direct
143/// SC peers.
144macro_rules! certificate_accessors {
145    () => {
146        /// The verified leaf certificate of the peer that originated the
147        /// request: the one [`principal`](Self::principal) fingerprints, for
148        /// rules by subject, Subject Alternative Name or issuer, which
149        /// survive certificate renewal. `None` exactly when `principal()`
150        /// is: other ingress, hub-relayed and routed requests (see
151        /// [`crate::policy`]). Its contents are the peer's: don't log them.
152        #[cfg(feature = "sc-tls")]
153        pub fn certificate(&self) -> Option<&$crate::policy::ScPeerCertificate> {
154            $crate::policy::originating_certificate(&self.provenance, self.source_network.as_ref())
155        }
156
157        /// The verified leaf certificate of the direct BACnet/SC peer that
158        /// handed this request over, a router included when the request
159        /// was routed: the one
160        /// [`forwarding_peer_principal`](Self::forwarding_peer_principal)
161        /// fingerprints. `None` off a verified direct SC connection.
162        #[cfg(feature = "sc-tls")]
163        pub fn forwarding_peer_certificate(&self) -> Option<&$crate::policy::ScPeerCertificate> {
164            self.provenance.leaf_certificate()
165        }
166    };
167}
168pub(crate) use certificate_accessors;
169
170/// The server's authorization decisions, each with a documented default.
171///
172/// See the [module documentation](self) for what it covers, the principal
173/// each context carries and the calling rules. Install one with
174/// `policy(..)` on a server builder, or in
175/// [`ServerConfig::policy`](crate::server::ServerConfig::policy).
176///
177/// Without any policy installed, the server behaves as these defaults do,
178/// except that it allows a covered mutation without decoding it for
179/// authorization first; so does a policy whose
180/// [`decides_mutations`](Self::decides_mutations) is `false`.
181///
182/// `Arc<P>` and `Box<P>` are policies when `P` is, so one policy can be
183/// shared or boxed.
184pub trait ServerPolicy: Send + Sync + 'static {
185    /// Whether [`authorize_mutation`](Self::authorize_mutation) decides
186    /// anything (default: `true`).
187    ///
188    /// When `false`, the server never asks it: each covered mutation is
189    /// allowed without being decoded for authorization first, and counted as
190    /// allowed before the handler runs, exactly as with no policy. A policy
191    /// that only decides LifeSafetyOperation or Audit receipt returns `false`
192    /// so that mutations keep that path. The server may read it more than
193    /// once for one request, so it must give the same answer for the
194    /// policy's lifetime.
195    fn decides_mutations(&self) -> bool {
196        true
197    }
198
199    /// Whether to carry out one covered mutation (default: allow).
200    ///
201    /// Asked after DeviceCommunicationControl's checks, request admission,
202    /// service decoding and element validation, and before any database
203    /// change, COV or event work, or Audit record. A denial writes no Audit
204    /// record; it is counted in `BACnetServer::mutation_decision_counters`
205    /// and answered SERVICES / SERVICE_REQUEST_DENIED.
206    /// [`MutationPolicy::DenyAll`](crate::mutation::MutationPolicy::DenyAll)
207    /// denies without asking.
208    ///
209    /// WritePropertyMultiple asks once per decoded, validated element, in
210    /// wire order, up to the first it denies, all before the database is
211    /// write-locked and with no database guard held (#2032); the writes
212    /// follow under the lock. A denial, or a malformed later element, leaves
213    /// the elements before it written. Since every element is asked about
214    /// first, the policy can be asked about an element the request never
215    /// reaches, when an earlier one fails as it is written. An element whose
216    /// object appears between the decisions and the writes, so it was never
217    /// asked about, is denied. The other confirmed services ask once, for
218    /// the whole decoded request, also with no database guard held.
219    ///
220    /// Every call for a received request runs on Tokio's blocking pool while
221    /// the request waits (#2032), so a policy that blocks delays only that
222    /// request: it holds no runtime worker, and `BACnetServer::stop` ends
223    /// without waiting for it (the abandoned call finishes on its own and is
224    /// ignored). That costs one trip to the pool per request (a
225    /// WritePropertyMultiple's elements share one; WriteGroup makes one per
226    /// Channel), so keep the policy quick all the same. A call that never
227    /// returns keeps its blocking thread after `stop()`, and a Tokio runtime
228    /// waits for blocking tasks when it shuts down, so such a policy keeps
229    /// the process from exiting. The other `authorize_*` methods for a
230    /// received request run the same way.
231    ///
232    /// An inbound WriteGroup asks once per Channel it would write, with no
233    /// database guard held, no invoke ID and the unconfirmed service in the
234    /// context. A denial skips that Channel silently (nothing answers an
235    /// unconfirmed request) and writes no Audit record; the other Channels
236    /// are decided on their own (#1319).
237    fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
238        let _ = context;
239        true
240    }
241
242    /// Whether to carry out a LifeSafetyOperation (default: deny).
243    ///
244    /// Asked after the request is validated and its object found, with no
245    /// database guard held, on Tokio's blocking pool as
246    /// [`authorize_mutation`](Self::authorize_mutation) is (#2032). A denial
247    /// is answered SERVICES / SERVICE_REQUEST_DENIED before the object
248    /// changes.
249    fn authorize_life_safety_operation(
250        &self,
251        context: &LifeSafetyOperationAuthorizationContext,
252    ) -> bool {
253        let _ = context;
254        false
255    }
256
257    /// Whether to carry out a DeviceCommunicationControl request (default:
258    /// allow).
259    ///
260    /// Asked after decoding, the password, the deprecated DISABLE refusal,
261    /// the static [`DccPolicy`](crate::server::DccPolicy) and the source
262    /// restriction; a request any of them refuses never reaches the policy.
263    /// For DISABLE_INITIATION the disable rate limit is charged only after
264    /// the policy allows, so a denied requester spends none of that shared
265    /// budget. Asked with no lock held, on Tokio's blocking pool as
266    /// [`authorize_mutation`](Self::authorize_mutation) is (#2032), before
267    /// the timer is replaced or the state changes. A denial is answered SERVICES /
268    /// SERVICE_REQUEST_DENIED, changes neither the state nor a running
269    /// timer, writes no Audit record and is counted in
270    /// `BACnetServer::dcc_outcome_counters` as `policy_denied_total`, which
271    /// also counts the static source and rate-limit refusals.
272    fn authorize_device_communication_control(
273        &self,
274        context: &DeviceCommunicationControlAuthorizationContext,
275    ) -> bool {
276        let _ = context;
277        true
278    }
279
280    /// Whether to store a ConfirmedAuditNotification in the configured
281    /// Audit Log sink (default: deny).
282    ///
283    /// Asked after the request is decoded and checked against completed
284    /// receipts, with no database guard held, on Tokio's blocking pool as
285    /// [`authorize_mutation`](Self::authorize_mutation) is (#2032). A denial
286    /// is answered SERVICES / SERVICE_REQUEST_DENIED and stores nothing.
287    fn authorize_audit_notification(
288        &self,
289        context: &AuditNotificationAuthorizationContext,
290    ) -> bool {
291        let _ = context;
292        false
293    }
294
295    /// Whether to store an UnconfirmedAuditNotification in the configured
296    /// Audit Log sink (default: deny).
297    ///
298    /// Asked after the request is decoded, with no database guard held, on
299    /// Tokio's blocking pool as
300    /// [`authorize_mutation`](Self::authorize_mutation) is (#2032). A denial
301    /// drops the request silently and stores nothing.
302    fn authorize_unconfirmed_audit_notification(
303        &self,
304        context: &UnconfirmedAuditNotificationAuthorizationContext,
305    ) -> bool {
306        let _ = context;
307        false
308    }
309}
310
311type Decide<C> = Arc<dyn Fn(&C) -> bool + Send + Sync>;
312
313/// A [`ServerPolicy`] made of closures, one per decision. A decision with no
314/// closure keeps the trait's default; with no mutation closure it doesn't
315/// decide mutations at all ([`ServerPolicy::decides_mutations`]).
316///
317/// Each closure follows the calling rules of the method it stands for.
318#[derive(Clone, Default)]
319pub struct FnPolicy {
320    mutation: Option<Decide<MutationAuthorizationContext>>,
321    life_safety_operation: Option<Decide<LifeSafetyOperationAuthorizationContext>>,
322    device_communication_control: Option<Decide<DeviceCommunicationControlAuthorizationContext>>,
323    audit_notification: Option<Decide<AuditNotificationAuthorizationContext>>,
324    unconfirmed_audit_notification:
325        Option<Decide<UnconfirmedAuditNotificationAuthorizationContext>>,
326}
327
328impl FnPolicy {
329    /// A policy that decides as the defaults do, until given closures.
330    pub fn new() -> Self {
331        Self::default()
332    }
333
334    /// Decide mutations with `decide`; see [`ServerPolicy::authorize_mutation`].
335    pub fn mutation(
336        mut self,
337        decide: impl Fn(&MutationAuthorizationContext) -> bool + Send + Sync + 'static,
338    ) -> Self {
339        self.mutation = Some(Arc::new(decide));
340        self
341    }
342
343    /// Decide LifeSafetyOperation with `decide`; see
344    /// [`ServerPolicy::authorize_life_safety_operation`].
345    pub fn life_safety_operation(
346        mut self,
347        decide: impl Fn(&LifeSafetyOperationAuthorizationContext) -> bool + Send + Sync + 'static,
348    ) -> Self {
349        self.life_safety_operation = Some(Arc::new(decide));
350        self
351    }
352
353    /// Decide DeviceCommunicationControl with `decide`; see
354    /// [`ServerPolicy::authorize_device_communication_control`].
355    pub fn device_communication_control(
356        mut self,
357        decide: impl Fn(&DeviceCommunicationControlAuthorizationContext) -> bool + Send + Sync + 'static,
358    ) -> Self {
359        self.device_communication_control = Some(Arc::new(decide));
360        self
361    }
362
363    /// Decide ConfirmedAuditNotification with `decide`; see
364    /// [`ServerPolicy::authorize_audit_notification`].
365    pub fn audit_notification(
366        mut self,
367        decide: impl Fn(&AuditNotificationAuthorizationContext) -> bool + Send + Sync + 'static,
368    ) -> Self {
369        self.audit_notification = Some(Arc::new(decide));
370        self
371    }
372
373    /// Decide UnconfirmedAuditNotification with `decide`; see
374    /// [`ServerPolicy::authorize_unconfirmed_audit_notification`].
375    pub fn unconfirmed_audit_notification(
376        mut self,
377        decide: impl Fn(&UnconfirmedAuditNotificationAuthorizationContext) -> bool
378        + Send
379        + Sync
380        + 'static,
381    ) -> Self {
382        self.unconfirmed_audit_notification = Some(Arc::new(decide));
383        self
384    }
385}
386
387impl<P: ServerPolicy + ?Sized> ServerPolicy for Arc<P> {
388    fn decides_mutations(&self) -> bool {
389        (**self).decides_mutations()
390    }
391    fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
392        (**self).authorize_mutation(context)
393    }
394    fn authorize_life_safety_operation(
395        &self,
396        context: &LifeSafetyOperationAuthorizationContext,
397    ) -> bool {
398        (**self).authorize_life_safety_operation(context)
399    }
400    fn authorize_device_communication_control(
401        &self,
402        context: &DeviceCommunicationControlAuthorizationContext,
403    ) -> bool {
404        (**self).authorize_device_communication_control(context)
405    }
406    fn authorize_audit_notification(
407        &self,
408        context: &AuditNotificationAuthorizationContext,
409    ) -> bool {
410        (**self).authorize_audit_notification(context)
411    }
412    fn authorize_unconfirmed_audit_notification(
413        &self,
414        context: &UnconfirmedAuditNotificationAuthorizationContext,
415    ) -> bool {
416        (**self).authorize_unconfirmed_audit_notification(context)
417    }
418}
419
420impl<P: ServerPolicy + ?Sized> ServerPolicy for Box<P> {
421    fn decides_mutations(&self) -> bool {
422        (**self).decides_mutations()
423    }
424    fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
425        (**self).authorize_mutation(context)
426    }
427    fn authorize_life_safety_operation(
428        &self,
429        context: &LifeSafetyOperationAuthorizationContext,
430    ) -> bool {
431        (**self).authorize_life_safety_operation(context)
432    }
433    fn authorize_device_communication_control(
434        &self,
435        context: &DeviceCommunicationControlAuthorizationContext,
436    ) -> bool {
437        (**self).authorize_device_communication_control(context)
438    }
439    fn authorize_audit_notification(
440        &self,
441        context: &AuditNotificationAuthorizationContext,
442    ) -> bool {
443        (**self).authorize_audit_notification(context)
444    }
445    fn authorize_unconfirmed_audit_notification(
446        &self,
447        context: &UnconfirmedAuditNotificationAuthorizationContext,
448    ) -> bool {
449        (**self).authorize_unconfirmed_audit_notification(context)
450    }
451}
452
453impl std::fmt::Debug for FnPolicy {
454    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
455        f.debug_struct("FnPolicy")
456            .field("mutation", &self.mutation.is_some())
457            .field(
458                "life_safety_operation",
459                &self.life_safety_operation.is_some(),
460            )
461            .field(
462                "device_communication_control",
463                &self.device_communication_control.is_some(),
464            )
465            .field("audit_notification", &self.audit_notification.is_some())
466            .field(
467                "unconfirmed_audit_notification",
468                &self.unconfirmed_audit_notification.is_some(),
469            )
470            .finish()
471    }
472}
473
474impl ServerPolicy for FnPolicy {
475    /// Only when it was given a mutation closure.
476    fn decides_mutations(&self) -> bool {
477        self.mutation.is_some()
478    }
479
480    fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
481        self.mutation.as_ref().is_none_or(|decide| decide(context))
482    }
483
484    fn authorize_life_safety_operation(
485        &self,
486        context: &LifeSafetyOperationAuthorizationContext,
487    ) -> bool {
488        self.life_safety_operation
489            .as_ref()
490            .is_some_and(|decide| decide(context))
491    }
492
493    fn authorize_device_communication_control(
494        &self,
495        context: &DeviceCommunicationControlAuthorizationContext,
496    ) -> bool {
497        self.device_communication_control
498            .as_ref()
499            .is_none_or(|decide| decide(context))
500    }
501
502    fn authorize_audit_notification(
503        &self,
504        context: &AuditNotificationAuthorizationContext,
505    ) -> bool {
506        self.audit_notification
507            .as_ref()
508            .is_some_and(|decide| decide(context))
509    }
510
511    fn authorize_unconfirmed_audit_notification(
512        &self,
513        context: &UnconfirmedAuditNotificationAuthorizationContext,
514    ) -> bool {
515        self.unconfirmed_audit_notification
516            .as_ref()
517            .is_some_and(|decide| decide(context))
518    }
519}
520
521#[cfg(test)]
522#[path = "policy_tests.rs"]
523mod tests;