Skip to content

3.0

Choose a tag to compare

@nicholasamorim nicholasamorim released this 02 Jun 10:30
· 6 commits to master since this release

If you upgrade from 2.5 without code changes, expect:

  1. UDP servers will drop Access-Request packets that don't carry Message-Authenticator. Pass require_message_authenticator=False to restore the old default.
  2. UDP clients will refuse Access-Accept / Reject / Challenge replies missing Message-Authenticator. Pass enforce_ma=False.
  3. Server will reject packets whose Request Authenticator fails MD5 verification. Pass enable_pkt_verify=False.
  4. RadSecServer / RadSecClient won't accept TLS 1.2 connections unless you pass minimum_tls_version=ssl.TLSVersion.TLSv1_2.
  5. Existing log pipelines watching for [pyrad2 trace] lines on PYRAD2_TRACE=1 will go silent until PYRAD2_TRACE_UNSAFE=1 is also set.

This is a security and defaults overhaul. Every UDP server / client in pyrad2 now ships with BlastRADIUS-safe defaults out of the box, RadSec deployments default to TLS 1.3, and the sync server now verifies request authenticators before invoking your handlers , matching the long-standing ServerAsync behaviour. All of these are observable behaviour breaks for existing deployments; the section below documents each opt-out.

Security defaults (BREAKING)

  • BlastRADIUS (CVE-2024-3596) mitigated by default. Server, ServerAsync, Client, and ClientAsync now default to enforcing Message-Authenticator on every Access-Request and on the matching Access-Accept / Reject / Challenge reply. To bridge a legacy NAS that doesn't emit the attribute, pass require_message_authenticator=False (servers) or enforce_ma=False (clients).
  • Sync Server now verifies request authenticators by default. Mirrors ServerAsync.enable_pkt_verify which previously was the only side that ran the check. Pass enable_pkt_verify=False to opt out for legacy NASes that emit malformed authenticators.
  • RadSec defaults to TLS 1.3. RadSecServer.DEFAULT_MINIMUM_TLS_VERSION and RadSecClient.DEFAULT_MINIMUM_TLS_VERSION are now ssl.TLSVersion.TLSv1_3 (RFC 9325 deprecates TLS 1.1-, treats 1.2 as legacy; RFC 9750 mandates 1.3 for RADIUS/1.1). Pass minimum_tls_version=ssl.TLSVersion.TLSv1_2 to bridge a legacy peer that can't yet negotiate 1.3. The floor is auto-promoted to 1.3 when radius_versions includes V1_1, regardless.
  • Constant-time MAC and MD5 comparisons across the verify path. All switched from == to hmac.compare_digest, closing a timing-side-channel that let an off-path attacker probe valid MACs one byte at a time.
  • Zero-authenticator Access-Request rejected. AuthPacket.verify_auth_request now rejects v1.0 Access-Requests whose Request Authenticator is all-zero. Packet.salt_crypt no longer falls back to a zero authenticator when one is missing , it calls create_authenticator() so salt-encrypted attributes (Tunnel-Password, MS-MPPE keys) can't be recovered without the shared secret.
  • PYRAD2_TRACE now requires a second-step acknowledgement. Setting PYRAD2_TRACE=1 no longer activates the wire trace on its own. The trace dumps the Request Authenticator and obfuscated User-Password bytes; with the shared secret known (commonly to anyone reading the log archive), the plaintext is fully recoverable. Set PYRAD2_TRACE_UNSAFE=1 alongside to acknowledge and enable. A warning is logged on activation.

Security hardening

  • $INCLUDE sandboxing. Dictionary / DictFile now confine $INCLUDE resolution to a base directory (defaults to the entry-point file's parent; configurable via include_base_dir=). A dictionary with $INCLUDE /etc/passwd or $INCLUDE ../../foo raises ParseError. The entry-point file is exempt , it defines the base.
  • ParseError signature tightened. The old **data swallowsilently dropped name=, path=, and similar typos. The new signature explicitly accepts file= and line=; the filename now consistently surfaces in error messages.
  • Vendor-id range check. Dictionary rejects VENDOR declarations outside 0..0xFFFFFF (RFC 2865 §5.26 SMI PEN range) instead of silently passing through values that later corrupt the VSA encoder.
  • eap.password_from_packet no longer falls back to User-Name. Returning User-Name as the EAP-MD5 secret silently mis-keyed the challenge; any observer who saw the request could reproduce the digest. Raises PacketError when User-Password is absent.
  • pw_decrypt warns on shared-secret mismatch. Non-UTF-8 bytes after de-obfuscation almost always indicate the receiver's secret doesn't match the sender's. The function now logs a WARNING once per call and continues with a lossy decode so legacy handlers don't crash.
  • Dedup cache failed-handler behaviour documented. A handler that exits without sending a reply still drops its in-flight marker, so a retransmission within the TTL window is processed fresh rather than suppressed , bug acknowledged.

Architecture

  • New pyrad2.router.RequestRouter unifies the dispatch state machine that Server and ServerAsync previously each owned separately.
  • Packet subclass deduplication.
  • Cross-client factory deduplication.

Per-transport identifier management

  • DatagramProtocolClient.create_id scans for a free slot instead of blindly returning (packet_id + 1) % 256. Raises a new typed IdentifierExhausted (in pyrad2.exceptions) when all 256 slots on a single (source IP, source port) flow are in flight. The bare-Exception collision error in send_packet is now also typed.
  • Module-level CURRENT_ID is thread-safe. A threading.Lock serialises the increment so concurrent Packet() construction across threads can't read+write the same counter and end up with colliding ids.

Performance

  • RFC 2865 keystream loops rewritten for _salt_en_decrypt, pw_crypt, and pw_decrypt. The byte-at-a-time bytes((hash[i] ^ buf[i],)) concat chain is replaced by an int-XOR + bytearray accumulator. ~3× faster on User-Password and Tunnel-Password sized inputs.
  • Asyncio deprecation cleanup.

DX / quality

  • Sync Client._send_packet no longer mutates the caller's Acct-Delay-Time. The retry loop still bumps the value while a request is in flight, but it now snapshots the caller's original list (or absence) and restores it on exit , so reusing the same AcctPacket across multiple send_packet calls no longer compounds the delay.
  • getaddrinfo fixes.
  • Test suite migrated to pytest-style.
  • Packaging metadata consolidated. setup.py and setup.cfg
    deleted. pyproject.toml carries the full PEP 639 license = "BSD-3-Clause" + license-files = ["LICENSE.txt"], full
    classifiers, dynamic = ["version"] reading from
    pyrad2/__init__.py, and [build-system] with hatchling.
  • Documentation tooling no longer ships to runtime users.