Skip to content

Commit

Permalink
Updated docs for new keystore.
Browse files Browse the repository at this point in the history
Also updated feature descriptions in the library docs, and the readme.

Updated the cross-platform doc builder script to know about the new keystore.
  • Loading branch information
brotskydotcom committed Oct 24, 2024
1 parent 658174e commit f59afd5
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 47 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ This crate allows clients to "bring their own credential store" by providing tra

This crate provides built-in implementations of the following platform-specific credential stores:

* _Linux_: The DBus-based Secret Service and the kernel keyutils.
* _Linux_: The DBus-based Secret Service, the kernel keyutils, and a combo of the two.
* _FreeBSD_, _OpenBSD_: The DBus-based Secret Service.
* _macOS_, _iOS_: The local keychain.
* _Windows_: The Windows Credential Manager.

To enable the stores you want, you use features: there is one feature for each possibly-included credential store. If you specify a feature (e.g., `dbus-secret-service`) _and_ your target platform (e.g., `freebsd`) supports that credential store, it will be included as the default credential store in that build. That way you can have a build command that specifies a single credential store for each of your target platforms, and use that same build command for all targets.

If you don't enable any credential stores that are supported on a given platform, or you enable multiple credential stores for some platform, the _mock_ keystore will be the default on that platform. See the [developer docs](https://docs.rs/keyring/) for details of which features control the inclusion of which credential stores (and which platforms each credential store targets).
If you don't enable any credential stores that are supported on a given platform, the _mock_ keystore will be the default on that platform. See the [developer docs](https://docs.rs/keyring/) for details of which features control the inclusion of which credential stores.

### Platform-specific issues

Expand Down
14 changes: 10 additions & 4 deletions build-xplat-docs.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#!/bin/bash
cargo doc --no-deps --features=linux-native --target aarch64-unknown-linux-musl $OPEN_DOCS
cargo doc --no-deps --features=windows-native --target aarch64-pc-windows-msvc $OPEN_DOCS
cargo doc --no-deps --features=apple-native --target aarch64-apple-darwin $OPEN_DOCS
cargo doc --no-deps --features=apple-native --target aarch64-apple-ios $OPEN_DOCS
if [[ "$OSTYPE" == "linux"* ]]; then
cargo doc --no-deps --features=linux-native-sync-persistent $OPEN_DOCS
cargo doc --no-deps --features=sync-secret-service $OPEN_DOCS
cargo doc --no-deps --features=linux-native $OPEN_DOCS
elif [[ "$OSTYPE" == "darwin"* ]]; then
cargo doc --no-deps --features=linux-native --target aarch64-unknown-linux-musl $OPEN_DOCS
cargo doc --no-deps --features=windows-native --target aarch64-pc-windows-msvc $OPEN_DOCS
cargo doc --no-deps --features=apple-native --target aarch64-apple-darwin $OPEN_DOCS
cargo doc --no-deps --features=apple-native --target aarch64-apple-ios $OPEN_DOCS
fi
75 changes: 45 additions & 30 deletions src/keyutils_persistent.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
/*!
# keyutils-persistent credential store
This store is a combination of the [keyutils](crate::keyutils) store
backed up with a persistent [secret-service](crate::secret_service)
store.
# Linux (keyutils) store with Secret Service backing
This store, contributed by [@soywod](https://github.com/soywod),
uses the [keyutils module](crate::keyutils) as a cache
available to headless processes, while using the
[secret-service module](crate::secret_service)
to provide credential storage beyond reboot.
The expected usage pattern
for this module is as follows:
- Processes that run on headless systems are built with `keyutils` support via the
`linux-native` feature of this crate. After each reboot, these processes
are either launched after the keyutils cache has been reloaded from the secret service,
or (if launched immediately) they wait until the keyutils cache has been reloaded.
- A headed "configuration" process is built with this module that allows its user
to configure the credentials needed by the headless processes. After each reboot,
this process unlocks the secret service (see both the keyutils and secret-service
module for information about how this can be done headlessly, if desired) and then
accesses each of the configured credentials (which loads them into keyutils). At
that point the headless clients can be started (or become active, if already started).
This store works by creating a keyutils entry and a secret-service entry for
each of its entries. Because keyutils entries don't have attributes, entries
in this store don't expose attributes either. Because keyutils entries can't
store empty passwords/secrets, this store's entries can't either.
See the documentation for the `keyutils` and `secret-service` modules if you
want details about how the underlying storage is handled.
*/

