@@ -25,10 +25,9 @@ use rand::Rng;
25
25
use rustls_pki_types:: { CertificateDer , PrivateKeyDer , PrivatePkcs8KeyDer } ;
26
26
use schemars:: JsonSchema ;
27
27
use serde:: { Deserialize , Serialize } ;
28
- use serde_with:: skip_serializing_none;
29
28
use url:: Url ;
30
29
31
- use super :: { secrets :: PasswordOrFile , ConfigurationSection } ;
30
+ use super :: ConfigurationSection ;
32
31
33
32
fn default_public_base ( ) -> Url {
34
33
"http://[::]:8080" . parse ( ) . unwrap ( )
@@ -99,7 +98,6 @@ impl UnixOrTcp {
99
98
}
100
99
101
100
/// Configuration of a single listener
102
- #[ skip_serializing_none]
103
101
#[ derive( Debug , Serialize , Deserialize , JsonSchema , Clone ) ]
104
102
#[ serde( untagged) ]
105
103
pub enum BindConfig {
@@ -108,7 +106,7 @@ pub enum BindConfig {
108
106
/// Host on which to listen.
109
107
///
110
108
/// Defaults to listening on all addresses
111
- #[ serde( default ) ]
109
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
112
110
host : Option < String > ,
113
111
114
112
/// Port on which to listen.
@@ -153,37 +151,49 @@ pub enum BindConfig {
153
151
} ,
154
152
}
155
153
156
- #[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
157
- #[ serde( rename_all = "snake_case" ) ]
158
- pub enum KeyOrFile {
159
- Key ( String ) ,
160
- #[ schemars( with = "String" ) ]
161
- KeyFile ( Utf8PathBuf ) ,
162
- }
163
-
164
- #[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
165
- #[ serde( rename_all = "snake_case" ) ]
166
- pub enum CertificateOrFile {
167
- Certificate ( String ) ,
168
- #[ schemars( with = "String" ) ]
169
- CertificateFile ( Utf8PathBuf ) ,
170
- }
171
-
172
154
/// Configuration related to TLS on a listener
173
- #[ skip_serializing_none]
174
155
#[ derive( Debug , Serialize , Deserialize , JsonSchema , Clone ) ]
175
156
pub struct TlsConfig {
176
157
/// PEM-encoded X509 certificate chain
177
- #[ serde( flatten) ]
178
- pub certificate : CertificateOrFile ,
158
+ ///
159
+ /// Exactly one of `certificate` or `certificate_file` must be set.
160
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
161
+ pub certificate : Option < String > ,
179
162
180
- /// Private key
181
- #[ serde( flatten) ]
182
- pub key : KeyOrFile ,
163
+ /// File containing the PEM-encoded X509 certificate chain
164
+ ///
165
+ /// Exactly one of `certificate` or `certificate_file` must be set.
166
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
167
+ #[ schemars( with = "Option<String>" ) ]
168
+ pub certificate_file : Option < Utf8PathBuf > ,
169
+
170
+ /// PEM-encoded private key
171
+ ///
172
+ /// Exactly one of `key` or `key_file` must be set.
173
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
174
+ pub key : Option < String > ,
175
+
176
+ /// File containing a PEM or DER-encoded private key
177
+ ///
178
+ /// Exactly one of `key` or `key_file` must be set.
179
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
180
+ #[ schemars( with = "Option<String>" ) ]
181
+ pub key_file : Option < Utf8PathBuf > ,
183
182
184
183
/// Password used to decode the private key
185
- #[ serde( flatten) ]
186
- pub password : Option < PasswordOrFile > ,
184
+ ///
185
+ /// One of `password` or `password_file` must be set if the key is
186
+ /// encrypted.
187
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
188
+ pub password : Option < String > ,
189
+
190
+ /// Password file used to decode the private key
191
+ ///
192
+ /// One of `password` or `password_file` must be set if the key is
193
+ /// encrypted.
194
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
195
+ #[ schemars( with = "Option<String>" ) ]
196
+ pub password_file : Option < Utf8PathBuf > ,
187
197
}
188
198
189
199
impl TlsConfig {
@@ -201,25 +211,28 @@ impl TlsConfig {
201
211
pub fn load (
202
212
& self ,
203
213
) -> Result < ( PrivateKeyDer < ' static > , Vec < CertificateDer < ' static > > ) , anyhow:: Error > {
204
- let password = match & self . password {
205
- Some ( PasswordOrFile :: Password ( password ) ) => Some ( Cow :: Borrowed ( password . as_str ( ) ) ) ,
206
- Some ( PasswordOrFile :: PasswordFile ( path ) ) => {
207
- Some ( Cow :: Owned ( std :: fs :: read_to_string ( path ) ? ) )
214
+ let password = match ( & self . password , & self . password_file ) {
215
+ ( None , None ) => None ,
216
+ ( Some ( _ ) , Some ( _ ) ) => {
217
+ bail ! ( "Only one of `password` or `password_file` can be set at a time" )
208
218
}
209
- None => None ,
219
+ ( Some ( password) , None ) => Some ( Cow :: Borrowed ( password) ) ,
220
+ ( None , Some ( path) ) => Some ( Cow :: Owned ( std:: fs:: read_to_string ( path) ?) ) ,
210
221
} ;
211
222
212
223
// Read the key either embedded in the config file or on disk
213
- let key = match & self . key {
214
- KeyOrFile :: Key ( key) => {
224
+ let key = match ( & self . key , & self . key_file ) {
225
+ ( None , None ) => bail ! ( "Either `key` or `key_file` must be set" ) ,
226
+ ( Some ( _) , Some ( _) ) => bail ! ( "Only one of `key` or `key_file` can be set at a time" ) ,
227
+ ( Some ( key) , None ) => {
215
228
// If the key was embedded in the config file, assume it is formatted as PEM
216
229
if let Some ( password) = password {
217
230
PrivateKey :: load_encrypted_pem ( key, password. as_bytes ( ) ) ?
218
231
} else {
219
232
PrivateKey :: load_pem ( key) ?
220
233
}
221
234
}
222
- KeyOrFile :: KeyFile ( path) => {
235
+ ( None , Some ( path) ) => {
223
236
// When reading from disk, it might be either PEM or DER. `PrivateKey::load*`
224
237
// will try both.
225
238
let key = std:: fs:: read ( path) ?;
@@ -235,9 +248,13 @@ impl TlsConfig {
235
248
let key = key. to_pkcs8_der ( ) ?;
236
249
let key = PrivatePkcs8KeyDer :: from ( key. to_vec ( ) ) . into ( ) ;
237
250
238
- let certificate_chain_pem = match & self . certificate {
239
- CertificateOrFile :: Certificate ( pem) => Cow :: Borrowed ( pem. as_str ( ) ) ,
240
- CertificateOrFile :: CertificateFile ( path) => Cow :: Owned ( std:: fs:: read_to_string ( path) ?) ,
251
+ let certificate_chain_pem = match ( & self . certificate , & self . certificate_file ) {
252
+ ( None , None ) => bail ! ( "Either `certificate` or `certificate_file` must be set" ) ,
253
+ ( Some ( _) , Some ( _) ) => {
254
+ bail ! ( "Only one of `certificate` or `certificate_file` can be set at a time" )
255
+ }
256
+ ( Some ( certificate) , None ) => Cow :: Borrowed ( certificate) ,
257
+ ( None , Some ( path) ) => Cow :: Owned ( std:: fs:: read_to_string ( path) ?) ,
241
258
} ;
242
259
243
260
let mut certificate_chain_reader = Cursor :: new ( certificate_chain_pem. as_bytes ( ) ) ;
@@ -254,7 +271,6 @@ impl TlsConfig {
254
271
}
255
272
256
273
/// HTTP resources to mount
257
- #[ skip_serializing_none]
258
274
#[ derive( Debug , Serialize , Deserialize , JsonSchema , Clone ) ]
259
275
#[ serde( tag = "name" , rename_all = "lowercase" ) ]
260
276
pub enum Resource {
@@ -295,28 +311,21 @@ pub enum Resource {
295
311
/// the upstream connection
296
312
#[ serde( rename = "connection-info" ) ]
297
313
ConnectionInfo ,
298
-
299
- /// Mount the single page app
300
- ///
301
- /// This is deprecated and will be removed in a future release.
302
- #[ deprecated = "This resource is deprecated and will be removed in a future release" ]
303
- Spa ,
304
314
}
305
315
306
316
/// Configuration of a listener
307
- #[ skip_serializing_none]
308
317
#[ derive( Debug , Serialize , Deserialize , JsonSchema , Clone ) ]
309
318
pub struct ListenerConfig {
310
319
/// A unique name for this listener which will be shown in traces and in
311
320
/// metrics labels
312
- #[ serde( default ) ]
321
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
313
322
pub name : Option < String > ,
314
323
315
324
/// List of resources to mount
316
325
pub resources : Vec < Resource > ,
317
326
318
327
/// HTTP prefix to mount the resources on
319
- #[ serde( default ) ]
328
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
320
329
pub prefix : Option < String > ,
321
330
322
331
/// List of sockets to bind
@@ -327,7 +336,7 @@ pub struct ListenerConfig {
327
336
pub proxy_protocol : bool ,
328
337
329
338
/// If set, makes the listener use TLS with the provided certificate and key
330
- #[ serde( default ) ]
339
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
331
340
pub tls : Option < TlsConfig > ,
332
341
}
333
342
@@ -403,6 +412,68 @@ impl ConfigurationSection for HttpConfig {
403
412
Ok ( Self :: default ( ) )
404
413
}
405
414
415
+ fn validate ( & self , figment : & figment:: Figment ) -> Result < ( ) , figment:: Error > {
416
+ for ( index, listener) in self . listeners . iter ( ) . enumerate ( ) {
417
+ let annotate = |mut error : figment:: Error | {
418
+ error. metadata = figment
419
+ . find_metadata ( & format ! ( "{root}.listeners" , root = Self :: PATH . unwrap( ) ) )
420
+ . cloned ( ) ;
421
+ error. profile = Some ( figment:: Profile :: Default ) ;
422
+ error. path = vec ! [
423
+ Self :: PATH . unwrap( ) . to_owned( ) ,
424
+ "listeners" . to_owned( ) ,
425
+ index. to_string( ) ,
426
+ ] ;
427
+ Err ( error)
428
+ } ;
429
+
430
+ if listener. resources . is_empty ( ) {
431
+ return annotate ( figment:: Error :: from ( "listener has no resources" . to_owned ( ) ) ) ;
432
+ }
433
+
434
+ if listener. binds . is_empty ( ) {
435
+ return annotate ( figment:: Error :: from (
436
+ "listener does not bind to any address" . to_owned ( ) ,
437
+ ) ) ;
438
+ }
439
+
440
+ if let Some ( tls_config) = & listener. tls {
441
+ if tls_config. certificate . is_some ( ) && tls_config. certificate_file . is_some ( ) {
442
+ return annotate ( figment:: Error :: from (
443
+ "Only one of `certificate` or `certificate_file` can be set at a time"
444
+ . to_owned ( ) ,
445
+ ) ) ;
446
+ }
447
+
448
+ if tls_config. certificate . is_none ( ) && tls_config. certificate_file . is_none ( ) {
449
+ return annotate ( figment:: Error :: from (
450
+ "TLS configuration is missing a certificate" . to_owned ( ) ,
451
+ ) ) ;
452
+ }
453
+
454
+ if tls_config. key . is_some ( ) && tls_config. key_file . is_some ( ) {
455
+ return annotate ( figment:: Error :: from (
456
+ "Only one of `key` or `key_file` can be set at a time" . to_owned ( ) ,
457
+ ) ) ;
458
+ }
459
+
460
+ if tls_config. key . is_none ( ) && tls_config. key_file . is_none ( ) {
461
+ return annotate ( figment:: Error :: from (
462
+ "TLS configuration is missing a private key" . to_owned ( ) ,
463
+ ) ) ;
464
+ }
465
+
466
+ if tls_config. password . is_some ( ) && tls_config. password_file . is_some ( ) {
467
+ return annotate ( figment:: Error :: from (
468
+ "Only one of `password` or `password_file` can be set at a time" . to_owned ( ) ,
469
+ ) ) ;
470
+ }
471
+ }
472
+ }
473
+
474
+ Ok ( ( ) )
475
+ }
476
+
406
477
fn test ( ) -> Self {
407
478
Self :: default ( )
408
479
}
0 commit comments