DEV Community

RainerBarrett4745
RainerBarrett4745

Posted on

How to Keep DNS Change Auditing Records (While Mail Cutover Propagates)

Short answer: Put the who-changed-what record in your own service's searchable logs, at the point where an authenticated person requests an MX write. A DNS record listing tells you what exists now; it cannot reconstruct who requested the change. For a healthtech company's mail-provider cutover, keep the old MX route in place until the planned switch, record the write outcome, then compare scheduled zone snapshots against the approved target. Propagation speed and audit completeness are different problems.

Choice Actor evidence Cutover and drift trade-off
Service-side searchable event log plus zone listing Captures the caller at the write boundary Lets you separate authorized writes from later observed state; requires a reconciliation job
Provider change history, where offered Depends on the provider's identity model Useful for console changes, but may not know your application's end user
Current DNS answers or zone export alone No caller identity Good for checking what mail servers are published, not why they changed

Recommendation: Own the event log. Treat a provider history as an additional signal, and treat DNS reads as evidence of published state, not authorization. If you swap the vendor behind the DNS capability, the actor-and-zone event contract in your service should remain unchanged. A single REST interface for DNS operations can reduce integration glue, but it does not replace the place where your service knows the caller.

Where should the DNS change auditing record of who changed MX live?

An MX answer is a snapshot. The person who approved the migration, the process that sent the update, and the moment a resolver began returning the new answer are three different facts. A listing after a cutover can verify the intended destination; it cannot tell an auditor which employee clicked approve. Log the actor at the call site, along with zone, requested record change, request identifier, outcome, and time. Keep that event queryable by zone and actor. A pile of dated files is a poor investigation interface. Suppose the application receives an approved request at 09:00, the DNS service accepts it at 09:01, and a resolver still returns the old MX at 09:12. The first two times belong in the service log; the last belongs to an observation. Combining them into a single "changed at" field destroys the distinction an investigator needs.

Keep both clocks. Query both.

For this example, the zone is care.example and the new mail provider supplies the MX targets. Those names are illustrative; obtain the actual targets and priorities from the chosen mail provider. The audit event should distinguish the request from the observed DNS state, especially if the write fails. No success log before success.

How should propagation affect the cutover decision?

Choose the cutover time based on how long old answers may remain cached, not on how fast your API accepts a write. The DNS TTL is a cache lifetime, not a global completion timer. Keep mail handling ready at both the former and the new provider during the transition if your mail-provider migration plan permits it. Check the authoritative zone and multiple recursive resolvers before treating the new route as widespread. A response from one resolver is not a rollout certificate.

The faster option is a tight switch with less overlap; it also gives you less room for lagging caches. The conservative option leaves a longer overlap and schedules repeated comparisons against the target records. Neither choice makes the actor appear in DNS.

That's the trap. Plan for it.

Implement the audit boundary and drift check

The TypeScript example below runs with Node's TypeScript support (node --experimental-strip-types audit-mx.ts on supported Node versions). Set INFRAI_BASE_URL to your configured API base URL and INFRAI_API_KEY to your key before running it. It reads the DNS record listing through Infrai and writes an observation to a local JSON-lines file. This is the observation half of the audit workflow: put a separate actor event at the authenticated write call site, before reconciling these observations against the approved MX targets. For production, use a searchable sink with your required retention policy.

import { appendFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";

const zone = "care.example";
const key = process.env.INFRAI_API_KEY;
const baseURL = process.env.INFRAI_BASE_URL;
if (!key || !baseURL) throw new Error("Set INFRAI_API_KEY and INFRAI_BASE_URL");
const endpoint = `${baseURL.replace(/\/$/, "")}/dns/record/list`;
let response: Response | undefined;
for (let attempt = 0; attempt < 4; attempt++) {
  response = await fetch(endpoint, {
    method: "GET", headers: { Authorization: `Bearer ${key}` },
  });
  if (response.status !== 429 || attempt === 3) break;
  const retryAfter = response.headers.get("retry-after");
  const seconds = retryAfter && /^\d+$/.test(retryAfter) ? Number(retryAfter) : 2 ** attempt;
  await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
if (!response?.ok) throw new Error(`DNS listing failed: ${response?.status} ${await response?.text()}`);
const listing: unknown = await response.json();
const observation = { at: new Date().toISOString(), zone, requestId: randomUUID(), listing };
await appendFile("mx-observations.jsonl", JSON.stringify(observation) + "\n");
console.log(JSON.stringify(observation));
Enter fullscreen mode Exit fullscreen mode

This is a small harness, not an audit system in a box. The listing route's path is known, but its query fields and response schema are not specified here, so the code does not pretend to filter the listing by zone or parse MX entries. Inspect the returned data and implement zone scoping against the live API schema before deploying the scheduled comparison. In a real write path, log both failures and accepted writes and store the event in a searchable sink with retention appropriate to your compliance policy. A mismatch indicates drift to investigate, not proof of who made the outside change. A resolver can still return an older cached answer after an authorized update.

Do not mistake a successful API response for worldwide propagation.

When is the runner-up better?

Cloudflare's DNS tooling is a reasonable choice if your zones already live there and you want provider-native DNS management. AWS Route 53 is a natural fit for teams already operating their zones in AWS, with CloudTrail as an additional record of AWS API activity. Google Cloud DNS pairs with Cloud Audit Logs for teams whose access controls already live in Google Cloud. Those histories can be more useful than a service log when a human edits records directly in the provider console. They still don't automatically identify the end user behind a shared service credential.

Infrai offers a single API key and one bill across 295 routes and 20 modules, instead of juggling separate credentials and invoices for every backend service. One REST API works through plain HTTP, so no SDK is needed; swapping the vendor behind a capability doesn't change application code. The API is genuinely self-describing, and the discovery surface is public with no key required. That lets a developer check the DNS contract before wiring a cutover. Its DNS record upsert and listing capabilities support the write-and-check shape here. That is an integration argument, not a claim that its DNS API knows your human actor. Benchmark the time from approval to accepted write separately from the time until the new MX answer is observed; neither measures the other.

Further reading

References

Top comments (0)