Private
Public Access
2
0

63 Commits

Author SHA1 Message Date
f5b4a493fd Update seb4itik/byow/galaxy.yml
All checks were successful
Publish collection to Galaxy / publish (push) Successful in 39s
2026-04-08 08:54:38 +02:00
15fc072b76 Update .gitea/workflows/publish.yml
Some checks failed
Publish collection to Galaxy / publish (push) Has been cancelled
2026-04-08 08:54:01 +02:00
2f1623b853 Update seb4itik/byow/galaxy.yml
Some checks failed
Publish collection to Galaxy / publish (push) Failing after 39s
2026-04-08 08:08:11 +02:00
b7c75f18f2 caddy: improve readability
Some checks failed
Publish collection to Galaxy / publish (push) Has been cancelled
2026-04-08 08:12:29 +03:00
8f76a7fa4f .gitignore 2026-03-18 09:55:53 +03:00
f2fe66668d example: remove known_hosts 2026-03-18 09:55:46 +03:00
cfd846f787 caddy: bin -> sbin 2026-03-18 09:55:23 +03:00
37ba58e8c2 caddy: add comments 2026-03-18 09:55:08 +03:00
3732b74306 consul: add link in sbin to binary 2026-03-18 09:54:56 +03:00
be58aa437c crowdsec: add jq in requirements 2026-03-18 09:54:31 +03:00
ad406e1b86 caddy: add jq in requirements 2026-03-18 09:54:13 +03:00
7ac6c74574 wordpress: add FIXME 2026-03-18 09:53:38 +03:00
63ed305852 Main target is now Debian 13 instead of Ubuntu 24.04 2026-03-18 09:53:24 +03:00
5199567bda add crowdsec role, move config logs site -> global, remove caddy_ca_root, modularized Caddy build and config
Some checks failed
Publish collection to Galaxy / publish (push) Failing after 42s
2026-03-16 21:52:36 +03:00
46089e1d7a example: fix double line 2026-03-16 21:19:46 +03:00
0999da5ac5 bump versions (Caddy, Go, Coraza Caddy, CRS) 2026-03-16 11:35:24 +03:00
ce565ecd44 caddy: fix caddy compilation 2026-03-16 11:34:34 +03:00
c6ce82732c Wordpress: fix permissions 2026-03-16 11:34:11 +03:00
f03a3c03b8 caddy: add site.header and path.strip_prefix
Some checks failed
Publish collection to Galaxy / publish (push) Failing after 42s
2026-03-11 15:40:38 +03:00
aa7ef1a41e Update seb4itik/byow/galaxy.yml
Some checks failed
Publish collection to Galaxy / publish (push) Failing after 48s
2026-03-10 13:24:03 +01:00
b9053b75a6 WIP: elastic, metabase
Some checks failed
Publish collection to Galaxy / publish (push) Failing after 57s
2026-03-10 15:19:53 +03:00
0e83e7d593 mysql: default db host localhost -> 127.0.0.1 2026-03-10 15:19:53 +03:00
f7d46af457 local_ca_certs: fix local_ca_certs_list default type 2026-03-10 15:19:53 +03:00
1b2476aeb5 wordpress: add wordpress_create_master 2026-03-10 15:19:53 +03:00
3261704b89 example: add more web backends 2026-03-10 15:19:53 +03:00
3453d66468 Update seb4itik/byow/galaxy.yml
Some checks failed
Publish collection to Galaxy / publish (push) Failing after 42s
2026-02-12 16:31:36 +01:00
61c3cba8e0 squid: readme
Some checks failed
Publish collection to Galaxy / publish (push) Has been cancelled
2026-02-12 16:28:47 +01:00
1d22405d3b Merge pull request 'dev' (#16) from dev into main
Some checks failed
Publish collection to Galaxy / publish (push) Failing after 40s
Reviewed-on: #16
2026-02-12 16:24:52 +01:00
7fe5e3f1e5 Merge branch 'main' into dev 2026-02-12 16:24:26 +01:00
91417f0e22 doc 2026-02-12 16:23:34 +01:00
7a35ed33bc example: misc fixes 2026-02-12 14:38:35 +01:00
1260ffcac0 example: no package update in cloud-init 2026-02-12 14:38:05 +01:00
5eeb74d53e consul: activate https 2026-02-12 14:37:42 +01:00
f80735417b caddy: fix site. in global log 2026-02-12 06:48:37 +01:00
91f0e92014 caddy: shell nologin 2026-02-12 06:47:41 +01:00
58ecc8ef98 consul: shell nologin 2026-02-12 06:47:28 +01:00
295b19aaa8 doc 2026-02-12 06:46:54 +01:00
b02fbe7117 caddy: fix bug cors origin without https:// 2026-02-09 08:46:37 +03:00
00c1f607f8 caddy: rename cors_allow_* -> cors_allowed_* 2026-02-09 08:45:45 +03:00
e3fc368704 caddy: add waf_id din logs 2026-02-06 19:58:35 +03:00
06112d7009 doc 2026-02-06 17:37:00 +03:00
d04643955a unbound: hardening 2026-02-06 17:36:47 +03:00
33fa678a40 consul: ACL, policies, tokens 2026-02-06 17:36:15 +03:00
67cea2f19e caddy: typpo in crs-setup.conf 2026-02-06 17:35:17 +03:00
b4f98a207d caddy: final integration with consul 2026-02-06 17:35:03 +03:00
c826b3a390 example: tests 2026-02-06 17:33:56 +03:00
72107cb471 exemple: inventory refactory 2026-02-06 17:33:17 +03:00
501cfacfe6 caddy: CRS v4.22.0 -> v4.23.0 2026-02-05 17:31:42 +03:00
df6f913d95 doc 2026-02-05 09:54:03 +03:00
943423143d tests 2026-02-05 09:53:50 +03:00
bbec8d35c5 optimizations 2026-02-05 09:53:38 +03:00
eecd5e9972 squid: add role 2026-02-05 09:52:04 +03:00
34f208b75b add comments, add *_add and *_del variables, add active and passive checks, change geoip2 db dir 2026-02-04 12:29:56 +03:00
34e401784b Clean old GeoIP implementation 2026-02-04 12:27:42 +03:00
17a52312dd add filter listadddel 2026-02-04 12:26:46 +03:00
a42ab66d4f caddy: send log to stdout for OVH LDP 2026-02-03 15:36:35 +03:00
99e240eec0 caddy: Caddyfile syntax args.0 -> args[0] 2026-02-03 14:25:35 +03:00
4aee70b07a Update seb4itik/byow/galaxy.yml
Some checks failed
Publish collection to Galaxy / publish (push) Failing after 40s
2026-02-02 20:23:20 +01:00
24034ad284 Merge pull request 'dev' (#15) from dev into main
Some checks failed
Publish collection to Galaxy / publish (push) Has been cancelled
Reviewed-on: #15
2026-02-02 20:22:50 +01:00
ac36dec16c README 2026-02-02 22:12:46 +03:00
c6edcc9bc5 workflows: publish and push artifacts 2026-02-02 22:11:07 +03:00
df42ad6e88 crs-setup: fix variables name 2026-02-02 22:10:35 +03:00
a213a75f72 coraza: add application/json to SecResponseBodyMimeType 2026-02-02 22:10:14 +03:00
66 changed files with 11002 additions and 671 deletions

View File

@@ -2,7 +2,9 @@ name: Publish collection to Galaxy
on: on:
push: push:
tags: tags:
- 'v*' - "v*"
branches:
- "main"
jobs: jobs:
publish: publish:
@@ -20,3 +22,12 @@ jobs:
# - uses: actions/gitea-upload-artifact@v3 # - uses: actions/gitea-upload-artifact@v3
# with: # with:
# path: /workspace/itik/bgwaf/seb4itik/byow/seb4itik-byow-0.1.2.tar.gz # path: /workspace/itik/bgwaf/seb4itik/byow/seb4itik-byow-0.1.2.tar.gz
# - name: Push artifacts
# run: |
# GITEA_REPO_OWNER=$(echo $GITEA_REPOSITORY | cut -d"/" -f1) && \
# GITEA_REPO_NAME=$(echo $GITEA_REPOSITORY | cut -d"/" -f2) && \
# curl \
# -X PUT \
# -H "Authorization: token ${{ secrets.CI_GITEA_PACKAGES_TOKEN }}" \
# --upload-file seb4itik-byow-${VER}.tar.gz \
# $GITEA_SERVER_URL/api/packages/${GITEA_REPO_OWNER}/generic/${GITEA_REPO_NAME}/${GITEA_SHA}/seb4itik-byow-${VER}.tar.gz

4
.gitignore vendored
View File

@@ -1,5 +1,7 @@
/notes/ notes/
.ansible/ .ansible/
__pycache__/ __pycache__/
infra-*/ infra-*/
.DS_Store .DS_Store
/seb4itik/byow/roles/elastic/
/seb4itik/byow/roles/metabase/

View File

@@ -1,4 +1,3 @@
/ca/ /ca/
/.vault_pass /.vault_pass
/ansible_local.env /ansible_local.env
/known_hosts

View File

@@ -5,4 +5,4 @@ vault_password_file = ./.vault_pass
[ssh_connection] [ssh_connection]
pipelining = True pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=300s -o StrictHostKeyChecking=no -o UserKnownHostsFile=./known_hosts ssh_args = -o ControlMaster=auto -o ControlPersist=300s -o StrictHostKeyChecking=no

View File

@@ -3,29 +3,38 @@ linux:
ansible_user: "root" ansible_user: "root"
debian13: debian13:
hosts:
bdd1:
nfs1:
waf1:
# waf2:
# waf3:
www1:
www2:
www3:
# www4:
# www5:
vars: vars:
ansible_python_interpreter: "/usr/bin/python3.13" ansible_python_interpreter: "/usr/bin/python3.13"
ubuntu24: ubuntu2204lts:
vars:
ansible_python_interpreter: "/usr/bin/python3.10"
ubuntu2404lts:
vars: vars:
ansible_python_interpreter: "/usr/bin/python3.12" ansible_python_interpreter: "/usr/bin/python3.12"
waf: test1:
hosts:
waf1:
waf2:
waf3:
mysql:
hosts:
sql1:
nfs:
hosts:
nfs1:
www:
hosts: hosts:
www1: www1:
www2: www2:
www3: www3:
test2:
hosts:
www4:
test3:
hosts:
www5:

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
import sys
import subprocess
import json
import re
out = subprocess.run(["multipass", "list", "--format=json"], capture_output=True)
if out.returncode != 0:
print(f"ERR executing multipass list: {out.returncode}")
sys.exit(out.returncode)
instances = json.loads(out.stdout)
ret = {
"_meta": {"hostvars": {}},
"linux": {"hosts": []}
}
for instance in instances['list']:
if instance['state'] != "Running":
continue
release = instance['release'].replace(' ', '').replace('.', '').lower()
if release not in ret:
ret[release] = {"hosts": []}
group = re.search('^[a-z]+', instance['name']).group()
if group not in ret:
ret[group] = {"hosts": []}
ret['_meta']['hostvars'][instance['name']] = {"ansible_host": instance['ipv4'][0] }
ret['linux']['hosts'].append(instance['name'])
ret[release]['hosts'].append(instance['name'])
ret[group]['hosts'].append(instance['name'])
print(json.dumps(ret))

View File

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

View File

@@ -17,6 +17,10 @@
- "DNS:{{ ansible_hostname }}" - "DNS:{{ ansible_hostname }}"
- "IP:{{ ansible_default_ipv4.address }}" - "IP:{{ ansible_default_ipv4.address }}"
caddy_config_only: true caddy_config_only: true
caddy_consul: true
caddy_consul_delegate_to: "waf1"
caddy_consul_admin_token: "{{ secrets.consul.bootstrap_secret }}"
caddy_consul_aes_key: "{{ secrets.caddy.storage_consul_aes_key }}"
caddy_sites: "{{ common.sites }}" caddy_sites: "{{ common.sites }}"
roles: roles:
- "local_ca_certs" - "local_ca_certs"

View File

@@ -4,8 +4,14 @@
- "vars/common.yml" - "vars/common.yml"
- "vars/secrets.yml" - "vars/secrets.yml"
vars: vars:
caddy_debug: true
caddy_config_only: true caddy_config_only: true
caddy_log_debug: true
caddy_crowdsec: true
caddy_crowdsec_api_key: "{{ secrets.crowdsec.lapi_key }}"
caddy_consul: true
caddy_consul_delegate_to: "waf1"
caddy_consul_admin_token: "{{ secrets.consul.bootstrap_secret }}"
caddy_consul_aes_key: "{{ secrets.caddy.storage_consul_aes_key }}"
caddy_sites: "{{ common.sites }}" caddy_sites: "{{ common.sites }}"
roles: roles:
- "caddy" - "caddy"

View File

@@ -24,6 +24,20 @@
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
@@ -31,6 +45,9 @@
- "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_search_domains:
- "multipass"
common_default_packages: common_default_packages:
- "net-tools" - "net-tools"
- "htop" - "htop"
@@ -59,20 +76,37 @@
- cn: "server.{{ common.consul.datacenter }}.{{ common.consul.domain }}" - cn: "server.{{ common.consul.datacenter }}.{{ common.consul.domain }}"
domains: domains:
- "DNS:server.{{ common.consul.datacenter }}.{{ common.consul.domain }}" - "DNS:server.{{ common.consul.datacenter }}.{{ common.consul.domain }}"
- "DNS:{{ ansible_fqdn }}"
- "DNS:{{ ansible_hostname }}"
# Sites # Sites
- cn: "test.lab" - cn: "test1.lab"
domains: domains:
- "DNS:test.lab" - "DNS:test1.lab"
- "DNS:{{ ansible_fqdn }}" - "DNS:{{ ansible_fqdn }}"
- "DNS:{{ ansible_hostname }}" - "DNS:{{ ansible_hostname }}"
- "IP:{{ ansible_default_ipv4.address }}" - "IP:{{ ansible_default_ipv4.address }}"
- cn: "test2.lab"
domains:
- "DNS:test2.lab"
- "DNS:{{ ansible_fqdn }}"
- "DNS:{{ ansible_hostname }}"
- "IP:{{ ansible_default_ipv4.address }}"
- cn: "test3.lab"
domains:
- "DNS:test3.lab"
- "DNS:{{ ansible_fqdn }}"
- "DNS:{{ ansible_hostname }}"
- "IP:{{ ansible_default_ipv4.address }}"
consul_server: true
consul_domain: "{{ common.consul.domain }}" consul_domain: "{{ common.consul.domain }}"
consul_datacenter: "{{ common.consul.datacenter }}" consul_datacenter: "{{ common.consul.datacenter }}"
consul_bootstrap_secret: "{{ secrets.consul.bootstrap_secret }}"
consul_secret_key: "{{ secrets.consul.secret_key }}" consul_secret_key: "{{ secrets.consul.secret_key }}"
consul_servers: ["waf1", "waf2", "waf3"] consul_servers: ["waf1", "waf2", "waf3"]
consul_server: true consul_ui_secret_id: "{{ secrets.consul.ui_secret }}"
consul_services: consul_services:
- name: "www" - name: "www"
address: "{{ ansible_default_ipv4.address }}"
port: 443 port: 443
check_http: "http://{{ ansible_default_ipv4.address }}:80/" check_http: "http://{{ ansible_default_ipv4.address }}:80/"
# keepalived_script_user: "byow" # keepalived_script_user: "byow"
@@ -98,37 +132,35 @@
unbound_stub_zones: unbound_stub_zones:
- name: "{{ common.consul.datacenter }}.{{ common.consul.domain }}" - name: "{{ common.consul.datacenter }}.{{ common.consul.domain }}"
stub_to: "127.0.0.1@8600" stub_to: "127.0.0.1@8600"
caddy_crowdsec: true
caddy_crowdsec_api_key: "{{ secrets.crowdsec.lapi_key }}"
caddy_consul: true
caddy_consul_delegate_to: "waf1"
caddy_consul_admin_token: "{{ secrets.consul.bootstrap_secret }}"
caddy_consul_aes_key: "{{ secrets.caddy.storage_consul_aes_key }}"
caddy_consul_client_cert: "/etc/ssl/certs/server.lab.itik.fr.crt"
caddy_consul_client_key: "/etc/ssl/private/server.lab.itik.fr.key"
caddy_sites: "{{ common.sites }}" caddy_sites: "{{ common.sites }}"
ovh_ldp_cluster: "gra2.logs.ovh.com" # ovh_ldp_cluster: "gra2.logs.ovh.com"
ovh_ldp_token: "{{ secrets.ovh_ldp.token }}" # ovh_ldp_token: "{{ secrets.ovh_ldp.token }}"
roles: roles:
- "local_ca_certs" - "local_ca_certs"
- "consul" - "consul"
- "crowdsec"
- "caddy" - "caddy"
# - "keepalived" # - "keepalived"
- "unbound" - "unbound"
- "ovh_ldp" # - "ovh_ldp"
tags: tags:
- "waf" - "waf"
- name: MySQL servers - name: MySQL servers
hosts: mysql hosts: bdd
gather_facts: false gather_facts: false
vars_files: vars_files:
- "vars/common.yml" - "vars/common.yml"
- "vars/secrets.yml" - "vars/secrets.yml"
vars: 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: mysql_databases:
- "wordpress" - "wordpress"
mysql_users: mysql_users:
@@ -136,22 +168,10 @@
password: "{{ secrets.wordpress.mysql_password }}" password: "{{ secrets.wordpress.mysql_password }}"
priv: "wordpress.*:ALL" priv: "wordpress.*:ALL"
host: "{{ ansible_default_ipv4.network }}/255.255.255.0" 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: roles:
- "local_ca_certs"
- "mysql" - "mysql"
- "consul"
tags: tags:
- "mysql" - "bdd"
- name: NFS servers - name: NFS servers
hosts: nfs hosts: nfs
@@ -165,65 +185,98 @@
owner: 1001 owner: 1001
group: 33 group: 33
clients: clients:
- address: "www1" - address: "www1.multipass"
options: "rw,no_root_squash" options: "rw,no_root_squash"
- address: "www2" - address: "www2.multipass"
options: "rw,no_root_squash" options: "rw,no_root_squash"
- address: "www3" - address: "www3.multipass"
options: "rw,no_root_squash" options: "rw,no_root_squash"
roles: roles:
- "nfs_server" - "nfs_server"
tags: tags:
- "nfs" - "nfs"
- name: HTTP Servers - name: HTTP Servers (test1.lab)
hosts: www hosts: test1
gather_facts: false gather_facts: false
vars_files: vars_files:
- "vars/common.yml" - "vars/common.yml"
- "vars/secrets.yml" - "vars/secrets.yml"
vars: 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 }}"
nfs_client_mounts: nfs_client_mounts:
- mount_point: "/var/www/wordpress" - mount_point: "/var/www/wordpress"
server: "nfs1:/srv/wordpress" server: "nfs1.multipass:/srv/wordpress"
owner: 1001 owner: 1001
group: 33 group: 33
wordpress_create_master: "www1" wordpress_create_master: "www1"
wordpress_site: "test.lab" wordpress_site: "test1.lab"
wordpress_url: "test.lab" wordpress_url: "test1.lab"
wordpress_site_title: "Lab Test" wordpress_site_title: "Lab Test I"
wordpress_admin_user: "spock" wordpress_admin_user: "spock1"
wordpress_admin_password: "{{ secrets.wordpress.admin_password }}" wordpress_admin_password: "{{ secrets.wordpress.admin_password }}"
wordpress_admin_email: "seb@itik.fr" wordpress_admin_email: "seb@itik.fr"
wordpress_themes: ["twentytwentyfive"] wordpress_themes: ["twentytwentyfive"]
wordpress_active_theme: "twentytwentyfive" wordpress_active_theme: "twentytwentyfive"
wordpress_db_host: "sql1" wordpress_db_host: "bdd1.multipass"
wordpress_db_pass: "{{ secrets.wordpress.mysql_password }}" 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: roles:
- "local_ca_certs"
- "nfs_client" - "nfs_client"
- "wordpress" - "wordpress"
- "consul"
tags: tags:
- "www" - "test1"
- name: HTTP Server (test2.lab)
hosts: test2
gather_facts: false
vars_files:
- "vars/common.yml"
- "vars/secrets.yml"
vars:
mysql_databases:
- "wordpress"
mysql_users:
- name: "wordpress"
password: "{{ secrets.wordpress.mysql_password }}"
priv: "wordpress.*:ALL"
wordpress_site: "test2.lab"
wordpress_url: "test2.lab"
wordpress_site_title: "Lab Test II"
wordpress_admin_user: "spock2"
wordpress_admin_password: "{{ secrets.wordpress.admin_password }}"
wordpress_admin_email: "seb@itik.fr"
wordpress_themes: ["twentytwentyfive"]
wordpress_active_theme: "twentytwentyfive"
wordpress_db_pass: "{{ secrets.wordpress.mysql_password }}"
roles:
- "mysql"
- "wordpress"
tags:
- "test2"
- name: HTTP Server (test3.lab)
hosts: test3
gather_facts: false
vars_files:
- "vars/common.yml"
- "vars/secrets.yml"
vars:
mysql_databases:
- "wordpress"
mysql_users:
- name: "wordpress"
password: "{{ secrets.wordpress.mysql_password }}"
priv: "wordpress.*:ALL"
wordpress_site: "test3.lab"
wordpress_url: "test3.lab"
wordpress_site_title: "Lab Test III"
wordpress_admin_user: "spock3"
wordpress_admin_password: "{{ secrets.wordpress.admin_password }}"
wordpress_admin_email: "seb@itik.fr"
wordpress_themes: ["twentytwentyfive"]
wordpress_active_theme: "twentytwentyfive"
wordpress_db_pass: "{{ secrets.wordpress.mysql_password }}"
roles:
- "mysql"
- "wordpress"
tags:
- "test3"

