Skip to main content

Access and login

There are four separate ways someone or something gets into Clouduler, and they are easy to mix up:

WhatWho it is forProtects
Dashboard sign-in (OIDC or API key)People running the clusterThe web UI and the API
API keysScripts, CI and the first loginThe API, as an admin
Service login (proxy.auth)Visitors of your appsApps that have no login of their own
AI client access (MCP OAuth)Claude Code, Cursor and similarThe /mcp endpoint

There are no local usernames or passwords. You sign in with an identity provider (OIDC, for example Authentik or Keycloak) or with an API key.

How it works​

  • Every successful login becomes a session stored in the cluster state. The browser only holds an opaque cl_session cookie (HttpOnly, Secure, SameSite=Lax). A session lasts auth.session_ttl (12 hours by default) from login and does not slide: an active admin is asked to sign in again after 12 hours.
  • Two roles. admin can change anything. viewer can read; secret values are never shown to anyone. API keys are always admin. An OIDC user is admin only when a claim in their ID token (default groups) contains the configured admin_value. Everyone else the provider lets in is a viewer.
  • Clouduler has no allow list. Who may sign in is decided at your identity provider.
  • A role is fixed when the session is created. A user demoted at the provider keeps admin until the session expires.

Sign in to the dashboard​

The login screen offers two ways in:

  • Sign in with your identity provider (OIDC). It always shows, even when OIDC is not configured; without OIDC it leads to a raw error page oidc is not configured.
  • Under the divider or use an API key: paste a key (placeholder clk_...) into API key and press Sign in with API key. A wrong key shows the banner That key was not accepted.

Sign out with the logout icon at the bottom of the side navigation (on phones: More > Sign out). Signing out ends only the Clouduler session; your identity provider session stays, so the next sign-in may not ask for a password.

/settings/access
Settings, API keys and AI clients: who you are, keys and connected tools.

API keys​

A key looks like clk_ + 12 characters + _ + 32 characters. Keys are shown once, have no scopes and no expiry, and are always admin. Deleting the key is the only way to revoke it. Only a hash is stored.

  1. Open Settings > API keys and AI clients (/settings/access).
  2. Press Create API key in the header. Give it a Name (for example ci-deploy). The Create key button stays disabled until you type one.
  3. Copy the key from Copy this key now. It is shown once and cannot be recovered.
  4. To revoke, press the trash button Delete <name> and confirm. "Anything using this key stops working."

The panel You shows your username and whether you are admin or read-only.

Set up OIDC sign-in​

