Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
4074ad1
Add test/test-cases
antoniolago Jun 22, 2025
ec7a428
add jobs logic to gha test
antoniolago Jun 22, 2025
a9838f1
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
c69d3f6
add AzSiAz test case
antoniolago Jun 22, 2025
45a5955
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
51ba3b9
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
23c3949
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
120bf68
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
236cdab
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
8eb6095
add future test case folder
antoniolago Jun 22, 2025
3daf0dc
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
76dc188
Update 01-definetelynobody-test-case.yaml
antoniolago Jun 22, 2025
14963c5
add test secret template
antoniolago Jun 22, 2025
47288bf
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
63c740d
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
7969d9a
add mock oidc server to pipeline
antoniolago Jun 22, 2025
f6af62d
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
075c873
Fix oidc mock
antoniolago Jun 22, 2025
21b6cc8
ct attempt
antoniolago Jun 22, 2025
96d2c6b
add api-token reference to headplane
antoniolago Jun 22, 2025
e58d0b0
Add future test case to test-cases
antoniolago Jun 22, 2025
cdf370a
Update 02-AzSiAz-test-case.yaml
antoniolago Jun 22, 2025
191023e
Actions jobs from sequential to parallel
antoniolago Jun 22, 2025
6e35677
Handle succeeded pod status in Helm chart workflow
antoniolago Jun 22, 2025
e2e92c0
Add check and creation for headscale-api-token secret
antoniolago Jun 22, 2025
3625e0c
Update 02-AzSiAz-test-case.yaml
antoniolago Jun 22, 2025
87a5bba
Update 02-AzSiAz-test-case.yaml
antoniolago Jun 22, 2025
cccd7a8
Refactor Headscale API token handling in Helm chart
antoniolago Jun 22, 2025
c012795
Update roles.yaml
antoniolago Jun 22, 2025
12cb7e2
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
4c9c0cc
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
823a88c
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
df04b25
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
6f12d29
move headscale_api_key to oidc-secrets
antoniolago Jun 22, 2025
0b21633
add headplane wait check for job to finish
antoniolago Jun 22, 2025
b80d1f9
Update secret-headplane.yaml
antoniolago Jun 22, 2025
dc632ca
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
ee67566
Update values.yaml
antoniolago Jun 22, 2025
866855d
Update publish-helm-chart.yml
antoniolago Jun 22, 2025
3eb4c0d
Update README.md.gotmpl
antoniolago Jun 22, 2025
319360c
update readme values
antoniolago Jun 23, 2025
553ba80
Update statefulset-headplane.yaml
antoniolago Jun 23, 2025
2706908
add placeholder headscale api token
antoniolago Jun 23, 2025
7bbc497
Update job.yaml
antoniolago Jun 23, 2025
2e9d46e
Update statefulset-headplane.yaml
antoniolago Jun 23, 2025
b478b4e
Update statefulset-headplane.yaml
antoniolago Jun 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 363 additions & 1 deletion .github/workflows/publish-helm-chart.yml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ helm uninstall headplane
| 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.client_id | string | `"PLACEHOLDER_USE_SECRET"` | |
| 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 | `""` | |
| 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"` | |
Expand Down
25 changes: 19 additions & 6 deletions templates/job.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,32 @@ 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
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 }}
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"
5 changes: 4 additions & 1 deletion templates/roles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -57,4 +60,4 @@ rules:
verbs: ["create"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "get"]
verbs: ["create", "get", "update", "patch"]
1 change: 1 addition & 0 deletions templates/secret-headplane.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 "hcap_placeholder_key_for_validation_only_12345678901234567890123456789012" | quote }}
{{- end }}
9 changes: 9 additions & 0 deletions templates/secret-headscale-api-token.yaml
Original file line number Diff line number Diff line change
@@ -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"
11 changes: 11 additions & 0 deletions templates/statefulset-headplane.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,21 @@ 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 }}
# optional: true
{{- end }}
{{- with .Values.headplane.envFrom }}
{{- toYaml . | nindent 10 }}
Expand Down
110 changes: 110 additions & 0 deletions test/templates/mock-oidc-server.yaml
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions test/templates/secret-oidc.yaml
Original file line number Diff line number Diff line change
@@ -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 }}
82 changes: 82 additions & 0 deletions test/test-cases/00-lag0-test-case.yaml
Original file line number Diff line number Diff line change
@@ -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"
18 changes: 18 additions & 0 deletions test/test-cases/01-definetelynobody-test-case.yaml
Original file line number Diff line number Diff line change
@@ -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"
22 changes: 22 additions & 0 deletions test/test-cases/02-AzSiAz-test-case.yaml
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 1 addition & 1 deletion values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ headplane:
# 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"
client_id: "PLACEHOLDER_USE_SECRET" #USE secret below instead
# Name of the secret containing OIDC credentials
secret_name: "oidc-secrets"
config:
Expand Down