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

roster

module
v0.1.2-0...-9af0e46 Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: Apache-2.0

README

roster

The store that answers who somebody is. People, the external identities they sign in with, the addresses they use, and the organisations, sites and teams they belong to -- held in one schema, so that changing identity provider is changing a login screen rather than changing the record of who works here.

It is one layer of an identity system and not the whole of one: the protocol is Ory Hydra's and the sign-in flow is an app's. roster owns the records they both ask about, and owns sub.

Read docs/glossary.md first. A handful of words here -- holder, tenant, roster operator, roster user, admin console, user console -- mean one thing each and are used that way everywhere, including in commits and issues. cli/docs_test.go holds the documentation to it.

roster stores facts and verifies claims about them. It never issues anything a third party verifies.

So it checks a password and a second factor, and it does not mint a session for somebody else's browser or sign a token another system verifies alone. docs/position.md is that line applied, including what it means you still have to run.

Quick start

Everything, on Postgres, with a customer already in it:

docker compose up --build
the admin console http://localhost:8081/ admin / admin
the user console, one customer's own organisation http://localhost:8080/ erin / correct horse battery staple
a customer's own people, through the app in front http://localhost:8090/ erin / correct horse battery staple
a product app, holding its own token http://localhost:5555/ sign in through Hydra
the same page behind oauth2-proxy http://localhost:4180/ the other relying-party shape
the data plane, for your app localhost:50051, or :8080 over HTTP a key, below
the directory ldap://localhost:1389 an app password

That is four processes, Hydra, and two demo products (docs/relying-party.md). docker compose down -v takes it all away.

Or from a checkout, in five commands
go run ./cmd/roster init                      # the operator who runs this deployment
go run ./cmd/roster serve &                   # the listeners roster.yaml names

roster tenant add @newco                      # your first customer
roster holder add @newco/admin                # somebody in it
roster role   add @newco/everything '{"methods":["/roster.*/*"]}'
echo '{"role":  {"slug":{"alias":"everything","tenant":{"alias":"newco"}}},
       "holder":{"slug":{"alias":"admin",     "tenant":{"alias":"newco"}}}}' \
  | roster binding add -

KEY=$(roster key add --tenant newco --holder admin --allow '/roster.*/*')

init writes the operator and no customer -- a tenant is a customer, and one written by a command is a customer nobody asked for. The key is printed once and resolves to that person, so the loop closes with a call:

curl -sS -X POST http://127.0.0.1:8080/roster.MeService/Get \
  -H 'content-type: application/json' -H 'connect-protocol-version: 1' \
  -H "authorization: Bearer ${KEY}" -d '{}'

A password instead of a key, for somebody at a browser:

roster vouch reset @newco/admin               # generated here, printed once

docs/usage/tutorial.md is this walked slowly, from an empty directory to a person signing in, and cmd/tutorial_test.go runs it on every commit so the page cannot drift from the binary.

Where to go next

you want to
decide whether roster is the right thing at all docs/position.md
follow one deployment end to end docs/usage/tutorial.md
know what to type docs/usage/ -- the CLI, customers, ways in, permissions
run it for real docs/operating.md -- databases, listeners, processes, the trail, replicas, TLS
sign people in docs/login.md, and docs/relying-party.md for the app in front
serve a directory docs/ldap.md
let a directory provision people here docs/scim.md
know the tables docs/entity.md
know what a word here means docs/glossary.md

docs/README.md is the same map with the reading order on it.

What it does that is worth knowing before you look

  • Verifies a password without handing the hash out -- argon2id, timing-safe, with attempt counting and a lockout, all in the one place that holds the row. A second factor (TOTP or WebAuthn) is the same story.
  • Answers to API keys. A second roster runs in the same process on its own database, holding the roster operators and the keys of the apps they run across tenants -- so a key never lives in the tables it protects.
  • Roles bound at a scope, in the shape Kubernetes settled on: a Site is a namespace, a role with no site is a ClusterRole, and nobody may grant what they do not hold.
  • /me -- who the caller is and every RPC they may call, in one round trip, from the same union the server enforces.
  • Answers about a token it issued, so a product app handed a key learns which person it stands for. Opaque on purpose: revoking is a delete, and it works now.
  • A terminal is enough. Every RPC that can have a command has one, and the same binary is a customer's own client (client.addr and their key) as well as a shell on the box.

What the image is made of

Every image main pushes to ghcr.io/lesomnus/roster carries its SBOM beside it: a CycloneDX document attached to the digest as an OCI referrer, and the same document signed as a keyless Sigstore attestation by ci.yml on main (lesomnus/sbom-attach). It lists the Go modules both binaries carry, the base image's Debian packages, and the npm packages the pages are bundled from -- which the image cannot show, so they come from ts/package-lock.json, without the dev dependencies. An image pushed before this was added has none.

Read it without pulling the image -- by digest, since a tag moves:

oras discover --artifact-type application/vnd.cyclonedx+json ghcr.io/lesomnus/roster@sha256:…
oras pull ghcr.io/lesomnus/roster@sha256:…        # the referrer's digest: sbom.cdx.json
trivy image --sbom-sources oci ghcr.io/lesomnus/roster@sha256:…

and check that this repository's workflow on main made it and nothing has changed it since:

