#!/usr/bin/env bash
set -euo pipefail

# dnssec-triage.sh
#
# Purpose:
#   Help troubleshoot DNSSEC validation failures by comparing responses from
#   validating and non-validating resolvers, and by checking DS/DNSKEY/answer
#   records for a target domain.
#
# Safe by default:
#   - Read-only checks only
#   - No changes to DNS infrastructure
#   - No destructive actions
#
# Requirements:
#   - dig
#   - bash
#
# Usage:
#   ./dnssec-triage.sh -d example.com -r 1.1.1.1 -n 8.8.8.8
#
# Options:
#   -d  Domain to test (required)
#   -r  Validating recursive resolver to query (default: 1.1.1.1)
#   -n  Non-validating or comparison resolver to query (default: 8.8.8.8)
#   -t  Record name to test (default: www.<domain>)
#   -T  Record type to test (default: A)
#   -h  Show help

DOMAIN=""
VALIDATING_RESOLVER="1.1.1.1"
NONVALIDATING_RESOLVER="8.8.8.8"
TARGET_NAME=""
TARGET_TYPE="A"

usage() {
  cat <<'EOF'
Usage: dnssec-triage.sh -d domain [-r validating_resolver] [-n comparison_resolver] [-t target_name] [-T record_type]

Examples:
  ./dnssec-triage.sh -d example.com
  ./dnssec-triage.sh -d example.com -r 1.1.1.1 -n 8.8.8.8 -t www.example.com -T A
EOF
}

require_cmd() {
  local cmd="$1"
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "Error: required command '$cmd' not found in PATH." >&2
    exit 1
  fi
}

while getopts ":d:r:n:t:T:h" opt; do
  case "$opt" in
    d) DOMAIN="$OPTARG" ;;
    r) VALIDATING_RESOLVER="$OPTARG" ;;
    n) NONVALIDATING_RESOLVER="$OPTARG" ;;
    t) TARGET_NAME="$OPTARG" ;;
    T) TARGET_TYPE="$OPTARG" ;;
    h)
      usage
      exit 0
      ;;
    :) 
      echo "Error: option -$OPTARG requires an argument." >&2
      usage
      exit 1
      ;;
    \?)
      echo "Error: invalid option -$OPTARG." >&2
      usage
      exit 1
      ;;
  esac
done

if [[ -z "$DOMAIN" ]]; then
  echo "Error: -d domain is required." >&2
  usage
  exit 1
fi

if [[ -z "$TARGET_NAME" ]]; then
  TARGET_NAME="www.${DOMAIN}"
fi

require_cmd dig

section() {
  echo
  echo "============================================================"
  echo "$1"
  echo "============================================================"
}

run_dig() {
  local resolver="$1"
  shift
  dig +time=2 +tries=1 +dnssec "@$resolver" "$@" 2>/dev/null || true
}

section "1) Basic comparison: validating vs comparison resolver"
echo "Domain:   $DOMAIN"
echo "Target:   $TARGET_NAME ($TARGET_TYPE)"
echo "Validating resolver:     $VALIDATING_RESOLVER"
echo "Comparison resolver:     $NONVALIDATING_RESOLVER"

echo
printf '%s\n' "--- Validating resolver lookup ---"
run_dig "$VALIDATING_RESOLVER" "$TARGET_NAME" "$TARGET_TYPE" "+multi"

echo
printf '%s\n' "--- Comparison resolver lookup ---"
run_dig "$NONVALIDATING_RESOLVER" "$TARGET_NAME" "$TARGET_TYPE" "+multi"

section "2) DNSSEC chain checks"
echo "--- Parent DS records for ${DOMAIN} ---"
run_dig "$VALIDATING_RESOLVER" "$DOMAIN" DS "+multi"

echo
printf '%s\n' "--- Child DNSKEY records for ${DOMAIN} ---"
run_dig "$VALIDATING_RESOLVER" "$DOMAIN" DNSKEY "+multi"

echo
printf '%s\n' "--- Answer record with DNSSEC data for ${TARGET_NAME} ---"
run_dig "$VALIDATING_RESOLVER" "$TARGET_NAME" "$TARGET_TYPE" "+multi" "+dnssec"

section "3) Authoritative lookup hint"
cat <<EOF
If the validating resolver fails but the comparison resolver succeeds:
  - Check whether DS in the parent matches the child's DNSKEY
  - Check whether the target RRset is signed and signatures are in date
  - Check for stale cache, clock drift, or incomplete key rollover

If both resolvers fail:
  - Check authoritative availability and zone correctness
  - Confirm the zone is being served consistently by all authoritative servers
  - Confirm the record exists and the delegation is intact

If only some resolvers fail:
  - Compare cache freshness and trust anchor state
  - Check resolver time synchronization
  - Verify all authoritative nodes publish the same DNSKEY/DS state
EOF

section "4) Next manual checks"
cat <<EOF
Suggested manual commands:
  dig +dnssec ${DOMAIN} DS
  dig +dnssec ${DOMAIN} DNSKEY
  dig +dnssec ${TARGET_NAME} ${TARGET_TYPE}
  dig +trace ${TARGET_NAME} ${TARGET_TYPE}

Operational note:
  Do not disable validation as a first response. First confirm whether the
  failure is in signing, delegation, resolver trust, or time synchronization.
EOF