Skip to content

Conversation

@chucklever
Copy link
Member

This series introduces TLS session tagging, a mechanism that enables kernel-side authorization decisions based on x.509 certificate content presented during TLS handshakes. After successful certificate chain validation, tlshd applies administrator-defined filters from /etc/tlshd/tags.d to the peer certificate, examining specific fields (subject, issuer, keyUsage, serialNumber, validity periods, and other RFC 5280 properties) and assigning tag names when all filters in a tag definition match. These matched tag names are returned to the kernel via new HANDSHAKE_A_DONE_TAG netlink attributes, allowing kernel TLS consumers to enforce per-peer access control policies that cannot be implemented using only socket-layer data visible to in-kernel LSM and NFS server subsystems. The feature is optional via --enable-session-tags to prevent handshake failures on older kernels that reject unknown netlink attributes; when enabled, kernel capability detection probes for tag attribute support at startup before attempting to use it. Configuration reload via SIGHUP is atomic: new filter and tag hash tables are constructed, populated from YAML files, and swapped in only upon successful parsing, ensuring malformed configuration cannot leave the subsystem inconsistent. The implementation leverages the existing fork-per-request architecture where child processes inherit copy-on-write snapshots of configuration data, eliminating the need for synchronization primitives while maintaining safe concurrent access.

Automake warns that addprefix is a non-POSIX variable name when
processing the systemd unit installation logic. The addprefix
function is a GNU make extension, limiting portability to build
environments that use other make implementations.

A shell for loop iterates over the unit file list at install time,
prefixing each filename with $(srcdir) as required for out-of-tree
builds. This achieves the same result while remaining compatible
with POSIX make, eliminating the automake warning and improving
build system portability.

Fixes: b922431 ("systemd: Fix out-of-tree builds")
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
The Doxyfile configuration file in the docs/ directory is generated
during the build process, not maintained in version control. When
the build system creates this file, git status incorrectly reports
it as an untracked change, cluttering the working directory state.

Adding docs/Doxyfile to .gitignore prevents this generated artifact
from appearing in status output, keeping the repository's tracked
and untracked file lists accurate.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Runtime configuration updates require restarting the tlshd daemon,
causing service interruption. Long-running systems benefit from
configuration reload without service downtime.

The config subsystem now stores the pathname of the configuration
file loaded during initialization. A new tlshd_config_reload()
function rereads this file and updates debug levels and keyring
links immediately. The main event loop catches SIGHUP and SIGUSR1
through the existing signalfd mechanism, invokes the reload
function, and continues polling for netlink messages rather than
terminating.

Configuration reload enables operators to adjust debug verbosity
and keyring associations without dropping active TLS sessions.
SIGINT and SIGTERM continue to terminate the daemon cleanly.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Now that tlshd has proper signal handling, add documentation of the
threading model to help guide developers adding new features to
tlshd.

The current fork-per-request architecture and single-threaded event
loop employs poll(2), multiplexes netlink messages and signals, and
child processes inherit copy-on-write snapshots of configuration
data. This makes pthread synchronization primitives unnecessary, and
puts a high wall between threads working on each handshake.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
An upcoming new feature requires configuration via YAML documents.
LibYAML provides the parser needed to process those configuration
files at runtime.

This change adds the configure.ac check to ensure the library is
present during the build process.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
The session tagging subsystem enables kernel TLS consumers to
perform authorization checks based on peer certificate content.
After certificate chain validation succeeds, tags matching
specific certificate field values are returned to the kernel
with the handshake result. The kernel consumer can then grant
or deny access to resources based on these tags.

This change adds subsystem lifecycle management and introduces
a new source file, tags.c, containing stub implementations that
will be populated with YAML parsing and certificate matching
logic in subsequent patches.

Tags initialization occurs in main.c after configuration parsing
completes, rather than being embedded within tlshd_config_init.
This separation follows the Single Responsibility Principle:
config.c focuses on configuration file parsing, while main.c
handles initialization sequencing. Tags shutdown occurs in
main.c before configuration cleanup during daemon termination.

