Skip to content

Commit 817b880

Browse files
committed
doc: Login process
While the existing document describes the protocol, it is hard to see how all the moving parts communicate together. Describe the steps for user/password, Kerberos, and client certificate authentication schemes. Assisted-By: Claude code (initial mermaid diagram and proofreading)
1 parent 61f8723 commit 817b880

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed

doc/authentication.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,86 @@ SSH connections:
203203

204204
* **COCKPIT_SSH_KNOWN_HOSTS_FILE** Path to knownhost files. Defaults to
205205
`PACKAGE_SYSCONF_DIR/ssh/ssh_known_hosts`
206+
207+
Login process: User/Password
208+
----------------------------
209+
210+
1. _User_ connects to cockpit URL, which lands at _cockpit-tls_
211+
2. _cockpit-tls_ connects to _cockpit-ws_ via the `cockpit-wsinstance-http@` or `cockpit-wsinstance-https@SHA256_NIL` systemd socket/service. See [cockpit-tls docs](../src/tls/README.md) and [systemd units](../src/systemd/) for details.
212+
3. _cockpit-ws_ responds with "401 Authentication failed" and sends the Login page
213+
4. _User_ fills in username/password and clicks "Log In". The login page sends a new request to _cockpit-ws_ with an `Authorization: Basic base64(user password)` header
214+
5. _cockpit-ws_ looks at `cockpit.conf` whether it has a customized session command for `basic`. If not, defaults to `cockpit-session`. It parses user/password from the header and spawns the session command with the target host as argument.
215+
6. _cockpit-session_ sends an `authorize` command with a `*` challenge (see above) to _cockpit-ws_, which responds with the user/password
216+
7. _cockpit-session_ starts a _PAM_ session for the user, and sets the initial credential to the received password
217+
8. If _PAM_ sends more messages, like e.g. 2FA prompts or changing expired passwords:
218+
* _cockpit-session_ sends corresponding `X-Conversation` authorize messages (see above) to _cockpit-ws_
219+
* _cockpit-ws_ forwards them to the Login page, which displays the text, and sends the user response as the authorize reply
220+
* _cockpit-ws_ forwards the authorize reply to PAM
221+
9. When PAM succeeds, _cockpit-session_ executes the bridge, and connects its stdio pipes to it, then _cockpit-ws_ starts the websocket on it
222+
223+
```mermaid
224+
sequenceDiagram
225+
participant User
226+
participant cockpit-tls
227+
participant cockpit-ws
228+
participant cockpit-session
229+
participant PAM
230+
231+
User->>cockpit-tls: https://server:9090
232+
cockpit-tls->>cockpit-ws: Connect via systemd socket
233+
cockpit-ws->>User: 401 + Login page
234+
User->>cockpit-ws: POST with Authorization: Basic
235+
cockpit-ws->>cockpit-session: Spawn
236+
cockpit-session->>cockpit-ws: authorize command with * challenge
237+
cockpit-ws->>cockpit-session: user/password
238+
cockpit-session->>PAM: pam_authenticate()
239+
PAM-->>cockpit-session: Success or conversation
240+
opt 2FA/password change
241+
cockpit-session->>cockpit-ws: X-Conversation challenge
242+
cockpit-ws->>User: Display prompt
243+
User->>cockpit-ws: Response
244+
cockpit-ws->>cockpit-session: Forward response
245+
cockpit-session->>PAM: Continue conversation
246+
end
247+
PAM->>cockpit-session: Success
248+
cockpit-session->>PAM: open_session()
249+
cockpit-session->>cockpit-ws: Execute bridge, connect stdio
250+
cockpit-ws->>User: Start websocket
251+
```
252+
253+
Login process: Kerberos/GSSAPI
254+
-------------------------------
255+
256+
1. _User_ connects to cockpit URL, which lands at _cockpit-tls_
257+
2. _cockpit-tls_ connects to _cockpit-ws_ via the `cockpit-wsinstance-http@` or `cockpit-wsinstance-https@SHA256_NIL` systemd socket/service. See [cockpit-tls docs](../src/tls/README.md) and [systemd units](../src/systemd/) for details.
258+
3. _cockpit-ws_ responds with "401 Authentication failed" and includes `WWW-Authenticate: Negotiate` header (if Kerberos is available)
259+
4. _Browser_ (if configured for SPNEGO/Kerberos) requests a service ticket from the _KDC_ for the HTTP service principal
260+
5. _Browser_ sends a new request with `Authorization: Negotiate <base64-gssapi-token>` header
261+
6. _cockpit-ws_ looks at `cockpit.conf` whether it has a customized session command for `negotiate`. If not, defaults to `cockpit-session`. It spawns the session command with the target host as argument.
262+
7. _cockpit-session_ calls `gss_accept_sec_context()` with the GSSAPI token to verify the Kerberos ticket
263+
8. If GSSAPI returns `GSS_S_CONTINUE_NEEDED` (multi-round negotiation):
264+
* _cockpit-session_ sends an authorize command with a `Negotiate` challenge containing the output token to _cockpit-ws_
265+
* _cockpit-ws_ responds with "401 Authentication failed" and `WWW-Authenticate: Negotiate <token>` to the _Browser_
266+
* _Browser_ sends another `Authorization: Negotiate <token>` request
267+
* This continues until GSSAPI negotiation completes
268+
9. When GSSAPI succeeds, _cockpit-session_ has the authenticated GSSAPI principal name
269+
10. _cockpit-session_ maps the GSSAPI name to a local username using `gss_localname()` (which applies configured mapping rules), or if that fails, falls back to `gss_display_name()` which returns the principal name as-is (e.g. `[email protected]`)
270+
11. _cockpit-session_ starts _PAM_, skipping the auth stack (as GSSAPI already authenticated), and runs the account, credential, and session stacks
271+
12. _cockpit-session_ stores the delegated Kerberos credentials (if delegation was negotiated) in a credential cache at `/run/user/<uid>/cockpit-session-<pid>.ccache` and sets `KRB5CCNAME` in the PAM environment, so that the bridge can use them for accessing other Kerberos-protected services (like SSH to remote machines)
272+
13. _cockpit-session_ executes the bridge, and connects its stdio pipes to it, then _cockpit-ws_ starts the websocket on it
273+
274+
Login process: Client Certificate
275+
----------------------------------
276+
277+
1. _User_ connects to cockpit URL with a client certificate, which lands at _cockpit-tls_
278+
2. _cockpit-tls_ calculates the SHA256(certificate) as the user fingerprint
279+
3. _cockpit-tls_ connects to the `cockpit-wsinstance-https@<fingerprint>` systemd socket/service (starting a dedicated _cockpit-ws_ instance for this certificate if needed). See [cockpit-tls docs](../src/tls/README.md) and [systemd units](../src/systemd/) for details.
280+
4. _cockpit-tls_ exports the certificate to `/run/cockpit/tls/clients/<fingerprint>` (kept as long as there is at least one active connection with that certificate)
281+
5. _cockpit-tls_ includes `"client-certificate": "<fingerprint>"` in its mini JSON protocol to _cockpit-ws_
282+
6. _cockpit-ws_ detects the client certificate metadata and uses `tls-cert <fingerprint>` as the authorization type
283+
7. _cockpit-ws_ looks at `cockpit.conf` whether it has a customized session command for `tls-cert`. If not, defaults to `cockpit-session`. It spawns the session command with the target host as argument.
284+
8. _cockpit-session_ receives the `tls-cert <fingerprint>` authorization and reads the certificate from `/run/cockpit/tls/clients/<fingerprint>`
285+
9. _cockpit-session_ validates that the certificate file exists and matches the expected _cockpit-ws_ cgroup
286+
10. _cockpit-session_ calls the _sssd_ D-Bus API (`org.freedesktop.sssd.infopipe.Users.FindByCertificate`) to map the certificate to a username
287+
11. When successful, _cockpit-session_ sets the username and starts _PAM_, skipping the auth stack (as the certificate itself was the authentication), and runs the account, credential, and session stacks
288+
12. _cockpit-session_ executes the bridge, and connects its stdio pipes to it, then _cockpit-ws_ starts the websocket on it

0 commit comments

Comments
 (0)