Skip to content

Commit 415806a

Browse files
feat(helm): support existing token Secret (SOPS) and scope token RBAC to (#497)
1 parent 141e22b commit 415806a

7 files changed

Lines changed: 135 additions & 37 deletions

File tree

docs/existing-secret-sops.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Use an existing Secret for the token (SOPS / GitOps)
2+
3+
Instead of putting `clusterToken` / `patToken` in your values, point the chart
4+
at a Secret you manage. You encrypt only that Secret with
5+
[SOPS](https://github.com/getsops/sops); your values file stays token-free.
6+
7+
## The one flag that matters
8+
9+
Take the helm command from the dashboard's **Connect cluster** button and add:
10+
11+
```
12+
--set zxporter.existingSecret.name=<your-secret-name>
13+
```
14+
15+
That's it — the chart then reads the token from your Secret instead of creating
16+
its own, and no token ever appears in the helm command or values.
17+
18+
## Steps (SOPS + age)
19+
20+
```bash
21+
export NS=devzero-system
22+
23+
# 1. Create the SOPS-encrypted Secret (encrypt only the data; commit the .enc.yaml).
24+
age-keygen -o key.txt # prints: Public key: age1xxxx...
25+
cat > zxporter-token.secret.yaml <<'EOF'
26+
apiVersion: v1
27+
kind: Secret
28+
metadata:
29+
name: zxporter-token
30+
type: Opaque
31+
stringData:
32+
PAT_TOKEN: "dzp_your_pat" # or CLUSTER_TOKEN: "..."
33+
EOF
34+
sops --encrypt --age age1xxxx... --encrypted-regex '^(data|stringData)$' \
35+
zxporter-token.secret.yaml > zxporter-token.secret.enc.yaml
36+
rm zxporter-token.secret.yaml
37+
38+
# 2. Apply it into the namespace (server-side keeps plaintext out of annotations).
39+
kubectl create namespace $NS --dry-run=client -o yaml | kubectl apply -f -
40+
SOPS_AGE_KEY_FILE=$PWD/key.txt sops --decrypt zxporter-token.secret.enc.yaml \
41+
| kubectl apply --server-side -n $NS -f -
42+
43+
# 3. Run the dashboard's Connect-cluster helm command with the extra flag:
44+
# --set zxporter.existingSecret.name=zxporter-token
45+
```
46+
47+
**GitOps:** commit `zxporter-token.secret.enc.yaml` and let Argo CD (KSOPS /
48+
`argocd-vault-plugin`) or Flux (`decryption.provider: sops`) decrypt it at sync
49+
time; set `zxporter.existingSecret.name` in the HelmRelease/Application values.
50+
51+
## Values
52+
53+
```yaml
54+
zxporter:
55+
existingSecret:
56+
name: "" # your Secret; empty = use clusterToken/patToken
57+
clusterTokenKey: "CLUSTER_TOKEN"
58+
patTokenKey: "PAT_TOKEN"
59+
```
60+
61+
Provide one token — `CLUSTER_TOKEN` or `PAT_TOKEN`. Only used when
62+
`useSecretForToken: true` (the default).
63+
64+
## Gotchas
65+
66+
- **Secret must exist in the release namespace before install** (`secretKeyRef`
67+
is namespace-local).
68+
- **Name must differ from `tokenSecretName`** (default `devzero-zxporter-token`) —
69+
the chart manages that one itself. The chart fails fast if they match.
70+
- **Wrong/missing key = no auth, no crash** (keys are `optional`). Check pod logs
71+
for `no URL or token was configured`.

helm-chart/zxporter/templates/NOTES.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,23 @@ To learn more about the release, try:
77
$ helm status {{ .Release.Name }} -n {{ .Release.Namespace }}
88
$ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }}
99

10-
{{- if and (not .Values.zxporter.clusterToken) (not .Values.zxporter.patToken) }}
10+
{{- if and (not .Values.zxporter.clusterToken) (not .Values.zxporter.patToken) (not (dig "existingSecret" "name" "" .Values.zxporter)) }}
1111

1212
NOTE: No authentication token configuration detected.
1313

