Skip to content

Conversation

@martinpitt
Copy link
Member

@martinpitt martinpitt commented Nov 4, 2025

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)


This was prompted by my trying to explain the login process to @Venefilyn . This is my complete brain dump, plus validating the steps through code reading and running cockpit. @Venefilyn can you please check and complain about steps that are unclear and need more detail? @mvollmer and @allisonkarlitskaya I'd really appreciate your reviews, I think the three of us together ought to know all the details. Time to pen them down!

Rendered: https://github.com/martinpitt/cockpit/blob/login-process/doc/authentication.md#login-process-userpassword

@martinpitt martinpitt added the no-test For doc/workflow changes, or experiments which don't need a full CI run, label Nov 4, 2025
9. When PAM succeeds, _cockpit-session_ executes the bridge, and connects its stdio pipes to it, then _cockpit-ws_ starts the websocket on it

```mermaid
sequenceDiagram
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you consider this useful, I'm happy to create diagrams for the other two as well. If not and the text variant is enough, I'm also happy to clean this up -- that just cost me 5 mins of clauding and cleaning up, as opposed to the 1.5 hours of researching and writing the text 😉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah diagrams are super helpful. With any potential X-Conversation stuff, are the threads still open? If possible we should show this in the diagram too

Copy link
Member

@Venefilyn Venefilyn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few questions after a glance through

We have default-bastion.conf that shows this:

[Basic]
Command = /container/cockpit-auth-ssh-key

It seems this would be in cockpit-ws step. Does that also mean python3 -m cockpit.beiboot is executed within cockpit-ws?


Is it possible to create and return a challenge from cockpit-ws? Something like this?

sequenceDiagram
    user->>+cockpit-ws: POST with Authorization: passkey <empty>
    cockpit-ws->>+user: login-options + generated challenge
    user->>+cockpit-ws: POST with Authorization: passkey <signature> 
Loading

How does interactive processes work? Given the text and chart it does seem like the process remains open (I'm guessing for the default 30s timeout) I assume we detect the output from the process and simply return X-Conversation and await response?


9. When PAM succeeds, _cockpit-session_ executes the bridge, and connects its stdio pipes to it, then _cockpit-ws_ starts the websocket on it

```mermaid
sequenceDiagram
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah diagrams are super helpful. With any potential X-Conversation stuff, are the threads still open? If possible we should show this in the diagram too

@martinpitt
Copy link
Member Author

Few questions after a glance through

We have default-bastion.conf that shows this:

[Basic]
Command = /container/cockpit-auth-ssh-key

It seems this would be in cockpit-ws step. Does that also mean python3 -m cockpit.beiboot is executed within cockpit-ws?

That's correct, it's a direct child. I added a section "Login process: SSH to remote machine" now. It's 'orribly complicated, and may have some wrong details -- I'd really like @allisonkarlitskaya to proofread that.

Is it possible to create and return a challenge from cockpit-ws? Something like this?

I might be missing something, but I'm pretty sure it's "no". cockpit-ws does not know much about specific auth protocols. It just knows how to communicate with session starters like cockpit-session or cockpit.beiboot. Any particular auth has to happen in them.

