From 6d9c43bafa4ef485e1127214ab7a5d77794d665b Mon Sep 17 00:00:00 2001
From: Gabriel Arazas <foodogsquared@foodogsquared.one>
Date: Mon, 16 Jan 2023 17:52:23 +0800
Subject: [PATCH] hosts/plover: initialize OpenVPN service

---
 hosts/plover/default.nix                  |   1 +
 hosts/plover/modules/services/openvpn.nix | 107 ++++++++++++++++++++++
 2 files changed, 108 insertions(+)
 create mode 100644 hosts/plover/modules/services/openvpn.nix

diff --git a/hosts/plover/default.nix b/hosts/plover/default.nix
index 6f8bb2aa..357694bf 100644
--- a/hosts/plover/default.nix
+++ b/hosts/plover/default.nix
@@ -34,6 +34,7 @@ in
     ./modules/services/keycloak.nix
     ./modules/services/portunus.nix
     ./modules/services/vaultwarden.nix
+    ./modules/services/openvpn.nix
   ];
 
   boot.loader.grub.enable = true;
diff --git a/hosts/plover/modules/services/openvpn.nix b/hosts/plover/modules/services/openvpn.nix
new file mode 100644
index 00000000..9c1e40d6
--- /dev/null
+++ b/hosts/plover/modules/services/openvpn.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+let
+  certs = config.security.acme.certs;
+
+  networks = import ../hardware/networks.nix;
+  inherit (networks) publicIP publicIPv6 privateNetworkGatewayIP;
+
+  vpnAddressPoolStart = "172.43.1.1";
+  vpnAddressPoolEnd = "172.43.1.255";
+
+  acmeName = "openvpn.foodogsquared.one";
+in
+{
+  # We need a bridge to access locally hosted services in the network of the
+  # deployed server.
+  services.openvpn.servers = {
+    server = {
+      config = let
+        certDirectory = certs."${acmeName}".directory;
+        dhParams = config.security.dhparams.params;
+      in
+      ''
+        ca ${certDirectory}/chain.pem
+        cert ${certDirectory}/fullchain.pem
+        key ${certDirectory}/key.pem
+        dh ${dhParams."openvpn-server".path}
+
+        proto udp
+        topology subnet
+
+        server-bridge 172.43.0.1 255.255.255.0 ${vpnAddressPoolStart} ${vpnAddressPoolEnd}
+        server-ipv6 fd00::/8
+
+        dev vpn-tap
+        dev-type tap
+
+        # Connecting clients will be able to reach to one another.
+        client-to-client
+
+        user nobody
+        group nobody
+      '';
+    };
+  };
+
+  # We're generating our own certificates for OpenVPN. Most of the
+  # configuration should be taken care of with the defaults from the host
+  # config.
+  security.acme.certs."${acmeName}" = { };
+
+  # For key generation, debugging, panic configuration, anything else.
+  environment.systemPackages = [ pkgs.openvpn ];
+
+  systemd.network = let
+    vpnBridgeIFName = "vpn-bridge";
+    vpnTapIFName = "vpn-tap";
+  in
+  {
+    netdevs = {
+      "90-${vpnBridgeIFName}".netdevConfig = {
+        Name = vpnBridgeIFName;
+        Kind = "bridge";
+      };
+
+      "90-${vpnTapIFName}" = {
+        netdevConfig = {
+          Name = vpnTapIFName;
+          Kind = "tap";
+        };
+
+        tapConfig = {
+          MultiQueue = true;
+          PacketInfo = true;
+        };
+      };
+    };
+
+    networks = {
+      "50-vpn-bridge-slave-1" = {
+        matchConfig.MACAddress = "86:00:00:32:48:20";
+        networkConfig.Bridge = vpnBridgeIFName;
+      };
+
+      "50-vpn-bridge-slave-tap" = {
+        matchConfig.Name = vpnTapIFName;
+        networkConfig.Bridge = vpnBridgeIFName;
+      };
+
+      "50-vpn-bridge-static" = {
+        matchConfig.Name = vpnBridgeIFName;
+
+        address = [
+          # The private network IP.
+          "172.43.0.1/32"
+
+          # Generate a new unique local IPv6 address.
+          "::"
+        ];
+
+        gateway = [ privateNetworkGatewayIP ];
+      };
+    };
+  };
+
+  security.dhparams.params.openvpn-server = { };
+}