Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

User accounts

fogwall supports four authentication backends. LDAP, AD, and OIDC are the expected production choices. Local auth manages users in the database (add/remove users, reset passwords via the dashboard) with passwords defined in YAML config. It is self-contained and requires no external directory, but every user must be provisioned manually. It is suitable for small teams or single-operator deployments; LDAP, AD, or OIDC are preferable when the org already has a directory.

Authentication backends

Backendauth.providerWhen to use
Local (static)localDev / demo only. Passwords in YAML config.
LDAPldapGeneric LDAP directory (OpenLDAP, 389 DS, etc.)
Active DirectoryadOn-premises AD domain. UPN bind, no user-dn-patterns needed.
OIDCoidcKeycloak, Okta, Entra ID, Dex, etc.

See Authentication for the full config reference and worked examples.

How users are provisioned per backend

Local: users are defined entirely in the users: YAML block. Each entry needs a username, BCrypt password hash, and at least one email. Roles and SCM identities are set here too. Changes require a config reload.

users:
  - username: alice
    password-hash: "{bcrypt}$2a$12$..."
    roles: [ADMIN]
    emails:
      - alice@corp.example.com
    scm-identities:
      - provider: github/github.com
        username: alice-github

LDAP / AD: users are provisioned automatically on first login. The proxy creates a user record from the directory attributes returned at bind time. The mail attribute (if present) is stored as a locked email — locked means it cannot be edited from the profile UI, since the directory is the source of truth. Roles are assigned via auth.role-mappings (LDAP group CNs → role names). By default, a user who does not match any mapped group is denied access entirely — they authenticate successfully against the directory but are refused by the proxy. This is intentional: the proxy is not open to all directory users by default. To grant baseline access, map a broad group (e.g. all-staff) to READER or USER, or set auth.default-role to admit anyone who authenticates. See Role mappings.

SCM identities and permissions still need to be set up after first login — either by the user themselves from their profile page, by an admin via the dashboard, or via a supplemental users: YAML entry (which can carry scm-identities without a password-hash for IdP-authed users).

OIDC: same auto-provisioning and deny-by-default behaviour as LDAP. Groups from the configured groups-claim (default: groups) are mapped to roles via auth.role-mappings. Email comes from the email claim in the ID token. Users whose token carries no matching group claim are denied access.

Every push must resolve to a fogwall user, in both proxy modes and over SSH: the push credential has to map to a user from the users: block or the database. A push that maps to none is refused as “Identity not linked”. users: [] with no other user source refuses every push.

Dashboard roles

Roles control what a user can do in the dashboard and REST API:

RoleWhat it grants
READERView push records, repositories, providers, and fetch and SCM API activity. Changes nothing
USER (default)View push records; approve or reject pushes they have REVIEW permission on; manage their own profile (emails, SCM identities)
AUDITORView push records, plus read-only access to users, groups and their permissions. Changes nothing, even alongside ADMIN
ADMINEverything USER can do, plus: create/delete users, reset passwords, manage any user’s profile, view all push records
SELF_CERTIFYGrants the capability to self-approve pushes. This is the prerequisite gate — it must be present before any per-repo SELF_CERTIFY permission takes effect.

Every session holds at least one of READER, USER, AUDITOR or ADMIN; SELF_CERTIFY alone admits no one. Acting needs USER or ADMIN. With an IdP, a mapping grants the role it names and ADMIN brings USER with it; a user matching no mapping is refused, or signs in with auth.default-role when that names a role. Map a broad group to USER, or set default-role: USER, to let all directory members act.

READER makes a transparent deployment possible: anyone can see what is going on without being able to review, cancel, forward, file issues or edit a profile. It is additive, so READER with USER acts as USER. A repository permission does nothing for a user who holds neither USER nor ADMIN, and Test Permission on their Permissions tab says so.

AUDITOR lets someone read the evidence of who was permitted to do what without being able to change it. An auditor’s session is read-only: every change answers 403, including approving, rejecting, cancelling or forwarding a push, filing an issue, and editing their own profile, and the pages render without those controls. This holds even for a user who is also ADMIN.

