Merge master into staging-next
This commit is contained in:
@@ -225,6 +225,7 @@ in
|
||||
mysql-backup = handleTest ./mysql/mysql-backup.nix {};
|
||||
mysql-replication = handleTest ./mysql/mysql-replication.nix {};
|
||||
nagios = handleTest ./nagios.nix {};
|
||||
nar-serve = handleTest ./nar-serve.nix {};
|
||||
nat.firewall = handleTest ./nat.nix { withFirewall = true; };
|
||||
nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
|
||||
nat.standalone = handleTest ./nat.nix { withFirewall = false; };
|
||||
@@ -368,6 +369,7 @@ in
|
||||
trezord = handleTest ./trezord.nix {};
|
||||
trickster = handleTest ./trickster.nix {};
|
||||
tuptime = handleTest ./tuptime.nix {};
|
||||
unbound = handleTest ./unbound.nix {};
|
||||
udisks2 = handleTest ./udisks2.nix {};
|
||||
unit-php = handleTest ./web-servers/unit-php.nix {};
|
||||
upnp = handleTest ./upnp.nix {};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import ./make-test-python.nix (
|
||||
{ pkgs, lib, ... }:
|
||||
{
|
||||
name = "nar-serve";
|
||||
meta.maintainers = [ lib.maintainers.rizary ];
|
||||
nodes =
|
||||
{
|
||||
server = { pkgs, ... }: {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts.default.root = "/var/www";
|
||||
};
|
||||
services.nar-serve = {
|
||||
enable = true;
|
||||
# Connect to the localhost nginx instead of the default
|
||||
# https://cache.nixos.org
|
||||
cacheURL = "http://localhost/";
|
||||
};
|
||||
environment.systemPackages = [
|
||||
pkgs.hello
|
||||
pkgs.curl
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 8383 ];
|
||||
|
||||
# virtualisation.diskSize = 2 * 1024;
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
# Create a fake cache with Nginx service the static files
|
||||
server.succeed(
|
||||
"nix copy --to file:///var/www ${pkgs.hello}"
|
||||
)
|
||||
server.wait_for_unit("nginx.service")
|
||||
server.wait_for_open_port(80)
|
||||
|
||||
# Check that nar-serve can return the content of the derivation
|
||||
drvName = os.path.basename("${pkgs.hello}")
|
||||
drvHash = drvName.split("-")[0]
|
||||
server.wait_for_unit("nar-serve.service")
|
||||
server.succeed(
|
||||
"curl -o hello -f http://localhost:8383/nix/store/{}/bin/hello".format(drvHash)
|
||||
)
|
||||
'';
|
||||
}
|
||||
)
|
||||
@@ -609,6 +609,50 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
sql = {
|
||||
exporterConfig = {
|
||||
configuration.jobs.points = {
|
||||
interval = "1m";
|
||||
connections = [
|
||||
"postgres://prometheus-sql-exporter@/data?host=/run/postgresql&sslmode=disable"
|
||||
];
|
||||
queries = {
|
||||
points = {
|
||||
labels = [ "name" ];
|
||||
help = "Amount of points accumulated per person";
|
||||
values = [ "amount" ];
|
||||
query = "SELECT SUM(amount) as amount, name FROM points GROUP BY name";
|
||||
};
|
||||
};
|
||||
};
|
||||
enable = true;
|
||||
user = "prometheus-sql-exporter";
|
||||
};
|
||||
metricProvider = {
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
initialScript = builtins.toFile "init.sql" ''
|
||||
CREATE DATABASE data;
|
||||
\c data;
|
||||
CREATE TABLE points (amount INT, name TEXT);
|
||||
INSERT INTO points(amount, name) VALUES (1, 'jack');
|
||||
INSERT INTO points(amount, name) VALUES (2, 'jill');
|
||||
INSERT INTO points(amount, name) VALUES (3, 'jack');
|
||||
|
||||
CREATE USER "prometheus-sql-exporter";
|
||||
GRANT ALL PRIVILEGES ON DATABASE data TO "prometheus-sql-exporter";
|
||||
GRANT SELECT ON points TO "prometheus-sql-exporter";
|
||||
'';
|
||||
};
|
||||
systemd.services.prometheus-sql-exporter.after = [ "postgresql.service" ];
|
||||
};
|
||||
exporterTest = ''
|
||||
wait_for_unit("prometheus-sql-exporter.service")
|
||||
wait_for_open_port(9237)
|
||||
succeed("curl http://localhost:9237/metrics | grep -c 'sql_points{' | grep -q 2")
|
||||
'';
|
||||
};
|
||||
|
||||
surfboard = {
|
||||
exporterConfig = {
|
||||
enable = true;
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
Test that our unbound module indeed works as most users would expect.
|
||||
There are a few settings that we must consider when modifying the test. The
|
||||
ususal use-cases for unbound are
|
||||
* running a recursive DNS resolver on the local machine
|
||||
* running a recursive DNS resolver on the local machine, forwarding to a local DNS server via UDP/53 & TCP/53
|
||||
* running a recursive DNS resolver on the local machine, forwarding to a local DNS server via TCP/853 (DoT)
|
||||
* running a recursive DNS resolver on a machine in the network awaiting input from clients over TCP/53 & UDP/53
|
||||
* running a recursive DNS resolver on a machine in the network awaiting input from clients over TCP/853 (DoT)
|
||||
|
||||
In the below test setup we are trying to implement all of those use cases.
|
||||
|
||||
Another aspect that we cover is access to the local control UNIX socket. It
|
||||
can optionally be enabled and users can optionally be in a group to gain
|
||||
access. Users that are not in the group (except for root) should not have
|
||||
access to that socket. Also, when there is no socket configured, users
|
||||
shouldn't be able to access the control socket at all. Not even root.
|
||||
*/
|
||||
import ./make-test-python.nix ({ pkgs, lib, ... }:
|
||||
let
|
||||
# common client configuration that we can just use for the multitude of
|
||||
# clients we are constructing
|
||||
common = { lib, pkgs, ... }: {
|
||||
config = {
|
||||
environment.systemPackages = [ pkgs.knot-dns ];
|
||||
|
||||
# disable the root anchor update as we do not have internet access during
|
||||
# the test execution
|
||||
services.unbound.enableRootTrustAnchor = false;
|
||||
};
|
||||
};
|
||||
|
||||
cert = pkgs.runCommandNoCC "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
|
||||
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=dns.example.local'
|
||||
mkdir -p $out
|
||||
cp key.pem cert.pem $out
|
||||
'';
|
||||
in
|
||||
{
|
||||
name = "unbound";
|
||||
meta = with pkgs.stdenv.lib.maintainers; {
|
||||
maintainers = [ andir ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
|
||||
# The server that actually serves our zones, this tests unbounds authoriative mode
|
||||
authoritative = { lib, pkgs, config, ... }: {
|
||||
imports = [ common ];
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
|
||||
{ address = "192.168.0.1"; prefixLength = 24; }
|
||||
];
|
||||
networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
|
||||
{ address = "fd21::1"; prefixLength = 64; }
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = [ 53 ];
|
||||
networking.firewall.allowedUDPPorts = [ 53 ];
|
||||
|
||||
services.unbound = {
|
||||
enable = true;
|
||||
interfaces = [ "192.168.0.1" "fd21::1" "::1" "127.0.0.1" ];
|
||||
allowedAccess = [ "192.168.0.0/24" "fd21::/64" "::1" "127.0.0.0/8" ];
|
||||
extraConfig = ''
|
||||
server:
|
||||
local-data: "example.local. IN A 1.2.3.4"
|
||||
local-data: "example.local. IN AAAA abcd::eeff"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# The resolver that knows that fowards (only) to the authoritative server
|
||||
# and listens on UDP/53, TCP/53 & TCP/853.
|
||||
resolver = { lib, nodes, ... }: {
|
||||
imports = [ common ];
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
|
||||
{ address = "192.168.0.2"; prefixLength = 24; }
|
||||
];
|
||||
networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
|
||||
{ address = "fd21::2"; prefixLength = 64; }
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
53 # regular DNS
|
||||
853 # DNS over TLS
|
||||
];
|
||||
networking.firewall.allowedUDPPorts = [ 53 ];
|
||||
|
||||
services.unbound = {
|
||||
enable = true;
|
||||
allowedAccess = [ "192.168.0.0/24" "fd21::/64" "::1" "127.0.0.0/8" ];
|
||||
interfaces = [ "::1" "127.0.0.1" "192.168.0.2" "fd21::2" "192.168.0.2@853" "fd21::2@853" "::1@853" "127.0.0.1@853" ];
|
||||
forwardAddresses = [
|
||||
(lib.head nodes.authoritative.config.networking.interfaces.eth1.ipv6.addresses).address
|
||||
(lib.head nodes.authoritative.config.networking.interfaces.eth1.ipv4.addresses).address
|
||||
];
|
||||
extraConfig = ''
|
||||
server:
|
||||
tls-service-pem: ${cert}/cert.pem
|
||||
tls-service-key: ${cert}/key.pem
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# machine that runs a local unbound that will be reconfigured during test execution
|
||||
local_resolver = { lib, nodes, config, ... }: {
|
||||
imports = [ common ];
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
|
||||
{ address = "192.168.0.3"; prefixLength = 24; }
|
||||
];
|
||||
networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
|
||||
{ address = "fd21::3"; prefixLength = 64; }
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
53 # regular DNS
|
||||
];
|
||||
networking.firewall.allowedUDPPorts = [ 53 ];
|
||||
|
||||
services.unbound = {
|
||||
enable = true;
|
||||
allowedAccess = [ "::1" "127.0.0.0/8" ];
|
||||
interfaces = [ "::1" "127.0.0.1" ];
|
||||
localControlSocketPath = "/run/unbound/unbound.ctl";
|
||||
extraConfig = ''
|
||||
include: "/etc/unbound/extra*.conf"
|
||||
'';
|
||||
};
|
||||
|
||||
users.users = {
|
||||
# user that is permitted to access the unix socket
|
||||
someuser.extraGroups = [
|
||||
config.users.users.unbound.group
|
||||
];
|
||||
|
||||
# user that is not permitted to access the unix socket
|
||||
unauthorizeduser = {};
|
||||
};
|
||||
|
||||
environment.etc = {
|
||||
"unbound-extra1.conf".text = ''
|
||||
forward-zone:
|
||||
name: "example.local."
|
||||
forward-addr: ${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv6.addresses).address}
|
||||
forward-addr: ${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv4.addresses).address}
|
||||
'';
|
||||
"unbound-extra2.conf".text = ''
|
||||
auth-zone:
|
||||
name: something.local.
|
||||
zonefile: ${pkgs.writeText "zone" ''
|
||||
something.local. IN A 3.4.5.6
|
||||
''}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
# plain node that only has network access and doesn't run any part of the
|
||||
# resolver software locally
|
||||
client = { lib, nodes, ... }: {
|
||||
imports = [ common ];
|
||||
networking.nameservers = [
|
||||
(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv6.addresses).address
|
||||
(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv4.addresses).address
|
||||
];
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{ address = "192.168.0.10"; prefixLength = 24; }
|
||||
];
|
||||
networking.interfaces.eth1.ipv6.addresses = [
|
||||
{ address = "fd21::10"; prefixLength = 64; }
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
import typing
|
||||
import json
|
||||
|
||||
zone = "example.local."
|
||||
records = [("AAAA", "abcd::eeff"), ("A", "1.2.3.4")]
|
||||
|
||||
|
||||
def query(
|
||||
machine,
|
||||
host: str,
|
||||
query_type: str,
|
||||
query: str,
|
||||
expected: typing.Optional[str] = None,
|
||||
args: typing.Optional[typing.List[str]] = None,
|
||||
):
|
||||
"""
|
||||
Execute a single query and compare the result with expectation
|
||||
"""
|
||||
text_args = ""
|
||||
if args:
|
||||
text_args = " ".join(args)
|
||||
|
||||
out = machine.succeed(
|
||||
f"kdig {text_args} {query} {query_type} @{host} +short"
|
||||
).strip()
|
||||
machine.log(f"{host} replied with {out}")
|
||||
if expected:
|
||||
assert expected == out, f"Expected `{expected}` but got `{out}`"
|
||||
|
||||
|
||||
def test(machine, remotes, /, doh=False, zone=zone, records=records, args=[]):
|
||||
"""
|
||||
Run queries for the given remotes on the given machine.
|
||||
"""
|
||||
for query_type, expected in records:
|
||||
for remote in remotes:
|
||||
query(machine, remote, query_type, zone, expected, args)
|
||||
query(machine, remote, query_type, zone, expected, ["+tcp"] + args)
|
||||
if doh:
|
||||
query(
|
||||
machine,
|
||||
remote,
|
||||
query_type,
|
||||
zone,
|
||||
expected,
|
||||
["+tcp", "+tls"] + args,
|
||||
)
|
||||
|
||||
|
||||
client.start()
|
||||
authoritative.wait_for_unit("unbound.service")
|
||||
|
||||
# verify that we can resolve locally
|
||||
with subtest("test the authoritative servers local responses"):
|
||||
test(authoritative, ["::1", "127.0.0.1"])
|
||||
|
||||
resolver.wait_for_unit("unbound.service")
|
||||
|
||||
with subtest("root is unable to use unbounc-control when the socket is not configured"):
|
||||
resolver.succeed("which unbound-control") # the binary must exist
|
||||
resolver.fail("unbound-control list_forwards") # the invocation must fail
|
||||
|
||||
# verify that the resolver is able to resolve on all the local protocols
|
||||
with subtest("test that the resolver resolves on all protocols and transports"):
|
||||
test(resolver, ["::1", "127.0.0.1"], doh=True)
|
||||
|
||||
resolver.wait_for_unit("multi-user.target")
|
||||
|
||||
with subtest("client should be able to query the resolver"):
|
||||
test(client, ["${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv6.addresses).address}", "${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv4.addresses).address}"], doh=True)
|
||||
|
||||
# discard the client we do not need anymore
|
||||
client.shutdown()
|
||||
|
||||
local_resolver.wait_for_unit("multi-user.target")
|
||||
|
||||
# link a new config file to /etc/unbound/extra.conf
|
||||
local_resolver.succeed("ln -s /etc/unbound-extra1.conf /etc/unbound/extra1.conf")
|
||||
|
||||
# reload the server & ensure the forwarding works
|
||||
with subtest("test that the local resolver resolves on all protocols and transports"):
|
||||
local_resolver.succeed("systemctl reload unbound")
|
||||
print(local_resolver.succeed("journalctl -u unbound -n 1000"))
|
||||
test(local_resolver, ["::1", "127.0.0.1"], args=["+timeout=60"])
|
||||
|
||||
with subtest("test that we can use the unbound control socket"):
|
||||
out = local_resolver.succeed(
|
||||
"sudo -u someuser -- unbound-control list_forwards"
|
||||
).strip()
|
||||
|
||||
# Thank you black! Can't really break this line into a readable version.
|
||||
expected = "example.local. IN forward ${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv6.addresses).address} ${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv4.addresses).address}"
|
||||
assert out == expected, f"Expected `{expected}` but got `{out}` instead."
|
||||
local_resolver.fail("sudo -u unauthorizeduser -- unbound-control list_forwards")
|
||||
|
||||
|
||||
# link a new config file to /etc/unbound/extra.conf
|
||||
local_resolver.succeed("ln -sf /etc/unbound-extra2.conf /etc/unbound/extra2.conf")
|
||||
|
||||
# reload the server & ensure the new local zone works
|
||||
with subtest("test that we can query the new local zone"):
|
||||
local_resolver.succeed("unbound-control reload")
|
||||
r = [("A", "3.4.5.6")]
|
||||
test(local_resolver, ["::1", "127.0.0.1"], zone="something.local.", records=r)
|
||||
'';
|
||||
})
|
||||
Reference in New Issue
Block a user