1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
// SPDX-License-Identifier: AGPL-3.0-or-later
//! This crate defines the common configuration system used by other Blocktree crates. To define a
//! new configuration struct all you need to do is derive [Serialize] and [Deserialize] and
//! implement the [Default] trait.
//!
//! Environment variables prefixed with "BT_" are used for setting configuration values. When a
//! configuration struct contains another struct in one of its fields, then names of the fields
//! in the sub-struct are separated from the struct's field name with `_`. In order for this
//! to work you have to use a [serde] attribute to rename fields which contain `_` characters.
//! For example:
//! ```
//! use btconfig::CredStoreConfig;
//! use std::path::PathBuf;
//! use serde::{Serialize, Deserialize};
//!
//! #[derive(Serialize, Deserialize)]
//! struct AppConfig {
//! #[serde(rename = "credstore")]
//! cred_store: CredStoreConfig,
//! path: PathBuf,
//! }
//! ```
//! For this struct, the path field is set using the `BT_PATH` environment variable. In order to set
//! credential store, the `BT_CREDSTORE_TYPE` variable must be set to a valid variant name of the
//! [CredStoreConfig] enum, and the
//! required fields must be set using their variables. For example, if `BT_CREDSTORE_TYPE=File`,
//! then `BT_CREDSTORE_PATH` must be set to the path to the credential store file.
//!
//! When an enum is used for configuration, serde must be configured to externally tag it in order
//! for a variant to be specified using an environment variable. This is done by adding
//! `#[serde(tag = "type")]` to the enum. See [CredStoreConfig] as an example.
use btlib::{
self,
crypto::{file_cred_store::FileCredStore, tpm::TpmCredStore, CredStore, CredStoreMut, Creds},
Result,
};
use figment::{providers::Env, Figment, Metadata, Provider};
use serde::{Deserialize, Serialize};
use std::{path::PathBuf, result::Result as StdResult, sync::Arc};
pub const SYS_CONFIG_DIR: &str = "/etc/blocktree";
/// Returns the path of the current user's configuration directory.
pub fn user_config_dir() -> PathBuf {
let home = std::env::var("HOME");
let base = if let Ok(ref home) = home { home } else { "." };
let mut path = PathBuf::from(base);
path.push(".config/blocktree");
path
}
/// Returns the default directory to store blocks in.
pub fn default_block_dir() -> PathBuf {
let mut path = user_config_dir();
path.push("blocks");
path
}
/// Attempts to unwrap the [Option] field in `$config` called `$name`. This macro internal
/// uses the `?` operator, and can only be used in methods which return a [Result] which has an
/// error type that a [btlib::Error] can be converted to.
#[macro_export]
macro_rules! get_setting {
($config:expr, $name:ident) => {
$config
.$name
.ok_or_else(|| bterr!(concat!("setting '", stringify!($name), "' was not set")))?
};
}
/// Attempts to unwrap all of the [Option] fields with the given names in the configuration struct
/// and returns them all as a tuple. The same consideration which apply to [get_setting!] apply to
/// this macro.
#[macro_export]
macro_rules! get_settings {
($config:expr$(, $names:ident)+) => {
($(get_setting!($config, $names),)+)
};
}
pub trait FigmentExt: Sized {
fn btconfig(self) -> Result<Self>;
}
impl FigmentExt for Figment {
fn btconfig(self) -> Result<Self> {
Ok(self.merge(Env::prefixed("BT_").split("_")))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
/// Configuration which specifies how to construct a [btlib::crypto::CredStore].
pub enum CredStoreConfig {
/// Use a local file as a credential store.
File {
/// The path where the credential store is located.
path: PathBuf,
},
/// Use the configured TPM as a credential store.
Tpm {
/// The state path to use for the TPM credential store.
path: PathBuf,
/// The configuration string to pass to `tss-esapi`.
tabrmd: String,
},
}
impl CredStoreConfig {
pub fn from<T: Provider>(provider: T) -> StdResult<Self, figment::Error> {
Figment::from(provider).extract()
}
pub fn figment() -> Figment {
Figment::from(CredStoreConfig::default())
}
}
impl Default for CredStoreConfig {
fn default() -> Self {
let mut path = user_config_dir();
path.push("file_credstore");
CredStoreConfig::File { path }
}
}
impl Provider for CredStoreConfig {
fn metadata(&self) -> figment::Metadata {
Metadata::named("btconfig")
}
fn data(
&self,
) -> StdResult<figment::value::Map<figment::Profile, figment::value::Dict>, figment::Error>
{
figment::providers::Serialized::defaults(Self::default()).data()
}
}
/// Trait for types which can consume a [CredStore] implementation.
pub trait CredStoreConsumer {
type Output;
fn consume<C: CredStore>(self, cred_store: C) -> Self::Output;
}
/// Trait for types which can consume a [CredStoreMut] implementation.
pub trait CredStoreMutConsumer {
type Output;
fn consume_mut<C: CredStoreMut>(self, cred_store: C) -> Self::Output;
}
macro_rules! impl_consume {
($name:ident, $consumer:path) => {
#[doc = concat!("Calls `", stringify!($name), "` with the credential store created based on the configuration data in `self`")]
pub fn $name<F: $consumer>(self, consumer: F) -> Result<F::Output> {
match self {
CredStoreConfig::File { path } => {
let store = FileCredStore::new(path)?;
Ok(consumer.$name(store))
}
CredStoreConfig::Tpm { path, tabrmd } => {
let store = TpmCredStore::from_tabrmd(&tabrmd, path)?;
Ok(consumer.$name(store))
}
}
}
}
}
impl CredStoreConfig {
impl_consume!(consume, CredStoreConsumer);
impl_consume!(consume_mut, CredStoreMutConsumer);
}
/// A [CredStoreConsumer] which gets the node creds from the [CredStore], then returns them
/// as an `Arc<dyn Creds>`.
pub struct NodeCredConsumer;
impl CredStoreConsumer for NodeCredConsumer {
type Output = Result<Arc<dyn Creds>>;
fn consume<C: CredStore>(self, cred_store: C) -> Self::Output {
Ok(Arc::new(cred_store.node_creds()?))
}
}