nixos modules for convenient deployment of cloud resources

feat: initial implementation with frewall resource for hetzner

ptr.pet b7f69b5a

verified
Changed files
+162
firewall
provider
+30
firewall/default.nix
···
+
{lib, config, options, ...}: let
+
l = lib;
+
t = l.types;
+
cfg = config.networking.firewall.public;
+
+
portOptions = {
+
inherit (options.networking.firewall)
+
allowedTCPPorts
+
allowedUDPPorts
+
allowedTCPPortRanges
+
allowedUDPPortRanges;
+
};
+
in {
+
options = {
+
networking.firewall.public = l.mkOption {
+
default = { };
+
type = t.attrsOf (t.submodule [{ options = portOptions; }]);
+
description = "Tagged open port sets.";
+
};
+
};
+
+
config = let
+
concatAll = name: l.concatLists (l.mapAttrsToList (_: opts: opts.${name}) cfg);
+
in {
+
networking.firewall.allowedTCPPorts = concatAll "allowedTCPPorts";
+
networking.firewall.allowedTCPPortRanges = concatAll "allowedTCPPortRanges";
+
networking.firewall.allowedUDPPorts = concatAll "allowedUDPPorts";
+
networking.firewall.allowedUDPPortRanges = concatAll "allowedUDPPortRanges";
+
};
+
}
+37
firewall/provider/hetzner/app.nu
···
+
use std/log
+
+
let authHeader = ["authorization" $"Bearer ($env.HETZNER_API_TOKEN)"]
+
+
def makeApiUrl [path: string] {
+
return $"https://api.hetzner.cloud/v1($path)"
+
}
+
def post [path: string] {
+
let resp = $in | http post -e --full -H authHeader --content-type application/json (makeApiUrl path)
+
$resp.body = $resp.body | from json
+
$resp
+
}
+
def get [path: string] {
+
let resp = http get -e --full -H authHeader (makeApiUrl path)
+
$resp.body = $resp.body | from json
+
$resp
+
}
+
+
# first fetch firewall to see if it even exists
+
let resp = get $"/firewalls/($firewallId)"
+
if $resp.status == 404 {
+
log error $"provided firewall \(id ($firewallId)\) does not exist"
+
exit 1
+
}
+
let firewall = $resp.body | get firewall
+
+
# backup firewall
+
let backupPath = $".hetzner/($firewallId).json"
+
mkdir .hetzner; $firewall | to json | save $backupPath
+
log info $"backing up firewall ($firewallId) to ($backupPath)"
+
+
# apply rules
+
let resp = open $rulesFile | from json | post $"/firewalls/($firewallId)/actions/set_rules"
+
if $resp.status != 201 {
+
log error $"could not apply firewall \(id ($firewallId)\)"
+
}
+
log info $"applied firewall ($firewallId)"
+56
firewall/provider/hetzner/default.nix
···
+
{pkgs, lib, config, options, ...}: let
+
l = lib;
+
t = l.types;
+
taggedPorts = config.networking.firewall.public;
+
cfg = config.providers.hetzner;
+
in {
+
options = {
+
providers.hetzner.firewall = {
+
id = l.mkOption {
+
type = t.ints.unsigned;
+
description = "The ID of the firewall to update.";
+
};
+
app = l.mkOption {
+
type = t.package;
+
readOnly = true;
+
description = ''
+
The generated app for this provider, run it to apply the configuration.
+
+
For this to work, you need to set the `HETZNER_API_TOKEN` environment variable to a valid API token from Hetzner.
+
'';
+
};
+
};
+
};
+
+
config = let
+
mkRule = proto: tag: port: {
+
description = tag;
+
direction = "in";
+
protocol = proto;
+
port =
+
if l.isAttrs port
+
then l.concatMapStringsSep "-" toString [port.from port.to]
+
else toString port;
+
};
+
mkTcpRule = mkRule "tcp";
+
mkUdpRule = mkRule "udp";
+
firewallRules = pkgs.writers.writeJSON "hetzner-firewall-${toString cfg.id}-rules.json" {
+
rules = l.flatten (
+
l.mapAttrsToList
+
(tag: ports: [
+
(l.map (mkTcpRule tag) ports.allowedTCPPorts)
+
(l.map (mkTcpRule tag) ports.allowedTCPPortRanges)
+
(l.map (mkUdpRule tag) ports.allowedUDPPorts)
+
(l.map (mkUdpRule tag) ports.allowedUDPPortRanges)
+
])
+
taggedPorts
+
);
+
};
+
in {
+
providers.hetzner.firewall.app = pkgs.writers.writeNu "apply-hetzner" ''
+
let firewallId = ${toString cfg.id}
+
let rulesFile = ${firewallRules}
+
${l.fileContents ./app.nu}
+
'';
+
};
+
}
+27
flake.lock
···
+
{
+
"nodes": {
+
"nixpkgs": {
+
"locked": {
+
"lastModified": 1752480373,
+
"narHash": "sha256-JHQbm+OcGp32wAsXTE/FLYGNpb+4GLi5oTvCxwSoBOA=",
+
"owner": "nixos",
+
"repo": "nixpkgs",
+
"rev": "62e0f05ede1da0d54515d4ea8ce9c733f12d9f08",
+
"type": "github"
+
},
+
"original": {
+
"owner": "nixos",
+
"ref": "nixos-unstable",
+
"repo": "nixpkgs",
+
"type": "github"
+
}
+
},
+
"root": {
+
"inputs": {
+
"nixpkgs": "nixpkgs"
+
}
+
}
+
},
+
"root": "root",
+
"version": 7
+
}
+12
flake.nix
···
+
{
+
description = "nixos modules for convenient deployment of cloud resources";
+
+
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+
+
outputs = inp: {
+
nixosModules = {
+
firewall = ./firewall/default.nix;
+
firewall-hetzner = ./firewall/provider/hetzner/default.nix;
+
};
+
};
+
}