Skip to main content

manta_shared/common/config/
types.rs

1//! Typed config-file schemas for `cli.toml` and `server.toml`.
2//!
3//! See [`CliConfiguration`] and [`ServerConfiguration`] for the
4//! top-level shapes. The loaders that materialise these from disk
5//! live in the parent module ([`super::get_cli_configuration`],
6//! [`super::get_server_configuration`]).
7
8use std::collections::HashMap;
9
10use crate::common::audit::Auditor;
11
12use manta_backend_dispatcher::types::K8sDetails;
13use serde::{Deserialize, Serialize};
14
15/// Which backend API this site speaks.
16#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
17#[serde(rename_all = "lowercase")]
18pub enum BackendTechnology {
19  /// HPE Cray System Management (CSM) backend.
20  Csm,
21  /// OpenCHAMI backend.
22  Ochami,
23}
24
25impl BackendTechnology {
26  /// Return the lowercase string expected by `StaticBackendDispatcher::new`.
27  pub fn as_str(&self) -> &'static str {
28    match self {
29      Self::Csm => "csm",
30      Self::Ochami => "ochami",
31    }
32  }
33}
34
35#[derive(Serialize, Deserialize, Debug)]
36/// Connection details for a single ALPS site (CSM or OCHAMI instance).
37///
38/// The Vault URL used by handlers requiring vault (sat-file, session,
39/// console, logs) is derived at startup from
40/// `[sites.X.k8s.authentication.vault] base_url`. The vault secret path
41/// is derived from a hard-coded prefix and the site name. Neither is
42/// configured here.
43pub struct Site {
44  /// Which backend implementation this site uses (`csm` or `ochami`).
45  pub backend: BackendTechnology,
46  /// Optional per-site SOCKS5 proxy URL used by every outbound HTTP
47  /// request to this site's backend. `None` means direct connection.
48  pub socks5_proxy: Option<String>,
49  /// Base URL of the backend API (e.g. `https://api.alps.cscs.ch`).
50  pub shasta_base_url: String,
51  /// Optional Kubernetes connection details, required by handlers
52  /// that stream CFS session logs or attach to consoles.
53  pub k8s: Option<K8sDetails>,
54  /// Path (absolute or relative to the config dir) of the backend's
55  /// root CA certificate, used to verify TLS to `shasta_base_url`.
56  pub root_ca_cert_file: String,
57}
58
59/// Top-level configuration for the `manta-cli` binary. Persisted as TOML
60/// under `~/.config/manta/cli.toml`. Carries only the fields the CLI uses
61/// — every backend connection detail (per-site URLs, TLS certs, vault,
62/// k8s, per-site SOCKS proxies) lives in `ServerConfiguration`. The CLI
63/// only knows about the *one* manta-server it talks to.
64#[derive(Serialize, Deserialize, Debug)]
65pub struct CliConfiguration {
66  /// `EnvFilter` directive string for the tracing subscriber
67  /// (e.g. `"info"`, `"manta=debug,hyper=warn"`).
68  pub log: String,
69  /// Path to the local file the CLI appends audit lines to.
70  pub audit_file: String,
71  /// Active site name, sent as the `X-Manta-Site` header on every
72  /// request to manta-server. Overridable per-invocation with `--site`.
73  /// The server validates that the name matches one of its configured
74  /// sites; the CLI does no local validation.
75  pub site: String,
76  /// Default HSM group threaded into commands that accept
77  /// `--hsm-group` when none is supplied on the command line.
78  pub parent_hsm_group: String,
79  /// URL of the manta HTTP server this CLI talks to. Required — the CLI
80  /// no longer calls CSM/OCHAMI backends directly; every operation
81  /// (including auth) is forwarded through `manta-server`.
82  pub manta_server_url: String,
83  /// Optional SOCKS5 proxy used to reach `manta_server_url`. Per-site
84  /// proxying for backend traffic is the server's concern.
85  pub socks5_proxy: Option<String>,
86  /// Optional Kafka audit forwarder. When `None`, the CLI emits no
87  /// audit messages.
88  pub auditor: Option<Auditor>,
89}
90
91/// Server-only settings — TLS, listen address, console behaviour. Lives
92/// under `[server]` in `server.toml`.
93#[derive(Serialize, Deserialize, Debug)]
94pub struct ServerSettings {
95  /// TCP listen address (e.g. "0.0.0.0").
96  pub listen_address: String,
97  /// TCP port for the TLS server.
98  pub port: u16,
99  /// Path to the TLS certificate (PEM).
100  pub cert: Option<String>,
101  /// Path to the TLS private key (PEM).
102  pub key: Option<String>,
103  /// How long a node-console WebSocket stays open without activity
104  /// before the server tears it down.
105  pub console_inactivity_timeout_secs: u64,
106  /// Per-source-IP rate limit for the `/api/v1/auth/*` endpoints,
107  /// in requests per minute. `None` disables in-process rate limiting
108  /// (operators are then expected to enforce it at the reverse proxy).
109  pub auth_rate_limit_per_minute: Option<u32>,
110}
111
112/// Top-level configuration for the `manta-server` binary. Persisted as
113/// TOML under `~/.config/manta/server.toml`. Has no notion of an "active"
114/// site — the server hosts every configured site simultaneously and
115/// clients select per-request via the `X-Manta-Site` header.
116#[derive(Serialize, Deserialize, Debug)]
117pub struct ServerConfiguration {
118  /// `EnvFilter` directive for the tracing subscriber.
119  pub log: String,
120  /// Path to the local file the server appends audit lines to.
121  pub audit_file: String,
122  /// Network / TLS / console / rate-limit knobs for the HTTPS server.
123  pub server: ServerSettings,
124  /// Per-site backend connection details, keyed by site name. The
125  /// `X-Manta-Site` header on each request picks which one to route to.
126  pub sites: HashMap<String, Site>,
127  /// Optional Kafka audit forwarder (typically used for `/auth/*`
128  /// attempts). When `None`, the server emits no audit messages.
129  pub auditor: Option<Auditor>,
130}
131
132#[cfg(test)]
133mod tests {
134  use super::*;
135
136  #[test]
137  fn site_deserialize_missing_backend_fails() {
138    let bad_toml = r#"
139      shasta_base_url = "https://api.example.com"
140      root_ca_cert_file = "cert.pem"
141      # missing backend
142    "#;
143    let result = toml::from_str::<Site>(bad_toml);
144    assert!(result.is_err());
145  }
146
147  #[test]
148  fn backend_technology_as_str() {
149    assert_eq!(BackendTechnology::Csm.as_str(), "csm");
150    assert_eq!(BackendTechnology::Ochami.as_str(), "ochami");
151  }
152
153  #[test]
154  fn backend_technology_roundtrip_toml() {
155    // Verify TOML serializes as lowercase "csm" / "ochami"
156    #[derive(Serialize, Deserialize)]
157    struct Wrapper {
158      backend: BackendTechnology,
159    }
160    let w = Wrapper {
161      backend: BackendTechnology::Csm,
162    };
163    let s = toml::to_string(&w).unwrap();
164    assert!(s.contains("\"csm\"") || s.contains("csm"));
165    let parsed: Wrapper = toml::from_str(&s).unwrap();
166    assert_eq!(parsed.backend, BackendTechnology::Csm);
167  }
168
169  fn make_minimal_site() -> Site {
170    Site {
171      backend: BackendTechnology::Csm,
172      socks5_proxy: None,
173      shasta_base_url: "https://api.example.com".to_string(),
174      k8s: None,
175      root_ca_cert_file: "cert.pem".to_string(),
176    }
177  }
178
179  #[test]
180  fn cli_configuration_roundtrip_toml_minimal() {
181    let cfg = CliConfiguration {
182      log: "info".to_string(),
183      audit_file: "/tmp/cli-audit.log".to_string(),
184      site: "alps".to_string(),
185      parent_hsm_group: "nodes_free".to_string(),
186      manta_server_url: "https://manta-server.cscs.ch:8443".to_string(),
187      socks5_proxy: Some("socks5h://127.0.0.1:1080".to_string()),
188      auditor: None,
189    };
190    let toml_str = toml::to_string(&cfg).unwrap();
191    let parsed: CliConfiguration = toml::from_str(&toml_str).unwrap();
192    assert_eq!(parsed.site, "alps");
193    assert_eq!(parsed.parent_hsm_group, "nodes_free");
194    assert_eq!(parsed.manta_server_url, "https://manta-server.cscs.ch:8443");
195    assert_eq!(
196      parsed.socks5_proxy.as_deref(),
197      Some("socks5h://127.0.0.1:1080")
198    );
199  }
200
201  #[test]
202  fn cli_configuration_socks5_proxy_optional() {
203    let toml_str = r#"
204      log = "info"
205      audit_file = "/tmp/cli-audit.log"
206      site = "alps"
207      parent_hsm_group = ""
208      manta_server_url = "https://manta-server.cscs.ch:8443"
209    "#;
210    let parsed: CliConfiguration = toml::from_str(toml_str).unwrap();
211    assert!(parsed.socks5_proxy.is_none());
212  }
213
214  #[test]
215  fn cli_configuration_missing_manta_server_url_fails() {
216    let bad_toml = r#"
217      log = "info"
218      audit_file = "/tmp/cli-audit.log"
219      site = "alps"
220      parent_hsm_group = ""
221      # missing manta_server_url
222    "#;
223    let result = toml::from_str::<CliConfiguration>(bad_toml);
224    assert!(result.is_err());
225  }
226
227  #[test]
228  fn server_configuration_roundtrip_toml_minimal() {
229    let mut sites = HashMap::new();
230    sites.insert("alps".to_string(), make_minimal_site());
231    let cfg = ServerConfiguration {
232      log: "info".to_string(),
233      audit_file: "/var/log/manta/server-audit.log".to_string(),
234      server: ServerSettings {
235        listen_address: "0.0.0.0".to_string(),
236        port: 8443,
237        cert: Some("/etc/manta/tls/server.crt".to_string()),
238        key: Some("/etc/manta/tls/server.key".to_string()),
239        console_inactivity_timeout_secs: 1800,
240        auth_rate_limit_per_minute: Some(60),
241      },
242      sites,
243      auditor: None,
244    };
245    let toml_str = toml::to_string(&cfg).unwrap();
246    let parsed: ServerConfiguration = toml::from_str(&toml_str).unwrap();
247    assert_eq!(parsed.server.port, 8443);
248    assert_eq!(parsed.server.listen_address, "0.0.0.0");
249    assert_eq!(parsed.server.console_inactivity_timeout_secs, 1800);
250    assert_eq!(
251      parsed.server.cert.as_deref(),
252      Some("/etc/manta/tls/server.crt")
253    );
254  }
255
256  #[test]
257  fn server_configuration_deserialize_missing_server_section_fails() {
258    let bad_toml = r#"
259      log = "info"
260      audit_file = "/tmp/server.log"
261      [sites]
262    "#;
263    let result = toml::from_str::<ServerConfiguration>(bad_toml);
264    assert!(result.is_err());
265  }
266
267  #[test]
268  fn server_settings_optional_tls_paths() {
269    // TLS cert/key are optional in the schema — flags can supply them
270    // at runtime when the config omits them.
271    let toml_str = r#"
272      listen_address = "0.0.0.0"
273      port = 8443
274      console_inactivity_timeout_secs = 1800
275    "#;
276    let parsed: ServerSettings = toml::from_str(toml_str).unwrap();
277    assert!(parsed.cert.is_none());
278    assert!(parsed.key.is_none());
279  }
280}