From 7e0b0d9362966cdc91e12246016329cdb260a036 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 28 Mar 2026 14:38:12 +0200 Subject: [PATCH] fix: headscale VPN setup hardening - Add SQLite backup CronJob (every 6h to NFS for cloud sync pickup) - Move headscale-ui secrets (COOKIE_SECRET, ROOT_API_KEY) from hardcoded values to Vault-managed secrets - Add DERP IPv6 address (2001:470:6e:43d::2) for IPv6-capable clients - Clean up stale test nodes, duplicate users, rename "localhost" nodes Also updated headscale_config in Vault to include DERP ipv6 field and headscale_ui_cookie_secret/headscale_ui_api_key secrets. --- stacks/headscale/main.tf | 16 ++++--- stacks/headscale/modules/headscale/main.tf | 56 +++++++++++++++++++++- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/stacks/headscale/main.tf b/stacks/headscale/main.tf index a0c731f3..5741c9aa 100644 --- a/stacks/headscale/main.tf +++ b/stacks/headscale/main.tf @@ -11,11 +11,13 @@ locals { } module "headscale" { - source = "./modules/headscale" - tls_secret_name = var.tls_secret_name - nfs_server = var.nfs_server - headscale_config = data.vault_kv_secret_v2.secrets.data["headscale_config"] - headscale_acl = data.vault_kv_secret_v2.secrets.data["headscale_acl"] - homepage_token = try(local.homepage_credentials["headscale"]["api_key"], "") - tier = local.tiers.core + source = "./modules/headscale" + tls_secret_name = var.tls_secret_name + nfs_server = var.nfs_server + headscale_config = data.vault_kv_secret_v2.secrets.data["headscale_config"] + headscale_acl = data.vault_kv_secret_v2.secrets.data["headscale_acl"] + homepage_token = try(local.homepage_credentials["headscale"]["api_key"], "") + tier = local.tiers.core + ui_cookie_secret = data.vault_kv_secret_v2.secrets.data["headscale_ui_cookie_secret"] + ui_api_key = data.vault_kv_secret_v2.secrets.data["headscale_ui_api_key"] } diff --git a/stacks/headscale/modules/headscale/main.tf b/stacks/headscale/modules/headscale/main.tf index bf73f28c..98b6575a 100644 --- a/stacks/headscale/modules/headscale/main.tf +++ b/stacks/headscale/modules/headscale/main.tf @@ -9,6 +9,14 @@ variable "homepage_token" { default = "" sensitive = true } +variable "ui_cookie_secret" { + type = string + sensitive = true +} +variable "ui_api_key" { + type = string + sensitive = true +} resource "kubernetes_namespace" "headscale" { metadata { @@ -192,11 +200,11 @@ resource "kubernetes_deployment" "headscale" { } env { name = "COOKIE_SECRET" - value = "kekekekke" + value = var.ui_cookie_secret } env { name = "ROOT_API_KEY" - value = "kekekekeke" + value = var.ui_api_key } } dns_config { @@ -364,3 +372,47 @@ resource "kubernetes_config_map" "headscale-config" { "acl.yaml" = var.headscale_acl } } + +# Backup CronJob — sqlite3 .backup to NFS for cloud sync pickup +resource "kubernetes_cron_job_v1" "headscale_backup" { + metadata { + name = "headscale-backup" + namespace = kubernetes_namespace.headscale.metadata[0].name + } + spec { + schedule = "0 */6 * * *" + successful_jobs_history_limit = 1 + failed_jobs_history_limit = 1 + job_template { + metadata {} + spec { + template { + metadata {} + spec { + container { + name = "backup" + image = "keinos/sqlite3:latest" + command = ["/bin/sh", "-c", <<-EOT + mkdir -p /mnt/headscale-backup && \ + sqlite3 /mnt/db.sqlite ".backup /mnt/headscale-backup/db.sqlite.bak" && \ + echo "Backup completed at $(date)" + EOT + ] + volume_mount { + name = "nfs-data" + mount_path = "/mnt" + } + } + volume { + name = "nfs-data" + persistent_volume_claim { + claim_name = module.nfs_data.claim_name + } + } + restart_policy = "OnFailure" + } + } + } + } + } +}