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;