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

access-roster

module
v1.20.0 Latest Latest
Warning

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

Go to latest
Published: Sep 20, 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; the same file says who belongs in which GitHub team, and a controller keeps the teams that way. A console shows you who holds what and why, and an audit trail in S3 says who did what.

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.

What ships

Every artifact is stamped by one tag, vX.Y.Z; pin one version of this repository.

Artifact Published at For
access-issuer chart and image oci://ghcr.io/truvity/charts/access-issuer, ghcr.io/truvity/access-roster/access-issuer the installation, once. One process: the directory, the policy, the OpenID provider, the login page, the console and the audit trail shipped
github-roster image, in the same chart ghcr.io/truvity/access-roster/github-roster a second process: one loop that keeps every connected GitHub organisation's teams as the policy says, reporting to the console shipped
access-proxy chart oci://ghcr.io/truvity/charts/access-proxy every console with no OpenID flow of its own shipped
Go module github.com/truvity/access-roster services and consoles in Go: verify a bearer, read the caller's groups shipped
TypeScript package @truvity/access-roster on GitHub Packages console UIs: useIdentity() over /.access/whoami; Node services: verify a bearer shipped
accessctl the release's archives, and a Nix flake on every release people on laptops and CI jobs: one sign-in, then kubeconfigs, AWS credentials, a token for any audience, and short-lived certificates a secret manager mints shipped
GitHub Action truvity/access-roster@<commit> workflows: one exchange, then a kubeconfig, AWS profiles, or a GitHub App token shipped
the policy one file, one schema the issuer and the controller shipped
an Entra directory backend — a second corporate directory, behind the same workspace record planned

Who it is for

A platform team running Kubernetes, with the Gateway API, cert-manager, and a corporate directory in Google Workspace, that wants one issuer for its clusters, cloud accounts, consoles and CI instead of an identity product. access-proxy needs Envoy Gateway. A Valkey (for more than one replica and for every proxy), an S3 bucket (for an audit trail that is a record) and OpenBAO (for certificates) are optional. None of those is installed here: the charts point at them. Nor is the signing key minted here — cert-manager issues it, or the installation delivers it — and no password, MFA or device policy lives here: sign-in stays with the corporate directory.

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. One accessctl login on your laptop, and kubectl works on every cluster you are granted, AWS credentials come with no long-lived key, and a token for any other audience is one command away. Link your GitHub account once and the teams the policy puts you in follow. 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, the ref and, if you want, the repository's visibility. The same accessctl, kubeconfig and AWS profile a person uses on a laptop work unchanged inside the job. 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, every open session, and every GitHub organisation with what the controller would change and why. A leaver disappears from the directory and, within the freshness window, from everything downstream, GitHub teams included. Every sign-in, refusal, exchange, revoke and console action is one record in an S3 bucket you own. What the console adds at runtime lives in a handful of Secrets that a copy of restores.

The model

Four nouns. The directory says who a person is and which directory groups they are in. The policy maps directory groups, CI jobs and workloads into internal groups, named <scope>:<thing>:<role>. A client is everything that trusts the issuer — a cluster, a cloud role, a console — and names the internal groups it requires. A token is minted for one client, carries the internal groups, and is refused before it exists when none of them is required.

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"]
    ctl["the GitHub controller<br/>keeps each organisation's teams as the policy says"]
  end

  proxy["access-proxy<br/>one per console with no OIDC of its own"]
  apps["Kubernetes · AWS · ArgoCD · Kargo · consoles"]
  orgs["GitHub organisations"]
  s3[("S3<br/>the audit trail")]

  idp -- "sign-in, and directory reads" --> iss
  gh -- "token exchange" --> iss
  k8s -- "token exchange" --> iss
  iss --> proxy --> apps
  iss -- "trusted by" --> apps
  iss -. "who holds which group" .-> ctl
  ctl -- "invites, teams, removals" --> orgs
  iss -- "every event" --> s3

One chart, one Valkey, one bucket. 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. The GitHub controller is a second process from the same chart, asking the issuer who holds which group and acting on GitHub with an App the organisation's owner created from the console.

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, and a runner App per tier for self-hosted runners
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.

Everything in both columns is built and in use, with one exception: the second directory backend (Entra) is designed behind the same workspace record and not written yet. architecture.md says how each row is expressed in configuration.

Install and a worked example

