[fix] everything: various bugs found in production
This commit is contained in:
parent
68658bf83f
commit
15e2d2da06
7 changed files with 138 additions and 178 deletions
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[build]
|
||||||
|
rustflags = ["-Clink-arg=-fuse-ld=mold", "-Zthreads=16"]
|
150
Cargo.lock
generated
150
Cargo.lock
generated
|
@ -132,6 +132,17 @@ dependencies = [
|
||||||
"http",
|
"http",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-client-ip"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72188bed20deb981f3a4a9fe674e5980fd9e9c2bd880baa94715ad5d60d64c67"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"forwarded-header-value",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
@ -153,29 +164,6 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "axum-extra"
|
|
||||||
version = "0.9.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733"
|
|
||||||
dependencies = [
|
|
||||||
"axum",
|
|
||||||
"axum-core",
|
|
||||||
"bytes",
|
|
||||||
"futures-util",
|
|
||||||
"headers",
|
|
||||||
"http",
|
|
||||||
"http-body",
|
|
||||||
"http-body-util",
|
|
||||||
"mime",
|
|
||||||
"pin-project-lite",
|
|
||||||
"serde",
|
|
||||||
"tower",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.71"
|
version = "0.3.71"
|
||||||
|
@ -218,15 +206,6 @@ version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block-buffer"
|
|
||||||
version = "0.10.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -303,35 +282,6 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cpufeatures"
|
|
||||||
version = "0.2.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crypto-common"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "digest"
|
|
||||||
version = "0.10.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|
||||||
dependencies = [
|
|
||||||
"block-buffer",
|
|
||||||
"crypto-common",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encode_unicode"
|
name = "encode_unicode"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
@ -363,6 +313,16 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "forwarded-header-value"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
|
||||||
|
dependencies = [
|
||||||
|
"nonempty",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
|
@ -396,16 +356,6 @@ dependencies = [
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "generic-array"
|
|
||||||
version = "0.14.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
|
||||||
dependencies = [
|
|
||||||
"typenum",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -423,30 +373,6 @@ version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "headers"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.21.7",
|
|
||||||
"bytes",
|
|
||||||
"headers-core",
|
|
||||||
"http",
|
|
||||||
"httpdate",
|
|
||||||
"mime",
|
|
||||||
"sha1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "headers-core"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
|
|
||||||
dependencies = [
|
|
||||||
"http",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -672,6 +598,12 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nonempty"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
|
@ -908,17 +840,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sha1"
|
|
||||||
version = "0.10.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cpufeatures",
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
@ -1190,12 +1111,6 @@ dependencies = [
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typenum"
|
|
||||||
version = "1.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
|
@ -1232,12 +1147,6 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
@ -1246,14 +1155,13 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webnsupdate"
|
name = "webnsupdate"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"axum-auth",
|
"axum-auth",
|
||||||
"axum-extra",
|
"axum-client-ip",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"clap",
|
"clap",
|
||||||
"headers",
|
|
||||||
"http",
|
"http",
|
||||||
"insta",
|
"insta",
|
||||||
"miette",
|
"miette",
|
||||||
|
|
28
Cargo.toml
28
Cargo.toml
|
@ -1,27 +1,31 @@
|
||||||
|
cargo-features = ["codegen-backend"]
|
||||||
[package]
|
[package]
|
||||||
description = "An HTTP server using HTTP basic auth to make secure calls to nsupdate"
|
description = "An HTTP server using HTTP basic auth to make secure calls to nsupdate"
|
||||||
name = "webnsupdate"
|
name = "webnsupdate"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = "0.7.5"
|
axum = "0.7.5"
|
||||||
axum-auth = { version = "0.7.0", default-features = false, features = [
|
axum-client-ip = "0.6.0"
|
||||||
"auth-basic",
|
|
||||||
] }
|
|
||||||
axum-extra = { version = "0.9.3", features = ["typed-header"] }
|
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
clap = { version = "4.5.4", features = ["derive", "env"] }
|
clap = { version = "4.5.4", features = ["derive", "env"] }
|
||||||
headers = "0.4.0"
|
|
||||||
http = "1.1.0"
|
http = "1.1.0"
|
||||||
insta = "1.38.0"
|
insta = "1.38.0"
|
||||||
miette = { version = "7.2.0", features = ["fancy"] }
|
miette = { version = "7.2.0", features = ["fancy"] }
|
||||||
ring = { version = "0.17.8", features = ["std"] }
|
ring = { version = "0.17.8", features = ["std"] }
|
||||||
tokio = { version = "1.37.0", features = [
|
|
||||||
"macros",
|
|
||||||
"rt",
|
|
||||||
"process",
|
|
||||||
"io-util",
|
|
||||||
] }
|
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
|
|
||||||
|
[dependencies.axum-auth]
|
||||||
|
version = "0.7.0"
|
||||||
|
default-features = false
|
||||||
|
features = ["auth-basic"]
|
||||||
|
|
||||||
|
[dependencies.tokio]
|
||||||
|
version = "1.37.0"
|
||||||
|
features = ["macros", "rt", "process", "io-util"]
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
debug = 0
|
||||||
|
codegen-backend = "cranelift"
|
||||||
|
|
24
flake.lock
24
flake.lock
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1714906307,
|
"lastModified": 1715534503,
|
||||||
"narHash": "sha256-UlRZtrCnhPFSJlDQE7M0eyhgvuuHBTe1eJ9N9AQlJQ0=",
|
"narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "25865a40d14b3f9cf19f19b924e2ab4069b09588",
|
"rev": "2057814051972fa1453ddfb0d98badbea9b83c06",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -17,7 +17,23 @@
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs",
|
||||||
|
"systems": "systems"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
32
flake.nix
32
flake.nix
|
@ -1,31 +1,21 @@
|
||||||
{
|
{
|
||||||
description = "An http server that calls nsupdate internally";
|
description = "An http server that calls nsupdate internally";
|
||||||
|
inputs = {
|
||||||
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
|
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||||
|
systems.url = "github:nix-systems/default";
|
||||||
|
};
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
|
systems,
|
||||||
}: let
|
}: let
|
||||||
supportedSystems = ["x86_64-linux" "aarch64-darwin" "x86_64-darwin" "aarch64-linux"];
|
forEachSupportedSystem = nixpkgs.lib.genAttrs (import systems);
|
||||||
forEachSupportedSystem = f:
|
|
||||||
nixpkgs.lib.genAttrs supportedSystems (system:
|
|
||||||
f {
|
|
||||||
inherit system;
|
|
||||||
pkgs = import nixpkgs {inherit system;};
|
|
||||||
});
|
|
||||||
in {
|
in {
|
||||||
formatter = forEachSupportedSystem ({pkgs, ...}: pkgs.alejandra);
|
formatter = forEachSupportedSystem (system: nixpkgs.legacyPackages.${system}.alejandra);
|
||||||
|
|
||||||
# checks = forEachSupportedSystem ({pkgs, ...}: {
|
packages = forEachSupportedSystem (system: {
|
||||||
# module = pkgs.testers.runNixOSTest {
|
default = nixpkgs.legacyPackages.${system}.callPackage ./default.nix {};
|
||||||
# name = "webnsupdate module test";
|
|
||||||
# nodes.testMachine = {imports = [self.nixosModules.default];};
|
|
||||||
# };
|
|
||||||
# });
|
|
||||||
|
|
||||||
packages = forEachSupportedSystem ({pkgs, ...}: {
|
|
||||||
default = pkgs.callPackage ./default.nix {};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
overlays.default = final: prev: {
|
overlays.default = final: prev: {
|
||||||
|
@ -34,7 +24,9 @@
|
||||||
|
|
||||||
nixosModules.default = ./module.nix;
|
nixosModules.default = ./module.nix;
|
||||||
|
|
||||||
devShells = forEachSupportedSystem ({pkgs, ...}: {
|
devShells = forEachSupportedSystem (system: let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
packages = [pkgs.cargo-insta];
|
packages = [pkgs.cargo-insta];
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,11 @@ in {
|
||||||
type = types.submodule {
|
type = types.submodule {
|
||||||
options = {
|
options = {
|
||||||
enable = mkEnableOption "webnsupdate";
|
enable = mkEnableOption "webnsupdate";
|
||||||
|
extraArgs = mkOption {
|
||||||
|
description = ''
|
||||||
|
Extra arguments to be passed to the webnsupdate server command.
|
||||||
|
'';
|
||||||
|
};
|
||||||
bindIp = mkOption {
|
bindIp = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
IP address to bind to.
|
IP address to bind to.
|
||||||
|
@ -130,6 +135,7 @@ in {
|
||||||
wantedBy = ["multi-user.target"];
|
wantedBy = ["multi-user.target"];
|
||||||
after = ["network.target" "bind.service"];
|
after = ["network.target" "bind.service"];
|
||||||
preStart = "${cmd} verify";
|
preStart = "${cmd} verify";
|
||||||
|
path = [pkgs.dig];
|
||||||
startLimitIntervalSec = 60;
|
startLimitIntervalSec = 60;
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = [cmd];
|
ExecStart = [cmd];
|
||||||
|
|
74
src/main.rs
74
src/main.rs
|
@ -8,19 +8,16 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use axum::{
|
use axum::{extract::State, routing::get, Json, Router};
|
||||||
extract::{ConnectInfo, State},
|
|
||||||
routing::get,
|
|
||||||
Json, Router,
|
|
||||||
};
|
|
||||||
use axum_auth::AuthBasic;
|
use axum_auth::AuthBasic;
|
||||||
|
use axum_client_ip::{SecureClientIp, SecureClientIpSource};
|
||||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use miette::{ensure, miette, Context, IntoDiagnostic, LabeledSpan, NamedSource, Result};
|
use miette::{ensure, miette, Context, IntoDiagnostic, LabeledSpan, NamedSource, Result};
|
||||||
use ring::digest::Digest;
|
use ring::digest::Digest;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tracing::{info, level_filters::LevelFilter, warn};
|
use tracing::{debug, error, info, level_filters::LevelFilter, trace, warn};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
const DEFAULT_TTL: Duration = Duration::from_secs(60);
|
const DEFAULT_TTL: Duration = Duration::from_secs(60);
|
||||||
|
@ -60,9 +57,14 @@ struct Opts {
|
||||||
/// If specified, then `webnsupdate` must have read access to the file
|
/// If specified, then `webnsupdate` must have read access to the file
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
key_file: Option<PathBuf>,
|
key_file: Option<PathBuf>,
|
||||||
/// Allow not setting a password when the server is exposed to the network
|
/// Allow not setting a password
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
insecure: bool,
|
insecure: bool,
|
||||||
|
/// Set client IP source
|
||||||
|
///
|
||||||
|
/// see: https://docs.rs/axum-client-ip/latest/axum_client_ip/enum.SecureClientIpSource.html
|
||||||
|
#[clap(long, default_value = "RightmostXForwardedFor")]
|
||||||
|
ip_source: SecureClientIpSource,
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
subcommand: Option<Cmd>,
|
subcommand: Option<Cmd>,
|
||||||
}
|
}
|
||||||
|
@ -112,6 +114,7 @@ async fn main() -> Result<()> {
|
||||||
records,
|
records,
|
||||||
salt,
|
salt,
|
||||||
ttl,
|
ttl,
|
||||||
|
ip_source,
|
||||||
} = Opts::parse();
|
} = Opts::parse();
|
||||||
let subscriber = tracing_subscriber::FmtSubscriber::builder()
|
let subscriber = tracing_subscriber::FmtSubscriber::builder()
|
||||||
.without_time()
|
.without_time()
|
||||||
|
@ -144,9 +147,14 @@ async fn main() -> Result<()> {
|
||||||
key_file: None,
|
key_file: None,
|
||||||
password_hash: None,
|
password_hash: None,
|
||||||
};
|
};
|
||||||
if let Some(password_file) = password_file {
|
if let Some(path) = password_file {
|
||||||
let pass = std::fs::read_to_string(password_file).into_diagnostic()?;
|
let pass = std::fs::read_to_string(&path).into_diagnostic()?;
|
||||||
let pass: Box<[u8]> = pass.trim().as_bytes().into();
|
|
||||||
|
let pass: Box<[u8]> = URL_SAFE_NO_PAD
|
||||||
|
.decode(pass.trim().as_bytes())
|
||||||
|
.into_diagnostic()
|
||||||
|
.wrap_err_with(|| format!("failed to decode password from {}", path.display()))?
|
||||||
|
.into();
|
||||||
state.password_hash = Some(Box::leak(pass));
|
state.password_hash = Some(Box::leak(pass));
|
||||||
} else {
|
} else {
|
||||||
ensure!(insecure, "a password must be used");
|
ensure!(insecure, "a password must be used");
|
||||||
|
@ -174,6 +182,7 @@ async fn main() -> Result<()> {
|
||||||
// Start services
|
// Start services
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/update", get(update_records))
|
.route("/update", get(update_records))
|
||||||
|
.layer(ip_source.into_extension())
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
info!("starting listener on {ip}:{port}");
|
info!("starting listener on {ip}:{port}");
|
||||||
let listener = tokio::net::TcpListener::bind(SocketAddr::new(ip, port))
|
let listener = tokio::net::TcpListener::bind(SocketAddr::new(ip, port))
|
||||||
|
@ -188,29 +197,35 @@ async fn main() -> Result<()> {
|
||||||
.into_diagnostic()
|
.into_diagnostic()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(state), level = "trace", ret(level = "warn"))]
|
#[tracing::instrument(skip(state, pass), level = "trace", ret(level = "info"))]
|
||||||
async fn update_records(
|
async fn update_records(
|
||||||
State(state): State<AppState<'static>>,
|
State(state): State<AppState<'static>>,
|
||||||
AuthBasic((username, pass)): AuthBasic,
|
AuthBasic((username, pass)): AuthBasic,
|
||||||
ConnectInfo(client): ConnectInfo<SocketAddr>,
|
SecureClientIp(ip): SecureClientIp,
|
||||||
) -> axum::response::Result<&'static str> {
|
) -> axum::response::Result<&'static str> {
|
||||||
let Some(pass) = pass else {
|
let Some(pass) = pass else {
|
||||||
return Err((StatusCode::UNAUTHORIZED, Json::from("no password provided")).into());
|
return Err((StatusCode::UNAUTHORIZED, Json::from("no password provided")).into());
|
||||||
};
|
};
|
||||||
if let Some(stored_pass) = state.password_hash {
|
if let Some(stored_pass) = state.password_hash {
|
||||||
let password = pass.trim().to_string();
|
let password = pass.trim().to_string();
|
||||||
|
let pass_hash = hash_identity(&username, &password, state.salt);
|
||||||
if hash_identity(&username, &password, state.salt).as_ref() != stored_pass {
|
if pass_hash.as_ref() != stored_pass {
|
||||||
warn!("rejected update from {username}@{client}");
|
warn!("rejected update");
|
||||||
|
trace!(
|
||||||
|
"mismatched hashes:\n{}\n{}",
|
||||||
|
URL_SAFE_NO_PAD.encode(pass_hash.as_ref()),
|
||||||
|
URL_SAFE_NO_PAD.encode(stored_pass.as_ref()),
|
||||||
|
);
|
||||||
return Err((StatusCode::UNAUTHORIZED, "invalid identity").into());
|
return Err((StatusCode::UNAUTHORIZED, "invalid identity").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let ip = client.ip();
|
info!("accepted update");
|
||||||
match nsupdate(ip, state.ttl, state.key_file, state.records).await {
|
match nsupdate(ip, state.ttl, state.key_file, state.records).await {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
if status.success() {
|
if status.success() {
|
||||||
Ok("successful update")
|
Ok("successful update")
|
||||||
} else {
|
} else {
|
||||||
|
error!("nsupdate failed");
|
||||||
Err((
|
Err((
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
"nsupdate failed, check server logs",
|
"nsupdate failed, check server logs",
|
||||||
|
@ -226,6 +241,7 @@ async fn update_records(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", ret(level = "warn"))]
|
||||||
async fn nsupdate(
|
async fn nsupdate(
|
||||||
ip: IpAddr,
|
ip: IpAddr,
|
||||||
ttl: Duration,
|
ttl: Duration,
|
||||||
|
@ -236,13 +252,27 @@ async fn nsupdate(
|
||||||
if let Some(key_file) = key_file {
|
if let Some(key_file) = key_file {
|
||||||
cmd.args([OsStr::new("-k"), key_file.as_os_str()]);
|
cmd.args([OsStr::new("-k"), key_file.as_os_str()]);
|
||||||
}
|
}
|
||||||
cmd.stdin(Stdio::piped());
|
debug!("spawning new process");
|
||||||
let mut child = cmd.spawn()?;
|
let mut child = cmd
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.inspect_err(|err| warn!("failed to spawn child: {err}"))?;
|
||||||
let mut stdin = child.stdin.take().expect("stdin not present");
|
let mut stdin = child.stdin.take().expect("stdin not present");
|
||||||
|
debug!("sending update request");
|
||||||
stdin
|
stdin
|
||||||
.write_all(update_ns_records(ip, ttl, records).as_bytes())
|
.write_all(update_ns_records(ip, ttl, records).as_bytes())
|
||||||
.await?;
|
.await
|
||||||
child.wait().await
|
.inspect_err(|err| warn!("failed to write to the stdin of nsupdate: {err}"))?;
|
||||||
|
debug!("closing stdin");
|
||||||
|
stdin
|
||||||
|
.shutdown()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| warn!("failed to close stdin to nsupdate: {err}"))?;
|
||||||
|
debug!("waiting for nsupdate to exit");
|
||||||
|
child
|
||||||
|
.wait()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| warn!("failed to wait for child: {err}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_ns_records(ip: IpAddr, ttl: Duration, records: &[&str]) -> String {
|
fn update_ns_records(ip: IpAddr, ttl: Duration, records: &[&str]) -> String {
|
||||||
|
@ -258,7 +288,7 @@ fn update_ns_records(ip: IpAddr, ttl: Duration, records: &[&str]) -> String {
|
||||||
writeln!(cmds, "update delete {record} {ttl_s} IN {rec_type}").unwrap();
|
writeln!(cmds, "update delete {record} {ttl_s} IN {rec_type}").unwrap();
|
||||||
writeln!(cmds, "update add {record} {ttl_s} IN {rec_type} {ip}").unwrap();
|
writeln!(cmds, "update add {record} {ttl_s} IN {rec_type} {ip}").unwrap();
|
||||||
}
|
}
|
||||||
writeln!(cmds, "send").unwrap();
|
writeln!(cmds, "send\nquit").unwrap();
|
||||||
cmds
|
cmds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,6 +453,7 @@ mod test {
|
||||||
update delete example.net. 60 IN A
|
update delete example.net. 60 IN A
|
||||||
update add example.net. 60 IN A 127.0.0.1
|
update add example.net. 60 IN A 127.0.0.1
|
||||||
send
|
send
|
||||||
|
quit
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,6 +473,7 @@ mod test {
|
||||||
update delete example.net. 60 IN AAAA
|
update delete example.net. 60 IN AAAA
|
||||||
update add example.net. 60 IN AAAA ::1
|
update add example.net. 60 IN AAAA ::1
|
||||||
send
|
send
|
||||||
|
quit
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue