diff --git a/example/ansible/play-debug.yml b/example/ansible/play-debug.yml index 8d9e944..a836dd9 100644 --- a/example/ansible/play-debug.yml +++ b/example/ansible/play-debug.yml @@ -4,8 +4,10 @@ - "vars/common.yml" - "vars/secrets.yml" vars: - caddy_debug: 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 }}" diff --git a/example/ansible/play-main.yml b/example/ansible/play-main.yml index 6c1db3d..c68fce3 100644 --- a/example/ansible/play-main.yml +++ b/example/ansible/play-main.yml @@ -24,19 +24,19 @@ tags: - "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: 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 hosts: all @@ -45,7 +45,7 @@ - "vars/common.yml" - "vars/secrets.yml" vars: - common_proxy_server: "{{ hostvars['proxy1'].ansible_default_ipv4.address }}:3128" +# common_proxy_server: "{{ hostvars['proxy1'].ansible_default_ipv4.address }}:3128" common_default_packages: - "net-tools" - "htop" @@ -130,6 +130,8 @@ unbound_stub_zones: - name: "{{ common.consul.datacenter }}.{{ common.consul.domain }}" 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 }}" @@ -142,6 +144,7 @@ roles: - "local_ca_certs" - "consul" + - "crowdsec" - "caddy" # - "keepalived" - "unbound" diff --git a/example/ansible/vars/secrets.yml b/example/ansible/vars/secrets.yml index d99f15a..0b49e87 100644 --- a/example/ansible/vars/secrets.yml +++ b/example/ansible/vars/secrets.yml @@ -1,29 +1,33 @@ $ANSIBLE_VAULT;1.1;AES256 -64353736626438653965303633376231356361353433626432333862363638353937386635396430 -6339336333323062333732626539333761396234316535360a376361326263393431626638373165 -34663632643038643630386331663035383738333364393636376137643037623939313136373665 -3238356630313039310a636661613764373133323033356339373334613331366337343935313334 -30326631386562666336363735316234616339396633356633343339313562386530303863613331 -30646638313535316433393361613461613932393933613932656362653666383638333739383831 -64363266336438373635376663616432346464303365316564326536623133356264326462393537 -38646261303962373465663266643139393366616133346662666331326539353635616662326138 -64393163396339336231303765366130323530336433666361353161376536326465333438326337 -64633731626636666430656335303431623663333464383138393231633435323338303862666634 -33373664383663393637623735656338346566626431333964366637636431653461656363396463 -65343033326433343863393865663236623133306663636237383532306137316534313436323137 -66346630383133383430383263656137653462656665313664626366613135323463613737346261 -32643833616135363066633632313863373563366362366534353762616162626239306135356164 -31323631323939343763643964656330363133383437306236646466326266333964613162623666 -61623139616466653438663065373635653162653461613131383531373632633235336332333935 -30666430396634373563646439353563646536353966653635323932306261383863653137333766 -39616564373330366230333562626435343934333331623761373336393937376562343433386334 -31386663613031613835353662373138663835373235613462316232373533626439343631353632 -36366133313862336336663134353935306661613935363331663136323930616361396431653439 -37356666363331303637326663623561346236356266323731383632323835336362633362646237 -31376230633165306135306537656434666630343865343333663762646361323462303031303935 -36356132313934323235343436303032623461343630623464353663303138653566313938653635 -36613037376430313966356237323362353862626139663339323834326637313636326534343031 -37373664653361373064666362343165613063303336663330643064643331383432653333353330 -38646535363835653039636531616632313130313963656435303739326530633434316237303562 -66646631393563356136303537303563346537366633316437373231313265633133653431323031 -33393937356430363739 +66313136343333353333666339316366663134303039333133376461333765383138663238376162 +3739613236313832623963346565393630636562356239640a613634643365653264373338393737 +64626435636131666132646566393735653364353764366139616533393135366662353564636666 +3139636566613065340a383931353561376339373730376633656537663435653764663139316665 +30666261313338656230323861633033323733346463326166323262613866323264663736633235 +66313334343963316261613137363439633938383134656164323963636665376534653963346534 +36383435323239666366653362373635306465316338303332656438333332623030333433643165 +39333432616666353539653766656363373365373335643238373336653433646536313666393163 +61653664626131343131376363316337373666396564646465333764353465393666636136646162 +34336364303139343864303766393963313135343531366131633731633234623133353631636234 +32626230393539343561393135386163363536376637376533356536356266613266613434396338 +34343364356364356262346161653962313336656433346132623437626332366336643034326532 +38346339333332303265353334386661353935623837633865373361313830613138613138323835 +34656535383664373039333734396564623662363330643332646535386533376265343863626632 +36636537393162373064323361376361306664363065633961363033313031313763613134353436 +63366434363137346263653966323239613164363634613235656364663037386537386335666431 +65363162623031633166613735393735333133626466313061396333306236393039383062326264 +38343464636330326162653036326663383631613836656461303462333035636335326135303766 +37323463666338323766393063633662343835393330326332616331373633663366393563303438 +37666162633461313333623934633138663930623135663638386634626430346264633735333532 +64623461613538343436306436626133383733393737393463306539383763663866346164306339 +64303335303539306632306164613761343035306632303063393233633262333264333431333363 +30373861636565363438346338376531346162303564653435373237636237366632323564646539 +37383361356362653132653961376134663930363763326437306338623064623862336435373664 +63393262323039616539626137396432613932633730316235396435396531366466616264393161 +62323339393039323235383663663265643065626137636562656335343233313734366166336264 +35633765316262633636323866633266653037326165646230326564323033613963316331346331 +32346265386164376361303532343730343139393837343162666238633366643361353332376137 +33633764323133316334323234353932363165636438656563353630373238376465643463656535 +30383337303861326433303137383539313536333465663063306137646264316438636665323263 +34383933666565333962363863313536653232363930306665336432373738353238666362353639 +37316136373639373233 diff --git a/example/tofu/infra.auto.tfvars b/example/tofu/infra.auto.tfvars index 182203f..a5e6618 100644 --- a/example/tofu/infra.auto.tfvars +++ b/example/tofu/infra.auto.tfvars @@ -1,7 +1,7 @@ vms = { - proxy1 = { - disk = "20g" - }, +# proxy1 = { +# disk = "20g" +# }, waf1 = { cpus = 4 }, diff --git a/seb4itik/byow/roles/caddy/defaults/main.yml b/seb4itik/byow/roles/caddy/defaults/main.yml index 5082f5c..fd919bd 100644 --- a/seb4itik/byow/roles/caddy/defaults/main.yml +++ b/seb4itik/byow/roles/caddy/defaults/main.yml @@ -6,19 +6,37 @@ caddy_http_port: 80 caddy_https_port: 443 caddy_default_bind: null +# Sotware versions +caddy_go_version: "1.26.1" +caddy_xcaddy_version: "0.4.5" +caddy_version: "2.11.2" + # If true, will not install software, just deploy Caddyfile and OWASP CRS # configuration files. caddy_config_only: false -# Debug will go to /var/log//debug.log. -caddy_debug: 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//site_.log +# - for WAF logs (Coraza), in /var/log//waf.log +caddy_log_to_file: true +caddy_log_format: "console" # "console" or "json" +caddy_log_debug: false # Debug will go to /var/log//debug.log + +# Paths +caddy_binary: "/usr/local/bin/caddy_{{ caddy_my_name }}" # Should begin by "caddy" for CrowdSec +caddy_system_user: "{{ caddy_my_name }}" +caddy_system_group: "{{ caddy_my_name }}" +caddy_home_dir: "/opt/{{ 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 }}" # Required for Let's Encrypt auto certificates. caddy_email: null -# If a custom CA root have to be used. -caddy_ca_root: null - # For using a Consul cluster. caddy_consul: false caddy_consul_server: "{{ ansible_fqdn }}:8501" @@ -33,32 +51,35 @@ 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/ -caddy_geoip_account_id: null -caddy_geoip_license_key: null +caddy_geoip: false +caddy_geoip_account_id: null # Required +caddy_geoip_license_key: null # Required caddy_geoip_edition_ids: "GeoLite2-ASN,GeoLite2-Country,GeoLite2-City" -caddy_geoip_update_frequency: 86400 # In seconds +caddy_geoip_update_frequency: 86400 # In seconds -# Sotware versions -caddy_version: "2.11.2" -caddy_go_version: "1.26.1" -caddy_xcaddy_version: "0.4.5" +# 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_owasp_crs_versions: ["4.24.1"] - -# Paths -caddy_binary: "/usr/local/bin/{{ caddy_my_name }}" -caddy_system_user: "{{ caddy_my_name }}" -caddy_system_group: "{{ caddy_my_name }}" -caddy_home_dir: "/opt/{{ 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_coraza_crs_versions: ["4.24.1"] # OWASP CRS plugins that can be actived per site. # Cf.: https://github.com/coreruleset/plugin-registry -caddy_crs_plugins: +caddy_coraza_crs_plugins: - name: "drupal-rule-exclusions" version: "1.0.0" - name: "wordpress-rule-exclusions" @@ -87,6 +108,7 @@ caddy_sites: [] # 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) # For overriding default global parameters. Will be merged with diff --git a/seb4itik/byow/roles/caddy/handlers/main.yml b/seb4itik/byow/roles/caddy/handlers/main.yml index b9aa94d..3bb8f2c 100644 --- a/seb4itik/byow/roles/caddy/handlers/main.yml +++ b/seb4itik/byow/roles/caddy/handlers/main.yml @@ -6,3 +6,8 @@ ansible.builtin.service: name: "{{ caddy_my_name }}" state: "restarted" + +- name: Restart Crowdsec Securiy Engine + ansible.builtin.systemd_service: + name: "crowdsec" + state: "restarted" diff --git a/seb4itik/byow/roles/caddy/tasks/caddy_config.yml b/seb4itik/byow/roles/caddy/tasks/caddy_config.yml index 5a18cd9..3bcc879 100644 --- a/seb4itik/byow/roles/caddy/tasks/caddy_config.yml +++ b/seb4itik/byow/roles/caddy/tasks/caddy_config.yml @@ -2,63 +2,63 @@ ansible.builtin.set_fact: caddy_sites_defaults: "{{ caddy_global_sites_defaults | combine(caddy_my_sites_defaults) }}" -- name: Create sites config directories - loop: "{{ caddy_sites }}" - ansible.builtin.file: - path: "{{ caddy_sites_dir }}/{{ item.id }}" - state: "directory" - group: "{{ caddy_system_group }}" - mode: "0750" - -- name: Generate sites coraza.conf config file - loop: "{{ caddy_sites }}" - loop_control: - loop_var: "site" - ansible.builtin.template: - src: "templates/coraza.conf" - dest: "{{ caddy_sites_dir }}/{{ site.id }}/coraza.conf" - group: "{{ caddy_system_group }}" - mode: "0640" - notify: - - "Restart Caddy" - -- name: Generate sites crs-setup config file - loop: "{{ caddy_sites }}" - loop_control: - loop_var: "site" - ansible.builtin.template: - src: "templates/crs-setup.conf" - dest: "{{ caddy_sites_dir }}/{{ site.id }}/crs-setup.conf" - group: "{{ caddy_system_group }}" - mode: "0640" - notify: - - "Restart Caddy" - -- name: Generate sites exclusions before request file - loop: "{{ caddy_sites }}" - loop_control: - loop_var: "site" - ansible.builtin.template: - src: "templates/exclusions-request-before.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" +- name: Configure Caddy Coraza CRS + when: "caddy_coraza" + block: + - name: Create sites config directories + loop: "{{ caddy_sites }}" + ansible.builtin.file: + path: "{{ caddy_sites_dir }}/{{ item.id }}" + state: "directory" + group: "{{ caddy_system_group }}" + mode: "0750" + - name: Generate sites coraza.conf config file + loop: "{{ caddy_sites }}" + loop_control: + loop_var: "site" + ansible.builtin.template: + src: "templates/coraza.conf" + dest: "{{ caddy_sites_dir }}/{{ site.id }}/coraza.conf" + group: "{{ caddy_system_group }}" + mode: "0640" + notify: + - "Restart Caddy" + - name: Generate sites crs-setup config file + loop: "{{ caddy_sites }}" + loop_control: + loop_var: "site" + ansible.builtin.template: + src: "templates/crs-setup.conf" + dest: "{{ caddy_sites_dir }}/{{ site.id }}/crs-setup.conf" + group: "{{ caddy_system_group }}" + mode: "0640" + notify: + - "Restart Caddy" + - name: Generate sites exclusions before request file + loop: "{{ caddy_sites }}" + loop_control: + loop_var: "site" + ansible.builtin.template: + src: "templates/exclusions-request-before.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" - 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" diff --git a/seb4itik/byow/roles/caddy/tasks/caddy_crowdsec.yml b/seb4itik/byow/roles/caddy/tasks/caddy_crowdsec.yml new file mode 100644 index 0000000..fba4e31 --- /dev/null +++ b/seb4itik/byow/roles/caddy/tasks/caddy_crowdsec.yml @@ -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" diff --git a/seb4itik/byow/roles/caddy/tasks/caddy_install.yml b/seb4itik/byow/roles/caddy/tasks/caddy_install.yml index 6fa370c..e153b73 100644 --- a/seb4itik/byow/roles/caddy/tasks/caddy_install.yml +++ b/seb4itik/byow/roles/caddy/tasks/caddy_install.yml @@ -37,11 +37,26 @@ ansible.builtin.set_fact: caddy_build_command: > /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/{{ caddy_coraza_caddy_version }} + {% endif %} + {% if 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 + {% endif %} + {% if caddy_geoip %} --with github.com/zhangjiayin/caddy-geoip2 + {% 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 @@ -95,7 +110,8 @@ owner: "{{ caddy_system_user }}" - name: Install OWASP CRS - loop: "{{ caddy_owasp_crs_versions }}" + when: "caddy_coraza" + loop: "{{ caddy_coraza_crs_versions }}" ansible.builtin.unarchive: src: "https://github.com/coreruleset/coreruleset/archive/refs/tags/v{{ item }}.tar.gz" dest: "{{ caddy_config_dir }}" @@ -103,8 +119,9 @@ group: "{{ caddy_system_group }}" remote_src: true -- name: Install plugins - loop: "{{ caddy_crs_plugins }}" +- name: Install CRS plugins + when: "caddy_coraza" + loop: "{{ caddy_coraza_crs_plugins }}" ansible.builtin.unarchive: src: "https://github.com/{{ item.provider | default('coreruleset') }}/{{ item.name }}-plugin/archive/refs/tags/v{{ item.version }}.tar.gz" dest: "{{ caddy_crs_plugins_dir }}" @@ -118,9 +135,9 @@ group: "{{ caddy_system_group }}" remote_src: true -- name: Create plugins after config - loop: "{{ caddy_crs_plugins }}" - when: "not(item.has_after_config | default(false))" +- 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" diff --git a/seb4itik/byow/roles/caddy/tasks/main.yml b/seb4itik/byow/roles/caddy/tasks/main.yml index 023e8ac..929aeed 100644 --- a/seb4itik/byow/roles/caddy/tasks/main.yml +++ b/seb4itik/byow/roles/caddy/tasks/main.yml @@ -6,5 +6,9 @@ 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 ansible.builtin.include_tasks: "caddy_config.yml" diff --git a/seb4itik/byow/roles/caddy/templates/Caddyfile b/seb4itik/byow/roles/caddy/templates/Caddyfile index 78885a6..4b684d4 100644 --- a/seb4itik/byow/roles/caddy/templates/Caddyfile +++ b/seb4itik/byow/roles/caddy/templates/Caddyfile @@ -1,31 +1,38 @@ { http_port {{ caddy_http_port }} https_port {{ caddy_https_port }} + admin 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 - admin off - persist_config off +{% endif %} {% if caddy_consul %} storage consul { address "{{ caddy_consul_server }}" - token "{{ caddy_out_consul_token.token.SecretID }}" + 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 }}" + aes_key "{{ caddy_consul_aes_key | mandatory }}" tls_enabled "true" tls_insecure "false" } {% endif %} -{% if caddy_geoip_account_id != None and caddy_geoip_license_key != None %} +{% if caddy_geoip %} geoip2 { - accountId "{{ caddy_geoip_account_id }}" - licenseKey "{{ caddy_geoip_license_key }}" + accountId "{{ caddy_geoip_account_id | mandatory }}" + licenseKey "{{ caddy_geoip_license_key | mandatory }}" databaseDirectory "{{ caddy_home_dir }}/geoip2" lockFile "{{ caddy_home_dir }}/geoip2/.geoip2.lock" editionID "{{ caddy_geoip_edition_ids }}" @@ -38,32 +45,48 @@ email "{{ caddy_email }}" {% endif %} -{% if caddy_ca_root | default(None) != None %} - tls { - ca_root "{{ caddy_ca_root }}" - } -{% endif %} - -{% if caddy_debug %} - log { - format json - level DEBUG - output file {{ caddy_log_dir }}/debug.log +{% if caddy_crowdsec %} + 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 %} +{% if caddy_coraza %} log waf { - format {{ caddy_sites_defaults.log_format }} include "http.handlers.waf" + format {{ caddy_log_format }} format append { waf_id {{ caddy_my_name }} } -{% if caddy_sites_defaults.log_to_file %} +{% if caddy_log_to_file %} output file {{ caddy_log_dir }}/waf.log {% else %} output stdout {% endif %} } +{% endif %} + +{% if caddy_log_debug %} + log { + level DEBUG + format {{ caddy_log_format }} + format append { + waf_id {{ caddy_my_name }} + } + output file {{ caddy_log_dir }}/debug.log + } +{% endif %} } # 0=origin, 1=methods, 2=headers, 3=allow credentials, 4=max age, 5=vary, 6=expose headers @@ -105,7 +128,7 @@ {% 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(', ') }} { redir https://{{ site.name }}{uri} } @@ -117,6 +140,10 @@ {{ site.name }} { encode gzip zstd +{% if caddy_crowdsec and site.crowdsec | default(caddy_sites_defaults.crowdsec) %} + crowdsec +{% endif %} + {% for path in site.paths %} handle{% if path.strip_prefix | default(false) %}_path{% endif %} {{ path.path | default('*') }} { reverse_proxy { @@ -161,7 +188,7 @@ respond @blocked "Access Denied" 403 {% endif %} -{% if site.geoip | default(caddy_sites_defaults.geoip) %} +{% if caddy_geoip and site.geoip | default(caddy_sites_defaults.geoip) %} geoip2_vars strict @geofilter expression {{ site.geoip_filter_expression | default(caddy_sites_defaults.geoip_filter_expression) }} respond @geofilter "Access Denied" 403 @@ -203,7 +230,7 @@ {% endfor %} } -{% if site.rate_limit | default(caddy_sites_defaults.rate_limit) %} +{% if caddy_ratelimit and site.rate_limit | default(caddy_sites_defaults.rate_limit) %} rate_limit @not_whitelisted { {% if caddy_consul %} distributed @@ -216,7 +243,7 @@ aes_key "{{ caddy_consul_aes_key }}" tls_enabled "true" tls_insecure "false" - } + } {% endif %} log_key zone remote_ip { @@ -227,7 +254,7 @@ } {% endif %} -{% if site.crs | default(caddy_sites_defaults.crs) %} +{% if caddy_coraza and site.crs | default(caddy_sites_defaults.crs) %} coraza_waf @not_whitelisted { load_owasp_crs directives ` @@ -261,7 +288,7 @@ import cors-deny {% 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) %} bot_barrier @not_whitelisted { 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) }} @@ -303,12 +330,12 @@ } log { - format {{ site.log_format | default(caddy_sites_defaults.log_format) }} level INFO + format {{ caddy_log_format }} format append { waf_id {{ caddy_my_name }} } -{% if site.log_to_file | default(caddy_sites_defaults.log_to_file) %} +{% if caddy_log_to_file %} output file {{ caddy_log_dir }}/site_{{ site.id }}.log {% else %} output stdout diff --git a/seb4itik/byow/roles/caddy/vars/main.yml b/seb4itik/byow/roles/caddy/vars/main.yml index 416b849..adda0f2 100644 --- a/seb4itik/byow/roles/caddy/vars/main.yml +++ b/seb4itik/byow/roles/caddy/vars/main.yml @@ -1,16 +1,9 @@ # Sites default parameter values caddy_global_sites_defaults: - # Site settings + # Site aliases + site_aliases_redirect: true site_aliases: [] - site_redirect_from_aliases: true - - # Log settings - # If log_to_file is true, logs will go: - # - for each site, in: /var/log//site_.log - # - for WAF logs (Coraza), in /var/log//waf.log - log_to_file: false - log_format: "json" # Proxy settings # See: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#lb_policy @@ -90,6 +83,7 @@ caddy_global_sites_defaults: - "Content-Length" - "Content-Range" + # Security headers header: true # Security headers @@ -274,12 +268,12 @@ caddy_global_sites_defaults: value: "🍷" # caddy-ratelimit - rate_limit: true + rate_limit: "{{ caddy_ratelimit }}" rate_limit_events: 1000 rate_limit_window: "1m" # steffenbusch/caddy-bot-barrier - bot_barrier: false + bot_barrier: "{{ caddy_bot_barrier }}" bot_barrier_secret: null # Required bot_barrier_complexity: 18 bot_barrier_valid_for: "60m" @@ -471,3 +465,5 @@ caddy_global_sites_defaults: geoip: false geoip_debug: false geoip_filter_expression: '{geoip2.country_code} == "UNK"' + + crowdsec: "{{ caddy_crowdsec }}" diff --git a/seb4itik/byow/roles/crowdsec/README.md b/seb4itik/byow/roles/crowdsec/README.md new file mode 100644 index 0000000..f137b3a --- /dev/null +++ b/seb4itik/byow/roles/crowdsec/README.md @@ -0,0 +1,5 @@ +# BYOW - Build Your Own WAF + +A WAF _à la carte_. + +## CrowdSec diff --git a/seb4itik/byow/roles/crowdsec/defaults/main.yml b/seb4itik/byow/roles/crowdsec/defaults/main.yml new file mode 100644 index 0000000..243a5d2 --- /dev/null +++ b/seb4itik/byow/roles/crowdsec/defaults/main.yml @@ -0,0 +1,4 @@ +crowdsec_collections: [] +crowdsec_bouncers: [] +# - name: +# key: diff --git a/seb4itik/byow/roles/crowdsec/handlers/main.yml b/seb4itik/byow/roles/crowdsec/handlers/main.yml new file mode 100644 index 0000000..beb635e --- /dev/null +++ b/seb4itik/byow/roles/crowdsec/handlers/main.yml @@ -0,0 +1,4 @@ +- name: Restart Crowdsec Securiy Engine + ansible.builtin.systemd_service: + name: "crowdsec" + state: "restarted" diff --git a/seb4itik/byow/roles/crowdsec/tasks/main.yml b/seb4itik/byow/roles/crowdsec/tasks/main.yml new file mode 100644 index 0000000..d7a95ce --- /dev/null +++ b/seb4itik/byow/roles/crowdsec/tasks/main.yml @@ -0,0 +1,70 @@ +# 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" + 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" diff --git a/seb4itik/byow/roles/crowdsec/templates/config.yaml b/seb4itik/byow/roles/crowdsec/templates/config.yaml new file mode 100644 index 0000000..2911030 --- /dev/null +++ b/seb4itik/byow/roles/crowdsec/templates/config.yaml @@ -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