Private
Public Access
2
0
This commit is contained in:
2026-01-19 15:06:34 +03:00
parent 278d1785cc
commit 3d9fe37983
59 changed files with 1084 additions and 9684 deletions

146
README.md Normal file
View File

@@ -0,0 +1,146 @@
# BYOW - Build Your Own WAF
Ce rôle Ansible déploie un WAF basé sur Caddy, Coraza et les OWASP Core Security Rules.
## Principes
### Go
Caddy, Coraza, le module Coraza pour Caddy et l'outil `xcaddy` sont
développés avec le langage Go.
L'outil `xcaddy` a besoin d'une version récente des outils de
développement Go. C'est pourquoi le rôle installe Go depuis
le site de téléchargement officiel et non via le gestionnaire
de paquets de la distribution Linux.
Go est installé dans le répertoire `/usr/local/go-1.25.5/go` et
un lien symbolique est créé de `/usr/local/bin/go` vers
`/usr/local/go-1.25.5/go/bin/go`.
### Caddy
Caddy est un serveur Web et reverse proxy HTTP puissant et versatile.
Étant développé en Go, son déploiement prend la forme d'un simble
binaire exécutable qui contient toutes les librairies requises. Ce
programme est construit avec l'outil `xcaddy` puis copié dans le
fichier `/usr/local/bin/caddy`. La ligne de commande utilisée pour
construire ce binaire est enregsitrée dans le fichier
`/usr/local/bin/.caddy.buil`.
Le fichier de configuration de caddy utilise le format `Caddyfile`.
Il est généré automatiquement à partir des paramètres du rôle puis
enregistré sous `/etc/caddy/Caddyfile`.
### Coraza
Coraza est directement inclus dans le binaire `caddy`
### Module Coraza pour Caddy
### xcaddy
qui permet de lier tous les modules Go requis
pour le WAF
### Plugins OWASP CRS
(À ne pas confondre avec les plugins Coraza.)
Voir: https://github.com/coreruleset/plugin-registry
- name: "drupal-rule-exclusions"
- name: "wordpress-rule-exclusions"
- name: "nextcloud-rule-exclusions"
- name: "dokuwiki-rule-exclusions"
- name: "phpmyadmin-rule-exclusions"
- name: "roundcube-rule-exclusions"
- name: "sogo-rule-exclusions"
## Paramètres du rôle Ansible
## TODO
Fonctionnalités déjà implémentées:
- Multisite (cavec configuration des CRS par site)
- Redirection vers nom canonique
- Rediction HTTP vers HTTPS
- Certificats SSL (autmotiques via Let's Encrypt et customs)
- OWASP CRS
- Redirect (SSL et www et autres)
- Filtrage par:
- adresses IP
- réseaux IP
- Antibot par:
- défi JavaScript
- Passive backend checks
- Compression Gzip et zstd
Fomctionnalités à ajouter:
- Active backend checks
- Antibot par:
- Cookie
- Captcha
- reCAPTCHA
- hCaptcha
- Turnstile
- mCaptcha
- Changement de format de configuration: Caddyfile -> JSON
- Mise en œuvre d'un cluster Redis (ou Consul) pour le partage de informations entre les WAF d'un cluster
- Mieux tester le configuration des CRS
- Mode détection sans blocage
- Filtrage par:
- rDNS
- DNSBL
- ASN
- User-Agent (robots, crawlers, IA bots)
- méthode HTTP
- taille
- URI patterns
- pays
- (ip:danmeuk-tor-exit, ua:mitchellkrogza-bad-user-agents, /etc/hapee-1.9/blacklist.acl)
- Gestion CORS
- Client cache
- Proxy cache
- Filtrage par pays
- Compression Brotli
- En-têtes de sécurité:
- HSTS
- CSP
- X-Frame-Options
- X-Content-Type-Options
- Referrer Policy
- Permissions Policy
- Keep Headers
- Cookie Flags
- Custom headers
- Reverse scan
- Robots.txt (DarkVisitors API/Community Lists/Custom URLs/Manual Rules)
- Sessions
- Whitelist
- Bad behavior
- Auth
- Anti DoDS
- Greylist
- HTML injection
- Metrics (Elasticsearch + Metabase)
- Static files et FastCGI pour PHP
## Ressources
- [Documentation officielle Caddy](https://caddyserver.com/docs/)
- [Site Officiel de Coraza](https://coraza.io)
- [Coraza WAF Caddy module](https://github.com/corazawaf/coraza-caddy)
- [Documentation AWOSP CRS](https://coreruleset.org/docs/)
Modules
- []()
- []()

40
ansible/README.md Normal file
View File

@@ -0,0 +1,40 @@
### Installation d'Ansible avec pipx
1. Désinstaller *Ansible* et *ansible-lint* s'ils sont déjà installés. Par
exemple avec *Homebrew*:
```
brew uninstall ansible ansible-lint
```
1. Installer *pipx*. Par exemple avec *Homebrew*:
```
brew install pipx
```
1. Inscrire le chemin de recherche des exécutables de *pipx* dans les
scripts d'ouverture de session (fermer le shell et en ouvrir un nouveau
pour activer ce chemin de recherche):
```
pipx ensurepath
```
1. Installer *Ansible* (version 11, et au moins version core 2.16.9, pour
être compatible avec *Mitogen*) dans un environnement isolé (qui sera
nommé `ansible`) puis injecter *ansible-lint* et les librairies requises
dans cet environnement:
```
pipx install ansible==12.3.0 --include-deps
pipx inject ansible ansible-lint --include-apps
pipx inject ansible requests
pipx inject ansible pip-system-certs
pipx inject ansible passlib # pour |password_hash sur macOS (et autres)
pipx inject ansible jmespath # pour |query_result
pipx inject ansible pytz # pour plugin inventory auto
pipx inject ansible pynetbox # pour collection netbox.netbox
pipx inject ansible netaddr # pour les rôles CheckMK
pipx inject ansible mitogen # optionnel, pour utiliser Mitogen
```
1. Vérifier l'environnement:
```
pipx list --include-injected
ansible --version -> ansible [core 2.16.9]
ansible-lint --version -> ansible-lint 24.7.0
```

View File

@@ -1,3 +1,17 @@
# Example of a ansible_local.env file that is used to customize the local
# operation of Ansible with environment variables.
# Usage:
# 1. Copy ansible/ansible_local.env.example to ansible/ansible_local.env or create a new version.
# 2. Customize the contents of the ansible_local.env file, there can only be definitions of environment variables.
# cf.: https://docs.ansible.com/ansible/latest/reference_appendices/config.html
# 3. The run_ansible.sh script will automatically source the ansible/ansible_local.env file if it exists before running ansible.
# The ansible_local.env file is ignored by Git (present in.gitignore).
# Example of using the ANSIBLE_STRATEGY_PLUGINS and ANSIBLE_STRATEGY environment variables to enable Mitogen (speeds up the execution of Ansible playbooks by a factor of 2 to 10).
# cf.: https://mitogen.networkgenomics.com/ansible_detailed.html
ANSIBLE_STRATEGY_PLUGINS=/home/seb/.local/share/pipx/venvs/ansible/lib/python3.13/site-packages/ansible_mitogen/plugins/strategy ANSIBLE_STRATEGY_PLUGINS=/home/seb/.local/share/pipx/venvs/ansible/lib/python3.13/site-packages/ansible_mitogen/plugins/strategy
export ANSIBLE_STRATEGY_PLUGINS export ANSIBLE_STRATEGY_PLUGINS

View File

@@ -1,20 +1,23 @@
linux: linux:
vars: vars:
ansible_user: "root" ansible_user: "root"
ubuntu24:
vars:
ansible_python_interpreter: "/usr/bin/python3.12" ansible_python_interpreter: "/usr/bin/python3.12"
proxy:
hosts:
proxy1:
www:
hosts:
www1:
www2:
www3:
waf: waf:
hosts: hosts:
waf1: waf1:
waf2: waf2:
waf3: waf3:
mysql:
hosts:
mysql1:
www:
hosts:
www1:
www2:
www3:

View File

@@ -1,6 +1,7 @@
#!/bin/sh #!/bin/sh
multipass list --format=json |jq '{ multipass list --format=json |jq '{
_meta: {hostvars: [.list[] | {(.name): {ansible_host: .ipv4[-1]}}] | add}, _meta: {hostvars: [.list[] | {(.name): {ansible_host: .ipv4[0]}}] | add},
linux: {hosts: [.list[].name]} linux: {hosts: [.list[].name]},
ubuntu24: {hosts: [.list[].name]},
}' }'

View File

@@ -4,12 +4,12 @@
- "vars/common.yml" - "vars/common.yml"
- "vars/secrets.yml" - "vars/secrets.yml"
vars: vars:
certs_ca_passphrase: "{{ secrets.ca.passphrase }}" local_ca_certs_ca_passphrase: "{{ secrets.ca.passphrase }}"
certs_default_country_name: "{{ common.certificates.country_name }}" local_ca_certs_default_country_name: "{{ common.certificates.country_name }}"
certs_default_locality_name: "{{ common.certificates.locality_name }}" local_ca_certs_default_locality_name: "{{ common.certificates.locality_name }}"
certs_default_organization_name: "{{ common.certificates.organization_name }}" local_ca_certs_default_organization_name: "{{ common.certificates.organization_name }}"
certs_default_email_address: "{{ common.certificates.email_address }}" local_ca_certs_default_email_address: "{{ common.certificates.email_address }}"
certs_list: local_ca_certs_list:
- cn: "test.lab" - cn: "test.lab"
domains: domains:
- "DNS:test.lab" - "DNS:test.lab"

View File

@@ -24,20 +24,6 @@
tags: tags:
- "local_ca" - "local_ca"
- name: Proxy server
hosts: proxy
gather_facts: false
vars_files:
- "vars/common.yml"
- "vars/secrets.yml"
vars:
squid_authorized_networks:
- "{{ ansible_default_ipv4.network }}/24"
roles:
- "squid"
tags:
- "proxy"
- name: Common - name: Common
hosts: all hosts: all
gather_facts: false gather_facts: false
@@ -45,23 +31,17 @@
- "vars/common.yml" - "vars/common.yml"
- "vars/secrets.yml" - "vars/secrets.yml"
vars: vars:
common_proxy_server: "{{ hostvars['proxy1'].ansible_default_ipv4.address }}:3128" common_default_packages:
- "net-tools"
- "htop"
- "aptitude"
- "screen"
- "tcpdump"
roles: roles:
- "common" - "common"
tags: tags:
- "common" - "common"
- name: HTTP Servers
hosts: www
gather_facts: false
vars_files:
- "vars/common.yml"
- "vars/secrets.yml"
roles:
- "www"
tags:
- "www"
- name: Web Application Firewalls (install) - name: Web Application Firewalls (install)
hosts: waf hosts: waf
gather_facts: false gather_facts: false
@@ -69,12 +49,12 @@
- "vars/common.yml" - "vars/common.yml"
- "vars/secrets.yml" - "vars/secrets.yml"
vars: vars:
certs_ca_passphrase: "{{ secrets.ca.passphrase }}" local_ca_certs_ca_passphrase: "{{ secrets.ca.passphrase }}"
certs_default_country_name: "{{ common.certificates.country_name }}" local_ca_certs_default_country_name: "{{ common.certificates.country_name }}"
certs_default_locality_name: "{{ common.certificates.locality_name }}" local_ca_certs_default_locality_name: "{{ common.certificates.locality_name }}"
certs_default_organization_name: "{{ common.certificates.organization_name }}" local_ca_certs_default_organization_name: "{{ common.certificates.organization_name }}"
certs_default_email_address: "{{ common.certificates.email_address }}" local_ca_certs_default_email_address: "{{ common.certificates.email_address }}"
certs_list: local_ca_certs_list:
# Consul # Consul
- cn: "server.{{ common.consul.datacenter }}.{{ common.consul.domain }}" - cn: "server.{{ common.consul.datacenter }}.{{ common.consul.domain }}"
domains: domains:
@@ -89,31 +69,132 @@
consul_domain: "{{ common.consul.domain }}" consul_domain: "{{ common.consul.domain }}"
consul_datacenter: "{{ common.consul.datacenter }}" consul_datacenter: "{{ common.consul.datacenter }}"
consul_secret_key: "{{ secrets.consul.secret_key }}" consul_secret_key: "{{ secrets.consul.secret_key }}"
consul_nodes: consul_servers: ["waf1", "waf2", "waf3"]
- "waf1" consul_server: true
- "waf2" consul_services:
- "waf3" - name: "in"
# FIXME: -> Cady modules port: 443
waf_install_cluster: true check_http: "http://{{ ansible_default_ipv4.address }}:80/"
vip_script_user: "caddy" # keepalived_script_user: "caddy"
vip_monitored_process: "caddy" # keepalived_monitored_process: "caddy"
vip_vrrp_password: "{{ secrets.keepalived.secret }}" # keepalived_vrrp_password: "{{ secrets.keepalived.secret }}"
vip_vrrp_ips: # keepalived_vrrp_ips:
- master: "waf1" # - master: "waf1"
virtual_router_id: 11 # virtual_router_id: 251
addr: "{{ {{ ansible_default_ipv4.address }} | split('.')[0-3] | join('.') }}.250" # addr: "{{ ansible_default_ipv4.address | regex_replace('\\.[0-9]+$', '') }}.251/24"
- master: "waf2" # - master: "waf2"
virtual_router_id: 12 # virtual_router_id: 252
addr: "{{ {{ ansible_default_ipv4.address }} | split('.')[0-3] | join('.') }}.251" # addr: "{{ ansible_default_ipv4.address | regex_replace('\\.[0-9]+$', '') }}.252/24"
- master: "waf3" # - master: "waf3"
virtual_router_id: 13 # virtual_router_id: 253
addr: "{{ {{ ansible_default_ipv4.address }} | split('.')[0-3] | join('.') }}.252" # addr: "{{ ansible_default_ipv4.address | regex_replace('\\.[0-9]+$', '') }}.253/24"
# # Multipass network doesn't seem to support well multicast.
# keepalived_unicast_peers:
# - "{{ hostvars['waf1'].ansible_default_ipv4.address }}"
# - "{{ hostvars['waf2'].ansible_default_ipv4.address }}"
# - "{{ hostvars['waf3'].ansible_default_ipv4.address }}"
unbound_listen_interfaces:
- "{{ ansible_default_ipv4.address }}"
unbound_stub_zones:
- name: "{{ common.consul.datacenter }}.{{ common.consul.domain }}"
stub_to: "127.0.0.1@8600"
waf_config_sites: "{{ common.sites }}" waf_config_sites: "{{ common.sites }}"
ovh_ldp_cluster: "gra2.logs.ovh.com"
ovh_ldp_token: ¨{{ secrets.ovh_ldp.token }}
roles: roles:
- "certs" - "local_ca_certs"
- "consul" - "consul"
- "waf_install" - "waf_install"
- "vip" # - "keepalived"
- "waf_config" - "waf_config"
- "unbound"
- "ovh_ldp"
tags: tags:
- "waf" - "waf"
- name: MySQL servers
hosts: mysql
gather_facts: false
vars_files:
- "vars/common.yml"
- "vars/secrets.yml"
vars:
local_ca_certs_ca_passphrase: "{{ secrets.ca.passphrase }}"
local_ca_certs_default_country_name: "{{ common.certificates.country_name }}"
local_ca_certs_default_locality_name: "{{ common.certificates.locality_name }}"
local_ca_certs_default_organization_name: "{{ common.certificates.organization_name }}"
local_ca_certs_default_email_address: "{{ common.certificates.email_address }}"
local_ca_certs_list:
- cn: "{{ ansible_fqdn }}"
domains:
- "DNS:{{ ansible_fqdn }}"
- "DNS:{{ ansible_hostname }}"
- "IP:{{ ansible_default_ipv4.address }}"
mysql_databases:
- "wordpress"
mysql_users:
- name: "wordpress"
password: "{{ secrets.wordpress.mysql_password }}"
priv: "wordpress.*:ALL"
host: "{{ ansible_default_ipv4.network }}/255.255.255.0"
consul_domain: "{{ common.consul.domain }}"
consul_datacenter: "{{ common.consul.datacenter }}"
consul_secret_key: "{{ secrets.consul.secret_key }}"
consul_key_file: "/etc/ssl/private/{{ ansible_fqdn }}.key"
consul_cert_file: "/etc/ssl/certs/{{ ansible_fqdn }}.crt"
consul_servers: ["waf1", "waf2", "waf3"]
consul_services:
- name: "test-lab-mysql"
port: 3306
check_tcp: "{{ ansible_default_ipv4.address }}:3306"
roles:
- "local_ca_certs"
- "mysql"
- "consul"
tags:
- "mysql"
- name: HTTP Servers
hosts: www
gather_facts: false
vars_files:
- "vars/common.yml"
- "vars/secrets.yml"
vars:
local_ca_certs_ca_passphrase: "{{ secrets.ca.passphrase }}"
local_ca_certs_default_country_name: "{{ common.certificates.country_name }}"
local_ca_certs_default_locality_name: "{{ common.certificates.locality_name }}"
local_ca_certs_default_organization_name: "{{ common.certificates.organization_name }}"
local_ca_certs_default_email_address: "{{ common.certificates.email_address }}"
local_ca_certs_list:
- cn: "{{ ansible_fqdn }}"
domains:
- "DNS:{{ ansible_fqdn }}"
- "DNS:{{ ansible_hostname }}"
- "IP:{{ ansible_default_ipv4.address }}"
wordpress_site: "test.lab"
wordpress_url: "test.lab"
wordpress_site_title: "Lab Test"
wordpress_admin_user: "spock"
wordpress_admin_password: "{{ secrets.wordpress.admin_password }}"
wordpress_admin_email: "seb@itik.fr"
wordpress_themes: ["twentytwentyfive"]
wordpress_active_theme: "twentytwentyfive"
wordpress_db_host: "mysql1"
wordpress_db_pass: "{{ secrets.wordpress.mysql_password }}"
consul_domain: "{{ common.consul.domain }}"
consul_datacenter: "{{ common.consul.datacenter }}"
consul_secret_key: "{{ secrets.consul.secret_key }}"
consul_key_file: "/etc/ssl/private/{{ ansible_fqdn }}.key"
consul_cert_file: "/etc/ssl/certs/{{ ansible_fqdn }}.crt"
consul_servers: ["waf1", "waf2", "waf3"]
consul_services:
- name: "test-lab-www"
port: 80
check_http: "http://localhost:80/"
roles:
- "local_ca_certs"
- "wordpress"
- "consul"
tags:
- "www"

View File

@@ -1,19 +0,0 @@
# Defaults
certs_default_country_name: null
certs_default_locality_name: null
certs_default_organization_name: null
certs_default_email_address: null
certs_default_not_after: "+365d" # valid for one year
certs_default_not_before: "-1d" # valid since yesterday
# Required
certs_ca_passphrase: null
certs_list: {}
# - cn: <string>
# domains: <array of strings>
# country_name: <string>
# locality_name: <string>
# organization_name: <string>
# email_address: <string>
# not_after: <string>
# not_before: <string>

View File

@@ -1,42 +0,0 @@
- name: Create private key for new certificate
community.crypto.openssl_privatekey:
path: "/etc/ssl/private/{{ cert.cn }}.key"
mode: "0640"
group: "ssl-cert"
- name: Create CSR for new certificate
community.crypto.openssl_csr:
path: "/etc/ssl/certs/{{ cert.cn }}.csr"
privatekey_path: "/etc/ssl/private/{{ cert.cn }}.key"
common_name: "{{ cert.cn }}"
subject_alt_name: "{{ cert.domains }}"
country_name: "{{ cert.country_name | default(certs_default_country_name) | mandatory }}"
locality_name: "{{ cert.locality_name | default(certs_default_locality_name) | mandatory }}"
organization_name: "{{ cert.organization_name | default(certs_default_organization_name) | mandatory }}"
email_address: "{{ cert.email_address | default(certs_default_email_address) | mandatory }}"
return_content: true
register: certs_csr
- name: Sign certificate with our CA
delegate_to: "localhost"
# noqa: no-handler
when: "certs_csr.changed"
community.crypto.x509_certificate_pipe:
csr_content: "{{ certs_csr.csr }}"
provider: "ownca"
ownca_path: "ca/ca-certificate.crt"
ownca_privatekey_path: "ca/ca-certificate.key"
ownca_privatekey_passphrase: "{{ certs_ca_passphrase }}"
ownca_not_after: "{{ cert.not_after | default(certs_default_not_after) }}"
ownca_not_before: "{{ cert.not_before | default(certs_default_not_before) }}"
register: certs_certificate
- name: Write certificate file on server
# noqa: no-handler
when: "certs_csr.changed"
ansible.builtin.copy:
dest: "/etc/ssl/certs/{{ cert.cn }}.crt"
content: "{{ certs_certificate.certificate }}"
mode: "0644"
notify:
- "Reboot host"

View File

@@ -0,0 +1,2 @@
common_default_packages: []
common_proxy_server: null

View File

@@ -1,2 +1,7 @@
- name: Setup proxy client - name: Setup proxy client
when: "common_proxy_server != None"
ansible.builtin.include_tasks: "setup_proxy_client.yml" ansible.builtin.include_tasks: "setup_proxy_client.yml"
- name: Setup packages
when: "common_default_packages | length > 0"
ansible.builtin.include_tasks: "setup_packages.yml"

View File

@@ -0,0 +1,9 @@
- name: Keep packages up to date
ansible.builtin.apt:
update_cache: true
upgrade: "safe"
- name: Install default packages
ansible.builtin.apt:
name: "{{ common_default_packages }}"
autoclean: true

View File

@@ -2,7 +2,11 @@
consul_datacenter: null consul_datacenter: null
consul_domain: null consul_domain: null
consul_secret_key: null consul_secret_key: null
consul_nodes: [] consul_server: false
consul_servers: []
# Required for registering services
consul_services: []
# Defaults # Defaults
consul_version: "1.22.2" consul_version: "1.22.2"
@@ -12,3 +16,5 @@ consul_ca_file: "/etc/ssl/certs/local-ca.crt"
consul_client_addr: "0.0.0.0" consul_client_addr: "0.0.0.0"
consul_bind_addr: "{{ ansible_default_ipv4.address }}" consul_bind_addr: "{{ ansible_default_ipv4.address }}"
consul_advertise_addr: "{{ ansible_default_ipv4.address }}" consul_advertise_addr: "{{ ansible_default_ipv4.address }}"
consul_default_check_interval: "15s"
consul_default_check_timeout: "2s"

View File

@@ -3,8 +3,3 @@
name: "consul" name: "consul"
state: "restarted" state: "restarted"
daemon_reload: true daemon_reload: true
- name: Reload Consul
ansible.builtin.service:
name: "consul"
state: "reloaded"

View File

@@ -0,0 +1,6 @@
Security:
- Gossip protocol encryption
- Built-in ACL
- Consul agent communication
- mTLS for authentitication and encryption
- Certificate authority

View File

@@ -60,7 +60,7 @@
group: "consul" group: "consul"
mode: "0640" mode: "0640"
notify: notify:
- "Reload Consul" - "Restart Consul"
- name: Deploy Consul service file - name: Deploy Consul service file
ansible.builtin.template: ansible.builtin.template:
@@ -76,3 +76,31 @@
state: "started" state: "started"
enabled: true enabled: true
daemon_reload: true daemon_reload: true
- name: Register services to consul
loop: "{{ consul_services }}"
community.general.consul_agent_service:
name: "{{ item.name }}"
service_port: "{{ item.port }}"
tags: ="{{ item.tags | default([]) }}"
- name: Register HTTP checks to consul
loop: "{{ consul_services }}"
when: "item.check_http | default(None) != None"
community.general.consul_agent_check:
name: "{{ item.name }}_check"
service_id: "{{ item.name }}"
interval: "{{ item.check_interval | default(consul_default_check_interval) }}"
timeout: "{{ item.check_timeout | default(consul_default_check_timeout) }}"
http: "{{ item.check_http }}"
scheme: "{{ item.check_scheme | default('http') }}"
- name: Register TCP checks to consul
loop: "{{ consul_services }}"
when: "item.check_tcp | default(None) != None"
community.general.consul_agent_check:
name: "{{ item.name }}_check"
service_id: "{{ item.name }}"
interval: "{{ item.check_interval | default(consul_default_check_interval) }}"
timeout: "{{ item.check_timeout | default(consul_default_check_timeout) }}"
tcp: "{{ item.check_tcp }}"

View File

@@ -1,8 +1,7 @@
{ {
"log_level": "INFO", "log_level": "INFO",
"node_name": "{{ ansible_hostname }}",
"domain": "{{ consul_domain }}", "domain": "{{ consul_domain }}",
"server": true, "datacenter": "{{ consul_datacenter | mandatory }}",
"key_file": "{{ consul_key_file }}", "key_file": "{{ consul_key_file }}",
"cert_file": "{{ consul_cert_file }}", "cert_file": "{{ consul_cert_file }}",
"ca_file": "{{ consul_ca_file }}", "ca_file": "{{ consul_ca_file }}",
@@ -10,17 +9,20 @@
"verify_outgoing": true, "verify_outgoing": true,
"verify_server_hostname": true, "verify_server_hostname": true,
"encrypt": "{{ consul_secret_key }}", "encrypt": "{{ consul_secret_key }}",
"data_dir": "/opt/consul",
{% if consul_server %}
"node_name": "{{ ansible_hostname }}",
"server": true,
"ui": true, "ui": true,
"leave_on_terminate": true, "leave_on_terminate": true,
"data_dir": "/opt/consul",
"datacenter": "{{ consul_datacenter | mandatory }}",
"client_addr": "{{ consul_client_addr }}", "client_addr": "{{ consul_client_addr }}",
"bind_addr": "{{ consul_bind_addr }}", "bind_addr": "{{ consul_bind_addr }}",
"advertise_addr": "{{ consul_advertise_addr }}", "advertise_addr": "{{ consul_advertise_addr }}",
"bootstrap_expect": {{ consul_nodes | length }}, "bootstrap_expect": {{ consul_servers | length }},
"retry_join": ["{{ consul_nodes | join('", "') }}"],
"enable_syslog": true, "enable_syslog": true,
"performance": { "performance": {
"raft_multiplier": 1 "raft_multiplier": 1
} },
{% endif %}
"retry_join": ["{{ consul_servers | join('", "') }}"]
} }

View File

@@ -10,7 +10,6 @@ Type=notify
User=consul User=consul
Group=consul Group=consul
ExecStart=/usr/local/bin/consul agent -config-file=/etc/consul/config.json ExecStart=/usr/local/bin/consul agent -config-file=/etc/consul/config.json
ExecReload=/usr/local/bin/consul reload
KillMode=process KillMode=process
Restart=on-failure Restart=on-failure
LimitNOFILE=65536 LimitNOFILE=65536

View File

@@ -0,0 +1,16 @@
# Required
keepalived_script_user: null
keepalived_monitored_process: null
keepalived_vrrp_password: null # Max 8 characters
keepalived_vrrp_ips: null
# - master: <string>
# virtual_router_id: <int>
# addr: <string>
# Optionnal
keepalived_alerts_to: []
keepalived_unicast_peers: []
# Required if length keepalived_alerts_to > 0
keepalived_smtp_from: null
keepalived_smtp_relay: null

View File

@@ -0,0 +1,58 @@
# FIXME: NOTICE: setting config option max_auto_priority should result in better keepalived performance
global_defs {
enable_script_security
script_user "{{ keepalived_script_user }}"
{% if keepalived_alerts_to | length > 0 %}
notification_email {
{% for dest in keepalived_alerts_to %}
{{ dest }}
{% endfor %}
}
notification_email_from {{ keepalived_smtp_from }}
smtp_server {{ keepalived_smtp_relay }}
smtp_connect_timeout 30
router_id {{ ansible_hostname }}
{% endif %}
}
vrrp_script chk_{{ keepalived_monitored_process }} {
script "/usr/bin/killall -0 {{ keepalived_monitored_process }}" # Check if process is running
interval 2 # Check every 2 seconds
weight 2 # Weight to influence master election
}
{% for ip in keepalived_vrrp_ips %}
# master: {{ ip.master }}
vrrp_instance VI_{{ ip.virtual_router_id }} {
state {{ (ansible_hostname==ip.master) | ternary('MASTER', 'BACKUP') }}
interface {{ ansible_default_ipv4.interface }}
virtual_router_id {{ ip.virtual_router_id }}
priority {{ (ansible_hostname==ip.master) | ternary('101', '100') }}
advert_int 1
promote_secondaries
{% if keepalived_alerts_to | length > 0 %}
smtp_alert true
{% endif %}
authentication {
auth_type PASS
auth_pass {{ keepalived_vrrp_password }}
}
virtual_ipaddress {
{{ ip.addr }}
}
{% if keepalived_unicast_peers | length > 0 %}
unicast_peer {
{% for peer in keepalived_unicast_peers %}
{% if peer != ansible_default_ipv4.address %}
{{ peer }}
{% endif %}
{% endfor %}
}
{% endif %}
track_script {
chk_{{ keepalived_monitored_process }}
}
}
{% endfor %}

View File

@@ -0,0 +1,19 @@
# Defaults
local_ca_certs_default_country_name: null
local_ca_certs_default_locality_name: null
local_ca_certs_default_organization_name: null
local_ca_certs_default_email_address: null
local_ca_certs_default_not_after: "+365d" # valid for one year
local_ca_certs_default_not_before: "-1d" # valid since yesterday
# Required
local_ca_certs_ca_passphrase: null
local_ca_certs_list: {}
# - cn: <string>
# domains: <array of strings>
# country_name: <string>
# locality_name: <string>
# organization_name: <string>
# email_address: <string>
# not_after: <string>
# not_before: <string>

View File

@@ -0,0 +1,42 @@
- name: Create private key for new certificate
community.crypto.openssl_privatekey:
path: "/etc/ssl/private/{{ cert.cn }}.key"
mode: "0640"
group: "ssl-cert"
- name: Create CSR for new certificate
community.crypto.openssl_csr:
path: "/etc/ssl/certs/{{ cert.cn }}.csr"
privatekey_path: "/etc/ssl/private/{{ cert.cn }}.key"
common_name: "{{ cert.cn }}"
subject_alt_name: "{{ cert.domains }}"
country_name: "{{ cert.country_name | default(local_ca_certs_default_country_name) | mandatory }}"
locality_name: "{{ cert.locality_name | default(local_ca_certs_default_locality_name) | mandatory }}"
organization_name: "{{ cert.organization_name | default(local_ca_certs_default_organization_name) | mandatory }}"
email_address: "{{ cert.email_address | default(local_ca_certs_default_email_address) | mandatory }}"
return_content: true
register: local_ca_certs_csr
- name: Sign certificate with our CA
delegate_to: "localhost"
# noqa: no-handler
when: "local_ca_certs_csr.changed"
community.crypto.x509_certificate_pipe:
csr_content: "{{ local_ca_certs_csr.csr }}"
provider: "ownca"
ownca_path: "ca/ca-certificate.crt"
ownca_privatekey_path: "ca/ca-certificate.key"
ownca_privatekey_passphrase: "{{ local_ca_certs_ca_passphrase }}"
ownca_not_after: "{{ cert.not_after | default(local_ca_certs_default_not_after) }}"
ownca_not_before: "{{ cert.not_before | default(local_ca_certs_default_not_before) }}"
register: local_ca_certs_certificate
- name: Write certificate file on server
# noqa: no-handler
when: "local_ca_certs_csr.changed"
ansible.builtin.copy:
dest: "/etc/ssl/certs/{{ cert.cn }}.crt"
content: "{{ local_ca_certs_certificate.certificate }}"
mode: "0644"
notify:
- "Reboot host"

View File

@@ -10,7 +10,7 @@
mode: "0644" mode: "0644"
- name: Create certs - name: Create certs
loop: "{{ certs_list }}" loop: "{{ local_ca_certs_list }}"
loop_control: loop_control:
loop_var: "cert" loop_var: "cert"
ansible.builtin.include_tasks: "create_cert.yml" ansible.builtin.include_tasks: "create_cert.yml"

View File

@@ -0,0 +1,6 @@
# Required
mysql_databases: []
mysql_users: []
# - name: <string
# password: <string>
# priv: <string>

View File

@@ -0,0 +1,5 @@
- name: Restart MySQL
ansible.builtin.service:
name: "mysql"
state: "restarted"
daemon_reload: true

View File

@@ -0,0 +1,31 @@
- name: Installation de MySQL
ansible.builtin.apt:
name:
- "mysql-server"
- "python3-pymysql"
notify:
- "Restart MySQL"
- name: Set MySQL bind_address parameter
ansible.builtin.lineinfile:
path: "/etc/mysql/mysql.conf.d/mysqld.cnf"
regexp: "^bind-address\\s+="
line: "bind-address = *"
notify:
- "Restart MySQL"
- name: Création des bases de données MySQL
loop: "{{ mysql_databases }}"
community.mysql.mysql_db:
login_unix_socket: "/var/run/mysqld/mysqld.sock"
name: "{{ item }}"
encoding: "utf8"
- name: Création des utilisateur MySQL
loop: "{{ mysql_users }}"
community.mysql.mysql_user:
login_unix_socket: "/var/run/mysqld/mysqld.sock"
name: "{{ item.name }}"
password: "{{ item.password }}"
priv: "{{ item.priv }}"
host: "{{ item.host }}"

View File

@@ -0,0 +1,6 @@
# Required
ovh_ldp_token: null
ovh_ldp_cluster: null
# Defaults
ovh_ldp_tls_syslog_port: 6514

View File

@@ -0,0 +1,5 @@
- name: Restart syslog-ng
ansible.builtin.service:
name: "syslog-ng"
state: "restarted"
daemon_reload: true

View File

@@ -0,0 +1,13 @@
- name: Install syslog-ng
ansible.builtin.apt:
name:
- "syslog-ng"
- "ca-certificates"
- name: Generate syslog-ng config file for OVH LDP
ansible.builtin.template:
src: "templates/ldp-ovh.conf"
dest: "/etc/syslog-ng/conf.d/ldp-ovh.conf"
mode: "0640"
notify:
- "Restart syslog-ng"

View File

@@ -0,0 +1,22 @@
rewrite ovh-token {
set("{{ ovh_ldp_token }}", value(".SDATA.token@29084.X-OVH-TOKEN"));
};
destination ovhPaaSLogs {
network("{{ ovh_ldp_cluster }}"
port({{ ovh_ldp_tls_syslog_port }})
transport("tls")
flags(syslog-protocol)
ts_format("iso")
frac-digits(6)
tls(peer-verify("required-trusted") ca_dir("/etc/ssl/certs/"))
keep-alive(yes)
so_keepalive(yes)
);
};
log {
source(s_src);
rewrite(ovh-token);
destination(ovhPaaSLogs);
};

View File

@@ -1,2 +0,0 @@
squid_authorized_networks: ["127.0.0.1"]
squid_cache_size: 10240

View File

@@ -1,4 +0,0 @@
- name: Restart squid
ansible.builtin.systemd:
name: "squid"
state: "restarted"

View File

@@ -1,23 +0,0 @@
- name: Install squid package
ansible.builtin.apt:
name: "squid"
notify:
- "Restart squid"
- name: Deploy squid configuration file
ansible.builtin.template:
src: "templates/squid.conf"
dest: "/etc/squid/squid.conf"
owner: "root"
group: "root"
mode: "0644"
notify:
- "Restart squid"
- name: Enable squid
ansible.builtin.systemd:
name: "squid"
enabled: true
daemon_reload: true
notify:
- "Restart squid"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
unbound_listen_interfaces:
- "0.0.0.0"
unbound_access_control:
- "0.0.0.0/0 allow"
unbound_forward_zones:
- name: "."
forward_to: "8.8.8.8"
unbound_stub_zones: []
# - name: <string>
# stub_to: <string>
# no_cache: no (default yes)

View File

@@ -0,0 +1,4 @@
- name: Restart unbound
ansible.builtin.service:
name: "unbound"
state: "restarted"

View File

@@ -0,0 +1,14 @@
- name: Install Unbound
ansible.builtin.apt:
name:
- "unbound"
notify:
- "Restart unbound"
- name: Deploy unbounf config file
ansible.builtin.template:
src: "templates/unbound.conf"
dest: "/etc/unbound/unbound.conf.d/unbound.conf"
mode: "0644"
notify:
- "Restart unbound"

View File

@@ -0,0 +1,21 @@
server:
do-not-query-localhost: no
{% for interface in unbound_listen_interfaces %}
interface: {{ interface }}
{% endfor %}
{% for access in unbound_access_control %}
access-control: {{ access }}
{% endfor %}
{% for zone in unbound_stub_zones %}
stub-zone:
name: "{{ zone.name }}"
stub-addr: {{ zone.stub_to }}
stub-no-cache: {{ zone.no_cache | default('yes') }}
{% endfor %}
{% for zone in unbound_forward_zones %}
forward-zone:
name: "{{ zone.name }}"
forward-addr: {{ zone.forward_to }}
{% endfor %}

View File

@@ -1,16 +0,0 @@
# Required
vip_script_user: null
vip_monitored_process: null
vip_vrrp_password: null # Max 8 characters
vip_vrrp_ips: null
# - master: <string>
# virtual_router_id: <int>
# addr: <string>
# Optionnal
vip_alerts_to: null
# - <string>
# Required if length vip_alerts_to > 0
vip_smtp_from: null
vip_smtp_relay: null

View File

@@ -1,45 +0,0 @@
global_defs {
enable_script_security
script_user "{{ vip_script_user }}"
{% if vip_alerts_to | default([]) | length > 0 %}
notification_email {
{% for dest in vip_alerts_to %}
{{ dest }}
{% endfor %}
}
notification_email_from {{ vip_smtp_from }}
smtp_server {{ vip_smtp_relay }}
smtp_connect_timeout 30
router_id {{ ansible_hostname }}
{% endif %}
}
vrrp_script chk_{{ vip_monitored_process }} {
script "/usr/bin/killall -0 {{ vip_monitored_process }}" # Check if process is running
interval 2 # Check every 2 seconds
weight 2 # Weight to influence master election
}
{% for ip in vip_vrrp_ips %}
# master: {{ ip.master }}
vrrp_instance VI_{{ ip.virtual_router_id }} {
state {{ (ansible_hostname==ip.master) | ternary('MASTER', 'BACKUP') }}
interface eth0
virtual_router_id {{ ip.virtual_router_id }}
priority {{ (ansible_hostname==ip.master) | ternary('101', '100') }}
advert_int 1
promote_secondaries
smtp_alert true
authentication {
auth_type PASS
auth_pass {{ vip_vrrp_password }}
}
virtual_ipaddress {
{{ ip.addr }}
}
track_script {
chk_{{ vip_monitored_process }}
}
}
{% endfor %}

View File

@@ -1,5 +1,5 @@
# Defaults # Defaults
waf_config_owasp_crs_version: "4.21.0" waf_config_owasp_crs_version: "4.22.0"
# Required unless not site.custom_cert # Required unless not site.custom_cert
waf_config_email: null waf_config_email: null
@@ -22,8 +22,6 @@ waf_config_sites: []
# - "" # - ""
# aliases: # aliases:
# - "" # - ""
# plugins: FIXME:TODO
# - ""
# exceptions: # exceptions:
# before_request: # before_request:
# - "" # - ""
@@ -35,6 +33,7 @@ waf_config_defaults:
custom_cert: true custom_cert: true
custom_cert_file: "" custom_cert_file: ""
custom_key_file: "" custom_key_file: ""
plugins: []
rate_events: 1000 rate_events: 1000
rate_window: "1m" rate_window: "1m"

View File

@@ -1,4 +1,4 @@
- name: Reload Caddy - name: Restart Caddy
ansible.builtin.service: ansible.builtin.service:
name: "caddy" name: "caddy"
state: "reloaded" state: "restarted"

View File

@@ -1,9 +1,7 @@
- name: Create sites config directories - name: Create sites config directories
loop: "{{ waf_config_sites }}" loop: "{{ waf_config_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.file: ansible.builtin.file:
path: "/etc/caddy/sites/{{ site.name }}" path: "/etc/caddy/sites/{{ item.name }}"
state: "directory" state: "directory"
group: "caddy" group: "caddy"
mode: "0750" mode: "0750"
@@ -18,7 +16,7 @@
group: "caddy" group: "caddy"
mode: "0640" mode: "0640"
notify: notify:
- "Reload Caddy" - "Restart Caddy"
- name: Generate sites crs-setup config file - name: Generate sites crs-setup config file
loop: "{{ waf_config_sites }}" loop: "{{ waf_config_sites }}"
@@ -30,7 +28,7 @@
group: "caddy" group: "caddy"
mode: "0640" mode: "0640"
notify: notify:
- "Reload Caddy" - "Restart Caddy"
- name: Generate sites exclusions before request file - name: Generate sites exclusions before request file
loop: "{{ waf_config_sites }}" loop: "{{ waf_config_sites }}"
@@ -42,7 +40,7 @@
group: "caddy" group: "caddy"
mode: "0640" mode: "0640"
notify: notify:
- "Reload Caddy" - "Restart Caddy"
- name: Generate sites exclusions after response file - name: Generate sites exclusions after response file
loop: "{{ waf_config_sites }}" loop: "{{ waf_config_sites }}"
@@ -54,7 +52,7 @@
group: "caddy" group: "caddy"
mode: "0640" mode: "0640"
notify: notify:
- "Reload Caddy" - "Restart Caddy"
- name: Generate Caddyfile - name: Generate Caddyfile
ansible.builtin.template: ansible.builtin.template:
@@ -63,7 +61,7 @@
group: "caddy" group: "caddy"
mode: "0640" mode: "0640"
notify: notify:
- "Reload Caddy" - "Restart Caddy"
- name: Copy bot barrier template - name: Copy bot barrier template
ansible.builtin.copy: ansible.builtin.copy:
@@ -72,4 +70,4 @@
group: "caddy" group: "caddy"
mode: "0640" mode: "0640"
notify: notify:
- "Reload Caddy" - "Restart Caddy"

View File

@@ -103,12 +103,16 @@
directives ` directives `
Include "/etc/caddy/sites/{{ site.name }}/coraza.conf" Include "/etc/caddy/sites/{{ site.name }}/coraza.conf"
Include "/etc/caddy/sites/{{ site.name }}/crs-setup.conf" Include "/etc/caddy/sites/{{ site.name }}/crs-setup.conf"
Include "/etc/caddy/plugins/*-config.conf" {% for plugin in site.plugins | default(waf_config_defaults.plugins) %}
Include "/etc/caddy/plugins/*-before.conf" Include "/etc/caddy/plugins/{{ plugin }}-config.conf"
Include "/etc/caddy/plugins/{{ plugin }}-before.conf"
{% endfor %}
Include "/etc/caddy/sites/{{ site.name }}/exclusions-request-before.conf" Include "/etc/caddy/sites/{{ site.name }}/exclusions-request-before.conf"
Include "/etc/caddy/coreruleset-{{ waf_config_owasp_crs_version }}/rules/*.conf" Include "/etc/caddy/coreruleset-{{ waf_config_owasp_crs_version }}/rules/*.conf"
Include "/etc/caddy/sites/{{ site.name }}/exclusions-response-after.conf" Include "/etc/caddy/sites/{{ site.name }}/exclusions-response-after.conf"
Include "/etc/caddy/plugins/*-after.conf" {% for plugin in site.plugins | default(waf_config_defaults.plugins) %}
Include "/etc/caddy/plugins/{{ plugin }}-after.conf"
{% endfor %}
SecRuleEngine On SecRuleEngine On
` `
} }

View File

@@ -2,10 +2,7 @@ waf_install_go_version: "1.25.5"
waf_install_xcaddy_version: "0.4.5" waf_install_xcaddy_version: "0.4.5"
waf_install_caddy_version: "2.10.2" waf_install_caddy_version: "2.10.2"
waf_install_coraza_caddy_version: "2.1.0" # Coraza v3.3.3 waf_install_coraza_caddy_version: "2.1.0" # Coraza v3.3.3
waf_install_owasp_crs_version: "4.21.0" waf_install_owasp_crs_version: "4.22.0"
# Cluster mode (e.g. with consul)
waf_install_cluster: false
# Cf.: https://github.com/coreruleset/plugin-registry # Cf.: https://github.com/coreruleset/plugin-registry
waf_install_crs_plugins: waf_install_crs_plugins:
@@ -19,6 +16,7 @@ waf_install_crs_plugins:
version: "1.0.0" version: "1.0.0"
- name: "phpmyadmin-rule-exclusions" - name: "phpmyadmin-rule-exclusions"
version: "1.0.0" version: "1.0.0"
has_after_config: true
- name: "roundcube-rule-exclusions" - name: "roundcube-rule-exclusions"
version: "1.0.4" version: "1.0.4"
provider: "EsadCetiner" provider: "EsadCetiner"

View File

@@ -30,7 +30,15 @@
group: "caddy" group: "caddy"
remote_src: true remote_src: true
notify: notify:
- "Reload Caddy" - "Restart Caddy"
- name: Create after config {{ plugin.name }}
when: "not(plugin.has_after_config | default(false))"
ansible.builtin.copy:
content: ""
dest: "/etc/caddy/plugins/{{ plugin.name }}-after.conf"
mode: "0644"
group: "caddy"
# FIXME: bad hack, why is it required? # FIXME: bad hack, why is it required?
- name: Adjust /etc/caddy/plugins permissions - name: Adjust /etc/caddy/plugins permissions

View File

@@ -30,20 +30,29 @@
dest: "/usr/local/bin/go" dest: "/usr/local/bin/go"
state: "link" state: "link"
# FIXME: no .deb - name: Create xcaddy directory
ansible.builtin.file:
path: "/usr/local/xcaddy-{{ waf_install_xcaddy_version }}"
state: "directory"
mode: "0755"
- name: Install xcaddy - name: Install xcaddy
when: | ansible.builtin.unarchive:
'xcaddy' not in ansible_facts.packages src: "{{ waf_install_xcaddy_download }}/v{{ waf_install_xcaddy_version }}/xcaddy_{{ waf_install_xcaddy_version }}_linux_amd64.tar.gz"
or ansible_facts.packages['xcaddy'][0]['version'] != waf_install_xcaddy_version dest: "/usr/local/xcaddy-{{ waf_install_xcaddy_version }}"
ansible.builtin.apt: remote_src: true
deb: "{{ waf_install_xcaddy_download }}/v{{ waf_install_xcaddy_version }}/xcaddy_{{ waf_install_xcaddy_version }}_linux_amd64.deb"
force: true - name: Link xcaddy
ansible.builtin.file:
src: "/usr/local/xcaddy-{{ waf_install_xcaddy_version }}/xcaddy"
dest: "/usr/local/bin/xcaddy"
state: "link"
- name: Create Caddy build command - name: Create Caddy build command
ansible.builtin.set_fact: ansible.builtin.set_fact:
waf_install_build_command: > waf_install_build_command: >
xcaddy build v{{ waf_install_caddy_version }} xcaddy build v{{ waf_install_caddy_version }}
{% if waf_install_cluster %} --with github.com/pteich/caddy-tlsconsul{% endif %} --with github.com/pteich/caddy-tlsconsul
--with github.com/corazawaf/coraza-caddy/v2@v{{ waf_install_coraza_caddy_version }} --with github.com/corazawaf/coraza-caddy/v2@v{{ waf_install_coraza_caddy_version }}
--with github.com/mholt/caddy-ratelimit --with github.com/mholt/caddy-ratelimit
--with github.com/steffenbusch/caddy-bot-barrier --with github.com/steffenbusch/caddy-bot-barrier

View File

@@ -0,0 +1,23 @@
# Required
wordpress_url: null
wordpress_site: null
wordpress_site_title: null
wordpress_admin_user: null
wordpress_admin_password: null
wordpress_admin_email: null
wordpress_db_pass: null
wordpress_themes: []
wordpress_active_theme: null
# Defaults
wordpress_user: "wordpress"
wordpress_plugins: []
wordpress_locale: "fr_FR"
wordpress_db_host: "localhost"
wordpress_db_name: "{{ wordpress_user }}"
wordpress_db_user: "{{ wordpress_user }}"
wordpress_db_prefix: "wp_"
wordpress_db_charset: "utf8mb4"
# Optionnal
wordpress_import_ocdi_indexes: []

View File

@@ -0,0 +1,17 @@
- name: Restart Nginx
ansible.builtin.systemd:
name: "nginx"
state: "restarted"
- name: Flush Wordpress
become: true
become_user: "{{ wordpress_user }}"
ansible.builtin.shell:
executable: "/usr/bin/bash"
chdir: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}"
cmd: |
set -o pipefail
wp cache flush
wp transient delete --all
wp rewrite flush --hard
changed_when: true

View File

@@ -0,0 +1,12 @@
- name: Installation des plugins Wordpress
become: true
become_user: "{{ wordpress_user }}"
loop: "{{ wordpress_plugins_to_install }}"
ansible.builtin.command:
chdir: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}"
cmd: "wp plugin install {{ item }} --activate"
creates: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}/wp-content/plugins/{{ item | basename | splitext | first }}"
register: "wordpress_out_plugin_install"
changed_when: "'Success: Installed 1 of 1 plugins' in wordpress_out_plugin_install.stdout"
notify:
- "Flush Wordpress"

View File

@@ -0,0 +1,199 @@
- name: Installation des pré-requis
ansible.builtin.apt:
name:
- "nginx"
- "php-fpm"
- "php-mysql"
- "php-curl"
- "php-json"
- "php-xml"
- "php-mbstring"
- "php-zip"
- "zip"
- name: Installation de wp-cli
ansible.builtin.get_url:
url: "https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar"
dest: "/usr/local/bin/wp"
owner: "root"
group: "root"
mode: "755"
- name: Création de l'utilisateur système pour Wordpress
ansible.builtin.user:
name: "{{ wordpress_user | mandatory }}"
home: "/var/www/{{ wordpress_user }}"
group: "www-data"
shell: "/usr/bin/bash"
password_lock: true
create_home: true
- name: Création du répertoire de Wordpress
ansible.builtin.file:
path: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}"
state: "directory"
owner: "{{ wordpress_user }}"
group: "www-data"
mode: "0750"
- name: Installation de Wordpress
become: true
become_user: "{{ wordpress_user }}"
ansible.builtin.command:
chdir: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}"
cmd: "wp core download --locale='{{ wordpress_locale }}' --skip-content"
creates: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}/wp-config-sample.php"
# TODO: https://make.wordpress.org/cli/handbook/references/config/#config-files
- name: Configuration de Wordpress
become: true
become_user: "{{ wordpress_user }}"
ansible.builtin.shell:
chdir: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}"
executable: "/usr/bin/bash"
cmd: |
set -o pipefail
wp config create \
--dbhost='{{ wordpress_db_host }}' \
--dbname='{{ wordpress_db_name }}' \
--dbuser='{{ wordpress_db_user }}' \
--dbpass='{{ wordpress_db_pass }}' \
--dbprefix='{{ wordpress_db_prefix }}' \
--dbcharset='{{ wordpress_db_charset }}' \
--extra-php <<PHP
define('FS_METHOD', 'direct');
define('FORCE_SSL_ADMIN', false);
define('WP_HOME', 'https://{{ wordpress_site }}');
define('WP_SITEURL', 'https://{{ wordpress_site }}');
error_reporting(0);
@ini_set('display_errors', 0);
define('DISALLOW_FILE_EDIT', true);
if (isset(\$_SERVER['HTTP_X_FORWARDED_PROTO']) && \$_SERVER['HTTP_X_FORWARDED_PROTO']=='https')
\$_SERVER['HTTPS'] = 'on';
PHP
creates: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}/wp-config.php"
notify:
- "Flush Wordpress"
- name: Create Wordpress database tables
become: true
become_user: "{{ wordpress_user }}"
run_once: true
ansible.builtin.command:
chdir: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}"
cmd: >
wp core install
--url='{{ wordpress_url }}'
--title='{{ wordpress_site_title }}'
--admin_user='{{ wordpress_admin_user }}'
--admin_password='{{ wordpress_admin_password }}'
--admin_email='{{ wordpress_admin_email }}'
--locale='{{ wordpress_locale }}'
--skip-email
creates: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}/wp-content/uploads/ }}"
register: "wordpress_out_core_install"
changed_when: "'Success: WordPress installed successfully.' in wordpress_out_core_install.stdout"
notify:
- "Flush Wordpress"
- name: Installation des thèmes Wordpress
become: true
become_user: "{{ wordpress_user }}"
loop: "{{ wordpress_themes }}"
ansible.builtin.command:
chdir: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}"
cmd: "wp theme install '{{ item }}'"
creates: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}/wp-content/themes/{{ item | basename | splitext | first }}"
register: "wordpress_out_theme_install"
changed_when: "'Success: Installed 1 of 1 themes' in wordpress_out_theme_install.stdout"
notify:
- "Flush Wordpress"
- name: Activation du thème Wordpress
become: true
become_user: "{{ wordpress_user }}"
ansible.builtin.command:
chdir: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}"
cmd: "wp theme activate '{{ wordpress_active_theme }}'"
register: "wordpress_out_theme_activate"
changed_when: "'Success: Switched to ' in wordpress_out_theme_activate.stdout"
notify:
- "Flush Wordpress"
- name: Installation des plugins wordpress-importer et one-click-demo-import
vars:
wordpress_plugins_to_install:
- "wordpress-importer"
- "one-click-demo-import"
ansible.builtin.include_tasks: "install_plugins.yml"
- name: Importation des plugins OCDI
become: true
become_user: "{{ wordpress_user }}"
loop: "{{ wordpress_import_ocdi_indexes }}"
ansible.builtin.shell:
chdir: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}"
executable: "/usr/bin/bash"
cmd: |
set -o pipefail
wp ocdi import --predefined='{{ item }}' \
&& touch /var/www/{{ wordpress_user }}/{{ wordpress_site }}/.ocdi_index{{ item }}_imported
creates: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}/.ocdi_index{{ item }}_imported"
register: "wordpress_out_ocdi_import"
changed_when: "'Success: Content import finished!' in wordpress_out_ocdi_import.stdout"
notify:
- "Flush Wordpress"
- name: Installation des plugins Wordpress
vars:
wordpress_plugins_to_install: "{{ wordpress_plugins }}"
ansible.builtin.include_tasks: "install_plugins.yml"
- name: Création du répertoire cache de Wordpress
ansible.builtin.file:
path: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}/wp-content/cache"
state: "directory"
owner: "{{ wordpress_user }}"
group: "www-data"
mode: "0750"
- name: Apply permissions
loop:
- "uploads"
- "cache"
ansible.builtin.file:
path: "/var/www/{{ wordpress_user }}/{{ wordpress_site }}/wp-content/{{ item }}/"
owner: "{{ wordpress_user }}"
group: "www-data"
mode: "u=rwX,g=rwX,o=rX"
recurse: true
- name: Configuration de Nginx
ansible.builtin.template:
src: "templates/nginx_vhost.conf"
dest: "/etc/nginx/sites-available/{{ wordpress_site }}.conf"
owner: "root"
group: "root"
mode: "0644"
notify:
- "Restart Nginx"
- name: Link Nginx vhost config
ansible.builtin.file:
src: "/etc/nginx/sites-available/{{ wordpress_site }}.conf"
dest: "/etc/nginx/sites-enabled/{{ wordpress_site }}.conf"
state: "link"
notify:
- "Restart Nginx"
- name: Unlink Nginx default vhost config
ansible.builtin.file:
path: "/etc/nginx/sites-enabled/default"
state: "absent"
notify:
- "Restart Nginx"
# FIXME: php. ini config
# Mainly: upload_max_filesize, post_max_size, max_execution_time

