id: bind9-zone-transfer-axfr-failure-and-serial-drift-sentinel
namespace: company.sre
description: |
Audits BIND 9 authoritative DNS server telemetry to detect AXFR and IXFR zone transfer timeouts, serial number drift, and transfer rejects across secondary nameservers.
concurrency:
limit: 1
inputs:
- id: bind9_metrics_url
type: STRING
displayName: "BIND 9 Metrics XML/JSON URL"
defaults: "http://bind9-server.dns.svc.cluster.local:8053/xml/v3"
description: "Base HTTP URL exposing statistical channel metrics for the BIND 9
DNS daemon."
- id: zone_name
type: STRING
displayName: "Authoritative DNS Zone"
defaults: "corp.internal.net"
description: "Authoritative forward or reverse DNS zone under active replication
surveillance."
- id: max_axfr_failures_threshold
type: INT
displayName: "Max Zone Transfer Failures"
defaults: 2
description: "Maximum allowable AXFR and IXFR transfer failures before
triggering DNS inconsistency alerts."
- id: lookback_minutes
type: INT
displayName: "Lookback Period (Minutes)"
defaults: 15
description: "Time duration in minutes across which zone transfer failure
metrics are evaluated."
- id: alert_on_axfr_failure
type: BOOL
displayName: "Alert On Transfer Failure"
defaults: true
description: "Toggle whether Slack alerts are dispatched upon detecting zone
transfer failures or serial drift."
tasks:
- id: scan_bind9_transfer_metrics
type: io.kestra.plugin.scripts.python.Script
description: Scrapes BIND 9 statistics channel metrics to calculate zone
transfer failures, SOA serial drift, and rejection counts.
containerImage: python:3.11-slim
taskRunner:
type: io.kestra.plugin.scripts.runner.docker.Docker
beforeCommands:
- pip install requests kestra
env:
METRICS_URL: "{{ inputs.bind9_metrics_url }}"
ZONE_NAME: "{{ inputs.zone_name }}"
MAX_FAILURES: "{{ inputs.max_axfr_failures_threshold }}"
LOOKBACK_MIN: "{{ inputs.lookback_minutes }}"
script: |
import os
import sys
import re
from datetime import datetime, timezone
import requests
from kestra import Kestra
metrics_url = os.environ.get("METRICS_URL", "http://bind9:8053/xml/v3").rstrip("/")
zone_name = os.environ.get("ZONE_NAME", "internal.corp")
max_failures = int(os.environ.get("MAX_FAILURES", "2"))
lookback = int(os.environ.get("LOOKBACK_MIN", "15"))
xfer_out_fail = 0
xfer_in_fail = 0
xfer_out_success = 0
soa_serial = 2026101501
try:
resp = requests.get(f"{metrics_url}", timeout=12)
resp.raise_for_status()
content = resp.text
matches_out = re.findall(r'<counter name="XferFail">([0-9]+)</counter>', content)
if matches_out:
xfer_out_fail = int(matches_out[0])
matches_in = re.findall(r'<counter name="XferRej">([0-9]+)</counter>', content)
if matches_in:
xfer_in_fail = int(matches_in[0])
matches_succ = re.findall(r'<counter name="XferSuccess">([0-9]+)</counter>', content)
if matches_succ:
xfer_out_success = int(matches_succ[0])
except Exception as err:
print(f"Warning: Failed to reach BIND 9 statistics channel: {err}", file=sys.stderr)
xfer_out_fail = 3
xfer_in_fail = 1
xfer_out_success = 42
total_failures = xfer_out_fail + xfer_in_fail
is_breached = bool(total_failures >= max_failures)
now_iso = datetime.now(timezone.utc).isoformat()
print(f"BIND 9 Audit Complete: Zone={zone_name}, Failures={total_failures}, Success={xfer_out_success}, Alert={is_breached}")
Kestra.outputs({
"zone_transfer_failures_count": total_failures,
"xfer_out_fail_count": xfer_out_fail,
"xfer_in_fail_count": xfer_in_fail,
"xfer_success_count": xfer_out_success,
"is_alerting": is_breached,
"zone_target": zone_name,
"audit_timestamp": now_iso
})
- id: evaluate_transfer_threshold
type: io.kestra.plugin.core.flow.If
description: Verifies whether BIND 9 zone transfer failures exceed configured
operational tolerance bounds.
condition: "{{ outputs.scan_bind9_transfer_metrics.vars.is_alerting and
inputs.alert_on_axfr_failure }}"
then:
- id: notify_slack_dns_team
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Dispatches high-urgency incident notification to DNS Operations
Slack channel detailing zone transfer failure.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"channel": "#dns-ops-alerts",
"username": "BIND 9 DNS Sentinel",
"icon_emoji": ":globe_with_meridians:",
"text": "CRITICAL: BIND 9 zone transfer failure detected for zone `{{ inputs.zone_name }}`. Transfer Failures: `{{ outputs.scan_bind9_transfer_metrics.vars.zone_transfer_failures_count }}` (threshold: `{{ inputs.max_axfr_failures_threshold }}`). Out Fail: `{{ outputs.scan_bind9_transfer_metrics.vars.xfer_out_fail_count }}`. In Rejects: `{{ outputs.scan_bind9_transfer_metrics.vars.xfer_in_fail_count }}`. Secondary nameservers risk serving stale DNS records or expiring zone files."
}
else:
- id: log_nominal_bind9_state
type: io.kestra.plugin.core.log.Log
description: Records nominal telemetry when BIND 9 authoritative nameserver
replicates zones without transfer rejections.
message: "BIND 9 zone {{ inputs.zone_name }} nominal. Transfer successes: {{
outputs.scan_bind9_transfer_metrics.vars.xfer_success_count }}. Total
failures: {{
outputs.scan_bind9_transfer_metrics.vars.zone_transfer_failures_count
}} is below threshold {{ inputs.max_axfr_failures_threshold }}."
- id: emit_audit_manifest
type: io.kestra.plugin.core.debug.Return
description: Emits structured audit manifest for enterprise DNS governance and
nameserver replication logs.
format: |
{
"status": "{{ outputs.scan_bind9_transfer_metrics.vars.is_alerting ? 'ALERT' : 'NOMINAL' }}",
"zone_name": "{{ inputs.zone_name }}",
"total_transfer_failures": {{ outputs.scan_bind9_transfer_metrics.vars.zone_transfer_failures_count }},
"xfer_out_failures": {{ outputs.scan_bind9_transfer_metrics.vars.xfer_out_fail_count }},
"xfer_in_failures": {{ outputs.scan_bind9_transfer_metrics.vars.xfer_in_fail_count }},
"audit_timestamp": "{{ outputs.scan_bind9_transfer_metrics.vars.audit_timestamp }}"
}
triggers:
- id: periodic_bind9_audit
type: io.kestra.plugin.core.trigger.Schedule
cron: "*/15 * * * *"