Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .agents/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ editing templates or values.
`authup.tplvalues.render`, so umbrella charts can inject template
expressions (the PrivateAIM lesson: their untemplatable `existingSecret`
forced a hardcoded-names table).
`server.route.enabled` / `adminConsole.route.enabled` extend this to a
BOOLEAN, read through `authup.flag`. That reader is strict by necessity:
the schema is widened to `[boolean, string]` so it no longer rejects
garbage, and a rendered `"false"` is a non-empty (truthy) string, so a
plain `if` would create the route exactly when the parent switched it off.
All six read sites (2 HTTPRoutes, 2 validations, 2 NOTES) convert together
or the sub-path catch-all guard of rule 18 stops covering umbrella users.
17. **`global` must stay open in the schema.** helm copies a parent chart's
ENTIRE `global` map into every subchart before validating that subchart's
schema, so `additionalProperties: false` there makes the chart
Expand Down
4 changes: 2 additions & 2 deletions .agents/references/authup.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
Repo: https://github.com/authup/authup (local checkout commonly at
`/opt/projects/authup/authup`). The chart encodes facts about the app; verify
against these sources when authup releases change behavior. Pinned against the
v1.0.0-beta.62 line (chart `appVersion`).
v1.0.0-beta.63 line (chart `appVersion`).

## Image / entrypoint contract

