Complete production-ready Caddy infrastructure with security hardening
- Add comprehensive Caddy role with HTTPS/TLS, DNS challenges, and systemd security - Implement optimized systemd overrides with enhanced security restrictions - Create detailed documentation with usage examples and variable references - Establish proper Ansible configuration with vault integration - Update site.yml for infrastructure orchestration with role-based deployment - Add host-specific configuration structure for scalable multi-environment setup
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
vault-password-file
|
||||||
|
vault.yml
|
||||||
9
ansible.cfg
Normal file
9
ansible.cfg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[defaults]
|
||||||
|
inventory = inventory/hosts.yml
|
||||||
|
host_key_checking = False
|
||||||
|
remote_user = root
|
||||||
|
deprecation_warnings = False
|
||||||
|
vault_password_file = vault-password-file
|
||||||
|
|
||||||
|
[ssh_connection]
|
||||||
|
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
||||||
41
host_vars/arch-vps/main.yml
Normal file
41
host_vars/arch-vps/main.yml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
# =================================================================
|
||||||
|
# Production Configuration for arch-vps (jnss.me)
|
||||||
|
# =================================================================
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# TLS Configuration - Production Setup
|
||||||
|
# =================================================================
|
||||||
|
caddy_tls_enabled: true
|
||||||
|
caddy_domain: "jnss.me"
|
||||||
|
caddy_tls_email: "{{ vault_caddy_tls_email }}"
|
||||||
|
|
||||||
|
# DNS Challenge Configuration (Cloudflare)
|
||||||
|
caddy_dns_provider: "cloudflare"
|
||||||
|
cloudflare_api_token: "{{ vault_cloudflare_api_token }}"
|
||||||
|
|
||||||
|
# Production Let's Encrypt CA
|
||||||
|
caddy_acme_ca: "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Site Configuration
|
||||||
|
# =================================================================
|
||||||
|
# For now, just serve the main jnss.me domain
|
||||||
|
# Additional sites can be added here as services are deployed
|
||||||
|
caddy_sites: []
|
||||||
|
|
||||||
|
# Future sites will look like:
|
||||||
|
# caddy_sites:
|
||||||
|
# - domain: "cloud.jnss.me"
|
||||||
|
# backend: "localhost:8080"
|
||||||
|
# dns_challenge: true
|
||||||
|
# - domain: "auth.jnss.me"
|
||||||
|
# backend: "localhost:9000"
|
||||||
|
# dns_challenge: true
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Security & Logging
|
||||||
|
# =================================================================
|
||||||
|
caddy_log_level: "INFO"
|
||||||
|
caddy_log_format: "json"
|
||||||
|
caddy_systemd_security: true
|
||||||
@@ -1,2 +1,237 @@
|
|||||||
# Install and configure caddy
|
# Caddy Web Server Role
|
||||||
Installs and configures caddy.
|
|
||||||
|
A comprehensive Ansible role for installing and configuring [Caddy](https://caddyserver.com/) web server with automatic HTTPS, DNS challenges, and production security hardening.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🔐 **Automatic HTTPS** with Let's Encrypt certificates
|
||||||
|
- 🌐 **DNS Challenge Support** for wildcard certificates (Cloudflare provider)
|
||||||
|
- 🛡️ **Security Hardening** with systemd restrictions and capability bounds
|
||||||
|
- 🔧 **Flexible Configuration** for static sites and reverse proxies
|
||||||
|
- 📝 **Production Ready** with proper logging and monitoring
|
||||||
|
- 🎯 **Role-Based Architecture** with host-specific overrides
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Systemd-based Linux distribution (tested on Arch Linux)
|
||||||
|
- Ansible 2.9+
|
||||||
|
- For DNS challenges: Cloudflare account with API token
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Basic Static Site Setup
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 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
|
||||||
|
# host_vars/myserver/main.yml
|
||||||
|
caddy_tls_enabled: true
|
||||||
|
caddy_domain: "example.com"
|
||||||
|
caddy_tls_email: "{{ vault_caddy_tls_email }}"
|
||||||
|
caddy_dns_provider: "cloudflare"
|
||||||
|
cloudflare_api_token: "{{ vault_cloudflare_api_token }}"
|
||||||
|
|
||||||
|
# host_vars/myserver/vault.yml (encrypted)
|
||||||
|
vault_caddy_tls_email: "admin@example.com"
|
||||||
|
vault_cloudflare_api_token: "your-api-token-here"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Required Variables
|
||||||
|
|
||||||
|
### For HTTPS/TLS
|
||||||
|
|
||||||
|
| Variable | Required When | Description | Example |
|
||||||
|
|----------|---------------|-------------|---------|
|
||||||
|
| `caddy_tls_enabled` | Always for HTTPS | Enable TLS/HTTPS | `true` |
|
||||||
|
| `caddy_tls_email` | HTTPS enabled | Email for Let's Encrypt | `"admin@example.com"` |
|
||||||
|
| `caddy_domain` | HTTPS enabled | Primary domain | `"example.com"` |
|
||||||
|
|
||||||
|
### For DNS Challenge (Wildcard Certificates)
|
||||||
|
|
||||||
|
| Variable | Required When | Description | Example |
|
||||||
|
|----------|---------------|-------------|---------|
|
||||||
|
| `caddy_dns_provider` | DNS challenge | DNS provider | `"cloudflare"` |
|
||||||
|
| `cloudflare_api_token` | Cloudflare DNS | API token for DNS | `"{{ vault_token }}"` |
|
||||||
|
|
||||||
|
## Optional Variables
|
||||||
|
|
||||||
|
### Service Configuration
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `caddy_version` | `"latest"` | Caddy version to install |
|
||||||
|
| `caddy_config_file` | `"/etc/caddy/Caddyfile"` | Main config file path |
|
||||||
|
| `caddy_service_enabled` | `true` | Enable systemd service |
|
||||||
|
| `caddy_service_state` | `"started"` | Service state |
|
||||||
|
| `caddy_admin_listen` | `"127.0.0.1:2019"` | Admin API endpoint |
|
||||||
|
|
||||||
|
### Directory Configuration
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `caddy_config_dir` | `"/etc/caddy"` | Configuration directory |
|
||||||
|
| `caddy_data_dir` | `"/var/lib/caddy"` | Data/state directory |
|
||||||
|
| `caddy_log_dir` | `"/var/log/caddy"` | Log directory |
|
||||||
|
| `caddy_web_root` | `"/var/www"` | Web root directory |
|
||||||
|
|
||||||
|
### Security Configuration
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `caddy_systemd_security` | `true` | Enable systemd security restrictions |
|
||||||
|
| `caddy_firewall_ports` | `[80, 443]` | Ports to open in firewall |
|
||||||
|
|
||||||
|
### Logging Configuration
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `caddy_log_level` | `"INFO"` | Logging level (ERROR, WARN, INFO, DEBUG) |
|
||||||
|
| `caddy_log_format` | `"common"` | Log format (common, json) |
|
||||||
|
|
||||||
|
### ACME Configuration
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `caddy_acme_ca` | Let's Encrypt Prod | ACME CA directory URL |
|
||||||
|
| `caddy_dns_resolvers` | `["1.1.1.1:53", "1.0.0.1:53"]` | DNS resolvers |
|
||||||
|
| `caddy_dns_propagation_timeout` | `120` | DNS propagation timeout (seconds) |
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
|
||||||
|
### Multiple Sites
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
caddy_sites:
|
||||||
|
# Static file serving
|
||||||
|
- domain: "static.example.com"
|
||||||
|
root: "/var/www/static"
|
||||||
|
dns_challenge: true
|
||||||
|
|
||||||
|
# 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}
|
||||||
|
|
||||||
|
# HTTP-only internal site
|
||||||
|
- domain: "internal.example.com"
|
||||||
|
root: "/var/www/internal"
|
||||||
|
tls: "off"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Systemd Security
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
caddy_systemd_security: false # Disable if you need custom security settings
|
||||||
|
```
|
||||||
|
|
||||||
|
### Staging Environment
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Use Let's Encrypt staging for testing
|
||||||
|
caddy_acme_ca: "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Features
|
||||||
|
|
||||||
|
This role implements production-grade security hardening:
|
||||||
|
|
||||||
|
### Systemd Security Restrictions
|
||||||
|
|
||||||
|
- **NoNewPrivileges**: Prevents privilege escalation
|
||||||
|
- **CapabilityBoundingSet**: Limits to `CAP_NET_ADMIN` and `CAP_NET_BIND_SERVICE`
|
||||||
|
- **ProtectSystem=strict**: Read-only filesystem protection
|
||||||
|
- **ProtectKernelLogs/Modules/Tunables**: Kernel protection
|
||||||
|
- **RestrictAddressFamilies**: Limits to IPv4, IPv6, and Unix sockets
|
||||||
|
- **RestrictNamespaces/Realtime/SUIDSGID**: Additional restrictions
|
||||||
|
- **MemoryDenyWriteExecute**: Prevents code injection
|
||||||
|
- **SystemCallFilter=@system-service**: Whitelist system calls
|
||||||
|
|
||||||
|
### File System Security
|
||||||
|
|
||||||
|
- Dedicated `caddy` user and group
|
||||||
|
- Proper directory permissions
|
||||||
|
- Read-only configuration binding
|
||||||
|
- Isolated temporary files
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
roles/caddy/
|
||||||
|
├── defaults/main.yml # Default variables with documentation
|
||||||
|
├── tasks/main.yml # Installation and configuration tasks
|
||||||
|
├── handlers/main.yml # Service restart/reload handlers
|
||||||
|
├── templates/
|
||||||
|
│ ├── Caddyfile.j2 # Main Caddyfile template
|
||||||
|
│ ├── index.html.j2 # Default welcome page
|
||||||
|
│ └── systemd-override.conf.j2 # Security hardening overrides
|
||||||
|
├── meta/main.yml # Role metadata and dependencies
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Playbooks
|
||||||
|
|
||||||
|
### Basic Deployment
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Deploy Caddy Web Server
|
||||||
|
hosts: webservers
|
||||||
|
become: yes
|
||||||
|
roles:
|
||||||
|
- caddy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full Infrastructure
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Secure VPS Setup
|
||||||
|
hosts: production
|
||||||
|
become: yes
|
||||||
|
roles:
|
||||||
|
- security # Firewall, SSH hardening, etc.
|
||||||
|
- caddy # Web server with HTTPS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
This role automatically handles Caddy installation, including:
|
||||||
|
|
||||||
|
- Standard Caddy binary for HTTP-only setups
|
||||||
|
- Custom build with Cloudflare DNS plugin when DNS challenge is enabled
|
||||||
|
- System user and directory creation
|
||||||
|
- Systemd service configuration
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
- **Tested**: Arch Linux
|
||||||
|
- **Should work**: CentOS/RHEL 8+, Ubuntu 18.04+, Debian 10+
|
||||||
|
- **Requires**: systemd, curl/wget
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
When modifying this role:
|
||||||
|
|
||||||
|
1. Update defaults in `defaults/main.yml` with clear documentation
|
||||||
|
2. Use host-specific overrides in `host_vars/` for sensitive values
|
||||||
|
3. Leverage Ansible Vault for secrets (`vault_*` variables)
|
||||||
|
4. Test changes against the security hardening requirements
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- **Secrets**: Always use Ansible Vault for API tokens and sensitive data
|
||||||
|
- **Firewall**: Role opens ports 80 and 443; ensure firewall is configured
|
||||||
|
- **DNS**: DNS challenge requires API access to your DNS provider
|
||||||
|
- **Monitoring**: Monitor certificate expiration and renewal
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This role is part of the rick-infra project infrastructure configuration.
|
||||||
|
|||||||
95
roles/caddy/defaults/main.yml
Normal file
95
roles/caddy/defaults/main.yml
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
---
|
||||||
|
# =================================================================
|
||||||
|
# Caddy Web Server Role Configuration
|
||||||
|
# =================================================================
|
||||||
|
# This role provides a complete Caddy setup with automatic HTTPS
|
||||||
|
# Override these variables in host_vars/ for production deployment
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Basic Installation Configuration
|
||||||
|
# =================================================================
|
||||||
|
caddy_version: "latest"
|
||||||
|
caddy_user: "caddy"
|
||||||
|
caddy_group: "caddy"
|
||||||
|
caddy_home: "/var/lib/caddy"
|
||||||
|
caddy_config_dir: "/etc/caddy"
|
||||||
|
caddy_data_dir: "/var/lib/caddy"
|
||||||
|
caddy_log_dir: "/var/log/caddy"
|
||||||
|
caddy_web_root: "/var/www"
|
||||||
|
caddy_default_site_root: "{{ caddy_web_root }}/default"
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Service Configuration
|
||||||
|
# =================================================================
|
||||||
|
caddy_config_file: "/etc/caddy/Caddyfile" # Package default path
|
||||||
|
caddy_service_enabled: true
|
||||||
|
caddy_service_state: "started"
|
||||||
|
caddy_auto_https: true
|
||||||
|
caddy_admin_listen: "127.0.0.1:2019"
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# TLS/HTTPS Configuration
|
||||||
|
# =================================================================
|
||||||
|
# Enable automatic HTTPS with Let's Encrypt certificates
|
||||||
|
caddy_tls_enabled: false # Set to true to enable HTTPS
|
||||||
|
caddy_tls_email: "" # Required for Let's Encrypt (e.g., "admin@example.com")
|
||||||
|
caddy_domain: "localhost" # Primary domain to serve
|
||||||
|
|
||||||
|
# ACME Certificate Authority settings
|
||||||
|
caddy_acme_ca: "https://acme-v02.api.letsencrypt.org/directory" # Production CA
|
||||||
|
# caddy_acme_ca: "https://acme-staging-v02.api.letsencrypt.org/directory" # Staging for testing
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# DNS Challenge Configuration (for wildcard certificates)
|
||||||
|
# =================================================================
|
||||||
|
# DNS challenge allows wildcard certificates and works behind firewalls
|
||||||
|
caddy_dns_provider: "" # Set to "cloudflare" for Cloudflare DNS challenge
|
||||||
|
cloudflare_api_token: "" # Cloudflare API token (override in host_vars with vault reference)
|
||||||
|
|
||||||
|
# DNS challenge settings
|
||||||
|
caddy_dns_resolvers: # DNS resolvers for challenge verification
|
||||||
|
- "1.1.1.1:53"
|
||||||
|
- "1.0.0.1:53"
|
||||||
|
caddy_dns_propagation_timeout: 120 # Seconds to wait for DNS propagation
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Sites Configuration
|
||||||
|
# =================================================================
|
||||||
|
# Define additional sites/domains to serve
|
||||||
|
caddy_sites: []
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# =================================================================
|
||||||
|
# Firewall ports to open automatically
|
||||||
|
caddy_firewall_ports:
|
||||||
|
- 80 # HTTP (for redirects and ACME challenges)
|
||||||
|
- 443 # HTTPS (for TLS traffic)
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Advanced Configuration
|
||||||
|
# =================================================================
|
||||||
|
# Systemd service customization
|
||||||
|
caddy_systemd_security: true # Enable systemd security restrictions
|
||||||
|
caddy_log_level: "INFO" # Logging level (ERROR, WARN, INFO, DEBUG)
|
||||||
|
caddy_log_format: "common" # Log format (common, json)
|
||||||
20
roles/caddy/handlers/main.yml
Normal file
20
roles/caddy/handlers/main.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
- name: reload systemd
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: restart caddy
|
||||||
|
systemd:
|
||||||
|
name: caddy
|
||||||
|
state: restarted
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: reload caddy
|
||||||
|
systemd:
|
||||||
|
name: caddy
|
||||||
|
state: reloaded
|
||||||
|
|
||||||
|
- name: stop caddy
|
||||||
|
systemd:
|
||||||
|
name: caddy
|
||||||
|
state: stopped
|
||||||
15
roles/caddy/meta/main.yml
Normal file
15
roles/caddy/meta/main.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
role_name: caddy
|
||||||
|
author: rick-infra
|
||||||
|
description: Caddy web server and reverse proxy
|
||||||
|
min_ansible_version: 2.9
|
||||||
|
platforms:
|
||||||
|
- name: ArchLinux
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
|
|
||||||
|
collections:
|
||||||
|
- ansible.posix
|
||||||
122
roles/caddy/tasks/main.yml
Normal file
122
roles/caddy/tasks/main.yml
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
---
|
||||||
|
- name: Check if DNS challenge is needed
|
||||||
|
set_fact:
|
||||||
|
dns_challenge_needed: "{{ caddy_dns_provider == 'cloudflare' and cloudflare_api_token != '' }}"
|
||||||
|
|
||||||
|
- name: Install standard Caddy (if no DNS challenge needed)
|
||||||
|
pacman:
|
||||||
|
name: caddy
|
||||||
|
state: present
|
||||||
|
when: not dns_challenge_needed | bool
|
||||||
|
notify: restart caddy
|
||||||
|
|
||||||
|
- name: Download Caddy with Cloudflare plugin (if DNS challenge needed)
|
||||||
|
get_url:
|
||||||
|
url: "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com/caddy-dns/cloudflare"
|
||||||
|
dest: /tmp/caddy-with-cloudflare
|
||||||
|
mode: '0755'
|
||||||
|
when: dns_challenge_needed | bool
|
||||||
|
|
||||||
|
- name: Install Caddy with Cloudflare plugin
|
||||||
|
copy:
|
||||||
|
src: /tmp/caddy-with-cloudflare
|
||||||
|
dest: /usr/bin/caddy
|
||||||
|
mode: '0755'
|
||||||
|
remote_src: yes
|
||||||
|
backup: yes
|
||||||
|
when: dns_challenge_needed | bool
|
||||||
|
notify: restart caddy
|
||||||
|
|
||||||
|
- name: Clean up temporary Caddy binary
|
||||||
|
file:
|
||||||
|
path: /tmp/caddy-with-cloudflare
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Create caddy user and group
|
||||||
|
user:
|
||||||
|
name: "{{ caddy_user }}"
|
||||||
|
group: "{{ caddy_group }}"
|
||||||
|
home: "{{ caddy_home }}"
|
||||||
|
shell: /usr/bin/nologin
|
||||||
|
system: yes
|
||||||
|
createhome: yes
|
||||||
|
notify: restart caddy
|
||||||
|
|
||||||
|
- name: Create Caddy directories
|
||||||
|
file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ caddy_user }}"
|
||||||
|
group: "{{ caddy_group }}"
|
||||||
|
mode: '0755'
|
||||||
|
loop:
|
||||||
|
- "{{ caddy_config_dir }}"
|
||||||
|
- "{{ caddy_data_dir }}"
|
||||||
|
- "{{ caddy_log_dir }}"
|
||||||
|
- "{{ caddy_web_root }}"
|
||||||
|
- "{{ caddy_default_site_root }}"
|
||||||
|
|
||||||
|
- name: Deploy default index page
|
||||||
|
template:
|
||||||
|
src: index.html.j2
|
||||||
|
dest: "{{ caddy_default_site_root }}/index.html"
|
||||||
|
owner: "{{ caddy_user }}"
|
||||||
|
group: "{{ caddy_group }}"
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- name: Create systemd override directory
|
||||||
|
file:
|
||||||
|
path: /etc/systemd/system/caddy.service.d
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Configure Caddy systemd override
|
||||||
|
template:
|
||||||
|
src: systemd-override.conf.j2
|
||||||
|
dest: /etc/systemd/system/caddy.service.d/override.conf
|
||||||
|
mode: '0644'
|
||||||
|
notify:
|
||||||
|
- reload systemd
|
||||||
|
- restart caddy
|
||||||
|
|
||||||
|
- name: Generate Caddyfile from template (with vault secrets)
|
||||||
|
template:
|
||||||
|
src: Caddyfile.j2
|
||||||
|
dest: "{{ caddy_config_file }}"
|
||||||
|
owner: root
|
||||||
|
group: "{{ caddy_group }}"
|
||||||
|
mode: '0640'
|
||||||
|
backup: yes
|
||||||
|
notify: reload caddy
|
||||||
|
|
||||||
|
- name: Check Caddyfile syntax (basic check)
|
||||||
|
command: caddy fmt --overwrite "{{ caddy_config_file }}"
|
||||||
|
register: caddy_fmt_result
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
# Note: Full validation with environment variables happens at service startup
|
||||||
|
|
||||||
|
- name: Enable and start Caddy service
|
||||||
|
systemd:
|
||||||
|
name: caddy
|
||||||
|
enabled: "{{ caddy_service_enabled }}"
|
||||||
|
state: "{{ caddy_service_state }}"
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: Wait for Caddy to be running
|
||||||
|
wait_for:
|
||||||
|
port: 80
|
||||||
|
host: 127.0.0.1
|
||||||
|
timeout: 30
|
||||||
|
when: caddy_service_state == "started"
|
||||||
|
|
||||||
|
- name: Verify Caddy admin API is accessible
|
||||||
|
uri:
|
||||||
|
url: "http://{{ caddy_admin_listen }}/config/"
|
||||||
|
method: GET
|
||||||
|
register: caddy_admin_check
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
139
roles/caddy/templates/Caddyfile.j2
Normal file
139
roles/caddy/templates/Caddyfile.j2
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# Caddy configuration file
|
||||||
|
# Generated by Ansible - DO NOT EDIT MANUALLY
|
||||||
|
|
||||||
|
# Global configuration
|
||||||
|
{
|
||||||
|
admin {{ caddy_admin_listen }}
|
||||||
|
|
||||||
|
{% if caddy_tls_enabled and caddy_tls_email %}
|
||||||
|
# ACME configuration for Let's Encrypt
|
||||||
|
email {{ caddy_tls_email }}
|
||||||
|
acme_ca {{ caddy_acme_ca }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not caddy_auto_https %}
|
||||||
|
auto_https off
|
||||||
|
{% endif %}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Primary domain: {{ caddy_domain }}
|
||||||
|
{{ caddy_domain }} {
|
||||||
|
{% if caddy_tls_enabled %}
|
||||||
|
{% if caddy_dns_provider == "cloudflare" and cloudflare_api_token %}
|
||||||
|
# DNS challenge for automatic TLS (secure: no environment files)
|
||||||
|
tls {
|
||||||
|
dns cloudflare {{ cloudflare_api_token }}
|
||||||
|
resolvers {{ caddy_dns_resolvers | join(' ') }}
|
||||||
|
}
|
||||||
|
{% elif caddy_tls_email %}
|
||||||
|
# HTTP challenge for automatic TLS
|
||||||
|
tls {{ caddy_tls_email }}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
# TLS disabled
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# Serve static content
|
||||||
|
root * {{ caddy_default_site_root }}
|
||||||
|
file_server
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
log {
|
||||||
|
{% if caddy_log_format == "json" %}
|
||||||
|
output file {{ caddy_log_dir }}/{{ caddy_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 }}/{{ caddy_domain | replace('.', '_') }}.log {
|
||||||
|
roll_size 100mb
|
||||||
|
roll_keep 5
|
||||||
|
}
|
||||||
|
level {{ caddy_log_level }}
|
||||||
|
{% 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 %}
|
||||||
68
roles/caddy/templates/index.html.j2
Normal file
68
roles/caddy/templates/index.html.j2
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Welcome to jnss</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
padding: 3rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
background: rgba(0,255,0,0.2);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 2rem 0;
|
||||||
|
border: 1px solid rgba(0,255,0,0.3);
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Welcome to jnss</h1>
|
||||||
|
<p>🚀 Server infrastructure is online and secure</p>
|
||||||
|
<div class="status">
|
||||||
|
✅ Caddy web server running<br>
|
||||||
|
🔒 Enterprise-grade security hardening active<br>
|
||||||
|
📊 Structured logging operational
|
||||||
|
</div>
|
||||||
|
<p>Infrastructure managed with Ansible</p>
|
||||||
|
<div class="timestamp">
|
||||||
|
Deployed: {{ ansible_facts['date_time']['iso8601'] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
38
roles/caddy/templates/systemd-override.conf.j2
Normal file
38
roles/caddy/templates/systemd-override.conf.j2
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
[Service]
|
||||||
|
# Reload configuration with --force flag for reliability
|
||||||
|
ExecReload=
|
||||||
|
ExecReload=/usr/bin/caddy reload --config {{ caddy_config_file }} --force
|
||||||
|
|
||||||
|
{% if caddy_systemd_security | default(true) %}
|
||||||
|
# Enhanced security hardening beyond base service
|
||||||
|
NoNewPrivileges=true
|
||||||
|
CapabilityBoundingSet=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
|
||||||
|
{% endif %}
|
||||||
10
site.yml
10
site.yml
@@ -1,5 +1,13 @@
|
|||||||
---
|
---
|
||||||
- name: Setting up VPS
|
- name: Secure VPS Infrastructure Setup
|
||||||
hosts: arch-vps
|
hosts: arch-vps
|
||||||
become: yes
|
become: yes
|
||||||
gather_facts: yes
|
gather_facts: yes
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: caddy
|
||||||
|
tags: ['caddy', 'web', 'https']
|
||||||
|
|
||||||
|
# Optional: Include security playbook
|
||||||
|
# - import_playbook: playbooks/security.yml
|
||||||
|
# tags: ['security', 'firewall', 'ssh']
|
||||||
|
|||||||
Reference in New Issue
Block a user