Skip to content

[freeradius] 1.1.0 modernization (Gateway API, modules, Keycloak, metrics)#122

Merged
firmansyahn merged 34 commits into
mainfrom
feat/readius
May 28, 2026
Merged

[freeradius] 1.1.0 modernization (Gateway API, modules, Keycloak, metrics)#122
firmansyahn merged 34 commits into
mainfrom
feat/readius

Conversation

@firmansyahn
Copy link
Copy Markdown
Collaborator

Summary

Major 1.1.0 release for the freeradius chart — end-to-end modernization.
Breaking: see charts/freeradius/CHANGELOG.md and README → "Upgrading → To
1.1.0". These migrations apply to any pre-1.1.0 release (1.0.3 still used the old
keys).

Highlights:

  • Key reshape: modsEnabled:modules:, sitesEnabled:sites:,
    ingress: removed, tls.autoGeneratortls.certManager.*,
    database.bootstrap.*bootstrap.database.*, configuration(s) rename.
  • Gateway API resource set adapted for RADIUS (Gateway / UDPRoute / TLSRoute
    / ReferenceGrant / ListenerSet) plus the istio path; chart-internal shared
    self-signed CA persisted via lookup.
  • Modules: SQL (mysql/postgresql/sqlite), REST, JSON, PAM, PAP, and EAP
    (EAP-TLS / EAP-TTLS) — each its own ConfigMap mounted at mods-enabled/<name>.
  • Keycloak integration (keycloak.mode: lua | rest) + generic modsConfig
    for custom mods-config data.
  • Metrics: standalone freeradius_exporter Deployment + Service +
    ServiceMonitor + PrometheusRule (not a sidecar).
  • DB schema bootstrap init container; prepare-sites init (0711 sites dir);
    SQL TLS moved to certs-sql; removed dead freeradius-sqlite / shared-certs
    volumes.
  • Bundled PostgreSQL subchart alongside MariaDB.
  • CI: ct lint on every branch; release job gated to main.