View File

@@ -11,29 +11,78 @@ common:
datacenter: "lab" datacenter: "lab"
sites: sites:
- id: "test"
name: "test.lab" - id: "test1"
name: "test1.lab"
paths: paths:
- addrs: - addrs:
- "http://www1" - "http://www1"
- "http://www2" - "http://www2"
- "http://www3" - "http://www3"
proxy_lb_policy: "random"
proxy_active_checks: true
proxy_active_checks_status: "301"
proxy_passive_checks: false
custom_cert: true custom_cert: true
custom_cert_file: "/etc/ssl/certs/test.lab.crt" custom_cert_file: "/etc/ssl/certs/test1.lab.crt"
custom_cert_key_file: "/etc/ssl/private/test.lab.key" custom_cert_key_file: "/etc/ssl/private/test1.lab.key"
filter_whitelist: ["10.60.117.1"] rate_limit_events: 10
rate_limit_window: "1m"
# filter_whitelist: ["10.60.117.1"]
crs_plugins: crs_plugins:
- "wordpress-rule-exclusions" - "wordpress-rule-exclusions"
crs_exceptions: # crs_exceptions:
# Cf. https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v3.x) # Cf. https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v3.x)
# before_request: # before_request:
# - 'SecRule REQUEST_FILENAME "@endsWith /wp-admin/admin-ajax.php" "id:1,nolog,ctl:ruleRemoveById=932260"' # - 'SecRule REQUEST_FILENAME "@endsWith /wp-admin/admin-ajax.php" "id:1,nolog,ctl:ruleRemoveById=932260"'
after_response: # after_response:
- 'SecRuleUpdateTargetById 932260 "!ARGS:tag-name"' # - 'SecRuleUpdateTargetById 932260 "!ARGS:tag-name"'
header_cross_origin_embedder_policy: "unsafe-none" header_cross_origin_embedder_policy: "unsafe-none"
header_content_security_policy: header_content_security_policy:
worker-src: "'self' blob:" worker-src: "'self' blob:"
img-src: "'self' data: https://secure.gravatar.com/avatar/" img-src: "'self' data: https://secure.gravatar.com/avatar/ https://s.w.org/images/"
font-src: "'self' data:"
script-src: "'self' 'unsafe-eval'"
script-src-elem: "'self' 'unsafe-inline'"
style-src-attr: "'self' 'unsafe-inline'"
style-src-elem: "'self' 'unsafe-inline'"
frame-src: "'self' blob:"
- id: "test2"
name: "test2.lab"
paths:
- addrs:
- "http://www4"
custom_cert: true
custom_cert_file: "/etc/ssl/certs/test2.lab.crt"
custom_cert_key_file: "/etc/ssl/private/test2.lab.key"
crs_plugins:
- "wordpress-rule-exclusions"
header_cross_origin_embedder_policy: "unsafe-none"
header_content_security_policy:
worker-src: "'self' blob:"
img-src: "'self' data: https://secure.gravatar.com/avatar/ https://s.w.org/images/"
font-src: "'self' data:"
script-src: "'self' 'unsafe-eval'"
script-src-elem: "'self' 'unsafe-inline'"
style-src-attr: "'self' 'unsafe-inline'"
style-src-elem: "'self' 'unsafe-inline'"
frame-src: "'self' blob:"
- id: "test3"
name: "test3.lab"
paths:
- addrs:
- "http://www4"
custom_cert: true
custom_cert_file: "/etc/ssl/certs/test3.lab.crt"
custom_cert_key_file: "/etc/ssl/private/test3.lab.key"
crs_plugins:
- "wordpress-rule-exclusions"
header_cross_origin_embedder_policy: "unsafe-none"
header_content_security_policy:
worker-src: "'self' blob:"
img-src: "'self' data: https://secure.gravatar.com/avatar/ https://s.w.org/images/"
font-src: "'self' data:" font-src: "'self' data:"
script-src: "'self' 'unsafe-eval'" script-src: "'self' 'unsafe-eval'"
script-src-elem: "'self' 'unsafe-inline'" script-src-elem: "'self' 'unsafe-inline'"

View File

@@ -1,24 +1,33 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
30356233646632316565363533613033376333306433626366623230633838356230363461383135 66313136343333353333666339316366663134303039333133376461333765383138663238376162
3834363137353762316262363134643763353632306362640a613538306235633565356563613532 3739613236313832623963346565393630636562356239640a613634643365653264373338393737
64643862613431313666363130376338356538323035346532636331316637646235346236333331 64626435636131666132646566393735653364353764366139616533393135366662353564636666
6336313466313434640a316437343866343061383838383362633638363633363535363330333463 3139636566613065340a383931353561376339373730376633656537663435653764663139316665
64616330333361643166663061306338616265316262646339663438306465623231336139653764 30666261313338656230323861633033323733346463326166323262613866323264663736633235
63353862303538636636306430333464383339316233326133623564613337306266353730616564 66313334343963316261613137363439633938383134656164323963636665376534653963346534
63366562626135626136663665353663323265346438346131363838383263663665653937393066 36383435323239666366653362373635306465316338303332656438333332623030333433643165
32353061396661346138386561633734353634363164663438656331383663323035613264653265 39333432616666353539653766656363373365373335643238373336653433646536313666393163
38646164633465363238623232393731633761393466326466653430316532633139636533613033 61653664626131343131376363316337373666396564646465333764353465393666636136646162
39303764363861353666356239306366633662643865663033383235336663623632373566383330 34336364303139343864303766393963313135343531366131633731633234623133353631636234
32363334653936393962633266666439623130616161373231313037343735616361303131383230 32626230393539343561393135386163363536376637376533356536356266613266613434396338
62333863313432353536363962623161383434363136316264663061343038633065316232313334 34343364356364356262346161653962313336656433346132623437626332366336643034326532
38653233356135623431343166356464666663636330373663303663326561363333633837303337 38346339333332303265353334386661353935623837633865373361313830613138613138323835
66616464393661653835636665353439353539393530623730656331316235643032336337616436 34656535383664373039333734396564623662363330643332646535386533376265343863626632
64373163373932653038613765633061306331363962333932383564333735353534646637303237 36636537393162373064323361376361306664363065633961363033313031313763613134353436
62376561303364386662343331373631343166326261636362623432323437643735663437333564 63366434363137346263653966323239613164363634613235656364663037386537386335666431
39323961626131616334323131393963323231626638393231326631306337306230666138323830 65363162623031633166613735393735333133626466313061396333306236393039383062326264
63373331636234646431666332633633353637313338353536663539303531663938336132356134 38343464636330326162653036326663383631613836656461303462333035636335326135303766
30323631646666396239323261366435656434623537343935393531633966643135633561633636 37323463666338323766393063633662343835393330326332616331373633663366393563303438
31626266366432613936643937646537636164646334316562396639363132313862616433653535 37666162633461313333623934633138663930623135663638386634626430346264633735333532
34666537653433663466323730373363363064393334386133366365383435616436623963626634 64623461613538343436306436626133383733393737393463306539383763663866346164306339
31376162306565623262663732356463616464376231356432316137373138356466393663653035 64303335303539306632306164613761343035306632303063393233633262333264333431333363
613336396164633836626637393036303336 30373861636565363438346338376531346162303564653435373237636237366632323564646539
37383361356362653132653961376134663930363763326437306338623064623862336435373664
63393262323039616539626137396432613932633730316235396435396531366466616264393161
62323339393039323235383663663265643065626137636562656335343233313734366166336264
35633765316262633636323866633266653037326165646230326564323033613963316331346331
32346265386164376361303532343730343139393837343162666238633366643361353332376137
33633764323133316334323234353932363165636438656563353630373238376465643463656535
30383337303861326433303137383539313536333465663063306137646264316438636665323263
34383933666565333962363863313536653232363930306665336432373738353238666362353639
37316136373639373233

View File

@@ -2,3 +2,5 @@ users:
- name: "root" - name: "root"
ssh_authorized_keys: ssh_authorized_keys:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOHAAcWecBuLe9gt/NLC5b3DCl68knbwCFcHrH9wPPTk seb@itik.fr" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOHAAcWecBuLe9gt/NLC5b3DCl68knbwCFcHrH9wPPTk seb@itik.fr"
package_update: false
package_upgrade: false

View File

@@ -1,10 +1,27 @@
vms = { vms = {
waf1 = {}, # proxy1 = {
waf2 = {}, # disk = "20g"
waf3 = {}, # },
sql1 = {}, waf1 = {
cpus = 4
},
waf2 = {
cpus = 4
image = "22.04"
},
waf3 = {
cpus = 4
image = "24.04"
},
bdd1 = {},
nfs1 = {}, nfs1 = {},
www1 = {}, www1 = {},
www2 = {}, www2 = {},
www3 = {}, www3 = {},
www4 = {
image = "22.04"
},
www5 = {
image = "24.04"
},
} }

View File

@@ -4,6 +4,6 @@ variable "vms" {
cpus = optional(number, 2), cpus = optional(number, 2),
memory = optional(string, "3g"), memory = optional(string, "3g"),
disk = optional(string, "12g"), disk = optional(string, "12g"),
image = optional(string, "24.04"), image = optional(string, "https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2"),
})) }))
} }

View File

@@ -72,8 +72,8 @@ Fonctionnalités déjà implémentées:
- Déploiement et configuration avec une collection Ansible - Déploiement et configuration avec une collection Ansible
- Serveurs transient - Serveurs transient
- VIP (avec Keepalived) - VIP (avec Keepalived)
- DNS dynamique (via Nomad) - DNS dynamique (via Consul)
- Cluster via Nomad - Cluster via Consul
- OWASP CRS - OWASP CRS
- Multisite (avec configuration complète des CRS par site) - Multisite (avec configuration complète des CRS par site)
- Alias avec redirection optionnelle vers nom canonique - Alias avec redirection optionnelle vers nom canonique
@@ -105,19 +105,15 @@ Fonctionnalités déjà implémentées:
- Collecte des logs via OVH LDP - Collecte des logs via OVH LDP
- Whitelist - Whitelist
- Static files et FastCGI pour PHP (NON, par design !) - Static files et FastCGI pour PHP (NON, par design !)
- Rate limit (non distribué) - Authorizations (NON, par design !)
- CORS
Urgemment: - Rate limiting distribué
- Connexion à consul, ACL consul
- Active backend checks
- Correction de la collecte des logs via OVH LDP
- Faire fonctionner le filtre dict2str via la collection Ansible
Fonctionnalités à ajouter: Fonctionnalités à ajouter:
- Rate limiting distribué - version de crs-setup.conf en fonction de la version des OWASP CRS
- Revérifier gestion CORS - option "api: true" for activating API default settings
- Antivirus
- Antibot par: - Antibot par:
- Cookie - Cookie
- Captcha - Captcha
@@ -134,17 +130,24 @@ Fonctionnalités à ajouter:
- (ip:danmeuk-tor-exit, ua:mitchellkrogza-bad-user-agents) - (ip:danmeuk-tor-exit, ua:mitchellkrogza-bad-user-agents)
- Client cache - Client cache
- Proxy cache - Proxy cache
- Random WAF signatures
- Compression Brotli - Compression Brotli
- Reverse scan - Reverse scan
- Robots.txt (DarkVisitors API/Community Lists/Custom URLs/Manual Rules) - Robots.txt (DarkVisitors API/Community Lists/Custom URLs/Manual Rules)
- Gestion des sessions - Gestion des sessions
- Bad behavior (via Caddy events ?) - Bad behavior (via Caddy events ?)
- Authentification - Authentification
- cf. https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
- avec vérification force du mot de passe (cf. CdC Essor)
- avec test mot de passe moins un caractère
- avec test mot de passe comprommis
- Anti DDoS - Anti DDoS
- Greylist - Greylist
- HTML Sanitization
- HTML injection - HTML injection
- Metrics (Elasticsearch + Metabase) - Metrics (Elasticsearch + Metabase)
- Crowdsec - Crowdsec
- Stream support
- Caddy as internal CA - Caddy as internal CA
- https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list - https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list
@@ -157,6 +160,18 @@ Fonctionnalités à ajouter:
- [Documentation AWOSP CRS](https://coreruleset.org/docs/) - [Documentation AWOSP CRS](https://coreruleset.org/docs/)
- https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v3.x) - https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v3.x)
- (1) https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html
- (2) https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.html
- https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html
- https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html
- https://cheatsheetseries.owasp.org/cheatsheets/Cookie_Theft_Mitigation_Cheat_Sheet.html
- https://cheatsheetseries.owasp.org/cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.html
- https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
- https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html
- https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html
### Installation d'Ansible avec pipx ### Installation d'Ansible avec pipx

