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;