Help us shape the future of Go! Take the 2026 Go Developer Survey

access-roster

module
v0.16.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2026 License: MIT

README

access-roster

CI

One small OpenID provider for your infrastructure, configured from a Helm chart, with no database and no users of its own.

It reads the groups your people already have in the corporate directory and puts them in a token. Kubernetes, AWS, ArgoCD, Kargo and every console behind your gateway trust that one token. CI jobs and workloads get the same treatment from the identity token they already hold. The whole policy is one file in git, and a console shows you who holds what and why.

Nothing here authenticates anyone. Sign-in, passwords, MFA and device policy stay with Google Workspace or Entra. This verifies the result, knows the directory, and applies the policy.

The niche

Every mature identity provider can do this. None of them is built for it, and the difference is what you run to get it.

dex Keycloak, Zitadel, Authentik Okta, Auth0, Entra ID access-roster
runs on a ConfigMap a database, an operator, a login UI you theme someone else's cloud a ConfigMap
users none, federates its own user store, plus federation its own user store none, federates
groups in the token only if the upstream IdP sends them — Google does not after you write a mapper or a login hook per IdP after you configure a sync read from the directory, always
several corporate IdPs, one issuer yes yes yes yes
one policy file for people and machines no policy at all no; roles per client, in the UI or the database no; per-app assignments yes, in git — and GitHub teams in the same file
CI and workloads without a stored secret connectors only for people machine users, with secrets machine users, with secrets token exchange from GitHub's or the cluster's own token
audience gating for cloud roles no via custom mappers via app assignments a requires list per client
who is in this group and why, at a glance no the admin UI, eventually the admin UI the directory console
operational footprint tiny large, and you own it none, and you rent it tiny

dex is the right shape and stops one step short: it has no idea what groups anyone is in unless the upstream provider says, and Google never says. The heavy providers can be made to do all of it, at the cost of running an identity product to use about a fifth of one. access-roster is dex with a directory reader and a policy file.

What you get

As a person. Sign in once, at one page, with your corporate account. Every console behind the gateway opens without another login. kubectl works on every cluster you are granted, through kubelogin. accessctl gives you AWS credentials for testing, ECR and the rest, with a browser confirmation and no long-lived key. Sign out once and it ends everywhere.

As a machine. A GitHub Actions job presents the identity token it already has and receives one for AWS or a cluster, under a rule that names the repository and the ref. A workload in any cluster does the same with its ServiceAccount token. No secret is stored anywhere, and the rule sits in the same file as the human ones.

As the operator. One chart. The policy is values. The console shows every person, every directory group, every internal group, every rule that grants one, and every open session. A leaver disappears from the directory and, within the freshness window, from everything downstream.

Many in, many out, one point in the middle

Fans in Fans out
corporate directories: several Google Workspaces, Entra next — each a workspace with its own credential and its own served domains Kubernetes clusters: each trusts the one issuer as its identity provider
GitHub Actions: one federated issuer, an owner allow-list AWS accounts: each trusts the one issuer as an OIDC provider
every cluster's own ServiceAccount tokens: one row per cluster naming its key set GitHub organisations: one controller App each, bindings in the same policy
consoles and applications: one client row each

Adding one of anything is one row and one trust registration. The issuer URL, the policy file and the console never multiply.

Built today: one service, three Google Workspaces on one cluster, four clients, workloads on any cluster proving themselves by that cluster's published key set, a GitHub Action that needs nothing of ours downloaded into a job, and accessctl for a laptop. Designed and ticketed, not yet built: the second directory connector, and the GitHub controller across organisations. architecture.md says which is which, per row.

The shape

flowchart LR
  idp["Corporate directory<br/>Google Workspace, Entra"]
  gh["GitHub Actions"]
  k8s["Any cluster's<br/>ServiceAccount tokens"]

  subgraph ar["access-roster"]
    iss["the issuer<br/>reads the directory · applies the policy · mints tokens<br/>serves the login page and the console"]
  end

  proxy["access-proxy<br/>one per console with no OIDC of its own"]
  apps["Kubernetes · AWS · ArgoCD · Kargo · consoles"]

  idp -- "sign-in, and directory reads" --> iss
  gh -- "token exchange" --> iss
  k8s -- "token exchange" --> iss
  iss --> proxy --> apps
  iss -- "trusted by" --> apps

