{ lib, config, ... }: let keys = import ./ssh-keys.nix; inherit (lib) mkOption types length concatMap listToAttrs catAttrs attrValues; cfgAdmins = config.bagel.admins; cfgGroups = config.bagel.groups; cfgUsers = config.bagel.users; userOpts = { name, ... }: { options = { sshKeys = mkOption { type = types.listOf types.str; description = "List of SSH keys associated to this user, defaults to `ssh-keys.nix` entries."; default = keys.users.${name} or [ ]; }; }; }; groupOpts = { name, ... }: { options = { members = mkOption { type = types.listOf types.str; description = "List of users member of this group"; example = [ "raito" ]; default = [ ]; }; }; }; # There might be duplicate in that list. We will turn it into an attribute set. allowedMembers = listToAttrs ( map (member: { name = member; value = cfgUsers.${member}; }) (concatMap (allowedGroup: cfgGroups.${allowedGroup}.members) cfgAdmins.allowedGroups)); rootKeys = concatMap ({ sshKeys, ... }: sshKeys) (attrValues allowedMembers); in { options.bagel.users = mkOption { type = types.attrsOf (types.submodule userOpts); description = "User configuration for server ACLs"; }; options.bagel.groups = mkOption { type = types.attrsOf (types.submodule groupOpts); description = "Group configuration for server ACLs"; }; options.bagel.admins = { allowedGroups = mkOption { type = types.listOf types.str; default = [ "catch-all" ]; description = "List of groups which are allowed to admin this machine."; example = [ "lix" "build-infra" ]; }; }; config = { assertions = [ { assertion = length config.users.users.root.openssh.authorizedKeys.keys > 0; # TODO: you can add printing of `concatStringsSep ", " cfg.allowedGroups` to diagnose # which are the allowed groups and existing admins. message = "root@${config.networking.fqdnOrHostName} has no SSH key attached, this machine will lose its access if you deploy it successfully! Set a valid `bagel.admins.allowedGroups` or ensure you have at least one administrator of the relevant group registered"; } ]; users.users.root.openssh.authorizedKeys.keys = rootKeys; }; }