use log::debug;
Expand All @@ -15,7 +37,7 @@ use super::credential::{
};
use super::error::{Error, Result};
use super::keyutils::KeyutilsCredential;
use super::secret_service::SsCredential;
use super::secret_service::{SsCredential, SsCredentialBuilder};

/// Representation of a keyutils-persistent credential.
///
Expand All @@ -36,13 +58,14 @@ impl CredentialApi for KeyutilsPersistentCredential {
/// Set a secret in the underlying store
///
/// It sets first the secret in keyutils, then in
/// secret-service. If the late one fails, keyutils secret change
/// secret-service. If the latter set fails, the former
/// is reverted.
fn set_secret(&self, secret: &[u8]) -> Result<()> {
let prev_secret = self.keyutils.get_secret();
self.keyutils.set_secret(secret)?;

if let Err(err) = self.ss.set_secret(secret) {
debug!("Failed set of secret-service: {err}; reverting keyutils");
match prev_secret {
Ok(ref secret) => self.keyutils.set_secret(secret),
Err(Error::NoEntry) => self.keyutils.delete_credential(),
Expand All @@ -66,11 +89,11 @@ impl CredentialApi for KeyutilsPersistentCredential {
return Ok(password);
}
Err(err) => {
debug!("cannot get password from keyutils: {err}, trying from secret service")
debug!("Failed get from keyutils: {err}; trying secret service")
}
}

let password = self.ss.get_password().map_err(ambigous_to_no_entry)?;
let password = self.ss.get_password().map_err(ambiguous_to_no_entry)?;
self.keyutils.set_password(&password)?;

Ok(password)
Expand All @@ -87,11 +110,11 @@ impl CredentialApi for KeyutilsPersistentCredential {
return Ok(secret);
}
Err(err) => {
debug!("cannot get secret from keyutils: {err}, trying from secret service")
debug!("Failed get from keyutils: {err}; trying secret service")
}
}

let secret = self.ss.get_secret().map_err(ambigous_to_no_entry)?;
let secret = self.ss.get_secret().map_err(ambiguous_to_no_entry)?;
self.keyutils.set_secret(&secret)?;

Ok(secret)
Expand Down Expand Up @@ -121,9 +144,8 @@ impl CredentialApi for KeyutilsPersistentCredential {
impl KeyutilsPersistentCredential {
/// Create the platform credential for a Keyutils entry.
///
/// An explicit target string is interpreted as the KeyRing to use for the entry.
/// If none is provided, then we concatenate the user and service in the string
/// `keyring-rs:user@service`.
/// This just passes the arguments to the underlying two stores
/// and wraps their results with an entry that holds both.
pub fn new_with_target(target: Option<&str>, service: &str, user: &str) -> Result<Self> {
let ss = SsCredential::new_with_target(target, service, user)?;
let keyutils = KeyutilsCredential::new_with_target(target, service, user)?;
Expand All @@ -144,27 +166,29 @@ pub fn default_credential_builder() -> Box<CredentialBuilder> {
}

impl CredentialBuilderApi for KeyutilsPersistentCredentialBuilder {
/// Build an [KeyutilsPersistentCredential] for the given target, service, and user.
/// Build a [KeyutilsPersistentCredential] for the given target, service, and user.
fn build(&self, target: Option<&str>, service: &str, user: &str) -> Result<Box<Credential>> {
Ok(Box::new(SsCredential::new_with_target(
target, service, user,
)?))
}

/// Return the underlying builder object with an `Any` type so that it can
/// be downgraded to an [KeyutilsPersistentCredentialBuilder] for platform-specific processing.
/// be downgraded to a [KeyutilsPersistentCredentialBuilder] for platform-specific processing.
fn as_any(&self) -> &dyn std::any::Any {
self
}

/// This keystore keeps credentials thanks to the inner secret-service store.
/// Return the persistence of this store.
///
/// This store's persistence derives from that of the secret service.
fn persistence(&self) -> CredentialPersistence {
CredentialPersistence::UntilDelete
SsCredentialBuilder {}.persistence()
}
}

/// Replace any Ambiguous error with a NoEntry one
fn ambigous_to_no_entry(err: Error) -> Error {
fn ambiguous_to_no_entry(err: Error) -> Error {
if let Error::Ambiguous(_) = err {
return Error::NoEntry;
};
Expand All @@ -174,18 +198,9 @@ fn ambigous_to_no_entry(err: Error) -> Error {

#[cfg(test)]
mod tests {
use crate::credential::CredentialPersistence;
use crate::{Entry, Error};

use super::{default_credential_builder, KeyutilsPersistentCredential};

#[test]
fn test_persistence() {
assert!(matches!(
default_credential_builder().persistence(),
CredentialPersistence::UntilDelete
))
}
use super::KeyutilsPersistentCredential;

fn entry_new(service: &str, user: &str) -> Entry {
crate::tests::entry_from_constructor(
Expand Down
29 changes: 18 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ example, the macOS Keychain credential store is only included if the `"apple-nat
feature is specified (and the crate is built with a macOS target).
If no specified credential store features apply to a given platform,
or multiple credential store features apply to a given platform,
this crate will use the (platform-independent) _mock_ credential store (see below)
on that platform. There are no
default features in this crate: you must specify explicitly which platform-specific
Expand All @@ -78,6 +77,18 @@ Here are the available credential store features:
- `linux-native`: Provides access to the `keyutils` storage on Linux.
- `linux-native-sync-persistent`: Uses both `keyutils` and `sync-secret-service`
(see below) for storage. See the docs for the `keyutils_persistent`
module for a full explanation of why both are used. Because this
store uses the `sync-secret-service`, you can use additional features related
to that store (described below).
- `linux-native-async-persistent`: Uses both `keyutils` and `async-secret-service`
(see below) for storage. See the docs for the `keyutils_persistent`
module for a full explanation of why both are used.
Because this store uses the `async-secret-service`, you
must specify the additional features required by that store (described below).
- `sync-secret-service`: Provides access to the DBus-based
[Secret Service](https://specifications.freedesktop.org/secret-service/latest/)
storage on Linux, FreeBSD, and OpenBSD. This is a _synchronous_ keystore that provides
Expand All @@ -99,17 +110,13 @@ Here are the available credential store features:
installed on the user's machine, specify the `vendored` feature
to statically link them with the built crate.
You cannot specify both the `sync-secret-service` and `async-secret-service` features;
this will produce a compile error. You must pick one or the other if you want to use
the secret service for credential storage.
The Linux platform is the only one for which this crate supplies multiple keystores:
secret-service and keyutils. The secret-service is the more widely used store, because
it provides persistence of credentials beyond reboot (which keyutils does not). However,
because secret-service relies on system UI for unlocking credentials, it often isn't
available on headless Linux installations, so keyutils is provided for those situations.
If you enable both the secret-service store and the keyutils store, the secret-service
store will be used as the default.
native (keyutils), sync or async secret service, and sync or async "combo" (both
keyutils and secret service). You cannot specify use of both sync and async
keystores; this will lead to a compile error. If you enable a combo keystore on Linux,
that will be the default keystore. If you don't enable a
combo keystore on Linux, but you do enable both the native and secret service keystores,
the secret service will be the default.
## Client-provided Credential Stores
Expand Down

0 comments on commit f59afd5

Please sign in to comment.