From 0f2053009cbc099be834e1047aa0fbc1e4ef150a Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Fri, 6 Jun 2025 00:58:35 -0300 Subject: [PATCH 01/12] Add helm chart --- helm/Chart.yaml | 6 + helm/README.md | 100 ++++++++++++++ helm/templates/_helpers.tpl | 10 ++ helm/templates/configmap-headplane.yaml | 21 +++ helm/templates/configmap-headscale-acl.yaml | 11 ++ helm/templates/configmap-headscale.yaml | 8 ++ helm/templates/ingress.yaml | 46 +++++++ helm/templates/job.yaml | 40 ++++++ helm/templates/pvc.yaml | 18 +++ helm/templates/rolebindings.yaml | 37 ++++++ helm/templates/roles.yaml | 57 ++++++++ helm/templates/secret-headplane.yaml | 27 ++++ helm/templates/secret-headscale.yaml | 18 +++ helm/templates/service.yaml | 21 +++ helm/templates/serviceaccounts.yaml | 15 +++ helm/templates/statefulset-headplane.yaml | 79 +++++++++++ .../statefulset-tailscale-relay.yaml | 77 +++++++++++ helm/values.yaml | 125 ++++++++++++++++++ 18 files changed, 716 insertions(+) create mode 100644 helm/Chart.yaml create mode 100644 helm/README.md create mode 100644 helm/templates/_helpers.tpl create mode 100644 helm/templates/configmap-headplane.yaml create mode 100644 helm/templates/configmap-headscale-acl.yaml create mode 100644 helm/templates/configmap-headscale.yaml create mode 100644 helm/templates/ingress.yaml create mode 100644 helm/templates/job.yaml create mode 100644 helm/templates/pvc.yaml create mode 100644 helm/templates/rolebindings.yaml create mode 100644 helm/templates/roles.yaml create mode 100644 helm/templates/secret-headplane.yaml create mode 100644 helm/templates/secret-headscale.yaml create mode 100644 helm/templates/service.yaml create mode 100644 helm/templates/serviceaccounts.yaml create mode 100644 helm/templates/statefulset-headplane.yaml create mode 100644 helm/templates/statefulset-tailscale-relay.yaml create mode 100644 helm/values.yaml diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 00000000..640f63e3 --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: headplane +description: "The headplane Helm chart provides an easy way to deploy a Headscale UI with headplane, including an embedded Tailscale relay. This chart simplifies the setup of a private networking solution using Kubernetes." +type: application +version: 0.1.34 +appVersion: "0.6.0" diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 00000000..320bb094 --- /dev/null +++ b/helm/README.md @@ -0,0 +1,100 @@ +# Headplane Helm Chart + +## Overview +This Helm chart deploys [Headplane](https://github.com/tale/headplane) and [Headscale](https://github.com/juanfont/headscale) in a Kubernetes cluster. Headplane provides an interface to manage headscale instances, which is a Tailscale-compatible coordination server. + +## Installation + +### Prerequisites +- Kubernetes cluster +- Helm installed (`helm version`) + +### Install the Chart +```sh +helm install headplane oci://harbor.lag0.com.br/library/headplane +``` + +### Upgrade the Chart +```sh +helm upgrade headplane oci://harbor.lag0.com.br/library/headplane +``` + +### Uninstall the Chart +```sh +helm uninstall headplane +``` + +## Configuration +This chart supports customization through the `values.yaml` file. + +### Values + +#### `headplane` +| Key | Description | Default | +|------|------------|---------| +| `image` | Headplane container image | `ghcr.io/tale/headplane:0.6.0` | +| `config.server.host` | Server host | `0.0.0.0` | +| `config.server.port` | Server port | `3000` | +| `config.server.cookie_secure` | Use secure cookies | `true` | +| `config.headscale.url` | Headscale URL | `https://vpn.example.com` | +| `config.headscale.config_path` | Path to Headscale config | `/etc/headscale/config.yaml` | +| `config.headscale.config_strict` | Enable strict config mode | `true` | +| `config.integration.kubernetes.enabled` | Enable Kubernetes integration | `true` | +| `config.integration.kubernetes.validate_manifest` | Validate Kubernetes manifest | `true` | +| `config.integration.kubernetes.pod_name` | Headplane pod name | `headplane-0` | +| `config.oidc.issuer` | OIDC issuer URL | `https://your-oidc-issuer-url.com` | +| `config.oidc.disable_api_key_login` | Disable API key login | `true` | +| `config.oidc.token_endpoint_auth_method` | OIDC token auth method | `client_secret_post` | +| `config.oidc.redirect_uri` | OIDC redirect URI | `https://your-headplane-admin-domain.com/admin/oidc/callback` | +| `config.oidc.client_id` | OIDC Client ID | `REPLACE_IT_WITH_YOUR_OIDC_CLIENT_ID_FOR_HEADPLANE` | + +#### `headplane.secret` +| Key | Description | Default | +|------|------------|---------| +| `name` | Secret name | `headplane-secret` | +| `create` | Whether to create the secret | `true` | +| `server.cookie_secret` | Cookie secret (if not provided, wil be generated and added to secret) | `` | +| `oidc.client_secret` | OIDC client secret (optional) | `` | +| `oidc.headscale_api_key` | Headscale API key (optional) | `` | + +#### `headscale.acl` +| Key | Description | Default | +|------|------------|---------| +| `acl` | Access Control List configuration in JSON format. Only applicable if headscale.config.policy.mode is file | `see values.yaml` | + +Example ACL configuration: +```yaml +headscale: + acl: | + { + "acls": [ + { + "action": "accept", + "src": ["user1@example.com", "user2@example.com"], + "dst": ["host1:80", "host1:443"] + } + ] + } + config: + policy: + mode: file +``` + + +## Using the Chart +1. Modify `values.yaml` with your settings. +2. Install or upgrade the Helm chart. +3. Check logs with `kubectl logs -l app=headplane`. + +For further customization, refer to the Kubernetes documentation on ConfigMaps and Secrets. + +## License +Copyright © 2025 antoniolago + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: + +``` +http://www.apache.org/licenses/LICENSE-2.0 +``` + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100644 index 00000000..e7786cfc --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,10 @@ +{{/* +Generate a random cookie secret if none is provided +*/}} +{{- define "headplane.cookieSecret" -}} +{{- if and .Values.headplane.secret.server (hasKey .Values.headplane.secret.server "cookie_secret") .Values.headplane.secret.server.cookie_secret -}} +{{- .Values.headplane.secret.server.cookie_secret -}} +{{- else -}} +{{- randAlphaNum 32 -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/helm/templates/configmap-headplane.yaml b/helm/templates/configmap-headplane.yaml new file mode 100644 index 00000000..a758a4b0 --- /dev/null +++ b/helm/templates/configmap-headplane.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: headplane-config +data: + config.yaml: | + server: +{{- toYaml .Values.headplane.config.server | nindent 6 }} + cookie_secret: {{ include "headplane.cookieSecret" . | quote }} + headscale: +{{- toYaml .Values.headplane.config.headscale | nindent 6 }} + integration: +{{- toYaml .Values.headplane.config.integration | nindent 6 }} +# only add oidc if .Values.headplane.config.oidc is set +{{- if .Values.headplane.config.oidc }} + oidc: +{{- toYaml .Values.headplane.config.oidc | nindent 6 }} + client_secret: "secret" + headscale_api_key: "secret" +{{- end }} diff --git a/helm/templates/configmap-headscale-acl.yaml b/helm/templates/configmap-headscale-acl.yaml new file mode 100644 index 00000000..27b3debd --- /dev/null +++ b/helm/templates/configmap-headscale-acl.yaml @@ -0,0 +1,11 @@ +{{- if .Values.headscale.acl }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: headscale-acl + labels: + app: headplane +data: + acl.hujson: |- + {{- .Values.headscale.acl | nindent 4 }} +{{- end }} \ No newline at end of file diff --git a/helm/templates/configmap-headscale.yaml b/helm/templates/configmap-headscale.yaml new file mode 100644 index 00000000..ef217f47 --- /dev/null +++ b/helm/templates/configmap-headscale.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: headscale-default-config +data: + config.yaml: | +{{- toYaml .Values.headscale.config | nindent 4 }} diff --git a/helm/templates/ingress.yaml b/helm/templates/ingress.yaml new file mode 100644 index 00000000..9bf8a93c --- /dev/null +++ b/helm/templates/ingress.yaml @@ -0,0 +1,46 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: headplane + {{- if .Values.ingress.annotations }} + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .Values.ingress.labels }} + labels: + {{- range $key, $value := .Values.ingress.labels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className }} + rules: + - host: {{ .Values.ingress.headscaleDomain }} + http: + paths: + - backend: + service: + name: headplane + port: + number: 8080 + path: / + pathType: Prefix + - host: {{ .Values.ingress.headplaneDomain }} + http: + paths: + - backend: + service: + name: headplane + port: + number: 3000 + path: /admin + pathType: Prefix + tls: + - hosts: + - {{ .Values.ingress.headplaneDomain }} + - {{ .Values.ingress.headscaleDomain }} + secretName: {{ .Values.ingress.tlsSecretName }} +{{- end }} diff --git a/helm/templates/job.yaml b/helm/templates/job.yaml new file mode 100644 index 00000000..d34ea790 --- /dev/null +++ b/helm/templates/job.yaml @@ -0,0 +1,40 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: headscale-generate-token +spec: + template: + spec: + restartPolicy: Never + serviceAccountName: headscale-token + containers: + - name: headscale-generate-token + image: bitnami/kubectl:latest + command: + - /bin/sh + - -c + - | + set -e + + echo "Waiting for headscale container to be Ready..." + while [ -z "$CONTAINER_STATUS" ]; do + CONTAINER_STATUS=$(kubectl get pod headplane-0 -n {{ .Release.Namespace }} -o=jsonpath="{.status.containerStatuses[?(@.name==\"headscale\")].state.running}") + sleep 1 + done + + echo "Checking if Secret 'headscale-api-token' exists..." + if kubectl get secret headscale-api-token -n {{ .Release.Namespace }} >/dev/null 2>&1; then + echo "Secret already exists. Skipping token generation." + exit 0 + fi + + echo "Secret not found. Generating Headscale API token..." + TOKEN=$(kubectl -n {{ .Release.Namespace }} exec -i headplane-0 -c headscale -- headscale apikeys create -e 100y) + + if [ -z "$TOKEN" ]; then + echo "Failed to retrieve API token" + exit 1 + fi + + echo "Creating Kubernetes Secret..." + kubectl create secret generic headscale-api-token --from-literal=HEADPLANE_OIDC__HEADSCALE_API_KEY="$TOKEN" -n {{ .Release.Namespace }} diff --git a/helm/templates/pvc.yaml b/helm/templates/pvc.yaml new file mode 100644 index 00000000..70671f20 --- /dev/null +++ b/helm/templates/pvc.yaml @@ -0,0 +1,18 @@ +{{- if .Values.pvc.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Values.pvc.name | quote }} +spec: + accessModes: + {{- range .Values.pvc.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.pvc.storage | quote }} + {{- if .Values.pvc.storageClassName }} + storageClassName: {{ .Values.pvc.storageClassName | quote }} + {{- end }} +{{- end }} diff --git a/helm/templates/rolebindings.yaml b/helm/templates/rolebindings.yaml new file mode 100644 index 00000000..3287adb9 --- /dev/null +++ b/helm/templates/rolebindings.yaml @@ -0,0 +1,37 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: headplane-agent +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: headplane-agent +subjects: +- kind: ServiceAccount + name: headplane +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: tailscale-relay +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: tailscale-relay +subjects: +- kind: ServiceAccount + name: tailscale-relay +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: headscale-token + namespace: headplane +subjects: + - kind: ServiceAccount + name: headscale-token +roleRef: + kind: Role + name: headscale-token + apiGroup: rbac.authorization.k8s.io diff --git a/helm/templates/roles.yaml b/helm/templates/roles.yaml new file mode 100644 index 00000000..a2f69fe6 --- /dev/null +++ b/helm/templates/roles.yaml @@ -0,0 +1,57 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: headplane-agent +rules: +- apiGroups: [''] + resources: ['pods'] + verbs: ['get', 'list'] +- apiGroups: ['apps'] + resources: ['deployments'] + verbs: ['get', 'list'] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: tailscale-relay +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create +- apiGroups: + - "" + resourceNames: + - tailscale-auth + resources: + - secrets + verbs: + - get + - update + - patch +- apiGroups: + - "" + resources: + - events + verbs: + - get + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: headscale-token +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["pods/exec"] + verbs: ["create"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get"] diff --git a/helm/templates/secret-headplane.yaml b/helm/templates/secret-headplane.yaml new file mode 100644 index 00000000..be1acea0 --- /dev/null +++ b/helm/templates/secret-headplane.yaml @@ -0,0 +1,27 @@ +{{- if .Values.headplane.secret.create -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.headplane.secret.name }} + {{- with .Values.headplane.secret.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +type: Opaque +data: + #only add if oidc is set + {{- if .Values.headplane.config.oidc }} + {{- if .Values.headplane.secret.oidc.client_secret}} + HEADPLANE_OIDC__CLIENT_SECRET: {{ .Values.headplane.secret.oidc.client_secret | b64enc | quote }} + {{- end }} + {{- if .Values.headplane.secret.oidc.client_id }} + HEADPLANE_OIDC__CLIENT_ID: {{ .Values.headplane.secret.oidc.client_id | b64enc | quote }} + {{- end }} + {{- if .Values.headplane.secret.oidc.headscale_api_key }} + HEADPLANE_OIDC__HEADSCALE_API_KEY: {{ .Values.headplane.secret.oidc.headscale_api_key | b64enc | quote }} + {{- end }} + {{- end }} + {{- if not (lookup "v1" "Secret" .Release.Namespace .Values.headplane.secret.name) }} + HEADPLANE_SERVER__COOKIE_SECRET: {{ include "headplane.cookieSecret" . | b64enc | quote }} + {{- end }} +{{- end }} diff --git a/helm/templates/secret-headscale.yaml b/helm/templates/secret-headscale.yaml new file mode 100644 index 00000000..3ca33d83 --- /dev/null +++ b/helm/templates/secret-headscale.yaml @@ -0,0 +1,18 @@ +{{- if .Values.headscale.secret.create -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.headscale.secret.name }} + {{- with .Values.headscale.secret.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +type: Opaque +data: + #only add if oidc is set + {{- if .Values.headscale.config.oidc }} + {{- if .Values.headscale.secret.oidc.client_secret}} + HEADSCALE_OIDC_CLIENT_SECRET: {{ .Values.headscale.secret.oidc.client_secret | b64enc | quote}} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml new file mode 100644 index 00000000..0cebfa82 --- /dev/null +++ b/helm/templates/service.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: headplane + name: headplane + namespace: headplane +spec: + ports: + - name: headscale + port: 8080 + protocol: TCP + targetPort: 8080 + - name: headplane + port: 3000 + protocol: TCP + targetPort: 3000 + selector: + app: headplane + type: ClusterIP diff --git a/helm/templates/serviceaccounts.yaml b/helm/templates/serviceaccounts.yaml new file mode 100644 index 00000000..b2624151 --- /dev/null +++ b/helm/templates/serviceaccounts.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: headplane +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tailscale-relay +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: headscale-token diff --git a/helm/templates/statefulset-headplane.yaml b/helm/templates/statefulset-headplane.yaml new file mode 100644 index 00000000..29a0cc83 --- /dev/null +++ b/helm/templates/statefulset-headplane.yaml @@ -0,0 +1,79 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: headplane + labels: + app: headplane +spec: + selector: + matchLabels: + app: headplane + template: + metadata: + labels: + app: headplane + spec: + shareProcessNamespace: true + serviceAccountName: headplane + initContainers: + - name: copy-headscale-config + image: busybox:latest + command: + - sh + - -c + - | + cp /headscale-default-config/config.yaml /headscale-config/config.yaml + cp /headscale-acl/acl.hujson /headscale-config/acl.hujson + volumeMounts: + - name: headscale-config + mountPath: /headscale-config + - name: headscale-default-config + mountPath: /headscale-default-config + - name: headscale-acl + mountPath: /headscale-acl + containers: + - name: headplane + image: {{ .Values.headplane.image }} + envFrom: + - secretRef: + name: {{ .Values.headplane.secret.name }} + - secretRef: + name: headscale-api-token + env: + - name: HEADPLANE_LOAD_ENV_OVERRIDES + value: 'true' + - name: 'HEADPLANE_INTEGRATION__KUBERNETES__POD_NAME' + valueFrom: + fieldRef: + fieldPath: metadata.name + volumeMounts: + - name: headplane-config + mountPath: /etc/headplane + - name: headscale-config + mountPath: /etc/headscale + - name: headscale + image: {{ .Values.headscale.image }} + args: + - serve + volumeMounts: + - name: headscale-config + mountPath: /etc/headscale + - name: headplane-config + mountPath: /etc/headplane + envFrom: + - secretRef: + name: {{ .Values.headscale.secret.name }} + volumes: + - name: headscale-default-config + configMap: + name: headscale-default-config + - name: headscale-config + persistentVolumeClaim: + claimName: headscale-config + - name: headplane-config + configMap: + name: headplane-config + - name: headscale-acl + configMap: + name: headscale-acl diff --git a/helm/templates/statefulset-tailscale-relay.yaml b/helm/templates/statefulset-tailscale-relay.yaml new file mode 100644 index 00000000..87aaf106 --- /dev/null +++ b/helm/templates/statefulset-tailscale-relay.yaml @@ -0,0 +1,77 @@ +{{- if .Values.relay.enabled -}} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: tailscale-relay + labels: + app: tailscale +spec: + replicas: 1 + selector: + matchLabels: + app: tailscale + template: + metadata: + labels: + app: tailscale + spec: + serviceAccountName: "tailscale-relay" + containers: + - name: tailscale + image: {{ .Values.relay.image }} + imagePullPolicy: Always + env: + {{- $extraArgs := list }} + {{- if .Values.relay.config.login_server }} + {{- $extraArgs = append $extraArgs (printf "--login-server=%s" .Values.relay.config.login_server) }} + {{- end }} + {{- if .Values.relay.config.advertise_exit_node }} + {{- $extraArgs = append $extraArgs "--advertise-exit-node" }} + {{- end }} + {{- if gt (len $extraArgs) 0 }} + - name: TS_EXTRA_ARGS + value: "{{ join " " $extraArgs }}" + {{- end }} + {{- if .Values.relay.config.exit_node }} + - name: TS_EXIT_NODE + value: "{{ .Values.relay.config.exit_node }}" + {{- end }} + {{- if .Values.relay.config.hostname }} + - name: TS_HOSTNAME + value: "{{ .Values.relay.config.hostname }}" + {{- end }} + {{- if .Values.relay.config.routes }} + - name: TS_ROUTES + value: "{{ .Values.relay.config.routes }}" + {{- end }} + {{- if .Values.relay.config.firewall_debug }} + - name: TS_DEBUG_FIREWALL_MODE + value: "{{ .Values.relay.config.firewall_debug }}" + {{- end }} +# Store the state in a k8s secret + - name: TS_KUBE_SECRET + value: "tailscale-auth" + - name: TS_USERSPACE + value: "true" + - name: TS_AUTHKEY + valueFrom: + secretKeyRef: + name: tailscale-auth + key: TS_AUTHKEY + optional: true + optional: true + + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + securityContext: + capabilities: + add: + - NET_ADMIN +{{- end }} diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 00000000..eefec424 --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,125 @@ +headplane: + image: ghcr.io/tale/headplane:0.6.0 + config: + server: + host: "0.0.0.0" + port: 3000 + cookie_secure: true + headscale: + url: "https://vpn.example.com" + config_path: "/etc/headscale/config.yaml" + config_strict: "true" + integration: + kubernetes: + enabled: true + validate_manifest: true + pod_name: "headplane-0" + # oidc: + # issuer: "https://your-oidc-issuer-url.com" + # disable_api_key_login: "true" + # token_endpoint_auth_method: "client_secret_post" + # redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" + # client_id: "REPLACE_IT_WITH_YOUR_OIDC_CLIENT_ID_FOR_HEADPLANE" + secret: + name: headplane-secret + create: true + # server: + # cookie_secret: "yjbqijkvfrgrtwwkoanqquykeyuyffywd" #if not set, it will be generated + # oidc: + # client_secret: "REPLACE_IT_WITH_YOUR_OIDC_CLIENT_SECRET_FOR_HEADPLANE" + # headscale_api_key: "REPLACE_IT_WITH_YOUR_HEADSCALE_API_KEY" +headscale: + image: headscale/headscale:0.25.1 + + # ACL configuration + # only applicable if headscale.config.policy.mode is 'file' + acl: | + { + "acls": [] + } + + config: + server_url: https://vpn.example.com + listen_addr: 0.0.0.0:8080 + metrics_listen_addr: 0.0.0.0:9090 + grpc_listen_addr: 0.0.0.0:50443 + grpc_allow_insecure: false + policy: + mode: database # 'file' or 'database' + path: "/etc/headscale/acl.hujson" + prefixes: + v4: 100.64.0.0/10 + v6: fd7a:115c:a1e0::/48 + allocation: sequential + database: + type: sqlite + debug: false + sqlite: + path: /etc/headscale/db.sqlite + noise: + private_key_path: /etc/headscale/noise_private.key + derp: + server: + enabled: false + region_id: 999 + region_code: "headscale" + region_name: "Headscale Embedded DERP" + stun_listen_addr: "0.0.0.0:3478" + private_key_path: /var/lib/headscale/derp_server_private.key + automatically_add_embedded_derp_region: true + ipv4: 1.2.3.4 + ipv6: 2001:db8::1 + urls: + - https://controlplane.tailscale.com/derpmap/default + paths: [] + dns: + magic_dns: true + base_domain: headscale.vpn + nameservers: + global: + - 1.1.1.1 + - 8.8.8.8 + # oidc: + # issuer: "https://your-oidc-issuer.com" + # client_id: "YOUR_OIDC_CLIENT_ID_FOR_HEADSCALE" +# allowed_groups: +# - vpn_access +# allowed_domains: +# - example.com +# allowed_users: +# - alice@example.com + + secret: + name: headscale-secret + create: true + # oidc: + # client_secret: "REPLACE_IT_WITH_YOUR_OIDC_CLIENT_SECRET" + +# NOT WORKING YET +# relay: +# enabled: true +# image: ghcr.io/tailscale/tailscale:v1.80.3 +# config: +# hostname: "example.com" +# exit_node: "example.com" +# login_server: "https://vpn.example.com" +# advertise_exit_node: "true" +# firewall_debug: "false" +# routes: "10.0.0.0/8" +pvc: + enabled: true + name: headscale-config + accessModes: + - ReadWriteOnce + storage: 1Gi +# storageClassName: default + +ingress: + enabled: false + className: nginx + annotations: + cert-manager.io/cluster-issuer: "cloudflare" + labels: [] + headplaneDomain: "headplane.example.com" + headscaleDomain: "vpn.example.com" + tlsSecretName: "headplane-tls" From 028886f482149da579c3e28c24aad76680319bbe Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Fri, 6 Jun 2025 22:26:21 -0300 Subject: [PATCH 02/12] Remove default annotation for ingress that wasnt supposed to be there --- helm/values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/values.yaml b/helm/values.yaml index eefec424..050654a5 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -117,8 +117,8 @@ pvc: ingress: enabled: false className: nginx - annotations: - cert-manager.io/cluster-issuer: "cloudflare" + # annotations: + # cert-manager.io/cluster-issuer: "cloudflare" labels: [] headplaneDomain: "headplane.example.com" headscaleDomain: "vpn.example.com" From 327d79883411de97d2cbba2935f2e8be79e1bfda Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Sat, 7 Jun 2025 01:17:18 -0300 Subject: [PATCH 03/12] Fix static namespace + Fix relay configs + secrets overhaul + helm-docs --- helm/Chart.yaml | 2 +- helm/README.md | 208 +++++++++++++----- helm/README.md.gotmpl | 114 ++++++++++ helm/templates/configmap-headplane.yaml | 10 +- helm/templates/configmap-headscale.yaml | 17 ++ helm/templates/job.yaml | 1 + helm/templates/pvc-tailscale-relay.yaml | 19 ++ helm/templates/rolebindings.yaml | 4 +- helm/templates/roles.yaml | 3 + helm/templates/secret-headplane.yaml | 27 --- helm/templates/secret-headscale.yaml | 18 -- helm/templates/service.yaml | 2 +- helm/templates/serviceaccounts.yaml | 3 + helm/templates/statefulset-headplane.yaml | 16 +- .../statefulset-tailscale-relay.yaml | 19 +- helm/values.yaml | 190 +++++++++++----- 16 files changed, 481 insertions(+), 172 deletions(-) create mode 100644 helm/README.md.gotmpl create mode 100644 helm/templates/pvc-tailscale-relay.yaml delete mode 100644 helm/templates/secret-headplane.yaml delete mode 100644 helm/templates/secret-headscale.yaml diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 640f63e3..c4ea3682 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: headplane description: "The headplane Helm chart provides an easy way to deploy a Headscale UI with headplane, including an embedded Tailscale relay. This chart simplifies the setup of a private networking solution using Kubernetes." type: application -version: 0.1.34 +version: 0.1.39 appVersion: "0.6.0" diff --git a/helm/README.md b/helm/README.md index 320bb094..421d762a 100644 --- a/helm/README.md +++ b/helm/README.md @@ -1,17 +1,37 @@ -# Headplane Helm Chart +# headplane -## Overview -This Helm chart deploys [Headplane](https://github.com/tale/headplane) and [Headscale](https://github.com/juanfont/headscale) in a Kubernetes cluster. Headplane provides an interface to manage headscale instances, which is a Tailscale-compatible coordination server. +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.6.0](https://img.shields.io/badge/AppVersion-0.6.0-informational?style=flat-square) + +This helm chart deploys [Headplane](https://github.com/tale/headplane) + [Headscale](https://github.com/juanfont/headscale) and an embedded Tailscale relay in a kubernetes cluster. ## Installation ### Prerequisites - Kubernetes cluster - Helm installed (`helm version`) +- (Optional) OCI registry access for pulling images + +### Add the Helm repository +```sh +helm repo add headplane https://harbor.lag0.com.br/library +helm repo update +``` ### Install the Chart ```sh +# Install with default values helm install headplane oci://harbor.lag0.com.br/library/headplane + +# Install with custom values +helm install headplane oci://harbor.lag0.com.br/library/headplane -f values.yaml + +# Install with specific values +helm install headplane oci://harbor.lag0.com.br/library/headplane \ + --set headplane.config.server.port=3000 \ + --set ingress.enabled=true + +# Install in a specific namespace +helm install headplane oci://harbor.lag0.com.br/library/headplane -n your-namespace ``` ### Upgrade the Chart @@ -24,69 +44,137 @@ helm upgrade headplane oci://harbor.lag0.com.br/library/headplane helm uninstall headplane ``` -## Configuration -This chart supports customization through the `values.yaml` file. - -### Values - -#### `headplane` -| Key | Description | Default | -|------|------------|---------| -| `image` | Headplane container image | `ghcr.io/tale/headplane:0.6.0` | -| `config.server.host` | Server host | `0.0.0.0` | -| `config.server.port` | Server port | `3000` | -| `config.server.cookie_secure` | Use secure cookies | `true` | -| `config.headscale.url` | Headscale URL | `https://vpn.example.com` | -| `config.headscale.config_path` | Path to Headscale config | `/etc/headscale/config.yaml` | -| `config.headscale.config_strict` | Enable strict config mode | `true` | -| `config.integration.kubernetes.enabled` | Enable Kubernetes integration | `true` | -| `config.integration.kubernetes.validate_manifest` | Validate Kubernetes manifest | `true` | -| `config.integration.kubernetes.pod_name` | Headplane pod name | `headplane-0` | -| `config.oidc.issuer` | OIDC issuer URL | `https://your-oidc-issuer-url.com` | -| `config.oidc.disable_api_key_login` | Disable API key login | `true` | -| `config.oidc.token_endpoint_auth_method` | OIDC token auth method | `client_secret_post` | -| `config.oidc.redirect_uri` | OIDC redirect URI | `https://your-headplane-admin-domain.com/admin/oidc/callback` | -| `config.oidc.client_id` | OIDC Client ID | `REPLACE_IT_WITH_YOUR_OIDC_CLIENT_ID_FOR_HEADPLANE` | - -#### `headplane.secret` -| Key | Description | Default | -|------|------------|---------| -| `name` | Secret name | `headplane-secret` | -| `create` | Whether to create the secret | `true` | -| `server.cookie_secret` | Cookie secret (if not provided, wil be generated and added to secret) | `` | -| `oidc.client_secret` | OIDC client secret (optional) | `` | -| `oidc.headscale_api_key` | Headscale API key (optional) | `` | - -#### `headscale.acl` -| Key | Description | Default | -|------|------------|---------| -| `acl` | Access Control List configuration in JSON format. Only applicable if headscale.config.policy.mode is file | `see values.yaml` | - -Example ACL configuration: +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| headplane.config.headscale.config_path | string | `"/etc/headscale/config.yaml"` | | +| headplane.config.headscale.config_strict | string | `"true"` | | +| headplane.config.headscale.url | string | `"https://vpn.example.com"` | | +| headplane.config.integration.kubernetes.enabled | bool | `true` | | +| headplane.config.integration.kubernetes.pod_name | string | `"headplane-0"` | | +| headplane.config.integration.kubernetes.validate_manifest | bool | `true` | | +| headplane.config.oidc.client_id | string | `"REPLACE_IT_WITH_YOUR_OIDC_CLIENT_ID_FOR_HEADPLANE"` | | +| headplane.config.oidc.disable_api_key_login | bool | `true` | | +| headplane.config.oidc.enabled | bool | `false` | | +| headplane.config.oidc.issuer | string | `"https://your-oidc-issuer-url.com"` | | +| headplane.config.oidc.redirect_uri | string | `"https://your-headplane-admin-domain.com/admin/oidc/callback"` | | +| headplane.config.oidc.secret_name | string | `"oidc-secrets"` | | +| headplane.config.oidc.token_endpoint_auth_method | string | `"client_secret_post"` | | +| headplane.config.server.cookie_secure | bool | `true` | | +| headplane.config.server.host | string | `"0.0.0.0"` | | +| headplane.config.server.port | int | `3000` | | +| headplane.envFrom | list | `[]` | | +| headplane.image | string | `"ghcr.io/tale/headplane:0.6.0"` | | +| headscale.acl | string | `"{\n \"acls\": []\n}\n"` | | +| headscale.config.database.debug | bool | `false` | | +| headscale.config.database.sqlite.path | string | `"/etc/headscale/db.sqlite"` | | +| headscale.config.database.type | string | `"sqlite"` | | +| headscale.config.derp.paths | list | `[]` | | +| headscale.config.derp.server.automatically_add_embedded_derp_region | bool | `true` | | +| headscale.config.derp.server.enabled | bool | `false` | | +| headscale.config.derp.server.ipv4 | string | `"1.2.3.4"` | | +| headscale.config.derp.server.ipv6 | string | `"2001:db8::1"` | | +| headscale.config.derp.server.private_key_path | string | `"/var/lib/headscale/derp_server_private.key"` | | +| headscale.config.derp.server.region_code | string | `"headscale"` | | +| headscale.config.derp.server.region_id | int | `999` | | +| headscale.config.derp.server.region_name | string | `"Headscale Embedded DERP"` | | +| headscale.config.derp.server.stun_listen_addr | string | `"0.0.0.0:3478"` | | +| headscale.config.derp.urls[0] | string | `"https://controlplane.tailscale.com/derpmap/default"` | | +| headscale.config.dns.base_domain | string | `"headscale.vpn"` | | +| headscale.config.dns.magic_dns | bool | `true` | | +| headscale.config.dns.nameservers.global[0] | string | `"1.1.1.1"` | | +| headscale.config.dns.nameservers.global[1] | string | `"8.8.8.8"` | | +| headscale.config.grpc_allow_insecure | bool | `false` | | +| headscale.config.grpc_listen_addr | string | `"0.0.0.0:50443"` | | +| headscale.config.listen_addr | string | `"0.0.0.0:8080"` | | +| headscale.config.metrics_listen_addr | string | `"0.0.0.0:9090"` | | +| headscale.config.noise.private_key_path | string | `"/etc/headscale/noise_private.key"` | | +| headscale.config.oidc.client_id | string | `"YOUR_OIDC_CLIENT_ID_FOR_HEADSCALE"` | | +| headscale.config.oidc.enabled | bool | `false` | | +| headscale.config.oidc.issuer | string | `"https://your-oidc-issuer.com"` | | +| headscale.config.oidc.secret_name | string | `"oidc-secrets"` | | +| headscale.config.policy.mode | string | `"database"` | | +| headscale.config.policy.path | string | `"/etc/headscale/acl.hujson"` | | +| headscale.config.prefixes.allocation | string | `"sequential"` | | +| headscale.config.prefixes.v4 | string | `"100.64.0.0/10"` | | +| headscale.config.prefixes.v6 | string | `"fd7a:115c:a1e0::/48"` | | +| headscale.config.server_url | string | `"https://vpn.example.com"` | | +| headscale.envFrom | list | `[]` | | +| headscale.image | string | `"headscale/headscale:0.25.1"` | | +| ingress.className | string | `"nginx"` | | +| ingress.enabled | bool | `false` | | +| ingress.headplaneDomain | string | `"headplane.example.com"` | | +| ingress.headscaleDomain | string | `"vpn.example.com"` | | +| ingress.labels | list | `[]` | | +| ingress.tlsSecretName | string | `"headplane-tls"` | | +| pvc.accessModes[0] | string | `"ReadWriteOnce"` | | +| pvc.enabled | bool | `true` | | +| pvc.name | string | `"headscale-config"` | | +| pvc.storage | string | `"1Gi"` | | +| relay.config.advertise_exit_node | string | `"true"` | | +| relay.config.authKey | string | `""` | | +| relay.config.exit_node | string | `"example.com"` | | +| relay.config.firewall_debug | string | `"false"` | | +| relay.config.hostname | string | `"example.com"` | | +| relay.config.login_server | string | `"https://vpn.example.com"` | | +| relay.config.routes | string | `"10.0.0.0/8"` | | +| relay.enabled | bool | `false` | | +| relay.image | string | `"ghcr.io/tailscale/tailscale:v1.80.3"` | | +| relay.pvc.accessModes[0] | string | `"ReadWriteOnce"` | | +| relay.pvc.enabled | bool | `false` | | +| relay.pvc.name | string | `"tailscale-relay-data"` | | +| relay.pvc.storage | string | `"1Gi"` | | + +### OIDC Configuration + +To use OIDC, you must provide the OIDC client secrets via a single Kubernetes secret: + +```sh +kubectl create secret generic oidc-secrets \ + --from-literal=HEADPLANE_OIDC__CLIENT_SECRET=your-headplane-oidc-client-secret \ + --from-literal=HEADPLANE_OIDC__CLIENT_ID=your-headplane-oidc-client-id \ + --from-literal=HEADSCALE_OIDC__CLIENT_SECRET=your-headscale-oidc-client-secret \ + --from-literal=HEADSCALE_OIDC__CLIENT_ID=your-headscale-oidc-client-id \ + -n +``` + +Then enable OIDC in your `values.yaml`: ```yaml +headplane: + config: + oidc: + enabled: true + issuer: "https://your-oidc-issuer-url.com" + redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" + secret_name: "oidc-secrets" # Name of your OIDC secret + headscale: - acl: | - { - "acls": [ - { - "action": "accept", - "src": ["user1@example.com", "user2@example.com"], - "dst": ["host1:80", "host1:443"] - } - ] - } config: - policy: - mode: file + oidc: + enabled: true + issuer: "https://your-oidc-issuer.com" + secret_name: "oidc-secrets" # Same secret as Headplane ``` +You can add any additional environment variables by creating more secrets or config-maps and adding them to the `envFrom` section. For example, to add custom configuration: -## Using the Chart -1. Modify `values.yaml` with your settings. -2. Install or upgrade the Helm chart. -3. Check logs with `kubectl logs -l app=headplane`. +```sh +kubectl create secret generic headplane-custom-config \ + --from-literal=CUSTOM_VAR=value \ + --from-literal=ANOTHER_VAR=another-value \ + -n +``` + +Then add it to your values: +```yaml +headplane: + envFrom: + - secretRef: + name: headplane-custom-config +``` -For further customization, refer to the Kubernetes documentation on ConfigMaps and Secrets. +Note: Make sure to keep your secrets secure and never commit them to version control. Consider using a secrets management solution in production. ## License Copyright © 2025 antoniolago @@ -97,4 +185,4 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use http://www.apache.org/licenses/LICENSE-2.0 ``` -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/helm/README.md.gotmpl b/helm/README.md.gotmpl new file mode 100644 index 00000000..205799ff --- /dev/null +++ b/helm/README.md.gotmpl @@ -0,0 +1,114 @@ +# headplane + +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.6.0](https://img.shields.io/badge/AppVersion-0.6.0-informational?style=flat-square) + +This helm chart deploys [Headplane](https://github.com/tale/headplane) + [Headscale](https://github.com/juanfont/headscale) and an embedded Tailscale relay in a kubernetes cluster. + +## Installation + +### Prerequisites +- Kubernetes cluster +- Helm installed (`helm version`) +- (Optional) OCI registry access for pulling images + +### Add the Helm repository +```sh +helm repo add headplane https://harbor.lag0.com.br/library +helm repo update +``` + +### Install the Chart +```sh +# Install with default values +helm install headplane oci://harbor.lag0.com.br/library/headplane + +# Install with custom values +helm install headplane oci://harbor.lag0.com.br/library/headplane -f values.yaml + +# Install with specific values +helm install headplane oci://harbor.lag0.com.br/library/headplane \ + --set headplane.config.server.port=3000 \ + --set ingress.enabled=true + +# Install in a specific namespace +helm install headplane oci://harbor.lag0.com.br/library/headplane -n your-namespace +``` + +### Upgrade the Chart +```sh +helm upgrade headplane oci://harbor.lag0.com.br/library/headplane +``` + +### Uninstall the Chart +```sh +helm uninstall headplane +``` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +{{- range .Values }} +| {{ .Key }} | {{ .Type }} | {{ .Default }} | {{ .Description }} | +{{- end }} + +### OIDC Configuration + +To use OIDC, you must provide the OIDC client secrets via a single Kubernetes secret: + +```sh +kubectl create secret generic oidc-secrets \ + --from-literal=HEADPLANE_OIDC__CLIENT_SECRET=your-headplane-oidc-client-secret \ + --from-literal=HEADPLANE_OIDC__CLIENT_ID=your-headplane-oidc-client-id \ + --from-literal=HEADSCALE_OIDC__CLIENT_SECRET=your-headscale-oidc-client-secret \ + --from-literal=HEADSCALE_OIDC__CLIENT_ID=your-headscale-oidc-client-id \ + -n +``` + +Then enable OIDC in your `values.yaml`: +```yaml +headplane: + config: + oidc: + enabled: true + issuer: "https://your-oidc-issuer-url.com" + redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" + secret_name: "oidc-secrets" # Name of your OIDC secret + +headscale: + config: + oidc: + enabled: true + issuer: "https://your-oidc-issuer.com" + secret_name: "oidc-secrets" # Same secret as Headplane +``` + +You can add any additional environment variables by creating more secrets or config-maps and adding them to the `envFrom` section. For example, to add custom configuration: + +```sh +kubectl create secret generic headplane-custom-config \ + --from-literal=CUSTOM_VAR=value \ + --from-literal=ANOTHER_VAR=another-value \ + -n +``` + +Then add it to your values: +```yaml +headplane: + envFrom: + - secretRef: + name: headplane-custom-config +``` + +Note: Make sure to keep your secrets secure and never commit them to version control. Consider using a secrets management solution in production. + +## License +Copyright © 2025 antoniolago + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: + +``` +http://www.apache.org/licenses/LICENSE-2.0 +``` + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/helm/templates/configmap-headplane.yaml b/helm/templates/configmap-headplane.yaml index a758a4b0..9a539ab0 100644 --- a/helm/templates/configmap-headplane.yaml +++ b/helm/templates/configmap-headplane.yaml @@ -13,9 +13,11 @@ data: integration: {{- toYaml .Values.headplane.config.integration | nindent 6 }} # only add oidc if .Values.headplane.config.oidc is set -{{- if .Values.headplane.config.oidc }} +{{- if .Values.headplane.config.oidc.enabled }} oidc: -{{- toYaml .Values.headplane.config.oidc | nindent 6 }} - client_secret: "secret" - headscale_api_key: "secret" + issuer: {{ .Values.headplane.config.oidc.issuer | quote }} + disable_api_key_login: {{ .Values.headplane.config.oidc.disable_api_key_login | quote }} + token_endpoint_auth_method: {{ .Values.headplane.config.oidc.token_endpoint_auth_method | quote }} + redirect_uri: {{ .Values.headplane.config.oidc.redirect_uri | quote }} + client_id: {{ .Values.headplane.config.oidc.client_id | quote }} {{- end }} diff --git a/helm/templates/configmap-headscale.yaml b/helm/templates/configmap-headscale.yaml index ef217f47..daf0acb3 100644 --- a/helm/templates/configmap-headscale.yaml +++ b/helm/templates/configmap-headscale.yaml @@ -6,3 +6,20 @@ metadata: data: config.yaml: | {{- toYaml .Values.headscale.config | nindent 4 }} +{{- if .Values.headscale.config.oidc.enabled }} + oidc: + issuer: {{ .Values.headscale.config.oidc.issuer | quote }} + client_id: {{ .Values.headscale.config.oidc.client_id | quote }} + {{- if .Values.headscale.config.oidc.allowed_groups }} + allowed_groups: + {{- toYaml .Values.headscale.config.oidc.allowed_groups | nindent 8 }} + {{- end }} + {{- if .Values.headscale.config.oidc.allowed_domains }} + allowed_domains: + {{- toYaml .Values.headscale.config.oidc.allowed_domains | nindent 8 }} + {{- end }} + {{- if .Values.headscale.config.oidc.allowed_users }} + allowed_users: + {{- toYaml .Values.headscale.config.oidc.allowed_users | nindent 8 }} + {{- end }} +{{- end }} diff --git a/helm/templates/job.yaml b/helm/templates/job.yaml index d34ea790..27300b54 100644 --- a/helm/templates/job.yaml +++ b/helm/templates/job.yaml @@ -2,6 +2,7 @@ apiVersion: batch/v1 kind: Job metadata: name: headscale-generate-token + namespace: {{ .Release.Namespace }} spec: template: spec: diff --git a/helm/templates/pvc-tailscale-relay.yaml b/helm/templates/pvc-tailscale-relay.yaml new file mode 100644 index 00000000..d1c88756 --- /dev/null +++ b/helm/templates/pvc-tailscale-relay.yaml @@ -0,0 +1,19 @@ +{{- if and .Values.relay.enabled .Values.relay.pvc.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Values.relay.pvc.name }} + namespace: {{ .Release.Namespace }} +spec: + accessModes: + {{- range .Values.relay.pvc.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.relay.pvc.storage | quote }} + {{- if .Values.relay.pvc.storageClassName }} + storageClassName: {{ .Values.relay.pvc.storageClassName | quote }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/templates/rolebindings.yaml b/helm/templates/rolebindings.yaml index 3287adb9..0cfc043b 100644 --- a/helm/templates/rolebindings.yaml +++ b/helm/templates/rolebindings.yaml @@ -3,6 +3,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: headplane-agent + namespace: {{ .Release.Namespace }} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -15,6 +16,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: tailscale-relay + namespace: {{ .Release.Namespace }} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -27,7 +29,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: headscale-token - namespace: headplane + namespace: {{ .Release.Namespace }} subjects: - kind: ServiceAccount name: headscale-token diff --git a/helm/templates/roles.yaml b/helm/templates/roles.yaml index a2f69fe6..39b2d72b 100644 --- a/helm/templates/roles.yaml +++ b/helm/templates/roles.yaml @@ -3,6 +3,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: headplane-agent + namespace: {{ .Release.Namespace }} rules: - apiGroups: [''] resources: ['pods'] @@ -15,6 +16,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: tailscale-relay + namespace: {{ .Release.Namespace }} rules: - apiGroups: - "" @@ -45,6 +47,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: headscale-token + namespace: {{ .Release.Namespace }} rules: - apiGroups: [""] resources: ["pods"] diff --git a/helm/templates/secret-headplane.yaml b/helm/templates/secret-headplane.yaml deleted file mode 100644 index be1acea0..00000000 --- a/helm/templates/secret-headplane.yaml +++ /dev/null @@ -1,27 +0,0 @@ -{{- if .Values.headplane.secret.create -}} -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Values.headplane.secret.name }} - {{- with .Values.headplane.secret.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -type: Opaque -data: - #only add if oidc is set - {{- if .Values.headplane.config.oidc }} - {{- if .Values.headplane.secret.oidc.client_secret}} - HEADPLANE_OIDC__CLIENT_SECRET: {{ .Values.headplane.secret.oidc.client_secret | b64enc | quote }} - {{- end }} - {{- if .Values.headplane.secret.oidc.client_id }} - HEADPLANE_OIDC__CLIENT_ID: {{ .Values.headplane.secret.oidc.client_id | b64enc | quote }} - {{- end }} - {{- if .Values.headplane.secret.oidc.headscale_api_key }} - HEADPLANE_OIDC__HEADSCALE_API_KEY: {{ .Values.headplane.secret.oidc.headscale_api_key | b64enc | quote }} - {{- end }} - {{- end }} - {{- if not (lookup "v1" "Secret" .Release.Namespace .Values.headplane.secret.name) }} - HEADPLANE_SERVER__COOKIE_SECRET: {{ include "headplane.cookieSecret" . | b64enc | quote }} - {{- end }} -{{- end }} diff --git a/helm/templates/secret-headscale.yaml b/helm/templates/secret-headscale.yaml deleted file mode 100644 index 3ca33d83..00000000 --- a/helm/templates/secret-headscale.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if .Values.headscale.secret.create -}} -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Values.headscale.secret.name }} - {{- with .Values.headscale.secret.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -type: Opaque -data: - #only add if oidc is set - {{- if .Values.headscale.config.oidc }} - {{- if .Values.headscale.secret.oidc.client_secret}} - HEADSCALE_OIDC_CLIENT_SECRET: {{ .Values.headscale.secret.oidc.client_secret | b64enc | quote}} - {{- end }} -{{- end }} -{{- end }} diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml index 0cebfa82..67e88d37 100644 --- a/helm/templates/service.yaml +++ b/helm/templates/service.yaml @@ -5,7 +5,7 @@ metadata: labels: app: headplane name: headplane - namespace: headplane + namespace: {{ .Release.Namespace }} spec: ports: - name: headscale diff --git a/helm/templates/serviceaccounts.yaml b/helm/templates/serviceaccounts.yaml index b2624151..5c4f1e4d 100644 --- a/helm/templates/serviceaccounts.yaml +++ b/helm/templates/serviceaccounts.yaml @@ -3,13 +3,16 @@ apiVersion: v1 kind: ServiceAccount metadata: name: headplane + namespace: {{ .Release.Namespace }} --- apiVersion: v1 kind: ServiceAccount metadata: name: tailscale-relay + namespace: {{ .Release.Namespace }} --- apiVersion: v1 kind: ServiceAccount metadata: name: headscale-token + namespace: {{ .Release.Namespace }} diff --git a/helm/templates/statefulset-headplane.yaml b/helm/templates/statefulset-headplane.yaml index 29a0cc83..c430f8ac 100644 --- a/helm/templates/statefulset-headplane.yaml +++ b/helm/templates/statefulset-headplane.yaml @@ -36,10 +36,13 @@ spec: - name: headplane image: {{ .Values.headplane.image }} envFrom: + {{- if .Values.headplane.config.oidc.enabled }} - secretRef: - name: {{ .Values.headplane.secret.name }} - - secretRef: - name: headscale-api-token + name: {{ .Values.headplane.config.oidc.secret_name }} + {{- end }} + {{- with .Values.headplane.envFrom }} + {{- toYaml . | nindent 10 }} + {{- end }} env: - name: HEADPLANE_LOAD_ENV_OVERRIDES value: 'true' @@ -62,8 +65,13 @@ spec: - name: headplane-config mountPath: /etc/headplane envFrom: + {{- if .Values.headscale.config.oidc.enabled }} - secretRef: - name: {{ .Values.headscale.secret.name }} + name: {{ .Values.headscale.config.oidc.secret_name }} + {{- end }} + {{- with .Values.headscale.envFrom }} + {{- toYaml . | nindent 10 }} + {{- end }} volumes: - name: headscale-default-config configMap: diff --git a/helm/templates/statefulset-tailscale-relay.yaml b/helm/templates/statefulset-tailscale-relay.yaml index 87aaf106..6d41e461 100644 --- a/helm/templates/statefulset-tailscale-relay.yaml +++ b/helm/templates/statefulset-tailscale-relay.yaml @@ -49,19 +49,21 @@ spec: - name: TS_DEBUG_FIREWALL_MODE value: "{{ .Values.relay.config.firewall_debug }}" {{- end }} -# Store the state in a k8s secret - name: TS_KUBE_SECRET value: "tailscale-auth" - name: TS_USERSPACE value: "true" + {{- if .Values.relay.config.authKey }} + - name: TS_AUTHKEY + value: "{{ .Values.relay.config.authKey }}" + {{- else }} - name: TS_AUTHKEY valueFrom: secretKeyRef: name: tailscale-auth key: TS_AUTHKEY optional: true - optional: true - + {{- end }} - name: POD_NAME valueFrom: fieldRef: @@ -70,8 +72,19 @@ spec: valueFrom: fieldRef: fieldPath: metadata.uid + {{- if and .Values.relay.enabled .Values.relay.pvc.enabled }} + volumeMounts: + - name: tailscale-data + mountPath: /var/lib/tailscale + {{- end }} securityContext: capabilities: add: - NET_ADMIN + {{- if and .Values.relay.enabled .Values.relay.pvc.enabled }} + volumes: + - name: tailscale-data + persistentVolumeClaim: + claimName: {{ .Values.relay.pvc.name }} + {{- end }} {{- end }} diff --git a/helm/values.yaml b/helm/values.yaml index 050654a5..0e64d1c2 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -1,125 +1,209 @@ +# Headplane UI and API configuration headplane: + # The container image for Headplane image: ghcr.io/tale/headplane:0.6.0 config: server: - host: "0.0.0.0" - port: 3000 - cookie_secure: true + # The host address for the Headplane server + host: "0.0.0.0" + # The port for the Headplane server + port: 3000 + # Use secure cookies (should be true in production) + cookie_secure: true headscale: - url: "https://vpn.example.com" - config_path: "/etc/headscale/config.yaml" - config_strict: "true" + # The URL for the Headscale server + url: "https://vpn.example.com" + # Path to the Headscale config file + config_path: "/etc/headscale/config.yaml" + # Enable strict config mode + config_strict: "true" integration: - kubernetes: - enabled: true - validate_manifest: true - pod_name: "headplane-0" - # oidc: - # issuer: "https://your-oidc-issuer-url.com" - # disable_api_key_login: "true" - # token_endpoint_auth_method: "client_secret_post" - # redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" - # client_id: "REPLACE_IT_WITH_YOUR_OIDC_CLIENT_ID_FOR_HEADPLANE" - secret: - name: headplane-secret - create: true - # server: - # cookie_secret: "yjbqijkvfrgrtwwkoanqquykeyuyffywd" #if not set, it will be generated - # oidc: - # client_secret: "REPLACE_IT_WITH_YOUR_OIDC_CLIENT_SECRET_FOR_HEADPLANE" - # headscale_api_key: "REPLACE_IT_WITH_YOUR_HEADSCALE_API_KEY" + kubernetes: + # Enable Kubernetes integration + enabled: true + # Validate Kubernetes manifest + validate_manifest: true + # The pod name for Headplane + pod_name: "headplane-0" + oidc: + # Enable OIDC integration for Headplane + enabled: false + # OIDC issuer URL + issuer: "https://your-oidc-issuer-url.com" + # Disable API key login + disable_api_key_login: true + # OIDC token endpoint auth method + token_endpoint_auth_method: "client_secret_post" + # OIDC redirect URI + redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" + # OIDC client ID + client_id: "REPLACE_IT_WITH_YOUR_OIDC_CLIENT_ID_FOR_HEADPLANE" + # Name of the secret containing OIDC credentials + secret_name: "oidc-secrets" + # Additional secrets to mount as environment variables + envFrom: [] + # - secretRef: + # name: headscale-api-token + +# Headscale server configuration headscale: + # The container image for Headscale image: headscale/headscale:0.25.1 - # ACL configuration - # only applicable if headscale.config.policy.mode is 'file' + # Access Control List configuration (JSON format) + # Only applicable if headscale.config.policy.mode is 'file' acl: | { "acls": [] } - + config: + # The public URL for the Headscale server server_url: https://vpn.example.com + # The address Headscale listens on listen_addr: 0.0.0.0:8080 + # The address for metrics metrics_listen_addr: 0.0.0.0:9090 + # The address for gRPC grpc_listen_addr: 0.0.0.0:50443 + # Allow insecure gRPC connections grpc_allow_insecure: false policy: - mode: database # 'file' or 'database' + # Policy mode: 'file' or 'database' + mode: database + # Path to the policy file (used if mode is 'file') path: "/etc/headscale/acl.hujson" prefixes: + # IPv4 prefix for Headscale v4: 100.64.0.0/10 + # IPv6 prefix for Headscale v6: fd7a:115c:a1e0::/48 + # IP allocation mode allocation: sequential database: + # Database type (sqlite recommended for demo/testing) type: sqlite + # Enable database debug logging debug: false sqlite: + # Path to the SQLite database file path: /etc/headscale/db.sqlite noise: + # Path to the Noise protocol private key private_key_path: /etc/headscale/noise_private.key derp: server: + # Enable embedded DERP server enabled: false + # DERP region ID region_id: 999 + # DERP region code region_code: "headscale" + # DERP region name region_name: "Headscale Embedded DERP" + # STUN listen address stun_listen_addr: "0.0.0.0:3478" + # Path to DERP server private key private_key_path: /var/lib/headscale/derp_server_private.key + # Automatically add embedded DERP region automatically_add_embedded_derp_region: true + # DERP IPv4 address ipv4: 1.2.3.4 + # DERP IPv6 address ipv6: 2001:db8::1 + # List of DERP map URLs urls: - https://controlplane.tailscale.com/derpmap/default + # Additional DERP paths paths: [] dns: + # Enable MagicDNS magic_dns: true + # Base domain for MagicDNS base_domain: headscale.vpn nameservers: global: - 1.1.1.1 - 8.8.8.8 - # oidc: - # issuer: "https://your-oidc-issuer.com" - # client_id: "YOUR_OIDC_CLIENT_ID_FOR_HEADSCALE" -# allowed_groups: -# - vpn_access -# allowed_domains: -# - example.com -# allowed_users: -# - alice@example.com + oidc: + # Enable OIDC integration for Headscale + enabled: false + # OIDC issuer URL + issuer: "https://your-oidc-issuer.com" + # OIDC client ID + client_id: "YOUR_OIDC_CLIENT_ID_FOR_HEADSCALE" + # Name of the secret containing OIDC credentials + secret_name: "oidc-secrets" + # allowed_groups: + # - vpn_access + # allowed_domains: + # - example.com + # allowed_users: + # - alice@example.com + # Additional secrets to mount as environment variables + envFrom: [] - secret: - name: headscale-secret - create: true - # oidc: - # client_secret: "REPLACE_IT_WITH_YOUR_OIDC_CLIENT_SECRET" +# Tailscale relay configuration +relay: + # Enable the Tailscale relay + enabled: false + # The container image for the Tailscale relay + image: ghcr.io/tailscale/tailscale:v1.80.3 + config: + # This is an insecure field, be sure to NOT use a reusable auth key here + # if not provided, will use TS_AUTHKEY from tailscale-auth secret + authKey: "" + # Hostname for the relay + hostname: "example.com" + # Exit node configuration + exit_node: "example.com" + # Tailscale login server URL + login_server: "https://vpn.example.com" + # Whether to advertise as exit node + advertise_exit_node: "true" + # Enable firewall debug mode + firewall_debug: "false" + # Routes to advertise + routes: "10.0.0.0/8" + pvc: + # Enable persistent storage for the relay + enabled: false + # Name of the PVC for relay data + name: tailscale-relay-data + # Access modes for the PVC + accessModes: + - ReadWriteOnce + # Storage size for the PVC + storage: 1Gi +# storageClassName: default -# NOT WORKING YET -# relay: -# enabled: true -# image: ghcr.io/tailscale/tailscale:v1.80.3 -# config: -# hostname: "example.com" -# exit_node: "example.com" -# login_server: "https://vpn.example.com" -# advertise_exit_node: "true" -# firewall_debug: "false" -# routes: "10.0.0.0/8" +# Persistent volume claim for Headscale pvc: + # Enable persistent storage for Headscale enabled: true + # Name of the PVC for Headscale data name: headscale-config + # Access modes for the PVC accessModes: - ReadWriteOnce + # Storage size for the PVC storage: 1Gi # storageClassName: default +# Ingress configuration ingress: + # Enable ingress for Headplane and Headscale enabled: false + # Ingress class name className: nginx + # Additional ingress annotations # annotations: # cert-manager.io/cluster-issuer: "cloudflare" + # Additional ingress labels labels: [] + # Domain for Headplane UI headplaneDomain: "headplane.example.com" + # Domain for Headscale API headscaleDomain: "vpn.example.com" + # Name of the TLS secret tlsSecretName: "headplane-tls" From 4bbfde53035ac5a0a22c48e32e49c558ee1f4740 Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Sat, 7 Jun 2025 02:05:23 -0300 Subject: [PATCH 04/12] oidc config + configmap/secrets overhaul --- helm/README.md | 24 ++++++------ helm/README.md.gotmpl | 2 + helm/templates/secret-headplane.yaml | 24 ++++++++++++ helm/templates/secret-headscale.yaml | 26 ++++++++++++ helm/templates/statefulset-headplane.yaml | 30 +++++++------- helm/values.yaml | 48 +++++++++++------------ 6 files changed, 104 insertions(+), 50 deletions(-) create mode 100644 helm/templates/secret-headplane.yaml create mode 100644 helm/templates/secret-headscale.yaml diff --git a/helm/README.md b/helm/README.md index 421d762a..c3289c9a 100644 --- a/helm/README.md +++ b/helm/README.md @@ -39,6 +39,8 @@ helm install headplane oci://harbor.lag0.com.br/library/headplane -n your-namesp helm upgrade headplane oci://harbor.lag0.com.br/library/headplane ``` +* Some config changes may require manual pod restart to take place + ### Uninstall the Chart ```sh helm uninstall headplane @@ -54,18 +56,18 @@ helm uninstall headplane | headplane.config.integration.kubernetes.enabled | bool | `true` | | | headplane.config.integration.kubernetes.pod_name | string | `"headplane-0"` | | | headplane.config.integration.kubernetes.validate_manifest | bool | `true` | | -| headplane.config.oidc.client_id | string | `"REPLACE_IT_WITH_YOUR_OIDC_CLIENT_ID_FOR_HEADPLANE"` | | -| headplane.config.oidc.disable_api_key_login | bool | `true` | | -| headplane.config.oidc.enabled | bool | `false` | | -| headplane.config.oidc.issuer | string | `"https://your-oidc-issuer-url.com"` | | -| headplane.config.oidc.redirect_uri | string | `"https://your-headplane-admin-domain.com/admin/oidc/callback"` | | -| headplane.config.oidc.secret_name | string | `"oidc-secrets"` | | -| headplane.config.oidc.token_endpoint_auth_method | string | `"client_secret_post"` | | | headplane.config.server.cookie_secure | bool | `true` | | | headplane.config.server.host | string | `"0.0.0.0"` | | | headplane.config.server.port | int | `3000` | | | headplane.envFrom | list | `[]` | | | headplane.image | string | `"ghcr.io/tale/headplane:0.6.0"` | | +| headplane.oidc.client_id | string | `"REPLACE_IT_WITH_YOUR_OIDC_CLIENT_ID_FOR_HEADPLANE"` | | +| headplane.oidc.disable_api_key_login | bool | `true` | | +| headplane.oidc.enabled | bool | `false` | | +| headplane.oidc.issuer | string | `"https://your-oidc-issuer-url.com"` | | +| headplane.oidc.redirect_uri | string | `"https://your-headplane-admin-domain.com/admin/oidc/callback"` | | +| headplane.oidc.secret_name | string | `"oidc-secrets"` | | +| headplane.oidc.token_endpoint_auth_method | string | `"client_secret_post"` | | | headscale.acl | string | `"{\n \"acls\": []\n}\n"` | | | headscale.config.database.debug | bool | `false` | | | headscale.config.database.sqlite.path | string | `"/etc/headscale/db.sqlite"` | | @@ -90,10 +92,6 @@ helm uninstall headplane | headscale.config.listen_addr | string | `"0.0.0.0:8080"` | | | headscale.config.metrics_listen_addr | string | `"0.0.0.0:9090"` | | | headscale.config.noise.private_key_path | string | `"/etc/headscale/noise_private.key"` | | -| headscale.config.oidc.client_id | string | `"YOUR_OIDC_CLIENT_ID_FOR_HEADSCALE"` | | -| headscale.config.oidc.enabled | bool | `false` | | -| headscale.config.oidc.issuer | string | `"https://your-oidc-issuer.com"` | | -| headscale.config.oidc.secret_name | string | `"oidc-secrets"` | | | headscale.config.policy.mode | string | `"database"` | | | headscale.config.policy.path | string | `"/etc/headscale/acl.hujson"` | | | headscale.config.prefixes.allocation | string | `"sequential"` | | @@ -102,6 +100,10 @@ helm uninstall headplane | headscale.config.server_url | string | `"https://vpn.example.com"` | | | headscale.envFrom | list | `[]` | | | headscale.image | string | `"headscale/headscale:0.25.1"` | | +| headscale.oidc.client_id | string | `"YOUR_OIDC_CLIENT_ID_FOR_HEADSCALE"` | | +| headscale.oidc.enabled | bool | `false` | | +| headscale.oidc.issuer | string | `"https://your-oidc-issuer.com"` | | +| headscale.oidc.secret_name | string | `"oidc-secrets"` | | | ingress.className | string | `"nginx"` | | | ingress.enabled | bool | `false` | | | ingress.headplaneDomain | string | `"headplane.example.com"` | | diff --git a/helm/README.md.gotmpl b/helm/README.md.gotmpl index 205799ff..bfc803c3 100644 --- a/helm/README.md.gotmpl +++ b/helm/README.md.gotmpl @@ -39,6 +39,8 @@ helm install headplane oci://harbor.lag0.com.br/library/headplane -n your-namesp helm upgrade headplane oci://harbor.lag0.com.br/library/headplane ``` +* Some config changes may require manual pod restart to take place + ### Uninstall the Chart ```sh helm uninstall headplane diff --git a/helm/templates/secret-headplane.yaml b/helm/templates/secret-headplane.yaml new file mode 100644 index 00000000..c7ab785b --- /dev/null +++ b/helm/templates/secret-headplane.yaml @@ -0,0 +1,24 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: headplane-config +type: Opaque +stringData: + config.yaml: | + server: +{{- toYaml .Values.headplane.config.server | nindent 6 }} + cookie_secret: {{ include "headplane.cookieSecret" . | quote }} + headscale: +{{- toYaml .Values.headplane.config.headscale | nindent 6 }} + integration: +{{- toYaml .Values.headplane.config.integration | nindent 6 }} +# only add oidc if .Values.headplane.oidc is set +{{- if .Values.headplane.oidc.enabled }} + oidc: + issuer: {{ .Values.headplane.oidc.issuer | quote }} + disable_api_key_login: {{ .Values.headplane.oidc.disable_api_key_login | quote }} + token_endpoint_auth_method: {{ .Values.headplane.oidc.token_endpoint_auth_method | quote }} + redirect_uri: {{ .Values.headplane.oidc.redirect_uri | quote }} + client_id: {{ .Values.headplane.oidc.client_id | quote }} +{{- end }} \ No newline at end of file diff --git a/helm/templates/secret-headscale.yaml b/helm/templates/secret-headscale.yaml new file mode 100644 index 00000000..1f5ae06b --- /dev/null +++ b/helm/templates/secret-headscale.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: headscale-config +type: Opaque +stringData: + config.yaml: | +{{- toYaml .Values.headscale.config | nindent 4 }} +{{- if .Values.headscale.oidc.enabled }} + oidc: + issuer: {{ .Values.headscale.oidc.issuer | quote }} + client_id: {{ .Values.headscale.oidc.client_id | quote }} + {{- if .Values.headscale.oidc.allowed_groups }} + allowed_groups: + {{- toYaml .Values.headscale.oidc.allowed_groups | nindent 8 }} + {{- end }} + {{- if .Values.headscale.oidc.allowed_domains }} + allowed_domains: + {{- toYaml .Values.headscale.oidc.allowed_domains | nindent 8 }} + {{- end }} + {{- if .Values.headscale.oidc.allowed_users }} + allowed_users: + {{- toYaml .Values.headscale.oidc.allowed_users | nindent 8 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/templates/statefulset-headplane.yaml b/helm/templates/statefulset-headplane.yaml index c430f8ac..c4a58daf 100644 --- a/helm/templates/statefulset-headplane.yaml +++ b/helm/templates/statefulset-headplane.yaml @@ -23,22 +23,22 @@ spec: - sh - -c - | - cp /headscale-default-config/config.yaml /headscale-config/config.yaml - cp /headscale-acl/acl.hujson /headscale-config/acl.hujson + cp /headscale-config/config.yaml /headscale-data/config.yaml + cp /headscale-acl/acl.hujson /headscale-data/acl.hujson volumeMounts: + - name: headscale-data + mountPath: /headscale-data - name: headscale-config mountPath: /headscale-config - - name: headscale-default-config - mountPath: /headscale-default-config - name: headscale-acl mountPath: /headscale-acl containers: - name: headplane image: {{ .Values.headplane.image }} envFrom: - {{- if .Values.headplane.config.oidc.enabled }} + {{- if .Values.headplane.oidc.enabled }} - secretRef: - name: {{ .Values.headplane.config.oidc.secret_name }} + name: {{ .Values.headplane.oidc.secret_name }} {{- end }} {{- with .Values.headplane.envFrom }} {{- toYaml . | nindent 10 }} @@ -53,35 +53,35 @@ spec: volumeMounts: - name: headplane-config mountPath: /etc/headplane - - name: headscale-config + - name: headscale-data mountPath: /etc/headscale - name: headscale image: {{ .Values.headscale.image }} args: - serve volumeMounts: - - name: headscale-config + - name: headscale-data mountPath: /etc/headscale - name: headplane-config mountPath: /etc/headplane envFrom: - {{- if .Values.headscale.config.oidc.enabled }} + {{- if .Values.headscale.oidc.enabled }} - secretRef: - name: {{ .Values.headscale.config.oidc.secret_name }} + name: {{ .Values.headscale.oidc.secret_name }} {{- end }} {{- with .Values.headscale.envFrom }} {{- toYaml . | nindent 10 }} {{- end }} volumes: - - name: headscale-default-config - configMap: - name: headscale-default-config - name: headscale-config + secret: + secretName: headscale-config + - name: headscale-data persistentVolumeClaim: claimName: headscale-config - name: headplane-config - configMap: - name: headplane-config + secret: + secretName: headplane-config - name: headscale-acl configMap: name: headscale-acl diff --git a/helm/values.yaml b/helm/values.yaml index 0e64d1c2..cad5d40e 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -2,6 +2,21 @@ headplane: # The container image for Headplane image: ghcr.io/tale/headplane:0.6.0 + oidc: + # Enable OIDC integration for Headplane + enabled: false + # OIDC issuer URL + issuer: "https://your-oidc-issuer-url.com" + # Disable API key login + disable_api_key_login: true + # OIDC token endpoint auth method + token_endpoint_auth_method: "client_secret_post" + # OIDC redirect URI + redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" + # OIDC client ID + client_id: "REPLACE_IT_WITH_YOUR_OIDC_CLIENT_ID_FOR_HEADPLANE" + # Name of the secret containing OIDC credentials + secret_name: "oidc-secrets" config: server: # The host address for the Headplane server @@ -25,21 +40,6 @@ headplane: validate_manifest: true # The pod name for Headplane pod_name: "headplane-0" - oidc: - # Enable OIDC integration for Headplane - enabled: false - # OIDC issuer URL - issuer: "https://your-oidc-issuer-url.com" - # Disable API key login - disable_api_key_login: true - # OIDC token endpoint auth method - token_endpoint_auth_method: "client_secret_post" - # OIDC redirect URI - redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" - # OIDC client ID - client_id: "REPLACE_IT_WITH_YOUR_OIDC_CLIENT_ID_FOR_HEADPLANE" - # Name of the secret containing OIDC credentials - secret_name: "oidc-secrets" # Additional secrets to mount as environment variables envFrom: [] # - secretRef: @@ -57,6 +57,15 @@ headscale: "acls": [] } + oidc: + # Enable OIDC integration for Headscale + enabled: false + # OIDC issuer URL + issuer: "https://your-oidc-issuer.com" + # OIDC client ID + client_id: "YOUR_OIDC_CLIENT_ID_FOR_HEADSCALE" + # Name of the secret containing OIDC credentials + secret_name: "oidc-secrets" config: # The public URL for the Headscale server server_url: https://vpn.example.com @@ -125,15 +134,6 @@ headscale: global: - 1.1.1.1 - 8.8.8.8 - oidc: - # Enable OIDC integration for Headscale - enabled: false - # OIDC issuer URL - issuer: "https://your-oidc-issuer.com" - # OIDC client ID - client_id: "YOUR_OIDC_CLIENT_ID_FOR_HEADSCALE" - # Name of the secret containing OIDC credentials - secret_name: "oidc-secrets" # allowed_groups: # - vpn_access # allowed_domains: From 31d9992d9ddb0a69424a5c0340214e688dcf27d9 Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:28:21 -0300 Subject: [PATCH 05/12] fix oidc docs --- helm/README.md | 22 ++++++++++------------ helm/README.md.gotmpl | 22 ++++++++++------------ 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/helm/README.md b/helm/README.md index c3289c9a..4855041d 100644 --- a/helm/README.md +++ b/helm/README.md @@ -130,7 +130,7 @@ helm uninstall headplane ### OIDC Configuration -To use OIDC, you must provide the OIDC client secrets via a single Kubernetes secret: +To use OIDC, you must provide the OIDC client secrets via Kubernetes secret: ```sh kubectl create secret generic oidc-secrets \ @@ -144,19 +144,17 @@ kubectl create secret generic oidc-secrets \ Then enable OIDC in your `values.yaml`: ```yaml headplane: - config: - oidc: - enabled: true - issuer: "https://your-oidc-issuer-url.com" - redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" - secret_name: "oidc-secrets" # Name of your OIDC secret + oidc: + enabled: true + issuer: "https://your-oidc-issuer-url.com" + redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" + secret_name: "oidc-secrets" # Name of your OIDC secret headscale: - config: - oidc: - enabled: true - issuer: "https://your-oidc-issuer.com" - secret_name: "oidc-secrets" # Same secret as Headplane + oidc: + enabled: true + issuer: "https://your-oidc-issuer.com" + secret_name: "oidc-secrets" # Same secret as Headplane ``` You can add any additional environment variables by creating more secrets or config-maps and adding them to the `envFrom` section. For example, to add custom configuration: diff --git a/helm/README.md.gotmpl b/helm/README.md.gotmpl index bfc803c3..eaff0fbe 100644 --- a/helm/README.md.gotmpl +++ b/helm/README.md.gotmpl @@ -56,7 +56,7 @@ helm uninstall headplane ### OIDC Configuration -To use OIDC, you must provide the OIDC client secrets via a single Kubernetes secret: +To use OIDC, you must provide the OIDC client secrets via Kubernetes secret: ```sh kubectl create secret generic oidc-secrets \ @@ -70,19 +70,17 @@ kubectl create secret generic oidc-secrets \ Then enable OIDC in your `values.yaml`: ```yaml headplane: - config: - oidc: - enabled: true - issuer: "https://your-oidc-issuer-url.com" - redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" - secret_name: "oidc-secrets" # Name of your OIDC secret + oidc: + enabled: true + issuer: "https://your-oidc-issuer-url.com" + redirect_uri: "https://your-headplane-admin-domain.com/admin/oidc/callback" + secret_name: "oidc-secrets" # Name of your OIDC secret headscale: - config: - oidc: - enabled: true - issuer: "https://your-oidc-issuer.com" - secret_name: "oidc-secrets" # Same secret as Headplane + oidc: + enabled: true + issuer: "https://your-oidc-issuer.com" + secret_name: "oidc-secrets" # Same secret as Headplane ``` You can add any additional environment variables by creating more secrets or config-maps and adding them to the `envFrom` section. For example, to add custom configuration: From 79ef1f114144d644543ab890b8efcf52709e295e Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:14:25 -0300 Subject: [PATCH 06/12] Add annotations/labels to pvc --- helm/README.md | 18 ++++-------------- helm/README.md.gotmpl | 13 ------------- helm/templates/pvc-tailscale-relay.yaml | 12 ++++++++++++ helm/templates/pvc.yaml | 12 ++++++++++++ helm/values.yaml | 15 ++++++++++----- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/helm/README.md b/helm/README.md index 4855041d..45dba306 100644 --- a/helm/README.md +++ b/helm/README.md @@ -11,12 +11,6 @@ This helm chart deploys [Headplane](https://github.com/tale/headplane) + [Headsc - Helm installed (`helm version`) - (Optional) OCI registry access for pulling images -### Add the Helm repository -```sh -helm repo add headplane https://harbor.lag0.com.br/library -helm repo update -``` - ### Install the Chart ```sh # Install with default values @@ -25,13 +19,6 @@ helm install headplane oci://harbor.lag0.com.br/library/headplane # Install with custom values helm install headplane oci://harbor.lag0.com.br/library/headplane -f values.yaml -# Install with specific values -helm install headplane oci://harbor.lag0.com.br/library/headplane \ - --set headplane.config.server.port=3000 \ - --set ingress.enabled=true - -# Install in a specific namespace -helm install headplane oci://harbor.lag0.com.br/library/headplane -n your-namespace ``` ### Upgrade the Chart @@ -68,7 +55,7 @@ helm uninstall headplane | headplane.oidc.redirect_uri | string | `"https://your-headplane-admin-domain.com/admin/oidc/callback"` | | | headplane.oidc.secret_name | string | `"oidc-secrets"` | | | headplane.oidc.token_endpoint_auth_method | string | `"client_secret_post"` | | -| headscale.acl | string | `"{\n \"acls\": []\n}\n"` | | +| headscale.acl | string | `""` | | | headscale.config.database.debug | bool | `false` | | | headscale.config.database.sqlite.path | string | `"/etc/headscale/db.sqlite"` | | | headscale.config.database.type | string | `"sqlite"` | | @@ -104,6 +91,7 @@ helm uninstall headplane | headscale.oidc.enabled | bool | `false` | | | headscale.oidc.issuer | string | `"https://your-oidc-issuer.com"` | | | headscale.oidc.secret_name | string | `"oidc-secrets"` | | +| ingress.annotations | list | `[]` | | | ingress.className | string | `"nginx"` | | | ingress.enabled | bool | `false` | | | ingress.headplaneDomain | string | `"headplane.example.com"` | | @@ -111,7 +99,9 @@ helm uninstall headplane | ingress.labels | list | `[]` | | | ingress.tlsSecretName | string | `"headplane-tls"` | | | pvc.accessModes[0] | string | `"ReadWriteOnce"` | | +| pvc.annotations | object | `{}` | | | pvc.enabled | bool | `true` | | +| pvc.labels | list | `[]` | | | pvc.name | string | `"headscale-config"` | | | pvc.storage | string | `"1Gi"` | | | relay.config.advertise_exit_node | string | `"true"` | | diff --git a/helm/README.md.gotmpl b/helm/README.md.gotmpl index eaff0fbe..943bb752 100644 --- a/helm/README.md.gotmpl +++ b/helm/README.md.gotmpl @@ -11,12 +11,6 @@ This helm chart deploys [Headplane](https://github.com/tale/headplane) + [Headsc - Helm installed (`helm version`) - (Optional) OCI registry access for pulling images -### Add the Helm repository -```sh -helm repo add headplane https://harbor.lag0.com.br/library -helm repo update -``` - ### Install the Chart ```sh # Install with default values @@ -25,13 +19,6 @@ helm install headplane oci://harbor.lag0.com.br/library/headplane # Install with custom values helm install headplane oci://harbor.lag0.com.br/library/headplane -f values.yaml -# Install with specific values -helm install headplane oci://harbor.lag0.com.br/library/headplane \ - --set headplane.config.server.port=3000 \ - --set ingress.enabled=true - -# Install in a specific namespace -helm install headplane oci://harbor.lag0.com.br/library/headplane -n your-namespace ``` ### Upgrade the Chart diff --git a/helm/templates/pvc-tailscale-relay.yaml b/helm/templates/pvc-tailscale-relay.yaml index d1c88756..3cad25db 100644 --- a/helm/templates/pvc-tailscale-relay.yaml +++ b/helm/templates/pvc-tailscale-relay.yaml @@ -5,6 +5,18 @@ kind: PersistentVolumeClaim metadata: name: {{ .Values.relay.pvc.name }} namespace: {{ .Release.Namespace }} + {{- if .Values.pvc.annotations }} + annotations: + {{- range $key, $value := .Values.pvc.annotations }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .Values.pvc.labels }} + labels: + {{- range $key, $value := .Values.pvc.labels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} spec: accessModes: {{- range .Values.relay.pvc.accessModes }} diff --git a/helm/templates/pvc.yaml b/helm/templates/pvc.yaml index 70671f20..648b4fce 100644 --- a/helm/templates/pvc.yaml +++ b/helm/templates/pvc.yaml @@ -4,6 +4,18 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: name: {{ .Values.pvc.name | quote }} + {{- if .Values.pvc.annotations }} + annotations: + {{- range $key, $value := .Values.pvc.annotations }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .Values.pvc.labels }} + labels: + {{- range $key, $value := .Values.pvc.labels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} spec: accessModes: {{- range .Values.pvc.accessModes }} diff --git a/helm/values.yaml b/helm/values.yaml index cad5d40e..abd5d6b9 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -52,10 +52,11 @@ headscale: # Access Control List configuration (JSON format) # Only applicable if headscale.config.policy.mode is 'file' - acl: | - { - "acls": [] - } + acl: "" + # acl: | + # { + # "acls": [] + # } oidc: # Enable OIDC integration for Headscale @@ -188,6 +189,10 @@ pvc: - ReadWriteOnce # Storage size for the PVC storage: 1Gi + # Annotations for the PVC + annotations: {} + # Labels for the PVC + labels: [] # storageClassName: default # Ingress configuration @@ -197,7 +202,7 @@ ingress: # Ingress class name className: nginx # Additional ingress annotations - # annotations: + annotations: [] # cert-manager.io/cluster-issuer: "cloudflare" # Additional ingress labels labels: [] From 6be42cacb26900474b18f08aef4c2bc2a7a34435 Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:22:24 -0300 Subject: [PATCH 07/12] minor doc adjust --- helm/README.md | 6 +++--- helm/README.md.gotmpl | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helm/README.md b/helm/README.md index 45dba306..e0bfb677 100644 --- a/helm/README.md +++ b/helm/README.md @@ -8,8 +8,7 @@ This helm chart deploys [Headplane](https://github.com/tale/headplane) + [Headsc ### Prerequisites - Kubernetes cluster -- Helm installed (`helm version`) -- (Optional) OCI registry access for pulling images +- Helm installed ### Install the Chart ```sh @@ -164,7 +163,8 @@ headplane: name: headplane-custom-config ``` -Note: Make sure to keep your secrets secure and never commit them to version control. Consider using a secrets management solution in production. +Note: Make sure to keep your secrets secure and never commit them to version control. +Consider using a secrets management solution in production like external-secrets. ## License Copyright © 2025 antoniolago diff --git a/helm/README.md.gotmpl b/helm/README.md.gotmpl index 943bb752..95b1580a 100644 --- a/helm/README.md.gotmpl +++ b/helm/README.md.gotmpl @@ -8,8 +8,7 @@ This helm chart deploys [Headplane](https://github.com/tale/headplane) + [Headsc ### Prerequisites - Kubernetes cluster -- Helm installed (`helm version`) -- (Optional) OCI registry access for pulling images +- Helm installed ### Install the Chart ```sh @@ -87,7 +86,8 @@ headplane: name: headplane-custom-config ``` -Note: Make sure to keep your secrets secure and never commit them to version control. Consider using a secrets management solution in production. +Note: Make sure to keep your secrets secure and never commit them to version control. +Consider using a secrets management solution in production like external-secrets. ## License Copyright © 2025 antoniolago From db336776655086cb9a7e36b4481029d02f05bf9e Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Thu, 19 Jun 2025 00:48:43 -0300 Subject: [PATCH 08/12] Restore acl value This is only applicable if the user choose policy mode file, but without this value the configmap is not being created --- helm/values.yaml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/helm/values.yaml b/helm/values.yaml index abd5d6b9..01a292a9 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -52,11 +52,10 @@ headscale: # Access Control List configuration (JSON format) # Only applicable if headscale.config.policy.mode is 'file' - acl: "" - # acl: | - # { - # "acls": [] - # } + acl: | + { + "acls": [] + } oidc: # Enable OIDC integration for Headscale From 5c23f80cf1f8be2dbc6de02b3fe875d69f53da87 Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Thu, 19 Jun 2025 23:28:55 -0300 Subject: [PATCH 09/12] add null check cookiesecret Co-Authored-By: definitelynobody <51100439+definitelynobody@users.noreply.github.com> --- helm/templates/_helpers.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index e7786cfc..103ecf75 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -2,7 +2,7 @@ Generate a random cookie secret if none is provided */}} {{- define "headplane.cookieSecret" -}} -{{- if and .Values.headplane.secret.server (hasKey .Values.headplane.secret.server "cookie_secret") .Values.headplane.secret.server.cookie_secret -}} +{{- if and .Values.headplane.secret .Values.headplane.secret.server (hasKey .Values.headplane.secret.server "cookie_secret") .Values.headplane.secret.server.cookie_secret -}} {{- .Values.headplane.secret.server.cookie_secret -}} {{- else -}} {{- randAlphaNum 32 -}} From 7641454dec55d730f636ccd480ac432cc1225edd Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Sat, 21 Jun 2025 15:39:44 -0300 Subject: [PATCH 10/12] Simplify PVC structure on manifests and update headscale version Co-Authored-By: definitelynobody <51100439+definitelynobody@users.noreply.github.com> --- helm/templates/pvc-tailscale-relay.yaml | 31 ------------------- helm/templates/pvc.yaml | 30 ------------------ helm/templates/secret-headplane.yaml | 1 - helm/templates/statefulset-headplane.yaml | 31 +++++++++++++++++-- .../statefulset-tailscale-relay.yaml | 22 +++++++++---- helm/values.yaml | 2 +- 6 files changed, 45 insertions(+), 72 deletions(-) delete mode 100644 helm/templates/pvc-tailscale-relay.yaml delete mode 100644 helm/templates/pvc.yaml diff --git a/helm/templates/pvc-tailscale-relay.yaml b/helm/templates/pvc-tailscale-relay.yaml deleted file mode 100644 index 3cad25db..00000000 --- a/helm/templates/pvc-tailscale-relay.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if and .Values.relay.enabled .Values.relay.pvc.enabled }} ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ .Values.relay.pvc.name }} - namespace: {{ .Release.Namespace }} - {{- if .Values.pvc.annotations }} - annotations: - {{- range $key, $value := .Values.pvc.annotations }} - {{ $key | quote }}: {{ $value | quote }} - {{- end }} - {{- end }} - {{- if .Values.pvc.labels }} - labels: - {{- range $key, $value := .Values.pvc.labels }} - {{ $key | quote }}: {{ $value | quote }} - {{- end }} - {{- end }} -spec: - accessModes: - {{- range .Values.relay.pvc.accessModes }} - - {{ . | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.relay.pvc.storage | quote }} - {{- if .Values.relay.pvc.storageClassName }} - storageClassName: {{ .Values.relay.pvc.storageClassName | quote }} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/helm/templates/pvc.yaml b/helm/templates/pvc.yaml deleted file mode 100644 index 648b4fce..00000000 --- a/helm/templates/pvc.yaml +++ /dev/null @@ -1,30 +0,0 @@ -{{- if .Values.pvc.enabled }} ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ .Values.pvc.name | quote }} - {{- if .Values.pvc.annotations }} - annotations: - {{- range $key, $value := .Values.pvc.annotations }} - {{ $key | quote }}: {{ $value | quote }} - {{- end }} - {{- end }} - {{- if .Values.pvc.labels }} - labels: - {{- range $key, $value := .Values.pvc.labels }} - {{ $key | quote }}: {{ $value | quote }} - {{- end }} - {{- end }} -spec: - accessModes: - {{- range .Values.pvc.accessModes }} - - {{ . | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.pvc.storage | quote }} - {{- if .Values.pvc.storageClassName }} - storageClassName: {{ .Values.pvc.storageClassName | quote }} - {{- end }} -{{- end }} diff --git a/helm/templates/secret-headplane.yaml b/helm/templates/secret-headplane.yaml index c7ab785b..db6a0f3e 100644 --- a/helm/templates/secret-headplane.yaml +++ b/helm/templates/secret-headplane.yaml @@ -13,7 +13,6 @@ stringData: {{- toYaml .Values.headplane.config.headscale | nindent 6 }} integration: {{- toYaml .Values.headplane.config.integration | nindent 6 }} -# only add oidc if .Values.headplane.oidc is set {{- if .Values.headplane.oidc.enabled }} oidc: issuer: {{ .Values.headplane.oidc.issuer | quote }} diff --git a/helm/templates/statefulset-headplane.yaml b/helm/templates/statefulset-headplane.yaml index c4a58daf..6d4b6bfd 100644 --- a/helm/templates/statefulset-headplane.yaml +++ b/helm/templates/statefulset-headplane.yaml @@ -76,12 +76,37 @@ spec: - name: headscale-config secret: secretName: headscale-config - - name: headscale-data - persistentVolumeClaim: - claimName: headscale-config - name: headplane-config secret: secretName: headplane-config - name: headscale-acl configMap: name: headscale-acl + {{- if .Values.pvc.enabled }} + volumeClaimTemplates: + - metadata: + name: headscale-data + {{- if .Values.pvc.annotations }} + annotations: + {{- range $key, $value := .Values.pvc.annotations }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .Values.pvc.labels }} + labels: + {{- range $key, $value := .Values.pvc.labels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} + spec: + accessModes: + {{- range .Values.pvc.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.pvc.storage | quote }} + {{- if .Values.pvc.storageClassName }} + storageClassName: {{ .Values.pvc.storageClassName | quote }} + {{- end }} + {{- end }} diff --git a/helm/templates/statefulset-tailscale-relay.yaml b/helm/templates/statefulset-tailscale-relay.yaml index 6d41e461..02e6d245 100644 --- a/helm/templates/statefulset-tailscale-relay.yaml +++ b/helm/templates/statefulset-tailscale-relay.yaml @@ -72,7 +72,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.uid - {{- if and .Values.relay.enabled .Values.relay.pvc.enabled }} + {{- if .Values.relay.pvc.enabled }} volumeMounts: - name: tailscale-data mountPath: /var/lib/tailscale @@ -81,10 +81,20 @@ spec: capabilities: add: - NET_ADMIN - {{- if and .Values.relay.enabled .Values.relay.pvc.enabled }} - volumes: - - name: tailscale-data - persistentVolumeClaim: - claimName: {{ .Values.relay.pvc.name }} + {{- if .Values.relay.pvc.enabled }} + volumeClaimTemplates: + - metadata: + name: tailscale-data + spec: + accessModes: + {{- range .Values.relay.pvc.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.relay.pvc.storage | quote }} + {{- if .Values.relay.pvc.storageClassName }} + storageClassName: {{ .Values.relay.pvc.storageClassName | quote }} {{- end }} + {{- end }} {{- end }} diff --git a/helm/values.yaml b/helm/values.yaml index 01a292a9..3a63fb84 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -48,7 +48,7 @@ headplane: # Headscale server configuration headscale: # The container image for Headscale - image: headscale/headscale:0.25.1 + image: headscale/headscale:0.26.1 # Access Control List configuration (JSON format) # Only applicable if headscale.config.policy.mode is 'file' From 1c938d4bc1b07eee1eb0947f79c84cc364abc22e Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:34:25 -0300 Subject: [PATCH 11/12] Add headscale.oidc.pkce Co-Authored-By: Stephan Deumier --- helm/README.md | 6 ++++-- helm/templates/secret-headplane.yaml | 1 + helm/templates/secret-headscale.yaml | 5 +++++ helm/values.yaml | 3 +++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/helm/README.md b/helm/README.md index e0bfb677..28e5d341 100644 --- a/helm/README.md +++ b/helm/README.md @@ -54,7 +54,7 @@ helm uninstall headplane | headplane.oidc.redirect_uri | string | `"https://your-headplane-admin-domain.com/admin/oidc/callback"` | | | headplane.oidc.secret_name | string | `"oidc-secrets"` | | | headplane.oidc.token_endpoint_auth_method | string | `"client_secret_post"` | | -| headscale.acl | string | `""` | | +| headscale.acl | string | `"{\n \"acls\": []\n}\n"` | | | headscale.config.database.debug | bool | `false` | | | headscale.config.database.sqlite.path | string | `"/etc/headscale/db.sqlite"` | | | headscale.config.database.type | string | `"sqlite"` | | @@ -85,10 +85,12 @@ helm uninstall headplane | headscale.config.prefixes.v6 | string | `"fd7a:115c:a1e0::/48"` | | | headscale.config.server_url | string | `"https://vpn.example.com"` | | | headscale.envFrom | list | `[]` | | -| headscale.image | string | `"headscale/headscale:0.25.1"` | | +| headscale.image | string | `"headscale/headscale:0.26.1"` | | | headscale.oidc.client_id | string | `"YOUR_OIDC_CLIENT_ID_FOR_HEADSCALE"` | | | headscale.oidc.enabled | bool | `false` | | | headscale.oidc.issuer | string | `"https://your-oidc-issuer.com"` | | +| headscale.oidc.pkce.enabled | bool | `false` | | +| headscale.oidc.pkce.method | string | `"S256"` | | | headscale.oidc.secret_name | string | `"oidc-secrets"` | | | ingress.annotations | list | `[]` | | | ingress.className | string | `"nginx"` | | diff --git a/helm/templates/secret-headplane.yaml b/helm/templates/secret-headplane.yaml index db6a0f3e..6a1423d6 100644 --- a/helm/templates/secret-headplane.yaml +++ b/helm/templates/secret-headplane.yaml @@ -20,4 +20,5 @@ stringData: token_endpoint_auth_method: {{ .Values.headplane.oidc.token_endpoint_auth_method | quote }} redirect_uri: {{ .Values.headplane.oidc.redirect_uri | quote }} client_id: {{ .Values.headplane.oidc.client_id | quote }} + headscale_api_key: {{ .Values.headplane.oidc.headscale_api_key | default "placeholder-token-will-be-replaced-by-job" | quote }} {{- end }} \ No newline at end of file diff --git a/helm/templates/secret-headscale.yaml b/helm/templates/secret-headscale.yaml index 1f5ae06b..7f59f2e1 100644 --- a/helm/templates/secret-headscale.yaml +++ b/helm/templates/secret-headscale.yaml @@ -23,4 +23,9 @@ stringData: allowed_users: {{- toYaml .Values.headscale.oidc.allowed_users | nindent 8 }} {{- end }} + {{- if .Values.headscale.oidc.pkce.enabled }} + pkce: + enabled: {{ .Values.headscale.oidc.pkce.enabled | quote }} + method: {{ .Values.headscale.oidc.pkce.method | quote }} + {{- end }} {{- end }} \ No newline at end of file diff --git a/helm/values.yaml b/helm/values.yaml index 3a63fb84..9eb76eaa 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -66,6 +66,9 @@ headscale: client_id: "YOUR_OIDC_CLIENT_ID_FOR_HEADSCALE" # Name of the secret containing OIDC credentials secret_name: "oidc-secrets" + pkce: + enabled: false + method: S256 config: # The public URL for the Headscale server server_url: https://vpn.example.com From 801f1de0c87e3dadc06246474833b547183cf1c6 Mon Sep 17 00:00:00 2001 From: antoniolago <45375617+antoniolago@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:37:30 -0300 Subject: [PATCH 12/12] Fix oidc missing configs + test cases Co-Authored-By: Stephan Deumier --- helm/Chart.yaml | 2 +- helm/templates/configmap-headplane.yaml | 23 ---- helm/templates/configmap-headscale.yaml | 25 ---- helm/templates/job.yaml | 28 ++++- helm/templates/roles.yaml | 5 +- helm/templates/secret-headplane.yaml | 2 +- .../templates/secret-headscale-api-token.yaml | 9 ++ helm/templates/secret-headscale.yaml | 4 +- helm/templates/statefulset-headplane.yaml | 10 ++ helm/test/templates/mock-oidc-server.yaml | 110 ++++++++++++++++++ helm/test/templates/secret-oidc.yaml | 17 +++ helm/test/test-cases/00-lag0-test-case.yaml | 82 +++++++++++++ .../01-definetelynobody-test-case.yaml | 18 +++ helm/test/test-cases/02-AzSiAz-test-case.yaml | 22 ++++ 14 files changed, 298 insertions(+), 59 deletions(-) delete mode 100644 helm/templates/configmap-headplane.yaml delete mode 100644 helm/templates/configmap-headscale.yaml create mode 100644 helm/templates/secret-headscale-api-token.yaml create mode 100644 helm/test/templates/mock-oidc-server.yaml create mode 100644 helm/test/templates/secret-oidc.yaml create mode 100644 helm/test/test-cases/00-lag0-test-case.yaml create mode 100644 helm/test/test-cases/01-definetelynobody-test-case.yaml create mode 100644 helm/test/test-cases/02-AzSiAz-test-case.yaml diff --git a/helm/Chart.yaml b/helm/Chart.yaml index c4ea3682..88ab572f 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: headplane description: "The headplane Helm chart provides an easy way to deploy a Headscale UI with headplane, including an embedded Tailscale relay. This chart simplifies the setup of a private networking solution using Kubernetes." type: application -version: 0.1.39 +version: 0.4.4 appVersion: "0.6.0" diff --git a/helm/templates/configmap-headplane.yaml b/helm/templates/configmap-headplane.yaml deleted file mode 100644 index 9a539ab0..00000000 --- a/helm/templates/configmap-headplane.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: headplane-config -data: - config.yaml: | - server: -{{- toYaml .Values.headplane.config.server | nindent 6 }} - cookie_secret: {{ include "headplane.cookieSecret" . | quote }} - headscale: -{{- toYaml .Values.headplane.config.headscale | nindent 6 }} - integration: -{{- toYaml .Values.headplane.config.integration | nindent 6 }} -# only add oidc if .Values.headplane.config.oidc is set -{{- if .Values.headplane.config.oidc.enabled }} - oidc: - issuer: {{ .Values.headplane.config.oidc.issuer | quote }} - disable_api_key_login: {{ .Values.headplane.config.oidc.disable_api_key_login | quote }} - token_endpoint_auth_method: {{ .Values.headplane.config.oidc.token_endpoint_auth_method | quote }} - redirect_uri: {{ .Values.headplane.config.oidc.redirect_uri | quote }} - client_id: {{ .Values.headplane.config.oidc.client_id | quote }} -{{- end }} diff --git a/helm/templates/configmap-headscale.yaml b/helm/templates/configmap-headscale.yaml deleted file mode 100644 index daf0acb3..00000000 --- a/helm/templates/configmap-headscale.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: headscale-default-config -data: - config.yaml: | -{{- toYaml .Values.headscale.config | nindent 4 }} -{{- if .Values.headscale.config.oidc.enabled }} - oidc: - issuer: {{ .Values.headscale.config.oidc.issuer | quote }} - client_id: {{ .Values.headscale.config.oidc.client_id | quote }} - {{- if .Values.headscale.config.oidc.allowed_groups }} - allowed_groups: - {{- toYaml .Values.headscale.config.oidc.allowed_groups | nindent 8 }} - {{- end }} - {{- if .Values.headscale.config.oidc.allowed_domains }} - allowed_domains: - {{- toYaml .Values.headscale.config.oidc.allowed_domains | nindent 8 }} - {{- end }} - {{- if .Values.headscale.config.oidc.allowed_users }} - allowed_users: - {{- toYaml .Values.headscale.config.oidc.allowed_users | nindent 8 }} - {{- end }} -{{- end }} diff --git a/helm/templates/job.yaml b/helm/templates/job.yaml index 27300b54..829b82a8 100644 --- a/helm/templates/job.yaml +++ b/helm/templates/job.yaml @@ -3,6 +3,9 @@ kind: Job metadata: name: headscale-generate-token namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded spec: template: spec: @@ -23,13 +26,24 @@ spec: sleep 1 done - echo "Checking if Secret 'headscale-api-token' exists..." + echo "Checking if API token needs to be generated..." if kubectl get secret headscale-api-token -n {{ .Release.Namespace }} >/dev/null 2>&1; then - echo "Secret already exists. Skipping token generation." - exit 0 + CURRENT_TOKEN=$(kubectl get secret headscale-api-token -n {{ .Release.Namespace }} -o jsonpath='{.data.HEADPLANE_OIDC__HEADSCALE_API_KEY}' | base64 -d) + echo "Current token value: '$CURRENT_TOKEN'" + echo "Current token length: ${#CURRENT_TOKEN}" + echo "Expected placeholder: 'placeholder-token-will-be-replaced-by-job'" + echo "Expected length: 42" + if [[ "$CURRENT_TOKEN" != "placeholder-token-will-be-replaced-by-job" ]]; then + echo "Real API token already exists. Skipping token generation." + exit 0 + else + echo "Placeholder token found. Generating real API token..." + fi + else + echo "Secret not found. Generating API token..." fi - echo "Secret not found. Generating Headscale API token..." + echo "Generating Headscale API token..." TOKEN=$(kubectl -n {{ .Release.Namespace }} exec -i headplane-0 -c headscale -- headscale apikeys create -e 100y) if [ -z "$TOKEN" ]; then @@ -37,5 +51,7 @@ spec: exit 1 fi - echo "Creating Kubernetes Secret..." - kubectl create secret generic headscale-api-token --from-literal=HEADPLANE_OIDC__HEADSCALE_API_KEY="$TOKEN" -n {{ .Release.Namespace }} + echo "Updating headscale-api-token secret with generated API token..." + kubectl patch secret headscale-api-token -n {{ .Release.Namespace }} -p="{\"data\":{\"HEADPLANE_OIDC__HEADSCALE_API_KEY\":\"$(echo -n "$TOKEN" | base64)\"}}" + + echo "Successfully updated headscale-api-token secret with real API token" diff --git a/helm/templates/roles.yaml b/helm/templates/roles.yaml index 39b2d72b..5c0e288a 100644 --- a/helm/templates/roles.yaml +++ b/helm/templates/roles.yaml @@ -11,6 +11,9 @@ rules: - apiGroups: ['apps'] resources: ['deployments'] verbs: ['get', 'list'] +- apiGroups: ['batch'] + resources: ['jobs'] + verbs: ['get', 'list', 'watch'] --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role @@ -57,4 +60,4 @@ rules: verbs: ["create"] - apiGroups: [""] resources: ["secrets"] - verbs: ["create", "get"] + verbs: ["create", "get", "update", "patch"] diff --git a/helm/templates/secret-headplane.yaml b/helm/templates/secret-headplane.yaml index 6a1423d6..3e628d44 100644 --- a/helm/templates/secret-headplane.yaml +++ b/helm/templates/secret-headplane.yaml @@ -21,4 +21,4 @@ stringData: redirect_uri: {{ .Values.headplane.oidc.redirect_uri | quote }} client_id: {{ .Values.headplane.oidc.client_id | quote }} headscale_api_key: {{ .Values.headplane.oidc.headscale_api_key | default "placeholder-token-will-be-replaced-by-job" | quote }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/helm/templates/secret-headscale-api-token.yaml b/helm/templates/secret-headscale-api-token.yaml new file mode 100644 index 00000000..3cf9a2d8 --- /dev/null +++ b/helm/templates/secret-headscale-api-token.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: headscale-api-token + namespace: {{ .Release.Namespace }} +type: Opaque +stringData: + HEADPLANE_OIDC__HEADSCALE_API_KEY: "placeholder-token-will-be-replaced-by-job" \ No newline at end of file diff --git a/helm/templates/secret-headscale.yaml b/helm/templates/secret-headscale.yaml index 7f59f2e1..c45b4b12 100644 --- a/helm/templates/secret-headscale.yaml +++ b/helm/templates/secret-headscale.yaml @@ -7,7 +7,7 @@ type: Opaque stringData: config.yaml: | {{- toYaml .Values.headscale.config | nindent 4 }} -{{- if .Values.headscale.oidc.enabled }} + {{- if .Values.headscale.oidc.enabled }} oidc: issuer: {{ .Values.headscale.oidc.issuer | quote }} client_id: {{ .Values.headscale.oidc.client_id | quote }} @@ -28,4 +28,4 @@ stringData: enabled: {{ .Values.headscale.oidc.pkce.enabled | quote }} method: {{ .Values.headscale.oidc.pkce.method | quote }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/helm/templates/statefulset-headplane.yaml b/helm/templates/statefulset-headplane.yaml index 6d4b6bfd..381696d8 100644 --- a/helm/templates/statefulset-headplane.yaml +++ b/helm/templates/statefulset-headplane.yaml @@ -35,7 +35,17 @@ spec: containers: - name: headplane image: {{ .Values.headplane.image }} + command: + - /bin/sh + - -c + - | + echo "Waiting for headscale-generate-token job to complete..." + kubectl wait --for=condition=complete job/headscale-generate-token -n {{ .Release.Namespace }} --timeout=300s + echo "Job completed successfully, starting headplane..." + exec node /app/build/server/index.js envFrom: + - secretRef: + name: headscale-api-token {{- if .Values.headplane.oidc.enabled }} - secretRef: name: {{ .Values.headplane.oidc.secret_name }} diff --git a/helm/test/templates/mock-oidc-server.yaml b/helm/test/templates/mock-oidc-server.yaml new file mode 100644 index 00000000..f4d4ec15 --- /dev/null +++ b/helm/test/templates/mock-oidc-server.yaml @@ -0,0 +1,110 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: mock-oidc-server +spec: + selector: + app: mock-oidc-server + ports: + - protocol: TCP + port: 80 + targetPort: 80 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: mock-oidc-config +data: + openid-configuration: | + { + "issuer": "http://mock-oidc-server", + "authorization_endpoint": "http://mock-oidc-server/oauth/authorize", + "token_endpoint": "http://mock-oidc-server/oauth/token", + "userinfo_endpoint": "http://mock-oidc-server/userinfo", + "jwks_uri": "http://mock-oidc-server/.well-known/jwks.json", + "response_types_supported": ["code", "token", "id_token"], + "subject_types_supported": ["public"], + "id_token_signing_alg_values_supported": ["RS256"], + "scopes_supported": ["openid", "profile", "email"], + "token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"], + "claims_supported": ["sub", "iss", "name", "email"] + } + jwks.json: | + { + "keys": [ + { + "kty": "RSA", + "kid": "test-key", + "use": "sig", + "alg": "RS256", + "n": "test-modulus", + "e": "AQAB" + } + ] + } + userinfo: | + { + "sub": "test-user-id", + "name": "Test User", + "email": "test@example.com" + } + nginx.conf: | + events { + worker_connections 1024; + } + http { + server { + listen 80; + + location /.well-known/openid-configuration { + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '{"issuer":"http://mock-oidc-server","authorization_endpoint":"http://mock-oidc-server/oauth/authorize","token_endpoint":"http://mock-oidc-server/oauth/token","userinfo_endpoint":"http://mock-oidc-server/userinfo","jwks_uri":"http://mock-oidc-server/.well-known/jwks.json","response_types_supported":["code","token","id_token"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"],"scopes_supported":["openid","profile","email"],"token_endpoint_auth_methods_supported":["client_secret_post","client_secret_basic"],"claims_supported":["sub","iss","name","email"]}'; + } + + location /.well-known/jwks.json { + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '{"keys":[{"kty":"RSA","kid":"test-key","use":"sig","alg":"RS256","n":"test-modulus","e":"AQAB"}]}'; + } + + location /userinfo { + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '{"sub":"test-user-id","name":"Test User","email":"test@example.com"}'; + } + + location / { + return 404; + } + } + } +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mock-oidc-server +spec: + replicas: 1 + selector: + matchLabels: + app: mock-oidc-server + template: + metadata: + labels: + app: mock-oidc-server + spec: + containers: + - name: mock-oidc-server + image: nginx:alpine + ports: + - containerPort: 80 + volumeMounts: + - name: nginx-config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + volumes: + - name: nginx-config + configMap: + name: mock-oidc-config \ No newline at end of file diff --git a/helm/test/templates/secret-oidc.yaml b/helm/test/templates/secret-oidc.yaml new file mode 100644 index 00000000..7bf6f80f --- /dev/null +++ b/helm/test/templates/secret-oidc.yaml @@ -0,0 +1,17 @@ +--- +{{- if or .Values.headplane.oidc.enabled .Values.headscale.oidc.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.headplane.oidc.secret_name | default .Values.headscale.oidc.secret_name | default "oidc-secrets" }} +type: Opaque +stringData: + {{- if .Values.headplane.oidc.enabled }} + HEADPLANE_OIDC__CLIENT_SECRET: "test-headplane-oidc-client-secret" + HEADPLANE_OIDC__CLIENT_ID: {{ .Values.headplane.oidc.client_id | default "test-headplane-oidc-client-id" | quote }} + {{- end }} + {{- if .Values.headscale.oidc.enabled }} + HEADSCALE_OIDC__CLIENT_SECRET: "test-headscale-oidc-client-secret" + HEADSCALE_OIDC__CLIENT_ID: {{ .Values.headscale.oidc.client_id | default "test-headscale-oidc-client-id" | quote }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/test/test-cases/00-lag0-test-case.yaml b/helm/test/test-cases/00-lag0-test-case.yaml new file mode 100644 index 00000000..8af843ef --- /dev/null +++ b/helm/test/test-cases/00-lag0-test-case.yaml @@ -0,0 +1,82 @@ +headplane: + image: ghcr.io/tale/headplane:0.6.0 + config: + server: + host: "0.0.0.0" + port: 3000 + cookie_secure: true + headscale: + url: "https://vpn.lag0.com.br" + config_path: "/etc/headscale/config.yaml" + config_strict: "true" + integration: + kubernetes: + enabled: true + validate_manifest: false + pod_name: "headplane-0" + secret: + name: headplane-secret + create: true +headscale: + image: headscale/headscale:0.26.0 + config: + server_url: https://headscale.lag0.com.br + listen_addr: 0.0.0.0:8080 + metrics_listen_addr: 0.0.0.0:9090 + grpc_listen_addr: 0.0.0.0:50443 + grpc_allow_insecure: false + policy: + mode: database + prefixes: + v4: 100.64.0.0/10 + v6: fd7a:115c:a1e0::/48 + allocation: sequential + database: + type: sqlite + debug: false + sqlite: + path: /etc/headscale/db.sqlite + noise: + private_key_path: /etc/headscale/noise_private.key + derp: + server: + enabled: true + region_id: 999 + region_code: "headscale" + region_name: "Headscale Embedded DERP" + stun_listen_addr: "0.0.0.0:3478" + private_key_path: /var/lib/headscale/derp_server_private.key + automatically_add_embedded_derp_region: true + ipv4: 1.2.3.4 + ipv6: 2001:db8::1 + urls: + - https://controlplane.tailscale.com/derpmap/default + paths: [] + dns: + magic_dns: true + base_domain: clients.lag0.com.br + nameservers: + global: + - 1.1.1.1 + - 8.8.8.8 +relay: + enabled: false +pvc: + enabled: true + name: headscale-config + accessModes: + - ReadWriteOnce + storage: 1Gi + annotations: + kustomize.toolkit.fluxcd.io/prune: disabled +# storageClassName: default + +ingress: + enabled: false + className: nginx + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-production" + labels: [] + headplaneDomain: "headscale.lag0.com.br" + headscaleDomain: "vpn.lag0.com.br" + tlsSecretName: "headplane-tls" diff --git a/helm/test/test-cases/01-definetelynobody-test-case.yaml b/helm/test/test-cases/01-definetelynobody-test-case.yaml new file mode 100644 index 00000000..1bfbc69e --- /dev/null +++ b/helm/test/test-cases/01-definetelynobody-test-case.yaml @@ -0,0 +1,18 @@ +headplane: + config: + headscale: + url: "https://vpn.test.example.com" + oidc: + # client_id: "test-headplane-client-id" + enabled: true + issuer: "http://mock-oidc-server" + redirect_uri: "https://headplane.test.example.com/admin/oidc/callback" +headscale: + config: + server_url: "https://vpn.test.example.com" + dns: + base_domain: "test.vpn" + oidc: + client_id: "test-headscale-client-id" + enabled: true + issuer: "http://mock-oidc-server" \ No newline at end of file diff --git a/helm/test/test-cases/02-AzSiAz-test-case.yaml b/helm/test/test-cases/02-AzSiAz-test-case.yaml new file mode 100644 index 00000000..51ba0816 --- /dev/null +++ b/helm/test/test-cases/02-AzSiAz-test-case.yaml @@ -0,0 +1,22 @@ +# Test case for PR #3 +headplane: + config: + headscale: + url: "https://vpn.test.example.com" + oidc: + client_id: "test-headplane-client-id" + enabled: true + issuer: "http://mock-oidc-server" + redirect_uri: "https://headplane.test.example.com/admin/oidc/callback" +headscale: + config: + server_url: "https://vpn.test.example.com" + dns: + base_domain: "test.vpn" + oidc: + pkce: + enabled: true + method: S256 + client_id: "test-headscale-client-id" + enabled: true + issuer: "http://mock-oidc-server" \ No newline at end of file