One service and one Valkey. A login makes no network call except to the corporate directory. The proxy is upstream oauth2-proxy in a chart, for applications that cannot run an OpenID flow themselves; anything that can, such as ArgoCD or Kargo, talks to the issuer directly.

Status. Running on one cluster with three Google Workspaces connected and four relying parties on the issuer. CHANGELOG.md says what exists at each version, and the documents below describe what is built.

Conformance

access-roster targets three OpenID Foundation profiles. A profile is claimed only once the suite says so, which is why this section carries the last run rather than an intention.

Last run 2026-09-12 against the deployed issuer at v0.15.2.

Profile Passed Review Skipped Warning Failed
Config OP 1 0 0 0 0
RP-Initiated Logout OP 3 8 0 0 0
Basic OP 21 4 4 6 0

The Foundation's rule is that PASSED, REVIEW, WARNING and SKIPPED all count, and only FAILED or INTERRUPTED disqualify a profile. On that rule all three are certifiable. The columns are here rather than a word like green because the four states mean different things and two of them need explaining.

REVIEW is a screenshot handed to a person

Twelve modules end at a page the suite cannot see — it must refuse, so there is no redirect back — and each asks a human to confirm what was shown. All twelve were reviewed on 2026-09-12 and every screenshot shows the page its step demanded:

What the step demands Modules What the screenshot shows
an error page, the redirect_uri is not registered 2 That sign-in request was not valid
an error page, the id_token_hint is not valid 2 That sign-out request was not valid — id_token_hint invalid
an error page, the post_logout_redirect_uri is not registered 3 That sign-out request was not valid — post_logout_redirect_uri invalid
the successful logout page 3 Signed out
the login prompt during a second authorization 2 the sign-in chooser

This review is not a formality. It has caught a defect both times it has been done: once two pages that were wrong (a signed-out page claiming other consoles were still running, and an unstyled library error), and once ten screenshots that were byte-identical pictures of the wrong page because of a fault in the driver. A REVIEW whose evidence is wrong passes silently.

SKIPPED means the discovery document was believed

Three modules skip because scopes_supported says we do not offer address or phone, and one because we answer request_not_supported. Those are optional features we decline: address and phone are standard scopes for a postal address and a telephone number, and a directory reader for infrastructure has no source for either and no business holding them. The suite read discovery, believed it, and skipped — which is evidence the discovery document is truthful rather than a gap.

WARNING is mostly personal data we decline to hold

Four warnings say userinfo does not carry every claim the profile and email scopes permit. Verified with a real Google sign-in, not the automated recovery path: sub, name, given_name, family_name, preferred_username, email, email_verified and groups are all returned. What is missing is birthdate, gender, zoneinfo, locale, picture, website, profile, nickname, middle_name and updated_at — data this issuer has no source for and no reason to carry. A warning for declining to hold somebody's birthdate is a warning worth keeping.

The other two come from one module noting the ID token carries claims the client did not request by scope. One is groups, which is the entire point of this issuer and what Kargo and ArgoCD read; gating it behind a scope would satisfy the suite and break every consumer. The other is client_id, which duplicates azp and comes from the library's own claims struct rather than from anything here.

One warning was a real defect and is fixed (v0.15.2): a reused authorization code must revoke what it issued, and userinfo went on answering with the access token from the first redemption.

Logout cannot be certified on its own

The Foundation requires a logout submission to carry RP-Initiated Logout OP and at least one of Session Management, Front-Channel or Back-Channel logout. This issuer serves none of those three on purpose — each is surface with no consumer here — so the RP-Initiated run above is worth having as a correctness check and is not a certifiable submission until one of them is built. Back-Channel is the candidate: it would close the window in which a proxy keeps serving after a sign-out, which ttl_cap currently bounds rather than eliminates.

How it is run

