Cockpit Authentication======================Cockpit authorizes users by looking at the Authorization header on requeststo /login. Cockpit will attempt to perform the start the authentication command thatis configured for the auth scheme contained in the header.To configure an auth scheme add a section to cockpit.conf for that scheme. For exampleto configure an command for the "Bearer" auth scheme your cockpit.conf should containthe following section.```[bearer]command = example-verify-tokentimeout = 300```The command is then responsible to:* verify the given credentials* setup an appropriate session and environment based on those credentials* launch a bridge that speaks the cockpit protocol on stdin and stdout.The default command is `cockpit-session` it is able to handle Password(`[basic]`), Kerberos/GSSAPI (`[negotiate]`), and TLS client certificate(`[tls-cert]`) authentication.Authentication commands are called with a single argument which is the host that the useris connecting to. They communicate with their parent process using the cockpit protocol onstdin and stdout.Instead of `Command`, it's also possible to specify `UnixPath` to connect to a unix socket at agiven path. The protocol is the same.Credentials can then be retrieved by issuing a authorize command with a challenge. The challengeshould correspond to the authorization type in header (ei: Basic or Bearer). For example:```{"command": "authorize","cookie": "cookie","challenge": "*"}```The response will look something like this```{"command": "authorize","cookie": "cookie","response": "Basic dXNlcjpwYXNzd29yZAo=","remote-peer": "1.2.3.4",}````remote-peer` is the IP address of the connecting user, if known (otherwiseunset).A `*` challenge requests whatever credentials the parent process has. Most authcommands will want to begin by issuing a `*` challenge.By default cockpit-ws will wait a maximum of 30 seconds to receive this response.The number of seconds to wait can be adjusted by adding a timeout parameter alongside the auth schema configuration in your config file. The given value should bea number between 1 and 900.If more information is needed the command should respond with a `X-Conversation` challenge.This takes the following format.```X-Conversation nonce base64(prompt message)```The message will be displayed to the user and the user will be prompted for a response.If the user does not respond within 60 seconds the command will be closed and the loginaborted. The number of seconds to wait can be adjusted by adding a response-timeout parameteralong side the auth schema configuration in your config file. The given value should be anumber between 1 and 900.Once a result is known a "init" command should be sent. If the login was successful you can usuallyjust let the bridge do this.If the login was not successful the JSON should include a problem field. Values of`authentication-failed`, `authentication-unavailable` or `access-denied`are translated to the appropriate cockpit error codes. Any other values are treatedas generic errors. Additionally a message field may be included as well.If an process exits without sending a init command, that will be treated as an internal error.If the authentication command has additional data that it would like to return with a successful responseit can do so by sending a `x-login-data` challenge. The command should have an additional JSON field`login-data`. The string placed there will be returned by along with a successful json response.For a simple python example see[cockpit-auth-ssh-key](https://github.com/cockpit-project/cockpit/blob/main/containers/ws/cockpit-auth-ssh-key).Remote machines---------------Cockpit also supports logging directly into remote machines. The remote machine toconnect to is provided by using a application name that begins with `cockpit+=`.The default command used for this is `python3 -m cockpit.beiboot`, whichinvokes `ssh`.The section `Ssh-Login` defines the options for all ssh commands. The sectionhas the same options as the other authentication sections with the following additions.* `host` The default host to log into. Defaults to 127.0.0.1. That host's keywill not be checked/validated.* `connectToUnknownHosts`. By default cockpit will refuse to connect to any machines thatare not already present in ssh's global `known_hosts` file (usually`/etc/ssh/ssh_known_hosts`). Set this to `true` is to allow those connectionsto proceed.After the user authentication with the `"*"` challenge, if the remotehost is not already present in any local `known_hosts` file, this will send an`"x-host-key"` challenge:```{"command": "authorize","challenge": "x-host-key""cookie": "cookie",}```The caller responds to that with either a valid key like below, or an emptystring response if there is no available key.```{"command": "authorize","cookie": "cookie","response": "ssh-rsa AAAA1234...",}```Client certificate authentication---------------------------------When a machine is joined to an Identity Management domain (like[FreeIPA](https://www.freeipa.org) or Active Directory) which has [client-sideuser certificates](https://www.freeipa.org/page/V4/User_Certificates) set up,then these can be enabled for authentication to Cockpit by setting this optionin cockpit.conf:```[WebService]ClientCertAuthentication = yes```This uses the `[tls-cert]` authentication scheme.When enabling this mode, other authentication types commonly get disabled. Seethe next section for details.See the [Certificate/smart card authentication guide](https://docs.cockpit-project.org/cockpit-guide/latest/guide/cert-authentication.html)for details how to set this up.Actions-------Setting an action can modify the behavior for an auth scheme. Currently two actionsare supported.* **remote-login-ssh** Use the `Ssh-Login` section instead.* **none** Disable this auth scheme.To configure an action add the `action` option. For example to disable basic authentication,cockpit.conf should contain the following section:```[basic]action = none```Likewise, if the machine is part of a Kerberos domain, but that should not be used toauthenticate to cockpit, create the following section:```[negotiate]action = none```Limits------Cockpit can be configured to limit the number of concurrent login processes. See cockpit.conffor more details. This will affect how many custom authentication processes can be launched.Environment Variables---------------------The following environment variables are set by cockpit-ws when spawning an auth process forSSH connections:* **COCKPIT_SSH_CONNECT_TO_UNKNOWN_HOSTS** Set to `1` to allow connecting tohosts that are not present in the current `known_hosts` files. If not set,Cockpit will only connect to unknown hosts if either the remote peer islocal, or if the `Ssh-Login` section in `cockpit.conf` has an`connectToUnknownHosts` option set to a true value (`1`, `yes` or `true`).* **COCKPIT_SSH_KNOWN_HOSTS_FILE** Path to knownhost files. Defaults to`PACKAGE_SYSCONF_DIR/ssh/ssh_known_hosts`Login process: User/Password----------------------------This section entirely ignores cockpit-tls for simplicity, as it is not involved in the authentication. See [cockpit-tls docs](../src/tls/README.md) for details.1. _User_ connects to cockpit URL.2. _cockpit-ws_ responds with "401 Authentication failed" and sends the Login page, unless when Kerberos is available (see below) or the server is configured to not use any authentication.3. _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)` header4. _cockpit-ws_ looks at `cockpit.conf` whether it has a customized session `Command` or `UnixPath` for `basic`. If not, defaults to `cockpit-session` (via `UnixPath = /run/cockpit/session`). It parses user/password from the header and spawns the session command with the target host as argument.5. _cockpit-session_ sends an `authorize` command with a `*` challenge (see above) to _cockpit-ws_, which responds with the user/password6. _cockpit-session_ starts a _PAM_ session for the user, and sets the initial credential to the received password7. 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* _cockpit-ws_ forwards the authorize reply to PAM8. When PAM succeeds, _cockpit-session_ executes the bridge and the session starts.```mermaidsequenceDiagramparticipant Userparticipant cockpit-wsparticipant cockpit-sessionparticipant PAMUser->>cockpit-ws: GET https://server:9090cockpit-ws->>User: 401 + Login pageUser->>cockpit-ws: GET with Authorization: Basiccockpit-ws->>cockpit-session: Spawncockpit-session->>cockpit-ws: authorize command with * challengecockpit-ws->>cockpit-session: user/passwordcockpit-session->>PAM: pam_authenticate()PAM-->>cockpit-session: Success or conversationopt 2FA/password changecockpit-session->>cockpit-ws: X-Conversation challengecockpit-ws->>User: Display promptUser->>cockpit-ws: Responsecockpit-ws->>cockpit-session: Forward responsecockpit-session->>PAM: Continue conversationendPAM->>cockpit-session: Successcockpit-session->>PAM: open_session()cockpit-ws->>User: 200 OK for the login page```Login process: Kerberos/GSSAPI-------------------------------1. _User_ connects to cockpit URL.2. _cockpit-ws_ responds with "401 Authentication failed" and includes `WWW-Authenticate: Negotiate` header (if Kerberos is available)3. _Browser_ (if configured for SPNEGO/Kerberos) requests a service ticket from the _KDC_ for the HTTP service principal4. _Browser_ sends a new request with `Authorization: Negotiate <base64-gssapi-token>` header5. _cockpit-ws_ looks at `cockpit.conf` whether it has a customized session command for `negotiate`. If not, defaults to `cockpit-session` and runs it in the same way as above.6. _cockpit-session_ calls `gss_accept_sec_context()` with the GSSAPI token to verify the Kerberos ticket7. If GSSAPI returns `GSS_S_CONTINUE_NEEDED` (multi-round negotiation):* _cockpit-session_ sends an authorize command with a `Negotiate` challenge containing the output token to _cockpit-ws_* _cockpit-ws_ responds with "401 Authentication failed" and `WWW-Authenticate: Negotiate <token>` to the _Browser_* _Browser_ sends another `Authorization: Negotiate <token>` request* This continues until GSSAPI negotiation completes8. When GSSAPI succeeds, _cockpit-session_ has the authenticated GSSAPI principal name9. _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. `user@REALM.COM`)10. _cockpit-session_ starts _PAM_, skipping the auth stack (as GSSAPI already authenticated), and runs the account, credential, and session stacks11. _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)12. Authentication completes, and the session starts as above.Login process: Client Certificate----------------------------------Unlike the other sections, this one involves _cockpit-tls_ as well as it provides a crucial part of the authentication.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 fingerprint3. _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)5. _cockpit-tls_ includes `"client-certificate": "<fingerprint>"` in its mini JSON protocol to _cockpit-ws_6. _cockpit-ws_ detects the client certificate metadata and uses `tls-cert <fingerprint>` as the authorization type7. _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_ cgroup10. _cockpit-session_ calls the _sssd_ D-Bus API (`org.freedesktop.sssd.infopipe.Users.FindByCertificate`) to map the certificate to a username11. 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 stacks13. Authentication completes, and the session starts as above.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`.2. _login.js_ sends the login HTTP request with `Authorization: Basic base64(user:password\0known_hosts)` header, where `known_hosts` contains any previously-stored SSH host keys for the target host from the browser's `localStorage`3. _cockpit-ws_ extracts the target host from the URL. As it has a host name, it looks at `cockpit.conf` for the `[Ssh-Login]` section's `Command` or `UnixPath`. If not customized, defaults to `cockpit.beiboot`4. _cockpit-ws_ spawns the ssh command with the target host as argument5. _cockpit.beiboot_ sends an `authorize` command with a `*` challenge to _cockpit-ws_, which responds with the user/password/known_hosts from the `Authorization` header (same as User/Password step 5)6. _cockpit.beiboot_ parses the credentials, writes any received `known_hosts` to a temporary file, and configures `ssh` to use it via `-o UserKnownHostsfile=...`7. _cockpit.beiboot_ connects to the remote host via SSH using the [ferny API](https://github.com/allisonkarlitskaya/ferny/).8. If the remote host's SSH key is unknown or has changed:* For **unknown** hosts: SSH prompts via its `SSH_ASKPASS` mechanism. _ferny_'s interaction agent detects the host key prompt, parses the fingerprint, and _cockpit.beiboot_ sends an `X-Conversation` challenge to _cockpit-ws_ with the host key fingerprint and hostname.* For **changed** keys: SSH immediately fails with a changed host key error. _ferny_ detects this as `SshChangedHostKeyError`. _cockpit.beiboot_ fails with `problem=invalid-hostkey`. _login.js_ catches this and retries the login **without** sending the old known_hosts entry, causing SSH to treat it as an unknown host (see above).* For localhost (127.0.0.1): _cockpit.beiboot_ automatically accepts the key without user interaction.* _cockpit-ws_ forwards the `X-Conversation` challenge to the web UI* _login.js_ shows a host key verification dialog with the fingerprint, key type, and hostname to the user; it remembers unknown vs. changed and shows appropriate UI* User accepts or rejects the key* _login.js_ sends the response back via `X-Conversation` header* _cockpit.beiboot_ returns the response to SSH's askpass mechanism* SSH proceeds with the connection (if accepted) or fails (if rejected)9. If SSH prompts for additional input (like 2FA): Similar to step 7 in User/Password section: _cockpit.beiboot_ sends `X-Conversation` messages back and forth10. When SSH authentication succeeds, _cockpit.beiboot_ either runs an existing remote `cockpit-bridge`, or sends its own Python module through the SSH connection (via [beipack](https://github.com/allisonkarlitskaya/beipack)). It starts bridge on the remote machine and connects its stdio to it11. Authentication completes, and the session starts as above.12. After successful SSH authentication and bridge startup, if a new host key was accepted, _cockpit.beiboot_ reads the updated `known_hosts` file and sends it to the browser in the `init` message's `login-data` field as `known-hosts`13. _login.js_ stores the received `known-hosts` entry in localStorage for future connections to this host