View File

@@ -0,0 +1,50 @@
server {
listen 80;
server_name {{ wordpress_site }} www.{{ wordpress_site }};
root /var/www/{{ wordpress_user }}/{{ wordpress_site }};
index index.html index.htm index.php;
client_max_body_size 50M;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
location ~ /\.ht {
deny all;
}
location ~ /wp-config\.php\$ {
deny all;
}
location ~ /wp-content/uploads/.*\.php\$ {
deny all;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php-fpm.sock;
}
# Réécritures pour WordPress
location ~ ^/index\.php$ { }
location / {
if (!-e $request_filename){
rewrite ^/(.*)$ /index.php last;
break;
}
}
# location / {
# try_files \$uri \$uri/ /index.php?\$args;
# }
# location ~ \.php\$ {
# include snippets/fastcgi-php.conf;
# fastcgi_pass unix:/var/run/php/php-fpm.sock;
# fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
# }
}

View File

@@ -1,4 +0,0 @@
- name: Install Apache 2
ansible.builtin.apt:
name:
- "apache2"

View File

@@ -21,7 +21,11 @@ common:
custom_cert: true custom_cert: true
custom_cert_file: "/etc/ssl/certs/test.lab.crt" custom_cert_file: "/etc/ssl/certs/test.lab.crt"
custom_key_file: "/etc/ssl/private/test.lab.key" custom_key_file: "/etc/ssl/private/test.lab.key"
# exceptions: plugins:
- "wordpress-rule-exclusions"
exceptions:
# Cf. https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v3.x)
# before_request: # before_request:
# - 'SecRule REQUEST_URI "@beginsWith /?bin/sh" "id:1,nolog,ctl:ruleRemoveById=932160"' # - 'SecRule REQUEST_FILENAME "@endsWith /wp-admin/admin-ajax.php" "id:1,nolog,ctl:ruleRemoveById=932260"'
# - 'SecRule REQUEST_URI "@beginsWith /?bin/sh" "id:2,nolog,ctl:ruleRemoveById=932250"' after_response:
- 'SecRuleUpdateTargetById 932260 "!ARGS:tag-name"'

