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}