mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-11-17 01:51:00 +03:00
pam: implement a zfs_key pam module
Implements a pam module for automatically loading zfs encryption keys for home datasets. The pam module: - loads a zfs key and mounts the dataset when a session opens. - unmounts the dataset and unloads the key when the session closes. - when the user is logged on and changes the password, the module changes the encryption key. Reviewed-by: Richard Laager <rlaager@wiktel.com> Reviewed-by: @jengelh <jengelh@inai.de> Reviewed-by: Ryan Moeller <ryan@iXsystems.com> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Felix Dörre <felix@dogcraft.de> Closes #9886 Closes #9903
This commit is contained in:
parent
7513807320
commit
221e67040f
37
config/user-pam.m4
Normal file
37
config/user-pam.m4
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
AC_DEFUN([ZFS_AC_CONFIG_USER_PAM], [
|
||||||
|
AC_ARG_ENABLE([pam],
|
||||||
|
AS_HELP_STRING([--enable-pam],
|
||||||
|
[install pam_zfs_key module [[default: check]]]),
|
||||||
|
[enable_pam=$enableval],
|
||||||
|
[enable_pam=check])
|
||||||
|
|
||||||
|
AC_ARG_WITH(pammoduledir,
|
||||||
|
AS_HELP_STRING([--with-pammoduledir=DIR],
|
||||||
|
[install pam module in dir [[$libdir/security]]]),
|
||||||
|
[pammoduledir="$withval"],[pammoduledir=$libdir/security])
|
||||||
|
|
||||||
|
AC_ARG_WITH(pamconfigsdir,
|
||||||
|
AS_HELP_STRING([--with-pamconfigsdir=DIR],
|
||||||
|
[install pam-config files in dir [[/usr/share/pamconfigs]]]),
|
||||||
|
[pamconfigsdir="$withval"],[pamconfigsdir=/usr/share/pam-configs])
|
||||||
|
|
||||||
|
AS_IF([test "x$enable_pam" != "xno"], [
|
||||||
|
AC_CHECK_HEADERS([security/pam_modules.h], [
|
||||||
|
enable_pam=yes
|
||||||
|
], [
|
||||||
|
AS_IF([test "x$enable_pam" == "xyes"], [
|
||||||
|
AC_MSG_FAILURE([
|
||||||
|
*** security/pam_modules.h missing, libpam0g-dev package required
|
||||||
|
])
|
||||||
|
],[
|
||||||
|
enable_pam=no
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
AS_IF([test "x$enable_pam" == "xyes"], [
|
||||||
|
DEFINE_PAM='--with "pam" --define "_pamconfigsdir $(pamconfigsdir)"'
|
||||||
|
])
|
||||||
|
AC_SUBST(DEFINE_PAM)
|
||||||
|
AC_SUBST(pammoduledir)
|
||||||
|
AC_SUBST(pamconfigsdir)
|
||||||
|
])
|
@ -17,6 +17,7 @@ AC_DEFUN([ZFS_AC_CONFIG_USER], [
|
|||||||
ZFS_AC_CONFIG_USER_LIBUDEV
|
ZFS_AC_CONFIG_USER_LIBUDEV
|
||||||
ZFS_AC_CONFIG_USER_LIBSSL
|
ZFS_AC_CONFIG_USER_LIBSSL
|
||||||
ZFS_AC_CONFIG_USER_LIBAIO
|
ZFS_AC_CONFIG_USER_LIBAIO
|
||||||
|
ZFS_AC_CONFIG_USER_PAM
|
||||||
ZFS_AC_CONFIG_USER_RUNSTATEDIR
|
ZFS_AC_CONFIG_USER_RUNSTATEDIR
|
||||||
ZFS_AC_CONFIG_USER_MAKEDEV_IN_SYSMACROS
|
ZFS_AC_CONFIG_USER_MAKEDEV_IN_SYSMACROS
|
||||||
ZFS_AC_CONFIG_USER_MAKEDEV_IN_MKDEV
|
ZFS_AC_CONFIG_USER_MAKEDEV_IN_MKDEV
|
||||||
|
@ -223,6 +223,7 @@ AC_DEFUN([ZFS_AC_CONFIG], [
|
|||||||
[test "x$qatsrc" != x ])
|
[test "x$qatsrc" != x ])
|
||||||
AM_CONDITIONAL([WANT_DEVNAME2DEVID], [test "x$user_libudev" = xyes ])
|
AM_CONDITIONAL([WANT_DEVNAME2DEVID], [test "x$user_libudev" = xyes ])
|
||||||
AM_CONDITIONAL([WANT_MMAP_LIBAIO], [test "x$user_libaio" = xyes ])
|
AM_CONDITIONAL([WANT_MMAP_LIBAIO], [test "x$user_libaio" = xyes ])
|
||||||
|
AM_CONDITIONAL([PAM_ZFS_ENABLED], [test "x$enable_pam" = xyes])
|
||||||
])
|
])
|
||||||
|
|
||||||
dnl #
|
dnl #
|
||||||
@ -284,6 +285,7 @@ AC_DEFUN([ZFS_AC_RPM], [
|
|||||||
RPM_DEFINE_UTIL+=' $(DEFINE_INITRAMFS)'
|
RPM_DEFINE_UTIL+=' $(DEFINE_INITRAMFS)'
|
||||||
RPM_DEFINE_UTIL+=' $(DEFINE_SYSTEMD)'
|
RPM_DEFINE_UTIL+=' $(DEFINE_SYSTEMD)'
|
||||||
RPM_DEFINE_UTIL+=' $(DEFINE_PYZFS)'
|
RPM_DEFINE_UTIL+=' $(DEFINE_PYZFS)'
|
||||||
|
RPM_DEFINE_UTIL+=' $(DEFINE_PAM)'
|
||||||
RPM_DEFINE_UTIL+=' $(DEFINE_PYTHON_VERSION)'
|
RPM_DEFINE_UTIL+=' $(DEFINE_PYTHON_VERSION)'
|
||||||
RPM_DEFINE_UTIL+=' $(DEFINE_PYTHON_PKG_VERSION)'
|
RPM_DEFINE_UTIL+=' $(DEFINE_PYTHON_PKG_VERSION)'
|
||||||
|
|
||||||
|
@ -98,6 +98,7 @@ AC_CONFIG_FILES([
|
|||||||
contrib/initramfs/hooks/Makefile
|
contrib/initramfs/hooks/Makefile
|
||||||
contrib/initramfs/scripts/Makefile
|
contrib/initramfs/scripts/Makefile
|
||||||
contrib/initramfs/scripts/local-top/Makefile
|
contrib/initramfs/scripts/local-top/Makefile
|
||||||
|
contrib/pam_zfs_key/Makefile
|
||||||
contrib/pyzfs/Makefile
|
contrib/pyzfs/Makefile
|
||||||
contrib/pyzfs/setup.py
|
contrib/pyzfs/setup.py
|
||||||
contrib/zcp/Makefile
|
contrib/zcp/Makefile
|
||||||
@ -351,6 +352,7 @@ AC_CONFIG_FILES([
|
|||||||
tests/zfs-tests/tests/functional/no_space/Makefile
|
tests/zfs-tests/tests/functional/no_space/Makefile
|
||||||
tests/zfs-tests/tests/functional/nopwrite/Makefile
|
tests/zfs-tests/tests/functional/nopwrite/Makefile
|
||||||
tests/zfs-tests/tests/functional/online_offline/Makefile
|
tests/zfs-tests/tests/functional/online_offline/Makefile
|
||||||
|
tests/zfs-tests/tests/functional/pam/Makefile
|
||||||
tests/zfs-tests/tests/functional/persist_l2arc/Makefile
|
tests/zfs-tests/tests/functional/persist_l2arc/Makefile
|
||||||
tests/zfs-tests/tests/functional/pool_checkpoint/Makefile
|
tests/zfs-tests/tests/functional/pool_checkpoint/Makefile
|
||||||
tests/zfs-tests/tests/functional/pool_names/Makefile
|
tests/zfs-tests/tests/functional/pool_names/Makefile
|
||||||
|
@ -2,4 +2,7 @@ SUBDIRS = bash_completion.d pyzfs zcp
|
|||||||
if BUILD_LINUX
|
if BUILD_LINUX
|
||||||
SUBDIRS += bpftrace dracut initramfs
|
SUBDIRS += bpftrace dracut initramfs
|
||||||
endif
|
endif
|
||||||
DIST_SUBDIRS = bash_completion.d bpftrace dracut initramfs pyzfs zcp
|
if PAM_ZFS_ENABLED
|
||||||
|
SUBDIRS += pam_zfs_key
|
||||||
|
endif
|
||||||
|
DIST_SUBDIRS = bash_completion.d bpftrace dracut initramfs pam_zfs_key pyzfs zcp
|
||||||
|
18
contrib/pam_zfs_key/Makefile.am
Normal file
18
contrib/pam_zfs_key/Makefile.am
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
include $(top_srcdir)/config/Rules.am
|
||||||
|
|
||||||
|
pammodule_LTLIBRARIES=pam_zfs_key.la
|
||||||
|
|
||||||
|
pam_zfs_key_la_SOURCES = pam_zfs_key.c
|
||||||
|
|
||||||
|
pam_zfs_key_la_LIBADD = \
|
||||||
|
$(top_builddir)/lib/libnvpair/libnvpair.la \
|
||||||
|
$(top_builddir)/lib/libuutil/libuutil.la \
|
||||||
|
$(top_builddir)/lib/libzfs/libzfs.la \
|
||||||
|
$(top_builddir)/lib/libzfs_core/libzfs_core.la
|
||||||
|
|
||||||
|
pam_zfs_key_la_LDFLAGS = -version-info 1:0:0 -avoid-version -module -shared
|
||||||
|
|
||||||
|
pam_zfs_key_la_LIBADD += -lpam $(LIBSSL)
|
||||||
|
|
||||||
|
pamconfigs_DATA = zfs_key
|
||||||
|
EXTRA_DIST = $(pamconfigs_DATA)
|
741
contrib/pam_zfs_key/pam_zfs_key.c
Normal file
741
contrib/pam_zfs_key/pam_zfs_key.c
Normal file
@ -0,0 +1,741 @@
|
|||||||
|
/*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * 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.
|
||||||
|
* * Neither the name of the <organization> nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 <COPYRIGHT HOLDER> 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.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Felix Dörre
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/dsl_crypt.h>
|
||||||
|
#include <sys/byteorder.h>
|
||||||
|
#include <libzfs.h>
|
||||||
|
|
||||||
|
#include <syslog.h>
|
||||||
|
|
||||||
|
#include <sys/zio_crypt.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
|
#define PAM_SM_AUTH
|
||||||
|
#define PAM_SM_PASSWORD
|
||||||
|
#define PAM_SM_SESSION
|
||||||
|
#include <security/pam_modules.h>
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
#include <security/pam_ext.h>
|
||||||
|
#elif defined(__FreeBSD__)
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
static void
|
||||||
|
pam_syslog(pam_handle_t *pamh, int loglevel, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsyslog(loglevel, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
static const char PASSWORD_VAR_NAME[] = "pam_zfs_key_authtok";
|
||||||
|
|
||||||
|
static libzfs_handle_t *g_zfs;
|
||||||
|
|
||||||
|
static void destroy_pw(pam_handle_t *pamh, void *data, int errcode);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t len;
|
||||||
|
char *value;
|
||||||
|
} pw_password_t;
|
||||||
|
|
||||||
|
static pw_password_t *
|
||||||
|
alloc_pw_size(size_t len)
|
||||||
|
{
|
||||||
|
pw_password_t *pw = malloc(sizeof (pw_password_t));
|
||||||
|
if (!pw) {
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
pw->len = len;
|
||||||
|
pw->value = malloc(len);
|
||||||
|
if (!pw->value) {
|
||||||
|
free(pw);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
mlock(pw->value, pw->len);
|
||||||
|
return (pw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static pw_password_t *
|
||||||
|
alloc_pw_string(const char *source)
|
||||||
|
{
|
||||||
|
pw_password_t *pw = malloc(sizeof (pw_password_t));
|
||||||
|
if (!pw) {
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
pw->len = strlen(source) + 1;
|
||||||
|
pw->value = malloc(pw->len);
|
||||||
|
if (!pw->value) {
|
||||||
|
free(pw);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
mlock(pw->value, pw->len);
|
||||||
|
memcpy(pw->value, source, pw->len);
|
||||||
|
return (pw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pw_free(pw_password_t *pw)
|
||||||
|
{
|
||||||
|
bzero(pw->value, pw->len);
|
||||||
|
munlock(pw->value, pw->len);
|
||||||
|
free(pw->value);
|
||||||
|
free(pw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static pw_password_t *
|
||||||
|
pw_fetch(pam_handle_t *pamh)
|
||||||
|
{
|
||||||
|
const char *token;
|
||||||
|
if (pam_get_authtok(pamh, PAM_AUTHTOK, &token, NULL) != PAM_SUCCESS) {
|
||||||
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"couldn't get password from PAM stack");
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
if (!token) {
|
||||||
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"token from PAM stack is null");
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
return (alloc_pw_string(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const pw_password_t *
|
||||||
|
pw_fetch_lazy(pam_handle_t *pamh)
|
||||||
|
{
|
||||||
|
pw_password_t *pw = pw_fetch(pamh);
|
||||||
|
if (pw == NULL) {
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
int ret = pam_set_data(pamh, PASSWORD_VAR_NAME, pw, destroy_pw);
|
||||||
|
if (ret != PAM_SUCCESS) {
|
||||||
|
pw_free(pw);
|
||||||
|
pam_syslog(pamh, LOG_ERR, "pam_set_data failed");
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
return (pw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const pw_password_t *
|
||||||
|
pw_get(pam_handle_t *pamh)
|
||||||
|
{
|
||||||
|
const pw_password_t *authtok = NULL;
|
||||||
|
int ret = pam_get_data(pamh, PASSWORD_VAR_NAME,
|
||||||
|
(const void**)(&authtok));
|
||||||
|
if (ret == PAM_SUCCESS)
|
||||||
|
return (authtok);
|
||||||
|
if (ret == PAM_NO_MODULE_DATA)
|
||||||
|
return (pw_fetch_lazy(pamh));
|
||||||
|
pam_syslog(pamh, LOG_ERR, "password not available");
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
pw_clear(pam_handle_t *pamh)
|
||||||
|
{
|
||||||
|
int ret = pam_set_data(pamh, PASSWORD_VAR_NAME, NULL, NULL);
|
||||||
|
if (ret != PAM_SUCCESS) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "clearing password failed");
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
destroy_pw(pam_handle_t *pamh, void *data, int errcode)
|
||||||
|
{
|
||||||
|
if (data != NULL) {
|
||||||
|
pw_free((pw_password_t *)data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
pam_zfs_init(pam_handle_t *pamh)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
if ((g_zfs = libzfs_init()) == NULL) {
|
||||||
|
error = errno;
|
||||||
|
pam_syslog(pamh, LOG_ERR, "Zfs initialization error: %s",
|
||||||
|
libzfs_error_init(error));
|
||||||
|
}
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pam_zfs_free(void)
|
||||||
|
{
|
||||||
|
libzfs_fini(g_zfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static pw_password_t *
|
||||||
|
prepare_passphrase(pam_handle_t *pamh, zfs_handle_t *ds,
|
||||||
|
const char *passphrase, nvlist_t *nvlist)
|
||||||
|
{
|
||||||
|
pw_password_t *key = alloc_pw_size(WRAPPING_KEY_LEN);
|
||||||
|
if (!key) {
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
uint64_t salt;
|
||||||
|
uint64_t iters;
|
||||||
|
if (nvlist != NULL) {
|
||||||
|
int fd = open("/dev/urandom", O_RDONLY);
|
||||||
|
if (fd < 0) {
|
||||||
|
pw_free(key);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
int bytes_read = 0;
|
||||||
|
char *buf = (char *)&salt;
|
||||||
|
size_t bytes = sizeof (uint64_t);
|
||||||
|
while (bytes_read < bytes) {
|
||||||
|
ssize_t len = read(fd, buf + bytes_read, bytes
|
||||||
|
- bytes_read);
|
||||||
|
if (len < 0) {
|
||||||
|
close(fd);
|
||||||
|
pw_free(key);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
bytes_read += len;
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (nvlist_add_uint64(nvlist,
|
||||||
|
zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), salt)) {
|
||||||
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"failed to add salt to nvlist");
|
||||||
|
pw_free(key);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
iters = DEFAULT_PBKDF2_ITERATIONS;
|
||||||
|
if (nvlist_add_uint64(nvlist, zfs_prop_to_name(
|
||||||
|
ZFS_PROP_PBKDF2_ITERS), iters)) {
|
||||||
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"failed to add iters to nvlist");
|
||||||
|
pw_free(key);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
salt = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_SALT);
|
||||||
|
iters = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_ITERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
salt = LE_64(salt);
|
||||||
|
if (!PKCS5_PBKDF2_HMAC_SHA1((char *)passphrase,
|
||||||
|
strlen(passphrase), (uint8_t *)&salt,
|
||||||
|
sizeof (uint64_t), iters, WRAPPING_KEY_LEN,
|
||||||
|
(uint8_t *)key->value)) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "pbkdf failed");
|
||||||
|
pw_free(key);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
return (key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
is_key_loaded(pam_handle_t *pamh, const char *ds_name)
|
||||||
|
{
|
||||||
|
zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
|
||||||
|
if (ds == NULL) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
int keystatus = zfs_prop_get_int(ds, ZFS_PROP_KEYSTATUS);
|
||||||
|
zfs_close(ds);
|
||||||
|
return (keystatus != ZFS_KEYSTATUS_UNAVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
change_key(pam_handle_t *pamh, const char *ds_name,
|
||||||
|
const char *passphrase)
|
||||||
|
{
|
||||||
|
zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
|
||||||
|
if (ds == NULL) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
nvlist_t *nvlist = fnvlist_alloc();
|
||||||
|
pw_password_t *key = prepare_passphrase(pamh, ds, passphrase, nvlist);
|
||||||
|
if (key == NULL) {
|
||||||
|
nvlist_free(nvlist);
|
||||||
|
zfs_close(ds);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
if (nvlist_add_string(nvlist,
|
||||||
|
zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
|
||||||
|
"prompt")) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "nvlist_add failed for keylocation");
|
||||||
|
pw_free(key);
|
||||||
|
nvlist_free(nvlist);
|
||||||
|
zfs_close(ds);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
if (nvlist_add_uint64(nvlist,
|
||||||
|
zfs_prop_to_name(ZFS_PROP_KEYFORMAT),
|
||||||
|
ZFS_KEYFORMAT_PASSPHRASE)) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "nvlist_add failed for keyformat");
|
||||||
|
pw_free(key);
|
||||||
|
nvlist_free(nvlist);
|
||||||
|
zfs_close(ds);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
int ret = lzc_change_key(ds_name, DCP_CMD_NEW_KEY, nvlist,
|
||||||
|
(uint8_t *)key->value, WRAPPING_KEY_LEN);
|
||||||
|
pw_free(key);
|
||||||
|
if (ret) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "change_key failed: %d", ret);
|
||||||
|
nvlist_free(nvlist);
|
||||||
|
zfs_close(ds);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
nvlist_free(nvlist);
|
||||||
|
zfs_close(ds);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
decrypt_mount(pam_handle_t *pamh, const char *ds_name,
|
||||||
|
const char *passphrase)
|
||||||
|
{
|
||||||
|
zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
|
||||||
|
if (ds == NULL) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
pw_password_t *key = prepare_passphrase(pamh, ds, passphrase, NULL);
|
||||||
|
if (key == NULL) {
|
||||||
|
zfs_close(ds);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
int ret = lzc_load_key(ds_name, B_FALSE, (uint8_t *)key->value,
|
||||||
|
WRAPPING_KEY_LEN);
|
||||||
|
pw_free(key);
|
||||||
|
if (ret) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "load_key failed: %d", ret);
|
||||||
|
zfs_close(ds);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
ret = zfs_mount(ds, NULL, 0);
|
||||||
|
if (ret) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "mount failed: %d", ret);
|
||||||
|
zfs_close(ds);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
zfs_close(ds);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
unmount_unload(pam_handle_t *pamh, const char *ds_name)
|
||||||
|
{
|
||||||
|
zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
|
||||||
|
if (ds == NULL) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
int ret = zfs_unmount(ds, NULL, 0);
|
||||||
|
if (ret) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "zfs_unmount failed with: %d", ret);
|
||||||
|
zfs_close(ds);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = lzc_unload_key(ds_name);
|
||||||
|
if (ret) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "unload_key failed with: %d", ret);
|
||||||
|
zfs_close(ds);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
zfs_close(ds);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *homes_prefix;
|
||||||
|
char *runstatedir;
|
||||||
|
uid_t uid;
|
||||||
|
const char *username;
|
||||||
|
int unmount_and_unload;
|
||||||
|
} zfs_key_config_t;
|
||||||
|
|
||||||
|
static int
|
||||||
|
zfs_key_config_load(pam_handle_t *pamh, zfs_key_config_t *config,
|
||||||
|
int argc, const char **argv)
|
||||||
|
{
|
||||||
|
config->homes_prefix = strdup("rpool/home");
|
||||||
|
if (config->homes_prefix == NULL) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "strdup failure");
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
config->runstatedir = strdup(RUNSTATEDIR "/pam_zfs_key");
|
||||||
|
if (config->runstatedir == NULL) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "strdup failure");
|
||||||
|
free(config->homes_prefix);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
const char *name;
|
||||||
|
if (pam_get_user(pamh, &name, NULL) != PAM_SUCCESS) {
|
||||||
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"couldn't get username from PAM stack");
|
||||||
|
free(config->runstatedir);
|
||||||
|
free(config->homes_prefix);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
struct passwd *entry = getpwnam(name);
|
||||||
|
if (!entry) {
|
||||||
|
free(config->runstatedir);
|
||||||
|
free(config->homes_prefix);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
config->uid = entry->pw_uid;
|
||||||
|
config->username = name;
|
||||||
|
config->unmount_and_unload = 1;
|
||||||
|
for (int c = 0; c < argc; c++) {
|
||||||
|
if (strncmp(argv[c], "homes=", 6) == 0) {
|
||||||
|
free(config->homes_prefix);
|
||||||
|
config->homes_prefix = strdup(argv[c] + 6);
|
||||||
|
} else if (strncmp(argv[c], "runstatedir=", 12) == 0) {
|
||||||
|
free(config->runstatedir);
|
||||||
|
config->runstatedir = strdup(argv[c] + 12);
|
||||||
|
} else if (strcmp(argv[c], "nounmount") == 0) {
|
||||||
|
config->unmount_and_unload = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
zfs_key_config_free(zfs_key_config_t *config)
|
||||||
|
{
|
||||||
|
free(config->homes_prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
zfs_key_config_get_dataset(zfs_key_config_t *config)
|
||||||
|
{
|
||||||
|
size_t len = ZFS_MAX_DATASET_NAME_LEN;
|
||||||
|
size_t total_len = strlen(config->homes_prefix) + 1
|
||||||
|
+ strlen(config->username);
|
||||||
|
if (total_len > len) {
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
char *ret = malloc(len + 1);
|
||||||
|
if (!ret) {
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
ret[0] = 0;
|
||||||
|
strcat(ret, config->homes_prefix);
|
||||||
|
strcat(ret, "/");
|
||||||
|
strcat(ret, config->username);
|
||||||
|
return (ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
zfs_key_config_modify_session_counter(pam_handle_t *pamh,
|
||||||
|
zfs_key_config_t *config, int delta)
|
||||||
|
{
|
||||||
|
const char *runtime_path = config->runstatedir;
|
||||||
|
if (mkdir(runtime_path, S_IRWXU) != 0 && errno != EEXIST) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "Can't create runtime path: %d",
|
||||||
|
errno);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
if (chown(runtime_path, 0, 0) != 0) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "Can't chown runtime path: %d",
|
||||||
|
errno);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
if (chmod(runtime_path, S_IRWXU) != 0) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "Can't chmod runtime path: %d",
|
||||||
|
errno);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
size_t runtime_path_len = strlen(runtime_path);
|
||||||
|
size_t counter_path_len = runtime_path_len + 1 + 10;
|
||||||
|
char *counter_path = malloc(counter_path_len + 1);
|
||||||
|
if (!counter_path) {
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
counter_path[0] = 0;
|
||||||
|
strcat(counter_path, runtime_path);
|
||||||
|
snprintf(counter_path + runtime_path_len, counter_path_len, "/%d",
|
||||||
|
config->uid);
|
||||||
|
const int fd = open(counter_path,
|
||||||
|
O_RDWR | O_CLOEXEC | O_CREAT | O_NOFOLLOW,
|
||||||
|
S_IRUSR | S_IWUSR);
|
||||||
|
free(counter_path);
|
||||||
|
if (fd < 0) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "Can't open counter file: %d", errno);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
if (flock(fd, LOCK_EX) != 0) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "Can't lock counter file: %d", errno);
|
||||||
|
close(fd);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
char counter[20];
|
||||||
|
char *pos = counter;
|
||||||
|
int remaining = sizeof (counter) - 1;
|
||||||
|
int ret;
|
||||||
|
counter[sizeof (counter) - 1] = 0;
|
||||||
|
while (remaining > 0 && (ret = read(fd, pos, remaining)) > 0) {
|
||||||
|
remaining -= ret;
|
||||||
|
pos += ret;
|
||||||
|
}
|
||||||
|
*pos = 0;
|
||||||
|
long int counter_value = strtol(counter, NULL, 10);
|
||||||
|
counter_value += delta;
|
||||||
|
if (counter_value < 0) {
|
||||||
|
counter_value = 0;
|
||||||
|
}
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
if (ftruncate(fd, 0) != 0) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "Can't truncate counter file: %d",
|
||||||
|
errno);
|
||||||
|
close(fd);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
snprintf(counter, sizeof (counter), "%ld", counter_value);
|
||||||
|
remaining = strlen(counter);
|
||||||
|
pos = counter;
|
||||||
|
while (remaining > 0 && (ret = write(fd, pos, remaining)) > 0) {
|
||||||
|
remaining -= ret;
|
||||||
|
pos += ret;
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
return (counter_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((visibility("default")))
|
||||||
|
PAM_EXTERN int
|
||||||
|
pam_sm_authenticate(pam_handle_t *pamh, int flags,
|
||||||
|
int argc, const char **argv)
|
||||||
|
{
|
||||||
|
if (pw_fetch_lazy(pamh) == NULL) {
|
||||||
|
return (PAM_AUTH_ERR);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((visibility("default")))
|
||||||
|
PAM_EXTERN int
|
||||||
|
pam_sm_setcred(pam_handle_t *pamh, int flags,
|
||||||
|
int argc, const char **argv)
|
||||||
|
{
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((visibility("default")))
|
||||||
|
PAM_EXTERN int
|
||||||
|
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
|
||||||
|
int argc, const char **argv)
|
||||||
|
{
|
||||||
|
if (geteuid() != 0) {
|
||||||
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"Cannot zfs_mount when not being root.");
|
||||||
|
return (PAM_PERM_DENIED);
|
||||||
|
}
|
||||||
|
zfs_key_config_t config;
|
||||||
|
if (zfs_key_config_load(pamh, &config, argc, argv) == -1) {
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
if (config.uid < 1000) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (pam_zfs_init(pamh) != 0) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
char *dataset = zfs_key_config_get_dataset(&config);
|
||||||
|
if (!dataset) {
|
||||||
|
pam_zfs_free();
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
int key_loaded = is_key_loaded(pamh, dataset);
|
||||||
|
if (key_loaded == -1) {
|
||||||
|
free(dataset);
|
||||||
|
pam_zfs_free();
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
free(dataset);
|
||||||
|
pam_zfs_free();
|
||||||
|
if (! key_loaded) {
|
||||||
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"key not loaded, returning try_again");
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_PERM_DENIED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
|
||||||
|
const pw_password_t *token = pw_get(pamh);
|
||||||
|
if (token == NULL) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
if (pam_zfs_init(pamh) != 0) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
char *dataset = zfs_key_config_get_dataset(&config);
|
||||||
|
if (!dataset) {
|
||||||
|
pam_zfs_free();
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
if (change_key(pamh, dataset, token->value) == -1) {
|
||||||
|
free(dataset);
|
||||||
|
pam_zfs_free();
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
free(dataset);
|
||||||
|
pam_zfs_free();
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
if (pw_clear(pamh) == -1) {
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
}
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
PAM_EXTERN int
|
||||||
|
pam_sm_open_session(pam_handle_t *pamh, int flags,
|
||||||
|
int argc, const char **argv)
|
||||||
|
{
|
||||||
|
if (geteuid() != 0) {
|
||||||
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"Cannot zfs_mount when not being root.");
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
zfs_key_config_t config;
|
||||||
|
zfs_key_config_load(pamh, &config, argc, argv);
|
||||||
|
if (config.uid < 1000) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter = zfs_key_config_modify_session_counter(pamh, &config, 1);
|
||||||
|
if (counter != 1) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pw_password_t *token = pw_get(pamh);
|
||||||
|
if (token == NULL) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SESSION_ERR);
|
||||||
|
}
|
||||||
|
if (pam_zfs_init(pamh) != 0) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
char *dataset = zfs_key_config_get_dataset(&config);
|
||||||
|
if (!dataset) {
|
||||||
|
pam_zfs_free();
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
if (decrypt_mount(pamh, dataset, token->value) == -1) {
|
||||||
|
free(dataset);
|
||||||
|
pam_zfs_free();
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
free(dataset);
|
||||||
|
pam_zfs_free();
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
if (pw_clear(pamh) == -1) {
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((visibility("default")))
|
||||||
|
PAM_EXTERN int
|
||||||
|
pam_sm_close_session(pam_handle_t *pamh, int flags,
|
||||||
|
int argc, const char **argv)
|
||||||
|
{
|
||||||
|
if (geteuid() != 0) {
|
||||||
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"Cannot zfs_mount when not being root.");
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
zfs_key_config_t config;
|
||||||
|
zfs_key_config_load(pamh, &config, argc, argv);
|
||||||
|
if (config.uid < 1000) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter = zfs_key_config_modify_session_counter(pamh, &config, -1);
|
||||||
|
if (counter != 0) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.unmount_and_unload) {
|
||||||
|
if (pam_zfs_init(pamh) != 0) {
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SERVICE_ERR);
|
||||||
|
}
|
||||||
|
char *dataset = zfs_key_config_get_dataset(&config);
|
||||||
|
if (!dataset) {
|
||||||
|
pam_zfs_free();
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SESSION_ERR);
|
||||||
|
}
|
||||||
|
if (unmount_unload(pamh, dataset) == -1) {
|
||||||
|
free(dataset);
|
||||||
|
pam_zfs_free();
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SESSION_ERR);
|
||||||
|
}
|
||||||
|
free(dataset);
|
||||||
|
pam_zfs_free();
|
||||||
|
}
|
||||||
|
|
||||||
|
zfs_key_config_free(&config);
|
||||||
|
return (PAM_SUCCESS);
|
||||||
|
}
|
13
contrib/pam_zfs_key/zfs_key
Normal file
13
contrib/pam_zfs_key/zfs_key
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Name: Unlock zfs datasets for user
|
||||||
|
Default: yes
|
||||||
|
Priority: 128
|
||||||
|
Auth-Type: Additional
|
||||||
|
Auth:
|
||||||
|
optional pam_zfs_key.so
|
||||||
|
Session-Interactive-Only: yes
|
||||||
|
Session-Type: Additional
|
||||||
|
Session:
|
||||||
|
optional pam_zfs_key.so
|
||||||
|
Password-Type: Additional
|
||||||
|
Password:
|
||||||
|
optional pam_zfs_key.so
|
@ -52,6 +52,7 @@
|
|||||||
%bcond_with debuginfo
|
%bcond_with debuginfo
|
||||||
%bcond_with asan
|
%bcond_with asan
|
||||||
%bcond_with systemd
|
%bcond_with systemd
|
||||||
|
%bcond_with pam
|
||||||
|
|
||||||
# Generic enable switch for systemd
|
# Generic enable switch for systemd
|
||||||
%if %{with systemd}
|
%if %{with systemd}
|
||||||
@ -329,6 +330,12 @@ image which is ZFS aware.
|
|||||||
%define pyzfs --disable-pyzfs
|
%define pyzfs --disable-pyzfs
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
|
%if %{with pam}
|
||||||
|
%define pam --enable-pam
|
||||||
|
%else
|
||||||
|
%define pam --disable-pam
|
||||||
|
%endif
|
||||||
|
|
||||||
%setup -q
|
%setup -q
|
||||||
|
|
||||||
%build
|
%build
|
||||||
@ -342,7 +349,8 @@ image which is ZFS aware.
|
|||||||
%{debug} \
|
%{debug} \
|
||||||
%{debuginfo} \
|
%{debuginfo} \
|
||||||
%{asan} \
|
%{asan} \
|
||||||
%{systemd}\
|
%{systemd} \
|
||||||
|
--with-pammoduledir=%{_libdir}/security %{pam} \
|
||||||
%{pyzfs}
|
%{pyzfs}
|
||||||
make %{?_smp_mflags}
|
make %{?_smp_mflags}
|
||||||
|
|
||||||
@ -457,6 +465,10 @@ systemctl --system daemon-reload >/dev/null || true
|
|||||||
%config(noreplace) %{_sysconfdir}/%{name}/zpool.d/*
|
%config(noreplace) %{_sysconfdir}/%{name}/zpool.d/*
|
||||||
%config(noreplace) %{_sysconfdir}/%{name}/vdev_id.conf.*.example
|
%config(noreplace) %{_sysconfdir}/%{name}/vdev_id.conf.*.example
|
||||||
%attr(440, root, root) %config(noreplace) %{_sysconfdir}/sudoers.d/*
|
%attr(440, root, root) %config(noreplace) %{_sysconfdir}/sudoers.d/*
|
||||||
|
%if %{with pam}
|
||||||
|
%{_libdir}/security/*
|
||||||
|
%{_pamconfigsdir}/*
|
||||||
|
%endif
|
||||||
|
|
||||||
%files -n libzpool2
|
%files -n libzpool2
|
||||||
%{_libdir}/libzpool.so.*
|
%{_libdir}/libzpool.so.*
|
||||||
|
@ -128,6 +128,10 @@ tags = ['functional', 'mmp']
|
|||||||
tests = ['umount_unlinked_drain']
|
tests = ['umount_unlinked_drain']
|
||||||
tags = ['functional', 'mount']
|
tags = ['functional', 'mount']
|
||||||
|
|
||||||
|
[tests/functional/pam:Linux]
|
||||||
|
tests = ['pam_basic', 'pam_nounmount']
|
||||||
|
tags = ['functional', 'pam']
|
||||||
|
|
||||||
[tests/functional/procfs:Linux]
|
[tests/functional/procfs:Linux]
|
||||||
tests = ['procfs_list_basic', 'procfs_list_concurrent_readers',
|
tests = ['procfs_list_basic', 'procfs_list_concurrent_readers',
|
||||||
'procfs_list_stale_read', 'pool_state']
|
'procfs_list_stale_read', 'pool_state']
|
||||||
|
@ -239,6 +239,7 @@ maybe = {
|
|||||||
'userquota/setup': ['SKIP', exec_reason],
|
'userquota/setup': ['SKIP', exec_reason],
|
||||||
'vdev_zaps/vdev_zaps_004_pos': ['FAIL', '6935'],
|
'vdev_zaps/vdev_zaps_004_pos': ['FAIL', '6935'],
|
||||||
'zvol/zvol_ENOSPC/zvol_ENOSPC_001_pos': ['FAIL', '5848'],
|
'zvol/zvol_ENOSPC/zvol_ENOSPC_001_pos': ['FAIL', '5848'],
|
||||||
|
'pam/setup': ['SKIP', "pamtester might be not available"],
|
||||||
}
|
}
|
||||||
|
|
||||||
if sys.platform.startswith('freebsd'):
|
if sys.platform.startswith('freebsd'):
|
||||||
|
@ -61,6 +61,7 @@ export SYSTEM_FILES_COMMON='arp
|
|||||||
net
|
net
|
||||||
od
|
od
|
||||||
openssl
|
openssl
|
||||||
|
pamtester
|
||||||
pax
|
pax
|
||||||
pgrep
|
pgrep
|
||||||
ping
|
ping
|
||||||
|
@ -46,6 +46,7 @@ SUBDIRS = \
|
|||||||
no_space \
|
no_space \
|
||||||
nopwrite \
|
nopwrite \
|
||||||
online_offline \
|
online_offline \
|
||||||
|
pam \
|
||||||
persist_l2arc \
|
persist_l2arc \
|
||||||
pool_checkpoint \
|
pool_checkpoint \
|
||||||
pool_names \
|
pool_names \
|
||||||
|
7
tests/zfs-tests/tests/functional/pam/Makefile.am
Normal file
7
tests/zfs-tests/tests/functional/pam/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/pam
|
||||||
|
dist_pkgdata_SCRIPTS = \
|
||||||
|
setup.ksh \
|
||||||
|
cleanup.ksh \
|
||||||
|
pam_basic.ksh \
|
||||||
|
pam_nounmount.ksh \
|
||||||
|
utilities.kshlib
|
32
tests/zfs-tests/tests/functional/pam/cleanup.ksh
Executable file
32
tests/zfs-tests/tests/functional/pam/cleanup.ksh
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/ksh -p
|
||||||
|
#
|
||||||
|
# CDDL HEADER START
|
||||||
|
#
|
||||||
|
# The contents of this file are subject to the terms of the
|
||||||
|
# Common Development and Distribution License (the "License").
|
||||||
|
# You may not use this file except in compliance with the License.
|
||||||
|
#
|
||||||
|
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||||
|
# or http://www.opensolaris.org/os/licensing.
|
||||||
|
# See the License for the specific language governing permissions
|
||||||
|
# and limitations under the License.
|
||||||
|
#
|
||||||
|
# When distributing Covered Code, include this CDDL HEADER in each
|
||||||
|
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||||
|
# If applicable, add the following below this CDDL HEADER, with the
|
||||||
|
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||||
|
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||||
|
#
|
||||||
|
# CDDL HEADER END
|
||||||
|
#
|
||||||
|
|
||||||
|
. $STF_SUITE/tests/functional/pam/utilities.kshlib
|
||||||
|
|
||||||
|
destroy_pool $TESTPOOL
|
||||||
|
del_user ${username}
|
||||||
|
del_group pamtestgroup
|
||||||
|
|
||||||
|
rm -rf "$runstatedir"
|
||||||
|
for dir in $TESTDIRS; do
|
||||||
|
rm -rf $dir
|
||||||
|
done
|
49
tests/zfs-tests/tests/functional/pam/pam_basic.ksh
Executable file
49
tests/zfs-tests/tests/functional/pam/pam_basic.ksh
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/ksh -p
|
||||||
|
#
|
||||||
|
# CDDL HEADER START
|
||||||
|
#
|
||||||
|
# The contents of this file are subject to the terms of the
|
||||||
|
# Common Development and Distribution License (the "License").
|
||||||
|
# You may not use this file except in compliance with the License.
|
||||||
|
#
|
||||||
|
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||||
|
# or http://www.opensolaris.org/os/licensing.
|
||||||
|
# See the License for the specific language governing permissions
|
||||||
|
# and limitations under the License.
|
||||||
|
#
|
||||||
|
# When distributing Covered Code, include this CDDL HEADER in each
|
||||||
|
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||||
|
# If applicable, add the following below this CDDL HEADER, with the
|
||||||
|
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||||
|
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||||
|
#
|
||||||
|
# CDDL HEADER END
|
||||||
|
#
|
||||||
|
|
||||||
|
. $STF_SUITE/tests/functional/pam/utilities.kshlib
|
||||||
|
|
||||||
|
log_mustnot ismounted "$TESTPOOL/pam/${username}"
|
||||||
|
keystatus unavailable
|
||||||
|
|
||||||
|
genconfig "homes=$TESTPOOL/pam runstatedir=${runstatedir}"
|
||||||
|
echo "testpass" | pamtester pam_zfs_key_test ${username} open_session
|
||||||
|
references 1
|
||||||
|
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||||
|
keystatus available
|
||||||
|
|
||||||
|
echo "testpass" | pamtester pam_zfs_key_test ${username} open_session
|
||||||
|
references 2
|
||||||
|
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||||
|
keystatus available
|
||||||
|
|
||||||
|
log_must pamtester pam_zfs_key_test ${username} close_session
|
||||||
|
references 1
|
||||||
|
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||||
|
keystatus available
|
||||||
|
|
||||||
|
log_must pamtester pam_zfs_key_test ${username} close_session
|
||||||
|
references 0
|
||||||
|
log_mustnot ismounted "$TESTPOOL/pam/${username}"
|
||||||
|
keystatus unavailable
|
||||||
|
|
||||||
|
log_pass "done."
|
51
tests/zfs-tests/tests/functional/pam/pam_nounmount.ksh
Executable file
51
tests/zfs-tests/tests/functional/pam/pam_nounmount.ksh
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/ksh -p
|
||||||
|
#
|
||||||
|
# CDDL HEADER START
|
||||||
|
#
|
||||||
|
# The contents of this file are subject to the terms of the
|
||||||
|
# Common Development and Distribution License (the "License").
|
||||||
|
# You may not use this file except in compliance with the License.
|
||||||
|
#
|
||||||
|
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||||
|
# or http://www.opensolaris.org/os/licensing.
|
||||||
|
# See the License for the specific language governing permissions
|
||||||
|
# and limitations under the License.
|
||||||
|
#
|
||||||
|
# When distributing Covered Code, include this CDDL HEADER in each
|
||||||
|
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||||
|
# If applicable, add the following below this CDDL HEADER, with the
|
||||||
|
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||||
|
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||||
|
#
|
||||||
|
# CDDL HEADER END
|
||||||
|
#
|
||||||
|
|
||||||
|
. $STF_SUITE/tests/functional/pam/utilities.kshlib
|
||||||
|
|
||||||
|
log_mustnot ismounted "$TESTPOOL/pam/${username}"
|
||||||
|
keystatus unavailable
|
||||||
|
|
||||||
|
genconfig "homes=$TESTPOOL/pam runstatedir=${runstatedir} nounmount"
|
||||||
|
echo "testpass" | pamtester pam_zfs_key_test ${username} open_session
|
||||||
|
references 1
|
||||||
|
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||||
|
keystatus available
|
||||||
|
|
||||||
|
echo "testpass" | pamtester pam_zfs_key_test ${username} open_session
|
||||||
|
references 2
|
||||||
|
keystatus available
|
||||||
|
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||||
|
|
||||||
|
log_must pamtester pam_zfs_key_test ${username} close_session
|
||||||
|
references 1
|
||||||
|
keystatus available
|
||||||
|
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||||
|
|
||||||
|
log_must pamtester pam_zfs_key_test ${username} close_session
|
||||||
|
references 0
|
||||||
|
keystatus available
|
||||||
|
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||||
|
log_must zfs unmount "$TESTPOOL/pam/${username}"
|
||||||
|
log_must zfs unload-key "$TESTPOOL/pam/${username}"
|
||||||
|
|
||||||
|
log_pass "done."
|
41
tests/zfs-tests/tests/functional/pam/setup.ksh
Executable file
41
tests/zfs-tests/tests/functional/pam/setup.ksh
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/ksh -p
|
||||||
|
#
|
||||||
|
# CDDL HEADER START
|
||||||
|
#
|
||||||
|
# The contents of this file are subject to the terms of the
|
||||||
|
# Common Development and Distribution License (the "License").
|
||||||
|
# You may not use this file except in compliance with the License.
|
||||||
|
#
|
||||||
|
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||||
|
# or http://www.opensolaris.org/os/licensing.
|
||||||
|
# See the License for the specific language governing permissions
|
||||||
|
# and limitations under the License.
|
||||||
|
#
|
||||||
|
# When distributing Covered Code, include this CDDL HEADER in each
|
||||||
|
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||||
|
# If applicable, add the following below this CDDL HEADER, with the
|
||||||
|
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||||
|
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||||
|
#
|
||||||
|
# CDDL HEADER END
|
||||||
|
#
|
||||||
|
|
||||||
|
. $STF_SUITE/tests/functional/pam/utilities.kshlib
|
||||||
|
|
||||||
|
if ! which pamtester; then
|
||||||
|
log_unsupported "pam tests require the pamtester utility to be installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISK=${DISKS%% *}
|
||||||
|
create_pool $TESTPOOL "$DISK"
|
||||||
|
|
||||||
|
log_must zfs create -o mountpoint="$TESTDIR" "$TESTPOOL/pam"
|
||||||
|
log_must add_group pamtestgroup
|
||||||
|
log_must add_user pamtestgroup ${username}
|
||||||
|
log_must mkdir -p "$runstatedir"
|
||||||
|
|
||||||
|
echo "testpass" | zfs create -o encryption=aes-256-gcm -o keyformat=passphrase -o keylocation=prompt "$TESTPOOL/pam/${username}"
|
||||||
|
log_must zfs unmount "$TESTPOOL/pam/${username}"
|
||||||
|
log_must zfs unload-key "$TESTPOOL/pam/${username}"
|
||||||
|
|
||||||
|
log_pass
|
40
tests/zfs-tests/tests/functional/pam/utilities.kshlib
Normal file
40
tests/zfs-tests/tests/functional/pam/utilities.kshlib
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/ksh -p
|
||||||
|
#
|
||||||
|
# CDDL HEADER START
|
||||||
|
#
|
||||||
|
# The contents of this file are subject to the terms of the
|
||||||
|
# Common Development and Distribution License (the "License").
|
||||||
|
# You may not use this file except in compliance with the License.
|
||||||
|
#
|
||||||
|
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||||
|
# or http://www.opensolaris.org/os/licensing.
|
||||||
|
# See the License for the specific language governing permissions
|
||||||
|
# and limitations under the License.
|
||||||
|
#
|
||||||
|
# When distributing Covered Code, include this CDDL HEADER in each
|
||||||
|
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||||
|
# If applicable, add the following below this CDDL HEADER, with the
|
||||||
|
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||||
|
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||||
|
#
|
||||||
|
# CDDL HEADER END
|
||||||
|
#
|
||||||
|
|
||||||
|
. $STF_SUITE/include/libtest.shlib
|
||||||
|
|
||||||
|
username="pamTestuser"
|
||||||
|
runstatedir="${TESTDIR}_run"
|
||||||
|
function keystatus {
|
||||||
|
log_must [ "$(zfs list -Ho keystatus "$TESTPOOL/pam/${username}")" == "$1" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
function genconfig {
|
||||||
|
for i in password auth session; do
|
||||||
|
printf "%s\trequired\tpam_permit.so\n%s\toptional\tpam_zfs_key.so\t%s\n" "$i" "$i" "$1"
|
||||||
|
done > /etc/pam.d/pam_zfs_key_test
|
||||||
|
}
|
||||||
|
|
||||||
|
function references {
|
||||||
|
log_must [ "$(cat "${runstatedir}/$(id -u ${username})")" == "$1" ]
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user