Skip to content

Add AWS Route 53 Resolver DNS security fetcher - #326

Open
iteuscher wants to merge 3 commits into
mainfrom
add-aws-route53-resolver-dns-security
Open

iteuscher wants to merge 3 commits into
mainfrom
add-aws-route53-resolver-dns-security

Conversation

@iteuscher

Copy link
Copy Markdown
Contributor

Requested by Isaac Teuscher · Slack thread

Before: Nothing collected Route 53 Resolver settings. Whether a VPC validated DNSSEC, filtered its DNS queries through DNS Firewall, or logged its queries had to be shown by hand, and nothing flagged a DNSSEC config still pointing at a VPC that had been deleted.

After: aws_route53_resolver_dns_security (EVD-R53-RESOLVER-DNS-SECURITY) writes one record per VPC per profile/region target. Each record has DnssecValidation, DnsFirewall (Associated, plus each rule group with its rules' Action and domain list or DNS Firewall Advanced threat protection) and QueryLogging (Enabled, plus each association's Status and Error). It also writes OrphanedDnssecConfigs. A field whose AWS call failed reads null, not off or empty, and the run exits 1 with the AWS error in metadata.error.

How: A bash fetcher based on aws/vpc_flow_logs. It makes five region-wide list calls (ec2 describe-vpcs and four route53resolver list calls) plus one list-firewall-rules per associated rule group, then joins the results per VPC with jq. The AWS CLI v2 paginates the calls itself, and none of them turns that off. The fetcher uses the shared aws.sh helpers (aws_target_id, failure classification, aws_finish).

KSIs: KSI-SVC-VCM, KSI-CNA-RNT, KSI-MLA-LET. Linear: PAR-91, PAR-1322 (query logging), PAR-1488 (DNS Firewall).

Review changes on top of Isaac's version

  • If a list call fails, the fields it feeds read null instead of NOT_CONFIGURED, false or []. OrphanedDnssecConfigs is null when describe-vpcs or the DNSSEC list fails. Before this change, a failed describe-vpcs listed every DNSSEC config as orphaned.
  • If a rule group's rules can't be read, its Rules is null, and the failure is logged at error level like the other calls.
  • DNS Firewall Advanced rules have no domain list, so DomainListName is now null for them instead of "unknown". FirewallThreatProtectionId, DnsThreatProtection and ConfidenceThreshold are captured. "unknown" now means only that the rule has a domain list id that can't be named.
  • QueryLogging.ConfigIds became QueryLogging.Associations, with ConfigId, Status and Error for each association, so ACTION_NEEDED and FAILED associations show in the evidence. Enabled is still true only for ACTIVE.
  • The instructions follow the authoring guide's layout and describe every value: all six DNSSEC ValidationStatus values plus NOT_CONFIGURED, the association statuses and the query-log error codes. The fetcher is added to the text-format contract check in tests/test_contracts.py.
  • tests/test_aws_route53_resolver_dns_security.py runs the fetcher against a stub aws CLI. It covers the normal case, a failed rule-group read, failed list calls and a failed VPC list.
  • Two existing tests failed with the new fetcher and are fixed here. The AWS fetcher count in tests/test_aws_failure_classification.py goes from 80 to 81. aws_discovery_beta gets a dns_firewall_association type, because test_every_service_an_aws_fetcher_reads_is_discovered requires discovery to cover route53resolver.
  • CHANGELOG entry added. docs/ksi_mapping.md is regenerated. The mapping on main was out of date, so the regeneration also adds aws_inspector_ecr_findings, aws_inspector_ecr_report and azure_entra_sign_in_logs.

Still needed

  • No sample.json. A real GovCloud run is still needed, then paramify samples to sanitize it. CI doesn't require one, and about 39 other AWS fetchers don't have one either. No synthetic sample is committed.
  • Not run against real AWS. Everything here used a stub CLI, so field names, enum values and --query shapes come from the AWS API reference and haven't been checked against a live account.
  • Route 53 Profiles. If a VPC gets its DNS Firewall rule groups, query logging or DNSSEC setting through a Route 53 Profile, the per-VPC association lists this fetcher reads probably won't show it. Such a VPC would read as not covered. If Paramify uses Profiles, this needs a route53profiles read. This hasn't been checked against AWS.
  • USE_LOCAL_RESOURCE_SETTING / UPDATING_TO_USE_LOCAL_RESOURCE_SETTING are passed through as AWS reports them. The instructions say only ENABLED proves validation is on.
  • Possible follow-up for KSI-MLA-LET: route53resolver list-resolver-query-log-configs would add each config's DestinationArn, which shows where the query logs go. It needs one more IAM action.
  • Like the other AWS bash fetchers, the payload has no metadata.partial_failure / api_failures. The failure reason goes to metadata.error through aws_finish.

