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.
This commit is contained in:
Viktor Barzin 2026-03-28 14:38:12 +02:00
parent b339d454dd
commit 7e0b0d9362
2 changed files with 63 additions and 9 deletions

View file

@ -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"]
}

View file

@ -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"
}
}
}
}
}
}