Convert users.extraUsers to an option set and add support for openssh
authorized_keys file generation. svn path=/nixos/trunk/; revision=30611
This commit is contained in:
parent
e4ba69aecf
commit
e264d1ab79
@ -5,43 +5,83 @@ with pkgs.lib;
|
|||||||
let
|
let
|
||||||
|
|
||||||
ids = config.ids;
|
ids = config.ids;
|
||||||
|
users = config.users;
|
||||||
|
|
||||||
|
userOpts = {name, config, ...}:
|
||||||
|
|
||||||
# User accounts to be created/updated by NixOS.
|
{
|
||||||
users =
|
options = {
|
||||||
let
|
name = mkOption {
|
||||||
defaultUsers =
|
type = with types; uniq string;
|
||||||
[ { name = "root";
|
description = "The name of the user account. If undefined, the name of the attribute set will be used.";
|
||||||
uid = ids.uids.root;
|
};
|
||||||
description = "System administrator";
|
description = mkOption {
|
||||||
home = "/root";
|
type = with types; uniq string;
|
||||||
shell = config.users.defaultUserShell;
|
default = "";
|
||||||
group = "root";
|
description = "A short description of the user account.";
|
||||||
}
|
};
|
||||||
{ name = "nobody";
|
uid = mkOption {
|
||||||
uid = ids.uids.nobody;
|
type = with types; uniq (nullOr int);
|
||||||
description = "Unprivileged account (don't use!)";
|
default = null;
|
||||||
}
|
description = "The account UID. If undefined, NixOS will select a UID.";
|
||||||
];
|
};
|
||||||
|
group = mkOption {
|
||||||
# !!! Use NixOS module system to add default attributes.
|
type = with types; uniq string;
|
||||||
addAttrs =
|
default = "nogroup";
|
||||||
{ name
|
description = "The user's primary group.";
|
||||||
, description
|
};
|
||||||
, uid ? ""
|
extraGroups = mkOption {
|
||||||
, group ? "nogroup"
|
type = types.listOf types.string;
|
||||||
, extraGroups ? []
|
default = [];
|
||||||
, home ? "/var/empty"
|
description = "The user's auxiliary groups.";
|
||||||
, shell ? (if useDefaultShell then config.users.defaultUserShell else "/noshell")
|
};
|
||||||
, createHome ? false
|
home = mkOption {
|
||||||
, useDefaultShell ? false
|
type = with types; uniq string;
|
||||||
, password ? null
|
default = "/var/empty";
|
||||||
, isSystemUser ? true
|
description = "The user's home directory.";
|
||||||
}:
|
};
|
||||||
{ inherit name description uid group extraGroups home shell createHome password isSystemUser; };
|
shell = mkOption {
|
||||||
|
type = with types; uniq string;
|
||||||
in map addAttrs (defaultUsers ++ config.users.extraUsers);
|
default = "/noshell";
|
||||||
|
description = "The path to the user's shell.";
|
||||||
|
};
|
||||||
|
createHome = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "If true, the home directory will be created automatically.";
|
||||||
|
};
|
||||||
|
useDefaultShell = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "If true, the user's shell will be set to <literal>users.defaultUserShell</literal>.";
|
||||||
|
};
|
||||||
|
password = mkOption {
|
||||||
|
type = with types; uniq (nullOr string);
|
||||||
|
default = null;
|
||||||
|
description = "The user's password. If undefined, no password is set for the user. Warning: do not set confidential information here because this data would be readable by all. This option should only be used for public account such as guest.";
|
||||||
|
};
|
||||||
|
isSystemUser = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Indicates if the user is a system user or not.";
|
||||||
|
};
|
||||||
|
createUser = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "
|
||||||
|
Indicates if the user should be created automatically as a local user.
|
||||||
|
Set this to false if the user for instance is an LDAP user. NixOS will
|
||||||
|
then not modify any of the basic properties for the user account.
|
||||||
|
";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
name = mkDefault name;
|
||||||
|
uid = mkDefault (attrByPath [name] null ids.uids);
|
||||||
|
shell = mkIf config.useDefaultShell (mkDefault users.defaultUserShell);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# Groups to be created/updated by NixOS.
|
# Groups to be created/updated by NixOS.
|
||||||
groups =
|
groups =
|
||||||
@ -109,11 +149,14 @@ let
|
|||||||
|
|
||||||
# Note: the 'X' in front of the password is to distinguish between
|
# Note: the 'X' in front of the password is to distinguish between
|
||||||
# having an empty password, and not having a password.
|
# having an empty password, and not having a password.
|
||||||
serializedUser = u: "${u.name}\n${u.description}\n${toString u.uid}\n${u.group}\n${toString (concatStringsSep "," u.extraGroups)}\n${u.home}\n${u.shell}\n${toString u.createHome}\n${if u.password != null then "X" + u.password else ""}\n${toString u.isSystemUser}\n";
|
serializedUser = userName: let u = getAttr userName config.users.extraUsers; in "${u.name}\n${u.description}\n${if u.uid != null then toString u.uid else ""}\n${u.group}\n${toString (concatStringsSep "," u.extraGroups)}\n${u.home}\n${u.shell}\n${toString u.createHome}\n${if u.password != null then "X" + u.password else ""}\n${toString u.isSystemUser}\n${if u.createUser then "yes" else "no"}\n";
|
||||||
|
|
||||||
serializedGroup = g: "${g.name}\n${toString g.gid}";
|
serializedGroup = g: "${g.name}\n${toString g.gid}";
|
||||||
|
|
||||||
# keep this extra file so that cat can be used to pass special chars such as "`" which is used in the avahi daemon
|
# keep this extra file so that cat can be used to pass special chars such as "`" which is used in the avahi daemon
|
||||||
usersFile = pkgs.writeText "users" (concatStrings (map serializedUser users));
|
usersFile = pkgs.writeText "users" (
|
||||||
|
concatMapStrings serializedUser (attrNames config.users.extraUsers)
|
||||||
|
);
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
@ -124,22 +167,24 @@ in
|
|||||||
options = {
|
options = {
|
||||||
|
|
||||||
users.extraUsers = mkOption {
|
users.extraUsers = mkOption {
|
||||||
default = [];
|
default = {};
|
||||||
example =
|
type = types.loaOf types.optionSet;
|
||||||
[ { name = "alice";
|
example = {
|
||||||
uid = 1234;
|
alice = {
|
||||||
description = "Alice";
|
uid = 1234;
|
||||||
home = "/home/alice";
|
description = "Alice";
|
||||||
createHome = true;
|
home = "/home/alice";
|
||||||
group = "users";
|
createHome = true;
|
||||||
extraGroups = ["wheel"];
|
group = "users";
|
||||||
shell = "/bin/sh";
|
extraGroups = ["wheel"];
|
||||||
password = "foobar";
|
shell = "/bin/sh";
|
||||||
}
|
password = "foobar";
|
||||||
];
|
};
|
||||||
|
};
|
||||||
description = ''
|
description = ''
|
||||||
Additional user accounts to be created automatically by the system.
|
Additional user accounts to be created automatically by the system.
|
||||||
'';
|
'';
|
||||||
|
options = [ userOpts ];
|
||||||
};
|
};
|
||||||
|
|
||||||
users.extraGroups = mkOption {
|
users.extraGroups = mkOption {
|
||||||
@ -154,6 +199,15 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
This option defines settings for individual users on the system.
|
||||||
|
'';
|
||||||
|
type = types.loaOf types.optionSet;
|
||||||
|
options = [ ];
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -161,6 +215,18 @@ in
|
|||||||
|
|
||||||
config = {
|
config = {
|
||||||
|
|
||||||
|
users.extraUsers = {
|
||||||
|
root = {
|
||||||
|
description = "System administrator";
|
||||||
|
home = "/root";
|
||||||
|
shell = config.users.defaultUserShell;
|
||||||
|
group = "root";
|
||||||
|
};
|
||||||
|
nobody = {
|
||||||
|
description = "Unprivileged account (don't use!)";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
system.activationScripts.rootPasswd = stringAfter [ "etc" ]
|
system.activationScripts.rootPasswd = stringAfter [ "etc" ]
|
||||||
''
|
''
|
||||||
# If there is no password file yet, create a root account with an
|
# If there is no password file yet, create a root account with an
|
||||||
@ -192,6 +258,11 @@ in
|
|||||||
read createHome
|
read createHome
|
||||||
read password
|
read password
|
||||||
read isSystemUser
|
read isSystemUser
|
||||||
|
read createUser
|
||||||
|
|
||||||
|
if ! test "$createUser" = "yes"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
if ! curEnt=$(getent passwd "$name"); then
|
if ! curEnt=$(getent passwd "$name"); then
|
||||||
useradd ''${isSystemUser:+--system} \
|
useradd ''${isSystemUser:+--system} \
|
||||||
|
@ -14,6 +14,98 @@ let
|
|||||||
v == "forced-commands-only" ||
|
v == "forced-commands-only" ||
|
||||||
v == "no";
|
v == "no";
|
||||||
|
|
||||||
|
userOptions = {
|
||||||
|
openssh.authorizedKeys = {
|
||||||
|
|
||||||
|
preserveExistingKeys = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
If this option is enabled, the keys specified in
|
||||||
|
<literal>keys</literal> and/or <literal>keyFiles</literal> will be
|
||||||
|
placed in a special section of the user's authorized_keys file
|
||||||
|
and any existing keys will be preserved. That section will be
|
||||||
|
regenerated each time NixOS is activated. However, if
|
||||||
|
<literal>preserveExisting</literal> isn't enabled, the complete file
|
||||||
|
will be generated, and any user modifications will be wiped out.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
keys = mkOption {
|
||||||
|
type = types.listOf types.string;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
A list of verbatim OpenSSH public keys that should be inserted into the
|
||||||
|
user's authorized_keys file. You can combine the <literal>keys</literal> and
|
||||||
|
<literal>keyFiles</literal> options.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
keyFiles = mkOption {
|
||||||
|
type = types.listOf types.string;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
A list of files each containing one OpenSSH public keys that should be
|
||||||
|
inserted into the user's authorized_keys file. You can combine
|
||||||
|
the <literal>keyFiles</literal> and
|
||||||
|
<literal>keys</literal> options.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mkAuthkeyScript =
|
||||||
|
let
|
||||||
|
marker1 = "### NixOS will regenerate this line and every line below it.";
|
||||||
|
marker2 = "### NixOS will regenerate this file. Do not edit!";
|
||||||
|
users = map (userName: getAttr userName config.users.extraUsers) (attrNames config.users.extraUsers);
|
||||||
|
usersWithKeys = flip filter users (u:
|
||||||
|
length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0
|
||||||
|
);
|
||||||
|
userLoop = flip concatMapStrings usersWithKeys (u:
|
||||||
|
let
|
||||||
|
authKeys = concatStringsSep "," u.openssh.authorizedKeys.keys;
|
||||||
|
authKeyFiles = concatStringsSep "," u.openssh.authorizedKeys.keyFiles;
|
||||||
|
preserveExisting = if u.openssh.authorizedKeys.preserveExistingKeys then "true" else "false";
|
||||||
|
in ''
|
||||||
|
mkAuthKeysFile "${u.name}" "${authKeys}" "${authKeyFiles}" "${preserveExisting}"
|
||||||
|
''
|
||||||
|
);
|
||||||
|
in ''
|
||||||
|
mkAuthKeysFile() {
|
||||||
|
local userName="$1"
|
||||||
|
local authKeys="$2"
|
||||||
|
local authKeyFiles="$3"
|
||||||
|
local preserveExisting="$4"
|
||||||
|
IFS=","
|
||||||
|
|
||||||
|
for f in $authKeyFiles; do
|
||||||
|
if [ -f "$f" ]; then
|
||||||
|
authKeys="$(${pkgs.coreutils}/bin/cat "$f"),$authKeys"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$authKeys" ]; then
|
||||||
|
eval authfile=~$userName/.ssh/authorized_keys
|
||||||
|
${pkgs.coreutils}/bin/mkdir -p "$(dirname $authfile)"
|
||||||
|
${pkgs.coreutils}/bin/touch "$authfile"
|
||||||
|
if [ "$preserveExisting" == "false" ]; then
|
||||||
|
rm -f "$authfile"
|
||||||
|
authKeys="${marker2},$authKeys"
|
||||||
|
else
|
||||||
|
${pkgs.gnused}/bin/sed -i '/^### NixOS.*$/,$d' "$authfile"
|
||||||
|
authKeys="${marker1},$authKeys"
|
||||||
|
fi
|
||||||
|
for key in $authKeys; do ${pkgs.coreutils}/bin/echo "$key" >> "$authfile"; done
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset IFS
|
||||||
|
}
|
||||||
|
${userLoop}
|
||||||
|
'';
|
||||||
|
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -102,6 +194,10 @@ in
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
users.extraUsers = mkOption {
|
||||||
|
options = [ userOptions ];
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -135,6 +231,8 @@ in
|
|||||||
|
|
||||||
preStart =
|
preStart =
|
||||||
''
|
''
|
||||||
|
${mkAuthkeyScript}
|
||||||
|
|
||||||
mkdir -m 0755 -p /etc/ssh
|
mkdir -m 0755 -p /etc/ssh
|
||||||
|
|
||||||
if ! test -f /etc/ssh/ssh_host_dsa_key; then
|
if ! test -f /etc/ssh/ssh_host_dsa_key; then
|
||||||
|
Loading…
x
Reference in New Issue
Block a user