0
0
Fork 0
mirror of https://github.com/NixOS/nixpkgs.git synced 2025-07-21 01:20:34 +03:00
nixpkgs/nixos/tests/vector/dnstap.nix

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

381 lines
11 KiB
Nix
Raw Normal View History

{ lib, pkgs, ... }:
let
dnstapSocket = "/var/run/vector/dnstap.sock";
in
{
name = "vector-dnstap";
meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
nodes = {
clickhouse =
{ config, pkgs, ... }:
{
networking.firewall.allowedTCPPorts = [ 6000 ];
services.vector = {
enable = true;
settings = {
sources = {
vector_dnstap_source = {
type = "vector";
address = "[::]:6000";
};
};
sinks = {
clickhouse = {
type = "clickhouse";
inputs = [
"vector_dnstap_source"
];
endpoint = "http://localhost:8123";
database = "dnstap";
table = "records";
date_time_best_effort = true;
};
};
};
};
services.clickhouse.enable = true;
};
knot =
{
config,
nodes,
pkgs,
...
}:
let
exampleZone = pkgs.writeTextDir "example.com.zone" ''
@ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
@ NS ns1
@ NS ns2
ns1 A 192.168.0.1
ns1 AAAA fd00::1
ns2 A 192.168.0.2
ns2 AAAA fd00::2
www A 192.0.2.1
www AAAA 2001:DB8::1
sub NS ns.example.com.
'';
knotZonesEnv = pkgs.buildEnv {
name = "knot-zones";
paths = [
exampleZone
];
};
in
{
networking.firewall.allowedUDPPorts = [ 53 ];
services.vector = {
enable = true;
settings = {
sources = {
dnstap = {
type = "dnstap";
multithreaded = true;
mode = "unix";
lowercase_hostnames = true;
socket_file_mode = 504;
socket_path = "${dnstapSocket}";
};
};
sinks = {
vector_dnstap_sink = {
type = "vector";
inputs = [ "dnstap" ];
address = "clickhouse:6000";
};
};
};
};
systemd.services.vector.serviceConfig = {
RuntimeDirectory = "vector";
RuntimeDirectoryMode = "0770";
};
services.knot = {
enable = true;
settings = {
server = {
listen = [
"0.0.0.0@53"
"::@53"
];
automatic-acl = true;
};
template.default = {
storage = knotZonesEnv;
dnssec-signing = false;
# Input-only zone files
# https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3
# prevents modification of the zonefiles, since the zonefiles are immutable
zonefile-sync = -1;
zonefile-load = "difference";
journal-content = "changes";
global-module = "mod-dnstap/capture_all";
};
zone = {
"example.com".file = "example.com.zone";
};
mod-dnstap = [
{
id = "capture_all";
sink = "unix:${dnstapSocket}";
}
];
};
};
systemd.services.knot = {
after = [ "vector.service" ];
wants = [ "vector.service" ];
serviceConfig = {
# DNSTAP access
ReadWritePaths = [ "/var/run/vector" ];
SupplementaryGroups = [ "vector" ];
};
};
};
unbound =
{
config,
nodes,
pkgs,
...
}:
{
networking.firewall.allowedUDPPorts = [ 53 ];
services.vector = {
enable = true;
settings = {
sources = {
dnstap = {
type = "dnstap";
multithreaded = true;
mode = "unix";
lowercase_hostnames = true;
socket_file_mode = 504;
socket_path = "${dnstapSocket}";
};
};
sinks = {
file = {
type = "file";
inputs = [ "dnstap" ];
path = "/var/lib/vector/logs.log";
encoding = {
codec = "json";
};
};
vector_dnstap_sink = {
type = "vector";
inputs = [ "dnstap" ];
address = "clickhouse:6000";
};
2024-05-21 17:21:57 +01:00
};
};
};
2024-05-21 17:21:57 +01:00
systemd.services.vector.serviceConfig = {
RuntimeDirectory = "vector";
RuntimeDirectoryMode = "0770";
};
services.unbound = {
enable = true;
enableRootTrustAnchor = false;
package = pkgs.unbound-full;
settings = {
server = {
interface = [
"0.0.0.0"
"::"
];
access-control = [
"192.168.0.0/24 allow"
"::/0 allow"
];
domain-insecure = "local";
private-domain = "local";
local-zone = "local. static";
local-data = [
''"test.local. 10800 IN A 192.168.123.5"''
];
};
2024-05-21 17:21:57 +01:00
forward-zone = [
{
name = "example.com.";
forward-addr = [
nodes.knot.networking.primaryIPv6Address
nodes.knot.networking.primaryIPAddress
];
}
];
dnstap = {
dnstap-enable = "yes";
dnstap-socket-path = "${dnstapSocket}";
dnstap-send-identity = "yes";
dnstap-send-version = "yes";
dnstap-log-client-query-messages = "yes";
dnstap-log-client-response-messages = "yes";
2024-05-21 17:21:57 +01:00
};
};
};
systemd.services.unbound = {
after = [ "vector.service" ];
wants = [ "vector.service" ];
serviceConfig = {
# DNSTAP access
ReadWritePaths = [ "/var/run/vector" ];
SupplementaryGroups = [ "vector" ];
};
2024-05-21 17:21:57 +01:00
};
};
dnsclient =
{ config, pkgs, ... }:
{
environment.systemPackages = [ pkgs.dig ];
};
};
testScript =
let
# work around quote/substitution complexity by Nix, Perl, bash and SQL.
databaseDDL = pkgs.writeText "database.sql" "CREATE DATABASE IF NOT EXISTS dnstap";
tableDDL = pkgs.writeText "table.sql" ''
CREATE TABLE IF NOT EXISTS dnstap.records (
timestamp DateTime64(6),
dataType LowCardinality(String),
dataTypeId UInt8,
messageType LowCardinality(String),
messageTypeId UInt8,
requestData Nullable(JSON),
responseData Nullable(JSON),
responsePort UInt16,
serverId LowCardinality(String),
serverVersion LowCardinality(String),
socketFamily LowCardinality(String),
socketProtocol LowCardinality(String),
sourceAddress String,
sourcePort UInt16,
)
ENGINE = MergeTree()
ORDER BY (serverId, timestamp)
PARTITION BY toYYYYMM(timestamp)
'';
tableView = pkgs.writeText "view.sql" ''
CREATE MATERIALIZED VIEW dnstap.domains_view (
timestamp DateTime64(6),
serverId LowCardinality(String),
domain String,
record_type LowCardinality(String)
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (serverId, toStartOfHour(timestamp), domain, timestamp)
POPULATE AS
SELECT
timestamp,
serverId,
JSONExtractString(requestData.question[1]::String, 'domainName') as domain,
JSONExtractString(requestData.question[1]::String, 'questionType') as record_type
FROM dnstap.records
WHERE messageTypeId = 5 # ClientQuery
'';
selectDomainCountQuery = pkgs.writeText "select-domain-count.sql" ''
SELECT
domain,
count(domain)
FROM dnstap.domains_view
GROUP BY domain
'';
selectAuthResponseQuery = pkgs.writeText "select-auth-response.sql" ''
SELECT
*
FROM dnstap.records
WHERE messageType = 'AuthResponse'
'';
in
''
clickhouse.wait_for_unit("clickhouse")
clickhouse.wait_for_open_port(6000)
clickhouse.wait_for_open_port(8123)
clickhouse.succeed(
"cat ${databaseDDL} | clickhouse-client",
"cat ${tableDDL} | clickhouse-client",
"cat ${tableView} | clickhouse-client",
)
knot.wait_for_unit("knot")
unbound.wait_for_unit("unbound")
for machine in knot, unbound:
machine.wait_for_unit("vector")
machine.wait_until_succeeds(
"journalctl -o cat -u vector.service | grep 'Socket permissions updated to 0o770'"
)
machine.wait_until_succeeds(
"journalctl -o cat -u vector.service | grep 'component_type=dnstap' | grep 'Listening... path=\"${dnstapSocket}\"'"
)
machine.wait_for_file("${dnstapSocket}")
machine.succeed("test 770 -eq $(stat -c '%a' ${dnstapSocket})")
dnsclient.systemctl("start network-online.target")
dnsclient.wait_for_unit("network-online.target")
dnsclient.succeed(
"dig @unbound test.local",
"dig @unbound www.example.com"
)
unbound.wait_for_file("/var/lib/vector/logs.log")
unbound.wait_until_succeeds(
"grep ClientQuery /var/lib/vector/logs.log | grep '\"domainName\":\"test.local.\"' | grep '\"rcodeName\":\"NoError\"'"
)
unbound.wait_until_succeeds(
"grep ClientResponse /var/lib/vector/logs.log | grep '\"domainName\":\"test.local.\"' | grep '\"rData\":\"192.168.123.5\"'"
)
clickhouse.log(clickhouse.wait_until_succeeds(
"cat ${selectDomainCountQuery} | clickhouse-client | grep 'test.local.'"
))
clickhouse.log(clickhouse.wait_until_succeeds(
"cat ${selectDomainCountQuery} | clickhouse-client | grep 'www.example.com.'"
))
clickhouse.log(clickhouse.wait_until_succeeds(
"cat ${selectAuthResponseQuery} | clickhouse-client | grep 'Knot DNS ${pkgs.knot-dns.version}'"
))
'';
}