Add TLS Support#533
Merged
Merged
Conversation
Add --enable-tls build option (OpenSSL >= 3.5), client-side TLS config parsing, and TLS transport to the audisp-remote plugin. The transport uses TLS 1.3 with X25519MLKEM768 hybrid key exchange for post-quantum confidentiality, with classical X25519 fallback when PQC groups are unavailable. The tls_require_pqc option enables fail-closed PQC enforcement via an allowlist in common/common.h. Both PSK and certificate-based authentication are supported. Server certificate verification is gated on tls_ca_file presence, with hostname/IP-aware SNI handling per RFC 6066. Session resumption and 0-RTT are disabled to force fresh key exchange per connection. Shared TLS helpers (is_pqc_group, tls_validate_key_file, tls_load_psk) are placed in common/common.h with a log callback to avoid code duplication with the server side. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add server-side TLS config parsing and transport to auditd for receiving audit events over encrypted connections. Mirrors the client-side TLS implementation with the same crypto defaults: TLS 1.3 minimum, X25519MLKEM768 hybrid key exchange, session resumption disabled. Adds tls_client_auth for optional or required mutual TLS with client certificates. PSK identity comparison uses CRYPTO_memcmp. Identity logging is sanitized to ASCII printable range. TLS config strings are freed during SIGHUP reconfigure to prevent leaks. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add test-tls.sh covering PSK and certificate handshakes, PQC key exchange negotiation, and binary linkage checks. Hardened with set -euo pipefail and dynamic port allocation. Document all TLS config options in both man pages, including PQC posture differences between PSK and certificate modes, certificate chain support, and SIGHUP reload limitations. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
The blocking SSL_accept held the single-threaded libev event loop for up to 5 seconds per connection, allowing a slow or malicious client to stall audit event processing for all connected clients. Replace it with a non-blocking state machine driven by ev_io and ev_timer callbacks. Pre-handshake clients live in a separate chain with a concurrency limit to prevent connection flooding. Per-address counting walks both chains so a single IP cannot exhaust the global handshake pool. Also fixes a config pointer scope bug where tls_require_pqc referenced an out-of-scope variable in the accept handler. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Move role-neutral TLS helper functions from common/common.h static inlines into a dedicated autls/ internal library (libautls.la). This keeps the OpenSSL dependency out of libaucommon consumers and gives the TLS code a clear ownership boundary. The library contains three source files: - autls-psk.c: key file validation and PSK loading - autls-io.c: deadline computation, TLS write, and TLS shutdown - autls-profile.c: PQC group classification and cipher selection All functions are renamed from tls_* to autls_* and constants from TLS_*_TIMEOUT_MS to AUTLS_*_TIMEOUT_MS. Consumers (auditd, audisp-remote, test-tls-helpers) now link libautls.la instead of raw $(OPENSSL_LIBS). Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add __nonnull, __wur, __attr_access, and __attribute_pure__ to the autls public API using the existing gcc-attributes.h wrappers. This enables compile-time detection of NULL pointer misuse, ignored return values, and buffer access violations. autls_is_pqc_group intentionally omits __nonnull because it accepts NULL input (returns 0). Signed-off-by: Sergio Correia <scorreia@redhat.com>
Raise the minimum OpenSSL version from 1.1.1 to 3.0 to match the APIs actually used (SSL_group_to_name, SSL_get_negotiated_group). Add function probes for the TLS 1.3 APIs the implementation depends on: SSL_CTX_set_ciphersuites, SSL_CTX_set_psk_find_session_callback, SSL_CTX_set_psk_use_session_callback, and SSL_get_negotiated_group. Missing required APIs now fail at configure time instead of at link time with undefined symbol errors. SSL_group_to_name is probed as optional and guarded with HAVE_SSL_GROUP_TO_NAME at call sites so the code degrades gracefully on older OpenSSL 3.x builds. Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add autls_validate_psk_identity() to enforce printable ASCII (0x21-0x7E) for PSK identities, and autls_profile_ciphers() / autls_profile_groups() to map crypto profile selections to OpenSSL cipher and group strings. These are the shared building blocks for Phase 0's policy surface (tls_crypto_profile) and identity authorization. The profile functions accept an integer profile value and return the appropriate TLS 1.3 ciphersuite or key exchange group string, with FIPS deferring to system crypto policy via NULL. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add the Phase 0 policy surface to both auditd and audisp-remote config parsers: tls_auth (accepts "psk" only for tech preview) and tls_crypto_profile (standard, fips, pqc). The tls_require_pqc option becomes a one-directional compatibility alias: "yes" sets tls_crypto_profile=pqc, "no" is a no-op that does not modify the profile. Key changes: - Profile enum defined in auditd-config.h (not autls.h) so the shared config parser can use it without linking libautls - Sanity check enforces tls_auth=psk requires tls_psk_file, rejecting cert-only configs under the PSK-only tech preview - tls_client_auth default changed to TCA_NONE since PSK mode does not use certificate-based client authentication Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Replace hard-coded cipher suite and key exchange group defaults with autls_profile_ciphers() and autls_profile_groups(), driven by the new tls_crypto_profile setting. When a profile function returns NULL (FIPS), the corresponding SSL_CTX call is skipped entirely, deferring to system crypto policy. The STANDARD profile preserves the existing fallback from PQC hybrid to X25519 when ML-KEM groups are unavailable. PQC and FIPS profiles fail closed with no fallback. The post-handshake PQC group check now uses the profile enum (== TLS_PROFILE_PQC) instead of the legacy tls_require_pqc flag. A _Static_assert guards the profile constant values between autls.h and the config headers. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Remove the silent fallback to "audit-client" as the default PSK identity in audisp-remote. The identity is now required and validated at TLS context init time via autls_validate_psk_identity(), which rejects non-printable-ASCII characters and overlength strings. The same validation is added to the collector side in init_tls_server_context() for the expected_psk_identity. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add autls_acl_load(), autls_acl_check(), and autls_acl_free() for parsing the collector-side client authorization file. The file format is one entry per line: identity, status (enabled/disabled), and optional notes. The parser validates file permissions (root-owned, not group or world writable, regular file, opened with O_NOFOLLOW), validates each identity via autls_validate_psk_identity(), rejects duplicate identities and unknown status values, and uses case-insensitive status matching. Identity lookup uses length-bounded comparison (strlen + memcmp) to safely handle the non-NUL-terminated buffers from OpenSSL's PSK callback. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add the tls_allowed_clients config option and integrate ACL-based authorization into the collector's TLS PSK callback. Config changes: - Add tls_allowed_clients path field to daemon_conf - Relax sanity_check to accept tls_psk_identity OR tls_allowed_clients (at least one required with PSK) - Add parser, default, free, and stub PSK callback authorization: - Validate incoming identity with autls_validate_psk_identity() before any authorization check - When ACL is loaded, check identity against the ACL table; reject unknown and disabled identities with stable reasons - When no ACL, fall back to expected_psk_identity comparison - Store accepted identity in SSL ex-data (strndup'd) for transfer to ev_tcp after handshake Identity lifecycle: - Ex-data indices registered without free callbacks (manual cleanup on all paths to prevent double-free) - Identity transferred from ex-data to client->accepted_identity after PQC check passes (so PQC failures go through abort path) - abort_handshake retrieves ex-data before SSL_free - release_client frees accepted_identity - uninit frees the ACL table Single-PSK constraint enforced at init: at most one enabled identity when tls_psk_file is set. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Replace the simple addr/port/res audit records with structured TLS establishment records containing: op=tls-accept res=<success|no> role=collector addr=<> port=<> id=<identity> profile=<standard|fips|pqc> tls_version=<> cipher=<> group=<> auth=psk reason=<stable reason> Add emit_tls_audit_record() helper with a 512-byte buffer and critical fields (op, res) placed first to survive truncation. Identity is truncated to 128 chars via %.128s. The abort_handshake() path now retrieves the failure reason from SSL ex-data (set by the PSK callback) and falls back to the op parameter when ex-data is NULL. This wires up the stable failure reasons: unknown-identity, disabled-identity, invalid-identity, handshake-timeout, unsupported-profile, openssl-error. Also adds a LOG_WARNING when strndup fails for the accepted identity so operators know the connection lacks attribution. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add tls_auth, tls_crypto_profile, and tls_allowed_clients to the TLS field copy block in auditd_tcp_listen_reconfigure(). The path string ownership is transferred from nconf to oconf (matching the pattern for all other TLS string fields), while the live ACL table and TLS context remain untouched -- TLS changes require a restart. Update the NOTES section in auditd.conf(5) to list all TLS options that require restart. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Document the Phase 0 TLS PSK tech preview configuration surface: - tls_auth (psk only for tech preview) - tls_crypto_profile (standard, fips, pqc) - tls_allowed_clients (ACL file format, permissions, single-PSK constraint) - tls_require_pqc updated as compatibility alias - tls_client_auth default updated to none (PSK mode) Sample configs reorganized to lead with PSK options and profile selection, with certificate mode options grouped separately and marked as not supported in this tech preview. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Extend test-tls-helpers with six new test functions covering: - autls_validate_psk_identity: empty, NUL bytes, control chars, space, DEL, high bytes (0x80-0xFF rejected), max/overlength, valid edge cases including boundary chars 0x21 and 0x7E - autls_profile_ciphers: standard/pqc return defaults, fips NULL - autls_profile_groups: standard/pqc/fips return correct strings - autls_acl_load: valid file, empty file, duplicates, invalid status, missing status, case-insensitive status, group-writable file rejected - autls_acl_check: enabled/disabled/unknown returns, empty identity, prefix mismatch (length-bounded comparison) - autls_authorize_psk_identity: composed validation+ACL path, catches if(rc) vs if(rc==1) bug class Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add tests to auditd_config_alloc_test for the new TLS config parsers, covering: - tls_auth_parser: accepts "psk", rejects unknown values - tls_crypto_profile_parser: accepts standard/fips/pqc, rejects unknown - tls_require_pqc compatibility alias: "yes" sets both tls_require_pqc and tls_crypto_profile to PQC, "no" is a no-op for the profile, and explicit tls_crypto_profile after "yes" wins (last-writer-wins sequential parsing) Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
The client_ack TLS path called autls_ssl_write() which blocks the single-threaded event loop for up to 100ms per client via poll(). With multiple stalled clients, this freezes the loop. Replace with a non-blocking SSL_write that queues unsent data in a per-client pending_ack buffer and arms EV_WRITE. The pending write is drained on the next event loop iteration before reading new data. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Pre-compute and store identity_len in autls_acl_entry at load time. This avoids re-computing strlen on every autls_acl_check call and provides a cheap early-out on length mismatch before the constant-time comparison. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Test the server-side sanity_check TLS validation: cert/key pairing, PSK/cert mutual exclusion, tls_auth=psk requires PSK file, credential requirement, PSK identity/ACL requirement, tls_require_pqc consistency, and valid minimal PSK config. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
- Eliminate TOCTOU in private key loading: add autls_load_key_file() using open(O_NOFOLLOW) + fstat + BIO, replacing the separate autls_validate_key_file + SSL_CTX_use_PrivateKey_file sequence - Use validate-before-free pattern in remote-config.c TLS parsers to avoid dangling pointer on validation failure - Use replace_string() in auditd-config.c TLS parsers for atomic strdup-before-free - Add logging to client PSK callback error paths - Send non-blocking close_notify in release_client to avoid stalling the event loop - Move TLS resource cleanup after client goodbye loop in shutdown - Add missing oconf->transport update in TLS reconfigure branch - Distinguish I/O errors from empty file in autls_load_psk via ferror() check Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add libautls.la to auditd_DEPENDENCIES so the link step has an explicit dependency on the convenience library, not just implicit SUBDIRS ordering. Move AC_SUBST for OPENSSL_CFLAGS/OPENSSL_LIBS outside the TLS conditional so autls/Makefile always gets proper substitution even when configured without --enable-tls. Preserve user-supplied OPENSSL_LIBS instead of unconditionally overwriting with -lssl -lcrypto. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
SIGCHLD is registered with sa_flags=0, so delivery interrupts blocking syscalls inside SSL_read/SSL_write. OpenSSL surfaces the resulting EINTR as SSL_ERROR_SYSCALL, which the TLS I/O paths treat as fatal — triggering a full reconnection cycle. Add SA_RESTART so SIGCHLD-interrupted syscalls are transparently restarted by the kernel. Also save and restore errno in the handler — waitpid sets errno to ECHILD when no children remain, which can clobber errno between completed syscalls and their error checks (e.g., in ar_read where errno == 0 gates the reconnection path). The other signal handlers (SIGHUP, SIGUSR1/2) intentionally lack SA_RESTART because they need to interrupt select() to trigger state changes. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Set SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER on the client TLS context to match the server-side setting. This is defensive — the current autls_ssl_write() does not move the buffer pointer between retries, but the flag ensures correctness if the write path is refactored. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
The TLS sanity_check unit test created configs with clear_config(), but clear_config() leaves space_left and admin_space_left at zero. The shared sanity_check() rejects that generic disk-space invariant before the test can exercise the TLS-specific constraints, so the supposedly valid TLS case aborted in make check. Add a small test helper that starts from clear_config() and satisfies the non-TLS disk-space invariant. Use it for the TLS sanity_check cases so those assertions cover the intended TLS credential/profile checks.
The Phase 0 TLS profile names exposed standard, fips, and pqc. The fips name implied that auditd was asserting FIPS enforcement even though the implementation intentionally deferred cipher and group selection to OpenSSL and the system crypto policy. The standard name also did not describe the compatibility-oriented default clearly. Rename the user-facing profile values to compatible, system, and pqc, and rename the matching internal constants. The system profile preserves the existing delegation behavior by leaving cipher suites and key exchange groups unset so OpenSSL and system crypto-policy choose them. The documentation now tells FIPS deployments to configure the host for FIPS mode and use the system profile. Updated the parser tests, autls profile helper tests, sample configs, and man pages for the new names.
Phase 0 TLS was still relying on the TLS-specific DAEMON_ACCEPT diagnostics for crypto session visibility. The sender did not retain CAP_AUDIT_WRITE after dropping privileges, and neither side emitted CRYPTO_SESSION/CRYPTO_KEY_USER records in the field order used by other userspace crypto audit producers. Add shared autls formatters for the CRYPTO_SESSION and CRYPTO_KEY_USER bodies, use audit_log_user_message() from audisp-remote, and retain CAP_AUDIT_WRITE for TLS transport. The collector emits internal CRYPTO_SESSION records with send_audit_event() only after successful TLS handshake and identity authorization, formats the exe field through libaudit's name/value encoder using the configured auditd install path, then closes just the new connection if those records cannot be created. Failed collector handshakes deliberately remain DAEMON_ACCEPT diagnostics only, with comments documenting the audit-flood concern from unauthenticated peers. Tests cover the shared crypto audit body formatting and truncation behavior.
SIGHUP reconfiguration updated tls_allowed_clients in the active config before the replacement ACL had been parsed and validated. A bad reload could therefore leave auditd reporting the new ACL path while continuing to use the previous in-memory ACL, and removing tls_allowed_clients could clear the only active PSK authorization source without rebuilding the live TLS context. Load replacement ACLs into a temporary table and only publish the new path and table together after validation succeeds. Failed reloads and single-PSK ACL cardinality errors now keep the old live ACL, and ACL removal is rejected when the live PSK listener has no tls_psk_identity fallback. The new path string is freed when it is not published so the existing reconfigure ownership model does not leak it. Add listener reconfiguration coverage to format_event_test for invalid ACL replacement, ACL-only removal on a live PSK listener, and successful ACL removal when a fallback identity is active.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See individual commits