From 7ab2b719dc6fe9ed6656a402c8554c031fe5fbdc Mon Sep 17 00:00:00 2001 From: Doni Crosby Date: Sat, 27 Sep 2025 13:36:00 -0400 Subject: [PATCH] feat: deployable playbook --- .cursorignore | 1 + .gitignore | 3 +- Makefile | 27 ++- README.md | 162 ++++++++++++++--- ansible.cfg | 6 +- group_vars/netbox.yml | 85 --------- group_vars/netbox/.gitkeep | 0 host_vars/netbox-server.yml.example | 11 -- inventory/group_vars/netbox_servers.yml | 169 ++++++++++++++++++ inventory/group_vars/netbox_servers_vault | 67 +++++++ inventory/hosts.yml | 20 +-- playbooks/backup-netbox.yml | 2 +- playbooks/deploy-netbox.yml | 13 +- playbooks/update-netbox.yml | 7 +- roles/netbox-deploy/defaults/main.yml | 5 + roles/netbox-deploy/tasks/main.yml | 31 +--- roles/netbox-deploy/vars/main.yml | 8 - roles/traefik/defaults/main.yml | 56 ++++++ roles/traefik/handlers/main.yml | 10 ++ roles/traefik/meta/main.yml | 19 ++ roles/traefik/tasks/main.yml | 101 +++++++++++ roles/traefik/templates/docker-compose.yml.j2 | 41 +++++ roles/traefik/templates/traefik.yml.j2 | 86 +++++++++ 23 files changed, 754 insertions(+), 176 deletions(-) create mode 100644 .cursorignore delete mode 100644 group_vars/netbox.yml delete mode 100644 group_vars/netbox/.gitkeep delete mode 100644 host_vars/netbox-server.yml.example create mode 100644 inventory/group_vars/netbox_servers.yml create mode 100644 inventory/group_vars/netbox_servers_vault delete mode 100644 roles/netbox-deploy/vars/main.yml create mode 100644 roles/traefik/defaults/main.yml create mode 100644 roles/traefik/handlers/main.yml create mode 100644 roles/traefik/meta/main.yml create mode 100644 roles/traefik/tasks/main.yml create mode 100644 roles/traefik/templates/docker-compose.yml.j2 create mode 100644 roles/traefik/templates/traefik.yml.j2 diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..50081a8 --- /dev/null +++ b/.cursorignore @@ -0,0 +1 @@ +.vault-password \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6431c61..ab1d853 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # ---> Ansible *.retry -vault.yml \ No newline at end of file +ansible.log +.vault-password diff --git a/Makefile b/Makefile index beaa292..b5105e6 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # NetBox Ansible Deployment Makefile -.PHONY: help install-collections deploy update backup check-syntax encrypt-vault decrypt-vault +.PHONY: help install-collections deploy update backup check-syntax encrypt-vault decrypt-vault traefik-status traefik-logs traefik-access-logs traefik-restart traefik-update-ca help: ## Show this help message @echo "NetBox Ansible Deployment" @@ -13,7 +13,7 @@ install-collections: ## Install required Ansible collections ansible-galaxy collection install -r requirements.yml deploy: ## Deploy NetBox (requires vault password) - ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --ask-vault-pass + ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --vault-password-file .vault-password update: ## Update NetBox ansible-playbook -i inventory/hosts.yml playbooks/update-netbox.yml @@ -27,16 +27,31 @@ check-syntax: ## Check Ansible playbook syntax ansible-playbook -i inventory/hosts.yml playbooks/backup-netbox.yml --syntax-check encrypt-vault: ## Encrypt vault file - ansible-vault encrypt group_vars/netbox/vault.yml + ansible-vault encrypt inventory/group_vars/netbox_servers_vault decrypt-vault: ## Decrypt vault file - ansible-vault decrypt group_vars/netbox/vault.yml + ansible-vault decrypt inventory/group_vars/netbox_servers_vault edit-vault: ## Edit encrypted vault file - ansible-vault edit group_vars/netbox/vault.yml + ansible-vault edit inventory/group_vars/netbox_servers_vault dry-run: ## Run playbook in check mode (dry run) - ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --check --ask-vault-pass + ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --check --vault-password-file .vault-password test-connection: ## Test connection to hosts ansible netbox -i inventory/hosts.yml -m ping + +traefik-status: ## Check Traefik container status + ansible netbox -i inventory/hosts.yml -m shell -a "docker compose ps" --args "chdir=/opt/traefik" + +traefik-logs: ## Show Traefik container logs + ansible netbox -i inventory/hosts.yml -m shell -a "docker compose logs traefik" --args "chdir=/opt/traefik" + +traefik-access-logs: ## Show Traefik access logs (JSON format) + ansible netbox -i inventory/hosts.yml -m shell -a "docker compose logs traefik | grep -E '^traefik.*access' | tail -50" + +traefik-restart: ## Restart Traefik container + ansible netbox -i inventory/hosts.yml -m shell -a "docker compose restart traefik" --args "chdir=/opt/traefik" + +traefik-update-ca: ## Update custom root CA certificate + ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --tags "custom-ca" --vault-password-file .vault-password diff --git a/README.md b/README.md index 35ad641..c57683e 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,12 @@ This Ansible project deploys NetBox using Docker Compose on Ubuntu servers. It f ## Features -- **Modular Design**: Separate roles for system updates, Docker installation, and NetBox deployment +- **Modular Design**: Separate roles for system updates, Docker installation, Traefik reverse proxy, and NetBox deployment - **Idempotent**: Safe to run multiple times without side effects - **Ubuntu Only**: Specifically designed for Ubuntu distributions (Focal, Jammy, Noble) - **Docker Compose**: Uses the official NetBox Docker repository +- **Traefik Integration**: Automatic reverse proxy with ACME TLS certificate management +- **HTTPS by Default**: Automatic HTTP to HTTPS redirection with Let's Encrypt certificates - **Configuration Management**: Templated environment files with Ansible variables - **Security**: Support for Ansible Vault for sensitive data - **Backup Support**: Built-in backup playbook for data protection @@ -30,6 +32,7 @@ netbox-ansible/ ├── roles/ │ ├── system-update/ # System package updates │ ├── docker-install/ # Docker and Docker Compose installation +│ ├── traefik/ # Traefik reverse proxy with ACME TLS │ └── netbox-deploy/ # NetBox deployment and configuration └── templates/ # Additional templates if needed ``` @@ -59,16 +62,23 @@ netbox-ansible/ # Edit group variables vim group_vars/netbox.yml + # Edit vault variables (domains, ACME email, etc.) + vim group_vars/netbox/vault.yml + # Encrypt sensitive variables ansible-vault encrypt group_vars/netbox/vault.yml ``` -3. **Deploy NetBox** +3. **Deploy NetBox with Traefik** ```bash - # Run the deployment playbook - ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml + # Run the deployment playbook (includes Traefik) + ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --ask-vault-pass ``` +4. **Access Your Services** + - NetBox: `https://your-domain.com` + - Traefik Dashboard: `https://traefik.your-domain.com:8080` + ## Configuration ### Group Variables (`group_vars/netbox.yml`) @@ -78,10 +88,12 @@ Key configuration options: - `netbox_install_dir`: Directory for NetBox installation (default: `/opt/netbox-docker`) - `netbox_data_dir`: Directory for persistent data (default: `/opt/netbox-data`) - `netbox_backup_dir`: Directory for backups (default: `/opt/netbox-backups`) +- `netbox_domain`: Domain name for NetBox (configured in vault) - `netbox_allowed_hosts`: Allowed hosts for NetBox - `netbox_superuser_*`: Superuser configuration - `netbox_db_*`: Database configuration - `netbox_redis_*`: Redis configuration +- `traefik_*`: Traefik reverse proxy configuration ### Vault Variables (`group_vars/netbox/vault.yml`) @@ -97,29 +109,33 @@ ansible-vault edit group_vars/netbox/vault.yml ### Docker Compose Overrides -Customize Docker Compose configuration via `netbox_docker_compose_overrides`: +NetBox is now configured to work with Traefik labels instead of port forwarding: ```yaml netbox_docker_compose_overrides: services: netbox: - ports: - - "8000:8080" - db: - volumes: - - "/opt/netbox-data/postgres:/var/lib/postgresql/data" + labels: + - "traefik.enable=true" + - "traefik.http.routers.netbox.rule=Host(`{{ netbox_domain }}`)" + - "traefik.http.routers.netbox.entrypoints=websecure" + - "traefik.http.routers.netbox.tls.certresolver=letsencrypt" + - "traefik.http.services.netbox.loadbalancer.server.port=8080" + networks: + - "traefik" ``` ## Playbooks ### Main Deployment (`deploy-netbox.yml`) -Deploys NetBox from scratch: +Deploys NetBox with Traefik reverse proxy: - Updates system packages - Installs Docker and Docker Compose +- Deploys Traefik reverse proxy with ACME TLS - Clones NetBox Docker repository - Configures environment files -- Starts NetBox services +- Starts NetBox services behind Traefik - Creates superuser account ### Update NetBox (`update-netbox.yml`) @@ -150,18 +166,25 @@ Creates comprehensive backup: - Configures Docker daemon - Adds users to docker group +### traefik +- Creates Traefik directories and configuration +- Sets up ACME certificate resolver for Let's Encrypt +- Configures Docker provider for automatic service discovery +- Deploys Traefik reverse proxy with HTTPS redirection +- Creates external network for service communication + ### netbox-deploy - Creates necessary directories - Clones NetBox Docker repository - Generates configuration files -- Starts NetBox services +- Starts NetBox services with Traefik labels - Creates superuser account ## Usage Examples -### Deploy NetBox +### Deploy NetBox with Traefik ```bash -ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml +ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --ask-vault-pass ``` ### Update NetBox @@ -181,16 +204,38 @@ ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --ask-vault- ### Run Specific Tags ```bash -ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --tags "docker-install" +ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --tags "traefik" +ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --tags "netbox-deploy" +``` + +### Traefik Management +```bash +# Check Traefik status +make traefik-status + +# View Traefik logs +make traefik-logs + +# View access logs (JSON format for auditing) +make traefik-access-logs + +# Restart Traefik +make traefik-restart + +# Update custom root CA certificate +make traefik-update-ca ``` ## Security Considerations 1. **Encrypt Sensitive Data**: Use `ansible-vault` for passwords and secrets 2. **SSH Key Authentication**: Use SSH keys instead of passwords -3. **Firewall Rules**: Configure appropriate firewall rules -4. **Regular Updates**: Keep NetBox and dependencies updated -5. **Backup Strategy**: Implement regular backup procedures +3. **Firewall Rules**: Configure appropriate firewall rules (ports 80, 443) +4. **TLS Certificates**: ACME certificates are automatically managed by Traefik +5. **Custom Root CA**: Support for custom certificate authorities alongside system CAs +6. **Access Logging**: Comprehensive JSON-formatted access logs for auditing +7. **Regular Updates**: Keep NetBox and dependencies updated +8. **Backup Strategy**: Implement regular backup procedures ## Troubleshooting @@ -198,8 +243,10 @@ ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml --tags "dock 1. **Permission Denied**: Ensure user has sudo privileges 2. **Docker Not Found**: Check Docker installation and user group membership -3. **Port Conflicts**: Verify port 8000 is available +3. **Port Conflicts**: Verify ports 80 and 443 are available 4. **Database Connection**: Check database configuration and connectivity +5. **TLS Certificate Issues**: Check ACME configuration and domain DNS +6. **Traefik Not Starting**: Check Docker network and configuration ### Logs and Debugging @@ -209,8 +256,83 @@ ansible-playbook -i inventory/hosts.yml playbooks/deploy-netbox.yml -vvv # Check Docker Compose logs ansible netbox -i inventory/hosts.yml -m shell -a "cd /opt/netbox-docker && docker compose logs" + +# Check Traefik logs +ansible netbox -i inventory/hosts.yml -m shell -a "cd /opt/traefik && docker compose logs traefik" ``` +## Logging and Auditing + +### Access Logs +Traefik is configured with comprehensive access logging in JSON format for easy parsing and auditing: + +- **Format**: JSON structured logs +- **Fields**: Includes request details, response codes, timing, and headers +- **Security**: Authorization headers are automatically dropped from logs +- **Headers Tracked**: User-Agent, Content-Type, X-Forwarded-For, X-Real-IP, etc. + +### Log Management Commands +```bash +# View recent access logs +make traefik-access-logs + +# View all Traefik logs +make traefik-logs + +# Follow logs in real-time +ansible netbox -i inventory/hosts.yml -m shell -a "cd /opt/traefik && docker compose logs -f traefik" +``` + +### Log Analysis Examples +```bash +# Count requests by status code +ansible netbox -i inventory/hosts.yml -m shell -a "cd /opt/traefik && docker compose logs traefik | grep access | jq '.DownstreamStatus' | sort | uniq -c" + +# Find failed requests +ansible netbox -i inventory/hosts.yml -m shell -a "cd /opt/traefik && docker compose logs traefik | grep access | jq 'select(.DownstreamStatus >= 400)'" + +# Analyze by IP address +ansible netbox -i inventory/hosts.yml -m shell -a "cd /opt/traefik && docker compose logs traefik | grep access | jq '.ClientHost' | sort | uniq -c" +``` + +## Custom Root CA Configuration + +Traefik can be configured to trust custom root certificate authorities while maintaining trust for system root CAs. This is useful for internal PKI environments or corporate certificate authorities. + +### Configuration + +Set the custom CA URL in your vault file: + +```yaml +# Custom Root CA Configuration +vault_traefik_custom_ca_url: "https://your-ca-server.com/root-ca.pem" +``` + +### Features + +- **Dual Trust**: Trusts both custom CA and system root CAs +- **Automatic Download**: Downloads CA certificate from web server during deployment +- **Certificate Validation**: Verifies certificate format using OpenSSL +- **Secure Storage**: CA certificate stored with appropriate permissions +- **Easy Updates**: Update CA certificate without full redeployment + +### Management Commands + +```bash +# Update custom root CA certificate +make traefik-update-ca + +# Verify CA certificate manually +ansible netbox -i inventory/hosts.yml -m shell -a "openssl x509 -in /etc/traefik/custom-ca.pem -text -noout" +``` + +### Requirements + +- Custom CA certificate must be accessible via HTTP/HTTPS GET request +- Certificate must be in PEM format +- Web server must be accessible from the deployment server +- Certificate validation can be disabled if needed (`traefik_custom_ca_verify_ssl: false`) + ## Contributing 1. Follow Ansible best practices diff --git a/ansible.cfg b/ansible.cfg index 12a4b31..e55d778 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,7 +1,7 @@ [defaults] # Basic configuration inventory = inventory/ -host_key_checking = False +roles_path = roles/ timeout = 30 forks = 10 gathering = smart @@ -9,7 +9,6 @@ fact_caching = memory # Logging log_path = ./ansible.log -stdout_callback = yaml bin_ansible_callbacks = True # SSH settings @@ -23,9 +22,6 @@ retry_files_enabled = False # Colors force_color = 1 -[inventory] -enable_plugins = host_list, script, auto, yaml, ini, toml - [privilege_escalation] become = True become_method = sudo diff --git a/group_vars/netbox.yml b/group_vars/netbox.yml deleted file mode 100644 index 4551e5b..0000000 --- a/group_vars/netbox.yml +++ /dev/null @@ -1,85 +0,0 @@ ---- -# Group variables for NetBox deployment -# These variables apply to all hosts in the netbox group - -# System Update Configuration -system_update_reboot_if_needed: false -system_update_autoremove: true - -# Docker Configuration -docker_users: - - "{{ ansible_user }}" -docker_daemon_config: - log-driver: "json-file" - log-opts: - max-size: "10m" - max-file: "3" - -# NetBox Configuration -netbox_install_dir: "/opt/netbox-docker" -netbox_backup_dir: "/opt/netbox-backups" -netbox_data_dir: "/opt/netbox-data" - -# Database Configuration -netbox_db_host: "db" -netbox_db_name: "netbox" -netbox_db_user: "netbox" -netbox_db_password: "{{ vault_netbox_db_password | default('netbox') }}" -netbox_db_port: "5432" - -# Redis Configuration -netbox_redis_host: "redis" -netbox_redis_port: "6379" -netbox_redis_password: "{{ vault_netbox_redis_password | default('') }}" - -# Redis Cache Configuration -netbox_redis_cache_host: "redis-cache" -netbox_redis_cache_port: "6379" -netbox_redis_cache_password: "{{ vault_netbox_redis_cache_password | default('') }}" - -# NetBox Settings -netbox_allowed_hosts: "{{ ansible_default_ipv4.address }},localhost,127.0.0.1" -netbox_time_zone: "UTC" -netbox_language_code: "en" -netbox_debug: false -netbox_log_level: "INFO" - -# Superuser Configuration -netbox_superuser_name: "admin" -netbox_superuser_email: "admin@{{ ansible_domain | default('example.com') }}" -netbox_superuser_password: "{{ vault_netbox_superuser_password | default('admin') }}" - -netbox_additional_env: - CORS_ORIGIN_ALLOW_ALL: True - EMAIL_FROM: "netbox@jeansburger.net" - EMAIL_PASSWORD: "{{ vault_netbox_email_password | default('') }}" - EMAIL_PORT: 587 - EMAIL_SERVER: "smtp.postmarkapp.com" - EMAIL_TIMEOUT: 5 - EMAIL_USERNAME: "{{ value_netbox_email_username | default('') }}" - # EMAIL_USE_SSL and EMAIL_USE_TLS are mutually exclusive, i.e. they can't both be `true`! - EMAIL_USE_SSL: False - EMAIL_USE_TLS: True - GRAPHQL_ENABLED: True - MEDIA_ROOT: "/opt/netbox/netbox/media" - METRICS_ENABLED: True - RELEASE_CHECK_URL: "https://api.github.com/repos/netbox-community/netbox/releases" - SECRET_KEY: "{{ vault_netbox_secret_key | default('') }}" - SKIP_SUPERUSER: True - WEBHOOKS_ENABLED: True - -# Docker Compose Overrides -netbox_docker_compose_overrides: - services: - netbox: - ports: - - "8000:8080" - db: - volumes: - - "{{ netbox_data_dir }}/postgres:/var/lib/postgresql/data" - redis: - volumes: - - "{{ netbox_data_dir }}/redis:/data" - redis-cache: - volumes: - - "{{ netbox_data_dir }}/redis-cache:/data" diff --git a/group_vars/netbox/.gitkeep b/group_vars/netbox/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/host_vars/netbox-server.yml.example b/host_vars/netbox-server.yml.example deleted file mode 100644 index 6b4d73d..0000000 --- a/host_vars/netbox-server.yml.example +++ /dev/null @@ -1,11 +0,0 @@ ---- -# Host-specific variables for NetBox deployment -# This file can be used to override group variables for specific hosts - -# Example host-specific overrides: -# netbox_install_dir: "/custom/netbox/path" -# netbox_allowed_hosts: "custom.domain.com,192.168.1.100" -# netbox_time_zone: "America/New_York" - -# Uncomment and modify as needed for your specific host -# netbox_superuser_email: "admin@yourdomain.com" diff --git a/inventory/group_vars/netbox_servers.yml b/inventory/group_vars/netbox_servers.yml new file mode 100644 index 0000000..94b1666 --- /dev/null +++ b/inventory/group_vars/netbox_servers.yml @@ -0,0 +1,169 @@ +--- +# Group variables for NetBox deployment +# These variables apply to all hosts in the netbox group + +# System Update Configuration +system_update_reboot_if_needed: false +system_update_autoremove: true + +# Docker Configuration +docker_users: + - "{{ ansible_user }}" +docker_daemon_config: + log-driver: "json-file" + log-opts: + max-size: "10m" + max-file: "3" + +# NetBox Configuration +netbox_install_dir: "/opt/netbox-docker" +netbox_backup_dir: "/opt/netbox-backups" +netbox_data_dir: "/opt/netbox-data" +netbox_repo_url: "https://github.com/netbox-community/netbox-docker.git" +netbox_repo_branch: "release" +netbox_repo_update: true + +# Database Configuration +netbox_db_host: "postgres" +netbox_db_name: "netbox" +netbox_db_user: "netbox" +netbox_db_password: "{{ vault_netbox_db_password }}" +netbox_db_port: "5432" + +# Redis Configuration +netbox_redis_host: "redis" +netbox_redis_port: "6379" +netbox_redis_password: "{{ vault_netbox_redis_password }}" + +# Redis Cache Configuration +netbox_redis_cache_host: "redis-cache" +netbox_redis_cache_port: "6379" +netbox_redis_cache_password: "{{ vault_netbox_redis_cache_password | default('') }}" + +# NetBox Settings +netbox_allowed_hosts: "*" +netbox_time_zone: "UTC" +netbox_language_code: "en" +netbox_debug: false +netbox_log_level: "INFO" +netbox_secret_key: "{{ vault_netbox_secret_key | default('') }}" + +# Superuser Configuration +netbox_superuser_name: "admin" +netbox_superuser_email: "admin@{{ ansible_domain | default('example.com') }}" +netbox_superuser_password: "{{ vault_netbox_superuser_password | default('admin') }}" + +netbox_additional_env: + DB_WAIT_DEBUG: 1 + CORS_ORIGIN_ALLOW_ALL: True + EMAIL_FROM: "netbox@jeansburger.net" + EMAIL_PASSWORD: "{{ vault_netbox_email_password | default('') }}" + EMAIL_PORT: 587 + EMAIL_SERVER: "smtp.postmarkapp.com" + EMAIL_TIMEOUT: 5 + EMAIL_USERNAME: "{{ vault_netbox_email_username | default('') }}" + # EMAIL_USE_SSL and EMAIL_USE_TLS are mutually exclusive, i.e. they can't both be `true`! + EMAIL_USE_SSL: False + EMAIL_USE_TLS: True + GRAPHQL_ENABLED: True + MEDIA_ROOT: "/opt/netbox/netbox/media" + METRICS_ENABLED: True + RELEASE_CHECK_URL: "https://api.github.com/repos/netbox-community/netbox/releases" + SECRET_KEY: "{{ vault_netbox_secret_key | default('') }}" + SKIP_SUPERUSER: True + WEBHOOKS_ENABLED: True + +# Domain Configuration +netbox_domain: "{{ vault_netbox_domain | default('netbox.example.com') }}" +traefik_domain: "{{ vault_netbox_domain | default('traefik.example.com') }}" + +# Traefik Configuration +traefik_install_dir: "/opt/traefik" +traefik_data_dir: "/opt/traefik-data" +traefik_config_dir: "/opt/traefik-config" +traefik_image: "traefik:v3.0" +traefik_dashboard_enabled: true +traefik_dashboard_port: 8080 +traefik_api_insecure: false +traefik_api_dashboard: true + +# ACME Configuration +traefik_acme_enabled: true +traefik_acme_email: "{{ vault_traefik_acme_email | default('admin@example.com') }}" +traefik_acme_ca_server: "{{ vault_traefik_acme_ca_server }}" +traefik_acme_storage: "/data/acme.json" +traefik_acme_key_type: "RSA4096" + +# Traefik Entry Points +traefik_entrypoints: + web: + address: ":80" + http: + redirections: + - entrypoint: + to: "websecure" + scheme: "https" + permanent: true + websecure: + address: ":443" + http: + tls: + certResolver: "letsencrypt" + +# Traefik Providers +traefik_providers: + docker: + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false + network: "traefik" + +# Traefik Networks +traefik_networks: + - name: "traefik" + external: true + +# Traefik Logging +traefik_log_level: "INFO" +traefik_access_logs: false + +# Custom Root CA Configuration +traefik_custom_ca_enabled: true +traefik_custom_ca_url: "{{ vault_traefik_custom_ca_url }}" +traefik_custom_ca_path: "/etc/traefik/custom-ca.pem" +traefik_custom_ca_server_name: "jeansburger-ca.lan" +traefik_custom_ca_verify_ssl: false + +# Docker Compose Overrides +netbox_docker_compose_overrides: + services: + netbox: + labels: + - "traefik.enable=true" + - "traefik.http.routers.netbox.rule=Host(`{{ netbox_domain }}`)" + - "traefik.http.routers.netbox.tls.certresolver=letsencrypt" + - "traefik.http.services.netbox.loadbalancer.server.port=8080" + networks: + - "traefik" + - "netbox" + netbox-worker: + networks: + - "netbox" + postgres: + networks: + - "netbox" + volumes: + - "{{ netbox_data_dir }}/postgres:/var/lib/postgresql/data" + redis: + networks: + - "netbox" + volumes: + - "{{ netbox_data_dir }}/redis:/data" + redis-cache: + networks: + - "netbox" + volumes: + - "{{ netbox_data_dir }}/redis-cache:/data" + networks: + traefik: + external: true + netbox: {} diff --git a/inventory/group_vars/netbox_servers_vault b/inventory/group_vars/netbox_servers_vault new file mode 100644 index 0000000..cc20620 --- /dev/null +++ b/inventory/group_vars/netbox_servers_vault @@ -0,0 +1,67 @@ +$ANSIBLE_VAULT;1.1;AES256 +37623935373466373531313732383935343638313038323037613064663465303362643636336438 +3962616234303265666235303266383539386462376431330a336336363966623533623730393962 +31313935633433636635356434393338626538333965386539373530363065393166656163343266 +6238396131613235610a356537393638613731333163376638656466636630383730663633363431 +35646665626564383136653863333762313637613934643437383335336566643830396131336431 +33626663636636636633326538343262393165623664313161643930656531323765313835343332 +63623837363763386165613438303762323432653064663066636163323962613632636531313430 +39303961373537303061666434303430323063393464363662653866373131666236646637326337 +65393836613831343765363266653737613161336133613939613234333664653066333864373536 +64633461303862363739393834306230336332363163303432663338663765363263633433613763 +31393963376164616566383566386562346265393963643530636561663063353738303934303363 +62663039333838323231313137386637663832373466333136323134626133653762373631623033 +66383964643439653262386438623933356532336235663866353137316639613266303839336566 +38373765316238303230353938643463366264306230303433363863616364383965343837346634 +32643135613032373163353632626439643266613633623662646161623237313364616331353861 +37396534396335346239363031373264323264326334636564333862353434396138313132643631 +65656136363031363534626638313830386666653235333131646265376366393235663264393065 +65386135353533313236333038376165346139306431643064373734343066333462616364613764 +39353439303362623938353338353666323063343533616262353563613064333835626331666233 +61656232613835313736373134373131306364663066366461376335373662386264363234373731 +32633166323030363237663761396638303837363138666365343533653638666461303138396435 +31666132353866376161323362343963366536646530346237633236323532383433343965666464 +31303666653665323161346239396133373234316665643436383362313436326139653264303637 +63656166306162336462656262386437353165636262303334363538366338653734346333393763 +66323435303262343336306630623864343731633762376135356563373266396563656430663764 +38303766363235346264656231643863333533313765393338333235353034653836393733333065 +36663864663838663334393232313036656534326363663066666335656432366538643562656263 +31393866353731616662313432393335636436396335356537376166326136666136323661326664 +36303562303339323264343365613535393037383038396131346161396539353936666133303562 +32373530616237306264626361613864633561343163303262313034616662663034393830626330 +30363462363264386234373763653438666232353235656139633463363265613231346639653461 +65353861353035663763313262663863633761333462356463613737633430373736366136393666 +36353139376431376566383761613538383133323563653336653835393037343562373738333361 +35613862383062336263626261646638633033393763336238333864656663613638353362353030 +66636465383032393434636665613664323331326131653234653766616563323833623739393631 +62363732613764313036366531363130313438396261376232333738376235346136346162333037 +34613966636261353237616664663666383364623262396363333365306661663933366431636430 +62316363313662326338653163306431383132333739303365393938616436396166386639313339 +36333962393063633630386162613232626431303064346662313865363032326465303163646238 +61333638613839333239653537373564363966633836376335613637643961323530626366393263 +34336633316438326432333537396636663063626339383630613965643238376362326432336366 +34313934323361386262336162363261613363353866393464316164636464393330613237643936 +65336431343037376163326433373566363663346635386162366137313165373431643863366134 +34343735393532656161626466343061623439663064343661396230393764663764363139383532 +32346662616235626365323663313430343130356563313335323635653863373565633235386130 +39353735646661393164393133303666316263643933393336613563623637386139633765386238 +65663439623036383436356361313062343632306232633363376235393634623438616462393733 +36313130386135346631656638663037373238643330333364633061316261393031353130316234 +30633737356564333231353237366637623930653333616536383331363762393461343533353765 +64356237376432643064626662393836346138633465363561333035623331373931353833663764 +38336266386236643531633135313833666336396231316231653666356339623262353234333537 +64393863396165616261616132613561386664646262333965636663396564313235623032663839 +62656630313031326232613962336636636433326639643534646664313062393135323164643631 +37353464666232653333636236343264386264303136613636393664323139306631393666663636 +32656261636635623261356365616163303665613462323131346466306661303233643566396631 +61636363323536383464616363666537343438363632333638623130613233376538346332303033 +31646164353735656162613863633630343537666232373036323337646564653338643762643464 +30633336653030613333613761646538303466636662386362373235623166643464393130623466 +64373234353765313339333261363366633766623861336663633131643161353238636663643235 +34333638363131626534653932363965353737653434326265353864383233316335393264633861 +33373365643239663161343361356430303437656264663735636164373862323135656530373463 +37656461653736633437353864366530373037633861646132376263613035616561366366643435 +64643036303563323466643937373232303736373966636239383766323439663264393631383562 +35656234343131353132366262623962313066313464656631383465613630666136356266333636 +38663065656336373561373563313161643362386339363730666434663930633861666266346162 +3631303764363237623466326266366131323065326163393962 diff --git a/inventory/hosts.yml b/inventory/hosts.yml index 4119ba4..005c06e 100644 --- a/inventory/hosts.yml +++ b/inventory/hosts.yml @@ -1,16 +1,14 @@ # NetBox Deployment Inventory # Example inventory file for NetBox deployment -[netbox] -# Add your NetBox server(s) here -# Example: -# netbox-server ansible_host=192.168.1.100 ansible_user=ubuntu -# netbox-server-2 ansible_host=192.168.1.101 ansible_user=ubuntu +netbox_servers: + hosts: + netbox: + ansible_host: netbox.lan + ansible_user: scrappy + + + # Uncomment and modify the following line to add your server: -# netbox-server ansible_host=YOUR_SERVER_IP ansible_user=YOUR_USERNAME - -[netbox:vars] -# Group variables can be defined here or in group_vars/netbox.yml -ansible_ssh_common_args='-o StrictHostKeyChecking=no' -ansible_python_interpreter=/usr/bin/python3 +# netbox-server ansible_host=YOUR_SERVER_IP ansible_user=YOUR_USERNAME \ No newline at end of file diff --git a/playbooks/backup-netbox.yml b/playbooks/backup-netbox.yml index 2949dcd..b2aff85 100644 --- a/playbooks/backup-netbox.yml +++ b/playbooks/backup-netbox.yml @@ -15,7 +15,7 @@ - backup - name: Backup PostgreSQL database - docker_compose: + community.docker.docker_compose_v2: project_src: "{{ netbox_install_dir }}" command: "db pg_dump -U {{ netbox_db_user }} {{ netbox_db_name }}" register: db_backup diff --git a/playbooks/deploy-netbox.yml b/playbooks/deploy-netbox.yml index a96c802..b43bf44 100644 --- a/playbooks/deploy-netbox.yml +++ b/playbooks/deploy-netbox.yml @@ -5,6 +5,9 @@ gather_facts: yes pre_tasks: + - name: Include vault variables + include_vars: ../inventory/group_vars/netbox_servers_vault + - name: Verify Ubuntu distribution fail: msg: "This playbook only supports Ubuntu distributions" @@ -33,6 +36,12 @@ - docker - containers + - role: traefik + tags: + - traefik-deploy + - traefik + - reverse-proxy + - role: netbox-deploy tags: - netbox-deploy @@ -44,13 +53,15 @@ debug: msg: - "NetBox deployment completed successfully!" - - "Access NetBox at: http://{{ ansible_default_ipv4.address }}:8000" + - "Access NetBox at: https://{{ netbox_domain }}" + - "Traefik Dashboard at: https://{{ traefik_domain }}:{{ traefik_dashboard_port }}" - "Default admin credentials:" - " Username: {{ netbox_superuser_name }}" - " Email: {{ netbox_superuser_email }}" - " Password: {{ netbox_superuser_password }}" - "Installation directory: {{ netbox_install_dir }}" - "Data directory: {{ netbox_data_dir }}" + - "Traefik installation directory: {{ traefik_install_dir }}" tags: always - name: Show Docker Compose status diff --git a/playbooks/update-netbox.yml b/playbooks/update-netbox.yml index c5a16f2..c49c714 100644 --- a/playbooks/update-netbox.yml +++ b/playbooks/update-netbox.yml @@ -16,14 +16,14 @@ - update-repo - name: Pull latest Docker images - docker_compose: + community.docker.docker_compose_v2: project_src: "{{ netbox_install_dir }}" pull: yes tags: - pull-images - name: Restart NetBox services - docker_compose: + community.docker.docker_compose_v2: project_src: "{{ netbox_install_dir }}" state: present recreate: yes @@ -32,9 +32,10 @@ - name: Wait for NetBox to be ready uri: - url: "http://localhost:8000/" + url: "https://{{ netbox_domain }}/" method: GET status_code: 200 + validate_certs: false register: netbox_ready until: netbox_ready.status == 200 retries: 30 diff --git a/roles/netbox-deploy/defaults/main.yml b/roles/netbox-deploy/defaults/main.yml index 4a474a6..f7ef189 100644 --- a/roles/netbox-deploy/defaults/main.yml +++ b/roles/netbox-deploy/defaults/main.yml @@ -25,6 +25,11 @@ netbox_redis_host: "redis" netbox_redis_port: "6379" netbox_redis_password: "" +# Redis Cache configuration +netbox_redis_cache_host: "redis-cache" +netbox_redis_cache_port: "6379" +netbox_redis_cache_password: "" + # NetBox settings netbox_allowed_hosts: "localhost,127.0.0.1" netbox_time_zone: "UTC" diff --git a/roles/netbox-deploy/tasks/main.yml b/roles/netbox-deploy/tasks/main.yml index 9e37348..ec36520 100644 --- a/roles/netbox-deploy/tasks/main.yml +++ b/roles/netbox-deploy/tasks/main.yml @@ -45,7 +45,7 @@ - name: Generate NetBox secret key set_fact: - netbox_secret_key: "{{ netbox_secret_key | default(128 | random_string) }}" + netbox_secret_key: "{{ netbox_secret_key | default(ansible.builtin.random_string(length=128)) }}" when: netbox_secret_key == "" tags: - netbox-deploy @@ -97,15 +97,15 @@ - config - name: Pull Docker images - docker_compose: + community.docker.docker_compose_v2: project_src: "{{ netbox_install_dir }}" - pull: yes + pull: always tags: - netbox-deploy - docker-pull - name: Start NetBox services - docker_compose: + community.docker.docker_compose_v2: project_src: "{{ netbox_install_dir }}" state: present tags: @@ -114,31 +114,14 @@ - name: Wait for NetBox to be ready uri: - url: "http://localhost:8000/" + url: "https://{{ netbox_domain }}/" method: GET status_code: 200 + validate_certs: false register: netbox_ready until: netbox_ready.status == 200 retries: 30 delay: 10 tags: - netbox-deploy - - health-check - -- name: Create NetBox superuser - docker_compose: - project_src: "{{ netbox_install_dir }}" - command: "netbox /opt/netbox/netbox/manage.py createsuperuser --noinput --username {{ netbox_superuser_name }} --email {{ netbox_superuser_email }}" - register: superuser_result - failed_when: superuser_result.rc != 0 and "already exists" not in superuser_result.stderr - tags: - - netbox-deploy - - superuser - -- name: Set NetBox superuser password - docker_compose: - project_src: "{{ netbox_install_dir }}" - command: "netbox /opt/netbox/netbox/manage.py shell -c \"from django.contrib.auth import get_user_model; User = get_user_model(); u = User.objects.get(username='{{ netbox_superuser_name }}'); u.set_password('{{ netbox_superuser_password }}'); u.save()\"" - tags: - - netbox-deploy - - superuser + - health-check \ No newline at end of file diff --git a/roles/netbox-deploy/vars/main.yml b/roles/netbox-deploy/vars/main.yml deleted file mode 100644 index e9e4496..0000000 --- a/roles/netbox-deploy/vars/main.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -# Variables for netbox-deploy role -netbox_install_dir: "/opt/netbox-docker" -netbox_repo_url: "https://github.com/netbox-community/netbox-docker.git" -netbox_repo_branch: "release" -netbox_repo_update: true -netbox_backup_dir: "/opt/netbox-backups" -netbox_data_dir: "/opt/netbox-data" diff --git a/roles/traefik/defaults/main.yml b/roles/traefik/defaults/main.yml new file mode 100644 index 0000000..71cbcae --- /dev/null +++ b/roles/traefik/defaults/main.yml @@ -0,0 +1,56 @@ +--- +# Default variables for traefik role + +# Traefik Configuration +traefik_install_dir: "/opt/traefik" +traefik_data_dir: "/opt/traefik-data" +traefik_config_dir: "/opt/traefik-config" + +# Traefik Image +traefik_image: "traefik:v3.0" +traefik_image_tag: "v3.0" + +# Traefik Configuration +traefik_dashboard_enabled: true +traefik_dashboard_port: 8080 +traefik_api_insecure: false +traefik_api_dashboard: true + +# ACME Configuration +traefik_acme_enabled: true +traefik_acme_email: "admin@example.com" +# traefik_acme_ca_server should be defined in group_vars or host_vars +traefik_acme_storage: "/data/acme.json" +traefik_acme_key_type: "RSA4096" + +# Entry Points +traefik_entrypoints: + web: + address: ":80" + websecure: + address: ":443" + traefik: + address: ":8080" + +# Providers +traefik_providers: + docker: + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false + network: "traefik" + +# Networks +traefik_networks: + - name: "traefik" + external: false + +# Logging +traefik_log_level: "INFO" +traefik_access_logs: false + +# Custom Root CA Configuration +# Users should define these in their group_vars or host_vars +# traefik_custom_ca_enabled: false +# traefik_custom_ca_url: "https://your-ca-server.com/root-ca.pem" +traefik_custom_ca_path: "/etc/traefik/custom-ca.pem" +traefik_custom_ca_verify_ssl: true diff --git a/roles/traefik/handlers/main.yml b/roles/traefik/handlers/main.yml new file mode 100644 index 0000000..5660e52 --- /dev/null +++ b/roles/traefik/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: restart traefik + community.docker.docker_compose_v2: + project_src: "{{ traefik_install_dir }}" + state: present + recreate: always + pull: always + tags: + - traefik-deploy + - handlers diff --git a/roles/traefik/meta/main.yml b/roles/traefik/meta/main.yml new file mode 100644 index 0000000..eb861a0 --- /dev/null +++ b/roles/traefik/meta/main.yml @@ -0,0 +1,19 @@ +--- +galaxy_info: + author: NetBox Ansible Deployment + description: Traefik reverse proxy with ACME TLS support + company: + license: MIT + min_ansible_version: 2.9 + platforms: + - name: Ubuntu + versions: + - focal + - jammy + galaxy_tags: + - traefik + - reverse-proxy + - tls + - acme + +dependencies: [] diff --git a/roles/traefik/tasks/main.yml b/roles/traefik/tasks/main.yml new file mode 100644 index 0000000..19d1323 --- /dev/null +++ b/roles/traefik/tasks/main.yml @@ -0,0 +1,101 @@ +--- +- name: Create Traefik installation directory + file: + path: "{{ traefik_install_dir }}" + state: directory + owner: root + group: root + mode: '0755' + tags: + - traefik-deploy + - directories + +- name: Create Traefik data directory + file: + path: "{{ traefik_data_dir }}" + state: directory + owner: root + group: root + mode: '0755' + tags: + - traefik-deploy + - directories + +- name: Create Traefik config directory + file: + path: "{{ traefik_config_dir }}" + state: directory + owner: root + group: root + mode: '0755' + tags: + - traefik-deploy + - directories + +- name: Create ACME storage file + file: + path: "{{ traefik_data_dir }}/acme.json" + state: touch + owner: root + group: root + mode: '0600' + tags: + - traefik-deploy + - config + +- name: Download custom root CA certificate + get_url: + url: "{{ traefik_custom_ca_url }}" + dest: "{{ traefik_custom_ca_path }}" + mode: '0644' + owner: root + group: root + validate_certs: "{{ traefik_custom_ca_verify_ssl }}" + timeout: 30 + when: traefik_custom_ca_enabled | default(false) and traefik_custom_ca_url | default('') != '' + notify: restart traefik + tags: + - traefik-deploy + - custom-ca + +- name: Create Traefik static configuration + template: + src: traefik.yml.j2 + dest: "{{ traefik_config_dir }}/traefik.yml" + mode: '0644' + notify: restart traefik + tags: + - traefik-deploy + - config + +- name: Create Traefik Docker Compose file + template: + src: docker-compose.yml.j2 + dest: "{{ traefik_install_dir }}/docker-compose.yml" + mode: '0644' + notify: restart traefik + tags: + - traefik-deploy + - config + +- name: Start Traefik services + community.docker.docker_compose_v2: + project_src: "{{ traefik_install_dir }}" + state: present + tags: + - traefik-deploy + - docker-start + +- name: Wait for Traefik to be ready + uri: + url: "https://traefik.{{ traefik_domain | default('localhost') }}/api/rawdata" + method: GET + status_code: 200 + ca_path: "{{ traefik_custom_ca_path }}" + register: traefik_ready + until: traefik_ready.status == 200 + retries: 30 + delay: 5 + tags: + - traefik-deploy + - health-check diff --git a/roles/traefik/templates/docker-compose.yml.j2 b/roles/traefik/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..fce0e8b --- /dev/null +++ b/roles/traefik/templates/docker-compose.yml.j2 @@ -0,0 +1,41 @@ +name: traefik + +services: + traefik: + image: "{{ traefik_image }}" + container_name: traefik + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - "{{ traefik_config_dir }}/traefik.yml:/etc/traefik/traefik.yml:ro" + - "{{ traefik_data_dir }}:/data" +{% if traefik_custom_ca_enabled | default(false) %} + - "{{ traefik_custom_ca_path }}:/etc/ssl/certs/custom-ca.pem:ro" +{% endif %} + networks: +{% for network in traefik_networks %} + - "{{ network.name }}" +{% endfor %} +{% if traefik_custom_ca_enabled | default(false) %} + environment: + - LEGO_CA_CERTIFICATES=/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/custom-ca.pem +{% endif %} + command: + - --configfile=/etc/traefik/traefik.yml + labels: + - "traefik.enable=true" + - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.{{ traefik_domain | default('localhost') }}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" + - "traefik.http.routers.traefik-dashboard.service=api@internal" + +networks: +{% for network in traefik_networks %} + {{ network.name }}: +{% if network.external | default(false) %} + external: true +{% else %} + driver: bridge +{% endif %} +{% endfor %} diff --git a/roles/traefik/templates/traefik.yml.j2 b/roles/traefik/templates/traefik.yml.j2 new file mode 100644 index 0000000..57e2d9f --- /dev/null +++ b/roles/traefik/templates/traefik.yml.j2 @@ -0,0 +1,86 @@ +# Traefik Static Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY + +# Global Configuration +global: + checkNewVersion: false + sendAnonymousUsage: false + +# API Configuration +api: + dashboard: true + insecure: false + +# Entry Points +entryPoints: +{% for name, config in traefik_entrypoints.items() %} + {{ name }}: + address: "{{ config.address }}" +{% if config.http is defined %} + http: +{% if config.http.tls is defined %} + tls: +{% if config.http.tls.certResolver is defined %} + certResolver: {{ config.http.tls.certResolver }} +{% endif %} +{% endif %} +{% if config.http.redirections is defined %} + redirections: +{% for redirection in config.http.redirections %} + entryPoint: + to: "{{ redirection.entrypoint.to }}" + scheme: "{{ redirection.entrypoint.scheme }}" + permanent: {{ redirection.entrypoint.permanent | lower }} +{% endfor %} +{% endif %} +{% endif %} +{% endfor %} + +# Providers +providers: +{% for name, config in traefik_providers.items() %} + {{ name }}: +{% for key, value in config.items() %} + {{ key }}: {{ value | to_json if value is mapping else (value | lower if value is boolean else value) }} +{% endfor %} +{% endfor %} + + +{% if traefik_acme_enabled %} +# Certificate Resolvers +certificatesResolvers: + letsencrypt: + acme: + email: "{{ traefik_acme_email }}" + storage: "{{ traefik_acme_storage }}" +{% if traefik_acme_ca_server is defined %} + caServer: "{{ traefik_acme_ca_server }}" +{% endif %} + httpChallenge: + entryPoint: web +{% endif %} + +# Logging +log: + level: "{{ traefik_log_level }}" + +{% if traefik_access_logs %} +accessLog: +{% if traefik_access_logs_format is defined %} + format: "{{ traefik_access_logs_format }}" +{% endif %} +{% if traefik_access_logs_fields is defined %} + fields: + defaultMode: "{{ traefik_access_logs_fields.defaultMode }}" +{% if traefik_access_logs_fields.headers is defined %} + headers: + defaultMode: "{{ traefik_access_logs_fields.headers.defaultMode }}" +{% if traefik_access_logs_fields.headers.names is defined %} + names: +{% for header, mode in traefik_access_logs_fields.headers.names.items() %} + "{{ header }}": "{{ mode }}" +{% endfor %} +{% endif %} +{% endif %} +{% endif %} +{% endif %}