How does interactive processes work? Given the text and chart it does seem like the process remains open (I'm guessing for the default 30s timeout) I assume we detect the output from the process and simply return X-Conversation and await response?

Yes, that's my understanding. Session programs can send authorize/conversation messages, and ws is the mediator to send them back and forth to the UI.

With any potential X-Conversation stuff, are the threads still open?

Which threads? Conversations happens as an ongoing protocol between login.js, cockpit-ws and a running session process, I think that's what you mean. Only the host key (and possibly initial password?) steps are different and rather stupid, as ws keeps spawning new ssh processes until "it works".

I'm sorry, I am really hazy beyond this point.

@Venefilyn
Copy link
Member

Thanks that clears it up a bit

With any potential X-Conversation stuff, are the threads still open?

Which threads? Conversations happens as an ongoing protocol between login.js, cockpit-ws and a running session process, I think that's what you mean. Only the host key (and possibly initial password?) steps are different and rather stupid, as ws keeps spawning new ssh processes until "it works".

I'm sorry, I am really hazy beyond this point.

Yeah, the running session process is what I meant. Particularly cause I wasn't sure how to return a challenge to the browser and then also verify said challenge when user sends the signed message that includes the challenge.

@martinpitt
Copy link
Member Author

@Venefilyn Right, that should work with X-Conversation messages.

@mvollmer
Copy link
Member

mvollmer commented Nov 6, 2025

@martinpitt, I think the last two steps in the diagram about "start bridge, connect stdio" and "start websocket" are confusing/wrongish and not necessary to explain the login process.

The show starts when cockpit-ws returns the actual HTML for the URL that the user is requesting. So we could end the diagram with that.

  • cockpit-session starts cockpit-bridge, which reports success to cockpit-ws with a init message
  • cockpit-ws asks the bridge for the resource for the URL (via a "http-stream1/internal" channel) and sends that back to the browser.

I think the diagram should end there. The websocket stuff happens a bit later:

  • browser loads that page and starts requesting more stuff like the JS files
  • cockpit-ws serves these requests by contacting the bridge, like above
  • browser starts executing the JS and eventually opens the .../socket URL
  • cockpit-ws does the websocket dance and start processing cockpit protocol messages on it. most of which get forwarded to the bridge

@mvollmer
Copy link
Member

mvollmer commented Nov 6, 2025

@martinpitt, as discussed on chat, let's remove cockpit-tls from the diagram (and description) entirely.

@mvollmer
Copy link
Member

mvollmer commented Nov 6, 2025

The show starts when cockpit-ws returns the actual HTML for the URL that the user is requesting.

That happens indirectly, actually:

  • login page JS sends a GET request (not POST as in the diagram, I think)
  • when that comes back with 200, it does some modifications of the URL (adding the remote machine is all in practice, I think...) and triggers a load of that URL.
  • then cockpit-ws will contact the bridge and send back the actual HTML which will start the show.

So we should end the diagram with the 200 response to the GET request of login.js.

Copy link
Member

@mvollmer mvollmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See individual comments.

Comment on lines 223 to 249
```mermaid
sequenceDiagram
participant User
participant cockpit-tls
participant cockpit-ws
participant cockpit-session
participant PAM
User->>cockpit-tls: https://server:9090
cockpit-tls->>cockpit-ws: Connect via systemd socket
cockpit-ws->>User: 401 + Login page
User->>cockpit-ws: POST with Authorization: Basic
cockpit-ws->>cockpit-session: Spawn
cockpit-session->>cockpit-ws: authorize command with * challenge
cockpit-ws->>cockpit-session: user/password
cockpit-session->>PAM: pam_authenticate()
PAM-->>cockpit-session: Success or conversation
opt 2FA/password change
cockpit-session->>cockpit-ws: X-Conversation challenge
cockpit-ws->>User: Display prompt
User->>cockpit-ws: Response
cockpit-ws->>cockpit-session: Forward response
cockpit-session->>PAM: Continue conversation
end
PAM->>cockpit-session: Success
cockpit-session->>PAM: open_session()
cockpit-session->>cockpit-ws: Execute bridge, connect stdio
cockpit-ws->>User: Start websocket
```
Copy link
Member

@Venefilyn Venefilyn Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be accurate? Added bars to indicate processes/requests being opened and closed

sequenceDiagram
    participant User
    participant cockpit-tls
    participant cockpit-ws
    participant cockpit-session
    participant PAM
    
    User->>cockpit-tls: https://server:9090
    activate User
    cockpit-tls->>cockpit-ws: Connect via systemd socket
    cockpit-ws->>User: 401 + Login page
    deactivate User
    User->>cockpit-ws: POST with Authorization: Basic
    activate User
    cockpit-ws->>cockpit-session: Spawn
    cockpit-session->>cockpit-ws: authorize command with * challenge
    cockpit-ws->>cockpit-session: user/password
    activate cockpit-ws
    activate cockpit-session
    cockpit-session->>PAM: pam_authenticate()
    activate PAM
    PAM-->>cockpit-session: Success or conversation
    opt 2FA/password change
        cockpit-session->>cockpit-ws: X-Conversation challenge
        cockpit-ws->>User: Display prompt
        deactivate User
        User->>cockpit-ws: Response
        activate User
        cockpit-ws->>cockpit-session: Forward response
        cockpit-session->>PAM: Continue conversation
    end
    PAM->>cockpit-session: Success
    deactivate PAM
    cockpit-session->>PAM: open_session()
    cockpit-session->>cockpit-ws: Execute bridge, connect stdio
    deactivate cockpit-session
    cockpit-ws->>User: Start websocket
    deactivate cockpit-ws
    deactivate User
Loading
Suggested change
```mermaid
sequenceDiagram
participant User
participant cockpit-tls
participant cockpit-ws
participant cockpit-session
participant PAM
User->>cockpit-tls: https://server:9090
cockpit-tls->>cockpit-ws: Connect via systemd socket
cockpit-ws->>User: 401 + Login page
User->>cockpit-ws: POST with Authorization: Basic
cockpit-ws->>cockpit-session: Spawn
cockpit-session->>cockpit-ws: authorize command with * challenge
cockpit-ws->>cockpit-session: user/password
cockpit-session->>PAM: pam_authenticate()
PAM-->>cockpit-session: Success or conversation
opt 2FA/password change
cockpit-session->>cockpit-ws: X-Conversation challenge
cockpit-ws->>User: Display prompt
User->>cockpit-ws: Response
cockpit-ws->>cockpit-session: Forward response
cockpit-session->>PAM: Continue conversation
end
PAM->>cockpit-session: Success
cockpit-session->>PAM: open_session()
cockpit-session->>cockpit-ws: Execute bridge, connect stdio
cockpit-ws->>User: Start websocket
```
```mermaid
sequenceDiagram
participant User
participant cockpit-tls
participant cockpit-ws
participant cockpit-session
participant PAM
User->>cockpit-tls: https://server:9090
activate User
cockpit-tls->>cockpit-ws: Connect via systemd socket
cockpit-ws->>User: 401 + Login page
deactivate User
User->>cockpit-ws: POST with Authorization: Basic
activate User
cockpit-ws->>cockpit-session: Spawn
cockpit-session->>cockpit-ws: authorize command with * challenge
cockpit-ws->>cockpit-session: user/password
activate cockpit-ws
activate cockpit-session
cockpit-session->>PAM: pam_authenticate()
activate PAM
PAM-->>cockpit-session: Success or conversation
opt 2FA/password change
cockpit-session->>cockpit-ws: X-Conversation challenge
cockpit-ws->>User: Display prompt
deactivate User
User->>cockpit-ws: Response
activate User
cockpit-ws->>cockpit-session: Forward response
cockpit-session->>PAM: Continue conversation
end
PAM->>cockpit-session: Success
deactivate PAM
cockpit-session->>PAM: open_session()
cockpit-session->>cockpit-ws: Execute bridge, connect stdio
deactivate cockpit-session
cockpit-ws->>User: Start websocket
deactivate cockpit-ws
deactivate User
```

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this adds activation bars, thanks! I adapted that to the current version, and also did a small fix: PAM gets reactivated in open_session.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I had a look at this again, and now found it much more confusing than before. E.g. cockpit-ws and c-session are certainly active at the beginning as well, and generally any participant who does a step in the protocol is "active". I reverted this for now. Can you please explain a bit more what these bars mean?

Copy link
Member

@allisonkarlitskaya allisonkarlitskaya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some suggestions

7. _cockpit-session_ starts a _PAM_ session for the user, and sets the initial credential to the received password
8. If _PAM_ sends more messages, like e.g. 2FA prompts or changing expired passwords:
* _cockpit-session_ sends corresponding `X-Conversation` authorize messages (see above) to _cockpit-ws_
* _cockpit-ws_ forwards them to the Login page, which displays the text, and sends the user response as the authorize reply
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is vague. How does the login page send the response? How does -ws track multiple outstanding requests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually know. But also, that isn't really important for how the authentication works? Describing every aspect of cockpit is too much, at least for this PR.


1. _User_ connects to cockpit URL, which lands at _cockpit-tls_
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.
3. _cockpit-ws_ responds with "401 Authentication failed" and includes `WWW-Authenticate: Negotiate` header (if Kerberos is available)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does -ws find out about krb being available? I think we also missed the part where the login page gets sent vs. the above sequence for passwords, no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs research, I have no idea how this works.

1. _User_ connects to cockpit URL with a client certificate, which lands at _cockpit-tls_
2. _cockpit-tls_ calculates the SHA256(certificate) as the user fingerprint
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.
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still do that? I thought we changed over to using a random nonce here...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See client_certificate_accept() for how this works now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/tls/README.md says so, it might be outdated as well.

6. _cockpit-ws_ detects the client certificate metadata and uses `tls-cert <fingerprint>` as the authorization type
7. _cockpit-ws_ looks at `cockpit.conf` whether it has a customized session command for `tls-cert`. If not, defaults to `cockpit-session` and runs it in the same way as above.
8. _cockpit-session_ receives the `tls-cert <fingerprint>` authorization and reads the certificate from `/run/cockpit/tls/clients/<fingerprint>`
9. _cockpit-session_ validates that the certificate file exists and matches the expected _cockpit-ws_ cgroup
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be worth explaining in more detail since it changed fairly substantially recently. Specifically: how do we know the cgroup of the -ws instance?

Login process: SSH to remote machine
------------------------------------

1. _User_ connects to a URL like `https://server:9090/=hostname`) or sets the "Connect to:" field in the Login page to `hostname`. See User/Password steps 2 to 4 for the precise _cockpit-tls_ / _cockpit-ws_ connection process
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs discussion about the mechanism that allows for the 'Connect To' field to appear and be considered during auth.

@martinpitt
Copy link
Member Author

martinpitt commented Nov 6, 2025

Thanks for the reviews! Next push addresses @mvollmer 's review, and some parts of @allisonkarlitskaya 's.

@martinpitt martinpitt force-pushed the login-process branch 3 times, most recently from e9ae558 to b540568 Compare November 6, 2025 20:25
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-test For doc/workflow changes, or experiments which don't need a full CI run,

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants