Simplify Caddy infrastructure to use file-based configuration instead of complex API registration system

This commit is contained in:
2025-11-15 00:11:46 +01:00
parent 7788410bfc
commit 8162e789ee
13 changed files with 706 additions and 216 deletions

View File

@@ -0,0 +1,271 @@
# Caddy Service Configuration
## Overview
The Caddy role uses a simple file-based approach where services deploy configuration files to a `sites-enabled` directory. This follows standard nginx-like patterns and uses Caddy's `import` directive for automatic configuration loading.
## Key Features
- **Simple & Reliable**: File-based configuration with no API dependencies
- **Zero Downtime**: Configuration reloads without service restarts
- **Automatic HTTPS**: New domains get certificates automatically
- **Standard Pattern**: Familiar nginx-like `sites-enabled/` approach
- **Easy Debugging**: Configuration files are plaintext and easily inspected
- **Version Control Friendly**: Configuration changes are tracked in git
## Architecture
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Service Role │ │ sites-enabled/ │ │ Caddy Core │
│ (Ansible) │───▶│ *.caddy files │───▶│ (Routes) │
│ │ │ │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
```
## Usage Pattern
### 1. Service Role Structure
Create a service role with dependency on Caddy:
```yaml
# roles/myservice/meta/main.yml
dependencies:
- role: caddy
```
### 2. Service Configuration Template
Create a Caddy configuration template:
```yaml
# roles/myservice/templates/myservice.caddy.j2
{{ service_domain }} {
reverse_proxy {{ service_backend }}
}
```
### 3. Service Deployment
In your service tasks:
```yaml
# roles/myservice/tasks/main.yml
- name: Deploy my service
# ... service deployment tasks
- name: Deploy Caddy configuration
template:
src: myservice.caddy.j2
dest: "{{ caddy_sites_enabled_dir }}/myservice.caddy"
owner: root
group: "{{ caddy_user }}"
mode: '0644'
notify: reload caddy
```
### 4. Advanced Configuration Example
For load balancing and custom headers:
```caddyfile
# roles/myapi/templates/myapi.caddy.j2
{{ service_domain }} {
reverse_proxy localhost:8080 localhost:8081 {
lb_policy round_robin
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Custom-Header "MyValue"
}
}
```
### 5. Service Removal
To remove a service:
```yaml
- name: Remove service configuration
file:
path: "{{ caddy_sites_enabled_dir }}/myservice.caddy"
state: absent
notify: reload caddy
```
## Available Handlers
### `reload caddy`
- Reloads Caddy configuration without restart
- Automatically triggered when .caddy files change
- Safe to run multiple times
## Configuration Variables
### Template Variables
| Variable | Required | Description | Example |
|----------|----------|-------------|---------|
| `service_domain` | Yes | Domain for the service | `"api.jnss.me"` |
| `service_backend` | Yes | Backend address | `"localhost:8080"` |
### Caddy Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `caddy_sites_enabled_dir` | `"/etc/caddy/sites-enabled"` | Directory for service configs |
| `caddy_user` | `"caddy"` | Caddy system user |
| `caddy_config_file` | `"/etc/caddy/Caddyfile"` | Main Caddyfile path |
## Security
- **File Permissions**: Configuration files owned by root:caddy (0644)
- **Process Isolation**: SystemD security restrictions
- **Network Isolation**: Firewall only opens 80/443
- **Configuration Validation**: Automatic syntax checking
## Examples
### Simple API Service
```caddyfile
# roles/simple-api/templates/simple-api.caddy.j2
simple.jnss.me {
reverse_proxy localhost:3000
}
```
```yaml
# roles/simple-api/tasks/main.yml
- name: Deploy simple API configuration
template:
src: simple-api.caddy.j2
dest: "{{ caddy_sites_enabled_dir }}/simple-api.caddy"
owner: root
group: "{{ caddy_user }}"
mode: '0644'
notify: reload caddy
```
### Load Balanced Service
```caddyfile
# roles/balanced-api/templates/balanced-api.caddy.j2
balanced.jnss.me {
reverse_proxy localhost:8080 localhost:8081 localhost:8082 {
lb_policy least_conn
health_timeout 5s
health_interval 30s
}
}
```
### Static File Service
```caddyfile
# roles/static-files/templates/static-files.caddy.j2
static.jnss.me {
root * /var/www/static
file_server browse
}
```
### API with Custom Headers
```caddyfile
# roles/api-service/templates/api-service.caddy.j2
api.jnss.me {
reverse_proxy localhost:8080 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto https
header_down -Server
}
}
```
## Troubleshooting
### List Service Configurations
```bash
ls -la /etc/caddy/sites-enabled/
```
### Check Configuration Files
```bash
cat /etc/caddy/sites-enabled/myservice.caddy
```
### Validate Caddy Configuration
```bash
caddy validate --config /etc/caddy/Caddyfile
```
### Test Configuration Changes
```bash
caddy fmt --overwrite /etc/caddy/Caddyfile
```
### View Caddy Logs
```bash
journalctl -u caddy -f
```
### Reload Configuration
```bash
systemctl reload caddy
```
### Test Service Directly
```bash
curl http://localhost:8080/health
```
### Test Through Caddy
```bash
curl https://myservice.jnss.me/health
```
## Directory Structure
After deployment, your configuration will look like:
```
/etc/caddy/
├── Caddyfile # Main config with "import sites-enabled/*"
└── sites-enabled/ # Service configurations
├── api.caddy # API service config
├── dashboard.caddy # Dashboard service config
└── static.caddy # Static files config
```
## Best Practices
1. **Naming**: Use descriptive, unique configuration file names
2. **Domains**: Follow consistent domain patterns (e.g., `service.domain.com`)
3. **Templates**: Use Jinja2 variables for flexibility
4. **Health Checks**: Always include health check endpoints in services
5. **Dependencies**: Declare Caddy dependency in service role meta
6. **Testing**: Validate configuration before deployment
7. **Cleanup**: Remove .caddy files when removing services
8. **Version Control**: Track all configuration changes in git
## Service Deployment Checklist
- [ ] Create service role with Caddy dependency
- [ ] Create `.caddy.j2` template file
- [ ] Deploy template to `{{ caddy_sites_enabled_dir }}/`
- [ ] Set proper file permissions (root:caddy 0644)
- [ ] Trigger `reload caddy` handler
- [ ] Test service accessibility
- [ ] Verify HTTPS certificate generation
## Support
For issues or questions about the Caddy service configuration system:
1. Check Caddy logs: `journalctl -u caddy -f`
2. Validate configuration: `caddy validate --config /etc/caddy/Caddyfile`
3. Check file permissions: `ls -la /etc/caddy/sites-enabled/`
4. Test service directly before adding to Caddy
5. Use `ansible-playbook --check` for dry-run testing

100
docs/deployment-guide.md Normal file
View File

@@ -0,0 +1,100 @@
# Deployment Guide
This guide explains how to deploy your infrastructure using the updated Caddy API registration system.
## Overview
The deployment system has been restructured to support:
- **Core Infrastructure**: Caddy web server with API capabilities
- **Service Registration**: Dynamic service registration via API
- **Zero Downtime**: Services can be added/removed without restarts
## Available Playbooks
### 1. `site.yml` - Core Infrastructure
Deploys security hardening followed by Caddy web server infrastructure.
```bash
ansible-playbook -i inventory/hosts.yml site.yml
```
**What it does:**
- **Phase 1 - Security**: System updates, SSH hardening, nftables firewall, fail2ban
- **Phase 2 - Caddy**: Installs Caddy with Cloudflare DNS plugin
- Configures TLS with Let's Encrypt
- Sets up named server for API targeting
- Enables API persistence with `--resume`
- Serves main domain (jnss.me)
## Deployment Patterns
### First-Time Deployment
⚠️ **Important**: First-time deployments include security hardening that may require a system reboot.
1. **Deploy Core Infrastructure**
```bash
# Option 1: Security + Basic infrastructure
ansible-playbook -i inventory/hosts.yml site.yml --ask-vault-pass
# Option 2: Complete deployment with comprehensive verification
ansible-playbook -i inventory/hosts.yml deploy.yml --ask-vault-pass
```
**Note**: The security hardening phase may:
- Update all system packages
- Reboot the system if kernel updates are applied
- Configure SSH, firewall, and fail2ban
- This ensures a secure foundation before deploying web services
## Configuration Management
### Host Variables
Core infrastructure settings in `host_vars/arch-vps/main.yml`:
```yaml
# TLS Configuration
caddy_tls_enabled: true
caddy_domain: "jnss.me"
caddy_tls_email: "{{ vault_caddy_tls_email }}"
# DNS Challenge
caddy_dns_provider: "cloudflare"
cloudflare_api_token: "{{ vault_cloudflare_api_token }}"
# API Configuration
caddy_api_enabled: true
caddy_server_name: "main"
# Logging
caddy_log_level: "INFO"
caddy_log_format: "json"
caddy_systemd_security: true
```
### Vault Variables
Sensitive data in `host_vars/arch-vps/vault.yml` (encrypted):
```yaml
vault_caddy_tls_email: "admin@jnss.me"
vault_cloudflare_api_token: "your-api-token-here"
```
### Security
- Always use vault for sensitive data
- Test deployments on staging first
- Monitor logs after deployment
- Verify HTTPS certificates are working
- Check that API is only accessible locally
### Monitoring
- Monitor Caddy logs: `journalctl -u caddy -f`
- Check API status: `curl http://localhost:2019/config/`
- Verify service health: `curl https://domain.com/health`
- Monitor certificate expiration