View File

@@ -1,6 +1,6 @@
namespace: "seb4itik" namespace: "seb4itik"
name: "byow" name: "byow"
version: "0.2.0" version: "0.3.3"
readme: "README.md" readme: "README.md"
authors: authors:
- "S. Namèche <sebastien@itik.fr>" - "S. Namèche <sebastien@itik.fr>"

View File

@@ -0,0 +1,52 @@
#!/usr/bin/python
DOCUMENTATION = r"""
name: listadddel
short_description: This filter add and remove items to/from a list.
version_added: "0.2.2"
author: Sébastien Namèche (@seb4itik)
description:
- If my_list is ["un", "deux", "trois"], then:
- {{ my_list | listadddel(["quatre", "cinq"], ["un"]) }}
- will produce:
- ["deux", "trois", "quatre", "cinq"]
options:
_input:
description: A list of strings.
type: list
elements: str
required: true
add:
description: A list of string to add to _input.
type: list
elements: str
default: []
delete:
description: A list of string to remove from _input.
type: list
elements: str
default: []
notes:
- delete is tronger than add. If the string is present in both delete and
add, it will not be in returned list.
"""
RETURN = r"""
_value:
type: list
elements: str
description:
- List of strings with new and removed elements.
"""
class FilterModule(object):
def filters(self):
return {
'listadddel': self.listadddel
}
def listadddel(self, l, add=[], delete=[]):
return [e for e in l+add if e not in delete]

View File

@@ -1,38 +1,85 @@
# Defaults # Several instances of the WAF can be deployed onto the same host if their
caddy_config_only: false # names are different and they bind to different ports/interfaces. These
caddy_debug: false # instances may use different versions of Go, xcaddy, Caddy, and Coraza.
caddy_my_name: "mywaf"
caddy_http_port: 80 caddy_http_port: 80
caddy_https_port: 443 caddy_https_port: 443
caddy_default_bind: "" caddy_default_bind: null
caddy_version: "2.10.2" # Sotware versions
caddy_go_version: "1.25.6" caddy_go_version: "1.26.1"
caddy_xcaddy_version: "0.4.5" caddy_xcaddy_version: "0.4.5"
caddy_coraza_caddy_version: "2.1.0" # Coraza v3.3.3 caddy_version: "2.11.2"
caddy_owasp_crs_versions: ["4.22.0"]
caddy_my_name: "mywaf" # If true, will not install software, just deploy Caddyfile and OWASP CRS
# configuration files.
caddy_config_only: false
# Log settings
# If log_to_file is false, will log to journald.
# If log_to_file is true, logs will go:
# - for each site, in: /var/log/<caddy_my_name>/site_<sit.id>.log
# - for WAF logs (Coraza), in /var/log/<caddy_my_name>/waf.log
caddy_log_to_file: true
caddy_log_format: "console" # "console" or "json"
caddy_log_debug: false # Debug will go to /var/log/<caddy_my_name>/debug.log
# Paths
caddy_binary: "/usr/local/sbin/caddy_{{ caddy_my_name }}" # Should begin by "caddy" for CrowdSec
caddy_system_user: "{{ caddy_my_name }}" caddy_system_user: "{{ caddy_my_name }}"
caddy_system_group: "{{ caddy_my_name }}" caddy_system_group: "{{ caddy_my_name }}"
caddy_home_dir: "/opt/{{ caddy_my_name }}" caddy_home_dir: "/opt/{{ caddy_my_name }}"
caddy_config_dir: "/etc/{{ caddy_my_name }}" caddy_config_dir: "/etc/{{ caddy_my_name }}"
caddy_crs_plugins_dir: "{{ caddy_config_dir }}/crs-plugins"
caddy_sites_dir: "{{ caddy_config_dir }}/sites"
caddy_log_dir: "/var/log/{{ caddy_my_name }}" caddy_log_dir: "/var/log/{{ caddy_my_name }}"
# Required for Let's Encrypt auto certificates # Required for Let's Encrypt auto certificates.
caddy_email: null caddy_email: null
# If a custom CA root have to be used # For using a Consul cluster.
caddy_ca_root: null caddy_consul: false
caddy_consul_server: "{{ ansible_fqdn }}:8501"
caddy_consul_delegate_to: null # Required, Consul host for running Consul commands
caddy_consul_admin_token: null # Required
caddy_consul_aes_key: null # Required, 32 characters
caddy_consul_client_cert: null # Required, CN must be "server.<consul datacenter>.<consul domain>"
# At least, SANs must include:
# - "DNS:server.<consul datacenter>.<consul domain>"
# - "DNS:<host FQDN>"
caddy_consul_client_key: null # Required
# Accound id and license key are required if a site use Geoip filtering.
# See: https://dev.maxmind.com/geoip/updating-databases/ # See: https://dev.maxmind.com/geoip/updating-databases/
caddy_geoip_account_id: null caddy_geoip: false
caddy_geoip_license_key: null caddy_geoip_account_id: null # Required
caddy_geoip_license_key: null # Required
caddy_geoip_edition_ids: "GeoLite2-ASN,GeoLite2-Country,GeoLite2-City" caddy_geoip_edition_ids: "GeoLite2-ASN,GeoLite2-Country,GeoLite2-City"
caddy_geoip_update_frequency: 86400 # In seconds caddy_geoip_update_frequency: 86400 # In seconds
# CrowdSec
caddy_crowdsec: false
caddy_crowdsec_api_key: null # Required
caddy_crowdsec_api_url: "http://127.0.0.1:8080"
caddy_crowdsec_ticker_interval: 15 # In seconds
caddy_crowdsec_appsec_url: null # "http://localhost:7422"
caddy_crowdsec_streaming: true
caddy_crowdsec_hard_fails: true
# Rate Limit
caddy_ratelimit: true
# Bot barrier
caddy_bot_barrier: false
# Coraza
caddy_coraza: true
caddy_coraza_caddy_version: "v2@v2.2.0" # Coraza v3.3.3
# Different sites can use different versions of OWASP CRS
caddy_coraza_crs_versions: ["4.24.1"]
# OWASP CRS plugins that can be actived per site.
# Cf.: https://github.com/coreruleset/plugin-registry # Cf.: https://github.com/coreruleset/plugin-registry
caddy_crs_plugins: caddy_coraza_crs_plugins:
- name: "drupal-rule-exclusions" - name: "drupal-rule-exclusions"
version: "1.0.0" version: "1.0.0"
- name: "wordpress-rule-exclusions" - name: "wordpress-rule-exclusions"
@@ -52,15 +99,18 @@ caddy_crs_plugins:
provider: "EsadCetiner" provider: "EsadCetiner"
# For each site, mandatories parameters # Sites
caddy_sites: [] caddy_sites: []
# - id: "" # - id: "" # Mandatory: short id for the site (for site specific directories and files)
# name: "" # name: "" # Mandatory: canonical domain name
# paths: # paths: # Mandatory: targets for the reverse proxy
# - path: "" (default "*") # - path: "*" # Optionnal: path of the request, default "*"
# addrs: (minimum 1 required) # strip_prefix: false # Optionnal: default "false"
# - "" # addrs: # Mandatory: upstreams
# - "" # Mandatory: URL of the upstream, minimum 1 required
# ... # Site configuration directives (see caddy_global_sites_defaults in vars/main.yml)
# Will be merged with caddy_global_sites_defaults # For overriding default global parameters. Will be merged with
# caddy_global_sites_defaults (in vars/main.yml).
caddy_my_sites_defaults: {} caddy_my_sites_defaults: {}

View File

@@ -1,8 +1,13 @@
- name: Reload systemd
ansible.builtin.service:
daemon_reload: true
- name: Restart Caddy - name: Restart Caddy
ansible.builtin.service: ansible.builtin.service:
name: "{{ caddy_my_name }}" name: "{{ caddy_my_name }}"
state: "restarted" state: "restarted"
# - name: Update GeoIP databases - name: Restart Crowdsec Securiy Engine
# ansible.builtin.command: ansible.builtin.systemd_service:
# cmd: "/usr/bin/geoipupdate" name: "crowdsec"
state: "restarted"

View File

@@ -2,57 +2,72 @@
ansible.builtin.set_fact: ansible.builtin.set_fact:
caddy_sites_defaults: "{{ caddy_global_sites_defaults | combine(caddy_my_sites_defaults) }}" caddy_sites_defaults: "{{ caddy_global_sites_defaults | combine(caddy_my_sites_defaults) }}"
- name: Create sites config directories - name: Configure Caddy Coraza CRS
loop: "{{ caddy_sites }}" when: "caddy_coraza"
ansible.builtin.file: block:
path: "{{ caddy_config_dir }}/sites/{{ item.id }}"
state: "directory"
group: "{{ caddy_system_group }}"
mode: "0750"
- name: Generate sites coraza.conf config file - name: Create sites config directories
loop: "{{ caddy_sites }}" loop: "{{ caddy_sites }}"
loop_control: ansible.builtin.file:
loop_var: "site" path: "{{ caddy_sites_dir }}/{{ item.id }}"
ansible.builtin.template: state: "directory"
src: "templates/coraza.conf" group: "{{ caddy_system_group }}"
dest: "{{ caddy_config_dir }}/sites/{{ site.id }}/coraza.conf" mode: "0750"
group: "{{ caddy_system_group }}"
mode: "0640"
notify:
- "Restart Caddy"
- name: Generate sites crs-setup config file - name: Generate sites coraza.conf config file
loop: "{{ caddy_sites }}" loop: "{{ caddy_sites }}"
loop_control: loop_control:
loop_var: "site" loop_var: "site"
ansible.builtin.template: ansible.builtin.template:
src: "templates/crs-setup.conf" src: "templates/coraza.conf"
dest: "{{ caddy_config_dir }}/sites/{{ site.id }}/crs-setup.conf" dest: "{{ caddy_sites_dir }}/{{ site.id }}/coraza.conf"
group: "{{ caddy_system_group }}" group: "{{ caddy_system_group }}"
mode: "0640" mode: "0640"
notify: notify:
- "Restart Caddy" - "Restart Caddy"
- name: Generate sites exclusions before request file - name: Generate sites crs-setup config file
loop: "{{ caddy_sites }}" loop: "{{ caddy_sites }}"
loop_control: loop_control:
loop_var: "site" loop_var: "site"
ansible.builtin.template: ansible.builtin.template:
src: "templates/exclusions-request-before.conf" src: "templates/crs-setup.conf"
dest: "{{ caddy_config_dir }}/sites/{{ site.id }}/exclusions-request-before.conf" dest: "{{ caddy_sites_dir }}/{{ site.id }}/crs-setup.conf"
group: "{{ caddy_system_group }}" group: "{{ caddy_system_group }}"
mode: "0640" mode: "0640"
notify: notify:
- "Restart Caddy" - "Restart Caddy"
- name: Generate sites exclusions after response file - name: Generate sites exclusions before request file
loop: "{{ caddy_sites }}" loop: "{{ caddy_sites }}"
loop_control: loop_control:
loop_var: "site" loop_var: "site"
ansible.builtin.template: ansible.builtin.template:
src: "templates/exclusions-response-after.conf" src: "templates/exclusions-request-before.conf"
dest: "{{ caddy_config_dir }}/sites/{{ site.id }}/exclusions-response-after.conf" dest: "{{ caddy_sites_dir }}/{{ site.id }}/exclusions-request-before.conf"
group: "{{ caddy_system_group }}"
mode: "0640"
notify:
- "Restart Caddy"
- name: Generate sites exclusions after response file
loop: "{{ caddy_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.template:
src: "templates/exclusions-response-after.conf"
dest: "{{ caddy_sites_dir }}/{{ site.id }}/exclusions-response-after.conf"
group: "{{ caddy_system_group }}"
mode: "0640"
notify:
- "Restart Caddy"
# FIXME: Should per site
- name: Copy bot barrier template
when: "caddy_bot_barrier"
ansible.builtin.copy:
src: "files/bot_barrier_template.html"
dest: "{{ caddy_config_dir }}/bot_barrier_template.html"
group: "{{ caddy_system_group }}" group: "{{ caddy_system_group }}"
mode: "0640" mode: "0640"
notify: notify:
@@ -67,27 +82,8 @@
notify: notify:
- "Restart Caddy" - "Restart Caddy"
- name: Copy bot barrier template
ansible.builtin.copy:
src: "files/bot_barrier_template.html"
dest: "{{ caddy_config_dir }}/bot_barrier_template.html"
group: "{{ caddy_system_group }}"
mode: "0640"
notify:
- "Restart Caddy"
# - name: Generate GeoIP config file
# when: "caddy_geoip_account_id != None and caddy_geoip_license_key != None"
# ansible.builtin.template:
# src: "templates/GeoIP.conf"
# dest: "/etc/GeoIP.conf"
# mode: "0640"
# notify:
# - "Update GeoIP databases"
- name: Enable and start Caddy - name: Enable and start Caddy
ansible.builtin.service: ansible.builtin.service:
name: "{{ caddy_my_name }}" name: "{{ caddy_my_name }}"
state: "started" state: "started"
enabled: true enabled: true
daemon_reload: true

View File

@@ -0,0 +1,47 @@
- name: Create consul policy for Caddy TLS and rate limit
delegate_to: "{{ caddy_consul_delegate_to }}"
run_once: true
community.general.consul_policy:
token: "{{ caddy_consul_admin_token }}"
name: "caddy"
rules: |
key_prefix "caddy-{{ caddy_my_name }}-tls" {
policy = "write"
}
key_prefix "caddy-{{ caddy_my_name }}-rl" {
policy = "write"
}
session_prefix "" {
policy = "write"
}
node_prefix "" {
policy = "read"
}
agent_prefix "" {
policy = "read"
}
- name: Create/retrieve Consul Caddy accessor_id
ansible.builtin.shell:
executable: "/usr/bin/bash"
cmd: |
set -o pipefail
if test -f /root/.consul_caddy_accessor_id; then
echo OK
else
uuidgen > /root/.consul_caddy_accessor_id
echo CHANGED
fi
cat /root/.consul_caddy_accessor_id
register: "consul_out_caddy_accessor_id"
changed_when: "consul_out_caddy_accessor_id.stdout_lines[0] == 'CHANGED'"
- name: Create/retrieve consul token
delegate_to: "{{ caddy_consul_delegate_to }}"
community.general.consul_token:
token: "{{ caddy_consul_admin_token }}"
accessor_id: "{{ consul_out_caddy_accessor_id.stdout_lines[1] }}"
description: "Caddy {{ caddy_my_name }} {{ ansible_fqdn }}"
policies:
- name: "caddy"
register: "caddy_out_consul_token"

View File

@@ -0,0 +1,19 @@
- name: Get collections list
ansible.builtin.shell:
executable: "/usr/bin/bash"
cmd: |
set -o pipefail
cscli collection list --output=json |jq -r '.collections[] |.name'
register: "crowdsec_out_list_collections"
changed_when: false
- name: Install collections
loop:
- "crowdsecurity/caddy"
- "barnoux/caddy-coraza"
when: "item not in crowdsec_out_list_collections.stdout_lines"
ansible.builtin.command:
cmd: "cscli collections install {{ item }}"
changed_when: true
notify:
- "Restart Crowdsec Securiy Engine"

View File