Configuration reload coordination remains in config.c, as
reload is an operational feature rather than initialization.
When SIGHUP triggers configuration reload, config.c reloads
both its own configuration and the tags configuration to
maintain consistency.

The tags directory path is currently hard-coded to
/etc/tlshd/tags.d but might become a configuration file option
in the future.
Administrators need documentation to configure and deploy the
session tagging mechanism. Without guidance on the YAML filter
syntax, certificate field matching rules, and tag assignment
behavior, operators cannot leverage session tags for policy
decisions.

This introduces tls-session-tags(7), which provides an overview
of how tlshd scans peer certificate fields during handshake
verification and assigns tags based on conditions defined in
/etc/tlshd/tags.d/. The man page establishes the conceptual
foundation: tags enable kernel TLS consumers to make
authorization decisions based on certificate content rather than
relying solely on CA-based trust.

Subsequent patches will populate the man page with detailed
configuration syntax, filter examples, and operational guidance.
The build system changes add man7 support to accommodate this
new documentation category.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Session tag assignment requires filtering peer certificates, but
no mechanism exists to specify which certificate fields to examine
or how to match values within them. This prevents administrators
from defining meaningful session tags based on certificate content.

Each filter type defines a specific certificate field or derived
property that can be matched during session establishment. The
implementation uses a static table of twelve filter types covering
RFC 5280 certificate fields (signatureAlgorithm, version,
serialNumber, signature, issuer, notBefore, notAfter, subject),
standard extensions (keyUsage, extendedKeyUsage), and locally
computed properties (fingerprint, selfSigned). Each entry in the
table includes a name following x509 dotted notation, plus function
pointers for validation and matching operations that will be added
in subsequent patches.

At daemon startup, a hash table indexes these filter types by name,
providing O(1) lookup when parsing tag configuration files. This
lookup path will be exercised when administrators create filters
referencing these type names.

Testing: daemon starts successfully with new hash initialization.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
TLS session tag definitions are to be stored in YAML files. These
files may be edited by hand, or for better UX, an assistant tool
might be constructed to make it easier to handle tags.

A parser is required to read these configuration files and
construct the internal data structures that represent filtering
rules for session tags.

This commit introduces a finite state machine parser built on
libyaml that validates YAML document structure in tag definition
files. The FSM tracks document boundaries (stream start/end,
document start/end, mapping start/end) and ensures input conforms
to expected YAML grammar. Directory scanning locates all .yml and
.yaml files in the configuration directory and processes each
through the parser.

The parser currently validates only document structure. Subsequent
commits will extend the FSM to recognize tag definition scalars
and construct the filtering data structures. This incremental
approach allows verification of the parsing framework before
adding semantic processing.

A debug helper translates libyaml event types into human-readable
symbols for troubleshooting YAML parsing failures. The FSM state
transition tables are structured to simplify addition of new
states as tag definition parsing logic is implemented.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Session tag assignment requires filters that specify which
certificate fields to examine and how to match their values, but
the YAML parser added previously validates only document structure
without extracting filter specifications. This prevents the daemon
from constructing the filter rules that govern tag assignment.

This commit extends the finite state machine with nine new states
that parse filter definitions from YAML input. Each filter
specification includes a unique name, a filter type (referencing
the type table from a prior commit), and type-specific arguments
such as wildcard patterns for string matching or purpose masks for
keyUsage validation. The parser validates filter names against a
regex constraint limiting characters to alphanumerics, hyphens,
and underscores. Parsed filters populate a hash table indexed by
name, providing O(1) lookup during subsequent tag processing.

The FSM transitions handle three argument classes: pattern-based
filters store a GLib pattern spec for wildcard matching,
purpose-based filters accumulate a bitmask of GNUTLS keyUsage
flags, and time-based filters (parsing not yet implemented) will
validate temporal constraints. Each filter undergoes type-specific
validation before insertion into the hash table, ensuring
malformed specifications are rejected at parse time rather than
during handshake processing.