Closes #67
Closes #96
Closes #97
(#84 — initContainers from values — already closed; fixed here too.)

Test plan

  • helm lint passes; ct lint green on the branch (CI).
  • helm template renders: default; SQL mysql/postgresql/sqlite; RADSEC TLS;
    EAP; REST; metrics; Keycloak mode=lua and mode=rest.
  • Deploy smoke test in a cluster (auth via PAP/SQL, RADSEC, metrics scrape).
  • Verify Keycloak ROPC + role mapping against a live Keycloak (needs an
    image with rlm_lua + lua-cjson + luasec).

End-to-end rework matching the adminer 1.0.0 playbook, adapted for RADIUS
traffic. See charts/freeradius/CHANGELOG.md and README §Upgrading for the
full diff and migration steps.

Highlights:
- values.yaml restructured with section banners; template subdirs lowercased
  (Istio/→istio/, ConfigMap/→configmap/, Secret/→secret/); _helpers/
  consolidated into a single _helpers.tpl with dotted-namespace helpers
  (freeradius.tls.*, freeradius.sql.tls.*, freeradius.gateway.*,
  freeradius.mariadb.*, freeradius.metrics.*).
- TLS architecture unified under tls.certManager.* + a shared self-signed CA
  helper (lookup-or-generate, persists across upgrades and across in-pod ↔
  gateway path migrations); gateway.tls.{enabled,existingSecret,selfSigned,
  secrets} for gateway-side TLS.
- Gateway API stack adapted for RADIUS: UDPRoute for auth/acct/coa, TLSRoute
  for RADSEC passthrough, plus ReferenceGrant and ListenerSet. Istio path
  (Gateway + VirtualService) reworked off ingress.* into gateway.hostnames.
- HorizontalPodAutoscaler.yaml, ServiceMonitor, PrometheusRule, NOTES.txt,
  extraDeploy.yaml added. PSS "restricted" container security context
  defaults. Apache-2.0 LICENSE file.
- Database schema bootstrap (#67): a db-bootstrap init container loads
  files/schema/<dialect>.sql via mysql/psql, gated on
  modsEnabled.sql.enabled + database.bootstrap.enabled; idempotent.
- configurations ConfigMap rendering (#97): templates/configmap/configuration.yaml
  materialises the dangling ConfigMap reference the Deployment was mounting.
  Renamed configuration→configurations / configurationConfigMap→configurationsConfigMap.
- FreeRADIUS exporter as a separate Deployment under templates/metrics/
  (bvantagelimited/freeradius_exporter), with its own Service + NetworkPolicy.
  Scrapes the in-cluster status virtual server (sitesEnabled.status.listen
  default changed to 0.0.0.0; status now published on the main Service).
- Hard validation via freeradius.validateValues aggregator: metrics.enabled
  requires sitesEnabled.status.enabled; tls.enabled requires a configured
  cert source. helm install / upgrade / template now fail rather than render
  a non-functional release.
- Many template fixes: .Values.resources typo, replicas vs HPA, lowercase
  subdir checksum paths, commonAnnotations propagation to pods, individual
  checksum gating, ingress.* references purged, st-common.labels.standard
  signature standardised across all templates.
- Chart bumped to 1.0.0; appVersion 3.2.7; st-common dep to 0.1.21.
…reshape

Builds on the prior 1.0.0 modernization commit (0bd862c). Still unreleased,
so this is a single follow-up cut with no backward-compat shims.

Backend support:
- Bundled PostgreSQL subchart (Bitnami 16.x.x) alongside MariaDB. Picks
  dialect from modules.sql.dialect; new freeradius.sql.backend.validate
  rejects two-subchart setups, dialect/subchart mismatches, sqlite+subchart,
  and no-backend-at-all.
- Helpers freeradius.mariadb.{host,port,name,user,secretName,secretKey}
  renamed to freeradius.sql.* with three-way branching
  (mariadb -> postgresql -> externalDatabase). externalDatabase.port now
  defaults to "" and the helper picks 3306/5432 from dialect.

New modules (via files/modules/<name> + envvars + helpers/validator where
needed):
- rest (rlm_rest): connect URI, per-section URI/method/body, HTTP auth
  (none/basic/digest/bearer), TLS material at /opt/.../certs-rest signed
  by the chart's shared CA. New freeradius.rest.{tls.*,secretName,
  secretKey,validate} helpers; mods-rest-password auto-generated when
  auth != none.
- json (rlm_json): stub config, xlat-only.
- pam (rlm_pam): one knob (modules.pam.pamAuth, default radiusd).

SQL knobs newly piped via env vars:
- modules.sql.readGroups and readProfiles (both default true; uncomments
  the matching directives in files/modules/sql).

Top-level key reshape (no shims):
- modsEnabled: -> modules: (Helm key); files/mods-available/ -> files/modules/;
  templates/configmap/mods-enabled.yaml -> modules.yaml; ConfigMap name
  <release>-mods -> <release>-modules; volume freeradius-mods -> freeradius-modules.
- sitesEnabled: -> sites:; files/sites-available/ -> files/sites/;
  templates/configmap/sites-enabled.yaml -> sites.yaml. K8s resource
  names (<release>-sites etc.) were already short and unchanged.
- database.bootstrap.* -> bootstrap.database.*; helpers
  freeradius.database.* -> freeradius.bootstrap.database.*. Lets future
  bootstrap kinds (TLS, users, ...) slot in alongside.

Layout:
- Exporter NetworkPolicy moved to templates/metrics/NetworkPolicy.yaml,
  next to the rest of the metrics resources. The complementary ingress
  rule on the FreeRADIUS pods (allow metrics -> status port) stays in
  templates/NetworkPolicy.yaml — it's a rule about the FreeRADIUS pod.

In-container paths (/etc/freeradius/{mods,sites}-enabled/<name>) and env
var names (FREERADIUS_MODS_*) are FreeRADIUS daemon conventions and
unchanged.

CHANGELOG + README §Upgrading updated with before/after migration snippets
for each break (#8 modules rename, #9 sites rename, #10 PostgreSQL +
backend selection).
…sql,_validate}.tpl

Refactor only — no values shape changes, no functional changes beyond a
small bootstrap-image-default tweak (see below).

- templates/_helpers.tpl → templates/helpers/_helpers.tpl (move).
  Helm globs *.tpl under templates/ so all helpers still load.

- Validation pipeline extracted to templates/helpers/_validate.tpl with a
  renamed namespace: freeradius.<area>.validate → freeradius.validate.<area>,
  aggregator freeradius.validateValues → freeradius.validate. NOTES.txt
  include updated. _helpers.tpl carries a one-line pointer comment.

- SQL backend helpers extracted to templates/helpers/_sql.tpl:
  freeradius.{mariadb,postgresql}.fullname + freeradius.sql.{tls.*,host,
  port,name,user,secretName,secretKey}. _helpers.tpl carries a one-line
  pointer comment in their place.

- bootstrap.database image defaults moved INTO the helper as a per-dialect
  dict literal (mysql → mariadb image, postgresql → postgresql image).
  values.yaml ships with empty registry/repository/tag so each empty
  field falls through to the dialect default at render time. Drops the
  repo-string-match auto-swap. Bumped registry to public.ecr.aws/bitnami
  and pinned tags to 12.2.2 (mariadb) / 18.4.0 (postgresql) so the images
  ship the matching CLI binary required by the bootstrap init container.

- CHANGELOG + README references swept to the new helper names.
- CHANGELOG.md: top section `## 1.0.0 (2026-05-27)` → `## 1.1.0
  (2026-05-27)`; intro paragraph rewritten to reflect the actual release
  body (postgresql subchart, REST/JSON/PAM modules, top-level key
  cleanups) instead of "matching the adminer playbook". Three other
  adminer mentions in scattered bullets dropped or reworded. 1.0.3
  paragraph at the bottom now points at 1.1.0 as the authoritative
  release notes.
- README.md: §Upgrading heading `### To 1.0.0 (breaking)` → `### To 1.1.0
  (breaking)`; every `# After (1.0.0)` snippet marker bumped to 1.1.0.
  The "matches adminer chart" hint in the gateway-shape migration section
  is removed.
- Chart.yaml: `artifacthub.io/changes` description bumped to "1.1.0 major
  release" with the actually-shipped feature list (REST/JSON/PAM, key
  cleanups) instead of just postgresql + UDPRoute.
… preset tweak

- files/sites/tls: comment out `dh_file = ${certdir}/dh` — the chart
  doesn't ship a DH params file, so loading it aborts RADSEC startup.
  `random_file` stays; operators who want explicit DH params can mount
  one and uncomment.
- values.yaml: default `networkPolicy.enabled` to true; change
  `resourcesPreset` default to `g-2xsmall`; reflow the
  `modules.sql.dialect` doc comment.
Wires rlm_eap into the modules ConfigMap behind `modules.eap.enabled`,
sharing one `tls-config tls-common` server cert across the TLS-based
methods. Adds a fourth chart-managed TLS context (eap-tls.yaml, signed
off the shared internal CA), an auto-generated key passphrase, and a
`freeradius.validate.eap` aggregator that rejects incoherent method /
defaultType / cert-source combinations.
…; split helpers

- Module files (json/pam/eap) now render through Helm `tpl`: each values map
  becomes `key = value` directives via `freeradius.tplvalues.renderConfig`.
  json gains a fixed `encode {}` block bound to `modules.json.encode.*`; eap
  is reworked to a `modules.eap.methods` list (each `modules.eap.<method>` map
  is its block body, inner methods render empty) and adds `peap`; pam drops
  the now-redundant `FREERADIUS_MODS_PAM_AUTH` env var.
- snake_case migration of config-shaped values so the renderer emits valid
  FreeRADIUS directives: clients (`nas_type`, `virtual_server`, `coa_server`,
  `max_connections`, `idle_timeout`) and eap `tlsConfig`
  (`random_file`, `cipher_list`, `cipher_server_preference`, `min_version`,
  `max_version`, `certificates_secret`, `cert_*`, `private_key_password`).
  EAP validator and credentials Secret updated to match.
