diff --git a/custom-domain/dstack-ingress/DNS_PROVIDERS.md b/custom-domain/dstack-ingress/DNS_PROVIDERS.md index 845d288..09202ca 100644 --- a/custom-domain/dstack-ingress/DNS_PROVIDERS.md +++ b/custom-domain/dstack-ingress/DNS_PROVIDERS.md @@ -17,7 +17,7 @@ This guide explains how to configure dstack-ingress to work with different DNS p - `GATEWAY_DOMAIN` - dstack gateway domain (e.g., `_.dstack-prod5.phala.network`) - `CERTBOT_EMAIL` - Email for Let's Encrypt registration - `TARGET_ENDPOINT` - Backend application endpoint to proxy to -- `DNS_PROVIDER` - DNS provider to use (`cloudflare`, `linode`, `namecheap`) +- `DNS_PROVIDER` - DNS provider to use (`cloudflare`, `linode`, `namecheap`, `route53`) ### Optional Variables @@ -104,8 +104,8 @@ PolicyDocument: ``` **Important Notes for Route53:** -- The certbot plugin uses the format `certbot-dns-route53` package -- CAA will merge AWS & Let's Encrypt CA domains to existing records if they exist +- The certbot plugin uses the `certbot-dns-route53` package +- CAA will merge AWS & Let's Encrypt CA domains into existing records if they exist - It is essential that the AWS service account used can only assume the limited role. See cloudformation example. ## Docker Compose Examples @@ -187,7 +187,8 @@ services: CERTBOT_EMAIL: ${CERTBOT_EMAIL} TARGET_ENDPOINT: http://backend:8080 SET_CAA: 'true' - +volumes: + cert-data: ``` ## Migration from Cloudflare-only Setup diff --git a/custom-domain/dstack-ingress/README.md b/custom-domain/dstack-ingress/README.md index d166cd2..e9eae37 100644 --- a/custom-domain/dstack-ingress/README.md +++ b/custom-domain/dstack-ingress/README.md @@ -53,39 +53,6 @@ The dstack-ingress now supports multiple domains in a single container: - Each domain gets its own SSL certificate - Flexible nginx configuration per domain -### Wildcard Domain Support - -You can use a wildcard domain (e.g. `*.myapp.com`) to route all subdomains to a single dstack application: - -- The TXT record is automatically set as `_dstack-app-address-wildcard.myapp.com` (instead of `_dstack-app-address.*.myapp.com`) -- CAA records use the `issuewild` tag on the base domain -- Requires dstack-gateway with wildcard TXT resolution support ([dstack#545](https://github.com/Dstack-TEE/dstack/pull/545)) - -```yaml -services: - dstack-ingress: - image: dstacktee/dstack-ingress:latest - ports: - - "443:443" - environment: - - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN} - - DOMAIN=*.myapp.com - - GATEWAY_DOMAIN=_.dstack-prod5.phala.network - - CERTBOT_EMAIL=${CERTBOT_EMAIL} - - SET_CAA=true - - TARGET_ENDPOINT=http://app:80 - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock - - /var/run/tappd.sock:/var/run/tappd.sock - - cert-data:/etc/letsencrypt - restart: unless-stopped - app: - image: nginx - restart: unless-stopped -volumes: - cert-data: -``` - ## Usage ### Prerequisites diff --git a/custom-domain/dstack-ingress/scripts/dns_providers/route53.py b/custom-domain/dstack-ingress/scripts/dns_providers/route53.py index 1c22a0f..214ee85 100644 --- a/custom-domain/dstack-ingress/scripts/dns_providers/route53.py +++ b/custom-domain/dstack-ingress/scripts/dns_providers/route53.py @@ -6,7 +6,6 @@ from .base import DNSProvider, DNSRecord, CAARecord, RecordType - class Route53DNSProvider(DNSProvider): """DNS provider implementation for AWS Route53.""" @@ -42,17 +41,7 @@ def __init__(self): def setup_certbot_credentials(self) -> bool: - """Setup AWS credentials file for certbot. - - This container will be provided with aws credentials purely for the purpose - of assuming a role. Doing so will enable the boto platform to provision - temporary access key and secret keys on demand! - - Using this strategy we can impose least permissive and fast expiring access - to our domain. - - """ - + """Setup AWS credentials file for certbot.""" try: # Pre-fetch hosted zone ID if we have a domain domain = os.getenv("DOMAIN") @@ -77,11 +66,7 @@ def validate_credentials(self) -> bool: return False def _get_hosted_zone_info(self, domain: str) -> Optional[tuple[str, str]]: - """Get the hosted zone ID and name for a domain. - - Returns: - Tuple of (hosted_zone_id, hosted_zone_name) or None - """ + """Get the hosted zone ID and name for a domain.""" try: # List all hosted zones paginator = self.client.get_paginator("list_hosted_zones") @@ -348,10 +333,6 @@ def delete_dns_record(self, record_id: str, domain: str) -> bool: def create_caa_record(self, caa_record: CAARecord) -> bool: """ Create or merge a CAA record set on the apex of the Route53 hosted zone. - - - Ignores the specific subdomain in caa_record.name for placement - - Uses it only to locate the correct hosted zone - - Merges hard-coded issuers with any existing CAA values on the apex """ # Ensure we know which hosted zone this belongs to hosted_zone_id = self._ensure_hosted_zone_id(caa_record.name) diff --git a/custom-domain/dstack-ingress/scripts/entrypoint.sh b/custom-domain/dstack-ingress/scripts/entrypoint.sh index d1994a3..25eb559 100644 --- a/custom-domain/dstack-ingress/scripts/entrypoint.sh +++ b/custom-domain/dstack-ingress/scripts/entrypoint.sh @@ -106,9 +106,6 @@ EOF setup_py_env setup_nginx_conf() { - local cert_name - cert_name=$(cert_dir_name "$DOMAIN") - local client_max_body_size_conf="" if [ -n "$CLIENT_MAX_BODY_SIZE" ]; then client_max_body_size_conf=" client_max_body_size ${CLIENT_MAX_BODY_SIZE};" @@ -151,8 +148,8 @@ server { server_name ${DOMAIN}; # SSL certificate configuration - ssl_certificate /etc/letsencrypt/live/${cert_name}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/${cert_name}/privkey.pem; + ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem; # Modern SSL configuration - TLS 1.2 and 1.3 only ssl_protocols TLSv1.2 TLSv1.3; @@ -169,7 +166,7 @@ server { # Enable OCSP stapling ssl_stapling on; ssl_stapling_verify on; - ssl_trusted_certificate /etc/letsencrypt/live/${cert_name}/fullchain.pem; + ssl_trusted_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; @@ -234,16 +231,8 @@ set_txt_record() { fi APP_ID=${APP_ID:-"$DSTACK_APP_ID"} - local txt_domain - if [[ "$domain" == \*.* ]]; then - # Wildcard domain: *.myapp.com → _dstack-app-address-wildcard.myapp.com - txt_domain="${TXT_PREFIX}-wildcard.${domain#\*.}" - else - txt_domain="${TXT_PREFIX}.${domain}" - fi - dnsman.py set_txt \ - --domain "$txt_domain" \ + --domain "${TXT_PREFIX}.${domain}" \ --content "$APP_ID:$PORT" if [ $? -ne 0 ]; then @@ -268,20 +257,11 @@ set_caa_record() { return fi - local caa_domain caa_tag - if [[ "$domain" == \*.* ]]; then - caa_domain="${domain#\*.}" - caa_tag="issuewild" - else - caa_domain="$domain" - caa_tag="issue" - fi - ACCOUNT_URI=$(jq -j '.uri' "$account_file") - echo "Adding CAA record ($caa_tag) for $caa_domain, accounturi=$ACCOUNT_URI" + echo "Adding CAA record for $domain, accounturi=$ACCOUNT_URI" dnsman.py set_caa \ - --domain "$caa_domain" \ - --caa-tag "$caa_tag" \ + --domain "$domain" \ + --caa-tag "issue" \ --caa-value "letsencrypt.org;validationmethods=dns-01;accounturi=$ACCOUNT_URI" if [ $? -ne 0 ]; then diff --git a/custom-domain/dstack-ingress/scripts/functions.sh b/custom-domain/dstack-ingress/scripts/functions.sh index f699a0a..1a5a75c 100644 --- a/custom-domain/dstack-ingress/scripts/functions.sh +++ b/custom-domain/dstack-ingress/scripts/functions.sh @@ -112,14 +112,6 @@ sanitize_proxy_buffers() { fi } -# Get the certbot certificate directory name for a domain. -# Certbot stores wildcard certs without the "*." prefix: -# *.example.com → /etc/letsencrypt/live/example.com/ -cert_dir_name() { - local domain="$1" - echo "${domain#\*.}" -} - get_letsencrypt_account_path() { local base_path="/etc/letsencrypt/accounts" local api_endpoint="acme-v02.api.letsencrypt.org" diff --git a/custom-domain/dstack-ingress/scripts/generate-evidences.sh b/custom-domain/dstack-ingress/scripts/generate-evidences.sh index afc8bfa..1c5b19b 100644 --- a/custom-domain/dstack-ingress/scripts/generate-evidences.sh +++ b/custom-domain/dstack-ingress/scripts/generate-evidences.sh @@ -23,7 +23,7 @@ fi # Copy all certificate files while IFS= read -r domain; do [[ -n "$domain" ]] || continue - cert_file="/etc/letsencrypt/live/$(cert_dir_name "$domain")/fullchain.pem" + cert_file="/etc/letsencrypt/live/${domain}/fullchain.pem" if [ -f "$cert_file" ]; then cp "$cert_file" "cert-${domain}.pem" else