Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
65 changes: 64 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,40 @@ 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_ACCESS_KEY_ID=service-account-key-that-can-assume-role
AWS_SECRET_ACCESS_KEY=service-account-secret-that-can-assume-role
AWS_ROLE_ARN=role-that-can-mod-route53
AWS_REGION=your-closest-region
```

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

## Docker Compose Examples

### Linode Example
Expand Down Expand Up @@ -127,6 +162,34 @@ 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
environment:
DNS_PROVIDER: route53
DOMAIN: app.example.com
GATEWAY_DOMAIN: _.${DSTACK_GATEWAY_DOMAIN}

AWS_REGION: ${AWS_REGION}
AWS_ROLE_ARN: ${AWS_ROLE_ARN}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}

CERTBOT_EMAIL: ${CERTBOT_EMAIL}
TARGET_ENDPOINT: http://backend:8080
SET_CAA: 'true'

```

## Migration from Cloudflare-only Setup

If you're currently using the Cloudflare-only version:
Expand Down Expand Up @@ -166,4 +229,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
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