- extraModules / extraSites passthrough: one ConfigMap + volume per entry,
  mounted at mods-enabled/<name> / sites-enabled/<name> (verbatim, no tpl).
- Split templates/helpers/_helpers.tpl into _images.tpl (image + pullSecrets),
  _tls.tpl (RADSEC/REST/EAP/gateway/CA), and _utils.tpl (renderConfig). Rename
  `freeradius.imagePullSecrets` -> `freeradius.image.pullSecrets`; inline the
  metrics exporter image via `st-common.images.image`.
…d ConfigMaps

- Fix `helm template` failure: literal `{{ ... }}` in the eap/json module
  header comments was parsed by `tpl` and aborted rendering. Rewrote the
  comments in prose (a tpl-rendered file must not contain literal `{{`).
- eap and pam now render into their own ConfigMaps (`<release>-mods-eap`,
  `<release>-mods-pam` under templates/modules/) and are mounted directly at
  mods-enabled/{eap,pam}, rather than aggregated into `<release>-modules`
  where they were rendered but never actually mounted. Adds the matching
  Deployment volume/volumeMount/checksum wiring for both.
- Inline the eap config into templates/modules/eap.yaml and delete the now
  redundant files/modules/eap (single source of truth).
- Drop the redundant `with` guards around `freeradius.tplvalues.renderConfig`
  in eap.yaml — the recursive helper already returns "" for an empty/nil map.
…irectly (snake_case)

