Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions doc/kcron.doc
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,11 @@ Configuration variables (sourced from these files):
**FULLPRINCIPAL**::
Complete principal name in the form +username/cron/host.domain@REALM+.

**KEYTAB_INIT**::
Path to the keytab initialization utility (default: +/usr/libexec/kcron/init-kcron-keytab+).
**KEYTAB_DIR_INIT**::
Path to the keytab directory initialization utility (default: +/usr/libexec/kcron/init-kerberos-client-dir+).

**KEYTAB_CREATE**::
Path to the empty keytab creation utility (default: +/usr/libexec/kcron/create-empty-client-keytab+).

**KEYTAB_NAME_UTIL**::
Path to the keytab name utility (default: +/usr/libexec/kcron/client-keytab-name+).
Expand Down Expand Up @@ -106,9 +109,9 @@ Do you want to continue? (y/n)y

== LIMITATIONS

The kcron helper utilities utilize filesystem capabilities(7) stored as xattr(7) values on the binaries. The application will not run as expected if your system does not support them.
The kcron helper utilities utilize filesystem capabilities(7) stored as xattr(7) values on the binaries. The application will not run as expected if your file system does not support them.

On modern Linux systems, these are enabled by default.
On modern Linux systems, these are enabled by default for most volumes.

NFS does not support capabilities(7) nor should it. However, if you set this SUID 'root', it will continue to work as expected. It is a terrible idea to permit SUID applications to run SUID from network file systems.

Expand Down
8 changes: 6 additions & 2 deletions fermilab-util_kcron.spec
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

Name: fermilab-util_kcron

Version: 2.0
Version: 2.1
Release: 1%{?dist}
Summary: A utility for getting Kerberos credentials in scheduled jobs

Expand Down Expand Up @@ -136,12 +136,16 @@ done
%config(noreplace) %{_sysconfdir}/sysconfig/kcron
%attr(0755,root,root) %{_bindir}/*
%attr(0755,root,root) /usr/libexec/kcron/client-keytab-name
%attr(0755,root,root) /usr/libexec/kcron/create-empty-client-keytab

# If you can edit the memory this allocates, you can redirect the caps
# so we still suid to prevent this. user 'bin' is basically unusable anyway.
%attr(4755,bin,root) %caps(cap_chown=p cap_dac_override=p) %{_libexecdir}/kcron/init-kcron-keytab
%attr(4755,bin,root) %caps(cap_chown=p cap_dac_override=p) %{_libexecdir}/kcron/init-kerberos-client-dir

%changelog
* Wed Sep 16 2026 Pat Riehecky <riehecky@fnal.gov> - 2.1
- Break out helper tools into smaller parts

* Sat Jan 31 2026 Pat Riehecky <riehecky@fnal.gov> - 2.0
- libcap now mandatory

Expand Down
49 changes: 37 additions & 12 deletions src/C/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ add_compile_options(
# Additional hardening for formats
add_compile_options(-D_GLIBCXX_ASSERTIONS)

# Enable source fortification for standard library functions
add_compile_options(-D_FORTIFY_SOURCE=2)

# Additional GCC features (not supported by all compilers)
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
add_compile_options(
Expand Down Expand Up @@ -261,13 +264,16 @@ endif()
#############################
# Build Targets

add_executable(init-kcron-keytab)
add_executable(init-kerberos-client-dir)
add_executable(create-empty-client-keytab)
add_executable(client-keytab-name)

#############################
# Installation Configuration

install(TARGETS init-kcron-keytab DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/kcron
install(TARGETS init-kerberos-client-dir DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/kcron
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
install(TARGETS create-empty-client-keytab DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/kcron
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
install(TARGETS client-keytab-name DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/kcron
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
Expand All @@ -276,24 +282,45 @@ message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}")
message(STATUS "Install libexec: ${CMAKE_INSTALL_LIBEXECDIR}")

#############################
# init-kcron-keytab Target Configuration
# init-kerberos-client-dir Target Configuration

target_compile_features(init-kcron-keytab PRIVATE
target_compile_features(init-kerberos-client-dir PRIVATE
c_std_17
c_restrict
c_function_prototypes
c_static_assert
)

target_sources(init-kcron-keytab PRIVATE
${PROJECT_SOURCE_DIR}/src/C/init-kcron-keytab.c
target_sources(init-kerberos-client-dir PRIVATE
${PROJECT_SOURCE_DIR}/src/C/init-kerberos-client-dir.c
)

# Link required libraries
target_link_libraries(init-kcron-keytab PRIVATE cap)
target_link_libraries(init-kerberos-client-dir PRIVATE cap)

if (USE_SECCOMP)
target_link_libraries(init-kcron-keytab PRIVATE seccomp)
target_link_libraries(init-kerberos-client-dir PRIVATE seccomp)
endif(USE_SECCOMP)

#############################
# create-empty-client-keytab Target Configuration

target_compile_features(create-empty-client-keytab PRIVATE
c_std_17
c_restrict
c_function_prototypes
c_static_assert
)

target_sources(create-empty-client-keytab PRIVATE
${PROJECT_SOURCE_DIR}/src/C/create-empty-client-keytab.c
)

# Link libcap so the runtime hardening path can drop any inherited capabilities.
target_link_libraries(create-empty-client-keytab PRIVATE cap)

if (USE_SECCOMP)
target_link_libraries(create-empty-client-keytab PRIVATE seccomp)
endif(USE_SECCOMP)

#############################
Expand Down Expand Up @@ -330,10 +357,8 @@ include_directories(${PROJECT_SOURCE_DIR}/src/C/)

message(STATUS "")
message(STATUS "=== SECURITY NOTICE ===")
message(STATUS "init-kcron-keytab MUST be installed with file capabilities:")
message(STATUS " capabilities: setcap cap_chown,cap_dac_override=ep ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}/kcron/init-kcron-keytab")
message(STATUS "")
message(STATUS "The program will drop privileges immediately after creating the keytab file.")
message(STATUS "init-kerberos-client-dir MUST be installed with file capabilities:")
message(STATUS " capabilities: setcap cap_chown,cap_dac_override=ep ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}/kcron/init-kerberos-client-dir")
message(STATUS "======================")
message(STATUS "")

Expand Down
247 changes: 247 additions & 0 deletions src/C/create-empty-client-keytab.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/*
*
* A simple program that creates an empty client keytab file.
*
* It runs with NO special privileges. The per-user keytab directory must
* already exist and be owned by the calling user.
*
*/
#include "autoconf.h" /* for our automatic config bits */
/*

Copyright 2023 Fermi Research Alliance, LLC

This software was produced under U.S. Government contract DE-AC02-07CH11359
for Fermi National Accelerator Laboratory (Fermilab), which is operated by
Fermi Research Alliance, LLC for the U.S. Department of Energy. The U.S.
Government has rights to use, reproduce, and distribute this software.
NEITHER THE GOVERNMENT NOR FERMI RESEARCH ALLIANCE, LLC MAKES ANY WARRANTY,
EXPRESS OR IMPLIED, OR ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE.
If software is modified to produce derivative works, such modified software
should be clearly marked, so as not to confuse it with the version available
from Fermilab.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR FERMI RESEARCH ALLIANCE, LLC BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

*/

