refactor: many changes
This commit is contained in:
@@ -4,186 +4,31 @@ with lib;
|
||||
|
||||
let
|
||||
cfg = config.cloonar-assistant;
|
||||
|
||||
vpn-client-opts =
|
||||
{ ... }:
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
publicKey = mkOption {
|
||||
example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
|
||||
type = types.singleLineStr;
|
||||
description = "The base64 public key of the peer.";
|
||||
};
|
||||
|
||||
presharedKey = mkOption {
|
||||
default = null;
|
||||
example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
Base64 preshared key generated by {command}`wg genpsk`.
|
||||
Optional, and may be omitted. This option adds an additional layer of
|
||||
symmetric-key cryptography to be mixed into the already existing
|
||||
public-key cryptography, for post-quantum resistance.
|
||||
|
||||
Warning: Consider using presharedKeyFile instead if you do not
|
||||
want to store the key in the world-readable Nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
presharedKeyFile = mkOption {
|
||||
default = null;
|
||||
example = "/private/wireguard_psk";
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
File pointing to preshared key as generated by {command}`wg genpsk`.
|
||||
Optional, and may be omitted. This option adds an additional layer of
|
||||
symmetric-key cryptography to be mixed into the already existing
|
||||
public-key cryptography, for post-quantum resistance.
|
||||
'';
|
||||
};
|
||||
|
||||
allowedIPs = mkOption {
|
||||
example = [
|
||||
"10.192.122.3/32"
|
||||
"10.192.124.1/24"
|
||||
];
|
||||
type = with types; listOf str;
|
||||
description = ''
|
||||
List of IP (v4 or v6) addresses with CIDR masks from
|
||||
which this peer is allowed to send incoming traffic and to which
|
||||
outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
|
||||
be specified for matching all IPv4 addresses, and ::/0 may be specified
|
||||
for matching all IPv6 addresses.'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
options.cloonar-assistant = {
|
||||
setup = lib.mkOption {
|
||||
setup = mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Enable access from Wan to Setup";
|
||||
};
|
||||
networkPrefix = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
networkPrefix = mkOption {
|
||||
type = types.str;
|
||||
default = "10.10";
|
||||
example = "10.10";
|
||||
description = "First two octets of the network";
|
||||
};
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
example = "example.smart.cloonar.com";
|
||||
description = "domain of the network";
|
||||
};
|
||||
updns-client = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Enable updns";
|
||||
};
|
||||
key = lib.mkOption {
|
||||
type = with types; nullOr str;
|
||||
example = "example";
|
||||
description = "key for updns";
|
||||
};
|
||||
secretFile = lib.mkOption {
|
||||
type = with types; nullOr str;
|
||||
example = "/private/updns_secret";
|
||||
description = "File pointing to secret as generated by {command}`wg genpsk`.";
|
||||
};
|
||||
};
|
||||
vpn = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Enable VPN";
|
||||
};
|
||||
privateKeyFile = lib.mkOption {
|
||||
type = with types; nullOr str;
|
||||
example = "/private/wireguard_private_key";
|
||||
description = "File pointing to private key as generated by {command}`wg genkey`.";
|
||||
};
|
||||
clients = mkOption {
|
||||
default = [ ];
|
||||
description = "VPN Clients";
|
||||
type = with types; listOf (submodule vpn-client-opts);
|
||||
};
|
||||
};
|
||||
multiroom-audio = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable multiroom audio";
|
||||
};
|
||||
};
|
||||
firewall = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Enable firewall";
|
||||
};
|
||||
ipv4 = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Enable firewall";
|
||||
};
|
||||
ipv6 = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Enable firewall";
|
||||
};
|
||||
interfaces = {
|
||||
wan = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "enp2s0";
|
||||
description = "Network interface for WAN";
|
||||
};
|
||||
internal = lib.mkOption {
|
||||
type = with types; nullOr str;
|
||||
example = "enp3s0";
|
||||
description = "Internal network interface";
|
||||
};
|
||||
};
|
||||
custom-rules = {
|
||||
input = lib.mkOption {
|
||||
type = with types; nullOr lines;
|
||||
default = '''';
|
||||
example = ''
|
||||
iifname "lan" udp dport 22 counter accept comment "Wireguard traffic"
|
||||
iifname "lan" udp dport 80 counter accept comment "Wireguard traffic"
|
||||
'';
|
||||
description = "Custom iptables rules for INPUT chain";
|
||||
};
|
||||
forward = lib.mkOption {
|
||||
type = with types; nullOr lines;
|
||||
default = '''';
|
||||
example = ''
|
||||
iifname "lan" oifname "server" tcp dport { 22 } counter accept
|
||||
iifname "lan" oifname "server" tcp dport { 80 } counter accept
|
||||
'';
|
||||
description = "Custom iptables rules for FORWARD chain";
|
||||
};
|
||||
prerouting = lib.mkOption {
|
||||
type = with types; nullOr lines;
|
||||
default = '''';
|
||||
example = ''
|
||||
iifname "server" ip daddr 10.0.96.255 udp dport { 9 } dnat to 10.0.96.255
|
||||
'';
|
||||
description = "Custom iptables rules for nat chain";
|
||||
};
|
||||
postrouting = lib.mkOption {
|
||||
type = with types; nullOr lines;
|
||||
default = '''';
|
||||
example = ''
|
||||
oifname { "wan" } masquerade
|
||||
'';
|
||||
description = "Custom iptables rules for nat chain";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
imports = [
|
||||
# Include the results of the hardware scan.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
domain = config.cloonar-assistant.domain;
|
||||
pkgs-with-home-assistant = import (builtins.fetchGit {
|
||||
@@ -12,7 +15,7 @@ let
|
||||
"backup"
|
||||
];
|
||||
|
||||
ha-config = lib.recursiveUpdate config.services.home-assistant.config {
|
||||
ha-config = recursiveUpdate config.services.home-assistant.config {
|
||||
recorder = {
|
||||
db_url = "mysql://hass@localhost/hass?unix_socket=/var/run/mysqld/mysqld.sock";
|
||||
};
|
||||
@@ -72,6 +75,8 @@ let
|
||||
gid = config.ids.gids.hass;
|
||||
in
|
||||
{
|
||||
services.home-assistant.config = mkDefault { };
|
||||
|
||||
users.users.hass = {
|
||||
home = "/var/lib/hass";
|
||||
createHome = true;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
{ ... }: {
|
||||
networking.hostName = "fw";
|
||||
|
||||
imports = [
|
||||
./interfaces.nix
|
||||
./dhcp.nix
|
||||
|
||||
@@ -1,159 +1,231 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
forward-chain = ''
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.cloonar-assistant.firewall;
|
||||
networkPrefix = config.cloonar-assistant.networkPrefix;
|
||||
|
||||
forward-chain = ''
|
||||
'';
|
||||
in {
|
||||
networking = {
|
||||
firewall.checkReversePath = false;
|
||||
nat.enable = false;
|
||||
nftables = {
|
||||
enable = true;
|
||||
tables = {
|
||||
"cloonar-fw" = {
|
||||
family = "inet";
|
||||
content = ''
|
||||
chain output {
|
||||
type filter hook output priority 100; policy accept;
|
||||
}
|
||||
options.cloonar-assistant.firewall = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable firewall";
|
||||
};
|
||||
ipv4 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable ipv4";
|
||||
};
|
||||
ipv6 = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable ipv6";
|
||||
};
|
||||
interfaces = {
|
||||
wan = mkOption {
|
||||
type = types.str;
|
||||
example = "enp2s0";
|
||||
description = "Network interface for WAN";
|
||||
};
|
||||
internal = mkOption {
|
||||
type = with types; nullOr str;
|
||||
example = "enp3s0";
|
||||
description = "Internal network interface";
|
||||
};
|
||||
};
|
||||
custom-rules = {
|
||||
input = mkOption {
|
||||
type = with types; nullOr lines;
|
||||
default = '''';
|
||||
example = ''
|
||||
iifname "lan" udp dport 22 counter accept comment "Wireguard traffic"
|
||||
iifname "lan" udp dport 80 counter accept comment "Wireguard traffic"
|
||||
'';
|
||||
description = "Custom iptables rules for INPUT chain";
|
||||
};
|
||||
forward = mkOption {
|
||||
type = with types; nullOr lines;
|
||||
default = '''';
|
||||
example = ''
|
||||
iifname "lan" oifname "server" tcp dport { 22 } counter accept
|
||||
iifname "lan" oifname "server" tcp dport { 80 } counter accept
|
||||
'';
|
||||
description = "Custom iptables rules for FORWARD chain";
|
||||
};
|
||||
prerouting = mkOption {
|
||||
type = with types; nullOr lines;
|
||||
default = '''';
|
||||
example = ''
|
||||
iifname "server" ip daddr 10.0.96.255 udp dport { 9 } dnat to 10.0.96.255
|
||||
'';
|
||||
description = "Custom iptables rules for nat chain";
|
||||
};
|
||||
postrouting = mkOption {
|
||||
type = with types; nullOr lines;
|
||||
default = '''';
|
||||
example = ''
|
||||
oifname { "wan" } masquerade
|
||||
'';
|
||||
description = "Custom iptables rules for nat chain";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
chain rpfilter {
|
||||
type filter hook prerouting priority mangle + 10; policy drop;
|
||||
meta nfproto ipv4 udp sport . udp dport { 68 . 67, 67 . 68 } accept comment "DHCPv4 client/server"
|
||||
fib saddr . mark . iif oif exists accept
|
||||
}
|
||||
config = {
|
||||
networking = {
|
||||
firewall.checkReversePath = false;
|
||||
nat.enable = false;
|
||||
nftables = {
|
||||
enable = true;
|
||||
tables = {
|
||||
"cloonar-fw" = {
|
||||
family = "inet";
|
||||
content = ''
|
||||
chain output {
|
||||
type filter hook output priority 100; policy accept;
|
||||
}
|
||||
|
||||
chain input {
|
||||
type filter hook input priority filter; policy drop;
|
||||
iifname "lo" accept comment "trusted interfaces"
|
||||
iifname "lan" counter accept comment "Spice"
|
||||
ct state vmap { invalid : drop, established : accept, related : accept, new : jump input-allow, untracked : jump input-allow }
|
||||
tcp flags syn / fin,syn,rst,ack log prefix "refused connection: " level info
|
||||
}
|
||||
chain rpfilter {
|
||||
type filter hook prerouting priority mangle + 10; policy drop;
|
||||
meta nfproto ipv4 udp sport . udp dport { 68 . 67, 67 . 68 } accept comment "DHCPv4 client/server"
|
||||
fib saddr . mark . iif oif exists accept
|
||||
}
|
||||
|
||||
chain input-allow {
|
||||
udp dport != { 53, 5353 } ct state new limit rate over 1/second burst 10 packets drop comment "rate limit for new connections"
|
||||
iifname lo accept
|
||||
${lib.optionalString config.cloonar-assistant.setup ''
|
||||
iifname "wan" accept
|
||||
''}
|
||||
${lib.optionalString config.cloonar-assistant.vpn.enable ''
|
||||
iifname "wan" udp dport 51820 counter accept comment "Wireguard traffic"
|
||||
''}
|
||||
${lib.optionalString config.cloonar-assistant.firewall.enable ''
|
||||
iifname "wan" tcp dport 9273 counter accept comment "Prometheus traffic"
|
||||
''}
|
||||
${lib.optionalString config.cloonar-assistant.firewall.enable ''
|
||||
iifname { "server", "vserver", "vm-*", "lan", "wg_cloonar" } counter accept comment "allow trusted to router"
|
||||
iifname { "multimedia", "smart", "infrastructure", "podman0", "setup" } udp dport { 53, 5353 } counter accept comment "DNS"
|
||||
''}
|
||||
chain input {
|
||||
type filter hook input priority filter; policy drop;
|
||||
iifname "lo" accept comment "trusted interfaces"
|
||||
iifname "lan" counter accept comment "Spice"
|
||||
ct state vmap { invalid : drop, established : accept, related : accept, new : jump input-allow, untracked : jump input-allow }
|
||||
tcp flags syn / fin,syn,rst,ack log prefix "refused connection: " level info
|
||||
}
|
||||
|
||||
iifname { "wan", "multimedia" } icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP"
|
||||
chain input-allow {
|
||||
udp dport != { 53, 5353 } ct state new limit rate over 1/second burst 10 packets drop comment "rate limit for new connections"
|
||||
iifname lo accept
|
||||
${optionalString config.cloonar-assistant.setup ''
|
||||
iifname "wan" accept
|
||||
''}
|
||||
${optionalString config.cloonar-assistant.vpn.enable ''
|
||||
iifname "wan" udp dport 51820 counter accept comment "Wireguard traffic"
|
||||
''}
|
||||
${optionalString cfg.enable ''
|
||||
iifname "wan" tcp dport 9273 counter accept comment "Prometheus traffic"
|
||||
iifname { "server", "vserver", "vm-*", "lan", "wg_cloonar" } counter accept comment "allow trusted to router"
|
||||
iifname { "multimedia", "smart", "infrastructure", "podman0", "setup" } udp dport { 53, 5353 } counter accept comment "DNS"
|
||||
iifname { "multimedia" } icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP"
|
||||
''}
|
||||
|
||||
# Accept mDNS for avahi reflection
|
||||
${lib.optionalString config.cloonar-assistant.multiroom-audio.enable ''
|
||||
iifname "server" ip saddr ${config.cloonar-assistant.networkPrefix}.97.20/32 tcp dport { llmnr } counter accept
|
||||
iifname "server" ip saddr ${config.cloonar-assistant.networkPrefix}.97.20/32 udp dport { mdns, llmnr } counter accept
|
||||
''}
|
||||
iifname "wan" icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP"
|
||||
|
||||
# Allow all returning traffic
|
||||
ct state { established, related } counter accept
|
||||
# Accept mDNS for avahi reflection
|
||||
${optionalString config.cloonar-assistant.multiroom-audio.enable ''
|
||||
iifname "server" ip saddr ${networkPrefix}.97.20/32 tcp dport { llmnr } counter accept
|
||||
iifname "server" ip saddr ${networkPrefix}.97.20/32 udp dport { mdns, llmnr } counter accept
|
||||
''}
|
||||
|
||||
# Allow returning traffic from wan and drop everthing else
|
||||
iifname "wan" ct state { established, related } accept comment "Allow established traffic"
|
||||
iifname "wan" icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP"
|
||||
iifname "wan" counter drop comment "Drop all other unsolicited traffic from wan"
|
||||
# Allow all returning traffic
|
||||
ct state { established, related } counter accept
|
||||
|
||||
${config.cloonar-assistant.firewall.custom-rules.input}
|
||||
# Allow returning traffic from wan and drop everthing else
|
||||
iifname "wan" ct state { established, related } accept comment "Allow established traffic"
|
||||
iifname "wan" icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP"
|
||||
iifname "wan" counter drop comment "Drop all other unsolicited traffic from wan"
|
||||
|
||||
limit rate 60/minute burst 100 packets log prefix "Input - Drop: " comment "Log any unmatched traffic"
|
||||
}
|
||||
${cfg.custom-rules.input}
|
||||
|
||||
limit rate 60/minute burst 100 packets log prefix "Input - Drop: " comment "Log any unmatched traffic"
|
||||
}
|
||||
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority filter; policy drop;
|
||||
|
||||
iifname "wg_cloonar" counter accept comment "test wireguard"
|
||||
|
||||
${lib.optionalString config.cloonar-assistant.vpn.enable ''
|
||||
iifname "wg_cloonar" oifname lo counter accept comment "wireguard to server"
|
||||
''}
|
||||
|
||||
# enable flow offloading for better throughput
|
||||
# ip protocol { tcp, udp } flow offload @f
|
||||
|
||||
# allow home assistant to wan
|
||||
iifname "ve-hass" oifname wan counter accept comment "Allow home assistant to WAN"
|
||||
|
||||
# multimedia airplay
|
||||
${lib.optionalString config.cloonar-assistant.multiroom-audio.enable ''
|
||||
iifname "multimedia" oifname { "lan" } counter accept
|
||||
iifname "multimedia" oifname "server" tcp dport { 1704, 1705 } counter accept
|
||||
iifname "lan" oifname "server" udp dport { 5000, 5353, 6001 - 6011 } counter accept
|
||||
# avahi
|
||||
iifname "server" ip saddr ${config.cloonar-assistant.networkPrefix}.97.20/32 oifname { "lan" } counter accept
|
||||
''}
|
||||
|
||||
${lib.optionalString config.cloonar-assistant.firewall.enable ''
|
||||
# smart home coap
|
||||
iifname "smart" oifname "server" ip daddr ${config.cloonar-assistant.networkPrefix}.97.20/32 udp dport { 5683 } counter accept
|
||||
iifname "smart" oifname "server" ip daddr ${config.cloonar-assistant.networkPrefix}.97.20/32 tcp dport { 1883 } counter accept
|
||||
|
||||
# lan and vpn to any
|
||||
iifname { "lan", "server", "vserver", "wg_cloonar" } oifname { "lan", "server", "vserver", "infrastructure", "multimedia", "smart", "wg_cloonar", "guest", "setup" } counter accept
|
||||
iifname { "infrastructure", "setup" } oifname { "server", "vserver" } counter accept
|
||||
iifname { "lan", "wan" } udp dport { 8211, 27015 } counter accept comment "palworld"
|
||||
|
||||
${config.cloonar-assistant.firewall.custom-rules.forward}
|
||||
''}
|
||||
chain forward {
|
||||
type filter hook forward priority filter; policy drop;
|
||||
|
||||
|
||||
# allow all established, related
|
||||
ct state { established, related } accept comment "Allow established traffic"
|
||||
${lib.optionalString config.cloonar-assistant.vpn.enable ''
|
||||
iifname "wg_cloonar" counter accept comment "test wireguard"
|
||||
iifname "wg_cloonar" oifname lo counter accept comment "wireguard to server"
|
||||
''}
|
||||
|
||||
# Allow trusted network WAN access
|
||||
${lib.optionalString config.cloonar-assistant.firewall.enable ''
|
||||
iifname {
|
||||
"lan",
|
||||
"infrastructure",
|
||||
"server",
|
||||
"vserver",
|
||||
"multimedia",
|
||||
"smart",
|
||||
"wg_cloonar",
|
||||
"podman*",
|
||||
"guest",
|
||||
"setup",
|
||||
} oifname {
|
||||
"wan",
|
||||
} counter accept comment "Allow trusted LAN to WAN"
|
||||
''}
|
||||
# enable flow offloading for better throughput
|
||||
# ip protocol { tcp, udp } flow offload @f
|
||||
|
||||
limit rate 60/minute burst 100 packets log prefix "Forward - Drop: " comment "Log any unmatched traffic"
|
||||
}
|
||||
'';
|
||||
};
|
||||
"cloonar-nat" = {
|
||||
family = "ip";
|
||||
content = ''
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority filter; policy accept;
|
||||
iifname "server" ip daddr ${config.cloonar-assistant.networkPrefix}.96.255 udp dport { 9 } dnat to ${config.cloonar-assistant.networkPrefix}.96.255
|
||||
${config.cloonar-assistant.firewall.custom-rules.prerouting}
|
||||
}
|
||||
# allow home assistant to wan
|
||||
iifname "ve-hass" oifname wan counter accept comment "Allow home assistant to WAN"
|
||||
|
||||
# Setup NAT masquerading on external interfaces
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority filter; policy accept;
|
||||
oifname { "wan" } masquerade
|
||||
# multimedia airplay
|
||||
${lib.optionalString config.cloonar-assistant.multiroom-audio.enable ''
|
||||
iifname "multimedia" oifname { "lan" } counter accept
|
||||
iifname "multimedia" oifname "server" tcp dport { 1704, 1705 } counter accept
|
||||
iifname "lan" oifname "server" udp dport { 5000, 5353, 6001 - 6011 } counter accept
|
||||
# avahi
|
||||
iifname "server" ip saddr ${networkPrefix}.97.20/32 oifname { "lan" } counter accept
|
||||
''}
|
||||
|
||||
${lib.optionalString config.cloonar-assistant.vpn.enable ''
|
||||
oifname { "wg_cloonar" } masquerade
|
||||
''}
|
||||
${lib.optionalString config.cloonar-assistant.firewall.enable ''
|
||||
# smart home coap
|
||||
iifname "smart" oifname "server" ip daddr ${networkPrefix}.97.20/32 udp dport { 5683 } counter accept
|
||||
iifname "smart" oifname "server" ip daddr ${networkPrefix}.97.20/32 tcp dport { 1883 } counter accept
|
||||
|
||||
${config.cloonar-assistant.firewall.custom-rules.postrouting}
|
||||
}
|
||||
'';
|
||||
# lan and vpn to any
|
||||
iifname { "lan", "server", "vserver", "wg_cloonar" } oifname { "lan", "server", "vserver", "infrastructure", "multimedia", "smart", "wg_cloonar", "guest", "setup" } counter accept
|
||||
iifname { "infrastructure", "setup" } oifname { "server", "vserver" } counter accept
|
||||
iifname { "lan", "wan" } udp dport { 8211, 27015 } counter accept comment "palworld"
|
||||
|
||||
${cfg.custom-rules.forward}
|
||||
''}
|
||||
|
||||
|
||||
# allow all established, related
|
||||
ct state { established, related } accept comment "Allow established traffic"
|
||||
|
||||
# Allow trusted network WAN access
|
||||
${lib.optionalString cfg.enable ''
|
||||
iifname {
|
||||
"lan",
|
||||
"infrastructure",
|
||||
"server",
|
||||
"vserver",
|
||||
"multimedia",
|
||||
"smart",
|
||||
"wg_cloonar",
|
||||
"podman*",
|
||||
"guest",
|
||||
"setup",
|
||||
} oifname {
|
||||
"wan",
|
||||
} counter accept comment "Allow trusted LAN to WAN"
|
||||
''}
|
||||
|
||||
limit rate 60/minute burst 100 packets log prefix "Forward - Drop: " comment "Log any unmatched traffic"
|
||||
}
|
||||
'';
|
||||
};
|
||||
"cloonar-nat" = {
|
||||
family = "ip";
|
||||
content = ''
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority filter; policy accept;
|
||||
iifname "server" ip daddr ${networkPrefix}.96.255 udp dport { 9 } dnat to ${networkPrefix}.96.255
|
||||
${cfg.custom-rules.prerouting}
|
||||
}
|
||||
|
||||
# Setup NAT masquerading on external interfaces
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority filter; policy accept;
|
||||
oifname { "wan" } masquerade
|
||||
|
||||
${lib.optionalString config.cloonar-assistant.vpn.enable ''
|
||||
oifname { "wg_cloonar" } masquerade
|
||||
''}
|
||||
|
||||
${cfg.custom-rules.postrouting}
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.cloonar-assistant.firewall.interfaces;
|
||||
networkPrefix = config.cloonar-assistant.networkPrefix;
|
||||
in {
|
||||
boot.kernel.sysctl = {
|
||||
# if you use ipv4, this is all you need
|
||||
"net.ipv4.conf.all.forwarding" = true;
|
||||
@@ -12,9 +18,13 @@
|
||||
wait-online.anyInterface = true;
|
||||
links = {
|
||||
"10-wan" = {
|
||||
matchConfig.Name = config.cloonar-assistant.firewall.interfaces.wan;
|
||||
matchConfig.Name = cfg.wan;
|
||||
linkConfig.Name = "wan";
|
||||
};
|
||||
"20-internal" = mkIf (cfg.internal != null) {
|
||||
matchConfig.Name = cfg.internal;
|
||||
linkConfig.Name = "internal";
|
||||
};
|
||||
};
|
||||
netdevs = {
|
||||
"30-server".netdevConfig = {
|
||||
@@ -34,32 +44,32 @@
|
||||
networking = if config.cloonar-assistant.firewall.enable then {
|
||||
useDHCP = false;
|
||||
# Define VLANS
|
||||
nameservers = [ "${config.cloonar-assistant.networkPrefix}.97.1" ];
|
||||
nameservers = [ "${networkPrefix}.97.1" ];
|
||||
# resolvconf.enable = false;
|
||||
vlans = {
|
||||
vlans = mkIf (cfg.internal != null) {
|
||||
infrastructure = {
|
||||
id = 101;
|
||||
interface = config.cloonar-assistant.firewall.interfaces.internal;
|
||||
interface = cfg.internal;
|
||||
};
|
||||
lan = {
|
||||
id = 96;
|
||||
interface = config.cloonar-assistant.firewall.interfaces.internal;
|
||||
interface = cfg.internal;
|
||||
};
|
||||
vserver = {
|
||||
id = 97;
|
||||
interface = config.cloonar-assistant.firewall.interfaces.internal;
|
||||
interface = cfg.internal;
|
||||
};
|
||||
multimedia = {
|
||||
id = 99;
|
||||
interface = config.cloonar-assistant.firewall.interfaces.internal;
|
||||
interface = cfg.internal;
|
||||
};
|
||||
smart = {
|
||||
id = 100;
|
||||
interface = config.cloonar-assistant.firewall.interfaces.internal;
|
||||
interface = cfg.internal;
|
||||
};
|
||||
guest = {
|
||||
id = 254;
|
||||
interface = config.cloonar-assistant.firewall.interfaces.internal;
|
||||
interface = cfg.internal;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -71,37 +81,37 @@
|
||||
wan.useDHCP = true;
|
||||
lan = {
|
||||
ipv4.addresses = [{
|
||||
address = "${config.cloonar-assistant.networkPrefix}.96.1";
|
||||
address = "${networkPrefix}.96.1";
|
||||
prefixLength = 24;
|
||||
}];
|
||||
};
|
||||
server = {
|
||||
ipv4.addresses = [{
|
||||
address = "${config.cloonar-assistant.networkPrefix}.97.1";
|
||||
address = "${networkPrefix}.97.1";
|
||||
prefixLength = 24;
|
||||
}];
|
||||
};
|
||||
infrastructure = {
|
||||
ipv4.addresses = [{
|
||||
address = "${config.cloonar-assistant.networkPrefix}.101.1";
|
||||
address = "${networkPrefix}.101.1";
|
||||
prefixLength = 24;
|
||||
}];
|
||||
};
|
||||
multimedia = {
|
||||
ipv4.addresses = [{
|
||||
address = "${config.cloonar-assistant.networkPrefix}.99.1";
|
||||
address = "${networkPrefix}.99.1";
|
||||
prefixLength = 24;
|
||||
}];
|
||||
};
|
||||
smart = {
|
||||
ipv4.addresses = [{
|
||||
address = "${config.cloonar-assistant.networkPrefix}.100.1";
|
||||
address = "${networkPrefix}.100.1";
|
||||
prefixLength = 24;
|
||||
}];
|
||||
};
|
||||
guest = {
|
||||
ipv4.addresses = [{
|
||||
address = "${config.cloonar-assistant.networkPrefix}.254.1";
|
||||
address = "${networkPrefix}.254.1";
|
||||
prefixLength = 24;
|
||||
}];
|
||||
};
|
||||
|
||||
@@ -1,10 +1,91 @@
|
||||
{ config, lib, ... }: {
|
||||
networking.wireguard.interfaces = lib.mkIf config.cloonar-assistant.vpn.enable {
|
||||
wg_cloonar = {
|
||||
ips = [ "${config.networkPrefix}.98.1/24" ];
|
||||
listenPort = 51820;
|
||||
privateKeyFile = config.cloonar-assistant.vpn.privateKeyFile;
|
||||
peers = config.cloonar-assistant.vpn.clients;
|
||||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.cloonar-assistant.vpn;
|
||||
|
||||
vpn-client-opts =
|
||||
{ ... }:
|
||||
{
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
example = "myphone";
|
||||
description = "Name of the VPN client peer.";
|
||||
};
|
||||
publicKey = mkOption {
|
||||
example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
|
||||
type = types.singleLineStr;
|
||||
description = "The base64 public key of the peer.";
|
||||
};
|
||||
presharedKey = mkOption {
|
||||
default = null;
|
||||
example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
Base64 preshared key generated by {command}`wg genpsk`.
|
||||
Optional, and may be omitted. This option adds an additional layer of
|
||||
symmetric-key cryptography to be mixed into the already existing
|
||||
public-key cryptography, for post-quantum resistance.
|
||||
|
||||
Warning: Consider using presharedKeyFile instead if you do not
|
||||
want to store the key in the world-readable Nix store.
|
||||
'';
|
||||
};
|
||||
presharedKeyFile = mkOption {
|
||||
default = null;
|
||||
example = "/private/wireguard_psk";
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
File pointing to preshared key as generated by {command}`wg genpsk`.
|
||||
Optional, and may be omitted. This option adds an additional layer of
|
||||
symmetric-key cryptography to be mixed into the already existing
|
||||
public-key cryptography, for post-quantum resistance.
|
||||
'';
|
||||
};
|
||||
allowedIPs = mkOption {
|
||||
example = [
|
||||
"10.192.122.3/32"
|
||||
"10.192.124.1/24"
|
||||
];
|
||||
type = with types; listOf str;
|
||||
description = ''
|
||||
List of IP (v4 or v6) addresses with CIDR masks from
|
||||
which this peer is allowed to send incoming traffic and to which
|
||||
outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
|
||||
be specified for matching all IPv4 addresses, and ::/0 may be specified
|
||||
for matching all IPv6 addresses.'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
options.cloonar-assistant.vpn = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable VPN";
|
||||
};
|
||||
privateKeyFile = mkOption {
|
||||
type = with types; nullOr str;
|
||||
example = "/private/wireguard_private_key";
|
||||
description = "File pointing to private key as generated by {command}`wg genkey`.";
|
||||
};
|
||||
clients = mkOption {
|
||||
default = [ ];
|
||||
description = "VPN Clients";
|
||||
type = with types; listOf (submodule vpn-client-opts);
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
networking.wireguard.interfaces = mkIf cfg.enable {
|
||||
wg_cloonar = {
|
||||
ips = [ "${config.cloonar-assistant.networkPrefix}.98.1/24" ];
|
||||
listenPort = 51820;
|
||||
privateKeyFile = cfg.privateKeyFile;
|
||||
peers = cfg.clients;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,95 +1,115 @@
|
||||
{ config, pkgs, lib, ... }: {
|
||||
### 1) Make sure we have the tools we need
|
||||
environment.systemPackages = with pkgs; [
|
||||
curl
|
||||
jq
|
||||
];
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
users.users.updns-client = {
|
||||
isSystemUser = true;
|
||||
group = "updns-client";
|
||||
};
|
||||
users.groups.updns-client = { };
|
||||
with lib;
|
||||
|
||||
sops.secrets.updns-client = {
|
||||
owner = "updns-client";
|
||||
restartUnits = [ "updns-client.service" ];
|
||||
};
|
||||
|
||||
### 3) Write the check‐script into /etc/external-ip/check.sh (0400, executable)
|
||||
environment.etc."updns-client/run.sh".text = lib.mkIf config.cloonar-assistant.updns-client.enable ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Where our secret lives (encrypted)
|
||||
SECRET=${config.cloonar-assistant.updns-client.secretFile}
|
||||
# Where we record the last‐seen IP
|
||||
LAST_IP_FILE=/var/lib/updns-client/last-ip
|
||||
|
||||
# Decrypt the API key at runtime
|
||||
API_KEY=$(cat "$SECRET")
|
||||
|
||||
# Fetch current external IP
|
||||
IP=$(curl -fsSL https://ifconfig.me)
|
||||
|
||||
# Ensure state directory exists
|
||||
mkdir -p "$(dirname \"$LAST_IP_FILE\")"
|
||||
|
||||
# Read old IP (if any)
|
||||
if [[ -f "$LAST_IP_FILE" ]]; then
|
||||
OLD_IP=$(< "$LAST_IP_FILE")
|
||||
else
|
||||
OLD_IP=""
|
||||
fi
|
||||
|
||||
# If it's changed, notify the API and update the file
|
||||
if [[ "$IP" != "$OLD_IP" ]]; then
|
||||
|
||||
PAYLOAD=$(jq -n \
|
||||
--arg key \"${config.cloonar-assistant.updns-client.key}" \
|
||||
--arg secret "$SECRET" \
|
||||
--arg host "${config.cloonar-assistant.domain}" \
|
||||
--arg ip "$IP" \
|
||||
'{key: $key, secret: $secret, host: $host, ip: $ip}')
|
||||
|
||||
curl -fsS -X POST https://updns-client.cloonar.com/update \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD"
|
||||
|
||||
echo "$IP" > "$LAST_IP_FILE"
|
||||
fi
|
||||
'';
|
||||
environment.etc."updns-client/run.sh".mode = "0500";
|
||||
|
||||
### 4) Ensure /var/lib/external-ip exists on boot
|
||||
systemd.tmpfiles.rules = [
|
||||
# path mode owner group age
|
||||
"d /var/lib/updns-client 0755 root root -"
|
||||
];
|
||||
|
||||
### 5) Define the oneshot service
|
||||
systemd.services.updns-client = lib.mkIf config.cloonar-assistant.updns-client.enable {
|
||||
description = "Check external IP and notify API on change";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
WorkingDirectory = "/var/lib/updns-client";
|
||||
ExecStart = "${pkgs.bash}/bin/bash /etc/updns-client/run.sh";
|
||||
User = "updns-client";
|
||||
let
|
||||
cfg = config.cloonar-assistant.updns-client;
|
||||
in {
|
||||
options.cloonar-assistant.updns-client = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable updns";
|
||||
};
|
||||
key = mkOption {
|
||||
type = with types; nullOr str;
|
||||
example = "example";
|
||||
description = "key for updns";
|
||||
};
|
||||
secretFile = mkOption {
|
||||
type = with types; nullOr str;
|
||||
example = "/private/updns_secret";
|
||||
description = "File pointing to secret as generated by {command}`wg genpsk`.";
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
### 6) Define the timer (runs at boot + every 5 minutes)
|
||||
systemd.timers.updns-client = lib.mkIf config.cloonar-assistant.updns-client.enable {
|
||||
description = "Run updns-client.service every 5 minutes";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = "1min";
|
||||
OnUnitActiveSec = "5min";
|
||||
Persistent = true;
|
||||
Unit = "updns-client.service";
|
||||
config = {
|
||||
environment.systemPackages = with pkgs; [
|
||||
curl
|
||||
jq
|
||||
];
|
||||
|
||||
users.users.updns-client = {
|
||||
isSystemUser = true;
|
||||
group = "updns-client";
|
||||
};
|
||||
users.groups.updns-client = { };
|
||||
|
||||
sops.secrets.updns-client = {
|
||||
owner = "updns-client";
|
||||
restartUnits = [ "updns-client.service" ];
|
||||
};
|
||||
|
||||
environment.etc."updns-client/run.sh".text = mkIf cfg.enable ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Where our secret lives (encrypted)
|
||||
SECRET=${cfg.secretFile}
|
||||
# Where we record the last‐seen IP
|
||||
LAST_IP_FILE=/var/lib/updns-client/last-ip
|
||||
|
||||
# Decrypt the API key at runtime
|
||||
API_KEY=$(cat "$SECRET")
|
||||
|
||||
# Fetch current external IP
|
||||
IP=$(curl -fsSL https://ifconfig.me)
|
||||
|
||||
# Ensure state directory exists
|
||||
mkdir -p "$(dirname \"$LAST_IP_FILE\")"
|
||||
|
||||
# Read old IP (if any)
|
||||
if [[ -f "$LAST_IP_FILE" ]]; then
|
||||
OLD_IP=$(< "$LAST_IP_FILE")
|
||||
else
|
||||
OLD_IP=""
|
||||
fi
|
||||
|
||||
# If it's changed, notify the API and update the file
|
||||
if [[ "$IP" != "$OLD_IP" ]]; then
|
||||
|
||||
PAYLOAD=$(jq -n \
|
||||
--arg key \"${cfg.key}" \
|
||||
--arg secret "$SECRET" \
|
||||
--arg host "${config.cloonar-assistant.domain}" \
|
||||
--arg ip "$IP" \
|
||||
'{key: $key, secret: $secret, host: $host, ip: $ip}')
|
||||
|
||||
curl -fsS -X POST https://updns-client.cloonar.com/update \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD"
|
||||
|
||||
echo "$IP" > "$LAST_IP_FILE"
|
||||
fi
|
||||
'';
|
||||
environment.etc."updns-client/run.sh".mode = "0500";
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/updns-client 0755 root root -"
|
||||
];
|
||||
|
||||
systemd.services.updns-client = mkIf cfg.enable {
|
||||
description = "Check external IP and notify API on change";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
WorkingDirectory = "/var/lib/updns-client";
|
||||
ExecStart = "${pkgs.bash}/bin/bash /etc/updns-client/run.sh";
|
||||
User = "updns-client";
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
systemd.timers.updns-client = mkIf cfg.enable {
|
||||
description = "Run updns-client.service every 5 minutes";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = "1min";
|
||||
OnUnitActiveSec = "5min";
|
||||
Persistent = true;
|
||||
Unit = "updns-client.service";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user