Skip to content

Add dynamic keytab loader - #135

Open
abbra wants to merge 5 commits into
gssapi:mainfrom
abbra:keytab_loader
Open

abbra wants to merge 5 commits into
gssapi:mainfrom
abbra:keytab_loader

Conversation

@abbra

@abbra abbra commented Oct 20, 2026

Copy link
Copy Markdown

Add a way to read keytab from a dynamic source.

A service with keytab_loader gets its keys from a program instead of a keytab file, so they never touch a filesystem. GSS-Proxy runs the program with a memfd as file descriptor 3 and, when it exits with status 0, copies the keytab it wrote into the service's MEMORY keytab and closes the memfd.

This allows to handle encrypted keytabs or keytabs protected by NBDE-like configurations.

Test keytab loader is providing a sample implementation that uses a XORed file. It also accepts a file that, if exists, causes a failure simulation.

Assisted-by: Claude Code (Opus 5.5)

abbra added 5 commits October 20, 2026 11:09
The keytab_loader option runs a program with a memfd for its output
and watches its exit through a pidfd from the event loop; close_range
closes the descriptors the program must not inherit.  Without the first
two the option is rejected.

Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
keytab_loader names a program that produces the service's keytab:

  cred_store = keytab:MEMORY:<name>
  keytab_loader = /usr/libexec/kuipa-unseal --memfd

The value is an absolute path and arguments, split on blanks without a
shell.  The service must use a MEMORY keytab, and services sharing one
must name the same loader.  Platforms without memfd_create and
pidfd_open reject the option.

gp_service_get_keytab() returns the keytab element of a service's cred
store, which setup_krb5_creds_handle() looked up inline before.

Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
A service with keytab_loader gets its keys from a program instead of a
keytab file, so they never touch a filesystem.  GSS-Proxy runs the
program with a memfd as file descriptor 3 and, when it exits with
status 0, copies the keytab it wrote into the service's MEMORY keytab
and closes the memfd.

* Loaders run asynchronously: a pidfd in the event loop reports their
  exit, and a loader running over five minutes is killed.
* A failing loader is retried with exponential backoff (5 seconds up to
  10 minutes); the keys loaded before stay in use meanwhile.
* SIGHUP runs every loader again; a keytab no longer used by any
  service is emptied and released.
* While a loader is configured the process is not dumpable, keeping the
  keys out of core dumps.

The keytab name never changes, because credentials exported to clients
refer to it by name and resolve it again when imported.  Its contents
are therefore replaced in place.  Removing an entry from a MEMORY keytab
frees it under any thread iterating over the keytab, so workers now hold
a read lock around each request, and old entries are removed only when
the main thread gets the write lock without waiting.  When workers are
busy the new keys are added at once and the old ones removed on a later
attempt, so a request never misses a key.

MIT destroys a MEMORY keytab when its last handle is closed, so each
loader keeps one open for as long as it exists.

Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
t_kt_loader "unseals" a keytab sealed by XORing each byte and writes it
to file descriptor 3, or fails with status 75 while a given file exists,
as kuipa-unseal does when no server can unseal.  The sealed file is not
a usable keytab, so gssproxy gets the keys only through the loader.

t_keytab_loader.py checks the initial load, the re-run on SIGHUP, a
failing loader (initiating as the service fails: there is no fallback to
another keytab), the retry once it succeeds, and that a failing re-run
keeps the keys loaded before.

Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
abbra added a commit to abbra/kuipa that referenced this pull request Oct 20, 2026
The keytab_loader change is submitted as gssapi/gssproxy#135: the memfd
loader into a MEMORY keytab, SIGHUP reload, retry with backoff, and core
dumps disabled while a loader is configured.  TODO keeps it open until it
merges, and spells out what kuipa protect then does with it.

Signed-off-by: Alexander Bokovoy <ab@vda.li>

@simo5 simo5 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the surface this looks good, but is there any reason why the keytab is not simply returned via stdout (ie over a simple pipe)?

The memfd dance is cool and everything, but also quite unnecessary and adds complexity that doesn't seem really necessary given the child is spawn each time a keytab needs to be loaded.

@abbra

abbra commented Oct 20, 2026

Copy link
Copy Markdown
Author

The only reason to use memfd is to enforce secure memory that is guaranteed not to be backed by the file system.
If you think it is too much, I can change to stdout indeed.

@simo5

simo5 commented Oct 20, 2026

Copy link
Copy Markdown
Member

The only reason to use memfd is to enforce secure memory that is guaranteed not to be backed by the file system. If you think it is too much, I can change to stdout indeed.

as fvar as I know pipes are never filesystem backed ... I guess they could be swapped in some rare cases, but then the solution is to simply use encrypted swap IMO.

If it is not too much of a problem I would prefer to avoid the memfd additional complexity in this case.

@abbra

abbra commented Oct 20, 2026

Copy link
Copy Markdown
Author

Thanks. I'll look at it tomorrow, too late tonight. :)

@abbra

abbra commented Oct 21, 2026

Copy link
Copy Markdown
Author

With a pipe we'd have to buffer the bytes in the event loop and parse the MIT keytab format ourselves (v2/v3 framing with the total-length/per-entry-length fields), then krb5_kt_add_entry.

From experiment:

  • FILE:/proc/self/fd/N over a pipe: start_seq_get returns "End of key table reached" with 0 entries and silently an empty keytab, no error.
  • Second pass on the same pipe: "Unsupported key table format version number" (offset can't rewind).
  • krb5_kt_get_entry() on the pipe: "Illegal seek" (ESPIPE)

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.

2 participants