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
8 changes: 8 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ jobs:
with:
nix_path: nixpkgs=channel:nixos-24.11
- run: nix build ./#checks.x86_64-linux.vault
flake-check-file:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-24.11
- run: nix build ./#checks.x86_64-linux.file
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
result*

.direnv

.nixos-test-history
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
faythe $DIR/$FILE --config-check >>$out
done
'';
file = pkgs.callPackage ./nixos/file-test.nix {};
vault = pkgs.callPackage ./nixos/vault-test.nix {};
clippy = pkgs."${pname}-clippy";
};
Expand Down
49 changes: 49 additions & 0 deletions nixos/file-test.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{ lib, pkgs }:
let
testLib = import ./lib.nix {
inherit lib pkgs;
};

domain = testLib.domain;
cert_path = "/tmp/faythe";
in
testLib.mkFaytheTest ({ nodes, ... }: {
name = "faythe-file-test";
extraModules.client = [
({ config, pkgs, ... }: {
environment.systemPackages = [pkgs.openssl];

systemd.services.faythe.preStart = ''
mkdir -p ${cert_path}
'';
})
];
faytheExtraConfig = {
file_monitor_configs = [
{
directory = cert_path;
prune = true;
specs = [
{
name = "path1-test";
cn = "path1.${domain}";
key_file_name = "key.pem";
}
];
}
];
};
testScript = ''
with subtest("Normal first time issue"):
client.wait_until_succeeds("stat ${cert_path}/path1-test")

client.wait_until_succeeds("""
journalctl -u faythe | grep "path1-test" | grep -q "touched"
journalctl -u faythe | grep -q "changing group for"
""")

client.succeed("""
openssl x509 -in ${cert_path}/path1-test/fullchain.pem -text -noout | grep -q "Issuer: CN=Pebble Intermediate"
""")
'';
})
170 changes: 170 additions & 0 deletions nixos/lib.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
{ pkgs, lib }:
let
nixos-lib = import (pkgs.path + "/nixos/lib") { };
acme-test-module = "${pkgs.path}/nixos/tests/common/acme/server";

domain = "faythe.test";
ns_host = "ns.${domain}";

# Pebble in version > 2.3.1 (NixOS 24.11 and up) is ramping up towards ACME
# profiles and not issuing CNs for tlsserver profile certs. We want to test
# against behaviour that matches the current letsencrypt behaviour, so stick
# to 2.3.1.
pebble-cn-overlay = self: super: {
pebble = super.pebble.overrideAttrs (oa: rec {
version = "2.3.1";
src = self.fetchFromGitHub {
owner = "letsencrypt";
repo = "pebble";
rev = "v${version}";
hash = "sha256-S9+iRaTSRt4F6yMKK0OJO6Zto9p0dZ3q/mULaipudVo=";
};
});
};
in
{
inherit domain ns_host;
mkFaytheTest = faytheTest:
nixos-lib.runTest (
test@{ nodes, ... }:
let
args = faytheTest test;
optionalExtraModules = name:
(args.extraModules or {}).${name} or [];
in
{
hostPkgs = pkgs;
name = args.name;
defaults = {
nixpkgs.overlays = [ pebble-cn-overlay ];
nixpkgs.pkgs = pkgs;
networking.nameservers = lib.mkForce [ nodes.ns.networking.primaryIPAddress ];
networking.dhcpcd.enable = false;
security.pki.certificateFiles = [ nodes.acme.test-support.acme.caCert ];
networking.hosts."${nodes.acme.networking.primaryIPAddress}" = [ nodes.acme.test-support.acme.caDomain ];
virtualisation.cores = 2;
};
nodes = {
acme =
{ pkgs, ... }:
{
imports = [ acme-test-module ] ++ (optionalExtraModules "acme");
};

ns =
{ pkgs, ... }:
{
imports = optionalExtraModules "ns";

environment.systemPackages = with pkgs; [
dig
dnsutils
];

environment.etc."bind/zones/${domain}.zone" = {
mode = "0644";
user = "named";
group = "named";
text = ''
$TTL 60
${domain}. IN SOA ${ns_host}. admin.${domain}. ( 1 3h 1h 1w 1d )

@ IN NS ${ns_host}.

${ns_host}. IN A ${nodes.ns.networking.primaryIPAddress}
'' + (args.extraBindZoneFileLines or "");
};

networking.firewall.allowedTCPPorts = [ 53 ];
networking.firewall.allowedUDPPorts = [ 53 ];

services.bind.enable = true;

services.bind.zones."${domain}" = {
master = true;
file = "/etc/bind/zones/${domain}.zone";
# the bind zone module is very opinionated and this sets allow-transfer.
slaves = [ nodes.client.networking.primaryIPAddress ];
extraConfig = ''
allow-update { ${nodes.client.networking.primaryIPAddress}; };
'';
};

# Hack to allow access to the directory copied from environment.etc
systemd.services.bind.serviceConfig.ExecStartPre = "+${pkgs.coreutils}/bin/chown named /etc/bind/zones";
};

client =
{ pkgs, config, ... }:
let
faytheConfig = {
lets_encrypt_url = "https://${nodes.acme.test-support.acme.caDomain}/dir";
lets_encrypt_email = "test_mail@${domain}";
zones = {
"${domain}" = {
auth_dns_server = ns_host;
challenge_driver.nsupdate ={
server = ns_host;
key = "test";
};
};
};
val_dns_servers = [ ns_host ];
} // args.faytheExtraConfig;

faytheConfigFile = pkgs.writeText "faythe.config.json" (builtins.toJSON faytheConfig);

faytheConfigFileChecked = pkgs.runCommand "faythe.config.checked.json" { } ''
${pkgs.faythe}/bin/faythe --config-check ${faytheConfigFile}
ln -s ${faytheConfigFile} $out
'';
in
{
imports = optionalExtraModules "client";

environment.systemPackages = with pkgs; [
dig
dnsutils
getent
lsof
];

systemd.services.faythe = {
path = with pkgs; [
dnsutils
dig
];
environment.RUST_BACKTRACE = "full";
environment.RUST_LOG = "warn,acme_lib=debug";
wantedBy = [ "multi-user.target" ];
preStart = ''
# vault provisioning time was masking this, but we need to
# wait for system nameserver to be up before we can start faythe
while ! dig +short -t SOA ${domain}; do
echo "Waiting for nameserver to be up"
sleep 1
done
'';
serviceConfig = {
ExecStart = "${pkgs.faythe}/bin/faythe ${faytheConfigFileChecked}";
};
};
};
};
testScript = ''
start_all()

ns.wait_for_unit("network-online.target")
acme.wait_for_unit("network-online.target")
client.wait_for_unit("network-online.target")

ns.wait_for_unit("bind.service")

client.wait_until_succeeds("ping -c1 ${nodes.ns.networking.primaryIPAddress}")
client.fail("host doesnotexist.${domain}")

client.wait_for_unit("faythe.service")
'' + args.testScript;
}
);
}
Loading