witchcraft_server_config/install/
mod.rs

1// Copyright 2021 Palantir Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Fixed configuration.
15use crate::ConfigError;
16use serde::de::Error;
17use serde::{Deserialize, Deserializer};
18use staged_builder::{staged_builder, Validate};
19use std::env;
20use std::path::{Path, PathBuf};
21use std::time::Duration;
22
23mod de;
24
25/// The fixed configuration for a Witchcraft server.
26#[derive(Clone, PartialEq, Debug)]
27#[staged_builder]
28#[builder(validate)]
29pub struct InstallConfig {
30    #[builder(into)]
31    product_name: String,
32    #[builder(into)]
33    product_version: String,
34    port: u16,
35    #[builder(default, into)]
36    management_port: Option<u16>,
37    #[builder(default)]
38    keystore: KeystoreConfig,
39    #[builder(default, into)]
40    client_auth_truststore: Option<ClientAuthTruststoreConfig>,
41    #[builder(into, default = "/".to_string())]
42    context_path: String,
43    #[builder(default = env::var_os("CONTAINER").is_some())]
44    use_console_log: bool,
45    #[builder(default)]
46    server: ServerConfig,
47    #[builder(default)]
48    minidump: MinidumpConfig,
49}
50
51impl Validate for InstallConfig {
52    type Error = ConfigError;
53
54    fn validate(&self) -> Result<(), Self::Error> {
55        if !(self.context_path == "/"
56            || (self.context_path.starts_with('/') && !self.context_path.ends_with('/')))
57        {
58            return Err(ConfigError(
59                "context-path must either be `/` or start but not end with a `/`".to_string(),
60            ));
61        }
62
63        Ok(())
64    }
65}
66
67impl<'de> Deserialize<'de> for InstallConfig {
68    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
69    where
70        D: Deserializer<'de>,
71    {
72        let raw = de::InstallConfig::deserialize(deserializer)?;
73        let mut builder = InstallConfig::builder()
74            .product_name(raw.product_name)
75            .product_version(raw.product_version)
76            .port(raw.port);
77        if let Some(management_port) = raw.management_port {
78            builder = builder.management_port(management_port);
79        }
80        if let Some(keystore) = raw.keystore {
81            builder = builder.keystore(keystore);
82        }
83        if let Some(client_auth_truststore) = raw.client_auth_truststore {
84            builder = builder.client_auth_truststore(client_auth_truststore);
85        }
86        if let Some(context_path) = raw.context_path {
87            builder = builder.context_path(context_path);
88        }
89        if let Some(use_console_log) = raw.use_console_log {
90            builder = builder.use_console_log(use_console_log);
91        }
92        if let Some(server) = raw.server {
93            builder = builder.server(server);
94        }
95        if let Some(minidump) = raw.minidump {
96            builder = builder.minidump(minidump);
97        }
98        builder.build().map_err(Error::custom)
99    }
100}
101
102impl AsRef<InstallConfig> for InstallConfig {
103    #[inline]
104    fn as_ref(&self) -> &InstallConfig {
105        self
106    }
107}
108
109impl InstallConfig {
110    /// Returns the service's name.
111    ///
112    /// Required.
113    #[inline]
114    pub fn product_name(&self) -> &str {
115        &self.product_name
116    }
117
118    /// Returns the service's version.
119    ///
120    /// Required.
121    #[inline]
122    pub fn product_version(&self) -> &str {
123        &self.product_version
124    }
125
126    /// Returns the port the server will listen on.
127    ///
128    /// Required.
129    #[inline]
130    pub fn port(&self) -> u16 {
131        self.port
132    }
133
134    /// Returns the port that the server's management APIs will listen on.
135    ///
136    /// Defaults to `port()`.
137    pub fn management_port(&self) -> Option<u16> {
138        self.management_port
139    }
140
141    /// Returns the server's TLS key configuration.
142    #[inline]
143    pub fn keystore(&self) -> &KeystoreConfig {
144        &self.keystore
145    }
146
147    /// Returns the server's TLS client authentication truststore configuration.
148    ///
149    /// If set, the server will request (but not require) the client to authenticate itself during the TLS handshake.
150    /// If a certificate is present and validated against the trust roots, all requests made over that connection will
151    /// include a `ClientCertificate` extension.
152    ///
153    /// Defaults to `None`.
154    #[inline]
155    pub fn client_auth_truststore(&self) -> Option<&ClientAuthTruststoreConfig> {
156        self.client_auth_truststore.as_ref()
157    }
158
159    /// Returns the server's context path.
160    ///
161    /// This must either be equal to `/` or start but not end with a `/`.
162    ///
163    /// Defaults to `/`.
164    #[inline]
165    pub fn context_path(&self) -> &str {
166        &self.context_path
167    }
168
169    /// If `true`, the server will log to standard output rather than to files.
170    ///
171    /// Defaults to `true` if running in a container and false otherwise.
172    #[inline]
173    pub fn use_console_log(&self) -> bool {
174        self.use_console_log
175    }
176
177    /// Returns advanced server settings.
178    #[inline]
179    pub fn server(&self) -> &ServerConfig {
180        &self.server
181    }
182
183    /// Returns minidump settings.
184    #[inline]
185    pub fn minidump(&self) -> &MinidumpConfig {
186        &self.minidump
187    }
188}
189
190/// TLS key configuration.
191#[derive(Clone, PartialEq, Debug)]
192#[staged_builder]
193pub struct KeystoreConfig {
194    #[builder(into, default = PathBuf::from("var/security/key.pem"))]
195    key_path: PathBuf,
196    #[builder(into, default = PathBuf::from("var/security/cert.cer"))]
197    cert_path: PathBuf,
198}
199
200impl Default for KeystoreConfig {
201    #[inline]
202    fn default() -> Self {
203        KeystoreConfig::builder().build()
204    }
205}
206
207impl<'de> Deserialize<'de> for KeystoreConfig {
208    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
209    where
210        D: Deserializer<'de>,
211    {
212        let raw = de::KeystoreConfig::deserialize(deserializer)?;
213        let mut builder = KeystoreConfig::builder();
214        if let Some(key_path) = raw.key_path {
215            builder = builder.key_path(key_path);
216        }
217        if let Some(cert_path) = raw.cert_path {
218            builder = builder.cert_path(cert_path);
219        }
220        Ok(builder.build())
221    }
222}
223
224impl KeystoreConfig {
225    /// Returns the path to the server's PEM-encoded private key.
226    ///
227    /// Defaults to `var/security/key.pem`.
228    #[inline]
229    pub fn key_path(&self) -> &Path {
230        &self.key_path
231    }
232
233    /// Returns the path to the server's PEM-encoded certificate chain.
234    ///
235    /// The file should contain a sequence of certificates starting with the leaf certificate corresponding to the key
236    /// in [`Self::key_path`] followed by the rest of the certificate chain up to a trusted root.
237    ///
238    /// Defaults to `var/security/cert.cer`.
239    #[inline]
240    pub fn cert_path(&self) -> &Path {
241        &self.cert_path
242    }
243}
244
245/// TLS client authentication configuration.
246#[derive(Clone, PartialEq, Debug)]
247#[staged_builder]
248pub struct ClientAuthTruststoreConfig {
249    #[builder(into, default = PathBuf::from("var/security/ca.cer"))]
250    path: PathBuf,
251}
252
253impl Default for ClientAuthTruststoreConfig {
254    #[inline]
255    fn default() -> Self {
256        ClientAuthTruststoreConfig::builder().build()
257    }
258}
259
260impl<'de> Deserialize<'de> for ClientAuthTruststoreConfig {
261    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
262    where
263        D: Deserializer<'de>,
264    {
265        let raw = de::ClientAuthTruststoreConfig::deserialize(deserializer)?;
266        let mut builder = ClientAuthTruststoreConfig::builder();
267        if let Some(path) = raw.path {
268            builder = builder.path(path);
269        }
270        Ok(builder.build())
271    }
272}
273
274impl ClientAuthTruststoreConfig {
275    /// Returns the path to a file containg PEM-encoded certificates (i.e. blocks of `-----BEGIN CERTIFICATE-----`)
276    /// that will act as the trust roots for validating the client's identity.
277    ///
278    /// Defaults to `var/security/ca.cer`.
279    #[inline]
280    pub fn path(&self) -> &Path {
281        &self.path
282    }
283}
284
285/// Advanced server configuration.
286#[derive(Clone, PartialEq, Debug)]
287#[staged_builder]
288pub struct ServerConfig {
289    #[builder(default = num_cpus::get())]
290    processors: usize,
291    #[builder(default, custom(type = usize, convert = Some))]
292    min_threads: Option<usize>,
293    #[builder(default, custom(type = usize, convert = Some))]
294    max_threads: Option<usize>,
295    #[builder(default, custom(type = usize, convert = Some))]
296    max_connections: Option<usize>,
297    #[builder(default, custom(type = usize, convert = Some))]
298    io_threads: Option<usize>,
299    #[builder(default = Duration::from_secs(5 * 60))]
300    idle_thread_timeout: Duration,
301    #[builder(default = Duration::from_secs(15))]
302    shutdown_timeout: Duration,
303    #[builder(default = true)]
304    gzip: bool,
305    #[builder(default = false)]
306    http2: bool,
307    #[builder(default, into)]
308    idle_connection_timeout: Option<Duration>,
309}
310
311impl Default for ServerConfig {
312    #[inline]
313    fn default() -> Self {
314        ServerConfig::builder().build()
315    }
316}
317
318impl<'de> Deserialize<'de> for ServerConfig {
319    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
320    where
321        D: Deserializer<'de>,
322    {
323        let raw = de::ServerConfig::deserialize(deserializer)?;
324        let mut builder = ServerConfig::builder();
325        if let Some(processors) = raw.processors {
326            builder = builder.processors(processors);
327        }
328        if let Some(min_threads) = raw.min_threads {
329            builder = builder.min_threads(min_threads);
330        }
331        if let Some(max_threads) = raw.max_threads {
332            builder = builder.max_threads(max_threads);
333        }
334        if let Some(max_connections) = raw.max_connections {
335            builder = builder.max_connections(max_connections);
336        }
337        if let Some(io_threads) = raw.io_threads {
338            builder = builder.io_threads(io_threads);
339        }
340        if let Some(idle_thread_timeout) = raw.idle_thread_timeout {
341            builder = builder.idle_thread_timeout(idle_thread_timeout);
342        }
343        if let Some(shutdown_timeout) = raw.shutdown_timeout {
344            builder = builder.shutdown_timeout(shutdown_timeout);
345        }
346        if let Some(gzip) = raw.gzip {
347            builder = builder.gzip(gzip);
348        }
349        if let Some(http2) = raw.http2 {
350            builder = builder.http2(http2);
351        }
352        if let Some(idle_connection_timeout) = raw.idle_connection_timeout {
353            builder = builder.idle_connection_timeout(idle_connection_timeout);
354        }
355
356        Ok(builder.build())
357    }
358}
359
360impl ServerConfig {
361    /// Returns the number of processors the server is allocated.
362    ///
363    /// This is only used to derive default values for other settings in this type.
364    ///
365    /// Defaults to the number of logical CPUs.
366    #[inline]
367    pub fn processors(&self) -> usize {
368        self.processors
369    }
370
371    /// Returns the minimum number of threads in the pool used to process blocking endpoints.
372    ///
373    /// Defaults to 8 times the number of processors.
374    #[inline]
375    pub fn min_threads(&self) -> usize {
376        self.min_threads.unwrap_or(self.processors * 8)
377    }
378
379    /// Returns the maximum number of threads in the pool used to process blocking endpoints.
380    ///
381    /// Defaults to the maximum of 32 times the number of processors and 256.
382    #[inline]
383    pub fn max_threads(&self) -> usize {
384        self.max_threads
385            .unwrap_or_else(|| usize::max(self.processors * 32, 256))
386    }
387
388    /// Returns the maximum number of live TCP connections the server will allow at any time.
389    ///
390    /// Defaults to 10 times the value of [`Self::max_threads`].
391    #[inline]
392    pub fn max_connections(&self) -> usize {
393        self.max_connections
394            .unwrap_or_else(|| self.max_threads() * 10)
395    }
396
397    /// Returns the number of threads used for nonblocking operations in the server's Tokio runtime.
398    ///
399    /// Defaults to half the number of processors.
400    #[inline]
401    pub fn io_threads(&self) -> usize {
402        self.io_threads
403            .unwrap_or_else(|| usize::max(1, self.processors / 2))
404    }
405
406    /// Returns the amount of time a thread in the blocking request pool will sit idle before shutting down.
407    ///
408    /// Defaults to 5 minutes.
409    #[inline]
410    pub fn idle_thread_timeout(&self) -> Duration {
411        self.idle_thread_timeout
412    }
413
414    /// Returns the amount of time the server will wait for pending requests to complete when shutting down.
415    ///
416    /// Defaults to 15 seconds.
417    #[inline]
418    pub fn shutdown_timeout(&self) -> Duration {
419        self.shutdown_timeout
420    }
421
422    /// Determines if responses larger than 1 MiB will be compressed with gzip.
423    ///
424    /// Defaults to `true`.
425    #[inline]
426    pub fn gzip(&self) -> bool {
427        self.gzip
428    }
429
430    /// Determines if the server will support the HTTP2 protocol.
431    ///
432    /// Defaults to `false`.
433    #[inline]
434    pub fn http2(&self) -> bool {
435        self.http2
436    }
437
438    /// Returns the amount of time the server allows TCP connections to remain idle before shutting them down.
439    ///
440    /// If `None`, defaults to 1 minute. If `Some`, the time will be included in HTTP responses in a `Keep-Alive`
441    /// header.
442    #[inline]
443    pub fn idle_connection_timeout(&self) -> Option<Duration> {
444        self.idle_connection_timeout
445    }
446}
447
448/// Minidump configuration.
449#[derive(Clone, PartialEq, Debug)]
450#[staged_builder]
451pub struct MinidumpConfig {
452    #[builder(default = true)]
453    enabled: bool,
454    #[builder(into, default = PathBuf::from("var/data/tmp"))]
455    socket_dir: PathBuf,
456}
457
458impl Default for MinidumpConfig {
459    #[inline]
460    fn default() -> Self {
461        MinidumpConfig::builder().build()
462    }
463}
464
465impl<'de> Deserialize<'de> for MinidumpConfig {
466    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
467    where
468        D: Deserializer<'de>,
469    {
470        let raw = de::MinidumpConfig::deserialize(deserializer)?;
471        let mut builder = MinidumpConfig::builder();
472        if let Some(enabled) = raw.enabled {
473            builder = builder.enabled(enabled);
474        }
475        if let Some(socket_dir) = raw.socket_dir {
476            builder = builder.socket_dir(socket_dir);
477        }
478        Ok(builder.build())
479    }
480}
481
482impl MinidumpConfig {
483    /// Determines if the server will spawn the minidump sidecar.
484    ///
485    /// The sidecar is required for the thread dump diagnostic and crash reports.
486    ///
487    /// Defaults to `true`.
488    #[inline]
489    pub fn enabled(&self) -> bool {
490        self.enabled
491    }
492
493    /// Returns the directory in which the minidump Unix socket will be placed.
494    ///
495    /// Defaults to `var/data/tmp`.
496    #[inline]
497    pub fn socket_dir(&self) -> &Path {
498        &self.socket_dir
499    }
500}