-
Notifications
You must be signed in to change notification settings - Fork 24
feat(azure): look up BYOD DNS zone in current subscription #2156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,12 +43,17 @@ const ( | |
| // for `serviceName` in `resourceGroup`. Steps: | ||
| // | ||
| // 1. Find the ContainerApp by tag (defang-service: <serviceName>). | ||
| // 2. Wait for DNS records (CNAME -> app FQDN, TXT asuid.<host> -> verificationId). | ||
| // 2. Wait for DNS records: a routing record (subdomain → CNAME to app FQDN; | ||
| // apex → A record to the env IP) plus TXT asuid.<host> → verificationId. | ||
| // 3. Register the custom hostname with bindingType: Disabled (validates asuid TXT). | ||
| // 4. Issue a managed certificate via CNAME validation. | ||
| // 4. Issue a managed certificate (subdomain → CNAME validation; apex → HTTP). | ||
| // 5. Flip the customDomain to bindingType: SniEnabled, attaching the cert. | ||
| // 6. Verify TLS is serving on https://<hostname>/. | ||
| // | ||
| // Apex domains (e.g. example.com) can't have a CNAME (RFC 1034), so they route | ||
| // via an A record and validate over HTTP. Apex vs subdomain is detected from DNS | ||
| // and from Azure's validation-method rejection — no caller hint is needed. | ||
| // | ||
| // Each ARM step is idempotent: re-running after a partial failure picks up | ||
| // where it left off. resolverAt is used by step 2 to chase the DNS chain — pass | ||
| // dns.DirectResolverAt to query authoritative servers directly (CD task) or | ||
|
|
@@ -137,23 +142,38 @@ func findContainerAppByService(ctx context.Context, client *armappcontainers.Con | |
| return nil, fmt.Errorf("no Container App in %s tagged %s=%s", rg, ServiceTagKey, serviceName) | ||
| } | ||
|
|
||
| // waitForBYODdns blocks until both the CNAME and asuid TXT records resolve | ||
| // correctly, prompting the user once with the values to add. | ||
| // waitForBYODdns blocks until a routing record and the asuid TXT record both | ||
| // resolve, prompting the user once with the values to add. | ||
| // | ||
| // The routing record is a CNAME → app FQDN for a subdomain, or an A record for | ||
| // an apex domain (no CNAME possible at the zone apex). We don't know which the | ||
| // hostname is, so we accept either: the precise CNAME→app-FQDN check, OR any | ||
| // resolvable address at the hostname (the apex A case — the env IP isn't known | ||
| // to this layer, and the cert step validates the apex binding over HTTP). The | ||
| // asuid TXT is always required. | ||
| func waitForBYODdns(ctx context.Context, hostname, expectedCname, expectedTxt string, resolverAt func(string) dns.Resolver) error { | ||
| asuid := "asuid." + hostname | ||
| deadline := time.Now().Add(dnsWaitTimeout) | ||
| promptShown := false | ||
|
|
||
| for { | ||
| cnameOK := dns.CheckDomainDNSReady(ctx, hostname, []string{expectedCname}, resolverAt) | ||
| routeOK := dns.CheckDomainDNSReady(ctx, hostname, []string{expectedCname}, resolverAt) | ||
| if !routeOK { | ||
| // Apex domains route via an A record, not a CNAME to the app FQDN; | ||
| // accept any resolvable address at the hostname. | ||
| if ips, err := resolverAt("").LookupIPAddr(ctx, hostname); err == nil && len(ips) > 0 { | ||
| routeOK = true | ||
| } | ||
| } | ||
| txtOK, _ := dns.LookupTXTContains(ctx, asuid, expectedTxt, resolverAt("")) | ||
| if cnameOK && txtOK { | ||
| if routeOK && txtOK { | ||
| term.Infof("DNS records for %s verified", hostname) | ||
| return nil | ||
| } | ||
| if !promptShown { | ||
| term.Printf("Configure DNS records for %s:\n", hostname) | ||
| term.Printf(" CNAME %s -> %s\n", hostname, expectedCname) | ||
| term.Printf(" CNAME %s -> %s (subdomain)\n", hostname, expectedCname) | ||
| term.Printf(" A %s -> <Container Apps environment IP> (apex)\n", hostname) | ||
|
Comment on lines
+145
to
+176
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Don’t accept any resolvable IP as apex readiness. After the exact CNAME check fails, Possible localized direction- if ips, err := resolverAt("").LookupIPAddr(ctx, hostname); err == nil && len(ips) > 0 {
+ hostIPs, hostErr := resolverAt("").LookupIPAddr(ctx, hostname)
+ targetIPs, targetErr := resolverAt("").LookupIPAddr(ctx, expectedCname)
+ if hostErr == nil && targetErr == nil && hasCommonIP(hostIPs, targetIPs) {
routeOK = true
}🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| term.Printf(" TXT asuid.%s -> %s\n", hostname, expectedTxt) | ||
| term.Infof("Waiting for DNS propagation (timeout %v)...", dnsWaitTimeout) | ||
| promptShown = true | ||
|
|
@@ -270,12 +290,15 @@ func hasCustomDomain(app *armappcontainers.ContainerApp, hostname string) bool { | |
| return false | ||
| } | ||
|
|
||
| // issueManagedCertificate creates the managed cert. CNAME validation is the | ||
| // default and works for any hostname that has a CNAME (subdomains pointing at | ||
| // the Container Apps FQDN). Apex domains can't have a CNAME (RFC 1034) and | ||
| // Azure rejects CNAME validation with InvalidValidationMethod; in that case | ||
| // we fall back to TXT validation, which requires the user to add a | ||
| // _dnsauth.<hostname> TXT record with the validationToken Azure returns. | ||
| // issueManagedCertificate creates the managed cert, choosing the validation | ||
| // method to match the hostname's routing record. CNAME validation is the | ||
| // default and works for subdomains pointing at the Container Apps FQDN. Apex | ||
| // domains can't have a CNAME (RFC 1034), so Azure rejects CNAME validation with | ||
| // InvalidValidationMethod — we detect that and retry with HTTP validation, which | ||
| // Container Apps completes automatically once the apex A record points at the | ||
| // env IP and the hostname is registered (no extra DNS record needed). If HTTP | ||
| // also fails we fall back to TXT validation (the interactive _dnsauth dance, | ||
| // usable from the CLI). | ||
| func issueManagedCertificate(ctx context.Context, client *armappcontainers.ManagedCertificatesClient, rg, envName, certName, hostname, location string) (*armappcontainers.ManagedCertificate, error) { | ||
| resp, err := submitManagedCert(ctx, client, rg, envName, certName, hostname, location, armappcontainers.ManagedCertificateDomainControlValidationCNAME) | ||
| if err == nil { | ||
|
|
@@ -284,7 +307,18 @@ func issueManagedCertificate(ctx context.Context, client *armappcontainers.Manag | |
| if !isInvalidValidationMethod(err) { | ||
| return nil, fmt.Errorf("issuing managed certificate %s: %w", certName, err) | ||
| } | ||
| term.Infof("CNAME validation rejected for %s (apex domain); falling back to TXT validation", hostname) | ||
|
|
||
| // Apex domain: CNAME validation is impossible. HTTP validation needs no extra | ||
| // DNS record (the asuid TXT + A record already in place suffice), so it works | ||
| // unattended in the CD task — unlike TXT, which requires an interactive | ||
| // _dnsauth record. | ||
| term.Infof("CNAME validation rejected for %s (apex domain); using HTTP validation", hostname) | ||
| resp, err = submitManagedCert(ctx, client, rg, envName, certName, hostname, location, armappcontainers.ManagedCertificateDomainControlValidationHTTP) | ||
| if err == nil { | ||
| return resp, nil | ||
| } | ||
|
|
||
| term.Infof("HTTP validation failed for %s (%v); falling back to TXT validation", hostname, err) | ||
| return submitManagedCertTXT(ctx, client, rg, envName, certName, hostname, location) | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.