Compare commits

...
Author SHA1 Message Date
Cédric Verstraeten c42388b96f Merge pull request #204 from kerberos-io/feature/hub-passkey-configuration
Add Hub API passkey configuration and bump chart to 0.167.0
2026-10-26 14:47:35 +01:00
Cédric VerstraetenandCopilot 04d6990460 feat(hub): configure API passkeys and bump chart to 0.167.0
Add opt-in API-only WebAuthn configuration, value validation, deployment documentation and rendering checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-26 13:33:22 +00:00
6 changed files with 202 additions and 1 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.166.0
version: 0.167.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
+55
View File
@@ -31,6 +31,61 @@ Uninstall the Kerberos Hub chart
helm uninstall hub -n kerberos-hub
### Passkeys
Users and sub-users can create and manage their own passkeys in **Profile >
Passkeys**, then use **Sign in with a passkey** for passwordless authentication
with device PIN/biometric verification. Existing password/MFA sign-in remains
available. Enrollment and removal require the current password and an
authenticator-app code when MFA is enabled.
Deploy Hub API and frontend images that support passkeys before enabling this
configuration; the chart version alone does not update the service image tags.
Passkeys are disabled by default. Configure their trust explicitly:
```yaml
kerberoshub:
api:
passkeys:
enabled: true
rpId: "hub.example.com"
origins:
- "https://hub.example.com"
rpName: "Kerberos Hub"
```
| Value | Default | Purpose |
| --- | --- | --- |
| `kerberoshub.api.passkeys.enabled` | `false` | Inject passkey configuration into Hub API pods only |
| `kerberoshub.api.passkeys.rpId` | `""` | Required when enabled; sets `PASSKEY_RP_ID` |
| `kerberoshub.api.passkeys.origins` | `[]` | Required when enabled; exact trusted frontend origins, joined with commas into `PASSKEY_ORIGINS` |
| `kerberoshub.api.passkeys.rpName` | `"Kerberos Hub"` | Display name; sets `PASSKEY_RP_NAME` |
Use the **frontend's public hostname and origins**, not the API URL. Origins
must have no path or trailing slash. Each origin hostname must equal the RP ID
or be its subdomain. Include only trusted origins, especially when sharing an
RP ID across tenant subdomains. Keep the RP ID stable: changing it makes
existing passkeys unusable. The API validates the RP/origin relationship at
runtime; missing/invalid configuration makes passkeys unavailable without
affecting password/MFA sign-in.
HTTPS and a compatible browser are required. Local development may use
`rpId: "localhost"` with `origins: ["http://localhost:4200"]`.
The frontend displays translated cancellation, verification and unavailable
messages when a ceremony cannot complete.
Apply values with a Helm upgrade or GitOps sync; changes trigger an API rollout.
If these variables were previously configured through `kerberoshub.extraEnv`,
remove those entries when enabling the dedicated settings to avoid duplicate
environment variables. Setting `enabled: false` omits the chart-managed
variables; any passkey variables supplied through `extraEnv` still take effect.
The API's MongoDB identity needs reads on `users`, CRUD on `passkeys`, and
insert/find-and-delete on `passkey_challenges`. It attempts to create the
`passkey_challenge_expiry_ttl` index on `expiresAt` with
`expireAfterSeconds: 0`; expiration and single-use enforcement work without the
index, but the index is needed for automatic expired-challenge cleanup.
### Sequence logging
Set `kerberospipeline.sequence.logLevel` to control the Sequence service's
@@ -436,6 +436,15 @@ spec:
- name: MFA_ISSUER
value: "{{ .Values.kerberoshub.api.mfaIssuer }}"
{{- if .Values.kerberoshub.api.passkeys.enabled }}
- name: PASSKEY_RP_ID
value: {{ required "kerberoshub.api.passkeys.rpId is required when passkeys are enabled" .Values.kerberoshub.api.passkeys.rpId | quote }}
- name: PASSKEY_ORIGINS
value: {{ required "kerberoshub.api.passkeys.origins is required when passkeys are enabled" .Values.kerberoshub.api.passkeys.origins | join "," | quote }}
- name: PASSKEY_RP_NAME
value: {{ .Values.kerberoshub.api.passkeys.rpName | quote }}
{{- end }}
# AWS (Legacy, use Kerberos Vault instead)
- name: AWS_REGION
value: "{{ .Values.kerberoshub.api.aws.region }}"
+32
View File
@@ -62,6 +62,38 @@
"api": {
"type": "object",
"properties": {
"passkeys": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {"type": "boolean"},
"rpId": {
"type": "string",
"description": "Stable WebAuthn relying-party hostname, without scheme, port or path. Must match each frontend origin's hostname or be its parent domain."
},
"origins": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^https://[^/?#]+$|^http://localhost(:[0-9]+)?$"
},
"description": "Exact trusted frontend origins, with no path or trailing slash. HTTPS is required except for HTTP localhost development."
},
"rpName": {"type": "string"}
},
"if": {
"required": ["enabled"],
"properties": {"enabled": {"const": true}}
},
"then": {
"required": ["rpId", "origins"],
"properties": {
"rpId": {"minLength": 1},
"origins": {"minItems": 1}
}
}
},
"corsAllowedOrigins": {
"type": "array",
"items": {
+7
View File
@@ -392,6 +392,13 @@ kerberoshub:
# MFA issuer name
mfaIssuer: "yourdomain.com"
# WebAuthn passkeys for users and sub-users; requires compatible API/frontend images.
# Use the public frontend hostname/origins, not the API URL. Keep rpId stable.
passkeys:
enabled: false
rpId: "" # e.g. "hub.example.com", without scheme, port or path.
origins: [] # e.g. ["https://hub.example.com"]; exact origins, no trailing slash.
rpName: "Kerberos Hub"
# Admin API's are made available for automation of Kerberos Hub.
# To access those API's (e.g. creation of owner users), an API key needs to be provided.
apiKey: "a-random-admin-api-key"
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -euo pipefail
CHART_DIR="${1:-charts/hub}"
extract_passkeys() {
awk '
/^---/ { deployment=0; name=""; variable="" }
/^kind: Deployment$/ { deployment=1 }
deployment && /^ name:/ { name=$2 }
deployment && /- name: PASSKEY_/ { variable=$3; next }
variable != "" && /value:/ {
value=$0
sub(/^[[:space:]]*value: /, "", value)
print name, variable, value
variable=""
}
' | sort
}
assert_passkeys() {
local rendered="$1" expected="$2" actual count expected_count
actual="$(printf '%s\n' "$rendered" | extract_passkeys)"
if [[ "$actual" != "$expected" ]]; then
printf 'Unexpected passkey environment:\n%s\nExpected:\n%s\n' "$actual" "$expected" >&2
return 1
fi
count="$(printf '%s\n' "$rendered" | awk '/- name: PASSKEY_/ { count++ } END { print count+0 }')"
expected_count="$(printf '%s\n' "$expected" | awk 'NF { count++ } END { print count+0 }')"
if [[ "$count" != "$expected_count" ]]; then
echo "Passkey variables leaked outside the expected Deployment or were duplicated" >&2
return 1
fi
}
CONFIGURED_FLAGS=(
--set-json kerberoshub.api.passkeys.enabled=true
--set-json 'kerberoshub.api.passkeys.rpId="example.com"'
--set-json 'kerberoshub.api.passkeys.origins=["https://hub.example.com","https://legacy.example.com:8443"]'
--set-json 'kerberoshub.api.passkeys.rpName="Example Hub"'
)
expected="$(printf '%s\n' \
'hub-api PASSKEY_ORIGINS "https://hub.example.com,https://legacy.example.com:8443"' \
'hub-api PASSKEY_RP_ID "example.com"' \
'hub-api PASSKEY_RP_NAME "Example Hub"' | sort)"
for mode in all ui pipeline; do
default_render="$(helm template hub "$CHART_DIR" --set "mode=$mode")"
assert_passkeys "$default_render" ""
configured_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${CONFIGURED_FLAGS[@]}")"
if [[ "$mode" == pipeline ]]; then
assert_passkeys "$configured_render" ""
else
assert_passkeys "$configured_render" "$expected"
fi
echo "OK ($mode): defaults opt out; enabled settings affect Hub API only"
done
disabled_render="$(helm template hub "$CHART_DIR" "${CONFIGURED_FLAGS[@]}" --set kerberoshub.api.passkeys.enabled=false)"
assert_passkeys "$disabled_render" ""
localhost_render="$(helm template hub "$CHART_DIR" --set mode=ui \
--set kerberoshub.api.passkeys.enabled=true \
--set-string kerberoshub.api.passkeys.rpId=localhost \
--set-json 'kerberoshub.api.passkeys.origins=["http://localhost:4200"]')"
assert_passkeys "$localhost_render" "$(printf '%s\n' \
'hub-api PASSKEY_ORIGINS "http://localhost:4200"' \
'hub-api PASSKEY_RP_ID "localhost"' \
'hub-api PASSKEY_RP_NAME "Kerberos Hub"' | sort)"
legacy_render="$(helm template hub "$CHART_DIR" --set mode=ui \
--set-json 'kerberoshub.extraEnv=[{"name":"PASSKEY_RP_ID","value":"example.com"}]')"
printf '%s\n' "$legacy_render" | grep -q -- '- name: PASSKEY_RP_ID'
expect_invalid() {
local output
if output="$(helm template hub "$CHART_DIR" "${CONFIGURED_FLAGS[@]}" "$@" 2>&1)"; then
echo "Invalid passkey settings unexpectedly rendered: $*" >&2
return 1
fi
if [[ "$output" != *"passkeys"* ]]; then
printf 'Rendering failed for an unexpected reason:\n%s\n' "$output" >&2
return 1
fi
}
expect_invalid --set-string kerberoshub.api.passkeys.rpId=
expect_invalid --set-json 'kerberoshub.api.passkeys.origins=[]'
expect_invalid --set-json 'kerberoshub.api.passkeys.origins=["https://hub.example.com/"]'
expect_invalid --set-json 'kerberoshub.api.passkeys.origins=["http://hub.example.com"]'
expect_invalid --set-json 'kerberoshub.api.passkeys.origins=["https://hub.example.com","https://hub.example.com"]'
expect_invalid --set-json 'kerberoshub.api.passkeys.origins="https://hub.example.com"'
expect_invalid --set-json 'kerberoshub.api.passkeys.enabled="true"'
expect_invalid --set-json 'kerberoshub.api.passkeys.rpId=123'
expect_invalid --set-json 'kerberoshub.api.passkeys.rpName=123'
expect_invalid --set-string kerberoshub.api.passkeys.origin=https://hub.example.com
echo "All Hub passkey configuration checks passed."