Skip to main content

arti/rpc/
configuration.rs

1//! Functionality for accessing and modifying configuration over RPC
2
3use std::sync::Arc;
4
5use tor_rpcbase as rpc;
6use tor_rtcompat::Runtime;
7
8use crate::rpc::RpcSuperuser;
9
10/// A set of configuration options maintained by the RPC subsystem.
11///
12/// These are applied after all loaded configuration values.
13#[derive(Clone, Debug, serde::Serialize)]
14#[serde(transparent)]
15#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
16pub(crate) struct ConfigSettings(serde_json::Value);
17
18impl Default for ConfigSettings {
19    fn default() -> Self {
20        Self(serde_json::Value::Object(Default::default()))
21    }
22}
23
24impl ConfigSettings {
25    /// Replace the value at `key` within this object with `value`.
26    ///
27    /// `key` is interpreted as a dot-separated sequence of dictionary keys.
28    ///
29    /// Either "." or "" can be used to refer to the root of the tree.
30    ///
31    /// Note this function can easily create an invalid configuration.
32    fn apply_key_value(&mut self, key: &str, value: serde_json::Value) {
33        use serde_json::{Map, Value};
34
35        if ["", "."].contains(&key) {
36            self.0 = value;
37            return;
38        }
39
40        let mut v: &mut Value = &mut self.0;
41        for path_elt in key.split('.') {
42            if v.is_object() {
43                let map = v.as_object_mut().expect("No longer an object");
44                v = map.entry(path_elt).or_insert(Value::Null);
45            } else {
46                let mut map: Map<String, Value> = Default::default();
47                map.insert(path_elt.to_string(), Value::Null);
48                *v = Value::Object(map);
49                v = v
50                    .as_object_mut()
51                    .expect("value stopped being an object")
52                    .get_mut(path_elt)
53                    .expect("entry in object disappeared");
54            }
55        }
56
57        *v = value;
58    }
59}
60
61/// One or more configuration settings returned by the RPC subsystem.
62#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
63#[serde(transparent)]
64#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
65pub(crate) struct ConfigValue(serde_json::Value);
66
67/// Change the value of a part of the configuration tree.
68///
69/// This method requires superuser capability,
70/// since it affects all Arti sessions.
71///
72/// ## Semantics
73///
74/// Arti takes its configuration from the following sources:
75///
76/// 1. A set of default values
77/// 2. Configuration files on disk
78/// 3. Command-line configuration arguments
79/// 4. Options provided via RPC
80///
81/// These sources are applied in order,
82/// with later options possibly overriding earlier ones.
83/// (When a set of options is stored in a map, the maps are merged,
84/// with the later map getting precedence.)
85/// The set of options forms a tree.
86/// In the RPC system, we represent this tree as a JSON Object.
87///
88/// > TODO: Explain all of this more clearly.
89///
90/// _This RPC method_ lets you change the set of options provided via RPC.
91/// (source 4 in the list above).
92///
93/// When you use this method, it _replaces_ part the the RPC configuration
94/// options at the path in the tree represented by `key`
95/// with the provided `value`.
96///
97/// ## Error behavior
98///
99/// This method will fail if:
100///
101/// - Any provided option is invalid
102/// - The transition from the current set of options
103///   to the new set is not allowed
104/// - Or something goes wrong when trying to transition.
105///
106/// If this method fails,
107/// and the configuration will (if possible[^original-state])
108/// stay in its original state,
109/// and the method will return an error.
110///
111/// > The current error type contains no machine-readable elements
112/// > describing how it failed;
113/// > we do intend to add some in the future.
114///
115/// **NOTE**: This method currently **will not fail** for any unrecognized options.
116/// We will, later, add options to require only recognized options,
117/// or to list unrecognized options.
118///
119/// [^original-state]: If arti can reject the configuration changes
120///    before it starts trying to apply them, we guarantee that
121///    the configuration will stay in its original state.
122///    Otherwise, arti will try its best to apply configuration
123///    changes on an all-or-nothing basis,
124///    but bugs or system limitations may prevent this from working completely.
125///
126/// ## Examples
127///
128/// ### Setting a single option
129///
130/// This invocation will disable client connections to "local"
131/// addresses over the Tor network.  It will override any
132/// value for this option set in any other configuration source.
133///
134/// ```json
135/// { "key": "address_filter.allow_local_addrs", "value": false }
136/// ```
137///
138/// ### Setting several options at a given path.
139///
140/// This invocation will disable client connections to local
141/// addresses.
142///
143/// This replaces the whole sub-tree of RPC-provided options at `address_filter`.
144/// Therefore, if there are any other RPC-provided options for `address_filter.*`,
145/// **this invocation will remove them**.
146///
147/// > We might add optional "merge" semantics (instead of replace) in the future.
148///
149/// ```json
150/// { "key": "address_filter",
151///   "value": { "allow_local_addrs": false } }
152/// ```
153///
154/// ### Clearing all RPC-provided options
155///
156/// This invocation will restore the configuration to the state
157/// of having _no_ RPC-provided options:
158///
159/// ```json
160/// { "key": '', "value": {} }
161/// ```
162///
163/// It works by replacing the root of the RPC option tree with an empty Object,
164/// so that only options from the other sources will remain.
165#[derive(Debug, serde::Deserialize, derive_deftly::Deftly)]
166#[derive_deftly(rpc::DynMethod)]
167#[deftly(rpc(method_name = "arti:set_config"))]
168pub(super) struct SetConfig {
169    /// A path in the RPC configuration tree to override.
170    ///
171    /// This path is either the empty string,
172    /// or a sequence of period-separated configuration identifiers.
173    /// The root of the tree can be represented as "." or as "".
174    ///
175    /// No validation is done to ensure that the identifiers are actually
176    /// a recognized configuration option.
177    key: String,
178
179    /// A value to replace part of the RPC configuration tree.
180    ///
181    /// See the method description for an overview of the semantics
182    /// of the RPC configuration tree.
183    /// Notably, this method _replaces_ parts of the RPC configuration tree,
184    /// and the RPC configuration tree is _merged into_
185    /// the configuration from other sources.
186    value: ConfigValue,
187}
188
189impl rpc::RpcMethod for SetConfig {
190    type Output = rpc::Nil;
191    type Update = rpc::NoUpdates;
192}
193
194/// Return the value of part of the configuration tree.
195///
196/// This method requires superuser access, since some options
197/// (like onion service configurations) can be sensitive.
198///
199/// This method can return either the value for a single option,
200/// or multiple values of the tree.
201///
202/// It returns the actual configuration values _as used_:
203///
204/// - All defaults are filled in.
205/// - Any renamed options are replaced with their real names.
206/// - Unrecognized options are omitted.
207#[derive(Debug, serde::Deserialize, derive_deftly::Deftly)]
208#[derive_deftly(rpc::DynMethod)]
209#[deftly(rpc(method_name = "arti:get_config"))]
210pub(super) struct GetConfig {
211    /// A path in the RPC configuration tree to retrieve.
212    ///
213    /// This path is either the empty string,
214    /// or a sequence of period-separated configuration identifiers.
215    /// The root of the tree can be represented as "." or as "".
216    key: String,
217}
218
219/// The result from a call to `arti:get_config`
220#[derive(Clone, Debug, serde::Serialize)]
221pub(super) struct GetConfigResult {
222    /// A JSON value representing part of the configuration tree,
223    /// or `null` if there is no value at that position in the tree.
224    value: Option<ConfigValue>,
225}
226
227impl rpc::RpcMethod for GetConfig {
228    type Output = GetConfigResult;
229    type Update = rpc::NoUpdates;
230}
231
232/// RPC method implementation: invoke `arti:set_config` on a superuser session.
233pub(super) async fn set_config_on_rpcsuperuser<R: Runtime>(
234    session: Arc<RpcSuperuser<R>>,
235    method: Box<SetConfig>,
236    _ctx: Arc<dyn rpc::Context>,
237) -> Result<rpc::Nil, rpc::RpcError> {
238    let cfg_mgr = &session.cfg_mgr;
239    cfg_mgr.try_modify_cfg(
240        |cfg| {
241            cfg.apply_key_value(method.key.as_str(), method.value.0);
242            Ok(())
243        },
244        tor_config::Reconfigure::AllOrNothing,
245    )?;
246    Ok(rpc::Nil::default())
247}
248
249/// RPC method implementation: invoke `arti:get_config` on a supuruser session.
250pub(super) async fn get_config_on_rpcsuperuser<R: Runtime>(
251    session: Arc<RpcSuperuser<R>>,
252    method: Box<GetConfig>,
253    _ctx: Arc<dyn rpc::Context>,
254) -> Result<GetConfigResult, rpc::RpcError> {
255    let cfg_mgr = &session.cfg_mgr;
256    let value = cfg_mgr
257        .get_cfg_setting(method.key.as_str())
258        .map_err(|e| rpc::RpcError::new(e.to_string(), rpc::RpcErrorKind::RequestError))?;
259    Ok(GetConfigResult { value })
260}