mirror of
https://github.com/NLnetLabs/dnst.git
synced 2026-09-26 11:44:55 +02:00
Merge branch 'keyset' into keyset-signer
This commit is contained in:
+402
-59
@@ -1,83 +1,426 @@
|
||||
name: ci
|
||||
on: [push, pull_request]
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
# ======================================================
|
||||
# Continuous Integration: making sure the codebase works
|
||||
# ======================================================
|
||||
#
|
||||
# This workflow tests modifications to 'dnst', ensuring that 'dnst' can be
|
||||
# used by others successfully. It verifies certain aspects of the codebase,
|
||||
# such as the formatting and feature flag combinations, and runs the full test
|
||||
# suite. It runs on Ubuntu, Mac OS, and Windows.
|
||||
#
|
||||
# Based on https://github.com/NLnetLabs/domain/blob/main/.github/workflows/ci.yml
|
||||
|
||||
name: CI
|
||||
|
||||
# When the worflow runs
|
||||
# ---------------------
|
||||
on:
|
||||
# Execute when a pull request is (re-) opened or its head changes (e.g. new
|
||||
# commits are added or the commit history is rewritten) ... but only if
|
||||
# build-related files change.
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.rs'
|
||||
- 'Cargo.{toml,lock}'
|
||||
- '.github/workflows/ci.yml'
|
||||
|
||||
# If a pull request is merged, at least one commit is added to the target
|
||||
# branch. If the target is another pull request, it will be caught by the
|
||||
# above event. We miss PRs that merge to a non-PR branch, except for the
|
||||
# 'main' branch.
|
||||
|
||||
# Execute when a commit is pushed to 'main' (including merged PRs) or to a
|
||||
# release tag ... but only if build-related files change.
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'releases/**'
|
||||
paths:
|
||||
- '**.rs'
|
||||
- 'Cargo.{toml,lock}'
|
||||
- '.github/workflows/ci.yml'
|
||||
|
||||
# Rebuild 'main' every week. This will account for changes to dependencies
|
||||
# and to Rust, either of which can trigger new failures. Rust releases are
|
||||
# every 6 weeks, on a Thursday; this event runs every Friday.
|
||||
schedule:
|
||||
- cron: '0 10 * * FRI'
|
||||
|
||||
# Jobs
|
||||
# ----------------------------------------------------------------------------
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
rust: [1.82.0, stable, beta, nightly]
|
||||
|
||||
# Check Formatting
|
||||
# ----------------
|
||||
#
|
||||
# NOTE: This job is run even if no '.rs' files have changed. Inserting such
|
||||
# a check would require using a separate workflow file or using third-party
|
||||
# actions. Most commits do change '.rs' files, and 'cargo-fmt' is pretty
|
||||
# fast, so optimizing this is not necessary.
|
||||
check-fmt:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
# Load the repository.
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Set up the Rust toolchain.
|
||||
#
|
||||
# Disable the cache since it's not relevant for formatting.
|
||||
- name: Set up Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: rustfmt
|
||||
cache: false
|
||||
|
||||
# Do the actual formatting check.
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
# Determine MSRV
|
||||
# --------------
|
||||
#
|
||||
# The MSRV needs to be determined as we will test 'dnst' against the Rust
|
||||
# compiler at that version.
|
||||
determine-msrv:
|
||||
name: Determine MSRV
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
msrv: ${{ steps.determine-msrv.outputs.msrv }}
|
||||
steps:
|
||||
|
||||
# Load the repository.
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Determine the MSRV.
|
||||
- name: Determine MSRV
|
||||
id: determine-msrv
|
||||
run: |
|
||||
msrv=`cargo metadata --no-deps --format-version 1 | jq -r '.packages[]|select(.name=="dnst")|.rust_version'`
|
||||
echo "msrv=$msrv" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Check Feature Flags
|
||||
# -------------------
|
||||
#
|
||||
# Rust does not provide any way to check that all possible feature flag
|
||||
# combinations will succeed, so we need to try them manually here. We will
|
||||
# assume this choice is not influenced by the OS or Rust version.
|
||||
check-feature-flags:
|
||||
name: Check feature flags
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
# Load the repository.
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Set up the Rust toolchain.
|
||||
- name: Set up Rust
|
||||
id: setup-rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
cache: false
|
||||
|
||||
- name: Install OpenSSL
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
|
||||
# Restore a cache of dependencies and 'target'.
|
||||
- name: Restore a dependency cache
|
||||
id: cache-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
target/
|
||||
# Cache by OS and Rust version.
|
||||
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}
|
||||
|
||||
# Do the actual feature flag checks.
|
||||
# (--all-features is done in check-minimal-versions)
|
||||
#
|
||||
# NOTE: This does not benefit from the 'target' folder cached by a 'cargo
|
||||
# check --all-features --all-targets' execution. Due to the minimal
|
||||
# dependency set, it still runs fairly quickly.
|
||||
# The empty feature set (as it is done in domain) is not allowed in dnst.
|
||||
- name: Check default feature set
|
||||
run: cargo check --all-targets
|
||||
|
||||
- name: Check openssl feature
|
||||
run: cargo check --all-targets --no-default-features -F openssl
|
||||
|
||||
- name: Check ring feature
|
||||
run: cargo check --all-targets --no-default-features -F ring
|
||||
|
||||
# Check Minimal Versions
|
||||
# ----------------------
|
||||
#
|
||||
# Ensure that 'dnst' compiles with the oldest compatible versions of all
|
||||
# packages, even those 'dnst' depends upon indirectly.
|
||||
check-minimal-versions:
|
||||
name: Check minimal versions
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine-msrv
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
steps:
|
||||
|
||||
# Load the repository.
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Set up the Rust toolchain.
|
||||
- name: Set up Rust nightly
|
||||
id: setup-rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ needs.determine-msrv.outputs.msrv }},nightly
|
||||
cache: false
|
||||
|
||||
- name: Install OpenSSL
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
|
||||
# TODO: Cache minimal-version dependencies?
|
||||
|
||||
# Lock all dependencies to their minimal versions.
|
||||
- name: Lock dependencies to minimal versions
|
||||
run: cargo +nightly update -Z minimal-versions
|
||||
|
||||
# Check that 'dnst' compiles.
|
||||
#
|
||||
# NOTE: This does not benefit from the 'target' folder cached by a 'cargo
|
||||
# check --all-features --all-targets' execution. It may be worthwhile to
|
||||
# cache this 'target' folder separately (TODO).
|
||||
- name: Check
|
||||
run: cargo check --all-targets --all-features --locked
|
||||
|
||||
# Clippy
|
||||
# ------
|
||||
#
|
||||
# We run Clippy separately, and only on nightly Rust because it offers a
|
||||
# superset of the lints.
|
||||
#
|
||||
# 'cargo clippy' and 'cargo build' can share some state for fast execution,
|
||||
# but it's faster to execute them in parallel than to establish an ordering
|
||||
# between them.
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
steps:
|
||||
|
||||
# Load the repository.
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Set up the Rust toolchain.
|
||||
- name: Set up Rust nightly
|
||||
id: setup-rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: clippy
|
||||
cache: false
|
||||
|
||||
# Restore a cache of dependencies and 'target'.
|
||||
- name: Restore from cache
|
||||
id: cache-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
target/
|
||||
# Cache by OS and Rust version.
|
||||
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}
|
||||
|
||||
# Do the actually Clippy run.
|
||||
- name: Check Clippy
|
||||
run: cargo +nightly clippy --all-targets --all-features
|
||||
|
||||
# Test
|
||||
# ----
|
||||
#
|
||||
# Ensure that 'dnst' compiles and its test suite passes, on a large number
|
||||
# of operating systems and Rust versions.
|
||||
test:
|
||||
name: Test
|
||||
needs: determine-msrv
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
rust: ["${{ needs.determine-msrv.outputs.msrv }}", stable, beta, nightly]
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
DNST_FEATURES: "--all-features"
|
||||
# We use 'vcpkg' to install OpenSSL on Windows.
|
||||
VCPKG_ROOT: "${{ github.workspace }}\\vcpkg"
|
||||
VCPKGRS_TRIPLET: x64-windows-release
|
||||
# Ensure that OpenSSL is dynamically linked.
|
||||
VCPKGRS_DYNAMIC: 1
|
||||
steps:
|
||||
|
||||
# Load the repository.
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v1
|
||||
- name: Install Rust
|
||||
uses: hecrj/setup-rust-action@v2
|
||||
with:
|
||||
rust-version: ${{ matrix.rust }}
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
- if: matrix.os == 'windows-latest'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Prepare the environment on Windows
|
||||
- name: Prepare Windows environment
|
||||
if: matrix.os == 'windows-latest'
|
||||
id: vcpkg
|
||||
uses: johnwason/vcpkg-action@v6
|
||||
uses: johnwason/vcpkg-action@v7
|
||||
with:
|
||||
pkgs: openssl
|
||||
triplet: ${{ env.VCPKGRS_TRIPLET }}
|
||||
token: ${{ github.token }}
|
||||
github-binarycache: true
|
||||
- if: matrix.rust == 'stable'
|
||||
run: rustup component add clippy
|
||||
- if: matrix.rust == 'stable'
|
||||
run: cargo clippy --all-features --all-targets -- -D warnings
|
||||
- if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest'
|
||||
run: cargo fmt --all -- --check
|
||||
- run: cargo check --no-default-features -F ring --all-targets
|
||||
- run: cargo check --no-default-features -F openssl --all-targets
|
||||
- run: cargo test --all-features
|
||||
|
||||
minimal-versions:
|
||||
name: Check minimal versions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v1
|
||||
- name: Install Rust
|
||||
uses: hecrj/setup-rust-action@v2
|
||||
with:
|
||||
rust-version: "1.82.0"
|
||||
- name: Install OpenSSL
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
- name: Install nightly Rust
|
||||
run: rustup install nightly
|
||||
- name: Check with minimal-versions
|
||||
run: |
|
||||
cargo +nightly update -Z minimal-versions
|
||||
cargo check --all-features --all-targets --locked
|
||||
|
||||
# Set up the Rust toolchain.
|
||||
- name: Set up Rust ${{ matrix.rust }}
|
||||
id: setup-rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
cache: false
|
||||
|
||||
# Restore a cache of dependencies and 'target'.
|
||||
- name: Restore from cache
|
||||
id: cache-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
target/
|
||||
# Cache by OS and Rust version.
|
||||
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}
|
||||
|
||||
# Build and run the test suite.
|
||||
- name: Test
|
||||
run: cargo test --all-targets $DNST_FEATURES
|
||||
|
||||
# Additional (ignored) Tests
|
||||
# --------------------------
|
||||
#
|
||||
# Ensure that the extended test suit passes.
|
||||
# (only on ubuntu-latest and rust stable)
|
||||
extra-tests:
|
||||
name: Extra tests
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
DNST_FEATURES: "--all-features"
|
||||
steps:
|
||||
|
||||
# Load the repository.
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v1
|
||||
- name: Install Rust
|
||||
uses: hecrj/setup-rust-action@v2
|
||||
- name: Install supporting tools/libraries
|
||||
run: |
|
||||
# The tests compare their output to that of LDNS tools, so we need to
|
||||
# install them. Some tests work with DNSSEC keys for which the OpenSSL
|
||||
# library must be compiled against which requires C build programs and
|
||||
# pkg-config. Install everything we need.
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential ldnsutils libssl-dev pkg-config
|
||||
- name: Run tests that are normally ignored
|
||||
run: cargo test --all-features --all-targets -- --ignored
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install supporting tools and libraries
|
||||
# The tests compare their output to that of LDNS tools, so we need to
|
||||
# install them. Some tests work with DNSSEC keys for which the OpenSSL
|
||||
# library must be compiled against which requires C build programs and
|
||||
# pkg-config. Install everything we need.
|
||||
run: sudo apt-get install -y build-essential ldnsutils libssl-dev pkg-config
|
||||
|
||||
# Set up the Rust toolchain.
|
||||
- name: Set up Rust stable
|
||||
id: setup-rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
cache: false
|
||||
|
||||
# Restore a cache of dependencies and 'target'.
|
||||
- name: Restore from cache
|
||||
id: cache-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
target/
|
||||
# Cache by OS and Rust version.
|
||||
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}
|
||||
|
||||
# Build and run the test suite.
|
||||
- name: Test ignored
|
||||
run: cargo test --all-targets $DNST_FEATURES -- --ignored
|
||||
|
||||
# Build Cache
|
||||
# -----------
|
||||
#
|
||||
# Prepare a cache for checking and building 'dnst', on 'main'.
|
||||
cache:
|
||||
name: Cache
|
||||
needs: determine-msrv
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
rust: ["${{ needs.determine-msrv.outputs.msrv }}", stable, beta, nightly]
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: github.ref == 'refs/heads/main'
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
DNST_FEATURES: "--all-features"
|
||||
# We use 'vcpkg' to install OpenSSL on Windows.
|
||||
VCPKG_ROOT: "${{ github.workspace }}\\vcpkg"
|
||||
VCPKGRS_TRIPLET: x64-windows-release
|
||||
# Ensure that OpenSSL is dynamically linked.
|
||||
VCPKGRS_DYNAMIC: 1
|
||||
steps:
|
||||
|
||||
# Load the repository.
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Prepare the environment on Windows
|
||||
- name: Prepare Windows environment
|
||||
if: matrix.os == 'windows-latest'
|
||||
id: vcpkg
|
||||
uses: johnwason/vcpkg-action@v7
|
||||
with:
|
||||
pkgs: openssl
|
||||
triplet: ${{ env.VCPKGRS_TRIPLET }}
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
|
||||
# Set up the Rust toolchain.
|
||||
- name: Set up Rust ${{ matrix.rust }}
|
||||
id: setup-rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
cache: false
|
||||
|
||||
# Restore a cache of dependencies and 'target'.
|
||||
- name: Restore from cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
target/
|
||||
# Cache by OS and Rust version.
|
||||
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}
|
||||
|
||||
# Build all of 'dnst'.
|
||||
- name: Build
|
||||
run: cargo build --all-targets $DNST_FEATURES
|
||||
|
||||
# Save to the cache.
|
||||
- name: Save to the cache
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
target/
|
||||
# Cache by OS and Rust version.
|
||||
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}
|
||||
|
||||
@@ -12,7 +12,7 @@ on:
|
||||
|
||||
jobs:
|
||||
package:
|
||||
uses: NLnetLabs/ploutos/.github/workflows/pkg-rust.yml@v7
|
||||
uses: NLnetLabs/ploutos/.github/workflows/pkg-rust.yml@v8
|
||||
# secrets:
|
||||
# DOCKER_HUB_ID: ${{ secrets.DOCKER_HUB_ID }}
|
||||
# DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
@@ -30,4 +30,4 @@ jobs:
|
||||
|
||||
deb_extra_build_packages: libssl-dev
|
||||
rpm_extra_build_packages: make openssl-devel
|
||||
rpm_scriptlets_path: pkg/rpm/scriptlets.toml
|
||||
rpm_scriptlets_path: pkg/rpm/scriptlets.toml
|
||||
|
||||
Generated
+488
-166
File diff suppressed because it is too large
Load Diff
+15
-6
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dnst"
|
||||
version = "0.1.1-dev"
|
||||
version = "0.1.0-rc2"
|
||||
edition = "2021"
|
||||
default-run = "dnst"
|
||||
readme = "README.md"
|
||||
@@ -17,17 +17,22 @@ name = "ldns"
|
||||
path = "src/bin/ldns.rs"
|
||||
|
||||
[features]
|
||||
default = ["openssl", "ring"]
|
||||
default = ["kmip", "openssl", "ring"]
|
||||
|
||||
# Cryptographic backends
|
||||
kmip = ["domain/kmip", "dep:indenter", "dep:rand"]
|
||||
openssl = ["domain/openssl"]
|
||||
ring = ["domain/ring"]
|
||||
|
||||
# For building in a cargo cross container that lacks openssl-dev so cannot
|
||||
# successfully compile the Rust OpenSSL crate.
|
||||
static-openssl = ["openssl/vendored"]
|
||||
|
||||
[dependencies]
|
||||
bytes = "1.8.0"
|
||||
chrono = "0.4.38"
|
||||
clap = { version = "4.3.4", features = ["cargo", "derive"] }
|
||||
domain = { git = "https://github.com/NLnetLabs/domain.git", branch = "crypto-and-keyset-fixes", features = [
|
||||
clap = { version = "4.3.4", features = ["cargo", "derive", "wrap_help"] }
|
||||
domain = { git = "https://github.com/NLnetLabs/domain.git", branch = "patches-for-nameshed-prototype", features = [
|
||||
"bytes",
|
||||
"net",
|
||||
"resolv",
|
||||
@@ -36,13 +41,16 @@ domain = { git = "https://github.com/NLnetLabs/domain.git", branch = "crypto-and
|
||||
"unstable-client-transport",
|
||||
"unstable-sign",
|
||||
"unstable-validator",
|
||||
"unstable-zonetree",
|
||||
"unstable-zonetree"
|
||||
] }
|
||||
indenter = { version = "0.3.4", optional = true }
|
||||
lexopt = "0.3.0"
|
||||
rayon = "1.10.0"
|
||||
octseq = "0.5.2"
|
||||
rand = { version = "0.9.2", optional = true }
|
||||
ring = "0.17.8"
|
||||
tokio = "1.40.0"
|
||||
openssl = { version = "*", features = ["vendored"], optional = true }
|
||||
|
||||
# LDNS-xxx mode specific dependencies.
|
||||
# TODO: put these behind a feature gate?
|
||||
@@ -58,13 +66,14 @@ smallvec = "1.13.2"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
url = "2.5.4"
|
||||
futures = "0.3.31"
|
||||
|
||||
[dev-dependencies]
|
||||
const_format = " 0.2.33"
|
||||
test_bin = "0.4.0"
|
||||
tempfile = "3.20.0"
|
||||
regex = "1.11.1"
|
||||
domain = { git = "https://github.com/NLnetLabs/domain.git", branch = "crypto-and-keyset-fixes", features = [
|
||||
domain = { git = "https://github.com/NLnetLabs/domain.git", branch = "patches-for-nameshed-prototype", features = [
|
||||
"unstable-stelline",
|
||||
] }
|
||||
pretty_assertions = "1.4.1"
|
||||
|
||||
@@ -46,7 +46,3 @@ Options
|
||||
|
||||
Print the help text (short summary with ``-h``, long help with
|
||||
``--help``).
|
||||
|
||||
.. option:: -V, --version
|
||||
|
||||
Print the version.
|
||||
|
||||
@@ -40,7 +40,3 @@ Options
|
||||
|
||||
Print the help text (short summary with ``-h``, long help with
|
||||
``--help``).
|
||||
|
||||
.. option:: -V, --version
|
||||
|
||||
Print the version.
|
||||
|
||||
@@ -140,10 +140,6 @@ NSEC3 options
|
||||
The following options can be used with ``-n`` to override the default NSEC3
|
||||
settings used.
|
||||
|
||||
.. option:: -a <ALGORITHM NUMBER OR MNEMONIC>
|
||||
|
||||
Specify the hashing algorithm. Defaults to SHA-1.
|
||||
|
||||
.. option:: -s <STRING>
|
||||
|
||||
Specify the salt as a hex string. Defaults to ``-``, meaning empty salt.
|
||||
|
||||
@@ -4,50 +4,152 @@ dnst update
|
||||
Synopsis
|
||||
--------
|
||||
|
||||
:program:`dnst update` ``<DOMAIN NAME>`` ``[ZONE]`` ``<IP>``
|
||||
``[<TSIG KEY NAME> <TSIG ALGORITHM> <TSIG KEY DATA>]``
|
||||
:program:`dnst update` ``[OPTIONS]`` ``<DOMAIN NAME>`` ``<COMMAND>``
|
||||
|
||||
:program:`dnst update` ``[OPTIONS]`` ``<DOMAIN NAME>`` :subcmd:`add` ``<RRTYPE>`` ``[RDATA]...``
|
||||
|
||||
:program:`dnst update` ``[OPTIONS]`` ``<DOMAIN NAME>`` :subcmd:`delete` ``<RRTYPE>`` ``[RDATA]...``
|
||||
|
||||
:program:`dnst update` ``[OPTIONS]`` ``<DOMAIN NAME>`` :subcmd:`clear` ``<RRTYPE>``
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
**dnst update** sends an RFC 2136 Dynamic Update message to the name servers
|
||||
for a zone to update an IP address (or delete all existing IP addresses) for a
|
||||
domain name.
|
||||
for a zone to add, update, or delete arbitrary Resource Records for a domain
|
||||
name.
|
||||
|
||||
The message to be sent can be optionally authenticated using a given TSIG key.
|
||||
|
||||
**dnst update [...] add** adds the given RRs to the domain.
|
||||
|
||||
**dnst update [...] delete** deletes the given RRs from the domain. It can be
|
||||
used to delete individual RRs or a whole RRset.
|
||||
|
||||
**dnst update [...] clear** clears (deletes) all RRs of any type from the
|
||||
domain name.
|
||||
|
||||
Arguments
|
||||
---------
|
||||
|
||||
.. option:: <DOMAIN NAME>
|
||||
|
||||
The domain name to update the IP address of.
|
||||
The domain name of the RR(s) to update.
|
||||
|
||||
.. option:: <ZONE>
|
||||
.. option:: <COMMAND>
|
||||
|
||||
The zone to send the update to (if omitted, derived from SOA record).
|
||||
|
||||
.. option:: <IP>
|
||||
|
||||
The IP address to update the domain with (``none`` to remove any
|
||||
existing IP addresses)
|
||||
|
||||
.. option:: <TSIG KEY NAME>
|
||||
|
||||
TSIG key name.
|
||||
|
||||
.. option:: <TSIG ALGORITHM>
|
||||
|
||||
TSIG algorithm (e.g. "hmac-sha256").
|
||||
|
||||
.. option:: <TSIG KEY DATA>
|
||||
|
||||
Base64 encoded TSIG key data.
|
||||
Which action to take: add, delete, or clear.
|
||||
|
||||
Options:
|
||||
--------
|
||||
|
||||
.. option:: -c, --class <CLASS>
|
||||
|
||||
Class
|
||||
|
||||
Defaults to IN.
|
||||
|
||||
.. option:: -t, --ttl <TTL>
|
||||
|
||||
TTL in seconds or with unit suffix (s, m, h, d).
|
||||
|
||||
Is only used by the :subcmd:`add` command and is otherwise ignored.
|
||||
|
||||
Defaults to 3600.
|
||||
|
||||
.. option:: -s, --server <IP>
|
||||
|
||||
The name server to send the update to.
|
||||
|
||||
By default, the update will be sent to the list of name servers fetched
|
||||
from the zone's NS RRset as per RFC 2136.
|
||||
|
||||
.. option:: -z, --zone <ZONE>
|
||||
|
||||
The zone the domain name belongs to (to skip a SOA query)
|
||||
|
||||
.. option:: -y, --tsig <NAME:KEY[:ALGO]>
|
||||
|
||||
TSIG credentials for the UPDATE packet
|
||||
|
||||
.. option:: --rrset-exists <DOMAIN_NAME_AND_TYPE>
|
||||
|
||||
Require that at least one RR with the given NAME and TYPE exists.
|
||||
This option can be provided multiple times, with format ``<DOMAIN_NAME>
|
||||
<TYPE>`` each, to build up a list of RRs.
|
||||
|
||||
If the domain name is relative, it will be relative to the zone's apex.
|
||||
|
||||
[aliases: --rrset]
|
||||
|
||||
.. option:: --rrset-exists-exact <RESOURCE_RECORD>
|
||||
|
||||
Require that an RRset exists and contains exactly the RRs with the given
|
||||
NAME, TYPE, and RDATA. This option can be provided multiple times, each
|
||||
with one RR in zonefile format, to build up one or more RRsets that is
|
||||
required to exist. CLASS and TTL can be omitted.
|
||||
|
||||
If the domain name is relative, it will be relative to the zone's apex.
|
||||
|
||||
[aliases: --rrset-exact]
|
||||
|
||||
.. option:: --rrset-non-existent <DOMAIN_NAME_AND_TYPE>
|
||||
|
||||
RRset does not exist. This option can be provided multiple times, with
|
||||
format ``<DOMAIN_NAME> <TYPE>`` each, to build up a list of RRs that
|
||||
specify that no RRs with a specified NAME and TYPE can exist.
|
||||
|
||||
If the domain name is relative, it will be relative to the zone's apex.
|
||||
|
||||
[aliases: --rrset-empty]
|
||||
|
||||
.. option:: --name-in-use <DOMAIN_NAME>
|
||||
|
||||
Name is in use. This option can be provided multiple times, with format
|
||||
``<DOMAIN_NAME>`` each, to collect a list of NAMEs that must own at least
|
||||
one RR.
|
||||
|
||||
Note that this prerequisite is NOT satisfied by empty nonterminals.
|
||||
|
||||
If the domain name is relative, it will be relative to the zone's apex.
|
||||
|
||||
[aliases: --name-used]
|
||||
|
||||
.. option:: --name-not-in-use <DOMAIN_NAME>
|
||||
|
||||
Name is not in use. This option can be provided multiple times, with
|
||||
format ``<DOMAIN_NAME>`` each, to collect a list of NAMEs that must NOT
|
||||
own any RRs.
|
||||
|
||||
Note that this prerequisite IS satisfied by empty nonterminals.
|
||||
|
||||
If the domain name is relative, it will be relative to the zone's apex.
|
||||
|
||||
[aliases: --name-unused]
|
||||
|
||||
.. option:: -h, --help
|
||||
|
||||
Print the help text (short summary with ``-h``, long help with
|
||||
``--help``).
|
||||
``--help``). Can also be used on the individual sub commands.
|
||||
|
||||
Arguments for :subcmd:`add` and :subcmd:`delete`
|
||||
------------------------------------------------------
|
||||
|
||||
.. option:: <RRTYPE>
|
||||
|
||||
The RR type to add or delete.
|
||||
|
||||
.. option:: [RDATA]...
|
||||
|
||||
One or more RDATA arguments for :subcmd:`add`, and zero or more for
|
||||
:subcmd:`delete`.
|
||||
|
||||
Each argument corresponds to a single RR's RDATA, so beware of (shell and
|
||||
DNS) quoting rules.
|
||||
|
||||
Each RDATA argument will be parsed as if it was read from a zone file.
|
||||
|
||||
| Examples:
|
||||
| :code:`dnst update some.example.com add AAAA ::1 2001:db8::`
|
||||
| :code:`dnst update some.example.com add TXT '"Spacious String" "Another
|
||||
string for the same TXT record"' '"This is another TXT RR"'`
|
||||
|
||||
@@ -17,8 +17,25 @@ managing DNS servers and DNS zones.
|
||||
Please consult the manual pages for these individual commands for more
|
||||
information.
|
||||
|
||||
dnst Commands
|
||||
-------------
|
||||
Options
|
||||
-------
|
||||
|
||||
.. option:: -v, --verbosity <LEVEL>
|
||||
|
||||
Set the verbosity to 0-5 or a level name (``off``, ``error``, ``warn``,
|
||||
``info``, ``debug`` or ``trace``). Defaults to ``warn``.
|
||||
|
||||
.. option:: -h, --help
|
||||
|
||||
Print the help text (short summary with ``-h``, long help with
|
||||
``--help``).
|
||||
|
||||
.. option:: -V, --version
|
||||
|
||||
Print the version.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
.. glossary::
|
||||
|
||||
|
||||
@@ -10,8 +10,10 @@ image:
|
||||
- "debian:buster" # debian/10
|
||||
- "debian:bullseye" # debian/11
|
||||
- "debian:bookworm" # debian/12
|
||||
- 'rockylinux:8' # compatible with EOL centos:8
|
||||
- 'rockylinux:9'
|
||||
- "debian:trixie" # debian/13
|
||||
- 'almalinux:8' # compatible with EOL centos:8
|
||||
- 'almalinux:9'
|
||||
- 'almalinux:10'
|
||||
target:
|
||||
- 'x86_64'
|
||||
include:
|
||||
|
||||
@@ -6,9 +6,11 @@ pkg:
|
||||
image:
|
||||
- "ubuntu:focal" # ubuntu/20.04
|
||||
- "ubuntu:jammy" # ubuntu/22.04
|
||||
- "ubuntu:noble" # ubuntu/24.04
|
||||
- "debian:buster" # debian/10
|
||||
- "debian:bullseye" # debian/11
|
||||
- "debian:bookworm" # debian/12
|
||||
- "debian:trixie" # debian/13
|
||||
published_pkg:
|
||||
- 'ldnsutils' # correct for Ubuntu/Debian
|
||||
target:
|
||||
@@ -26,24 +28,36 @@ test-mode:
|
||||
- 'upgrade-from-published'
|
||||
include:
|
||||
- pkg: 'dnst'
|
||||
image: 'rockylinux:8'
|
||||
image: 'almalinux:8'
|
||||
target: 'x86_64'
|
||||
test-mode: 'fresh-install'
|
||||
|
||||
- pkg: 'dnst'
|
||||
image: 'rockylinux:8'
|
||||
image: 'almalinux:8'
|
||||
target: 'x86_64'
|
||||
test-mode: 'upgrade-from-published'
|
||||
published_pkg: 'ldns-utils'
|
||||
rpm_yum_extra_args: --enablerepo powertools
|
||||
|
||||
- pkg: 'dnst'
|
||||
image: 'rockylinux:9'
|
||||
image: 'almalinux:9'
|
||||
target: 'x86_64'
|
||||
test-mode: 'fresh-install'
|
||||
|
||||
- pkg: 'dnst'
|
||||
image: 'rockylinux:9'
|
||||
image: 'almalinux:9'
|
||||
target: 'x86_64'
|
||||
test-mode: 'upgrade-from-published'
|
||||
published_pkg: 'ldns-utils'
|
||||
rpm_yum_extra_args: --enablerepo crb
|
||||
|
||||
- pkg: 'dnst'
|
||||
image: 'almalinux:10'
|
||||
target: 'x86_64'
|
||||
test-mode: 'fresh-install'
|
||||
|
||||
- pkg: 'dnst'
|
||||
image: 'almalinux:10'
|
||||
target: 'x86_64'
|
||||
test-mode: 'upgrade-from-published'
|
||||
published_pkg: 'ldns-utils'
|
||||
@@ -57,4 +71,4 @@ include:
|
||||
# mode: 'upgrade-from-published'
|
||||
# - pkg: 'routinator'
|
||||
# image: 'debian:bookworm'
|
||||
# mode: 'upgrade-from-published'
|
||||
# mode: 'upgrade-from-published'
|
||||
|
||||
+14
-1
@@ -4,12 +4,22 @@ use super::commands::Command;
|
||||
use super::error::Error;
|
||||
|
||||
use clap::Parser;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[command(version, disable_help_subcommand = true)]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
|
||||
/// Verbosity: 0-5 or a level name ("off", "error", "warn", "info", "debug" or "trace")
|
||||
#[arg(
|
||||
short = 'v',
|
||||
long = "verbosity",
|
||||
value_name = "level",
|
||||
default_value_t = LevelFilter::from_level(tracing::Level::WARN),
|
||||
)]
|
||||
pub verbosity: LevelFilter,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
@@ -20,6 +30,9 @@ impl Args {
|
||||
|
||||
impl From<Command> for Args {
|
||||
fn from(value: Command) -> Self {
|
||||
Args { command: value }
|
||||
Args {
|
||||
command: value,
|
||||
verbosity: LevelFilter::from_level(tracing::Level::WARN),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-9
@@ -6,21 +6,37 @@
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
||||
use dnst::env::Env;
|
||||
use dnst::log::LogFormatter;
|
||||
use dnst::try_ldns_compatibility;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let env = dnst::env::RealEnv;
|
||||
run(env)
|
||||
}
|
||||
|
||||
fn run(env: impl Env) -> ExitCode {
|
||||
let mut args = env.args_os();
|
||||
let argv0 = args.next().unwrap();
|
||||
let stderr = env.stderr();
|
||||
let subscriber = tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_ansi(stderr.is_terminal())
|
||||
.with_writer(stderr)
|
||||
.with_max_level(LevelFilter::WARN)
|
||||
.event_format(LogFormatter {
|
||||
program: argv0.to_string_lossy().to_string(),
|
||||
});
|
||||
|
||||
let mut args = std::env::args_os();
|
||||
args.next().unwrap();
|
||||
let args =
|
||||
try_ldns_compatibility(args).map(|args| args.expect("ldns commmand lacks ldns- prefix"));
|
||||
|
||||
match args.and_then(|args| args.execute(&env)) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(err) => {
|
||||
err.pretty_print(env);
|
||||
ExitCode::FAILURE
|
||||
tracing::subscriber::with_default(subscriber.finish(), || {
|
||||
match args.and_then(|args| args.execute(&env)) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(err) => {
|
||||
err.pretty_print(env);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+122
-20
@@ -1,4 +1,5 @@
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write as _};
|
||||
use std::path::PathBuf;
|
||||
@@ -6,8 +7,8 @@ use std::path::PathBuf;
|
||||
use clap::builder::ValueParser;
|
||||
use clap::Parser;
|
||||
use domain::base::iana::{DigestAlgorithm, SecurityAlgorithm};
|
||||
use domain::base::zonefile_fmt::{DisplayKind, ZonefileFmt};
|
||||
use domain::base::Record;
|
||||
use domain::base::zonefile_fmt::ZonefileFmt;
|
||||
use domain::base::{Record, RecordData, ToName};
|
||||
use domain::dnssec::validator::base::DnskeyExt;
|
||||
use domain::rdata::Ds;
|
||||
use domain::zonefile::inplace::{Entry, ScannedRecordData};
|
||||
@@ -15,12 +16,11 @@ use lexopt::Arg;
|
||||
|
||||
use crate::env::Env;
|
||||
use crate::error::Error;
|
||||
use crate::Args;
|
||||
use crate::{Args, DISPLAY_KIND};
|
||||
|
||||
use super::{Command, LdnsCommand};
|
||||
|
||||
#[derive(Clone, Debug, Parser, PartialEq, Eq)]
|
||||
#[command(version)]
|
||||
pub struct Key2ds {
|
||||
/// ignore SEP flag (i.e. make DS records for any key)
|
||||
#[arg(long = "ignore-sep")]
|
||||
@@ -45,6 +45,13 @@ pub struct Key2ds {
|
||||
/// Keyfile to read
|
||||
#[arg()]
|
||||
keyfile: PathBuf,
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Non-command line argument fields:
|
||||
// -----------------------------------------------------------------------
|
||||
/// Whether or not we were invoked as `ldns-key2ds`.
|
||||
#[arg(skip = false)]
|
||||
invoked_as_ldns: bool,
|
||||
}
|
||||
|
||||
pub fn parse_digest_alg(arg: &str) -> Result<DigestAlgorithm, Error> {
|
||||
@@ -103,7 +110,6 @@ impl LdnsCommand for Key2ds {
|
||||
}
|
||||
keyfile = Some(val);
|
||||
}
|
||||
Arg::Short('v') => return Ok(Self::report_version()),
|
||||
Arg::Short(x) => return Err(format!("Invalid short option: -{x}").into()),
|
||||
Arg::Long(x) => {
|
||||
return Err(format!("Long options are not supported, but `--{x}` given").into())
|
||||
@@ -123,6 +129,7 @@ impl LdnsCommand for Key2ds {
|
||||
// present in the ldns version of this command.
|
||||
force_overwrite: true,
|
||||
keyfile: keyfile.into(),
|
||||
invoked_as_ldns: true,
|
||||
})))
|
||||
}
|
||||
}
|
||||
@@ -166,9 +173,13 @@ impl Key2ds {
|
||||
|
||||
let key_tag = dnskey.key_tag();
|
||||
let sec_alg = dnskey.algorithm();
|
||||
let digest_alg = self
|
||||
.algorithm
|
||||
.unwrap_or_else(|| determine_hash_from_sec_alg(sec_alg));
|
||||
let digest_alg = if let Some(alg) = self.algorithm {
|
||||
alg
|
||||
} else if self.invoked_as_ldns {
|
||||
determine_hash_from_sec_alg(sec_alg).unwrap_or(DigestAlgorithm::SHA1)
|
||||
} else {
|
||||
determine_hash_from_sec_alg(sec_alg)?
|
||||
};
|
||||
|
||||
if digest_alg == DigestAlgorithm::GOST {
|
||||
return Err("Error: the GOST algorithm is deprecated and must not be used. Try a different algorithm.".into());
|
||||
@@ -185,7 +196,11 @@ impl Key2ds {
|
||||
let rr = Record::new(owner, class, ttl, ds);
|
||||
|
||||
if self.write_to_stdout {
|
||||
writeln!(env.stdout(), "{}", rr.display_zonefile(DisplayKind::Simple));
|
||||
if self.invoked_as_ldns {
|
||||
let _ = write_rr_as_ldns(&env.stdout(), &rr);
|
||||
} else {
|
||||
writeln!(env.stdout(), "{}", rr.display_zonefile(DISPLAY_KIND));
|
||||
}
|
||||
} else {
|
||||
let owner = owner.fmt_with_dot();
|
||||
let sec_alg = sec_alg.to_int();
|
||||
@@ -215,8 +230,13 @@ impl Key2ds {
|
||||
let mut out_file =
|
||||
res.map_err(|e| format!("Could not create file \"{filename}\": {e}"))?;
|
||||
|
||||
writeln!(out_file, "{}", rr.display_zonefile(DisplayKind::Simple))
|
||||
.map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?;
|
||||
if self.invoked_as_ldns {
|
||||
write_rr_as_ldns(out_file, &rr)
|
||||
.map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?;
|
||||
} else {
|
||||
writeln!(out_file, "{}", rr.display_zonefile(DISPLAY_KIND))
|
||||
.map_err(|e| format!("Could not write to file \"{filename}\": {e}"))?;
|
||||
}
|
||||
|
||||
writeln!(env.stdout(), "{keyname}");
|
||||
}
|
||||
@@ -226,19 +246,38 @@ impl Key2ds {
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_hash_from_sec_alg(sec_alg: SecurityAlgorithm) -> DigestAlgorithm {
|
||||
fn determine_hash_from_sec_alg(sec_alg: SecurityAlgorithm) -> Result<DigestAlgorithm, Error> {
|
||||
match sec_alg {
|
||||
SecurityAlgorithm::RSASHA256
|
||||
| SecurityAlgorithm::RSASHA512
|
||||
| SecurityAlgorithm::ED25519
|
||||
| SecurityAlgorithm::ED448
|
||||
| SecurityAlgorithm::ECDSAP256SHA256 => DigestAlgorithm::SHA256,
|
||||
SecurityAlgorithm::ECDSAP384SHA384 => DigestAlgorithm::SHA384,
|
||||
SecurityAlgorithm::ECC_GOST => DigestAlgorithm::GOST,
|
||||
_ => DigestAlgorithm::SHA1,
|
||||
| SecurityAlgorithm::ECDSAP256SHA256 => Ok(DigestAlgorithm::SHA256),
|
||||
SecurityAlgorithm::ECDSAP384SHA384 => Ok(DigestAlgorithm::SHA384),
|
||||
SecurityAlgorithm::ECC_GOST => Ok(DigestAlgorithm::GOST),
|
||||
_ => Err(concat!(
|
||||
"Unable to derive digest algorithm from used key algorithm. ",
|
||||
"Please select a digest algorithm using --algorithm."
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_rr_as_ldns<W: io::Write, N: ToName, D: RecordData + Display>(
|
||||
mut w: W,
|
||||
rr: &Record<N, D>,
|
||||
) -> Result<(), io::Error> {
|
||||
writeln!(
|
||||
w,
|
||||
"{}\t{}\t{}\t{}\t{}",
|
||||
rr.owner().fmt_with_dot(),
|
||||
rr.ttl().as_secs(),
|
||||
rr.class(),
|
||||
rr.rtype(),
|
||||
rr.data()
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use domain::base::iana::DigestAlgorithm;
|
||||
@@ -276,6 +315,7 @@ mod test {
|
||||
force_overwrite: false,
|
||||
algorithm: None,
|
||||
keyfile: PathBuf::from("keyfile1.key"),
|
||||
invoked_as_ldns: false,
|
||||
};
|
||||
|
||||
// Check the defaults
|
||||
@@ -365,6 +405,7 @@ mod test {
|
||||
force_overwrite: true, // note that this is true
|
||||
algorithm: None,
|
||||
keyfile: PathBuf::from("keyfile1.key"),
|
||||
invoked_as_ldns: true,
|
||||
};
|
||||
|
||||
// Check the defaults
|
||||
@@ -426,6 +467,11 @@ mod test {
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
let mut file = File::create(dir.path().join("key3.key")).unwrap();
|
||||
// DSA Key for fallback testing
|
||||
file
|
||||
.write_all(b". IN DNSKEY 256 3 3 CJ7RxFssAV8F41ftNWyGNW6eo49FhiDQpuR1Nop1dIzcFVD15Do76z1mZaLoheVttGMtE3jah0gFThjyUeBI65pmofV1pALXzrfbyDsoKJM2cjdaqICr9Zg/OcQ4yqkkJpTt1+e+w8xv6YrBOrvSwYwOIUGkj2ci/84HXC90W8I8ph6MHk1vaYmGoOS9wM+S9x2RO6sxD/UtB4QUGFp1qDlFc+C4dc1kB+rs868C004mRGoHe+PHHeDXdfKwr5zVpdgPc85TkBJAY82XYfzIE7rUpXFI4JzRE1Lz1Mu7rXZnwNve6bVykghJ8uOAaDO1KFLdIgThqJh1N8XMSinNKhPwU2qPMX7dsC5hh82BHg04fc3O2hzUG3A1z49i6Hbqa+zgvJe70AQZdqiFSaOpfgC6a8wRfdyGSTYBxsi2YhG2G1/x6qr8sToEeZjq9awPRY+bXT8kJijg9AH9/adrjvF77m+NJASepFTre40I79+Ymo3fnjFU1oSnsGTB91tQbbSNV55fd5Jsqndj9AJkhYJUpbVN ;{id = 15147 (zsk), size = 1024b}\n")
|
||||
.unwrap();
|
||||
|
||||
dir
|
||||
}
|
||||
@@ -441,7 +487,7 @@ mod test {
|
||||
assert_eq!(res.stderr, "");
|
||||
|
||||
let out = std::fs::read_to_string(dir.path().join("Kexample.test.+015+60136.ds")).unwrap();
|
||||
assert_eq!(out, "example.test. 3600 IN DS 60136 15 2 52BD3BF40C8220BF1A3E2A3751C423BC4B69BCD7F328D38C4CD021A85DE65AD4\n");
|
||||
assert_eq!(out, "example.test.\t3600\tIN\tDS\t60136 15 2 52BD3BF40C8220BF1A3E2A3751C423BC4B69BCD7F328D38C4CD021A85DE65AD4\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -455,10 +501,10 @@ mod test {
|
||||
assert_eq!(res.stderr, "");
|
||||
|
||||
let out = std::fs::read_to_string(dir.path().join("Kone.test.+015+38429.ds")).unwrap();
|
||||
assert_eq!(out, "one.test. 3600 IN DS 38429 15 2 B85F7D27C48A7B84D633C7A41C3022EA0F7FC80896227B61AE7BFC59BF5F0256\n");
|
||||
assert_eq!(out, "one.test.\t3600\tIN\tDS\t38429 15 2 B85F7D27C48A7B84D633C7A41C3022EA0F7FC80896227B61AE7BFC59BF5F0256\n");
|
||||
|
||||
let out = std::fs::read_to_string(dir.path().join("Ktwo.test.+015+00425.ds")).unwrap();
|
||||
assert_eq!(out, "two.test. 3600 IN DS 425 15 2 AA2030287A7C5C56CB3C0E9C64BE55616729C0C78DE2B83613D03B10C0F1EA93\n");
|
||||
assert_eq!(out, "two.test.\t3600\tIN\tDS\t425 15 2 AA2030287A7C5C56CB3C0E9C64BE55616729C0C78DE2B83613D03B10C0F1EA93\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -472,7 +518,7 @@ mod test {
|
||||
assert_eq!(res.exit_code, 0);
|
||||
assert_eq!(
|
||||
res.stdout,
|
||||
"example.test. 3600 IN DS 60136 15 2 52BD3BF40C8220BF1A3E2A3751C423BC4B69BCD7F328D38C4CD021A85DE65AD4\n"
|
||||
"example.test.\t3600\tIN\tDS\t60136 15 2 52BD3BF40C8220BF1A3E2A3751C423BC4B69BCD7F328D38C4CD021A85DE65AD4\n"
|
||||
);
|
||||
assert_eq!(res.stderr, "");
|
||||
}
|
||||
@@ -500,4 +546,60 @@ mod test {
|
||||
assert_eq!(res.stdout, "Kexample.test.+015+60136\n");
|
||||
assert_eq!(res.stderr, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ldns_lowercase_digest() {
|
||||
let dir = run_setup();
|
||||
|
||||
let res = FakeCmd::new(["ldns-key2ds", "-n", "key1.key"])
|
||||
.cwd(&dir)
|
||||
.run();
|
||||
|
||||
assert_eq!(res.exit_code, 0);
|
||||
assert_eq!(
|
||||
res.stdout,
|
||||
"example.test.\t3600\tIN\tDS\t60136 15 2 52bd3bf40c8220bf1a3e2a3751c423bc4b69bcd7f328d38c4cd021a85de65ad4\n"
|
||||
);
|
||||
assert_eq!(res.stderr, "");
|
||||
|
||||
let res = FakeCmd::new(["ldns-key2ds", "key1.key"]).cwd(&dir).run();
|
||||
|
||||
assert_eq!(res.exit_code, 0, "{res:?}");
|
||||
assert_eq!(res.stdout, "Kexample.test.+015+60136\n");
|
||||
assert_eq!(res.stderr, "");
|
||||
|
||||
let out = std::fs::read_to_string(dir.path().join("Kexample.test.+015+60136.ds")).unwrap();
|
||||
assert_eq!(out, "example.test.\t3600\tIN\tDS\t60136 15 2 52bd3bf40c8220bf1a3e2a3751c423bc4b69bcd7f328d38c4cd021a85de65ad4\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ldns_algorithm_fallback() {
|
||||
let dir = run_setup();
|
||||
|
||||
let res = FakeCmd::new(["ldns-key2ds", "-nf", "key3.key"])
|
||||
.cwd(&dir)
|
||||
.run();
|
||||
|
||||
assert_eq!(res.exit_code, 0);
|
||||
assert_eq!(
|
||||
res.stdout,
|
||||
".\t3600\tIN\tDS\t15147 3 1 dbccc2cc359d4661ab39c72898ef58e9cdcd27ab\n"
|
||||
);
|
||||
assert_eq!(res.stderr, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dnst_algorithm_fallback_err() {
|
||||
let dir = run_setup();
|
||||
|
||||
let res = FakeCmd::new(["dnst", "key2ds", "-n", "--ignore-sep", "key3.key"])
|
||||
.cwd(&dir)
|
||||
.run();
|
||||
|
||||
assert_eq!(res.exit_code, 1);
|
||||
assert_eq!(res.stdout, "");
|
||||
assert!(res.stderr.contains(
|
||||
"Unable to derive digest algorithm from used key algorithm. Please select a digest algorithm using --algorithm."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
+29
-11
@@ -16,7 +16,7 @@ use lexopt::Arg;
|
||||
use crate::env::Env;
|
||||
use crate::error::{Context, Error};
|
||||
use crate::parse::parse_name;
|
||||
use crate::{util, Args};
|
||||
use crate::{util, Args, DISPLAY_KIND};
|
||||
|
||||
use super::{parse_os, parse_os_with, Command, LdnsCommand};
|
||||
|
||||
@@ -36,6 +36,8 @@ pub struct Keygen {
|
||||
feature = "openssl",
|
||||
doc = " - ED448: An Ed448 key (algorithm 16)"
|
||||
)]
|
||||
///
|
||||
/// Tip: Using the algorithm number instead of the name is also supported.
|
||||
#[allow(rustdoc::invalid_html_tags)]
|
||||
#[arg(
|
||||
short = 'a',
|
||||
@@ -268,11 +270,11 @@ impl From<Keygen> for Command {
|
||||
impl Keygen {
|
||||
fn parse_algorithm(value: &str) -> Result<GenerateParams, clap::Error> {
|
||||
match value {
|
||||
"RSASHA256" => return Ok(GenerateParams::RsaSha256 { bits: 2048 }),
|
||||
"ECDSAP256SHA256" => return Ok(GenerateParams::EcdsaP256Sha256),
|
||||
"ECDSAP384SHA384" => return Ok(GenerateParams::EcdsaP384Sha384),
|
||||
"ED25519" => return Ok(GenerateParams::Ed25519),
|
||||
"ED448" => return Ok(GenerateParams::Ed448),
|
||||
"RSASHA256" | "8" => return Ok(GenerateParams::RsaSha256 { bits: 2048 }),
|
||||
"ECDSAP256SHA256" | "13" => return Ok(GenerateParams::EcdsaP256Sha256),
|
||||
"ECDSAP384SHA384" | "14" => return Ok(GenerateParams::EcdsaP384Sha384),
|
||||
"ED25519" | "15" => return Ok(GenerateParams::Ed25519),
|
||||
"ED448" | "16" => return Ok(GenerateParams::Ed448),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -281,7 +283,7 @@ impl Keygen {
|
||||
if let Some((name, params)) = value.split_once(':') {
|
||||
#[allow(clippy::single_match)]
|
||||
match name {
|
||||
"RSASHA256" => {
|
||||
"RSASHA256" | "8" => {
|
||||
let bits: u32 = params.parse().map_err(|err| {
|
||||
clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
@@ -357,7 +359,7 @@ impl Keygen {
|
||||
let algorithm = public_key.algorithm();
|
||||
let secret_key = secret_key.display_as_bind().to_string();
|
||||
let public_key = format!(
|
||||
"{} IN DNSKEY {}",
|
||||
"{} IN DNSKEY {}\n",
|
||||
self.name.fmt_with_dot(),
|
||||
public_key.display_zonefile(DisplayKind::Simple)
|
||||
);
|
||||
@@ -367,7 +369,7 @@ impl Keygen {
|
||||
self.name.fmt_with_dot(),
|
||||
Ds::new(key_tag, algorithm, digest_alg, digest)
|
||||
.expect("we generated the digest, so don't expect it to be too long")
|
||||
.display_zonefile(DisplayKind::Simple)
|
||||
.display_zonefile(DISPLAY_KIND)
|
||||
)
|
||||
});
|
||||
|
||||
@@ -475,6 +477,22 @@ mod test {
|
||||
..base.clone()
|
||||
}
|
||||
);
|
||||
// - Specifying the algorithm by number
|
||||
assert_eq!(
|
||||
parse(cmd.args(["-a", "8", "example.org"])),
|
||||
Keygen {
|
||||
algorithm: GenerateParams::RsaSha256 { bits: 2048 },
|
||||
..base.clone()
|
||||
}
|
||||
);
|
||||
// - Specifying the algorithm by number incl. keysize
|
||||
assert_eq!(
|
||||
parse(cmd.args(["-a", "8:1024", "example.org"])),
|
||||
Keygen {
|
||||
algorithm: GenerateParams::RsaSha256 { bits: 1024 },
|
||||
..base.clone()
|
||||
}
|
||||
);
|
||||
|
||||
// Test 'make_ksk':
|
||||
assert_eq!(
|
||||
@@ -612,7 +630,7 @@ mod test {
|
||||
|
||||
let name_regex = Regex::new(r"^Kexample\.org\.\+015\+[0-9]{5}$").unwrap();
|
||||
let public_key_regex =
|
||||
Regex::new(r"^example.org. IN DNSKEY 256 3 15 [A-Za-z0-9/+=]+").unwrap();
|
||||
Regex::new(r"^example.org. IN DNSKEY 256 3 15 [A-Za-z0-9/+=]+\n$").unwrap();
|
||||
let secret_key_regex = Regex::new(
|
||||
r"^Private-key-format: v1\.2\nAlgorithm: 15 \(ED25519\)\nPrivateKey: [A-Za-z0-9/+=]+\n$",
|
||||
)
|
||||
@@ -644,7 +662,7 @@ mod test {
|
||||
|
||||
let name_regex = Regex::new(r"^Kexample\.org\.\+015\+[0-9]{5}$").unwrap();
|
||||
let public_key_regex =
|
||||
Regex::new(r"^example.org. IN DNSKEY 257 3 15 [A-Za-z0-9/+=]+").unwrap();
|
||||
Regex::new(r"^example.org. IN DNSKEY 257 3 15 [A-Za-z0-9/+=]+\n$").unwrap();
|
||||
let digest_key_regex =
|
||||
Regex::new(r"^example.org. IN DS [0-9]+ 15 2 [0-9a-fA-F]+\n$").unwrap();
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
pub mod cmd;
|
||||
|
||||
#[cfg(feature = "kmip")]
|
||||
pub mod kmip;
|
||||
|
||||
pub use cmd::*;
|
||||
+13
-8
@@ -48,6 +48,10 @@ pub enum Command {
|
||||
#[command(name = "keygen", verbatim_doc_comment)]
|
||||
Keygen(self::keygen::Keygen),
|
||||
|
||||
/// Maintain a set of DNSSEC keys. EXPERIMENTAL.
|
||||
#[command(name = "keyset")]
|
||||
Keyset(self::keyset::Keyset),
|
||||
|
||||
/// Generate a DS RR from the DNSKEYS in keyfile
|
||||
///
|
||||
/// The following file will be created for each key:
|
||||
@@ -80,12 +84,12 @@ pub enum Command {
|
||||
#[command(name = "update")]
|
||||
Update(self::update::Update),
|
||||
|
||||
/// Maintain a set of DNSSEC keys
|
||||
#[command(name = "keyset")]
|
||||
Keyset(self::keyset::Keyset),
|
||||
|
||||
/// Show the manual pages
|
||||
Help(self::help::Help),
|
||||
/// Send an UPDATE packet ldns compatibility variant
|
||||
///
|
||||
/// This variant is not a dnst command and only used to provide
|
||||
/// a separate implementation from the dnst update command.
|
||||
#[command(skip)]
|
||||
LdnsUpdate(self::update::LdnsUpdate),
|
||||
|
||||
/// Report a string to stdout
|
||||
///
|
||||
@@ -100,13 +104,14 @@ impl Command {
|
||||
match self {
|
||||
Self::Key2ds(key2ds) => key2ds.execute(env),
|
||||
Self::Keygen(keygen) => keygen.execute(env),
|
||||
Self::Keyset(keyset) => keyset.execute(env),
|
||||
Self::Nsec3Hash(nsec3hash) => nsec3hash.execute(env),
|
||||
Self::Notify(notify) => notify.execute(env),
|
||||
Self::Signer(signer) => signer.execute(env),
|
||||
Self::SignZone(signzone) => signzone.execute(env),
|
||||
Self::Update(update) => update.execute(env),
|
||||
Self::Keyset(keyset) => keyset.execute(env),
|
||||
Self::Help(help) => help.execute(),
|
||||
// Self::Help(help) => help.execute(env),
|
||||
Self::LdnsUpdate(ldnsupdate) => ldnsupdate.execute(env),
|
||||
Self::Report(s) => {
|
||||
writeln!(env.stdout(), "{s}");
|
||||
Ok(())
|
||||
|
||||
+131
-39
@@ -99,7 +99,7 @@ pub struct SignZone {
|
||||
///
|
||||
/// Using this flag enables -O and -R automatically.
|
||||
#[arg(
|
||||
help_heading = Some("OUTPUT FORMATTING"),
|
||||
help_heading = Some("Output Formatting"),
|
||||
short = 'b',
|
||||
default_value_t = false
|
||||
)]
|
||||
@@ -186,14 +186,7 @@ pub struct SignZone {
|
||||
allow_zonemd_without_signing: bool,
|
||||
|
||||
/// Hashing algorithm.
|
||||
#[arg(
|
||||
help_heading = Some("NSEC3 (when using '-n')"),
|
||||
short = 'a',
|
||||
value_name = "algorithm",
|
||||
default_value = "SHA-1",
|
||||
value_parser = ValueParser::new(Nsec3Hash::parse_nsec3_alg),
|
||||
requires = "nsec3"
|
||||
)]
|
||||
#[arg(skip = Nsec3HashAlgorithm::SHA1)]
|
||||
algorithm: Nsec3HashAlgorithm,
|
||||
|
||||
/// Number of hash iterations.
|
||||
@@ -252,7 +245,7 @@ pub struct SignZone {
|
||||
///
|
||||
/// Requires -n.
|
||||
#[arg(
|
||||
help_heading = Some("OUTPUT FORMATTING"),
|
||||
help_heading = Some("Output Formatting"),
|
||||
short = 'L',
|
||||
default_value_t = false,
|
||||
requires = "nsec3"
|
||||
@@ -263,7 +256,7 @@ pub struct SignZone {
|
||||
///
|
||||
/// Enabled automatically by -b.
|
||||
#[arg(
|
||||
help_heading = Some("OUTPUT FORMATTING"),
|
||||
help_heading = Some("Output Formatting"),
|
||||
short = 'O',
|
||||
default_value_t = false,
|
||||
default_value_if("extra_comments", "true", Some("true")),
|
||||
@@ -275,7 +268,7 @@ pub struct SignZone {
|
||||
///
|
||||
/// Enabled automatically by -b.
|
||||
#[arg(
|
||||
help_heading = Some("OUTPUT FORMATTING"),
|
||||
help_heading = Some("Output Formatting"),
|
||||
short = 'R',
|
||||
default_value_t = false,
|
||||
default_value_if("extra_comments", "true", Some("true")),
|
||||
@@ -286,7 +279,7 @@ pub struct SignZone {
|
||||
///
|
||||
/// Cannot be used with -Z or -H.
|
||||
#[arg(
|
||||
help_heading = Some("OUTPUT FORMATTING"),
|
||||
help_heading = Some("Output Formatting"),
|
||||
short = 'T',
|
||||
default_value_t = false,
|
||||
conflicts_with_all = ["allow_zonemd_without_signing", "hash_only"],
|
||||
@@ -505,8 +498,8 @@ impl LdnsCommand for SignZone {
|
||||
hash_only: false,
|
||||
use_yyyymmddhhmmss_rrsig_format: true,
|
||||
preceed_zone_with_hash_list,
|
||||
order_rrsigs_after_the_rtype_they_cover: extra_comments,
|
||||
order_nsec3_rrs_by_unhashed_owner_name: extra_comments,
|
||||
order_rrsigs_after_the_rtype_they_cover: true,
|
||||
order_nsec3_rrs_by_unhashed_owner_name: true,
|
||||
zonefile_path,
|
||||
key_paths,
|
||||
invoked_as_ldns: true,
|
||||
@@ -977,7 +970,11 @@ impl SignZone {
|
||||
|
||||
let mut nsec3_hashes: Option<Nsec3HashMap> = None;
|
||||
|
||||
if self.use_nsec3 && (self.extra_comments || self.preceed_zone_with_hash_list) {
|
||||
if self.use_nsec3
|
||||
&& (self.extra_comments
|
||||
|| self.preceed_zone_with_hash_list
|
||||
|| self.order_nsec3_rrs_by_unhashed_owner_name)
|
||||
{
|
||||
// Create a collection of NSEC3 hashes that can later be used for
|
||||
// debug output.
|
||||
let mut hash_provider = Nsec3HashMap::new();
|
||||
@@ -1160,9 +1157,6 @@ impl SignZone {
|
||||
// compatibility with ldns-signzone, re-order them to be in canonical
|
||||
// order by unhashed owner name and so that hashed names come after
|
||||
// equivalent unhashed names.
|
||||
//
|
||||
// INCOMAPATIBILITY WARNING: Unlike ldns-signzone, we only apply this
|
||||
// ordering if `-b` is specified.
|
||||
let mut owner_rrs;
|
||||
let owner_rrs_iter: AnyOwnerRrsIter =
|
||||
if self.order_nsec3_rrs_by_unhashed_owner_name && nsec3_hashes.is_some() {
|
||||
@@ -1255,12 +1249,22 @@ impl SignZone {
|
||||
// we skip that, and we skip RRSIGs as they are output only after
|
||||
// the RRset that they cover.
|
||||
if self.order_rrsigs_after_the_rtype_they_cover {
|
||||
for rrset in owner_rrs
|
||||
.rrsets()
|
||||
.filter(|rrset| !matches!(rrset.rtype(), Rtype::SOA | Rtype::RRSIG))
|
||||
{
|
||||
for rrset in owner_rrs.rrsets().filter(|rrset| {
|
||||
!(matches!(rrset.rtype(), Rtype::SOA | Rtype::RRSIG)
|
||||
// If run as ldns-signzone we want to list the NSEC RR
|
||||
// at the end of the RRset of the apex. By default,
|
||||
// the NSEC RR would preceed the DNSKEY RRset, so we
|
||||
// need to filter it out here to manually reinsert it
|
||||
// later. This is only necessary for the NSEC RR at
|
||||
// the apex, as the ordering issue doesn't appear at
|
||||
// other locations than the apex.
|
||||
|| (self.invoked_as_ldns
|
||||
&& rrset.rtype() == Rtype::NSEC
|
||||
&& rrset.owner() == apex))
|
||||
}) {
|
||||
for rr in rrset.iter() {
|
||||
self.write_rr(&mut writer, rr)?;
|
||||
|
||||
match rr.data() {
|
||||
ZoneRecordData::Nsec3(nsec3) if self.extra_comments => {
|
||||
nsec3.comment(&mut writer, rr, nsec3_cs)?
|
||||
@@ -1283,13 +1287,52 @@ impl SignZone {
|
||||
for covering_rrsigs in owner_rrs
|
||||
.rrsets()
|
||||
.filter(|this_rrset| this_rrset.rtype() == Rtype::RRSIG)
|
||||
.map(|this_rrset| this_rrset.iter().filter(|rr| matches!(rr.data(), ZoneRecordData::Rrsig(rrsig) if rrsig.type_covered() == rrset.rtype())))
|
||||
.map(|this_rrset| {
|
||||
this_rrset.iter().filter(|rr| {
|
||||
matches!(rr.data(), ZoneRecordData::Rrsig(rrsig)
|
||||
if rrsig.type_covered() == rrset.rtype()
|
||||
&& if self.invoked_as_ldns && rr.owner() == apex {
|
||||
// Withhold an RRSIG that covers the NSEC of the apex
|
||||
// as we' reinserting them at the end of the apex' RRsets
|
||||
rrsig.type_covered() != Rtype::NSEC
|
||||
} else { true }
|
||||
)
|
||||
})
|
||||
})
|
||||
{
|
||||
for covering_rrsig_rr in covering_rrsigs {
|
||||
self.writeln_rr(&mut writer, covering_rrsig_rr)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If running as ldns-signzone, we've been withholding the NSEC and NSEC's RRSIG at
|
||||
// the apex above to reinsert them after all other RRsets at the apex. By default,
|
||||
// the DNSKEY RRset and it's RRSIG would take the rear of the RRsets at the apex.
|
||||
// This doesn't apply, if we're using NSEC3. Additionally, the NSEC RRs at other
|
||||
// places than the apex do not have the ordering issue.
|
||||
if self.invoked_as_ldns && !self.use_nsec3 && owner_rrs.owner() == apex {
|
||||
if let Some(nsec_rrset) = owner_rrs
|
||||
.rrsets()
|
||||
.find(|this_rrset| this_rrset.rtype() == Rtype::NSEC)
|
||||
{
|
||||
self.writeln_rr(&mut writer, nsec_rrset.first())?;
|
||||
}
|
||||
|
||||
if let Some(rrsig_rrset) = owner_rrs
|
||||
.rrsets()
|
||||
.find(|this_rrset| this_rrset.rtype() == Rtype::RRSIG)
|
||||
{
|
||||
for rr in rrsig_rrset.iter() {
|
||||
if matches!(rr.data(), ZoneRecordData::Rrsig(rrsig) if rrsig.type_covered() == Rtype::NSEC)
|
||||
{
|
||||
self.writeln_rr(&mut writer, rr)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.extra_comments {
|
||||
writer.write_str(";\n")?;
|
||||
}
|
||||
@@ -1334,6 +1377,13 @@ impl SignZone {
|
||||
}
|
||||
}
|
||||
|
||||
if self.invoked_as_ldns {
|
||||
if let ZoneRecordData::Nsec3(nsec3) = rr.data() {
|
||||
let rr = Record::new(rr.owner(), rr.class(), rr.ttl(), LdnsNsec3(nsec3));
|
||||
return writer.write_fmt(format_args!("{}", rr.display_zonefile(DISPLAY_KIND)));
|
||||
}
|
||||
}
|
||||
|
||||
writer.write_fmt(format_args!("{}", rr.display_zonefile(DISPLAY_KIND)))
|
||||
}
|
||||
|
||||
@@ -1562,12 +1612,8 @@ impl SignZone {
|
||||
Self::write_iterations_warning(env, "NSEC3 iterations larger than 0 increases performance cost while providing only moderate protection!");
|
||||
}
|
||||
|
||||
fn write_iterations_warning(env: &impl Env, text: &str) {
|
||||
warn!("{text}");
|
||||
writeln!(
|
||||
env.stderr(),
|
||||
"See: https://www.rfc-editor.org/rfc/rfc9276.html"
|
||||
);
|
||||
fn write_iterations_warning(_env: &impl Env, text: &str) {
|
||||
warn!("{text}\nSee: https://www.rfc-editor.org/rfc/rfc9276.html");
|
||||
}
|
||||
|
||||
/// Create the ZONEMD digest for the SIMPLE scheme.
|
||||
@@ -2033,6 +2079,52 @@ impl<O, N> RecordData for YyyyMmDdHhMMSsRrsig<'_, O, N> {
|
||||
}
|
||||
}
|
||||
|
||||
//------------ LdnsNsec3 -----------------------------------------------------
|
||||
|
||||
/// A wrapper around Nsec3 to print the Nsec3 data in the exact format used by
|
||||
/// ldns-signzone with all its quirks.
|
||||
struct LdnsNsec3<'a, O>(&'a Nsec3<O>);
|
||||
|
||||
impl<O: AsRef<[u8]>> ZonefileFmt for LdnsNsec3<'_, O> {
|
||||
fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
|
||||
// This block of code was copied from the `domain` crate impl of
|
||||
// `Zonefilefmt` for domain::rdata::nsec3::Nsec3 and adapted for
|
||||
// ldns output format.
|
||||
p.block(|p| {
|
||||
p.write_show(self.0.hash_algorithm())?;
|
||||
p.write_token(self.0.flags())?;
|
||||
p.write_comment(format_args!(
|
||||
"flags: {}",
|
||||
if self.0.opt_out() {
|
||||
"opt-out"
|
||||
} else {
|
||||
"<none>"
|
||||
}
|
||||
))?;
|
||||
p.write_token(self.0.iterations())?;
|
||||
p.write_comment("iterations")?;
|
||||
p.write_show(self.0.salt())?;
|
||||
p.write_token(format!(
|
||||
" {}",
|
||||
domain::utils::base32::encode_display_hex(&self.0.next_owner())
|
||||
.to_string()
|
||||
.to_lowercase()
|
||||
))?;
|
||||
p.write_show(self.0.types())?;
|
||||
// ldns-signzone ends its NSEC3 rtype bitmap with a trailing
|
||||
// space. Adding an empty token, because the formatter will add
|
||||
// a space as a delimiter.
|
||||
p.write_token("")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<O> RecordData for LdnsNsec3<'_, O> {
|
||||
fn rtype(&self) -> Rtype {
|
||||
Rtype::NSEC3
|
||||
}
|
||||
}
|
||||
|
||||
//-------------- Nsec3HashMap ------------------------------------------------
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -2427,8 +2519,8 @@ mod test {
|
||||
hash_only: false,
|
||||
use_yyyymmddhhmmss_rrsig_format: true,
|
||||
preceed_zone_with_hash_list: false,
|
||||
order_rrsigs_after_the_rtype_they_cover: false,
|
||||
order_nsec3_rrs_by_unhashed_owner_name: false,
|
||||
order_rrsigs_after_the_rtype_they_cover: true,
|
||||
order_nsec3_rrs_by_unhashed_owner_name: true,
|
||||
zonefile_path: PathBuf::from("example.org.zone"),
|
||||
key_paths: Vec::from([PathBuf::from("anykey")]),
|
||||
invoked_as_ldns: true,
|
||||
@@ -2967,14 +3059,14 @@ m.root-servers.net.\t3600000\tIN\tAAAA\t2001:dc3::35
|
||||
|
||||
// (dnst) ldns-signzone -np -f - -e 20241127162422 -i 20241127162422 nsec3_optout1_example.org.zone ksk1 | grep NSEC3
|
||||
let ldns_dnst_output_stripped: &str = "\
|
||||
example.org.\t3600\tIN\tRRSIG\tNSEC3PARAM 15 2 3600 20241127162422 20241127162422 38873 example.org. 0XdDm1l2Mm8dyhtzbyQb91CmyNONs8lc9d22FUGvpjfqo8T2h0xs04x5MIfP0DjmiVnNqIyPK6sipnDqf6tCDg==\n\
|
||||
example.org.\t3600\tIN\tNSEC3PARAM\t1 1 1 -\n\
|
||||
example.org.\t3600\tIN\tRRSIG\tNSEC3PARAM 15 2 3600 20241127162422 20241127162422 38873 example.org. 0XdDm1l2Mm8dyhtzbyQb91CmyNONs8lc9d22FUGvpjfqo8T2h0xs04x5MIfP0DjmiVnNqIyPK6sipnDqf6tCDg==\n\
|
||||
93u63bg57ppj6649al2n31l92iedkjd6.example.org.\t240\tIN\tNSEC3\t1 1 1 - k71ku6aicr5jpdjoe9j7cdnlk6d5c3ue A NS SOA RRSIG DNSKEY NSEC3PARAM \n\
|
||||
93u63bg57ppj6649al2n31l92iedkjd6.example.org.\t240\tIN\tRRSIG\tNSEC3 15 3 240 20241127162422 20241127162422 38873 example.org. z4ceUmbSZiSnluFj8CDJ7B9fukCR2flTWgca4GE2xrw48+fiieH/04xCKhJmDRJUJTVkKtIYpB4p0Q4m60M1Cg==\n\
|
||||
93u63bg57ppj6649al2n31l92iedkjd6.example.org.\t240\tIN\tNSEC3\t1 1 1 - K71KU6AICR5JPDJOE9J7CDNLK6D5C3UE A NS SOA RRSIG DNSKEY NSEC3PARAM\n\
|
||||
k71ku6aicr5jpdjoe9j7cdnlk6d5c3ue.example.org.\t240\tIN\tNSEC3\t1 1 1 - ojicmhri4vp8po7h2kvej99sklqnj5p2 NS \n\
|
||||
k71ku6aicr5jpdjoe9j7cdnlk6d5c3ue.example.org.\t240\tIN\tRRSIG\tNSEC3 15 3 240 20241127162422 20241127162422 38873 example.org. HUrf7tOm3simXqpZj1oZeKX/P3eWoTTKc3fsyqfuLD6sGssXrBfpv1/LINBR9eEBjJ9rFbQXILgweS6huBL/Ag==\n\
|
||||
k71ku6aicr5jpdjoe9j7cdnlk6d5c3ue.example.org.\t240\tIN\tNSEC3\t1 1 1 - OJICMHRI4VP8PO7H2KVEJ99SKLQNJ5P2 NS\n\
|
||||
ojicmhri4vp8po7h2kvej99sklqnj5p2.example.org.\t240\tIN\tNSEC3\t1 1 1 - 93u63bg57ppj6649al2n31l92iedkjd6 NS DS RRSIG \n\
|
||||
ojicmhri4vp8po7h2kvej99sklqnj5p2.example.org.\t240\tIN\tRRSIG\tNSEC3 15 3 240 20241127162422 20241127162422 38873 example.org. NG/8jk3UHht1ZYNEjUZ4swaEHea1amF4l3jZ893oARi95oxtPVLKoinVbBbfVuoanicOgeZxUPpKWHMBR12XDA==\n\
|
||||
ojicmhri4vp8po7h2kvej99sklqnj5p2.example.org.\t240\tIN\tNSEC3\t1 1 1 - 93U63BG57PPJ6649AL2N31L92IEDKJD6 NS DS RRSIG\n\
|
||||
";
|
||||
|
||||
let res = FakeCmd::new([
|
||||
@@ -3003,14 +3095,14 @@ m.root-servers.net.\t3600000\tIN\tAAAA\t2001:dc3::35
|
||||
fn ldns_signzone_disables_minus_b_when_output_is_to_stdout() {
|
||||
let expected_output = r###"example.org.\t239\tIN\tSOA\texample.net. hostmaster.example.net. 1234567890 28800 7200 604800 238
|
||||
example.org.\t239\tIN\tRRSIG\tSOA 8 2 239 20241127162422 20241127162422 51331 example.org. XD5+Exk0KLfvLYA7y+Qs6jhF+JeESFONqZAjkSvznXdjod80W6cv9C77XeHqqod+5glGHlw9bXmVhuJ/5n056BbnDcMWF+AV4taFc/RrDcZb5A0tS6LnRWbpO9puKeLVK10FeAChCygct6/+GNiE12DDLnzKJFuyMuu+nLa2p88=
|
||||
example.org.\t238\tIN\tRRSIG\tNSEC 8 2 238 20241127162422 20241127162422 51331 example.org. AT4PDLEolpApcrYi7mcTXrqCQ6psXeZNdmFub08m6BJRs2jeW07fM11Amft53FXKgqbT23WILkEM7Raai8E8qPJoSdDCys6zYXW/NCU9Cf/oXIKdD4nxQXXWbnX4GCMN4XJy382dYnxTDssQK6lNIKKi4OvGYIxVUPthaLKJFU0=
|
||||
example.org.\t239\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b}
|
||||
example.org.\t239\tIN\tRRSIG\tDNSKEY 8 2 239 20241127162422 20241127162422 51331 example.org. rLwqlu9fYkzAy0jM9crtw5du4rUaDVH9PI4m06lRwjSKhu1VQ1AHjRhlKy1OgUee/5LovXSRGcgNZi4wiTS5ZULTJw7UQTBRXaaNhVACENX/MoVw9SmYuDSTyvQboChmFmYSMch3Q/02VhgN+BT8F7+OdDVgsWqZUEKPVNixk/0=
|
||||
example.org.\t238\tIN\tNSEC\tsome.example.org. SOA RRSIG NSEC DNSKEY
|
||||
example.org.\t239\tIN\tDNSKEY\t257 3 8 AwEAAckp/oMmocs+pv4KsCkCciazIl2+SohAZ2/bH2viAMg3tHAPjw5YfPNErUBqMGvN4c23iBCnt9TktT5bVoQdpXyCJ+ZwmWrFxlXvXIqG8rpkwHi1xFoXWVZLrG9XYCqLVMq2cB+FgMIaX504XMGk7WQydtV1LAqLgP3B8JA2Fc1j ;{id = 51331 (ksk), size = 1024b}
|
||||
example.org.\t238\tIN\tRRSIG\tNSEC 8 2 238 20241127162422 20241127162422 51331 example.org. AT4PDLEolpApcrYi7mcTXrqCQ6psXeZNdmFub08m6BJRs2jeW07fM11Amft53FXKgqbT23WILkEM7Raai8E8qPJoSdDCys6zYXW/NCU9Cf/oXIKdD4nxQXXWbnX4GCMN4XJy382dYnxTDssQK6lNIKKi4OvGYIxVUPthaLKJFU0=
|
||||
some.example.org.\t240\tIN\tA\t1.2.3.4
|
||||
some.example.org.\t240\tIN\tRRSIG\tA 8 3 240 20241127162422 20241127162422 51331 example.org. xdVbhbaMXEyMySCOKy2yYQgU2URAOnu+jLU5py+4R8R3yVVvdl6yMjzdUD3vyxprHitJ+xLrXU/wHSQvtjSwmxVL53ztu+9wrnrhQm6nqXGLW+iw58LepdLVRlppz2WlV0CJAlLIQPJ8rw4hND3NYLJojnO8OdrgpHL89ajD4II=
|
||||
some.example.org.\t238\tIN\tRRSIG\tNSEC 8 3 238 20241127162422 20241127162422 51331 example.org. PP4tH4Y6JNymWSJebPd3zjvDrjyZXVBF8QTKxKAmbmtPacbWyIcRuI0L8+8Z1folAN2U5cUZmCaIbt5Ylaj6ab4UAYHiy0BrcF/zbNIeLRSTz4hOteencIooTDvqIqYuI9/xTVXcfJ+gVzzlIh2dJK2GW5O4+B1xR+CINLNJ/j8=
|
||||
some.example.org.\t238\tIN\tNSEC\texample.org. A RRSIG NSEC
|
||||
some.example.org.\t238\tIN\tRRSIG\tNSEC 8 3 238 20241127162422 20241127162422 51331 example.org. PP4tH4Y6JNymWSJebPd3zjvDrjyZXVBF8QTKxKAmbmtPacbWyIcRuI0L8+8Z1folAN2U5cUZmCaIbt5Ylaj6ab4UAYHiy0BrcF/zbNIeLRSTz4hOteencIooTDvqIqYuI9/xTVXcfJ+gVzzlIh2dJK2GW5O4+B1xR+CINLNJ/j8=
|
||||
"###.replace("\\t", "\t");
|
||||
|
||||
let zone_file_path =
|
||||
|
||||
+1441
-184
File diff suppressed because it is too large
Load Diff
Vendored
+14
-11
@@ -96,17 +96,20 @@ impl<T: io::Write> io::Write for &Stream<T> {
|
||||
|
||||
impl<T: io::Write> Stream<T> {
|
||||
pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) {
|
||||
// This unwrap is not _really_ safe, but we are using this as stdout.
|
||||
// The `println` macro also ignores errors and `push_str` of the
|
||||
// fake stream also does not return an error. If this fails, it means
|
||||
// we can't write to stdout anymore so a graceful exit will be very
|
||||
// hard anyway.
|
||||
self.writer
|
||||
.lock()
|
||||
.unwrap()
|
||||
.deref_mut()
|
||||
.write_fmt(args)
|
||||
.unwrap();
|
||||
if let Err(e) = self.writer.lock().unwrap().deref_mut().write_fmt(args) {
|
||||
// This can happen when output is piped to a command that
|
||||
// terminates before we do. Note: If stderr is also broken this
|
||||
// will panc, but then neither the panic nor this error would be
|
||||
// printed out.
|
||||
let prog = std::env::args_os()
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
eprintln!("{prog}: failed printing to stdout: {e}");
|
||||
// using exit(101) to replicate rust's default error exit code
|
||||
std::process::exit(101);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_terminal(&self) -> bool {
|
||||
|
||||
+3
-3
@@ -74,11 +74,11 @@ impl Error {
|
||||
PrimaryError::Other(error) => error,
|
||||
};
|
||||
|
||||
error!("{msg}");
|
||||
let mut err = env.stderr();
|
||||
let mut buf = String::new();
|
||||
for context in &self.0.context {
|
||||
writeln!(err, "... while {context}");
|
||||
buf.push_str(&format!("... while {context}\n"));
|
||||
}
|
||||
error!("{msg}\n{buf}");
|
||||
}
|
||||
|
||||
pub fn exit_code(&self) -> u8 {
|
||||
|
||||
+16
-15
@@ -7,13 +7,13 @@ use commands::keygen::Keygen;
|
||||
use commands::notify::Notify;
|
||||
use commands::nsec3hash::Nsec3Hash;
|
||||
use commands::signzone::SignZone;
|
||||
use commands::update::Update;
|
||||
use commands::update::LdnsUpdate;
|
||||
use commands::LdnsCommand;
|
||||
use domain::base::zonefile_fmt::DisplayKind;
|
||||
use env::Env;
|
||||
use error::Error;
|
||||
use log::LogFormatter;
|
||||
|
||||
use domain::base::zonefile_fmt::DisplayKind;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
|
||||
pub use self::args::Args;
|
||||
|
||||
@@ -49,7 +49,7 @@ pub fn try_ldns_compatibility<I: IntoIterator<Item = OsString>>(
|
||||
"keygen" => Keygen::parse_ldns_args(args_iter),
|
||||
"nsec3-hash" => Nsec3Hash::parse_ldns_args(args_iter),
|
||||
"signzone" => SignZone::parse_ldns_args(args_iter),
|
||||
"update" => Update::parse_ldns_args(args_iter),
|
||||
"update" => LdnsUpdate::parse_ldns_args(args_iter),
|
||||
_ => Err(format!("Unrecognized ldns command 'ldns-{binary_name}'").into()),
|
||||
}?;
|
||||
|
||||
@@ -100,22 +100,23 @@ fn parse_args(env: impl Env) -> Result<Args, Error> {
|
||||
|
||||
pub fn run(env: impl Env) -> u8 {
|
||||
let stderr = env.stderr();
|
||||
let subscriber = tracing_subscriber::FmtSubscriber::builder()
|
||||
let mut subscriber = tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_ansi(stderr.is_terminal())
|
||||
.with_writer(stderr)
|
||||
.with_max_level(LevelFilter::WARN)
|
||||
.event_format(LogFormatter {
|
||||
program: env.args_os().next().unwrap().to_string_lossy().to_string(),
|
||||
})
|
||||
.finish();
|
||||
|
||||
tracing::subscriber::with_default(subscriber, || {
|
||||
let res = parse_args(&env).and_then(|args| args.execute(&env));
|
||||
match res {
|
||||
Ok(()) => 0,
|
||||
Err(err) => {
|
||||
});
|
||||
let res = parse_args(&env);
|
||||
if let Ok(args) = &res {
|
||||
subscriber = subscriber.with_max_level(args.verbosity);
|
||||
}
|
||||
tracing::subscriber::with_default(subscriber.finish(), || {
|
||||
res.and_then(|args| args.execute(&env))
|
||||
.map(|()| 0)
|
||||
.unwrap_or_else(|err| {
|
||||
err.pretty_print(&env);
|
||||
err.exit_code()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user