By a person, at every major and minor release, as a whole. It used to run the Config profile nightly and nothing else, which is worse than no check: one profile passing daily reads as conformance passes while the two that actually sign somebody in have not run for weeks — and those two are where every defect has been. The Config workflow is still one click away (.github/workflows/conformance.yaml), because it needs no credential and guards the discovery document, which changes silently when a provider option changes.

docs/operations/conformance.md is the procedure, and hack/conformance_drive.py drives headless Chrome through the browser half — signing in through recovery, and answering the suite's manual steps with a screenshot of the page it is looking at — so thirty-five modules are one command rather than thirty-five sign-ins. --manual hands one module's browser step to a person instead, which is how the profile and email claims above were checked with a real account.

You want to Read
understand the ideas behind it docs/why.md, then docs/design/trust.md
see every piece and how they connect docs/architecture.md
learn the ten words used precisely docs/concepts.md
write the policy docs/reference/policy.md
deploy it docs/reference/configuration.md, then docs/operations/connect-runbook.md
put a console behind the gateway docs/connect/console-app.md
connect a cluster, an AWS account, a GitHub organisation, ArgoCD, Kargo, a workflow docs/connect/
run the conformance suite docs/operations/conformance.md
build a service that accepts both people and workloads docs/connect/service-to-service.md

What ships

Artifact For
access-issuer service and chart the installation, once. One process: the directory, the policy, the OpenID provider, the login page and the console
access-proxy chart every console with no OpenID flow of its own
Go module github.com/truvity/access-roster services and consoles in Go: verify a bearer, read the caller's groups
TypeScript package access-roster console UIs: useIdentity() over /.access/whoami
accessctl people on laptops: kubeconfigs and AWS credentials
GitHub Action truvity/access-roster@v1 workflows: one exchange, then a kubeconfig and AWS profiles
the policy one file, one schema, both services

Developing

devbox shell (or direnv), then just check. See CONTRIBUTING.md.

License

MIT.

Directories

