Skip to content

Add cross-account AssumeRole support for Route 53 DNS validation.#749

Open
VA6DAH wants to merge 1 commit into
webprofusion:developmentfrom
VA6DAH:feature/route53-assume-role
Open

Add cross-account AssumeRole support for Route 53 DNS validation.#749
VA6DAH wants to merge 1 commit into
webprofusion:developmentfrom
VA6DAH:feature/route53-assume-role

Conversation

@VA6DAH

@VA6DAH VA6DAH commented Jun 28, 2026

Copy link
Copy Markdown

Summary

Adds optional STS AssumeRole support to the Amazon Route 53 DNS-01 provider so Certify can update hosted zones in a different AWS account than the IAM user whose access keys are stored in credentials.

Assume-role settings are defined on the provider using the existing IsCredential parameter model: they appear under stored credentials (access keys, assume-role toggle, role ARN, session name, optional external ID). Certificate configuration keeps only DNS Zone Id and propagation delay.

Scope: changes are limited to the Route 53 plugin (DnsProviderAWSRoute53.cs and Plugin.DNS.AWSRoute53.csproj) only.

Why this matters (especially in AWS Organizations)

In many AWS Organizations, DNS is centralized in a shared/network account while applications and certificate workloads run in member accounts. Route 53 hosted zones often cannot (and should not) be managed from the same account that runs the app server.

Without AssumeRole, operators typically must either:

  • Create long-lived IAM users in the DNS account and copy keys onto every Certify host, or
  • Grant overly broad Route 53 permissions to identities in workload accounts that do not own the zones.

AssumeRole is the standard AWS pattern for this:

Concern How AssumeRole helps
Account separation Workload account holds a caller identity; DNS account holds a role trusted for sts:AssumeRole only.
Least privilege Caller: sts:AssumeRole on one role. Target role: scoped route53:ChangeResourceRecordSets on specific hosted zones.
Org governance Aligns with centralized DNS, SCPs, and landing-zone designs where member accounts should not administer org-wide DNS directly.
Credential hygiene No need to duplicate DNS-account IAM users across many servers; rotate/trust at the role boundary.
Cross-account safety Optional External ID supports confused-deputy protection in the role trust policy.

What changed

DnsProviderAWSRoute53.cs

  • When Assume cross-account role is enabled in stored credentials, calls sts:AssumeRole with the caller's access keys, then creates the Route 53 client with temporary session credentials.
  • Supports Role ARN, session name (default CertifyTheWeb), and optional External ID.
  • Reads assume-role settings from credentials first, with fallback to certificate parameters for backward compatibility.
  • New credential parameters use default IsCredential (same pattern as Cloudflare/Azure); only zoneid and propagationdelay remain certificate parameters (IsCredential=false).

Plugin.DNS.AWSRoute53.csproj

  • Adds AWSSDK.SecurityToken and pins aligned AWSSDK.Core / AWSSDK.Route53 versions to avoid TypeLoadException at plugin load.

Credential vs certificate configuration

Stored credentials Per-certificate DNS config
Access Key DNS Zone Id
Secret Access Key Propagation delay
Assume cross-account role
Role ARN
Session name
External ID (optional)

Screenshot

Route 53 stored credentials with Assume Role settings

Test plan

  • Route 53 plugin loads without errors (custom plugin + aligned AWSSDK assemblies deployed to service folder)
  • Route 53 appears in credential provider dropdown
  • Assume-role fields appear in Settings → Credentials for Route 53
  • Certificate DNS config shows zone ID and propagation delay only
  • End-to-end Let's Encrypt staging DNS-01 issuance with cross-account AssumeRole (see logs below)
  • Same-account Route 53 (assume role disabled) — regression
  • Credential Test action against DNS account (optional; issuance test covers the path)

Verified: staging certificate request log

Cross-account setup: caller IAM user in workload account → sts:AssumeRolearn:aws:iam::123456789012:role/CertifyRoute53 → TXT create/validate/delete in hosted zone /hostedzone/Z0EXAMPLEZONEID for example.com.

(Domain, AWS account ID, hosted zone ID, ACME order/challenge URLs, and DNS-01 token value redacted.)