@@ -4,7 +4,7 @@
- "git" - "git"
- "libnss3-tools" - "libnss3-tools"
- "ssl-cert" - "ssl-cert"
# - "geoipupdate" - "jq"
- name: Create Go directory - name: Create Go directory
ansible.builtin.file: ansible.builtin.file:
@@ -16,6 +16,9 @@
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "https://go.dev/dl/go{{ caddy_go_version }}.linux-amd64.tar.gz" src: "https://go.dev/dl/go{{ caddy_go_version }}.linux-amd64.tar.gz"
dest: "/usr/local/go-{{ caddy_go_version }}" dest: "/usr/local/go-{{ caddy_go_version }}"
creates: "/usr/local/go-{{ caddy_go_version }}/bin/go"
extra_opts:
- "--strip-components=1"
remote_src: true remote_src: true
- name: Create xcaddy directory - name: Create xcaddy directory
@@ -28,25 +31,41 @@
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "https://github.com/caddyserver/xcaddy/releases/download/v{{ caddy_xcaddy_version }}/xcaddy_{{ caddy_xcaddy_version }}_linux_amd64.tar.gz" src: "https://github.com/caddyserver/xcaddy/releases/download/v{{ caddy_xcaddy_version }}/xcaddy_{{ caddy_xcaddy_version }}_linux_amd64.tar.gz"
dest: "/usr/local/xcaddy-{{ caddy_xcaddy_version }}" dest: "/usr/local/xcaddy-{{ caddy_xcaddy_version }}"
creates: "/usr/local/xcaddy-{{ caddy_xcaddy_version }}/xcaddy"
remote_src: true remote_src: true
- name: Create Caddy build command - name: Create Caddy build command
ansible.builtin.set_fact: ansible.builtin.set_fact:
caddy_build_command: > caddy_build_command: >
/usr/local/xcaddy-{{ caddy_xcaddy_version }}/xcaddy build v{{ caddy_version }} /usr/local/xcaddy-{{ caddy_xcaddy_version }}/xcaddy build v{{ caddy_version }}
--with github.com/pteich/caddy-tlsconsul {% if caddy_coraza %}
--with github.com/corazawaf/coraza-caddy/v2@v{{ caddy_coraza_caddy_version }} --with github.com/corazawaf/coraza-caddy/{{ caddy_coraza_caddy_version }}
{% endif %}
{% if caddy_ratelimit %}
--with github.com/mholt/caddy-ratelimit --with github.com/mholt/caddy-ratelimit
{% endif %}
{% if caddy_consul %}
--with github.com/pteich/caddy-tlsconsul
{% endif %}
{% if caddy_bot_barrier %}
--with github.com/steffenbusch/caddy-bot-barrier --with github.com/steffenbusch/caddy-bot-barrier
{% endif %}
{% if caddy_geoip %}
--with github.com/zhangjiayin/caddy-geoip2 --with github.com/zhangjiayin/caddy-geoip2
--output /usr/local/bin/{{ caddy_my_name }} {% endif %}
{% if caddy_crowdsec %}
--with github.com/hslatman/caddy-crowdsec-bouncer/http
--with github.com/hslatman/caddy-crowdsec-bouncer/layer4
--with github.com/hslatman/caddy-crowdsec-bouncer/appsec
{% endif %}
--output {{ caddy_binary }}
- name: Remember and test Caddy version - name: Remember and test Caddy version
ansible.builtin.copy: ansible.builtin.copy:
dest: "/usr/local/bin/.caddy.build" dest: "{{ caddy_binary }}.build"
content: > content: >
XCADDY_WHICH_GO=/usr/local/go-{{ caddy_go_version }}/go/bin/go XCADDY_WHICH_GO=/usr/local/go-{{ caddy_go_version }}/bin/go
{{ caddy_build_command }}" {{ caddy_build_command }}
mode: "0644" mode: "0644"
register: caddy_test_caddy_version register: caddy_test_caddy_version
@@ -54,10 +73,9 @@
# noqa: no-handler # noqa: no-handler
when: "caddy_test_caddy_version.changed" when: "caddy_test_caddy_version.changed"
environment: environment:
XCADDY_WHICH_GO: "/usr/local/go-{{ caddy_go_version }}/go/bin/go" XCADDY_WHICH_GO: "/usr/local/go-{{ caddy_go_version }}/bin/go"
ansible.builtin.command: ansible.builtin.command:
cmd: "{{ caddy_build_command }}" cmd: "{{ caddy_build_command }}"
creates: "/usr/local/bin/{{ caddy_my_name }}"
notify: notify:
- "Restart Caddy" - "Restart Caddy"
@@ -73,39 +91,59 @@
groups: ["ssl-cert"] groups: ["ssl-cert"]
password: "!" password: "!"
system: true system: true
shell: "/usr/sbin/nologin"
home: "{{ caddy_home_dir }}" home: "{{ caddy_home_dir }}"
- name: Create Caddy config directories - name: Create Caddy directories
ansible.builtin.file: ansible.builtin.file:
path: "{{ item }}" path: "{{ item.path }}"
state: "directory" state: "directory"
owner: "{{ item.owner | default('root') }}"
group: "{{ caddy_system_group }}" group: "{{ caddy_system_group }}"
mode: "0750" mode: "{{ item.mode | default('0750') }}"
loop: loop:
- "{{ caddy_config_dir }}" - path: "{{ caddy_config_dir }}"
- "{{ caddy_config_dir }}/crs-plugins" - path: "{{ caddy_crs_plugins_dir }}"
- "{{ caddy_config_dir }}/sites" - path: "{{ caddy_sites_dir }}"
- path: "{{ caddy_log_dir }}"
- name: Create Caddy log dir mode: "0770"
ansible.builtin.file: - path: "{{ caddy_home_dir }}/geoip2"
path: "{{ caddy_log_dir }}" owner: "{{ caddy_system_user }}"
state: "directory"
group: "{{ caddy_system_group }}"
mode: "0770"
- name: Install OWASP CRS - name: Install OWASP CRS
loop: "{{ caddy_owasp_crs_versions }}" when: "caddy_coraza"
loop: "{{ caddy_coraza_crs_versions }}"
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "https://github.com/coreruleset/coreruleset/archive/refs/tags/v{{ item }}.tar.gz" src: "https://github.com/coreruleset/coreruleset/archive/refs/tags/v{{ item }}.tar.gz"
dest: "{{ caddy_config_dir }}" dest: "{{ caddy_config_dir }}"
creates: "{{ caddy_config_dir }}/coreruleset-{{ item }}/rules/"
group: "{{ caddy_system_group }}" group: "{{ caddy_system_group }}"
remote_src: true remote_src: true
- name: Install plugins - name: Install CRS plugins
loop: "{{ caddy_crs_plugins }}" when: "caddy_coraza"
loop_control: loop: "{{ caddy_coraza_crs_plugins }}"
loop_var: "plugin" ansible.builtin.unarchive:
ansible.builtin.include_tasks: "plugin_install.yml" src: "https://github.com/{{ item.provider | default('coreruleset') }}/{{ item.name }}-plugin/archive/refs/tags/v{{ item.version }}.tar.gz"
dest: "{{ caddy_crs_plugins_dir }}"
creates: "{{ caddy_crs_plugins_dir }}/{{ item.name }}-config.conf"
include:
- "*.conf"
extra_opts:
- "--strip-components=2"
- "--wildcards"
mode: "0644"
group: "{{ caddy_system_group }}"
remote_src: true
- name: Create CRS plugins after config
loop: "{{ caddy_coraza_crs_plugins }}"
when: "caddy_coraza and not(item.has_after_config | default(false))"
ansible.builtin.copy:
content: ""
dest: "{{ caddy_crs_plugins_dir }}/{{ item.name }}-after.conf"
mode: "0644"
group: "{{ caddy_system_group }}"
- name: Deploy caddy service file - name: Deploy caddy service file
ansible.builtin.template: ansible.builtin.template:
@@ -113,4 +151,5 @@
dest: "/etc/systemd/system/{{ caddy_my_name }}.service" dest: "/etc/systemd/system/{{ caddy_my_name }}.service"
mode: "0644" mode: "0644"
notify: notify:
- "Reload systemd"
- "Restart Caddy" - "Restart Caddy"

View File

@@ -2,5 +2,13 @@
when: "not(caddy_config_only)" when: "not(caddy_config_only)"
ansible.builtin.include_tasks: "caddy_install.yml" ansible.builtin.include_tasks: "caddy_install.yml"
- name: Configure Consul for Caddy
when: "caddy_consul"
ansible.builtin.include_tasks: "caddy_consul.yml"
- name: Configure CrowdSec for Caddy
when: "caddy_crowdsec"
ansible.builtin.include_tasks: "caddy_crowdsec.yml"
- name: Configure Caddy - name: Configure Caddy
ansible.builtin.include_tasks: "caddy_config.yml" ansible.builtin.include_tasks: "caddy_config.yml"

View File

@@ -1,55 +0,0 @@
- name: Download plugin {{ plugin.name }}
ansible.builtin.get_url:
url: "https://github.com/{{ plugin.provider | default('coreruleset') }}/{{ plugin.name }}-plugin/archive/refs/tags/v{{ plugin.version }}.tar.gz"
dest: "/usr/local/src/"
mode: "0644"
register: "caddy_plugin_downloaded"
- name: Deploy plugin {{ plugin.name }}
# noqa: no-handler
when: "caddy_plugin_downloaded.changed"
block:
- name: Get temp dir {{ plugin.name }}
ansible.builtin.tempfile:
state: "directory"
register: "caddy_temp_plugin_dir"
- name: Unarchive plugin {{ plugin.name }}
ansible.builtin.unarchive:
src: "/usr/local/src/{{ plugin.name }}-plugin-{{ plugin.version }}.tar.gz"
dest: "{{ caddy_temp_plugin_dir.path }}"
remote_src: true
- name: Copy plugin files {{ plugin.name }}
ansible.builtin.copy:
src: "{{ caddy_temp_plugin_dir.path }}/{{ plugin.name }}-plugin-{{ plugin.version }}/plugins/"
dest: "{{ caddy_config_dir }}/crs-plugins/"
mode: "0644"
# directory_mode: "0750"
group: "{{ caddy_system_group }}"
remote_src: true
notify:
- "Restart Caddy"
- name: Create after config {{ plugin.name }}
when: "not(plugin.has_after_config | default(false))"
ansible.builtin.copy:
content: ""
dest: "{{ caddy_config_dir }}/crs-plugins/{{ plugin.name }}-after.conf"
mode: "0644"
group: "{{ caddy_system_group }}"
# FIXME: bad hack, why is it required?
- name: Adjust plugins diretory permissions
ansible.builtin.file:
path: "{{ caddy_config_dir }}/crs-plugins"
state: "directory"
group: "{{ caddy_system_group }}"
mode: "0750"
- name: Delete temp dir {{ plugin.name }}
when: "caddy_temp_plugin_dir.path is defined"
ansible.builtin.file:
path: "{{ caddy_temp_plugin_dir.path }}"
state: "absent"

View File