View File

@@ -1,13 +1,24 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
61643639653530613066383464333939383034643565386434616361653331656237303835653837 30356233646632316565363533613033376333306433626366623230633838356230363461383135
3431646133336265393237383366653364373039376365340a396438643530383965343865383837 3834363137353762316262363134643763353632306362640a613538306235633565356563613532
62636338366437356661356561356138383032353133396461643362323536303965353564666232 64643862613431313666363130376338356538323035346532636331316637646235346236333331
3735363038323139620a386364633933303139623436626337346535663737386264303036323866 6336313466313434640a316437343866343061383838383362633638363633363535363330333463
31303762336638313961383034386137376566383662323961336536303230303063396661333135 64616330333361643166663061306338616265316262646339663438306465623231336139653764
66646133333565656233303461356166303862303037663133383534613263383130316261643738 63353862303538636636306430333464383339316233326133623564613337306266353730616564
65656165346432636363393436646532643531393066346332326561656230316232383866643665 63366562626135626136663665353663323265346438346131363838383263663665653937393066
36626333656335626534333737633231396561633831653464383036323431616431306433623139 32353061396661346138386561633734353634363164663438656331383663323035613264653265
61346630663636313463376237353036343264613936366539393165623265656233303864386563 38646164633465363238623232393731633761393466326466653430316532633139636533613033
63386661343061373930383139376666353035373531386230306432663132653130633039616665 39303764363861353666356239306366633662643865663033383235336663623632373566383330
38383735326339353738383166623932316564636634343830326432613563306436666532313562 32363334653936393962633266666439623130616161373231313037343735616361303131383230
65326431346639636338 62333863313432353536363962623161383434363136316264663061343038633065316232313334
38653233356135623431343166356464666663636330373663303663326561363333633837303337
66616464393661653835636665353439353539393530623730656331316235643032336337616436
64373163373932653038613765633061306331363962333932383564333735353534646637303237
62376561303364386662343331373631343166326261636362623432323437643735663437333564
39323961626131616334323131393963323231626638393231326631306337306230666138323830
63373331636234646431666332633633353637313338353536663539303531663938336132356134
30323631646666396239323261366435656434623537343935393531633966643135633561633636
31626266366432613936643937646537636164646334316562396639363132313862616433653535
34666537653433663466323730373363363064393334386133366365383435616436623963626634
31376162306565623262663732356463616464376231356432316137373138356466393663653035
613336396164633836626637393036303336

View File

@@ -1,12 +1,9 @@
vms = { vms = {
waf1 = {}, waf1 = {},
waf2 = {}, waf2 = {},
waf3 = {}, waf3 = {},
www1 = {}, mysql1 = {},
www2 = {}, www1 = {},
www3 = {}, www2 = {},
proxy1 = { www3 = {},
memory = "8g"
disk = "20g"
},
} }