The setup is the same for any provider. Provider-specific walkthroughs: Authentik, Keycloak.

  1. At your identity provider create an OpenID Connect application (authorization code flow). Register this redirect URI, exactly: https://<dashboard address>/auth/oidc/callback, for example https://cloud.int.example.com/auth/oidc/callback. The dashboard address is web.address qualified with the domain of web.network. The Sign in with OIDC help text in the UI shows it.
  2. Open Settings > Sign-in and backup (/settings/security), panel Sign-in. Switch on Sign in with OIDC.
  3. Fill in: Issuer URL (must start with https://), Client ID, Client secret (write-only; Save secret or Rotate secret), Username claim (for example preferred_username), Admin group claim (for example groups) and Admin group value (blank means nobody gets admin).
  4. Apply. Leave Allow API keys on until you have confirmed OIDC works.
/settings/security
Settings, Sign-in and backup.

What your provider must give Clouduler:

ItemRequired value
FlowOpenID Connect, authorization code, PKCE S256 (always sent). A confidential client with a secret is recommended; a public client also works.
Redirect URIhttps://<dashboard fqdn>/auth/oidc/callback, exactly one.
Scopesopenid profile email by default; must include openid.
ID token claimssub, your username claim, and the group claim named in admin_claim (string or array). Claims are read from the ID token only; Clouduler never calls the userinfo endpoint.
IssuerThe exact issuer from the discovery document at <issuer>/.well-known/openid-configuration. Trailing slashes matter.
Who may log inRestrict at the provider. Anyone it lets in gets at least viewer.

Service login: protect apps that have no login​

The service login (proxy.auth) is a second, separate OIDC login that guards your apps, not the dashboard. You turn it on for a route with the authenticated middleware. Use it in front of an app that has no authentication of its own.

What it does, exactly:

  1. A visitor opens a protected route and has no valid cl_proxy_auth cookie for that host. The proxy redirects (302) their browser to the portal, https://login.<network domain>/login?callback=.... A request that is not a page navigation (a POST, or one that does not accept HTML) gets 401 login required instead.
  2. The portal shows a sign-in page with a button, then sends the visitor to your identity provider. If the browser already has a portal session, the page is skipped.
  3. After login the portal gives the browser a one-time token (valid 60 seconds) for the app's host. The app host spends it and sets its own cl_proxy_auth cookie, then returns the visitor to the page they asked for.
  4. On every later request the proxy checks that cookie and the route's group rule, then forwards the request to the app with the gate's own cookies removed.
  5. A signed-in visitor outside the allowed groups sees a No access page: "<username> is not in a group allowed to open this page."

What it does not do. Clouduler's proxy is a login wall, not header-based forward-auth. It does not call an endpoint per request and it does not tell the app who the visitor is. No X-Remote-User-style header is sent; only X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host are added. The app either trusts that everyone reaching it is logged in or does its own check. The UI hint for Username claim that says the name is "passed to apps" is wrong.

Policy on the route:

  • authenticated (bare): any signed-in visitor.
  • authenticated: {groups: [family, admins]}: needs at least one listed group, matched exactly against the groups claim.
  • allow_internal (default true): a request whose TCP peer is inside one of the cluster's internal subnets (containers and nodes) skips the login. Set allow_internal: false to force login for in-cluster callers too.
  1. At your provider create a separate OIDC client for the portal. Register one redirect URI per network you offer the login on: https://<portal host>/callback, for example https://login.lan.example.com/callback. The page lists them for you.
  2. Open Settings > Sign-in and backup, panel Service login. Switch on Enable service login.
  3. Pick Networks (at least one, each with a DNS domain), set Portal host (a label such as login, qualified with each network's domain), Issuer URL, Client ID and Client secret.
  4. Optionally set Scopes (default openid profile email groups), Groups claim, Username claim, Stay signed in for, Portal title, Palette (Grape, Sunset, Ocean, Forest) and Accent colour.
  5. Apply. Changes to proxy arm the rollback timer; confirm once you can still reach the dashboard.

Cookies the gate uses (all HttpOnly, SameSite=Lax): cl_proxy_portal on the portal host, cl_proxy_auth on each protected app host, cl_proxy_login (10 minutes, sealed) while a login is in flight. Sign out of apps through /.well-known/clouduler-logout on the app's host, which ends the portal session, every app session handed out from it and any remembered address.

Remember this IP address. Networks listed in proxy.auth.remember_ip offer a checkbox "Let visitors remember their IP address". A visitor whose address is inside that network is then trusted for remember_ip_ttl (default 30 days). Everyone sharing that address, such as everyone behind a NAT router, is let in too. Off by default.

AI clients (MCP)​

AI tools such as Claude Code, Cursor and VS Code connect to https://<dashboard>/mcp using OAuth 2.1 with PKCE and dynamic client registration. A human approves each tool once on a consent page and chooses Read-only (inspect nodes, stacks, containers and logs; secrets stay redacted) or Admin (can also change the cluster). A non-admin can only grant read-only. Access tokens last 1 hour and refresh tokens 30 days; revoking a client is immediate.

An API key also works on /mcp (as Authorization: Bearer clk_...) and always grants admin.

  • Turn it on or off: Settings > Sign-in and backup, section AI access, Allow AI clients.
  • Connect a tool: Settings > API keys and AI clients, panel Connected AI clients. It shows the MCP URL, the Claude Code command (claude mcp add --transport http clouduler https://<fqdn>/mcp) and links for Claude.ai connectors, Cursor and VS Code.
  • Approve: the tool opens the Connect an application page. Choose Read-only or Admin, then Approve access or Deny access. Pending requests also appear under Settings > API keys and AI clients with Approve read-only, Approve admin and Deny.
  • Revoke: the trash button Revoke <client> in Connected AI clients: "It loses access immediately."

See MCP server and the MCP clients guide.

If you are locked out​

Work down this list:

  1. An API key still exists somewhere. Use it on the login screen.
  2. OIDC works at the provider, but admin is missing. Check that the ID token contains the admin_claim with the admin_value, and that scopes includes what releases it. Log in as a viewer, then ask another admin.
  3. You deleted every API key and OIDC does not work. The bootstrap key task runs when the API server starts and mints a key named bootstrap only if the leader finds the key store empty. Restarting the daemon on the leader (sudo systemctl restart clouduler) logs a new bootstrap key in journalctl -u clouduler.service (the task retries every 2 seconds until the key is stored). The CLI has no key command, so this is the recovery path.
  4. A bad OIDC edit. Use the key from step 1 or 3 and fix Settings > Sign-in and backup. There is no rollback timer for auth edits.
  5. Wrong proxy or web edit. The rollback timer reverts it after the countdown, or run clouduler config reload with a known-good file on a manager.

Cluster key and node trust (for completeness)​

Nodes do not use logins. They prove they hold the cluster key (/etc/clouduler/secrets.key) in a challenge-response handshake on the raft, drive and relay connections, and HMAC-sign the HTTP calls they make to each other. The same key seals every stored secret (including the OIDC client secrets) and signs MCP tokens. Back it up: without it every stored secret is unrecoverable. A key file readable by the world or writable by the group is refused. See File locations.

Options​

auth​

Option
Type / default
Reload
Description
objectdefault {api_keys: true, session_ttl: 12h}
n/a
Sign-in to the dashboard and API.
booldefault true
live reload
Intended switch for API key sign-in.
Watch out: false is rejected unless auth.oidc is set. The research found that the flag is not enforced at runtime today: keys keep working even when false.
durationdefault 12h
live reload
How long a dashboard session lasts from login. No sliding renewal.
Watch out: Existing sessions keep their original expiry.
objectdefault absent
live reload
OIDC identity provider for people who operate the cluster. Absent means OIDC is off. The client secret is a sealed secret, not in this file.
https URLno default
live reload
Base URL. Discovery is read from ISSUER/.well-known/openid-configuration.
Watch out: Required when oidc is set. Error: must be an https url. Trailing slashes matter.
stringno default
live reload
OIDC client ID.
Watch out: Required when oidc is set.
listdefault [openid, profile, email]
live reload
Scopes requested at login. Must include openid.
Watch out: No UI control. Add groups here if your provider releases groups only by scope.
stringdefault groups
live reload
ID token claim (string or array) checked for admin_value.
stringdefault none
live reload
Value in the admin claim that grants admin. Blank means nobody becomes admin through OIDC; everyone else is a viewer.
Watch out: Matching is exact and case-sensitive.
stringdefault preferred_username
live reload
Claim shown as the signed-in user's name. Falls back to sub.

proxy.auth​

Option
Type / default
Reload
Description
objectdefault absent
live reload
Service login: a portal that stands in front of apps whose routes use the authenticated middleware. A separate OIDC client from auth.oidc.
label or FQDNno default
live reload
Portal hostname. A bare label gets each listed network's domain appended.
Watch out: Required when proxy.auth is set. Must differ from the dashboard hostname.
list of network namesno default
live reload
Networks that offer the login. Each needs a domain. A route can require login only on a listed network.
Watch out: Required, at least one.
https URLno default
live reload
Identity provider for the portal.
Watch out: Required.
stringno default
live reload
Client ID for the portal. Its secret is sealed separately.
Watch out: Required.
listdefault [openid, profile, email, groups]
live reload
OIDC scopes. Must include openid.
Watch out: The UI takes space- or comma-separated text.
stringdefault groups
live reload
Claim listing the visitor's groups. Routes restrict by group name.
stringdefault preferred_username
live reload
Claim shown as the user's name on the portal. Falls back to sub.
Watch out: It is not passed to apps.
durationdefault 12h
live reload
Lifetime of a portal login and of each app cookie it issues. Must be greater than zero.
stringdefault Sign in to continue
live reload
Heading of the login page.
enumdefault grape
live reload
Colour set of the login page: grape, sunset, ocean or forest.
hex colourdefault none
live reload
Overrides the palette's main colour. The server accepts #rgb or #rrggbb.
Watch out: The UI accepts only six digits; a three-digit accent set in the file shows as unset in the UI.
list of network namesdefault []
live reload
Networks whose login page offers remember this IP address. Each must be listed in proxy.auth.networks and be internal, private or public.
durationdefault 30d
live reload
How long a remembered IP skips login, counted from the login that saved it. Must be greater than zero.

mcp​

Option
Type / default
Reload
Description
booldefault true
live reload
Turns the MCP endpoint and its OAuth pages on or off. Off makes them answer 404.
Watch out: clouduler init writes false into a new cluster.