Validator ideas (not committed; validators aren't committed on fetcher branches)

  • Every non-default VPC has DnssecValidation == "ENABLED".
  • Every non-default VPC has DnsFirewall.Associated true with at least one BLOCK rule on an AWSManaged* list. This passes once PAR-1488 is fixed and should accept ALERT during rollout.
  • Every non-default VPC has QueryLogging.Enabled true.
  • OrphanedDnssecConfigs is empty, and no Default: true VPC exists.

Testing

  • pytest -q -n auto: 8845 passed, 83 skipped, 0 failed. ruff check framework/ and mypy framework/ are clean.
  • Smoke steps: paramify list shows the fetcher as [fanout], catalog and ksi pass, describe works for all 344 fetchers, and tools/cli_goldens.py passes.
  • Stub CLI that applies the real --query expressions with jmespath: normal case, a VPC with no DNSSEC config, a default VPC, an orphaned DNSSEC config, a BLOCK rule on AWSManagedDomainsAggregateThreatList, and a DNS Firewall Advanced rule. Failure cases for sts, describe-vpcs, the DNSSEC list, one rule group, and three list calls together each exit 1, write the JSON, and put a not_authorized reason in the status file.
  • paramify run with two region targets wrote one envelope per target.
  • shellcheck reports only SC1091, the same as sibling fetchers. Not run: gitleaks, the Wiz scan, the TUI job, and Python 3.10–3.12/3.14 (only 3.13 locally).

Generated by Claude Code

- A failed list call now reads null instead of NOT_CONFIGURED/false/[]:
  DnssecValidation, DnsFirewall, QueryLogging, and OrphanedDnssecConfigs
  (which no longer lists every DNSSEC config when describe-vpcs fails).
- A rule group whose rules could not be read has Rules: null, and the
  failure is logged at error level like the other calls.
- DNS Firewall Advanced rules: DomainListName is null when there is no
  domain list; FirewallThreatProtectionId, DnsThreatProtection and
  ConfidenceThreshold are captured.
- QueryLogging lists each association's ConfigId, Status and Error.
- A failure building the evidence JSON is recorded and still writes a file.
- Instructions rewritten to the authoring guide's layout and describe every
  DNSSEC, association and error value; fetcher added to the text-format
  contract check.
- Tests: stub-CLI tests for the normal and failure paths; AWS fetcher count
  80 -> 81; aws_discovery_beta gains a route53resolver type so
  test_every_service_an_aws_fetcher_reads_is_discovered passes.
- CHANGELOG entry; docs/ksi_mapping.md regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01494hRBrpFog5PySYkZfNCU
@iteuscher

Copy link
Copy Markdown
Contributor Author

Giving me too much credit Claude, it was just a different claude session that wrote the v1 not me lol

Reviewers can see the payload from a GovCloud run without account identifiers.

Co-authored-by: Cursor <cursoragent@cursor.com>
@iteuscher

Copy link
Copy Markdown
Contributor Author

Ran this against GovCloud with a read-only SSO permission set, one target each for us-gov-west-1 and us-gov-east-1.

Both targets exited 0 in about 4 seconds and wrote one envelope each. That permission set covered every call this fetcher makes (ec2:DescribeVpcs, the four route53resolver list calls, and sts:GetCallerIdentity). No field came back null.

What the live responses confirmed:

  • DnssecValidation came back as ENABLED and as NOT_CONFIGURED.
  • DnsFirewall.Associated was false with RuleGroups: [], and QueryLogging.Enabled was false with Associations: [].
  • OrphanedDnssecConfigs returned a config whose ResourceId matched no VPC in that region, with ValidationStatus: ENABLED. That is the deleted-VPC case.
  • Default and non-default VPCs both showed up. A VPC with no Name tag has Name: "".

Still only covered by the stub tests, because the account I ran against has none of them: DNS Firewall rule groups (rule Action, domain list names, DNS Firewall Advanced fields) and query log associations (Status and Error). The other DNSSEC statuses (ENABLING, DISABLING, DISABLED, USE_LOCAL_RESOURCE_SETTING, UPDATING_TO_USE_LOCAL_RESOURCE_SETTING) did not appear. Route 53 Profiles were not checked.

A sanitized sample.json from this run is committed. Identifiers are replaced, and reviewed is true.

@iteuscher
iteuscher marked this pull request as ready for review November 11, 2026 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants