auth dns over atproto
at main 419 lines 14 kB view raw
1{ 2 description = "onis decentralized DNS over ATProto"; 3 4 inputs = { 5 nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 crane.url = "github:ipetkov/crane"; 7 flake-utils.url = "github:numtide/flake-utils"; 8 }; 9 10 outputs = { self, nixpkgs, crane, flake-utils, ... }: 11 let 12 perSystem = flake-utils.lib.eachSystem [ "x86_64-linux" ] (system: 13 let 14 pkgs = import nixpkgs { 15 inherit system; 16 }; 17 18 craneLib = crane.mkLib pkgs; 19 20 commonArgs = { 21 src = let 22 sqlFilter = path: _type: builtins.match ".*\.sql$" path != null; 23 sqlOrCargo = path: type: 24 (sqlFilter path type) || (craneLib.filterCargoSources path type); 25 in 26 pkgs.lib.cleanSourceWith { 27 src = ./.; 28 filter = sqlOrCargo; 29 }; 30 31 buildInputs = with pkgs; [ 32 openssl 33 ]; 34 35 nativeBuildInputs = with pkgs; [ 36 pkg-config 37 ]; 38 }; 39 40 cargoArtifacts = craneLib.buildDepsOnly commonArgs; 41 42 onis-appview = craneLib.buildPackage (commonArgs // { 43 inherit cargoArtifacts; 44 cargoExtraArgs = "--bin onis-appview"; 45 }); 46 47 onis-dns = craneLib.buildPackage (commonArgs // { 48 inherit cargoArtifacts; 49 cargoExtraArgs = "--bin onis-dns"; 50 }); 51 52 onis-verify = craneLib.buildPackage (commonArgs // { 53 inherit cargoArtifacts; 54 cargoExtraArgs = "--bin onis-verify"; 55 }); 56 in 57 { 58 packages = { 59 inherit onis-appview onis-dns onis-verify; 60 default = pkgs.symlinkJoin { 61 name = "onis"; 62 paths = [ onis-appview onis-dns onis-verify ]; 63 }; 64 }; 65 66 checks = { 67 onis-clippy = craneLib.cargoClippy (commonArgs // { 68 inherit cargoArtifacts; 69 cargoClippyExtraArgs = "-- --deny warnings"; 70 }); 71 72 onis-fmt = craneLib.cargoFmt { 73 src = commonArgs.src; 74 }; 75 76 onis-test = craneLib.cargoNextest (commonArgs // { 77 inherit cargoArtifacts; 78 }); 79 }; 80 81 devShells.default = craneLib.devShell { 82 checks = self.checks.${system}; 83 84 packages = with pkgs; [ 85 clippy 86 rust-analyzer 87 sqlx-cli 88 cargo-watch 89 pkg-config 90 openssl 91 sqlite 92 dig 93 ]; 94 }; 95 } 96 ); 97 in 98 perSystem // { 99 100 nixosModules.default = { config, lib, pkgs, ... }: 101 let 102 inherit (lib) mkEnableOption mkOption types mkIf mkMerge; 103 cfg = config.services.onis; 104 settingsFormat = pkgs.formats.toml { }; 105 106 configFile = settingsFormat.generate "onis.toml" { 107 appview = { 108 inherit (cfg.appview) bind tap_url tap_acks tap_reconnect_delay index_path db_dir; 109 database = { 110 inherit (cfg.appview.database) busy_timeout user_max_connections index_max_connections; 111 }; 112 }; 113 dns = { 114 inherit (cfg.dns) appview_url bind port tcp_timeout ttl_floor slow_query_threshold_ms ns metrics_bind; 115 soa = { 116 inherit (cfg.dns.soa) ttl refresh retry expire minimum mname rname; 117 }; 118 }; 119 verify = { 120 inherit (cfg.verify) appview_url bind port check_interval recheck_interval expected_ns nameservers dns_port; 121 }; 122 }; 123 in 124 { 125 options.services.onis = { 126 127 # ----------------------------------------------------------------- 128 # Appview 129 # ----------------------------------------------------------------- 130 appview = { 131 enable = mkEnableOption "onis appview service"; 132 133 package = mkOption { 134 type = types.package; 135 default = self.packages.${pkgs.system}.onis-appview; 136 defaultText = lib.literalExpression "self.packages.\${pkgs.system}.onis-appview"; 137 description = "The onis-appview package to use."; 138 }; 139 140 bind = mkOption { 141 type = types.str; 142 default = "localhost:3000"; 143 description = "Address and port for the appview HTTP server."; 144 }; 145 146 tap_url = mkOption { 147 type = types.str; 148 default = "ws://localhost:2480/channel"; 149 description = "WebSocket URL for the TAP firehose."; 150 }; 151 152 tap_acks = mkOption { 153 type = types.bool; 154 default = true; 155 description = "Whether to acknowledge TAP messages."; 156 }; 157 158 tap_reconnect_delay = mkOption { 159 type = types.int; 160 default = 5; 161 description = "Seconds to wait before reconnecting after a TAP connection error."; 162 }; 163 164 index_path = mkOption { 165 type = types.str; 166 default = "/var/lib/onis-appview/index.db"; 167 description = "Path to the shared index SQLite database."; 168 }; 169 170 db_dir = mkOption { 171 type = types.str; 172 default = "/var/lib/onis-appview/dbs"; 173 description = "Directory for per-user SQLite databases."; 174 }; 175 176 database = { 177 busy_timeout = mkOption { 178 type = types.int; 179 default = 5; 180 description = "Seconds to wait when the database is locked."; 181 }; 182 183 user_max_connections = mkOption { 184 type = types.int; 185 default = 5; 186 description = "Max connections for per-user database pools."; 187 }; 188 189 index_max_connections = mkOption { 190 type = types.int; 191 default = 10; 192 description = "Max connections for the shared index database pool."; 193 }; 194 }; 195 }; 196 197 # ----------------------------------------------------------------- 198 # DNS 199 # ----------------------------------------------------------------- 200 dns = { 201 enable = mkEnableOption "onis DNS server"; 202 203 package = mkOption { 204 type = types.package; 205 default = self.packages.${pkgs.system}.onis-dns; 206 defaultText = lib.literalExpression "self.packages.\${pkgs.system}.onis-dns"; 207 description = "The onis-dns package to use."; 208 }; 209 210 appview_url = mkOption { 211 type = types.str; 212 default = "http://localhost:3000"; 213 description = "URL of the onis appview API."; 214 }; 215 216 bind = mkOption { 217 type = types.str; 218 default = "0.0.0.0"; 219 description = "Address for the DNS server to listen on."; 220 }; 221 222 port = mkOption { 223 type = types.port; 224 default = 53; 225 description = "Port for the DNS server."; 226 }; 227 228 tcp_timeout = mkOption { 229 type = types.int; 230 default = 30; 231 description = "Seconds before a TCP connection times out."; 232 }; 233 234 ttl_floor = mkOption { 235 type = types.int; 236 default = 60; 237 description = "Minimum TTL enforced on all DNS responses."; 238 }; 239 240 slow_query_threshold_ms = mkOption { 241 type = types.int; 242 default = 50; 243 description = "Log a warning for queries slower than this (milliseconds)."; 244 }; 245 246 ns = mkOption { 247 type = types.listOf types.str; 248 default = [ "ns1.example.com." "ns2.example.com." ]; 249 description = "NS records to serve for all zones (fully qualified, trailing dot)."; 250 }; 251 252 metrics_bind = mkOption { 253 type = types.str; 254 default = "0.0.0.0:9100"; 255 description = "Address and port for the DNS metrics HTTP server."; 256 }; 257 258 soa = { 259 ttl = mkOption { 260 type = types.int; 261 default = 3600; 262 description = "SOA record TTL in seconds."; 263 }; 264 265 refresh = mkOption { 266 type = types.int; 267 default = 3600; 268 description = "SOA refresh interval in seconds."; 269 }; 270 271 retry = mkOption { 272 type = types.int; 273 default = 900; 274 description = "SOA retry interval in seconds."; 275 }; 276 277 expire = mkOption { 278 type = types.int; 279 default = 604800; 280 description = "SOA expire interval in seconds."; 281 }; 282 283 minimum = mkOption { 284 type = types.int; 285 default = 300; 286 description = "SOA minimum (negative cache) TTL in seconds."; 287 }; 288 289 mname = mkOption { 290 type = types.str; 291 default = "ns1.example.com."; 292 description = "SOA MNAME primary nameserver, fully qualified."; 293 }; 294 295 rname = mkOption { 296 type = types.str; 297 default = "admin.example.com."; 298 description = "SOA RNAME admin email in DNS format, fully qualified."; 299 }; 300 }; 301 }; 302 303 # ----------------------------------------------------------------- 304 # Verify 305 # ----------------------------------------------------------------- 306 verify = { 307 enable = mkEnableOption "onis verify service"; 308 309 package = mkOption { 310 type = types.package; 311 default = self.packages.${pkgs.system}.onis-verify; 312 defaultText = lib.literalExpression "self.packages.\${pkgs.system}.onis-verify"; 313 description = "The onis-verify package to use."; 314 }; 315 316 appview_url = mkOption { 317 type = types.str; 318 default = "http://localhost:3000"; 319 description = "URL of the onis appview API."; 320 }; 321 322 bind = mkOption { 323 type = types.str; 324 default = "0.0.0.0"; 325 description = "Address for the verify HTTP server to listen on."; 326 }; 327 328 port = mkOption { 329 type = types.port; 330 default = 3001; 331 description = "Port for the verify HTTP server."; 332 }; 333 334 check_interval = mkOption { 335 type = types.int; 336 default = 60; 337 description = "Seconds between scheduled verification runs."; 338 }; 339 340 recheck_interval = mkOption { 341 type = types.int; 342 default = 3600; 343 description = "Seconds a zone must be stale before rechecking."; 344 }; 345 346 expected_ns = mkOption { 347 type = types.listOf types.str; 348 default = [ "ns1.example.com" "ns2.example.com" ]; 349 description = "Expected NS records that indicate correct delegation."; 350 }; 351 352 nameservers = mkOption { 353 type = types.listOf types.str; 354 default = [ ]; 355 description = "Optional custom resolver IP addresses."; 356 }; 357 358 dns_port = mkOption { 359 type = types.port; 360 default = 53; 361 description = "Port used when resolving against custom nameservers."; 362 }; 363 }; 364 }; 365 366 config = mkMerge [ 367 (mkIf cfg.appview.enable { 368 systemd.services.onis-appview = { 369 description = "onis appview ATProto DNS appview"; 370 wantedBy = [ "multi-user.target" ]; 371 after = [ "network.target" ]; 372 environment.ONIS_CONFIG = "${configFile}"; 373 serviceConfig = { 374 ExecStart = "${cfg.appview.package}/bin/onis-appview"; 375 DynamicUser = true; 376 StateDirectory = "onis-appview"; 377 Restart = "on-failure"; 378 RestartSec = 5; 379 }; 380 }; 381 }) 382 383 (mkIf cfg.dns.enable { 384 systemd.services.onis-dns = { 385 description = "onis DNS authoritative DNS server"; 386 wantedBy = [ "multi-user.target" ]; 387 after = [ "network.target" ]; 388 environment.ONIS_CONFIG = "${configFile}"; 389 serviceConfig = { 390 ExecStart = "${cfg.dns.package}/bin/onis-dns"; 391 DynamicUser = true; 392 StateDirectory = "onis-dns"; 393 Restart = "on-failure"; 394 RestartSec = 5; 395 AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; 396 }; 397 }; 398 }) 399 400 (mkIf cfg.verify.enable { 401 systemd.services.onis-verify = { 402 description = "onis verify DNS delegation checker"; 403 wantedBy = [ "multi-user.target" ]; 404 after = [ "network.target" ]; 405 environment.ONIS_CONFIG = "${configFile}"; 406 serviceConfig = { 407 ExecStart = "${cfg.verify.package}/bin/onis-verify"; 408 DynamicUser = true; 409 StateDirectory = "onis-verify"; 410 Restart = "on-failure"; 411 RestartSec = 5; 412 }; 413 }; 414 }) 415 ]; 416 }; 417 418 }; 419}