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()?))
    }
}