-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d83bd73
commit 94f7d49
Showing
7 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
CMakeCache.txt | ||
build | ||
CMakeFiles/ | ||
cmake_install.cmake | ||
pam_krb5_cc_move.so | ||
*.tar.gz | ||
*.tgz | ||
src/*.o |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
|
||
Apache License | ||
Version 2.0, January 2004 | ||
http://www.apache.org/licenses/ | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
CC=gcc | ||
CFLAGS="-fPIC -fno-stack-protector" | ||
INSTALLDIR=/usr/lib64/security | ||
MANDIR=/usr/share/man | ||
|
||
all: pam_krb5_cc_move.so | ||
|
||
src/pam_krb5_cc_move.o: src/pam_krb5_cc_move.c | ||
gcc $(EXTRA_CFLAGS) -fPIC -fno-stack-protector -c src/pam_krb5_cc_move.c -o src/pam_krb5_cc_move.o | ||
|
||
pam_krb5_cc_move.so: src/pam_krb5_cc_move.o | ||
gcc $(EXTRA_CFLAGS) -shared -o pam_krb5_cc_move.so src/pam_krb5_cc_move.o -lpam -lkrb5 | ||
|
||
install: all | ||
install -d $(DESTDIR)$(MANDIR)/man8 | ||
install -p -m 0644 man/pam_krb5_cc_move.8 $(DESTDIR)$(MANDIR)/man8/pam_krb5_cc_move.8 | ||
install -p -m 0755 pam_krb5_cc_move.so $(DESTDIR)/$(INSTALLDIR)/pam_krb5_cc_move.so | ||
|
||
clean: | ||
rm -rf build/ $(TARFILE) src/*.o *.so | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# pam_krb5_cc_move | ||
|
||
Pam session module to move a Kerberos credential cache from one location to another. | ||
|
||
Default locations for source and destination are as below | ||
|
||
``` | ||
session optional pam_krb5_cc_move.so source=FILE:/tmp/source destination=FILE:/tmp/destination debug | ||
``` | ||
|
||
if the source cc is missing this is NOT considered an error. | ||
|
||
## Use case with SSHD and Kerberos | ||
|
||
With SSHD and Kerberos authentication it is not possible to create a keytab at `/run/user/<uid>` since this directory | ||
does not exist when SSHD does the delegation. The workaround is have SSHD delegate to KEYRING and then this pam module | ||
can move the credential to the correct location. | ||
|
||
* Configure `/etc/krb5.conf` with cache of `FILE:/run/user/%{uid}/krb5cc`. | ||
* Run sshd with an environment of `KRB5_CONFIG=/etc/krb5_sshd.conf` to use a custom cache location for SSHD only. | ||
|
||
```ini | ||
# File /etc/krb5_sshd.conf | ||
[libdefaults] | ||
default_ccache_name = KEYRING:persistent:%{uid} | ||
|
||
include /etc/krb5.conf | ||
``` | ||
|
||
* Use two pam modules to create $XDG_RUNTIME_DIR and then move the credential there with each login. | ||
|
||
``` | ||
session optional pam_xdg_runtime_dir.so | ||
session optional pam_krb5_cc_move.so | ||
``` | ||
|
||
Default source and destination in pam_krb5_cc_move is `KEYRING:persistant:%{uid}` and `FILE:/run/user/%{uid}/krb5cc` | ||
|
||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
.TH PAM_KRB5_CC_MOVE 8 "January 2024" "Version 1.0" "pam_krb5_cc_move Manual" | ||
|
||
.SH NAME | ||
pam_krb5_cc_move \- Pluggable Authentication Module for Kerberos Credential Cache Migration | ||
|
||
.SH SYNOPSIS | ||
.B pam_krb5_cc_move.so | ||
|
||
.SH DESCRIPTION | ||
pam_krb5_cc_move is a PAM (Pluggable Authentication Module) that facilitates the migration of Kerberos credential caches. It is designed to be called during the PAM session phase, specifically in the opening session. | ||
|
||
.SH OPTIONS | ||
.TP | ||
.B source= | ||
Specifies the source credential cache format. The default is "KEYRING:persistent:%{uid}". | ||
|
||
.TP | ||
.B destination= | ||
Specifies the destination credential cache format. The default is "FILE:/run/user/%{uid}/krb5cc". | ||
|
||
.TP | ||
.B debug | ||
Enables debug mode, providing additional log information. | ||
|
||
.SH RETURN VALUES | ||
.PP | ||
.BR PAM_SUCCESS | ||
Successful completion of the credential cache migration. | ||
|
||
.PP | ||
.BR PAM_SESSION_ERR | ||
Error during the PAM session phase. | ||
|
||
.PP | ||
.BR PAM_AUTH_ERR | ||
Authentication-related error. | ||
|
||
.SH EXAMPLES | ||
The following PAM configuration demonstrates the usage of pam_krb5_cc_move in the /etc/pam.d/common-session file: | ||
|
||
.B | ||
session required pam_krb5_cc_move.so source=KEYRING:persistent:%{uid} destination=FILE:/tmp/krb5cc debug | ||
|
||
.SH FILES | ||
.TP | ||
/etc/pam.d/common-session | ||
PAM configuration file for session management. | ||
|
||
.SH SEE ALSO | ||
.BR pam.conf (5), krb5_ccache (3) | ||
|
||
.SH BUGS | ||
None known. | ||
|
||
.SH AUTHOR | ||
Your Name <[email protected]> | ||
|
||
.SH COPYRIGHT | ||
Copyright (c) 2024 Your Organization. All rights reserved. | ||
|
||
.SH DISCLAIMER | ||
This module is provided "as is" without any warranty. | ||
|
||
.SH NOTES | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Just use for testing | ||
session optional pam_krb5_cc_move.so debug source=FILE:/tmp/source destination=FILE:/tmp/destination |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
// Copyright 2024 CERN | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <unistd.h> | ||
#include <grp.h> | ||
#include <sys/types.h> | ||
#include <sys/stat.h> | ||
#include <sys/wait.h> | ||
#include <security/pam_appl.h> | ||
#include <security/pam_modules.h> | ||
#include <security/pam_ext.h> | ||
#include <pwd.h> | ||
#include <krb5/krb5.h> | ||
#include <errno.h> | ||
#include <syslog.h> | ||
#include <string.h> | ||
|
||
#define DEBUG_LOG_LEVEL LOG_DEBUG | ||
|
||
// Function to replace %{uid} with the actual UID | ||
void replaceSubstring(char *str, const char *search, const char *replace) { | ||
char *ptr = strstr(str, search); | ||
|
||
while (ptr != NULL) { | ||
int searchLen = strlen(search); | ||
int replaceLen = strlen(replace); | ||
int tailLen = strlen(ptr + searchLen); | ||
|
||
memmove(ptr + replaceLen, ptr + searchLen, tailLen + 1); | ||
memcpy(ptr, replace, replaceLen); | ||
|
||
ptr = strstr(ptr + replaceLen, search); | ||
} | ||
} | ||
|
||
// Function to parse options with equal signs | ||
void parseOptions(const char **argv, const char **source, const char **destination, int *debug) { | ||
|
||
for (int i = 0; argv[i] != NULL; ++i) { | ||
if (strncmp(argv[i], "source=", 7) == 0) { | ||
*source = &argv[i][7]; | ||
} else if (strncmp(argv[i], "destination=", 12) == 0) { | ||
*destination = &argv[i][12]; | ||
} else if (strcmp(argv[i], "debug") == 0) { | ||
*debug = 1; | ||
} | ||
} | ||
} | ||
|
||
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { | ||
const char *pam_user; | ||
struct passwd *pwd; | ||
krb5_context context; | ||
krb5_principal principal; | ||
krb5_ccache keyring_cache; | ||
int ret; | ||
|
||
int debug = 0; | ||
const char *source = "KEYRING:persistent:%{uid}"; | ||
const char *destination = "FILE:/run/user/%{uid}/krb5cc"; | ||
|
||
// Parse options | ||
parseOptions(argv, &source, &destination, &debug); | ||
|
||
if (debug) { | ||
pam_syslog(pamh, DEBUG_LOG_LEVEL, "Debug mode enabled"); | ||
} | ||
|
||
// Get the PAM_USER (user being authenticated) | ||
ret = pam_get_user(pamh, &pam_user, NULL); | ||
if (ret != PAM_SUCCESS) { | ||
pam_syslog(pamh, LOG_ERR, "Error getting PAM_USER: %s", pam_strerror(pamh, ret)); | ||
return PAM_SESSION_ERR; | ||
} | ||
|
||
// Get user information using getpwnam | ||
pwd = getpwnam(pam_user); | ||
if (pwd == NULL) { | ||
pam_syslog(pamh, LOG_ERR, "Error getting user information for PAM_USER: %s", pam_user); | ||
return PAM_SESSION_ERR; | ||
} | ||
char uidstr[20]; | ||
sprintf(uidstr, "%d", pwd->pw_uid); | ||
|
||
// Fork as the user and do the migration | ||
if (debug) { | ||
pam_syslog(pamh, DEBUG_LOG_LEVEL, "Starting fork as uid %d", pwd->pw_uid); | ||
} | ||
|
||
pid_t child_pid; | ||
child_pid = fork(); | ||
if (child_pid == -1) { | ||
// Fork failed | ||
pam_syslog(pamh, LOG_ERR, "Fork failed"); | ||
return PAM_AUTH_ERR; | ||
} | ||
|
||
if (child_pid == 0) { | ||
ret = setgid(pwd->pw_gid); | ||
if (ret != 0) { | ||
pam_syslog(pamh, LOG_ERR, "Error calling setgid to %d", pwd->pw_gid); | ||
exit(EXIT_FAILURE); | ||
} | ||
|
||
ret = setgroups(1, &pwd->pw_gid); | ||
if (ret != 0) { | ||
pam_syslog(pamh, LOG_ERR, "Error calling setgroups to %d", pwd->pw_gid); | ||
exit(EXIT_FAILURE); | ||
} | ||
|
||
ret = setuid(pwd->pw_uid); | ||
if (ret != 0) { | ||
pam_syslog(pamh, LOG_ERR, "Error calling setuid to %d", pwd->pw_uid); | ||
exit(EXIT_FAILURE); | ||
} | ||
|
||
if (debug) { | ||
pam_syslog(pamh, DEBUG_LOG_LEVEL, "Forked off as user %d", pwd->pw_uid); | ||
} | ||
|
||
// Initialize Kerberos context | ||
ret = krb5_init_context(&context); | ||
if (ret != 0) { | ||
pam_syslog(pamh, LOG_ERR, "Error initializing Kerberos context: %s", krb5_get_error_message(context, ret)); | ||
exit(EXIT_FAILURE); | ||
} | ||
|
||
// Construct KEYRING cache name with UID | ||
char keyring_cache_name[1024]; | ||
snprintf(keyring_cache_name, sizeof(keyring_cache_name), "%s", source); | ||
replaceSubstring(keyring_cache_name, "%{uid}", uidstr); | ||
|
||
if (debug) { | ||
pam_syslog(pamh, DEBUG_LOG_LEVEL, "Source Credential Cache %s", keyring_cache_name); | ||
} | ||
|
||
// Open KEYRING cache | ||
ret = krb5_cc_resolve(context, keyring_cache_name, &keyring_cache); | ||
if (ret != 0) { | ||
pam_syslog(pamh, LOG_ERR, "Error resolving KEYRING cache: %s", krb5_get_error_message(context, ret)); | ||
krb5_free_context(context); | ||
exit(EXIT_FAILURE); | ||
} | ||
|
||
// Get principal from KEYRING cache | ||
ret = krb5_cc_get_principal(context, keyring_cache, &principal); | ||
if (ret != 0) { | ||
pam_syslog(pamh, LOG_INFO, "No delegate kerberos principal in KEYRING cache: %s", krb5_get_error_message(context, ret)); | ||
krb5_cc_close(context, keyring_cache); | ||
krb5_free_context(context); | ||
exit(EXIT_SUCCESS); | ||
} | ||
|
||
// Log the principal name | ||
char *principal_name = NULL; | ||
krb5_unparse_name(context, principal, &principal_name); | ||
if (debug) { | ||
pam_syslog(pamh, DEBUG_LOG_LEVEL, "Principal name: %s", principal_name); | ||
} | ||
free(principal_name); | ||
|
||
// Specify the path for the cache | ||
char file_cache_name[PATH_MAX]; | ||
snprintf(file_cache_name, sizeof(file_cache_name), "%s", destination); | ||
replaceSubstring(file_cache_name, "%{uid}", uidstr); | ||
|
||
// Resolve and initialize the cache | ||
krb5_ccache file_cache; | ||
ret = krb5_cc_resolve(context, file_cache_name, &file_cache); | ||
if (ret == KRB5_FCC_NOFILE) { | ||
// If the cache file doesn't exist, initialize a new cache | ||
ret = krb5_cc_initialize(context, file_cache, principal); | ||
if (ret != 0) { | ||
pam_syslog(pamh, LOG_ERR, "Error initializing cache: %s", krb5_get_error_message(context, ret)); | ||
krb5_free_principal(context, principal); | ||
krb5_free_context(context); | ||
exit(EXIT_FAILURE); | ||
} | ||
} else if (ret != 0) { | ||
pam_syslog(pamh, LOG_ERR, "Error resolving cache: %s", krb5_get_error_message(context, ret)); | ||
krb5_free_principal(context, principal); | ||
krb5_free_context(context); | ||
exit(EXIT_FAILURE); | ||
} | ||
|
||
if (debug) { | ||
pam_syslog(pamh, DEBUG_LOG_LEVEL, "File Cache Initialized at %s", file_cache_name); | ||
} | ||
|
||
// Move credentials from KCM to file cache | ||
ret = krb5_cc_move(context, keyring_cache, file_cache); | ||
if (ret != 0) { | ||
pam_syslog(pamh, LOG_ERR, "Error moving credentials from KCM to file cache: %s", krb5_get_error_message(context, ret)); | ||
krb5_cc_close(context, keyring_cache); | ||
krb5_cc_close(context, file_cache); | ||
krb5_free_principal(context, principal); | ||
krb5_free_context(context); | ||
exit(EXIT_FAILURE); | ||
} | ||
|
||
exit(EXIT_SUCCESS); | ||
} else { | ||
int status; | ||
if (waitpid(child_pid, &status, 0) == -1) { | ||
pam_syslog(pamh, LOG_ERR, "Wait failed"); | ||
exit(EXIT_FAILURE); | ||
} | ||
|
||
if (WIFEXITED(status)) { | ||
if (debug) { | ||
pam_syslog(pamh, DEBUG_LOG_LEVEL, "Child process exited with status %d", WEXITSTATUS(status)); | ||
} | ||
} else { | ||
pam_syslog(pamh, LOG_ERR, "Child processes exited badly %d", WEXITSTATUS(status)); | ||
return PAM_SESSION_ERR; | ||
} | ||
} | ||
|
||
return PAM_SUCCESS; | ||
} | ||
|
||
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { | ||
// Perform any cleanup if necessary | ||
return PAM_SUCCESS; | ||
} |