-
Notifications
You must be signed in to change notification settings - Fork 25
Update SSH Agent deep dive with architecture of v2 #828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| # Bitwarden SSH Agent architecture | ||
|
|
||
|  | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. β We use Mermaid for all documentation diagrams (via code) now. |
||
|
|
||
| Bitwardenβs SSH Agent is primarily implemented in the Rust language, as a server that accepts ssh | ||
| client connections over the SSH Agent protocol. This server interfaces with the Desktop UI through | ||
| napi bindings and the Electron services. | ||
|
|
||
| Our agent is unique from native OS ones in that: | ||
|
|
||
| - true storage of keys is on a (remote) server which individual Desktop clients interface with | ||
| - users of the agent manage keys with a UI | ||
| - users of the agent (optionally) approve requests with a UI | ||
|
|
||
| --- | ||
|
|
||
| ## End to end data flow | ||
|
|
||
|  | ||
|
|
||
| When the user enables the SSH Agent feature in user settings, the SSH key vault items are retrieved | ||
| from the vault and sent to the agent. The agent server is started and itβs keystore populated with | ||
| the new keys. | ||
|
|
||
| The Bitwarden SSH Agent server spawns a thread to handle any each new client connection. Each | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π¨ SUGGESTED: A few grammar typos in the new prose worth cleaning up. Details
These are not caught by spellcheck since each word is individually valid. |
||
| connection results in an async callback chain. | ||
|
|
||
| The Electron app replaces the keys in the agentβs keystore when there are vault changes to SSH key | ||
| items. | ||
|
|
||
| --- | ||
|
|
||
| ## The SSH Agent | ||
|
|
||
|  | ||
|
|
||
| The core agent implementation itself is part of the Rust Desktop Native layer in the Desktop client. | ||
| It is an almost entirely self-contained workspace crate within desktop_native. | ||
|
|
||
| The architecture of the agent was designed with these goals: | ||
|
|
||
| - efficient adherence to the SSH agent protocol spec | ||
| - clean interfaces isolating the server protocol, Bitwardenβs business logic, and user interface | ||
| - maintainability | ||
| - testability | ||
|
|
||
| ### Modules | ||
|
|
||
| #### server | ||
|
|
||
| Low level agent protocol implementation. It is responsible for listening for and managing new | ||
| connections (clients), handling requests from connections and returning responses. | ||
|
|
||
| #### crypto | ||
|
|
||
| Cryptographic primitive type definitions for public and private key types that we support and | ||
| handling of the signing of requests and any key-based operation. | ||
|
|
||
| #### storage | ||
|
|
||
| Logic that handles the storage and management of data that the agent uses, effectively the SSH keys. | ||
| Currently our implementation of a key store is in-memory. | ||
|
|
||
| #### authorization | ||
|
|
||
| Defines the concrete policy which our agent enforces for authorization of requests. The | ||
| authorization of requests is requested by the server. | ||
|
|
||
| #### approval | ||
|
|
||
| Interface for our agent to get approval for requests via an external entity (in our present case, | ||
| Electron). | ||
|
|
||
| #### agent | ||
|
|
||
| The agent crate itself is a the orchestrator of all the above components. It starts and stops the | ||
| server, updates the storage of keys, enforces the authorization policy, and interfaces with the | ||
| external approver of requests. | ||
|
|
||
| ## Abstractions | ||
|
|
||
| There are two critical abstractions to understand about the agent implementation. These abstractions | ||
| both serve two purposes: | ||
|
|
||
| 1. Low coupling ; high cohesion | ||
|
|
||
| 2. Testability | ||
|
|
||
| ### AuthPolicy | ||
|
|
||
| The server requests authorization for requests that it receives from client connections. | ||
|
|
||
| The server does not need to know about the business logic that goes into whether a request is | ||
| authorized. | ||
|
|
||
| This interface provides an abstraction from the raw server and the agentβs business logic. | ||
|
|
||
| ```rust | ||
| pub trait AuthPolicy: Send + Sync { | ||
| async fn authorize(&self, request: &AuthRequest) -> Result<bool, AuthError>; | ||
| } | ||
| ``` | ||
|
|
||
| ### ApprovalRequester | ||
|
|
||
| The agent requests approval from an external entity, when needed based on itβs authorization policy. | ||
|
|
||
| The agent does not need to know about the logic or complexities of the external entity that it | ||
| requests approvals through. | ||
|
|
||
| ```rust | ||
| pub trait ApprovalRequester: Send + Sync { | ||
| async fn request_sign_approval( | ||
| &self, | ||
| request: SignApprovalRequest, | ||
| ) -> Result<bool, ApprovalError>; | ||
|
|
||
| async fn request_list_approval(&self) -> Result<bool, ApprovalError>; | ||
| } | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Napi bindings | ||
|
|
||
| The `sshagent_v2` module contains the definitions for the napi bindings which act as the bridge | ||
| between the Rust implementation of the agent, and the Electron UI. | ||
|
|
||
| This layer is intentionally a pass-through. There is no unique logic. A wrapper around the agent is | ||
| provided to expose it to Electron. | ||
|
|
||
| The ElectronApprovalRequester is our concrete implementation of the ApprovalRequester trait. It | ||
| simply provides a mechanism to wire-up the async callbacks that we use in the Electron services to | ||
| answer the approval requests that are cascaded up from the server. | ||
|
|
||
| --- | ||
|
|
||
| ## Electron services | ||
|
|
||
| ### Main service | ||
|
|
||
| The Main SSH Agent service is primarily a pass-through for exchanging request/response calls between | ||
| Render and agent. | ||
|
|
||
| It maintains a request ID counter and stores the pending requests keyed by that ID. | ||
|
|
||
| ### Render service | ||
|
|
||
| The Render SSH Agent service is the primary business logic that interfaces between the vault data, | ||
| the user interactions, and the downstream agent. | ||
|
|
||
| It has the following responsibilities: | ||
|
|
||
| - subscribes to changes in the user setting for enabling the ssh agent feature, and correspondingly | ||
| starts/stops the agent. | ||
| - receives the vault ssh key items when any ssh key item is changed, and updates the agent with the | ||
| new key data. | ||
| - subscribes to active account changes, and either updates the key data or stops the server | ||
| depending on the new accountβs state. | ||
| - receives authorization requests via the main service, and depending on the userβs setting for | ||
| authorization requests, may prompt the user to authorize the request, or grant it based on cached | ||
| approval, or automatically approve if authorizations arenβt configured. | ||
|
|
||
| --- | ||
|
|
||
| ## Vault lock state behavior | ||
|
|
||
| This section describes how the agent behaves in the various [vault states](../lock-states.md). For | ||
| simplicity, assume only one account is active on the client and that the feature is enabled in user | ||
| settings. | ||
|
|
||
| ### Logged out | ||
|
|
||
| The agent is not running. | ||
|
|
||
| ### Locked Before First Unlock (BFU) | ||
|
|
||
| The agent is running. All requests result in a toast prompt to unlock the vault. | ||
|
|
||
| ### Locked After First Unlock (AFU) | ||
|
|
||
| The agent is running. List requests are supported. Sign requests are supported- if authorization is | ||
| required based on user setting, user is prompted to unlock their vault first, then prompted to | ||
| approve the request. | ||
|
|
||
| ### Unlocked | ||
|
|
||
| The agent is running, all functionality supported. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # SSH Keys and SSH Agent | ||
|
|
||
| This section contains pages that build an understanding of what SSH keys are, what an SSH Agent is, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. βοΈ Please remove all the "this section" language -- it's implied. Go right into the content. |
||
| and how the Bitwarden SSH Agent is architected and implemented. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. β QUESTION: The sidebar for this section is autogenerated and ordered alphabetically, so pages will appear as |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,4 @@ | ||
| # SSH Keys and Agent | ||
|
|
||
| :::info | ||
|
|
||
| For more information on how Bitwarden implements its SSH agent, see [SSH Agent](./agent). | ||
|
|
||
| ::: | ||
|
|
||
| ## What are SSH Keys? | ||
| # What are SSH keys? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. β Explaining what an SSH key is, briefly, was acceptable as preamble to the prior main content but ... does not need to be something we explain ourselves. You can get rid of key and agent explanation. |
||
|
|
||
| SSH keys are asymmetric key pairs that can be used for signing. Historically they were intended for | ||
| logging into servers, but recently they can also be used for signing arbitrary data such as Git | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
β Per my other comments, why can't this just be one SSH agent architecture page? I don't think we need the reorganization and it would be prudent to go the other way and be simpler.