infra/stacks/privatebin/main.tf
Viktor Barzin 8197842646 anubis: fix 500 on multi-replica + roll out to 6 more public sites
Browser visits to viktorbarzin.me started returning HTTP 500 with
`store: key not found: "challenge:..."` in pod logs. Root cause:
each Anubis pod stores in-flight challenges in process memory; with
2 replicas behind a ClusterIP, the PoW-solved request can be
routed to a different pod than the one that issued the challenge.
Anubis upstream documents the same caveat ("when running multiple
instances on the same base domain, the key must be the same across
all instances" — true for the ed25519 signing key, but the
challenge store is still pod-local without a shared backend).

Drop module default replicas: 2 → 1. Worst-case: ~1s cold-start on
pod restart. Real fix (Redis-backed challenge store) noted as a
follow-up in CLAUDE.md.

Roll Anubis out to: f1-stream, cyberchef (cc), jsoncrack (json),
privatebin (pb), homepage (home), real-estate-crawler (wrongmove
UI only — `/api` ingress stays direct via path-based ingress carve-
out so XHRs from the SPA bypass the challenge).

End-state: 9 public hosts now Anubis-fronted (blog, www, kms,
travel, f1, cc, json, pb, home, wrongmove). All return the
challenge HTML to bare curl/browser; verified-IP search engines and
/robots.txt + /.well-known still skip via the strict-policy
allowlist.
2026-05-10 11:12:40 +00:00

160 lines
4.2 KiB
HCL

variable "tls_secret_name" {
type = string
sensitive = true
}
variable "nfs_server" { type = string }
resource "kubernetes_namespace" "privatebin" {
metadata {
name = "privatebin"
labels = {
"istio-injection" : "disabled"
tier = local.tiers.edge
}
}
lifecycle {
# KYVERNO_LIFECYCLE_V1: goldilocks-vpa-auto-mode ClusterPolicy stamps this label on every namespace
ignore_changes = [metadata[0].labels["goldilocks.fairwinds.com/vpa-update-mode"]]
}
}
module "tls_secret" {
source = "../../modules/kubernetes/setup_tls_secret"
namespace = kubernetes_namespace.privatebin.metadata[0].name
tls_secret_name = var.tls_secret_name
}
resource "kubernetes_persistent_volume_claim" "data_proxmox" {
wait_until_bound = false
metadata {
name = "privatebin-data-proxmox"
namespace = kubernetes_namespace.privatebin.metadata[0].name
annotations = {
"resize.topolvm.io/threshold" = "80%"
"resize.topolvm.io/increase" = "100%"
"resize.topolvm.io/storage_limit" = "5Gi"
}
}
spec {
access_modes = ["ReadWriteOnce"]
storage_class_name = "proxmox-lvm"
resources {
requests = {
storage = "1Gi"
}
}
}
}
resource "kubernetes_deployment" "privatebin" {
metadata {
name = "privatebin"
namespace = kubernetes_namespace.privatebin.metadata[0].name
labels = {
app = "privatebin"
tier = local.tiers.edge
}
}
spec {
replicas = 1
strategy {
type = "Recreate"
}
selector {
match_labels = {
app = "privatebin"
}
}
template {
metadata {
labels = {
app = "privatebin"
}
}
spec {
container {
image = "privatebin/nginx-fpm-alpine"
name = "privatebin"
image_pull_policy = "IfNotPresent"
port {
container_port = 8080
}
volume_mount {
name = "data"
mount_path = "/srv/data"
sub_path = "data"
}
resources {
requests = {
cpu = "10m"
memory = "128Mi"
}
limits = {
memory = "128Mi"
}
}
}
volume {
name = "data"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name
}
}
}
}
}
lifecycle {
# KYVERNO_LIFECYCLE_V1: Kyverno admission webhook mutates dns_config with ndots=2
ignore_changes = [spec[0].template[0].spec[0].dns_config]
}
}
resource "kubernetes_service" "privatebin" {
metadata {
name = "privatebin"
namespace = kubernetes_namespace.privatebin.metadata[0].name
labels = {
"app" = "privatebin"
}
}
spec {
selector = {
app = "privatebin"
}
port {
port = "80"
target_port = "8080"
}
}
}
module "anubis" {
source = "../../modules/kubernetes/anubis_instance"
name = "privatebin"
namespace = kubernetes_namespace.privatebin.metadata[0].name
target_url = "http://${kubernetes_service.privatebin.metadata[0].name}.${kubernetes_namespace.privatebin.metadata[0].name}.svc.cluster.local"
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
namespace = kubernetes_namespace.privatebin.metadata[0].name
name = "privatebin"
host = "pb"
dns_type = "proxied"
service_name = module.anubis.service_name
port = module.anubis.service_port
anti_ai_scraping = false
tls_secret_name = var.tls_secret_name
custom_content_security_policy = "script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval'"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "PrivateBin"
"gethomepage.dev/description" = "Encrypted pastebin"
"gethomepage.dev/icon" = "privatebin.png"
"gethomepage.dev/group" = "Development & CI"
"gethomepage.dev/pod-selector" = ""
}
}