From 273b9c77d682015d87c1ec4b6d9ebb989f12a208 Mon Sep 17 00:00:00 2001 From: Jonas Juselius Date: Mon, 13 Oct 2025 15:57:44 +0200 Subject: [PATCH] feat: add separate headscale for (un-snatted) subnet routing --- helmfile.d/headscale-router.yaml.gotmpl | 42 +++++ .../headscale-router/env-oceanbox.yaml.gotmpl | 3 + values/headscale-router/env.yaml.gotmpl | 4 + .../manifests/headscale-router.yaml | 42 +++++ values/headscale-router/values/values.yaml | 171 ++++++++++++++++++ values/headscale/env-oceanbox.yaml.gotmpl | 1 - 6 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 helmfile.d/headscale-router.yaml.gotmpl create mode 100644 values/headscale-router/env-oceanbox.yaml.gotmpl create mode 100644 values/headscale-router/env.yaml.gotmpl create mode 100644 values/headscale-router/manifests/headscale-router.yaml create mode 100644 values/headscale-router/values/values.yaml diff --git a/helmfile.d/headscale-router.yaml.gotmpl b/helmfile.d/headscale-router.yaml.gotmpl new file mode 100644 index 00000000..df45fee4 --- /dev/null +++ b/helmfile.d/headscale-router.yaml.gotmpl @@ -0,0 +1,42 @@ +bases: + - ../envs/environments.yaml.gotmpl + +repositories: +- name: headscale + url: https://charts.gabe565.com + +commonLabels: + tier: system + +releases: +- name: headscale-router + namespace: headscale + chart: headscale/headscale + condition: headscale-router.enabled + values: + - ../values/headscale-router/values/values.yaml + - ../values/headscale-router/values/values-{{ .Environment.Name }}.yaml + postRenderer: ../bin/kustomizer + postRendererArgs: + - ../values/headscale-router/kustomize/{{ .Environment.Name }} + missingFileHandler: Info +- name: manifests + namespace: headscale-system + chart: manifests + condition: headscale-router.enabled + missingFileHandler: Info + values: + - ../values/env.yaml + - ../values/env-{{ requiredEnv "ARGOCD_ENV_CLUSTER_NAME" }}.yaml + - ../values/headscale-router/env.yaml.gotmpl + - ../values/headscale-router/env-{{ requiredEnv "ARGOCD_ENV_CLUSTER_NAME" }}.yaml.gotmpl + hooks: + - events: [ prepare, cleanup ] + showlogs: true + command: ../bin/helmify + args: + - '{{`{{ if eq .Event.Name "prepare" }}build{{ else }}clean{{ end }}`}}' + - '{{`{{ .Release.Chart }}`}}' + - '{{`{{ .Environment.Name }}`}}' + - ../values/headscale-router/manifests + - manifests diff --git a/values/headscale-router/env-oceanbox.yaml.gotmpl b/values/headscale-router/env-oceanbox.yaml.gotmpl new file mode 100644 index 00000000..e7ccd598 --- /dev/null +++ b/values/headscale-router/env-oceanbox.yaml.gotmpl @@ -0,0 +1,3 @@ +headscale-router: + enabled: true + diff --git a/values/headscale-router/env.yaml.gotmpl b/values/headscale-router/env.yaml.gotmpl new file mode 100644 index 00000000..93f0536a --- /dev/null +++ b/values/headscale-router/env.yaml.gotmpl @@ -0,0 +1,4 @@ +headscale-router: + enabled: false + autosync: false + diff --git a/values/headscale-router/manifests/headscale-router.yaml b/values/headscale-router/manifests/headscale-router.yaml new file mode 100644 index 00000000..9ad92e92 --- /dev/null +++ b/values/headscale-router/manifests/headscale-router.yaml @@ -0,0 +1,42 @@ +{{- if .Values.clusterConfig.argo.enabled }} +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: headscale-router + namespace: argocd + annotations: + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: headscale + server: 'https://kubernetes.default.svc' + sources: + - repoURL: {{ .Values.clusterConfig.manifests }} + targetRevision: HEAD + path: helmfile.d + plugin: + name: helmfile-cmp + env: + - name: CLUSTER_NAME + value: {{ .Values.clusterConfig.cluster }} + - name: HELMFILE_ENVIRONMENT + value: default + - name: HELMFILE_FILE_PATH + value: headscale-router.yaml.gotmpl + project: sys + syncPolicy: + managedNamespaceMetadata: + labels: + component: sys + syncOptions: + - CreateNamespace=true + - ApplyOutOfSyncOnly=true + # - ServerSideApply=true + {{- if .Values.headscale-router.autosync }} + automated: + prune: true + # selfHeal: false + {{- end }} +{{- end }} diff --git a/values/headscale-router/values/values.yaml b/values/headscale-router/values/values.yaml new file mode 100644 index 00000000..7087bcd7 --- /dev/null +++ b/values/headscale-router/values/values.yaml @@ -0,0 +1,171 @@ +image: + repository: ghcr.io/juanfont/headscale + pullPolicy: IfNotPresent + tag: v0.26.1 + +args: [ "serve" ] + +env: + HEADSCALE_DNS_BASE_DOMAIN: "relay.obx" + + # HACK: Workaround for fortigate block of WG udp port + HEADSCALE_RANDOMIZE_CLIENT_PORT: "true" + HEADSCALE_OIDC_ONLY_START_IF_OIDC_IS_AVAILABLE: "true" + HEADSCALE_OIDC_ISSUER: "https://login.microsoftonline.com/3f737008-e9a0-4485-9d27-40329d288089/v2.0" + HEADSCALE_OIDC_CLIENT_ID: "688e9096-f140-4498-a46a-e3d1939184de" + HEADSCALE_OIDC_CLIENT_SECRET: "dPW8Q~1rctY-D0Ih.A1-1KqLl0uj1rX_ixNTcbrh" + + # -- Split DNS for obx and ts.obx + HEADSCALE_DNS_NAMESERVERS_SPLIT: | + { + "obx": [ "10.255.241.210" ] + } + + # -- Node IPv4 prefixes + HEADSCALE_PREFIXES_V4: "100.64.0.0/10" + # -- Node IPv6 prefixes + HEADSCALE_PREFIXES_V6: "fd7a:115c:a1e0::/48" + + # -- Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/). + HEADSCALE_DNS_MAGIC_DNS: "true" + # -- List of DNS servers to expose to clients. + HEADSCALE_DNS_NAMESERVERS_GLOBAL: "1.1.1.1 1.0.0.1" + + HEADSCALE_DERP_URLS: "https://controlplane.tailscale.com/derpmap/default" + HEADSCALE_DERP_AUTO_UPDATE_ENABLED: "true" + HEADSCALE_DERP_UPDATE_FREQUENCY: "24h" + + HEADSCALE_EPHEMERAL_NODE_INACTIVITY_TIMEOUT: "30m" + +ingress: + main: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: letsencrypt-production + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: HTTP + hosts: + - host: headscale-router.adm.oceanbox.io + paths: + - path: / + tls: + - secretName: headscale-router-tls + hosts: + - headscale-router.adm.oceanbox.io + +persistence: + config: + enabled: true + mountPath: /etc/headscale + retain: true + # storageClass: "" + # accessMode: ReadWriteOnce + # size: 1Gi + +# -- Enable and configure postgresql database subchart under this key. +# @default -- See [values.yaml](./values.yaml) +postgresql: + enabled: false + auth: + database: headscale + postgresPassword: changeme + primary: + persistence: + enabled: false + # storageClass: "" + # size: 8Gi + +serviceMonitor: + main: + # -- Enables or disables the serviceMonitor. + enabled: true + # -- Configures the endpoints for the serviceMonitor. + # @default -- See [values.yaml](./values.yaml) + endpoints: + - port: metrics + scheme: http + path: /metrics + interval: 30s + scrapeTimeout: 10s + +configMaps: + acl: + enabled: true + data: + policy: | + { + "groups": { + "group:admin": [ + "jonas.juselius@oceanbox.io", + "Moritz.Jorg@oceanbox.io", + "simen.kirkvik@oceanbox.io", + "stig.r.jensen@oceanbox.io", + ], + }, + "tagOwners": { + "tag:k8s": [ "group:admin" ], + "tag:hpc": [ "group:admin" ], + "tag:tos-relay": [ "group:admin" ], + "tag:vtn-relay": [ "group:admin" ], + "tag:mumindalen": [ "group:admin" ], + "tag:ekman": [ "group:admin" ], + "tag:rossby": [ "group:admin" ], + }, + // hosts should be defined using its IP addresses and a subnet mask. + // to define a single host, use a /32 mask. You cannot use DNS entries here, + // as they're prone to be hijacked by replacing their IP addresses. + // see https://github.com/tailscale/tailscale/issues/3800 for more information. + "hosts": { + "ingress.ekman.tos": "10.255.241.99/32", + "ingress.ceph.tos": "10.255.241.10/32", + "ingress.ceph.vtn": "172.16.239.50/32", + "ingress.adm.ceph.vtn": "172.16.239.51/32", + "ingress.oceanbox.tos": "10.255.241.11/32", + "manage.ekman.tos": "10.255.241.99/32", + "k8s.oceanbox.tos": "10.255.241.200/32", + "k8s.ekman.tos": "10.255.241.99/32", + "k8s.ceph.tos": "10.255.241.29/32", + "printer.office.tos": "10.132.46.108/32", + "net.office.tos": "10.132.46.0/24", + "net.dc.tos": "10.255.241.0/24", + "net.100gbe.tos": "10.255.244.0/24", + "net.mgmt.tos": "10.255.240.0/24", + "net.dc.vtn": "172.16.239.0/24", + "net.mgmt.vtn": "172.16.238.0/24", + }, + "acls": [ + { + "action": "accept", + "src": [ "tag:tos-relay", "net.dc.tos" ], + "dst": [ + "tag:vtn-relay:*", + "net.dc.vtn:*", + ] + }, + { + "action": "accept", + "src": [ "tag:vtn-relay", "net.dc.vtn" ], + "dst": [ + "tag:tos-relay:*", + "net.dc.tos:*", + ] + }, + ] + } + dns: + enabled: true + data: + records: | + [ + { "name": "auth.oceanbox.io", "type": "A", "value": "10.255.241.11" }, + { "name": "auth.adm.oceanbox.io", "type": "A", "value": "10.255.241.11" }, + { "name": "keycloak.adm.oceanbox.io", "type": "A", "value": "10.255.241.11" }, + { "name": "grafana.adm.oceanbox.io", "type": "A", "value": "10.255.241.11" }, + { "name": "prometheus.adm.oceanbox.io", "type": "A", "value": "10.255.241.11" }, + { "name": "alertmanager.adm.oceanbox.io", "type": "A", "value": "10.255.241.11" }, + { "name": "argocd.adm.oceanbox.io", "type": "A", "value": "10.255.241.11" }, + { "name": "hubble.adm.oceanbox.io", "type": "A", "value": "10.255.241.11" }, + { "name": "plausible.adm.oceanbox.io", "type": "A", "value": "10.255.241.11" }, + { "name": "dapr.adm.oceanbox.io", "type": "A", "value": "10.255.241.11" }, + ] diff --git a/values/headscale/env-oceanbox.yaml.gotmpl b/values/headscale/env-oceanbox.yaml.gotmpl index 751125e4..2ed7f1e0 100644 --- a/values/headscale/env-oceanbox.yaml.gotmpl +++ b/values/headscale/env-oceanbox.yaml.gotmpl @@ -1,3 +1,2 @@ headscale: enabled: true -