mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-03-11 04:46:18 +03:00
Added support for multiple homes in pam_zfs_key module (#18084)
This implemented support for having multiple datasets unlocked and mounted when a session is opened. Example: `homes=rpool/home,tank/users` Extra unit tests have been added A man page documents have been added `man 8 pam_zfs_key`. A few references to the new man page have also been added in other documents. Signed-off-by: Dennis Vestergaard Værum <github@varum.dk> Reviewed-by: Tony Hutter <hutter2@llnl.gov> Reviewed-by: Tino Reichardt <milky-zfs@mcmilk.de>
This commit is contained in:
parent
7e33476a7c
commit
07ae463d1a
@ -1,2 +1,3 @@
|
||||
usr/lib/*/security/pam_zfs_key.so
|
||||
usr/share/man/man8/pam_zfs_key.8
|
||||
usr/share/pam-configs/zfs_key
|
||||
|
||||
@ -754,6 +754,82 @@ zfs_key_config_get_dataset(pam_handle_t *pamh, zfs_key_config_t *config)
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback type for foreach_dataset.
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
typedef int (*dataset_callback_t)(pam_handle_t *, zfs_key_config_t *,
|
||||
const char *, void *);
|
||||
|
||||
/*
|
||||
* Iterate over comma-separated homes prefixes and call callback for each
|
||||
* existing dataset. Returns number of successful callbacks, or -1 if none
|
||||
* succeeded.
|
||||
*/
|
||||
static int
|
||||
foreach_dataset(pam_handle_t *pamh, zfs_key_config_t *config,
|
||||
dataset_callback_t callback, void *data)
|
||||
{
|
||||
if (config->homes_prefix == NULL)
|
||||
return (-1);
|
||||
|
||||
/* Check if this is a comma-separated list */
|
||||
if (strchr(config->homes_prefix, ',') == NULL) {
|
||||
/* Single home - use existing logic */
|
||||
char *dataset = zfs_key_config_get_dataset(pamh, config);
|
||||
if (dataset == NULL)
|
||||
return (-1);
|
||||
int ret = callback(pamh, config, dataset, data);
|
||||
free(dataset);
|
||||
return (ret == 0 ? 1 : -1);
|
||||
}
|
||||
|
||||
/* Multiple homes - parse and iterate */
|
||||
pam_syslog(pamh, LOG_DEBUG,
|
||||
"processing multiple home prefixes: %s", config->homes_prefix);
|
||||
|
||||
char *homes_copy = strdup(config->homes_prefix);
|
||||
if (homes_copy == NULL)
|
||||
return (-1);
|
||||
|
||||
char *saved_prefix = config->homes_prefix;
|
||||
char *saveptr;
|
||||
char *token = strtok_r(homes_copy, ",", &saveptr);
|
||||
int success_count = 0;
|
||||
boolean_t failed = B_FALSE;
|
||||
|
||||
while (token != NULL) {
|
||||
/* Temporarily set homes_prefix to this single prefix */
|
||||
config->homes_prefix = token;
|
||||
char *dataset = zfs_key_config_get_dataset(pamh, config);
|
||||
if (dataset != NULL) {
|
||||
pam_syslog(pamh, LOG_DEBUG,
|
||||
"processing dataset '%s' for prefix '%s'",
|
||||
dataset, token);
|
||||
if (callback(pamh, config, dataset, data) == 0) {
|
||||
success_count++;
|
||||
} else {
|
||||
failed = B_TRUE;
|
||||
pam_syslog(pamh, LOG_WARNING,
|
||||
"operation failed for dataset '%s'",
|
||||
dataset);
|
||||
}
|
||||
free(dataset);
|
||||
} else {
|
||||
pam_syslog(pamh, LOG_DEBUG,
|
||||
"no dataset found for prefix '%s', skip", token);
|
||||
}
|
||||
token = strtok_r(NULL, ",", &saveptr);
|
||||
}
|
||||
|
||||
config->homes_prefix = saved_prefix;
|
||||
free(homes_copy);
|
||||
pam_syslog(pamh, LOG_DEBUG,
|
||||
"processed %d datasets, %s",
|
||||
success_count, failed ? "with failures" : "all successful");
|
||||
return (!failed && success_count > 0 ? success_count : -1);
|
||||
}
|
||||
|
||||
static int
|
||||
zfs_key_config_modify_session_counter(pam_handle_t *pamh,
|
||||
zfs_key_config_t *config, int delta)
|
||||
@ -825,6 +901,15 @@ zfs_key_config_modify_session_counter(pam_handle_t *pamh,
|
||||
return (counter_value);
|
||||
}
|
||||
|
||||
/* Callback for authentication - verify password works (noop mode) */
|
||||
static int
|
||||
auth_callback(pam_handle_t *pamh, zfs_key_config_t *config,
|
||||
const char *dataset, void *data)
|
||||
{
|
||||
const char *passphrase = data;
|
||||
return (decrypt_mount(pamh, config, dataset, passphrase, B_TRUE));
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
PAM_EXTERN int
|
||||
pam_sm_authenticate(pam_handle_t *pamh, int flags,
|
||||
@ -857,21 +942,14 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags,
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
char *dataset = zfs_key_config_get_dataset(pamh, &config);
|
||||
if (!dataset) {
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
if (decrypt_mount(pamh, &config, dataset, token->value, B_TRUE) == -1) {
|
||||
free(dataset);
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_AUTH_ERR);
|
||||
}
|
||||
free(dataset);
|
||||
|
||||
int ret = foreach_dataset(pamh, &config, auth_callback,
|
||||
(void *)token->value);
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
if (ret < 0) {
|
||||
return (PAM_AUTH_ERR);
|
||||
}
|
||||
return (PAM_SUCCESS);
|
||||
}
|
||||
|
||||
@ -884,6 +962,39 @@ pam_sm_setcred(pam_handle_t *pamh, int flags,
|
||||
return (PAM_SUCCESS);
|
||||
}
|
||||
|
||||
/* Context for password change callback */
|
||||
typedef struct {
|
||||
const char *old_pass;
|
||||
const char *new_pass;
|
||||
} chauthtok_ctx_t;
|
||||
|
||||
/* Callback for password change */
|
||||
static int
|
||||
chauthtok_callback(pam_handle_t *pamh, zfs_key_config_t *config,
|
||||
const char *dataset, void *data)
|
||||
{
|
||||
chauthtok_ctx_t *ctx = data;
|
||||
int was_loaded = is_key_loaded(pamh, dataset);
|
||||
if (!was_loaded) {
|
||||
int ret = decrypt_mount(pamh, config, dataset,
|
||||
ctx->old_pass, B_FALSE);
|
||||
if (ret == -1) {
|
||||
pam_syslog(pamh, LOG_ERR,
|
||||
"failed to load key for '%s' during "
|
||||
"password change", dataset);
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
int ret = change_key(pamh, dataset, ctx->new_pass);
|
||||
if (ret == -1) {
|
||||
pam_syslog(pamh, LOG_ERR,
|
||||
"failed to change key for dataset '%s'", dataset);
|
||||
}
|
||||
if (!was_loaded)
|
||||
unmount_unload(pamh, dataset, config);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
PAM_EXTERN int
|
||||
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
|
||||
@ -904,34 +1015,27 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
|
||||
}
|
||||
const pw_password_t *old_token = pw_get(pamh,
|
||||
PAM_OLDAUTHTOK, OLD_PASSWORD_VAR_NAME);
|
||||
{
|
||||
if (pam_zfs_init(pamh) != 0) {
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
char *dataset = zfs_key_config_get_dataset(pamh, &config);
|
||||
if (!dataset) {
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
if (!old_token) {
|
||||
pam_syslog(pamh, LOG_ERR,
|
||||
"old password from PAM stack is null");
|
||||
free(dataset);
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
if (decrypt_mount(pamh, &config, dataset,
|
||||
old_token->value, B_TRUE) == -1) {
|
||||
pam_syslog(pamh, LOG_ERR,
|
||||
"old token mismatch");
|
||||
free(dataset);
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_PERM_DENIED);
|
||||
}
|
||||
|
||||
if (!old_token) {
|
||||
pam_syslog(pamh, LOG_ERR,
|
||||
"old password from PAM stack is 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);
|
||||
}
|
||||
|
||||
/* First verify old password works for all datasets */
|
||||
int ret = foreach_dataset(pamh, &config, auth_callback,
|
||||
(void *)old_token->value);
|
||||
if (ret < 0) {
|
||||
pam_syslog(pamh, LOG_ERR, "old token mismatch");
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_PERM_DENIED);
|
||||
}
|
||||
|
||||
if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
|
||||
@ -944,41 +1048,51 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
|
||||
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
char *dataset = zfs_key_config_get_dataset(pamh, &config);
|
||||
if (!dataset) {
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
|
||||
pw_clear(pamh, PASSWORD_VAR_NAME);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
int was_loaded = is_key_loaded(pamh, dataset);
|
||||
if (!was_loaded && decrypt_mount(pamh, &config, dataset,
|
||||
old_token->value, B_FALSE) == -1) {
|
||||
free(dataset);
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
|
||||
pw_clear(pamh, PASSWORD_VAR_NAME);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
int changed = change_key(pamh, dataset, token->value);
|
||||
if (!was_loaded) {
|
||||
unmount_unload(pamh, dataset, &config);
|
||||
}
|
||||
free(dataset);
|
||||
|
||||
chauthtok_ctx_t ctx = {
|
||||
.old_pass = old_token->value,
|
||||
.new_pass = token->value
|
||||
};
|
||||
|
||||
ret = foreach_dataset(pamh, &config, chauthtok_callback, &ctx);
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
|
||||
if (ret < 0) {
|
||||
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
|
||||
pw_clear(pamh, PASSWORD_VAR_NAME);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
|
||||
if (pw_clear(pamh, OLD_PASSWORD_VAR_NAME) == -1 ||
|
||||
pw_clear(pamh, PASSWORD_VAR_NAME) == -1 || changed == -1) {
|
||||
pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
} else {
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
}
|
||||
return (PAM_SUCCESS);
|
||||
}
|
||||
|
||||
/* Callback for session open - decrypt and mount */
|
||||
static int
|
||||
open_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
|
||||
const char *dataset, void *data)
|
||||
{
|
||||
const char *passphrase = data;
|
||||
return (decrypt_mount(pamh, config, dataset, passphrase, B_FALSE));
|
||||
}
|
||||
|
||||
/* Callback for session close - unmount and unload */
|
||||
static int
|
||||
close_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
|
||||
const char *dataset, void *data)
|
||||
{
|
||||
(void) data;
|
||||
return (unmount_unload(pamh, dataset, config));
|
||||
}
|
||||
|
||||
PAM_EXTERN int
|
||||
pam_sm_open_session(pam_handle_t *pamh, int flags,
|
||||
int argc, const char **argv)
|
||||
@ -1016,22 +1130,15 @@ pam_sm_open_session(pam_handle_t *pamh, int flags,
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
char *dataset = zfs_key_config_get_dataset(pamh, &config);
|
||||
if (!dataset) {
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
if (decrypt_mount(pamh, &config, dataset,
|
||||
token->value, B_FALSE) == -1) {
|
||||
free(dataset);
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
free(dataset);
|
||||
|
||||
int ret = foreach_dataset(pamh, &config, open_session_callback,
|
||||
(void *)token->value);
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
|
||||
if (ret < 0) {
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
if (pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
@ -1071,20 +1178,15 @@ pam_sm_close_session(pam_handle_t *pamh, int flags,
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SERVICE_ERR);
|
||||
}
|
||||
char *dataset = zfs_key_config_get_dataset(pamh, &config);
|
||||
if (!dataset) {
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SESSION_ERR);
|
||||
}
|
||||
if (unmount_unload(pamh, dataset, &config) == -1) {
|
||||
free(dataset);
|
||||
pam_zfs_free();
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SESSION_ERR);
|
||||
}
|
||||
free(dataset);
|
||||
|
||||
int ret = foreach_dataset(pamh, &config,
|
||||
close_session_callback, NULL);
|
||||
pam_zfs_free();
|
||||
|
||||
if (ret < 0) {
|
||||
zfs_key_config_free(&config);
|
||||
return (PAM_SESSION_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
zfs_key_config_free(&config);
|
||||
|
||||
@ -112,6 +112,7 @@ endif
|
||||
|
||||
if BUILD_LINUX
|
||||
dist_man_MANS += \
|
||||
%D%/man8/pam_zfs_key.8 \
|
||||
%D%/man8/zfs-unzone.8 \
|
||||
%D%/man8/zfs-zone.8
|
||||
endif
|
||||
|
||||
221
man/man8/pam_zfs_key.8
Normal file
221
man/man8/pam_zfs_key.8
Normal file
@ -0,0 +1,221 @@
|
||||
.\" SPDX-License-Identifier: BSD-3-Clause
|
||||
.\"
|
||||
.\" Copyright (c) 2020, Felix Dörre
|
||||
.\" All rights reserved.
|
||||
.\"
|
||||
.Dd December 24, 2025
|
||||
.Dt PAM_ZFS_KEY 8
|
||||
.Os
|
||||
.
|
||||
.Sh NAME
|
||||
.Nm pam_zfs_key
|
||||
.Nd PAM module for ZFS encryption key management
|
||||
.Sh SYNOPSIS
|
||||
.Nm pam_zfs_key.so
|
||||
.Op Ar options
|
||||
.
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a PAM module that automatically manages encryption keys for ZFS
|
||||
datasets during user authentication and session management.
|
||||
When a user logs in, the module uses their password to unlock their encrypted
|
||||
home directory.
|
||||
When the last session closes, the module unmounts the dataset and unloads
|
||||
the key.
|
||||
.Pp
|
||||
The module tracks active sessions using reference counting to support multiple
|
||||
simultaneous logins from the same user.
|
||||
.Ss Multiple Home Prefixes
|
||||
When configured with multiple home prefixes, the module attempts operations
|
||||
on all matching datasets.
|
||||
Operations that succeed are not rolled back if others fail.
|
||||
The module returns success only if all operations succeed.
|
||||
.Pp
|
||||
For example, with datasets 1, 2, 3 where dataset 2 fails:
|
||||
.Bl -bullet -compact
|
||||
.It
|
||||
Auth/session: datasets 1 and 3 are unlocked and mounted, dataset 2 is not.
|
||||
.It
|
||||
Password change: datasets 1 and 3 have the new password,
|
||||
dataset 2 retains the old.
|
||||
.El
|
||||
.Pp
|
||||
With
|
||||
.Sy required ,
|
||||
login fails even though datasets 1 and 3 succeeded.
|
||||
With
|
||||
.Sy optional ,
|
||||
login proceeds.
|
||||
For password changes, datasets 1 and 3 are updated while dataset 2
|
||||
retains the old password.
|
||||
With
|
||||
.Sy required ,
|
||||
the user sees an error.
|
||||
With
|
||||
.Sy optional ,
|
||||
the user sees success and may not notice the inconsistency.
|
||||
Either way, passwords are left out of sync.
|
||||
.Pp
|
||||
Errors are logged to syslog.
|
||||
Use
|
||||
.Xr zfs-change-key 8
|
||||
to resync passwords after partial failure.
|
||||
.
|
||||
.Sh OPTIONS
|
||||
.Bl -tag -width "mount_recursively"
|
||||
.It Sy homes Ns = Ns Ar path Ns Oo , Ns Ar path2 Ns ... Oc
|
||||
Comma-separated list of dataset prefixes where user home directories
|
||||
are located.
|
||||
The module constructs the full dataset path as
|
||||
.Ar prefix Ns / Ns Ar username .
|
||||
Default:
|
||||
.Sy zroot/home
|
||||
on
|
||||
.Fx ,
|
||||
.Sy rpool/home
|
||||
on Linux.
|
||||
.It Sy runstatedir Ns = Ns Ar path
|
||||
Directory for storing session reference counts.
|
||||
Default:
|
||||
.Pa /var/run/pam_zfs_key .
|
||||
.It Sy uid_min Ns = Ns Ar uid
|
||||
Minimum user ID for which the module will operate.
|
||||
Default: 1000.
|
||||
.It Sy uid_max Ns = Ns Ar uid
|
||||
Maximum user ID for which the module will operate.
|
||||
Default: MAXUID.
|
||||
.It Sy nounmount
|
||||
Do not unmount datasets or unload encryption keys when sessions close.
|
||||
Datasets remain mounted and keys remain loaded.
|
||||
.It Sy forceunmount
|
||||
Force unmount datasets even if busy
|
||||
.Pq Dv MS_FORCE .
|
||||
.It Sy recursive_homes
|
||||
Recursively search for encrypted datasets under the homes prefix.
|
||||
.It Sy mount_recursively
|
||||
Mount and unmount child datasets recursively.
|
||||
.It Sy prop_mountpoint
|
||||
Find the user's dataset by matching the dataset's
|
||||
.Sy mountpoint
|
||||
property to the user's home directory from
|
||||
.Pa /etc/passwd ,
|
||||
instead of constructing the dataset name as
|
||||
.Ar prefix Ns / Ns Ar username .
|
||||
.El
|
||||
.
|
||||
.Sh FILES
|
||||
.Bl -tag -width Pa
|
||||
.It Pa /var/run/pam_zfs_key/ Ns Ar uid
|
||||
Session reference count files tracking active logins per user.
|
||||
.El
|
||||
.
|
||||
.Sh EXAMPLES
|
||||
.Ss Example 1: Basic Configuration
|
||||
Add to
|
||||
.Pa /etc/pam.d/system-auth :
|
||||
.Bd -literal -offset indent
|
||||
auth optional pam_zfs_key.so
|
||||
password optional pam_zfs_key.so
|
||||
session optional pam_zfs_key.so
|
||||
.Ed
|
||||
.Pp
|
||||
This configuration uses default settings.
|
||||
User home datasets are expected at
|
||||
.Sy zroot/home/ Ns Ar username
|
||||
on
|
||||
.Fx
|
||||
or
|
||||
.Sy rpool/home/ Ns Ar username
|
||||
on Linux.
|
||||
.
|
||||
.Ss Example 2: Custom Home Directory Prefix
|
||||
.Bd -literal -offset indent
|
||||
auth optional pam_zfs_key.so homes=tank/users
|
||||
password optional pam_zfs_key.so homes=tank/users
|
||||
session optional pam_zfs_key.so homes=tank/users
|
||||
.Ed
|
||||
.Pp
|
||||
Looks for user datasets at
|
||||
.Sy tank/users/ Ns Ar username .
|
||||
.
|
||||
.Ss Example 3: Multiple Dataset Prefixes
|
||||
.Bd -literal -offset indent
|
||||
session optional pam_zfs_key.so homes=rpool/home,tank/users
|
||||
.Ed
|
||||
.Pp
|
||||
Searches for user datasets in both
|
||||
.Sy rpool/home
|
||||
and
|
||||
.Sy tank/users .
|
||||
.
|
||||
.Ss Example 4: Keep Datasets Mounted
|
||||
.Bd -literal -offset indent
|
||||
session optional pam_zfs_key.so nounmount
|
||||
.Ed
|
||||
.Pp
|
||||
Leaves datasets mounted and keys loaded when sessions close.
|
||||
Useful for systems with background processes accessing the home directory.
|
||||
.
|
||||
.Ss Example 5: Recursive Mounting
|
||||
.Bd -literal -offset indent
|
||||
session optional pam_zfs_key.so mount_recursively
|
||||
.Ed
|
||||
.Pp
|
||||
Mounts child datasets recursively, useful when user data is organized
|
||||
hierarchically like
|
||||
.Sy rpool/home/alice/documents
|
||||
and
|
||||
.Sy rpool/home/alice/photos .
|
||||
.
|
||||
.Ss Example 6: Creating an Encrypted Home Dataset
|
||||
.Bd -literal -offset indent
|
||||
# zfs create -o encryption=on \e
|
||||
-o keyformat=passphrase \e
|
||||
-o keylocation=prompt \e
|
||||
-o canmount=on \e
|
||||
-o mountpoint=/home/alice \e
|
||||
rpool/home/alice
|
||||
.Ed
|
||||
.Pp
|
||||
The user's login password must match the dataset passphrase for automatic
|
||||
unlocking to work.
|
||||
The dataset must have a ZFS-managed mountpoint (not legacy) and
|
||||
.Sy canmount Ns = Ns Sy on
|
||||
for automatic mounting.
|
||||
.
|
||||
.Ss Example 7: Multiple Homes with Password Sync Check
|
||||
.Bd -literal -offset indent
|
||||
auth optional pam_zfs_key.so homes=rpool/home,tank/home
|
||||
password required pam_zfs_key.so homes=rpool/home,tank/home
|
||||
session optional pam_zfs_key.so homes=rpool/home,tank/home
|
||||
.Ed
|
||||
.Pp
|
||||
Login proceeds even if some datasets are unavailable.
|
||||
Password changes fail if any dataset cannot be updated, ensuring
|
||||
the user is notified of sync issues.
|
||||
See
|
||||
.Sx Multiple Home Prefixes
|
||||
for failure behavior.
|
||||
.
|
||||
.Sh SEE ALSO
|
||||
.Xr pam 8 ,
|
||||
.Xr zfs-change-key 8 ,
|
||||
.Xr zfs-load-key 8 ,
|
||||
.Xr zfs-mount 8
|
||||
.
|
||||
.Sh NOTES
|
||||
.Bl -bullet -compact
|
||||
.It
|
||||
Only works with datasets using
|
||||
.Sy keyformat Ns = Ns Sy passphrase .
|
||||
.It
|
||||
Datasets must have
|
||||
.Sy keylocation Ns = Ns Sy prompt .
|
||||
.It
|
||||
Datasets with
|
||||
.Sy mountpoint Ns = Ns Sy legacy ,
|
||||
.Sy canmount Ns = Ns Sy off ,
|
||||
or
|
||||
.Sy canmount Ns = Ns Sy noauto
|
||||
will have keys loaded but not be automatically mounted.
|
||||
.El
|
||||
@ -92,6 +92,9 @@ will ask for the key and mount the dataset
|
||||
see
|
||||
.Xr zfs-mount 8
|
||||
.Pc .
|
||||
For automated key management during user login,
|
||||
.Xr pam_zfs_key 8
|
||||
can load keys and mount encrypted home directories on systems with PAM support.
|
||||
Once the key is loaded the
|
||||
.Sy keystatus
|
||||
property will become
|
||||
@ -301,5 +304,6 @@ written.
|
||||
.
|
||||
.Sh SEE ALSO
|
||||
.Xr zfsprops 7 ,
|
||||
.Xr pam_zfs_key 8 ,
|
||||
.Xr zfs-create 8 ,
|
||||
.Xr zfs-set 8
|
||||
|
||||
@ -110,6 +110,9 @@ on each encryption root before mounting it.
|
||||
Note that if a filesystem has
|
||||
.Sy keylocation Ns = Ns Sy prompt ,
|
||||
this will cause the terminal to interactively block after asking for the key.
|
||||
On systems with PAM support,
|
||||
.Xr pam_zfs_key 8
|
||||
can automate this process during user login.
|
||||
.It Fl v
|
||||
Report mount progress.
|
||||
.It Fl f
|
||||
@ -138,3 +141,6 @@ The command can also be given a path to a ZFS file system mount point on the
|
||||
system.
|
||||
.El
|
||||
.El
|
||||
.
|
||||
.Sh SEE ALSO
|
||||
.Xr pam_zfs_key 8
|
||||
|
||||
@ -805,6 +805,7 @@ don't wait.
|
||||
.Xr exportfs 8 ,
|
||||
.Xr mount 8 ,
|
||||
.Xr net 8 ,
|
||||
.Xr pam_zfs_key 8 ,
|
||||
.Xr selinux 8 ,
|
||||
.Xr zfs-allow 8 ,
|
||||
.Xr zfs-bookmark 8 ,
|
||||
|
||||
@ -51,4 +51,62 @@ references 0
|
||||
log_mustnot ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus unavailable
|
||||
|
||||
|
||||
|
||||
log_mustnot ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus unavailable
|
||||
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh unavailable
|
||||
|
||||
genconfig "homes=$TESTPOOL/pam,$TESTPOOL/pam-multi-home runstatedir=${runstatedir}"
|
||||
echo "testpass" | pamtester ${pamservice} ${username} open_session
|
||||
references 1
|
||||
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus available
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh available
|
||||
|
||||
echo "testpass" | pamtester ${pamservice} ${username} open_session
|
||||
references 2
|
||||
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus available
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh available
|
||||
|
||||
log_must pamtester ${pamservice} ${username} close_session
|
||||
references 1
|
||||
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus available
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh available
|
||||
|
||||
log_must pamtester ${pamservice} ${username} close_session
|
||||
references 0
|
||||
log_mustnot ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus unavailable
|
||||
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh unavailable
|
||||
|
||||
# Test a 'homes' with many entries
|
||||
allhomes="$TESTPOOL/pam-multi-home1"
|
||||
for i in {2..$PAM_MULTI_HOME_COUNT} ; do
|
||||
allhomes="$allhomes,$TESTPOOL/pam-multi-home$i"
|
||||
done
|
||||
|
||||
genconfig "homes=$allhomes runstatedir=${runstatedir}"
|
||||
|
||||
echo "testpass" | pamtester ${pamservice} ${username} open_session
|
||||
for i in {1..$PAM_MULTI_HOME_COUNT} ; do
|
||||
references 1
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home$i/${username}"
|
||||
keystatus_mh available $i
|
||||
done
|
||||
|
||||
log_must pamtester ${pamservice} ${username} close_session
|
||||
for i in {1..$PAM_MULTI_HOME_COUNT} ; do
|
||||
references 0
|
||||
log_mustnot ismounted "$TESTPOOL/pam-multi-home$i/${username}"
|
||||
keystatus_mh unavailable $i
|
||||
done
|
||||
|
||||
log_pass "done."
|
||||
|
||||
@ -29,28 +29,39 @@ fi
|
||||
|
||||
log_mustnot ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus unavailable
|
||||
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh unavailable
|
||||
|
||||
genconfig "homes=$TESTPOOL/pam runstatedir=${runstatedir}"
|
||||
genconfig "homes=$TESTPOOL/pam,$TESTPOOL/pam-multi-home runstatedir=${runstatedir}"
|
||||
|
||||
printf "testpass\nsecondpass\nsecondpass\n" | pamtester -v ${pamservice} ${username} chauthtok
|
||||
|
||||
log_mustnot ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus unavailable
|
||||
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh unavailable
|
||||
|
||||
echo "secondpass" | pamtester ${pamservice} ${username} open_session
|
||||
references 1
|
||||
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus available
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh available
|
||||
|
||||
printf "secondpass\ntestpass\ntestpass\n" | pamtester -v ${pamservice} ${username} chauthtok
|
||||
|
||||
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus available
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh available
|
||||
|
||||
log_must pamtester ${pamservice} ${username} close_session
|
||||
references 0
|
||||
log_mustnot ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus unavailable
|
||||
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh unavailable
|
||||
|
||||
log_pass "done."
|
||||
|
||||
@ -29,28 +29,40 @@ fi
|
||||
|
||||
log_mustnot ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus unavailable
|
||||
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh unavailable
|
||||
|
||||
genconfig "homes=$TESTPOOL/pam runstatedir=${runstatedir} nounmount"
|
||||
genconfig "homes=$TESTPOOL/pam,$TESTPOOL/pam-multi-home runstatedir=${runstatedir} nounmount"
|
||||
echo "testpass" | pamtester ${pamservice} ${username} open_session
|
||||
references 1
|
||||
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus available
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
keystatus_mh available
|
||||
|
||||
echo "testpass" | pamtester ${pamservice} ${username} open_session
|
||||
references 2
|
||||
keystatus available
|
||||
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus_mh available
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
|
||||
log_must pamtester ${pamservice} ${username} close_session
|
||||
references 1
|
||||
keystatus available
|
||||
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||
keystatus_mh available
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
|
||||
log_must pamtester ${pamservice} ${username} close_session
|
||||
references 0
|
||||
keystatus available
|
||||
keystatus_mh available
|
||||
log_must ismounted "$TESTPOOL/pam/${username}"
|
||||
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
|
||||
log_must zfs unmount "$TESTPOOL/pam/${username}"
|
||||
log_must zfs unmount "$TESTPOOL/pam-multi-home/${username}"
|
||||
log_must zfs unload-key "$TESTPOOL/pam/${username}"
|
||||
log_must zfs unload-key "$TESTPOOL/pam-multi-home/${username}"
|
||||
|
||||
log_pass "done."
|
||||
|
||||
@ -30,6 +30,7 @@ DISK=${DISKS%% *}
|
||||
create_pool $TESTPOOL "$DISK"
|
||||
|
||||
log_must zfs create -o mountpoint="$TESTDIR" "$TESTPOOL/pam"
|
||||
log_must zfs create -o mountpoint="$TESTDIR-multi-home" "$TESTPOOL/pam-multi-home"
|
||||
log_must add_group pamtestgroup
|
||||
log_must add_user pamtestgroup ${username}
|
||||
log_must mkdir -p "$runstatedir"
|
||||
@ -37,5 +38,15 @@ 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}"
|
||||
echo "testpass" | zfs create -o encryption=aes-256-gcm -o keyformat=passphrase -o keylocation=prompt "$TESTPOOL/pam-multi-home/${username}"
|
||||
log_must zfs unmount "$TESTPOOL/pam-multi-home/${username}"
|
||||
log_must zfs unload-key "$TESTPOOL/pam-multi-home/${username}"
|
||||
|
||||
for i in {1..$PAM_MULTI_HOME_COUNT} ; do
|
||||
log_must zfs create -o mountpoint="$TESTDIR-multi-home$i" "$TESTPOOL/pam-multi-home$i"
|
||||
echo "testpass" | zfs create -o encryption=aes-256-gcm -o keyformat=passphrase -o keylocation=prompt "$TESTPOOL/pam-multi-home$i/${username}"
|
||||
log_must zfs unmount "$TESTPOOL/pam-multi-home$i/${username}"
|
||||
log_must zfs unload-key "$TESTPOOL/pam-multi-home$i/${username}"
|
||||
done
|
||||
|
||||
log_pass
|
||||
|
||||
@ -28,11 +28,17 @@ runstatedir="${TESTDIR}_run"
|
||||
pammodule="@pammoduledir@/pam_zfs_key.so"
|
||||
pamservice="pam_zfs_key_test"
|
||||
pamconfig="/etc/pam.d/${pamservice}"
|
||||
PAM_MULTI_HOME_COUNT=20
|
||||
|
||||
function keystatus {
|
||||
log_must [ "$(get_prop keystatus "$TESTPOOL/pam/${username}")" = "$1" ]
|
||||
}
|
||||
|
||||
function keystatus_mh {
|
||||
typeset suffix="${2:-}"
|
||||
log_must [ "$(get_prop keystatus "$TESTPOOL/pam-multi-home${suffix}/${username}")" = "$1" ]
|
||||
}
|
||||
|
||||
function genconfig {
|
||||
printf '%s\trequired\tpam_permit.so\n%s\toptional\t%s\t%s\n' \
|
||||
password password "$pammodule" "$1" \
|
||||
|
||||
Loading…
Reference in New Issue
Block a user