A subsequent patch will add tag definitions that reference these
parsed filters.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Session tag assignment requires a collection of tags, each
associating a name with a list of filters that must match for
the tag to apply to a TLS session, but the parser implemented
previously extracts only filter definitions from YAML input
without recognizing tag specifications. This prevents the daemon
from constructing the complete rule set needed to assign tags
during handshake completion.

This extends the finite state machine with six new states that
parse tag definitions from YAML input. Each tag specification
includes a unique name (validated against the same alphanumeric
plus hyphen/underscore regex used for filter names) and a list
of filter references. Filter names may be prefixed with "not "
to invert their match semantics. The parser separates inverted
and non-inverted filter references into distinct pointer arrays,
simplifying the matching logic that will be added in subsequent
patches. Parsed tags populate a hash table indexed by name,
providing O(1) lookup when kernel consumers query available
tags.

Tag specifications reference filters by name only, requiring
those filters to exist in the filter hash table populated by
earlier parsing. A subsequent patch will add the matching logic
that applies these filter lists to incoming peer certificates
during handshake completion and attaches matching tag names to
the resulting TLS session.

Parse errors in tag configuration files are reported but do not
prevent daemon startup, allowing partial tag definitions to
remain available even when some configuration files contain
errors.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Authorization for NFS clients on TLS connections requires per-peer
access control that kernel mechanisms alone cannot provide. The
in-kernel LSM and NFS server access controls can only examine data
in the socket structure; x.509 certificate fields presented during
the TLS handshake remain inaccessible to those subsystems.

Filter-based tagging provides the needed mechanism. An x.509 peer
certificate passes through a set of administrator-defined filters
read from /etc/tlshd/tags.d at start-up. Each tag consists of
one or more filter expressions that match against specific x.509
fields. When all of a tag's filters match, that tag name becomes
associated with the TLS session.

The new tlshd_tags_match_session() function parses the client's
certificate after a successful handshake and applies each tag's
filters. Pattern filters use Glib pattern matching. Time filters
compare certificate validity windows against specified points in
time. Purpose and key usage filters apply bitmask operations.
Filters are composable: all non-inverted filters in a tag must
match for the tag to apply, while any matched inverted filter
disqualifies the tag.

A subsequent change will transmit the matched tag list to the
kernel upon handshake completion, enabling kernel-side policy
enforcement based on certificate properties.

Suggested-by: Benjamin Coddington <bcodding@redhat.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
A recent commit added documentation describing the concurrency
architecture for tlshd. This commit adds Doxygen documentation pages
in tags.c that references the primary threading documentation and
explains tag-specific concurrency properties. It documents that the
parent process handles configuration reload atomically during signal
processing, while child processes perform only read operations on
inherited hash tables. This design ensures that no concurrent
modification can occur.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Transmitting session tags to the kernel upon handshake completion
requires a new netlink attribute to carry each tag name. The kernel
handshake uAPI header defines HANDSHAKE_A_DONE_TAG for this purpose,
but the tlshd copy of that header predates this addition.

Synchronize the local netlink.h with the kernel's
include/uapi/linux/handshake.h by adding the HANDSHAKE_A_DONE_TAG
attribute definition. A subsequent change will use this attribute
to return matched tag names in the DONE netlink command.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
After a successful TLS handshake, the daemon has matched the peer
certificate against administrator-defined filters and accumulated
a list of tag names. However, without a mechanism to deliver these
tags to the kernel, kernel-side consumers cannot perform policy
decisions based on certificate properties.

Extend the DONE netlink response to include matched tag names. The
new tlshd_tags_for_each_matched() function iterates over the global
tag hash table and invokes a callback for each tag marked as
matched. The tlshd_genl_put_tag() callback serializes each tag name
as a HANDSHAKE_A_DONE_TAG string attribute in the netlink message.

Multiple tags may match a single session; each appears as a
separate attribute in the response. Kernel TLS consumers that
receive this response can examine these tag names to enforce access
control policies that depend on certificate properties inaccessible
at the socket layer.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
The session tagging subsystem reads filter and tag definitions from
YAML files in /etc/tlshd/tags.d/, but no installation target creates
this directory. Administrators would need to create it manually
before session tagging could function.