Path Synopsis
Package backend is the contract a directory backend implements: read-only access to one tenant's accounts, groups and domains, plus the credential handling that gets there.
Package backend is the contract a directory backend implements: read-only access to one tenant's accounts, groups and domains, plus the credential handling that gets there.
fake
Package fake is a directory backend held in memory, with a scriptable failure for every operation.
Package fake is a directory backend held in memory, with a scriptable failure for every operation.
google
Package google reads a Google Workspace through the Admin SDK.
Package google reads a Google Workspace through the Admin SDK.
cmd
acceptance command
Command acceptance exercises access-issuer against a real Kubernetes API server.
Command acceptance exercises access-issuer against a real Kubernetes API server.
access-issuer command
Command access-issuer runs access-roster.
Command access-issuer runs access-roster.
accessctl command
Command accessctl is what a person on a laptop runs.
Command accessctl is what a person on a laptop runs.
Package frontend embeds the built console, so that the hub is one binary with no static-file deployment beside it and the SPA is same-origin with the API it calls.
Package frontend embeds the built console, so that the hub is one binary with no static-file deployment beside it and the SPA is same-origin with the API it calls.
gen
accessissuer/v1/accessissuerv1connect
Package accessissuer.v1 is what the issuer answers ABOUT itself, as opposed to the OpenID surface it answers protocol on.
Package accessissuer.v1 is what the issuer answers ABOUT itself, as opposed to the OpenID surface it answers protocol on.
directory/v1/directoryv1connect
Package directory.v1 is the directory contract: liveness and flat group membership for the corporate directories an installation owns.
Package directory.v1 is the directory contract: liveness and flat group membership for the corporate directories an installation owns.
directoryroster/v1/directoryrosterv1connect
Package directoryroster.v1 is the operator contract of the hub: the workspaces it holds credentials for, its settings, and access to the hub itself.
Package directoryroster.v1 is the operator contract of the hub: the workspaces it holds credentials for, its settings, and access to the hub itself.
Package identity turns a token somebody else issued into the caller a listener acts on.
Package identity turns a token somebody else issued into the caller a listener acts on.
internal
access
Package access decides who is at the console and what they may do.
Package access decides who is at the console and what they may do.
app
Package app assembles the directory hub from its configuration: the stores, the snapshot cache, the backends, the policy, the connectors, the recovery path and the three listeners.
Package app assembles the directory hub from its configuration: the stores, the snapshot cache, the backends, the policy, the connectors, the recovery path and the three listeners.
connector
Package connector turns a directory's own sign-in screen into a workspace the hub can read.
Package connector turns a directory's own sign-in screen into a workspace the hub can read.
demo
Package demo builds the demonstration tenants the prototype runs on: fixture accounts and groups held in memory, no credential and no network, so that every use-case can be walked through before a real directory is connected.
Package demo builds the demonstration tenants the prototype runs on: fixture accounts and groups held in memory, no credential and no network, so that every use-case can be walked through before a real directory is connected.
emailaddr
Package emailaddr splits addresses the way the hub routes them: by the domain after the last '@', lower-cased.
Package emailaddr splits addresses the way the hub routes them: by the domain after the last '@', lower-cased.
health
Package health serves the two probes, and keeps them different.
Package health serves the two probes, and keeps them different.
hub
Package hub is the directory hub: the workspaces it holds credentials for, the snapshots it keeps of them, the routing from an email address to the workspace that serves it, and the freshness policy that decides whether a read is answered from a snapshot or from the backend.
Package hub is the directory hub: the workspaces it holds credentials for, the snapshots it keeps of them, the routing from an email address to the workspace that serves it, and the freshness policy that decides whether a read is answered from a snapshot or from the backend.
hublocal
Package hublocal asks the directory about a person by CALLING it.
Package hublocal asks the directory about a person by CALLING it.
issuer
Package issuer is the token service: it verifies proofs produced elsewhere, applies the shared policy, and issues tokens.
Package issuer is the token service: it verifies proofs produced elsewhere, applies the shared policy, and issues tokens.
issuerapp
Package issuerapp assembles the token service from its configuration: the policy, the door to the hub, the signing key, the verifiers that turn somebody else's token into a proof, and the OpenID surface over all of it.
Package issuerapp assembles the token service from its configuration: the policy, the door to the hub, the signing key, the verifiers that turn somebody else's token into a proof, and the OpenID surface over all of it.
kube
Package kube keeps what a console changes in the hub's own namespace, as plain Kubernetes objects.
Package kube keeps what a console changes in the hub's own namespace, as plain Kubernetes objects.
logsafe
Package logsafe makes a value safe to write into a log line.
Package logsafe makes a value safe to write into a log line.
rosterapp
Package rosterapp assembles the whole of access-roster as ONE process: the directory connectors, the snapshot and its refresher, the policy, the OpenID provider, the login page and the console.
Package rosterapp assembles the whole of access-roster as ONE process: the directory connectors, the snapshot and its refresher, the policy, the OpenID provider, the login page and the console.
server
Package server maps the hub onto its two listeners: DirectoryService for consumers on the API port, and the operator services plus the console on the console port.
Package server maps the hub onto its two listeners: DirectoryService for consumers on the API port, and the operator services plus the console on the console port.
settings
Package settings keeps the two things an operator can change from the console: the OAuth client used for admin consent and for sign-in, and the memberships added there.
Package settings keeps the two things an operator can change from the console: the OAuth client used for admin consent and for sign-in, and the memberships added there.
valkey
Package valkey keeps the hub's snapshots where every replica can see the same one.
Package valkey keeps the hub's snapshots where every replica can see the same one.
verify
Package verify turns a token somebody else issued into a proof this installation will act on.
Package verify turns a token somebody else issued into a proof this installation will act on.
version
Package version carries the build's identity, so that an operator looking at a console can say which build they are looking at without asking a cluster.
Package version carries the build's identity, so that an operator looking at a console can say which build they are looking at without asking a cluster.
Package policy is the schema both services load: five tables that answer four questions and no others — who is in which internal group, what a group adds to a token, how long a token lives, and which client may be issued one.
Package policy is the schema both services load: five tables that answer four questions and no others — who is in which internal group, what a group adds to a token, how long a token lives, and which client may be issued one.
Package tokens trades one token for another, and writes the two shapes a credential helper has to speak.
Package tokens trades one token for another, and writes the two shapes a credential helper has to speak.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL