diff --git a/ansible-playbook.yml b/ansible-playbook.yml index 6448d5a..b2f64a1 100755 --- a/ansible-playbook.yml +++ b/ansible-playbook.yml @@ -1,433 +1,47 @@ --- - hosts: internal-apps become: true - vars_files: + vars_files: - ./vars.yml tasks: - - name: Update Packages - ansible.builtin.dnf: - update_cache: yes - - - name: Install Packages - ansible.builtin.dnf: - name: - - vim - - podman - - fail2ban - - python3-libdnf5 - state: present - - - name: Create directories - file: - path: "{{ item }}" - state: directory - mode: '0755' - loop: "{{ directories }}" - - - name: Create Jellyfin directories - file: - path: "{{ item }}" - state: directory - mode: '0777' - loop: "{{ directories_jellyfin }}" - - - name: Create internal podman network - containers.podman.podman_network: - name: npm_internal - ip_range: 10.10.0.128/25 - subnet: 10.10.0.0/24 - gateway: 10.10.0.1 - state: present - - - name: Ensure WireGuard kernel module is loaded - ansible.builtin.modprobe: - name: wireguard - state: present - - - name: Persist WireGuard kernel module across reboots - ansible.builtin.copy: - dest: /etc/modules-load.d/wg-easy.conf - content: | - wireguard - nft_masq - mode: '0644' - - - name: Enable IP forwarding on host - ansible.posix.sysctl: - name: net.ipv4.ip_forward - value: '1' - sysctl_set: true - state: present - - - name: Enable source valid mark on host - ansible.posix.sysctl: - name: net.ipv4.conf.all.src_valid_mark - value: '1' - sysctl_set: true - state: present - reload: true - - - name: Raise vm.max_map_count for Wazuh indexer - ansible.posix.sysctl: - name: vm.max_map_count - value: '262144' - sysctl_set: true - state: present - reload: true - - - name: pull images + - name: Pull images containers.podman.podman_image: name: "{{ item }}" loop: "{{ images }}" - - name: Deploy Nginx Proxy Manager - containers.podman.podman_container: - image: "docker.io/jc21/nginx-proxy-manager:latest" - name: npm - restart_policy: always - recreate: true - state: started - ports: - - "80:80" - - "81:81" - - "443:443" - volumes: - - "/opt/apps/npm:/data:z" - - "/opt/apps/letsencrypt:/etc/letsencrypt:z" - network: - - npm_internal - ip: '10.10.0.19' - - - - name: Deploy Gitea - containers.podman.podman_container: - image: docker.gitea.com/gitea:nightly - name: gitea - env: - USER_UID: 1000 - USER_GID: 1000 - restart_policy: always - recreate: true - state: started - volumes: - - /opt/apps/gitea:/data:z - - /usr/share/zoneinfo/America/Denver:/etc/timezone:ro - - /etc/localtime:/etc/localtime:ro - network: - - npm_internal - ip: '10.10.0.25' - ports: - # - "3000:3000" - - "222:22" - - - name: Deploy Jellyfin - containers.podman.podman_container: - image: lscr.io/linuxserver/jellyfin:latest - name: jellyfin - env: - USER_UID: 1000 - USER_GID: 1000 - JELLYFIN_PublishedServerUrl: http://jellyfin.boxswapper.io - restart_policy: always - recreate: true - state: started - volumes: - - /opt/apps/jellyfin/config:/config:z - - /opt/apps/jellyfin/tvseries:/data/tvshows:z - - /opt/apps/jellyfin/movies:/data/movies:z - - /usr/share/zoneinfo/America/Denver:/etc/timezone:ro - - /etc/localtime:/etc/localtime:ro - network: - - npm_internal - ip: '10.10.0.30' - # ports: - # - 8096:8096 - # - 8920:8920 #optional - # - 7359:7359/udp #optional - # - 1900:1900/udp #optional - - # WireGuard Easy VPN (v15) — UI at 10.10.0.36:51821 on npm_internal, fronted by NPM - # (vpn.boxswapper.io -> http://10.10.0.36:51821). Login: admin / wireguard_ui_password from vars.yml. - # v15 init is ONE-TIME: the INIT_* envs below are consumed only when /etc/wireguard/wg-easy.db is ABSENT. - # Force a re-init (wipes clients + server keys): podman rm -f wg-easy && rm -rf /opt/apps/wireguard, then re-run. - # NAT from VPN clients to LAN/apps (10.10.1.0/24 -> 10.10.0.0/24) uses NFTABLES hooks — - # wg-easy's iptables-legacy hooks cannot init the nat table inside a podman netns - # ("Table does not exist"), which makes wg-quick up fail and every client API call - # 500 with "Unable to access interface". scripts/patch-wg-hooks.py rewrites the hooks - # to nft rules and the tasks below restart wg-easy to apply them. See the official - # wg-easy "Podman + nftables" tutorial. - # EXTERNAL ACCESS: router MUST forward UDP 51820 -> 192.168.0.98. Test from LTE, - # not your LAN — NAT hairpin breaks public-IP handshakes from inside the network. - - name: Deploy WireGuard Easy (wg-easy) - containers.podman.podman_container: - name: wg-easy - image: "{{ wireguard_image }}" - restart_policy: always - recreate: true - state: started - env: - INIT_ENABLED: "true" - INIT_USERNAME: "{{ wireguard_ui_username }}" - INIT_PASSWORD: "{{ wireguard_ui_password }}" - INIT_HOST: "{{ wireguard_host }}" - INIT_PORT: "{{ wireguard_port }}" - INIT_DNS: "{{ wireguard_default_dns }}" - INIT_IPV4_CIDR: "{{ wireguard_cidr }}" - INIT_IPV6_CIDR: "{{ wireguard_ipv6_cidr }}" - INIT_ALLOWED_IPS: "{{ wireguard_allowed_ips }}" - PORT: "{{ wireguard_ui_port }}" - INSECURE: "true" - DISABLE_VERSION_CHECK: "true" - ports: - - "{{ wireguard_port }}:{{ wireguard_port }}/udp" - - "{{ wireguard_ui_port }}:{{ wireguard_ui_port }}/tcp" - volumes: - - "/opt/apps/wireguard:/etc/wireguard:z" - - "/lib/modules:/lib/modules:ro" - cap_add: - - NET_ADMIN - - SYS_MODULE - - NET_RAW - sysctl: - net.ipv4.ip_forward: "1" - net.ipv4.conf.all.src_valid_mark: "1" - net.ipv6.conf.all.disable_ipv6: "0" - net.ipv6.conf.all.forwarding: "1" - net.ipv6.conf.default.forwarding: "1" - network: - - npm_internal - ip: '{{ wireguard_ip }}' - - - name: Copy wg-easy hook repair script - ansible.builtin.copy: - src: ./scripts/patch-wg-hooks.py - dest: /opt/apps/wireguard/patch-wg-hooks.py - owner: root - group: root - mode: '0755' - - - name: Replace iptables hooks with nftables (iptables-legacy broken in podman) - ansible.builtin.command: python3 /opt/apps/wireguard/patch-wg-hooks.py - register: wg_hooks_patch - changed_when: "'patched hooks' in wg_hooks_patch.stdout" - - - name: Restart wg-easy to apply nft hooks - ansible.builtin.command: podman restart wg-easy - when: "'patched hooks' in wg_hooks_patch.stdout" - - - name: Wait for WireGuard Easy web UI - ansible.builtin.wait_for: - host: '{{ wireguard_ip }}' - port: '{{ wireguard_ui_port }}' - delay: 5 - timeout: 120 - - # Wazuh SIEM (single-node: indexer + manager + dashboard) on npm_internal - # Dashboard is NOT exposed on host ports — NPM proxies wazuh.boxswapper.io to https://10.10.0.40:5601 - # DNS record must be grey-cloud (Cloudflare 523 lesson from vpn.boxswapper.io), cert via NPM - # Config is copied from ./wazuh-docker/config before first deploy - - name: Copy Wazuh config files to host - ansible.builtin.copy: - src: ./wazuh-docker/config/ - dest: /opt/apps/wazuh/config/ - owner: root - group: root - mode: preserve - force: true - - - name: Check for stale Wazuh certificate residue - ansible.builtin.stat: - path: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem - register: wazuh_cert_residue - - - name: Remove stale Wazuh certificate directories - ansible.builtin.file: - path: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs - state: absent - when: wazuh_cert_residue.stat.exists and wazuh_cert_residue.stat.isdir - - - name: Recreate Wazuh certificates directory - ansible.builtin.file: - path: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs - state: directory - mode: '0755' - owner: root - group: root - - - name: Generate Wazuh certificates - ansible.builtin.command: podman run --rm -e CERT_TOOL_VERSION=4.14 -v /opt/apps/wazuh/config/certs.yml:/config/certs.yml:z -v /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/:/certificates:z docker.io/wazuh/wazuh-certs-generator:0.0.3 - args: - creates: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem - - - name: Copy root CA as manager root CA - ansible.builtin.copy: - src: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem - dest: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca-manager.pem - remote_src: true - force: false - - - name: Deploy Wazuh indexer - containers.podman.podman_container: - name: wazuh.indexer - image: "docker.io/wazuh/wazuh-indexer:{{ wazuh_version }}" - hostname: wazuh.indexer - restart_policy: always - recreate: true - state: started - env: - OPENSEARCH_JAVA_OPTS: "-Xms1g -Xmx1g" - ulimit: - - "memlock=-1:-1" - - "nofile=65536:65536" - volumes: - - "wazuh-indexer-data:/var/lib/wazuh-indexer" - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/config/certs/root-ca.pem:z" - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.key:z" - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem:z" - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/admin.pem:/usr/share/wazuh-indexer/config/certs/admin.pem:z" - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/admin-key.pem:/usr/share/wazuh-indexer/config/certs/admin-key.pem:z" - - "/opt/apps/wazuh/config/wazuh_indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/config/opensearch.yml:z" - - "/opt/apps/wazuh/config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/config/opensearch-security/internal_users.yml:z" - network: - - npm_internal - ip: '{{ wazuh_indexer_ip }}' - - - name: Wait for Wazuh indexer to be ready - ansible.builtin.wait_for: - host: '{{ wazuh_indexer_ip }}' - port: 9200 - delay: 10 - timeout: 300 - - - name: Deploy Wazuh manager - containers.podman.podman_container: - name: wazuh.manager - image: "docker.io/wazuh/wazuh-manager:{{ wazuh_version }}" - hostname: wazuh.manager - restart_policy: always - recreate: true - state: started - env: - INDEXER_URL: "https://wazuh.indexer:9200" - INDEXER_USERNAME: "admin" - INDEXER_PASSWORD: "*WD*oW9m6iKf4hDovy7BbMmhLxC9D?Tv" - FILEBEAT_SSL_VERIFICATION_MODE: "full" - SSL_CERTIFICATE_AUTHORITIES: "/etc/ssl/root-ca.pem" - SSL_CERTIFICATE: "/etc/ssl/filebeat.pem" - SSL_KEY: "/etc/ssl/filebeat.key" - API_USERNAME: "wazuh-wui" - API_PASSWORD: "MyS3cr37P450r.*-" - ulimit: - - "memlock=-1:-1" - - "nofile=655360:655360" - ports: - - "1514:1514" - - "1515:1515" - - "514:514/udp" - - "55000:55000" - volumes: - - "wazuh_api_configuration:/var/ossec/api/configuration" - - "wazuh_etc:/var/ossec/etc" - - "wazuh_logs:/var/ossec/logs" - - "wazuh_queue:/var/ossec/queue" - - "wazuh_var_multigroups:/var/ossec/var/multigroups" - - "wazuh_integrations:/var/ossec/integrations" - - "wazuh_agentless:/var/ossec/agentless" - - "wazuh_active_response:/var/ossec/active-response/bin" - - "wazuh_wodles:/var/ossec/wodles" - - "filebeat_etc:/etc/filebeat" - - "filebeat_var:/var/lib/filebeat" - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca-manager.pem:/etc/ssl/root-ca.pem:z" - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.manager.pem:/etc/ssl/filebeat.pem:z" - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key:z" - - "/opt/apps/wazuh/config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf:z" - network: - - npm_internal - ip: '{{ wazuh_manager_ip }}' - - - name: Deploy Wazuh dashboard - containers.podman.podman_container: - name: wazuh.dashboard - image: "docker.io/wazuh/wazuh-dashboard:{{ wazuh_version }}" - hostname: wazuh.dashboard - restart_policy: always - recreate: true - state: started - env: - INDEXER_USERNAME: "admin" - INDEXER_PASSWORD: "*WD*oW9m6iKf4hDovy7BbMmhLxC9D?Tv" - WAZUH_API_URL: "https://wazuh.manager" - DASHBOARD_USERNAME: "kibanaserver" - DASHBOARD_PASSWORD: "kibanaserver" - API_USERNAME: "wazuh-wui" - API_PASSWORD: "MyS3cr37P450r.*-" - volumes: - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem:z" - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem:z" - - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem:z" - - "/opt/apps/wazuh/config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml:z" - - "/opt/apps/wazuh/config/wazuh_dashboard/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml:z" - - "wazuh-dashboard-config:/usr/share/wazuh-dashboard/data/wazuh/config" - - "wazuh-dashboard-custom:/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom" - network: - - npm_internal - ip: '{{ wazuh_dashboard_ip }}' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + roles: + - packages + - directories + - sysctl + - network + - wireguard + - npm + - gitea + - jellyfin + - wazuh #----N8N deployment, no GPU avail for deployment ------# - - # - name: Deploy n8n - # containers.podman.podman_container: - # image: docker.n8n.io/n8nio/n8n - # name: n8n - # env: - # N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: true - # N8N_RUNNERS_ENABLED: true - # restart_policy: always - # recreate: true - # state: started - # volumes: - # - /opt/apps/n8n/data:/home/node/.n8n:z - # - /opt/apps/n8n/files:/files:z - # - /usr/share/zoneinfo/America/Denver:/etc/timezone:ro - # - /etc/localtime:/etc/localtime:ro - - # network: - # - npm_internal - # ip: '10.10.0.35' - # # ports: - # # - 5678:5678 - - +# To enable n8n: +# 1. Uncomment the n8n image in vars.yml (images list) +# 2. Uncomment the n8n directory in vars.yml (directories list) +# 3. Create roles/n8n/tasks/main.yml with the n8n container block +# 4. Add "n8n" to the roles list above +# +# - name: Deploy n8n +# containers.podman.podman_container: +# image: docker.n8n.io/n8nio/n8n +# name: n8n +# env: +# N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: true +# N8N_RUNNERS_ENABLED: true +# restart_policy: always +# recreate: true +# state: started +# volumes: +# - /opt/apps/n8n/data:/home/node/.n8n:z +# - /opt/apps/n8n/files:/files:z +# - /usr/share/zoneinfo/America/Denver:/etc/timezone:ro +# - /etc/localtime:/etc/localtime:ro +# network: +# - npm_internal +# ip: '10.10.0.35' diff --git a/roles/directories/tasks/main.yml b/roles/directories/tasks/main.yml new file mode 100644 index 0000000..1553688 --- /dev/null +++ b/roles/directories/tasks/main.yml @@ -0,0 +1,14 @@ +--- +- name: Create directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: '0755' + loop: "{{ directories }}" + +- name: Create Jellyfin directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: '0777' + loop: "{{ directories_jellyfin }}" diff --git a/roles/gitea/tasks/main.yml b/roles/gitea/tasks/main.yml new file mode 100644 index 0000000..623fe93 --- /dev/null +++ b/roles/gitea/tasks/main.yml @@ -0,0 +1,20 @@ +--- +- name: Deploy Gitea + containers.podman.podman_container: + image: docker.gitea.com/gitea:nightly + name: gitea + env: + USER_UID: 1000 + USER_GID: 1000 + restart_policy: always + recreate: true + state: started + volumes: + - /opt/apps/gitea:/data:z + - /usr/share/zoneinfo/America/Denver:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + network: + - npm_internal + ip: '10.10.0.25' + ports: + - "222:22" diff --git a/roles/jellyfin/tasks/main.yml b/roles/jellyfin/tasks/main.yml new file mode 100644 index 0000000..e198c87 --- /dev/null +++ b/roles/jellyfin/tasks/main.yml @@ -0,0 +1,21 @@ +--- +- name: Deploy Jellyfin + containers.podman.podman_container: + image: lscr.io/linuxserver/jellyfin:latest + name: jellyfin + env: + USER_UID: 1000 + USER_GID: 1000 + JELLYFIN_PublishedServerUrl: http://jellyfin.boxswapper.io + restart_policy: always + recreate: true + state: started + volumes: + - /opt/apps/jellyfin/config:/config:z + - /opt/apps/jellyfin/tvseries:/data/tvshows:z + - /opt/apps/jellyfin/movies:/data/movies:z + - /usr/share/zoneinfo/America/Denver:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + network: + - npm_internal + ip: '10.10.0.30' diff --git a/roles/network/tasks/main.yml b/roles/network/tasks/main.yml new file mode 100644 index 0000000..64b1570 --- /dev/null +++ b/roles/network/tasks/main.yml @@ -0,0 +1,8 @@ +--- +- name: Create internal podman network + containers.podman.podman_network: + name: npm_internal + ip_range: 10.10.0.128/25 + subnet: 10.10.0.0/24 + gateway: 10.10.0.1 + state: present diff --git a/roles/npm/tasks/main.yml b/roles/npm/tasks/main.yml new file mode 100644 index 0000000..82d148d --- /dev/null +++ b/roles/npm/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Nginx Proxy Manager + containers.podman.podman_container: + image: "docker.io/jc21/nginx-proxy-manager:latest" + name: npm + restart_policy: always + recreate: true + state: started + ports: + - "80:80" + - "81:81" + - "443:443" + volumes: + - "/opt/apps/npm:/data:z" + - "/opt/apps/letsencrypt:/etc/letsencrypt:z" + network: + - npm_internal + ip: '10.10.0.19' diff --git a/roles/packages/tasks/main.yml b/roles/packages/tasks/main.yml new file mode 100644 index 0000000..ce4754b --- /dev/null +++ b/roles/packages/tasks/main.yml @@ -0,0 +1,13 @@ +--- +- name: Update Packages + ansible.builtin.dnf: + update_cache: yes + +- name: Install Packages + ansible.builtin.dnf: + name: + - vim + - podman + - fail2ban + - python3-libdnf5 + state: present diff --git a/roles/sysctl/tasks/main.yml b/roles/sysctl/tasks/main.yml new file mode 100644 index 0000000..34f94c2 --- /dev/null +++ b/roles/sysctl/tasks/main.yml @@ -0,0 +1,23 @@ +--- +- name: Enable IP forwarding on host + ansible.posix.sysctl: + name: net.ipv4.ip_forward + value: '1' + sysctl_set: true + state: present + +- name: Enable source valid mark on host + ansible.posix.sysctl: + name: net.ipv4.conf.all.src_valid_mark + value: '1' + sysctl_set: true + state: present + reload: true + +- name: Raise vm.max_map_count for Wazuh indexer + ansible.posix.sysctl: + name: vm.max_map_count + value: '262144' + sysctl_set: true + state: present + reload: true diff --git a/roles/wazuh/tasks/main.yml b/roles/wazuh/tasks/main.yml new file mode 100644 index 0000000..80ff1a2 --- /dev/null +++ b/roles/wazuh/tasks/main.yml @@ -0,0 +1,157 @@ +--- +# Wazuh SIEM (single-node: indexer + manager + dashboard) on npm_internal +# Dashboard is NOT exposed on host ports — NPM proxies wazuh.boxswapper.io to https://10.10.0.40:5601 +# DNS record must be grey-cloud (Cloudflare 523 lesson from vpn.boxswapper.io), cert via NPM +# Config is copied from ./wazuh-docker/config before first deploy + +- name: Copy Wazuh config files to host + ansible.builtin.copy: + src: ./wazuh-docker/config/ + dest: /opt/apps/wazuh/config/ + owner: root + group: root + mode: preserve + force: true + +- name: Check for stale Wazuh certificate residue + ansible.builtin.stat: + path: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem + register: wazuh_cert_residue + +- name: Remove stale Wazuh certificate directories + ansible.builtin.file: + path: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs + state: absent + when: wazuh_cert_residue.stat.exists and wazuh_cert_residue.stat.isdir + +- name: Recreate Wazuh certificates directory + ansible.builtin.file: + path: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs + state: directory + mode: '0755' + owner: root + group: root + +- name: Generate Wazuh certificates + ansible.builtin.command: > + podman run --rm + -e CERT_TOOL_VERSION=4.14 + -v /opt/apps/wazuh/config/certs.yml:/config/certs.yml:z + -v /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/:/certificates:z + docker.io/wazuh/wazuh-certs-generator:0.0.3 + args: + creates: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem + +- name: Copy root CA as manager root CA + ansible.builtin.copy: + src: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem + dest: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca-manager.pem + remote_src: true + force: false + +- name: Deploy Wazuh indexer + containers.podman.podman_container: + name: wazuh.indexer + image: "docker.io/wazuh/wazuh-indexer:{{ wazuh_version }}" + hostname: wazuh.indexer + restart_policy: always + recreate: true + state: started + env: + OPENSEARCH_JAVA_OPTS: "-Xms1g -Xmx1g" + ulimit: + - "memlock=-1:-1" + - "nofile=65536:65536" + volumes: + - "wazuh-indexer-data:/var/lib/wazuh-indexer" + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/config/certs/root-ca.pem:z" + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.key:z" + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem:z" + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/admin.pem:/usr/share/wazuh-indexer/config/certs/admin.pem:z" + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/admin-key.pem:/usr/share/wazuh-indexer/config/certs/admin-key.pem:z" + - "/opt/apps/wazuh/config/wazuh_indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/config/opensearch.yml:z" + - "/opt/apps/wazuh/config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/config/opensearch-security/internal_users.yml:z" + network: + - npm_internal + ip: '{{ wazuh_indexer_ip }}' + +- name: Wait for Wazuh indexer to be ready + ansible.builtin.wait_for: + host: '{{ wazuh_indexer_ip }}' + port: 9200 + delay: 10 + timeout: 300 + +- name: Deploy Wazuh manager + containers.podman.podman_container: + name: wazuh.manager + image: "docker.io/wazuh/wazuh-manager:{{ wazuh_version }}" + hostname: wazuh.manager + restart_policy: always + recreate: true + state: started + env: + INDEXER_URL: "https://wazuh.indexer:9200" + INDEXER_USERNAME: "admin" + INDEXER_PASSWORD: "*WD*oW9m6iKf4hDovy7BbMmhLxC9D?Tv" + FILEBEAT_SSL_VERIFICATION_MODE: "full" + SSL_CERTIFICATE_AUTHORITIES: "/etc/ssl/root-ca.pem" + SSL_CERTIFICATE: "/etc/ssl/filebeat.pem" + SSL_KEY: "/etc/ssl/filebeat.key" + API_USERNAME: "wazuh-wui" + API_PASSWORD: "MyS3cr37P450r.*-" + ulimit: + - "memlock=-1:-1" + - "nofile=655360:655360" + ports: + - "1514:1514" + - "1515:1515" + - "514:514/udp" + - "55000:55000" + volumes: + - "wazuh_api_configuration:/var/ossec/api/configuration" + - "wazuh_etc:/var/ossec/etc" + - "wazuh_logs:/var/ossec/logs" + - "wazuh_queue:/var/ossec/queue" + - "wazuh_var_multigroups:/var/ossec/var/multigroups" + - "wazuh_integrations:/var/ossec/integrations" + - "wazuh_agentless:/var/ossec/agentless" + - "wazuh_active_response:/var/ossec/active-response/bin" + - "wazuh_wodles:/var/ossec/wodles" + - "filebeat_etc:/etc/filebeat" + - "filebeat_var:/var/lib/filebeat" + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca-manager.pem:/etc/ssl/root-ca.pem:z" + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.manager.pem:/etc/ssl/filebeat.pem:z" + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key:z" + - "/opt/apps/wazuh/config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf:z" + network: + - npm_internal + ip: '{{ wazuh_manager_ip }}' + +- name: Deploy Wazuh dashboard + containers.podman.podman_container: + name: wazuh.dashboard + image: "docker.io/wazuh/wazuh-dashboard:{{ wazuh_version }}" + hostname: wazuh.dashboard + restart_policy: always + recreate: true + state: started + env: + INDEXER_USERNAME: "admin" + INDEXER_PASSWORD: "*WD*oW9m6iKf4hDovy7BbMmhLxC9D?Tv" + WAZUH_API_URL: "https://wazuh.manager" + DASHBOARD_USERNAME: "kibanaserver" + DASHBOARD_PASSWORD: "kibanaserver" + API_USERNAME: "wazuh-wui" + API_PASSWORD: "MyS3cr37P450r.*-" + volumes: + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem:z" + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem:z" + - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem:z" + - "/opt/apps/wazuh/config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml:z" + - "/opt/apps/wazuh/config/wazuh_dashboard/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml:z" + - "wazuh-dashboard-config:/usr/share/wazuh-dashboard/data/wazuh/config" + - "wazuh-dashboard-custom:/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom" + network: + - npm_internal + ip: '{{ wazuh_dashboard_ip }}' diff --git a/roles/wireguard/tasks/main.yml b/roles/wireguard/tasks/main.yml new file mode 100644 index 0000000..64f62f8 --- /dev/null +++ b/roles/wireguard/tasks/main.yml @@ -0,0 +1,89 @@ +--- +- name: Ensure WireGuard kernel module is loaded + ansible.builtin.modprobe: + name: wireguard + state: present + +- name: Persist WireGuard kernel module across reboots + ansible.builtin.copy: + dest: /etc/modules-load.d/wg-easy.conf + content: | + wireguard + nft_masq + mode: '0644' + +# WireGuard Easy VPN (v15) — UI at 10.10.0.36:51821 on npm_internal, fronted by NPM +# (vpn.boxswapper.io -> http://10.10.0.36:51821). Login: admin / wireguard_ui_password from vars.yml. +# v15 init is ONE-TIME: the INIT_* envs below are consumed only when /etc/wireguard/wg-easy.db is ABSENT. +# Force a re-init (wipes clients + server keys): podman rm -f wg-easy && rm -rf /opt/apps/wireguard, then re-run. +# NAT from VPN clients to LAN/apps (10.10.1.0/24 -> 10.10.0.0/24) uses NFTABLES hooks — +# wg-easy's iptables-legacy hooks cannot init the nat table inside a podman netns +# ("Table does not exist"), which makes wg-quick up fail and every client API call +# 500 with "Unable to access interface". scripts/patch-wg-hooks.py rewrites the hooks +# to nft rules and the tasks below restart wg-easy to apply them. See the official +# wg-easy "Podman + nftables" tutorial. +# EXTERNAL ACCESS: router MUST forward UDP 51820 -> 192.168.0.98. Test from LTE, +# not your LAN — NAT hairpin breaks public-IP handshakes from inside the network. +- name: Deploy WireGuard Easy (wg-easy) + containers.podman.podman_container: + name: wg-easy + image: "{{ wireguard_image }}" + restart_policy: always + recreate: true + state: started + env: + INIT_ENABLED: "true" + INIT_USERNAME: "{{ wireguard_ui_username }}" + INIT_PASSWORD: "{{ wireguard_ui_password }}" + INIT_HOST: "{{ wireguard_host }}" + INIT_PORT: "{{ wireguard_port }}" + INIT_DNS: "{{ wireguard_default_dns }}" + INIT_IPV4_CIDR: "{{ wireguard_cidr }}" + INIT_IPV6_CIDR: "{{ wireguard_ipv6_cidr }}" + INIT_ALLOWED_IPS: "{{ wireguard_allowed_ips }}" + PORT: "{{ wireguard_ui_port }}" + INSECURE: "true" + DISABLE_VERSION_CHECK: "true" + ports: + - "{{ wireguard_port }}:{{ wireguard_port }}/udp" + - "{{ wireguard_ui_port }}:{{ wireguard_ui_port }}/tcp" + volumes: + - "/opt/apps/wireguard:/etc/wireguard:z" + - "/lib/modules:/lib/modules:ro" + cap_add: + - NET_ADMIN + - SYS_MODULE + - NET_RAW + sysctl: + net.ipv4.ip_forward: "1" + net.ipv4.conf.all.src_valid_mark: "1" + net.ipv6.conf.all.disable_ipv6: "0" + net.ipv6.conf.all.forwarding: "1" + net.ipv6.conf.default.forwarding: "1" + network: + - npm_internal + ip: '{{ wireguard_ip }}' + +- name: Copy wg-easy hook repair script + ansible.builtin.copy: + src: ./scripts/patch-wg-hooks.py + dest: /opt/apps/wireguard/patch-wg-hooks.py + owner: root + group: root + mode: '0755' + +- name: Replace iptables hooks with nftables (iptables-legacy broken in podman) + ansible.builtin.command: python3 /opt/apps/wireguard/patch-wg-hooks.py + register: wg_hooks_patch + changed_when: "'patched hooks' in wg_hooks_patch.stdout" + +- name: Restart wg-easy to apply nft hooks + ansible.builtin.command: podman restart wg-easy + when: "'patched hooks' in wg_hooks_patch.stdout" + +- name: Wait for WireGuard Easy web UI + ansible.builtin.wait_for: + host: '{{ wireguard_ip }}' + port: '{{ wireguard_ui_port }}' + delay: 5 + timeout: 120