#ifndef __PROGRAM_NAME
#define __PROGRAM_NAME "create-empty-client-keytab"
#endif

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "kcron_empty_keytab_file.h"
#include "kcron_filename.h"

#ifndef _0600
#define _0600 (S_IRUSR | S_IWUSR)
#endif

/*
* Cleanup helper to free all allocated buffers.
* Ensures consistent cleanup on all exit paths.
*/
static void free_buffers(char *keytab, char *keytab_dirname, char *keytab_filename) __attribute__((flatten)) __attribute__((cold));
static void free_buffers(char *keytab, char *keytab_dirname, char *keytab_filename) {
/* NULL checks required for explicitness even though free() accepts NULL */
if (keytab != NULL) {
(void)free(keytab);
}
if (keytab_dirname != NULL) {
(void)free(keytab_dirname);
}
if (keytab_filename != NULL) {
(void)free(keytab_filename);
}
}


static int validate_keytab_directory(const char *keytab_dirname) __attribute__((access(read_only, 1))) __attribute__((warn_unused_result));
static int validate_keytab_directory(const char *keytab_dirname) {
struct stat lst = {0};
struct stat st = {0};

if (keytab_dirname == NULL) {
(void)fprintf(stderr, "%s: Keytab directory pointer is NULL.\n", __PROGRAM_NAME);
return 1;
}

/* Check symlink on keytab_dirname before stat to prevent symlink attacks */
if (lstat(keytab_dirname, &lst) != 0) {
(void)fprintf(stderr, "%s: Keytab directory does not exist: %s.\n", __PROGRAM_NAME, keytab_dirname);
(void)fprintf(stderr, "%s: Run init-kerberos-client-dir first.\n", __PROGRAM_NAME);
return 1;
}
if (S_ISLNK(lst.st_mode)) {
(void)fprintf(stderr, "%s: Keytab directory %s is a symlink, not allowed.\n", __PROGRAM_NAME, keytab_dirname);
return 1;
}

if (stat(keytab_dirname, &st) == -1) {
(void)fprintf(stderr, "%s: Keytab directory does not exist: %s.\n", __PROGRAM_NAME, keytab_dirname);
(void)fprintf(stderr, "%s: Run init-kerberos-client-dir first.\n", __PROGRAM_NAME);
return 1;
}

if (!S_ISDIR(st.st_mode)) {
(void)fprintf(stderr, "%s: Keytab directory %s is not a directory.\n", __PROGRAM_NAME, keytab_dirname);
return 1;
}

return 0;
}

