Added nutboy3 and nuttyclub
This commit is contained in:
parent
1c059841e6
commit
d7ee19dd2f
@ -62,20 +62,25 @@
|
||||
local-admins = [ "niten" ];
|
||||
admin-email = "viator@informis.land";
|
||||
gssapi-realm = "INFORMIS.LAND";
|
||||
kerberos-master = "procul";
|
||||
kerberos-slaves = [ "legatus" ];
|
||||
primary-nameserver = "procul";
|
||||
};
|
||||
|
||||
eur.fudo.org = {
|
||||
"eur.fudo.org" = {
|
||||
local-networks = [
|
||||
"208.81.1.128/28"
|
||||
"208.81.3.112/28"
|
||||
"91.229.23.204/31"
|
||||
];
|
||||
|
||||
local-users = [ "niten"];
|
||||
local-users = [ "niten" "reaper" ];
|
||||
local-groups = [ "admin" ];
|
||||
local-admins = [ "niten" ];
|
||||
local-admins = [ "niten" "reaper" ];
|
||||
admin-email = "nitenn@fudo.org";
|
||||
gssapi-realm = "FUDO.ORG";
|
||||
# kerberos-master = "legatus";
|
||||
primary-nameserver = "legatus";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -67,8 +67,8 @@ with lib; {
|
||||
|
||||
interfaces = {
|
||||
extif0 = {
|
||||
# output of: echo legatus-extif0|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'
|
||||
macAddress = pkgs.lib.fudo.network.generate-mac-address "legatus" "extif0";
|
||||
macAddress =
|
||||
pkgs.lib.fudo.network.generate-mac-address "legatus" "extif0";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
74
config/hardware/nutboy3.nix
Normal file
74
config/hardware/nutboy3.nix
Normal file
@ -0,0 +1,74 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib; {
|
||||
boot = {
|
||||
initrd = {
|
||||
availableKernelModules = [
|
||||
FIXME
|
||||
];
|
||||
kernelModules = [ "dm-snapshot" ];
|
||||
};
|
||||
kernelModules = [ FIXME ];
|
||||
extraModulePackages = [ ];
|
||||
loader.grub = {
|
||||
enable = true;
|
||||
version = 2;
|
||||
device = "/dev/sda";
|
||||
};
|
||||
|
||||
supportedFilesystems = [ "btrfs" ];
|
||||
};
|
||||
|
||||
fileSystems = {
|
||||
"/" = {
|
||||
device = "root-tmpfs";
|
||||
fsType = "tmpfs";
|
||||
options = [ "mode=755" "noexec" ];
|
||||
};
|
||||
|
||||
"/boot" = {
|
||||
device = "/dev/disk/by-label/boot";
|
||||
fsType = "ext4";
|
||||
options = [ "noexec" "noatime" "nodiratime" ];
|
||||
};
|
||||
|
||||
"/nix" = {
|
||||
device = "/dev/disk/by-label/data";
|
||||
fsType = "btrfs";
|
||||
options = [ "subvol=@nix" "compress=zstd" "noatime" "nodiratime" ];
|
||||
};
|
||||
|
||||
"/var/log" = {
|
||||
device = "/dev/disk/by-label/data";
|
||||
fsType = "btrfs";
|
||||
options = [ "subvol=@logs" "compress=zstd" "noatime" "nodiratime" "noexec" ];
|
||||
neededForBoot = true;
|
||||
};
|
||||
|
||||
"/state" = {
|
||||
device = "/dev/disk/by-label/data";
|
||||
fsType = "btrfs";
|
||||
options = [ "subvol=@state" "compress=zstd" "noatime" "nodiratime" "noexec" ];
|
||||
};
|
||||
};
|
||||
|
||||
swapDevices = [{ device = "/dev/disk/by-label/swap"; }];
|
||||
|
||||
networking = {
|
||||
macvlans = {
|
||||
extif0 = {
|
||||
interface = "eno1";
|
||||
mode = "bridge";
|
||||
};
|
||||
};
|
||||
|
||||
useDHCP = false;
|
||||
|
||||
interfaces = {
|
||||
extif0 = {
|
||||
macAddress =
|
||||
pkgs.lib.fudo.network.generate-mac-address config.instance.hostname "extif0";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -8,7 +8,6 @@ let
|
||||
domain = config.fudo.domains.${domain-name};
|
||||
site-name = config.fudo.hosts.${hostname}.site;
|
||||
site = config.fudo.sites.${site-name};
|
||||
host-fqdn = "${hostname}.${domain-name}";
|
||||
|
||||
local-packages = with pkgs; [ ldns.examples ];
|
||||
|
||||
@ -56,80 +55,67 @@ in {
|
||||
# };
|
||||
# };
|
||||
|
||||
fudo = {
|
||||
fudo = {
|
||||
hosts.legatus.external-interfaces = [ "extif0" ];
|
||||
secrets.host-secrets.legatus = let
|
||||
files = config.fudo.secrets.files;
|
||||
in {
|
||||
# postgres-keytab = {
|
||||
# source-file = files.service-keytabs.procul.postgres;
|
||||
# target-file = "/srv/postgres/secure/postgres.keytab";
|
||||
# user = "root";
|
||||
# };
|
||||
|
||||
# secrets.host-secrets.procul = let
|
||||
# files = config.fudo.secrets.files;
|
||||
# in {
|
||||
# postgres-keytab = {
|
||||
# source-file = files.service-keytabs.procul.postgres;
|
||||
# target-file = "/srv/postgres/secure/postgres.keytab";
|
||||
# user = "root";
|
||||
# };
|
||||
# gitea-database-password = {
|
||||
# source-file = files.service-passwords.procul.gitea-database;
|
||||
# target-file = "/srv/gitea/secure/database.passwd";
|
||||
# user = config.fudo.git.user;
|
||||
# };
|
||||
|
||||
# gitea-database-password = {
|
||||
# source-file = files.service-passwords.procul.gitea-database;
|
||||
# target-file = "/srv/gitea/secure/database.passwd";
|
||||
# user = config.fudo.git.user;
|
||||
# };
|
||||
# };
|
||||
heimdal-master-key = {
|
||||
source-file = files.realm-master-keys."FUDO.ORG";
|
||||
target-file = "/run/heimdal/master-key";
|
||||
user = config.fudo.auth.kdc.user;
|
||||
};
|
||||
|
||||
# client.dns = {
|
||||
# enable = true;
|
||||
# ipv4 = true;
|
||||
# ipv6 = true;
|
||||
# user = "fudo-client";
|
||||
# external-interface = "extif0";
|
||||
# };
|
||||
ipropd-keytab = {
|
||||
source-file = files.service-keytabs.legatus.ipropd;
|
||||
target-file = "/run/heimdal/ipropd.keytab";
|
||||
user = config.fudo.auth.kdc.user;
|
||||
};
|
||||
};
|
||||
|
||||
# auth.kdc = {
|
||||
# enable = true;
|
||||
# realm = "INFORMIS.LAND";
|
||||
# bind-addresses = [ host-ipv4 "127.0.0.1" ];
|
||||
# acl = {
|
||||
# "niten" = { perms = [ "add" "change-password" "list" ]; };
|
||||
# "*/root" = { perms = [ "all" ]; };
|
||||
# };
|
||||
# };
|
||||
client.dns = {
|
||||
ipv4 = true;
|
||||
ipv6 = true;
|
||||
user = "fudo-client";
|
||||
external-interface = "extif0";
|
||||
};
|
||||
|
||||
# secure-dns-proxy = {
|
||||
# enable = true;
|
||||
# upstream-dns =
|
||||
# [ "https://1.1.1.1/dns-query" "https://1.0.0.1/dns-query" ];
|
||||
# bootstrap-dns = "1.1.1.1";
|
||||
# listen-ips = [ "127.0.0.1" ];
|
||||
# listen-port = 53;
|
||||
# allowed-networks = [ "1.1.1.1/32" "1.0.0.1/32" "localhost" "link-local" ];
|
||||
# };
|
||||
auth.kdc = {
|
||||
enable = true;
|
||||
realm = "FUDO.ORG";
|
||||
bind-addresses = [ host-ipv4 "127.0.0.1" ];
|
||||
master-key-file =
|
||||
secrets.heimdal-master-key.target-file;
|
||||
state-directory = "/state/kerberos";
|
||||
slave-config = {
|
||||
master-host = "france";
|
||||
ipropd-keytab = secrets.ipropd-keytab.target-file;
|
||||
};
|
||||
};
|
||||
|
||||
# dns = {
|
||||
# enable = true;
|
||||
# identity = "procul.informis.land";
|
||||
# nameservers = {
|
||||
# ns1 = {
|
||||
# ipv4-address = host-ipv4;
|
||||
# description = "Primary Informis Nameserver";
|
||||
# };
|
||||
# ns2 = {
|
||||
# ipv4-address = host-ipv4;
|
||||
# description = "Secondary Informis Nameserver";
|
||||
# };
|
||||
# };
|
||||
secure-dns-proxy = {
|
||||
enable = true;
|
||||
upstream-dns =
|
||||
[ "https://1.1.1.1/dns-query" "https://1.0.0.1/dns-query" ];
|
||||
bootstrap-dns = "1.1.1.1";
|
||||
listen-ips = [ "127.0.0.1" ];
|
||||
listen-port = 53;
|
||||
allowed-networks = [ "1.1.1.1/32" "1.0.0.1/32" "localhost" "link-local" ];
|
||||
};
|
||||
|
||||
# listen-ips = [ host-ipv4 ];
|
||||
|
||||
# domains = {
|
||||
# "informis.land" = {
|
||||
# dnssec = true;
|
||||
# default-host = host-ipv4;
|
||||
# gssapi-realm = "INFORMIS.LAND";
|
||||
# mx = [ "smtp.informis.land" ];
|
||||
# network-definition = config.fudo.networks."informis.land";
|
||||
# dmarc-report-address = "dmarc-report@informis.land";
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
dns.state-directory = "/state/nsd";
|
||||
|
||||
# mail-server = {
|
||||
# enable = true;
|
||||
@ -218,16 +204,5 @@ in {
|
||||
# listen-port = 2222;
|
||||
# };
|
||||
# };
|
||||
|
||||
# acme = {
|
||||
# enable = true;
|
||||
# admin-address = "admin@${domain-name}";
|
||||
# hostnames = [
|
||||
# "informis.land"
|
||||
# "imap.informis.land"
|
||||
# "smtp.informis.land"
|
||||
# "gemini.informis.land"
|
||||
# ];
|
||||
# };
|
||||
};
|
||||
}
|
||||
|
@ -60,13 +60,7 @@ in {
|
||||
network-definition = config.fudo.networks.${domain-name};
|
||||
};
|
||||
|
||||
client.dns = {
|
||||
enable = true;
|
||||
external-interface = "enp1s0";
|
||||
## This is now set by hosts.nix
|
||||
# password-file =
|
||||
# config.fudo.secrets.host-secrets.limina.backplane-client-passwd.target-file;
|
||||
};
|
||||
client.dns.external-interface = "enp1s0";
|
||||
|
||||
garbage-collector = {
|
||||
enable = true;
|
||||
|
196
config/host-config/nutboy3.nix
Normal file
196
config/host-config/nutboy3.nix
Normal file
@ -0,0 +1,196 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
hostname = "nutboy3";
|
||||
host-ipv4 = "199.87.154.175";
|
||||
domain-name = config.fudo.hosts.${hostname}.domain;
|
||||
domain = config.fudo.domains.${domain-name};
|
||||
site-name = config.fudo.hosts.${hostname}.site;
|
||||
site = config.fudo.sites.${site-name};
|
||||
|
||||
local-packages = with pkgs; [ ldns.examples ];
|
||||
|
||||
secrets = config.fudo.secrets.host-secrets.${hostname};
|
||||
|
||||
in {
|
||||
networking = {
|
||||
enableIPv6 = true;
|
||||
|
||||
nameservers = [ "1.1.1.1" ];
|
||||
defaultGateway = {
|
||||
address = site.gateway-v4;
|
||||
interface = "extif0";
|
||||
};
|
||||
|
||||
interfaces.extif0.ipv4.addresses = [{
|
||||
address = host-ipv4;
|
||||
prefixLength = 31;
|
||||
}];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"L /etc/adjtime - - - - /state/etc/adjtime"
|
||||
];
|
||||
|
||||
environment.systemPackages = local-packages;
|
||||
|
||||
# networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
|
||||
# informis.cl-gemini = {
|
||||
# enable = true;
|
||||
|
||||
# hostname = "gemini.informis.land";
|
||||
# server-ip = host-ipv4;
|
||||
# document-root = "/srv/gemini/root";
|
||||
# textfiles-archive = "${pkgs.textfiles}";
|
||||
# slynk-port = 4005;
|
||||
|
||||
# feeds = {
|
||||
# viator = {
|
||||
# title = "viator's phlog";
|
||||
# path = "/home/viator/gemini-public/feed/";
|
||||
# url = "gemini://informis.land/user/viator/feed/";
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
|
||||
fudo = {
|
||||
hosts.${hostname}.external-interfaces = [ "extif0" ];
|
||||
secrets.host-secrets.nutboy3 = let
|
||||
files = config.fudo.secrets.files;
|
||||
in {
|
||||
# heimdal-master-key = {
|
||||
# source-file = files.realm-master-keys."FUDO.ORG";
|
||||
# target-file = "/run/heimdal/master-key";
|
||||
# user = config.fudo.auth.kdc.user;
|
||||
# };
|
||||
|
||||
# ipropd-keytab = {
|
||||
# source-file = files.service-keytabs.legatus.ipropd;
|
||||
# target-file = "/run/heimdal/ipropd.keytab";
|
||||
# user = config.fudo.auth.kdc.user;
|
||||
# };
|
||||
};
|
||||
|
||||
client.dns = {
|
||||
ipv4 = true;
|
||||
ipv6 = true;
|
||||
user = "fudo-client";
|
||||
external-interface = "extif0";
|
||||
};
|
||||
|
||||
# auth.kdc = {
|
||||
# enable = true;
|
||||
# realm = "FUDO.ORG";
|
||||
# bind-addresses = [ host-ipv4 "127.0.0.1" ];
|
||||
# master-key-file =
|
||||
# secrets.heimdal-master-key.target-file;
|
||||
# state-directory = "/state/kerberos";
|
||||
# slave-config = {
|
||||
# master-host = "france";
|
||||
# ipropd-keytab = secrets.ipropd-keytab.target-file;
|
||||
# };
|
||||
# };
|
||||
|
||||
# secure-dns-proxy = {
|
||||
# enable = true;
|
||||
# upstream-dns =
|
||||
# [ "https://1.1.1.1/dns-query" "https://1.0.0.1/dns-query" ];
|
||||
# bootstrap-dns = "1.1.1.1";
|
||||
# listen-ips = [ "127.0.0.1" ];
|
||||
# listen-port = 53;
|
||||
# allowed-networks = [ "1.1.1.1/32" "1.0.0.1/32" "localhost" "link-local" ];
|
||||
# };
|
||||
|
||||
# dns.state-directory = "/state/nsd";
|
||||
|
||||
# mail-server = {
|
||||
# enable = true;
|
||||
# debug = true;
|
||||
|
||||
# domain = domain-name;
|
||||
# mail-hostname = "${host-fqdn}";
|
||||
# monitoring = false;
|
||||
# mail-user = "mailuser";
|
||||
# mail-user-id = 525;
|
||||
# mail-group = "mailgroup";
|
||||
# clamav.enable = true;
|
||||
# dkim.signing = true;
|
||||
|
||||
# dovecot = {
|
||||
# ssl-certificate = acme-certificate "imap.${domain-name}";
|
||||
# ssl-private-key = acme-private-key "imap.${domain-name}";
|
||||
# };
|
||||
|
||||
# postfix = {
|
||||
# ssl-certificate = acme-certificate "smtp.${domain-name}";
|
||||
# ssl-private-key = acme-private-key "smtp.${domain-name}";
|
||||
# };
|
||||
|
||||
# # This should NOT include the primary domain
|
||||
# local-domains = [ host-fqdn "smtp.${domain-name}" ];
|
||||
|
||||
# mail-directory = "/srv/mailserver/mail";
|
||||
# state-directory = "/srv/mailserver/state";
|
||||
|
||||
# trusted-networks = [ "172.86.179.16/29" "127.0.0.0/16" ];
|
||||
|
||||
# alias-users = {
|
||||
# root = [ "niten" ];
|
||||
# postmaster = [ "niten" ];
|
||||
# hostmaster = [ "niten" ];
|
||||
# webmaster = [ "niten" ];
|
||||
# system = [ "niten" ];
|
||||
# admin = [ "niten" ];
|
||||
# dmarc-report = [ "niten" ];
|
||||
# };
|
||||
# };
|
||||
|
||||
# postgresql = {
|
||||
# enable = true;
|
||||
# ssl-certificate = (acme-certificate host-fqdn);
|
||||
# ssl-private-key = (acme-private-key host-fqdn);
|
||||
# keytab = secrets.postgres-keytab.target-file;
|
||||
# local-networks = local-networks;
|
||||
|
||||
# users = {
|
||||
# gituser = {
|
||||
# password-file =
|
||||
# secrets.gitea-database-password.target-file;
|
||||
# databases = {
|
||||
# git = {
|
||||
# access = "CONNECT";
|
||||
# entity-access = {
|
||||
# "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE";
|
||||
# "ALL SEQUENCES IN SCHEMA public" = "SELECT, UPDATE";
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
|
||||
# databases = { git = { users = [ "niten" ]; }; };
|
||||
# };
|
||||
|
||||
# git = {
|
||||
# enable = true;
|
||||
# hostname = "git.informis.land";
|
||||
# site-name = "informis git";
|
||||
# user = "gituser";
|
||||
# repository-dir = /srv/git/repo;
|
||||
# state-dir = /srv/git/state;
|
||||
# database = {
|
||||
# user = "gituser";
|
||||
# password-file =
|
||||
# secrets.gitea-database-password.target-file;
|
||||
# hostname = "127.0.0.1";
|
||||
# name = "git";
|
||||
# };
|
||||
# ssh = {
|
||||
# listen-ip = host-ipv4;
|
||||
# listen-port = 2222;
|
||||
# };
|
||||
# };
|
||||
};
|
||||
}
|
@ -101,25 +101,22 @@ in {
|
||||
target-file = "/srv/gitea/secure/database.passwd";
|
||||
user = config.fudo.git.user;
|
||||
};
|
||||
|
||||
heimdal-master-key = {
|
||||
source-file = files.realm-master-keys."INFORMIS.LAND";
|
||||
target-file = "/run/heimdal/master-key";
|
||||
user = config.fudo.auth.kdc.user;
|
||||
};
|
||||
};
|
||||
|
||||
client.dns = {
|
||||
enable = true;
|
||||
ipv4 = true;
|
||||
ipv6 = true;
|
||||
user = "fudo-client";
|
||||
external-interface = "extif0";
|
||||
};
|
||||
|
||||
auth.kdc = {
|
||||
enable = true;
|
||||
realm = "INFORMIS.LAND";
|
||||
bind-addresses = [ host-ipv4 "127.0.0.1" ];
|
||||
acl = {
|
||||
"niten" = { perms = [ "add" "change-password" "list" ]; };
|
||||
"*/root" = { perms = [ "all" ]; };
|
||||
};
|
||||
};
|
||||
auth.kdc.master-key-file = secrets.heimdal-master-key.target-file;
|
||||
|
||||
secure-dns-proxy = {
|
||||
enable = true;
|
||||
@ -131,34 +128,6 @@ in {
|
||||
allowed-networks = [ "1.1.1.1/32" "1.0.0.1/32" "localhost" "link-local" ];
|
||||
};
|
||||
|
||||
dns = {
|
||||
enable = true;
|
||||
identity = "procul.informis.land";
|
||||
nameservers = {
|
||||
ns1 = {
|
||||
ipv4-address = host-ipv4;
|
||||
description = "Primary Informis Nameserver";
|
||||
};
|
||||
ns2 = {
|
||||
ipv4-address = host-ipv4;
|
||||
description = "Secondary Informis Nameserver";
|
||||
};
|
||||
};
|
||||
|
||||
listen-ips = [ host-ipv4 ];
|
||||
|
||||
domains = {
|
||||
"informis.land" = {
|
||||
dnssec = true;
|
||||
default-host = host-ipv4;
|
||||
gssapi-realm = "INFORMIS.LAND";
|
||||
mx = [ "smtp.informis.land" ];
|
||||
network-definition = config.fudo.networks."informis.land";
|
||||
dmarc-report-address = "dmarc-report@informis.land";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
mail-server = {
|
||||
enable = true;
|
||||
debug = true;
|
||||
@ -232,8 +201,8 @@ in {
|
||||
hostname = "git.informis.land";
|
||||
site-name = "informis git";
|
||||
user = "gituser";
|
||||
repository-dir = /srv/git/repo;
|
||||
state-dir = /srv/git/state;
|
||||
repository-dir = "/srv/git/repo";
|
||||
state-dir = "/srv/git/state";
|
||||
database = {
|
||||
user = "gituser";
|
||||
password-file =
|
||||
|
@ -49,11 +49,6 @@ in {
|
||||
|
||||
system.stateVersion = "21.05";
|
||||
|
||||
security.sudo.extraConfig = ''
|
||||
# Due to tmpfs home, it'll always lecture otherwise
|
||||
Defaults lecture = never
|
||||
'';
|
||||
|
||||
services = {
|
||||
openssh = {
|
||||
hostKeys = [
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
description = "informis.land server.";
|
||||
description = "eur.fudo.org server.";
|
||||
rp = "niten";
|
||||
admin-email = "niten@fudo.org";
|
||||
domain = "eur.fudo.org";
|
||||
@ -11,7 +11,7 @@
|
||||
nixos-system = true;
|
||||
machine-id = "749bbf411088411b8784b76bb44bd617";
|
||||
master-key = {
|
||||
public-key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIqUnzf8bfPyoJX6XjFqD6v5MZQnV8STP0152VS3uwM7";
|
||||
public-key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGsTkxsVViISxZYtqwNs6DEK2XgyBUPhqio4XPQbMKNo";
|
||||
key-path = "/state/master-key/ed25519_key";
|
||||
};
|
||||
# initrd-network = {
|
||||
|
24
config/hosts/nutboy3.nix
Normal file
24
config/hosts/nutboy3.nix
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
description = "fudo.org server.";
|
||||
rp = "reaper";
|
||||
admin-email = "reaper@fudo.org";
|
||||
domain = "fudo.org";
|
||||
site = "pioneer";
|
||||
profile = "server";
|
||||
enable-gui = false;
|
||||
arch = "x86_64-linux";
|
||||
nixos-system = true;
|
||||
machine-id = "d608fb62dc1e493a9a0ebf173ab255b2";
|
||||
master-key = {
|
||||
public-key = FIXME;
|
||||
key-path = "/state/master-key/ed25519_key";
|
||||
};
|
||||
# initrd-network = {
|
||||
# ip = "172.86.179.18";
|
||||
# interface = "enp0s25";
|
||||
# keypair = {
|
||||
# public-key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIgvl/pxPGN5XuUFsEywHV/PJMI+wPHA6NKTtE8SZC04";
|
||||
# private-key-file = "/state/ssh/initrd/ssh_ed25519_key";
|
||||
# };
|
||||
# };
|
||||
}
|
@ -10,10 +10,6 @@
|
||||
|
||||
srv-records = {
|
||||
tcp = {
|
||||
domain = [{
|
||||
host = "ns1.informis.land";
|
||||
port = 53;
|
||||
}];
|
||||
ssh = [{
|
||||
host = "procul.informis.land";
|
||||
port = 22;
|
||||
@ -22,14 +18,6 @@
|
||||
host = "procul.informis.land";
|
||||
port = 587;
|
||||
}];
|
||||
kerberos = [{
|
||||
host = "procul.informis.land";
|
||||
port = 88;
|
||||
}];
|
||||
kerberos-adm = [{
|
||||
host = "procul.informis.land";
|
||||
port = 749;
|
||||
}];
|
||||
imaps = [{
|
||||
host = "procul.informis.land";
|
||||
port = 993;
|
||||
@ -49,25 +37,6 @@
|
||||
port = 443;
|
||||
}];
|
||||
};
|
||||
|
||||
udp = {
|
||||
domain = [{
|
||||
host = "ns1.informis.land";
|
||||
port = 53;
|
||||
}];
|
||||
kerberos = [{
|
||||
host = "procul.informis.land";
|
||||
port = 88;
|
||||
}];
|
||||
kerberos-master = [{
|
||||
host = "procul.informis.land";
|
||||
port = 88;
|
||||
}];
|
||||
kpasswd = [{
|
||||
host = "procul.informis.land";
|
||||
port = 464;
|
||||
}];
|
||||
};
|
||||
};
|
||||
|
||||
hosts = {
|
||||
|
@ -8,6 +8,7 @@ let
|
||||
cryptsetup
|
||||
git
|
||||
heimdal
|
||||
mosh
|
||||
openssh_gssapi
|
||||
tldr
|
||||
vim
|
||||
|
@ -7,15 +7,21 @@ let
|
||||
try-attr = attr: set: if (hasAttr attr set) then set.${attr} else null;
|
||||
|
||||
in {
|
||||
config = mkIf has-secret-files {
|
||||
fudo.secrets.host-secrets.${hostname} = let
|
||||
keytab-file = try-attr hostname config.fudo.secrets.files.host-keytabs;
|
||||
in mkIf (keytab-file != null) {
|
||||
host-keytab = {
|
||||
source-file = keytab-file;
|
||||
target-file = "/etc/krb5.keytab";
|
||||
user = "root";
|
||||
};
|
||||
config = mkIf has-secret-files (let
|
||||
keytab-file = try-attr hostname config.fudo.secrets.files.host-keytabs;
|
||||
in {
|
||||
environment.etc."krb5.keytab" = mkIf (keytab-file != null) {
|
||||
source =
|
||||
config.fudo.secrets.host-secrets.${hostname}.host-keytab.target-file;
|
||||
user = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
};
|
||||
|
||||
fudo.secrets.host-secrets.${hostname}.host-keytab = mkIf (keytab-file != null) {
|
||||
source-file = keytab-file;
|
||||
target-file = "/run/kerberos/krb5.keytab";
|
||||
user = "root";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ in {
|
||||
(map
|
||||
(keypair: nameValuePair "host-${keypair.key-type}-private-key" {
|
||||
source-file = keypair.private-key;
|
||||
target-file = "/var/run/ssh/private/host-${keypair.key-type}-private-key";
|
||||
target-file = "/run/openssh/private/host-${keypair.key-type}-private-key";
|
||||
user = "root";
|
||||
})
|
||||
host-keypairs);
|
||||
@ -52,8 +52,11 @@ in {
|
||||
}) config.fudo.secrets.files.host-ssh-keypairs);
|
||||
};
|
||||
|
||||
services.openssh.hostKeys = map (keypair: {
|
||||
path = "/var/run/ssh/private/host-${keypair.key-type}-private-key";
|
||||
services.openssh.hostKeys = let
|
||||
host-secrets = config.fudo.secrets.host-secrets.${hostname};
|
||||
in map (keypair: {
|
||||
path =
|
||||
host-secrets."host-${keypair.key-type}-private-key".target-file;
|
||||
type = keypair.key-type;
|
||||
}) host-keypairs;
|
||||
});
|
||||
|
5
config/site-config/nuttyclub.nix
Normal file
5
config/site-config/nuttyclub.nix
Normal file
@ -0,0 +1,5 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
|
||||
}
|
@ -38,21 +38,23 @@ in {
|
||||
options = [ "comment=systemd.automount" ];
|
||||
};
|
||||
|
||||
# "proto=tcp"
|
||||
|
||||
# NOTE: these are pointing directly to nostromo so the krb lookup works
|
||||
"/net/documents" = {
|
||||
device = "nostromo.sea.fudo.org:/export/documents";
|
||||
fsType = "nfs4";
|
||||
options = [ "comment=systemd.automount" "sec=krb5p" "proto=tcp" ];
|
||||
options = [ "comment=systemd.automount" "sec=krb5p" ];
|
||||
};
|
||||
"/net/downloads" = {
|
||||
device = "nostromo.sea.fudo.org:/export/downloads";
|
||||
fsType = "nfs4";
|
||||
options = [ "comment=systemd.automount" "sec=krb5i" "proto=tcp" ];
|
||||
options = [ "comment=systemd.automount" "sec=krb5i" ];
|
||||
};
|
||||
"/net/projects" = {
|
||||
device = "nostromo.sea.fudo.org:/export/projects";
|
||||
fsType = "nfs4";
|
||||
options = [ "comment=systemd.automount" "sec=krb5p" "proto=tcp" ];
|
||||
options = [ "comment=systemd.automount" "sec=krb5p" ];
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -38,6 +38,18 @@
|
||||
mail-server = "mail.fudo.org";
|
||||
};
|
||||
|
||||
nuttyclub = {
|
||||
gateway-v4 = "199.87.154.174";
|
||||
network = "199.87.154.174/31";
|
||||
nameservers = [ "1.1.1.1" ];
|
||||
timezone = "America/Winnipeg";
|
||||
deploy-pubkeys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDPwh522lvafTJYA0X2uFdP7Ws+Um1f8gZsARK1Y5nMzf6ZcWBF1jplTOKUVSOl4isMWni0Tu0TnX4zqCcgocWUVbwIwXSIRYqdiCPvVOH+/Ibc97n1/dYxk5JPMtbrsEw6/gWZxVg0qwe0J3dQWldEMiDY7iWhlrmIr7YL+Y3PUd7DOwp3PbfWfNyzTfE1kXcz5YvTeN+txFhbbXT0oS2R2wtc1vYXFZ/KbNstjqd+i8jszAq3ZkbbwL3aNR0RO4n8+GoIILGw8Ya4eP7D6+mYk608IhAoxpGyMrUch2TC2uvOK3rd/rw1hsTxf4AKjAZbrfd/FJaYru9ZeoLjD4bRGMdVp56F1m7pLvRiWRK62pV2Q/fjx+4KjHUrgyPd601eUIP0ayS/Rfuq8ijLpBJgO5/Y/6mFus/kjZIfRR9dXfLM67IMpyEzEITYrc/R2sedWf+YHxSh6eguAZ/kLzioar1nHLR7Wzgeu0tgWkD78WQGjpXGoefAz3xHeBg3Et0="
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGVez4of30f+j0cWKj5kYCKeFjyNsYvG9UbOMxF5hImD2lP5MSbFBv31gFgHjx3yCG4zQRZlpuyU5uWo0qIwe9N84/LcZcB9WrWKZXDmuof7zPFy0J+Hj+LVLDQI/mVXHNwkMhBMHpPrdwA05EYDAYCYklWT4cSByu10pHtST+olF8i+A+UQgUzgNZzdJVeiYZv6MBDTYsJWptGeDUkl2B0Es3gtbGYcCCfnyS3RC7DIXlDo3NBbAr7WaHY2MBbT+R/+jicn9E3IY3NCM5jENxqmvHy9MDsxEEYgFNm7IDwq4V1VRUWy277YsvRbmEaHb+osOA5u1VNN4z3UftOZcSZgR5C/vR71cENXoPt1YQpCzu7i38ojtvL+tDVEKT7sIovrQw8q1sszNlW2nXh8RSPiIq5TMnrV73MP0egKcr9n3tfxwi1BIkLjvfom/02BkTK9R9v+VMNhYU1YwROhORCiMIgoxUGiUvtH8u38JGr7E0hhMoAjCE5k80WPUivl0="
|
||||
];
|
||||
mail-server = "mail.fudo.org";
|
||||
};
|
||||
|
||||
russell = {
|
||||
gateway-v4 = "10.0.0.1";
|
||||
nameservers = [ "10.0.0.1" ];
|
||||
@ -61,7 +73,7 @@
|
||||
};
|
||||
|
||||
worldstream = {
|
||||
gateway-v4 = "91.229.23.204";
|
||||
gateway-v4 = "91.229.23.1";
|
||||
network = "91.229.23.0/24";
|
||||
nameservers = [ "1.1.1.1" "2606:4700:4700::1111" ];
|
||||
timezone = "Europe/Amsterdam";
|
||||
|
@ -34,6 +34,7 @@ with lib; {
|
||||
./fudo/netinfo-email.nix
|
||||
./fudo/networks.nix
|
||||
./fudo/node-exporter.nix
|
||||
./fudo/nsd.nix
|
||||
./fudo/password.nix
|
||||
./fudo/postgres.nix
|
||||
./fudo/prometheus.nix
|
||||
|
@ -11,8 +11,6 @@ let
|
||||
|
||||
in {
|
||||
options.fudo.client.dns = {
|
||||
enable = mkEnableOption "Enable Fudo DynDNS Client.";
|
||||
|
||||
ipv4 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
@ -71,7 +69,7 @@ in {
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
config = {
|
||||
|
||||
users.users = {
|
||||
"${cfg.user}" = {
|
||||
|
@ -116,6 +116,12 @@ in {
|
||||
description = "A list of IPs on which to listen for DNS queries.";
|
||||
example = [ "1.2.3.4" ];
|
||||
};
|
||||
|
||||
state-directory = mkOption {
|
||||
type = str;
|
||||
description = "Path at which to store nameserver state, including DNSSEC keys.";
|
||||
default = "/var/lib/nsd";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
@ -124,48 +130,49 @@ in {
|
||||
allowedUDPPorts = [ 53 ];
|
||||
};
|
||||
|
||||
services.nsd = {
|
||||
fudo.nsd = {
|
||||
enable = true;
|
||||
identity = cfg.identity;
|
||||
interfaces = cfg.listen-ips;
|
||||
stateDir = cfg.state-directory;
|
||||
zones = mapAttrs' (dom: dom-cfg: let
|
||||
net-cfg = dom-cfg.network-definition;
|
||||
in nameValuePair "${dom}." {
|
||||
dnssec = dom-cfg.dnssec;
|
||||
dnssec = dom-cfg.dnssec;
|
||||
|
||||
data = ''
|
||||
$ORIGIN ${dom}.
|
||||
$TTL 12h
|
||||
data = ''
|
||||
$ORIGIN ${dom}.
|
||||
$TTL 12h
|
||||
|
||||
@ IN SOA ns1.${dom}. hostmaster.${dom}. (
|
||||
${toString config.instance.build-timestamp}
|
||||
30m
|
||||
2m
|
||||
3w
|
||||
5m)
|
||||
@ IN SOA ns1.${dom}. hostmaster.${dom}. (
|
||||
${toString config.instance.build-timestamp}
|
||||
30m
|
||||
2m
|
||||
3w
|
||||
5m)
|
||||
|
||||
${optionalString (dom-cfg.default-host != null)
|
||||
${optionalString (dom-cfg.default-host != null)
|
||||
"@ IN A ${dom-cfg.default-host}"}
|
||||
|
||||
${mxRecords dom-cfg.mx}
|
||||
${mxRecords dom-cfg.mx}
|
||||
|
||||
$TTL 6h
|
||||
$TTL 6h
|
||||
|
||||
${optionalString (dom-cfg.gssapi-realm != null)
|
||||
${optionalString (dom-cfg.gssapi-realm != null)
|
||||
''_kerberos IN TXT "${dom-cfg.gssapi-realm}"''}
|
||||
|
||||
${nsRecords dom cfg.nameservers}
|
||||
${join-lines (mapAttrsToList hostRecords cfg.nameservers)}
|
||||
${nsRecords dom cfg.nameservers}
|
||||
${join-lines (mapAttrsToList hostRecords cfg.nameservers)}
|
||||
|
||||
${dmarcRecord dom-cfg.dmarc-report-address}
|
||||
${dmarcRecord dom-cfg.dmarc-report-address}
|
||||
|
||||
${join-lines
|
||||
${join-lines
|
||||
(mapAttrsToList makeSrvProtocolRecords net-cfg.srv-records)}
|
||||
${join-lines (mapAttrsToList hostRecords net-cfg.hosts)}
|
||||
${join-lines (mapAttrsToList cnameRecord net-cfg.aliases)}
|
||||
${join-lines net-cfg.verbatim-dns-records}
|
||||
'';
|
||||
}) cfg.domains;
|
||||
${join-lines (mapAttrsToList hostRecords net-cfg.hosts)}
|
||||
${join-lines (mapAttrsToList cnameRecord net-cfg.aliases)}
|
||||
${join-lines net-cfg.verbatim-dns-records}
|
||||
'';
|
||||
}) cfg.domains;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
69
lib/fudo/domain/dns.nix
Normal file
69
lib/fudo/domain/dns.nix
Normal file
@ -0,0 +1,69 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
hostname = config.instance.hostname;
|
||||
domain = config.instance.local-domain;
|
||||
cfg = config.fudo.domains.${domain};
|
||||
|
||||
served-domain = cfg.primary-nameserver != null;
|
||||
|
||||
is-primary = hostname == cfg.primary-nameserver;
|
||||
|
||||
create-srv-record = port: hostname: {
|
||||
port = port;
|
||||
host = hostname;
|
||||
};
|
||||
|
||||
in {
|
||||
config = {
|
||||
fudo.dns = mkIf is-primary (let
|
||||
primary-ip = pkgs.lib.fudo.network.host-ipv4 config hostname;
|
||||
all-ips = pkgs.lib.fudo.network.host-ips config hostname;
|
||||
in {
|
||||
enable = true;
|
||||
identity = "${hostname}.${domain}";
|
||||
nameservers = {
|
||||
ns1 = {
|
||||
ipv4-address = primary-ip;
|
||||
description = "Primary ${domain} nameserver";
|
||||
};
|
||||
};
|
||||
|
||||
# Deliberately leaving out localhost so the primary nameserver
|
||||
# can use a custom recursor
|
||||
listen-ips = all-ips;
|
||||
|
||||
domains = {
|
||||
${domain} = {
|
||||
dnssec = true;
|
||||
default-host = primary-ip;
|
||||
gssapi-realm = cfg.gssapi-realm;
|
||||
mx = optional (cfg.primary-mailserver != null)
|
||||
cfg.primary-mailserver;
|
||||
# TODO: there's no guarantee this exists...
|
||||
dmarc-report-address = "dmarc-report@${domain}";
|
||||
|
||||
network-definition = let
|
||||
network = config.fudo.networks.${domain};
|
||||
in network // {
|
||||
srv-records = {
|
||||
tcp = {
|
||||
domain = [{
|
||||
host = "ns1.${domain}";
|
||||
port = 53;
|
||||
}];
|
||||
};
|
||||
udp = {
|
||||
domain = [{
|
||||
host = "ns1.${domain}";
|
||||
port = 53;
|
||||
}];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
74
lib/fudo/domain/kerberos.nix
Normal file
74
lib/fudo/domain/kerberos.nix
Normal file
@ -0,0 +1,74 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
hostname = config.instance.hostname;
|
||||
domain = config.instance.local-domain;
|
||||
cfg = config.fudo.domains.${domain};
|
||||
|
||||
in {
|
||||
config = let
|
||||
hostname = config.instance.hostname;
|
||||
is-master = hostname == cfg.kerberos-master;
|
||||
is-slave = elem hostname cfg.kerberos-slaves;
|
||||
|
||||
kerberized-domain = cfg.kerberos-master != null;
|
||||
|
||||
in {
|
||||
fudo = {
|
||||
auth.kdc = mkIf (is-master || is-slave) {
|
||||
enable = true;
|
||||
realm = cfg.gssapi-realm;
|
||||
# TODO: Also bind to ::1?
|
||||
bind-addresses =
|
||||
(pkgs.lib.fudo.network.host-ips config hostname) ++
|
||||
[ "127.0.0.1" ] ++ (optional config.networking.enableIPv6 "::1");
|
||||
master-config = mkIf is-master {
|
||||
acl = let
|
||||
admin-entries = genAttrs cfg.local-admins
|
||||
(admin: {
|
||||
perms = [ "add" "change-password" "list" ];
|
||||
});
|
||||
in admin-entries // {
|
||||
"*/root" = { perms = [ "all" ]; };
|
||||
};
|
||||
};
|
||||
slave-config = mkIf is-slave {
|
||||
master-host = cfg.kerberos-master;
|
||||
# You gotta provide the keytab yourself, sorry...
|
||||
};
|
||||
};
|
||||
|
||||
dns.domains.${domain} = {
|
||||
network-definition = mkIf kerberized-domain {
|
||||
srv-records = let
|
||||
get-fqdn = hostname:
|
||||
"${hostname}.${config.fudo.hosts.${hostname}.domain}";
|
||||
|
||||
create-srv-record = port: hostname: {
|
||||
port = port;
|
||||
host = hostname;
|
||||
};
|
||||
|
||||
all-servers = map get-fqdn
|
||||
([cfg.kerberos-master] ++ cfg.kerberos-slaves);
|
||||
|
||||
master-servers =
|
||||
map get-fqdn [cfg.kerberos-master];
|
||||
|
||||
in {
|
||||
tcp = {
|
||||
kerberos = map (create-srv-record 88) all-servers;
|
||||
kerberos-adm = map (create-srv-record 749) master-servers;
|
||||
};
|
||||
udp = {
|
||||
kerberos = map (create-srv-record 88) all-servers;
|
||||
kerberos-master = map (create-srv-record 88) master-servers;
|
||||
kpasswd = map (create-srv-record 464) master-servers;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -2,50 +2,80 @@
|
||||
|
||||
with lib;
|
||||
let
|
||||
domainOpts = { domain, ... }: {
|
||||
options = {
|
||||
hostname = config.instance.hostname;
|
||||
domain = config.instance.local-domain;
|
||||
|
||||
domainOpts = { name, ... }: let
|
||||
domain = name;
|
||||
in {
|
||||
options = with types; {
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
type = str;
|
||||
description = "Domain name.";
|
||||
default = domain;
|
||||
};
|
||||
|
||||
local-networks = mkOption {
|
||||
type = with types; listOf str;
|
||||
type = listOf str;
|
||||
description =
|
||||
"A list of networks to be considered trusted on this network.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
local-users = mkOption {
|
||||
type = with types; listOf str;
|
||||
type = listOf str;
|
||||
description =
|
||||
"A list of users who should have local (i.e. login) access to _all_ hosts in this domain.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
local-admins = mkOption {
|
||||
type = with types; listOf str;
|
||||
type = listOf str;
|
||||
description =
|
||||
"A list of users who should have admin access to _all_ hosts in this domain.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
local-groups = mkOption {
|
||||
type = with types; listOf str;
|
||||
type = listOf str;
|
||||
description = "List of groups which should exist within this domain.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
admin-email = mkOption {
|
||||
type = types.str;
|
||||
type = str;
|
||||
description = "Email for the administrator of this domain.";
|
||||
default = "admin@fudo.org";
|
||||
default = "admin@${domain}";
|
||||
};
|
||||
|
||||
gssapi-realm = mkOption {
|
||||
type = with types; nullOr str;
|
||||
type = str;
|
||||
description = "GSSAPI (i.e. Kerberos) realm of this domain.";
|
||||
default = toUpper domain;
|
||||
};
|
||||
|
||||
kerberos-master = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Hostname of the Kerberos master server for the domain, if applicable.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
kerberos-slaves = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of hosts acting as Kerberos slaves for the domain.";
|
||||
default = [];
|
||||
};
|
||||
|
||||
primary-nameserver = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Hostname of the primary nameserver for this domain.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
primary-mailserver = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Hostname of the primary mail server for this domain.";
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -56,4 +86,9 @@ in {
|
||||
description = "Domain configurations for all domains known to the system.";
|
||||
default = { };
|
||||
};
|
||||
|
||||
imports = [
|
||||
./domain/kerberos.nix
|
||||
./domain/dns.nix
|
||||
];
|
||||
}
|
||||
|
@ -64,15 +64,15 @@ in {
|
||||
};
|
||||
|
||||
repository-dir = mkOption {
|
||||
type = path;
|
||||
type = str;
|
||||
description = "Path at which to store repositories.";
|
||||
example = /srv/git/repo;
|
||||
example = "/srv/git/repo";
|
||||
};
|
||||
|
||||
state-dir = mkOption {
|
||||
type = path;
|
||||
type = str;
|
||||
description = "Path at which to store server state.";
|
||||
example = /srv/git/state;
|
||||
example = "/srv/git/state";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
@ -103,6 +103,15 @@ in {
|
||||
networking.firewall.allowedTCPPorts =
|
||||
mkIf (cfg.ssh != null) [ cfg.ssh.listen-port ];
|
||||
|
||||
environment.systemPackages = with pkgs; let
|
||||
gitea-admin = writeShellScriptBin "gitea-admin" ''
|
||||
TMP=$(mktemp -d /tmp/gitea-XXXXXXXX)
|
||||
${gitea}/bin/gitea --custom-path ${cfg.state-dir}/custom --config ${cfg.state-dir}/custom/conf/app.ini --work-path $TMP $@
|
||||
'';
|
||||
in [
|
||||
gitea-admin
|
||||
];
|
||||
|
||||
services = {
|
||||
gitea = {
|
||||
enable = true;
|
||||
@ -118,8 +127,8 @@ in {
|
||||
domain = cfg.hostname;
|
||||
httpAddress = "127.0.0.1";
|
||||
httpPort = cfg.local-port;
|
||||
repositoryRoot = toString cfg.repository-dir;
|
||||
stateDir = toString cfg.state-dir;
|
||||
repositoryRoot = cfg.repository-dir;
|
||||
stateDir = cfg.state-dir;
|
||||
rootUrl = "https://${cfg.hostname}/";
|
||||
user = mkIf (cfg.user != null) cfg.user;
|
||||
ssh = {
|
||||
|
@ -36,6 +36,11 @@ in {
|
||||
has-build-keys = (length host-cfg.build-pubkeys) > 0;
|
||||
|
||||
in {
|
||||
security.sudo.extraConfig = ''
|
||||
# I get it, I get it
|
||||
Defaults lecture = never
|
||||
'';
|
||||
|
||||
networking = {
|
||||
hostName = config.instance.hostname;
|
||||
domain = domain-name;
|
||||
@ -65,11 +70,11 @@ in {
|
||||
config.fudo.system.hostfile-entries;
|
||||
in mkForce {
|
||||
text = ''
|
||||
127.0.0.1 ${hostname}.${domain-name} ${hostname} localhost
|
||||
127.0.0.2 ${hostname} localhost
|
||||
::1 ${hostname}.${domain-name} ${hostname} localhost
|
||||
${concatStringsSep "\n" host-entries}
|
||||
'';
|
||||
127.0.0.1 ${hostname}.${domain-name} ${hostname} localhost
|
||||
127.0.0.2 ${hostname} localhost
|
||||
::1 ${hostname}.${domain-name} ${hostname} localhost
|
||||
${concatStringsSep "\n" host-entries}
|
||||
'';
|
||||
user = "root";
|
||||
group = "root";
|
||||
mode = "0444";
|
||||
|
454
lib/fudo/kdc.nix
454
lib/fudo/kdc.nix
@ -1,59 +1,125 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, ... } @ toplevel:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.auth.kdc;
|
||||
|
||||
database-file = "${cfg.state-directory}/principals.db";
|
||||
iprop-log = "${cfg.state-directory}/iprop.log";
|
||||
acl-file = generate-acl-file cfg.acl;
|
||||
kdc-conf =
|
||||
generate-kdc-conf cfg.realm database-file cfg.master-key-file acl-file;
|
||||
hostname = config.instance.hostname;
|
||||
|
||||
get-domain-hosts = domain:
|
||||
attrNames
|
||||
(filterAttrs (host: hostOpts: hostOpts.domain == domain) config.fudo.hosts);
|
||||
state-directory = toplevel.config.fudo.auth.kdc.state-directory;
|
||||
|
||||
get-host-principals = realm: hostname:
|
||||
let host = config.fudo.hosts.${hostname};
|
||||
in map (service: "${service}/${hostname}.${toLower realm}@${realm}")
|
||||
host.kerberos-services;
|
||||
database-file = "${state-directory}/principals.db";
|
||||
iprop-log = "${state-directory}/iprop.log";
|
||||
|
||||
add-principal-str = kdc-conf: principal:
|
||||
"${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- add --random-key --use-defaults ${principal}";
|
||||
master-server = cfg.master-config != null;
|
||||
slave-server = cfg.slave-config != null;
|
||||
|
||||
add-hosts-principals = realm: kdc-conf:
|
||||
concatStringsSep "\n" (map (add-principal-str kdc-conf)
|
||||
(concatMap (get-host-principals realm)
|
||||
(get-domain-hosts (toLower realm))));
|
||||
get-fqdn = hostname:
|
||||
"${hostname}.${config.fudo.hosts.${hostname}.domain}";
|
||||
|
||||
kdc-conf = generate-kdc-conf {
|
||||
realm = cfg.realm;
|
||||
db-file = database-file;
|
||||
key-file = cfg.master-key-file;
|
||||
acl-data = if master-server then cfg.master-config.acl else null;
|
||||
};
|
||||
|
||||
initialize-db =
|
||||
realm: user: group: kdc-conf: key-file: db-name: max-lifetime: max-renewal: primary-keytab: kadmin-keytab: kpasswd-keytab: local-hostname:
|
||||
pkgs.writeShellScript "initialize-kdc-db.sh" ''
|
||||
if [ ! -e ${key-file} ]; then
|
||||
${pkgs.heimdalFull}/bin/kstash --key-file=${key-file} --random-key
|
||||
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- init --realm-max-ticket-life="${max-lifetime}" --realm-max-renewable-life="${max-renewal}" ${realm}
|
||||
${add-hosts-principals realm kdc-conf}
|
||||
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${primary-keytab} */${local-hostname}@${realm}
|
||||
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${kadmin-keytab} kadmin/admin@${realm}
|
||||
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${kpasswd-keytab} kadmin/changepw@${realm}
|
||||
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${kpasswd-keytab} kadmin/changepw@${realm}
|
||||
#${pkgs.coreutils}/bin/chown ${user}:${group} ${key-file}
|
||||
#${pkgs.coreutils}/bin/chown ${user}:${group} ${db-name}
|
||||
#${pkgs.coreutils}/bin/chown ${user}:${group} ${iprop-log}
|
||||
#${pkgs.coreutils}/bin/chown ${user}:${group} ${primary-keytab}
|
||||
#${pkgs.coreutils}/bin/chown ${user}:${group} ${kadmin-keytab}
|
||||
fi
|
||||
'';
|
||||
{ realm, user, group, kdc-conf, key-file, db-name, max-lifetime, max-renewal,
|
||||
primary-keytab, kadmin-keytab, kpasswd-keytab, ipropd-keytab, local-hostname }: let
|
||||
|
||||
generate-kdc-conf = realm: db-file: key-file: acl-file:
|
||||
kadmin-cmd = "kadmin -l -c ${kdc-conf} --";
|
||||
|
||||
get-domain-hosts = domain: let
|
||||
host-in-subdomain = host: hostOpts:
|
||||
(builtins.match "(.+[.])?${domain}$" hostOpts.domain) != null;
|
||||
in attrNames (filterAttrs host-in-subdomain config.fudo.hosts);
|
||||
|
||||
get-host-principals = realm: hostname: let
|
||||
host = config.fudo.hosts.${hostname};
|
||||
in map (service: "${service}/${hostname}.${host.domain}@${realm}")
|
||||
host.kerberos-services;
|
||||
|
||||
add-principal-str = principal:
|
||||
"${kadmin-cmd} add --random-key --use-defaults ${principal}";
|
||||
|
||||
test-existence = principal:
|
||||
"[[ $( ${kadmin-cmd} get ${principal} ) ]]";
|
||||
|
||||
exists-or-add = principal: ''
|
||||
if ${test-existence principal}; then
|
||||
echo "skipping ${principal}, already exists"
|
||||
else
|
||||
${add-principal-str principal}
|
||||
fi
|
||||
'';
|
||||
|
||||
ensure-host-principals = realm:
|
||||
concatStringsSep "\n"
|
||||
(map exists-or-add
|
||||
(concatMap (get-host-principals realm)
|
||||
(get-domain-hosts (toLower realm))));
|
||||
|
||||
slave-hostnames = map get-fqdn cfg.master-config.slave-hosts;
|
||||
|
||||
ensure-iprop-principals = concatStringsSep "\n"
|
||||
(map (host: exists-or-add "iprop/${host}@${realm}")
|
||||
[ local-hostname ] ++ slave-hostnames);
|
||||
|
||||
copy-slave-principals-file = let
|
||||
slave-principals = map
|
||||
(host: "iprop/${hostname}@${cfg.realm}")
|
||||
slave-hostnames;
|
||||
slave-principals-file = pkgs.writeText "heimdal-slave-principals"
|
||||
(concatStringsSep "\n" slave-principals);
|
||||
in optionalString (slave-principals-file != null) ''
|
||||
cp ${slave-principals-file} ${state-directory}/slaves
|
||||
# Since it's copied from /nix/store, this is by default read-only,
|
||||
# which causes updates to fail.
|
||||
chmod u+w ${state-directory}/slaves
|
||||
'';
|
||||
|
||||
in pkgs.writeShellScript "initialize-kdc-db.sh" ''
|
||||
TMP=$(mktemp -d -t kdc-XXXXXXXX)
|
||||
if [ ! -e ${database-file} ]; then
|
||||
## CHANGING HOW THIS WORKS
|
||||
## Now we expect the key to be provided
|
||||
# kstash --key-file=${key-file} --random-key
|
||||
${kadmin-cmd} init --realm-max-ticket-life="${max-lifetime}" --realm-max-renewable-life="${max-renewal}" ${realm}
|
||||
fi
|
||||
|
||||
${ensure-host-principals realm}
|
||||
|
||||
${ensure-iprop-principals}
|
||||
|
||||
echo "*** BEGIN EXTRACTING KEYTABS"
|
||||
echo "*** You can probably ignore the 'principal does not exist' errors that follow,"
|
||||
echo "*** they're just testing for principal existence before creating those that"
|
||||
echo "*** don't already exist"
|
||||
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/primary.keytab */${local-hostname}@${realm}
|
||||
mv $TMP/primary.keytab ${primary-keytab}
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/kadmin.keytab kadmin/admin@${realm}
|
||||
mv $TMP/kadmin.keytab ${kadmin-keytab}
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/kpasswd.keytab kadmin/changepw@${realm}
|
||||
mv $TMP/kpasswd.keytab ${kpasswd-keytab}
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/ipropd.keytab iprop/${local-hostname}@${realm}
|
||||
mv $TMP/ipropd.keytab ${ipropd-keytab}
|
||||
|
||||
echo "*** END EXTRACTING KEYTABS"
|
||||
|
||||
${copy-slave-principals-file}
|
||||
'';
|
||||
|
||||
generate-kdc-conf = { realm, db-file, key-file, acl-data }:
|
||||
pkgs.writeText "kdc.conf" ''
|
||||
[kdc]
|
||||
database = {
|
||||
dbname = sqlite:${db-file}
|
||||
realm = ${realm}
|
||||
mkey_file = ${key-file}
|
||||
acl_file = ${acl-file}
|
||||
${optionalString (acl-data != null)
|
||||
"acl_file = ${generate-acl-file acl-data}"}
|
||||
log_file = ${iprop-log}
|
||||
}
|
||||
|
||||
@ -63,8 +129,8 @@ let
|
||||
}
|
||||
|
||||
[logging]
|
||||
kdc = FILE:${cfg.state-directory}/kerberos.log
|
||||
default = FILE:${cfg.state-directory}/kerberos.log
|
||||
kdc = FILE:${state-directory}/kerberos.log
|
||||
default = FILE:${state-directory}/kerberos.log
|
||||
'';
|
||||
|
||||
aclEntry = { principal, ... }: {
|
||||
@ -95,25 +161,84 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
perms-to-permstring = perms: concatStringsSep "," perms;
|
||||
|
||||
generate-acl-file = acl-entries:
|
||||
generate-acl-file = acl-entries: let
|
||||
perms-to-permstring = perms: concatStringsSep "," perms;
|
||||
in
|
||||
pkgs.writeText "kdc.acl" (concatStringsSep "\n" (mapAttrsToList
|
||||
(principal: opts:
|
||||
"${principal} ${perms-to-permstring opts.perms}${
|
||||
optionalString (opts.target != null) " ${opts.target}"
|
||||
}") acl-entries));
|
||||
optionalString (opts.target != null) " ${opts.target}" }")
|
||||
acl-entries));
|
||||
|
||||
kadmin-local = kdc-conf: kadmin-keytab:
|
||||
kadmin-local = kdc-conf:
|
||||
pkgs.writeShellScriptBin "kadmin.local" ''
|
||||
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} --keytab=${kadmin-keytab}
|
||||
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} $@
|
||||
'';
|
||||
|
||||
masterOpts = { ... }: {
|
||||
options = with types; {
|
||||
acl = mkOption {
|
||||
type = attrsOf (submodule aclEntry);
|
||||
description = "Mapping of pricipals to a list of permissions.";
|
||||
default = { "*/admin" = [ "all" ]; };
|
||||
example = {
|
||||
"*/root" = [ "all" ];
|
||||
"admin-user" = [ "add" "list" "modify" ];
|
||||
};
|
||||
};
|
||||
|
||||
kadmin-keytab = mkOption {
|
||||
type = str;
|
||||
description = "Location at which to store keytab for kadmind.";
|
||||
default = "${state-directory}/kadmind.keytab";
|
||||
};
|
||||
|
||||
kpasswdd-keytab = mkOption {
|
||||
type = str;
|
||||
description = "Location at which to store keytab for kpasswdd.";
|
||||
default = "${state-directory}/kpasswdd.keytab";
|
||||
};
|
||||
|
||||
ipropd-keytab = mkOption {
|
||||
type = str;
|
||||
description = "Location at which to store keytab for ipropd master.";
|
||||
default = "${state-directory}/ipropd.keytab";
|
||||
};
|
||||
|
||||
slave-hosts = mkOption {
|
||||
type = listOf str;
|
||||
description = ''
|
||||
A list of host to which the database should be propagated.
|
||||
|
||||
Must exist in the Fudo Host database.
|
||||
'';
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
slaveOpts = { ... }: {
|
||||
options = with types; {
|
||||
master-host = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
Host from which to recieve database updates.
|
||||
|
||||
Must exist in the Fudo Host database.
|
||||
'';
|
||||
};
|
||||
|
||||
ipropd-keytab = mkOption {
|
||||
type = str;
|
||||
description = "Location at which to find keytab for ipropd slave.";
|
||||
default = "${state-directory}/ipropd.keytab";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.auth.kdc = with types; let
|
||||
default-state-dir = "/var/kerberos";
|
||||
in {
|
||||
options.fudo.auth.kdc = with types; {
|
||||
enable = mkEnableOption "Fudo KDC";
|
||||
|
||||
realm = mkOption {
|
||||
@ -121,16 +246,6 @@ in {
|
||||
description = "The realm for which we are the acting KDC.";
|
||||
};
|
||||
|
||||
acl = mkOption {
|
||||
type = attrsOf (submodule aclEntry);
|
||||
description = "Mapping of pricipals to a list of permissions.";
|
||||
default = { "*/admin" = [ "all" ]; };
|
||||
example = {
|
||||
"*/root" = [ "all" ];
|
||||
"admin-user" = [ "add" "list" "modify" ];
|
||||
};
|
||||
};
|
||||
|
||||
bind-addresses = mkOption {
|
||||
type = listOf str;
|
||||
description = "A list of IP addresses on which to bind.";
|
||||
@ -152,38 +267,34 @@ in {
|
||||
state-directory = mkOption {
|
||||
type = str;
|
||||
description = "Path at which to store kerberos database.";
|
||||
default = default-state-dir;
|
||||
default = "/var/lib/kerberos";
|
||||
};
|
||||
|
||||
master-key-file = mkOption {
|
||||
type = str;
|
||||
description = "File containing the master key for the realm.";
|
||||
default = "${default-state-dir}/master.key";
|
||||
description = ''
|
||||
File containing the master key for the realm.
|
||||
|
||||
Must be provided!
|
||||
'';
|
||||
};
|
||||
|
||||
primary-keytab = mkOption {
|
||||
type = str;
|
||||
description = "Location of keytab for kadmind.";
|
||||
default = "${default-state-dir}/host.keytab";
|
||||
description = "Location of host master keytab.";
|
||||
default = "${state-directory}/host.keytab";
|
||||
};
|
||||
|
||||
kadmin-keytab = mkOption {
|
||||
type = str;
|
||||
description = "Location of keytab for kadmind.";
|
||||
default = "${default-state-dir}/kadmind.keytab";
|
||||
master-config = mkOption {
|
||||
type = nullOr (submodule masterOpts);
|
||||
description = "Configuration for the master KDC server.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
kpasswdd-keytab = mkOption {
|
||||
type = str;
|
||||
description = "Location of keytab for kpasswdd.";
|
||||
default = "${default-state-dir}/kpasswdd.keytab";
|
||||
};
|
||||
|
||||
kdc-internal-port = mkOption {
|
||||
type = port;
|
||||
description =
|
||||
"Localhost port on which to listen for KDC traffic. Port 88 will be forwarded";
|
||||
default = 4088;
|
||||
slave-config = mkOption {
|
||||
type = nullOr (submodule slaveOpts);
|
||||
description = "Configuration for slave KDC servers.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
max-ticket-lifetime = mkOption {
|
||||
@ -200,10 +311,24 @@ in {
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = master-server || slave-server;
|
||||
message =
|
||||
"For the KDC to be enabled, a master OR slave config must be provided.";
|
||||
}
|
||||
{
|
||||
assertion = !(master-server && slave-server);
|
||||
message =
|
||||
"Only one of master-config and slave-config may be provided.";
|
||||
}
|
||||
];
|
||||
|
||||
users = {
|
||||
users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
home = cfg.state-directory;
|
||||
home = state-directory;
|
||||
group = cfg.group;
|
||||
};
|
||||
|
||||
@ -217,81 +342,166 @@ in {
|
||||
ticket_lifetime = cfg.max-ticket-lifetime;
|
||||
renew_lifetime = cfg.max-ticket-renewal;
|
||||
};
|
||||
realms = { ${cfg.realm} = { enable-http = false; }; };
|
||||
# Sorry, port 80 isn't available!
|
||||
realms.${cfg.realm}.enable-http = false;
|
||||
extraConfig = ''
|
||||
default = FILE:${cfg.state-directory}/kerberos.log
|
||||
default = FILE:${state-directory}/kerberos.log
|
||||
'';
|
||||
};
|
||||
|
||||
environment = {
|
||||
systemPackages =
|
||||
[ pkgs.heimdalFull (kadmin-local kdc-conf cfg.kadmin-keytab) ];
|
||||
systemPackages = [ pkgs.heimdalFull (kadmin-local kdc-conf) ];
|
||||
|
||||
etc = {
|
||||
"krb5.keytab" = {
|
||||
user = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
source = cfg.primary-keytab;
|
||||
};
|
||||
};
|
||||
## This shouldn't be necessary...every host gets a krb5.keytab
|
||||
# etc = {
|
||||
# "krb5.keytab" = {
|
||||
# user = "root";
|
||||
# group = "root";
|
||||
# mode = "0400";
|
||||
# source = cfg.primary-keytab;
|
||||
# };
|
||||
# };
|
||||
};
|
||||
|
||||
fudo.system = {
|
||||
ensure-directories = {
|
||||
"${cfg.state-directory}" = {
|
||||
"${state-directory}" = {
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
perms = "0740";
|
||||
};
|
||||
};
|
||||
|
||||
services = {
|
||||
services = if master-server then {
|
||||
|
||||
heimdal-kdc = let
|
||||
listen-addrs = concatStringsSep " "
|
||||
(map (addr: "--addresses=${addr}") cfg.bind-addresses);
|
||||
in {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
description =
|
||||
"Heimdal Kerberos Key Distribution Center (ticket server).";
|
||||
execStart = "${pkgs.heimdalFull}/libexec/heimdal/kdc -c ${kdc-conf} --ports=88 ${listen-addrs}";
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
workingDirectory = state-directory;
|
||||
privateNetwork = false;
|
||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
requiredCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||
};
|
||||
|
||||
heimdal-kadmin = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requires = [ "heimdal-kdc.service" ];
|
||||
description =
|
||||
"Heimdal Kerberos Remote Administration Server.";
|
||||
# Doesn't have any way to specify IPs to listen on...sigh
|
||||
execStart = "${pkgs.heimdalFull}/libexec/heimdal/kadmin -c ${kdc-conf} -k ${cfg.master-key-file} -r ${cfg.realm} --ports=749";
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
workingDirectory = state-directory;
|
||||
privateNetwork = false;
|
||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
requiredCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||
};
|
||||
|
||||
heimdal-kdc-init = let
|
||||
init-cmd = initialize-db {
|
||||
realm = cfg.realm;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
kdc-conf = kdc-conf;
|
||||
key-file = cfg.master-key-file;
|
||||
db-name = database-file;
|
||||
max-lifetime = cfg.max-ticket-lifetime;
|
||||
max-renewal = cfg.max-ticket-renewal;
|
||||
primary-keytab = cfg.primary-keytab;
|
||||
kadmin-keytab = cfg.master-config.kadmin-keytab;
|
||||
kpasswd-keytab = cfg.master-config.kpasswdd-keytab;
|
||||
ipropd-keytab = cfg.master-config.ipropd-keytab;
|
||||
local-hostname =
|
||||
"${config.instance.hostname}.${config.instance.local-domain}";
|
||||
};
|
||||
in {
|
||||
requires = [ "heimdal-kdc.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description = "Initialization script for Heimdal KDC.";
|
||||
type = "oneshot";
|
||||
execStart = "${init-cmd}";
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
path = with pkgs; [ heimdalFull ];
|
||||
protectSystem = "full";
|
||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
workingDirectory = state-directory;
|
||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||
};
|
||||
|
||||
heimdal-ipropd-master = mkIf (length cfg.master-config.slave-hosts > 0) {
|
||||
requires = [ "heimdal-kdc.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description = "Propagate changes to the master KDC DB to all slaves.";
|
||||
path = with pkgs; [ heimdalFull ];
|
||||
execStart = "${pkgs.heimdalFull}/libexec/heimdal/ipropd-master -c ${kdc-conf} -k ${cfg.master.ipropd-keytab}";
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
workingDirectory = state-directory;
|
||||
privateNetwork = false;
|
||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
heimdal-kdc-slave = let
|
||||
listen-addrs = concatStringsSep " "
|
||||
(map (addr: "--addresses=${addr}") cfg.bind-addresses);
|
||||
command =
|
||||
"${pkgs.heimdalFull}/libexec/heimdal/kdc -c ${kdc-conf} --ports=88 ${listen-addrs}";
|
||||
in {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
description =
|
||||
"Heimdal Kerberos Key Distribution Center (ticket server).";
|
||||
"Heimdal Slave Kerberos Key Distribution Center (ticket server).";
|
||||
execStart = command;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
workingDirectory = cfg.state-directory;
|
||||
workingDirectory = state-directory;
|
||||
privateNetwork = false;
|
||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
requiredCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||
};
|
||||
|
||||
heimdal-kdc-init = {
|
||||
requires = [ "heimdal-kdc.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description = "Initialization script for Heimdal KDC.";
|
||||
type = "oneshot";
|
||||
execStart = "${initialize-db cfg.realm cfg.user cfg.group kdc-conf
|
||||
cfg.master-key-file database-file cfg.max-ticket-lifetime
|
||||
cfg.max-ticket-renewal cfg.primary-keytab cfg.kadmin-keytab
|
||||
cfg.kpasswdd-keytab
|
||||
"${config.networking.hostName}.${toLower cfg.realm}"}";
|
||||
heimdal-ipropd-slave = {
|
||||
#wantedBy = [ "multi-user.target" ];
|
||||
description = "Receive changes propagated from the KDC master server.";
|
||||
path = with pkgs; [ heimdalFull ];
|
||||
execStart = concatStringsSep " " [
|
||||
"${pkgs.heimdalFull}/libexec/heimdal/ipropd-slave"
|
||||
"--config-file=${kdc-conf}"
|
||||
"--keytab=${cfg.slave-config.ipropd-keytab}"
|
||||
"--realm=${cfg.realm}"
|
||||
"--hostname=${get-fqdn hostname}"
|
||||
"--port=2121"
|
||||
"--verbose"
|
||||
(get-fqdn cfg.slave-config.master-host)
|
||||
];
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
protectSystem = "full";
|
||||
workingDirectory = state-directory;
|
||||
privateNetwork = false;
|
||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
workingDirectory = cfg.state-directory;
|
||||
requiredCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# FIXME: is this even allowed to be a link?
|
||||
# systemd.tmpfiles.rules = mkIf (cfg.primary-keytab != "/etc/krb5.keytab")
|
||||
# [ "L /etc/krb5.keytab - - - - ${cfg.primary-keytab}" ];
|
||||
|
||||
services.xinetd = {
|
||||
services.xinetd = mkIf master-server {
|
||||
enable = true;
|
||||
|
||||
services = [
|
||||
@ -301,7 +511,7 @@ in {
|
||||
server = "${pkgs.heimdalFull}/libexec/heimdal/kadmind";
|
||||
protocol = "tcp";
|
||||
serverArgs =
|
||||
"--config-file=${kdc-conf} --keytab=${cfg.kadmin-keytab}";
|
||||
"--config-file=${kdc-conf} --keytab=${cfg.master-config.kadmin-keytab}";
|
||||
}
|
||||
{
|
||||
name = "kpasswd";
|
||||
@ -309,14 +519,20 @@ in {
|
||||
server = "${pkgs.heimdalFull}/libexec/heimdal/kpasswdd";
|
||||
protocol = "udp";
|
||||
serverArgs =
|
||||
"--config-file=${kdc-conf} --keytab=${cfg.kpasswdd-keytab}";
|
||||
"--config-file=${kdc-conf} --keytab=${cfg.master-config.kpasswdd-keytab}";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [ 88 749 ];
|
||||
allowedUDPPorts = [ 88 464 ];
|
||||
networking = {
|
||||
firewall = {
|
||||
allowedTCPPorts = [ 88 ] ++
|
||||
(optionals master-server [ 749 ]) ++
|
||||
(optionals slave-server [ 2121 ]);
|
||||
allowedUDPPorts = [ 88 ] ++
|
||||
(optionals master-server [ 464 ]) ++
|
||||
(optionals slave-server [ 2121 ]);
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -198,7 +198,8 @@ in {
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.mail-directory} 770 ${cfg.mail-user} ${cfg.mail-group} - -"
|
||||
"d ${cfg.mail-directory} 775 ${cfg.mail-user} ${cfg.mail-group} - -"
|
||||
"d ${cfg.state-directory} 775 root ${cfg.mail-group} - -"
|
||||
];
|
||||
|
||||
networking.firewall = {
|
||||
|
978
lib/fudo/nsd.nix
Normal file
978
lib/fudo/nsd.nix
Normal file
@ -0,0 +1,978 @@
|
||||
### NOTE:
|
||||
## This is a copy of the upstream version, which allows for overriding the state directory
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.fudo.nsd;
|
||||
|
||||
username = "nsd";
|
||||
stateDir = cfg.stateDir;
|
||||
pidFile = stateDir + "/var/nsd.pid";
|
||||
|
||||
# build nsd with the options needed for the given config
|
||||
nsdPkg = pkgs.nsd.override {
|
||||
bind8Stats = cfg.bind8Stats;
|
||||
ipv6 = cfg.ipv6;
|
||||
ratelimit = cfg.ratelimit.enable;
|
||||
rootServer = cfg.rootServer;
|
||||
zoneStats = length (collect (x: (x.zoneStats or null) != null) cfg.zones) > 0;
|
||||
};
|
||||
|
||||
mkZoneFileName = name: if name == "." then "root" else name;
|
||||
|
||||
# replaces include: directives for keys with fake keys for nsd-checkconf
|
||||
injectFakeKeys = keys: concatStrings
|
||||
(mapAttrsToList
|
||||
(keyName: keyOptions: ''
|
||||
fakeKey="$(${pkgs.bind}/bin/tsig-keygen -a ${escapeShellArgs [ keyOptions.algorithm keyName ]} | grep -oP "\s*secret \"\K.*(?=\";)")"
|
||||
sed "s@^\s*include:\s*\"${stateDir}/private/${keyName}\"\$@secret: $fakeKey@" -i $out/nsd.conf
|
||||
'')
|
||||
keys);
|
||||
|
||||
nsdEnv = pkgs.buildEnv {
|
||||
name = "nsd-env";
|
||||
|
||||
paths = [ configFile ]
|
||||
++ mapAttrsToList (name: zone: writeZoneData name zone.data) zoneConfigs;
|
||||
|
||||
postBuild = ''
|
||||
echo "checking zone files"
|
||||
cd $out/zones
|
||||
for zoneFile in *; do
|
||||
echo "|- checking zone '$out/zones/$zoneFile'"
|
||||
${nsdPkg}/sbin/nsd-checkzone "$zoneFile" "$zoneFile" || {
|
||||
if grep -q \\\\\\$ "$zoneFile"; then
|
||||
echo zone "$zoneFile" contains escaped dollar signs \\\$
|
||||
echo Escaping them is not needed any more. Please make sure \
|
||||
to unescape them where they prefix a variable name.
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
echo "checking configuration file"
|
||||
# Save original config file including key references...
|
||||
cp $out/nsd.conf{,.orig}
|
||||
# ...inject mock keys into config
|
||||
${injectFakeKeys cfg.keys}
|
||||
# ...do the checkconf
|
||||
${nsdPkg}/sbin/nsd-checkconf $out/nsd.conf
|
||||
# ... and restore original config file.
|
||||
mv $out/nsd.conf{.orig,}
|
||||
'';
|
||||
};
|
||||
|
||||
writeZoneData = name: text: pkgs.writeTextFile {
|
||||
name = "nsd-zone-${mkZoneFileName name}";
|
||||
inherit text;
|
||||
destination = "/zones/${mkZoneFileName name}";
|
||||
};
|
||||
|
||||
|
||||
# options are ordered alphanumerically by the nixos option name
|
||||
configFile = pkgs.writeTextDir "nsd.conf" ''
|
||||
server:
|
||||
chroot: "${stateDir}"
|
||||
username: ${username}
|
||||
# The directory for zonefile: files. The daemon chdirs here.
|
||||
zonesdir: "${stateDir}"
|
||||
# the list of dynamically added zones.
|
||||
database: "${stateDir}/var/nsd.db"
|
||||
pidfile: "${pidFile}"
|
||||
xfrdfile: "${stateDir}/var/xfrd.state"
|
||||
xfrdir: "${stateDir}/tmp"
|
||||
zonelistfile: "${stateDir}/var/zone.list"
|
||||
# interfaces
|
||||
${forEach " ip-address: " cfg.interfaces}
|
||||
ip-freebind: ${yesOrNo cfg.ipFreebind}
|
||||
hide-version: ${yesOrNo cfg.hideVersion}
|
||||
identity: "${cfg.identity}"
|
||||
ip-transparent: ${yesOrNo cfg.ipTransparent}
|
||||
do-ip4: ${yesOrNo cfg.ipv4}
|
||||
ipv4-edns-size: ${toString cfg.ipv4EDNSSize}
|
||||
do-ip6: ${yesOrNo cfg.ipv6}
|
||||
ipv6-edns-size: ${toString cfg.ipv6EDNSSize}
|
||||
log-time-ascii: ${yesOrNo cfg.logTimeAscii}
|
||||
${maybeString "nsid: " cfg.nsid}
|
||||
port: ${toString cfg.port}
|
||||
reuseport: ${yesOrNo cfg.reuseport}
|
||||
round-robin: ${yesOrNo cfg.roundRobin}
|
||||
server-count: ${toString cfg.serverCount}
|
||||
${maybeToString "statistics: " cfg.statistics}
|
||||
tcp-count: ${toString cfg.tcpCount}
|
||||
tcp-query-count: ${toString cfg.tcpQueryCount}
|
||||
tcp-timeout: ${toString cfg.tcpTimeout}
|
||||
verbosity: ${toString cfg.verbosity}
|
||||
${maybeString "version: " cfg.version}
|
||||
xfrd-reload-timeout: ${toString cfg.xfrdReloadTimeout}
|
||||
zonefiles-check: ${yesOrNo cfg.zonefilesCheck}
|
||||
${maybeString "rrl-ipv4-prefix-length: " cfg.ratelimit.ipv4PrefixLength}
|
||||
${maybeString "rrl-ipv6-prefix-length: " cfg.ratelimit.ipv6PrefixLength}
|
||||
rrl-ratelimit: ${toString cfg.ratelimit.ratelimit}
|
||||
${maybeString "rrl-slip: " cfg.ratelimit.slip}
|
||||
rrl-size: ${toString cfg.ratelimit.size}
|
||||
rrl-whitelist-ratelimit: ${toString cfg.ratelimit.whitelistRatelimit}
|
||||
${keyConfigFile}
|
||||
remote-control:
|
||||
control-enable: ${yesOrNo cfg.remoteControl.enable}
|
||||
control-key-file: "${cfg.remoteControl.controlKeyFile}"
|
||||
control-cert-file: "${cfg.remoteControl.controlCertFile}"
|
||||
${forEach " control-interface: " cfg.remoteControl.interfaces}
|
||||
control-port: ${toString cfg.remoteControl.port}
|
||||
server-key-file: "${cfg.remoteControl.serverKeyFile}"
|
||||
server-cert-file: "${cfg.remoteControl.serverCertFile}"
|
||||
${concatStrings (mapAttrsToList zoneConfigFile zoneConfigs)}
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
yesOrNo = b: if b then "yes" else "no";
|
||||
maybeString = prefix: x: if x == null then "" else ''${prefix} "${x}"'';
|
||||
maybeToString = prefix: x: if x == null then "" else ''${prefix} ${toString x}'';
|
||||
forEach = pre: l: concatMapStrings (x: pre + x + "\n") l;
|
||||
|
||||
|
||||
keyConfigFile = concatStrings (mapAttrsToList (keyName: keyOptions: ''
|
||||
key:
|
||||
name: "${keyName}"
|
||||
algorithm: "${keyOptions.algorithm}"
|
||||
include: "${stateDir}/private/${keyName}"
|
||||
'') cfg.keys);
|
||||
|
||||
copyKeys = concatStrings (mapAttrsToList (keyName: keyOptions: ''
|
||||
secret=$(cat "${keyOptions.keyFile}")
|
||||
dest="${stateDir}/private/${keyName}"
|
||||
echo " secret: \"$secret\"" > "$dest"
|
||||
chown ${username}:${username} "$dest"
|
||||
chmod 0400 "$dest"
|
||||
'') cfg.keys);
|
||||
|
||||
|
||||
# options are ordered alphanumerically by the nixos option name
|
||||
zoneConfigFile = name: zone: ''
|
||||
zone:
|
||||
name: "${name}"
|
||||
zonefile: "${stateDir}/zones/${mkZoneFileName name}"
|
||||
${maybeString "outgoing-interface: " zone.outgoingInterface}
|
||||
${forEach " rrl-whitelist: " zone.rrlWhitelist}
|
||||
${maybeString "zonestats: " zone.zoneStats}
|
||||
${maybeToString "max-refresh-time: " zone.maxRefreshSecs}
|
||||
${maybeToString "min-refresh-time: " zone.minRefreshSecs}
|
||||
${maybeToString "max-retry-time: " zone.maxRetrySecs}
|
||||
${maybeToString "min-retry-time: " zone.minRetrySecs}
|
||||
allow-axfr-fallback: ${yesOrNo zone.allowAXFRFallback}
|
||||
${forEach " allow-notify: " zone.allowNotify}
|
||||
${forEach " request-xfr: " zone.requestXFR}
|
||||
${forEach " notify: " zone.notify}
|
||||
notify-retry: ${toString zone.notifyRetry}
|
||||
${forEach " provide-xfr: " zone.provideXFR}
|
||||
'';
|
||||
|
||||
zoneConfigs = zoneConfigs' {} "" { children = cfg.zones; };
|
||||
|
||||
zoneConfigs' = parent: name: zone:
|
||||
if !(zone ? children) || zone.children == null || zone.children == { }
|
||||
# leaf -> actual zone
|
||||
then listToAttrs [ (nameValuePair name (parent // zone)) ]
|
||||
|
||||
# fork -> pattern
|
||||
else zipAttrsWith (name: head) (
|
||||
mapAttrsToList (name: child: zoneConfigs' (parent // zone // { children = {}; }) name child)
|
||||
zone.children
|
||||
);
|
||||
|
||||
# fighting infinite recursion
|
||||
zoneOptions = zoneOptionsRaw // childConfig zoneOptions1 true;
|
||||
zoneOptions1 = zoneOptionsRaw // childConfig zoneOptions2 false;
|
||||
zoneOptions2 = zoneOptionsRaw // childConfig zoneOptions3 false;
|
||||
zoneOptions3 = zoneOptionsRaw // childConfig zoneOptions4 false;
|
||||
zoneOptions4 = zoneOptionsRaw // childConfig zoneOptions5 false;
|
||||
zoneOptions5 = zoneOptionsRaw // childConfig zoneOptions6 false;
|
||||
zoneOptions6 = zoneOptionsRaw // childConfig null false;
|
||||
|
||||
childConfig = x: v: { options.children = { type = types.attrsOf x; visible = v; }; };
|
||||
|
||||
# options are ordered alphanumerically
|
||||
zoneOptionsRaw = types.submodule {
|
||||
options = {
|
||||
|
||||
allowAXFRFallback = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
If NSD as secondary server should be allowed to AXFR if the primary
|
||||
server does not allow IXFR.
|
||||
'';
|
||||
};
|
||||
|
||||
allowNotify = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name"
|
||||
"10.0.3.4&255.255.0.0 BLOCKED"
|
||||
];
|
||||
description = ''
|
||||
Listed primary servers are allowed to notify this secondary server.
|
||||
<screen><![CDATA[
|
||||
Format: <ip> <key-name | NOKEY | BLOCKED>
|
||||
<ip> either a plain IPv4/IPv6 address or range. Valid patters for ranges:
|
||||
* 10.0.0.0/24 # via subnet size
|
||||
* 10.0.0.0&255.255.255.0 # via subnet mask
|
||||
* 10.0.0.1-10.0.0.254 # via range
|
||||
A optional port number could be added with a '@':
|
||||
* 2001:1234::1@1234
|
||||
<key-name | NOKEY | BLOCKED>
|
||||
* <key-name> will use the specified TSIG key
|
||||
* NOKEY no TSIG signature is required
|
||||
* BLOCKED notifies from non-listed or blocked IPs will be ignored
|
||||
* ]]></screen>
|
||||
'';
|
||||
};
|
||||
|
||||
children = mkOption {
|
||||
default = {};
|
||||
description = ''
|
||||
Children zones inherit all options of their parents. Attributes
|
||||
defined in a child will overwrite the ones of its parent. Only
|
||||
leaf zones will be actually served. This way it's possible to
|
||||
define maybe zones which share most attributes without
|
||||
duplicating everything. This mechanism replaces nsd's patterns
|
||||
in a save and functional way.
|
||||
'';
|
||||
};
|
||||
|
||||
data = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "";
|
||||
description = ''
|
||||
The actual zone data. This is the content of your zone file.
|
||||
Use imports or pkgs.lib.readFile if you don't want this data in your config file.
|
||||
'';
|
||||
};
|
||||
|
||||
dnssec = mkEnableOption "DNSSEC";
|
||||
|
||||
dnssecPolicy = {
|
||||
algorithm = mkOption {
|
||||
type = types.str;
|
||||
default = "RSASHA256";
|
||||
description = "Which algorithm to use for DNSSEC";
|
||||
};
|
||||
keyttl = mkOption {
|
||||
type = types.str;
|
||||
default = "1h";
|
||||
description = "TTL for dnssec records";
|
||||
};
|
||||
coverage = mkOption {
|
||||
type = types.str;
|
||||
default = "1y";
|
||||
description = ''
|
||||
The length of time to ensure that keys will be correct; no action will be taken to create new keys to be activated after this time.
|
||||
'';
|
||||
};
|
||||
zsk = mkOption {
|
||||
type = keyPolicy;
|
||||
default = { keySize = 2048;
|
||||
prePublish = "1w";
|
||||
postPublish = "1w";
|
||||
rollPeriod = "1mo";
|
||||
};
|
||||
description = "Key policy for zone signing keys";
|
||||
};
|
||||
ksk = mkOption {
|
||||
type = keyPolicy;
|
||||
default = { keySize = 4096;
|
||||
prePublish = "1mo";
|
||||
postPublish = "1mo";
|
||||
rollPeriod = "0";
|
||||
};
|
||||
description = "Key policy for key signing keys";
|
||||
};
|
||||
};
|
||||
|
||||
maxRefreshSecs = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Limit refresh time for secondary zones. This is the timer which
|
||||
checks to see if the zone has to be refetched when it expires.
|
||||
Normally the value from the SOA record is used, but this option
|
||||
restricts that value.
|
||||
'';
|
||||
};
|
||||
|
||||
minRefreshSecs = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Limit refresh time for secondary zones.
|
||||
'';
|
||||
};
|
||||
|
||||
maxRetrySecs = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Limit retry time for secondary zones. This is the timeout after
|
||||
a failed fetch attempt for the zone. Normally the value from
|
||||
the SOA record is used, but this option restricts that value.
|
||||
'';
|
||||
};
|
||||
|
||||
minRetrySecs = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Limit retry time for secondary zones.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
notify = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ];
|
||||
description = ''
|
||||
This primary server will notify all given secondary servers about
|
||||
zone changes.
|
||||
<screen><![CDATA[
|
||||
Format: <ip> <key-name | NOKEY>
|
||||
<ip> a plain IPv4/IPv6 address with on optional port number (ip@port)
|
||||
<key-name | NOKEY>
|
||||
* <key-name> sign notifies with the specified key
|
||||
* NOKEY don't sign notifies
|
||||
]]></screen>
|
||||
'';
|
||||
};
|
||||
|
||||
notifyRetry = mkOption {
|
||||
type = types.int;
|
||||
default = 5;
|
||||
description = ''
|
||||
Specifies the number of retries for failed notifies. Set this along with notify.
|
||||
'';
|
||||
};
|
||||
|
||||
outgoingInterface = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "2000::1@1234";
|
||||
description = ''
|
||||
This address will be used for zone-transfere requests if configured
|
||||
as a secondary server or notifications in case of a primary server.
|
||||
Supply either a plain IPv4 or IPv6 address with an optional port
|
||||
number (ip@port).
|
||||
'';
|
||||
};
|
||||
|
||||
provideXFR = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ];
|
||||
description = ''
|
||||
Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED
|
||||
address range 192.0.2.0/24, 1.2.3.4&255.255.0.0, 3.0.2.20-3.0.2.40
|
||||
'';
|
||||
};
|
||||
|
||||
requestXFR = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [];
|
||||
description = ''
|
||||
Format: <code>[AXFR|UDP] <ip-address> <key-name | NOKEY></code>
|
||||
'';
|
||||
};
|
||||
|
||||
rrlWhitelist = mkOption {
|
||||
type = with types; listOf (enum [ "nxdomain" "error" "referral" "any" "rrsig" "wildcard" "nodata" "dnskey" "positive" "all" ]);
|
||||
default = [];
|
||||
description = ''
|
||||
Whitelists the given rrl-types.
|
||||
'';
|
||||
};
|
||||
|
||||
zoneStats = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "%s";
|
||||
description = ''
|
||||
When set to something distinct to null NSD is able to collect
|
||||
statistics per zone. All statistics of this zone(s) will be added
|
||||
to the group specified by this given name. Use "%s" to use the zones
|
||||
name as the group. The groups are output from nsd-control stats
|
||||
and stats_noreset.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
keyPolicy = types.submodule {
|
||||
options = {
|
||||
keySize = mkOption {
|
||||
type = types.int;
|
||||
description = "Key size in bits";
|
||||
};
|
||||
prePublish = mkOption {
|
||||
type = types.str;
|
||||
description = "How long in advance to publish new keys";
|
||||
};
|
||||
postPublish = mkOption {
|
||||
type = types.str;
|
||||
description = "How long after deactivation to keep a key in the zone";
|
||||
};
|
||||
rollPeriod = mkOption {
|
||||
type = types.str;
|
||||
description = "How frequently to change keys";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dnssecZones = (filterAttrs (n: v: if v ? dnssec then v.dnssec else false) zoneConfigs);
|
||||
|
||||
dnssec = dnssecZones != {};
|
||||
|
||||
dnssecTools = pkgs.bind.override { enablePython = true; };
|
||||
|
||||
signZones = optionalString dnssec ''
|
||||
mkdir -p ${stateDir}/dnssec
|
||||
chown ${username}:${username} ${stateDir}/dnssec
|
||||
chmod 0600 ${stateDir}/dnssec
|
||||
${concatStrings (mapAttrsToList signZone dnssecZones)}
|
||||
'';
|
||||
signZone = name: zone: ''
|
||||
${dnssecTools}/bin/dnssec-keymgr -g ${dnssecTools}/bin/dnssec-keygen -s ${dnssecTools}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${policyFile name zone.dnssecPolicy} ${name}
|
||||
${dnssecTools}/bin/dnssec-signzone -S -K ${stateDir}/dnssec -o ${name} -O full -N date ${stateDir}/zones/${name}
|
||||
${nsdPkg}/sbin/nsd-checkzone ${name} ${stateDir}/zones/${name}.signed && mv -v ${stateDir}/zones/${name}.signed ${stateDir}/zones/${name}
|
||||
'';
|
||||
policyFile = name: policy: pkgs.writeText "${name}.policy" ''
|
||||
zone ${name} {
|
||||
algorithm ${policy.algorithm};
|
||||
key-size zsk ${toString policy.zsk.keySize};
|
||||
key-size ksk ${toString policy.ksk.keySize};
|
||||
keyttl ${policy.keyttl};
|
||||
pre-publish zsk ${policy.zsk.prePublish};
|
||||
pre-publish ksk ${policy.ksk.prePublish};
|
||||
post-publish zsk ${policy.zsk.postPublish};
|
||||
post-publish ksk ${policy.ksk.postPublish};
|
||||
roll-period zsk ${policy.zsk.rollPeriod};
|
||||
roll-period ksk ${policy.ksk.rollPeriod};
|
||||
coverage ${policy.coverage};
|
||||
};
|
||||
'';
|
||||
in
|
||||
{
|
||||
# options are ordered alphanumerically
|
||||
options.fudo.nsd = {
|
||||
|
||||
enable = mkEnableOption "NSD authoritative DNS server";
|
||||
|
||||
bind8Stats = mkEnableOption "BIND8 like statistics";
|
||||
|
||||
dnssecInterval = mkOption {
|
||||
type = types.str;
|
||||
default = "1h";
|
||||
description = ''
|
||||
How often to check whether dnssec key rollover is required
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra nsd config.
|
||||
'';
|
||||
};
|
||||
|
||||
hideVersion = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether NSD should answer VERSION.BIND and VERSION.SERVER CHAOS class queries.
|
||||
'';
|
||||
};
|
||||
|
||||
identity = mkOption {
|
||||
type = types.str;
|
||||
default = "unidentified server";
|
||||
description = ''
|
||||
Identify the server (CH TXT ID.SERVER entry).
|
||||
'';
|
||||
};
|
||||
|
||||
interfaces = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "127.0.0.0" "::1" ];
|
||||
description = ''
|
||||
What addresses the server should listen to.
|
||||
'';
|
||||
};
|
||||
|
||||
ipFreebind = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to bind to nonlocal addresses and interfaces that are down.
|
||||
Similar to ip-transparent.
|
||||
'';
|
||||
};
|
||||
|
||||
ipTransparent = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Allow binding to non local addresses.
|
||||
'';
|
||||
};
|
||||
|
||||
ipv4 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to listen on IPv4 connections.
|
||||
'';
|
||||
};
|
||||
|
||||
ipv4EDNSSize = mkOption {
|
||||
type = types.int;
|
||||
default = 4096;
|
||||
description = ''
|
||||
Preferred EDNS buffer size for IPv4.
|
||||
'';
|
||||
};
|
||||
|
||||
ipv6 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to listen on IPv6 connections.
|
||||
'';
|
||||
};
|
||||
|
||||
ipv6EDNSSize = mkOption {
|
||||
type = types.int;
|
||||
default = 4096;
|
||||
description = ''
|
||||
Preferred EDNS buffer size for IPv6.
|
||||
'';
|
||||
};
|
||||
|
||||
logTimeAscii = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Log time in ascii, if false then in unix epoch seconds.
|
||||
'';
|
||||
};
|
||||
|
||||
nsid = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
NSID identity (hex string, or "ascii_somestring").
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 53;
|
||||
description = ''
|
||||
Port the service should bind do.
|
||||
'';
|
||||
};
|
||||
|
||||
reuseport = mkOption {
|
||||
type = types.bool;
|
||||
default = pkgs.stdenv.isLinux;
|
||||
description = ''
|
||||
Whether to enable SO_REUSEPORT on all used sockets. This lets multiple
|
||||
processes bind to the same port. This speeds up operation especially
|
||||
if the server count is greater than one and makes fast restarts less
|
||||
prone to fail
|
||||
'';
|
||||
};
|
||||
|
||||
rootServer = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether this server will be a root server (a DNS root server, you
|
||||
usually don't want that).
|
||||
'';
|
||||
};
|
||||
|
||||
roundRobin = mkEnableOption "round robin rotation of records";
|
||||
|
||||
serverCount = mkOption {
|
||||
type = types.int;
|
||||
default = 1;
|
||||
description = ''
|
||||
Number of NSD servers to fork. Put the number of CPUs to use here.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.str;
|
||||
description = "Directory at which to store NSD state data.";
|
||||
default = "/var/lib/nsd";
|
||||
};
|
||||
|
||||
statistics = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Statistics are produced every number of seconds. Prints to log.
|
||||
If null no statistics are logged.
|
||||
'';
|
||||
};
|
||||
|
||||
tcpCount = mkOption {
|
||||
type = types.int;
|
||||
default = 100;
|
||||
description = ''
|
||||
Maximum number of concurrent TCP connections per server.
|
||||
'';
|
||||
};
|
||||
|
||||
tcpQueryCount = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
Maximum number of queries served on a single TCP connection.
|
||||
0 means no maximum.
|
||||
'';
|
||||
};
|
||||
|
||||
tcpTimeout = mkOption {
|
||||
type = types.int;
|
||||
default = 120;
|
||||
description = ''
|
||||
TCP timeout in seconds.
|
||||
'';
|
||||
};
|
||||
|
||||
verbosity = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
Verbosity level.
|
||||
'';
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The version string replied for CH TXT version.server and version.bind
|
||||
queries. Will use the compiled package version on null.
|
||||
See hideVersion for enabling/disabling this responses.
|
||||
'';
|
||||
};
|
||||
|
||||
xfrdReloadTimeout = mkOption {
|
||||
type = types.int;
|
||||
default = 1;
|
||||
description = ''
|
||||
Number of seconds between reloads triggered by xfrd.
|
||||
'';
|
||||
};
|
||||
|
||||
zonefilesCheck = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to check mtime of all zone files on start and sighup.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
keys = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
|
||||
algorithm = mkOption {
|
||||
type = types.str;
|
||||
default = "hmac-sha256";
|
||||
description = ''
|
||||
Authentication algorithm for this key.
|
||||
'';
|
||||
};
|
||||
|
||||
keyFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to the file which contains the actual base64 encoded
|
||||
key. The key will be copied into "${stateDir}/private" before
|
||||
NSD starts. The copied file is only accessibly by the NSD
|
||||
user.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
example = literalExample ''
|
||||
{ "tsig.example.org" = {
|
||||
algorithm = "hmac-md5";
|
||||
keyFile = "/path/to/my/key";
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Define your TSIG keys here.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
ratelimit = {
|
||||
|
||||
enable = mkEnableOption "ratelimit capabilities";
|
||||
|
||||
ipv4PrefixLength = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
IPv4 prefix length. Addresses are grouped by netblock.
|
||||
'';
|
||||
};
|
||||
|
||||
ipv6PrefixLength = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
IPv6 prefix length. Addresses are grouped by netblock.
|
||||
'';
|
||||
};
|
||||
|
||||
ratelimit = mkOption {
|
||||
type = types.int;
|
||||
default = 200;
|
||||
description = ''
|
||||
Max qps allowed from any query source.
|
||||
0 means unlimited. With an verbosity of 2 blocked and
|
||||
unblocked subnets will be logged.
|
||||
'';
|
||||
};
|
||||
|
||||
slip = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Number of packets that get discarded before replying a SLIP response.
|
||||
0 disables SLIP responses. 1 will make every response a SLIP response.
|
||||
'';
|
||||
};
|
||||
|
||||
size = mkOption {
|
||||
type = types.int;
|
||||
default = 1000000;
|
||||
description = ''
|
||||
Size of the hashtable. More buckets use more memory but lower
|
||||
the chance of hash hash collisions.
|
||||
'';
|
||||
};
|
||||
|
||||
whitelistRatelimit = mkOption {
|
||||
type = types.int;
|
||||
default = 2000;
|
||||
description = ''
|
||||
Max qps allowed from whitelisted sources.
|
||||
0 means unlimited. Set the rrl-whitelist option for specific
|
||||
queries to apply this limit instead of the default to them.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
remoteControl = {
|
||||
|
||||
enable = mkEnableOption "remote control via nsd-control";
|
||||
|
||||
controlCertFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/nsd/nsd_control.pem";
|
||||
description = ''
|
||||
Path to the client certificate signed with the server certificate.
|
||||
This file is used by nsd-control and generated by nsd-control-setup.
|
||||
'';
|
||||
};
|
||||
|
||||
controlKeyFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/nsd/nsd_control.key";
|
||||
description = ''
|
||||
Path to the client private key, which is used by nsd-control
|
||||
but not by the server. This file is generated by nsd-control-setup.
|
||||
'';
|
||||
};
|
||||
|
||||
interfaces = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "127.0.0.1" "::1" ];
|
||||
description = ''
|
||||
Which interfaces NSD should bind to for remote control.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 8952;
|
||||
description = ''
|
||||
Port number for remote control operations (uses TLS over TCP).
|
||||
'';
|
||||
};
|
||||
|
||||
serverCertFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/nsd/nsd_server.pem";
|
||||
description = ''
|
||||
Path to the server self signed certificate, which is used by the server
|
||||
but and by nsd-control. This file is generated by nsd-control-setup.
|
||||
'';
|
||||
};
|
||||
|
||||
serverKeyFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/nsd/nsd_server.key";
|
||||
description = ''
|
||||
Path to the server private key, which is used by the server
|
||||
but not by nsd-control. This file is generated by nsd-control-setup.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
zones = mkOption {
|
||||
type = types.attrsOf zoneOptions;
|
||||
default = {};
|
||||
example = literalExample ''
|
||||
{ "serverGroup1" = {
|
||||
provideXFR = [ "10.1.2.3 NOKEY" ];
|
||||
children = {
|
||||
"example.com." = {
|
||||
data = '''
|
||||
$ORIGIN example.com.
|
||||
$TTL 86400
|
||||
@ IN SOA a.ns.example.com. admin.example.com. (
|
||||
...
|
||||
''';
|
||||
};
|
||||
"example.org." = {
|
||||
data = '''
|
||||
$ORIGIN example.org.
|
||||
$TTL 86400
|
||||
@ IN SOA a.ns.example.com. admin.example.com. (
|
||||
...
|
||||
''';
|
||||
};
|
||||
};
|
||||
};
|
||||
"example.net." = {
|
||||
provideXFR = [ "10.3.2.1 NOKEY" ];
|
||||
data = '''
|
||||
...
|
||||
''';
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Define your zones here. Zones can cascade other zones and therefore
|
||||
inherit settings from parent zones. Look at the definition of
|
||||
children to learn about inheritance and child zones.
|
||||
The given example will define 3 zones (example.(com|org|net).). Both
|
||||
example.com. and example.org. inherit their configuration from
|
||||
serverGroup1.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = singleton {
|
||||
assertion = zoneConfigs ? "." -> cfg.rootServer;
|
||||
message = "You have a root zone configured. If this is really what you "
|
||||
+ "want, please enable 'services.nsd.rootServer'.";
|
||||
};
|
||||
|
||||
environment = {
|
||||
systemPackages = [ nsdPkg ];
|
||||
etc."nsd/nsd.conf".source = "${configFile}/nsd.conf";
|
||||
};
|
||||
|
||||
users.groups.${username}.gid = config.ids.gids.nsd;
|
||||
|
||||
users.users.${username} = {
|
||||
description = "NSD service user";
|
||||
home = stateDir;
|
||||
createHome = true;
|
||||
uid = config.ids.uids.nsd;
|
||||
group = username;
|
||||
};
|
||||
|
||||
systemd.services.nsd = {
|
||||
description = "NSD authoritative only domain name service";
|
||||
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
startLimitBurst = 4;
|
||||
startLimitIntervalSec = 5 * 60; # 5 mins
|
||||
serviceConfig = {
|
||||
ExecStart = "${nsdPkg}/sbin/nsd -d -c ${nsdEnv}/nsd.conf";
|
||||
StandardError = "null";
|
||||
PIDFile = pidFile;
|
||||
Restart = "always";
|
||||
RestartSec = "4s";
|
||||
};
|
||||
|
||||
preStart = ''
|
||||
rm -Rf "${stateDir}/private/"
|
||||
rm -Rf "${stateDir}/tmp/"
|
||||
mkdir -m 0700 -p "${stateDir}/private"
|
||||
mkdir -m 0700 -p "${stateDir}/tmp"
|
||||
mkdir -m 0700 -p "${stateDir}/var"
|
||||
cat > "${stateDir}/don't touch anything in here" << EOF
|
||||
Everything in this directory except NSD's state in var and dnssec
|
||||
is automatically generated and will be purged and redeployed by
|
||||
the nsd.service pre-start script.
|
||||
EOF
|
||||
chown ${username}:${username} -R "${stateDir}/private"
|
||||
chown ${username}:${username} -R "${stateDir}/tmp"
|
||||
chown ${username}:${username} -R "${stateDir}/var"
|
||||
rm -rf "${stateDir}/zones"
|
||||
cp -rL "${nsdEnv}/zones" "${stateDir}/zones"
|
||||
${copyKeys}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.nsd-dnssec = mkIf dnssec {
|
||||
description = "Automatic DNSSEC key rollover";
|
||||
|
||||
wantedBy = [ "nsd.service" ];
|
||||
|
||||
timerConfig = {
|
||||
OnActiveSec = cfg.dnssecInterval;
|
||||
OnUnitActiveSec = cfg.dnssecInterval;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.nsd-dnssec = mkIf dnssec {
|
||||
description = "DNSSEC key rollover";
|
||||
|
||||
wantedBy = [ "nsd.service" ];
|
||||
before = [ "nsd.service" ];
|
||||
|
||||
script = signZones;
|
||||
|
||||
postStop = ''
|
||||
/run/current-system/systemd/bin/systemctl kill -s SIGHUP nsd.service
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
}
|
@ -16,32 +16,28 @@ let
|
||||
|
||||
decrypt-script = { secret-name, source-file, target-host, target-file
|
||||
, host-master-key, user, group, permissions }:
|
||||
pkgs.writeShellScript
|
||||
"decrypt-fudo-secret-${target-host}-${secret-name}.sh" ''
|
||||
rm -rf ${target-file}
|
||||
age -d -i ${host-master-key.key-path} -o ${target-file} ${
|
||||
encrypt-on-disk {
|
||||
inherit secret-name source-file target-host;
|
||||
target-pubkey = host-master-key.public-key;
|
||||
}
|
||||
}
|
||||
pkgs.writeShellScript "decrypt-fudo-secret-${target-host}-${secret-name}.sh" ''
|
||||
rm -f ${target-file}
|
||||
touch ${target-file}
|
||||
chown ${user}:${group} ${target-file}
|
||||
chmod ${permissions} ${target-file}
|
||||
# NOTE: silly hack because sometimes age leaves a blank line
|
||||
# Only include lines with at least one non-space character
|
||||
SRC=$(mktemp fudo-secret-${target-host}-${secret-name}.XXXXXXXX)
|
||||
cat ${encrypt-on-disk {
|
||||
inherit secret-name source-file target-host;
|
||||
target-pubkey = host-master-key.public-key;
|
||||
}} | grep "[^ ]" > $SRC
|
||||
age -d -i ${host-master-key.key-path} -o ${target-file} $SRC
|
||||
rm -f $SRC
|
||||
'';
|
||||
|
||||
secret-service = target-host: secret-name:
|
||||
{ source-file, target-file, user, group, permissions }: {
|
||||
{ source-file, target-file, user, group, permissions, ... }: {
|
||||
description = "decrypt secret ${secret-name} for ${target-host}.";
|
||||
wantedBy = [ "default.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStartPre = pkgs.writeShellScript
|
||||
"prepare-${target-host}-${secret-name}-secret-dir.sh" ''
|
||||
TARGET_DIR=$(dirname ${target-file})
|
||||
if [[ ! -d "$TARGET_DIR" ]]; then
|
||||
mkdir -p "$TARGET_DIR"
|
||||
fi
|
||||
'';
|
||||
ExecStart = let
|
||||
host-master-key = config.fudo.hosts.${target-host}.master-key;
|
||||
in decrypt-script {
|
||||
@ -82,6 +78,12 @@ let
|
||||
description = "Permissions to set on the target file.";
|
||||
default = "0400";
|
||||
};
|
||||
|
||||
metadata = mkOption {
|
||||
type = attrsOf anything;
|
||||
description = "Arbitrary metadata associated with this secret.";
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -166,17 +168,33 @@ in {
|
||||
|
||||
systemd = let
|
||||
hostname = config.instance.hostname;
|
||||
|
||||
host-secrets = if (hasAttr hostname cfg.host-secrets) then
|
||||
cfg.host-secrets.${hostname}
|
||||
else
|
||||
{ };
|
||||
|
||||
host-secret-services = mapAttrs' (secret: secretOpts:
|
||||
(nameValuePair "fudo-secret-${hostname}-${secret}"
|
||||
(secret-service hostname secret secretOpts))) host-secrets;
|
||||
|
||||
trace-all = obj: builtins.trace obj obj;
|
||||
|
||||
host-secret-paths = mapAttrsToList
|
||||
(secret: secretOpts:
|
||||
let perms = if secretOpts.group != "nobody" then "550" else "500";
|
||||
in "d ${dirOf secretOpts.target-file} ${perms} ${secretOpts.user} ${secretOpts.group} - -")
|
||||
host-secrets;
|
||||
|
||||
build-secret-paths =
|
||||
map (path: "d '${path}' - root ${cfg.secret-group} - -")
|
||||
cfg.secret-paths;
|
||||
|
||||
in {
|
||||
tmpfiles.rules = host-secret-paths ++ build-secret-paths;
|
||||
|
||||
services = host-secret-services // {
|
||||
fudo-secrets-watcher = {
|
||||
fudo-secrets-watcher = mkIf (length cfg.secret-paths > 0) {
|
||||
wantedBy = [ "default.target" ];
|
||||
description =
|
||||
"Ensure access for group ${cfg.secret-group} to fudo secret paths.";
|
||||
@ -190,7 +208,7 @@ in {
|
||||
};
|
||||
};
|
||||
|
||||
paths.fudo-secrets-watcher = mkIf ((length cfg.secret-paths) > 0) {
|
||||
paths.fudo-secrets-watcher = mkIf (length cfg.secret-paths > 0) {
|
||||
wantedBy = [ "default.target" ];
|
||||
description = "Watch fudo secret paths, and correct perms on changes.";
|
||||
pathConfig = {
|
||||
@ -198,9 +216,6 @@ in {
|
||||
Unit = "fudo-secrets-watcher.service";
|
||||
};
|
||||
};
|
||||
|
||||
tmpfiles.rules = map (path: "d '${path}' - root ${cfg.secret-group} - -")
|
||||
cfg.secret-paths;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -11,34 +11,6 @@ let
|
||||
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};
|
||||
|
||||
@ -145,17 +117,10 @@ in {
|
||||
};
|
||||
|
||||
# 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" ];
|
||||
};
|
||||
systemd.tmpfiles.rules = let
|
||||
groups-with-members = attrNames
|
||||
(filterAttrs (group: groupOpts: (length groupOpts.members) > 0)
|
||||
sys.local-groups);
|
||||
in listToAttrs (map ensure-group-directories groups-with-members);
|
||||
in map (group: "d /home/${group} 550 root ${group} - -") groups-with-members;
|
||||
};
|
||||
}
|
||||
|
@ -10,8 +10,44 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
# dropUntil = pred: lst: let
|
||||
# drop-until-helper = pred: lst:
|
||||
# if (length lst) == 0 then [] else
|
||||
# if (pred (head lst)) then lst else (drop-until-helper pred (tail lst));
|
||||
# in drop-until-helper pred lst;
|
||||
|
||||
# dropWhile = pred: dropUntil (el: !(pred el));
|
||||
|
||||
# is-whitespace = str: (builtins.match "^[[:space:]]*$" str) != null;
|
||||
|
||||
# stripWhitespace = str: let
|
||||
# lines = builtins.split "\n" str;
|
||||
# lines-front-stripped = dropWhile is-whitespace lines;
|
||||
# lines-rear-stripped = lib.reverseList
|
||||
# (dropWhile is-whitespace
|
||||
# (lib.reverseList lines-front-stripped));
|
||||
# in concatStringsSep "\n" lines-rear-stripped;
|
||||
|
||||
host-ipv4 = config: hostname: let
|
||||
domain = config.fudo.hosts.${hostname}.domain;
|
||||
host-network = config.fudo.networks.${domain};
|
||||
in host-network.hosts.${hostname}.ipv4-address;
|
||||
|
||||
host-ipv6 = config: hostname: let
|
||||
domain = config.fudo.hosts.${hostname}.domain;
|
||||
host-network = config.fudo.networks.${domain};
|
||||
in host-network.hosts.${hostname}.ipv6-address;
|
||||
|
||||
host-ips = config: hostname: let
|
||||
ipv4 = host-ipv4 config hostname;
|
||||
ipv6 = host-ipv6 config hostname;
|
||||
not-null = o: o != null;
|
||||
in filter not-null [ ipv4 ipv6 ];
|
||||
|
||||
in {
|
||||
inherit host-ipv4 host-ipv6 host-ips;
|
||||
|
||||
generate-mac-address = hostname: interface: let
|
||||
pkg = generate-mac-address hostname interface;
|
||||
in builtins.readFile "${pkg}";
|
||||
in removeSuffix "\n" (builtins.readFile "${pkg}");
|
||||
}
|
||||
|
@ -79,12 +79,6 @@ rec {
|
||||
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.";
|
||||
|
Loading…
Reference in New Issue
Block a user