Add the tags.d directory to the Makefile.am install target so that
"make install" creates it alongside the existing /etc/tlshd/
configuration directory. Include a tags.example file demonstrating
the YAML syntax for filter and tag definitions. The example
illustrates pattern-based filters matching issuer and subject
fields, keyUsage filters validating certificate purposes, and tag
definitions that combine multiple filters.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
TLS session tags require libyaml for parsing definition files, but
not all deployment environments provide this library or require the
tagging functionality. Building session tags unconditionally would
impose an unnecessary dependency on environments that do not use
this feature.

Introduce --enable-session-tags to make session tag support
optional. When disabled (the default), tags.c is excluded from
the build, libyaml is not required as a dependency, and the
/etc/tlshd/tags.d directory is not created during installation.
Stub inline functions in tlshd.h ensure call sites compile without
modification when the feature is absent, returning success values
that allow normal daemon operation without tag processing.

The default setting is disabled to prevent handshake failures on
kernels that do not implement TLS session tags. When a DONE
downcall contains netlink arguments the kernel does not recognize,
the kernel discards the entire downcall rather than processing it
with the unsupported arguments ignored. On such kernels, enabling
session tags would cause tlshd to send DONE downcalls with tag
arguments, triggering this behavior and breaking handshake
completion. Requiring explicit opt-in ensures tlshd operates
correctly across kernel versions by default.

The CI workflow configurations are updated to enable session tags
so that the tagging code receives build and test coverage in
automated checks.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
The SIGHUP handler invokes tlshd_tags_config_reload(), which
previously returned success without performing any action. This
created a false expectation for administrators that sending
SIGHUP would reload the tags configuration from disk.

Actual reload is now performed atomically by constructing new
hash tables, loading the updated configuration files, and
replacing the global tables only upon successful parsing. If
parsing fails at any stage, the new tables are discarded and the
existing configuration remains active. This ensures that a
malformed configuration file cannot leave the subsystem in an
inconsistent state.

The implementation saves pointers to the current filter and tag
hash tables, clears the global pointers, initializes fresh hash
tables, and attempts to parse the tags directory. On success,
the old tables are destroyed and the new configuration takes
effect. On failure, the fresh tables are destroyed, the saved
pointers are restored, and an error is logged indicating that
the previous configuration is retained.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Older kernels reject the entire HANDSHAKE_CMD_DONE message when
it contains netlink attributes they do not recognize, rather
than ignoring unknown attributes. This causes handshakes to fail
on kernels that lack support for optional features such as
session tags or remote peer identity reporting.

To prevent this failure mode, tlshd now probes the kernel at
startup to determine which optional attributes are supported. A
generic probe function sends test messages containing each
optional attribute and observes whether the kernel accepts the
attribute type. Results are cached in a capability structure for
consultation before constructing messages.

The probe mechanism sends a minimally-valid message for each
command type, including required attributes but with values that
will cause the kernel to reject the operation for reasons other
than attribute support. For HANDSHAKE_CMD_DONE, the probe
supplies an invalid socket descriptor, which the kernel rejects
after successfully parsing all attributes. Error codes
distinguish between "attribute not supported" and "operation
failed for other reasons."

Capability detection occurs in tlshd_genl_dispatch after joining
the handshake multicast group but before entering the event
loop. Detected capabilities are logged at notice level to inform
administrators which features are available. The DONE command
consults capability flags before adding optional attributes,
ensuring messages contain only attributes the kernel will
accept.

This design scales to additional optional attributes: adding a
new feature requires appending one field to the capability
structure, adding one probe call, and checking the flag before
using the attribute. No per-attribute detection logic or version
comparisons are needed.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
@chucklever chucklever merged commit 4d4045b into main Dec 29, 2025
12 of 13 checks passed
@chucklever chucklever deleted the tls-session-tags branch December 29, 2025 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant