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:
Nicolas Pierron 2011-11-29 06:08:55 +00:00
parent e4ba69aecf
commit e264d1ab79
2 changed files with 218 additions and 49 deletions

View file

@ -5,43 +5,83 @@ with pkgs.lib;
let
ids = config.ids;
users = config.users;
userOpts = {name, config, ...}:
# User accounts to be created/updated by NixOS.
users =
let
defaultUsers =
[ { name = "root";
uid = ids.uids.root;
description = "System administrator";
home = "/root";
shell = config.users.defaultUserShell;
group = "root";
}
{ name = "nobody";
uid = ids.uids.nobody;
description = "Unprivileged account (don't use!)";
}
];
# !!! Use NixOS module system to add default attributes.
addAttrs =
{ name
, description
, uid ? ""
, group ? "nogroup"
, extraGroups ? []
, home ? "/var/empty"
, shell ? (if useDefaultShell then config.users.defaultUserShell else "/noshell")
, createHome ? false
, useDefaultShell ? false
, password ? null
, isSystemUser ? true
}:
{ inherit name description uid group extraGroups home shell createHome password isSystemUser; };
in map addAttrs (defaultUsers ++ config.users.extraUsers);
{
options = {
name = mkOption {
type = with types; uniq string;
description = "The name of the user account. If undefined, the name of the attribute set will be used.";
};
description = mkOption {
type = with types; uniq string;
default = "";
description = "A short description of the user account.";
};
uid = mkOption {
type = with types; uniq (nullOr int);
default = null;
description = "The account UID. If undefined, NixOS will select a UID.";
};
group = mkOption {
type = with types; uniq string;
default = "nogroup";
description = "The user's primary group.";
};
extraGroups = mkOption {
type = types.listOf types.string;
default = [];
description = "The user's auxiliary groups.";
};
home = mkOption {
type = with types; uniq string;
default = "/var/empty";
description = "The user's home directory.";
};
shell = mkOption {
type = with types; uniq string;
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 =
@ -109,11 +149,14 @@ let
# Note: the 'X' in front of the password is to distinguish between
# 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}";
# 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
@ -124,22 +167,24 @@ in
options = {
users.extraUsers = mkOption {
default = [];
example =
[ { name = "alice";
uid = 1234;
description = "Alice";
home = "/home/alice";
createHome = true;
group = "users";
extraGroups = ["wheel"];
shell = "/bin/sh";
password = "foobar";
}
];
default = {};
type = types.loaOf types.optionSet;
example = {
alice = {
uid = 1234;
description = "Alice";
home = "/home/alice";
createHome = true;
group = "users";
extraGroups = ["wheel"];
shell = "/bin/sh";
password = "foobar";
};
};
description = ''
Additional user accounts to be created automatically by the system.
'';
options = [ userOpts ];
};
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 = {
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" ]
''
# If there is no password file yet, create a root account with an
@ -192,6 +258,11 @@ in
read createHome
read password
read isSystemUser
read createUser
if ! test "$createUser" = "yes"; then
continue
fi
if ! curEnt=$(getent passwd "$name"); then
useradd ''${isSystemUser:+--system} \

View file

@ -14,6 +14,98 @@ let
v == "forced-commands-only" ||
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
{
@ -102,6 +194,10 @@ in
};
users.extraUsers = mkOption {
options = [ userOptions ];
};
};
@ -135,6 +231,8 @@ in
preStart =
''
${mkAuthkeyScript}
mkdir -m 0755 -p /etc/ssh
if ! test -f /etc/ssh/ssh_host_dsa_key; then