1414
ZXPorter requires either a cluster token or PAT token to authenticate with the DevZero platform.
1515
Please configure one of the following:
1616
1. Set zxporter.clusterToken with your cluster token from the DevZero dashboard
1717
2. Set zxporter.patToken with your Personal Access Token for automatic token exchange (recommended for production)
18+
3. Set zxporter.existingSecret.name to a pre-existing Secret (in this namespace) holding CLUSTER_TOKEN and/or PAT_TOKEN (recommended for SOPS/GitOps)
19+
20+
{{- end }}
21+
{{- if dig "existingSecret" "name" "" .Values.zxporter }}
22+
23+
NOTE: Using pre-existing token Secret "{{ dig "existingSecret" "name" "" .Values.zxporter }}".
24+
Ensure it exists in namespace {{ .Release.Namespace }} and contains at least one of:
25+
- {{ dig "existingSecret" "clusterTokenKey" "CLUSTER_TOKEN" .Values.zxporter }} (cluster token)
26+
- {{ dig "existingSecret" "patTokenKey" "PAT_TOKEN" .Values.zxporter }} (PAT token)
1827

1928
{{- end }}
2029

helm-chart/zxporter/templates/_helpers.tpl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,15 @@ app.kubernetes.io/instance: {{ .Release.Name }}
5252
Validate required configuration
5353
*/}}
5454
{{- define "zxporter.validateConfig" -}}
55-
{{- if and (empty .Values.zxporter.clusterToken) (empty .Values.zxporter.patToken) -}}
56-
{{- fail "ERROR: Either zxporter.clusterToken or zxporter.patToken must be provided. Please set one of these values in your values file." -}}
55+
{{- if and (empty .Values.zxporter.clusterToken) (empty .Values.zxporter.patToken) (empty (dig "existingSecret" "name" "" .Values.zxporter)) -}}
56+
{{- fail "ERROR: A token must be provided. Set one of zxporter.clusterToken, zxporter.patToken, or zxporter.existingSecret.name (a pre-existing Secret holding the token)." -}}
5757
{{- end -}}
58-
58+
59+
{{- $existingName := dig "existingSecret" "name" "" .Values.zxporter -}}
60+
{{- if and $existingName (eq $existingName .Values.zxporter.tokenSecretName) -}}
61+
{{- fail (printf "ERROR: zxporter.existingSecret.name (%q) must not equal zxporter.tokenSecretName. The chart creates and writes to that runtime Secret itself, so reusing the name would collide on install and let the controller overwrite your managed Secret." .Values.zxporter.tokenSecretName) -}}
62+
{{- end -}}
63+
5964
{{- if empty .Values.zxporter.kubeContextName -}}
6065
{{- fail "ERROR: zxporter.kubeContextName is required. Please provide a unique identifier for your cluster." -}}
6166
{{- end -}}

helm-chart/zxporter/templates/deployment.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,26 @@ spec:
6060
fieldRef:
6161
fieldPath: metadata.namespace
6262
{{- if .Values.zxporter.useSecretForToken }}
63+
{{- $existingName := dig "existingSecret" "name" "" .Values.zxporter }}
64+
{{- if $existingName }}
65+
# Tokens sourced from a pre-existing (e.g. SOPS-managed) Secret.
66+
# Both keys are optional so a Secret with only one of them is valid.
67+
# CLUSTER_TOKEN is taken from this Secret rather than the runtime Secret;
68+
# after a PAT exchange the controller still recovers the exchanged token
69+
# from the runtime Secret via the Kubernetes API (see custom.go).
70+
- name: CLUSTER_TOKEN
71+
valueFrom:
72+
secretKeyRef:
73+
name: {{ $existingName }}
74+
key: {{ dig "existingSecret" "clusterTokenKey" "CLUSTER_TOKEN" .Values.zxporter }}
75+
optional: true
76+
- name: PAT_TOKEN
77+
valueFrom:
78+
secretKeyRef:
79+
name: {{ $existingName }}
80+
key: {{ dig "existingSecret" "patTokenKey" "PAT_TOKEN" .Values.zxporter }}
81+
optional: true
82+
{{- else }}
6383
- name: CLUSTER_TOKEN
6484
valueFrom:
6585
secretKeyRef:
@@ -74,6 +94,7 @@ spec:
7494
key: PAT_TOKEN
7595
{{- end }}
7696
{{- end }}
97+
{{- end }}
7798
livenessProbe:
7899
httpGet:
79100
path: /healthz