cosign verify-attestation --type cyclonedx \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity https://github.com/lesomnus/roster/.github/workflows/ci.yml@refs/heads/main \
  ghcr.io/lesomnus/roster@sha256:…

That proves where the document came from, not that it is complete: a dependency that leaves no trace in the image or in the lockfile is in neither scan. And the signing is public -- Sigstore's transparency log records the repository, the workflow, the commit and the digest of each one.

Working on roster

Most of this app is generated from proto/, so the usual shape of a change is: edit the schema, regenerate, then write the part no schema can state.

go tool pd gen . && go tool pd gen --ts .    # after touching proto/
./scripts/test.sh                            # everything CI decides on
  • CLAUDE.md -- the rules: what is generated, what is yours, and the one about fixing payday rather than working around it here.
  • docs/development.md -- the same ground at length: generation, upgrading payday, the three pages, the sandbox, and what the generator refuses.
  • docs/baseline.md -- the promises a normal user relies on, each pinned to its tests.
  • docs/roadmap.md -- how it was built and how far new work has got.

roster is also the second app payday is tried against, and the more demanding one.

Directories

Path Synopsis
Package account is roster's own front door: the app a customer's people sign in at and manage their own account through.
Package account is roster's own front door: the app a customer's people sign in at and manage their own account through.
Package arrives is a `Connection` row turned into a relying party.
Package arrives is a `Connection` row turned into a relying party.
Package cli is this app's command line, and what only a process does.
Package cli is this app's command line, and what only a process does.
cmd
Package cmd is this app's own wiring, and it is short on purpose.
Package cmd is this app's own wiring, and it is short on purpose.
roster command
Command roster is a payday app.
Command roster is a payday app.
examples
product command
Package main is a product app that trusts `sso.contoso.example`, and nothing else.
Package main is a product app that trusts `sso.contoso.example`, and nothing else.
sso
Package sso signs somebody in with an external identity provider and finds out who they are here.
Package sso signs somebody in with an external identity provider and finds out who they are here.
Package frontdoor is the sign-in an app would otherwise write itself.
Package frontdoor is the sign-in an app would otherwise write itself.
internal
ent
idptest
Package idptest is a provider that agrees with everybody.
Package idptest is a provider that agrees with everybody.
Package ldap is a directory over roster: what `roster ldap serve` is.
Package ldap is a directory over roster: what `roster ldap serve` is.
wire
Package wire is the LDAPv3 this repository speaks, and nothing above it.
Package wire is the LDAPv3 this repository speaks, and nothing above it.
Package login is the Login App: the box Hydra hands a `login_challenge` to, and the one that answers `acceptLoginRequest{subject}`.
Package login is the Login App: the box Hydra hands a `login_challenge` to, and the one that answers `acceptLoginRequest{subject}`.
Package rstr holds the messages of the schema, the service servers generated from them, and the pieces every implementation of those servers shares.
Package rstr holds the messages of the schema, the service servers generated from them, and the pieces every implementation of those servers shares.
Package scim is roster as a SCIM 2.0 service provider (RFC 7643, RFC 7644): where a directory -- Entra, Okta, OneLogin -- pushes the people it provisions, what changed about them, and their leaving.
Package scim is roster as a SCIM 2.0 service provider (RFC 7643, RFC 7644): where a directory -- Entra, Okta, OneLogin -- pushes the people it provisions, what changed about them, and their leaving.
server
console
Package console is what a console asks that no entity answers.
Package console is what a console asks that no entity answers.
core
Package core is what roster means that no schema can state.
Package core is what roster means that no schema can state.
forget
Package forget is what "destroy this person" means here, and it is one act with two triggers.
Package forget is what "destroy this person" means here, and it is one act with two triggers.
front
Package front answers the two questions a front door has before it knows anything.
Package front answers the two questions a front door has before it knows anything.
keys
Package keys is how a caller proves it may call this deployment.
Package keys is how a caller proves it may call this deployment.
me
Package me answers what a caller is, in one round trip.
Package me answers what a caller is, in one round trip.
pd
Package pd is what the schema said, as code.
Package pd is what the schema said, as code.
portrait
Package portrait is a picture of somebody as roster keeps one: an image handed over once, rendered at the sizes a screen draws a person, and kept on their row as `data:` URIs.
Package portrait is a picture of somebody as roster keeps one: an image handed over once, rendered at the sizes a screen draws a person, and kept on their row as `data:` URIs.
prove
Package prove is how a tenant shows a hostname is theirs.
Package prove is how a tenant shows a hostname is theirs.
session
Package session keeps a console's cookies in a table.
Package session keeps a console's cookies in a table.
sync
Package sync is the stream an app holds open to hear that a decision it made has stopped being good.
Package sync is the stream an app holds open to hear that a decision it made has stopped being good.
vouch
Package vouch is how a secret is used without ever leaving the store.
Package vouch is how a secret is used without ever leaving the store.
vouch/vouchtest
Package vouchtest is an authenticator, for a test that needs one.
Package vouchtest is an authenticator, for a test that needs one.
The consoles' server, in the page.
The consoles' server, in the page.
sandbox
Package sandbox is what the servers in a page share: the two things a message port cannot carry, and nothing else.
Package sandbox is what the servers in a page share: the two things a message port cannot carry, and nothing else.
schema
Package schema is the sandbox's database, already made.
Package schema is the sandbox's database, already made.

Jump to

Keyboard shortcuts

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