@@ -1,20 +1,42 @@
{ {
http_port {{ caddy_http_port }} http_port {{ caddy_http_port }}
https_port {{ caddy_https_port }} https_port {{ caddy_https_port }}
{% if caddy_default_bind != "" %}
default_bind {{ caddy_default_bind }}
{% endif %}
order geoip2_vars first
order coraza_waf first
admin off admin off
persist_config off persist_config off
{% if caddy_default_bind != None %}
default_bind {{ caddy_default_bind }}
{% endif %}
{% if caddy_crowdsec %}
order crowdsec first
{% endif %}
{% if caddy_geoip %}
order geoip2_vars first
{% endif %}
{% if caddy_coraza %}
order coraza_waf first
{% endif %}
{% if caddy_geoip_account_id != None and caddy_geoip_license_key != None %} {% if caddy_consul %}
# See: https://github.com/pteich/caddy-tlsconsul
storage consul {
address "{{ caddy_consul_server }}"
token "{{ caddy_out_consul_token.token.SecretID | mandatory }}"
timeout 10
prefix "caddy-{{ caddy_my_name }}-tls"
value_prefix "{{ caddy_my_name }}"
aes_key "{{ caddy_consul_aes_key | mandatory }}"
tls_enabled "true"
tls_insecure "false"
}
{% endif %}
{% if caddy_geoip %}
# See: https://github.com/zhangjiayin/caddy-geoip2
geoip2 { geoip2 {
accountId "{{ caddy_geoip_account_id }}" accountId "{{ caddy_geoip_account_id | mandatory }}"
licenseKey "{{ caddy_geoip_license_key }}" licenseKey "{{ caddy_geoip_license_key | mandatory }}"
databaseDirectory "{{ caddy_home_dir }}" databaseDirectory "{{ caddy_home_dir }}/geoip2"
lockFile "{{ caddy_home_dir }}/.geoip2.lock" lockFile "{{ caddy_home_dir }}/geoip2/.geoip2.lock"
editionID "{{ caddy_geoip_edition_ids }}" editionID "{{ caddy_geoip_edition_ids }}"
updateUrl "https://updates.maxmind.com" updateUrl "https://updates.maxmind.com"
updateFrequency {{ caddy_geoip_update_frequency }} updateFrequency {{ caddy_geoip_update_frequency }}
@@ -25,54 +47,78 @@
email "{{ caddy_email }}" email "{{ caddy_email }}"
{% endif %} {% endif %}
{% if caddy_ca_root | default(None) != None %} {% if caddy_crowdsec %}
tls { # See: https://github.com/hslatman/caddy-crowdsec-bouncer
ca_root "{{ caddy_ca_root }}" crowdsec {
api_key {{ caddy_crowdsec_api_key | mandatory }}
api_url {{ caddy_crowdsec_api_url }}
ticker_interval {{ caddy_crowdsec_ticker_interval }}s
{% if caddy_crowdsec_appsec_url != None %}
appsec_url {{ caddy_crowdsec_appsec_url }}
{% endif %}
{% if not caddy_crowdsec_streaming %}
disable_streaming
{% endif %}
{% if caddy_crowdsec_hard_fails %}
enable_hard_fails
{% endif %}
} }
{% endif %} {% endif %}
{% if caddy_debug %} {% if caddy_coraza %}
log waf {
include "http.handlers.waf"
format {{ caddy_log_format }}
format append {
waf_id {{ caddy_my_name }}
}
{% if caddy_log_to_file %}
output file {{ caddy_log_dir }}/waf.log
{% else %}
output stdout
{% endif %}
}
{% endif %}
{% if caddy_log_debug %}
log { log {
format json
level DEBUG level DEBUG
format {{ caddy_log_format }}
format append {
waf_id {{ caddy_my_name }}
}
output file {{ caddy_log_dir }}/debug.log output file {{ caddy_log_dir }}/debug.log
} }
{% endif %} {% endif %}
log waf {
format json
include "http.handlers.waf"
output file {{ caddy_log_dir }}/waf.log
}
} }
# 0=origin, 1=methods, 2=headers, 3=allow credentials, 4=max age, 5=vary, 6=expose headers # 0=origin, 1=methods, 2=headers, 3=allow credentials, 4=max age, 5=vary, 6=expose headers
(cors) { (cors) {
@match-cors-preflight-{args.0} { @match-cors-preflight-{args[0]} {
header Origin {args.0} header Origin {args[0]}
method OPTIONS method OPTIONS
} }
header @match-cors-preflight-{args.0} { header @match-cors-preflight-{args[0]} {
Access-Control-Allow-Origin "{args.0}" Access-Control-Allow-Origin "{args[0]}"
Access-Control-Allow-Methods "{args.1}" Access-Control-Allow-Methods "{args[1]}"
Access-Control-Allow-Headers "{args.2}" Access-Control-Allow-Headers "{args[2]}"
Access-Control-Allow-Credentials "{args.3}" Access-Control-Allow-Credentials "{args[3]}"
Access-Control-Max-Age "{args.4}" Access-Control-Max-Age "{args[4]}"
Vary "{args.5}" Vary "{args[5]}"
} }
handle @match-cors-preflight-{args.0} { handle @match-cors-preflight-{args[0]} {
respond "" 204 respond "" 204
} }
@match-cors-request-{args.0} { @match-cors-request-{args[0]} {
header Origin {args.0} header Origin {args[0]}
not method OPTIONS not method OPTIONS
} }
header @match-cors-request-{args.0} { header @match-cors-request-{args[0]} {
>Access-Control-Allow-Origin "{args.0}" >Access-Control-Allow-Origin "{args[0]}"
>Access-Control-Allow-Credentials "{args.3}" >Access-Control-Allow-Credentials "{args[3]}"
>Vary "{args.5}" >Vary "{args[5]}"
>Access-Control-Expose-Headers "{args.6}" >Access-Control-Expose-Headers "{args[6]}"
} }
} }
@@ -85,14 +131,11 @@
{% for site in caddy_sites %} {% for site in caddy_sites %}
{% if (site.site_redirect_from_aliases | default(caddy_sites_defaults.site_redirect_from_aliases)) and (site.site_aliases | default([]) | length) > 0 %} {% if (site.site_aliases_redirect | default(caddy_sites_defaults.site_aliases_redirect)) and (site.site_aliases | default([]) | length) > 0 %}
{{ site.site_aliases | join(', ') }} { {{ site.site_aliases | join(', ') }} {
redir https://{{ site.name }}{uri} redir https://{{ site.name }}{uri}
} }
{% else %} {% else %}
{% for alias in site.site_aliases | default(caddy_sites_defaults.site_aliases) %} {% for alias in site.site_aliases | default(caddy_sites_defaults.site_aliases) %}
{{ alias }}, {{ alias }},
{% endfor %} {% endfor %}
@@ -100,25 +143,37 @@
{{ site.name }} { {{ site.name }} {
encode gzip zstd encode gzip zstd
{% if caddy_crowdsec and site.crowdsec | default(caddy_sites_defaults.crowdsec) %}
crowdsec
{% endif %}
{% for path in site.paths %} {% for path in site.paths %}
handle {{ path.path | default('*') }} { handle{% if path.strip_prefix | default(false) %}_path{% endif %} {{ path.path | default('*') }} {
reverse_proxy { reverse_proxy {
to {{ path.addrs | join(' ') }} to {{ path.addrs | join(' ') }}
header_up X-Real-IP {remote} header_up X-Real-IP {remote}
lb_policy {{ site.proxy_lb_policy | default(caddy_sites_defaults.proxy_lb_policy) }} lb_policy {{ site.proxy_lb_policy | default(caddy_sites_defaults.proxy_lb_policy) }}
# Active checks
# health_uri / {% if site.proxy_active_checks | default(caddy_sites_defaults.proxy_active_checks) %}
# health_interval 10s health_uri {{ site.proxy_active_checks_uri | default(caddy_sites_defaults.proxy_active_checks_uri) }}
# health_timeout 5s health_port {{ site.proxy_active_checks_port | default(caddy_sites_defaults.proxy_active_checks_port) }}
# health_status 200 health_interval {{ site.proxy_active_checks_interval | default(caddy_sites_defaults.proxy_active_checks_interval) }}
# Passive checks health_timeout {{ site.proxy_active_checks_timeout | default(caddy_sites_defaults.proxy_active_checks_timeout) }}
fail_duration 30s health_method {{ site.proxy_active_checks_method | default(caddy_sites_defaults.proxy_active_checks_method) }}
max_fails 3 health_status {{ site.proxy_active_checks_status | default(caddy_sites_defaults.proxy_active_checks_status) }}
unhealthy_latency 2000ms {% if site.proxy_active_checks_body | default(caddy_sites_defaults.proxy_active_checks_body) != None %}
transport http { health_body {{ site.proxy_active_checks_body | default(caddy_sites_defaults.proxy_active_checks_body) }}
# tls_server_name {host} {% endif %}
# tls_insecure_skip_verify {% endif %}
} {% if site.proxy_passive_checks | default(caddy_sites_defaults.proxy_passive_checks) %}
fail_duration {{ site.proxy_passive_checks_fail_duration | default(caddy_sites_defaults.proxy_passive_checks_fail_duration) }}
max_fails {{ site.proxy_passive_checks_max_fails | default(caddy_sites_defaults.proxy_passive_checks_max_fails) }}
unhealthy_status {{ site.proxy_passive_checks_unhealthy_status | default(caddy_sites_defaults.proxy_passive_checks_unhealthy_status) }}
unhealthy_latency {{ site.proxy_passive_checks_unhealthy_latency | default(caddy_sites_defaults.proxy_passive_checks_unhealthy_latency) }}
{% if site.proxy_passive_checks_unhealthy_request_count | default(caddy_sites_defaults.proxy_passive_checks_unhealthy_request_count) != None %}
unhealthy_request_count {{ site.proxy_passive_checks_unhealthy_request_count | default(caddy_sites_defaults.proxy_passive_checks_unhealthy_request_count) }}
{% endif %}
{% endif %}
} }
} }
{% endfor %} {% endfor %}
@@ -136,7 +191,7 @@
respond @blocked "Access Denied" 403 respond @blocked "Access Denied" 403
{% endif %} {% endif %}
{% if site.geoip | default(caddy_sites_defaults.geoip) %} {% if caddy_geoip and site.geoip | default(caddy_sites_defaults.geoip) %}
geoip2_vars strict geoip2_vars strict
@geofilter expression {{ site.geoip_filter_expression | default(caddy_sites_defaults.geoip_filter_expression) }} @geofilter expression {{ site.geoip_filter_expression | default(caddy_sites_defaults.geoip_filter_expression) }}
respond @geofilter "Access Denied" 403 respond @geofilter "Access Denied" 403
@@ -178,9 +233,23 @@
{% endfor %} {% endfor %}
} }
{% if site.rate_limit | default(caddy_sites_defaults.rate_limit) %} {% if caddy_ratelimit and site.rate_limit | default(caddy_sites_defaults.rate_limit) %}
# See: https://github.com/mholt/caddy-ratelimit
rate_limit @not_whitelisted { rate_limit @not_whitelisted {
# distributed {% if caddy_consul %}
distributed
storage consul {
address "{{ caddy_consul_server }}"
token "{{ caddy_out_consul_token.token.SecretID }}"
timeout 10
prefix "caddy-{{ caddy_my_name }}-rl"
value_prefix "{{ caddy_my_name }}"
aes_key "{{ caddy_consul_aes_key }}"
tls_enabled "true"
tls_insecure "false"
}
{% endif %}
log_key
zone remote_ip { zone remote_ip {
key {remote.ip} key {remote.ip}
events {{ site.rate_limit_events | default(caddy_sites_defaults.rate_limit_events) }} events {{ site.rate_limit_events | default(caddy_sites_defaults.rate_limit_events) }}
@@ -189,21 +258,21 @@
} }
{% endif %} {% endif %}
{% if site.crs | default(caddy_sites_defaults.crs) %} {% if caddy_coraza and site.crs | default(caddy_sites_defaults.crs) %}
coraza_waf @not_whitelisted { coraza_waf @not_whitelisted {
load_owasp_crs load_owasp_crs
directives ` directives `
Include "{{ caddy_config_dir }}/sites/{{ site.id }}/coraza.conf" Include "{{ caddy_sites_dir }}/{{ site.id }}/coraza.conf"
Include "{{ caddy_config_dir }}/sites/{{ site.id }}/crs-setup.conf" Include "{{ caddy_sites_dir }}/{{ site.id }}/crs-setup.conf"
{% for plugin in site.crs_plugins | default(caddy_sites_defaults.crs_plugins) %} {% for plugin in site.crs_plugins | default(caddy_sites_defaults.crs_plugins) %}
Include "{{ caddy_config_dir }}/crs-plugins/{{ plugin }}-config.conf" Include "{{ caddy_crs_plugins_dir }}/{{ plugin }}-config.conf"
Include "{{ caddy_config_dir }}/crs-plugins/{{ plugin }}-before.conf" Include "{{ caddy_crs_plugins_dir }}/{{ plugin }}-before.conf"
{% endfor %} {% endfor %}
Include "{{ caddy_config_dir }}/sites/{{ site.id }}/exclusions-request-before.conf" Include "{{ caddy_sites_dir }}/{{ site.id }}/exclusions-request-before.conf"
Include "{{ caddy_config_dir }}/coreruleset-{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}/rules/*.conf" Include "{{ caddy_config_dir }}/coreruleset-{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}/rules/*.conf"
Include "{{ caddy_config_dir }}/sites/{{ site.id }}/exclusions-response-after.conf" Include "{{ caddy_sites_dir }}/{{ site.id }}/exclusions-response-after.conf"
{% for plugin in site.crs_plugins | default(caddy_sites_defaults.crs_plugins) %} {% for plugin in site.crs_plugins | default(caddy_sites_defaults.crs_plugins) %}
Include "{{ caddy_config_dir }}/crs-plugins/{{ plugin }}-after.conf" Include "{{ caddy_crs_plugins_dir }}/{{ plugin }}-after.conf"
{% endfor %} {% endfor %}
SecRuleEngine On SecRuleEngine On
` `
@@ -211,13 +280,20 @@
{% endif %} {% endif %}
{% if site.cors | default(caddy_sites_defaults.cors) %} {% if site.cors | default(caddy_sites_defaults.cors) %}
{% for origin in site.cors_allow_origins | mandatory %} {% for origin in site.cors_allowed_origins | mandatory %}
import cors {{ origin }} "{{ site.cors_allow_methods | default(caddy_sites_defaults.cors_allow_methods) | join(', ') }}" "{{ site.cors_allow_headers | default(caddy_sites_defaults.cors_allow_headers) | join(', ') }}" "{{ site.cors_allow_credentials | default(caddy_sites_defaults.cors_allow_credentials) | ternary('true', 'false') }}" "{{ site.cors_max_age | default(caddy_sites_defaults.cors_max_age) }}" "{{ site.cors_vary | default(caddy_sites_defaults.cors_vary) | join(', ') }}" "{{ site.cors_expose_headers | default(caddy_sites_defaults.cors_expose_headers) | join(', ') }}" import cors https://{{ origin }} "{{
site.cors_allowed_methods | default(caddy_sites_defaults.cors_allowed_methods) | listadddel(site.cors_allowed_methods_add | default([]), site.cors_allowed_methods_del | default([])) | join(', ') }}" "{{
site.cors_allowed_headers | default(caddy_sites_defaults.cors_allowed_headers) | listadddel(site.cors_allowed_headers_add | default([]), site.cors_allowed_headers_del | default([])) | join(', ') }}" "{{
site.cors_allowed_credentials | default(caddy_sites_defaults.cors_allowed_credentials) | ternary('true', 'false') }}" "{{
site.cors_max_age | default(caddy_sites_defaults.cors_max_age) }}" "{{
site.cors_vary | default(caddy_sites_defaults.cors_vary) | listadddel(site.cors_vary_add | default([]), site.cors_vary_del | default([])) | join(', ') }}" "{{
site.cors_exposed_headers | default(caddy_sites_defaults.cors_exposed_headers) | listadddel(site.cors_exposed_headers_add | default([]), site.cors_exposed_headers_del | default([])) | join(', ') }}"
{% endfor %} {% endfor %}
import cors-deny import cors-deny
{% endif %} {% endif %}
{% if site.bot_barrier | default(caddy_sites_defaults.bot_barrier) %} {% if caddy_bot_barrier and site.bot_barrier | default(caddy_sites_defaults.bot_barrier) %}
# See: https://github.com/steffenbusch/caddy-bot-barrier
bot_barrier @not_whitelisted { bot_barrier @not_whitelisted {
secret {{ site.bot_barrier_secret | default(caddy_sites_defaults.bot_barrier_secret) | mandatory }} secret {{ site.bot_barrier_secret | default(caddy_sites_defaults.bot_barrier_secret) | mandatory }}
complexity {{ site.bot_barrier_complexity | default(caddy_sites_defaults.bot_barrier_complexity) }} complexity {{ site.bot_barrier_complexity | default(caddy_sites_defaults.bot_barrier_complexity) }}
@@ -229,12 +305,12 @@
} }
{% endif %} {% endif %}
{% if site.header | default(caddy_sites_defaults.header) %}
header { header {
Content-Security-Policy "{{ caddy_sites_defaults.header_content_security_policy | combine(site.header_content_security_policy | default({})) | dict2str(sep1=' ', sep2='; ', sep3=';') }}" Content-Security-Policy "{{ caddy_sites_defaults.header_content_security_policy | combine(site.header_content_security_policy | default({})) | dict2str(sep1=' ', sep2='; ', sep3=';') }}"
# FIXME Cross-Origin-Embedder-Policy "{{ site.header_cross_origin_embedder_policy | default(caddy_sites_defaults.header_cross_origin_embedder_policy) }}"
# Cross-Origin-Embedder-Policy "{{ site.header_cross_origin_embedder_policy | default(caddy_sites_defaults.header_cross_origin_embedder_policy) }}" Cross-Origin-Opener-Policy "{{ site.header_cross_origin_opener_policy | default(caddy_sites_defaults.header_cross_origin_opener_policy) }}"
# Cross-Origin-Opener-Policy "{{ site.header_cross_origin_opener_policy | default(caddy_sites_defaults.header_cross_origin_opener_policy) }}" Cross-Origin-Resource-Policy "{{ site.header_cross_origin_resource_policy | default(caddy_sites_defaults.header_cross_origin_resource_policy) }}"
# Cross-Origin-Resource-Policy "{{ site.header_cross_origin_resource_policy | default(caddy_sites_defaults.header_cross_origin_resource_policy) }}"
Feature-Policy "{{ caddy_sites_defaults.header_feature_policy | combine(site.header_feature_policy | default({})) | dict2str(sep1=' ', sep2='; ', sep3=';') }}" Feature-Policy "{{ caddy_sites_defaults.header_feature_policy | combine(site.header_feature_policy | default({})) | dict2str(sep1=' ', sep2='; ', sep3=';') }}"
Permissions-Policy "{{ caddy_sites_defaults.header_permissions_policy | combine(site.header_permissions_policy | default({})) | dict2str(sep2=', ') }}" Permissions-Policy "{{ caddy_sites_defaults.header_permissions_policy | combine(site.header_permissions_policy | default({})) | dict2str(sep2=', ') }}"
Referrer-Policy "{{ site.header_referrer_policy | default(caddy_sites_defaults.header_referrer_policy) }}" Referrer-Policy "{{ site.header_referrer_policy | default(caddy_sites_defaults.header_referrer_policy) }}"
@@ -251,6 +327,7 @@
{{ header.name }} "{{ header.value }}" {{ header.name }} "{{ header.value }}"
{% endfor %} {% endfor %}
} }
{% endif %}
handle_errors 403 { handle_errors 403 {
header X-Blocked "true" header X-Blocked "true"
@@ -258,8 +335,16 @@
} }
log { log {
format json level INFO
format {{ caddy_log_format }}
format append {
waf_id {{ caddy_my_name }}
}
{% if caddy_log_to_file %}
output file {{ caddy_log_dir }}/site_{{ site.id }}.log output file {{ caddy_log_dir }}/site_{{ site.id }}.log
{% else %}
output stdout
{% endif %}
} }
} }

View File

@@ -1,3 +0,0 @@
AccountID {{ caddy_geoip_account_id }}
LicenseKey {{ caddy_geoip_license_key }}
EditionIDs {{ caddy_geoip_edition_ids }}

View File

@@ -1,19 +1,25 @@
[Unit] [Unit]
Description=WAF {{ caddy_my_name }} (built with BYOW: Build Your Own WAF) Description=WAF ({{ caddy_my_name }}) (built with BYOW: Build Your Own WAF)
After=network.target network-online.target After=network.target network-online.target
Requires=network-online.target Requires=network-online.target{% if caddy_consul %} consul.service{% endif %}
[Service] [Service]
Type=notify Type=notify
User={{ caddy_system_user }} User={{ caddy_system_user }}
Group={{ caddy_system_group }} Group={{ caddy_system_group }}
ExecStart=/usr/local/bin/{{ caddy_my_name }} run --environ --config {{ caddy_config_dir }}/Caddyfile ExecStart={{ caddy_binary }} run --environ --config {{ caddy_config_dir }}/Caddyfile
ExecReload=/usr/local/bin/{{ caddy_my_name }} reload --config {{ caddy_config_dir }}/Caddyfile --force ExecReload={{ caddy_binary }} reload --config {{ caddy_config_dir }}/Caddyfile --force
TimeoutStopSec=15s TimeoutStopSec=15s
LimitNOFILE=1048576 LimitNOFILE=1048576
PrivateTmp=true PrivateTmp=true
ProtectSystem=full ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
StandardOutput=journal
StandardError=journal
SyslogIdentifier={{ caddy_my_name }}
{% if caddy_consul %}
Environment="CONSUL_CLIENT_CERT={{ caddy_consul_client_cert }}" "CONSUL_CLIENT_KEY={{ caddy_consul_client_key }}"
{% endif %}
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -1,4 +1,5 @@
# -- Rule engine initialization ---------------------------------------------- # -- Rule engine initialization ----------------------------------------------
# See: https://coraza.io/docs/seclang/directives/
# Enable Coraza, attaching it to every transaction. Use detection # Enable Coraza, attaching it to every transaction. Use detection
# only to start with, because that minimises the chances of post-installation # only to start with, because that minimises the chances of post-installation
@@ -78,7 +79,7 @@ SecResponseBodyAccess On
# Which response MIME types do you want to inspect? You should adjust the # Which response MIME types do you want to inspect? You should adjust the
# configuration below to catch documents but avoid static files # configuration below to catch documents but avoid static files
# (e.g., images and archives). # (e.g., images and archives).
SecResponseBodyMimeType text/plain text/html text/xml SecResponseBodyMimeType text/plain text/html text/xml application/json
# Buffer response bodies of up to 512 KB in length. # Buffer response bodies of up to 512 KB in length.
SecResponseBodyLimit 524288 SecResponseBodyLimit 524288

View File

@@ -11,7 +11,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.blocking_crs_paranoia_level={{ site.crs_paranoia_level | default(caddy_sites_defaults.crs_paranoia_level) }}" setvar:tx.blocking_paranoia_level={{ site.crs_paranoia_level | default(caddy_sites_defaults.crs_paranoia_level) }}"
{% if site.crs_detection_paranoia_level | default(caddy_sites_defaults.crs_detection_paranoia_level) != None %} {% if site.crs_detection_paranoia_level | default(caddy_sites_defaults.crs_detection_paranoia_level) != None %}
## Detection paranoia level ## Detection paranoia level
@@ -23,7 +23,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_detection_paranoia_level={{ site.crs_detection_paranoia_level | default(caddy_sites_defaults.crs_detection_paranoia_level) }}" setvar:tx.detection_paranoia_level={{ site.crs_detection_paranoia_level | default(caddy_sites_defaults.crs_detection_paranoia_level) }}"
{% endif %} {% endif %}
## Enforce Body Processor URLENCODED ## Enforce Body Processor URLENCODED
@@ -35,7 +35,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_enforce_bodyproc_urlencoded={{ site.crs_enforce_bodyproc_urlencoded | default(caddy_sites_defaults.crs_enforce_bodyproc_urlencoded) }}" setvar:tx.enforce_bodyproc_urlencoded={{ site.crs_enforce_bodyproc_urlencoded | default(caddy_sites_defaults.crs_enforce_bodyproc_urlencoded) }}"
## Anomaly Scoring Mode Severity Levels ## Anomaly Scoring Mode Severity Levels
SecAction \ SecAction \
@@ -46,10 +46,10 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_critical_anomaly_score={{ site.crs_critical_anomaly_score | default(caddy_sites_defaults.crs_critical_anomaly_score) }},\ setvar:tx.critical_anomaly_score={{ site.crs_critical_anomaly_score | default(caddy_sites_defaults.crs_critical_anomaly_score) }},\
setvar:tx.crs_error_anomaly_score={{ site.crs_error_anomaly_score | default(caddy_sites_defaults.crs_error_anomaly_score) }},\ setvar:tx.error_anomaly_score={{ site.crs_error_anomaly_score | default(caddy_sites_defaults.crs_error_anomaly_score) }},\
setvar:tx.crs_warning_anomaly_score={{ site.crs_warning_anomaly_score | default(caddy_sites_defaults.crs_warning_anomaly_score) }},\ setvar:tx.warning_anomaly_score={{ site.crs_warning_anomaly_score | default(caddy_sites_defaults.crs_warning_anomaly_score) }},\
setvar:tx.crs_notice_anomaly_score={{ site.crs_notice_anomaly_score | default(caddy_sites_defaults.crs_notice_anomaly_score) }}" setvar:tx.notice_anomaly_score={{ site.crs_notice_anomaly_score | default(caddy_sites_defaults.crs_notice_anomaly_score) }}"
## Anomaly Scoring Mode Blocking Threshold Levels ## Anomaly Scoring Mode Blocking Threshold Levels
SecAction \ SecAction \
@@ -60,8 +60,8 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_inbound_anomaly_score_threshold={% if site.crs_log_only | default(caddy_sites_defaults.crs_log_only) %}1000{% else %}{{ site.crs_inbound_anomaly_score_threshold | default(caddy_sites_defaults.crs_inbound_anomaly_score_threshold) }}{% endif %},\ setvar:tx.inbound_anomaly_score_threshold={% if site.crs_log_only | default(caddy_sites_defaults.crs_log_only) %}1000{% else %}{{ site.crs_inbound_anomaly_score_threshold | default(caddy_sites_defaults.crs_inbound_anomaly_score_threshold) }}{% endif %},\
setvar:tx.crs_outbound_anomaly_score_threshold={% if site.crs_log_only | default(caddy_sites_defaults.crs_log_only) %}1000{% else %}{{ site.crs_outbound_anomaly_score_threshold | default(caddy_sites_defaults.crs_outbound_anomaly_score_threshold) }}{% endif %}" setvar:tx.outbound_anomaly_score_threshold={% if site.crs_log_only | default(caddy_sites_defaults.crs_log_only) %}1000{% else %}{{ site.crs_outbound_anomaly_score_threshold | default(caddy_sites_defaults.crs_outbound_anomaly_score_threshold) }}{% endif %}"
## Application Specific Rule Exclusions ## Application Specific Rule Exclusions
# FIXME # FIXME
@@ -88,7 +88,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_reporting_level={{ site.crs_reporting_level | default(caddy_sites_defaults.crs_reporting_level) }}" setvar:tx.reporting_level={{ site.crs_reporting_level | default(caddy_sites_defaults.crs_reporting_level) }}"
## Early Anomaly Scoring Mode Blocking ## Early Anomaly Scoring Mode Blocking
SecAction \ SecAction \
@@ -112,7 +112,7 @@ SecAction \
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.enable_default_collections=1" setvar:tx.enable_default_collections=1"
## HTTP Policy Settings ## Allowed HTTP methods
SecAction \ SecAction \
"id:900200,\ "id:900200,\
phase:1,\ phase:1,\
@@ -121,7 +121,17 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:'tx.crs_allowed_methods={{ site.crs_allowed_methods | default(caddy_sites_defaults.crs_allowed_methods) | join(" ") }}'" setvar:'tx.allowed_methods={{ site.crs_allowed_methods | default(caddy_sites_defaults.crs_allowed_methods) | listadddel(site.crs_allowed_methods_add | default([]), site.crs_allowed_methods_del | default([])) | join(" ") }}'"
## HTTP Method Override Parameter
SecAction \
"id:900210,\
phase:1,\
pass,\
nolog,\
tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:'tx.allow_method_override_parameter={{ site.crs_allow_method_override_parameter | default(caddy_sites_defaults.crs_allow_method_override_parameter) }}'"
## Content-Types that a client is allowed to send in a request ## Content-Types that a client is allowed to send in a request
SecAction \ SecAction \
@@ -132,7 +142,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:'tx.crs_allowed_request_content_type={{ site.crs_allowed_request_content_type | default(caddy_sites_defaults.crs_allowed_request_content_type) | join(" ") }}'" setvar:'tx.allowed_request_content_type={{ site.crs_allowed_request_content_type | default(caddy_sites_defaults.crs_allowed_request_content_type) | listadddel(site.crs_allowed_request_content_type_add | default([]), site.crs_allowed_request_content_type_del | default([])) | join(" ") }}'"
## Allowed HTTP versions ## Allowed HTTP versions
SecAction \ SecAction \
@@ -143,7 +153,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:'tx.crs_allowed_http_versions={{ site.crs_allowed_http_versions | default(caddy_sites_defaults.crs_allowed_http_versions) | join(" ") }}'" setvar:'tx.allowed_http_versions={{ site.crs_allowed_http_versions | default(caddy_sites_defaults.crs_allowed_http_versions) | listadddel(site.crs_allowed_http_versions_add | default([]), site.crs_allowed_http_versions_del | default([])) | join(" ") }}'"
## Forbidden file extensions ## Forbidden file extensions
SecAction \ SecAction \
@@ -154,7 +164,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:'tx.crs_restricted_extensions={{ site.crs_restricted_extensions | default(caddy_sites_defaults.crs_restricted_extensions) | join(" ") }}'" setvar:'tx.restricted_extensions={{ site.crs_restricted_extensions | default(caddy_sites_defaults.crs_restricted_extensions) | listadddel(site.crs_restricted_extensions_add | default([]), site.crs_restricted_extensions_del | default([])) | join(" ") }}'"
## Restricted request headers ## Restricted request headers
SecAction \ SecAction \
@@ -165,7 +175,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:'tx.crs_restricted_headers_basic={{ site.crs_restricted_headers_basic | default(caddy_sites_defaults.crs_restricted_headers_basic) | join(" ") }}'" setvar:'tx.restricted_headers_basic={{ site.crs_restricted_headers_basic | default(caddy_sites_defaults.crs_restricted_headers_basic) | listadddel(site.restricted_headers_basic_add | default([]), site.restricted_headers_basic_del | default([])) | join(" ") }}'"
## Extended restricted request headers (forbidden at a higher paranoia level) ## Extended restricted request headers (forbidden at a higher paranoia level)
SecAction \ SecAction \
@@ -176,7 +186,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:'tx.crs_restricted_headers_extended={{ site.crs_restricted_headers_extended | default(caddy_sites_defaults.crs_restricted_headers_extended) | join(" ") }}'" setvar:'tx.restricted_headers_extended={{ site.crs_restricted_headers_extended | default(caddy_sites_defaults.crs_restricted_headers_extended) | listadddel(site.restricted_headers_extended_add | default([]), site.restricted_headers_extended_del | default([])) | join(" ") }}'"
## Content-Types charsets that a client is allowed to send in a request ## Content-Types charsets that a client is allowed to send in a request
SecAction \ SecAction \
@@ -187,7 +197,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:'tx.crs_allowed_request_content_type_charset={{ caddy_sites_defaults.crs_allowed_request_content_type_charset | default(caddy_sites_defaults.crs_allowed_request_content_type_charset) | join(" ") }}'" setvar:'tx.allowed_request_content_type_charset={{ caddy_sites_defaults.crs_allowed_request_content_type_charset | default(caddy_sites_defaults.crs_allowed_request_content_type_charset) | listadddel(site.allowed_request_content_type_charset_add | default([]), site.allowed_request_content_type_charset_del | default([])) | join(" ") }}'"
## Block request if number of arguments is too high ## Block request if number of arguments is too high
SecAction \ SecAction \
@@ -198,7 +208,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_max_num_args={{ site.crs_max_num_args | default(caddy_sites_defaults.crs_max_num_args) }}" setvar:tx.max_num_args={{ site.crs_max_num_args | default(caddy_sites_defaults.crs_max_num_args) }}"
## Block request if the length of any argument name is too high ## Block request if the length of any argument name is too high
SecAction \ SecAction \
@@ -209,7 +219,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_arg_name_length={{ site.crs_arg_name_length | default(caddy_sites_defaults.crs_arg_name_length) }}" setvar:tx.name_length={{ site.crs_arg_name_length | default(caddy_sites_defaults.crs_arg_name_length) }}"
## Block request if the length of any argument value is too high ## Block request if the length of any argument value is too high
SecAction \ SecAction \
@@ -220,7 +230,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_arg_length={{ site.crs_arg_length | default(caddy_sites_defaults.crs_arg_length) }}" setvar:tx.arg_length={{ site.crs_arg_length | default(caddy_sites_defaults.crs_arg_length) }}"
## Block request if the total length of all combined arguments is too high ## Block request if the total length of all combined arguments is too high
SecAction \ SecAction \
@@ -231,7 +241,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_total_arg_length={{ site.crs_total_arg_length | default(caddy_sites_defaults.crs_total_arg_length) }}" setvar:tx.total_arg_length={{ site.crs_total_arg_length | default(caddy_sites_defaults.crs_total_arg_length) }}"
## Block request if the file size of any individual uploaded file is too high ## Block request if the file size of any individual uploaded file is too high
SecAction \ SecAction \
@@ -242,7 +252,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_max_file_size={{ site.crs_max_file_size | default(caddy_sites_defaults.crs_max_file_size) }}" setvar:tx.max_file_size={{ site.crs_max_file_size | default(caddy_sites_defaults.crs_max_file_size) }}"
## Block request if the total size of all combined uploaded files is too high ## Block request if the total size of all combined uploaded files is too high
SecAction \ SecAction \
@@ -253,7 +263,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_combined_file_sizes={{ site.crs_combined_file_sizes | default(caddy_sites_defaults.crs_combined_file_sizes) }}" setvar:tx.combined_file_sizes={{ site.crs_combined_file_sizes | default(caddy_sites_defaults.crs_combined_file_sizes) }}"
## Easing In / Sampling Percentage ## Easing In / Sampling Percentage
SecAction \ SecAction \
@@ -263,7 +273,7 @@ SecAction \
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\ ver:'OWASP_CRS/{{ site.crs_version | default(caddy_sites_defaults.crs_version) }}',\
setvar:tx.crs_sampling_percentage={{ site.crs_sampling_percentage | default(caddy_sites_defaults.crs_sampling_percentage) }}" setvar:tx.sampling_percentage={{ site.crs_sampling_percentage | default(caddy_sites_defaults.crs_sampling_percentage) }}"
## Check UTF-8 encoding ## Check UTF-8 encoding
SecAction \ SecAction \

View File

@@ -1,12 +1,12 @@
# Sites default parameter values # Sites default parameter values
caddy_global_sites_defaults: caddy_global_sites_defaults:
# Site settings # Site aliases
site_aliases_redirect: true
site_aliases: [] site_aliases: []
site_redirect_from_aliases: true
# Proxy settings # Proxy settings
# See: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#lb_policy
proxy_lb_policy: "ip_hash" # random proxy_lb_policy: "ip_hash" # random
# random_choose <n> # random_choose <n>
# first # first
@@ -19,41 +19,73 @@ caddy_global_sites_defaults:
# query [key] # query [key]
# header [field] # header [field]
# cookie [<name> [<secret>]] # cookie [<name> [<secret>]]
# Upstreams active checks
# See: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#active-health-checks
proxy_active_checks: false
proxy_active_checks_uri: "/"
proxy_active_checks_port: 80
proxy_active_checks_interval: "30s"
proxy_active_checks_timeout: "5s"
proxy_active_checks_method: "GET"
proxy_active_checks_status: "2xx"
proxy_active_checks_body: null
# Upstreams passive checks
# See: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#passive-health-checks
proxy_passive_checks: false
proxy_passive_checks_fail_duration: "30s"
proxy_passive_checks_max_fails: 1
proxy_passive_checks_unhealthy_status: "5xx"
proxy_passive_checks_unhealthy_latency: "2000ms"
proxy_passive_checks_unhealthy_request_count: null
# Filters # Filters
filter_blacklist: [] # Superseeds filter_whitelist filter_blacklist: [] # Superseeds filter_whitelist
filter_whitelist: ["127.0.0.1"] filter_whitelist: ["127.0.0.1"]
# Custom TLS certificate # Custom TLS certificate
custom_cert: false custom_cert: false
custom_cert_file: "" custom_cert_file: null # Mandatory if custum_cert is true
custom_cert_key_file: "" custom_cert_key_file: null # Mandatory if custum_cert is true
# CORS - Cross Origin Resource Sharing # CORS - Cross Origin Resource Sharing
cors: false cors: false
cors_allow_origins: null # Mandatory cors_allowed_origins: [] # Minimum 1 required
cors_allow_methods: cors_allowed_methods:
# These variables can be used for adding and removing methods from the list:
# cors_allowed_methods_add: ["XXX"]
# cors_allowed_methods_del: ["YYY"]
- "GET" - "GET"
- "HEAD" - "HEAD"
- "POST" - "POST"
- "OPTIONS" - "OPTIONS"
cors_allow_headers: cors_allowed_headers:
# These variables can be used for adding and removing headers from the list:
# cors_allowed_headers_add: ["Xxx"]
# cors_allowed_headers_del: ["Yyy"]
- "Cache-Control" - "Cache-Control"
- "Content-Type" - "Content-Type"
- "If-Modified-Since" - "If-Modified-Since"
- "Range" - "Range"
- "User-Agent" - "User-Agent"
cors_max_age: 3600 cors_max_age: 3600
cors_allow_credentials: false cors_allowed_credentials: false
cors_vary: cors_vary:
# These variables can be used for adding and removing headers from the list:
# cors_vary_add: ["Xxx"]
# cors_vary_del: ["Yyy"]
- "Origin" - "Origin"
cors_expose_headers: cors_exposed_headers:
# These variables can be used for adding and removing headers from the list:
# cors_exposed_headers_add: ["Xxx"]
# cors_exposed_headers_del: ["Yyy"]
- "Content-Length" - "Content-Length"
- "Content-Range" - "Content-Range"
# Security headers
header: true
# Security headers # Security headers
header_content_security_policy: header_content_security_policy:
@@ -235,29 +267,26 @@ caddy_global_sites_defaults:
- name: "X-BYOW" - name: "X-BYOW"
value: "🍷" value: "🍷"
# caddy-ratelimit # caddy-ratelimit
rate_limit: true rate_limit: "{{ caddy_ratelimit }}"
rate_limit_events: 1000 rate_limit_events: 1000
rate_limit_window: "1m" rate_limit_window: "1m"
# steffenbusch/caddy-bot-barrier # steffenbusch/caddy-bot-barrier
bot_barrier: false bot_barrier: "{{ caddy_bot_barrier }}"
bot_barrier_secret: null # Required bot_barrier_secret: null # Required
bot_barrier_complexity: 18 bot_barrier_complexity: 18
bot_barrier_valid_for: "60m" bot_barrier_valid_for: "60m"
# corazawaf/coraza-caddy # corazawaf/coraza-caddy
crs: true crs: true
crs_version: "4.22.0" crs_version: "4.24.1"
crs_log_only: false crs_log_only: false
crs_plugins: [] crs_plugins: []
crs_exceptions: crs_exceptions:
before_request: [] before_request: []
after_response: [] after_response: []
crs_paranoia_level: 1 crs_paranoia_level: 4
crs_detection_paranoia_level: null crs_detection_paranoia_level: null
crs_enforce_bodyproc_urlencoded: 1 crs_enforce_bodyproc_urlencoded: 1
crs_critical_anomaly_score: 5 crs_critical_anomaly_score: 5
@@ -274,21 +303,29 @@ caddy_global_sites_defaults:
crs_max_file_size: 10485760 # 10 Mo crs_max_file_size: 10485760 # 10 Mo
crs_combined_file_sizes: 20970520 # 20 Mo crs_combined_file_sizes: 20970520 # 20 Mo
crs_sampling_percentage: 100 crs_sampling_percentage: 100
crs_allow_method_override_parameter: 0
crs_allowed_methods: crs_allowed_methods:
# These variables can be used for adding and removing methods from the list:
# crs_allowed_methods_add: ["XXX"]
# crs_allowed_methods_del: ["YYY"]
- "GET" - "GET"
- "HEAD" - "HEAD"
- "POST" - "POST"
- "OPTIONS" - "OPTIONS"
crs_allowed_request_content_type: crs_allowed_request_content_type:
- "|application/csp-report|" # These variables can be used for adding and removing content types from the list:
# crs_allowed_request_content_type_add: ["|xxx/yyy|"]
# crs_allowed_request_content_type_del: ["|zzz/aaa|"]
- "|application/json|" - "|application/json|"
- "|application/reports+json|"
- "|application/soap+xml|" - "|application/soap+xml|"
- "|application/x-www-form-urlencoded|" - "|application/x-www-form-urlencoded|"
- "|application/xml|" - "|application/xml|"
- "|multipart/form-data|" - "|multipart/form-data|"
- "|text/xml|" - "|text/xml|"
crs_restricted_extensions: crs_restricted_extensions:
# These variables can be used for adding and removing extensions from the list:
# crs_restricted_extensions_add: [".xxx/"]
# crs_restricted_extensions_del: [".yyy/"]
- ".ani/" - ".ani/"
- ".asa/" - ".asa/"
- ".asax/" - ".asax/"
@@ -332,6 +369,7 @@ caddy_global_sites_defaults:
- ".inc/" - ".inc/"
- ".inf/" - ".inf/"
- ".ini/" - ".ini/"
- ".jks/"
- ".jse/" - ".jse/"
- ".key/" - ".key/"
- ".licx/" - ".licx/"
@@ -364,9 +402,12 @@ caddy_global_sites_defaults:
- ".sql/" - ".sql/"
- ".sqlite/" - ".sqlite/"
- ".sqlite3/" - ".sqlite3/"
- ".swap/"
- ".swo/"
- ".swp/" - ".swp/"
- ".sys/" - ".sys/"
- ".temp/" - ".temp/"
- ".tfstate/"
- ".tlb/" - ".tlb/"
- ".tmp/" - ".tmp/"
- ".vb/" - ".vb/"
@@ -383,6 +424,9 @@ caddy_global_sites_defaults:
- ".xsd/" - ".xsd/"
- ".xsx/" - ".xsx/"
crs_restricted_headers_basic: crs_restricted_headers_basic:
# These variables can be used for adding and removing headers from the list:
# crs_restricted_headers_basic_add: ["/xxx/"]
# crs_restricted_headers_basic_del: ["/yyy/"]
- "/content-encoding/" - "/content-encoding/"
- "/content-range/" - "/content-range/"
- "/expect/" - "/expect/"
@@ -394,8 +438,14 @@ caddy_global_sites_defaults:
- "/x-method-override/" - "/x-method-override/"
- "/x-middleware-subrequest/" - "/x-middleware-subrequest/"
crs_restricted_headers_extended: crs_restricted_headers_extended:
# These variables can be used for adding and removing headers from the list:
# crs_restricted_headers_extended_add: ["/xxx/"]
# crs_restricted_headers_extended_del: ["/yyy/"]
- "/accept-charset/" - "/accept-charset/"
crs_allowed_http_versions: crs_allowed_http_versions:
# These variables can be used for adding and removing protocols from the list:
# crs_allowed_http_versions_add: ["HTTP/x.x"]
# crs_allowed_http_versions_del: ["HTTP/y.y"]
- "HTTP/1.0" - "HTTP/1.0"
- "HTTP/1.1" - "HTTP/1.1"
- "HTTP/2" - "HTTP/2"
@@ -403,13 +453,17 @@ caddy_global_sites_defaults:
- "HTTP/3" - "HTTP/3"
- "HTTP/3.0" - "HTTP/3.0"
crs_allowed_request_content_type_charset: crs_allowed_request_content_type_charset:
# These variables can be used for adding and removing charsets from the list:
# crs_allowed_request_content_type_charset_add: ["|xxx|"]
# crs_allowed_request_content_type_charset_del: ["|yyy|"]
- "|utf-8|" - "|utf-8|"
- "|iso-8859-1|" - "|iso-8859-1|"
- "|iso-8859-15|" - "|iso-8859-15|"
- "|windows-1252|" - "|windows-1252|"
# github.com/zhangjiayin/caddy-geoip2 # github.com/zhangjiayin/caddy-geoip2
geoip: false geoip: false
geoip_debug: false geoip_debug: false
geoip_filter_expression: '{geoip2.country_code} == "UNK"' geoip_filter_expression: '{geoip2.country_code} == "UNK"'
crowdsec: "{{ caddy_crowdsec }}"

View File

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

View File

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

View File

@@ -1,7 +1,11 @@
- name: Setup hosts file
ansible.builtin.include_tasks: "setup_hosts.yml"
- name: Setup resolved
ansible.builtin.include_tasks: "setup_resolved.yml"
- 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 - name: Setup packages
when: "common_default_packages | length > 0"
ansible.builtin.include_tasks: "setup_packages.yml" ansible.builtin.include_tasks: "setup_packages.yml"

View File

@@ -0,0 +1,25 @@
- name: Check if /etc/cloud/cloud.cfg exists
ansible.builtin.stat:
path: "/etc/cloud/cloud.cfg"
register: common_out_cloud_cfg
- name: Disable /etc/hosts management by cloud-init
when: "common_out_cloud_cfg.stat.exists"
block:
- name: Disable /etc/hosts management by cloud-init (manage_etc_hosts)
ansible.builtin.lineinfile:
path: "/etc/cloud/cloud.cfg"
search_string: 'manage_etc_hosts:'
line: "manage_etc_hosts: false"
insertafter: "preserve_hostname:"
- name: Disable /etc/hosts management by cloud-init (update_etc_hosts)
ansible.builtin.replace:
path: "/etc/cloud/cloud.cfg"
regexp: '^([^#]*update_etc_hosts.*)$'
replace: '# \\1'
- name: Configure /etc/hosts file
ansible.builtin.template:
src: "templates/hosts"
dest: "/etc/hosts"
mode: "0644"

View File

@@ -4,6 +4,7 @@
upgrade: "safe" upgrade: "safe"
- name: Install default packages - name: Install default packages
when: "common_default_packages | length > 0"
ansible.builtin.apt: ansible.builtin.apt:
name: "{{ common_default_packages }}" name: "{{ common_default_packages }}"
autoclean: true autoclean: true

View File

@@ -1,4 +1,5 @@
- name: Create 00proxy.conf file - name: Create 00proxy.conf file
when: "common_proxy_server != None"
ansible.builtin.template: ansible.builtin.template:
src: "templates/apt_proxy.conf" src: "templates/apt_proxy.conf"
dest: "/etc/apt/apt.conf.d/00proxy.conf" dest: "/etc/apt/apt.conf.d/00proxy.conf"
@@ -7,6 +8,7 @@
group: "root" group: "root"
- name: Add http_proxy and https_proxy variable to file /etc/environment - name: Add http_proxy and https_proxy variable to file /etc/environment
when: "common_proxy_server != None"
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
path: "/etc/environment" path: "/etc/environment"
regexp: '^{{ item.key }}=' regexp: '^{{ item.key }}='

View File

@@ -0,0 +1,8 @@
- name: Add multipass in search domains
when: "common_search_domains | length > 0"
ansible.builtin.lineinfile:
path: "/etc/systemd/resolved.conf"
search_string: 'Domains='
line: "Domains={{ common_search_domains | join(' ') }}"
notify:
- "Restart systemd-resolved"

View File

@@ -0,0 +1,9 @@
127.0.0.1 localhost
{{ ansible_default_ipv4.address }} {{ ansible_fqdn }} {{ ansible_hostname }}
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

View File

@@ -1,22 +1,34 @@
# Required # Required.
consul_datacenter: null
consul_domain: null
consul_secret_key: null
consul_server: false consul_server: false
consul_datacenter: null
consul_secret_key: null # Generate with "consul keygen"
consul_servers: [] consul_servers: []
# Required for registering services # Required if consul_server.
consul_services: [] consul_bootstrap_secret: null # Generate with "uuidgen"
# Defaults # Required for activating UI.
consul_version: "1.22.2" consul_ui_secret_id: null # Generate with "uuidgen"
consul_key_file: "/etc/ssl/private/server.{{ consul_datacenter }}.{{ consul_domain }}.key"
consul_cert_file: "/etc/ssl/certs/server.{{ consul_datacenter }}.{{ consul_domain }}.crt" # Defaults.
consul_ca_file: "/etc/ssl/certs/local-ca.crt" consul_domain: "consul"
consul_client_addr: "0.0.0.0" consul_version: "1.22.3"
consul_client_addr: "127.0.0.1"
consul_bind_addr: "{{ ansible_default_ipv4.address }}" consul_bind_addr: "{{ ansible_default_ipv4.address }}"
consul_advertise_addr: "{{ ansible_default_ipv4.address }}"
consul_default_check_interval: "15s" consul_default_check_interval: "15s"
consul_default_check_timeout: "2s" consul_default_check_timeout: "2s"
consul_acl_bootstraped: false # Attributes for certificate must be:
# cn: server.<consul_datacenter>.<consul_domain>
# SANs: DNS:server.<consul_datacenter>.<consul_domain>
consul_key_file: "/etc/ssl/private/server.{{ consul_datacenter }}.{{ consul_domain }}.key"
consul_cert_file: "/etc/ssl/certs/server.{{ consul_datacenter }}.{{ consul_domain }}.crt"
consul_ca_file: "/etc/ssl/certs/local-ca.crt"
# Required for registering services.
consul_services: []
# - name: "www"
# port: 443
# check_tcp: ""
# check_http: "http://{{ ansible_default_ipv4.address }}:80/"
# scheme: "" (default "http")

View File

@@ -1,5 +1,11 @@
- name: Restart Consul - name: Restart Consul - Restart service
listen: "Restart Consul"
ansible.builtin.service: ansible.builtin.service:
name: "consul" name: "consul"
state: "restarted" state: "restarted"
daemon_reload: true daemon_reload: true
- name: Restart Consul - Wait a little
listen: "Restart Consul"
ansible.builtin.wait_for:
timeout: 5

View File

@@ -3,13 +3,6 @@
name: name:
- "unzip" - "unzip"
# Not required for Ubuntu 24, was a test for Debian 13
- name: Disable IPv6 on all interfaces
ansible.posix.sysctl:
name: "net.ipv6.conf.all.disable_ipv6"
value: "1"
sysctl_set: true
- name: Create Consul directory - name: Create Consul directory
ansible.builtin.file: ansible.builtin.file:
path: "/usr/local/consul-{{ consul_version }}" path: "/usr/local/consul-{{ consul_version }}"
@@ -20,40 +13,40 @@
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "https://releases.hashicorp.com/consul/{{ consul_version }}/consul_{{ consul_version }}_linux_amd64.zip" src: "https://releases.hashicorp.com/consul/{{ consul_version }}/consul_{{ consul_version }}_linux_amd64.zip"
dest: "/usr/local/consul-{{ consul_version }}" dest: "/usr/local/consul-{{ consul_version }}"
creates: "/usr/local/consul-{{ consul_version }}/consul"
remote_src: true remote_src: true
notify: notify:
- "Restart Consul" - "Restart Consul"
- name: Link Consul - name: Link consul
ansible.builtin.file: ansible.builtin.file:
src: "/usr/local/consul-{{ consul_version }}/consul" src: "/usr/local/consul-{{ consul_version }}/consul"
dest: "/usr/local/bin/consul" dest: "/usr/local/sbin/consul"
state: "link" state: "link"
notify:
- "Restart Consul"
- name: Create consul system group - name: Create Consul system group
ansible.builtin.group: ansible.builtin.group:
name: "consul" name: "consul"
system: true system: true
- name: Create consul system user - name: Create Consul system user
ansible.builtin.user: ansible.builtin.user:
name: "consul" name: "consul"
group: "consul" group: "consul"
groups: ["ssl-cert"] groups: ["ssl-cert"]
password: "!" password: "!"
system: true system: true
shell: "/usr/sbin/nologin"
home: "/opt/consul" home: "/opt/consul"
- name: Create consul config directory - name: Create Consul config directory
ansible.builtin.file: ansible.builtin.file:
path: "/etc/consul" path: "/etc/consul"
state: "directory" state: "directory"
group: "consul" group: "consul"
mode: "0750" mode: "0750"
- name: Create consul log dir - name: Create Consul log dir
ansible.builtin.file: ansible.builtin.file:
path: "/var/log/consul" path: "/var/log/consul"
state: "directory" state: "directory"
@@ -84,35 +77,162 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
# consul acl bootstrap -format=json - name: Generate Consul ACL config
# { when: "consul_server"
# "CreateIndex": 1042, ansible.builtin.template:
# "ModifyIndex": 1042, src: "templates/acl.json"
# "AccessorID": "50f8bfb6-b259-4c08-f9aa-2dc0eeb08e99", dest: "/etc/consul/acl.json"
# "SecretID": "b7f16a8a-0b4f-4661-fea2-f79d95dd011b", group: "consul"
# "Description": "Bootstrap Token (Global Management)", mode: "0640"
# "Policies": [ notify:
# { - "Restart Consul"
# "ID": "00000000-0000-0000-0000-000000000001",
# "Name": "global-management" - name: Running handlers for restarting Consul if required
# } ansible.builtin.meta: "flush_handlers"
# ],
# "Local": false, - name: Bootstrap Consul ACL system
# "CreateTime": "2026-01-19T18:41:13.294800712+03:00", when: "consul_server"
# "Hash": "X2AgaFhnQGRhSSF/h0m6qpX1wj/HJWbyXcxkEM/5GrY=" run_once: true
# } community.general.consul_acl_bootstrap:
bootstrap_secret: "{{ consul_bootstrap_secret }}"
- name: Create/retrieve Consul node agent accessor_id
ansible.builtin.shell:
executable: "/usr/bin/bash"
cmd: |
set -o pipefail
if test -f /root/.consul_agent_accessor_id; then
echo OK
else
uuidgen > /root/.consul_agent_accessor_id
echo CHANGED
fi
cat /root/.consul_agent_accessor_id
register: "consul_out_agent_accessor_id"
changed_when: "consul_out_agent_accessor_id.stdout_lines[0] == 'CHANGED'"
- name: Create/retrieve Consul node agent token
community.general.consul_token:
token: "{{ consul_bootstrap_secret }}"
accessor_id: "{{ consul_out_agent_accessor_id.stdout_lines[1] }}"
description: "Node {{ ansible_fqdn }}"
node_identities:
- datacenter: "{{ consul_datacenter }}"
node_name: "{{ ansible_hostname }}"
register: "consul_out_agent_token"
- name: Create Consul policy for DNS agent
when: "consul_server"
run_once: true
community.general.consul_policy:
token: "{{ consul_bootstrap_secret }}"
name: "dns-access"
rules: |
node_prefix "" {
policy = "read"
}
{% for service in consul_services %}
service "{{ service.name }}" {
policy = "read"
}
{% endfor %}
- name: Create/retrieve Consul DNS agent accessor_id
ansible.builtin.shell:
executable: "/usr/bin/bash"
cmd: |
set -o pipefail
if test -f /root/.consul_dns_accessor_id; then
echo OK
else
uuidgen > /root/.consul_dns_accessor_id
echo CHANGED
fi
cat /root/.consul_dns_accessor_id
register: "consul_out_dns_accessor_id"
changed_when: "consul_out_dns_accessor_id.stdout_lines[0] == 'CHANGED'"
- name: Create/retrieve Consul DNS agent token
when: "consul_server"
community.general.consul_token:
token: "{{ consul_bootstrap_secret }}"
accessor_id: "{{ consul_out_dns_accessor_id.stdout_lines[1] }}"
description: "DNS {{ ansible_fqdn }}"
policies:
- name: "dns-access"
register: "consul_out_dns_token"
- name: Generate Consul agent tokens config
ansible.builtin.template:
src: "templates/tokens.json"
dest: "/etc/consul/tokens.json"
group: "consul"
mode: "0640"
notify:
- "Restart Consul"
- name: Running handlers for restarting Consul if required
ansible.builtin.meta: "flush_handlers"
- name: Create Consul policy for UI
when: "consul_server and consul_ui_secret_id != None"
run_once: true
community.general.consul_policy:
token: "{{ consul_bootstrap_secret }}"
name: "ui-access"
rules: |
key_prefix "" {
policy = "write"
}
node_prefix "" {
policy = "read"
}
service_prefix "" {
policy = "read"
}
- name: Create/retrieve Consul UI accessor_id
when: "consul_server and consul_ui_secret_id != None and consul_servers[0] == ansible_hostname"
ansible.builtin.shell:
executable: "/usr/bin/bash"
cmd: |
set -o pipefail
if test -f /root/.consul_ui_accessor_id; then
echo OK
else
uuidgen > /root/.consul_ui_accessor_id
echo CHANGED
fi
cat /root/.consul_ui_accessor_id
register: "consul_out_ui_accessor_id"
changed_when: "consul_out_ui_accessor_id.stdout_lines[0] == 'CHANGED'"
- name: Create/retrieve Consul UI token
when: "consul_server and consul_ui_secret_id != None and consul_servers[0] == ansible_hostname"
community.general.consul_token:
token: "{{ consul_bootstrap_secret }}"
accessor_id: "{{ consul_out_ui_accessor_id.stdout_lines[1] }}"
secret_id: "{{ consul_ui_secret_id }}"
description: "UI"
policies:
- name: "ui-access"
- name: Register services to consul - name: Register services to consul
loop: "{{ consul_services }}" loop: "{{ consul_services }}"
community.general.consul_agent_service: community.general.consul_agent_service:
token: "{{ consul_bootstrap_secret }}"
name: "{{ item.name }}" name: "{{ item.name }}"
address: "{{ item.address | default(ansible_default_ipv4.address) }}"
service_port: "{{ item.port }}" service_port: "{{ item.port }}"
tags: "{{ item.tags | default([]) }}" tags: "{{ item.tags | default([]) }}"
# Because community.general.consul_agent_check is not idempotent:
register: "consul_out_register_services"
- name: Register HTTP checks to consul - name: Register HTTP checks to consul
loop: "{{ consul_services }}" loop: "{{ consul_services }}"
when: "item.check_http | default(None) != None" when: "consul_out_register_services.changed and (item.check_http | default(None) != None)"
community.general.consul_agent_check: community.general.consul_agent_check:
token: "{{ consul_bootstrap_secret }}"
name: "{{ item.name }}_check" name: "{{ item.name }}_check"
service_id: "{{ item.name }}" service_id: "{{ item.name }}"
interval: "{{ item.check_interval | default(consul_default_check_interval) }}" interval: "{{ item.check_interval | default(consul_default_check_interval) }}"
@@ -122,8 +242,9 @@
- name: Register TCP checks to consul - name: Register TCP checks to consul
loop: "{{ consul_services }}" loop: "{{ consul_services }}"
when: "item.check_tcp | default(None) != None" when: "consul_out_register_services and (item.check_tcp | default(None) != None)"
community.general.consul_agent_check: community.general.consul_agent_check:
token: "{{ consul_bootstrap_secret }}"
name: "{{ item.name }}_check" name: "{{ item.name }}_check"
service_id: "{{ item.name }}" service_id: "{{ item.name }}"
interval: "{{ item.check_interval | default(consul_default_check_interval) }}" interval: "{{ item.check_interval | default(consul_default_check_interval) }}"

View File

@@ -0,0 +1,9 @@
{
"acl" : {
"enabled" : true,
{% if consul_server %}
"default_policy" : "deny",
{% endif %}
"down_policy" : "extend-cache"
}
}

View File

@@ -1,28 +1,45 @@
{ {
"log_level": "INFO", "log_level": "INFO",
"enable_syslog": true,
"node_name": "{{ ansible_hostname }}",
"domain": "{{ consul_domain }}", "domain": "{{ consul_domain }}",
"datacenter": "{{ consul_datacenter | mandatory }}", "datacenter": "{{ consul_datacenter | mandatory }}",
"key_file": "{{ consul_key_file }}",
"cert_file": "{{ consul_cert_file }}",
"ca_file": "{{ consul_ca_file }}",
"verify_incoming": true,
"verify_outgoing": true,
"verify_server_hostname": true,
"encrypt": "{{ consul_secret_key }}", "encrypt": "{{ consul_secret_key }}",
"data_dir": "/opt/consul", "data_dir": "/opt/consul",
"retry_join": ["{{ consul_servers | join('", "') }}"],
"disable_coordinates": true,
"disable_remote_exec": true,
"disable_update_check": true,
"tls": {
"defaults": {
"ca_file": "{{ consul_ca_file }}",
"cert_file": "{{ consul_cert_file }}",
"key_file": "{{ consul_key_file }}",
"verify_incoming": true,
"verify_outgoing": true
},
"internal_rpc": {
"verify_server_hostname": true
}
},
"addresses": {
"http": "127.0.0.1",
"https": "0.0.0.0"
},
"ports": {
"https": 8501
},
{% if consul_server %} {% if consul_server %}
"node_name": "{{ ansible_hostname }}",
"server": true, "server": true,
"ui": true,
"leave_on_terminate": true, "leave_on_terminate": true,
"client_addr": "{{ consul_client_addr }}",
"bind_addr": "{{ consul_bind_addr }}", "bind_addr": "{{ consul_bind_addr }}",
"advertise_addr": "{{ consul_advertise_addr }}", "bootstrap_expect": {{ consul_servers | length }},
"bootstrap_expect": {{ (consul_servers | length) - 1 }}, "ui_config": {
"enable_syslog": true, "enabled": {{ (consul_ui_secret_id != None) | ternary('true', 'false') }}
},
"performance": { "performance": {
"raft_multiplier": 1 "raft_multiplier": 1
}, }
{% else %}
{% endif %} {% endif %}
"retry_join": ["{{ consul_servers | join('", "') }}"]
} }

View File

@@ -9,10 +9,10 @@ ConditionFileNotEmpty=/etc/consul/config.json
Type=notify 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/consul-{{ consul_version }}/consul agent -config-file=/etc/consul/
KillMode=process KillMode=process
Restart=on-failure Restart=on-failure
LimitNOFILE=65536 LimitNOFILE=65536
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -0,0 +1,10 @@
{
"acl" : {
"tokens": {
{% if consul_server %}
"dns": "{{ consul_out_dns_token.token.SecretID }}",
{% endif %}
"default": "{{ consul_out_agent_token.token.SecretID }}"
}
}
}

View File

@@ -0,0 +1,5 @@
# BYOW - Build Your Own WAF
A WAF _à la carte_.
## CrowdSec

View File

@@ -0,0 +1,4 @@
crowdsec_collections: []
crowdsec_bouncers: []
# - name: <name>
# key: <LAPI key>

View File

@@ -0,0 +1,4 @@
- name: Restart Crowdsec Securiy Engine
ansible.builtin.systemd_service:
name: "crowdsec"
state: "restarted"

View File

@@ -0,0 +1,71 @@
# FIXME: cluster
# FIXME: BdD PostgreSQL
# FIXME: sudo cscli console enroll cmmszy92r000402l1kugvmw76
# FIXME: AppSec
- name: Add Crowdsec repository
ansible.builtin.deb822_repository:
name: "crowdsec"
uris: "https://packagecloud.io/crowdsec/crowdsec/any/"
suites: "any"
components: "main"
signed_by: "https://packagecloud.io/crowdsec/crowdsec/gpgkey"
- name: Install Crowdsec Security Engine
ansible.builtin.apt:
name:
- "crowdsec"
- "jq"
update_cache: true
notify:
- "Restart Crowdsec Securiy Engine"
- name: Configure Crowdsec
ansible.builtin.template:
src: "templates/config.yaml"
dest: "/etc/crowdsec/config.yaml"
mode: "0600"
notify:
- "Restart Crowdsec Securiy Engine"
- name: Enable and start Crowdsec Securiy Engine
ansible.builtin.systemd_service:
name: "crowdsec"
state: "started"
enabled: true
- name: Get collections list
ansible.builtin.shell:
executable: "/usr/bin/bash"
cmd: |
set -o pipefail
cscli collection list --output=json |jq -r '.collections[] |.name'
register: "crowdsec_out_list_collections"
changed_when: false
- name: Install collections
loop: "{{ crowdsec_collections }}"
when: "item not in crowdsec_out_list_collections.stdout_lines"
ansible.builtin.command:
cmd: "cscli collections install {{ item }}"
changed_when: true
notify:
- "Restart Crowdsec Securiy Engine"
- name: Get bouncers list
ansible.builtin.shell:
executable: "/usr/bin/bash"
cmd: |
set -o pipefail
cscli bouncers list --output=json |jq -r '.[] |.name'
register: "crowdsec_out_list_bouncers"
changed_when: false
- name: Create bouncers API keys
loop: "{{ crowdsec_bouncers }}"
when: "item.name not in crowdsec_out_list_bouncers.stdout_lines"
ansible.builtin.command:
cmd: "cscli bouncers add {{ item.name }} --key '{{ item.key }}'"
changed_when: true
notify:
- "Restart Crowdsec Securiy Engine"

View File

@@ -0,0 +1,66 @@
common:
daemonize: true
log_media: "file"
log_level: "info"
log_dir: "/var/log/"
log_max_size: 20
compress_logs: true
log_max_files: 10
config_paths:
config_dir: "/etc/crowdsec/"
data_dir: "/var/lib/crowdsec/data/"
simulation_path: "/etc/crowdsec/simulation.yaml"
hub_dir: "/etc/crowdsec/hub/"
index_path: "/etc/crowdsec/hub/.index.json"
notification_dir: "/etc/crowdsec/notifications/"
plugin_dir: "/usr/lib/crowdsec/plugins/"
crowdsec_service:
#console_context_path: "/etc/crowdsec/console/context.yaml"
#acquisition_path: "/etc/crowdsec/acquis.yaml"
acquisition_dir: "/etc/crowdsec/acquis.d"
parser_routines: 1
cscli:
output: "human"
color: "auto"
db_config:
log_level: "info"
type: "sqlite"
db_path: "/var/lib/crowdsec/data/crowdsec.db"
#max_open_conns: 100
#user:
#password:
#db_name:
#host:
#port:
flush:
max_items: 5000
max_age: "7d"
plugin_config:
user: "nobody"
group: "nogroup"
api:
client:
insecure_skip_verify: false
credentials_path: "/etc/crowdsec/local_api_credentials.yaml"
server:
log_level: "info"
listen_uri: "127.0.0.1:8080"
profiles_path: "/etc/crowdsec/profiles.yaml"
console_path: "/etc/crowdsec/console.yaml"
online_client: # Central API credentials (to push signals and receive bad IPs)
credentials_path: "/etc/crowdsec/online_api_credentials.yaml"
trusted_ips: # IP ranges, or IPs which can have admin API access
- "127.0.0.1"
- "::1"
# tls:
# cert_file: "/etc/crowdsec/ssl/cert.pem"
# key_file: "/etc/crowdsec/ssl/key.pem"
prometheus:
enabled: false

View File

@@ -8,7 +8,7 @@ local_ca_certs_default_not_before: "-1d" # valid since yesterday
# Required # Required
local_ca_certs_ca_passphrase: null local_ca_certs_ca_passphrase: null
local_ca_certs_list: {} local_ca_certs_list: []
# - cn: <string> # - cn: <string>
# domains: <array of strings> # domains: <array of strings>
# country_name: <string> # country_name: <string>

View File

@@ -4,3 +4,4 @@ mysql_users: []
# - name: <string # - name: <string
# password: <string> # password: <string>
# priv: <string> # priv: <string>
# host: <string> (default "127.0.0.1")

View File

@@ -1,14 +1,14 @@
- name: Installation de MySQL - name: Installation de MySQL
ansible.builtin.apt: ansible.builtin.apt:
name: name:
- "mysql-server" - "{{ mysql_distros[ansible_distribution].mysql_package }}"
- "python3-pymysql" - "python3-pymysql"
notify: notify:
- "Restart MySQL" - "Restart MySQL"
- name: Set MySQL bind_address parameter - name: Set MySQL bind_address parameter
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
path: "/etc/mysql/mysql.conf.d/mysqld.cnf" path: "{{ mysql_distros[ansible_distribution].mysql_config_file }}"
regexp: "^bind-address\\s+=" regexp: "^bind-address\\s+="
line: "bind-address = *" line: "bind-address = *"
notify: notify:
@@ -28,4 +28,4 @@
name: "{{ item.name }}" name: "{{ item.name }}"
password: "{{ item.password }}" password: "{{ item.password }}"
priv: "{{ item.priv }}" priv: "{{ item.priv }}"
host: "{{ item.host }}" host: "{{ item.host | default('127.0.0.1') }}"

View File

@@ -0,0 +1,7 @@
mysql_distros:
Debian:
mysql_package: "default-mysql-server"
mysql_config_file: "/etc/mysql/mariadb.conf.d/50-server.cnf"
Ubuntu:
mysql_package: "mysql-server"
mysql_config_file: "/etc/mysql/mysql.conf.d/mysqld.cnf"

View File

@@ -1,5 +1,5 @@
# Required # Required
ovh_ldp_token: null ovh_ldp_token: null # Jeton d'écriture
ovh_ldp_cluster: null ovh_ldp_cluster: null
# Defaults # Defaults

View File

@@ -0,0 +1,5 @@
# BYOW - Build Your Own WAF
A WAF _à la carte_.
## Squid

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
- 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

@@ -1,4 +1,12 @@
server: server:
do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
hide-identity: yes
hide-version: yes
harden-glue: yes
use-caps-for-id: yes
do-not-query-localhost: no do-not-query-localhost: no
{% for interface in unbound_listen_interfaces %} {% for interface in unbound_listen_interfaces %}
interface: {{ interface }} interface: {{ interface }}
@@ -6,15 +14,18 @@ server:
{% for access in unbound_access_control %} {% for access in unbound_access_control %}
access-control: {{ access }} access-control: {{ access }}
{% endfor %} {% endfor %}
{% for zone in unbound_stub_zones %}
# Consul ne supporte pas DNSSEC
# FIXME: domain-insecure: "consul"
{% for zone in unbound_stub_zones %}
stub-zone: stub-zone:
name: "{{ zone.name }}" name: "{{ zone.name }}"
stub-addr: {{ zone.stub_to }} stub-addr: {{ zone.stub_to }}
stub-no-cache: {{ zone.no_cache | default('yes') }} stub-no-cache: {{ zone.no_cache | default('yes') }}
{% endfor %} {% endfor %}
{% for zone in unbound_forward_zones %}
{% for zone in unbound_forward_zones %}
forward-zone: forward-zone:
name: "{{ zone.name }}" name: "{{ zone.name }}"
forward-addr: {{ zone.forward_to }} forward-addr: {{ zone.forward_to }}

View File

@@ -13,7 +13,7 @@ wordpress_active_theme: null
wordpress_user: "wordpress" wordpress_user: "wordpress"
wordpress_plugins: [] wordpress_plugins: []
wordpress_locale: "fr_FR" wordpress_locale: "fr_FR"
wordpress_db_host: "localhost" wordpress_db_host: "127.0.0.1"
wordpress_db_name: "{{ wordpress_user }}" wordpress_db_name: "{{ wordpress_user }}"
wordpress_db_user: "{{ wordpress_user }}" wordpress_db_user: "{{ wordpress_user }}"
wordpress_db_prefix: "wp_" wordpress_db_prefix: "wp_"

View File

@@ -0,0 +1,135 @@
# FIXME: Wait for DB to be ready
- name: Téléchargement 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 }}"
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: "0775"
- name: Apply permissions
loop:
- "cache"
- "upgrade"
- "uploads"
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