- Move sql/rest/json into their own ConfigMaps (templates/modules/<name>.yaml,
  mounted at mods-enabled/<name>); delete files/modules/* and the aggregated
  configmap/modules.yaml. Deployment now has a per-module volume/mount/checksum
  for all five modules and no longer declares the freeradius-modules volume.
- rest: render `.Values.modules.rest.*` directly into the module file, dropping
  the FREERADIUS_MODS_REST_* `$ENV{}` indirection — only the password stays as
  `$ENV{}` (a secret injected via the Deployment's secretKeyRef). Config keys
  renamed to snake_case (connect_uri/connect_timeout/post_auth and
  tls.{auto_generated,certificates_secret,cert_*,check_cert,check_cert_cn});
  envvars/_tls.tpl/_validate.tpl references updated to match (fixes the
  already-broken connectUri/connectTimeout refs). Pool sizing is now tunable via
  `modules.rest.pool`, rendered through freeradius.tplvalues.renderConfig.
- eap: top-level timer/session knobs moved to values (timerExpire,
  ignoreUnknownEapTypes, ciscoAccountingUsernameBug, maxSessions); cache/verify/
  ocsp and the method blocks pass values straight to renderConfig (dropped the
  redundant `with` guards now that the helper returns "" for empty maps).
- pam: wrap the renderConfig value under the `pam` block name so it emits a
  valid `pam { }` block rather than a bare directive.
- Docs (CHANGELOG/README/values comments/sqlite.sql) updated for the per-module
  layout.
…directly

- Add `modules.<name>.existingConfigMap` to all five modules: when set, the
  module's ConfigMap template skips rendering and the Deployment volume mounts
  the external ConfigMap instead (its checksum annotation is also gated off).
  New `freeradius.module.configMapName` helper resolves BYO-or-chart-rendered.
- sql module: render `.Values.modules.sql.*` and the `freeradius.sql.*`
  connection helpers directly into templates/modules/sql.yaml, dropping the
  FREERADIUS_MODS_SQL_* `$ENV{}` indirection. Only the password stays `$ENV{}`
  (a secret, injected by the Deployment via secretKeyRef whenever sql is
  enabled). The entire SQL block is removed from the envvars ConfigMap.
- Docs (values comments, _sql.tpl helper doc, CHANGELOG, README upgrade notes)
  updated: module config is rendered directly from values; only secrets
  (sql/rest passwords, eap private-key passphrase) remain as `$ENV{}`.
Mirror the per-module split for virtual servers: each site renders into its
own `<fullname>-sites-<name>` ConfigMap (templates/sites/<name>.yaml) with a
`sites.<name>.existingConfigMap` BYO override (new `freeradius.site.configMapName`
helper), its own pod volume/mount and per-site checksum annotation. Replaces the
single bundled `configmap/sites.yaml`; `files/sites/` is removed (content inlined).
`default` + `inner-tunnel` always render; `coa`/`status`/`dhcp`/`tls` are gated.
Adds the `sites.dhcp` toggle, wires the inner-tunnel mount, and gates the status
mount on `sites.status.enabled`.

Site config (listen ports/addresses, RADSEC cert/key/CA paths, cipher) now renders
directly from `.Values` into each site ConfigMap; only secrets (status secret,
RADSEC key passphrase, client secret) stay as `$ENV{}` injected via secretKeyRef.
Drops the orphaned `FREERADIUS_SITES_*` env vars from configmap/envvars.yaml
(keeps `STATUS_PORT`, still referenced by the radclient probes).

Also adds the pap module (rlm_pap) following the same per-module ConfigMap pattern.
Remove the inner `pap` EAP method (methods list, the `modules.eap.pap`
subsection, and the `default_eap_type: pap` refs in ttls/peap, now commented).
Move the `tlsConfig.ocsp.enable` @param next to the `ocsp` block and add
commented `verify` examples (tmpdir/client).
…nabled

Wrap every `sql`/`-sql` invocation in the default virtual server (authorize,
accounting, session, post-auth logging, Post-Auth-Type REJECT) in
`{{- if .Values.modules.sql.enabled }}` so the SQL module is only referenced
when it is actually enabled.
…tion

The radclient liveness/readiness/startup probes now render the status port
directly from `.Values.containerPorts.status` instead of
`${FREERADIUS_SITES_STATUS_PORT}`; the status secret stays as `${...}` (injected
via secretKeyRef). Removes the now-unused `FREERADIUS_SITES_STATUS_PORT` entry
from configmap/envvars.yaml.
Replace the condensed default virtual-server body with the upstream 3.2.8
`sites-available/default` (restores the full comments and the commented `dpsk` /
Active-Directory-PAP example blocks). Functional chart customizations preserved:
templated auth/acct listen ports (ipv4 + ipv6) and the sql/eap module-call gates
on `modules.{sql,eap}.enabled`.

Adopts upstream defaults: `idle_timeout = 900` on the first auth listener, and
the `unix` (authorize), `sql` (session/Simultaneous-Use) and `eap` (pre-proxy)
calls revert to commented-out as upstream ships them.

Also reorder `sites.{status,tls}.existingConfigMap` to sit right after `enabled`.
…xisting CMs

Sites:
- Mount all virtual servers from a single `projected` volume (chart sites +
  extraSites) at sites-enabled/, replacing the per-site configMap volumes and
  subPath mounts. Chart site ConfigMaps are single-key so they project whole;
  extraSites keep `items` since their data key may differ from the on-disk path.
- Add `sites.default.enabled` / `sites.innerTunnel.enabled` toggles (default
  true) gating each site's ConfigMap, projected source, mount and checksum.

Probes:
- Move the radclient status-check scripts into a `<release>-scripts` ConfigMap
  (templates/configmap/scripts.yaml) mounted at `healthCheck.mountPath` (/health);
  the default startup/liveness/readiness probes now run `sh <mountPath>/<script>`.
- New `healthCheck.{mountPath,startupScript,livenessScript}` values drive the
  mount path, the probe command paths, and the ConfigMap data keys.

extraModules / extraSites:
- Now mount existing ConfigMaps you provide (`{name, existingConfigMap, key?}`)
  instead of rendering inline `config`. Removed templates/configmap/extra-modules.yaml
  and extra-sites.yaml and their checksum annotations.
…onf; sites at /opt

- db-bootstrap: replace the helper-rendered schema command with an in-container
  `case "$DB_DIALECT"` (mysql/postgresql/skip) and a new DB_DIALECT env; gate the
  init container and db-schema volume on the dialect directly. Remove the unused
  freeradius.bootstrap.database.cmd helper (and its stale _images.tpl reference).
- Bundle a stock 3.2.8 files/radiusd.conf (rendered into the configurations
  ConfigMap, mounted at /etc/freeradius/radiusd.conf) and add a sites.configurations
  inline draft.
- Mount the projected sites volume at /opt/startechnica/freeradius/sites-enabled.
…nclude sites from /opt

Move the inline radiusd.conf from the unwired `sites.configurations` to the
top-level `configurations` key that the configurations ConfigMap actually reads,
so the rendered radiusd.conf `$INCLUDE /opt/startechnica/freeradius/sites-enabled/`
now matches where the projected sites volume is mounted.
…rap.sh in scripts CM

- relocate volumePermissions config + init container under bootstrap.*
- bundle db-bootstrap.sh into the scripts ConfigMap, mounted via subPath
- drop healthCheck.* indirection; fixed script names under /scripts
- add sites.includeDir; probes invoke scripts from /scripts
…t; chmod 0711

- add prepare-sites init container that copies the read-only projected
  virtual servers into a writable emptyDir and sets sites-enabled to 0711
- rename projected volume to freeradius-sites-tmp; freeradius-sites is now
  the emptyDir backing sites.includeDir in the main container
- drop the unreachable chmod of sites.includeDir from volume-permissions
The $needsDbBootstrap assignment kept a trailing -}} that trimmed the
newline before `initContainers:`, gluing it onto the serviceAccountName
value after the wrapping `{{- if }}` was removed. Use }} so the newline
is preserved.
…m names

- mount freeradius-sql-tls at /opt/startechnica/freeradius/certs-sql (readOnly),
  resolving the path collision with the RADSEC freeradius-tls mount at .../certs
- point sql TLS cert path helpers at certs-sql with native tls.crt/tls.key/ca.crt
  filenames, dropping the bespoke sql-*.crt secret item renaming
- align hardcoded mongo ca_dir / ca_path to /opt/startechnica/freeradius/certs-sql
…ad sqlite/shared-certs volumes

- keycloak.* integration with two modes: `lua` (self-contained rlm_lua doing
  ROPC + introspection -> control:Class, with unlang keycloak_authorize /
  keycloak_roles for role->reply mapping) and `rest` (auth-only rlm_rest ROPC).
  Gated by keycloak.mode and wired into the default site's authorize section
  via wireDefaultSite. Client secret injected via $ENV{KC_CLIENT_SECRET}.
- modsConfig: per-subdir mods-config ConfigMaps projected at
  /etc/freeradius/mods-config/<subdir>/.
- sites.includeDir / policies.includeDir values.
- remove the redundant freeradius-sqlite emptyDir (the SQLite db lives on the
  data volume) and the dead read-only shared-certs emptyDir.
…3 history, fix breaking-change baseline

- document keycloak / modsConfig / prepare-sites / certs-sql / volume removals
  under the unreleased 1.1.0 section
- backlink GitHub issues #84 and #96 (already-fixed, previously unlinked)
- backfill per-version history for 1.0.3 down to 0.1.0, reconstructed from the
  freeradius-<version> git tags
- baseline the breaking-change notes against 1.0.3 (the latest pre-1.1.0
  release, which still used modsEnabled/sitesEnabled/ingress/configuration),
  not solely 0.x
- add a `lint` job using helm/chart-testing-action (`ct lint`), linting only
  charts changed vs the default branch
- trigger the workflow on push to any branch; gate the `release` job with
  `if: github.ref == 'refs/heads/main'` so publishing still only happens on main
chart-testing 3.12.0 bundles a yamale that references ast.Num (removed in
Python 3.12); setup-python with "3.x" pulled 3.14, crashing Chart.yaml schema
validation with `AttributeError: module 'ast' has no attribute 'Num'`.
Add .github/workflows/** to the push paths so CI changes re-run the lint job
(the charts/** filter previously skipped workflow-only commits).
@firmansyahn firmansyahn merged commit 322735f into main May 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

freeradius: .Values.configuration doesn't create any configmap FreeRadius 1.0.3 broken FreeRADIUS: MariaDB/MySQL Database not initalizing

1 participant