All checks were successful
ci/woodpecker/push/default Pipeline was successful
The Traefik bouncer plugin's API key was never registered with LAPI — the crowdsec stack reads many keys from Vault but not ingress_crowdsec_api_key, and the chart registers no bouncer. So LAPI returned 403 to the plugin, which with updateMaxFailure=-1 failed open and enforced NOTHING: no community-blocklist bans, and the (now-Turnstile-wired) captcha never fired. cscli bouncers list was empty; the registration was likely lost in the MySQL->PostgreSQL DB migration with no IaC to recreate it. Seed the bouncer at LAPI startup via BOUNCER_KEY_traefik, valued from the same Vault key the middleware presents — so they match by construction, and the bouncer re-registers automatically on every LAPI start (survives DB wipes). - stacks/crowdsec/main.tf: read ingress_crowdsec_api_key, pass to module. - module main.tf: new sensitive var + thread into the values templatefile. - values.yaml: BOUNCER_KEY_traefik on lapi.env. - docs/architecture/security.md: document registration + fail-open history and the proxied-app coverage caveat. Activates enforcement (community blocklist bans + captcha) on non-proxied apps; internal IPs stay bypassed (clientTrustedIPs), fail-open-on-LAPI-down preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
35 lines
1.8 KiB
HCL
35 lines
1.8 KiB
HCL
# =============================================================================
|
|
# CrowdSec Stack — Security/WAF
|
|
# =============================================================================
|
|
|
|
variable "tls_secret_name" { type = string }
|
|
variable "mysql_host" { type = string }
|
|
variable "postgresql_host" { type = string }
|
|
|
|
data "vault_kv_secret_v2" "secrets" {
|
|
mount = "secret"
|
|
name = "platform"
|
|
}
|
|
|
|
locals {
|
|
homepage_credentials = jsondecode(data.vault_kv_secret_v2.secrets.data["homepage_credentials"])
|
|
}
|
|
|
|
module "crowdsec" {
|
|
source = "./modules/crowdsec"
|
|
tier = local.tiers.cluster
|
|
tls_secret_name = var.tls_secret_name
|
|
mysql_host = var.mysql_host
|
|
postgresql_host = var.postgresql_host
|
|
homepage_username = local.homepage_credentials["crowdsec"]["username"]
|
|
homepage_password = local.homepage_credentials["crowdsec"]["password"]
|
|
enroll_key = data.vault_kv_secret_v2.secrets.data["crowdsec_enroll_key"]
|
|
db_password = data.vault_kv_secret_v2.secrets.data["crowdsec_db_password"]
|
|
crowdsec_dash_api_key = data.vault_kv_secret_v2.secrets.data["crowdsec_dash_api_key"]
|
|
crowdsec_dash_machine_id = data.vault_kv_secret_v2.secrets.data["crowdsec_dash_machine_id"]
|
|
crowdsec_dash_machine_password = data.vault_kv_secret_v2.secrets.data["crowdsec_dash_machine_password"]
|
|
slack_webhook_url = data.vault_kv_secret_v2.secrets.data["alertmanager_slack_api_url"]
|
|
# Same key the traefik-stack bouncer middleware uses — seeded into LAPI so the
|
|
# bouncer authenticates and pulls decisions (was unregistered → 403 → fail-open).
|
|
ingress_bouncer_key = data.vault_kv_secret_v2.secrets.data["ingress_crowdsec_api_key"]
|
|
}
|