hswaw/machines/customs: check in code.hackerspace.pl/vuko/customs

Change-Id: Ic698cce2ef0060a54b195cf90574696b8be1eb0f
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1162
Reviewed-by: informatic <informatic@hackerspace.pl>
diff --git a/hswaw/machines/customs.hackerspace.pl/configuration.nix b/hswaw/machines/customs.hackerspace.pl/configuration.nix
new file mode 100644
index 0000000..c00debb
--- /dev/null
+++ b/hswaw/machines/customs.hackerspace.pl/configuration.nix
@@ -0,0 +1,578 @@
+{ config, pkgs, ... }:
+
+let
+  # hscloud checkout, hscloud.routing used to set up dynamic routing (OSPFv6 via bird)
+  hscloud = fetchGit {
+    url = "https://gerrit.hackerspace.pl/hscloud.git";
+    name = "hscloud";
+    rev = "e401735fdd241b25dac4cb82d828dcfa6f84b198";
+  };
+
+  hw = builtins.fromJSON (builtins.readFile ./hw.json);
+  fw = import ./fw-7535.nix;
+  vuko-pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFhaCaC/CVYv6hphqmEdKaPrIn+Q946+myvL9SSnzFZk vuko@eagle";
+  q3k-pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG599UildOrAq+LIOQjKqtGMwjgjIxozI1jtQQRKHtCP q3k@mimeomia";
+  networks = {
+    uplink = {
+      description = "Hackerspace Internet Uplink";
+      hw_addr = builtins.elemAt fw.hw_addresses 0;
+      ipv4 = "185.236.240.5";
+      ipv6 = "2a0d:eb00:2137:1::3";
+    };
+    lan = {
+      description = "Hackerspace LAN";
+      hw_addr = builtins.elemAt fw.hw_addresses 1;
+      ipv4 = "10.8.1.2";
+      ipv6 = "2a0d:eb00:4242::1";
+    };
+    managment = {
+      description = "Management network (temporary routing)";
+      hw_addr = builtins.elemAt fw.hw_addresses 2;
+    };
+    lte = {
+      description = "temp LTE uplink";
+      hw_addr = builtins.elemAt fw.hw_addresses 3;
+    };
+    vpn = {
+      description = "Hackerspace members vpn";
+      ipv4 = "10.9.1.1";
+    };
+  };
+  hostname = "customs";
+  openvpn-auth = import ./openvpn-auth { inherit pkgs; };
+  secrets-path = "/etc/nixos/secrets/";
+  update_authorized_keys = pkgs.writeShellScriptBin "update_authorized_keys" ''
+    ${pkgs.python3.withPackages (pp: [ pp.ldap3 ])}/bin/python ${./update_authorized_keys.py} ${hostname} ${secrets-path}/ldap-password.txt
+  '';
+
+in {
+  imports =
+    [
+      ./ulogd2/service.nix
+      #./hardware-configuration.nix
+      <nixpkgs/nixos/modules/profiles/minimal.nix>
+      <nixpkgs/nixos/modules/profiles/all-hardware.nix>
+      "${hscloud}/bgpwtf/machines/modules/routing.nix"
+      ./checkinator-tracker.nix
+      ./checkinator-web.nix
+      ./mikrotik-exporter.nix
+      ./netboot.nix
+      ./laserproxy/service.nix
+    ];
+
+
+  boot.loader.grub.enable = true;
+  boot.loader.grub.version = 2;
+  boot.loader.grub.device = "nodev";
+  boot.loader.grub.extraConfig = ''
+    serial --unit=0 --speed=115200
+    terminal_input serial
+    terminal_output serial
+  '';
+  boot.kernelParams = ["console=tty0" "console=ttyS0,115200"];
+
+  time.timeZone = "Europe/Warsaw";
+
+  fileSystems."/" = {
+    device = "/dev/disk/by-partuuid/${hw.rootUUID}";
+    fsType = "ext4";
+  };
+
+  services.postfix = let acme_dir = "/var/lib/acme"; in {
+    enable = true;
+    domain = "customs.hackerspace.pl";
+    hostname = "customs.hackerspace.pl";
+    destination = [ "localhost" ];
+    sslCert = "${acme_dir}/customs.hackerspace.pl/full.pem";
+    sslKey = "${acme_dir}/customs.hackerspace.pl/key.pem";
+    enableSmtp = true;
+    enableSubmission = false;
+    #relayHost = "hackerspace.pl";
+    extraConfig = ''
+      inet_interfaces = loopback-only
+    '';
+  };
+
+  fileSystems."/mnt/secrets" = {
+    fsType = "tmpfs";
+    options = [ "rw" "mode=755" "size=200M" "nosuid" "nodev" "relatime" "noexec" ];
+  };
+
+  networking.hostName = hostname;
+  networking.domain = "hackerspace.pl";
+
+  networking.useDHCP = false;
+  networking.vlans = {
+    laser = {
+      id = 4001;
+      interface = "lan";
+    };
+    bms = {
+      id = 4002;
+      interface = "lan";
+    };
+  };
+
+  systemd.services.secrets = {
+    enable = true;
+    description = "Copy secrets and fix permissions";
+    script = ''
+      ${pkgs.coreutils}/bin/install --owner=root --mode=700 --directory /mnt/secrets/nginx/
+      ${pkgs.coreutils}/bin/install --owner=root --mode=400 -t /mnt/secrets/nginx/ \
+        ${secrets-path}/nginx/at.hackerspace.pl.key \
+        ${secrets-path}/nginx/at.hackerspace.pl.crt
+      ${pkgs.acl}/bin/setfacl -m "u:nginx:rx" /mnt/secrets/nginx
+      ${pkgs.acl}/bin/setfacl -m "u:nginx:r" /mnt/secrets/nginx/*
+    '';
+    wantedBy = [ "nginx.service" ];
+    partOf = [ "nginx.service" ];
+    serviceConfig.Type = "oneshot";
+    serviceConfig.RemainAfterExit = "true";
+    serviceConfig.User = "root";
+  };
+
+  services.prometheus.exporters.node = {
+    enable = true;
+    listenAddress = "[::1]";
+    port = 9100;
+    enabledCollectors = [ "systemd" ];
+  };
+
+  systemd.network.links = builtins.listToAttrs (map (
+    name: { name = "10-link-${name}"; value = {
+      enable = true;
+      matchConfig = {
+        MACAddress = networks."${name}".hw_addr;
+      };
+      linkConfig = {
+        Name = "${name}";
+      };
+    }; }
+  ) (builtins.filter (name: builtins.hasAttr "hw_addr" networks."${name}") (builtins.attrNames networks)));
+
+  #networking.interfaces.vpn = {
+  #  virtual = true;
+  #  name = "vpn";
+  #  #ipv4.addresses = [ { address = 10.9.1.1; prefixlen = 16; } ];
+  #};
+
+  boot.kernel.sysctl = {
+    "net.ipv4.ip_forward" = true;
+    "net.ipv6.conf.all.forwarding" = true;
+  };
+
+  # using nftables so firewall has to be disabled
+  networking.firewall.enable = false;
+  networking.nftables.enable = true;
+  networking.nftables.ruleset = ''
+    table inet filter {
+      chain input {
+        type filter hook input priority 0;
+    
+        # accept any localhost traffic
+        iifname lo accept
+    
+        # accept traffic originated from us
+        ct state {established, related} accept
+    
+        # ICMP
+        ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, mld-listener-query, nd-router-solicit } accept
+        ip protocol icmp icmp type { destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept
+    
+        # allow "ping"
+        ip6 nexthdr icmpv6 icmpv6 type echo-request accept
+        ip protocol icmp icmp type echo-request accept
+
+        # allow OSPFv3
+        ip6 nexthdr 89 accept
+
+        tcp dport 22 accept
+        tcp dport 53 accept
+        udp dport 53 accept
+        tcp dport 80 accept
+        tcp dport 443 accept
+        udp dport tftp accept
+
+        iifname managment udp dport tftp accept
+        iifname lan tcp dport 8080 accept
+
+        # openvpn-members
+        udp dport 20001 accept
+        tcp dport 20001 accept
+
+        # laserproxy
+        udp dport 40200 accept
+        udp dport 50200 accept
+    
+        counter drop
+      }
+    
+      # Allow all outgoing connections.
+      chain output {
+        type filter hook output priority 0; policy accept;
+      }
+    
+      chain forward {
+        type filter hook forward priority 0; policy drop;
+        ct state {established, related} jump accepted
+        oifname "loop" jump accepted
+        ip saddr 10.8.0.0/16 iifname "lan" jump accepted
+        ip saddr 10.9.0.0/16 iifname "vpn" jump accepted
+        ip6 saddr 2a0d:eb00:4242::0/64 iifname "lan" jump accepted
+        ip6 saddr 2a0d:eb00:4242:1::0/64 iifname "vpn" jump accepted
+        ip6 saddr 2a0d:eb00:4242:1::1/128 iifname "loop" jump accepted
+      }
+
+      chain accepted {
+        # IMPORTANT
+        # Log all connections to the outside world from LAN interface, as we are
+        # required to do so
+        oifname != "uplink" accept
+        iifname "uplink" accept
+        ip daddr { 10.0.0.0/8, 225.225.225.225/32 } accept
+        ip6 daddr { 2a0d:eb00::/29, fe80::/8 } accept
+        log group 2 accept
+      }
+    }
+
+    table inet net {
+      chain postrouting {
+        type nat hook postrouting priority 100;
+        ip saddr 10.8.0.0/16 oifname uplink snat ${networks.uplink.ipv4}
+        ip saddr 10.9.0.0/16 oifname uplink snat ${networks.uplink.ipv4}
+      }
+      
+      chain prerouting {
+        type nat hook prerouting priority -100;
+
+        # Access to staszkecoin from Internet
+        ip version 4 iifname "uplink" tcp dport 8333 dnat 10.8.1.49
+      }
+    }
+  '';
+
+  systemd.services."loop-netdev" = let n = "loop"; in {
+    description = "Dummy interface: loop";
+    wantedBy = [ "network-setup.service" "sys-subsystem-net-devices-${n}.device" ];
+    partOf = [ "network-setup.service" ];
+    after = [ "network-pre.target" ];
+    before = [ "network-setup.service" ];
+    serviceConfig.Type = "oneshot";
+    serviceConfig.RemainAfterExit = true;
+    path = [ pkgs.iproute ];
+    script = ''
+      # Remove Dead Interfaces
+      ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+      ip link add "${n}" type dummy
+      ip link set "${n}" up
+    '';
+    postStop = ''
+      ip link delete "${n}"
+    '';
+  };
+
+
+  networking.interfaces = {
+    uplink = {
+      ipv4.addresses = [ { address = networks.uplink.ipv4; prefixLength = 31; } ];
+      ipv6.addresses = [
+        { address = networks.uplink.ipv6; prefixLength = 112; }
+      ];
+    };
+    lan = {
+      ipv4.addresses = [ { address = networks.lan.ipv4; prefixLength = 16; } ];
+      ipv6.addresses = [ { address = networks.lan.ipv6; prefixLength = 64; } ];
+    };
+    loop = {
+      ipv6.addresses = [ { address = "2a0d:eb00:4242:1::1"; prefixLength = 128; } ];
+    };
+    laser = {
+      ipv4.addresses = [ { address = "10.11.0.1"; prefixLength = 24; } ];
+    };
+    bms = {
+      ipv4.addresses = [ { address = "10.11.1.1"; prefixLength = 24; } ];
+    };
+    managment = {
+      ipv4.addresses = [ { address = "10.10.1.1"; prefixLength = 24; } ];
+    };
+    lte = {
+      ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
+    };
+  };
+
+  networking.defaultGateway = {
+    address = "185.236.240.4";
+    interface = "uplink";
+  };
+  networking.defaultGateway6 = {
+    address = "2a0d:eb00:2137:1::1";
+    interface = "uplink";
+  };
+
+
+  networking.nameservers = [ "1.0.0.1" "8.8.8.8" ];
+
+  services.openssh = {
+    enable = true;
+    passwordAuthentication = false;
+    logLevel = "INFO";
+  };
+
+  users.users.root.openssh.authorizedKeys.keys = [ vuko-pubkey q3k-pubkey ];
+
+  services.dhcpd4 = {
+    enable = true;
+    configFile = ./dhcpd.conf;
+    interfaces = ["lan"];
+  };
+
+  hscloud.routing = {
+    enable = true;
+    # TODO(q3k): make this optional in upstream
+    extra = "";
+    routerID = "185.236.240.5";
+    tables.master.program = true;
+    pipe.v6.aggregate_to_kernel = {
+      table = "master";
+      peerTable = "aggregate";
+      filterIn = ''
+        if source = RTS_OSPF then accept;
+        if source = RTS_OSPF_EXT2 then accept;
+        reject;
+      '';
+    };
+    ospf.v6.upstream = {
+      table = "aggregate";
+      area."0.0.0.0" = {
+        interfaces.uplink = { type = "bcast"; };
+        interfaces.lan = { type = "bcast"; stub = true; };
+        interfaces.loop = { type = "ptp"; stub = true; };
+      };
+    };
+  };
+
+  services.radvd = {
+    enable = true;
+    config = ''
+      interface lan {
+        AdvSendAdvert on;
+        prefix 2a0d:eb00:4242::/64 {
+        };
+        route 0::/0 { };
+      };
+      interface vpn {
+        AdvSendAdvert on;
+        prefix 2a0d:eb00:4242:1::/64 {
+          AdvRouterAddr on;
+        };
+        route 0::/0 { };
+      };
+    '';
+  };
+
+  services.logrotate = {
+    enable = true;
+    paths = {
+      ulogd = {
+        enable = true;
+        frequency = "weekly";
+        path = "/var/log/ulogd.pcap";
+        extraConfig = ''
+          postrotate
+            ${pkgs.killall}/bin/killall -HUP ulogd
+          endscript
+        '';
+        keep = 55;
+      };
+    };
+  };
+
+  services.cron = let
+    log-neigh = pkgs.writeShellScript "log-neigh"  ''
+      mkdir -p /var/log/arptables
+      chmod 700 /var/log/arptables
+
+      # Larger than 10MB? rotate.
+      if [[ $(find /var/log/arptables/arptables.log -type f -size +10485760c 2>/dev/null) ]]; then
+          f=/var/log/arptables/$(date "+%s").log
+          cp /var/log/arptables/arptables.log $f
+          gzip -9 $f
+          rm /var/log/arptables/arptables.log
+      fi
+
+      ip neigh >> /var/log/arptables/arptables.log
+      date --iso-8601=seconds >> /var/log/arptables/arptables.log
+    '';
+  in {
+    mailto = "vuko@hackerspace.pl";
+    enable = true;
+    systemCronJobs = [
+      "*/5 * * * *      root    ${log-neigh}"
+      "0   3 * * *      root    ${update_authorized_keys}/bin/update_authorized_keys"
+    ];
+  };
+
+  services.knot = {
+    enable = true;
+    extraConfig = ''
+      server:
+          listen: ${networks.uplink.ipv4}@53
+          listen: ${networks.uplink.ipv6}@53
+      
+      zone:
+        - domain: waw.hackerspace.pl
+          storage: ${./zones}
+          file: waw.hackerspace.pl
+        - domain: i
+          storage: ${./zones}
+          file: i
+        - domain: api.ustream.tv
+          storage: ${./zones}
+          file: api.ustream.tv
+        - domain: api.eye.fi
+          storage: ${./zones}
+          file: api.eye.fi
+      log:
+        - target: syslog
+          any: info
+    '';
+  };
+
+  services.nginx.enable = true;
+  services.nginx.mapHashBucketSize = 64;
+  services.nginx.appendHttpConfig = ''
+    server_names_hash_bucket_size 64;
+  '';
+
+  security.acme = {
+    email = "bofh@hackerspace.pl";
+    acceptTerms = true;
+  };
+
+  services.nginx.virtualHosts."customs.hackerspace.pl" = {
+    default = true;
+    enableACME = true;
+
+    locations."/" = {
+      extraConfig = ''
+        return 302 https://isztar.mf.gov.pl;
+      '';
+    };
+    locations."/metrics/luftdaten" = {
+      proxyPass = "http://10.8.0.146";
+    };
+    locations."/metrics/spejsiot" = {
+      proxyPass = "http://spejsiot.waw.hackerspace.pl/metrics";
+    };
+    locations."/metrics/apm" = {
+      proxyPass = "http://10.8.1.40:5000/metrics";
+    };
+    locations."/metrics/vending" = {
+      proxyPass = "http://10.8.1.32:8000/";
+    };
+    locations."/metrics/sztancarka" = {
+      proxyPass = "http://10.8.0.96:8888/";
+    };
+    locations."/metrics/mikrotik" = {
+      proxyPass = "http://127.0.0.1:9436/metrics";
+      extraConfig = ''
+        allow 209.250.231.127;
+        deny all;
+      '';
+    };
+    locations."/metrics/node" = {
+      proxyPass = "http://[::1]:9100/metrics";
+      extraConfig = ''
+        allow 209.250.231.127;
+        deny all;
+      '';
+    };
+    locations."/stats/sztancarka-ppm" = {
+      proxyPass = "http://10.8.0.96:9090/api/v1/query?query=rate%28cut_count_total%5B15m%5D%29+*+60";
+    };
+    locations."/stats/sztancarka-last-24h" = {
+      proxyPass = "http://10.8.0.96:9090/api/v1/query?query=round(increase(cut_count_total[24h]))";
+    };
+  };
+
+  services.unbound = {
+    enable = true;
+    interfaces = [
+      networks.lan.ipv4
+      "127.0.0.1"
+      "::1"
+      # networks.lan.ipv6 TODO
+    ];
+    allowedAccess = [
+      "127.0.0.1/8"
+      "10.0.0.0/8"
+    ];
+    extraConfig = builtins.concatStringsSep "\n" ((map (
+      name: ''
+        stub-zone:
+            name: ${name}
+            stub-addr: ${networks.uplink.ipv4}
+      ''
+    ) [ "waw.hackerspace.pl" "api.eye.fi" "api.ustream.tv" "i"]) ++ [''
+      forward-zone:
+          name: "."
+          forward-addr: 185.236.240.1
+    '']);
+  };
+
+  
+
+  # Public VPN access for Hackerspace members
+  services.openvpn.servers.members.config = ''
+    script-security 3
+    auth-user-pass-verify ${openvpn-auth}/bin/openvpn-auth-member via-env
+    verify-client-cert none
+    username-as-common-name
+    
+    #user _openvpn
+    #group _openvpn
+    multihome
+    
+    port 20001
+    proto udp
+    proto udp6
+    dev vpn
+    dev-type tun
+    ca ${secrets-path}/openvpn-public/ca.crt
+    cert ${secrets-path}/openvpn-public/server.crt
+    key ${secrets-path}/openvpn-public/server.key
+    dh ${secrets-path}/openvpn-public/dh.pem
+    server 10.9.1.0 255.255.255.0
+    
+    push "route 10.8.0.0 255.255.0.0"
+    push "route 10.9.0.0 255.255.0.0"
+    push "route 10.10.0.0 255.255.0.0"
+    push "route 10.11.0.0 255.255.0.0"
+    push "dhcp-option DNS ${networks.lan.ipv4}"
+    push "dhcp-option DOMAIN waw.hackerspace.pl"
+    
+    ifconfig-pool-persist /var/lib/openvpn-public/ipp.txt
+    #client-config-dir /var/lib/openvpn-public/ccd
+    client-to-client
+    keepalive 10 120
+    comp-lzo
+    persist-key
+    persist-tun
+  '';
+
+  environment.systemPackages = with pkgs; [
+    vim tcpdump htop nmon tmux git file procps parted dmidecode ack utillinux nmap mosh ncdu tree lz4 bind
+    rxvt_unicode.terminfo update_authorized_keys
+  ];
+  programs.mtr.enable = true;
+
+  environment.variables = {
+    EDITOR = "vim";
+  };
+
+  system.stateVersion = "20.03";
+
+  boot.vesa = false;
+  boot.loader.grub.splashImage = null;
+}
+