Skip to main content

Module policy

Module policy 

Source
Expand description

One place for the server’s authorization decisions (#524).

ServerPolicy answers every “may this peer do this?” question the server asks of an application: the covered mutations, LifeSafetyOperation, DeviceCommunicationControl and both AuditNotification services. Each method has a documented default, so a policy overrides only what it decides.

What stays outside it, and why:

  • MutationPolicy::DenyAll is a static hardening switch on ServerConfig. It wins over any policy, which isn’t asked, and its denials are counted apart.
  • DeviceCommunicationControl’s password, static DccPolicy, source restriction and disable rate limit are settings, not callbacks. The policy is asked after the password, DccPolicy and source checks, and before the rate limit is charged.
  • on_reinitialize carries out a restart rather than deciding on one, and runs after the server’s own checks.
  • on_time_sync observes a change it can’t refuse.
  • Routing control belongs to the router (bacnet-network’s ControlAuthorizer), not to the server.

§Who sent it

Every context carries the claimed source addresses, the ingress TransportProvenance, and from those a principal(): the ScPrincipal of the leaf certificate when the request came over a verified direct BACnet/SC connection from the peer that originated it, and None otherwise. B/IP, B/IPv6, MS/TP and Ethernet have no principal. Neither does a request a hub relays: the hub authenticated its own TLS client, and the origin’s certificate never reaches this node, so the policy can’t tell one hub member from another.

A routed request (one with an SNET/SADR source) has no principal either, even over a direct connection: the connection authenticates the router that forwarded it, not the device behind it. forwarding_peer_principal() returns that router’s principal, for a policy that trusts a router with everything it forwards. A source VMAC, UUID or routed SNET/SADR is a claim, never a principal.

With sc-tls, every context’s certificate() returns the verified leaf certificate that principal() fingerprints, under the same rule, and forwarding_peer_certificate() the forwarding peer’s. Its metadata gives the subject, issuer and Subject Alternative Names, so a rule can follow a name across certificate renewal instead of listing old and new fingerprints. Allow on a name found rather than deny on one missing, and never log a certificate’s contents.

So principal() means the direct SC peer that sent the NPDU without an SNET. A router that doesn’t follow the standard, or an application gateway that re-originates requests, shows its own principal for what it relays: trust a principal as far as everything its holder may relay.

SC mutual TLS authenticates a connection; it doesn’t authorize a service. Without a policy, any peer a CA admits may use every service the server’s defaults allow.

§Calling rules

Every method should be quick and free of side effects. For a received request the server calls them on Tokio’s blocking pool with no object database guard held (#2032; each method says when it asks), so a slow policy delays only its own request, but a call that never returns keeps the process from exiting. Calls may run concurrently. Returning false, or panicking, denies, and the request changes nothing.

use bacnet_server::mutation::MutationAuthorizationContext;
use bacnet_server::ServerPolicy;
use bacnet_transport::port::ScPrincipal;

/// Writes only from the operator workstation's certificate.
struct Site {
    operator: ScPrincipal,
}

impl ServerPolicy for Site {
    fn authorize_mutation(&self, context: &MutationAuthorizationContext) -> bool {
        context.principal() == Some(self.operator)
    }
}

FnPolicy builds one from closures instead, keeping the default of every decision it isn’t given:

use bacnet_server::policy::FnPolicy;
use bacnet_server::server::BACnetServer;

// Remote writes from verified direct SC peers only; LifeSafetyOperation
// from anyone. Audit notifications keep denying.
let builder = BACnetServer::bip_builder().policy(
    FnPolicy::new()
        .mutation(|context| context.principal().is_some())
        .life_safety_operation(|_| true),
);

Structs§

FnPolicy
A ServerPolicy made of closures, one per decision. A decision with no closure keeps the trait’s default; with no mutation closure it doesn’t decide mutations at all (ServerPolicy::decides_mutations).
ScCertificateMetadatasc-tls
Bounded display metadata. Parsing is informational, never verification.
ScPeerCertificatesc-tls
One original DER certificate with optional display interpretation. A parser failure preserves the DER and does not change TLS acceptance.
ScPrincipal
Who sent a request, as far as this node can prove it: the leaf certificate of a verified direct BACnet/SC connection.

Enums§

ScSubjectAltNamesc-tls
One Subject Alternative Name of a certificate, as written in it.

Traits§

ServerPolicy
The server’s authorization decisions, each with a documented default.