Skip to content

Identity

How kubelatch knows who you are, how your account is linked to GitHub or to your organization's identity provider, and what happens to your access when you leave. This also explains why break-glass accounts exist.

Subjects: people and bots

Anything that can hold permissions in kubelatch is a subject. There are two kinds:

  • People. They sign in to the web interface, request credentials for themselves, and can be administrators.
  • Bots. They have no password or session: only credentials. An admin issues theirs from Credentials, or CI obtains them by exchanging its GitHub Actions OIDC token (see trust rules).

Each subject has an immutable login that's never reused, not even after the account is disabled. By default it's the name clusters see it by: user:<login> for people and bot:<name> for bots, plus an internal identifier as uid. If a login were recycled, the cluster's native audit log would mix up two different people. A cluster can name people as its own SSO does instead, such as oidc:ana@acme.com, with its identity template (kubelatch on top of the SSO you already have); bots keep bot:<name>.

There's no self-registration. An admin creates accounts from Users and shares a single-use invitation link (<KUBELATCH_BASE_URL>/account-link#kli_…, valid for 72 h). The other route is the server CLI, kubelatch-server user create (see Server CLI).

How people sign in: passwords, GitHub or OIDC

kubelatch signs people in with local passwords, with GitHub, with an OpenID Connect identity provider, or with GitHub and a provider at once, depending on which of the GITHUB_* and OIDC_* variables are set:

Passwords only With the GitHub App With an OIDC provider
How people sign in Local login and password Sign in with GitHub Sign in with {name}, {name} being the provider's name
Passwords All accounts Only break-glass accounts Only break-glass accounts
Two-factor No Whatever the GitHub organization enforces Whatever the provider enforces
Offboarding Manual Automatic on leaving the organization Manual: the provider gives no member list

Without an external login there's neither two-factor nor automatic offboarding: that mode is meant for trying out kubelatch or for environments without an identity provider (see Security model). In production, configure the GitHub App (GitHub login) or your provider (OIDC login (SSO)). With both on, the login page offers both, and each person can have either identity or both.

Switching modes doesn't touch accounts: same login, same permissions, same credentials. With GitHub, you link each account with its GitHub account; with an OIDC provider, most accounts link themselves on their first sign-in.

Linking an account with GitHub

Linking is associating a kubelatch account with the numeric id of a GitHub account. kubelatch doesn't use the GitHub login as identity, because GitHub allows renaming and recycling it; it only stores it for display and refreshes it when it changes. A GitHub account can only be linked to one kubelatch account.

There are three paths, all with the same result:

  • The invitation link for a new account leads to Link with GitHub and sign in.
  • The Linking link that an admin generates from the account's page in Users (24 h) links an existing account, or switches it to a different GitHub account. Generating it closes that person's sessions at once.
  • A person who can already sign in uses My account → Identities → Link with GitHub, only if their account isn't linked yet.

Unlinking is an admin's job, with Unlink GitHub, and it closes that person's sessions. There's no automatic sign-up: a GitHub account that isn't linked to anyone can't sign in, even if it's a member of the organization.

What signing in with GitHub checks

sequenceDiagram
    participant N as Browser
    participant K as kubelatch
    participant G as GitHub
    N->>K: "Sign in with GitHub"
    K-->>N: Signed state cookie + redirect
    N->>G: Authorizes the GitHub App
    G-->>N: Redirect to the callback with code and state
    N->>K: /api/auth/github/callback
    K->>K: state == cookie
    K->>G: Exchanges the code (client secret)
    K->>G: GET /user (numeric id)
    K->>G: Active organization membership
    K->>G: Does the organization require 2FA?
    K->>K: Account linked to that id and enabled
    K-->>N: Session and redirect to Home

Each step can fail with a code that the login page translates (the list is in Errors). Details that matter:

  • Only active members of the configured organization. A pending invitation doesn't count.
  • The state travels in a signed cookie valid for 10 minutes. The code is exchanged server-side with the client secret, and the person's GitHub token is discarded after login.
  • The callback shares the same per-IP rate limit as password login.
  • GitHub login deliberately ignores the failed-password lockout. If it respected it, anyone could lock out a person who signs in with GitHub by trying passwords against their login.

What kubelatch asks GitHub for

What for With which token Call
Identity of who's signing in The person's, only during login GET /user
Organization membership The person's GET /user/memberships/orgs/<org>, which must return state: active
App installation token An RS256 JWT signed with the App's private key GET /orgs/<org>/installation and POST /app/installations/<id>/access_tokens
Member list (sync) The installation's GET /orgs/<org>/members
Does the organization require 2FA? The installation's The two_factor_requirement_enabled field of GET /orgs/<org>. GitHub only returns it to an App with Administration: read.

The OIDC identity

With an OIDC provider, the identity isn't the e-mail address: it's the provider's issuer, the name of the claim that identifies the person (sub, or oid for Entra ID) and that claim's value. An identity opens a single kubelatch account, and an account holds at most one identity of the provider. Bots never have one.

The e-mail address only matters on a person's first sign-in. While their identity isn't linked yet, kubelatch looks for the account with the e-mail the provider vouches for (verified, in an allowed domain) and links it, if the link policy allows it and exactly one enabled account has that address. From then on the e-mail plays no part:

  • It never replaces an identity of the same provider. If the account it matches already has another identity of the provider, the sign-in is refused (identity_mismatch): that's what protects an account whose e-mail address is handed to someone else after its owner left. An administrator decides: unlink the identity when it's the same person, whose account at the provider was recreated, or disable the old account and create one for the newcomer.
  • An identity stored under another issuer, or another claim name, belongs to another provider. The person's next sign-in replaces it by e-mail, as the link policy allows, and the event says so: that's how a change of provider, or a corrected OIDC_SUBJECT_CLAIM, sorts itself out.
  • If the provider later sends another e-mail for the same identity, kubelatch updates the identity's e-mail and records the change (oidc.email_changed); the account's own e-mail, which an administrator set, doesn't change.

