Live Numbers refresh every 15 minutes The Access Engine · access manager v2.4

Every service, every group, one matrix.

A small app that turns single sign-on, a gateway and Kubernetes into a managed identity platform. It finds every service, knows which ones the internet can reach, writes the access rules, ships them to git and audits every click — whether the click came from an admin, an app or an agent.

87Services managed
206Gateway routes watched
56Generated access rules
12Groups
7OIDC clients
568Audited changes

Counted live from the running database, the cluster and the source tree.

The problem

"Just one more service" is six chores

Single sign-on is the brain, the gateway is the door, Kubernetes is the house. The glue between them is configuration — hand-edited, unless something does it for you.

Edit the routeAttach the sign-on check to the new service's gateway route. Hope the namespace is right.
Edit the policyAdd a rule in exactly the right place — the first match wins. One wrong field and the sign-on service won't start.
ReloadRestart, wait, test. Did it pick up the change? If not, read logs and start over.
Mirror to gitCopy, commit, push. Forget it once and the live config and the declared config drift apart silently.
Track who did whatGit says a file changed. It doesn't say who granted which group what. That needs its own audit trail.
Repeat 87 timesFor every service, every role change, every new app that wants sign-on. This is the chore it removes.
The architecture

A cluster-aware access loop

One app reads the cluster, keeps a model of services × groups, writes the rules and reloads sign-on. Every step is real code.

  1. 1
    An admin opens the managerReachable only from admin networks, behind its own hardened login.
  2. 2
    It reads the clusterEvery few minutes it lists every gateway route, how each one is protected and whether the internet or only the LAN can reach it.
  3. 3
    It keeps one modelA small database of services, domains, groups and who may reach what.
  4. 4
    It writes the rules three placesThe live config, the git repository and an audit row — in one ordered action.
  5. 5
    It reloads sign-onA graceful reload in about three seconds, with a restart as fallback.
  6. 6
    Every service is protectedOne place to grant. One place to revoke. One audit row per click.
The flows

Six motions, step by step

The first three are most of an admin's day. The last three arrived in October 2026: an app enrols its own people, an agent tightens a door, a site goes public on purpose. Each is one click or one call; underneath is a precise chain of reads, writes and audit entries.

Flow 1

Scan → sync → review

  1. RList every route; capture every hostname it serves.
  2. RGroup routes into logical apps with friendly names.
  3. WShow a diff: new apps, new domains, stale ones.
  4. WApply it — new apps wait until an admin assigns groups.
  5. ALog the change; stay silent when nothing changed.
Flow 2

Apply access, in order

  1. RRead the whole matrix: 95 service × group entries.
  2. RBuild rules in first-match order and merge duplicates — 56 rules, 428 lines.
  3. WBack up the live file, then write the new one.
  4. WCommit and push the same file to git.
  5. AReload sign-on and record every result in one audit row.
Flow 3

Toggle sign-on per route

  1. RFetch the live route for a local service.
  2. WAdd the sign-on check if the namespace lacks it.
  3. WPatch the live route.
  4. WFind the matching manifest in git and change it too.
  5. AAudit it — "who protected that dashboard at 2 AM?" has a one-second answer.
Flow 4 · new

An app enrols its own people

  1. RThe classroom calls the service API with its own token; the API knows which group prefix it owns.
  2. WCreate the student with an unguessable password nobody sees; the person sets their own through sign-on's reset mail.
  3. WPut a course group behind the library or the meeting server at password or second-factor level — only from an allowlist.
  4. WApply — the same ordered write as Flow 2.
  5. AAudited as the service and as the teacher it acted for. An admin, another service, a password: out of reach by construction.
Flow 5 · new

An agent tightens a door

  1. RA local agent lists the services that are open and may be protected — public sites and machine endpoints are on a never-touch list.
  2. RDry run: what would change, for which groups.
  3. WProtect: add sign-on, grant existing groups, raise a policy — never open, never lower, never bypass.
  4. WApply, like an admin would.
  5. AAudited under the agent's own name. Loosening anything stays a human's click in the UI.
Flow 6 · new

