From ef2436991ae7250d3abd864c641d5ce53396b2d3 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 22 Jul 2020 11:26:40 -0700 Subject: [PATCH 01/15] Don't enable ssh auth agent --- defaults.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/defaults.nix b/defaults.nix index 8ddfe0f..5e07dec 100644 --- a/defaults.nix +++ b/defaults.nix @@ -163,7 +163,6 @@ ''; security.pam = { - enableSSHAgentAuth = true; # TODO: add yubico? services.sshd = { # This should only ask for a code if ~/.google_authenticator exists, but it asks anyway. From 65925e218392994b1b06079006b14cce05685453 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 23 Jul 2020 22:39:31 -0700 Subject: [PATCH 02/15] switch atom to simple gui with stumpwm --- fudo/profiles/common-ui.nix | 18 ++++++++++-------- hosts/atom.nix | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/fudo/profiles/common-ui.nix b/fudo/profiles/common-ui.nix index 8beae7f..48cdbd6 100644 --- a/fudo/profiles/common-ui.nix +++ b/fudo/profiles/common-ui.nix @@ -86,15 +86,17 @@ in mkIf ((profile == "desktop") || (profile == "laptop")) { displayManager.gdm.enable = true; - displayManager.defaultSession = "gnome"; + # displayManager.defaultSession = "gnome"; - windowManager.session = pkgs.lib.singleton { - name = "stumpwm"; - start = '' - ${pkgs.lispPackages.stumpwm}/bin/stumpwm & - waidPID=$! - ''; - }; + windowManager.stumpwm.enable = true; + + # windowManager.session = pkgs.lib.singleton { + # name = "stumpwm"; + # start = '' + # ${pkgs.lispPackages.stumpwm}/bin/stumpwm & + # waidPID=$! + # ''; + # }; } else { layout = "us"; xkbVariant = "dvp"; diff --git a/hosts/atom.nix b/hosts/atom.nix index 2a8aef4..efd4498 100644 --- a/hosts/atom.nix +++ b/hosts/atom.nix @@ -34,7 +34,7 @@ in { }; fudo.laptop.use-network-manager = false; - fudo.common.enable-gui = false; + fudo.common.enable-gui = true; hardware.opengl.driSupport32Bit = true; hardware.opengl.extraPackages32 = with pkgs.pkgsi686Linux; [ libva ]; From bc94c1acb1386ae33477983c4a438bfce54b5dd9 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 24 Jul 2020 23:06:10 -0700 Subject: [PATCH 03/15] Peers was redundant --- config/fudo/vpn.nix | 6 ------ 1 file changed, 6 deletions(-) diff --git a/config/fudo/vpn.nix b/config/fudo/vpn.nix index bd0908b..1467fbb 100644 --- a/config/fudo/vpn.nix +++ b/config/fudo/vpn.nix @@ -42,12 +42,6 @@ in { default = 51820; }; - peers = mkOption { - type = listOf str; - description = "A list of peers for which to generate config files."; - default = []; - }; - peers = mkOption { type = loaOf (submodule peerOpts); description = "A list of peers allowed to connect."; From 5aca7986d38fce417c4e2e765dc476d333037df9 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 26 Jul 2020 10:22:28 -0500 Subject: [PATCH 04/15] france -> mail for cert --- config/fudo/mail/dovecot.nix | 4 ++++ fudo/email.nix | 7 +----- hosts/france.nix | 41 ++++++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/config/fudo/mail/dovecot.nix b/config/fudo/mail/dovecot.nix index ae994b7..8724f07 100644 --- a/config/fudo/mail/dovecot.nix +++ b/config/fudo/mail/dovecot.nix @@ -232,6 +232,10 @@ in { user = root } + service imap { + vsz_limit = 1024M + } + namespace inbox { separator = "/" inbox = yes diff --git a/fudo/email.nix b/fudo/email.nix index e622728..11c7c40 100644 --- a/fudo/email.nix +++ b/fudo/email.nix @@ -1,15 +1,10 @@ # Fudo email settings { config }: -let - mail-hostname = "france.fudo.org"; - -in { - +{ domain = "fudo.org"; local-domains = [ - "mail.fudo.org" "${config.networking.hostName}" "selby.ca" "mail.selby.ca" diff --git a/hosts/france.nix b/hosts/france.nix index 4158f01..44722a2 100644 --- a/hosts/france.nix +++ b/hosts/france.nix @@ -4,7 +4,7 @@ with lib; let domain = "fudo.org"; hostname = "france.${domain}"; - mail-hostname = hostname; + mail-hostname = "mail.${domain}"; host_ipv4 = "208.81.3.117"; # Use a special IP for git.fudo.org, since it needs to be SSH-able git_ipv4 = "208.81.3.126"; @@ -274,7 +274,21 @@ in { "webmail.test.fudo.org" = { title = "Fudo Webmail"; favicon = "/etc/nixos/static/fudo.org/favicon.ico"; - mail-server = "mail.fudo.org"; + mail-server = mail-hostname; + domain = "fudo.org"; + edit-mode = "Plain"; + database = { + name = "webmail"; + hostname = "localhost"; + user = "webmail"; + password-file = "/srv/webmail/secure/db.passwd"; + }; + }; + + "webmail.fudo.org" = { + title = "Fudo Webmail"; + favicon = "/etc/nixos/static/fudo.org/favicon.ico"; + mail-server = mail-hostname; domain = "fudo.org"; edit-mode = "Plain"; database = { @@ -288,7 +302,20 @@ in { "webmail.test.selby.ca" = { title = "Selby Webmail"; favicon = "/etc/nixos/static/selby.ca/favicon.ico"; - mail-server = "mail.selby.ca"; + mail-server = mail-hostname; + domain = "selby.ca"; + database = { + name = "webmail"; + hostname = "localhost"; + user = "webmail"; + password-file = "/srv/webmail/secure/db.passwd"; + }; + }; + + "webmail.selby.ca" = { + title = "Selby Webmail"; + favicon = "/etc/nixos/static/selby.ca/favicon.ico"; + mail-server = mail-hostname; domain = "selby.ca"; database = { name = "webmail"; @@ -305,7 +332,7 @@ in { hostname = "chat.fudo.org"; site-name = "Fudo Chat"; - smtp-server = "france.fudo.org"; + smtp-server = "mail.fudo.org"; smtp-user = "chat"; smtp-password-file = "/srv/mattermost/secure/smtp.passwd"; database = { @@ -494,6 +521,12 @@ in { ''; }; }; + + # Needed to grab a cert for the mail server. + "mail.fudo.org" = { + enableACME = true; + globalRedirect = "webmail.fudo.org"; + }; }; }; }; From ee8671e63326ad0473ea44222d93d35132d9e9ef Mon Sep 17 00:00:00 2001 From: root Date: Tue, 25 Aug 2020 22:05:59 -0700 Subject: [PATCH 05/15] Minor local changes on spark --- .gitignore | 2 +- hosts/spark.nix | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 947f765..e0f8ae3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ configuration.nix -hardware-configuration.nix *~ +hardware-configuration.nix diff --git a/hosts/spark.nix b/hosts/spark.nix index bc2adae..b8a6834 100644 --- a/hosts/spark.nix +++ b/hosts/spark.nix @@ -11,15 +11,33 @@ in { ../hardware-configuration.nix ]; + nixpkgs.config.permittedInsecurePackages = [ + "google-chrome-81.0.4044.138" + ]; + + environment.systemPackages = with pkgs; [ + # androidStudioPackages.canary + androidenv.androidPkgs_9_0.platform-tools + cmake + glxinfo + opencv4 + qemu_kvm + signal-cli + signal-desktop + teamviewer + thunderbird + wireshark + ]; + # Use the systemd-boot EFI boot loader. boot.loader.systemd-boot.enable = true; boot.loader.efi = { canTouchEfiVariables = true; - efibootmgr = { - efiDisk = "/dev/sda1"; - }; + }; - # efiSysMountPoint = "/boot/efi"; + programs = { + adb.enable = true; + bash.enableCompletion = true; }; networking.hostName = hostname; @@ -29,4 +47,13 @@ in { hardware.opengl.driSupport32Bit = true; hardware.opengl.driSupport = true; + services = { + trezord.enable = true; + }; + + virtualisation.libvirtd = { + enable = true; + qemuPackage = pkgs.qemu_kvm; + onShutdown = "shutdown"; + }; } From 944f3f3eafb38b104234fa052361d485f7c62e4f Mon Sep 17 00:00:00 2001 From: root Date: Sun, 30 Aug 2020 23:18:51 -0700 Subject: [PATCH 06/15] Switch spark to profile/site --- hosts/spark.nix | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hosts/spark.nix b/hosts/spark.nix index b8a6834..e86fc06 100644 --- a/hosts/spark.nix +++ b/hosts/spark.nix @@ -6,8 +6,6 @@ let in { imports = [ ../defaults.nix - ../networks/sea.fudo.org.nix - ../profiles/desktop.nix ../hardware-configuration.nix ]; @@ -29,6 +27,12 @@ in { wireshark ]; + fudo.common = { + profile = "desktop"; + site = "seattle"; + enable-gui = true; + }; + # Use the systemd-boot EFI boot loader. boot.loader.systemd-boot.enable = true; boot.loader.efi = { From 4fbf603d9ec14d756fb0d8faa398d67f6fda50ec Mon Sep 17 00:00:00 2001 From: root Date: Tue, 29 Sep 2020 11:08:48 -0500 Subject: [PATCH 07/15] Working ejabberd server on france --- config/fudo/mail-container.nix | 16 --- config/fudo/mail/postfix.nix | 2 +- config/fudo/prometheus.nix | 2 + defaults.nix | 2 + fudo/alias-users.nix | 2 + fudo/system-users.nix | 5 + fudo/users.nix | 7 ++ hosts/france.nix | 41 +++--- hosts/france/jabber.nix | 222 +++++++++++++++++++++++++++++++++ packages/archiva.nix | 27 ++++ packages/local.nix | 17 +++ static/backplane-auth.scm | 66 ++++++++++ 12 files changed, 368 insertions(+), 41 deletions(-) create mode 100644 hosts/france/jabber.nix create mode 100644 packages/archiva.nix create mode 100644 static/backplane-auth.scm diff --git a/config/fudo/mail-container.nix b/config/fudo/mail-container.nix index e12b9ff..c918116 100644 --- a/config/fudo/mail-container.nix +++ b/config/fudo/mail-container.nix @@ -160,22 +160,6 @@ in rec { }; }; - # users = { - # users = { - # ${container-mail-user} = { - # isSystemUser = true; - # uid = container-mail-user-id; - # group = "mailer"; - # }; - # }; - - # groups = { - # ${container-mail-group} = { - # members = ["mailer"]; - # }; - # }; - # }; - fudo.mail-server = { enable = true; hostname = cfg.hostname; diff --git a/config/fudo/mail/postfix.nix b/config/fudo/mail/postfix.nix index d05bf6d..7b96652 100644 --- a/config/fudo/mail/postfix.nix +++ b/config/fudo/mail/postfix.nix @@ -130,7 +130,7 @@ in { virtual = '' ${make-user-aliases cfg.user-aliases} - ${make-alias-users cfg.local-domains cfg.alias-users} + ${make-alias-users ([cfg.domain] ++ cfg.local-domains) cfg.alias-users} ''; sslCert = cfg.postfix.ssl-certificate; diff --git a/config/fudo/prometheus.nix b/config/fudo/prometheus.nix index 54e5b6a..7951932 100644 --- a/config/fudo/prometheus.nix +++ b/config/fudo/prometheus.nix @@ -111,6 +111,8 @@ in { webExternalUrl = "https://${cfg.hostname}"; + listenAddress = "127.0.0.1:9090"; + scrapeConfigs = [ { job_name = "docker"; diff --git a/defaults.nix b/defaults.nix index 7635608..2df8303 100644 --- a/defaults.nix +++ b/defaults.nix @@ -46,7 +46,9 @@ ipfs iptables jdk + jq kerberos + leiningen libisofs libstdcxxHook lispPackages.alexandria diff --git a/fudo/alias-users.nix b/fudo/alias-users.nix index a0bd333..bbced07 100644 --- a/fudo/alias-users.nix +++ b/fudo/alias-users.nix @@ -15,4 +15,6 @@ in { system = admin-users; asdf = ["mswaffer@gmail.com" "bouncetest@fudo.org"]; + + network-info = ["niten@fudo.org"]; } diff --git a/fudo/system-users.nix b/fudo/system-users.nix index 21830ef..4ac5c76 100644 --- a/fudo/system-users.nix +++ b/fudo/system-users.nix @@ -13,4 +13,9 @@ description = "User Database Reader"; hashed-password = "{SSHA}IVKhrB+wMOCI/CCzbJW8sNDbH67ZTMBv"; }; + + jabber = { + description = "Jabber Server"; + hashed-password = "{SSHA}KlQpe0n+NP0WcJUniHTD+JzUugzLo8Ib"; + }; } diff --git a/fudo/users.nix b/fudo/users.nix index 565f14b..b7c943e 100644 --- a/fudo/users.nix +++ b/fudo/users.nix @@ -422,4 +422,11 @@ common-name = "Kevin"; hashed-password = "{SSHA}1onx6HPMKCJvmLnRf1tiWFJ1D92DEtnl"; }; + + netinfo = { + uid = 10113; + group = "fudo"; + common-name = "Network Info Mailer"; + hashed-password = "{SSHA}UQHfW0IzjIbRU6VV+DraxvZFWt0to3oc"; + }; } diff --git a/hosts/france.nix b/hosts/france.nix index 44722a2..72f8aec 100644 --- a/hosts/france.nix +++ b/hosts/france.nix @@ -30,8 +30,16 @@ in { imports = [ ../hardware-configuration.nix - ../defaults.nix + ./france/jabber.nix + ]; + + environment.systemPackages = with pkgs; [ + docker + lxd + multipath-tools + nix-prefetch-docker + tshark ]; # services.openssh = { @@ -62,14 +70,6 @@ in { ]; }; - environment.systemPackages = with pkgs; [ - docker - lxd - multipath-tools - nix-prefetch-docker - tshark - ]; - fudo.prometheus = { enable = true; hostname = "metrics.fudo.org"; @@ -186,16 +186,9 @@ in { # sslKey = (acme-private-key hostname); # sslCACert = acme-ca; - # TODO: loop over v4 and v6 IPs. listen-uris = [ "ldap:///" "ldaps:///" - # "ldap://${host_ipv4}/" - # "ldaps://${host_ipv4}/" - # "ldap://localhost/" - # "ldaps://localhost/" - # "ldap://127.0.1.1/" - # "ldaps://127.0.1.1/" "ldapi:///" ]; @@ -369,7 +362,7 @@ in { useDHCP = false; # TODO: fix IPv6 - enableIPv6 = false; + enableIPv6 = true; # Create a bridge for VMs to use macvlans = { @@ -487,14 +480,10 @@ in { }; }; - fudo.system = { - disableTransparentHugePages = true; - postHugePageServices = ["redis.service"]; - }; - security.acme.certs = { "archiva.fudo.org".email = config.fudo.common.admin-email; "git.fudo.org".email = config.fudo.common.admin-email; + "mail.fudo.org".email = config.fudo.common.admin-email; }; services = { @@ -525,7 +514,10 @@ in { # Needed to grab a cert for the mail server. "mail.fudo.org" = { enableACME = true; - globalRedirect = "webmail.fudo.org"; + # Stopped relocating all because we need /metrics/... paths to remain unforwarded + locations."/" = { + return = "301 https://webmail.fudo.org$request_uri"; + }; }; }; }; @@ -543,6 +535,7 @@ in { environment = { # Not directly connected to the world anyway SSL_ENABLED = "false"; + PROXY_BASE_URL = "https://archiva.fudo.org/"; }; }; }; @@ -553,7 +546,7 @@ in { fudo.minecraft-server = { enable = true; - package = pkgs.minecraft-server_1_16_1; + package = pkgs.minecraft-server_1_16_2; data-dir = minecraft-data-dir; world-name = "selbyland"; motd = "Welcome to the Selby Minecraft server."; diff --git a/hosts/france/jabber.nix b/hosts/france/jabber.nix new file mode 100644 index 0000000..df4c16b --- /dev/null +++ b/hosts/france/jabber.nix @@ -0,0 +1,222 @@ +{ pkgs, lib, config, ... }: + +with lib; +let + backplane-auth = "/etc/nixos/static/backplane-auth.scm"; + + cert-basedir = "/var/lib/ejabberd/certs"; + + target-certs = ["key" "cert" "chain" "fullchain"]; + + cert-origin = hostname: filename: "/var/lib/acme/${hostname}/${filename}.pem"; + cert-target = hostname: filename: "${cert-basedir}/${hostname}-${filename}.pem"; + + move-server-certs = hostnames: + let + move-server-cert = hostname: + map (filename: '' + ensure_exists ${cert-origin hostname filename} + cp -L ${cert-origin hostname filename} ${cert-target hostname filename} + '') + target-certs; + in pkgs.writeShellScript "move-server-certs" '' + function ensure_exists() { + FILENAME=$1 + if [ ! -e $FILENAME ]; then + echo "file does not exist: $FILENAME" + exit 1 + fi + } + + if [ -d ${cert-basedir} ]; then + mkdir ${cert-basedir} + fi + + ${concatStringsSep "\n" (concatMap move-server-cert hostnames)} + + chown -R ${config.services.ejabberd.user}:${config.services.ejabberd.group} ${cert-basedir} + + exit 0 + ''; + + remove-server-certs = pkgs.writeShellScript "ejabberd-rm-combined-certs" '' + rm ${cert-basedir}/*.pem + ''; + + +in { + config = { + + security.acme.certs."fudo.im".email = "admin@fudo.org"; + security.acme.certs."backplane.fudo.org".email = "admin@fudo.org"; + + systemd.services = { + ejabberd-generate-certs = { + enable = true; + description = "Generate required SSL certs for ejabberd."; + wantedBy = [ "ejabberd.service" ]; + after = [ + "acme-backplane.fudo.org.service" + "acme-fudo.im.service" + ]; + + serviceConfig = { + Type = "oneshot"; + ExecStart = "${move-server-certs ["fudo.im" "backplane.fudo.org"]}"; + RemainAfterExit = true; + ExecStop = remove-server-certs; + StandardOutput = "journal"; + }; + }; + + ejabberd = { + requires = [ "ejabberd-generate-certs.service" ]; + environment = { + FUDO_HOST_PASSWD_FILE = "/srv/jabber/secret/hosts-passwd.scm"; + FUDO_SERVICE_PASSWD_FILE = "/srv/jabber/secret/services-passwd.scm"; + }; + }; + }; + + services = { + nginx = { + virtualHosts = { + "backplane.fudo.org" = { + enableACME = true; + }; + + "fudo.im" = { + enableACME = true; + }; + }; + }; + + ejabberd = { + enable = true; + + configFile = pkgs.writeText "ejabberd-config.yml" (builtins.toJSON { + loglevel = 4; + + access_rules = { + c2s = { allow = "all"; }; + announce = { allow = "admin"; }; + configure = { allow = "admin"; }; + pubsub_createnode = { allow = "local"; }; + }; + + acl = { + admin = { + user = [ + "niten@fudo.org" + ]; + }; + }; + + hosts = [ + "fudo.im" + "backplane.fudo.org" + ]; + + listen = [ + { + port = 5222; + module = "ejabberd_c2s"; + ip = "0.0.0.0"; + starttls = true; + starttls_required = true; + } + ]; + + certfiles = + concatMap (hostname: map (filename: cert-target hostname filename) target-certs) + ["fudo.im" "backplane.fudo.org"]; + + host_config = { + "fudo.im" = { + auth_method = "ldap"; + ldap_servers = ["auth.fudo.org"]; + ldap_port = 389; + ldap_rootdn = "cn=jabber,dc=fudo,dc=org"; + ldap_password = fileContents /srv/jabber/secret/ldap.passwd; + ldap_base = "ou=members,dc=fudo,dc=org"; + ldap_filter = "(objectClass=posixAccount)"; + ldap_uids = { uid = "%u"; }; + + modules = { + mod_adhoc = {}; + mod_announce = {}; + mod_avatar = {}; + mod_blocking = {}; + 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_privacy = {}; + mod_private = {}; + 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_vcard = { + search = false; + }; + mod_vcard_xupdate = {}; + mod_version = {}; + }; + }; + + "backplane.fudo.org" = { + auth_method = "external"; + extauth_program = "${pkgs.guile}/bin/guile -s ${backplane-auth}"; + 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 = {}; + }; + }; + }; + }); + }; + }; + }; +} diff --git a/packages/archiva.nix b/packages/archiva.nix new file mode 100644 index 0000000..054cc8d --- /dev/null +++ b/packages/archiva.nix @@ -0,0 +1,27 @@ +{ pkgs, fetchurl, ... }: + +let + version = "2.2.5"; + url = "https://mirrors.sonic.net/apache/archiva/${version}/binaries/apache-archiva-${version}-bin.tar.gz"; + sha256 = "01119af2d9950eacbcce0b7f8db5067b166ad26c1e1701bef829105441bb6e29"; + +in pkgs.stdenv.mkDerivation { + name = "archiva-${version}"; + + src = builtins.fetchurl { + url = url; + sha256 = sha256; + }; + + phases = ["installPhase"]; + + buildInputs = with pkgs; [ stdenv procps makeWrapper ]; + + installPhase = '' + mkdir $out + tar -xzf $src + cd apache-archiva-${version} + mv {LICENSE,NOTICE,apps,bin,conf,contexts,lib,logs,temp} $out + makeWrapper $out/bin/archiva $out/bin/archivaWrapped --set PATH ${pkgs.stdenv.lib.makeBinPath [ pkgs.procps ]} + ''; +} diff --git a/packages/local.nix b/packages/local.nix index 7e72555..16637da 100644 --- a/packages/local.nix +++ b/packages/local.nix @@ -35,6 +35,18 @@ }; })); + minecraft-server_1_16_2 = let + version = "1.16.2"; + url = "https://launcher.mojang.com/v1/objects/c5f6fb23c3876461d46ec380421e42b289789530/server.jar"; + sha256 = "0fbghwrj9b2y9lkn2b17id4ghglwvyvcc8065h582ksfz0zys0i9"; + in (pkgs.minecraft-server.overrideAttrs (oldAttrs: rec { + name = "minecraft-server-${version}"; + inherit version; + src = pkgs.fetchurl { + inherit url sha256; + }; + })); + postgresql_11_gssapi = pkgs.postgresql_11.overrideAttrs (oldAttrs: rec { configureFlags = oldAttrs.configureFlags ++ [ "--with-gssapi" ]; buildInputs = oldAttrs.buildInputs ++ [ pkgs.krb5 ]; @@ -54,5 +66,10 @@ stdenv = pkgs.stdenv; fetchgit = pkgs.fetchgit; }; + + fudo-service = import ./fudo-service.nix { + fetchgit = pkgs.fetchgit; + pkgs = pkgs; + }; }; } diff --git a/static/backplane-auth.scm b/static/backplane-auth.scm new file mode 100644 index 0000000..f23ee80 --- /dev/null +++ b/static/backplane-auth.scm @@ -0,0 +1,66 @@ +(use-modules (srfi srfi-1) + (srfi srfi-13) + (ice-9 binary-ports) + (ice-9 textual-ports) + (ice-9 format) + (ice-9 regex) + (rnrs bytevectors)) + +(define *host-passwd-file* (getenv "FUDO_HOST_PASSWD_FILE")) +(when (not *host-passwd-file*) + (format (current-error-port "FUDO_HOST_PASSWD_FILE not set~%")) + (exit 1)) + +(define *service-passwd-file* (getenv "FUDO_SERVICE_PASSWD_FILE")) +(when (not *service-passwd-file*) + (format (current-error-port "FUDO_SERVICE_PASSWD_FILE not set~%")) + (exit 1)) + +(define host-regex "^host-([a-zA-Z][a-zA-Z0-9_-]+)$") +(define service-regex "^service-([a-zA-Z][a-zA-Z0-9_-]+)$") + +(define (make-verifier passwd-file) + (let ((passwds (load passwd-file))) + (lambda (username passwd) + (and (> (string-length passwd) 6) + (equal? (assoc-ref passwds username) passwd))))) + +(define (make-authenticator host-verifier service-verifier) + (lambda (username hostname password) + (cond ((string-match host-regex username) + (host-verifier (match:substring (string-match host-regex username) 1) + password)) + + ((string-match service-regex username) + (service-verifier (match:substring (string-match service-regex username) 1) + password)) + + (else #f)))) + +(define (make-handler handlers) + (lambda (request) + (let ((op (assoc-ref handlers (first request)))) + (if op + (apply op (cdr request)) + #f)))) + +(define (auth-listener handler) + (let ((in (current-input-port)) + (out (current-output-port))) + (while #t + (let ((size (bytevector-u16-ref (get-bytevector-n in 2) 0 (endianness big))) + (response (make-bytevector 4 0))) + (bytevector-u8-set! response 1 #x02) + (if (handler (string-split (get-string-n in size) #\:)) + (begin (bytevector-u8-set! response 3 #x01) + (put-bytevector out response 0 4) + (force-output out)) + (begin (bytevector-u8-set! response 3 #x00) + (put-bytevector out response 0 4) + (force-output out))))))) + +(auth-listener + (make-handler + (list (cons "auth" + (make-authenticator (make-verifier *host-passwd-file*) + (make-verifier *service-passwd-file*)))))) From c1328d39aa6d3c4480d685ea9010d97cb9fd3ba7 Mon Sep 17 00:00:00 2001 From: nostoromo root Date: Fri, 16 Oct 2020 09:09:44 -0700 Subject: [PATCH 08/15] added netinfo, etc --- config/fudo/netinfo-email.nix | 93 +++++++++++++++++++++++++++++++++++ config/local.nix | 1 + fudo/sites/seattle.nix | 23 ++++++++- hosts/nostromo.nix | 12 ++--- static/send-netinfo.rb | 32 ++++++++++++ 5 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 config/fudo/netinfo-email.nix create mode 100755 static/send-netinfo.rb diff --git a/config/fudo/netinfo-email.nix b/config/fudo/netinfo-email.nix new file mode 100644 index 0000000..4dcc3a2 --- /dev/null +++ b/config/fudo/netinfo-email.nix @@ -0,0 +1,93 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.fudo.netinfo-email; + + make-script = server: port: target: pkgs.writeText "netinfo-script.rb" '' + #!${pkgs.ruby}/bin/ruby + + require 'net/smtp' + + raise RuntimeError.new("NETINFO_SMTP_USERNAME not set!") if not ENV['NETINFO_SMTP_USERNAME'] + user = ENV['NETINFO_SMTP_USERNAME'] + + raise RuntimeError.new("NETINFO_SMTP_PASSWD not set!") if not ENV['NETINFO_SMTP_PASSWD'] + passwd = ENV['NETINFO_SMTP_PASSWD'] + + hostname = `${pkgs.inetutils}/bin/hostname -f`.strip + date = `${pkgs.coreutils}/bin/date +%Y-%m-%d`.strip + email_date = `${pkgs.coreutils}/bin/date` + ipinfo = `${pkgs.iproute}/bin/ip addr` + + message = < " if not (ARGV[0] and ARGV[1]) +server = ARGV[0] +user = ARGV[1] + +error "NETINFO_SMTP_PASSWD not set!" if not ENV['NETINFO_SMTP_PASSWD'] +passwd = ENV['NETINFO_SMTP_PASSWD'] + +hostname = `hostname -f`.strip +date = `date +%Y-%m-%d`.strip +email_date = `date` +ipinfo = `ip addr` + +message = < Date: Fri, 16 Oct 2020 11:17:50 -0500 Subject: [PATCH 09/15] added clunk --- fudo/sites/default.nix | 1 + fudo/sites/russell.nix | 34 +++++++++++ hosts/clunk.nix | 126 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 fudo/sites/russell.nix create mode 100644 hosts/clunk.nix diff --git a/fudo/sites/default.nix b/fudo/sites/default.nix index fd59359..63a6ab0 100644 --- a/fudo/sites/default.nix +++ b/fudo/sites/default.nix @@ -4,6 +4,7 @@ imports = [ ./joes.nix ./portage.nix + ./russell.nix ./seattle.nix ]; } diff --git a/fudo/sites/russell.nix b/fudo/sites/russell.nix new file mode 100644 index 0000000..e10a3c4 --- /dev/null +++ b/fudo/sites/russell.nix @@ -0,0 +1,34 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + admin = "admin@fudo.org"; + + nameservers = [ + "1.1.1.1" + "8.8.8.8" + ]; + + hostname = config.networking.hostName; + +in { + config = mkIf (config.fudo.common.site == "russell") { + time.timeZone = "America/Winnipeg"; + + services.cron = { + mailto = admin; + }; + + networking = { + domain = "fudo.org"; + search = ["fudo.org"]; + firewall.enable = false; + nameservers = nameservers; + }; + + # fudo.node-exporter = { + # enable = true; + # hostname = hostname; + # }; + }; +} diff --git a/hosts/clunk.nix b/hosts/clunk.nix new file mode 100644 index 0000000..b1fb4fb --- /dev/null +++ b/hosts/clunk.nix @@ -0,0 +1,126 @@ +{ lib, config, pkgs, ... }: + +let + hostname = "clunk"; + host-internal-ip = "10.0.0.1"; + inherit (lib.strings) concatStringsSep; + +in { + + # Use the systemd-boot EFI boot loader. + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + hardware.bluetooth.enable = false; + + imports = [ + ../defaults.nix + ../hardware-configuration.nix + ]; + + fudo.common = { + profile = "server"; + site = "russell"; + }; + + # fudo.local-network = { + # enable = true; + # dns-servers = [ host-internal-ip ]; + # gateway = host-internal-ip; + # dhcp-interfaces = [ "intif0" ]; + # dns-serve-ips = [ host-internal-ip "127.0.0.1" "127.0.1.1" ]; + # # Using a pihole running in docker, see below + # recursive-resolver = "${host-internal-ip} port 5353"; + # # recursive-resolver = "1.1.1.1"; + # server-ip = host-internal-ip; + # }; + + networking = { + hostName = hostname; + + nameservers = [ host-internal-ip ]; + + # Create a bridge for VMs to use + macvlans = { + intif0 = { + interface = "enp2s0"; + mode = "bridge"; + }; + }; + + interfaces = { + enp2s0.useDHCP = false; + enp3s0.useDHCP = false; + enp4s0.useDHCP = false; + + enp1s0.useDHCP = true; + + intif0 = { + useDHCP = false; + # Result of: + # echo clunk-intif0|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/' + macAddress = "02:44:d1:eb:c3:6b"; + ipv4.addresses = [ + { + address = host-internal-ip; + prefixLength = 22; + } + ]; + }; + }; + + nat = { + enable = true; + externalInterface = "enp1s0"; + internalInterfaces = ["intif0"]; + }; + }; + + fudo = { + secure-dns-proxy = { + enable = true; + port = 3535; + upstream-dns = [ + "https://cloudflare-dns.com/dns-query" + ]; + bootstrap-dns = "1.1.1.1"; + }; + }; + + environment.systemPackages = with pkgs; [ + dnsproxy + ]; + + virtualisation = { + docker = { + enable = true; + autoPrune.enable = true; + enableOnBoot = true; + }; + }; + + docker-containers = { + pihole = { + image = "pihole/pihole:4.3.2-1"; + ports = [ + "5353:53/tcp" + "5353:53/udp" + "3080:80/tcp" + ]; + environment = { + ServerIP = host-internal-ip; + VIRTUAL_HOST = "dns-hole.sea.fudo.org"; + DNS1 = "1.1.1.1"; + DNS2 = "8.8.8.8"; + }; + volumes = [ + "/srv/pihole/etc-pihole/:/etc/pihole/" + "/srv/pihole/etc-dnsmasq.d/:/etc/dnsmasq.d/" + ]; + # TODO: DNS-over-HTTPS via cloudflared + # extraDockerOptions = [ + # "--dns=1.1.1.1" + # ]; + }; + }; +} From ebba38a8ab8cf0775417e54edba7336f418c66dd Mon Sep 17 00:00:00 2001 From: Niten Date: Sat, 24 Oct 2020 11:14:46 -0500 Subject: [PATCH 10/15] Changes for rus.selby.ca --- config/fudo/garbage-collector.nix | 49 ++++++++++ config/fudo/local-network.nix | 12 ++- config/local.nix | 1 + defaults.nix | 1 - fudo/profiles/common-ui.nix | 1 + fudo/profiles/server.nix | 5 +- fudo/sites/russell.nix | 154 +++++++++++++++++++++++++++--- fudo/sites/seattle.nix | 4 +- hosts/clunk.nix | 88 +++++++++++++---- hosts/nostromo.nix | 1 + 10 files changed, 278 insertions(+), 38 deletions(-) create mode 100644 config/fudo/garbage-collector.nix diff --git a/config/fudo/garbage-collector.nix b/config/fudo/garbage-collector.nix new file mode 100644 index 0000000..3eafdd6 --- /dev/null +++ b/config/fudo/garbage-collector.nix @@ -0,0 +1,49 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.fudo.garbage-collector; + +in { + + options.fudo.garbage-collector = { + enable = mkEnableOption "Enable periodic NixOS garbage collection"; + + timing = mkOption { + type = types.str; + default = "weekly"; + description = "Period (systemd format) at which to run garbage collector."; + }; + + age = mkOption { + type = types.str; + default = "30d"; + description = "Age of garbage to collect (eg. 30d)."; + }; + }; + + config = mkIf cfg.enable { + systemd = { + timers.fudo-garbage-collector = { + enable = true; + description = "Collect NixOS garbage older than ${cfg.age}"; + partOf = [ "fudo-garbage-collector.service" ]; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = cfg.timing; + }; + }; + + services.fudo-garbage-collector = { + enable = true; + serviceConfig = { + Type = "oneshot"; + StandardOutput = "journal"; + }; + script = '' + ${pkgs.nix}/bin/nix-collect-garbage --delete-older-than ${cfg.age} + ''; + }; + }; + }; +} diff --git a/config/fudo/local-network.nix b/config/fudo/local-network.nix index 4cc653a..024b6d5 100644 --- a/config/fudo/local-network.nix +++ b/config/fudo/local-network.nix @@ -19,10 +19,11 @@ let }; mac-address = mkOption { - type = types.str; + type = with types; nullOr types.str; description = '' The MAC address of a given host, if desired for IP reservation. ''; + default = null; }; ssh-fingerprints = mkOption { @@ -174,7 +175,7 @@ in { ethernetAddress = hostOpts.mac-address; hostName = hostname; ipAddress = hostOpts.ip-address; - }) cfg.hosts; + }) (filterAttrs (host: hostOpts: hostOpts.mac-address != null) cfg.hosts); interfaces = cfg.dhcp-interfaces; @@ -242,6 +243,13 @@ in { cacheNetworks = [ cfg.network "localhost" "localnets" ]; forwarders = [ cfg.recursive-resolver ]; listenOn = cfg.dns-serve-ips; + extraOptions = concatStringsSep "\n" [ + "dnssec-enable yes;" + "dnssec-validation auto;" + "auth-nxdomain no;" + "recursion yes;" + "allow-recursion { ${cfg.network}; };" + ]; zones = [ { master = true; diff --git a/config/local.nix b/config/local.nix index e2fdf34..92c15f7 100644 --- a/config/local.nix +++ b/config/local.nix @@ -8,6 +8,7 @@ with lib; ./fudo/chat.nix ./fudo/common.nix ./fudo/dns.nix + ./fudo/garbage-collector.nix ./fudo/git.nix ./fudo/grafana.nix ./fudo/kdc.nix diff --git a/defaults.nix b/defaults.nix index d6b19cd..9d1f40c 100644 --- a/defaults.nix +++ b/defaults.nix @@ -30,7 +30,6 @@ clang curl dpkg - emacs enca fail2ban file diff --git a/fudo/profiles/common-ui.nix b/fudo/profiles/common-ui.nix index e016ef8..0e0cbfd 100644 --- a/fudo/profiles/common-ui.nix +++ b/fudo/profiles/common-ui.nix @@ -19,6 +19,7 @@ let corefonts chrome-gnome-shell chromium + emacs evince firefox gimp diff --git a/fudo/profiles/server.nix b/fudo/profiles/server.nix index b99c451..5266e10 100644 --- a/fudo/profiles/server.nix +++ b/fudo/profiles/server.nix @@ -51,6 +51,7 @@ in { config = mkIf (config.fudo.common.profile == "server") { environment = { systemPackages = with pkgs; [ + emacs-nox ldns ldns.examples racket-minimal @@ -67,8 +68,8 @@ in { networking = { networkmanager.enable = mkForce false; - } -; + }; + boot.tmpOnTmpfs = true; services.xserver.enable = false; diff --git a/fudo/sites/russell.nix b/fudo/sites/russell.nix index e10a3c4..0eb6a64 100644 --- a/fudo/sites/russell.nix +++ b/fudo/sites/russell.nix @@ -4,10 +4,11 @@ with lib; let admin = "admin@fudo.org"; - nameservers = [ - "1.1.1.1" - "8.8.8.8" - ]; + local-domain = "rus.selby.ca"; + + gateway = "10.0.0.1"; + + nameservers = [ "10.0.0.1" ]; hostname = config.networking.hostName; @@ -20,15 +21,146 @@ in { }; networking = { - domain = "fudo.org"; - search = ["fudo.org"]; - firewall.enable = false; + domain = "rus.selby.ca"; + search = [local-domain "fudo.org" "selby.ca"]; nameservers = nameservers; + + # Don't set the gateway if we ARE the gateway. + # This is the most generic way I can think of to do that. local-network is really + # about running all the local servers (DNS, DHCP, and providing gateway). + defaultGateway = optionalString (config.fudo.local-network.enable != true) gateway; + + enableIPv6 = true; + + + # Necessary to make sure than Kerberos and Avahi both work (the former + # needs the full reverse-lookup name of the server, the latter wants + # `hostname` to return just the host itself. + hosts = { + "127.0.0.1" = [ + "${config.networking.hostName}.${local-domain}" + config.networking.hostName + ]; + }; }; - # fudo.node-exporter = { - # enable = true; - # hostname = hostname; - # }; + krb5.libdefaults.default_realm = "FUDO.ORG"; + + users.extraUsers = { + guest = { + isNormalUser = true; + uid = 1000; + description = "Guest User"; + extraGroups = ["audio" "video" "disk" "floppy" "lp" "cdrom" "tape" "input"]; + }; + ken = { + isNormalUser = true; + uid = 10035; + createHome = true; + description = "Ken Selby"; + extraGroups = ["audio" "video" "disk" "floppy" "lp" "cdrom" "tape" "input"]; + group = "users"; + home = "/home/selby/ken"; + hashedPassword = "$6$EwK9fpbH8$gYVzYY1IYw2/G0wCeUxXrZZqvjWCkCZbBqCOhxowbMuYtC5G0vp.AoYhVKWOJcHJM2c7TdPmAdnhLIe2KYStf."; + }; + xiaoxuan = { + isNormalUser = true; + uid = 10065; + createHome = true; + description = "Xiaoxuan Jin"; + extraGroups = ["audio" "video" "disk" "floppy" "lp" "cdrom" "tape" "input"]; + group = "users"; + home = "/home/xiaoxuan"; + hashedPassword = "$6$C8lYHrK7KvdKm/RE$cHZ2hg5gEOEjTV8Zoayik8sz5h.Vh0.ClCgOlQn8l/2Qx/qdxqZ7xCsAZ1GZ.IEyESfhJeJbjLpykXDwPpfVF0"; + }; + }; + + fudo.common.domain = "sea.fudo.org"; + + fudo.local-network = { + domain = "${local-domain}"; + + network = "10.0.0.0/16"; + + dhcp-dynamic-network = "10.0.1.0/24"; + + enable-reverse-mappings = true; + + srv-records = { + tcp = { + domain = [{ + port = 53; + host = "clunk.${local-domain}"; + }]; + kerberos = [{ + port = 88; + host = "france.fudo.org"; + }]; + kerberos-adm = [{ + port = 88; + host = "france.fudo.org"; + }]; + ssh = [{ + port = 22; + host = "clunk.${local-domain}"; + }]; + }; + + udp = { + domain = [{ + port = 53; + host = "clunk.${local-domain}"; + }]; + kerberos = [{ + port = 88; + host = "france.fudo.org"; + }]; + kerboros-master = [{ + port = 88; + host = "france.fudo.org"; + }]; + kpasswd = [{ + port = 464; + host = "france.fudo.org"; + }]; + }; + }; + + aliases = { + dns-hole = "clunk"; + }; + + hosts = { + clunk = { + ip-address = "10.0.0.1"; + mac-address = "02:44:d1:eb:c3:6b"; + }; + + dns-proxy = { + ip-address = "10.0.0.2"; + # This is just an alias for clunk's primary interface + }; + + google-wifi = { + ip-address = "10.0.0.11"; + mac-address = "70:3a:cb:c0:3b:09"; + }; + + pselby-work = { + ip-address = "10.0.0.151"; + mac-address = "00:50:b6:aa:bd:b3"; + }; + + downstairs-desktop = { + ip-address = "10.0.0.100"; + mac-address = "90:b1:1c:8e:29:cf"; + }; + + upstairs-desktop = { + ip-address = "10.0.0.101"; + mac-address = "00:21:70:70:25:7d"; + }; + }; + }; }; } diff --git a/fudo/sites/seattle.nix b/fudo/sites/seattle.nix index 289166f..9a0384b 100644 --- a/fudo/sites/seattle.nix +++ b/fudo/sites/seattle.nix @@ -118,8 +118,8 @@ in { domain = "${local-domain}"; aliases = { - kadmin = "slab"; - kdc = "slab"; + kadmin = "nostromo"; + kdc = "nostromo"; photo = "doraemon"; music = "doraemon"; panopticon = "hyperion"; diff --git a/hosts/clunk.nix b/hosts/clunk.nix index b1fb4fb..8dd89de 100644 --- a/hosts/clunk.nix +++ b/hosts/clunk.nix @@ -3,6 +3,7 @@ let hostname = "clunk"; host-internal-ip = "10.0.0.1"; + dns-proxy-ip = "10.0.0.2"; inherit (lib.strings) concatStringsSep; in { @@ -11,6 +12,10 @@ in { boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; + boot = { + runSize = "50%"; + }; + hardware.bluetooth.enable = false; imports = [ @@ -23,17 +28,16 @@ in { site = "russell"; }; - # fudo.local-network = { - # enable = true; - # dns-servers = [ host-internal-ip ]; - # gateway = host-internal-ip; - # dhcp-interfaces = [ "intif0" ]; - # dns-serve-ips = [ host-internal-ip "127.0.0.1" "127.0.1.1" ]; - # # Using a pihole running in docker, see below - # recursive-resolver = "${host-internal-ip} port 5353"; - # # recursive-resolver = "1.1.1.1"; - # server-ip = host-internal-ip; - # }; + fudo.local-network = { + enable = true; + dns-servers = [ host-internal-ip ]; + gateway = host-internal-ip; + dhcp-interfaces = [ "intif0" ]; + dns-serve-ips = [ host-internal-ip "127.0.0.1" "127.0.1.1" ]; + # Using a pihole running in docker, see below + recursive-resolver = "${host-internal-ip} port 5353"; + server-ip = host-internal-ip; + }; networking = { hostName = hostname; @@ -48,6 +52,11 @@ in { }; }; + firewall = { + enable = true; + trustedInterfaces = [ "intif0" ]; + }; + interfaces = { enp2s0.useDHCP = false; enp3s0.useDHCP = false; @@ -65,6 +74,11 @@ in { address = host-internal-ip; prefixLength = 22; } + + { + address = dns-proxy-ip; + prefixLength = 32; + } ]; }; }; @@ -77,13 +91,19 @@ in { }; fudo = { + garbage-collector = { + enable = true; + timing = "hourly"; + }; + secure-dns-proxy = { enable = true; - port = 3535; + port = 53; upstream-dns = [ "https://cloudflare-dns.com/dns-query" ]; bootstrap-dns = "1.1.1.1"; + listen-ips = [dns-proxy-ip]; }; }; @@ -101,7 +121,7 @@ in { docker-containers = { pihole = { - image = "pihole/pihole:4.3.2-1"; + image = "pihole/pihole:v5.1.2"; ports = [ "5353:53/tcp" "5353:53/udp" @@ -109,18 +129,46 @@ in { ]; environment = { ServerIP = host-internal-ip; - VIRTUAL_HOST = "dns-hole.sea.fudo.org"; - DNS1 = "1.1.1.1"; - DNS2 = "8.8.8.8"; + VIRTUAL_HOST = "dns-hole.rus.selby.ca"; + # Not working? + DNS1 = dns-proxy-ip; + #DNS1 = "1.1.1.1"; }; volumes = [ "/srv/pihole/etc-pihole/:/etc/pihole/" "/srv/pihole/etc-dnsmasq.d/:/etc/dnsmasq.d/" ]; - # TODO: DNS-over-HTTPS via cloudflared - # extraDockerOptions = [ - # "--dns=1.1.1.1" - # ]; + }; + }; + + services.nginx = { + enable = true; + + recommendedOptimisation = true; + recommendedGzipSettings = true; + + virtualHosts = { + "dns-hole.rus.selby.ca" = { + serverAliases = [ + "pihole.rus.selby.ca" + "hole.rus.selby.ca" + "pihole" + "dns-hole" + "hole" + ]; + + locations."/" = { + proxyPass = "http://127.0.0.1:3080"; + + extraConfig = '' + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-By $server_addr:$server_port; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + ''; + }; + }; }; }; } diff --git a/hosts/nostromo.nix b/hosts/nostromo.nix index 4779a37..76e048f 100644 --- a/hosts/nostromo.nix +++ b/hosts/nostromo.nix @@ -166,6 +166,7 @@ in { }; services = { + # HMMMMMMM dhcpd6.enable = false; nginx = { From 64ad31ab4f8c01ee120e01c4ec094a194dd5710d Mon Sep 17 00:00:00 2001 From: Niten Date: Wed, 4 Nov 2020 12:33:14 -0600 Subject: [PATCH 11/15] Fixes to local-network DNS --- config/fudo/local-network.nix | 8 ++++---- hosts/clunk.nix | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/config/fudo/local-network.nix b/config/fudo/local-network.nix index 024b6d5..1b282cd 100644 --- a/config/fudo/local-network.nix +++ b/config/fudo/local-network.nix @@ -70,7 +70,7 @@ in { enable = mkEnableOption "Enable local network configuration (DHCP & DNS)."; hosts = mkOption { - type = with types; loaOf (submodule hostOpts); + type = with types; attrsOf (submodule hostOpts); default = {}; description = "A map of hostname => { host_attributes }."; }; @@ -101,7 +101,7 @@ in { }; aliases = mkOption { - type = with types; loaOf str; + type = with types; attrsOf str; default = {}; description = "A mapping of host-alias => hostname to use on the local network."; }; @@ -245,10 +245,10 @@ in { listenOn = cfg.dns-serve-ips; extraOptions = concatStringsSep "\n" [ "dnssec-enable yes;" - "dnssec-validation auto;" + "dnssec-validation yes;" "auth-nxdomain no;" "recursion yes;" - "allow-recursion { ${cfg.network}; };" + "allow-recursion { any; };" ]; zones = [ { diff --git a/hosts/clunk.nix b/hosts/clunk.nix index 8dd89de..b93770a 100644 --- a/hosts/clunk.nix +++ b/hosts/clunk.nix @@ -33,7 +33,7 @@ in { dns-servers = [ host-internal-ip ]; gateway = host-internal-ip; dhcp-interfaces = [ "intif0" ]; - dns-serve-ips = [ host-internal-ip "127.0.0.1" "127.0.1.1" ]; + dns-serve-ips = [ host-internal-ip "127.0.0.1" "127.0.1.1" "::1" ]; # Using a pihole running in docker, see below recursive-resolver = "${host-internal-ip} port 5353"; server-ip = host-internal-ip; @@ -54,7 +54,7 @@ in { firewall = { enable = true; - trustedInterfaces = [ "intif0" ]; + trustedInterfaces = [ "intif0" "docker0" ]; }; interfaces = { @@ -100,7 +100,9 @@ in { enable = true; port = 53; upstream-dns = [ - "https://cloudflare-dns.com/dns-query" + "https://1.1.1.1/dns-query" + "https://1.0.0.1/dns-query" + #"https://9.9.9.9/dns-query" ]; bootstrap-dns = "1.1.1.1"; listen-ips = [dns-proxy-ip]; From 4d6e8cb2648e31777d0cba28129f66835f033193 Mon Sep 17 00:00:00 2001 From: nostoromo root Date: Mon, 16 Nov 2020 12:39:37 -0800 Subject: [PATCH 12/15] Working backplane dyndns client/server --- config/fudo/backplane/default.nix | 7 + config/fudo/backplane/dns.nix | 271 ++++++++++++++++++++++ config/fudo/client/dns.nix | 94 ++++++++ config/fudo/local-network.nix | 40 +--- config/fudo/password.nix | 114 +++++++++ config/fudo/postgres.nix | 218 ++++++++++++----- config/local.nix | 3 + defaults.nix | 1 + fudo/profiles/server.nix | 2 +- hosts/nostromo.nix | 180 ++++++++++---- lib/dns.nix | 58 +++++ lib/utils.nix | 14 ++ packages/backplane-dns-client.nix | 35 +++ packages/backplane-dns.nix | 24 ++ packages/local.nix | 36 +++ static/backplane-dns-client/Gemfile | 3 + static/backplane-dns-client/Gemfile.lock | 13 ++ static/backplane-dns-client/dns-client.rb | 250 ++++++++++++++++++++ static/backplane-dns-client/gemset.nix | 12 + 19 files changed, 1242 insertions(+), 133 deletions(-) create mode 100644 config/fudo/backplane/default.nix create mode 100644 config/fudo/backplane/dns.nix create mode 100644 config/fudo/client/dns.nix create mode 100644 config/fudo/password.nix create mode 100644 lib/dns.nix create mode 100644 lib/utils.nix create mode 100644 packages/backplane-dns-client.nix create mode 100644 packages/backplane-dns.nix create mode 100644 static/backplane-dns-client/Gemfile create mode 100644 static/backplane-dns-client/Gemfile.lock create mode 100644 static/backplane-dns-client/dns-client.rb create mode 100644 static/backplane-dns-client/gemset.nix diff --git a/config/fudo/backplane/default.nix b/config/fudo/backplane/default.nix new file mode 100644 index 0000000..028c784 --- /dev/null +++ b/config/fudo/backplane/default.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ + imports = [ + ./dns.nix + ]; +} diff --git a/config/fudo/backplane/dns.nix b/config/fudo/backplane/dns.nix new file mode 100644 index 0000000..beeeaa3 --- /dev/null +++ b/config/fudo/backplane/dns.nix @@ -0,0 +1,271 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + cfg = config.fudo.backplane.dns; + + dns = import ../../../lib/dns.nix { inherit lib; }; + + backup-directory = "/var/lib/fudo/backplane/dns"; + + powerdns-home = "/var/lib/powerdns"; + + powerdns-conf-dir = "${powerdns-home}/conf.d"; + + backplaneOpts = { ... }: { + options = { + host = mkOption { + type = types.str; + description = "Hostname of the backplane jabber server."; + }; + + role = mkOption { + type = types.str; + description = "Backplane XMPP role name for the DNS server."; + default = "service-dns"; + }; + + password-file = mkOption { + type = types.str; + description = "File containing XMPP password for backplane role."; + }; + + database = mkOption { + type = with types; submodule databaseOpts; + description = "Database settings for backplane server."; + }; + }; + }; + + databaseOpts = { ... }: { + options = { + host = mkOption { + type = types.str; + description = "Hostname or IP of the PostgreSQL server."; + }; + + database = mkOption { + type = types.str; + description = "Database to use for DNS backplane."; + default = "backplane_dns"; + }; + + username = mkOption { + type = types.str; + description = "Database user for DNS backplane."; + default = "backplane_dns"; + }; + + password-file = mkOption { + type = types.str; + description = "File containing password for database user."; + }; + }; + }; + + lisp-libs = []; + + launchScript = pkgs.writeText "launch-backplane-dns.lisp" '' + (load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname))) + (ql:quickload :backplane-dns) + (backplane-dns:start-listener-with-env) + (loop (sleep 600)) + ''; + +in { + options.fudo.backplane.dns = { + enable = mkEnableOption "Enable backplane dynamic DNS server."; + + port = mkOption { + type = types.port; + description = "Port on which to serve authoritative DNS requests."; + default = 53; + }; + + listen-addresses = mkOption { + type = with types; listOf str; + description = "IP addresses on which to listen for dns requests."; + default = [ "0.0.0.0" ]; + }; + + required-services = mkOption { + type = with types; listOf str; + description = "A list of services required before the DNS server can start."; + }; + + user = mkOption { + type = types.str; + description = "User as which to run DNS backplane listener service."; + default = "backplane-dns"; + }; + + group = mkOption { + type = types.str; + description = "Group as which to run DNS backplane listener service."; + default = "backplane-dns"; + }; + + database = mkOption { + type = with types; submodule databaseOpts; + description = "Database settings for the DNS server."; + }; + + backplane = mkOption { + type = with types; submodule backplaneOpts; + description = "Backplane Jabber settings for the DNS server."; + }; + }; + + config = mkIf cfg.enable { + users = { + users = { + "${cfg.user}" = { + isSystemUser = true; + group = cfg.group; + createHome = true; + home = "/var/home/${cfg.user}"; + }; + backplane-powerdns = { + isSystemUser = true; + }; + }; + + groups = { + "${cfg.group}" = { + members = [cfg.user]; + }; + backplane-powerdns = { + members = [ "backplane-powerdns" ]; + }; + }; + }; + + systemd = { + targets = { + backplane-dns = { + description = "Fudo DNS backplane services."; + wantedBy = [ "multi-user.target" ]; + }; + }; + + services = { + + backplane-powerdns = let + configDir = pkgs.writeTextDir "pdns.conf" '' + local-address=${lib.concatStringsSep ", " cfg.listen-addresses} + local-port=${toString cfg.port} + launch= + include-dir=${powerdns-conf-dir}/ + ''; + + psql-user = config.services.postgresql.superUser; + + in { + unitConfig.Documentation = "man:pdns_server(1) man:pdns_control(1)"; + description = "Backplane PowerDNS name server"; + requires = [ + "postgresql.service" + "backplane-dns-config-generator.service" + "backplane-dns.target" + ]; + after = [ + "network.target" + "postgresql.service" + ]; + wantedBy = [ "multi-user.target" ]; + + path = with pkgs; [ postgresql ]; + + serviceConfig = { + Restart="on-failure"; + RestartSec="10"; + StartLimitInterval="0"; + PrivateDevices=true; + # CapabilityBoundingSet="CAP_CHOWN CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_SYS_CHROOT"; + # NoNewPrivileges=true; + ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${powerdns-home}"; + ExecStart = "${pkgs.powerdns}/bin/pdns_server --setuid=backplane-powerdns --setgid=backplane-powerdns --chroot=${powerdns-home} --socket-dir=/ --daemon=no --guardian=no --disable-syslog --write-pid=no --config-dir=${configDir}"; + ProtectSystem="full"; + # ProtectHome=true; + RestrictAddressFamilies="AF_UNIX AF_INET AF_INET6"; + }; + }; + + backplane-dns-config-generator = { + description = "Generate postgres configuration for backplane DNS server."; + requiredBy = [ "backplane-powerdns.service" ]; + requires = cfg.required-services; + serviceConfig.Type = "oneshot"; + restartIfChanged = true; + partOf = [ "backplane-dns.target" ]; + + # This builds the config in a bash script, to avoid storing the password + # in the nix store at any point + script = '' + if [ ! -d ${powerdns-conf-dir} ]; then + mkdir ${powerdns-conf-dir} + fi + + TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d -t pdns-XXXXXXXXXX) + TMPCONF=$TMPDIR/pdns.local.gpgsql.conf + + if [ ! -f ${cfg.database.password-file} ]; then + echo "${cfg.database.password-file} does not exist!" + exit 1 + fi + + touch $TMPCONF + chown backplane-powerdns:backplane-powerdns $TMPCONF + chmod go-rwx $TMPCONF + PASSWORD=$(cat ${cfg.database.password-file}) + echo "launch+=gpgsql" >> $TMPCONF + echo "gpgsql-host=${cfg.database.host}" >> $TMPCONF + echo "gpgsql-dbname=${cfg.database.database}" >> $TMPCONF + echo "gpgsql-user=${cfg.database.username}" >> $TMPCONF + echo "gpgsql-password=$PASSWORD" >> $TMPCONF + echo "gpgsql-dnssec=yes" >> $TMPCONF + + mv $TMPCONF ${powerdns-conf-dir}/pdns.local.gpgsql.conf + + rm -rf $TMPDIR + + exit 0 + ''; + }; + + backplane-dns = { + description = "Fudo DNS Backplane Server"; + restartIfChanged = true; + + serviceConfig = { + ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init"; + ExecStart = "${pkgs.sbcl}/bin/sbcl --load ${launchScript}"; + Restart = "on-failure"; + PIDFile = "/run/backplane-dns.$USERNAME.pid"; + User = cfg.user; + Group = cfg.group; + }; + + environment = { + LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib"; + + FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane.host; + FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane.role; + FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane.password-file; + FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.backplane.database.host; + FUDO_DNS_BACKPLANE_DATABASE_NAME = cfg.backplane.database.database; + FUDO_DNS_BACKPLANE_DATABASE_USERNAME = cfg.backplane.database.username; + FUDO_DNS_BACKPLANE_DATABASE_PASSWORD_FILE = cfg.backplane.database.password-file; + + CL_SOURCE_REGISTRY = lib.concatStringsSep ":" (map (pkg: "${pkg}//") + (lisp-libs ++ [pkgs.backplane-dns])); + }; + + requires = cfg.required-services; + partOf = [ "backplane-dns.target" ]; + wantedBy = [ "multi-user.target" ]; + }; + }; + }; + }; +} diff --git a/config/fudo/client/dns.nix b/config/fudo/client/dns.nix new file mode 100644 index 0000000..c2bcced --- /dev/null +++ b/config/fudo/client/dns.nix @@ -0,0 +1,94 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + cfg = config.fudo.client.dns; + +in { + options.fudo.client.dns = { + enable = mkEnableOption "Enable Fudo DynDNS Client."; + + ipv4 = mkOption { + type = types.bool; + default = true; + description = "Report host external IPv4 address to Fudo DynDNS server."; + }; + + ipv6 = mkOption { + type = types.bool; + default = true; + description = "Report host external IPv6 address to Fudo DynDNS server."; + }; + + domain = mkOption { + type = types.str; + description = "Domain under which this host is registered."; + default = "fudo.link"; + }; + + server = mkOption { + type = types.str; + description = "Backplane DNS server to which changes will be reported."; + default = "backplane.fudo.org"; + }; + + password-file = mkOption { + type = types.str; + description = "File containing host password for backplane."; + example = "/path/to/secret.passwd"; + }; + + frequency = mkOption { + type = types.str; + description = "Frequency at which to report the local IP(s) to backplane."; + default = "*:0/15"; + }; + + user = mkOption { + type = types.str; + description = "User as which to run the client script (must have access to password file)."; + default = "backplane-dns-client"; + }; + + external-interface = mkOption { + type = with types; nullOr str; + description = "Interface with which this host communicates with the larger internet."; + default = null; + }; + }; + + config = mkIf cfg.enable { + users.users = { + "${cfg.user}" = { + isSystemUser = true; + createHome = true; + home = "/var/home/${cfg.user}"; + }; + }; + + systemd = { + timers.backplane-dns-client = { + enable = true; + description = "Report local IP addresses to Fudo backplane."; + partOf = [ "backplane-dns-client.service" ]; + wantedBy = [ "timers.target" ]; + requires = [ "network-online.target" ]; + timerConfig = { + OnCalendar = cfg.frequency; + }; + }; + + services.backplane-dns-client = { + enable = true; + serviceConfig = { + Type = "oneshot"; + StandardOutput = "journal"; + User = cfg.user; + }; + script = '' + ${pkgs.backplane-dns-client}/bin/backplane-dns-client ${optionalString cfg.ipv4 "-4"} ${optionalString cfg.ipv6 "-6"} ${optionalString (cfg.external-interface != null) "--interface=${cfg.external-interface}"} --domain=${cfg.domain} --server=${cfg.server} --password-file=${cfg.password-file} + ''; + }; + }; + }; +} diff --git a/config/fudo/local-network.nix b/config/fudo/local-network.nix index 4cc653a..1b036f2 100644 --- a/config/fudo/local-network.nix +++ b/config/fudo/local-network.nix @@ -7,7 +7,8 @@ let join-lines = concatStringsSep "\n"; - ip = import ../../lib/ip.nix { lib = lib; }; + ip = import ../../lib/ip.nix { inherit lib; }; + dns = import ../../lib/dns.nix { inherit lib; }; hostOpts = { hostname, ... }: { options = { @@ -35,33 +36,6 @@ let traceout = out: builtins.trace out out; - srvRecordOpts = with types; { - options = { - weight = mkOption { - type = int; - description = "Weight relative to other records."; - default = 1; - }; - - priority = mkOption { - type = int; - description = "Priority to give this record."; - default = 0; - }; - - port = mkOption { - type = port; - description = "Port to use when connecting."; - }; - - host = mkOption { - type = str; - description = "Host to contact for this service."; - example = "my-host.my-domain.com."; - }; - }; - }; - in { options.fudo.local-network = { @@ -143,7 +117,7 @@ in { }; srv-records = mkOption { - type = with types; attrsOf (attrsOf (listOf (submodule srvRecordOpts))); + type = dns.srvRecords; description = "Map of traffic type to srv records."; default = {}; example = { @@ -231,12 +205,6 @@ in { hostSshFpRecords = host: data: join-lines (map (sshfp: "${host} IN SSHFP ${sshfp}") data.ssh-fingerprints); cnameRecord = alias: host: "${alias} IN CNAME ${host}"; - makeSrvRecords = protocol: type: records: - join-lines (map (record: "_${type}._${protocol} IN SRV ${toString record.priority} ${toString record.weight} ${toString record.port} ${record.host}.") - records); - - makeSrvProtocolRecords = protocol: types: join-lines (mapAttrsToList (makeSrvRecords protocol) types); - in { enable = true; cacheNetworks = [ cfg.network "localhost" "localnets" ]; @@ -267,7 +235,7 @@ in { ${join-lines (mapAttrsToList hostSshFpRecords cfg.hosts)} ${join-lines (mapAttrsToList cnameRecord cfg.aliases)} ${join-lines cfg.extra-dns-records} - ${join-lines (mapAttrsToList makeSrvProtocolRecords cfg.srv-records)} + ${dns.srvRecordsToBindZone cfg.srv-records} ''; } ] ++ blockZones; diff --git a/config/fudo/password.nix b/config/fudo/password.nix new file mode 100644 index 0000000..8381df1 --- /dev/null +++ b/config/fudo/password.nix @@ -0,0 +1,114 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.fudo.password; + + genOpts = { + options = { + file = mkOption { + type = types.str; + description = "Password file in which to store a generated password."; + }; + + user = mkOption { + type = types.str; + description = "User to which the file should belong."; + }; + + group = mkOption { + type = with types; nullOr str; + description = "Group to which the file should belong."; + default = "nogroup"; + }; + + restart-services = mkOption { + type = with types; listOf str; + description = "List of services to restart when the password file is generated."; + default = []; + }; + }; + }; + + generate-passwd-file = file: user: group: pkgs.writeShellScriptBin "generate-passwd-file.sh" '' + if touch ${file}; then + ${pkgs.pwgen}/bin/pwgen 30 1 > ${file} + else + echo "cannot write to ${file}" + exit 2 + fi + + if [ ! -f ${file} ]; then + echo "Failed to create file ${file}" + exit 3 + fi + + chown ${user}${optionalString (group != null) ":${group}"} ${file} + + if [ $? -ne 0 ]; then + rm ${file} + echo "failed to set permissions on ${file}" + exit 4 + fi + + ${if (group != null) then + "chmod 640 ${file}" + else + "chmod 600 ${file}"} + + echo "created password file ${file}" + exit 0 + ''; + + restart-script = service-name: '' + SYSCTL=${pkgs.systemd}/bin/systemctl + JOBTYPE=$(${pkgs.systemd}/bin/systemctl show ${service-name} -p Type) + if $SYSCTL is-active --quiet ${service-name} || + [ $JOBTYPE == "Type=simple" ] || + [ $JOBTYPE == "Type=oneshot" ] ; then + echo "restarting service ${service-name} because password has changed." + $SYSCTL restart ${service-name} + fi + ''; + + filterForRestarts = filterAttrs (name: opts: opts.restart-services != []); + +in { + options.fudo.password = { + file-generator = mkOption { + type = with types; loaOf (submodule genOpts); + description = "List of password files to generate."; + default = {}; + }; + }; + + config = { + systemd.targets.fudo-passwords = { + description = "Target indicating that all Fudo passwords have been generated."; + wantedBy = [ "default.target" ]; + }; + + systemd.services = fold (a: b: a // b) {} (mapAttrsToList (name: opts: { + "file-generator-${name}" = { + partOf = [ "fudo-passwords.target" ]; + serviceConfig.Type = "oneshot"; + description = "Generate password file for ${name}."; + script = "${generate-passwd-file opts.file opts.user opts.group}/bin/generate-passwd-file.sh"; + }; + + "file-generator-watcher-${name}" = mkIf (! (opts.restart-services == [])) { + description = "Restart services upon regenerating password for ${name}"; + after = [ "file-generator-${name}.service" ]; + partOf = [ "fudo-passwords.target" ]; + serviceConfig.Type = "oneshot"; + script = concatStringsSep "\n" (map restart-script opts.restart-services); + }; + }) cfg.file-generator); + + systemd.paths = mapAttrs' (name: opts: + nameValuePair "file-generator-watcher-${name}" { + partOf = [ "fudo-passwords.target"]; + pathConfig.PathChanged = opts.file; + }) (filterForRestarts cfg.file-generator); + }; +} diff --git a/config/fudo/postgres.nix b/config/fudo/postgres.nix index 4486a11..198db56 100644 --- a/config/fudo/postgres.nix +++ b/config/fudo/postgres.nix @@ -2,23 +2,51 @@ with lib; let - cfg = config.fudo.postgresql; + utils = import ../../lib/utils.nix { inherit lib; }; + + join-lines = lib.concatStringsSep "\n"; + + userDatabaseOpts = { database, ... }: { + options = { + access = mkOption { + type = types.str; + description = "Privileges for user on this database."; + default = "CONNECT"; + }; + + entity-access = mkOption { + type = with types; attrsOf str; + description = "A list of entities mapped to the access this user should have."; + default = {}; + example = { + "TABLE users" = "SELECT,DELETE"; + "ALL SEQUENCES IN public" = "SELECT"; + }; + }; + }; + }; + userOpts = { username, ... }: { options = { - password = mkOption { + password-file = mkOption { type = with types; nullOr str; - description = "The user's (plaintext) password."; + description = "A file containing the user's (plaintext) password."; default = null; }; databases = mkOption { - type = with types; loaOf str; - description = "Map of databases to which this user has access, to the required perms."; + type = with types; attrsOf (submodule userDatabaseOpts); + description = "Map of databases to required database/table perms."; default = {}; example = { - my_database = "ALL PRIVILEGES"; + my_database = { + access = "ALL PRIVILEGES"; + entity-access = { + "ALL TABLES" = "SELECT"; + }; + }; }; }; }; @@ -34,43 +62,68 @@ let }; }; - userDatabaseAccess = user: databases: - mapAttrs' (database: perms: - nameValuePair "DATABASE ${database}" perms) - databases; + filterPasswordedUsers = filterAttrs (user: opts: opts.password-file != null); - stringJoin = joiner: els: - if (length els) == 0 then - "" - else - foldr(lel: rel: "${lel}${joiner}${rel}") (last els) (init els); + password-setter-script = user: password-file: sql-file: '' + unset PASSWORD + if [ ! -f ${password-file} ]; then + echo "file does not exist: ${password-file}" + exit 1 + fi + PASSWORD=$(cat ${password-file}) + echo "setting password for user ${user}" + echo "ALTER USER ${user} ENCRYPTED PASSWORD '$PASSWORD';" >> ${sql-file} + ''; + + passwords-setter-script = users: + pkgs.writeScriptBin "postgres-set-passwords.sh" '' + #!${pkgs.bash}/bin/bash + + if [ $# -ne 1 ]; then + echo "usage: $0 output-file.sql" + exit 1 + fi + + OUTPUT_FILE=$1 + + if [ ! -f $OUTPUT_FILE ]; then + echo "file doesn't exist: $OUTPUT_FILE" + exit 2 + fi + + ${join-lines + (mapAttrsToList + (user: opts: password-setter-script user opts.password-file "$OUTPUT_FILE") + (filterPasswordedUsers users))} + ''; + + userDatabaseAccess = user: databases: + mapAttrs' (database: databaseOpts: + nameValuePair "DATABASE ${database}" databaseOpts.access) + databases; makeEntry = nw: "host all all ${nw} gss include_realm=0 krb_realm=FUDO.ORG"; - makeNetworksEntry = networks: - stringJoin "\n" (map makeEntry networks); - - setPasswordSql = username: attrs: - "ALTER USER ${username} ENCRYPTED PASSWORD '${attrs.password}';"; - - setPasswordsSql = users: - (stringJoin "\n" - (mapAttrsToList (username: attrs: setPasswordSql username attrs) - (filterAttrs (user: attrs: attrs.password != null) users))) + "\n"; + makeNetworksEntry = networks: join-lines (map makeEntry networks); makeLocalUserPasswordEntries = users: - stringJoin "\n" - (mapAttrsToList - (username: attrs: - stringJoin "\n" - (map (db: '' - local ${db} ${username} md5 - host ${db} ${username} 127.0.0.1/16 md5 - host ${db} ${username} ::1/128 md5 - '') (attrNames attrs.databases))) - users); + join-lines (mapAttrsToList + (user: opts: join-lines + (map (db: '' + local ${db} ${user} md5 + host ${db} ${user} 127.0.0.1/16 md5 + host ${db} ${user} ::1/128 md5 + '') (attrNames opts.databases))) + (filterPasswordedUsers users)); + userTableAccessSql = user: entity: access: "GRANT ${access} ON ${entity} TO ${user};"; + userDatabaseAccessSql = user: database: dbOpts: '' + \c ${database} + ${join-lines (mapAttrsToList (userTableAccessSql user) dbOpts.entity-access)} + ''; + userAccessSql = user: userOpts: join-lines (mapAttrsToList (userDatabaseAccessSql user) userOpts.databases); + usersAccessSql = users: join-lines (mapAttrsToList userAccessSql users); in { @@ -106,8 +159,15 @@ in { description = "A map of users to user attributes."; example = { sampleUser = { - password = "some-password"; - databases = [ "sample_user_db" ]; + password-file = "/path/to/password/file"; + databases = { + some_database = { + access = "CONNECT"; + entity-access = { + "TABLE some_table" = "SELECT,UPDATE"; + }; + }; + }; }; }; default = {}; @@ -136,6 +196,13 @@ in { description = "Users able to access the server via local socket."; default = []; }; + + required-services = mkOption { + type = with types; listOf str; + description = "List of services that should run before postgresql."; + default = []; + example = [ "password-generator.service" ]; + }; }; config = mkIf cfg.enable { @@ -166,13 +233,6 @@ in { group = "postgres"; source = cfg.keytab; }; - - "postgresql/private/user-script.sql" = { - mode = "0400"; - user = "postgres"; - group = "postgres"; - text = setPasswordsSql cfg.users; - }; }; }; @@ -187,15 +247,20 @@ in { package = pkgs.postgresql_11_gssapi; enableTCPIP = true; ensureDatabases = mapAttrsToList (name: value: name) cfg.databases; - ensureUsers = mapAttrsToList + ensureUsers = ((mapAttrsToList (username: attrs: { name = username; - ensurePermissions = - #{ "DATABASE ${username}" = "ALL PRIVILEGES"; }; - (userDatabaseAccess username attrs.databases); + ensurePermissions = userDatabaseAccess username attrs.databases; }) - cfg.users; + cfg.users) ++ (flatten (mapAttrsToList + (database: opts: + (map (username: { + name = username; + ensurePermissions = { + "DATABASE ${database}" = "ALL PRIVILEGES"; + }; + }) opts.users)) cfg.databases))); extraConfig = '' krb_server_keyfile = '/etc/postgresql/private/postgres.keytab' @@ -221,15 +286,54 @@ in { # local networks ${makeNetworksEntry cfg.local-networks} ''; - - # initialScript = pkgs.writeText "database-init.sql" '' - # ${setPasswordsSql cfg.users} - # ''; }; - systemd.services.postgresql.postStart = '' - ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${pkgs.postgresql}/bin/psql --port ${toString config.services.postgresql.port} -f /etc/postgresql/private/user-script.sql -d postgres - ${pkgs.coreutils}/bin/chgrp ${cfg.socket-group} ${cfg.socket-directory}/.s.PGSQL* - ''; + systemd = { + + services = { + + postgresql-password-setter = let + passwords-script = passwords-setter-script cfg.users; + password-wrapper-script = pkgs.writeScriptBin "password-script-wrapper.sh" '' + #!${pkgs.bash}/bin/bash + TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d -t postgres-XXXXXXXXXX) + echo "using temp dir $TMPDIR" + PASSWORD_SQL_FILE=$TMPDIR/user-passwords.sql + echo "password file $PASSWORD_SQL_FILE" + touch $PASSWORD_SQL_FILE + chown ${config.services.postgresql.superUser} $PASSWORD_SQL_FILE + chmod go-rwx $PASSWORD_SQL_FILE + ${passwords-script}/bin/postgres-set-passwords.sh $PASSWORD_SQL_FILE + echo "executing $PASSWORD_SQL_FILE" + ${pkgs.postgresql}/bin/psql --port ${toString config.services.postgresql.port} -d postgres -f $PASSWORD_SQL_FILE + echo rm $PASSWORD_SQL_FILE + echo "Postgresql user passwords set."; + exit 0 + ''; + + in { + description = "A service to set postgresql user passwords after the server has started."; + after = [ "postgresql.service" ] ++ cfg.required-services; + requires = [ "postgresql.service" ] ++ cfg.required-services; + serviceConfig = { + Type = "oneshot"; + User = config.services.postgresql.superUser; + }; + script = "${password-wrapper-script}/bin/password-script-wrapper.sh"; + }; + + postgresql.postStart = let + allow-user-login = user: "ALTER ROLE ${user} WITH LOGIN;"; + + extra-settings-sql = pkgs.writeText "settings.sql" '' + ${concatStringsSep "\n" (map allow-user-login (mapAttrsToList (key: val: key) cfg.users))} + ${usersAccessSql cfg.users} + ''; + in '' + ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${pkgs.postgresql}/bin/psql --port ${toString config.services.postgresql.port} -d postgres -f ${extra-settings-sql} + ${pkgs.coreutils}/bin/chgrp ${cfg.socket-group} ${cfg.socket-directory}/.s.PGSQL* + ''; + }; + }; }; } diff --git a/config/local.nix b/config/local.nix index e2fdf34..681da1e 100644 --- a/config/local.nix +++ b/config/local.nix @@ -5,7 +5,9 @@ with lib; imports = [ ./fudo/acme-for-hostname.nix ./fudo/authentication.nix + ./fudo/backplane ./fudo/chat.nix + ./fudo/client/dns.nix ./fudo/common.nix ./fudo/dns.nix ./fudo/git.nix @@ -18,6 +20,7 @@ with lib; ./fudo/minecraft-server.nix ./fudo/netinfo-email.nix ./fudo/node-exporter.nix + ./fudo/password.nix ./fudo/postgres.nix ./fudo/prometheus.nix ./fudo/secure-dns-proxy.nix diff --git a/defaults.nix b/defaults.nix index d6b19cd..4178d9c 100644 --- a/defaults.nix +++ b/defaults.nix @@ -59,6 +59,7 @@ lshw mkpasswd ncurses5 + nixfmt nix-index nix-prefetch-git nmap diff --git a/fudo/profiles/server.nix b/fudo/profiles/server.nix index b99c451..c9d9dd3 100644 --- a/fudo/profiles/server.nix +++ b/fudo/profiles/server.nix @@ -32,7 +32,7 @@ let if [ $# -gt 1 ]; then echo "usage: $0 [timeout]" exit 1 - elif [ $# -eq 1 ]; then + elif [ $# -eq 1 ]; the TIMEOUT=$1 else TIMEOUT=15m diff --git a/hosts/nostromo.nix b/hosts/nostromo.nix index 4779a37..d912e2c 100644 --- a/hosts/nostromo.nix +++ b/hosts/nostromo.nix @@ -1,12 +1,21 @@ { lib, config, pkgs, ... }: +with lib; let hostname = "nostromo"; host-internal-ip = "10.0.0.1"; - inherit (lib.strings) concatStringsSep; in { + environment.systemPackages = with pkgs; [ + dnsproxy + google-photos-uploader + libguestfs-with-appliance + libvirt + powerdns + virtmanager + ]; + boot.kernelModules = [ "kvm-amd" ]; boot.loader.grub.enable = true; @@ -34,7 +43,6 @@ in { dns-serve-ips = [ host-internal-ip "127.0.0.1" "127.0.1.1" ]; # Using a pihole running in docker, see below recursive-resolver = "${host-internal-ip} port 5353"; - # recursive-resolver = "1.1.1.1"; server-ip = host-internal-ip; }; @@ -86,21 +94,150 @@ in { nat = { enable = true; externalInterface = "eno2"; - internalInterfaces = ["intif0"]; + internalInterfaces = [ "intif0" ]; + }; + }; + + users = { + users = { + backplane-powerdns = { + isSystemUser = true; + }; + backplane-dns = { + isSystemUser = true; + }; + fudo-client = { + isSystemUser = true; + }; + }; + + groups = { + backplane-powerdns = { + members = [ "backplane-powerdns" ]; + }; + backplane-dns = { + members = [ "backplane-dns" ]; + }; }; }; fudo = { + password.file-generator = { + dns_backplane_powerdns = { + file = "/srv/backplane/dns/secure/db_powerdns.passwd"; + user = config.services.postgresql.superUser; + group = "backplane-powerdns"; + restart-services = [ + "backplane-dns-config-generator.service" + "postgresql-password-setter.service" + "backplane-powerdns.service" + ]; + }; + dns_backplane_database = { + file = "/srv/backplane/dns/secure/db_backplane.passwd"; + user = config.services.postgresql.superUser; + group = "backplane-dns"; + restart-services = [ + "backplane-dns.service" + "postgresql-password-setter.service" + ]; + }; + }; + + backplane.dns = { + enable = true; + port = 353; + listen-addresses = [ "10.0.0.1" ]; + required-services = [ "fudo-passwords.target" ]; + user = "backplane-dns"; + group = "backplane-dns"; + database = { + username = "backplane_powerdns"; + database = "backplane_dns"; + # Uses an IP to avoid cyclical dependency...not really relevant, but + # whatever + host = "127.0.0.1"; + password-file = "/srv/backplane/dns/secure/db_powerdns.passwd"; + }; + backplane = { + host = "backplane.fudo.org"; + role = "service-dns"; + password-file = "/srv/backplane/dns/secure/backplane.passwd"; + database = { + username = "backplane_dns"; + database = "backplane_dns"; + host = "127.0.0.1"; + password-file = "/srv/backplane/dns/secure/db_backplane.passwd"; + }; + }; + }; + + client.dns = { + enable = true; + ipv4 = true; + ipv6 = true; + domain = "dyn.fudo.org"; + user = "fudo-client"; + external-interface = "eno2"; + password-file = "/srv/client/secure/client.passwd"; + }; + postgresql = { enable = true; ssl-private-key = "/srv/nostromo/certs/private/privkey.pem"; ssl-certificate = "/srv/nostromo/certs/cert.pem"; keytab = "/srv/nostromo/keytabs/postgres.keytab"; + required-services = [ "fudo-passwords.target" ]; local-networks = [ "10.0.0.1/24" "127.0.0.1/8" ]; + + users = { + backplane_powerdns = { + password-file = "/srv/backplane/dns/secure/db_powerdns.passwd"; + databases = { + backplane_dns = { + access = "CONNECT"; + entity-access = { + "ALL TABLES IN SCHEMA public" = "SELECT"; + }; + }; + }; + }; + backplane_dns = { + password-file = "/srv/backplane/dns/secure/db_backplane.passwd"; + databases = { + backplane_dns = { + access = "CONNECT"; + entity-access = { + "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE"; + "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE"; + }; + }; + }; + }; + niten = { + databases = { + backplane_dns = { + access = "ALL PRIVILEGES"; + entity-access = { + "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES"; + "ALL SEQUENCES IN SCHEMA public" = "ALL PRIVILEGES"; + }; + }; + }; + }; + }; + + local-users = ["niten"]; + + databases = { + backplane_dns = { + users = ["niten"]; + }; + }; }; secure-dns-proxy = { @@ -119,13 +256,6 @@ in { }; }; - environment.systemPackages = with pkgs; [ - dnsproxy - libguestfs-with-appliance - libvirt - virtmanager - ]; - virtualisation = { docker = { enable = true; @@ -166,7 +296,7 @@ in { }; services = { - dhcpd6.enable = false; + # dhcpd6.enable = true; nginx = { enable = true; @@ -192,33 +322,5 @@ in { }; }; }; - - # ceph = { - # enable = true; - - # global = { - # clusterName = "sea-data"; - # clusterNetwork = "10.0.10.0/24"; - # fsid = "d443e192-896d-4102-a60f-f8f0777eb2a3"; - # monHost = "10.0.10.2"; - # monInitialMembers = "mon-1"; - # publicNetwork = "10.0.0.0/22"; - # }; - - # mds = { - # enable = true; - # daemons = ["srv-2"]; - # }; - - # mgr = { - # enable = true; - # daemons = ["srv-2"]; - # }; - - # mon = { - # enable = true; - # daemons = ["srv-2"]; - # }; - # }; }; } diff --git a/lib/dns.nix b/lib/dns.nix new file mode 100644 index 0000000..7efaf8c --- /dev/null +++ b/lib/dns.nix @@ -0,0 +1,58 @@ +{ lib }: + +with lib; +let + join-lines = concatStringsSep "\n"; + + makeSrvRecords = protocol: type: records: + join-lines (map (record: + "_${type}._${protocol} IN SRV ${toString record.priority} ${toString record.weight} ${toString record.port} ${record.host}.") + records); + + makeSrvProtocolRecords = protocol: types: join-lines (mapAttrsToList (makeSrvRecords protocol) types); + + srvRecordOpts = with types; { + options = { + weight = mkOption { + type = int; + description = "Weight relative to other records."; + default = 1; + }; + + priority = mkOption { + type = int; + description = "Priority to give this record."; + default = 0; + }; + + port = mkOption { + type = port; + description = "Port to use when connecting."; + }; + + host = mkOption { + type = str; + description = "Host to contact for this service."; + example = "my-host.my-domain.com."; + }; + }; + }; + + srvRecordPair = domain: protocol: type: record: { + "_${type}._${protocol}.${domain}" = "${toString record.priority} ${toString record.weight} ${toString record.port} ${record.host}."; + }; + +in rec { + + srvRecords = with types; attrsOf (attrsOf (listOf (submodule srvRecordOpts))); + + srvRecordsToBindZone = srvRecords: join-lines (mapAttrsToList makeSrvProtocolRecords srvRecords); + + concatMapAttrs = f: attrs: concatMap (x: x) (mapAttrsToList (key: val: f key val) attrs); + + srvRecordsToPairs = domain: srvRecords: + listToAttrs + (concatMapAttrs (protocol: types: + concatMapAttrs (type: records: map (srvRecordPair domain protocol type) records) types) + srvRecords); +} diff --git a/lib/utils.nix b/lib/utils.nix new file mode 100644 index 0000000..6c6a581 --- /dev/null +++ b/lib/utils.nix @@ -0,0 +1,14 @@ +{ lib }: + +with lib; +{ + recursiveMergeAttrs = a: b: let + commonAttrs = intersectLists (attrNames a) (attrNames b); + aAttrs = subtractLists (attrNames a) commonAttrs; + bAttrs = subtractLists (attrNames b) commonAttrs; + aSide = (filterAttrs (k: v: elem k aAttrs) a); + bSide = (filterAttrs (k: v: elem k bAttrs) b); + common = (foldr (a: b: a // b) {} + (map (k: { ${k} = a.${k} // b.${k}; }) commonAttrs)); + in aSide // bSide // common; +} diff --git a/packages/backplane-dns-client.nix b/packages/backplane-dns-client.nix new file mode 100644 index 0000000..ab9dfdc --- /dev/null +++ b/packages/backplane-dns-client.nix @@ -0,0 +1,35 @@ +{ stdenv, fetchgit, pkgs, bundlerEnv }: + +let + url = "https://git.fudo.org/fudo-public/backplane-dns-client.git"; + version = "0.1"; + srcdir = ../static/backplane-dns-client; + gems = bundlerEnv { + name = "backplane-dns-client-env"; + ruby = pkgs.ruby; + gemdir = srcdir; + }; + +in stdenv.mkDerivation { + name = "backplane-dns-client-${version}"; + + src = srcdir; + + buildInputs = [gems pkgs.ruby]; + + phases = ["installPhase"]; + + installPhase = '' + mkdir -p "$out/bin" "$out/lib" + + cp "$src/dns-client.rb" "$out/lib" + + BIN="$out/bin/backplane-dns-client" + + cat > $BIN < e + nil + end + end + @client = nil + end + + def send(msg_content) + msg_id = SecureRandom::uuid + encoded_payload = payload(msg_content, msg_id).to_json + msg = Jabber::Message.new(@service_jid, encoded_payload) + msg.type = :chat + @client.send(msg) + response = receive_response(msg_id) + response and response["status"] == "OK" + end + + def send_ip(ip) + send(ip_payload(ip)) + end + + def payload(req, msg_id) + { + version: 1, + service: :dns, + msgid: msg_id, + payload: req + } + end + + def ip_payload(ip) + { + request: ip.ipv4? ? :change_ipv4 : :change_ipv6, + domain: @domain, + ip: ip.to_s + } + end + + def register_response_callback + @client.add_message_callback do |msg| + enqueue_message(JSON.parse(msg.body)) + end + end + + def enqueue_message(msg) + @responses << msg + end + + def receive_response(msg_id) + msg = @responses.pop + return msg if (msg and (msg["msgid"] == msg_id.to_s)) + raise "failed to receive message: #{msg}" + end +end + +RESERVED_V4_NETWORKS = [ + "0.0.0.0/8", + "10.0.0.0/8", + "100.64.0.0/10", + "127.0.0.0/8", + "169.254.0.0/16", + "172.16.0.0/12", + "192.0.0.0/24", + "192.0.2.0/24", + "192.88.99.0/24", + "192.168.0.0/16", + "198.18.0.0/15", + "198.51.100.0/24", + "203.0.113.0/24", + "224.0.0.0/4", + "240.0.0.0/4", + "255.255.255.255/32" +].map { |ip| IPAddr.new(ip) } + +def public_ip?(ip) + if (ip.ipv4?) + not RESERVED_V4_NETWORKS.any? { |network| network.include? ip } + elsif (ip.ipv6?) + not (ip.link_local? or ip.loopback? or ip.private?) + else + false + end +end + +def to_ipaddr(addrinfo) + if addrinfo.ipv4? + IPAddr.new addrinfo.ip_address + else + IPAddr.new(addrinfo.ip_address.split("%")[0]) + end +end + +def local_addresses + Socket::ip_address_list.map do |addrinfo| + to_ipaddr(addrinfo) + end.select { |ip| public_ip?(ip) } +end + +def interface_addresses(interface) + Socket::getifaddrs.select do |ifaddr| + ifaddr.name == interface + end.select do |ifaddr| + ifaddr.addr.ip? and (ifaddr.flags & Socket::IFF_MULTICAST != 0) + end.map do |ifaddr| + to_ipaddr(ifaddr.addr) + end.filter do |ip| + public_ip? ip + end +end + +client = XMPPClient::new(options[:domain], + Socket::gethostname, + options[:server], + password) + +success = true + +begin + client.connect + + addrs = if options[:interface] + interface_addresses(options[:interface]) + else + local_addresses + end + + if options[:ipv4] + ipv4 = addrs.find { |ip| ip.ipv4? } + if ipv4 + puts "#{options[:server]}: #{Socket::gethostname}.#{options[:domain]} IN A => #{ipv4.to_s}" + if client.send_ip(ipv4) + puts "OK" + else + puts "ERROR" + success = false + end + else + puts "#{options[:server]}: no valid public IPv4 found on the local host" + end + end + + if options[:ipv6] + ipv6 = addrs.find { |ip| ip.ipv6? } + if ipv6 + puts "#{options[:server]}: #{Socket::gethostname}.#{options[:domain]} IN AAAA => #{ipv6.to_s}" + if client.send_ip(ipv6) + puts "OK" + else + puts "ERROR" + success = false + end + else + puts "#{options[:server]}: no valid public IPv6 found on the local host" + end + end +ensure + client.disconnect +end + +exit success ? 0 : 1 diff --git a/static/backplane-dns-client/gemset.nix b/static/backplane-dns-client/gemset.nix new file mode 100644 index 0000000..fc227aa --- /dev/null +++ b/static/backplane-dns-client/gemset.nix @@ -0,0 +1,12 @@ +{ + xmpp4r = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "15ls2yqjvflxrc8chv5pcdh2p1p9fjsky74yc8y7wvw90wz0izrb"; + type = "gem"; + }; + version = "0.5.6"; + }; +} \ No newline at end of file From 80be04edd5abe6fe6ba00b22dc8e829198e59ece Mon Sep 17 00:00:00 2001 From: nostoromo root Date: Tue, 17 Nov 2020 15:23:07 -0800 Subject: [PATCH 13/15] Working backplane dns client/server --- config/fudo/client/dns.nix | 11 +- hosts/nostromo.nix | 124 ---------------------- static/backplane-dns-client/dns-client.rb | 41 +++++++ 3 files changed, 51 insertions(+), 125 deletions(-) diff --git a/config/fudo/client/dns.nix b/config/fudo/client/dns.nix index c2bcced..7a9d348 100644 --- a/config/fudo/client/dns.nix +++ b/config/fudo/client/dns.nix @@ -20,6 +20,12 @@ in { description = "Report host external IPv6 address to Fudo DynDNS server."; }; + sshfp = mkOption { + type = types.bool; + default = true; + description = "Report host SSH fingerprints to the Fudo DynDNS server."; + }; + domain = mkOption { type = types.str; description = "Domain under which this host is registered."; @@ -55,6 +61,8 @@ in { description = "Interface with which this host communicates with the larger internet."; default = null; }; + + # FIXME: take the relevant SSH package }; config = mkIf cfg.enable { @@ -85,8 +93,9 @@ in { StandardOutput = "journal"; User = cfg.user; }; + path = [ pkgs.openssh ]; script = '' - ${pkgs.backplane-dns-client}/bin/backplane-dns-client ${optionalString cfg.ipv4 "-4"} ${optionalString cfg.ipv6 "-6"} ${optionalString (cfg.external-interface != null) "--interface=${cfg.external-interface}"} --domain=${cfg.domain} --server=${cfg.server} --password-file=${cfg.password-file} + ${pkgs.backplane-dns-client}/bin/backplane-dns-client ${optionalString cfg.ipv4 "-4"} ${optionalString cfg.ipv6 "-6"} ${optionalString cfg.sshfp "-f"} ${optionalString (cfg.external-interface != null) "--interface=${cfg.external-interface}"} --domain=${cfg.domain} --server=${cfg.server} --password-file=${cfg.password-file} ''; }; }; diff --git a/hosts/nostromo.nix b/hosts/nostromo.nix index d912e2c..22a1675 100644 --- a/hosts/nostromo.nix +++ b/hosts/nostromo.nix @@ -100,146 +100,22 @@ in { users = { users = { - backplane-powerdns = { - isSystemUser = true; - }; - backplane-dns = { - isSystemUser = true; - }; fudo-client = { isSystemUser = true; }; }; - - groups = { - backplane-powerdns = { - members = [ "backplane-powerdns" ]; - }; - backplane-dns = { - members = [ "backplane-dns" ]; - }; - }; }; fudo = { - password.file-generator = { - dns_backplane_powerdns = { - file = "/srv/backplane/dns/secure/db_powerdns.passwd"; - user = config.services.postgresql.superUser; - group = "backplane-powerdns"; - restart-services = [ - "backplane-dns-config-generator.service" - "postgresql-password-setter.service" - "backplane-powerdns.service" - ]; - }; - dns_backplane_database = { - file = "/srv/backplane/dns/secure/db_backplane.passwd"; - user = config.services.postgresql.superUser; - group = "backplane-dns"; - restart-services = [ - "backplane-dns.service" - "postgresql-password-setter.service" - ]; - }; - }; - - backplane.dns = { - enable = true; - port = 353; - listen-addresses = [ "10.0.0.1" ]; - required-services = [ "fudo-passwords.target" ]; - user = "backplane-dns"; - group = "backplane-dns"; - database = { - username = "backplane_powerdns"; - database = "backplane_dns"; - # Uses an IP to avoid cyclical dependency...not really relevant, but - # whatever - host = "127.0.0.1"; - password-file = "/srv/backplane/dns/secure/db_powerdns.passwd"; - }; - backplane = { - host = "backplane.fudo.org"; - role = "service-dns"; - password-file = "/srv/backplane/dns/secure/backplane.passwd"; - database = { - username = "backplane_dns"; - database = "backplane_dns"; - host = "127.0.0.1"; - password-file = "/srv/backplane/dns/secure/db_backplane.passwd"; - }; - }; - }; - client.dns = { enable = true; ipv4 = true; ipv6 = true; - domain = "dyn.fudo.org"; user = "fudo-client"; external-interface = "eno2"; password-file = "/srv/client/secure/client.passwd"; }; - postgresql = { - enable = true; - ssl-private-key = "/srv/nostromo/certs/private/privkey.pem"; - ssl-certificate = "/srv/nostromo/certs/cert.pem"; - keytab = "/srv/nostromo/keytabs/postgres.keytab"; - required-services = [ "fudo-passwords.target" ]; - - local-networks = [ - "10.0.0.1/24" - "127.0.0.1/8" - ]; - - users = { - backplane_powerdns = { - password-file = "/srv/backplane/dns/secure/db_powerdns.passwd"; - databases = { - backplane_dns = { - access = "CONNECT"; - entity-access = { - "ALL TABLES IN SCHEMA public" = "SELECT"; - }; - }; - }; - }; - backplane_dns = { - password-file = "/srv/backplane/dns/secure/db_backplane.passwd"; - databases = { - backplane_dns = { - access = "CONNECT"; - entity-access = { - "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE"; - "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE"; - }; - }; - }; - }; - niten = { - databases = { - backplane_dns = { - access = "ALL PRIVILEGES"; - entity-access = { - "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES"; - "ALL SEQUENCES IN SCHEMA public" = "ALL PRIVILEGES"; - }; - }; - }; - }; - }; - - local-users = ["niten"]; - - databases = { - backplane_dns = { - users = ["niten"]; - }; - }; - }; - secure-dns-proxy = { enable = true; port = 3535; diff --git a/static/backplane-dns-client/dns-client.rb b/static/backplane-dns-client/dns-client.rb index 34570a1..d2fca16 100644 --- a/static/backplane-dns-client/dns-client.rb +++ b/static/backplane-dns-client/dns-client.rb @@ -42,6 +42,11 @@ OptionParser.new do |opts| "Check for a public IPv6 and register with the backplane.") do options[:ipv6] = true end + + opts.on("-f", "--sshfp", + "Register host SSH key fingerprints with the backplane.") do + options[:sshfp] = true + end end.parse! def error(msg) @@ -98,10 +103,12 @@ class XMPPClient def send(msg_content) msg_id = SecureRandom::uuid encoded_payload = payload(msg_content, msg_id).to_json + puts "payload: #{encoded_payload}" msg = Jabber::Message.new(@service_jid, encoded_payload) msg.type = :chat @client.send(msg) response = receive_response(msg_id) + puts "response: #{response}" response and response["status"] == "OK" end @@ -109,6 +116,10 @@ class XMPPClient send(ip_payload(ip)) end + def send_sshfp(fps) + send(sshfp_payload(fps)) + end + def payload(req, msg_id) { version: 1, @@ -126,6 +137,14 @@ class XMPPClient } end + def sshfp_payload(fp) + { + request: :change_sshfp, + domain: @domain, + sshfp: fp + } + end + def register_response_callback @client.add_message_callback do |msg| enqueue_message(JSON.parse(msg.body)) @@ -198,6 +217,13 @@ def interface_addresses(interface) end end +def host_sshfp + keys = `ssh-keygen -r hostname`.split("\n").map do |k| + k.match(/[0-9] [0-9] [a-fA-F0-9]{32,64}$/)[0] + end + keys.compact +end + client = XMPPClient::new(options[:domain], Socket::gethostname, options[:server], @@ -243,6 +269,21 @@ begin puts "#{options[:server]}: no valid public IPv6 found on the local host" end end + + if options[:sshfp] + fps = host_sshfp + if not fps.empty? + puts "#{options[:server]}: #{Socket::gethostname}.#{options[:domain]} IN SSHFP => #{fps}" + if client.send_sshfp(fps) + puts "OK" + else + puts "ERROR" + success = false + end + else + puts "#{options[:server]}: no valid sshfps found" + end + end ensure client.disconnect end From 9c92bb7e80334c5dd57197373eb9ee584c2ae17b Mon Sep 17 00:00:00 2001 From: root Date: Tue, 17 Nov 2020 17:29:44 -0600 Subject: [PATCH 14/15] france as a dns backplane server --- config/fudo/backplane/dns.nix | 83 ++++++++++++------------ config/fudo/password.nix | 18 +++--- config/fudo/webmail.nix | 7 +- defaults.nix | 15 +++++ fudo/selby.ca.nix | 81 +++++++++++++++++++++++ hosts/france.nix | 84 ++++++++++++++++++------ hosts/france/backplane.nix | 117 ++++++++++++++++++++++++++++++++++ packages/backplane-dns.nix | 4 +- static/backplane-auth.scm | 4 +- 9 files changed, 339 insertions(+), 74 deletions(-) create mode 100644 fudo/selby.ca.nix create mode 100644 hosts/france/backplane.nix diff --git a/config/fudo/backplane/dns.nix b/config/fudo/backplane/dns.nix index beeeaa3..e6dee50 100644 --- a/config/fudo/backplane/dns.nix +++ b/config/fudo/backplane/dns.nix @@ -191,17 +191,22 @@ in { }; }; - backplane-dns-config-generator = { - description = "Generate postgres configuration for backplane DNS server."; - requiredBy = [ "backplane-powerdns.service" ]; - requires = cfg.required-services; - serviceConfig.Type = "oneshot"; - restartIfChanged = true; - partOf = [ "backplane-dns.target" ]; + backplane-dns-config-generator = { + description = "Generate postgres configuration for backplane DNS server."; + requiredBy = [ "backplane-powerdns.service" ]; + requires = cfg.required-services; + serviceConfig.Type = "oneshot"; + restartIfChanged = true; + partOf = [ "backplane-dns.target" ]; - # This builds the config in a bash script, to avoid storing the password - # in the nix store at any point - script = '' + preStart = '' + mkdir -p ${powerdns-conf-dir} + chown backplane-powerdns:backplane-powerdns ${powerdns-conf-dir} + ''; + + # This builds the config in a bash script, to avoid storing the password + # in the nix store at any point + script = '' if [ ! -d ${powerdns-conf-dir} ]; then mkdir ${powerdns-conf-dir} fi @@ -231,40 +236,40 @@ in { exit 0 ''; + }; + + backplane-dns = { + description = "Fudo DNS Backplane Server"; + restartIfChanged = true; + + serviceConfig = { + ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init"; + ExecStart = "${pkgs.sbcl}/bin/sbcl --load ${launchScript}"; + Restart = "on-failure"; + PIDFile = "/run/backplane-dns.$USERNAME.pid"; + User = cfg.user; + Group = cfg.group; }; - backplane-dns = { - description = "Fudo DNS Backplane Server"; - restartIfChanged = true; + environment = { + LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib"; - serviceConfig = { - ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init"; - ExecStart = "${pkgs.sbcl}/bin/sbcl --load ${launchScript}"; - Restart = "on-failure"; - PIDFile = "/run/backplane-dns.$USERNAME.pid"; - User = cfg.user; - Group = cfg.group; - }; + FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane.host; + FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane.role; + FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane.password-file; + FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.backplane.database.host; + FUDO_DNS_BACKPLANE_DATABASE_NAME = cfg.backplane.database.database; + FUDO_DNS_BACKPLANE_DATABASE_USERNAME = cfg.backplane.database.username; + FUDO_DNS_BACKPLANE_DATABASE_PASSWORD_FILE = cfg.backplane.database.password-file; - environment = { - LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib"; - - FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane.host; - FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane.role; - FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane.password-file; - FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.backplane.database.host; - FUDO_DNS_BACKPLANE_DATABASE_NAME = cfg.backplane.database.database; - FUDO_DNS_BACKPLANE_DATABASE_USERNAME = cfg.backplane.database.username; - FUDO_DNS_BACKPLANE_DATABASE_PASSWORD_FILE = cfg.backplane.database.password-file; - - CL_SOURCE_REGISTRY = lib.concatStringsSep ":" (map (pkg: "${pkg}//") - (lisp-libs ++ [pkgs.backplane-dns])); - }; - - requires = cfg.required-services; - partOf = [ "backplane-dns.target" ]; - wantedBy = [ "multi-user.target" ]; + CL_SOURCE_REGISTRY = lib.concatStringsSep ":" (map (pkg: "${pkg}//") + (lisp-libs ++ [pkgs.backplane-dns])); }; + + requires = cfg.required-services; + partOf = [ "backplane-dns.target" ]; + wantedBy = [ "multi-user.target" ]; + }; }; }; }; diff --git a/config/fudo/password.nix b/config/fudo/password.nix index 8381df1..2322d9e 100644 --- a/config/fudo/password.nix +++ b/config/fudo/password.nix @@ -31,7 +31,15 @@ let }; generate-passwd-file = file: user: group: pkgs.writeShellScriptBin "generate-passwd-file.sh" '' + mkdir -p $(dirname ${file}) + if touch ${file}; then + chown ${user}${optionalString (group != null) ":${group}"} ${file} + if [ $? -ne 0 ]; then + rm ${file} + echo "failed to set permissions on ${file}" + exit 4 + fi ${pkgs.pwgen}/bin/pwgen 30 1 > ${file} else echo "cannot write to ${file}" @@ -43,14 +51,6 @@ let exit 3 fi - chown ${user}${optionalString (group != null) ":${group}"} ${file} - - if [ $? -ne 0 ]; then - rm ${file} - echo "failed to set permissions on ${file}" - exit 4 - fi - ${if (group != null) then "chmod 640 ${file}" else @@ -90,10 +90,12 @@ in { systemd.services = fold (a: b: a // b) {} (mapAttrsToList (name: opts: { "file-generator-${name}" = { + enable = true; partOf = [ "fudo-passwords.target" ]; serviceConfig.Type = "oneshot"; description = "Generate password file for ${name}."; script = "${generate-passwd-file opts.file opts.user opts.group}/bin/generate-passwd-file.sh"; + reloadIfChanged = true; }; "file-generator-watcher-${name}" = mkIf (! (opts.restart-services == [])) { diff --git a/config/fudo/webmail.nix b/config/fudo/webmail.nix index cf93502..9c8a5cd 100644 --- a/config/fudo/webmail.nix +++ b/config/fudo/webmail.nix @@ -329,6 +329,7 @@ in { wantedBy = [ "multi-user.target" ]; description = "Change ownership of the phpfpm socket for webmail once it's started."; requires = [ "phpfpm-webmail.service" ]; + after = [ "phpfpm.target" ]; serviceConfig = { ExecStart = '' ${pkgs.coreutils}/bin/chown ${webmail-user}:${webmail-group} ${config.services.phpfpm.pools.webmail.socket} @@ -337,8 +338,10 @@ in { }; nginx = { - requires = [ "webmail-init.service" ]; - wantedBy = [ "phpfpm-webmail-socket-perm.service" ]; + requires = [ + "webmail-init.service" + "phpfpm-webmail-socket-perm.service" + ]; }; }; }; diff --git a/defaults.nix b/defaults.nix index 89e36e1..daa666a 100644 --- a/defaults.nix +++ b/defaults.nix @@ -222,4 +222,19 @@ }; }; + systemd.services.fudo-environment-init = { + enable = true; + description = "Fudo common settings."; + wantedBy = [ "default.target" ]; + + # Careful, this WILL run many times + script = '' + # Create a directory for system user homedirs if it doesn't already exist + if [ ! -d /var/home ]; then + mkdir -p /var/home + chmod +x /var/home + fi + ''; + }; + } diff --git a/fudo/selby.ca.nix b/fudo/selby.ca.nix new file mode 100644 index 0000000..8d62144 --- /dev/null +++ b/fudo/selby.ca.nix @@ -0,0 +1,81 @@ +{ host_ipv4, config }: + +{ + dnssec = true; + + mx = ["mail.fudo.org"]; + + hosts = { + forum = { + ip-addresses = [ "208.81.3.117" ]; + }; + }; + + default-host = "208.81.3.117"; + + srv-records = { + tcp = { + domain = [{ + host = "ns1.fudo.org"; + port = "53"; + }]; + ssh = [{ + host = "france.fudo.org"; + port = 22; + }]; + submission = [{ + host = "mail.fudo.org"; + port = 587; + }]; + kerberos = [{ + host = "auth.fudo.org"; + port = 88; + }]; + imaps = [{ + host = "mail.fudo.org"; + port = 993; + }]; + pop3s = [{ + host = "mail.fudo.org"; + port = 995; + }]; + http = [{ + host = "forum.selby.ca"; + port = 80; + }]; + https = [{ + host = "forum.selby.ca"; + port = 80; + }]; + }; + udp = { + domain = [{ + host = "auth.fudo.org"; + port = 53; + }]; + kerberos = [{ + host = "auth.fudo.org"; + port = 88; + }]; + }; + }; + + aliases = { + pop = "mail.fudo.org."; + smtp = "mail.fudo.org."; + imap = "mail.fudo.org."; + mail = "mail.fudo.org."; + ns1 = "ns1.fudo.org."; + ns2 = "ns2.fudo.org."; + webmail = "france.fudo.org."; + forum = "frankfurt.fudo.org."; + }; + + extra-dns-records = [ + ''_kerberos IN TXT "FUDO.ORG"'' + ''@ IN TXT "v=spf1 mx ip4:${host_ipv4}/29 -all"'' + ''@ IN SPF "v=spf1 mx ip4:${host_ipv4}/29 -all"'' + ]; + + dmarc-report-address = "dmarc-report@selby.ca"; +} diff --git a/hosts/france.nix b/hosts/france.nix index 72f8aec..73c6e46 100644 --- a/hosts/france.nix +++ b/hosts/france.nix @@ -32,6 +32,7 @@ in { ../hardware-configuration.nix ../defaults.nix ./france/jabber.nix + ./france/backplane.nix ]; environment.systemPackages = with pkgs; [ @@ -42,15 +43,6 @@ in { tshark ]; - # services.openssh = { - # listenAddresses = [ - # { - # addr = host_ipv4; - # port = 22; - # } - # ]; - # }; - fudo.common = { # Sets some server-common settings. See /etc/nixos/fudo/profiles/... profile = "server"; @@ -122,44 +114,94 @@ in { users = { fudo_git = { - password = fileContents "/srv/git/secure/db.passwd"; + password-file = "/srv/git/secure/db.passwd"; databases = { - fudo_git = "ALL PRIVILEGES"; + fudo_git = { + access = "CONNECT"; + entity-access = { + "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE"; + "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE"; + }; + }; }; }; grafana = { - password = fileContents "/srv/grafana/secure/db.passwd"; + password-file = "/srv/grafana/secure/db.passwd"; databases = { - grafana = "ALL PRIVILEGES"; + grafana = { + access = "CONNECT"; + entity-access = { + "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE"; + "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE"; + }; + }; }; }; mattermost = { - password = fileContents "/srv/mattermost/secure/db.passwd"; + password-file = "/srv/mattermost/secure/db.passwd"; databases = { - mattermost = "ALL PRIVILEGES"; + mattermost = { + access = "CONNECT"; + entity-access = { + "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE"; + "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE"; + }; + }; }; }; webmail = { - password = fileContents "/srv/webmail/secure/db.passwd"; + password-file = "/srv/webmail/secure/db.passwd"; databases = { - webmail = "ALL PRIVILEGES"; + webmail = { + access = "CONNECT"; + entity-access = { + "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE"; + "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE"; + }; + }; }; }; niten = {}; }; local-users = [ + "niten" "fudo_git" ]; databases = { - fudo_git = ["niten"]; - grafana = ["niten"]; - mattermost = ["niten"]; - webmail = ["niten"]; + fudo_git = { + users = ["niten"]; + }; + grafana = { + users = ["niten"]; + }; + mattermost = { + users = ["niten"]; + }; + webmail = { + users = ["niten"]; + }; }; }; + # fudo.dns = { + # enable = true; + + # dns-hosts = { + # "ns1.fudo.org" = host_ipv4; + # "ns2.fudo.org" = ""; + # }; + + # listen-ips = [host_ipv4]; + + # domains = { + # "selby.ca" = import ../fudo.org/selby.ca.nix { + # inherit host_ipv4 config; + # }; + # }; + # }; + # Not all users need access to france; don't allow LDAP-user access. fudo.authentication.enable = false; diff --git a/hosts/france/backplane.nix b/hosts/france/backplane.nix new file mode 100644 index 0000000..8ce0ca8 --- /dev/null +++ b/hosts/france/backplane.nix @@ -0,0 +1,117 @@ +{ pkgs, lib, config, ... }: + +with lib; +let + +in { + config = { + users = { + users = { + backplane-powerdns = { + isSystemUser = true; + }; + backplane-dns = { + isSystemUser = true; + }; + }; + + groups = { + backplane-powerdns = { + members = [ "backplane-powerdns" ]; + }; + backplane-dns = { + members = [ "backplane-dns" ]; + }; + }; + }; + + fudo = { + password.file-generator = { + dns_backplane_powerdns = { + file = "/srv/backplane/dns/secure/db_powerdns.passwd"; + user = config.services.postgresql.superUser; + group = "backplane-powerdns"; + restart-services = [ + "backplane-dns-config-generator.service" + "postgresql-password-setter.service" + "backplane-powerdns.service" + ]; + }; + dns_backplane_database = { + file = "/srv/backplane/dns/secure/db_backplane.passwd"; + user = config.services.postgresql.superUser; + group = "backplane-dns"; + restart-services = [ + "backplane-dns.service" + "postgresql-password-setter.service" + ]; + }; + }; + + postgresql = { + enable = true; + required-services = [ "fudo-passwords.target" ]; + + users = { + backplane_powerdns = { + password-file = "/srv/backplane/dns/secure/db_powerdns.passwd"; + databases = { + backplane_dns = { + access = "CONNECT"; + entity-access = { + "ALL TABLES IN SCHEMA public" = "SELECT"; + }; + }; + }; + }; + backplane_dns = { + password-file = "/srv/backplane/dns/secure/db_backplane.passwd"; + databases = { + backplane_dns = { + access = "CONNECT"; + entity-access = { + "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE"; + "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE"; + }; + }; + }; + }; + }; + + databases = { + backplane_dns = { + users = ["niten"]; + }; + }; + }; + + backplane.dns = { + enable = true; + port = 353; + listen-addresses = [ "208.81.3.117" ]; + required-services = [ "fudo-passwords.target" ]; + user = "backplane-dns"; + group = "backplane-dns"; + database = { + username = "backplane_powerdns"; + database = "backplane_dns"; + # Uses an IP to avoid cyclical dependency...not really relevant, but + # whatever + host = "127.0.0.1"; + password-file = "/srv/backplane/dns/secure/db_powerdns.passwd"; + }; + backplane = { + host = "backplane.fudo.org"; + role = "service-dns"; + password-file = "/srv/backplane/dns/secure/backplane.passwd"; + database = { + username = "backplane_dns"; + database = "backplane_dns"; + host = "127.0.0.1"; + password-file = "/srv/backplane/dns/secure/db_backplane.passwd"; + }; + }; + }; + }; + }; +} diff --git a/packages/backplane-dns.nix b/packages/backplane-dns.nix index 0f437e6..85287bb 100644 --- a/packages/backplane-dns.nix +++ b/packages/backplane-dns.nix @@ -9,8 +9,8 @@ in stdenv.mkDerivation { src = fetchgit { url = url; - rev = "bfad36c9d223c7c8949fab50424c32a11164cd3a"; - sha256 = "0s8g5cm9mdjr9wb8w6a8lc1dv5cg85hxp8bdcgr1xd6hs4fnr745"; + rev = "543df72f3962cf91b0e0508d15cdc083a3cd7ed4"; + sha256 = "0hda1wjf9wd4rvxchdlxw0af3i2cvl5plg37ric3ckma6gfzkmm0"; fetchSubmodules = false; }; diff --git a/static/backplane-auth.scm b/static/backplane-auth.scm index f23ee80..e017da5 100644 --- a/static/backplane-auth.scm +++ b/static/backplane-auth.scm @@ -16,8 +16,8 @@ (format (current-error-port "FUDO_SERVICE_PASSWD_FILE not set~%")) (exit 1)) -(define host-regex "^host-([a-zA-Z][a-zA-Z0-9_-]+)$") -(define service-regex "^service-([a-zA-Z][a-zA-Z0-9_-]+)$") +(define host-regex "^host-([a-zA-Z][a-zA-Z0-9_-]+)") +(define service-regex "^service-([a-zA-Z][a-zA-Z0-9_-]+)") (define (make-verifier passwd-file) (let ((passwds (load passwd-file))) From 74784a28d74d703c9783c9ca5aa0772c7e3d10c9 Mon Sep 17 00:00:00 2001 From: nostoromo root Date: Wed, 18 Nov 2020 10:48:40 -0800 Subject: [PATCH 15/15] switch to minimal hostname --- static/backplane-dns-client/dns-client.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/static/backplane-dns-client/dns-client.rb b/static/backplane-dns-client/dns-client.rb index d2fca16..0688cdf 100644 --- a/static/backplane-dns-client/dns-client.rb +++ b/static/backplane-dns-client/dns-client.rb @@ -224,8 +224,12 @@ def host_sshfp keys.compact end +def hostname + Socket.gethostname.split(".").first +end + client = XMPPClient::new(options[:domain], - Socket::gethostname, + hostname, options[:server], password) @@ -243,7 +247,7 @@ begin if options[:ipv4] ipv4 = addrs.find { |ip| ip.ipv4? } if ipv4 - puts "#{options[:server]}: #{Socket::gethostname}.#{options[:domain]} IN A => #{ipv4.to_s}" + puts "#{options[:server]}: #{hostname}.#{options[:domain]} IN A => #{ipv4.to_s}" if client.send_ip(ipv4) puts "OK" else @@ -258,7 +262,7 @@ begin if options[:ipv6] ipv6 = addrs.find { |ip| ip.ipv6? } if ipv6 - puts "#{options[:server]}: #{Socket::gethostname}.#{options[:domain]} IN AAAA => #{ipv6.to_s}" + puts "#{options[:server]}: #{hostname}.#{options[:domain]} IN AAAA => #{ipv6.to_s}" if client.send_ip(ipv6) puts "OK" else @@ -273,7 +277,7 @@ begin if options[:sshfp] fps = host_sshfp if not fps.empty? - puts "#{options[:server]}: #{Socket::gethostname}.#{options[:domain]} IN SSHFP => #{fps}" + puts "#{options[:server]}: #{hostname}.#{options[:domain]} IN SSHFP => #{fps}" if client.send_sshfp(fps) puts "OK" else