Each link by e-mail leaves an oidc.link event, revokes the account's pending invitation and linking links, and shows up in the administrators' Notices for a week. The other ways to link are the same as with GitHub: an invitation or a Linking link leads to Link with {name} and sign in, and My account offers Link with {name} in its Identities card while the account has no identity of the provider. Unlinking, with Unlink {name}, is an administrator's job and closes the person's sessions. An e-mail address with no account gets in only through a rule an administrator made for its domain, which creates the account in a group, or by asking to join and being approved (Domain access and join requests). The link policies and each provider's details are in OIDC login (SSO); what each sign-in checks is in Security model.

Two-factor: enforced by the organization

kubelatch has no two-factor of its own. It relies on whatever the GitHub organization enforces on all its members. If the organization requires it, anyone who signs in with GitHub has passed it.

kubelatch can only verify this if the App has the Organization → Administration: read permission. With that, and with GITHUB_REQUIRE_ORG_2FA left at its default true, it rejects logins while the organization doesn't require it. Without that permission, GitHub tells it nothing: kubelatch lets people in and warns on every sync. If it can't ask because of a real failure (a 5xx, a timeout), it rejects the login.

In short: 2FA is guaranteed by the organization's configuration, not by kubelatch.

With an OIDC provider, two-factor is whatever the provider asks for: kubelatch doesn't enforce it, but records the authentication methods the provider reports (amr and acr, when the token carries them) in every sign-in event.

Break-glass accounts

With GitHub or OIDC login on, no password opens a session except those of break-glass accounts. They exist for when the external login isn't available: a GitHub or provider outage, the App uninstalled, a configuration error such as a mistyped domain.

  • They can only be marked from the server CLI (kubelatch-server user create --break-glass or kubelatch-server user set-break-glass), never from the interface or the API.
  • They have no two-factor. There should be few of them, with long passwords in a secrets manager, used only in emergencies.
  • On the login page, they follow Sign in with a password, at the foot of the page, to a step of its own.
  • A break-glass account that gets linked to GitHub becomes subject to automatic offboarding like any other. Ones that are never linked stay outside the sync.
  • Linking one to the OIDC provider doesn't take its password away: it still signs in with it when the provider is down.
  • Every such sign-in is e-mailed to the administrators and shown in their bell for 12 hours (Notifications).

kubelatch prevents removing the role, disabling, unlinking, or unmarking as break-glass for the last administrator who can sign in: with a usable password, with GitHub linked while GitHub login is on, or with an identity of the configured provider while OIDC login is on. On startup with GitHub or OIDC on and no admin able to sign in, it logs this as an error.

This doesn't replace native emergency access to each cluster, which doesn't depend on kubelatch (see Accounts and recovery).

Automatic offboarding

With the GitHub App configured, whoever leaves the organization gets disabled automatically, through two paths:

  • Hourly sync. Once an hour (the first time, one minute after starting), one replica lists the organization's members with an App installation token and disables linked accounts whose id no longer appears. It also refreshes renamed GitHub logins.
  • member_removed webhook. With GITHUB_WEBHOOK_SECRET, GitHub notifies immediately and the offboarding is instant. The HMAC signature is the authentication.

The sync is deliberately conservative. It never disables on an error: a 5xx, a timeout, a failing page, the App being uninstalled, or a list with zero members all abort the pass without touching anyone. And it never re-enables: if someone rejoins the organization, an admin re-enables them by hand.

That's why the sync doesn't fix an unwarranted offboarding, such as one caused by a repeated webhook delivery, which isn't deduplicated (see Security model). What it does fix is a missed webhook: it disables whoever left even if the delivery never arrived.

An OIDC provider gives kubelatch no member list, so with OIDC alone there's no automatic offboarding: disabling someone in the provider stops their next sign-in, while what they already hold keeps working until it expires or an admin disables the account (see Security model). Whoever also has a GitHub account linked is still disabled by the sync when they leave the organization.

What happens when an account is disabled

Whoever disables it (an admin with Disable, the sync, or the webhook), the effects are the same and happen at once:

  • Sessions: all closed. The next request with that person's cookie is rejected.
  • Pending links: invitation or linking links are revoked.
  • Credentials: all valid ones are revoked. The next request to the proxy gets 401, and open exec or watch sessions are cut within 10 s at most.
  • Agents acting on their behalf: every delegated session is cut. Its tokens stop working, its pending requests close, and the event user.disable counts the sessions in details.agent_sessions_revoked.
  • Clusters: the next reconciliation removes the person from the list of users the proxy can impersonate, and their bindings too if no one else holds them.
  • Permissions: kept, but they don't count while the account is disabled.

Re-enabling restores the effect of the permissions, but not the credentials or the agents' sessions: those stay revoked, new credentials have to be issued and each agent has to be connected again. The credentials' maximum TTL (30 days for people by default) limits what an oversight could leave open.

Sessions

The web session is a signed cookie that lasts 12 h (how it is protected is in the Security model). Signing out, changing the password, using a reset or linking link, unlinking GitHub or the OIDC identity, disabling the account, or an admin's Sign out web sessions invalidate all of that person's web sessions at once.

The web session isn't usable to talk to clusters. That requires a credential, which is independent of the session.