helm-chart/zxporter/templates/secret-credentials.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{{- if .Values.zxporter.useSecretForToken }}
2+
{{- if not (dig "existingSecret" "name" "" .Values.zxporter) }}
23
{{- if or .Values.zxporter.clusterToken .Values.zxporter.patToken }}
34
# Input credentials Secret (user-managed)
45
# Contains initial PAT/CLUSTER tokens provided by user
@@ -19,4 +20,5 @@ data:
1920
PAT_TOKEN: {{ .Values.zxporter.patToken | b64enc | quote }}
2021
{{- end }}
2122
{{- end }}
23+
{{- end }}
2224
{{- end }}

helm-chart/zxporter/templates/zxporter-rbac.yaml

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -221,39 +221,6 @@ rules:
221221
- list
222222
- watch
223223
- update
224-
{{- if .Values.zxporter.useSecretForToken }}
225-
# Secret access for cluster token persistence when useSecretForToken is enabled
226-
227-
# General Secret create permission (needed to create runtime token secret)
228-
- apiGroups:
229-
- ""
230-
resources:
231-
- secrets
232-
verbs:
233-
- create
234-
235-
# Read-only access to credentials Secret (user-provided PAT/CLUSTER tokens)
236-
- apiGroups:
237-
- ""
238-
resourceNames:
239-
- {{ .Values.zxporter.tokenCredentialsSecretName }}
240-
resources:
241-
- secrets
242-
verbs:
243-
- get
244-
245-
# Read/write access to runtime token Secret (exchanged tokens)
246-
- apiGroups:
247-
- ""
248-
resourceNames:
249-
- {{ .Values.zxporter.tokenSecretName }}
250-
resources:
251-
- secrets
252-
verbs:
253-
- get
254-
- patch
255-
- update
256-
{{- end }}
257224
# Core Kubernetes resources (READ-ONLY monitoring)
258225
- apiGroups:
259226
- ""

helm-chart/zxporter/values.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,29 @@ zxporter:
4343
logLevel: "error"
4444
# Disable GPU metrics collection (set to "true" for clusters without GPUs)
4545
disableGPUMetrics: ""
46+
# Point to a pre-existing Secret that already holds the auth token(s), instead of
47+
# passing clusterToken/patToken through this values file. This is intended for
48+
# GitOps/SOPS workflows where you want to encrypt only the Secret, not the whole
49+
# values file.
50+
#
51+
# When existingSecret.name is set:
52+
# - the chart does NOT create the credentials Secret (you manage it yourself)
53+
# - the controller sources PAT_TOKEN and/or CLUSTER_TOKEN from this Secret
54+
# - it satisfies the "a token is required" validation
55+
#
56+
# Requirements:
57+
# - The Secret must already exist in the release namespace (secretKeyRef is
58+
# namespace-local) before the controller pod starts.
59+
# - It must contain at least one of the keys below. Both env vars are injected
60+
# with optional=true, so a Secret with only one key is fine.
61+
# - Only meaningful when useSecretForToken=true.
62+
existingSecret:
63+
# Name of the pre-existing Secret. Leave empty to use clusterToken/patToken above.
64+
name: ""
65+
# Key within the Secret holding the cluster token (direct-token installs).
66+
clusterTokenKey: "CLUSTER_TOKEN"
67+
# Key within the Secret holding the PAT token (PAT-exchange installs).
68+
patTokenKey: "PAT_TOKEN"
4669
# Resource names for token storage (configurable for different environments)
4770
# Input credentials Secret: Contains initial PAT/CLUSTER tokens (user-managed)
4871
tokenCredentialsSecretName: "devzero-zxporter-credentials"

0 commit comments

Comments
 (0)