Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions custom-domain/dstack-ingress/CLOUDFORMATION_EXAMPLE.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
HostedZoneId:
Type: String
Default:

Check failure on line 6 in custom-domain/dstack-ingress/CLOUDFORMATION_EXAMPLE.yaml

View workflow job for this annotation

GitHub Actions / Basic Checks (dev.sh)

6:13 [trailing-spaces] trailing spaces

Check failure on line 6 in custom-domain/dstack-ingress/CLOUDFORMATION_EXAMPLE.yaml

View workflow job for this annotation

GitHub Actions / Basic Checks (dev.sh)

6:13 [empty-values] empty value in block mapping

Check failure on line 6 in custom-domain/dstack-ingress/CLOUDFORMATION_EXAMPLE.yaml

View workflow job for this annotation

GitHub Actions / check-all

6:13 [trailing-spaces] trailing spaces

Check failure on line 6 in custom-domain/dstack-ingress/CLOUDFORMATION_EXAMPLE.yaml

View workflow job for this annotation

GitHub Actions / check-all

6:13 [empty-values] empty value in block mapping
Description: Route53 Hosted Zone ID
UserName:
Type: String
Description: IAM user that can only assume the Route53 role

Resources:
User:
Type: AWS::IAM::User
Properties:
UserName: !Ref UserName

AccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref User
Status: Active

Route53Role:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${UserName}-route53-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
# The *account root* as trusted principal.
# This avoids invalid-principal errors while remaining safe,
# because the USER policy enforces the actual restriction.
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
Action: sts:AssumeRole
Policies:
- PolicyName: Route53DnsChallenges
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowDnsChallengeChanges
Effect: Allow
Action:
- route53:ChangeResourceRecordSets
Resource: !Sub arn:aws:route53:::hostedzone/${HostedZoneId}
- Sid: AllowListingForDnsChallenge
Effect: Allow
Action:
- route53:ListHostedZonesByName
- route53:ListHostedZones
- route53:GetChange
- route53:ListResourceRecordSets
Resource: "*"

UserAssumeRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub '${UserName}-assume-route53-role'
Users:
- !Ref User
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/${UserName}-route53-role

Outputs:
AWSAccessKeyId:
Description: Access key ID for the IAM user
Value: !Ref AccessKey

AWSSecretAccessKey:
Description: Secret access key for the IAM user
Value: !GetAtt AccessKey.SecretAccessKey

AWSUserArn:
Description: IAM User ARN
Value: !Sub arn:aws:iam::${AWS::AccountId}:user/${UserName}

Route53RoleArn:
Description: ARN of the Route53 role used by Certbot
Value: !Sub arn:aws:iam::${AWS::AccountId}:role/${UserName}-route53-role
79 changes: 78 additions & 1 deletion custom-domain/dstack-ingress/DNS_PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This guide explains how to configure dstack-ingress to work with different DNS p
- **Cloudflare** - The original and default provider
- **Linode DNS** - For Linode-hosted domains
- **Namecheap** - For Namecheap-hosted domains
- **Route53** - For AWS hosted domains

## Environment Variables

Expand Down Expand Up @@ -73,6 +74,37 @@ NAMECHEAP_CLIENT_IP=your-client-ip
- Namecheap doesn't support CAA records through their API currently
- The certbot plugin uses the format `certbot-dns-namecheap` package

### Route53

```bash
DNS_PROVIDER=route53
AWS_PROFILE=your-injected-aws-profile
```

**Required Permissions:**
```yaml
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowDnsChallengeChanges
Effect: Allow
Action:
- route53:ChangeResourceRecordSets
Resource: !Sub arn:aws:route53:::hostedzone/${HostedZoneId}
- Sid: AllowListingForDnsChallenge
Effect: Allow
Action:
- route53:ListHostedZonesByName
- route53:ListHostedZones
- route53:GetChange
- route53:ListResourceRecordSets
```

**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
- It is recommended that the AWS service account can only assume the limited role. See cloudformation example.

## Docker Compose Examples

### Linode Example
Expand Down Expand Up @@ -127,6 +159,51 @@ services:
- ./evidences:/evidences
```

### Route53 Example

```yaml
services:
dstack-ingress:
image: dstack-ingress:latest
restart: unless-stopped
volumes:
- /var/run/dstack.sock:/var/run/dstack.sock
- cert-data:/etc/letsencrypt
ports:
- 443:443
configs:
- source: aws_config
target: /root/.aws/config
mode: 0600
- source: aws_credentials
target: /root/.aws/credentials
mode: 0600
environment:
DNS_PROVIDER: route53
DOMAIN: app.example.com
GATEWAY_DOMAIN: _.${DSTACK_GATEWAY_DOMAIN}

CERTBOT_EMAIL: ${CERTBOT_EMAIL}
TARGET_ENDPOINT: http://backend:8080

AWS_PROFILE: certbot
SET_CAA: 'true'

configs:
aws_config:
content: |
[profile certbot]
role_arn=${ROUTE53_AWS_ROLE_ARN}
source_profile=certbot-source
region=${AWS_REGION}

aws_credentials:
content: |
[certbot-source]
aws_access_key_id=${ROUTE53_AWS_ACCESS_KEY_ID}
aws_secret_access_key=${ROUTE53_AWS_SECRET_ACCESS_KEY}
```

## Migration from Cloudflare-only Setup

If you're currently using the Cloudflare-only version:
Expand Down Expand Up @@ -166,4 +243,4 @@ Ensure your API tokens/credentials have the necessary permissions listed above f
1. Go to https://ap.www.namecheap.com/settings/tools/api-access/
2. Enable API access for your account
3. Note down your API key and username
4. Make sure your IP address is whitelisted in the API settings
4. Make sure your IP address is whitelisted in the API settings
2 changes: 1 addition & 1 deletion custom-domain/dstack-ingress/scripts/certman.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ def _build_certbot_command(self, action: str, domain: str, email: str) -> List[s
if os.environ.get("CERTBOT_STAGING", "false") == "true":
base_cmd.extend(["--staging"])

if getattr(self.provider, 'CERTBOT_PROPAGATION_SECONDS'):
if getattr(self.provider, 'CERTBOT_PROPAGATION_SECONDS') and self.provider.CERTBOT_PROPAGATION_SECONDS is not None:
propagation_seconds = self.provider.CERTBOT_PROPAGATION_SECONDS
propagation_param = f"--dns-{self.provider_type}-propagation-seconds={propagation_seconds}"
base_cmd.extend([propagation_param])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .cloudflare import CloudflareDNSProvider
from .linode import LinodeDNSProvider
from .namecheap import NamecheapDNSProvider
from .route53 import Route53DNSProvider


class DNSProviderFactory:
Expand All @@ -15,6 +16,7 @@ class DNSProviderFactory:
"cloudflare": CloudflareDNSProvider,
"linode": LinodeDNSProvider,
"namecheap": NamecheapDNSProvider,
"route53": Route53DNSProvider,
}

@classmethod
Expand Down Expand Up @@ -67,4 +69,4 @@ def _detect_provider_type(cls) -> str:
@classmethod
def get_supported_providers(cls) -> list:
"""Get list of supported DNS providers."""
return list(cls.PROVIDERS.keys())
return list(cls.PROVIDERS.keys())
Loading
Loading