View File

@@ -1,5 +1,4 @@
# Setup guide # Setup guide
## Get a VPS with Arch Linux OS ## Get a VPS with Arch Linux OS
- We are using [Hostinger](https://hostinger.com) - We are using [Hostinger](https://hostinger.com)
- Find it's IP in the Hostinger Dashboard - Find it's IP in the Hostinger Dashboard
@@ -12,5 +11,5 @@
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@<VPS_IP> ssh-copy-id -i ~/.ssh/id_ed25519.pub root@<VPS_IP>
``` ```
- Add host to Ansible inventory - Add host to Ansible inventory
- Test connection `ansible -i inventory/hosts.yml arch-vps -m ping` - Test connection `ansible arch-vps -m ping`
- ```ansible-playbook -i inventory/hosts/yml site.yml``` - ```ansible-playbook -i inventory/hosts/yml site.yml```

View File

@@ -18,20 +18,22 @@ cloudflare_api_token: "{{ vault_cloudflare_api_token }}"
caddy_acme_ca: "https://acme-v02.api.letsencrypt.org/directory" caddy_acme_ca: "https://acme-v02.api.letsencrypt.org/directory"
# ================================================================= # =================================================================
# Site Configuration # API Service Registration Configuration
# ================================================================= # =================================================================
# For now, just serve the main jnss.me domain # Services now self-register using Caddy's admin API
# Additional sites can be added here as services are deployed caddy_api_enabled: true
caddy_sites: [] caddy_server_name: "main"
# Future sites will look like: # Static site configuration is deprecated - use API registration instead
# caddy_sites: # Services should use the registration handlers:
# - domain: "cloud.jnss.me" #
# backend: "localhost:8080" # Example service registration pattern:
# dns_challenge: true # - name: Register my service
# - domain: "auth.jnss.me" # set_fact:
# backend: "localhost:9000" # service_name: "myapi"
# dns_challenge: true # service_domain: "api.jnss.me"
# service_backend: "localhost:8080"
# notify: register service with caddy
# ================================================================= # =================================================================
# Security & Logging # Security & Logging

View File

@@ -55,7 +55,7 @@
- name: Reboot system if kernel/module mismatch detected - name: Reboot system if kernel/module mismatch detected
reboot: reboot:
reboot_timeout: 60 reboot_timeout: 120
test_command: uptime test_command: uptime
when: reboot_needed | bool when: reboot_needed | bool
@@ -199,25 +199,6 @@
changed_when: false changed_when: false
when: nft_config_changed.changed when: nft_config_changed.changed
- name: Create firewall rollback safety script
copy:
content: |
#!/bin/bash
# Safety rollback script - automatically disables firewall after 5 minutes
echo "$(date): Starting 5-minute firewall safety timer"
sleep 300
echo "$(date): Safety timer expired, disabling firewall"
nft flush ruleset
systemctl stop nftables
rm -f /tmp/nft-rollback.sh
dest: /tmp/nft-rollback.sh
mode: '0755'
when: nft_config_changed.changed
- name: Start rollback safety timer in background
shell: nohup /tmp/nft-rollback.sh >> /tmp/nft-rollback.log 2>&1 &
when: nft_config_changed.changed
- name: Enable and start nftables service - name: Enable and start nftables service
systemd: systemd:
name: nftables name: nftables
@@ -239,10 +220,6 @@
become: no become: no
when: nft_config_changed.changed when: nft_config_changed.changed
- name: Cancel rollback timer if SSH connection works
shell: pkill -f nft-rollback.sh || true
when: nft_config_changed.changed
- name: Verify nftables rules are loaded - name: Verify nftables rules are loaded
command: nft list ruleset command: nft list ruleset
register: nft_rules register: nft_rules
@@ -330,7 +307,6 @@
- { name: 'net.ipv6.conf.default.disable_ipv6', value: '0' } - { name: 'net.ipv6.conf.default.disable_ipv6', value: '0' }
handlers: handlers:
- name: restart fail2ban - name: restart fail2ban
systemd: systemd:
name: fail2ban name: fail2ban

View File

@@ -1,15 +1,16 @@
# Caddy Web Server Role # Caddy Web Server Role
A comprehensive Ansible role for installing and configuring [Caddy](https://caddyserver.com/) web server with automatic HTTPS, DNS challenges, and production security hardening. A modern Ansible role for installing and configuring [Caddy](https://caddyserver.com/) web server with automatic HTTPS, file-based service configuration, and security hardening.
## Features ## Features
- 🔐 **Automatic HTTPS** with Let's Encrypt certificates - 🔐 **Automatic HTTPS** with Let's Encrypt certificates
- 🌐 **DNS Challenge Support** for wildcard certificates (Cloudflare provider) - 🌐 **DNS Challenge Support** for wildcard certificates (Cloudflare provider)
- 📁 **File-Based Configuration** using sites-enabled directory pattern
- 🛡️ **Security Hardening** with systemd restrictions and capability bounds - 🛡️ **Security Hardening** with systemd restrictions and capability bounds
- 🔧 **Flexible Configuration** for static sites and reverse proxies - **Zero Downtime** configuration reloads without service restarts
- 📝 **Production Ready** with proper logging and monitoring - 🎯 **Simple Architecture** following nginx-like configuration patterns
- 🎯 **Role-Based Architecture** with host-specific overrides - 🔄 **Git-Friendly** configuration files for version control
## Requirements ## Requirements
@@ -19,16 +20,9 @@ A comprehensive Ansible role for installing and configuring [Caddy](https://cadd
## Quick Start ## Quick Start
### Basic Static Site Setup ### Infrastructure Setup (One-time)
```yaml Configure core Caddy infrastructure in your host vars:
# host_vars/myserver/main.yml
caddy_tls_enabled: true
caddy_domain: "example.com"
caddy_tls_email: "admin@example.com"
```
### Production Setup with DNS Challenge
```yaml ```yaml
# host_vars/myserver/main.yml # host_vars/myserver/main.yml
@@ -43,6 +37,40 @@ vault_caddy_tls_email: "admin@example.com"
vault_cloudflare_api_token: "your-api-token-here" vault_cloudflare_api_token: "your-api-token-here"
``` ```
### Service Configuration (Per-Service)
Services deploy configuration files to the sites-enabled directory:
```yaml
# In your service role (e.g., roles/myapi/tasks/main.yml)
- name: Deploy API service
# ... your service deployment tasks ...
- name: Deploy Caddy configuration
template:
src: myapi.caddy.j2
dest: "{{ caddy_sites_enabled_dir }}/myapi.caddy"
owner: root
group: "{{ caddy_user }}"
mode: '0644'
notify: reload caddy
```
```caddyfile
# roles/myapi/templates/myapi.caddy.j2
{{ service_domain }} {
reverse_proxy {{ service_backend }}
}
```
### Service Role Dependencies
```yaml
# roles/myapi/meta/main.yml
dependencies:
- role: caddy
```
## Required Variables ## Required Variables
### For HTTPS/TLS ### For HTTPS/TLS
@@ -60,6 +88,15 @@ vault_cloudflare_api_token: "your-api-token-here"
| `caddy_dns_provider` | DNS challenge | DNS provider | `"cloudflare"` | | `caddy_dns_provider` | DNS challenge | DNS provider | `"cloudflare"` |
| `cloudflare_api_token` | Cloudflare DNS | API token for DNS | `"{{ vault_token }}"` | | `cloudflare_api_token` | Cloudflare DNS | API token for DNS | `"{{ vault_token }}"` |
### For Service Configuration (Per-Service)
| Variable | Required | Description | Example |
|----------|----------|-------------|---------|
| `service_domain` | Yes | Domain for the service | `"api.example.com"` |
| `service_backend` | Yes | Backend address | `"localhost:8080"` |
These are typically set in service role defaults or passed as template variables.
## Optional Variables ## Optional Variables
### Service Configuration ### Service Configuration
@@ -68,6 +105,7 @@ vault_cloudflare_api_token: "your-api-token-here"
|----------|---------|-------------| |----------|---------|-------------|
| `caddy_version` | `"latest"` | Caddy version to install | | `caddy_version` | `"latest"` | Caddy version to install |
| `caddy_config_file` | `"/etc/caddy/Caddyfile"` | Main config file path | | `caddy_config_file` | `"/etc/caddy/Caddyfile"` | Main config file path |
| `caddy_sites_enabled_dir` | `"/etc/caddy/sites-enabled"` | Service config directory |
| `caddy_service_enabled` | `true` | Enable systemd service | | `caddy_service_enabled` | `true` | Enable systemd service |
| `caddy_service_state` | `"started"` | Service state | | `caddy_service_state` | `"started"` | Service state |
| `caddy_admin_listen` | `"127.0.0.1:2019"` | Admin API endpoint | | `caddy_admin_listen` | `"127.0.0.1:2019"` | Admin API endpoint |
@@ -93,7 +131,8 @@ vault_cloudflare_api_token: "your-api-token-here"
| Variable | Default | Description | | Variable | Default | Description |
|----------|---------|-------------| |----------|---------|-------------|
| `caddy_log_level` | `"INFO"` | Logging level (ERROR, WARN, INFO, DEBUG) | | `caddy_log_level` | `"INFO"` | Logging level (ERROR, WARN, INFO, DEBUG) |
| `caddy_log_format` | `"common"` | Log format (common, json) | | `caddy_log_format` | `"json"` | Log format (common, json) |
| `caddy_log_credentials` | `false` | Log credentials in access logs |
### ACME Configuration ### ACME Configuration
@@ -103,31 +142,84 @@ vault_cloudflare_api_token: "your-api-token-here"
| `caddy_dns_resolvers` | `["1.1.1.1:53", "1.0.0.1:53"]` | DNS resolvers | | `caddy_dns_resolvers` | `["1.1.1.1:53", "1.0.0.1:53"]` | DNS resolvers |
| `caddy_dns_propagation_timeout` | `120` | DNS propagation timeout (seconds) | | `caddy_dns_propagation_timeout` | `120` | DNS propagation timeout (seconds) |
## Advanced Configuration ## Service Configuration Patterns
### Multiple Sites ### Simple API Service
```caddyfile
# roles/simple-api/templates/simple-api.caddy.j2
api.example.com {
reverse_proxy localhost:3000
}
```
```yaml ```yaml
caddy_sites: # roles/simple-api/tasks/main.yml
# Static file serving - name: Deploy API configuration
- domain: "static.example.com" template:
root: "/var/www/static" src: simple-api.caddy.j2
dns_challenge: true dest: "{{ caddy_sites_enabled_dir }}/simple-api.caddy"
owner: root
# Reverse proxy to backend service group: "{{ caddy_user }}"
- domain: "api.example.com" mode: '0644'
backend: "localhost:8080" notify: reload caddy
dns_challenge: true
extra_config: |
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
# HTTP-only internal site
- domain: "internal.example.com"
root: "/var/www/internal"
tls: "off"
``` ```
### Load Balanced Service
```caddyfile
# roles/balanced-api/templates/balanced-api.caddy.j2
api.example.com {
reverse_proxy localhost:8080 localhost:8081 localhost:8082 {
lb_policy least_conn
health_timeout 5s
health_interval 30s
}
}
```
### Static File Service
```caddyfile
# roles/static-files/templates/static-files.caddy.j2
static.example.com {
root * /var/www/static
file_server browse
}
```
### Service Cleanup
```yaml
# Remove service when decommissioning
- name: Remove service configuration
file:
path: "{{ caddy_sites_enabled_dir }}/old-service.caddy"
state: absent
notify: reload caddy
```
## Available Handlers
This role provides Ansible handlers for service management:
### `reload caddy`
- Reloads Caddy configuration without restart
- Automatically triggered when .caddy files change
- Picks up new configurations from sites-enabled directory
- Safe to run multiple times
### `restart caddy`
- Fully restarts the Caddy service
- Used for major configuration changes or binary updates
- May cause brief downtime
### `reload systemd`
- Reloads systemd daemon configuration
- Used when service files are modified
## Advanced Configuration
### Custom Systemd Security ### Custom Systemd Security
```yaml ```yaml
@@ -169,15 +261,27 @@ This role implements production-grade security hardening:
roles/caddy/ roles/caddy/
├── defaults/main.yml # Default variables with documentation ├── defaults/main.yml # Default variables with documentation
├── tasks/main.yml # Installation and configuration tasks ├── tasks/main.yml # Installation and configuration tasks
├── handlers/main.yml # Service restart/reload handlers ├── handlers/main.yml # Service management handlers
├── templates/ ├── templates/
│ ├── Caddyfile.j2 # Main Caddyfile template │ ├── Caddyfile.j2 # Main config with import directive
│ ├── caddy.service.j2 # Systemd service for custom builds
│ ├── index.html.j2 # Default welcome page │ ├── index.html.j2 # Default welcome page
│ └── systemd-override.conf.j2 # Security hardening overrides │ └── systemd-override.conf.j2 # Security hardening overrides
├── meta/main.yml # Role metadata and dependencies ├── meta/main.yml # Role metadata and dependencies
└── README.md # This file └── README.md # This file
``` ```
## Directory Structure After Deployment
```
/etc/caddy/
├── Caddyfile # Main config with "import sites-enabled/*"
└── sites-enabled/ # Service configurations
├── api.caddy # API service config
├── dashboard.caddy # Dashboard service config
└── static.caddy # Static files config
```
## Example Playbooks ## Example Playbooks
### Basic Deployment ### Basic Deployment
@@ -190,25 +294,27 @@ roles/caddy/
- caddy - caddy
``` ```
### Full Infrastructure ### Infrastructure with Services
```yaml ```yaml
- name: Secure VPS Setup - name: Deploy Infrastructure and Services
hosts: production hosts: production
become: yes become: yes
roles: roles:
- security # Firewall, SSH hardening, etc. - caddy # Core web server infrastructure
- caddy # Web server with HTTPS - myapi # API service (deploys .caddy config automatically)
- mydashboard # Dashboard service (deploys .caddy config automatically)
``` ```
## Dependencies ## Dependencies
This role automatically handles Caddy installation, including: This role automatically handles Caddy installation and configuration:
- Standard Caddy binary for HTTP-only setups - Standard Caddy binary for HTTP-only setups
- Custom build with Cloudflare DNS plugin when DNS challenge is enabled - Custom build with Cloudflare DNS plugin when DNS challenge is enabled
- System user and directory creation - System user and directory creation
- Systemd service configuration - Systemd service configuration with security hardening
- Sites-enabled directory structure for service configurations
## Compatibility ## Compatibility
@@ -216,21 +322,40 @@ This role automatically handles Caddy installation, including:
- **Should work**: CentOS/RHEL 8+, Ubuntu 18.04+, Debian 10+ - **Should work**: CentOS/RHEL 8+, Ubuntu 18.04+, Debian 10+
- **Requires**: systemd, curl/wget - **Requires**: systemd, curl/wget
## Contributing ## Troubleshooting
When modifying this role: ### List Service Configurations
```bash
ls -la /etc/caddy/sites-enabled/
```
1. Update defaults in `defaults/main.yml` with clear documentation ### Check Configuration Files
2. Use host-specific overrides in `host_vars/` for sensitive values ```bash
3. Leverage Ansible Vault for secrets (`vault_*` variables) cat /etc/caddy/sites-enabled/myservice.caddy
4. Test changes against the security hardening requirements ```
## Security Considerations ### Validate Configuration
```bash
caddy validate --config /etc/caddy/Caddyfile
```
- **Secrets**: Always use Ansible Vault for API tokens and sensitive data ### View Caddy Logs
- **Firewall**: Role opens ports 80 and 443; ensure firewall is configured ```bash
- **DNS**: DNS challenge requires API access to your DNS provider journalctl -u caddy -f
- **Monitoring**: Monitor certificate expiration and renewal ```
### Reload Configuration
```bash
systemctl reload caddy
```
## Examples
See other roles associated with the rick-infra project
## Additional Documentation
- [Caddy Service Configuration Guide](../../docs/caddy-service-configuration.md) - Comprehensive usage guide
- [Caddy Official Documentation](https://caddyserver.com/docs/) - Official Caddy documentation
## License ## License

View File

@@ -10,7 +10,6 @@
# ================================================================= # =================================================================
caddy_version: "latest" caddy_version: "latest"
caddy_user: "caddy" caddy_user: "caddy"
caddy_group: "caddy"
caddy_home: "/var/lib/caddy" caddy_home: "/var/lib/caddy"
caddy_config_dir: "/etc/caddy" caddy_config_dir: "/etc/caddy"
caddy_data_dir: "/var/lib/caddy" caddy_data_dir: "/var/lib/caddy"
@@ -22,6 +21,7 @@ caddy_default_site_root: "{{ caddy_web_root }}/default"
# Service Configuration # Service Configuration
# ================================================================= # =================================================================
caddy_config_file: "/etc/caddy/Caddyfile" # Package default path caddy_config_file: "/etc/caddy/Caddyfile" # Package default path
caddy_sites_enabled_dir: "/etc/caddy/sites-enabled" # Directory for service configurations
caddy_service_enabled: true caddy_service_enabled: true
caddy_service_state: "started" caddy_service_state: "started"
caddy_auto_https: true caddy_auto_https: true
@@ -53,30 +53,10 @@ caddy_dns_resolvers: # DNS resolvers for challenge verifi
caddy_dns_propagation_timeout: 120 # Seconds to wait for DNS propagation caddy_dns_propagation_timeout: 120 # Seconds to wait for DNS propagation
# ================================================================= # =================================================================
# Sites Configuration # Service Configuration
# ================================================================= # =================================================================
# Define additional sites/domains to serve # File-based service configuration using import directive
caddy_sites: [] # Services deploy .caddy files to sites-enabled directory
# Example configurations:
# caddy_sites:
# # Static file serving
# - domain: "static.example.com"
# root: "/var/www/static"
# dns_challenge: true # Use DNS challenge for this domain
#
# # Reverse proxy to backend service
# - domain: "api.example.com"
# backend: "localhost:8080"
# dns_challenge: true
# extra_config: |
# header_up Host {upstream_hostport}
# header_up X-Real-IP {remote_host}
#
# # Simple HTTP-only site
# - domain: "internal.example.com"
# root: "/var/www/internal"
# tls: "off"
# ================================================================= # =================================================================
# Security & Network Configuration # Security & Network Configuration
@@ -92,4 +72,5 @@ caddy_firewall_ports:
# Systemd service customization # Systemd service customization
caddy_systemd_security: true # Enable systemd security restrictions caddy_systemd_security: true # Enable systemd security restrictions
caddy_log_level: "INFO" # Logging level (ERROR, WARN, INFO, DEBUG) caddy_log_level: "INFO" # Logging level (ERROR, WARN, INFO, DEBUG)
caddy_log_format: "common" # Log format (common, json) caddy_log_format: "json" # Log format (common, json)
caddy_log_credentials: false # Log credentials in access logs (security risk)

View File

@@ -1,4 +1,5 @@
--- ---
# Simple service management handlers
- name: reload systemd - name: reload systemd
systemd: systemd:
daemon_reload: yes daemon_reload: yes

View File

@@ -35,7 +35,6 @@
- name: Create caddy user and group - name: Create caddy user and group
user: user:
name: "{{ caddy_user }}" name: "{{ caddy_user }}"
group: "{{ caddy_group }}"
home: "{{ caddy_home }}" home: "{{ caddy_home }}"
shell: /usr/bin/nologin shell: /usr/bin/nologin
system: yes system: yes
@@ -47,10 +46,11 @@
path: "{{ item }}" path: "{{ item }}"
state: directory state: directory
owner: "{{ caddy_user }}" owner: "{{ caddy_user }}"
group: "{{ caddy_group }}" group: "{{ caddy_user }}"
mode: '0755' mode: '0755'
loop: loop:
- "{{ caddy_config_dir }}" - "{{ caddy_config_dir }}"
- "{{ caddy_sites_enabled_dir }}"
- "{{ caddy_data_dir }}" - "{{ caddy_data_dir }}"
- "{{ caddy_log_dir }}" - "{{ caddy_log_dir }}"
- "{{ caddy_web_root }}" - "{{ caddy_web_root }}"
@@ -61,22 +61,34 @@
src: index.html.j2 src: index.html.j2
dest: "{{ caddy_default_site_root }}/index.html" dest: "{{ caddy_default_site_root }}/index.html"
owner: "{{ caddy_user }}" owner: "{{ caddy_user }}"
group: "{{ caddy_group }}" group: "{{ caddy_user }}"
mode: '0644' mode: '0644'
- name: Create systemd override directory - name: Create systemd service file for custom Caddy installation
template:
src: caddy.service.j2
dest: /usr/lib/systemd/system/caddy.service
mode: '0644'
when: dns_challenge_needed | bool
notify:
- reload systemd
- restart caddy
- name: Create systemd override directory (for standard installation)
file: file:
path: /etc/systemd/system/caddy.service.d path: /etc/systemd/system/caddy.service.d
state: directory state: directory
mode: '0755' mode: '0755'
when: not dns_challenge_needed | bool
- name: Configure Caddy systemd override - name: Configure Caddy systemd override (for standard installation)
template: template:
src: systemd-override.conf.j2 src: systemd-override.conf.j2
dest: /etc/systemd/system/caddy.service.d/override.conf dest: /etc/systemd/system/caddy.service.d/override.conf
mode: '0644' mode: '0644'
when: not dns_challenge_needed | bool
notify: notify:
- reload systemd - reload systemd
- restart caddy - restart caddy
@@ -86,7 +98,7 @@
src: Caddyfile.j2 src: Caddyfile.j2
dest: "{{ caddy_config_file }}" dest: "{{ caddy_config_file }}"
owner: root owner: root
group: "{{ caddy_group }}" group: "{{ caddy_user }}"
mode: '0640' mode: '0640'
backup: yes backup: yes
notify: reload caddy notify: reload caddy

View File

@@ -3,24 +3,30 @@
# Global configuration # Global configuration
{ {
admin {{ caddy_admin_listen }}
{% if caddy_tls_enabled and caddy_tls_email %} {% if caddy_tls_enabled and caddy_tls_email %}
# ACME configuration for Let's Encrypt # ACME configuration for Let's Encrypt
email {{ caddy_tls_email }} email {{ caddy_tls_email }}
acme_ca {{ caddy_acme_ca }} acme_ca {{ caddy_acme_ca }}
{% endif %} {% endif %}
{% if caddy_dns_provider == "cloudflare" and cloudflare_api_token %}
# DNS challenge for wildcard certificates
acme_dns cloudflare {{ cloudflare_api_token }}
{% endif %}
{% if not caddy_auto_https %} {% if not caddy_auto_https %}
auto_https off auto_https off
{% endif %} {% endif %}
} }
# Import service configurations
import {{ caddy_sites_enabled_dir }}/*
# Primary domain: {{ caddy_domain }} # Primary domain: {{ caddy_domain }}
{{ caddy_domain }} { {{ caddy_domain }} {
{% if caddy_tls_enabled %} {% if caddy_tls_enabled %}
{% if caddy_dns_provider == "cloudflare" and cloudflare_api_token %} {% if caddy_dns_provider == "cloudflare" and cloudflare_api_token %}
# DNS challenge for automatic TLS (secure: no environment files) # DNS challenge for automatic TLS
tls { tls {
dns cloudflare {{ cloudflare_api_token }} dns cloudflare {{ cloudflare_api_token }}
resolvers {{ caddy_dns_resolvers | join(' ') }} resolvers {{ caddy_dns_resolvers | join(' ') }}
@@ -29,8 +35,6 @@
# HTTP challenge for automatic TLS # HTTP challenge for automatic TLS
tls {{ caddy_tls_email }} tls {{ caddy_tls_email }}
{% endif %} {% endif %}
{% else %}
# TLS disabled
{% endif %} {% endif %}
# Serve static content # Serve static content
@@ -57,83 +61,3 @@
{% endif %} {% endif %}
} }
} }
# Additional configured sites
{% for site in caddy_sites %}
{{ site.domain }}{% if site.port is defined %}:{{ site.port }}{% endif %} {
{% if caddy_tls_enabled and site.tls != "off" %}
{% if site.dns_challenge | default(false) and caddy_dns_provider == "cloudflare" and cloudflare_api_token %}
# DNS challenge for this site (secure: direct variable substitution)
tls {
dns cloudflare {{ cloudflare_api_token }}
resolvers {{ caddy_dns_resolvers | join(' ') }}
}
{% elif caddy_tls_email and site.tls != "off" %}
# HTTP challenge for this site
tls {{ caddy_tls_email }}
{% endif %}
{% elif site.tls == "off" %}
# TLS explicitly disabled for this site
tls off
{% endif %}
{% if site.root is defined %}
# Static file serving
root * {{ site.root }}
file_server
{% endif %}
{% if site.backend is defined %}
# Reverse proxy
reverse_proxy {{ site.backend }} {
# Standard proxy headers
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
}
{% endif %}
# Logging for this site
log {
{% if caddy_log_format == "json" %}
output file {{ caddy_log_dir }}/{{ site.domain | replace('.', '_') }}.log {
roll_size 100mb
roll_keep 5
}
format json {
time_format "2006-01-02T15:04:05.000Z07:00"
}
level {{ caddy_log_level }}
{% else %}
output file {{ caddy_log_dir }}/{{ site.domain | replace('.', '_') }}.log {
roll_size 100mb
roll_keep 5
}
level {{ caddy_log_level }}
{% endif %}
}
{% if site.extra_config is defined %}
# Additional site configuration
{{ site.extra_config | indent(4) }}
{% endif %}
}
{% endfor %}
{% if caddy_tls_enabled %}
# HTTP to HTTPS redirects
http://{{ caddy_domain }} {
redir https://{host}{uri} permanent
}
{% for site in caddy_sites %}
{% if site.tls != "off" %}
http://{{ site.domain }} {
redir https://{host}{uri} permanent
}
{% endif %}
{% endfor %}
{% endif %}

View File

@@ -0,0 +1,69 @@
# Custom Caddy systemd service file
# Combines official Caddy service with enhanced security and API persistence
# Generated by Ansible - DO NOT EDIT MANUALLY
[Unit]
Description=Caddy web server
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target
[Service]
Type=notify
User={{ caddy_user }}
Group={{ caddy_user }}
ExecStart=/usr/bin/caddy run --environ --config {{ caddy_config_file }}{% if caddy_api_enabled %} --resume{% endif %}
ExecReload=/usr/bin/caddy reload --config {{ caddy_config_file }} --force
{% if caddy_api_enabled %}
# Wait for API to be ready before considering service started
ExecStartPost=/bin/bash -c 'until curl -s http://{{ caddy_admin_listen }}/config/ >/dev/null 2>&1; do sleep 1; done'
{% endif %}
# Standard settings from official service
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
{% if caddy_systemd_security | default(true) %}
# Enhanced security hardening
NoNewPrivileges=true
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
RemoveIPC=true
# Filesystem restrictions (upgrade from ProtectSystem=full)
ProtectSystem=strict
ProtectHome=true
ReadWritePaths={{ caddy_data_dir }} {{ caddy_log_dir }}
BindReadOnlyPaths={{ caddy_config_dir }}
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectClock=true
# Network and namespace restrictions
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
# Process restrictions
LimitNPROC=1048576
MemoryDenyWriteExecute=true
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
# Logging (explicit configuration)
StandardOutput=journal
StandardError=journal
SyslogIdentifier=caddy
{% else %}
# Standard security from official service
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
{% endif %}
[Install]
WantedBy=multi-user.target

View File

@@ -1,8 +1,13 @@
[Service] [Service]
# Reload configuration with --force flag for reliability ExecStart=
ExecStart=/usr/bin/caddy run --config {{ caddy_config_file }} --resume
ExecReload= ExecReload=
ExecReload=/usr/bin/caddy reload --config {{ caddy_config_file }} --force ExecReload=/usr/bin/caddy reload --config {{ caddy_config_file }} --force
# Wait for API to be ready before considering service started
ExecStartPost=/bin/bash -c 'until curl -s http://{{ caddy_admin_listen }}/config/ >/dev/null 2>&1; do sleep 1; done'
{% if caddy_systemd_security | default(true) %} {% if caddy_systemd_security | default(true) %}
# Enhanced security hardening beyond base service # Enhanced security hardening beyond base service
NoNewPrivileges=true NoNewPrivileges=true

View File

@@ -1,13 +1,38 @@
--- ---
- name: Secure VPS Infrastructure Setup # Core infrastructure deployment with security hardening first
# Security hardening establishes secure foundation before web services
- import_playbook: playbooks/security.yml
- name: Deploy Core Infrastructure
hosts: arch-vps hosts: arch-vps
become: yes become: yes
gather_facts: yes gather_facts: yes
roles: roles:
- role: caddy - role: caddy
tags: ['caddy', 'web', 'https'] tags: ['caddy', 'infrastructure', 'web']
# Optional: Include security playbook post_tasks:
# - import_playbook: playbooks/security.yml - name: Verify Caddy API is accessible
# tags: ['security', 'firewall', 'ssh'] uri:
url: "http://{{ caddy_admin_listen }}/config/"
method: GET
status_code: 200
retries: 5
delay: 2
- name: Display infrastructure status
debug:
msg: |
✅ Core infrastructure deployment completed!
🌐 Primary domain: {{ caddy_domain }}
🔒 HTTPS: {{ 'Enabled with DNS challenge (' + caddy_dns_provider + ')' if caddy_dns_provider else 'Enabled with HTTP challenge' }}
🚀 API registration: {{ 'Ready' if caddy_api_enabled else 'Disabled' }}
📍 Admin API: http://{{ caddy_admin_listen }} (localhost only)
📁 Web root: {{ caddy_web_root }}
📝 Logs: {{ caddy_log_dir }}
📖 Documentation: docs/caddy-api-registration.md