Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2311.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

- Create the first release note entry in this section!

- [acme-dns](https://github.com/joohoi/acme-dns), a limited DNS server to handle ACME DNS challenges easily and securely. Available as [services.acme-dns](#opt-services.acme-dns.enable).

<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->

- [river](https://github.com/riverwm/river), A dynamic tiling wayland compositor. Available as [programs.river](#opt-programs.river.enable).
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,7 @@
./services/network-filesystems/xtreemfs.nix
./services/network-filesystems/yandex-disk.nix
./services/networking/3proxy.nix
./services/networking/acme-dns.nix
./services/networking/adguardhome.nix
./services/networking/alice-lg.nix
./services/networking/amuled.nix
Expand Down
154 changes: 154 additions & 0 deletions nixos/modules/services/networking/acme-dns.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
{ lib
, config
, pkgs
, ...
}:

let
cfg = config.services.acme-dns;
format = pkgs.formats.toml { };
inherit (lib)
literalExpression
mdDoc
mkEnableOption
mkOption
mkPackageOptionMD
types
;
domain = "acme-dns.example.com";
in
{
options.services.acme-dns = {
enable = mkEnableOption (mdDoc "acme-dns");

package = mkPackageOptionMD pkgs "acme-dns" { };

settings = mkOption {
description = mdDoc ''
Free-form settings written directly to the `acme-dns.cfg` file.
Refer to <https://github.com/joohoi/acme-dns/blob/master/README.md#configuration> for supported values.
'';

default = { };

type = types.submodule {
freeformType = format.type;
options = {
general = {
listen = mkOption {
type = types.str;
description = mdDoc "IP+port combination to bind and serve the DNS server on.";
default = "[::]:53";
example = "127.0.0.1:53";
};

protocol = mkOption {
type = types.enum [ "both" "both4" "both6" "udp" "udp4" "udp6" "tcp" "tcp4" "tcp6" ];
description = mdDoc "Protocols to serve DNS responses on.";
default = "both";
};

domain = mkOption {
type = types.str;
description = mdDoc "Domain name to serve the requests off of.";
example = domain;
};

nsname = mkOption {
type = types.str;
description = mdDoc "Zone name server.";
example = domain;
};

nsadmin = mkOption {
type = types.str;
description = mdDoc "Zone admin email address for `SOA`.";
example = "admin.example.com";
};

records = mkOption {
type = types.listOf types.str;
description = mdDoc "Predefined DNS records served in addition to the `_acme-challenge` TXT records.";
example = literalExpression ''
[
# replace with your acme-dns server's public IPv4
"${domain}. A 198.51.100.1"
# replace with your acme-dns server's public IPv6
"${domain}. AAAA 2001:db8::1"
# ${domain} should resolve any *.${domain} records
"${domain}. NS ${domain}."
]
'';
};
};

database = {
engine = mkOption {
type = types.enum [ "sqlite3" "postgres" ];
description = mdDoc "Database engine to use.";
default = "sqlite3";
};
connection = mkOption {
type = types.str;
description = mdDoc "Database connection string.";
example = "postgres://user:password@localhost/acmedns";
default = "/var/lib/acme-dns/acme-dns.db";
};
};

api = {
ip = mkOption {
type = types.str;
description = mdDoc "IP to bind the HTTP API on.";
default = "[::]";
example = "127.0.0.1";
};

port = mkOption {
type = types.port;
description = mdDoc "Listen port for the HTTP API.";
default = 8080;
# acme-dns expects this value to be a string
apply = toString;
};

disable_registration = mkOption {
type = types.bool;
description = mdDoc "Whether to disable the HTTP registration endpoint.";
default = false;
example = true;
};

tls = mkOption {
type = types.enum [ "letsencrypt" "letsencryptstaging" "cert" "none" ];
description = mdDoc "TLS backend to use.";
default = "none";
};
};


logconfig = {
loglevel = mkOption {
type = types.enum [ "error" "warning" "info" "debug" ];
description = mdDoc "Level to log on.";
default = "info";
};
};
};
};
};
};

config = lib.mkIf cfg.enable {
systemd.packages = [ cfg.package ];
systemd.services.acme-dns = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = [ "" "${lib.getExe cfg.package} -c ${format.generate "acme-dns.toml" cfg.settings}" ];
StateDirectory = "acme-dns";
WorkingDirectory = "%S/acme-dns";
DynamicUser = true;
};
};
};
}
50 changes: 50 additions & 0 deletions nixos/tests/acme-dns.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import ./make-test-python.nix ({ ... }: {
name = "acme-dns";

nodes.machine = { pkgs, ... }: {
services.acme-dns = {
enable = true;
settings = {
general = rec {
domain = "acme-dns.home.arpa";
nsname = domain;
nsadmin = "admin.home.arpa";
records = [
"${domain}. A 127.0.0.1"
"${domain}. AAAA ::1"
"${domain}. NS ${domain}."
];
};
logconfig.loglevel = "debug";
};
};
environment.systemPackages = with pkgs; [ curl bind ];
};

testScript = ''
import json

machine.wait_for_unit("acme-dns.service")
machine.wait_for_open_port(53) # dns
machine.wait_for_open_port(8080) # http api

result = machine.succeed("curl --fail -X POST http://localhost:8080/register")
print(result)

registration = json.loads(result)

machine.succeed(f'dig -t TXT @localhost {registration["fulldomain"]} | grep "SOA" | grep "admin.home.arpa"')

# acme-dns exspects a TXT value string length of exactly 43 chars
txt = "___dummy_validation_token_for_txt_record___"

machine.succeed(
"curl --fail -X POST http://localhost:8080/update "
+ f' -H "X-Api-User: {registration["username"]}"'
+ f' -H "X-Api-Key: {registration["password"]}"'
+ f' -d \'{{"subdomain":"{registration["subdomain"]}", "txt":"{txt}"}}\'''
)

assert txt in machine.succeed(f'dig -t TXT +short @localhost {registration["fulldomain"]}')
'';
})
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ in {
_3proxy = runTest ./3proxy.nix;
aaaaxy = runTest ./aaaaxy.nix;
acme = runTest ./acme.nix;
acme-dns = handleTest ./acme-dns.nix {};
adguardhome = runTest ./adguardhome.nix;
aesmd = runTestOn ["x86_64-linux"] ./aesmd.nix;
agate = runTest ./web-servers/agate.nix;
Expand Down
34 changes: 34 additions & 0 deletions pkgs/servers/dns/acme-dns/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{ lib
, buildGoModule
, fetchFromGitHub
, nixosTests
}:

buildGoModule rec {
pname = "acme-dns";
version = "1.0";

src = fetchFromGitHub {
owner = "joohoi";
repo = pname;
rev = "v${version}";
hash = "sha256-qQwvhouqzkChWeu65epgoeMNqZyAD18T+xqEMgdMbhA=";
};

vendorHash = "sha256-q/P+cH2OihvPxPj2XWeLsTBHzQQABp0zjnof+Ys/qKo=";

postInstall = ''
install -D -m0444 -t $out/lib/systemd/system ./acme-dns.service
substituteInPlace $out/lib/systemd/system/acme-dns.service --replace "/usr/local/bin/acme-dns" "$out/bin/acme-dns"
'';

passthru.tests = { inherit (nixosTests) acme-dns; };

meta = {
description = "Limited DNS server to handle ACME DNS challenges easily and securely";
homepage = "https://github.com/joohoi/acme-dns";
changelog = "https://github.com/joohoi/acme-dns/releases/tag/${src.rev}";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ emilylange ];
};
}
2 changes: 2 additions & 0 deletions pkgs/top-level/all-packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,8 @@ with pkgs;

accuraterip-checksum = callPackage ../tools/audio/accuraterip-checksum { };

acme-dns = callPackage ../servers/dns/acme-dns/default.nix { };

acme-sh = callPackage ../tools/admin/acme-sh { };

acousticbrainz-client = callPackage ../tools/audio/acousticbrainz-client { };
Expand Down