2026-07-05 15:30:12.811 -06:00 [INF] ---- Beginning Request [example.com] ----
2026-07-05 15:30:12.815 -06:00 [INF] Certify/7.1.0.0 (Microsoft Windows 10.0.26200; Microsoft Windows NT 10.0.26200.0) 
2026-07-05 15:30:13.038 -06:00 [INF] Beginning certificate request process: example.com using ACME provider Anvil
2026-07-05 15:30:13.038 -06:00 [INF] The selected Certificate Authority is: Let's Encrypt
2026-07-05 15:30:13.040 -06:00 [INF] Requested identifiers to include on certificate: example.com
2026-07-05 15:30:15.355 -06:00 [INF] Created ACME Order: https://acme-staging-v02.api.letsencrypt.org/acme/order/[REDACTED]
2026-07-05 15:30:16.677 -06:00 [INF] Got http-01 challenge https://acme-staging-v02.api.letsencrypt.org/acme/chall/[REDACTED]
2026-07-05 15:30:16.751 -06:00 [INF] Got dns-01 challenge https://acme-staging-v02.api.letsencrypt.org/acme/chall/[REDACTED]
2026-07-05 15:30:17.336 -06:00 [INF] Preparing automated challenge responses for: example.com
2026-07-05 15:30:17.439 -06:00 [INF] Route53 :: Initialized with assumed role arn:aws:iam::123456789012:role/CertifyRoute53
2026-07-05 15:30:17.440 -06:00 [INF] DNS: Creating TXT Record '_acme-challenge.example.com' with value '[REDACTED]', [example.com] in ZoneId '/hostedzone/Z0EXAMPLEZONEID' using API provider 'Amazon Route 53 DNS API'
2026-07-05 15:30:38.932 -06:00 [INF] DNS change completed.
2026-07-05 15:30:38.933 -06:00 [INF] DNS: Amazon Route 53 DNS API :: Dns Record Created/Updated: _acme-challenge.example.com
2026-07-05 15:31:39.595 -06:00 [INF] Resuming certificate request using CA: Let's Encrypt
2026-07-05 15:31:39.596 -06:00 [INF] Attempting challenge response validation for: example.com
2026-07-05 15:31:39.597 -06:00 [INF] [Progress] Checking automated challenge response for: example.com
2026-07-05 15:31:39.597 -06:00 [INF] Submitting challenge for validation: example.com 
2026-07-05 15:31:44.201 -06:00 [INF] [Progress] Validation completed: example.com
2026-07-05 15:31:44.451 -06:00 [INF] Route53 :: Initialized with assumed role arn:aws:iam::123456789012:role/CertifyRoute53
2026-07-05 15:31:44.452 -06:00 [INF] DNS: Deleting TXT Record '_acme-challenge.example.com' :'[REDACTED]', [example.com] in ZoneId '/hostedzone/Z0EXAMPLEZONEID' using API provider 'Amazon Route 53 DNS API'
2026-07-05 15:31:44.809 -06:00 [INF] Route53 :: Delete Record : Zone matched /hostedzone/Z0EXAMPLEZONEID /hostedzone/Z0EXAMPLEZONEID : Fetching TXT record set _acme-challenge.example.com 
2026-07-05 15:31:44.912 -06:00 [INF] Route53 :: Delete Record : Fetched TXT record set OK _acme-challenge.example.com. 
2026-07-05 15:32:07.719 -06:00 [INF] DNS change completed.
2026-07-05 15:32:07.720 -06:00 [INF] [Progress] Requesting certificate via Certificate Authority
2026-07-05 15:32:13.197 -06:00 [INF] [Progress] Completed certificate request.
2026-07-05 15:32:13.262 -06:00 [INF] [Progress] Performing automated certificate binding
2026-07-05 15:32:14.333 -06:00 [INF] CertificateStorage: Certificate Stored - Certificate stored OK
2026-07-05 15:32:14.333 -06:00 [INF] Completed certificate request and automated binding updates
2026-07-05 15:32:14.334 -06:00 [INF] [Progress] New certificate received and standard deployment performed OK.

Key lines: Route53 :: Initialized with assumed role … before TXT create and cleanup; challenge validation completed; certificate issued and stored.

Notes for reviewers

  • Backward compatible: existing Route 53 credentials without assume-role behave as before.
  • No core/UI changes: uses the existing credential vs certificate parameter split (IsCredential on ProviderParameter); no changes outside the Route 53 plugin.
  • SDK pinning: AWSSDK.SecurityToken is added alongside pinned Core/Route53 versions so the plugin loads reliably when SecurityToken is required.

@CLAassistant

CLAassistant commented Jun 28, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@webprofusion-chrisc

Copy link
Copy Markdown
Contributor

Thanks for your proposal, it's much appreciated.

A few things:

  • If your change is produced by an agent it's usually better to ask us via support for the feature to be considered because we can prompt it ourselves and save you the effort
  • Your change should only touch the AWS Route53 provider, every other part of the change is fixing stuff that's unrelated or working around perceived problems.
  • Consider if adopting the Posh-ACME Route53 provider might solve this problem: https://poshac.me/docs/latest/Plugins/Route53/ if so, we can support using that.
  • The core premise of the change seems to be preventing the need to store DNS credentials on each install. Our Certify Management Hub product solves this problem across all DNS providers with Managed Challenges and/or Certificate Subscriptions.

@VA6DAH

VA6DAH commented Jun 29, 2026

Copy link
Copy Markdown
Author

Hey Chris,

Yes. This was written with curser. My initial attempt only touched the route 53 provider but that the change caused the provider to disappear entirely from the list. The only time I got it to work without touching other components all the new fields appeared in the DNS-01 configuration on the certificate config instead of within the credential config.

  • Posh-ACME does work well and I could see this resolving my use case.
  • In some cases , line of sight to the Certify Hub isn't possible, or should still support temporary credentials.
  • One thing that would also be lovely to see is support for no credentials if the application is deployed directly on a EC2 instance.
  • I would be happy to either convert this into a feature request or a discussion. I am happy to test and assist documenting more complex AWS integrations.

Authenticate with IAM access keys stored in credentials, optionally assume a role in another AWS account via STS, and use the resulting session credentials to update Route 53 for ACME DNS-01 challenges. Assume-role settings use default IsCredential (stored credentials); zone ID and propagation delay remain certificate parameters.
@VA6DAH VA6DAH force-pushed the feature/route53-assume-role branch from 51a81a2 to eaff92b Compare July 5, 2026 19:56
@VA6DAH

VA6DAH commented Jul 5, 2026

Copy link
Copy Markdown
Author

Hi @webprofusion-chrisc,

You were absolutely right. Did some more digging and was able to accomplish the goal with the editing of two files. How does this look now?

Dakota

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.

3 participants