2026-06-09 08:45:33 +00:00
|
|
|
variable "tls_secret_name" { type = string }
|
|
|
|
|
variable "redis_host" { type = string }
|
|
|
|
|
|
|
|
|
|
data "vault_kv_secret_v2" "secrets" {
|
|
|
|
|
mount = "secret"
|
|
|
|
|
name = "platform"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# x402 wallet lives under secret/viktor (Viktor's personal config) — not
|
|
|
|
|
# secret/platform — and is the only field this stack needs from there.
|
|
|
|
|
data "vault_kv_secret_v2" "viktor" {
|
|
|
|
|
mount = "secret"
|
|
|
|
|
name = "viktor"
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-19 16:38:38 +00:00
|
|
|
# Cloudflare Turnstile widget backing the CrowdSec captcha remediation. When
|
|
|
|
|
# LAPI issues a `captcha` decision (rate-limit / 403 / crawl / sensitive-file
|
|
|
|
|
# abuse — the captcha_remediation profile in stacks/crowdsec .../values.yaml),
|
|
|
|
|
# the Traefik bouncer plugin serves this widget so flagged users can
|
|
|
|
|
# self-unblock instead of getting a hard 403 (which is what happened before:
|
|
|
|
|
# the plugin had no captcha provider, so captcha decisions fell through to ban).
|
|
|
|
|
# Scoped to the registrable domain — a Turnstile hostname covers its subdomains,
|
|
|
|
|
# so one widget works on every *.viktorbarzin.me app the bouncer fronts.
|
|
|
|
|
# Same IaC pattern as stacks/forgejo/turnstile.tf; the CF Global API Key
|
|
|
|
|
# (cloudflare_provider.tf) has account-wide Turnstile access. The widget secret
|
|
|
|
|
# is sensitive and lands in TF state (Tier-1 PG, encrypted) — same trust level
|
|
|
|
|
# as the CrowdSec LAPI key already passed into the bouncer middleware.
|
|
|
|
|
data "cloudflare_accounts" "main" {}
|
|
|
|
|
|
|
|
|
|
resource "cloudflare_turnstile_widget" "crowdsec_captcha" {
|
|
|
|
|
account_id = data.cloudflare_accounts.main.accounts[0].id
|
|
|
|
|
name = "crowdsec-captcha"
|
|
|
|
|
domains = ["viktorbarzin.me"]
|
|
|
|
|
# "managed" = Cloudflare adaptively decides whether to show an interactive
|
|
|
|
|
# challenge; lowest friction for real users, strong against bots.
|
|
|
|
|
mode = "managed"
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 08:45:33 +00:00
|
|
|
module "traefik" {
|
|
|
|
|
source = "./modules/traefik"
|
|
|
|
|
tier = local.tiers.core
|
|
|
|
|
crowdsec_api_key = data.vault_kv_secret_v2.secrets.data["ingress_crowdsec_api_key"]
|
2026-06-19 16:38:38 +00:00
|
|
|
captcha_site_key = cloudflare_turnstile_widget.crowdsec_captcha.id
|
|
|
|
|
captcha_secret_key = cloudflare_turnstile_widget.crowdsec_captcha.secret
|
2026-06-09 08:45:33 +00:00
|
|
|
redis_host = var.redis_host
|
|
|
|
|
tls_secret_name = var.tls_secret_name
|
|
|
|
|
auth_fallback_htpasswd = data.vault_kv_secret_v2.secrets.data["auth_fallback_htpasswd"]
|
|
|
|
|
x402_wallet_address = lookup(data.vault_kv_secret_v2.viktor.data, "x402_wallet_address", "")
|
|
|
|
|
# Reuses the existing Alertmanager Slack incoming webhook — same channel as
|
|
|
|
|
# other infra alerts. Payment events arrive as a normal Slack message.
|
|
|
|
|
x402_notify_webhook_url = lookup(data.vault_kv_secret_v2.viktor.data, "alertmanager_slack_api_url", "")
|
|
|
|
|
}
|