An auditor holds no repository permission: entries naming them, directly or through a group, are ignored, so they cannot push, propose or merge. The dashboard refuses to grant an auditor a permission or add them to a group, and startup fails when the config does either for a user it declares an auditor. A user who becomes an auditor keeps their existing entries on record, inert. Assign the role the way any other role is assigned: AUDITOR under auth.role-mappings for LDAP, AD and OIDC, roles: [AUDITOR] on a configured local user, or the Role field in the Add User form.

ROLE_SELF_CERTIFY is the prerequisite gate for self-approval. It represents the capability, attested by your org’s IdP or IAM process. Self-approval requires both this role and a per-repo SELF_CERTIFY permission entry — neither alone is sufficient. This separation lets organisations externalise the capability grant (who is trusted to self-certify at all) to their existing directory/IAM procedures, while the per-repo entitlement remains managed inside fogwall.

Both are checked again when the approved push is forwarded, in either proxy mode. The role is read from the user’s stored record, which an IdP login refreshes; a role removed in the IdP stops counting once the user next logs in, or immediately if the user is deleted.

How to grant ROLE_SELF_CERTIFY:

  • LDAP / AD / OIDC: add SELF_CERTIFY to auth.role-mappings and map it to the appropriate IdP group.
  • Local auth (config user): add SELF_CERTIFY to roles: in the user’s users: YAML entry.
  • Local auth (dashboard-created user): tick the “Grant self-certify role” box in the Add User form. Local auth is intended for demonstration and proof-of-concept; a production deployment grants the capability through its IdP instead.

Note

Organisations that require mandatory peer review (four-eyes) for all activity should simply not grant SELF_CERTIFY role or permissions. If no user holds SELF_CERTIFY, all pushes require a separate reviewer.

Roles are dashboard-level access only. They do not control which repos a user can push to — that is what permissions (below) are for.

Emails and SCM identities

Every user record carries two independent data sets that the proxy uses to verify identity on each push:

Emails — the set of email addresses the user commits with (i.e. the value in git config user.email). On every push, every author and committer email in the incoming commits is checked against this list. If an email is not registered to the authenticated user, the push fails in strict mode or warns in warn mode. This is what ties commit attribution to a verified real person.

SCM identities — the upstream provider username(s) for this user (e.g. their GitHub login). On every push, the proxy calls the upstream API using the PAT supplied in the git credentials and checks the returned username against this list. This confirms that the token being used actually belongs to the person who authenticated with the proxy, not a shared or borrowed token.

These are two independent checks and both must pass in strict mode. They catch different things: a commit email mismatch means the developer’s git client is misconfigured or the commit is attributed to someone else; an SCM identity mismatch means the token does not belong to the authenticated user.

How emails are populated:

  • Local auth: set in the users: YAML block; editable from the profile UI.
  • LDAP/AD: the directory mail attribute is imported on first login as a locked email (not editable from the UI — the directory is the source of truth). Additional emails can be added via the admin dashboard.
  • OIDC: the email claim from the ID token is imported on first login as a locked email.

How SCM identities are populated:

There is no automatic source for SCM identities — they must be added manually regardless of auth backend. After first login, either the user themselves or an admin can add SCM identities from the profile page in the dashboard. For example: provider github/github.com, username alice-gh. Users manage their own profile; admins can manage any user’s profile.

For local auth, SCM identities can also be set in the users: YAML block:

users:
  - username: alice
    # ...
    scm-identities:
      - provider: github/github.com
        username: alice-gh
      - provider: gitlab/gitlab.com
        username: alice

Until SCM identities are populated, pushes from that user will fail identity verification in strict mode. Use attribution-policy with committer: warn during rollout to let pushes through while identities are being registered. See Identity verification for the developer-facing view of what these checks look like at the terminal.

Disabling local admin when using an IdP

When LDAP, AD, or OIDC is configured, the static users: block still works and is evaluated alongside the IdP. In most production setups you want to remove static local accounts (or at minimum remove any with roles: [ADMIN]) once IdP-based login is confirmed working. See #103 for planned enforcement of this.