Add text library function (format-json-file)
This commit is contained in:
parent
f49233c0ac
commit
125d2d3d57
|
@ -0,0 +1,272 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.adguard-dns-proxy;
|
||||
|
||||
hostname = config.instance.hostname;
|
||||
|
||||
get-basename = filename:
|
||||
head (builtins.match "^[a-zA-Z0-9]+-(.+)$" (baseNameOf filename));
|
||||
|
||||
format-json-file = filename: pkgs.stdenv.mkDerivation {
|
||||
name = "formatted-${get-basename filename}";
|
||||
phases = [ "installPhase" ];
|
||||
buildInputs = with pkgs; [ python ];
|
||||
installPhase = "python -mjson.tool ${filename} > $out";
|
||||
};
|
||||
|
||||
|
||||
admin-passwd-file =
|
||||
pkgs.lib.passwd.stablerandom-passwd-file
|
||||
"adguard-dns-proxy-admin"
|
||||
config.instance.build-seed;
|
||||
|
||||
filterOpts = {
|
||||
options = with types; {
|
||||
enable = mkOption {
|
||||
type = bool;
|
||||
description = "Enable this filter on DNS traffic.";
|
||||
default = true;
|
||||
};
|
||||
name = mkOption {
|
||||
type = str;
|
||||
description = "Name of this filter.";
|
||||
};
|
||||
url = mkOption {
|
||||
type = str;
|
||||
description = "URL to the filter itself.";
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
generate-config = { dns,
|
||||
http,
|
||||
filters,
|
||||
verbose,
|
||||
upstream-dns,
|
||||
bootstrap-dns,
|
||||
blocked-hosts,
|
||||
enable-dnssec,
|
||||
local-domain-name,
|
||||
... }: {
|
||||
bind_host = http.listen-ip;
|
||||
bind_port = http.listen-port;
|
||||
users = [
|
||||
{
|
||||
name = "admin";
|
||||
password = pkgs.lib.passwd.bcrypt-passwd
|
||||
"adguard-dns-proxy-admin"
|
||||
admin-passwd-file;
|
||||
}
|
||||
];
|
||||
auth_attempts = 5;
|
||||
block_auth_min = 30;
|
||||
web_session_ttl = 720;
|
||||
dns = {
|
||||
bind_hosts = dns.listen-ips;
|
||||
port = dns.listen-port;
|
||||
upstream_dns = upstream-dns;
|
||||
bootstrap_dns = bootstrap-dns;
|
||||
blocking_mode = "default";
|
||||
blocked_hosts = blocked-hosts;
|
||||
enable_dnssec = enable-dnssec;
|
||||
local_domain_name = local-domain-name;
|
||||
};
|
||||
tls.enabled = false;
|
||||
filters = imap1 (i: filter: {
|
||||
enabled = true;
|
||||
name = filter.name;
|
||||
url = filter.url;
|
||||
}) filters;
|
||||
dhcp.enabled = false;
|
||||
clients = [];
|
||||
verbose = verbose;
|
||||
schema_version = 10;
|
||||
};
|
||||
|
||||
generate-config-file = opts:
|
||||
format-json-file (pkgs.writeText "adguard-dns-proxy-config.yaml"
|
||||
(builtins.toJSON (generate-config opts)));
|
||||
|
||||
in {
|
||||
options.fudo.adguard-dns-proxy = with types; {
|
||||
enable = mkEnableOption "Enable AdGuardHome DNS proxy.";
|
||||
|
||||
dns = {
|
||||
listen-ips = mkOption {
|
||||
type = listOf str;
|
||||
description = "IP on which to listen for incoming DNS requests.";
|
||||
default = [ "0.0.0.0" ];
|
||||
};
|
||||
|
||||
listen-port = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to listen for DNS queries.";
|
||||
default = 53;
|
||||
};
|
||||
};
|
||||
|
||||
http = {
|
||||
listen-ip = mkOption {
|
||||
type = str;
|
||||
description = "IP on which to listen for incoming HTTP requests.";
|
||||
};
|
||||
|
||||
listen-port = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to listen for incoming HTTP queries.";
|
||||
default = 8053;
|
||||
};
|
||||
};
|
||||
|
||||
filters = mkOption {
|
||||
type = listOf (submodule filterOpts);
|
||||
description = "List of filters to apply to DNS traffic.";
|
||||
default = [
|
||||
{
|
||||
name = "AdGuard DNS filter";
|
||||
url = "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt";
|
||||
}
|
||||
{
|
||||
name = "AdAway Default Blocklist";
|
||||
url = "https://adaway.org/hosts.txt";
|
||||
}
|
||||
{
|
||||
name = "MalwareDomainList.com Hosts List";
|
||||
url = "https://www.malwaredomainlist.com/hostslist/hosts.txt";
|
||||
}
|
||||
{
|
||||
name = "OISD.NL Blocklist";
|
||||
url = "https://abp.oisd.nl/";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
blocked-hosts = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of hosts to explicitly block.";
|
||||
default = [
|
||||
"version.bind"
|
||||
"id.server"
|
||||
"hostname.bind"
|
||||
];
|
||||
};
|
||||
|
||||
enable-dnssec = mkOption {
|
||||
type = bool;
|
||||
description = "Enable DNSSEC";
|
||||
default = true;
|
||||
};
|
||||
|
||||
upstream-dns = mkOption {
|
||||
type = listOf str;
|
||||
description = ''
|
||||
List of upstream DNS services to use.
|
||||
|
||||
See https://github.com/AdguardTeam/dnsproxy for correct formatting.
|
||||
'';
|
||||
default = [
|
||||
"https://1.1.1.1/dns-query"
|
||||
"https://1.0.0.1/dns-query"
|
||||
# These 11 addrs send the network, so the response can prefer closer answers
|
||||
"https://9.9.9.11/dns-query"
|
||||
"https://149.112.112.11/dns-query"
|
||||
"https://2620:fe::11/dns-query"
|
||||
"https://2620:fe::fe:11/dns-query"
|
||||
];
|
||||
};
|
||||
|
||||
bootstrap-dns = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of DNS servers used to bootstrap DNS-over-HTTPS.";
|
||||
default = [
|
||||
"1.1.1.1"
|
||||
"1.0.0.1"
|
||||
"9.9.9.9"
|
||||
"149.112.112.112"
|
||||
"2620:fe::10"
|
||||
"2620:fe::fe:10"
|
||||
];
|
||||
};
|
||||
|
||||
allowed-networks = mkOption {
|
||||
type = nullOr (listOf str);
|
||||
description = "Optional list of networks with which this job may communicate.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User as which this job will run.";
|
||||
default = "adguard-dns-proxy";
|
||||
};
|
||||
|
||||
local-domain-name = mkOption {
|
||||
type = str;
|
||||
description = "Local domain name.";
|
||||
};
|
||||
|
||||
verbose = mkEnableOption "Keep verbose logs.";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (let
|
||||
upgrade-perms = cfg.dns.listen-port <= 1024 || cfg.http.listen-port <= 1024;
|
||||
in {
|
||||
users = mkIf upgrade-perms {
|
||||
users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = cfg.user;
|
||||
};
|
||||
|
||||
groups.${cfg.user} = {
|
||||
members = [ cfg.user ];
|
||||
};
|
||||
};
|
||||
|
||||
fudo = {
|
||||
secrets.host-secrets.${hostname} = {
|
||||
adguard-dns-proxy-admin-password = {
|
||||
source-file = admin-passwd-file;
|
||||
target-file = "/run/adguard-dns-proxy/admin.passwd";
|
||||
user = "root";
|
||||
};
|
||||
};
|
||||
|
||||
system.services.adguard-dns-proxy = let
|
||||
cfg-path = "/run/adguard-dns-proxy/config.yaml";
|
||||
in {
|
||||
description = "DNS Proxy for ad filtering and DNS-over-HTTPS lookups.";
|
||||
wantedBy = [ "default.target" ];
|
||||
after = [ "syslog.target" ];
|
||||
requires = [ "network.target" ];
|
||||
privateNetwork = false;
|
||||
requiredCapabilities = optional upgrade-perms "CAP_NET_BIND_SERVICE";
|
||||
restartWhen = "always";
|
||||
addressFamilies = null;
|
||||
networkWhitelist = cfg.allowed-networks;
|
||||
user = mkIf upgrade-perms cfg.user;
|
||||
runtimeDirectory = "adguard-dns-proxy";
|
||||
stateDirectory = "adguard-dns-proxy";
|
||||
preStart = ''
|
||||
cp ${generate-config-file cfg} ${cfg-path};
|
||||
chown $USER ${cfg-path};
|
||||
chmod u+w ${cfg-path};
|
||||
'';
|
||||
|
||||
execStart = let
|
||||
args = [
|
||||
"--no-check-update"
|
||||
"--work-dir /var/lib/adguard-dns-proxy"
|
||||
"--pidfile /run/adguard-dns-proxy/adguard-dns-proxy.pid"
|
||||
"--host ${cfg.http.listen-ip}"
|
||||
"--port ${toString cfg.http.listen-port}"
|
||||
"--config ${cfg-path}"
|
||||
];
|
||||
arg-string = concatStringsSep " " args;
|
||||
in "${pkgs.adguardhome}/bin/adguardhome ${arg-string}";
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
{ config, lib, pkgs, ... } @ toplevel:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.backplane.dns;
|
||||
|
||||
backplane-dns-home = "${config.instance.service-home}/backplane-dns";
|
||||
|
||||
in {
|
||||
options.fudo.backplane.dns = with types; {
|
||||
enable = mkEnableOption "Enable DNS backplane service.";
|
||||
|
||||
required-services = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of systemd units on which the DNS backplane job depends.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
backplane-server = mkOption {
|
||||
type = str;
|
||||
description = "Backplane XMPP server hostname.";
|
||||
default = toplevel.config.fudo.backplane.backplane-hostname;
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User as which to run the backplane dns service.";
|
||||
default = "backplane-dns";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = str;
|
||||
description = "Group as which to run the backplane dns service.";
|
||||
default = "backplane-dns";
|
||||
};
|
||||
|
||||
database = {
|
||||
host = mkOption {
|
||||
type = str;
|
||||
description = "Hostname or IP of the PostgreSQL server.";
|
||||
};
|
||||
|
||||
database = mkOption {
|
||||
type = str;
|
||||
description = "Database to use for DNS backplane.";
|
||||
default = "backplane_dns";
|
||||
};
|
||||
|
||||
username = mkOption {
|
||||
type = str;
|
||||
description = "Database user for DNS backplane.";
|
||||
default = "backplane_dns";
|
||||
};
|
||||
|
||||
password-file = mkOption {
|
||||
type = str;
|
||||
description = "File containing password for DNS backplane database user.";
|
||||
};
|
||||
|
||||
ssl-mode = mkOption {
|
||||
type = enum ["no" "yes" "full" "try" "require"];
|
||||
description = "SSL connection mode.";
|
||||
default = "require";
|
||||
};
|
||||
};
|
||||
|
||||
backplane-role = {
|
||||
role = mkOption {
|
||||
type = str;
|
||||
description = "Backplane XMPP role name for DNS backplane job.";
|
||||
default = "service-dns";
|
||||
};
|
||||
|
||||
password-file = mkOption {
|
||||
type = str;
|
||||
description = "Password file for backplane XMPP for DNS backplane job.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users = {
|
||||
users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = backplane-dns-home;
|
||||
createHome = true;
|
||||
};
|
||||
groups.${cfg.group} = {
|
||||
members = [ cfg.user ];
|
||||
};
|
||||
};
|
||||
|
||||
fudo.system.services = {
|
||||
backplane-dns = {
|
||||
description = "Fudo DNS Backplane Server";
|
||||
restartIfChanged = true;
|
||||
path = with pkgs; [ backplane-dns-server ];
|
||||
execStart = "${pkgs.backplane-dns-server}/bin/launch-backplane-dns.sh";
|
||||
#pidFile = "/run/backplane/dns.pid";
|
||||
partOf = [ "backplane-dns.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requires = cfg.required-services;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
memoryDenyWriteExecute = false; # Needed becuz Lisp
|
||||
readWritePaths = [ backplane-dns-home ];
|
||||
privateNetwork = false;
|
||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
environment = {
|
||||
FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane-server;
|
||||
FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane-role.role;
|
||||
FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane-role.password-file;
|
||||
|
||||
FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.database.host;
|
||||
FUDO_DNS_BACKPLANE_DATABASE_NAME = cfg.database.database;
|
||||
FUDO_DNS_BACKPLANE_DATABASE_USERNAME =
|
||||
cfg.database.username;
|
||||
FUDO_DNS_BACKPLANE_DATABASE_PASSWORD_FILE =
|
||||
cfg.database.password-file;
|
||||
FUDO_DNS_BACKPLANE_DATABASE_USE_SSL = cfg.database.ssl-mode;
|
||||
|
||||
CL_SOURCE_REGISTRY =
|
||||
pkgs.lib.lisp.lisp-source-registry pkgs.backplane-dns-server;
|
||||
|
||||
LD_LIBRARY_PATH = "${pkgs.openssl.out}/lib";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.backplane;
|
||||
hostname = config.instance.hostname;
|
||||
|
||||
host-secrets = config.fudo.secrets.host-secrets.${hostname};
|
||||
|
||||
generate-auth-file = name: files: let
|
||||
make-entry = name: passwd-file:
|
||||
''("${name}" . "${readFile passwd-file}")'';
|
||||
entries = mapAttrsToList make-entry files;
|
||||
content = concatStringsSep "\n" entries;
|
||||
in pkgs.writeText "${name}-backplane-auth.scm" "'(${content})";
|
||||
|
||||
host-auth-file = generate-auth-file "host"
|
||||
(mapAttrs (hostname: hostOpts: hostOpts.password-file)
|
||||
cfg.client-hosts);
|
||||
|
||||
service-auth-file = generate-auth-file "service"
|
||||
(mapAttrs (service: serviceOpts: serviceOpts.password-file)
|
||||
cfg.services);
|
||||
|
||||
clientHostOpts = { name, ... }: {
|
||||
options = with types; {
|
||||
password-file = mkOption {
|
||||
type = path;
|
||||
description =
|
||||
"Location (on the build host) of the file containing the host password.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
serviceOpts = { name, ... }: {
|
||||
options = with types; {
|
||||
password-file = mkOption {
|
||||
type = path;
|
||||
description =
|
||||
"Location (on the build host) of the file containing the service password.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
options.fudo.backplane = with types; {
|
||||
enable = mkEnableOption "Enable backplane XMPP server.";
|
||||
|
||||
client-hosts = mkOption {
|
||||
type = attrsOf (submodule clientHostOpts);
|
||||
description = "List of backplane client options.";
|
||||
default = {};
|
||||
};
|
||||
|
||||
services = mkOption {
|
||||
type = attrsOf (submodule serviceOpts);
|
||||
description = "List of backplane service options.";
|
||||
default = {};
|
||||
};
|
||||
|
||||
backplane-hostname = mkOption {
|
||||
type = str;
|
||||
description = "Hostname of the backplane XMPP server.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
fudo = {
|
||||
secrets.host-secrets.${hostname} = {
|
||||
backplane-host-auth = {
|
||||
source-file = generate-auth-file "host"
|
||||
(mapAttrs (hostname: hostOpts: hostOpts.password-file)
|
||||
cfg.client-hosts);
|
||||
target-file = "/run/backplane/host-passwords.scm";
|
||||
user = config.fudo.jabber.user;
|
||||
};
|
||||
backplane-service-auth = {
|
||||
source-file = generate-auth-file "service"
|
||||
(mapAttrs (service: serviceOpts: serviceOpts.password-file)
|
||||
cfg.services);
|
||||
target-file = "/run/backplane/service-passwords.scm";
|
||||
user = config.fudo.jabber.user;
|
||||
};
|
||||
};
|
||||
|
||||
jabber = {
|
||||
environment = {
|
||||
FUDO_HOST_PASSWD_FILE =
|
||||
host-secrets.backplane-host-auth.target-file;
|
||||
FUDO_SERVICE_PASSWD_FILE =
|
||||
host-secrets.backplane-service-auth.target-file;
|
||||
};
|
||||
|
||||
sites.${cfg.backplane-hostname} = {
|
||||
hostname = cfg.backplane-hostname;
|
||||
|
||||
site-config = {
|
||||
auth_method = "external";
|
||||
extauth_program =
|
||||
"${pkgs.guile}/bin/guile -s ${pkgs.backplane-auth}/backplane-auth.scm";
|
||||
extauth_pool_size = 3;
|
||||
auth_use_cache = true;
|
||||
|
||||
modules = {
|
||||
mod_adhoc = {};
|
||||
mod_caps = {};
|
||||
mod_carboncopy = {};
|
||||
mod_client_state = {};
|
||||
mod_configure = {};
|
||||
mod_disco = {};
|
||||
mod_fail2ban = {};
|
||||
mod_last = {};
|
||||
mod_offline.access_max_user_messages = 5000;
|
||||
mod_ping = {};
|
||||
mod_pubsub = {
|
||||
access_createnode = "pubsub_createnode";
|
||||
ignore_pep_from_offline = true;
|
||||
last_item_cache = false;
|
||||
plugins = [
|
||||
"flat"
|
||||
"pep"
|
||||
];
|
||||
};
|
||||
mod_roster = {};
|
||||
mod_stream_mgmt = {};
|
||||
mod_time = {};
|
||||
mod_version = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
{ config, lib, pkgs, ... } @ toplevel:
|
||||
|
||||
# NOTE: To get DNS records:
|
||||
# pdnsutil --config-dir=... show-zone <domain>
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.powerdns;
|
||||
|
||||
hostname = config.instance.hostname;
|
||||
|
||||
runtime-dir = "/run/powerdns/conf";
|
||||
|
||||
target-gpgsql-config = "${runtime-dir}/pdns.local.gpgsql.conf";
|
||||
|
||||
gpgsql-template = pkgs.writeText "pdns.gpgsql.conf.template" ''
|
||||
launch+=gpgsql
|
||||
gpgsql-host=${cfg.database.host}
|
||||
gpgsql-dbname=${cfg.database.database}
|
||||
gpgsql-user=${cfg.database.user}
|
||||
gpgsql-password=__PASSWORD__
|
||||
gpgsql-dnssec=${if cfg.enable-dnssec then "yes" else "no"}
|
||||
gpgsql-extra-connection-parameters=sslmode=require
|
||||
'';
|
||||
|
||||
pdns-config-dir = pkgs.writeTextDir "pdns.conf" ''
|
||||
local-address=${concatStringsSep ", " cfg.listen-v4-addresses}
|
||||
local-ipv6=${concatStringsSep ", " cfg.listen-v6-addresses}
|
||||
local-port=${toString cfg.port}
|
||||
launch=
|
||||
include-dir=${runtime-dir}
|
||||
'';
|
||||
|
||||
make-pgpass-file = user: target-file: let
|
||||
db = cfg.database;
|
||||
in pkgs.writeShellScript "genenrate-pgpass-file.sh" ''
|
||||
touch ${target-file}
|
||||
chown ${user} ${target-file}
|
||||
chmod 700 ${target-file}
|
||||
PASSWORD=$(cat ${db.password-file})
|
||||
echo "${db.host}:${toString db.port}:${db.database}:${db.user}:__PASSWORD__" | sed "s/__PASSWORD__/$PASSWORD/" > ${target-file}
|
||||
'';
|
||||
|
||||
mkRecord = name: type: content: {
|
||||
inherit name type content;
|
||||
};
|
||||
|
||||
initialize-domain-sql = domain: let
|
||||
domain-name = domain.domain;
|
||||
host-ip = pkgs.lib.network.host-ipv4 config hostname;
|
||||
ipv6-net = net: (builtins.match ":" net) != null;
|
||||
ipv4-net = net: !(ipv6-net net);
|
||||
domain-records = [
|
||||
(mkRecord domain-name "SOA" "ns1.${domain-name} hostmaster.${domain-name} ${toString config.instance.build-timestamp} 10800 3600 1209600 3600")
|
||||
(mkRecord "_dmark.${domain-name}" "TXT" ''"v=DMARC1; p=reject; rua=mailto:${domain.admin}; ruf=mailto:${domain.admin}; fo=1;"'')
|
||||
(mkRecord domain-name "NS" "ns1.${domain-name}")
|
||||
(mkRecord domain-name "TXT" (let
|
||||
networks = config.instance.local-networks;
|
||||
v4-nets = map (net: "ip4:${net}") (filter ipv4-net networks);
|
||||
v6-nets = map (net: "ip6:${net}") (filter ipv6-net networks);
|
||||
networks-string = concatStringsSep " " (v4-nets ++ v6-nets);
|
||||
in ''"v=spf1 mx ${networks-string} -all"''))
|
||||
(mkRecord "ns1.${domain-name}" "A" host-ip)
|
||||
] ++
|
||||
(optional (domain.gssapi-realm != null)
|
||||
(mkRecord "_kerberos.${domain-name}" "TXT" ''"domain.gssapi-realm"'')) ++
|
||||
(mapAttrsToList (alias: target: mkRecord alias "CNAME" target)
|
||||
domain.aliases);
|
||||
records-strings = map (record:
|
||||
"INSERT INTO records (domain_id, name, type, content) SELECT id, '${record.name}', '${record.type}', '${record.content}' FROM domains WHERE name='${domain-name}';")
|
||||
domain-records;
|
||||
in pkgs.writeText "initialize-${domain-name}.sql" ''
|
||||
INSERT INTO domains (name, master, type, notified_serial) VALUES ('${domain-name}', '${host-ip}', 'MASTER', '${toString config.instance.build-timestamp}');
|
||||
${concatStringsSep "\n" records-strings}
|
||||
'';
|
||||
|
||||
initialize-domain-script = domain: let
|
||||
domain-name = domain.domain;
|
||||
in pkgs.writeShellScript "initialize-${domain-name}.sh" ''
|
||||
if [ "$( psql -tAc "SELECT id FROM domains WHERE name='${domain-name}'" )" ]; then
|
||||
logger "${domain-name} already initialized, skipping"
|
||||
exit 0
|
||||
else
|
||||
logger "initializing ${domain-name} in powerdns database"
|
||||
psql -f ${initialize-domain-sql domain}
|
||||
fi
|
||||
'';
|
||||
|
||||
domainOpts = { name, ... }: {
|
||||
options = with types; {
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
description = "Domain name.";
|
||||
default = name;
|
||||
};
|
||||
|
||||
admin = mkOption {
|
||||
type = str;
|
||||
description = "Administrator email.";
|
||||
default = "admin@${name}";
|
||||
};
|
||||
|
||||
aliases = mkOption {
|
||||
type = attrsOf str;
|
||||
description = "Map of alias to authoritative hostname for this domain.";
|
||||
default = {};
|
||||
};
|
||||
|
||||
gssapi-realm = mkOption {
|
||||
type = nullOr str;
|
||||
description = "GSSAPI realm of this domain.";
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
options.fudo.powerdns = with types; {
|
||||
enable = mkEnableOption "Enable PowerDNS nameserver.";
|
||||
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to listen for DNS requests.";
|
||||
default = 53;
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User as which to run PowerDNS server.";
|
||||
default = "powerdns";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = str;
|
||||
description = "Group as which to run PowerDNS server.";
|
||||
default = "powerdns";
|
||||
};
|
||||
|
||||
listen-v4-addresses = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of IPv4 addresses on which to listen.";
|
||||
};
|
||||
|
||||
listen-v6-addresses = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of IPv6 addresses on which to listen.";
|
||||
default = [];
|
||||
};
|
||||
|
||||
domains = mkOption {
|
||||
type = attrsOf (submodule domainOpts);
|
||||
description = "Domains to be served by this DNS server.";
|
||||
};
|
||||
|
||||
enable-dnssec = mkOption {
|
||||
type = bool;
|
||||
description = "Enable DNSSEC for this domain.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
database = {
|
||||
host = mkOption {
|
||||
type = str;
|
||||
description = "Hostname or IP of the PostgreSQL server.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = "Port of the PostgreSQL server.";
|
||||
default = 5432;
|
||||
};
|
||||
|
||||
database = mkOption {
|
||||
type = str;
|
||||
description = "Database to use for DNS backplane.";
|
||||
default = "backplane_dns";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "Database user for DNS backplane.";
|
||||
default = "backplane_dns";
|
||||
};
|
||||
|
||||
password-file = mkOption {
|
||||
type = str;
|
||||
description = "File containing password for database user.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
networking.firewall.allowedTCPPorts = [ cfg.port ];
|
||||
|
||||
users = {
|
||||
users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
};
|
||||
groups.${cfg.group}.members = [ cfg.user ];
|
||||
};
|
||||
|
||||
fudo.system.services.powerdns-config-generator = {
|
||||
description = "Generate PostgreSQL config for backplane DNS server.";
|
||||
type = "oneshot";
|
||||
restartIfChanged = true;
|
||||
readWritePaths = [ runtime-dir ];
|
||||
user = cfg.user;
|
||||
execStart = let
|
||||
script = pkgs.writeShellScript "generate-powerdns-config.sh" ''
|
||||
TARGET=${target-gpgsql-config}
|
||||
touch $TARGET
|
||||
chown ${cfg.user}:${cfg.group} $TARGET
|
||||
chmod 0700 $TARGET
|
||||
PASSWORD=$( cat ${cfg.database.password-file} | tr -d '\n')
|
||||
sed -e 's/__PASSWORD__/$PASSWORD/' ${gpgsql-template} > $TARGET
|
||||
'';
|
||||
in "${script}";
|
||||
};
|
||||
|
||||
systemd = let
|
||||
pgpass-file = "${runtime-dir}/pgpass";
|
||||
|
||||
initialize-jobs = mapAttrs' (_: domain: let
|
||||
domain-name = domain.domain;
|
||||
in nameValuePair "powerdns-initialize-${domain-name}" {
|
||||
description = "Initialize the ${domain-name} domain";
|
||||
requires = [ "powerdns-initialize-db.service" "powerdns-generate-pgpass.service" ];
|
||||
after = [ "powerdns-initialize-db.service" "powerdns-generate-pgpass.service" ];
|
||||
requiredBy = [ "powerdns.service" ];
|
||||
wantedBy = [ "powerdns.service" ];
|
||||
before = [ "powerdns.service" ];
|
||||
environment = {
|
||||
PGHOST = cfg.database.host;
|
||||
PGUSER = cfg.database.user;
|
||||
PGDATABASE = cfg.database.database;
|
||||
PGPORT = toString cfg.database.port;
|
||||
PGSSLMODE= "require";
|
||||
PGPASSFILE = pgpass-file;
|
||||
};
|
||||
path = with pkgs; [ postgresql util-linux ];
|
||||
serviceConfig = {
|
||||
ExecStart = initialize-domain-script domain;
|
||||
};
|
||||
}) cfg.domains;
|
||||
in {
|
||||
tmpfiles.rules = [
|
||||
"d ${runtime-dir} 0750 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
services = initialize-jobs // {
|
||||
powerdns-generate-pgpass = {
|
||||
description = "Create pgpass file required for database init.";
|
||||
serviceConfig = {
|
||||
ExecStart = make-pgpass-file cfg.user "${runtime-dir}/pgpass";
|
||||
};
|
||||
};
|
||||
|
||||
powerdns-initialize-db = {
|
||||
description = "Initialize the powerdns database.";
|
||||
requiredBy = [ "powerdns.service" ];
|
||||
before = [ "powerdns.service" ];
|
||||
requires = [ "powerdns-generate-pgpass.service" ];
|
||||
after = [ "powerdns-generate-pgpass.service" ];
|
||||
path = with pkgs; [ postgresql util-linux ];
|
||||
environment = {
|
||||
PGHOST = cfg.database.host;
|
||||
PGUSER = cfg.database.user;
|
||||
PGDATABASE = cfg.database.database;
|
||||
PGPORT = toString cfg.database.port;
|
||||
PGSSLMODE= "require";
|
||||
PGPASSFILE = pgpass-file;
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStart = pkgs.writeShellScript "powerdns-initialize-db.sh" ''
|
||||
if [ "$( psql -tAc "SELECT to_regclass('public.domains')" )" ]; then
|
||||
logger "database initialized, skipping"
|
||||
else
|
||||
logger "initializing powerdns database"
|
||||
psql -f ${pkgs.powerdns}/share/doc/pdns/schema.pgsql.sql
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
powerdns = {
|
||||
description = "PowerDNS nameserver.";
|
||||
requires = [ "powerdns-config-generator.service" ];
|
||||
after = [
|
||||
"network.target"
|
||||
"powerdns-config-generator.service"
|
||||
];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = with pkgs; [ powerdns postgresql util-linux ];
|
||||
serviceConfig = {
|
||||
ExecStartPre = pkgs.writeShellScript "powerdns-init-config.sh" ''
|
||||
TARGET=${target-gpgsql-config}
|
||||
touch $TARGET
|
||||
chown ${cfg.user}:${cfg.group} $TARGET
|
||||
chmod 0700 $TARGET
|
||||
PASSWORD=$( cat ${cfg.database.password-file} | tr -d '\n')
|
||||
sed -e "s/__PASSWORD__/$PASSWORD/" ${gpgsql-template} > $TARGET
|
||||
'';
|
||||
ExecStart = pkgs.writeShellScript "launch-powerdns.sh" (concatStringsSep " " [
|
||||
"${pkgs.powerdns}/bin/pdns_server"
|
||||
"--setuid=${cfg.user}"
|
||||
"--setgid=${cfg.group}"
|
||||
"--chroot=${runtime-dir}"
|
||||
"--daemon=no"
|
||||
"--guardian=no"
|
||||
"--disable-syslog"
|
||||
"--write-pid=no"
|
||||
"--config-dir=${pdns-config-dir}"
|
||||
]);
|
||||
ExecStartPost = pkgs.writeShellScript "powerdns-secure-zones.sh"
|
||||
(concatStringsSep "\n"
|
||||
(mapAttrsToList
|
||||
(_: domain: ''
|
||||
DNSINFO=$(${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} show-zone ${domain.domain})
|
||||
if [[ "x$DNSINFO" =~ "xNo such zone in the database" ]]; then
|
||||
logger "zone ${domain.domain} does not exist in powerdns database"
|
||||
elif [[ "x$DNSINFO" =~ "xZone is not actively secured" ]]; then
|
||||
logger "securing zone ${domain.domain} in powerdns database"
|
||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
|
||||
elif [[ "x$DNSINFO" =~ "xNo keys for zone" ]]; then
|
||||
logger "securing zone ${domain.domain} in powerdns database"
|
||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
|
||||
else
|
||||
logger "not securing zone ${domain.domain} in powerdns database"
|
||||
fi
|
||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} rectify-zone ${domain.domain}
|
||||
'')
|
||||
cfg.domains));
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
let
|
||||
get-basename = filename:
|
||||
head (builtins.match "^[a-zA-Z0-9]+-(.+)$" (baseNameOf filename));
|
||||
|
||||
format-json-file = filename: pkgs.stdenv.mkDerivation {
|
||||
name = "formatted-${get-basename filename}";
|
||||
phases = [ "installPhase" ];
|
||||
buildInputs = with pkgs; [ python ];
|
||||
installPhase = "python -mjson.tool ${filename} > $out";
|
||||
};
|
||||
|
||||
in {
|
||||
inherit format-json-file;
|
||||
}
|
Loading…
Reference in New Issue