rocket/config/
mod.rs

1//! Server and application configuration.
2//!
3//! See the [configuration guide] for full details.
4//!
5//! [configuration guide]: https://rocket.rs/v0.5/guide/configuration/
6//!
7//! ## Extracting Configuration Parameters
8//!
9//! Rocket exposes the active [`Figment`] via [`Rocket::figment()`]. Any value
10//! that implements [`Deserialize`] can be extracted from the figment:
11//!
12//! ```rust
13//! use rocket::fairing::AdHoc;
14//!
15//! #[derive(serde::Deserialize)]
16//! struct AppConfig {
17//!     id: Option<usize>,
18//!     port: u16,
19//! }
20//!
21//! #[rocket::launch]
22//! fn rocket() -> _ {
23//!     rocket::build().attach(AdHoc::config::<AppConfig>())
24//! }
25//! ```
26//!
27//! [`Figment`]: figment::Figment
28//! [`Rocket::figment()`]: crate::Rocket::figment()
29//! [`Rocket::figment()`]: crate::Rocket::figment()
30//! [`Deserialize`]: serde::Deserialize
31//!
32//! ## Workers
33//!
34//! The `workers` parameter sets the number of threads used for parallel task
35//! execution; there is no limit to the number of concurrent tasks. Due to a
36//! limitation in upstream async executers, unlike other values, the `workers`
37//! configuration value cannot be reconfigured or be configured from sources
38//! other than those provided by [`Config::figment()`]. In other words, only the
39//! values set by the `ROCKET_WORKERS` environment variable or in the `workers`
40//! property of `Rocket.toml` will be considered - all other `workers` values
41//! are ignored.
42//!
43//! ## Custom Providers
44//!
45//! A custom provider can be set via [`rocket::custom()`], which replaces calls to
46//! [`rocket::build()`]. The configured provider can be built on top of
47//! [`Config::figment()`], [`Config::default()`], both, or neither. The
48//! [Figment](figment) documentation has full details on instantiating existing
49//! providers like [`Toml`]() and [`Env`] as well as creating custom providers for
50//! more complex cases.
51//!
52//! Configuration values can be overridden at runtime by merging figment's tuple
53//! providers with Rocket's default provider:
54//!
55//! ```rust
56//! # #[macro_use] extern crate rocket;
57//! use rocket::data::{Limits, ToByteUnit};
58//!
59//! #[launch]
60//! fn rocket() -> _ {
61//!     let figment = rocket::Config::figment()
62//!         .merge(("port", 1111))
63//!         .merge(("limits", Limits::new().limit("json", 2.mebibytes())));
64//!
65//!     rocket::custom(figment).mount("/", routes![/* .. */])
66//! }
67//! ```
68//!
69//! An application that wants to use Rocket's defaults for [`Config`], but not
70//! its configuration sources, while allowing the application to be configured
71//! via an `App.toml` file that uses top-level keys as profiles (`.nested()`)
72//! and `APP_` environment variables as global overrides (`.global()`), and
73//! `APP_PROFILE` to configure the selected profile, can be structured as
74//! follows:
75//!
76//! ```rust
77//! # #[macro_use] extern crate rocket;
78//! use serde::{Serialize, Deserialize};
79//! use figment::{Figment, Profile, providers::{Format, Toml, Serialized, Env}};
80//! use rocket::fairing::AdHoc;
81//!
82//! #[derive(Debug, Deserialize, Serialize)]
83//! struct Config {
84//!     app_value: usize,
85//!     /* and so on.. */
86//! }
87//!
88//! impl Default for Config {
89//!     fn default() -> Config {
90//!         Config { app_value: 3, }
91//!     }
92//! }
93//!
94//! #[launch]
95//! fn rocket() -> _ {
96//!     let figment = Figment::from(rocket::Config::default())
97//!         .merge(Serialized::defaults(Config::default()))
98//!         .merge(Toml::file("App.toml").nested())
99//!         .merge(Env::prefixed("APP_").global())
100//!         .select(Profile::from_env_or("APP_PROFILE", "default"));
101//!
102//!     rocket::custom(figment)
103//!         .mount("/", routes![/* .. */])
104//!         .attach(AdHoc::config::<Config>())
105//! }
106//! ```
107//!
108//! [`rocket::custom()`]: crate::custom()
109//! [`rocket::build()`]: crate::build()
110//! [`Toml`]: figment::providers::Toml
111//! [`Env`]: figment::providers::Env
112
113#[macro_use]
114mod ident;
115mod config;
116mod shutdown;
117mod ip_header;
118
119#[cfg(feature = "tls")]
120mod tls;
121
122#[cfg(feature = "secrets")]
123mod secret_key;
124
125#[doc(hidden)]
126pub use config::{pretty_print_error, bail_with_config_error};
127
128pub use config::Config;
129pub use crate::log::LogLevel;
130pub use shutdown::Shutdown;
131pub use ident::Ident;
132
133#[cfg(feature = "tls")]
134pub use tls::{TlsConfig, CipherSuite};
135
136#[cfg(feature = "mtls")]
137pub use tls::MutualTls;
138
139#[cfg(feature = "secrets")]
140pub use secret_key::SecretKey;
141
142#[cfg(unix)]
143pub use shutdown::Sig;
144
145#[cfg(test)]
146mod tests {
147    use std::net::Ipv4Addr;
148    use figment::{Figment, Profile};
149    use pretty_assertions::assert_eq;
150
151    use crate::log::LogLevel;
152    use crate::data::{Limits, ToByteUnit};
153    use crate::config::Config;
154
155    #[test]
156    fn test_figment_is_default() {
157        figment::Jail::expect_with(|_| {
158            let mut default: Config = Config::figment().extract().unwrap();
159            default.profile = Config::default().profile;
160            assert_eq!(default, Config::default());
161            Ok(())
162        });
163    }
164
165    #[test]
166    fn test_default_round_trip() {
167        figment::Jail::expect_with(|_| {
168            let original = Config::figment();
169            let roundtrip = Figment::from(Config::from(&original));
170            for figment in &[original, roundtrip] {
171                let config = Config::from(figment);
172                assert_eq!(config, Config::default());
173            }
174
175            Ok(())
176        });
177    }
178
179    #[test]
180    fn test_profile_env() {
181        figment::Jail::expect_with(|jail| {
182            jail.set_env("ROCKET_PROFILE", "debug");
183            let figment = Config::figment();
184            assert_eq!(figment.profile(), "debug");
185
186            jail.set_env("ROCKET_PROFILE", "release");
187            let figment = Config::figment();
188            assert_eq!(figment.profile(), "release");
189
190            jail.set_env("ROCKET_PROFILE", "random");
191            let figment = Config::figment();
192            assert_eq!(figment.profile(), "random");
193
194            Ok(())
195        });
196    }
197
198    #[test]
199    fn test_toml_file() {
200        figment::Jail::expect_with(|jail| {
201            jail.create_file("Rocket.toml", r#"
202                [default]
203                address = "1.2.3.4"
204                ident = "Something Cool"
205                port = 1234
206                workers = 20
207                keep_alive = 10
208                log_level = "off"
209                cli_colors = 0
210            "#)?;
211
212            let config = Config::from(Config::figment());
213            assert_eq!(config, Config {
214                address: Ipv4Addr::new(1, 2, 3, 4).into(),
215                port: 1234,
216                workers: 20,
217                ident: ident!("Something Cool"),
218                keep_alive: 10,
219                log_level: LogLevel::Off,
220                cli_colors: false,
221                ..Config::default()
222            });
223
224            jail.create_file("Rocket.toml", r#"
225                [global]
226                address = "1.2.3.4"
227                ident = "Something Else Cool"
228                port = 1234
229                workers = 20
230                keep_alive = 10
231                log_level = "off"
232                cli_colors = 0
233            "#)?;
234
235            let config = Config::from(Config::figment());
236            assert_eq!(config, Config {
237                address: Ipv4Addr::new(1, 2, 3, 4).into(),
238                port: 1234,
239                workers: 20,
240                ident: ident!("Something Else Cool"),
241                keep_alive: 10,
242                log_level: LogLevel::Off,
243                cli_colors: false,
244                ..Config::default()
245            });
246
247            jail.set_env("ROCKET_CONFIG", "Other.toml");
248            jail.create_file("Other.toml", r#"
249                [default]
250                address = "1.2.3.4"
251                port = 1234
252                workers = 20
253                keep_alive = 10
254                log_level = "off"
255                cli_colors = 0
256            "#)?;
257
258            let config = Config::from(Config::figment());
259            assert_eq!(config, Config {
260                address: Ipv4Addr::new(1, 2, 3, 4).into(),
261                port: 1234,
262                workers: 20,
263                keep_alive: 10,
264                log_level: LogLevel::Off,
265                cli_colors: false,
266                ..Config::default()
267            });
268
269            Ok(())
270        });
271    }
272
273    #[test]
274    #[cfg(feature = "tls")]
275    fn test_tls_config_from_file() {
276        use crate::config::{TlsConfig, CipherSuite, Ident, Shutdown};
277
278        figment::Jail::expect_with(|jail| {
279            jail.create_file("Rocket.toml", r#"
280                [global]
281                shutdown.ctrlc = 0
282                ident = false
283
284                [global.tls]
285                certs = "/ssl/cert.pem"
286                key = "/ssl/key.pem"
287
288                [global.limits]
289                forms = "1mib"
290                json = "10mib"
291                stream = "50kib"
292            "#)?;
293
294            let config = Config::from(Config::figment());
295            assert_eq!(config, Config {
296                shutdown: Shutdown { ctrlc: false, ..Default::default() },
297                ident: Ident::none(),
298                tls: Some(TlsConfig::from_paths("/ssl/cert.pem", "/ssl/key.pem")),
299                limits: Limits::default()
300                    .limit("forms", 1.mebibytes())
301                    .limit("json", 10.mebibytes())
302                    .limit("stream", 50.kibibytes()),
303                ..Config::default()
304            });
305
306            jail.create_file("Rocket.toml", r#"
307                [global.tls]
308                certs = "cert.pem"
309                key = "key.pem"
310            "#)?;
311
312            let config = Config::from(Config::figment());
313            assert_eq!(config, Config {
314                tls: Some(TlsConfig::from_paths(
315                    jail.directory().join("cert.pem"),
316                    jail.directory().join("key.pem")
317                )),
318                ..Config::default()
319            });
320
321            jail.create_file("Rocket.toml", r#"
322                [global.tls]
323                certs = "cert.pem"
324                key = "key.pem"
325                prefer_server_cipher_order = true
326                ciphers = [
327                    "TLS_CHACHA20_POLY1305_SHA256",
328                    "TLS_AES_256_GCM_SHA384",
329                    "TLS_AES_128_GCM_SHA256",
330                    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
331                    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
332                    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
333                ]
334            "#)?;
335
336            let config = Config::from(Config::figment());
337            let cert_path = jail.directory().join("cert.pem");
338            let key_path = jail.directory().join("key.pem");
339            assert_eq!(config, Config {
340                tls: Some(TlsConfig::from_paths(cert_path, key_path)
341                         .with_preferred_server_cipher_order(true)
342                         .with_ciphers([
343                             CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
344                             CipherSuite::TLS_AES_256_GCM_SHA384,
345                             CipherSuite::TLS_AES_128_GCM_SHA256,
346                             CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
347                             CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
348                             CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
349                         ])),
350                ..Config::default()
351            });
352
353            jail.create_file("Rocket.toml", r#"
354                [global]
355                shutdown.ctrlc = 0
356                ident = false
357
358                [global.tls]
359                certs = "/ssl/cert.pem"
360                key = "/ssl/key.pem"
361
362                [global.limits]
363                forms = "1mib"
364                json = "10mib"
365                stream = "50kib"
366            "#)?;
367
368            let config = Config::from(Config::figment());
369            assert_eq!(config, Config {
370                shutdown: Shutdown { ctrlc: false, ..Default::default() },
371                ident: Ident::none(),
372                tls: Some(TlsConfig::from_paths("/ssl/cert.pem", "/ssl/key.pem")),
373                limits: Limits::default()
374                    .limit("forms", 1.mebibytes())
375                    .limit("json", 10.mebibytes())
376                    .limit("stream", 50.kibibytes()),
377                ..Config::default()
378            });
379
380            jail.create_file("Rocket.toml", r#"
381                [global.tls]
382                certs = "cert.pem"
383                key = "key.pem"
384            "#)?;
385
386            let config = Config::from(Config::figment());
387            assert_eq!(config, Config {
388                tls: Some(TlsConfig::from_paths(
389                    jail.directory().join("cert.pem"),
390                    jail.directory().join("key.pem")
391                )),
392                ..Config::default()
393            });
394
395            jail.create_file("Rocket.toml", r#"
396                [global.tls]
397                certs = "cert.pem"
398                key = "key.pem"
399                prefer_server_cipher_order = true
400                ciphers = [
401                    "TLS_CHACHA20_POLY1305_SHA256",
402                    "TLS_AES_256_GCM_SHA384",
403                    "TLS_AES_128_GCM_SHA256",
404                    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
405                    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
406                    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
407                ]
408            "#)?;
409
410            let config = Config::from(Config::figment());
411            let cert_path = jail.directory().join("cert.pem");
412            let key_path = jail.directory().join("key.pem");
413            assert_eq!(config, Config {
414                tls: Some(TlsConfig::from_paths(cert_path, key_path)
415                         .with_preferred_server_cipher_order(true)
416                         .with_ciphers([
417                             CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
418                             CipherSuite::TLS_AES_256_GCM_SHA384,
419                             CipherSuite::TLS_AES_128_GCM_SHA256,
420                             CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
421                             CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
422                             CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
423                         ])),
424                ..Config::default()
425            });
426
427            Ok(())
428        });
429    }
430
431    #[test]
432    #[cfg(feature = "mtls")]
433    fn test_mtls_config() {
434        use std::path::Path;
435
436        figment::Jail::expect_with(|jail| {
437            jail.create_file("Rocket.toml", r#"
438                [default.tls]
439                certs = "/ssl/cert.pem"
440                key = "/ssl/key.pem"
441            "#)?;
442
443            let config = Config::from(Config::figment());
444            assert!(config.tls.is_some());
445            assert!(config.tls.as_ref().unwrap().mutual.is_none());
446            assert!(config.tls_enabled());
447            assert!(!config.mtls_enabled());
448
449            jail.create_file("Rocket.toml", r#"
450                [default.tls]
451                certs = "/ssl/cert.pem"
452                key = "/ssl/key.pem"
453                mutual = { ca_certs = "/ssl/ca.pem" }
454            "#)?;
455
456            let config = Config::from(Config::figment());
457            assert!(config.tls_enabled());
458            assert!(config.mtls_enabled());
459
460            let mtls = config.tls.as_ref().unwrap().mutual.as_ref().unwrap();
461            assert_eq!(mtls.ca_certs().unwrap_left(), Path::new("/ssl/ca.pem"));
462            assert!(!mtls.mandatory);
463
464            jail.create_file("Rocket.toml", r#"
465                [default.tls]
466                certs = "/ssl/cert.pem"
467                key = "/ssl/key.pem"
468
469                [default.tls.mutual]
470                ca_certs = "/ssl/ca.pem"
471                mandatory = true
472            "#)?;
473
474            let config = Config::from(Config::figment());
475            let mtls = config.tls.as_ref().unwrap().mutual.as_ref().unwrap();
476            assert_eq!(mtls.ca_certs().unwrap_left(), Path::new("/ssl/ca.pem"));
477            assert!(mtls.mandatory);
478
479            jail.create_file("Rocket.toml", r#"
480                [default.tls]
481                certs = "/ssl/cert.pem"
482                key = "/ssl/key.pem"
483                mutual = { ca_certs = "relative/ca.pem" }
484            "#)?;
485
486            let config = Config::from(Config::figment());
487            let mtls = config.tls.as_ref().unwrap().mutual().unwrap();
488            assert_eq!(mtls.ca_certs().unwrap_left(),
489                jail.directory().join("relative/ca.pem"));
490
491            Ok(())
492        });
493    }
494
495    #[test]
496    fn test_profiles_merge() {
497        figment::Jail::expect_with(|jail| {
498            jail.create_file("Rocket.toml", r#"
499                [default.limits]
500                stream = "50kb"
501
502                [global]
503                limits = { forms = "2kb" }
504
505                [debug.limits]
506                file = "100kb"
507            "#)?;
508
509            jail.set_env("ROCKET_PROFILE", "unknown");
510            let config = Config::from(Config::figment());
511            assert_eq!(config, Config {
512                profile: Profile::const_new("unknown"),
513                limits: Limits::default()
514                    .limit("stream", 50.kilobytes())
515                    .limit("forms", 2.kilobytes()),
516                ..Config::default()
517            });
518
519            jail.set_env("ROCKET_PROFILE", "debug");
520            let config = Config::from(Config::figment());
521            assert_eq!(config, Config {
522                profile: Profile::const_new("debug"),
523                limits: Limits::default()
524                    .limit("stream", 50.kilobytes())
525                    .limit("forms", 2.kilobytes())
526                    .limit("file", 100.kilobytes()),
527                ..Config::default()
528            });
529
530            Ok(())
531        });
532    }
533
534    #[test]
535    #[cfg(feature = "tls")]
536    fn test_env_vars_merge() {
537        use crate::config::{TlsConfig, Ident};
538
539        figment::Jail::expect_with(|jail| {
540            jail.set_env("ROCKET_PORT", 9999);
541            let config = Config::from(Config::figment());
542            assert_eq!(config, Config {
543                port: 9999,
544                ..Config::default()
545            });
546
547            jail.set_env("ROCKET_TLS", r#"{certs="certs.pem"}"#);
548            let first_figment = Config::figment();
549            jail.set_env("ROCKET_TLS", r#"{key="key.pem"}"#);
550            let prev_figment = Config::figment().join(&first_figment);
551            let config = Config::from(&prev_figment);
552            assert_eq!(config, Config {
553                port: 9999,
554                tls: Some(TlsConfig::from_paths("certs.pem", "key.pem")),
555                ..Config::default()
556            });
557
558            jail.set_env("ROCKET_TLS", r#"{certs="new.pem"}"#);
559            let config = Config::from(Config::figment().join(&prev_figment));
560            assert_eq!(config, Config {
561                port: 9999,
562                tls: Some(TlsConfig::from_paths("new.pem", "key.pem")),
563                ..Config::default()
564            });
565
566            jail.set_env("ROCKET_LIMITS", r#"{stream=100kiB}"#);
567            let config = Config::from(Config::figment().join(&prev_figment));
568            assert_eq!(config, Config {
569                port: 9999,
570                tls: Some(TlsConfig::from_paths("new.pem", "key.pem")),
571                limits: Limits::default().limit("stream", 100.kibibytes()),
572                ..Config::default()
573            });
574
575            jail.set_env("ROCKET_IDENT", false);
576            let config = Config::from(Config::figment().join(&prev_figment));
577            assert_eq!(config, Config {
578                port: 9999,
579                tls: Some(TlsConfig::from_paths("new.pem", "key.pem")),
580                limits: Limits::default().limit("stream", 100.kibibytes()),
581                ident: Ident::none(),
582                ..Config::default()
583            });
584
585            Ok(())
586        });
587    }
588
589    #[test]
590    fn test_precedence() {
591        figment::Jail::expect_with(|jail| {
592            jail.create_file("Rocket.toml", r#"
593                [global.limits]
594                forms = "1mib"
595                stream = "50kb"
596                file = "100kb"
597            "#)?;
598
599            let config = Config::from(Config::figment());
600            assert_eq!(config, Config {
601                limits: Limits::default()
602                    .limit("forms", 1.mebibytes())
603                    .limit("stream", 50.kilobytes())
604                    .limit("file", 100.kilobytes()),
605                ..Config::default()
606            });
607
608            jail.set_env("ROCKET_LIMITS", r#"{stream=3MiB,capture=2MiB}"#);
609            let config = Config::from(Config::figment());
610            assert_eq!(config, Config {
611                limits: Limits::default()
612                    .limit("file", 100.kilobytes())
613                    .limit("forms", 1.mebibytes())
614                    .limit("stream", 3.mebibytes())
615                    .limit("capture", 2.mebibytes()),
616                ..Config::default()
617            });
618
619            jail.set_env("ROCKET_PROFILE", "foo");
620            let val: Result<String, _> = Config::figment().extract_inner("profile");
621            assert!(val.is_err());
622
623            Ok(())
624        });
625    }
626
627    #[test]
628    #[cfg(feature = "secrets")]
629    #[should_panic]
630    fn test_err_on_non_debug_and_no_secret_key() {
631        figment::Jail::expect_with(|jail| {
632            jail.set_env("ROCKET_PROFILE", "release");
633            let rocket = crate::custom(Config::figment());
634            let _result = crate::local::blocking::Client::untracked(rocket);
635            Ok(())
636        });
637    }
638
639    #[test]
640    #[cfg(feature = "secrets")]
641    #[should_panic]
642    fn test_err_on_non_debug2_and_no_secret_key() {
643        figment::Jail::expect_with(|jail| {
644            jail.set_env("ROCKET_PROFILE", "boop");
645            let rocket = crate::custom(Config::figment());
646            let _result = crate::local::blocking::Client::tracked(rocket);
647            Ok(())
648        });
649    }
650
651    #[test]
652    fn test_no_err_on_debug_and_no_secret_key() {
653        figment::Jail::expect_with(|jail| {
654            jail.set_env("ROCKET_PROFILE", "debug");
655            let figment = Config::figment();
656            assert!(crate::local::blocking::Client::untracked(crate::custom(&figment)).is_ok());
657            crate::async_main(async {
658                let rocket = crate::custom(&figment);
659                assert!(crate::local::asynchronous::Client::tracked(rocket).await.is_ok());
660            });
661
662            Ok(())
663        });
664    }
665
666    #[test]
667    fn test_no_err_on_release_and_custom_secret_key() {
668        figment::Jail::expect_with(|jail| {
669            jail.set_env("ROCKET_PROFILE", "release");
670            let key = "Bx4Gb+aSIfuoEyMHD4DvNs92+wmzfQK98qc6MiwyPY4=";
671            let figment = Config::figment().merge(("secret_key", key));
672
673            assert!(crate::local::blocking::Client::tracked(crate::custom(&figment)).is_ok());
674            crate::async_main(async {
675                let rocket = crate::custom(&figment);
676                assert!(crate::local::asynchronous::Client::untracked(rocket).await.is_ok());
677            });
678
679            Ok(())
680        });
681    }
682}