diff --git a/lib/libpam/modules/modules.inc b/lib/libpam/modules/modules.inc --- a/lib/libpam/modules/modules.inc +++ b/lib/libpam/modules/modules.inc @@ -33,3 +33,4 @@ .endif MODULES += pam_tacplus MODULES += pam_unix +MODULES += pam_zfs_key diff --git a/lib/libpam/modules/pam_zfs_key/Makefile b/lib/libpam/modules/pam_zfs_key/Makefile new file mode 100644 --- /dev/null +++ b/lib/libpam/modules/pam_zfs_key/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +LIB= pam_zfs_key +SRCS= pam_zfs_key.c +MAN= pam_zfs_key.8 + +LIBADD = zfs + +CFLAGS+=-DHAVE_ISSETUGID +CFLAGS+=-I${SRCTOP}/sys/contrib/openzfs/include/ +CFLAGS+=-I${SRCTOP}/sys/cddl/compat/opensolaris/ +CFLAGS+=-isystem ${SRCTOP}/sys/contrib/openzfs/lib/libspl/include/ +CFLAGS+=-isystem ${SRCTOP}/sys/contrib/openzfs/lib/libspl/include/os/freebsd/ +CFLAGS+=-isystem ${SRCTOP}/sys/contrib/openzfs/lib/libspl/include/os/freebsd/sys + +WARNS?= 1 + +.include diff --git a/lib/libpam/modules/pam_zfs_key/pam_zfs_key.8 b/lib/libpam/modules/pam_zfs_key/pam_zfs_key.8 new file mode 100644 --- /dev/null +++ b/lib/libpam/modules/pam_zfs_key/pam_zfs_key.8 @@ -0,0 +1,120 @@ +.\" Copyright (c) 2021 Eric McCorkle. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd Sept 1, 2021 +.Dt PAM_ZFS_KEY 8 +.Os +.Sh NAME +.Nm pam_zfs_key +.Nd ZFS Key PAM module +.Sh SYNOPSIS +.Op Ar service-name +.Ar module-type +.Ar control-flag +.Pa pam_exec +.Op Ar arguments +.Sh DESCRIPTION +The ZFS key service module for PAM provides functionality for providing +a user's auth token as a ZFS key for a per-user encrypted dataset. It +provides a function +.Pq Fn pam_sm_authenticate +that determines a user-specific ZFS dataset (usually a home directory) +for which to provide the user's auth token as a ZFS encryption key. +This is intended primarily for use after another service module has +authenticated a user, as a way to automatically prepare for a user's +home directory to be mounted. +.Fn pam_sm_authenticate +first determines a ZFS dataset for which to load key, then obtains an +auth token, which it supplies directly as the ZFS key material, +bypassing the normal key ZFS location mechanism. If the auth token is +incorrect for the given ZFS data set, this will be treated as an +authentication failure. +.Pp +When prompting for the current password, the authentication +module will use the default prompt. +.Pp +This module should not be used as a primary authentication mechanism, as +.Fn pam_sm_authenticate +may succeed without performing any key validity check if the +corresponding ZFS key is already loaded. +.Pp +The following options may be passed to the authentication module: +.Bl -tag -width ".Cm use_first_pass" +.It Cm debug +.Xr syslog 3 +debugging information at +.Dv LOG_DEBUG +level. +.It Cm no_warn +suppress warning messages to the user. +These messages include +reasons why the user's +authentication attempt was declined. +.It Cm use_first_pass +If the authentication module is not the first in the stack, +and a previous module obtained the user's password, that password is +used to authenticate the user. +If this fails, the authentication +module returns failure without prompting the user for a password. +This option has no effect if the authentication module is +the first in the stack, or if no previous modules obtained the +user's password. +.It Cm try_first_pass +This option is similar to the +.Cm use_first_pass +option, except that if the previously obtained password fails, the +user is prompted for another password. +.It Cm zvol Ns = Ns Ar name +Use +.Ar name +as the name of the ZFS volume. By default, this will be the hostname +as indicated by +.Fn gethostname . +.It Cm path Ns = Ns Ar path +Use +.Ar path +to construct the path to the specific ZFS data set. All instances of +the character '&' will be replaced by the user name, and the +.Ar zvol +parameter will be prepended to construct the full dataset name. The +default value for this is "/home/&". +.It Cm excludes Ns = Ns Ar list +Do not attempt to load keys for users in the comma-separated list +.Ar list . +The default value for this argument is "root". +.Sh SEE ALSO +.Xr zfs 8 , +.Xr zfs-load-key 8 , +.Xr syslog 3 , +.Xr pam.conf 5 , +.Xr pam 8 +.Sh NOTES +This module should not be used as a primary authentication mechanism, as +.Fn pam_sm_authenticate +may succeed without performing any key validity check if the +corresponding ZFS key is already loaded. diff --git a/lib/libpam/modules/pam_zfs_key/pam_zfs_key.c b/lib/libpam/modules/pam_zfs_key/pam_zfs_key.c new file mode 100644 --- /dev/null +++ b/lib/libpam/modules/pam_zfs_key/pam_zfs_key.c @@ -0,0 +1,293 @@ +/*- + * Copyright (c) 2021 Eric McCorkle. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#ifndef PAM_ZFS_KEY +#define PAM_ZFS_KEY +#endif + +#define DIRECT_PREFIX "direct:" +#define DIRECT_PREFIX_LEN (sizeof(DIRECT_PREFIX) - 1) + +#define ZVOL_PARAM "zvol=" +#define ZVOL_PARAM_LEN (sizeof(ZVOL_PARAM) - 1) + +#define PATH_PARAM "path=" +#define PATH_PARAM_LEN (sizeof(PATH_PARAM) - 1) + +#define EXCLUDES_PARAM "excludes=" +#define EXCLUDES_PARAM_LEN (sizeof(EXCLUDES_PARAM) - 1) + +static int +subst_path(const char srcpath[MAXPATHLEN], const char *zvol, const char *user, + char path[MAXPATHLEN]) +{ + const int userlen = strlen(user); + int i; + int gap = 0; + + i = stpcpy(path, zvol) - path; + gap += i; + + while (srcpath[i - gap] != '\0') { + const char *currsrcpath = srcpath + i - gap; + const char *next = strchrnul(currsrcpath, '&'); + const size_t len = next - currsrcpath; + + if (len + i < MAXPATHLEN - 1) { + memcpy(path + i, currsrcpath, len); + i += len; + } else { + return (PAM_SYSTEM_ERR); + } + + if (*next == '&') { + if (userlen + i < MAXPATHLEN - 1) { + memcpy(path + i, user, userlen); + i += userlen; + gap += userlen - 1; + } else { + return (PAM_SYSTEM_ERR); + } + } + } + + path[i] = '\0'; + + return (PAM_SUCCESS); +} + +static int +parse_options(int argc, const char *argv[], char zvol[MAXPATHLEN], + char path[MAXPATHLEN], const char **excludes) +{ + int i; + int err; + + zvol[0] = '\0'; + path[0] = '\0'; + *excludes = NULL; + + for (i = 0; i < argc; ++i) { + if (strncmp(argv[i], ZVOL_PARAM, ZVOL_PARAM_LEN) == 0) { + strncpy(zvol, argv[i] + ZVOL_PARAM_LEN, MAXPATHLEN); + } else if (strncmp(argv[i], PATH_PARAM, PATH_PARAM_LEN) == 0) { + strncpy(path, argv[i] + PATH_PARAM_LEN, MAXPATHLEN); + } else if (strncmp(argv[i], EXCLUDES_PARAM, + EXCLUDES_PARAM_LEN) == 0) { + *excludes = argv[i] + EXCLUDES_PARAM_LEN; + } else { + PAM_LOG("ignored argument: %s", argv[i]); + } + } + + if (zvol[0] == '\0' && (err = gethostname(zvol, MAXPATHLEN)) != 0) { + PAM_LOG("couldn't get hostname (%s)", strerror(err)); + + return (PAM_SYSTEM_ERR); + } + + if (path[0] == '\0') { + strncpy(path, "/home/&", MAXPATHLEN); + } + + if (*excludes == NULL) { + *excludes = "root"; + } + + return (PAM_SUCCESS); +} + +PAM_EXTERN int +pam_sm_authenticate(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + char zvol[MAXPATHLEN]; + char path[MAXPATHLEN]; + char dataset[MAXPATHLEN]; + const char *user; + const char *authtok; + const char *excludes = NULL; + const char *curr; + libzfs_handle_t *libh = NULL; + zfs_handle_t *zfsh = NULL; + char *location = NULL; + int loclen; + int pam_err; + + if ((pam_err = parse_options(argc, argv, zvol, path, + &excludes)) != PAM_SUCCESS) { + return (pam_err); + } + + if ((pam_err = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) { + return (pam_err); + } + + PAM_LOG("Got user: %s", user); + + for(curr = excludes; *curr != '\0';) { + char *next = strchrnul(curr, ','); + if (strncmp(user, curr, next - curr) == 0) { + PAM_LOG("Excluding user: %s", user); + + return (PAM_SUCCESS); + } + + curr = next; + + if (*curr != '\0') { + curr++; + } + } + + if ((pam_err = subst_path(path, zvol, user, dataset)) != PAM_SUCCESS) { + return (pam_err); + } + + if ((pam_err = pam_get_authtok(pamh, PAM_AUTHTOK, &authtok, + NULL)) != PAM_SUCCESS) { + return (pam_err); + } + + if (authtok == NULL) { + return (PAM_AUTHTOK_ERR); + } + + loclen = DIRECT_PREFIX_LEN + strlen(authtok) + 1; + location = malloc(loclen); + memcpy(location, DIRECT_PREFIX, DIRECT_PREFIX_LEN); + strcpy(location + DIRECT_PREFIX_LEN, authtok); + + PAM_LOG("Got authentication token"); + + if ((libh = libzfs_init()) == NULL) { + PAM_LOG("cannot initialize ZFS (%s)", + strerror(errno)); + + pam_err = PAM_SYSTEM_ERR; + } else if ((zfsh = zfs_open(libh, dataset, + ZFS_TYPE_FILESYSTEM)) == NULL) { + PAM_LOG("cannot open ZFS dataset %s (%s)", + dataset, strerror(errno)); + + pam_err = PAM_SYSTEM_ERR; + } else { + switch (zfs_crypto_load_key(zfsh, B_FALSE, location)) { + case EEXIST: + PAM_LOG("ZFS key for %s already loaded", dataset); + pam_err = PAM_SUCCESS; + break; + case 0: + PAM_LOG("loaded ZFS key for %s", dataset); + pam_err = PAM_SUCCESS; + break; + case EPERM: + PAM_LOG("incorrect ZFS key for dataset %s", dataset); + pam_err = PAM_AUTH_ERR; + break; + default: + PAM_LOG("failed to load ZFS key for dataset %s (%s)", + dataset, strerror(errno)); + pam_err = PAM_AUTHTOK_ERR; + break; + } + } + + + if (zfsh != NULL) { + zfs_close(zfsh); + } + + if (libh != NULL) { + libzfs_fini(libh); + } + + if (location != NULL) { + memset(location, 0, loclen); + free(location); + } + + return (pam_err); +} + +PAM_EXTERN int +pam_sm_setcred(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + + return (PAM_SUCCESS); +} + +PAM_EXTERN int +pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + + return (PAM_SUCCESS); +} + +PAM_EXTERN int +pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + + return (PAM_SUCCESS); +} + +PAM_EXTERN int +pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + + return (PAM_SUCCESS); +} + +PAM_EXTERN int +pam_sm_chauthtok(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + + return (PAM_SERVICE_ERR); +} + +#ifdef PAM_MODULE_ENTRY +PAM_MODULE_ENTRY("pam_zfs_key"); +#endif diff --git a/sys/contrib/openzfs/include/sys/fs/zfs.h b/sys/contrib/openzfs/include/sys/fs/zfs.h --- a/sys/contrib/openzfs/include/sys/fs/zfs.h +++ b/sys/contrib/openzfs/include/sys/fs/zfs.h @@ -454,6 +454,8 @@ ZFS_KEYLOCATION_NONE = 0, ZFS_KEYLOCATION_PROMPT, ZFS_KEYLOCATION_URI, + /* This is used by PAM */ + ZFS_KEYLOCATION_DIRECT, ZFS_KEYLOCATION_LOCATIONS } zfs_keylocation_t; diff --git a/sys/contrib/openzfs/lib/libzfs/libzfs_crypto.c b/sys/contrib/openzfs/lib/libzfs/libzfs_crypto.c --- a/sys/contrib/openzfs/lib/libzfs/libzfs_crypto.c +++ b/sys/contrib/openzfs/lib/libzfs/libzfs_crypto.c @@ -65,6 +65,9 @@ #define MAX_PASSPHRASE_LEN 512 #define MAX_KEY_PROMPT_ATTEMPTS 3 +#define DIRECT_PREFIX "direct:" +#define DIRECT_PREFIX_LEN (sizeof(DIRECT_PREFIX) - 1) + static int caught_interrupt; static int get_key_material_file(libzfs_handle_t *, const char *, const char *, @@ -112,7 +115,10 @@ if (strcmp("prompt", str) == 0) { *locp = ZFS_KEYLOCATION_PROMPT; return (0); - } + } else if (strncmp(DIRECT_PREFIX, str, DIRECT_PREFIX_LEN) == 0) { + *locp = ZFS_KEYLOCATION_DIRECT; + return (0); + } regmatch_t pmatch[2]; @@ -696,6 +702,12 @@ /* open the appropriate file descriptor */ switch (keyloc) { + case ZFS_KEYLOCATION_DIRECT: + /* direct keys are stored in the key location */ + kmlen = strlen(keylocation) - DIRECT_PREFIX_LEN; + km = malloc(kmlen + 1); + strcpy(km, keylocation + DIRECT_PREFIX_LEN); + break; case ZFS_KEYLOCATION_PROMPT: if (isatty(fileno(stdin))) { can_retry = keyformat != ZFS_KEYFORMAT_RAW;