Sign-in file format
auth.yaml sits in the estate's ownership directory, beside teams.yaml and
users.yaml. It says two things: which ways of signing in this Instance
offers, and what a group your identity provider asserts means inside the
estate.
The file is optional. Without it, the Instance offers basic auth alone,
verified against the password hashes users.yaml already carries.
teams.yaml # the team tree
users.yaml # who may sign in, and the Owner each acts as
auth.yaml # how they sign in, and what a group means
idp-metadata.xml # a SAML identity provider's own metadata document
Editing the file is a pull request, exactly like editing who may author. A merged change takes effect on the next poll of the estate; nothing restarts.
No field takes a secret
Every field that concerns secret material names it. The name is not the material: the deployment places a file of that name in the secret directory, and the process reads it there.
printf %s "$CLIENT_SECRET" > /run/secrets/staff-oidc
A secret name is lower-case letters, digits and hyphens, so a name can never describe a path. A name nobody placed a file for stops the start rather than withdrawing sign-in quietly:
serve: provider "staff": the secret "staff-oidc" is named, and there is no file of that name in /run/secrets
providers
One document with a providers key holding a list. The order is the order the
sign-in surface offers them in.
| Field | Type | Required | Description |
|---|---|---|---|
kind |
string | yes | basic, oidc or saml. |
name |
string | no | What the sign-in surface shows, and what the round-trip paths carry. Defaults to the kind. |
groups_claim |
string | no | The claim or attribute this provider carries group membership in. See groups. Not valid on basic. |
basic
Bootstrap and break-glass, verified against the hashes in users.yaml.
Nothing else is configured. Production sign-in belongs on oidc or saml.
A deployment can refuse basic auth outright, whatever the estate declares, by
running the server with -basic-auth=false. Use it where the people signing
in are not the people who run the deployment. An estate that declares basic
on such a deployment stops the start and names the entry.
providers:
- kind: basic
Generate a hash with telecraft passwd, which reads the secret from standard
input and prints the value for the user's password field.
oidc
The authorisation-code flow against any OpenID Connect issuer, including a self-hosted one with no route to the internet.
| Field | Type | Required | Description |
|---|---|---|---|
issuer |
string | yes, unless preset |
The issuer URL, exactly as the provider names itself. Discovery is served beneath it. |
preset |
string | no | A provider Telecraft already knows the issuer of: google or entra. Write this or issuer, never both. |
directory |
string | with entra |
Your own directory, which is what gives the issuer its address. Only for a preset that gives each customer one. |
client_id |
string | yes | What this Instance is registered as with the issuer. |
secret |
string | yes | The name of the client secret, never its value. |
providers:
- kind: oidc
name: staff
issuer: https://issuer.example
client_id: telecraft
secret: staff-oidc
Register https://telecraft.example/api/v1/auth/staff/callback as the redirect
URI with your provider, replacing staff with the name you gave the entry.
The ID token must carry an email claim, because the email is what joins the
identity to users.yaml. Nothing else is fetched: the flow reads the token it
is given.
presets
A preset is the issuer of a provider most organisations already have, so that an entry using one writes a client id and a secret name and nothing it would otherwise have to look up. It changes nothing else: the flow, the redirect URI and the email claim are the same.
providers:
- kind: oidc
preset: google
client_id: telecraft
secret: google-client-secret
- kind: oidc
preset: entra
directory: acme.onmicrosoft.com
client_id: telecraft
secret: entra-client-secret
| Preset | Shows as | Needs a directory |
|---|---|---|
google |
no | |
entra |
Microsoft Entra ID | yes |
The name defaults to what the preset is known by, so a sign-in surface reads
like the button it is. Write name to call it something else.
An estate that names no preset is unchanged, and an air-gapped Instance reaches none of these addresses unless its own estate authored one.
saml
The service-provider-initiated flow against a SAML 2.0 identity provider. The Instance sends the person out on the redirect binding and the identity provider returns them by posting a signed assertion back.
| Field | Type | Required | Description |
|---|---|---|---|
entity_id |
string | yes | What this Instance calls itself: the entity identifier the identity provider knows it by, and the audience every assertion must be restricted to. |
metadata_file |
string | yes | The identity provider's metadata document, saved beside auth.yaml. A file name, never a path. |
secret |
string | no | The name of the service provider key pair: the certificate and private key, PEM, in one file. Set it when the identity provider requires signed authentication requests or encrypts its assertions. |
email_attribute |
string | no | The attribute the assertion carries the email address in. |
name_attribute |
string | no | The attribute the assertion carries the display name in. |
providers:
- kind: saml
name: staff
entity_id: https://telecraft.example/saml
metadata_file: idp-metadata.xml
Save the identity provider's own metadata document beside auth.yaml and name
it here. It is read at load, so a document that names no single sign-on
endpoint, or no signing certificate, stops the start. It is not secret: it is
the identity provider's public description of itself, and it belongs under
review, because its signing certificate changing is exactly the kind of change
somebody should see.
Register https://telecraft.example/api/v1/auth/staff/callback as the
assertion consumer service with your identity provider, on the HTTP POST
binding, replacing staff with the name you gave the entry.
SAML needs HTTPS. The assertion arrives as a form post from the identity
provider's page, and the cookie that carries the sign-in attempt across that
post is only sent over an encrypted connection. Serve the Instance behind TLS
and give it an external URL that begins with https://, or the start refuses
with the reason.
Sign-in is service-provider-initiated: the Instance sends the person out and matches the assertion that comes back to the attempt that asked for it. An assertion posted at somebody who never started a sign-in here is refused, so an identity-provider-initiated link into the console will not work.
Leave email_attribute and name_attribute unset unless your identity
provider releases those claims under names of its own. Without them the
assertion is read for the usual names, and the address falls back to the name
identifier when that is an email address.
groups
An optional list mapping a group the identity provider asserts to the Owner its
members act as. It resolves who somebody is at sign-in. It never writes the
team tree: the Owners and the Teams stay in teams.yaml, and a rule naming an
Owner that file does not hold is a load error.
| Field | Type | Required | Description |
|---|---|---|---|
group |
string | yes | The group, exactly as the identity provider spells it. Matching is exact. |
owner |
string | yes | The Owner its members act as. Must be in the team tree. |
providers:
- kind: oidc
issuer: https://issuer.example
client_id: telecraft
secret: staff-oidc
groups_claim: groups
groups:
- group: platform-engineering
owner: gateway-owners
- group: security
owner: pii-guardians
Two halves, and they are separate on purpose. The provider entry says where
the groups are, because that is the identity provider's own vocabulary: a claim
name for OIDC, an attribute name for SAML. The groups list says what a group
means, and there is one such list for the whole Instance, because two identity
providers asserting the same group name are naming the same people.
Rules that mention groups but that no provider feeds are a load error: a mapping nothing reaches would place nobody while reading as though it worked.
How somebody is placed
- If
users.yamlnames their email, that entry decides. It is the explicit statement about one person, and a rule never overrides it. - Otherwise, the first rule in
groupswhose group they carry decides. - Otherwise they cannot sign in, and the refusal names both files.
Order is authority. Somebody in two mapped groups acts as the Owner of the first rule that matches, so you decide precedence by the order you write the rules in.
Membership is resolved again on every request, not decided once at sign-in. Repointing a group at another Owner, or moving that Owner in the tree, changes what somebody may do on their next request. A rule you add for a group they hold but were not carrying applies at their next sign-in.
What a session carries
A session carries the groups this file names and no others. A directory can put one person in hundreds of groups, and the ones the estate says nothing about could never change an answer.
When to use it
Use users.yaml alone while the list of people is short enough to read. Reach
for the mapping when it is not, or when your identity provider is already where
joiners and leavers are managed. The two work together: name the handful of
people who need a specific Owner, and let a rule place the rest.
Basic auth asserts nothing about groups. People who sign in with a password are
placed by users.yaml and nowhere else.
Loading
The whole file is refused, and the start stops, when any of these is true:
- It declares no providers.
- A provider names a kind this build does not offer.
- Two providers share a name.
- A field carries a value where the schema takes a name.
- A named secret has no file of that name in the secret directory.
- A SAML metadata document is missing, unreadable, or names no endpoint or certificate.
- A group maps to an Owner the team tree does not hold, or a group is mapped twice.
- Groups are mapped and no provider names the claim they arrive in.
Every problem in the file is reported at once, so fixing it is one pass.
See also
- Estate layout for where these files sit.
- Run an Instance for serving the console and placing secrets.
- Command line reference for
telecraft serveandtelecraft passwd.