| Fact | authup source | Chart counterpart |
|---|---|---|
| One image `authup/authup`, arg-dispatched entrypoint (`server/core start`, `client/admin-console start`, `server/core migration run`, `server/core healthcheck`) | `Dockerfile`, `entrypoint.sh` (repo root) | `args` in `templates/{server,admin-console}/deployment.yaml`, `server/migration-job.yaml` |
| Entrypoint force-exports `PORT=3000` / `NUXT_PORT=3000` (chart-set PORT is dead) | `entrypoint.sh` | containerPort pinned 3000 everywhere |
| Image runs as root; writable paths `/usr/src/app/writable` + npm cache | `Dockerfile` (`WRITABLE_DIRECTORY_PATH`, no `USER`) | emptyDir mounts + `npm_config_cache=/tmp/.npm-cache`; root securityContext default |
| Image runs as root; writable directory `/var/lib/authup` (moved there from `/usr/src/app/writable` in v1.0.0-beta.63, authup/authup#3474) + npm cache. The CODE default is still `<rootPath>/writable`, so only the image sets the FHS path | `Dockerfile` (`WRITABLE_DIRECTORY_PATH`, no `USER`), `apps/server-core/src/app/modules/config/read/env.ts` | emptyDir mounts + `npm_config_cache=/tmp/.npm-cache`; root securityContext default. The chart SETS `WRITABLE_DIRECTORY_PATH` to the path it mounts instead of inheriting the image default, so it works on either side of that bump and with a pinned older `image.tag` |
| `latest`/`<version>`/`beta`/`next` tags | `.github/workflows/release.yml`, `docker-nightly.yml` | `image.tag` defaults to `Chart.AppVersion` |
| `authup` CLI supervisor NOT routable through the entrypoint | `entrypoint.sh` case statement | chart never offers a combined pod |
| An unknown service arg EXITS 1 since beta.59 (it used to exit 0 and start nothing); `client/web` was renamed `client/admin-console` with no alias | `entrypoint.sh` `*)` branch | chart already passes `client/admin-console` |
Expand Down
21 changes: 21 additions & 0 deletions .agents/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ helm template t charts/authup --set server.publicUrl=auth.example.com # s
helm template t charts/authup --set postgresql.enabled=false --set externalDatabase.host=db # extdb w/o password
helm template t charts/authup --set server.ingress.enabled=true # ingress w/o hostname
helm template t charts/authup --set server.config.PUBLIC_URL=http://x # first-class collision
helm template t charts/authup --set server.config.WRITABLE_DIRECTORY_PATH=/x # ditto; the chart pins this one to the path it mounts
helm template t charts/authup --set 'server.route.enabled=yes' # flag that is neither true nor false
helm template t charts/authup --set adminConsole.enabled=false --set adminConsole.route.enabled=yes # ditto: validated even with the component off
helm template t charts/authup --set server.theme.enabled=true # theme with no carrier
helm template t charts/authup --set server.theme.enabled=true --set server.theme.title=X --set server.theme.existingConfigMap=cm # manifest + existing CM
helm template t charts/authup --set server.theme.enabled=true --set server.theme.logo=logo.svg # asset outside assets/
Expand All @@ -62,6 +65,24 @@ case needs its own line: only the origin reaches the HTTPRoute hostname, and the
dropped path is exactly what turns the rule into a catch-all. Adding
`--set 'server.route.matches[0].path.value=/auth'` must make both RENDER.

`server.route.enabled` / `adminConsole.route.enabled` accept a tpl-rendered
string, so an umbrella can drive them from one of its own switches. `--set-string`
cannot carry `{{ }}` (helm fails parsing on the closing brace), so both directions
go through a values file, and BOTH are needed: a rendered `"false"` is a non-empty
string, which a Go template `if` reads as true.

```bash
printf 'global:\n gw:\n enabled: false\nserver:\n route:\n enabled: "{{ .Values.global.gw.enabled }}"\n' \
| helm template t charts/authup -f - | grep -c 'kind: HTTPRoute' # must be 0
printf 'global:\n gw:\n enabled: true\nserver:\n publicUrl: https://auth.example.com\n route:\n enabled: "{{ .Values.global.gw.enabled }}"\n parentRefs:\n - name: gw\n' \
| helm template t charts/authup -f - | grep -c 'kind: HTTPRoute' # must be 1
```

The sub-path catch-all guard and the NOTES path-prefix warning read the same
flag, so all six read sites convert together: leave one raw and an umbrella-driven
route renders unguarded. `ci/default-values.yaml` carries the false direction as
the in-repo regression guard.

Umbrella use is part of the contract: `global` must stay open. Render a throwaway
parent chart with authup in `charts/` and an unrelated global (`global.myOrgKey`)
whenever the schema generation changes; `ci/default-values.yaml` carries a stray
Expand Down
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ fail-closed semantics (an invalid file aborts boot) and camelCase keys.
- Hardened `podSecurityContext` / `containerSecurityContext` defaults
(runAsNonRoot 1000, readOnlyRootFilesystem, drop ALL, seccompRuntimeDefault) —
with the caveat that the upstream image runs as root and `npm` wants a writable
HOME: the chart mounts emptyDirs at `/usr/src/app/writable` and `/tmp`, sets
HOME: the chart mounts emptyDirs at `/var/lib/authup` and `/tmp`, sets
`npm_config_cache=/tmp/.npm-cache`, and documents that full hardening is
best-effort until upstream ships a non-root image (tracked as an upstream issue).
- `/metrics` is unauthenticated: `metrics.serviceMonitor` targets the Service
Expand Down
18 changes: 18 additions & 0 deletions charts/authup/BREAKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@
This chart uses `0.major.minor` versioning while below 1.0.0: breaking changes
land on the middle digit. Every entry lists the value migrations required.

## Next release (unreleased)

- The writable directory moves from `/usr/src/app/writable` to `/var/lib/authup`,
following the image (authup/authup#3474, shipped in v1.0.0-beta.63). The chart
mounts an emptyDir there, so nothing persists across the change; only a
`server.extraVolumeMounts` / `server.extraVolumes` entry aimed at the old path
needs updating, along with anything reading the container's log files by path.
- The chart now SETS `WRITABLE_DIRECTORY_PATH` to the path it mounts instead of
inheriting the image default, so it works with a pinned older `image.tag` too.
As a consequence `server.config.WRITABLE_DIRECTORY_PATH` now fails the render:
it would have emitted a duplicate ConfigMap key and pointed the server at a
path the chart mounts nothing at, which fails silently (production logs on the
container layer, file provisioning scanning a directory that does not exist).
To move the directory anyway, set it through `server.extraEnvVars` and mount
the same path with `server.extraVolumeMounts`; `server.provisioning` then needs
its source (ConfigMap or Secret) re-mounted at `<new path>/provisioning` by
hand, because the chart's own provisioning mount stays where the chart puts it.

## 0.2.0 (unreleased)

Follows the upstream rename of the admin UI app (authup/authup#3370) and its
Expand Down
10 changes: 5 additions & 5 deletions charts/authup/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Authup is an authentication & authorization system. This chart
with optional built-in PostgreSQL, MySQL and Valkey instances.
type: application
version: 0.2.2
appVersion: "1.0.0-beta.62"
appVersion: "1.0.0-beta.63"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
kubeVersion: ">=1.25.0-0"
home: https://authup.org
icon: https://raw.githubusercontent.com/authup/helm/master/assets/icon.svg
Expand All @@ -32,8 +32,8 @@ annotations:
url: https://github.com/authup/helm
artifacthub.io/changes: |
- kind: fixed
description: A parent chart's own global.* keys no longer fail schema validation
description: The writable directory moves to /var/lib/authup and WRITABLE_DIRECTORY_PATH is set to the path the chart mounts, so an image default change cannot silently stop file provisioning
- kind: changed
description: Track authup v1.0.0-beta.63, whose image writes to /var/lib/authup
- kind: added
description: server.route / adminConsole.route accept matches and filters
- kind: added
description: An HTTPRoute for a sub-path public URL without matches now fails the render
description: server.route.enabled / adminConsole.route.enabled accept a tpl-rendered string, so an umbrella chart can drive them from a global
Comment thread
coderabbitai[bot] marked this conversation as resolved.
8 changes: 4 additions & 4 deletions charts/authup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# authup

![Version](https://img.shields.io/badge/Version-0.2.2?style=flat-square&color=informational) <!-- x-release-please-version -->
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-beta.62](https://img.shields.io/badge/AppVersion-1.0.0--beta.62-informational?style=flat-square)
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-beta.63](https://img.shields.io/badge/AppVersion-1.0.0--beta.63-informational?style=flat-square)

Authup is an authentication & authorization system. This chart deploys the server-core IdP/API service and the client-admin-console admin UI, with optional built-in PostgreSQL, MySQL and Valkey instances. It deploys:

Expand Down Expand Up @@ -230,7 +230,7 @@ Kubernetes: `>=1.25.0-0`
| adminConsole.resources | object | `{"limits":{"memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | UI container resources |
| adminConsole.revisionHistoryLimit | int | `3` | Deployment revision history limit |
| adminConsole.route.annotations | object | `{}` | HTTPRoute annotations |
| adminConsole.route.enabled | bool | `false` | Create a Gateway API HTTPRoute for the UI |
| adminConsole.route.enabled | bool | `false` | Create a Gateway API HTTPRoute for the UI (tpl-rendered: a string rendering to "true" enables it, so an umbrella chart can drive this from one of its own switches; "false" and "" disable it, anything else fails the render) |
| adminConsole.route.filters | list | `[]` | Rule filters (tpl-rendered), e.g. a URLRewrite stripping a path prefix |
| adminConsole.route.hostnames | list | `[]` | Route hostnames ([] = derived from adminConsole.publicUrl / ingress hostname; only the host is kept, a public URL path is dropped and needs its own matches entry) |
| adminConsole.route.matches | list | `[]` | Rule matches (tpl-rendered); [] is the Gateway API default, PathPrefix "/" |
Expand Down Expand Up @@ -349,7 +349,7 @@ Kubernetes: `>=1.25.0-0`
| server.command | list | `[]` | Override the container command |
| server.config | object | `{}` | Extra environment variables rendered literally into the env ConfigMap (map of NAME: value) for options without first-class values, e.g. AUTH_CONSOLE_PATH / ACCOUNT_CONSOLE_PATH, which replace a served console with your own build (pair them with extraVolumes; the substituted package owns the login flow, so use server.theme for branding instead) |
| server.configuration | string | `""` | Content of an authup.server.core.conf mounted into the working directory for file-only options (middleware objects, per-field SMTP, CORS allowlist). Environment variables always win over file values. |
| server.containerSecurityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"enabled":true,"readOnlyRootFilesystem":false,"runAsNonRoot":false,"runAsUser":0,"seccompProfile":{"type":"RuntimeDefault"}}` | Container security context. The upstream image runs as root and needs a writable npm cache; the chart mounts emptyDirs at /usr/src/app/writable and /tmp to keep readOnlyRootFilesystem viable. |
| server.containerSecurityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"enabled":true,"readOnlyRootFilesystem":false,"runAsNonRoot":false,"runAsUser":0,"seccompProfile":{"type":"RuntimeDefault"}}` | Container security context. The upstream image runs as root and needs a writable npm cache; the chart mounts emptyDirs at /var/lib/authup and /tmp to keep readOnlyRootFilesystem viable. |
| server.customLivenessProbe | object | `{}` | Custom liveness probe |
| server.customReadinessProbe | object | `{}` | Custom readiness probe |
| server.customStartupProbe | object | `{}` | Custom startup probe overriding the structured one |
Expand Down Expand Up @@ -434,7 +434,7 @@ Kubernetes: `>=1.25.0-0`
| server.resources | object | `{"limits":{"memory":"2Gi"},"requests":{"cpu":"250m","memory":"512Mi"}}` | Server container resources |
| server.revisionHistoryLimit | int | `3` | Deployment revision history limit |
| server.route.annotations | object | `{}` | HTTPRoute annotations |
| server.route.enabled | bool | `false` | Create a Gateway API HTTPRoute for server-core |
| server.route.enabled | bool | `false` | Create a Gateway API HTTPRoute for server-core (tpl-rendered: a string rendering to "true" enables it, so an umbrella chart can drive this from one of its own switches; "false" and "" disable it, anything else fails the render) |
| server.route.filters | list | `[]` | Rule filters (tpl-rendered), e.g. a URLRewrite stripping a path prefix |
| server.route.hostnames | list | `[]` | Route hostnames ([] = derived from server.publicUrl / ingress hostname; only the host is kept, a public URL path is dropped and needs its own matches entry) |
| server.route.matches | list | `[]` | Rule matches (tpl-rendered); [] is the Gateway API default, PathPrefix "/" |
Expand Down
6 changes: 6 additions & 0 deletions charts/authup/ci/default-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
# reject globals this chart does not declare.
global:
umbrellaProbe: parent-injected
# A templated route.enabled rendering to "false" is the second umbrella guard:
# the schema must accept the string, and the render must produce no HTTPRoute
# (a rendered "false" is a non-empty, i.e. truthy, string to a Go template).
gatewayApiProbe: false
postgresql:
persistence:
enabled: false
Expand All @@ -17,3 +21,5 @@ adminConsole:
requests:
cpu: 25m
memory: 128Mi
route:
enabled: '{{ .Values.global.gatewayApiProbe }}'
4 changes: 2 additions & 2 deletions charts/authup/templates/NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ small deployments. For production, consider an external database
{{- end }}

{{- $prefixed := list }}
{{- if and .Values.server.enabled .Values.server.route.enabled (urlParse $serverUrl).path (ne (urlParse $serverUrl).path "/") }}
{{- if and .Values.server.enabled (include "authup.flag" (dict "value" .Values.server.route.enabled "context" $ "key" "server.route.enabled")) (urlParse $serverUrl).path (ne (urlParse $serverUrl).path "/") }}
{{- $prefixed = append $prefixed (printf "server.route %s" (urlParse $serverUrl).path) }}
{{- end }}
{{- if and .Values.adminConsole.enabled .Values.adminConsole.route.enabled (urlParse $uiUrl).path (ne (urlParse $uiUrl).path "/") }}
{{- if and .Values.adminConsole.enabled (include "authup.flag" (dict "value" .Values.adminConsole.route.enabled "context" $ "key" "adminConsole.route.enabled")) (urlParse $uiUrl).path (ne (urlParse $uiUrl).path "/") }}
{{- $prefixed = append $prefixed (printf "adminConsole.route %s" (urlParse $uiUrl).path) }}
{{- end }}
{{- if $prefixed }}
Expand Down
20 changes: 20 additions & 0 deletions charts/authup/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,26 @@ Usage: {{ include "authup.tplvalues.render" (dict "value" .Values.<path> "contex
{{- end -}}
{{- end -}}

{{/*
Boolean that may arrive as a tpl-rendered string, so an umbrella chart can drive
it from one of its own switches. Returns "true" (truthy) or "" (falsy).

Strict on purpose. The schema no longer rejects a string here, and a rendered
"false" is a NON-EMPTY string, i.e. truthy to a Go template `if`: a lenient
reader would create the resource exactly when the parent switched it off.
Anything that is not true/false/"" fails the render instead.
Usage: {{ if (include "authup.flag" (dict "value" .Values.server.route.enabled "context" $ "key" "server.route.enabled")) }}
*/}}
{{- define "authup.flag" -}}
{{- $rendered := include "authup.tplvalues.render" (dict "value" .value "context" .context) | trim -}}
{{- $value := lower $rendered -}}
{{- if eq $value "true" -}}
true
{{- else if not (or (eq $value "false") (eq $value "")) -}}
{{- fail (printf "authup: %s must be true or false (or a template rendering to one of them), got %q." .key $rendered) -}}
{{- end -}}
{{- end -}}

{{/*
Standard labels.
Usage: {{ include "authup.labels" (dict "context" $ "component" "server") }}
Expand Down
13 changes: 9 additions & 4 deletions charts/authup/templates/_server-env.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,15 @@ CLIENT_SYSTEM_ENABLED: "true"
CLIENT_SYSTEM_SECRET_RESET: "true"
{{- end }}
{{- end }}
{{- $reserved := list "DB_TYPE" "DB_HOST" "DB_PORT" "DB_USERNAME" "DB_DATABASE" "DB_PASSWORD" "PUBLIC_URL" "TRUSTED_ORIGINS" "TRUST_PROXY" "REGISTRATION_ENABLED" "PASSWORD_RECOVERY_ENABLED" "EMAIL_VERIFICATION_ENABLED" "ACCOUNT_CONSOLE_ENABLED" "MFA_ENABLED" "MFA_REQUIRED" "THEME_DIRECTORY_PATH" "THEME_FRAGMENTS_ENABLED" "USER_ADMIN_PASSWORD" "USER_ADMIN_PASSWORD_RESET" "CLIENT_SYSTEM_ENABLED" "CLIENT_SYSTEM_SECRET" "CLIENT_SYSTEM_SECRET_RESET" "REDIS" "SMTP" "SECRETS_ENCRYPTION_KEY" }}
{{- /* Pinned to the path the volumeMounts use, not inherited from the image, whose default
moved here in v1.0.0-beta.63: a mount that stops matching it fails silently (logs on
the container layer, file provisioning scanning a directory that is not there). */}}
WRITABLE_DIRECTORY_PATH: "/var/lib/authup"
{{- $reserved := list "DB_TYPE" "DB_HOST" "DB_PORT" "DB_USERNAME" "DB_DATABASE" "DB_PASSWORD" "PUBLIC_URL" "TRUSTED_ORIGINS" "TRUST_PROXY" "REGISTRATION_ENABLED" "PASSWORD_RECOVERY_ENABLED" "EMAIL_VERIFICATION_ENABLED" "ACCOUNT_CONSOLE_ENABLED" "MFA_ENABLED" "MFA_REQUIRED" "WRITABLE_DIRECTORY_PATH" "THEME_DIRECTORY_PATH" "THEME_FRAGMENTS_ENABLED" "USER_ADMIN_PASSWORD" "USER_ADMIN_PASSWORD_RESET" "CLIENT_SYSTEM_ENABLED" "CLIENT_SYSTEM_SECRET" "CLIENT_SYSTEM_SECRET_RESET" "REDIS" "SMTP" "SECRETS_ENCRYPTION_KEY" }}
{{- range $key, $value := .Values.server.config }}
{{- if has $key $reserved }}
{{- fail (printf "authup: server.config.%s collides with a first-class chart value — set it through the dedicated value instead." $key) }}
{{- $instead := ternary "server.extraEnvVars plus a matching server.extraVolumeMounts" "the dedicated value" (eq $key "WRITABLE_DIRECTORY_PATH") }}
{{- fail (printf "authup: server.config.%s collides with a first-class chart value — set it through %s instead." $key $instead) }}
{{- end }}
{{ $key }}: {{ include "authup.tplvalues.render" (dict "value" ($value | toString) "context" $) | quote }}
{{- end }}
Expand Down Expand Up @@ -102,12 +107,12 @@ provisioning files, config file).
*/}}
{{- define "authup.server.volumeMounts" -}}
- name: writable
mountPath: /usr/src/app/writable
mountPath: /var/lib/authup
- name: tmp
mountPath: /tmp
{{- if and .Values.server.provisioning.enabled (or .Values.server.provisioning.files .Values.server.provisioning.existingConfigMap .Values.server.provisioning.existingSecret) }}
- name: provisioning
mountPath: /usr/src/app/writable/provisioning
mountPath: /var/lib/authup/provisioning
readOnly: true
{{- end }}
{{- if or .Values.server.configuration .Values.server.existingConfigmap }}
Expand Down
2 changes: 1 addition & 1 deletion charts/authup/templates/admin-console/httproute.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if and .Values.adminConsole.enabled .Values.adminConsole.route.enabled }}
{{- if and .Values.adminConsole.enabled (include "authup.flag" (dict "value" .Values.adminConsole.route.enabled "context" $ "key" "adminConsole.route.enabled")) }}
{{- include "authup.httproute" (dict
"context" $
"component" "admin-console"
Expand Down
Loading
Loading