View File

@@ -16,7 +16,6 @@
- "zip" - "zip"
- name: Installation de wp-cli - name: Installation de wp-cli
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname"
ansible.builtin.get_url: ansible.builtin.get_url:
url: "https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar" url: "https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar"
dest: "/usr/local/bin/wp" dest: "/usr/local/bin/wp"
@@ -42,148 +41,8 @@
mode: "0750" mode: "0750"
- name: Installation de Wordpress - name: Installation de Wordpress
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname" when: "wordpress_create_master == None or wordpress_create_master == ansible_hostname"
become: true ansible.builtin.include_tasks: "install_wordpress.yml"
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
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname"
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
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname"
become: true
become_user: "{{ wordpress_user }}"
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
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname"
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
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname"
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
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname"
vars:
wordpress_plugins_to_install:
- "wordpress-importer"
- "one-click-demo-import"
ansible.builtin.include_tasks: "install_plugins.yml"
- name: Importation des plugins OCDI
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname"
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
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname"
vars:
wordpress_plugins_to_install: "{{ wordpress_plugins }}"
ansible.builtin.include_tasks: "install_plugins.yml"
- name: Création du répertoire cache de Wordpress
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname"
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
when: "wordpress_create_master != None and wordpress_create_master == ansible_hostname"
loop:
- "cache"
- "upgrade"
- "uploads"
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 - name: Configuration de Nginx
ansible.builtin.template: ansible.builtin.template: