diff --git a/stacks/poison-fountain/main.tf b/stacks/poison-fountain/main.tf new file mode 100644 index 00000000..add4832b --- /dev/null +++ b/stacks/poison-fountain/main.tf @@ -0,0 +1,273 @@ +variable "tls_secret_name" { type = string } + +locals { + tiers = { + core = "0-core" + cluster = "1-cluster" + gpu = "2-gpu" + edge = "3-edge" + aux = "4-aux" + } +} + +resource "kubernetes_namespace" "poison_fountain" { + metadata { + name = "poison-fountain" + labels = { + "istio-injection" = "disabled" + tier = local.tiers.aux + } + } +} + +module "tls_secret" { + source = "../../modules/kubernetes/setup_tls_secret" + namespace = kubernetes_namespace.poison_fountain.metadata[0].name + tls_secret_name = var.tls_secret_name +} + +# ConfigMap for the Python service code +resource "kubernetes_config_map" "poison_fountain_code" { + metadata { + name = "poison-fountain-code" + namespace = kubernetes_namespace.poison_fountain.metadata[0].name + } + + data = { + "server.py" = file("${path.module}/app/server.py") + } +} + +# ConfigMap for the fetcher script +resource "kubernetes_config_map" "poison_fountain_fetcher" { + metadata { + name = "poison-fountain-fetcher" + namespace = kubernetes_namespace.poison_fountain.metadata[0].name + } + + data = { + "fetch-poison.sh" = file("${path.module}/app/fetch-poison.sh") + } +} + +# Main service deployment +resource "kubernetes_deployment" "poison_fountain" { + metadata { + name = "poison-fountain" + namespace = kubernetes_namespace.poison_fountain.metadata[0].name + labels = { + app = "poison-fountain" + tier = local.tiers.aux + } + } + + spec { + replicas = 1 + strategy { + type = "Recreate" + } + selector { + match_labels = { + app = "poison-fountain" + } + } + template { + metadata { + labels = { + app = "poison-fountain" + } + } + spec { + container { + name = "poison-fountain" + image = "python:3.12-slim" + command = ["python", "/app/server.py"] + + port { + container_port = 8080 + } + + env { + name = "CACHE_DIR" + value = "/data/cache" + } + env { + name = "DRIP_BYTES" + value = "50" + } + env { + name = "DRIP_DELAY" + value = "0.5" + } + env { + name = "POISON_DOMAIN" + value = "poison.viktorbarzin.me" + } + + volume_mount { + name = "code" + mount_path = "/app" + read_only = true + } + volume_mount { + name = "data" + mount_path = "/data" + } + + liveness_probe { + http_get { + path = "/healthz" + port = 8080 + } + initial_delay_seconds = 5 + period_seconds = 30 + } + readiness_probe { + http_get { + path = "/healthz" + port = 8080 + } + initial_delay_seconds = 3 + period_seconds = 10 + } + + resources { + requests = { + cpu = "10m" + memory = "32Mi" + } + limits = { + cpu = "100m" + memory = "128Mi" + } + } + } + + volume { + name = "code" + config_map { + name = kubernetes_config_map.poison_fountain_code.metadata[0].name + } + } + volume { + name = "data" + nfs { + server = "10.0.10.15" + path = "/mnt/main/poison-fountain" + } + } + } + } + } +} + +# Internal service (for ForwardAuth from Traefik) +resource "kubernetes_service" "poison_fountain" { + metadata { + name = "poison-fountain" + namespace = kubernetes_namespace.poison_fountain.metadata[0].name + labels = { + app = "poison-fountain" + } + } + + spec { + selector = { + app = "poison-fountain" + } + port { + name = "http" + port = 8080 + target_port = 8080 + } + } +} + +# Public ingress for the poison trap subdomain +# Deliberately NO rate limiting, NO CrowdSec, NO anti-AI (we WANT scrapers here) +module "ingress" { + source = "../../modules/kubernetes/ingress_factory" + namespace = kubernetes_namespace.poison_fountain.metadata[0].name + name = "poison-fountain" + host = "poison" + port = 8080 + tls_secret_name = var.tls_secret_name + skip_default_rate_limit = true + exclude_crowdsec = true + anti_ai_scraping = false +} + +# CronJob to fetch and cache poisoned content from Poison Fountain +resource "kubernetes_cron_job_v1" "poison_fetcher" { + metadata { + name = "poison-fountain-fetcher" + namespace = kubernetes_namespace.poison_fountain.metadata[0].name + } + + spec { + schedule = "0 */6 * * *" + successful_jobs_history_limit = 1 + failed_jobs_history_limit = 1 + concurrency_policy = "Forbid" + + job_template { + metadata { + name = "poison-fountain-fetcher" + } + spec { + template { + metadata { + name = "poison-fountain-fetcher" + } + spec { + container { + name = "fetcher" + image = "curlimages/curl:latest" + command = ["sh", "/scripts/fetch-poison.sh"] + + env { + name = "CACHE_DIR" + value = "/data/cache" + } + env { + name = "POISON_URL" + value = "https://rnsaffn.com/poison2/" + } + env { + name = "FETCH_COUNT" + value = "50" + } + + volume_mount { + name = "scripts" + mount_path = "/scripts" + read_only = true + } + volume_mount { + name = "data" + mount_path = "/data" + } + } + + volume { + name = "scripts" + config_map { + name = kubernetes_config_map.poison_fountain_fetcher.metadata[0].name + default_mode = "0755" + } + } + volume { + name = "data" + nfs { + server = "10.0.10.15" + path = "/mnt/main/poison-fountain" + } + } + + restart_policy = "Never" + } + } + } + } + } +} diff --git a/stacks/poison-fountain/secrets b/stacks/poison-fountain/secrets new file mode 120000 index 00000000..ca54a7cf --- /dev/null +++ b/stacks/poison-fountain/secrets @@ -0,0 +1 @@ +../../secrets \ No newline at end of file diff --git a/stacks/poison-fountain/terragrunt.hcl b/stacks/poison-fountain/terragrunt.hcl new file mode 100644 index 00000000..0d1c8e53 --- /dev/null +++ b/stacks/poison-fountain/terragrunt.hcl @@ -0,0 +1,8 @@ +include "root" { + path = find_in_parent_folders() +} + +dependency "platform" { + config_path = "../platform" + skip_outputs = true +}