Public on purpose

  1. RInternet traffic enters on its own gateway entry point; a route is public exactly when it lists it.
  2. RTwo invariants every sync: an internal name on the internet is critical, a public site that is LAN-only is a warning.
  3. WPublish or make LAN-only: patch the live route and the manifest in git, comments kept.
  4. WRefuse, always, to publish an internal hostname.
  5. AAudit it; a message goes out only when the set of violations changes — today it is empty.
The numbers

A running system, counted live

Pulled from the database, the cluster and the source tree every 15 minutes. No marketing numbers.

The cluster it manages

Gateway routes206
Routing rules233
Unique hostnames192
Gateway middlewares85

The identity model

Services87
Service domains197
Groups12
Generated rules56
OIDC clients7
Service-API clients2

How routes are protected

Single sign-on84
Network allowlist8
Public by design141
Reachable from the internet60
LAN-only150

The audit trail

All changes568
Access grants & revokes132
Rule applies53
Manager logins196
Changes by apps & agents31

The codebase

Lines of Python6,821
Python modules35
Lines of plain JS3,098
Database tables8
Engineering

Three decisions worth talking about

The details that don't make a README — each one a response to a real failure.

Byte-stable output

Same data, same bytes

A normal YAML dump reorders indentation and inserts aliases, so identical rules produce different files — and noisy diffs. A tiny custom dumper fixes it: diffs show only real change.

class StableDumper(yaml.SafeDumper):
  def increase_indent(self, flow=False, indentless=False):
    return super().increase_indent(flow, False)
  def ignore_aliases(self, _):
    return True
Ordered writes

Atomic enough

True atomicity across a file, git and a database is impossible. Ordering the writes makes any single failure recoverable.

1. back up the live file
2. write the new file      # live
3. copy to the repository
4. commit & push           # may fail — OK
5. reload sign-on          # ~3 s
6. write one audit row     # records 1–5
A production lesson

Bypass has no subject

An early version gave "bypass" rules a group subject. Reasonable — and a config error the sign-on service refuses to start with. Now the generator simply never emits one.

if policy == "bypass":
  rule["policy"] = "bypass"   # no subject
else:
  rule["subject"] = [groups]
Threat model

An access manager is a target

If it falls, everything behind it falls. It was hardened against an internal audit; every finding has a control.

Unprivileged containerNon-root, no extra capabilities, no privilege escalation.
Locked-down networkOnly the gateway and the metrics scraper can reach it.
Admin networks onlyThe web UI is unreachable from anywhere else.
Least-privilege databaseIt can read sign-on state and reset second factors — and nothing else.
Hardened loginStrong password hashing, rate limiting, admin-only writes.
Narrow cluster rightsA handful of permissions on routes and middlewares. No secrets, no nodes, no cluster-admin.
Errors that don't leakDatabase internals are stripped from every error the browser sees.
Everything auditedEvery grant, revoke, apply, toggle and login leaves a row.
Scoped service tokensAn app's token is bound in code to its own group prefix and a service allowlist. It cannot widen its scope, and it cannot see an admin.
Agents may only tightenAgent tokens own no groups at all. They can add sign-on and raise policies — never open, lower or bypass.
Passwords never cross the APINew accounts get a random password nobody is shown; the person sets their own through sign-on's reset mail.
The edge guardrailAn internal hostname can never be published to the internet, and every sync checks that nothing drifted.
The closest commercial equivalent is a hosted identity service plus a custom cluster operator plus a GitOps tool. This one knows every route in the cluster, writes byte-stable rules, and proves what changed in the same row that proves who changed it.— the design brief, written backwards from what was needed
The stack

Boring tools that compose well

Python · FlaskPostgreSQLKubernetesTraefikAuthelia · OIDCGitea · GitOpsPrometheus · GrafanaPlain JS, no build stepAdversarial test suite
Curious?

Running single sign-on on your own cluster?

Happy to talk through the design, the trade-offs and the production lessons.

Built with Claude Code as a collaborator — the architecture, the bugs and the production lessons are mine. Proprietary software; not licensed for reproduction or derivative works.