Commit 76e2e7f
Changed files (10)
modules
core
hosts
users
modules/core/openssh.nix
@@ -22,7 +22,7 @@
includes = [
({user, ...}: {
name = "core.openssh.access.user";
- user.openssh.authorizedKeys.keys = user.authorizedKeys;
+ user.openssh.authorizedKeys.keys = map (k: k.key) user.identity.sshKeys;
})
];
modules/hosts/hyacine/default.nix
@@ -6,15 +6,14 @@
Based on a KVM virtual host,
with AMD EPYC 7K62 2 core @ 2.6GHz, 2GB RAM and 30GB SSD storage.
'';
- users.hpcesia = {
- authorizedKeys = [
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFMKaGC2I3an4AJDeWzVx5vhm63+kxi6zJNdh7yEp6CK hpcesia@kevin"
- ];
- };
pubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINy57YGTUNEbBVs1YHD6m/zhJnJKnicNEaLB5fEcJln6 root@hyacine";
};
+ den.users.access.by-host.hyacine.groups = [
+ "admins"
+ ];
+
den.aspects.hyacine = {
includes = [
den.aspects.roles.default
modules/hosts/kevin/default.nix
@@ -6,16 +6,19 @@
Based on a Lenovo ThinkBook 16P G5 IRX,
with Intel Core i7-14650HX, 32GB RAM and 2TB NVMe storage.
'';
- users.hpcesia = {
- classes = [
- "user"
- "homeManager"
- ];
- };
-
pubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOO9CyAqGo/WbJkncrt1a9jxS1E+hd550SC4A08I/l0/ root@kevin";
};
+ den.users.access.by-host.kevin = {
+ groups = [
+ "admins"
+ ];
+ classes = [
+ "user"
+ "homeManager"
+ ];
+ };
+
den.aspects.kevin = {
includes = [
den.aspects.roles.default
modules/hosts/mobius/default.nix
@@ -6,18 +6,14 @@
Based on a Topfell DeskOne T2 mini PC,
with AMD Ryzen 7 H255, 16GB RAM and 1TB NVMe storage.
'';
- users.hpcesia = {
- classes = [
- "user"
- ];
- authorizedKeys = [
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFMKaGC2I3an4AJDeWzVx5vhm63+kxi6zJNdh7yEp6CK hpcesia@kevin"
- ];
- };
pubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKSHlIARpQYDi744NML9QmFMxOEZwShfyj/ovQh6lCt7 root@mobius";
};
+ den.users.access.by-host.mobius.groups = [
+ "admins"
+ ];
+
den.aspects.mobius = {
includes = [
den.aspects.roles.default
modules/hosts/tribios/default.nix
@@ -6,15 +6,13 @@
Based on a Friendly Nano Pi R6S board,
with Rockchip RK3588S, 4GB RAM and 64GB eMMC storage.
'';
- users.hpcesia = {
- authorizedKeys = [
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFMKaGC2I3an4AJDeWzVx5vhm63+kxi6zJNdh7yEp6CK hpcesia@kevin"
- ];
- };
-
pubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQTx9RhZJa4WOAJyAUzlQLaFExcDv921sVsfM02vB9h root@tribios";
};
+ den.users.access.by-host.tribios.groups = [
+ "admins"
+ ];
+
den.aspects.tribios = {
includes = [
den.aspects.roles.default
modules/hosts/schema.nix
@@ -85,12 +85,6 @@
in
lib.types.submodule (nodeModule (den.aspects or {}));
in {
- description = lib.mkOption {
- description = "Description of the host.";
- type = lib.types.str;
- default = "";
- };
-
settings = lib.mkOption {
description = "Per-aspect typed settings";
type = settingsType;
modules/users/hpcesia/default.nix
@@ -5,4 +5,20 @@
den.batteries.primary-user
];
};
+
+ den.users.registry.hpcesia = {
+ groups = [
+ "admins"
+ ];
+ identity = {
+ displayName = "HPCesia";
+ email = "me@hpcesia.com";
+ sshKeys = [
+ {
+ tag = "hpcesia@kevin";
+ key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFMKaGC2I3an4AJDeWzVx5vhm63+kxi6zJNdh7yEp6CK";
+ }
+ ];
+ };
+ };
}
modules/users/default.nix
@@ -3,7 +3,7 @@
den.batteries.define-user
({user, ...}: {
name = "users/description";
- user.description = user.description;
+ user.description = user.identity.displayName;
})
];
}
modules/users/registry.nix
@@ -0,0 +1,131 @@
+{
+ lib,
+ den,
+ config,
+ ...
+}: let
+ inherit (lib) mkOption types;
+
+ # Submodule for group-based access grants.
+ # Per-host classes override the registry-level defaults.
+ accessGrantType = types.submodule {
+ options = {
+ groups = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Groups granted access";
+ };
+ classes = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = "Per-host nix classes override (null = use registry default)";
+ };
+ };
+ };
+
+ # Registry entry type — mirrors the standard user entity shape so that
+ # pipeline self-provide, define-user, and other batteries find the
+ # expected attributes (userName, aspect, classes).
+ registryUserType = types.submodule (
+ {
+ name,
+ config,
+ ...
+ }: {
+ freeformType = types.attrsOf types.anything;
+ options = {
+ name = mkOption {
+ type = types.str;
+ default = name;
+ description = "User name (from attrset key)";
+ };
+ userName = mkOption {
+ type = types.str;
+ default = name;
+ description = "User account name";
+ };
+ classes = mkOption {
+ type = types.listOf types.str;
+ default = ["user"];
+ description = "Home management nix classes";
+ };
+ aspect = mkOption {
+ type = types.raw;
+ default = lib.mkDefault {};
+ defaultText = "den.aspects.<name>";
+ description = "Aspect that configures this user";
+ };
+ groups = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Group memberships for access policy selection";
+ };
+ };
+ }
+ );
+in {
+ # User registry option (under den.users.list for schema-compatibility).
+ options.den.users = mkOption {
+ type = types.submodule {
+ options.registry = mkOption {
+ type = types.attrsOf registryUserType;
+ default = {};
+ description = "User registry with extended schema for access policy resolution";
+ };
+ options.access = {
+ by-host = mkOption {
+ type = types.attrsOf accessGrantType;
+ default = {};
+ description = "Grant user groups access to a specific host";
+ };
+ };
+ };
+ default = {};
+ description = "Den users registry and access control";
+ };
+
+ config = let
+ registry = config.den.users.registry;
+
+ matchRegistryUsers = grantedGroups:
+ lib.filter (
+ name: let
+ userGroups = registry.${name}.groups or [];
+ in
+ builtins.any (g: lib.elem g grantedGroups) userGroups
+ ) (builtins.attrNames registry);
+
+ resolveUser = hostClasses: name: let
+ user = registry.${name};
+ userAspect = den.aspects.${name} or {};
+ in
+ user
+ // {aspect = userAspect;}
+ // lib.optionalAttrs (hostClasses != null) {classes = hostClasses;};
+ in {
+ den.policies.env-users = {host, ...}: let
+ hostName = host.name or host.hostName;
+ hostAccess = config.den.users.access.by-host.${hostName} or {};
+ accessGroups = hostAccess.groups or [];
+ hostClasses = hostAccess.classes or null;
+ in
+ map (name:
+ den.lib.policy.resolve.shared.to "user" {user = resolveUser hostClasses name;}) (
+ matchRegistryUsers accessGroups
+ );
+
+ den.hosts =
+ lib.mapAttrs (hostName: hostAccess: let
+ accessGroups = hostAccess.groups;
+ hostClasses = hostAccess.classes;
+ in {
+ users = lib.genAttrs (matchRegistryUsers accessGroups) (resolveUser hostClasses);
+ })
+ config.den.users.access.by-host;
+
+ den.schema.host = {
+ includes = [den.policies.env-users];
+ excludes = [den.policies.host-to-users];
+ };
+ };
+}
modules/users/schema.nix
@@ -1,16 +1,66 @@
-{lib, ...}: {
- den.schema.user = {config, ...}: {
+{lib, ...}: let
+ inherit (lib) mkOption types;
+
+ sshKeyType = types.submodule {
options = {
- description = lib.mkOption {
- description = "Description of the user.";
- type = lib.types.str;
- default = config.userName;
+ tag = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Tag to categorize the SSH key (e.g., 'laptop', 'workstation', 'yubikey')";
};
- authorizedKeys = lib.mkOption {
- type = lib.types.listOf lib.types.str;
- description = "List of authorized keys for the user.";
- default = [];
+ key = mkOption {
+ type = types.str;
+ description = "SSH public key string";
};
};
};
+in {
+ den.schema.user.imports = [
+ (_: {
+ options = {
+ identity = mkOption {
+ type = types.submodule {
+ options = {
+ displayName = mkOption {
+ type = types.str;
+ default = "";
+ description = "Display name for the user";
+ };
+ email = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Email address for the user";
+ };
+ gpgKey = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "GPG key ID for the user";
+ };
+ sshKeys = mkOption {
+ type = types.listOf sshKeyType;
+ default = [];
+ description = "SSH public keys for the user, each with an optional tag";
+ };
+ };
+ };
+ default = {};
+ description = "User identity information";
+ };
+
+ system = mkOption {
+ type = types.submodule {
+ options = {
+ settings = mkOption {
+ type = types.attrsOf (types.attrsOf types.anything);
+ default = {};
+ description = "Per-user feature settings (freeform nested namespace)";
+ };
+ };
+ };
+ default = {};
+ description = "Unix account defaults and system configuration";
+ };
+ };
+ })
+ ];
}