Skip to content

Commit 4a7160e

Browse files
fix(nats): extra accounts (#13)
match existing secret format add local e2e test coverage Signed-off-by: Frank Spitulski <fspitulski@nvidia.com>
1 parent fce38a2 commit 4a7160e

21 files changed

Lines changed: 1110 additions & 499 deletions

deploy/README.md

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,11 @@ CSC-side secrets to authorize incoming CPC leaf connections.
151151

152152
| Secret | Keys | Purpose |
153153
|--------|------|---------|
154-
| `nats-leaf-csc` | seed, pubkey | CPC to CSC leaf (CPC only) |
155-
| `nats-leaf-cpc-{id}` | seed, pubkey | CPC leaf key pair (CSC only, pubkey via auth-callout.extraEnvs) |
154+
| `nats-leaf-csc` | seed | CPC to CSC leaf (CPC only) |
155+
| `nats-leaf-cpc-{id}` | pubkey | CPC leaf user (CSC only, via auth-callout.extraEnvs) |
156+
| `nats-leaf-{account}-csc` | seed | Extra-account CPC to CSC leaf (CPC only) |
157+
| `nats-leaf-{account}-cpc-{id}` | pubkey | Extra-account CPC leaf user (CSC only, via auth-callout.extraEnvs) |
158+
| `nats-{account}-client` | seed, pubkey | Generated extra-account NKey client credential; only active when added to permissions |
156159

157160
### Generating Secrets
158161

@@ -167,17 +170,26 @@ An example script is provided to generate all required secrets to local files:
167170

168171
# Options:
169172
# -o, --output DIR Output root directory (default: deploy/secrets)
173+
# --extra-account NAME Generate client and CPC-to-CSC leaf keys for an extra account
170174
# -h, --help Show help message
171175

172176
# Examples:
173177
./scripts/generate-nkeys.sh # Generate CSC only
174178
./scripts/generate-nkeys.sh 1 2 3 # Generate CSC, CPC-1, CPC-2, CPC-3
179+
./scripts/generate-nkeys.sh --extra-account LaunchLayer 1 2
175180
./scripts/generate-nkeys.sh -o ./my-secrets 4 # Generate CSC and CPC-4 under ./my-secrets
176181
```
177182

178-
The script is additive. Existing cluster outputs are left unchanged. For each
179-
requested CPC, the same CPC-to-CSC leaf key pair is stored in CSC as
180-
`nats-leaf-cpc-{id}` and in that CPC as `nats-leaf-csc`.
183+
The script requires `nsc` and `nk` on `PATH`.
184+
185+
The script is additive. Existing cluster outputs are left unchanged except for
186+
unused leaf credential keys from older layouts. For each requested CPC, the
187+
CPC-to-CSC leaf seed is stored in that CPC as `nats-leaf-csc`; CSC stores only
188+
the matching public key as `nats-leaf-cpc-{id}`. For each extra account, the
189+
same split is used with `nats-leaf-{account}-csc` on the CPC and
190+
`nats-leaf-{account}-cpc-{id}` on CSC. Each cluster also gets a
191+
`nats-{account}-client` NKey pair for explicit client permissions and local
192+
functional tests; leaf credentials should not be reused as client credentials.
181193

182194
Generated secret files are written with mode `0600`, and generated secret
183195
directories are written with mode `0700`. Treat the full output directory as
@@ -197,7 +209,9 @@ deploy/secrets/
197209
│ ├── nats-mtls-sys-leaf/{seed,pubkey}
198210
│ ├── nats-surveyor/{seed,pubkey}
199211
│ ├── auth-callout-keys/{nkey-seed,issuer-seed,xkey-seed}
200-
│ └── nats-leaf-cpc-{id}/{seed,pubkey}
212+
│ ├── nats-{account}-client/{seed,pubkey}
213+
│ ├── nats-leaf-cpc-{id}/pubkey
214+
│ └── nats-leaf-{account}-cpc-{id}/pubkey
201215
└── cpc-{id}/
202216
└── nkeys/
203217
├── nats-auth-signing/{seed,pubkey}
@@ -209,7 +223,9 @@ deploy/secrets/
209223
├── nats-mtls-sys-leaf/{seed,pubkey}
210224
├── nats-surveyor/{seed,pubkey}
211225
├── auth-callout-keys/{nkey-seed,issuer-seed,xkey-seed}
212-
└── nats-leaf-csc/{seed,pubkey}
226+
├── nats-{account}-client/{seed,pubkey}
227+
├── nats-leaf-csc/seed
228+
└── nats-leaf-{account}-csc/seed
213229
```
214230

215231
## Chart Dependencies
@@ -351,7 +367,22 @@ eventBus:
351367
Kiwi: {} # Minimal account with defaults
352368
```
353369

354-
All properties are passed through to the NATS account configuration. On CPCs, `jetstream` is always forced to `false` and a JetStream domain mapping to CSC is automatically added.
370+
Extra account names must use letters and numbers only, start with a letter, and
371+
must not use built-in account names (`SYS`, `AUTH`, `AUTHX`, `CSC`, `CPC`) or
372+
start with `cpc` in any case.
373+
All properties are passed through to the NATS account configuration except
374+
`enabled`. On CPCs, `jetstream` is always forced to `false` and a JetStream
375+
domain mapping to CSC is automatically added. Every enabled extra account gets a
376+
CPC-to-CSC leaf connection by default. Provide the CPC seed env
377+
`LEAF_{ACCOUNT}_USER_SEED` from `nats-leaf-{account}-csc` and the CSC pubkey env
378+
`NKEY_LEAF_{ACCOUNT}_CPC_{id}_PUBKEY` from `nats-leaf-{account}-cpc-{id}`.
379+
The chart fails rendering if those explicit secret refs are missing, point at a
380+
different secret/key, or are marked optional.
381+
382+
The chart generates `eventBus.auth.permissions.nkey` entries named
383+
`leaf-cpc-{id}` and `leaf-{account}-cpc-{id}` for leaf authorization. Do not
384+
define manual NKey permission entries with those names; use distinct names for
385+
operator-managed clients.
355386

356387
### mTLS MQTT Endpoint
357388

deploy/nats-event-bus/templates/_helpers.tpl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,52 @@ CPC
1414
{{- end -}}
1515
{{- end -}}
1616

17+
{{/*
18+
extraAccountEnvName: Converts an extra account name into a stable env-var token.
19+
*/}}
20+
{{- define "nats-event-bus.extraAccountEnvName" -}}
21+
{{- regexReplaceAll "[^A-Z0-9]" (upper .) "_" -}}
22+
{{- end -}}
23+
24+
{{/*
25+
extraAccountSecretName: Converts an extra account name into a stable Kubernetes
26+
secret-name token.
27+
*/}}
28+
{{- define "nats-event-bus.extraAccountSecretName" -}}
29+
{{- trimAll "-" (regexReplaceAll "[^a-z0-9-]" (lower .) "-") -}}
30+
{{- end -}}
31+
32+
{{/*
33+
validateSecretKeyRef: Fails rendering unless a value is a required
34+
valueFrom.secretKeyRef pointing at the expected secret key.
35+
*/}}
36+
{{- define "nats-event-bus.validateSecretKeyRef" -}}
37+
{{- $value := .value -}}
38+
{{- $path := .path -}}
39+
{{- $secretName := .secretName -}}
40+
{{- $key := .key -}}
41+
{{- if not (kindIs "map" $value) -}}
42+
{{- fail (printf "%s must be a valueFrom.secretKeyRef for secret %s key %s." $path $secretName $key) -}}
43+
{{- end -}}
44+
{{- $valueFrom := get $value "valueFrom" | default dict -}}
45+
{{- if not (kindIs "map" $valueFrom) -}}
46+
{{- fail (printf "%s.valueFrom must be set to secretKeyRef for secret %s key %s." $path $secretName $key) -}}
47+
{{- end -}}
48+
{{- $secretKeyRef := get $valueFrom "secretKeyRef" | default dict -}}
49+
{{- if not (kindIs "map" $secretKeyRef) -}}
50+
{{- fail (printf "%s.valueFrom.secretKeyRef must reference secret %s key %s." $path $secretName $key) -}}
51+
{{- end -}}
52+
{{- if ne (get $secretKeyRef "name") $secretName -}}
53+
{{- fail (printf "%s.valueFrom.secretKeyRef.name must be %s." $path $secretName) -}}
54+
{{- end -}}
55+
{{- if ne (get $secretKeyRef "key") $key -}}
56+
{{- fail (printf "%s.valueFrom.secretKeyRef.key must be %s." $path $key) -}}
57+
{{- end -}}
58+
{{- if eq (toString (get $secretKeyRef "optional")) "true" -}}
59+
{{- fail (printf "%s.valueFrom.secretKeyRef.optional must not be true; missing leaf secrets must fail fast." $path) -}}
60+
{{- end -}}
61+
{{- end -}}
62+
1763
{{/*
1864
natsConfFields: Renders a NATS configuration block body from a flat map.
1965
Strings are quoted; booleans and numbers are unquoted.

deploy/nats-event-bus/templates/auth-callout-permissions.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@ Pubkeys use ${ENV_VAR} syntax for runtime expansion from secrets.
2020
{{- range .Values.eventBus.cpcIds }}
2121
{{- $autoNkeys = set $autoNkeys (printf "leaf-cpc-%s" .) (dict "public_key" (printf "${NKEY_LEAF_CPC_%s_PUBKEY}" .) "account" "CSC") }}
2222
{{- end }}
23+
{{- range $accountName, $config := .Values.eventBus.extraAccounts }}
24+
{{- $enabled := true -}}
25+
{{- if hasKey $config "enabled" -}}
26+
{{- $enabled = $config.enabled -}}
27+
{{- end -}}
28+
{{- if $enabled }}
29+
{{- $accountEnvName := include "nats-event-bus.extraAccountEnvName" $accountName -}}
30+
{{- $accountSecretName := include "nats-event-bus.extraAccountSecretName" $accountName -}}
31+
{{- $pubkeyEnvPrefix := printf "NKEY_LEAF_%s_CPC" $accountEnvName -}}
32+
{{- range $.Values.eventBus.cpcIds }}
33+
{{- $cpcId := toString . -}}
34+
{{- $pubkeyEnv := printf "%s_%s_PUBKEY" $pubkeyEnvPrefix $cpcId -}}
35+
{{- $autoNkeys = set $autoNkeys (printf "leaf-%s-cpc-%s" $accountSecretName $cpcId) (dict "public_key" (printf "${%s}" $pubkeyEnv) "account" $accountName) }}
36+
{{- end }}
37+
{{- end }}
38+
{{- end }}
2339
{{- end }}
2440
{{- $userNkeys := .Values.eventBus.auth.permissions.nkey | default dict -}}
2541
{{- $mergedNkeys := merge $userNkeys $autoNkeys -}}

deploy/nats-event-bus/templates/nats-accounts-config.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,19 @@ data:
2222
jetstream: true
2323
}
2424
{{- range $name, $config := $.Values.eventBus.extraAccounts }}
25+
{{- $enabled := true -}}
26+
{{- if hasKey $config "enabled" -}}
27+
{{- $enabled = $config.enabled -}}
28+
{{- end -}}
29+
{{- if $enabled }}
2530
{{ $name }} {
2631
{{- range $key, $value := $config }}
32+
{{- if and (ne $key "enabled") (ne $key "leaf") }}
2733
{{ $key }}: {{ $value }}
2834
{{- end }}
35+
{{- end }}
2936
}
37+
{{- end }}
3038
{{- end }}
3139
{{ else if eq .Values.eventBus.clusterType "cpc" }}
3240
{{- $clusterId := .Values.eventBus.clusterId -}}
@@ -79,16 +87,22 @@ data:
7987
]
8088
}
8189
{{- range $name, $config := $.Values.eventBus.extraAccounts }}
90+
{{- $enabled := true -}}
91+
{{- if hasKey $config "enabled" -}}
92+
{{- $enabled = $config.enabled -}}
93+
{{- end -}}
94+
{{- if $enabled }}
8295
{{ $name }} {
8396
jetstream: false
8497
mappings: {
8598
"$JS.API.>": "$JS.CSC.API.>"
8699
}
87100
{{- range $key, $value := $config }}
88-
{{- if ne $key "jetstream" }}
101+
{{- if and (ne $key "jetstream") (ne $key "enabled") (ne $key "leaf") }}
89102
{{ $key }}: {{ $value }}
90103
{{- end }}
91104
{{- end }}
92105
}
106+
{{- end }}
93107
{{- end }}
94108
{{- end }}

deploy/nats-event-bus/templates/nats-leafnodes-config.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,23 @@ data:
3030
{{- if not (empty $remoteTls) }}
3131
{{ include "nats-event-bus.natsConfBlock" (dict "name" "tls" "fields" $remoteTls) | nindent 8 }}{{- end }}
3232
}
33+
{{- range $name, $config := $.Values.eventBus.extraAccounts }}
34+
{{- $enabled := true -}}
35+
{{- if hasKey $config "enabled" -}}
36+
{{- $enabled = $config.enabled -}}
37+
{{- end -}}
38+
{{- if $enabled }}
39+
{{- $accountEnvName := include "nats-event-bus.extraAccountEnvName" $name -}}
40+
{{- $seedEnv := printf "LEAF_%s_USER_SEED" $accountEnvName -}}
41+
{{ printf "\n" }}
42+
{
43+
urls: [{{ $.Values.eventBus.cscEndpoint | quote }}]
44+
nkey: ${{ $seedEnv }}
45+
account: {{ $name | quote }}
46+
{{- if not (empty $remoteTls) }}
47+
{{ include "nats-event-bus.natsConfBlock" (dict "name" "tls" "fields" $remoteTls) | nindent 8 }}{{- end }}
48+
}
49+
{{- end }}
50+
{{- end }}
3351
]
3452
{{- end }}

deploy/nats-event-bus/templates/validation.yaml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,100 @@ This template emits no resources; it fails rendering on unsafe combinations.
77
*/}}
88
{{- $authCallout := index .Values "auth-callout" | default dict -}}
99
{{- $extraEnvs := get $authCallout "extraEnvs" | default dict -}}
10+
{{- $nats := .Values.nats | default dict -}}
11+
{{- $natsContainer := get $nats "container" | default dict -}}
12+
{{- $natsEnvs := get $natsContainer "env" | default dict -}}
13+
{{- $extraAccountEnvNames := dict -}}
14+
{{- $extraAccountSecretNames := dict -}}
15+
{{- $reservedAccountNames := dict "SYS" true "AUTH" true "AUTHX" true "CSC" true "CPC" true -}}
16+
{{- $cpcIds := dict -}}
17+
{{- range .Values.eventBus.cpcIds }}
18+
{{- $cpcId := toString . -}}
19+
{{- if not (regexMatch "^[a-z0-9]+$" $cpcId) -}}
20+
{{- fail (printf "eventBus.cpcIds includes %q, but CPC IDs must use lower-case letters and numbers only so generated secret names and env vars are valid." $cpcId) -}}
21+
{{- end -}}
22+
{{- if hasKey $cpcIds $cpcId -}}
23+
{{- fail (printf "eventBus.cpcIds includes duplicate ID %q." $cpcId) -}}
24+
{{- end -}}
25+
{{- $_ := set $cpcIds $cpcId true -}}
26+
{{- end -}}
27+
{{- range $accountName, $config := .Values.eventBus.extraAccounts }}
28+
{{- $enabled := true -}}
29+
{{- if hasKey $config "enabled" -}}
30+
{{- $enabled = $config.enabled -}}
31+
{{- end -}}
32+
{{- if $enabled -}}
33+
{{- if not (regexMatch "^[A-Za-z][A-Za-z0-9]*$" $accountName) -}}
34+
{{- fail (printf "eventBus.extraAccounts.%s is not a valid NATS account name. Use letters and numbers only, starting with a letter." $accountName) -}}
35+
{{- end -}}
36+
{{- if hasKey $reservedAccountNames (upper $accountName) -}}
37+
{{- fail (printf "eventBus.extraAccounts.%s conflicts with a built-in NATS account name." $accountName) -}}
38+
{{- end -}}
39+
{{- if regexMatch "^CPC" (upper $accountName) -}}
40+
{{- fail (printf "eventBus.extraAccounts.%s is not allowed because extra account names must not start with cpc." $accountName) -}}
41+
{{- end -}}
42+
{{- $envToken := include "nats-event-bus.extraAccountEnvName" $accountName -}}
43+
{{- $secretToken := include "nats-event-bus.extraAccountSecretName" $accountName -}}
44+
{{- if eq $envToken "" -}}
45+
{{- fail (printf "eventBus.extraAccounts includes %q, but its env token is empty after normalization." $accountName) -}}
46+
{{- end -}}
47+
{{- if eq $secretToken "" -}}
48+
{{- fail (printf "eventBus.extraAccounts includes %q, but its secret-name token is empty after normalization." $accountName) -}}
49+
{{- end -}}
50+
{{- if hasKey $extraAccountEnvNames $envToken -}}
51+
{{- fail (printf "eventBus.extraAccounts.%s normalizes to env token %s, which is already used by eventBus.extraAccounts.%s." $accountName $envToken (get $extraAccountEnvNames $envToken)) -}}
52+
{{- end -}}
53+
{{- if hasKey $extraAccountSecretNames $secretToken -}}
54+
{{- fail (printf "eventBus.extraAccounts.%s normalizes to secret token %s, which is already used by eventBus.extraAccounts.%s." $accountName $secretToken (get $extraAccountSecretNames $secretToken)) -}}
55+
{{- end -}}
56+
{{- $_ := set $extraAccountEnvNames $envToken $accountName -}}
57+
{{- $_ := set $extraAccountSecretNames $secretToken $accountName -}}
58+
{{- end -}}
59+
{{- end -}}
1060
{{- if and (eq .Values.eventBus.clusterType "csc") (gt (len .Values.eventBus.cpcIds) 0) -}}
1161
{{- range .Values.eventBus.cpcIds }}
1262
{{- $cpcId := toString . -}}
1363
{{- $envName := printf "NKEY_LEAF_CPC_%s_PUBKEY" $cpcId -}}
1464
{{- if not (hasKey $extraEnvs $envName) -}}
1565
{{- fail (printf "eventBus.cpcIds includes %q, but auth-callout.extraEnvs.%s is missing. Add a secretKeyRef to nats-leaf-cpc-%s key pubkey so CSC can authorize the CPC leaf connection." $cpcId $envName $cpcId) -}}
1666
{{- end -}}
67+
{{- include "nats-event-bus.validateSecretKeyRef" (dict "value" (get $extraEnvs $envName) "path" (printf "auth-callout.extraEnvs.%s" $envName) "secretName" (printf "nats-leaf-cpc-%s" $cpcId) "key" "pubkey") -}}
68+
{{- end -}}
69+
{{- end -}}
70+
{{- if eq .Values.eventBus.clusterType "cpc" -}}
71+
{{- range $accountName, $config := .Values.eventBus.extraAccounts }}
72+
{{- $enabled := true -}}
73+
{{- if hasKey $config "enabled" -}}
74+
{{- $enabled = $config.enabled -}}
75+
{{- end -}}
76+
{{- if $enabled -}}
77+
{{- $accountEnvName := include "nats-event-bus.extraAccountEnvName" $accountName -}}
78+
{{- $accountSecretName := include "nats-event-bus.extraAccountSecretName" $accountName -}}
79+
{{- $seedEnvName := printf "LEAF_%s_USER_SEED" $accountEnvName -}}
80+
{{- if not (hasKey $natsEnvs $seedEnvName) -}}
81+
{{- fail (printf "eventBus.extraAccounts.%s is enabled on a CPC cluster, but nats.container.env.%s is missing. Add a secretKeyRef to nats-leaf-%s-csc key seed so the CPC can connect this account leaf to CSC." $accountName $seedEnvName $accountSecretName) -}}
82+
{{- end -}}
83+
{{- include "nats-event-bus.validateSecretKeyRef" (dict "value" (get $natsEnvs $seedEnvName) "path" (printf "nats.container.env.%s" $seedEnvName) "secretName" (printf "nats-leaf-%s-csc" $accountSecretName) "key" "seed") -}}
84+
{{- end -}}
85+
{{- end -}}
86+
{{- end -}}
87+
{{- if and (eq .Values.eventBus.clusterType "csc") (gt (len .Values.eventBus.cpcIds) 0) -}}
88+
{{- range $accountName, $config := .Values.eventBus.extraAccounts }}
89+
{{- $enabled := true -}}
90+
{{- if hasKey $config "enabled" -}}
91+
{{- $enabled = $config.enabled -}}
92+
{{- end -}}
93+
{{- if $enabled -}}
94+
{{- $accountEnvName := include "nats-event-bus.extraAccountEnvName" $accountName -}}
95+
{{- $accountSecretName := include "nats-event-bus.extraAccountSecretName" $accountName -}}
96+
{{- range $.Values.eventBus.cpcIds }}
97+
{{- $cpcId := toString . -}}
98+
{{- $envName := printf "NKEY_LEAF_%s_CPC_%s_PUBKEY" $accountEnvName $cpcId -}}
99+
{{- if not (hasKey $extraEnvs $envName) -}}
100+
{{- fail (printf "eventBus.extraAccounts.%s is enabled on CSC, but auth-callout.extraEnvs.%s is missing. Add a secretKeyRef to nats-leaf-%s-cpc-%s key pubkey so CSC can authorize this extra-account CPC leaf." $accountName $envName $accountSecretName $cpcId) -}}
101+
{{- end -}}
102+
{{- include "nats-event-bus.validateSecretKeyRef" (dict "value" (get $extraEnvs $envName) "path" (printf "auth-callout.extraEnvs.%s" $envName) "secretName" (printf "nats-leaf-%s-cpc-%s" $accountSecretName $cpcId) "key" "pubkey") -}}
103+
{{- end -}}
104+
{{- end -}}
17105
{{- end -}}
18106
{{- end -}}

0 commit comments

Comments
 (0)