diff --git a/config.tfvars b/config.tfvars index ce173138..804ef7b8 100644 Binary files a/config.tfvars and b/config.tfvars differ diff --git a/stacks/actualbudget/providers.tf b/stacks/actualbudget/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/actualbudget/providers.tf +++ b/stacks/actualbudget/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/audiobookshelf/providers.tf b/stacks/audiobookshelf/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/audiobookshelf/providers.tf +++ b/stacks/audiobookshelf/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/audiobookshelf/terragrunt.hcl b/stacks/audiobookshelf/terragrunt.hcl index f4c920ab..7aa411a2 100644 --- a/stacks/audiobookshelf/terragrunt.hcl +++ b/stacks/audiobookshelf/terragrunt.hcl @@ -7,7 +7,3 @@ dependency "platform" { skip_outputs = true } -dependency "vault" { - config_path = "../vault" - skip_outputs = true -} diff --git a/stacks/calibre/providers.tf b/stacks/calibre/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/calibre/providers.tf +++ b/stacks/calibre/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/calibre/terragrunt.hcl b/stacks/calibre/terragrunt.hcl index f4c920ab..7aa411a2 100644 --- a/stacks/calibre/terragrunt.hcl +++ b/stacks/calibre/terragrunt.hcl @@ -7,7 +7,3 @@ dependency "platform" { skip_outputs = true } -dependency "vault" { - config_path = "../vault" - skip_outputs = true -} diff --git a/stacks/changedetection/providers.tf b/stacks/changedetection/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/changedetection/providers.tf +++ b/stacks/changedetection/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/changedetection/terragrunt.hcl b/stacks/changedetection/terragrunt.hcl index f4c920ab..7aa411a2 100644 --- a/stacks/changedetection/terragrunt.hcl +++ b/stacks/changedetection/terragrunt.hcl @@ -7,7 +7,3 @@ dependency "platform" { skip_outputs = true } -dependency "vault" { - config_path = "../vault" - skip_outputs = true -} diff --git a/stacks/coturn/providers.tf b/stacks/coturn/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/coturn/providers.tf +++ b/stacks/coturn/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/dbaas/modules/dbaas/main.tf b/stacks/dbaas/modules/dbaas/main.tf index 233913f2..a45d850a 100644 --- a/stacks/dbaas/modules/dbaas/main.tf +++ b/stacks/dbaas/modules/dbaas/main.tf @@ -324,11 +324,19 @@ resource "kubernetes_cron_job_v1" "mysql-backup" { container { name = "mysql-backup" image = "mysql" - # TODO: would be nice to rotate at some point... Current size is 11MB so not really needed atm + env { + name = "MYSQL_PWD" + value_from { + secret_key_ref { + name = "cluster-secret" + key = "ROOT_PASSWORD" + } + } + } command = ["/bin/bash", "-c", <<-EOT set -euxo pipefail export now=$(date +"%Y_%m_%d_%H_%M") - mysqldump --all-databases -u root -p${var.dbaas_root_password} --host mysql.dbaas.svc.cluster.local > /backup/dump_$now.sql + mysqldump --all-databases -u root --host mysql.dbaas.svc.cluster.local > /backup/dump_$now.sql # Rotate - delete last log file cd /backup @@ -1068,7 +1076,7 @@ resource "kubernetes_cron_job_v1" "postgresql-backup" { # Rotate - delete last log file cd /backup - find . -name "dump_*.sql" -type f -mtime +7 -delete # 7 day retention of backups + find . -name "dump_*.sql" -type f -mtime +14 -delete # 14 day retention of backups echo Done EOT ] diff --git a/stacks/echo/providers.tf b/stacks/echo/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/echo/providers.tf +++ b/stacks/echo/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/freedify/providers.tf b/stacks/freedify/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/freedify/providers.tf +++ b/stacks/freedify/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/freshrss/providers.tf b/stacks/freshrss/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/freshrss/providers.tf +++ b/stacks/freshrss/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/freshrss/terragrunt.hcl b/stacks/freshrss/terragrunt.hcl index f4c920ab..7aa411a2 100644 --- a/stacks/freshrss/terragrunt.hcl +++ b/stacks/freshrss/terragrunt.hcl @@ -7,7 +7,3 @@ dependency "platform" { skip_outputs = true } -dependency "vault" { - config_path = "../vault" - skip_outputs = true -} diff --git a/stacks/frigate/providers.tf b/stacks/frigate/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/frigate/providers.tf +++ b/stacks/frigate/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/infra/.terraform.lock.hcl b/stacks/infra/.terraform.lock.hcl index 28fba95b..44e446dc 100644 --- a/stacks/infra/.terraform.lock.hcl +++ b/stacks/infra/.terraform.lock.hcl @@ -20,6 +20,25 @@ provider "registry.terraform.io/hashicorp/null" { ] } +provider "registry.terraform.io/hashicorp/vault" { + version = "5.8.0" + hashes = [ + "h1:gk1cR+x1D+TEz05MKWmpp0p06+Trob5cN0eYU1vZGJs=", + "zh:18e79b42c8c155a5c541a45d54a6ccdeab23c404c239acdeed336a17cbfc2fd4", + "zh:241f50d1ea40030578034b4440e41676f1c9b5e8a2be5cd3afdb6e387914e0bf", + "zh:3c25da9ca98df3ae13fd08aa32a94ae4d15959cbb7165044b5f411d16317dfa1", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:85a2cf2e47fe7eab5c81ad5051668c09dcdfd6b6318420f42b91ea179dee8eb0", + "zh:861d09d5ab848704cea3bf6c6765288e60938634bf54ed3c29dd2d9a9dc8f7c9", + "zh:9822fc41def7ef6f9ffe6ef8d6aaf5014870961d12f5050b60997fa0c12699e1", + "zh:9b63003071d47299bd2cb85af0a7d9b33329a8152a5ed06cdd3ba46e839f0c43", + "zh:c3ce51f4ab24ac788a0a3eeb0ec16c04f38b74ad9997f86d4dca384ab1472a5a", + "zh:c53702a1e829226c63baac15af2f727a8979e00384e220862c8047fd2fcf6b38", + "zh:e68cebb3c78f0fed01a3e15ac54f09b4276418d7b7d12811bcc79568e58dcfc1", + "zh:f8d97c753305077ddcbd9e60780c5b06b173de2127943b9590d7e8decda4bd7a", + ] +} + provider "registry.terraform.io/telmate/proxmox" { version = "3.0.2-rc07" constraints = "3.0.2-rc07" diff --git a/stacks/navidrome/providers.tf b/stacks/navidrome/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/navidrome/providers.tf +++ b/stacks/navidrome/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/navidrome/terragrunt.hcl b/stacks/navidrome/terragrunt.hcl index f4c920ab..7aa411a2 100644 --- a/stacks/navidrome/terragrunt.hcl +++ b/stacks/navidrome/terragrunt.hcl @@ -7,7 +7,3 @@ dependency "platform" { skip_outputs = true } -dependency "vault" { - config_path = "../vault" - skip_outputs = true -} diff --git a/stacks/novelapp/providers.tf b/stacks/novelapp/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/novelapp/providers.tf +++ b/stacks/novelapp/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/ntfy/providers.tf b/stacks/ntfy/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/ntfy/providers.tf +++ b/stacks/ntfy/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/openclaw/main.tf b/stacks/openclaw/main.tf index 9f1f019d..362505a2 100644 --- a/stacks/openclaw/main.tf +++ b/stacks/openclaw/main.tf @@ -992,3 +992,163 @@ resource "kubernetes_cron_job_v1" "task_processor" { } } } + +# --- OpenLobster: Multi-user Telegram AI assistant (trial) --- + +module "nfs_openlobster_data" { + source = "../../modules/kubernetes/nfs_volume" + name = "openlobster-data" + namespace = kubernetes_namespace.openclaw.metadata[0].name + nfs_server = var.nfs_server + nfs_path = "/mnt/main/openclaw/openlobster-data" +} + +resource "random_password" "openlobster_graphql_token" { + length = 32 + special = false +} + +resource "kubernetes_deployment" "openlobster" { + metadata { + name = "openlobster" + namespace = kubernetes_namespace.openclaw.metadata[0].name + labels = { + app = "openlobster" + tier = local.tiers.aux + } + } + spec { + strategy { + type = "Recreate" + } + replicas = 0 + selector { + match_labels = { + app = "openlobster" + } + } + template { + metadata { + labels = { + app = "openlobster" + } + } + spec { + # node4 has corrupted containerd content store — avoid it + affinity { + node_affinity { + required_during_scheduling_ignored_during_execution { + node_selector_term { + match_expressions { + key = "kubernetes.io/hostname" + operator = "NotIn" + values = ["k8s-node4"] + } + } + } + } + } + container { + name = "openlobster" + image = "ghcr.io/neirth/openlobster/openlobster:latest" + port { + container_port = 8080 + } + env { + name = "OPENLOBSTER_GRAPHQL_AUTH_TOKEN" + value = random_password.openlobster_graphql_token.result + } + env { + name = "OPENLOBSTER_PROVIDERS_ANTHROPIC_API_KEY" + value_from { + secret_key_ref { + name = "openclaw-secrets" + key = "anthropic_api_key" + } + } + } + env { + name = "OPENLOBSTER_PROVIDERS_ANTHROPIC_MODEL" + value = "claude-sonnet-4-20250514" + } + env { + name = "OPENLOBSTER_CHANNELS_TELEGRAM_TOKEN" + value_from { + secret_key_ref { + name = "openclaw-secrets" + key = "telegram_bot_token" + } + } + } + env { + name = "OPENLOBSTER_DATABASE_DRIVER" + value = "sqlite" + } + env { + name = "OPENLOBSTER_DATABASE_DSN" + value = "/app/data/openlobster.db" + } + env { + name = "OPENLOBSTER_AGENT_NAME" + value = "Lobster" + } + env { + name = "OPENLOBSTER_MEMORY_BACKEND" + value = "file" + } + volume_mount { + name = "openlobster-data" + mount_path = "/app/data" + } + resources { + requests = { + cpu = "10m" + memory = "64Mi" + } + limits = { + memory = "256Mi" + } + } + } + volume { + name = "openlobster-data" + persistent_volume_claim { + claim_name = module.nfs_openlobster_data.claim_name + } + } + } + } + } + lifecycle { + ignore_changes = [spec[0].template[0].spec[0].dns_config] + } +} + +resource "kubernetes_service" "openlobster" { + metadata { + name = "openlobster" + namespace = kubernetes_namespace.openclaw.metadata[0].name + labels = { + app = "openlobster" + } + } + spec { + selector = { + app = "openlobster" + } + port { + port = 80 + target_port = 8080 + } + } +} + +module "openlobster_ingress" { + source = "../../modules/kubernetes/ingress_factory" + namespace = kubernetes_namespace.openclaw.metadata[0].name + name = "openlobster" + tls_secret_name = var.tls_secret_name + host = "openlobster" + port = 80 + protected = true +} diff --git a/stacks/owntracks/providers.tf b/stacks/owntracks/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/owntracks/providers.tf +++ b/stacks/owntracks/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/platform/.terraform.lock.hcl b/stacks/platform/.terraform.lock.hcl index 9bb9232c..eca3dfe9 100644 --- a/stacks/platform/.terraform.lock.hcl +++ b/stacks/platform/.terraform.lock.hcl @@ -1,30 +1,6 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. -provider "registry.terraform.io/cloudflare/cloudflare" { - version = "4.52.5" - constraints = "~> 4.0" - hashes = [ - "h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=", - "h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=", - "zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b", - "zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a", - "zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7", - "zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238", - "zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b", - "zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072", - "zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661", - "zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c", - "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54", - "zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852", - "zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0", - "zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5", - "zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036", - "zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803", - ] -} - provider "registry.terraform.io/hashicorp/helm" { version = "3.1.1" hashes = [ @@ -65,26 +41,6 @@ provider "registry.terraform.io/hashicorp/kubernetes" { ] } -provider "registry.terraform.io/hashicorp/null" { - version = "3.2.4" - hashes = [ - "h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=", - "h1:hkf5w5B6q8e2A42ND2CjAvgvSN3puAosDmOJb3zCVQM=", - "zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43", - "zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a", - "zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991", - "zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f", - "zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e", - "zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615", - "zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442", - "zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5", - "zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f", - "zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f", - ] -} - provider "registry.terraform.io/hashicorp/vault" { version = "4.8.0" constraints = "~> 4.0" diff --git a/stacks/platform/backend.tf b/stacks/platform/backend.tf index 6d424f69..f9db2d0d 100644 --- a/stacks/platform/backend.tf +++ b/stacks/platform/backend.tf @@ -1,6 +1,6 @@ # Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa terraform { backend "local" { - path = "/woodpecker/src/github.com/ViktorBarzin/infra/state/stacks/platform/terraform.tfstate" + path = "/Users/viktorbarzin/code/infra/state/stacks/platform/terraform.tfstate" } } diff --git a/stacks/platform/modules/crowdsec/values.yaml b/stacks/platform/modules/crowdsec/values.yaml index fcfbb3af..fc491ac4 100644 --- a/stacks/platform/modules/crowdsec/values.yaml +++ b/stacks/platform/modules/crowdsec/values.yaml @@ -49,6 +49,9 @@ agent: - name: whitelist configMap: name: crowdsec-whitelist +podAnnotations: + dependency.kyverno.io/wait-for: "mysql.dbaas:3306" + lapi: resources: requests: diff --git a/stacks/platform/modules/infra-maintenance/main.tf b/stacks/platform/modules/infra-maintenance/main.tf index abf50453..d69a8f14 100644 --- a/stacks/platform/modules/infra-maintenance/main.tf +++ b/stacks/platform/modules/infra-maintenance/main.tf @@ -101,8 +101,8 @@ resource "kubernetes_cron_job_v1" "backup-etcd" { container { name = "backup-etcd" image = "registry.k8s.io/etcd:3.5.21-0" - command = ["etcdctl"] - args = ["--endpoints=https://127.0.0.1:2379", "--cacert=/etc/kubernetes/pki/etcd/ca.crt", "--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt", "--key=/etc/kubernetes/pki/etcd/healthcheck-client.key", "snapshot", "save", "/backup/etcd-snapshot-latest.db"] + command = ["/bin/sh", "-c"] + args = ["ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key snapshot save /backup/etcd-snapshot-$(date +%Y%m%d-%H%M%S).db"] env { name = "ETCDCTL_API" value = "3" diff --git a/stacks/platform/modules/kyverno/security-policies.tf b/stacks/platform/modules/kyverno/security-policies.tf index 1f1c83a8..00bcf3a6 100644 --- a/stacks/platform/modules/kyverno/security-policies.tf +++ b/stacks/platform/modules/kyverno/security-policies.tf @@ -160,6 +160,97 @@ resource "kubernetes_manifest" "policy_restrict_capabilities" { depends_on = [helm_release.kyverno] } +# ============================================================================= +# Image Pull Policy Governance +# ============================================================================= +# Mutate imagePullPolicy to IfNotPresent for all containers with pinned tags +# (non-:latest). This prevents pods from getting stuck in ImagePullBackOff +# when the pull-through cache at 10.0.20.10 has transient failures. +# For :latest or untagged images, set to Always so stale images don't persist. + +resource "kubernetes_manifest" "policy_set_image_pull_policy" { + manifest = { + apiVersion = "kyverno.io/v1" + kind = "ClusterPolicy" + metadata = { + name = "set-image-pull-policy" + annotations = { + "policies.kyverno.io/title" = "Set Image Pull Policy" + "policies.kyverno.io/category" = "Best Practices" + "policies.kyverno.io/severity" = "medium" + "policies.kyverno.io/description" = "Set imagePullPolicy to IfNotPresent for pinned tags and Always for :latest to prevent ImagePullBackOff from transient cache failures." + } + } + spec = { + background = false + rules = [ + { + name = "set-ifnotpresent-for-pinned-tags" + match = { + any = [{ + resources = { + kinds = ["Pod"] + } + }] + } + mutate = { + foreach = [{ + list = "request.object.spec.containers" + preconditions = { + all = [{ + key = "{{ ends_with(element.image, ':latest') || !contains(element.image, ':') }}" + operator = "Equals" + value = false + }] + } + patchStrategicMerge = { + spec = { + containers = [{ + name = "{{ element.name }}" + imagePullPolicy = "IfNotPresent" + }] + } + } + }] + } + }, + { + name = "set-always-for-latest" + match = { + any = [{ + resources = { + kinds = ["Pod"] + } + }] + } + mutate = { + foreach = [{ + list = "request.object.spec.containers" + preconditions = { + all = [{ + key = "{{ ends_with(element.image, ':latest') || !contains(element.image, ':') }}" + operator = "Equals" + value = true + }] + } + patchStrategicMerge = { + spec = { + containers = [{ + name = "{{ element.name }}" + imagePullPolicy = "Always" + }] + } + } + }] + } + } + ] + } + } + + depends_on = [helm_release.kyverno] +} + resource "kubernetes_manifest" "policy_require_trusted_registries" { manifest = { apiVersion = "kyverno.io/v1" diff --git a/stacks/platform/modules/monitoring/prometheus_chart_values.tpl b/stacks/platform/modules/monitoring/prometheus_chart_values.tpl index c4d5070b..970d943e 100755 --- a/stacks/platform/modules/monitoring/prometheus_chart_values.tpl +++ b/stacks/platform/modules/monitoring/prometheus_chart_values.tpl @@ -500,6 +500,90 @@ serverFiles: severity: critical annotations: summary: "etcd backup CronJob has never completed successfully" + - alert: PostgreSQLBackupStale + expr: (time() - kube_cronjob_status_last_successful_time{cronjob="postgresql-backup", namespace="dbaas"}) > 129600 + for: 30m + labels: + severity: critical + annotations: + summary: "PostgreSQL backup is {{ $value | humanizeDuration }} old (threshold: 36h)" + - alert: PostgreSQLBackupNeverSucceeded + expr: kube_cronjob_status_last_successful_time{cronjob="postgresql-backup", namespace="dbaas"} == 0 + for: 1h + labels: + severity: critical + annotations: + summary: "PostgreSQL backup CronJob has never completed successfully" + - alert: MySQLBackupStale + expr: (time() - kube_cronjob_status_last_successful_time{cronjob="mysql-backup", namespace="dbaas"}) > 129600 + for: 30m + labels: + severity: critical + annotations: + summary: "MySQL backup is {{ $value | humanizeDuration }} old (threshold: 36h)" + - alert: MySQLBackupNeverSucceeded + expr: kube_cronjob_status_last_successful_time{cronjob="mysql-backup", namespace="dbaas"} == 0 + for: 1h + labels: + severity: critical + annotations: + summary: "MySQL backup CronJob has never completed successfully" + - alert: VaultBackupStale + expr: (time() - kube_cronjob_status_last_successful_time{cronjob="vault-raft-backup", namespace="vault"}) > 129600 + for: 30m + labels: + severity: critical + annotations: + summary: "Vault backup is {{ $value | humanizeDuration }} old (threshold: 36h)" + - alert: VaultBackupNeverSucceeded + expr: kube_cronjob_status_last_successful_time{cronjob="vault-raft-backup", namespace="vault"} == 0 + for: 1h + labels: + severity: critical + annotations: + summary: "Vault backup CronJob has never completed successfully" + - alert: VaultwardenBackupStale + expr: (time() - kube_cronjob_status_last_successful_time{cronjob="vaultwarden-backup", namespace="vaultwarden"}) > 129600 + for: 30m + labels: + severity: critical + annotations: + summary: "Vaultwarden backup is {{ $value | humanizeDuration }} old (threshold: 36h)" + - alert: VaultwardenBackupNeverSucceeded + expr: kube_cronjob_status_last_successful_time{cronjob="vaultwarden-backup", namespace="vaultwarden"} == 0 + for: 1h + labels: + severity: critical + annotations: + summary: "Vaultwarden backup CronJob has never completed successfully" + - alert: RedisBackupStale + expr: (time() - kube_cronjob_status_last_successful_time{cronjob="redis-backup", namespace="redis"}) > 14400 + for: 30m + labels: + severity: critical + annotations: + summary: "Redis backup is {{ $value | humanizeDuration }} old (threshold: 4h)" + - alert: RedisBackupNeverSucceeded + expr: kube_cronjob_status_last_successful_time{cronjob="redis-backup", namespace="redis"} == 0 + for: 1h + labels: + severity: critical + annotations: + summary: "Redis backup CronJob has never completed successfully" + - alert: CSIDriverCrashLoop + expr: kube_pod_container_status_waiting_reason{reason="CrashLoopBackOff", namespace=~"nfs-csi|iscsi-csi"} > 0 + for: 10m + labels: + severity: critical + annotations: + summary: "CSI driver CrashLoopBackOff in {{ $labels.namespace }}/{{ $labels.pod }} — storage-layer failure risk" + - alert: BackupCronJobFailed + expr: kube_job_status_failed{job_name=~".*backup.*"} > 0 + for: 15m + labels: + severity: warning + annotations: + summary: "Backup job failed: {{ $labels.namespace }}/{{ $labels.job_name }}" - alert: NewTailscaleClient expr: irate(headscale_machine_registrations_total{action="reauth"}[5m]) > 0 for: 5m diff --git a/stacks/platform/modules/redis/main.tf b/stacks/platform/modules/redis/main.tf index 5f5c5966..1a89deea 100644 --- a/stacks/platform/modules/redis/main.tf +++ b/stacks/platform/modules/redis/main.tf @@ -283,12 +283,15 @@ resource "kubernetes_cron_job_v1" "redis-backup" { image = "redis:7-alpine" command = ["/bin/sh", "-c", <<-EOT set -eux + TIMESTAMP=$(date +%Y%m%d-%H%M) # Trigger a fresh RDB save on the master redis-cli -h redis.redis BGSAVE sleep 5 # Copy the RDB via redis-cli --rdb - redis-cli -h redis.redis --rdb /backup/dump.rdb - echo "Backup complete: $(ls -lh /backup/dump.rdb)" + redis-cli -h redis.redis --rdb /backup/redis-$TIMESTAMP.rdb + # Rotate — 7-day retention + find /backup -name 'redis-*.rdb' -type f -mtime +7 -delete + echo "Backup complete: redis-$TIMESTAMP.rdb" EOT ] volume_mount { diff --git a/stacks/privatebin/providers.tf b/stacks/privatebin/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/privatebin/providers.tf +++ b/stacks/privatebin/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/servarr/providers.tf b/stacks/servarr/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/servarr/providers.tf +++ b/stacks/servarr/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true } diff --git a/stacks/url/main.tf b/stacks/url/main.tf index d39d07a8..9dba0b24 100644 --- a/stacks/url/main.tf +++ b/stacks/url/main.tf @@ -170,6 +170,9 @@ resource "kubernetes_deployment" "shlink" { labels = { run = "shlink" } + annotations = { + "dependency.kyverno.io/wait-for" = "mysql.dbaas:3306" + } } spec { container { diff --git a/stacks/vault/main.tf b/stacks/vault/main.tf index 8959be73..8d527e52 100644 --- a/stacks/vault/main.tf +++ b/stacks/vault/main.tf @@ -163,8 +163,8 @@ resource "vault_jwt_auth_backend_role" "default" { backend = vault_jwt_auth_backend.oidc.path role_name = "default" token_policies = ["default"] - token_ttl = 3600 - token_max_ttl = 86400 + token_ttl = 604800 + token_max_ttl = 604800 user_claim = "email" groups_claim = "groups" role_type = "oidc" diff --git a/stacks/ytdlp/providers.tf b/stacks/ytdlp/providers.tf index f4845cc8..860c9eba 100644 --- a/stacks/ytdlp/providers.tf +++ b/stacks/ytdlp/providers.tf @@ -13,12 +13,6 @@ variable "kube_config_path" { default = "~/.kube/config" } -variable "vault_root_token" { - type = string - sensitive = true - default = "" -} - provider "kubernetes" { config_path = var.kube_config_path } @@ -31,6 +25,5 @@ provider "helm" { provider "vault" { address = "https://vault.viktorbarzin.me" - token = var.vault_root_token skip_child_token = true }