helm install access-issuer oci://ghcr.io/truvity/charts/access-issuer \
  --version X.Y.Z --namespace access-issuer --create-namespace \
  --values issuer-values.yaml
issuerURL: https://access.example.com      # stable for the life of the installation
route:
  host: access.example.com
  rootRedirect: /console/
  gatewayClassName: example-gateway-class
  certificate: { issuerName: example-ca, issuerKind: ClusterIssuer }
valkey:
  address: valkey.access-issuer.svc:6379
oauthClient:
  secret: { name: access-issuer-google-client }   # keys client-id and client-secret
console:
  client: access-console
policy:
  groups:
    all:access-roster:operator:
      members: [platform-admins@example.com]
      matchers:                                   # the first way in: recovery
        - service_account: { namespace: access-issuer, name: access-issuer-recovery }
    all:access-roster:viewer:
      matchers: [{ email_domain: example.com }]
  clients:
    access-console:
      kind: public
      redirects: [https://access.example.com/console/]
      requires: [all:access-roster:operator, all:access-roster:viewer]

Then sign in once with a recovery token (kubectl -n access-issuer create token access-issuer-recovery --audience access-issuer-recovery), and the console's Overview walks the rest: connecting the directory, and the first operator who signs in as themselves. docs/operations/adoption-plain-helm.md is the whole walk-through, with the prerequisites and an access-proxy beside it.

Conformance

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

Last run 2026-09-12 against the deployed issuer at v1.0.0, from the suite running in the cluster.

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

The Foundation's rule is that only FAILED or INTERRUPTED disqualify a profile. REVIEW is a screenshot the suite hands to a person; all twelve were looked at on this run and each shows the page its step demanded. WARNING is mostly personal data this issuer declines to hold — a birthdate, a gender, a locale — with one real defect among them found and fixed in v0.15.2. The logout pair the Foundation requires for a submission, RP-Initiated plus Back-Channel, is green for the first time; the first Back-Channel run found two defects, fixed in v0.17.1. Every column, and why, is in docs/conformance.md.

Documentation

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/operations/adoption-plain-helm.md, docs/reference/configuration.md, then docs/operations/connect-runbook.md
run it: what to check, what to back up, how to restore docs/operations/runbook.md, configuration.md — restoring from the Secrets alone
use it from a laptop or a CI job docs/reference/accessctl.md
put a console behind the gateway docs/connect/console-app.md
keep a GitHub organisation's teams in step with the policy docs/connect/github-organisation.md
declare GitHub Apps as data and create them from the console docs/connect/github-apps-catalogue.md
give a Pulumi or Terraform program that manages the organisation an identity of its own docs/connect/infrastructure-as-code.md
connect a cluster, an AWS account, ArgoCD, Kargo, a workflow docs/connect/
mint a short-lived SSH, database or client certificate docs/connect/openbao.md
see what the conformance suite said, and why docs/conformance.md
run the conformance suite docs/operations/conformance.md
build a service that accepts both people and workloads docs/connect/service-to-service.md

The rule that makes this repository public

Mechanism only. Nothing here names an account, a zone, a hostname, a cluster, an issuer or a secret path of any installation: every such thing is a value with a neutral example, and the installation supplies it from its own repository. Examples use example.com, *.example and the acme and globex organisations. The rule covers code, docs, the CHANGELOG, tests, commit messages and pull request text, because public history cannot be unpublished. Review enforces it today; the shared leak canary is not vendored in this repository yet.

This repository follows the shared component contract.

Status

Used in production by its maintainers. CHANGELOG.md says what exists at each version, and releases are on the releases page.

Development

devbox shell        # or direnv: Go, buf, golangci-lint, helm, just, lefthook
just check          # build, test, lint, chart-lint, archive-check, docs-check, ts, console, vuln
just generate       # proto → gen/ after a contract change; the generated code is committed

CONTRIBUTING.md has the conventions and the console's build order.

Releasing

Push a tag vX.Y.Z: the release workflow publishes the images, both charts, accessctl and its Nix flake, and the TypeScript package at that version, and the Go module and the Action are the same tag. Auto-release is present but not armed, so every release today is a manual tag; when armed it cuts patches only, and minors and majors stay manual.

Licence

MIT — see LICENSE.

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.
github-roster command
Command github-roster is the GitHub controller: it makes each GitHub organisation's teams match the policy's github table, and reports what it found and did.
Command github-roster is the GitHub controller: it makes each GitHub organisation's teams match the policy's github table, and reports what it found and did.
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.
audit
Package audit is what happened in access-roster, where an operator can read it.
Package audit is what happened in access-roster, where an operator can read it.
audit/sinkrpc
Package sinkrpc joins what records audit events to what writes them down, in the same process.
Package sinkrpc joins what records audit events to what writes them down, in the same process.
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.
githubapp
Package githubapp is the part of GitHub's App API that connecting an organisation needs — creating an App from a manifest, finding where it is installed, and uninstalling it — and the part linking a person's GitHub account needs: authorizing the link App as themselves, and reading back who they are and which addresses GitHub verified for them.
Package githubapp is the part of GitHub's App API that connecting an organisation needs — creating an App from a manifest, finding where it is installed, and uninstalling it — and the part linking a person's GitHub account needs: authorizing the link App as themselves, and reading back who they are and which addresses GitHub verified for them.
githubapp/catalogue
Package catalogue is GitHub Apps declared as data: what each App is called, which organisation owns it, what it may do, and — for a later exchange that mints installation tokens — which groups may ask for how much of it.
Package catalogue is GitHub Apps declared as data: what each App is called, which organisation owns it, what it may do, and — for a later exchange that mints installation tokens — which groups may ask for how much of it.
githubapp/githubfake
Package githubfake is one GitHub organisation, in memory, answering the calls the controller makes and changing as they change it.
Package githubfake is one GitHub organisation, in memory, answering the calls the controller makes and changing as they change it.
githubapp/mints
Package mints keeps the last installation tokens asked of each GitHub App, so that an App's page can show them.
Package mints keeps the last installation tokens asked of each GitHub App, so that an App's page can show them.
githubroster/app
Package app assembles the GitHub controller from its configuration: the policy's GitHub table, the console it reads with its own ServiceAccount token, the report it writes, and the credentials mounted beside it.
Package app assembles the GitHub controller from its configuration: the policy's GitHub table, the console it reads with its own ServiceAccount token, the report it writes, and the credentials mounted beside it.
githubroster/catalogueapp
Package catalogueapp is what creating a catalogue App leaves behind: the App's record, and its key.
Package catalogueapp is what creating a catalogue App leaves behind: the App's record, and its key.
githubroster/connection
Package connection is what connecting a GitHub organisation leaves behind, shared by the service that writes it and the controller that reads it.
Package connection is what connecting a GitHub organisation leaves behind, shared by the service that writes it and the controller that reads it.
githubroster/controller
Package controller is the GitHub controller's loop: every pass, for every organisation the policy binds, read what GitHub holds and who holds the bound groups, decide, act where the organisation is enabled, and report.
Package controller is the GitHub controller's loop: every pass, for every organisation the policy binds, read what GitHub holds and who holds the bound groups, decide, act where the organisation is enabled, and report.
githubroster/link
Package link is what a person linking their GitHub account leaves behind, shared by the service that writes a link and the controller that keeps checking it.
Package link is what a person linking their GitHub account leaves behind, shared by the service that writes a link and the controller that keeps checking it.
githubroster/reconcile
Package reconcile is what the GitHub controller decides, with no network in it: from the policy's bindings, the holders of the groups they name, and what GitHub holds, what one organisation should look like and what to change to get there.
Package reconcile is what the GitHub controller decides, with no network in it: from the policy's bindings, the holders of the groups they name, and what GitHub holds, what one organisation should look like and what to change to get there.
githubroster/runnerapp
Package runnerapp is what creating a runner App leaves behind: the App a self-hosted runner scale set in one tier registers with in one organisation.
Package runnerapp is what creating a runner App leaves behind: the App a self-hosted runner scale set in one tier registers with in one organisation.
githubroster/status
Package status is the contract between the GitHub controller and the console: what the controller last did to each organisation, and why, written where the console can read it.
Package status is the contract between the GitHub controller and the console: what the controller last did to each organisation, and why, written where the console can read it.
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.
s3audit
Package s3audit keeps the audit trail in S3: the durable record of what happened in access-roster, and the store the console's Audit page reads.
Package s3audit keeps the audit trail in S3: the durable record of what happened in access-roster, and the store the console's Audit page reads.
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.
telemetry
Package telemetry is the OpenTelemetry metrics both processes publish.
Package telemetry is the OpenTelemetry metrics both processes publish.
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