Reported symptoms
Failover to the second site completed inside the planned sequence. Storage came up, the databases opened, the application tier started, and the order service reaches its database, its queue and its cache. Measured from the inside, the recovery worked; measured from the outside, the estate is deaf.
Settlement pricing calls to api.rates-vendor.example fail: the TLS handshake
completes and the endpoint returns HTTP 403, with a body naming the source
address it saw. Staff sign-in fails differently — the LDAPS bind to the shared
group directory ldaps.identity.corp.example at 192.0.2.40 never establishes
a connection, returning an immediate refusal rather than a timeout.
Both destinations answered from the primary site an hour earlier, and neither
far end reports an outage. The two failures look different enough that the
bridge opens with them as separate incidents, and the first theory is the
recovery edge: rbdr-dr-edge was rebuilt from IaC three weeks ago, and somebody
remembers a review comment about outbound rules.
Evidence provided
$ sudo nft list table ip natThe recovered application subnet is translated to 198.51.100.24.
$ curl -sS -o /dev/null -w '%{http_code}' "https://$VENDOR_HOST/v1/rates"The status is 403, and the response body names 198.51.100.24 as the source
address the vendor observed. The vendor portal shows one allowlist entry for the
API key: 203.0.113.10/32, created at commissioning and never amended.
$ sudo tcpdump -ni "$WAN_IF" -c 20 "host $DIR_IP and port 636"The SYN leaves with source 198.51.100.24. One round trip later a RST arrives
from 192.0.2.40 itself — not from a local gateway, and not as an ICMP
administratively-prohibited message. The group identity team supplies the source
object group on their 636/tcp rule: it holds the primary site prefix only, and
their platform rejects rather than drops.
$ sudo conntrack -L -d "$VENDOR_IP"The flow is ESTABLISHED with bytes counted in both directions.
$ curl -sS -o /dev/null -w '%{http_code}' "https://$CONTROL_HOST/"It succeeds.
Finally, the recovery site dependency inventory, generated from the asset register by filtering to configuration items the estate owns. It lists neither destination. The DR runbook step that pre-registers recovery addresses with dependency owners reads that inventory — so it ran, found nothing to register, and reported success.
Work the evidence before reading on
- One failure is a 403, the other a TCP refusal. What must be true of the path for either response to exist at all?
- The 403 quotes an address back. Which one, and what does the vendor knowing it establish?
- If the recovery edge were dropping this traffic, what would
conntrackhold, and what would the capture show instead of a RST from the destination? - The pre-registration step completed successfully. What did it read, and what could that source never have contained?
Root cause
The far ends authorise by source address, and failover changed it
Both dependencies key access on where the traffic comes from: the vendor binds
the API key to 203.0.113.10/32, and the identity team’s object group holds the
primary prefix. Neither control is a property of the service; both are
properties of the site. The recovered services present 198.51.100.24, which no
far end has ever been told about.
That is the whole defect. The failures diverge only because the enforcement points sit at different layers: the vendor terminates TLS and refuses in the application, so the client sees a 403, while the identity team’s firewall refuses before the handshake.
The dependency inventory stopped at the boundary of the estate
The inventory was generated by filtering the asset register to configuration items the estate owns. That filter answers a question about ownership; the question that matters at failover is about need. Neither a commercial third party nor another team’s service is an owned asset. Both were needed; neither was listed. So the pre-registration step, reading that inventory, had nothing to act on and exited clean — and every DR exercise since passed with the same gap.
Resolution
Start the request that runs on somebody else’s clock. Ask the vendor to add
198.51.100.24/32 to the API key allowlist, and the identity team to add the
recovery prefix to their object group. Say explicitly that this is an addition:
an edit that swaps the primary address for the recovery one turns this incident
into a failback incident three days from now.
While that is in flight, decide what the service can do without each dependency; settlement pricing and staff sign-in are not equally degradable, and that answer determines whether the site opens partially or stays closed.
An interim path exists only if the primary edge survives: route the affected destinations through the registered address. Say the cost out loud — it couples the recovered estate to the site it just failed away from.
Verification
The vendor call, repeated from a host inside the recovery site, succeeds. Repeated from the primary site, it still succeeds — that second check proves the entry was added rather than swapped.
The LDAPS bind completes from the recovery site and a real account signs in end to end, because a connection that establishes proves only that the firewall changed. Each far end supplies its allowlist as it now stands, showing both addresses.
A day of observed egress destinations is diffed against the dependency inventory, and the difference is empty. That is the check that says the inventory describes reality, not the asset register.
Prevention
Build the dependency inventory from observed traffic. Every destination outside the recovery boundary is a dependency, owned or not. An inventory filtered to owned configuration items cannot contain a vendor.
Record what each far end keys on — source address, API key bound to an address, client certificate, mutual TLS subject — then ask the question DR cares about: which of those changes when the workload moves sites. The address always does.
Pre-register recovery egress addresses in advance. Registration runs on somebody else’s change process at somebody else’s lead time; both addresses belong in every allowlist at all times.
Prefer an identity that survives a site change. Where the far end supports client certificates or workload identity, the credential travels with the service and the address stops being load-bearing.
Make the DR exercise reach outward. An exercise that only calls the estate’s own systems validates exactly the part of the plan the inventory covered — which is how this gap passed every rehearsal.