Added nutboy3 and nuttyclub
This commit is contained in:
parent
1c059841e6
commit
d7ee19dd2f
@ -62,20 +62,25 @@
|
|||||||
local-admins = [ "niten" ];
|
local-admins = [ "niten" ];
|
||||||
admin-email = "viator@informis.land";
|
admin-email = "viator@informis.land";
|
||||||
gssapi-realm = "INFORMIS.LAND";
|
gssapi-realm = "INFORMIS.LAND";
|
||||||
|
kerberos-master = "procul";
|
||||||
|
kerberos-slaves = [ "legatus" ];
|
||||||
|
primary-nameserver = "procul";
|
||||||
};
|
};
|
||||||
|
|
||||||
eur.fudo.org = {
|
"eur.fudo.org" = {
|
||||||
local-networks = [
|
local-networks = [
|
||||||
"208.81.1.128/28"
|
"208.81.1.128/28"
|
||||||
"208.81.3.112/28"
|
"208.81.3.112/28"
|
||||||
"91.229.23.204/31"
|
"91.229.23.204/31"
|
||||||
];
|
];
|
||||||
|
|
||||||
local-users = [ "niten"];
|
local-users = [ "niten" "reaper" ];
|
||||||
local-groups = [ "admin" ];
|
local-groups = [ "admin" ];
|
||||||
local-admins = [ "niten" ];
|
local-admins = [ "niten" "reaper" ];
|
||||||
admin-email = "nitenn@fudo.org";
|
admin-email = "nitenn@fudo.org";
|
||||||
gssapi-realm = "FUDO.ORG";
|
gssapi-realm = "FUDO.ORG";
|
||||||
|
# kerberos-master = "legatus";
|
||||||
|
primary-nameserver = "legatus";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -67,8 +67,8 @@ with lib; {
|
|||||||
|
|
||||||
interfaces = {
|
interfaces = {
|
||||||
extif0 = {
|
extif0 = {
|
||||||
# output of: echo legatus-extif0|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'
|
macAddress =
|
||||||
macAddress = pkgs.lib.fudo.network.generate-mac-address "legatus" "extif0";
|
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};
|
domain = config.fudo.domains.${domain-name};
|
||||||
site-name = config.fudo.hosts.${hostname}.site;
|
site-name = config.fudo.hosts.${hostname}.site;
|
||||||
site = config.fudo.sites.${site-name};
|
site = config.fudo.sites.${site-name};
|
||||||
host-fqdn = "${hostname}.${domain-name}";
|
|
||||||
|
|
||||||
local-packages = with pkgs; [ ldns.examples ];
|
local-packages = with pkgs; [ ldns.examples ];
|
||||||
|
|
||||||
@ -56,80 +55,67 @@ in {
|
|||||||
# };
|
# };
|
||||||
# };
|
# };
|
||||||
|
|
||||||
fudo = {
|
fudo = {
|
||||||
hosts.legatus.external-interfaces = [ "extif0" ];
|
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
|
# gitea-database-password = {
|
||||||
# files = config.fudo.secrets.files;
|
# source-file = files.service-passwords.procul.gitea-database;
|
||||||
# in {
|
# target-file = "/srv/gitea/secure/database.passwd";
|
||||||
# postgres-keytab = {
|
# user = config.fudo.git.user;
|
||||||
# source-file = files.service-keytabs.procul.postgres;
|
# };
|
||||||
# target-file = "/srv/postgres/secure/postgres.keytab";
|
|
||||||
# user = "root";
|
|
||||||
# };
|
|
||||||
|
|
||||||
# gitea-database-password = {
|
heimdal-master-key = {
|
||||||
# source-file = files.service-passwords.procul.gitea-database;
|
source-file = files.realm-master-keys."FUDO.ORG";
|
||||||
# target-file = "/srv/gitea/secure/database.passwd";
|
target-file = "/run/heimdal/master-key";
|
||||||
# user = config.fudo.git.user;
|
user = config.fudo.auth.kdc.user;
|
||||||
# };
|
};
|
||||||
# };
|
|
||||||
|
|
||||||
# client.dns = {
|
ipropd-keytab = {
|
||||||
# enable = true;
|
source-file = files.service-keytabs.legatus.ipropd;
|
||||||
# ipv4 = true;
|
target-file = "/run/heimdal/ipropd.keytab";
|
||||||
# ipv6 = true;
|
user = config.fudo.auth.kdc.user;
|
||||||
# user = "fudo-client";
|
};
|
||||||
# external-interface = "extif0";
|
};
|
||||||
# };
|
|
||||||
|
|
||||||
# auth.kdc = {
|
client.dns = {
|
||||||
# enable = true;
|
ipv4 = true;
|
||||||
# realm = "INFORMIS.LAND";
|
ipv6 = true;
|
||||||
# bind-addresses = [ host-ipv4 "127.0.0.1" ];
|
user = "fudo-client";
|
||||||
# acl = {
|
external-interface = "extif0";
|
||||||
# "niten" = { perms = [ "add" "change-password" "list" ]; };
|
};
|
||||||
# "*/root" = { perms = [ "all" ]; };
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
|
|
||||||
# secure-dns-proxy = {
|
auth.kdc = {
|
||||||
# enable = true;
|
enable = true;
|
||||||
# upstream-dns =
|
realm = "FUDO.ORG";
|
||||||
# [ "https://1.1.1.1/dns-query" "https://1.0.0.1/dns-query" ];
|
bind-addresses = [ host-ipv4 "127.0.0.1" ];
|
||||||
# bootstrap-dns = "1.1.1.1";
|
master-key-file =
|
||||||
# listen-ips = [ "127.0.0.1" ];
|
secrets.heimdal-master-key.target-file;
|
||||||
# listen-port = 53;
|
state-directory = "/state/kerberos";
|
||||||
# allowed-networks = [ "1.1.1.1/32" "1.0.0.1/32" "localhost" "link-local" ];
|
slave-config = {
|
||||||
# };
|
master-host = "france";
|
||||||
|
ipropd-keytab = secrets.ipropd-keytab.target-file;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# dns = {
|
secure-dns-proxy = {
|
||||||
# enable = true;
|
enable = true;
|
||||||
# identity = "procul.informis.land";
|
upstream-dns =
|
||||||
# nameservers = {
|
[ "https://1.1.1.1/dns-query" "https://1.0.0.1/dns-query" ];
|
||||||
# ns1 = {
|
bootstrap-dns = "1.1.1.1";
|
||||||
# ipv4-address = host-ipv4;
|
listen-ips = [ "127.0.0.1" ];
|
||||||
# description = "Primary Informis Nameserver";
|
listen-port = 53;
|
||||||
# };
|
allowed-networks = [ "1.1.1.1/32" "1.0.0.1/32" "localhost" "link-local" ];
|
||||||
# ns2 = {
|
};
|
||||||
# ipv4-address = host-ipv4;
|
|
||||||
# description = "Secondary Informis Nameserver";
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
|
|
||||||
# listen-ips = [ host-ipv4 ];
|
dns.state-directory = "/state/nsd";
|
||||||
|
|
||||||
# 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 = {
|
# mail-server = {
|
||||||
# enable = true;
|
# enable = true;
|
||||||
@ -218,16 +204,5 @@ in {
|
|||||||
# listen-port = 2222;
|
# 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};
|
network-definition = config.fudo.networks.${domain-name};
|
||||||
};
|
};
|
||||||
|
|
||||||
client.dns = {
|
client.dns.external-interface = "enp1s0";
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
garbage-collector = {
|
garbage-collector = {
|
||||||
enable = true;
|
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";
|
target-file = "/srv/gitea/secure/database.passwd";
|
||||||
user = config.fudo.git.user;
|
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 = {
|
client.dns = {
|
||||||
enable = true;
|
|
||||||
ipv4 = true;
|
ipv4 = true;
|
||||||
ipv6 = true;
|
ipv6 = true;
|
||||||
user = "fudo-client";
|
user = "fudo-client";
|
||||||
external-interface = "extif0";
|
external-interface = "extif0";
|
||||||
};
|
};
|
||||||
|
|
||||||
auth.kdc = {
|
auth.kdc.master-key-file = secrets.heimdal-master-key.target-file;
|
||||||
enable = true;
|
|
||||||
realm = "INFORMIS.LAND";
|
|
||||||
bind-addresses = [ host-ipv4 "127.0.0.1" ];
|
|
||||||
acl = {
|
|
||||||
"niten" = { perms = [ "add" "change-password" "list" ]; };
|
|
||||||
"*/root" = { perms = [ "all" ]; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
secure-dns-proxy = {
|
secure-dns-proxy = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@ -131,34 +128,6 @@ in {
|
|||||||
allowed-networks = [ "1.1.1.1/32" "1.0.0.1/32" "localhost" "link-local" ];
|
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 = {
|
mail-server = {
|
||||||
enable = true;
|
enable = true;
|
||||||
debug = true;
|
debug = true;
|
||||||
@ -232,8 +201,8 @@ in {
|
|||||||
hostname = "git.informis.land";
|
hostname = "git.informis.land";
|
||||||
site-name = "informis git";
|
site-name = "informis git";
|
||||||
user = "gituser";
|
user = "gituser";
|
||||||
repository-dir = /srv/git/repo;
|
repository-dir = "/srv/git/repo";
|
||||||
state-dir = /srv/git/state;
|
state-dir = "/srv/git/state";
|
||||||
database = {
|
database = {
|
||||||
user = "gituser";
|
user = "gituser";
|
||||||
password-file =
|
password-file =
|
||||||
|
@ -49,11 +49,6 @@ in {
|
|||||||
|
|
||||||
system.stateVersion = "21.05";
|
system.stateVersion = "21.05";
|
||||||
|
|
||||||
security.sudo.extraConfig = ''
|
|
||||||
# Due to tmpfs home, it'll always lecture otherwise
|
|
||||||
Defaults lecture = never
|
|
||||||
'';
|
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
openssh = {
|
openssh = {
|
||||||
hostKeys = [
|
hostKeys = [
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
description = "informis.land server.";
|
description = "eur.fudo.org server.";
|
||||||
rp = "niten";
|
rp = "niten";
|
||||||
admin-email = "niten@fudo.org";
|
admin-email = "niten@fudo.org";
|
||||||
domain = "eur.fudo.org";
|
domain = "eur.fudo.org";
|
||||||
@ -11,7 +11,7 @@
|
|||||||
nixos-system = true;
|
nixos-system = true;
|
||||||
machine-id = "749bbf411088411b8784b76bb44bd617";
|
machine-id = "749bbf411088411b8784b76bb44bd617";
|
||||||
master-key = {
|
master-key = {
|
||||||
public-key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIqUnzf8bfPyoJX6XjFqD6v5MZQnV8STP0152VS3uwM7";
|
public-key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGsTkxsVViISxZYtqwNs6DEK2XgyBUPhqio4XPQbMKNo";
|
||||||
key-path = "/state/master-key/ed25519_key";
|
key-path = "/state/master-key/ed25519_key";
|
||||||
};
|
};
|
||||||
# initrd-network = {
|
# 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 = {
|
srv-records = {
|
||||||
tcp = {
|
tcp = {
|
||||||
domain = [{
|
|
||||||
host = "ns1.informis.land";
|
|
||||||
port = 53;
|
|
||||||
}];
|
|
||||||
ssh = [{
|
ssh = [{
|
||||||
host = "procul.informis.land";
|
host = "procul.informis.land";
|
||||||
port = 22;
|
port = 22;
|
||||||
@ -22,14 +18,6 @@
|
|||||||
host = "procul.informis.land";
|
host = "procul.informis.land";
|
||||||
port = 587;
|
port = 587;
|
||||||
}];
|
}];
|
||||||
kerberos = [{
|
|
||||||
host = "procul.informis.land";
|
|
||||||
port = 88;
|
|
||||||
}];
|
|
||||||
kerberos-adm = [{
|
|
||||||
host = "procul.informis.land";
|
|
||||||
port = 749;
|
|
||||||
}];
|
|
||||||
imaps = [{
|
imaps = [{
|
||||||
host = "procul.informis.land";
|
host = "procul.informis.land";
|
||||||
port = 993;
|
port = 993;
|
||||||
@ -49,25 +37,6 @@
|
|||||||
port = 443;
|
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 = {
|
hosts = {
|
||||||
|
@ -8,6 +8,7 @@ let
|
|||||||
cryptsetup
|
cryptsetup
|
||||||
git
|
git
|
||||||
heimdal
|
heimdal
|
||||||
|
mosh
|
||||||
openssh_gssapi
|
openssh_gssapi
|
||||||
tldr
|
tldr
|
||||||
vim
|
vim
|
||||||
|
@ -7,15 +7,21 @@ let
|
|||||||
try-attr = attr: set: if (hasAttr attr set) then set.${attr} else null;
|
try-attr = attr: set: if (hasAttr attr set) then set.${attr} else null;
|
||||||
|
|
||||||
in {
|
in {
|
||||||
config = mkIf has-secret-files {
|
config = mkIf has-secret-files (let
|
||||||
fudo.secrets.host-secrets.${hostname} = let
|
keytab-file = try-attr hostname config.fudo.secrets.files.host-keytabs;
|
||||||
keytab-file = try-attr hostname config.fudo.secrets.files.host-keytabs;
|
in {
|
||||||
in mkIf (keytab-file != null) {
|
environment.etc."krb5.keytab" = mkIf (keytab-file != null) {
|
||||||
host-keytab = {
|
source =
|
||||||
source-file = keytab-file;
|
config.fudo.secrets.host-secrets.${hostname}.host-keytab.target-file;
|
||||||
target-file = "/etc/krb5.keytab";
|
user = "root";
|
||||||
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
|
(map
|
||||||
(keypair: nameValuePair "host-${keypair.key-type}-private-key" {
|
(keypair: nameValuePair "host-${keypair.key-type}-private-key" {
|
||||||
source-file = keypair.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";
|
user = "root";
|
||||||
})
|
})
|
||||||
host-keypairs);
|
host-keypairs);
|
||||||
@ -52,8 +52,11 @@ in {
|
|||||||
}) config.fudo.secrets.files.host-ssh-keypairs);
|
}) config.fudo.secrets.files.host-ssh-keypairs);
|
||||||
};
|
};
|
||||||
|
|
||||||
services.openssh.hostKeys = map (keypair: {
|
services.openssh.hostKeys = let
|
||||||
path = "/var/run/ssh/private/host-${keypair.key-type}-private-key";
|
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;
|
type = keypair.key-type;
|
||||||
}) host-keypairs;
|
}) 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" ];
|
options = [ "comment=systemd.automount" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# "proto=tcp"
|
||||||
|
|
||||||
# NOTE: these are pointing directly to nostromo so the krb lookup works
|
# NOTE: these are pointing directly to nostromo so the krb lookup works
|
||||||
"/net/documents" = {
|
"/net/documents" = {
|
||||||
device = "nostromo.sea.fudo.org:/export/documents";
|
device = "nostromo.sea.fudo.org:/export/documents";
|
||||||
fsType = "nfs4";
|
fsType = "nfs4";
|
||||||
options = [ "comment=systemd.automount" "sec=krb5p" "proto=tcp" ];
|
options = [ "comment=systemd.automount" "sec=krb5p" ];
|
||||||
};
|
};
|
||||||
"/net/downloads" = {
|
"/net/downloads" = {
|
||||||
device = "nostromo.sea.fudo.org:/export/downloads";
|
device = "nostromo.sea.fudo.org:/export/downloads";
|
||||||
fsType = "nfs4";
|
fsType = "nfs4";
|
||||||
options = [ "comment=systemd.automount" "sec=krb5i" "proto=tcp" ];
|
options = [ "comment=systemd.automount" "sec=krb5i" ];
|
||||||
};
|
};
|
||||||
"/net/projects" = {
|
"/net/projects" = {
|
||||||
device = "nostromo.sea.fudo.org:/export/projects";
|
device = "nostromo.sea.fudo.org:/export/projects";
|
||||||
fsType = "nfs4";
|
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";
|
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 = {
|
russell = {
|
||||||
gateway-v4 = "10.0.0.1";
|
gateway-v4 = "10.0.0.1";
|
||||||
nameservers = [ "10.0.0.1" ];
|
nameservers = [ "10.0.0.1" ];
|
||||||
@ -61,7 +73,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
worldstream = {
|
worldstream = {
|
||||||
gateway-v4 = "91.229.23.204";
|
gateway-v4 = "91.229.23.1";
|
||||||
network = "91.229.23.0/24";
|
network = "91.229.23.0/24";
|
||||||
nameservers = [ "1.1.1.1" "2606:4700:4700::1111" ];
|
nameservers = [ "1.1.1.1" "2606:4700:4700::1111" ];
|
||||||
timezone = "Europe/Amsterdam";
|
timezone = "Europe/Amsterdam";
|
||||||
|
@ -34,6 +34,7 @@ with lib; {
|
|||||||
./fudo/netinfo-email.nix
|
./fudo/netinfo-email.nix
|
||||||
./fudo/networks.nix
|
./fudo/networks.nix
|
||||||
./fudo/node-exporter.nix
|
./fudo/node-exporter.nix
|
||||||
|
./fudo/nsd.nix
|
||||||
./fudo/password.nix
|
./fudo/password.nix
|
||||||
./fudo/postgres.nix
|
./fudo/postgres.nix
|
||||||
./fudo/prometheus.nix
|
./fudo/prometheus.nix
|
||||||
|
@ -11,8 +11,6 @@ let
|
|||||||
|
|
||||||
in {
|
in {
|
||||||
options.fudo.client.dns = {
|
options.fudo.client.dns = {
|
||||||
enable = mkEnableOption "Enable Fudo DynDNS Client.";
|
|
||||||
|
|
||||||
ipv4 = mkOption {
|
ipv4 = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
@ -71,7 +69,7 @@ in {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = {
|
||||||
|
|
||||||
users.users = {
|
users.users = {
|
||||||
"${cfg.user}" = {
|
"${cfg.user}" = {
|
||||||
|
@ -116,6 +116,12 @@ in {
|
|||||||
description = "A list of IPs on which to listen for DNS queries.";
|
description = "A list of IPs on which to listen for DNS queries.";
|
||||||
example = [ "1.2.3.4" ];
|
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 {
|
config = mkIf cfg.enable {
|
||||||
@ -124,48 +130,49 @@ in {
|
|||||||
allowedUDPPorts = [ 53 ];
|
allowedUDPPorts = [ 53 ];
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nsd = {
|
fudo.nsd = {
|
||||||
enable = true;
|
enable = true;
|
||||||
identity = cfg.identity;
|
identity = cfg.identity;
|
||||||
interfaces = cfg.listen-ips;
|
interfaces = cfg.listen-ips;
|
||||||
|
stateDir = cfg.state-directory;
|
||||||
zones = mapAttrs' (dom: dom-cfg: let
|
zones = mapAttrs' (dom: dom-cfg: let
|
||||||
net-cfg = dom-cfg.network-definition;
|
net-cfg = dom-cfg.network-definition;
|
||||||
in nameValuePair "${dom}." {
|
in nameValuePair "${dom}." {
|
||||||
dnssec = dom-cfg.dnssec;
|
dnssec = dom-cfg.dnssec;
|
||||||
|
|
||||||
data = ''
|
data = ''
|
||||||
$ORIGIN ${dom}.
|
$ORIGIN ${dom}.
|
||||||
$TTL 12h
|
$TTL 12h
|
||||||
|
|
||||||
@ IN SOA ns1.${dom}. hostmaster.${dom}. (
|
@ IN SOA ns1.${dom}. hostmaster.${dom}. (
|
||||||
${toString config.instance.build-timestamp}
|
${toString config.instance.build-timestamp}
|
||||||
30m
|
30m
|
||||||
2m
|
2m
|
||||||
3w
|
3w
|
||||||
5m)
|
5m)
|
||||||
|
|
||||||
${optionalString (dom-cfg.default-host != null)
|
${optionalString (dom-cfg.default-host != null)
|
||||||
"@ IN A ${dom-cfg.default-host}"}
|
"@ 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}"''}
|
''_kerberos IN TXT "${dom-cfg.gssapi-realm}"''}
|
||||||
|
|
||||||
${nsRecords dom cfg.nameservers}
|
${nsRecords dom cfg.nameservers}
|
||||||
${join-lines (mapAttrsToList hostRecords 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)}
|
(mapAttrsToList makeSrvProtocolRecords net-cfg.srv-records)}
|
||||||
${join-lines (mapAttrsToList hostRecords net-cfg.hosts)}
|
${join-lines (mapAttrsToList hostRecords net-cfg.hosts)}
|
||||||
${join-lines (mapAttrsToList cnameRecord net-cfg.aliases)}
|
${join-lines (mapAttrsToList cnameRecord net-cfg.aliases)}
|
||||||
${join-lines net-cfg.verbatim-dns-records}
|
${join-lines net-cfg.verbatim-dns-records}
|
||||||
'';
|
'';
|
||||||
}) cfg.domains;
|
}) 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;
|
with lib;
|
||||||
let
|
let
|
||||||
domainOpts = { domain, ... }: {
|
hostname = config.instance.hostname;
|
||||||
options = {
|
domain = config.instance.local-domain;
|
||||||
|
|
||||||
|
domainOpts = { name, ... }: let
|
||||||
|
domain = name;
|
||||||
|
in {
|
||||||
|
options = with types; {
|
||||||
domain = mkOption {
|
domain = mkOption {
|
||||||
type = types.str;
|
type = str;
|
||||||
description = "Domain name.";
|
description = "Domain name.";
|
||||||
default = domain;
|
default = domain;
|
||||||
};
|
};
|
||||||
|
|
||||||
local-networks = mkOption {
|
local-networks = mkOption {
|
||||||
type = with types; listOf str;
|
type = listOf str;
|
||||||
description =
|
description =
|
||||||
"A list of networks to be considered trusted on this network.";
|
"A list of networks to be considered trusted on this network.";
|
||||||
default = [ ];
|
default = [ ];
|
||||||
};
|
};
|
||||||
|
|
||||||
local-users = mkOption {
|
local-users = mkOption {
|
||||||
type = with types; listOf str;
|
type = listOf str;
|
||||||
description =
|
description =
|
||||||
"A list of users who should have local (i.e. login) access to _all_ hosts in this domain.";
|
"A list of users who should have local (i.e. login) access to _all_ hosts in this domain.";
|
||||||
default = [ ];
|
default = [ ];
|
||||||
};
|
};
|
||||||
|
|
||||||
local-admins = mkOption {
|
local-admins = mkOption {
|
||||||
type = with types; listOf str;
|
type = listOf str;
|
||||||
description =
|
description =
|
||||||
"A list of users who should have admin access to _all_ hosts in this domain.";
|
"A list of users who should have admin access to _all_ hosts in this domain.";
|
||||||
default = [ ];
|
default = [ ];
|
||||||
};
|
};
|
||||||
|
|
||||||
local-groups = mkOption {
|
local-groups = mkOption {
|
||||||
type = with types; listOf str;
|
type = listOf str;
|
||||||
description = "List of groups which should exist within this domain.";
|
description = "List of groups which should exist within this domain.";
|
||||||
default = [ ];
|
default = [ ];
|
||||||
};
|
};
|
||||||
|
|
||||||
admin-email = mkOption {
|
admin-email = mkOption {
|
||||||
type = types.str;
|
type = str;
|
||||||
description = "Email for the administrator of this domain.";
|
description = "Email for the administrator of this domain.";
|
||||||
default = "admin@fudo.org";
|
default = "admin@${domain}";
|
||||||
};
|
};
|
||||||
|
|
||||||
gssapi-realm = mkOption {
|
gssapi-realm = mkOption {
|
||||||
type = with types; nullOr str;
|
type = str;
|
||||||
description = "GSSAPI (i.e. Kerberos) realm of this domain.";
|
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.";
|
description = "Domain configurations for all domains known to the system.";
|
||||||
default = { };
|
default = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
./domain/kerberos.nix
|
||||||
|
./domain/dns.nix
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -64,15 +64,15 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
repository-dir = mkOption {
|
repository-dir = mkOption {
|
||||||
type = path;
|
type = str;
|
||||||
description = "Path at which to store repositories.";
|
description = "Path at which to store repositories.";
|
||||||
example = /srv/git/repo;
|
example = "/srv/git/repo";
|
||||||
};
|
};
|
||||||
|
|
||||||
state-dir = mkOption {
|
state-dir = mkOption {
|
||||||
type = path;
|
type = str;
|
||||||
description = "Path at which to store server state.";
|
description = "Path at which to store server state.";
|
||||||
example = /srv/git/state;
|
example = "/srv/git/state";
|
||||||
};
|
};
|
||||||
|
|
||||||
user = mkOption {
|
user = mkOption {
|
||||||
@ -103,6 +103,15 @@ in {
|
|||||||
networking.firewall.allowedTCPPorts =
|
networking.firewall.allowedTCPPorts =
|
||||||
mkIf (cfg.ssh != null) [ cfg.ssh.listen-port ];
|
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 = {
|
services = {
|
||||||
gitea = {
|
gitea = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@ -118,8 +127,8 @@ in {
|
|||||||
domain = cfg.hostname;
|
domain = cfg.hostname;
|
||||||
httpAddress = "127.0.0.1";
|
httpAddress = "127.0.0.1";
|
||||||
httpPort = cfg.local-port;
|
httpPort = cfg.local-port;
|
||||||
repositoryRoot = toString cfg.repository-dir;
|
repositoryRoot = cfg.repository-dir;
|
||||||
stateDir = toString cfg.state-dir;
|
stateDir = cfg.state-dir;
|
||||||
rootUrl = "https://${cfg.hostname}/";
|
rootUrl = "https://${cfg.hostname}/";
|
||||||
user = mkIf (cfg.user != null) cfg.user;
|
user = mkIf (cfg.user != null) cfg.user;
|
||||||
ssh = {
|
ssh = {
|
||||||
|
@ -36,6 +36,11 @@ in {
|
|||||||
has-build-keys = (length host-cfg.build-pubkeys) > 0;
|
has-build-keys = (length host-cfg.build-pubkeys) > 0;
|
||||||
|
|
||||||
in {
|
in {
|
||||||
|
security.sudo.extraConfig = ''
|
||||||
|
# I get it, I get it
|
||||||
|
Defaults lecture = never
|
||||||
|
'';
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
hostName = config.instance.hostname;
|
hostName = config.instance.hostname;
|
||||||
domain = domain-name;
|
domain = domain-name;
|
||||||
@ -65,11 +70,11 @@ in {
|
|||||||
config.fudo.system.hostfile-entries;
|
config.fudo.system.hostfile-entries;
|
||||||
in mkForce {
|
in mkForce {
|
||||||
text = ''
|
text = ''
|
||||||
127.0.0.1 ${hostname}.${domain-name} ${hostname} localhost
|
127.0.0.1 ${hostname}.${domain-name} ${hostname} localhost
|
||||||
127.0.0.2 ${hostname} localhost
|
127.0.0.2 ${hostname} localhost
|
||||||
::1 ${hostname}.${domain-name} ${hostname} localhost
|
::1 ${hostname}.${domain-name} ${hostname} localhost
|
||||||
${concatStringsSep "\n" host-entries}
|
${concatStringsSep "\n" host-entries}
|
||||||
'';
|
'';
|
||||||
user = "root";
|
user = "root";
|
||||||
group = "root";
|
group = "root";
|
||||||
mode = "0444";
|
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;
|
with lib;
|
||||||
let
|
let
|
||||||
cfg = config.fudo.auth.kdc;
|
cfg = config.fudo.auth.kdc;
|
||||||
|
|
||||||
database-file = "${cfg.state-directory}/principals.db";
|
hostname = config.instance.hostname;
|
||||||
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;
|
|
||||||
|
|
||||||
get-domain-hosts = domain:
|
state-directory = toplevel.config.fudo.auth.kdc.state-directory;
|
||||||
attrNames
|
|
||||||
(filterAttrs (host: hostOpts: hostOpts.domain == domain) config.fudo.hosts);
|
|
||||||
|
|
||||||
get-host-principals = realm: hostname:
|
database-file = "${state-directory}/principals.db";
|
||||||
let host = config.fudo.hosts.${hostname};
|
iprop-log = "${state-directory}/iprop.log";
|
||||||
in map (service: "${service}/${hostname}.${toLower realm}@${realm}")
|
|
||||||
host.kerberos-services;
|
|
||||||
|
|
||||||
add-principal-str = kdc-conf: principal:
|
master-server = cfg.master-config != null;
|
||||||
"${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- add --random-key --use-defaults ${principal}";
|
slave-server = cfg.slave-config != null;
|
||||||
|
|
||||||
add-hosts-principals = realm: kdc-conf:
|
get-fqdn = hostname:
|
||||||
concatStringsSep "\n" (map (add-principal-str kdc-conf)
|
"${hostname}.${config.fudo.hosts.${hostname}.domain}";
|
||||||
(concatMap (get-host-principals realm)
|
|
||||||
(get-domain-hosts (toLower realm))));
|
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 =
|
initialize-db =
|
||||||
realm: user: group: kdc-conf: key-file: db-name: max-lifetime: max-renewal: primary-keytab: kadmin-keytab: kpasswd-keytab: local-hostname:
|
{ realm, user, group, kdc-conf, key-file, db-name, max-lifetime, max-renewal,
|
||||||
pkgs.writeShellScript "initialize-kdc-db.sh" ''
|
primary-keytab, kadmin-keytab, kpasswd-keytab, ipropd-keytab, local-hostname }: let
|
||||||
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
|
|
||||||
'';
|
|
||||||
|
|
||||||
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" ''
|
pkgs.writeText "kdc.conf" ''
|
||||||
[kdc]
|
[kdc]
|
||||||
database = {
|
database = {
|
||||||
dbname = sqlite:${db-file}
|
dbname = sqlite:${db-file}
|
||||||
realm = ${realm}
|
realm = ${realm}
|
||||||
mkey_file = ${key-file}
|
mkey_file = ${key-file}
|
||||||
acl_file = ${acl-file}
|
${optionalString (acl-data != null)
|
||||||
|
"acl_file = ${generate-acl-file acl-data}"}
|
||||||
log_file = ${iprop-log}
|
log_file = ${iprop-log}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +129,8 @@ let
|
|||||||
}
|
}
|
||||||
|
|
||||||
[logging]
|
[logging]
|
||||||
kdc = FILE:${cfg.state-directory}/kerberos.log
|
kdc = FILE:${state-directory}/kerberos.log
|
||||||
default = FILE:${cfg.state-directory}/kerberos.log
|
default = FILE:${state-directory}/kerberos.log
|
||||||
'';
|
'';
|
||||||
|
|
||||||
aclEntry = { principal, ... }: {
|
aclEntry = { principal, ... }: {
|
||||||
@ -95,25 +161,84 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
perms-to-permstring = perms: concatStringsSep "," perms;
|
generate-acl-file = acl-entries: let
|
||||||
|
perms-to-permstring = perms: concatStringsSep "," perms;
|
||||||
generate-acl-file = acl-entries:
|
in
|
||||||
pkgs.writeText "kdc.acl" (concatStringsSep "\n" (mapAttrsToList
|
pkgs.writeText "kdc.acl" (concatStringsSep "\n" (mapAttrsToList
|
||||||
(principal: opts:
|
(principal: opts:
|
||||||
"${principal} ${perms-to-permstring opts.perms}${
|
"${principal} ${perms-to-permstring opts.perms}${
|
||||||
optionalString (opts.target != null) " ${opts.target}"
|
optionalString (opts.target != null) " ${opts.target}" }")
|
||||||
}") acl-entries));
|
acl-entries));
|
||||||
|
|
||||||
kadmin-local = kdc-conf: kadmin-keytab:
|
kadmin-local = kdc-conf:
|
||||||
pkgs.writeShellScriptBin "kadmin.local" ''
|
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 {
|
in {
|
||||||
|
|
||||||
options.fudo.auth.kdc = with types; let
|
options.fudo.auth.kdc = with types; {
|
||||||
default-state-dir = "/var/kerberos";
|
|
||||||
in {
|
|
||||||
enable = mkEnableOption "Fudo KDC";
|
enable = mkEnableOption "Fudo KDC";
|
||||||
|
|
||||||
realm = mkOption {
|
realm = mkOption {
|
||||||
@ -121,16 +246,6 @@ in {
|
|||||||
description = "The realm for which we are the acting KDC.";
|
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 {
|
bind-addresses = mkOption {
|
||||||
type = listOf str;
|
type = listOf str;
|
||||||
description = "A list of IP addresses on which to bind.";
|
description = "A list of IP addresses on which to bind.";
|
||||||
@ -152,38 +267,34 @@ in {
|
|||||||
state-directory = mkOption {
|
state-directory = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
description = "Path at which to store kerberos database.";
|
description = "Path at which to store kerberos database.";
|
||||||
default = default-state-dir;
|
default = "/var/lib/kerberos";
|
||||||
};
|
};
|
||||||
|
|
||||||
master-key-file = mkOption {
|
master-key-file = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
description = "File containing the master key for the realm.";
|
description = ''
|
||||||
default = "${default-state-dir}/master.key";
|
File containing the master key for the realm.
|
||||||
|
|
||||||
|
Must be provided!
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
primary-keytab = mkOption {
|
primary-keytab = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
description = "Location of keytab for kadmind.";
|
description = "Location of host master keytab.";
|
||||||
default = "${default-state-dir}/host.keytab";
|
default = "${state-directory}/host.keytab";
|
||||||
};
|
};
|
||||||
|
|
||||||
kadmin-keytab = mkOption {
|
master-config = mkOption {
|
||||||
type = str;
|
type = nullOr (submodule masterOpts);
|
||||||
description = "Location of keytab for kadmind.";
|
description = "Configuration for the master KDC server.";
|
||||||
default = "${default-state-dir}/kadmind.keytab";
|
default = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
kpasswdd-keytab = mkOption {
|
slave-config = mkOption {
|
||||||
type = str;
|
type = nullOr (submodule slaveOpts);
|
||||||
description = "Location of keytab for kpasswdd.";
|
description = "Configuration for slave KDC servers.";
|
||||||
default = "${default-state-dir}/kpasswdd.keytab";
|
default = null;
|
||||||
};
|
|
||||||
|
|
||||||
kdc-internal-port = mkOption {
|
|
||||||
type = port;
|
|
||||||
description =
|
|
||||||
"Localhost port on which to listen for KDC traffic. Port 88 will be forwarded";
|
|
||||||
default = 4088;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
max-ticket-lifetime = mkOption {
|
max-ticket-lifetime = mkOption {
|
||||||
@ -200,10 +311,24 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
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 = {
|
||||||
users.${cfg.user} = {
|
users.${cfg.user} = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
home = cfg.state-directory;
|
home = state-directory;
|
||||||
group = cfg.group;
|
group = cfg.group;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -217,81 +342,166 @@ in {
|
|||||||
ticket_lifetime = cfg.max-ticket-lifetime;
|
ticket_lifetime = cfg.max-ticket-lifetime;
|
||||||
renew_lifetime = cfg.max-ticket-renewal;
|
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 = ''
|
extraConfig = ''
|
||||||
default = FILE:${cfg.state-directory}/kerberos.log
|
default = FILE:${state-directory}/kerberos.log
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
environment = {
|
environment = {
|
||||||
systemPackages =
|
systemPackages = [ pkgs.heimdalFull (kadmin-local kdc-conf) ];
|
||||||
[ pkgs.heimdalFull (kadmin-local kdc-conf cfg.kadmin-keytab) ];
|
|
||||||
|
|
||||||
etc = {
|
## This shouldn't be necessary...every host gets a krb5.keytab
|
||||||
"krb5.keytab" = {
|
# etc = {
|
||||||
user = "root";
|
# "krb5.keytab" = {
|
||||||
group = "root";
|
# user = "root";
|
||||||
mode = "0400";
|
# group = "root";
|
||||||
source = cfg.primary-keytab;
|
# mode = "0400";
|
||||||
};
|
# source = cfg.primary-keytab;
|
||||||
};
|
# };
|
||||||
|
# };
|
||||||
};
|
};
|
||||||
|
|
||||||
fudo.system = {
|
fudo.system = {
|
||||||
ensure-directories = {
|
ensure-directories = {
|
||||||
"${cfg.state-directory}" = {
|
"${state-directory}" = {
|
||||||
user = cfg.user;
|
user = cfg.user;
|
||||||
group = cfg.group;
|
group = cfg.group;
|
||||||
perms = "0740";
|
perms = "0740";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services = {
|
services = if master-server then {
|
||||||
|
|
||||||
heimdal-kdc = let
|
heimdal-kdc = let
|
||||||
listen-addrs = concatStringsSep " "
|
listen-addrs = concatStringsSep " "
|
||||||
(map (addr: "--addresses=${addr}") cfg.bind-addresses);
|
(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 =
|
command =
|
||||||
"${pkgs.heimdalFull}/libexec/heimdal/kdc -c ${kdc-conf} --ports=88 ${listen-addrs}";
|
"${pkgs.heimdalFull}/libexec/heimdal/kdc -c ${kdc-conf} --ports=88 ${listen-addrs}";
|
||||||
in {
|
in {
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "network.target" ];
|
after = [ "network.target" ];
|
||||||
description =
|
description =
|
||||||
"Heimdal Kerberos Key Distribution Center (ticket server).";
|
"Heimdal Slave Kerberos Key Distribution Center (ticket server).";
|
||||||
execStart = command;
|
execStart = command;
|
||||||
user = cfg.user;
|
user = cfg.user;
|
||||||
group = cfg.group;
|
group = cfg.group;
|
||||||
workingDirectory = cfg.state-directory;
|
workingDirectory = state-directory;
|
||||||
privateNetwork = false;
|
privateNetwork = false;
|
||||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||||
requiredCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
requiredCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||||
};
|
};
|
||||||
|
|
||||||
heimdal-kdc-init = {
|
heimdal-ipropd-slave = {
|
||||||
requires = [ "heimdal-kdc.service" ];
|
#wantedBy = [ "multi-user.target" ];
|
||||||
wantedBy = [ "multi-user.target" ];
|
description = "Receive changes propagated from the KDC master server.";
|
||||||
description = "Initialization script for Heimdal KDC.";
|
path = with pkgs; [ heimdalFull ];
|
||||||
type = "oneshot";
|
execStart = concatStringsSep " " [
|
||||||
execStart = "${initialize-db cfg.realm cfg.user cfg.group kdc-conf
|
"${pkgs.heimdalFull}/libexec/heimdal/ipropd-slave"
|
||||||
cfg.master-key-file database-file cfg.max-ticket-lifetime
|
"--config-file=${kdc-conf}"
|
||||||
cfg.max-ticket-renewal cfg.primary-keytab cfg.kadmin-keytab
|
"--keytab=${cfg.slave-config.ipropd-keytab}"
|
||||||
cfg.kpasswdd-keytab
|
"--realm=${cfg.realm}"
|
||||||
"${config.networking.hostName}.${toLower cfg.realm}"}";
|
"--hostname=${get-fqdn hostname}"
|
||||||
|
"--port=2121"
|
||||||
|
"--verbose"
|
||||||
|
(get-fqdn cfg.slave-config.master-host)
|
||||||
|
];
|
||||||
user = cfg.user;
|
user = cfg.user;
|
||||||
group = cfg.group;
|
group = cfg.group;
|
||||||
protectSystem = "full";
|
workingDirectory = state-directory;
|
||||||
|
privateNetwork = false;
|
||||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||||
workingDirectory = cfg.state-directory;
|
requiredCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# FIXME: is this even allowed to be a link?
|
services.xinetd = mkIf master-server {
|
||||||
# systemd.tmpfiles.rules = mkIf (cfg.primary-keytab != "/etc/krb5.keytab")
|
|
||||||
# [ "L /etc/krb5.keytab - - - - ${cfg.primary-keytab}" ];
|
|
||||||
|
|
||||||
services.xinetd = {
|
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
services = [
|
services = [
|
||||||
@ -301,7 +511,7 @@ in {
|
|||||||
server = "${pkgs.heimdalFull}/libexec/heimdal/kadmind";
|
server = "${pkgs.heimdalFull}/libexec/heimdal/kadmind";
|
||||||
protocol = "tcp";
|
protocol = "tcp";
|
||||||
serverArgs =
|
serverArgs =
|
||||||
"--config-file=${kdc-conf} --keytab=${cfg.kadmin-keytab}";
|
"--config-file=${kdc-conf} --keytab=${cfg.master-config.kadmin-keytab}";
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
name = "kpasswd";
|
name = "kpasswd";
|
||||||
@ -309,14 +519,20 @@ in {
|
|||||||
server = "${pkgs.heimdalFull}/libexec/heimdal/kpasswdd";
|
server = "${pkgs.heimdalFull}/libexec/heimdal/kpasswdd";
|
||||||
protocol = "udp";
|
protocol = "udp";
|
||||||
serverArgs =
|
serverArgs =
|
||||||
"--config-file=${kdc-conf} --keytab=${cfg.kpasswdd-keytab}";
|
"--config-file=${kdc-conf} --keytab=${cfg.master-config.kpasswdd-keytab}";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
networking.firewall = {
|
networking = {
|
||||||
allowedTCPPorts = [ 88 749 ];
|
firewall = {
|
||||||
allowedUDPPorts = [ 88 464 ];
|
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 {
|
config = mkIf cfg.enable {
|
||||||
systemd.tmpfiles.rules = [
|
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 = {
|
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
|
decrypt-script = { secret-name, source-file, target-host, target-file
|
||||||
, host-master-key, user, group, permissions }:
|
, host-master-key, user, group, permissions }:
|
||||||
pkgs.writeShellScript
|
pkgs.writeShellScript "decrypt-fudo-secret-${target-host}-${secret-name}.sh" ''
|
||||||
"decrypt-fudo-secret-${target-host}-${secret-name}.sh" ''
|
rm -f ${target-file}
|
||||||
rm -rf ${target-file}
|
touch ${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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chown ${user}:${group} ${target-file}
|
chown ${user}:${group} ${target-file}
|
||||||
chmod ${permissions} ${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:
|
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}.";
|
description = "decrypt secret ${secret-name} for ${target-host}.";
|
||||||
wantedBy = [ "default.target" ];
|
wantedBy = [ "default.target" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
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
|
ExecStart = let
|
||||||
host-master-key = config.fudo.hosts.${target-host}.master-key;
|
host-master-key = config.fudo.hosts.${target-host}.master-key;
|
||||||
in decrypt-script {
|
in decrypt-script {
|
||||||
@ -82,6 +78,12 @@ let
|
|||||||
description = "Permissions to set on the target file.";
|
description = "Permissions to set on the target file.";
|
||||||
default = "0400";
|
default = "0400";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
metadata = mkOption {
|
||||||
|
type = attrsOf anything;
|
||||||
|
description = "Arbitrary metadata associated with this secret.";
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -166,17 +168,33 @@ in {
|
|||||||
|
|
||||||
systemd = let
|
systemd = let
|
||||||
hostname = config.instance.hostname;
|
hostname = config.instance.hostname;
|
||||||
|
|
||||||
host-secrets = if (hasAttr hostname cfg.host-secrets) then
|
host-secrets = if (hasAttr hostname cfg.host-secrets) then
|
||||||
cfg.host-secrets.${hostname}
|
cfg.host-secrets.${hostname}
|
||||||
else
|
else
|
||||||
{ };
|
{ };
|
||||||
|
|
||||||
host-secret-services = mapAttrs' (secret: secretOpts:
|
host-secret-services = mapAttrs' (secret: secretOpts:
|
||||||
(nameValuePair "fudo-secret-${hostname}-${secret}"
|
(nameValuePair "fudo-secret-${hostname}-${secret}"
|
||||||
(secret-service hostname secret secretOpts))) host-secrets;
|
(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 {
|
in {
|
||||||
|
tmpfiles.rules = host-secret-paths ++ build-secret-paths;
|
||||||
|
|
||||||
services = host-secret-services // {
|
services = host-secret-services // {
|
||||||
fudo-secrets-watcher = {
|
fudo-secrets-watcher = mkIf (length cfg.secret-paths > 0) {
|
||||||
wantedBy = [ "default.target" ];
|
wantedBy = [ "default.target" ];
|
||||||
description =
|
description =
|
||||||
"Ensure access for group ${cfg.secret-group} to fudo secret paths.";
|
"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" ];
|
wantedBy = [ "default.target" ];
|
||||||
description = "Watch fudo secret paths, and correct perms on changes.";
|
description = "Watch fudo secret paths, and correct perms on changes.";
|
||||||
pathConfig = {
|
pathConfig = {
|
||||||
@ -198,9 +216,6 @@ in {
|
|||||||
Unit = "fudo-secrets-watcher.service";
|
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;
|
let user-list = attrNames users;
|
||||||
in filter (username: list-includes user-list username) group-members;
|
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;
|
hostname = config.instance.hostname;
|
||||||
host-cfg = config.fudo.hosts.${hostname};
|
host-cfg = config.fudo.hosts.${hostname};
|
||||||
|
|
||||||
@ -145,17 +117,10 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Group home directories have to exist, otherwise users can't log in
|
# Group home directories have to exist, otherwise users can't log in
|
||||||
systemd.services = let
|
systemd.tmpfiles.rules = let
|
||||||
ensure-group-directories = group:
|
|
||||||
nameValuePair "ensure-group-directories-${group}" {
|
|
||||||
script = ensure-group-dirs-script group [ "/home/${group}" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
requires = [ "local-fs.target" ];
|
|
||||||
after = [ "remote-fs.target" ];
|
|
||||||
};
|
|
||||||
groups-with-members = attrNames
|
groups-with-members = attrNames
|
||||||
(filterAttrs (group: groupOpts: (length groupOpts.members) > 0)
|
(filterAttrs (group: groupOpts: (length groupOpts.members) > 0)
|
||||||
sys.local-groups);
|
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 {
|
in {
|
||||||
|
inherit host-ipv4 host-ipv6 host-ips;
|
||||||
|
|
||||||
generate-mac-address = hostname: interface: let
|
generate-mac-address = hostname: interface: let
|
||||||
pkg = generate-mac-address hostname interface;
|
pkg = generate-mac-address hostname interface;
|
||||||
in builtins.readFile "${pkg}";
|
in removeSuffix "\n" (builtins.readFile "${pkg}");
|
||||||
}
|
}
|
||||||
|
@ -79,12 +79,6 @@ rec {
|
|||||||
default = [ ];
|
default = [ ];
|
||||||
};
|
};
|
||||||
|
|
||||||
# home-manager-generator = mkOption {
|
|
||||||
# type = nullOr (functionTo attrs);
|
|
||||||
# description = "Home Manager configuration for the given user.";
|
|
||||||
# default = null;
|
|
||||||
# };
|
|
||||||
|
|
||||||
home-directory = mkOption {
|
home-directory = mkOption {
|
||||||
type = nullOr str;
|
type = nullOr str;
|
||||||
description = "Default home directory for the given user.";
|
description = "Default home directory for the given user.";
|
||||||
|
Loading…
Reference in New Issue
Block a user