nixos-config/lib/fudo/users.nix

296 lines
8.5 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
systemUserOpts = { username, ... }: {
options = with types; {
username = mkOption {
type = str;
description = "The system user's login name.";
default = username;
};
description = mkOption {
type = str;
description = "Description of this system user's purpose or role";
};
ldap-hashed-password = mkOption {
type = str;
description =
"LDAP-formatted hashed password for this user. Generate with slappasswd.";
};
};
};
userOpts = { username, ... }: {
options = with types; {
username = mkOption {
type = str;
description = "The user's login name.";
default = username;
};
uid = mkOption {
type = int;
description = "Unique UID number for the user.";
};
common-name = mkOption {
type = str;
description = "The user's common or given name.";
};
primary-group = mkOption {
type = str;
description = "Primary group to which the user belongs.";
};
login-shell = mkOption {
type = nullOr shellPackage;
description = "The user's preferred shell.";
};
description = mkOption {
type = str;
default = "Fudo Member";
description = "A description of this user's role.";
};
ldap-hashed-passwd = mkOption {
type = nullOr str;
description =
"LDAP-formatted hashed password, used for email and other services. Use slappasswd to generate the properly-formatted password.";
default = null;
};
login-hashed-passwd = mkOption {
type = nullOr str;
description =
"Hashed password for shell, used for shell access to hosts. Use mkpasswd to generate the properly-formatted password.";
default = null;
};
ssh-authorized-keys = mkOption {
type = listOf str;
description = "SSH public keys this user can use to log in.";
default = [ ];
};
# home-manager-generator = mkOption {
# type = nullOr (functionTo attrs);
# description = "Home Manager configuration for the given user.";
# default = null;
# };
home-directory = mkOption {
type = nullOr str;
description = "Default home directory for the given user.";
default = null;
};
k5login = mkOption {
type = listOf str;
description = "List of Kerberos principals that map to this user.";
default = [ ];
};
ssh-keys = mkOption {
type = nullOr (submodule sshKeyOpts);
description = "Path to the user's public and private key files.";
default = null;
};
};
};
sshKeyOpts = { ... }: {
options = with types; {
private-key = mkOption {
type = str;
description = "Path to the user's private key.";
};
public-key = mkOption {
type = str;
description = "Path to the user's public key.";
};
};
};
groupOpts = { group-name, ... }: {
options = with types; {
group-name = mkOption {
type = nullOr str;
default = group-name;
description = "Group name.";
};
description = mkOption {
type = str;
description = "Description of the group or it's purpose.";
};
members = mkOption {
type = listOf str;
default = [ ];
description = "A list of users who are members of the current group.";
};
gid = mkOption {
type = int;
description = "GID number of the group.";
};
};
};
list-includes = list: el: isNull (findFirst (this: this == el) null list);
filterExistingUsers = users: group-members:
let user-list = attrNames users;
in filter (username: list-includes user-list username) group-members;
ensure-group-directory = group: dir: ''
if [[ -d ${dir} ]]; then
GROUP="$(stat --format '%G' "${dir}")"
if [[ "$GROUP" = "${group}" ]]; then
echo "${dir} exists and belongs to ${group}"
exit 0
else
echo "setting ownership of ${dir} to ${group}"
chgrp ${group} ${dir}
chmod g+rx ${dir}
fi
elif [[ ! -e ${dir} ]]; then
echo "creating ${dir} and setting ownership to ${group}"
mkdir ${dir}
chgrp ${group} ${dir}
chmod g+rx ${dir}
elif [[ -e ${dir} && ! -d ${dir} ]]; then
echo "unable to create directory ${dir}, object exists"
exit 2
else
echo "unknown error creating ${dir}"
exit 3
fi
'';
ensure-group-dirs-script = group: dirs:
concatStringsSep "\n" (map (ensure-group-directory group) dirs);
hostname = config.instance.hostname;
host-cfg = config.fudo.hosts.${hostname};
in {
options.fudo = {
users = mkOption {
type = with types; attrsOf (submodule userOpts);
description = "Users";
default = { };
};
groups = mkOption {
type = with types; attrsOf (submodule groupOpts);
description = "Groups";
default = { };
};
system-users = mkOption {
type = with types; attrsOf (submodule systemUserOpts);
description = "System users (probably not what you're looking for!)";
default = { };
};
};
imports = [ ./users-common.nix ];
config = let sys = import ../system.nix { inherit lib config; };
in {
fudo.auth.ldap-server = let
ldapUsers = (filterAttrs
(username: userOpts: userOpts.ldap-hashed-password != null))
config.fudo.users;
in {
users = mapAttrs (username: userOpts: {
uid = userOpts.uid;
group = userOpts.primary-group;
common-name = userOpts.common-name;
hashed-password = userOpts.ldap-hashed-password;
}) ldapUsers;
groups = mapAttrs (groupname: groupOpts: {
gid = groupOpts.gid-number;
description = groupOpts.description;
members = filterExistingUsers ldapUsers groupOpts.members;
}) config.fudo.groups;
system-users = mapAttrs (username: userOpts: {
description = userOpts.description;
hashed-password = userOpts.ldap-hashed-passwd;
}) config.fudo.system-users;
};
programs.ssh.extraConfig = mkAfter ''
IdentityFile %h/.ssh/id_rsa
IdentityFile /etc/ssh/private_keys.d/%u.key
'';
environment.etc = let
privkey-users = filterAttrs (username: userOpts: userOpts.ssh-keys != null) sys.local-users;
in mapAttrs' (username: userOpts:
nameValuePair "ssh/private_keys.bd/${username}.key" userOpts.ssh-keys.private-key) privkey-users;
users = {
users = mapAttrs (username: userOpts: {
isNormalUser = true;
uid = userOpts.uid;
createHome = true;
description = userOpts.common-name;
group = userOpts.primary-group;
home = if (userOpts.home-directory != null) then
userOpts.home-directory
else
"/home/${userOpts.primary-group}/${username}";
hashedPassword = userOpts.login-hashed-passwd;
openssh.authorizedKeys.keys = userOpts.ssh-authorized-keys;
}) sys.local-users;
groups = (mapAttrs (groupname: groupOpts: {
gid = groupOpts.gid;
members = filterExistingUsers sys.local-users groupOpts.members;
}) sys.local-groups) // {
wheel = { members = sys.local-admins; };
docker = mkIf (host-cfg.docker-server) { members = sys.local-admins; };
};
};
home-manager.useGlobalPkgs = true;
# home-manager.useGlobalPkgs = {
# useGlobalPkgs = true;
# users = let
# home-manager-users =
# filterAttrs (username: userOpts: userOpts.home-manager-generator != null)
# sys.local-users;
# in mapAttrs (username: userOpts: userOpts.home-manager-generator {
# enable-gui = host-cfg.enable-gui;
# }) home-manager-users;
# };
# Group home directories have to exist, otherwise users can't log in
systemd.services = let
ensure-group-directories = group:
nameValuePair "ensure-group-directories-${group}" {
script = ensure-group-dirs-script group [ "/home/${group}" ];
wantedBy = [ "multi-user.target" ];
requires = [ "local-fs.target" ];
after = [ "remote-fs.target" ];
};
groups-with-members = attrNames
(filterAttrs (group: groupOpts: (length groupOpts.members) > 0)
sys.local-groups);
in listToAttrs (map ensure-group-directories groups-with-members);
};
}