static int create_keytab_file(const char *keytab_dirname, const char *keytab_filename, const char *keytab) __attribute__((warn_unused_result));
static int create_keytab_file(const char *keytab_dirname, const char *keytab_filename, const char *keytab) {
struct stat st = {0};
struct stat lst = {0};
DIR *keytab_dir = NULL;
int filedescriptor = -1;

/* Validate non-null input pointers */
if (keytab_dirname == NULL) {
(void)fprintf(stderr, "%s: keytab_dirname pointer is NULL.\n", __PROGRAM_NAME);
return 1;
}
if (keytab_filename == NULL) {
(void)fprintf(stderr, "%s: keytab_filename pointer is NULL.\n", __PROGRAM_NAME);
return 1;
}
if (keytab == NULL) {
(void)fprintf(stderr, "%s: keytab pointer is NULL.\n", __PROGRAM_NAME);
return 1;
}

/* Check if keytab_dirname exists and is not a symlink (to avoid TOCTOU symlink attacks) */
if (lstat(keytab_dirname, &lst) != 0) {
(void)fprintf(stderr, "%s: %s does not exist.\n", __PROGRAM_NAME, keytab_dirname);
return 1;
}
if (S_ISLNK(lst.st_mode)) {
(void)fprintf(stderr, "%s: %s is a symlink, not allowed.\n", __PROGRAM_NAME, keytab_dirname);
return 1;
}

/* Open the directory handle to perform file operations safely on the directory inode */
keytab_dir = opendir(keytab_dirname);
if (keytab_dir == NULL) {
(void)fprintf(stderr, "%s: Unable to open %s.\n", __PROGRAM_NAME, keytab_dirname);
(void)fprintf(stderr, "%s: This may be a permissions error?\n", __PROGRAM_NAME);
return 1;
}

/* Verify the opened directory is indeed a directory */
if (fstat(dirfd(keytab_dir), &st) != 0) {
(void)fprintf(stderr, "%s: %s could not be read.\n", __PROGRAM_NAME, keytab_dirname);
(void)closedir(keytab_dir);
return 1;
}

if (!S_ISDIR(st.st_mode)) {
(void)fprintf(stderr, "%s: %s is not a directory.\n", __PROGRAM_NAME, keytab_dirname);
(void)closedir(keytab_dir);
return 1;
}

/* Open the keytab file with O_NOFOLLOW to prevent symlink attacks,
* O_CREAT | O_EXCL to create only if missing and atomically detect existence,
* and O_CLOEXEC for descriptor safety. Permissions set to 0600 for security.
*/
filedescriptor = openat(dirfd(keytab_dir), keytab_filename, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, _0600);
if (filedescriptor < 0) {
if (errno == EEXIST) {
(void)fprintf(stderr, "%s: %s already exists.\n", __PROGRAM_NAME, keytab);
} else {
(void)fprintf(stderr, "%s: %s cannot be created: %s\n", __PROGRAM_NAME, keytab, strerror(errno));
}
(void)closedir(keytab_dir);
return 1;
}
(void)closedir(keytab_dir);

/* Verify that the created file is a regular file */
if (fstat(filedescriptor, &st) != 0) {
(void)close(filedescriptor);
(void)fprintf(stderr, "%s: %s could not be created.\n", __PROGRAM_NAME, keytab);
return 1;
}
if (!S_ISREG(st.st_mode)) {
(void)close(filedescriptor);
(void)fprintf(stderr, "%s: %s is not a regular file.\n", __PROGRAM_NAME, keytab);
return 1;
}

/* Write empty keytab content to the file */
if (write_empty_keytab(filedescriptor) != 0) {
(void)close(filedescriptor);
(void)fprintf(stderr, "%s: Cannot create keytab : %s.\n", __PROGRAM_NAME, keytab);
return 1;
}

/* Ensure permissions are exactly 0600 */
if (fchmod(filedescriptor, _0600) != 0) {
(void)close(filedescriptor);
(void)fprintf(stderr, "%s: Unable to chmod %o %s\n", __PROGRAM_NAME, _0600, keytab);
return 1;
}

(void)close(filedescriptor);
return 0;
}

int main(void) {
char *keytab = calloc(FILE_PATH_MAX_LENGTH + 3, sizeof(char));
char *keytab_dirname = calloc(FILE_PATH_MAX_LENGTH + 3, sizeof(char));
char *keytab_filename = calloc(FILE_PATH_MAX_LENGTH + 3, sizeof(char));

if ((keytab == NULL) || (keytab_dirname == NULL) || (keytab_filename == NULL)) {
(void)fprintf(stderr, "%s: Unable to allocate memory.\n", __PROGRAM_NAME);
(void)free_buffers(keytab, keytab_dirname, keytab_filename);
exit(EXIT_FAILURE);
}

if (get_filenames(keytab_dirname, keytab_filename, keytab) != 0) {
(void)fprintf(stderr, "%s: Cannot determine keytab filename.\n", __PROGRAM_NAME);
(void)free_buffers(keytab, keytab_dirname, keytab_filename);
exit(EXIT_FAILURE);
}

if (validate_keytab_directory(keytab_dirname) != 0) {
(void)free_buffers(keytab, keytab_dirname, keytab_filename);
exit(EXIT_FAILURE);
}

if (create_keytab_file(keytab_dirname, keytab_filename, keytab) != 0) {
(void)free_buffers(keytab, keytab_dirname, keytab_filename);
exit(EXIT_FAILURE);
}

(void)free_buffers(keytab, keytab_dirname, keytab_filename);

exit(EXIT_SUCCESS);
}
Loading