You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-06 06:02:40 +03:00
Flatten the http config
Also properly remove the `spa` resource
This commit is contained in:
@@ -48,7 +48,7 @@ use rustls::ServerConfig;
|
|||||||
use sentry_tower::{NewSentryLayer, SentryHttpLayer};
|
use sentry_tower::{NewSentryLayer, SentryHttpLayer};
|
||||||
use tower::Layer;
|
use tower::Layer;
|
||||||
use tower_http::{services::ServeDir, set_header::SetResponseHeaderLayer};
|
use tower_http::{services::ServeDir, set_header::SetResponseHeaderLayer};
|
||||||
use tracing::{warn, Span};
|
use tracing::Span;
|
||||||
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
||||||
|
|
||||||
use crate::app_state::AppState;
|
use crate::app_state::AppState;
|
||||||
@@ -243,12 +243,6 @@ where
|
|||||||
format!("{connection:?}")
|
format!("{connection:?}")
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
mas_config::HttpResource::Spa { .. } => {
|
|
||||||
warn!("The SPA HTTP resource is deprecated");
|
|
||||||
router
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,10 +25,9 @@ use rand::Rng;
|
|||||||
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use super::{secrets::PasswordOrFile, ConfigurationSection};
|
use super::ConfigurationSection;
|
||||||
|
|
||||||
fn default_public_base() -> Url {
|
fn default_public_base() -> Url {
|
||||||
"http://[::]:8080".parse().unwrap()
|
"http://[::]:8080".parse().unwrap()
|
||||||
@@ -99,7 +98,6 @@ impl UnixOrTcp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration of a single listener
|
/// Configuration of a single listener
|
||||||
#[skip_serializing_none]
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum BindConfig {
|
pub enum BindConfig {
|
||||||
@@ -108,7 +106,7 @@ pub enum BindConfig {
|
|||||||
/// Host on which to listen.
|
/// Host on which to listen.
|
||||||
///
|
///
|
||||||
/// Defaults to listening on all addresses
|
/// Defaults to listening on all addresses
|
||||||
#[serde(default)]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
host: Option<String>,
|
host: Option<String>,
|
||||||
|
|
||||||
/// Port on which to listen.
|
/// Port on which to listen.
|
||||||
@@ -153,37 +151,49 @@ pub enum BindConfig {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum KeyOrFile {
|
|
||||||
Key(String),
|
|
||||||
#[schemars(with = "String")]
|
|
||||||
KeyFile(Utf8PathBuf),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum CertificateOrFile {
|
|
||||||
Certificate(String),
|
|
||||||
#[schemars(with = "String")]
|
|
||||||
CertificateFile(Utf8PathBuf),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration related to TLS on a listener
|
/// Configuration related to TLS on a listener
|
||||||
#[skip_serializing_none]
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
||||||
pub struct TlsConfig {
|
pub struct TlsConfig {
|
||||||
/// PEM-encoded X509 certificate chain
|
/// PEM-encoded X509 certificate chain
|
||||||
#[serde(flatten)]
|
///
|
||||||
pub certificate: CertificateOrFile,
|
/// Exactly one of `certificate` or `certificate_file` must be set.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub certificate: Option<String>,
|
||||||
|
|
||||||
/// Private key
|
/// File containing the PEM-encoded X509 certificate chain
|
||||||
#[serde(flatten)]
|
///
|
||||||
pub key: KeyOrFile,
|
/// Exactly one of `certificate` or `certificate_file` must be set.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schemars(with = "Option<String>")]
|
||||||
|
pub certificate_file: Option<Utf8PathBuf>,
|
||||||
|
|
||||||
|
/// PEM-encoded private key
|
||||||
|
///
|
||||||
|
/// Exactly one of `key` or `key_file` must be set.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub key: Option<String>,
|
||||||
|
|
||||||
|
/// File containing a PEM or DER-encoded private key
|
||||||
|
///
|
||||||
|
/// Exactly one of `key` or `key_file` must be set.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schemars(with = "Option<String>")]
|
||||||
|
pub key_file: Option<Utf8PathBuf>,
|
||||||
|
|
||||||
/// Password used to decode the private key
|
/// Password used to decode the private key
|
||||||
#[serde(flatten)]
|
///
|
||||||
pub password: Option<PasswordOrFile>,
|
/// One of `password` or `password_file` must be set if the key is
|
||||||
|
/// encrypted.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub password: Option<String>,
|
||||||
|
|
||||||
|
/// Password file used to decode the private key
|
||||||
|
///
|
||||||
|
/// One of `password` or `password_file` must be set if the key is
|
||||||
|
/// encrypted.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schemars(with = "Option<String>")]
|
||||||
|
pub password_file: Option<Utf8PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TlsConfig {
|
impl TlsConfig {
|
||||||
@@ -201,17 +211,20 @@ impl TlsConfig {
|
|||||||
pub fn load(
|
pub fn load(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<(PrivateKeyDer<'static>, Vec<CertificateDer<'static>>), anyhow::Error> {
|
) -> Result<(PrivateKeyDer<'static>, Vec<CertificateDer<'static>>), anyhow::Error> {
|
||||||
let password = match &self.password {
|
let password = match (&self.password, &self.password_file) {
|
||||||
Some(PasswordOrFile::Password(password)) => Some(Cow::Borrowed(password.as_str())),
|
(None, None) => None,
|
||||||
Some(PasswordOrFile::PasswordFile(path)) => {
|
(Some(_), Some(_)) => {
|
||||||
Some(Cow::Owned(std::fs::read_to_string(path)?))
|
bail!("Only one of `password` or `password_file` can be set at a time")
|
||||||
}
|
}
|
||||||
None => None,
|
(Some(password), None) => Some(Cow::Borrowed(password)),
|
||||||
|
(None, Some(path)) => Some(Cow::Owned(std::fs::read_to_string(path)?)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read the key either embedded in the config file or on disk
|
// Read the key either embedded in the config file or on disk
|
||||||
let key = match &self.key {
|
let key = match (&self.key, &self.key_file) {
|
||||||
KeyOrFile::Key(key) => {
|
(None, None) => bail!("Either `key` or `key_file` must be set"),
|
||||||
|
(Some(_), Some(_)) => bail!("Only one of `key` or `key_file` can be set at a time"),
|
||||||
|
(Some(key), None) => {
|
||||||
// If the key was embedded in the config file, assume it is formatted as PEM
|
// If the key was embedded in the config file, assume it is formatted as PEM
|
||||||
if let Some(password) = password {
|
if let Some(password) = password {
|
||||||
PrivateKey::load_encrypted_pem(key, password.as_bytes())?
|
PrivateKey::load_encrypted_pem(key, password.as_bytes())?
|
||||||
@@ -219,7 +232,7 @@ impl TlsConfig {
|
|||||||
PrivateKey::load_pem(key)?
|
PrivateKey::load_pem(key)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyOrFile::KeyFile(path) => {
|
(None, Some(path)) => {
|
||||||
// When reading from disk, it might be either PEM or DER. `PrivateKey::load*`
|
// When reading from disk, it might be either PEM or DER. `PrivateKey::load*`
|
||||||
// will try both.
|
// will try both.
|
||||||
let key = std::fs::read(path)?;
|
let key = std::fs::read(path)?;
|
||||||
@@ -235,9 +248,13 @@ impl TlsConfig {
|
|||||||
let key = key.to_pkcs8_der()?;
|
let key = key.to_pkcs8_der()?;
|
||||||
let key = PrivatePkcs8KeyDer::from(key.to_vec()).into();
|
let key = PrivatePkcs8KeyDer::from(key.to_vec()).into();
|
||||||
|
|
||||||
let certificate_chain_pem = match &self.certificate {
|
let certificate_chain_pem = match (&self.certificate, &self.certificate_file) {
|
||||||
CertificateOrFile::Certificate(pem) => Cow::Borrowed(pem.as_str()),
|
(None, None) => bail!("Either `certificate` or `certificate_file` must be set"),
|
||||||
CertificateOrFile::CertificateFile(path) => Cow::Owned(std::fs::read_to_string(path)?),
|
(Some(_), Some(_)) => {
|
||||||
|
bail!("Only one of `certificate` or `certificate_file` can be set at a time")
|
||||||
|
}
|
||||||
|
(Some(certificate), None) => Cow::Borrowed(certificate),
|
||||||
|
(None, Some(path)) => Cow::Owned(std::fs::read_to_string(path)?),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut certificate_chain_reader = Cursor::new(certificate_chain_pem.as_bytes());
|
let mut certificate_chain_reader = Cursor::new(certificate_chain_pem.as_bytes());
|
||||||
@@ -254,7 +271,6 @@ impl TlsConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// HTTP resources to mount
|
/// HTTP resources to mount
|
||||||
#[skip_serializing_none]
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
||||||
#[serde(tag = "name", rename_all = "lowercase")]
|
#[serde(tag = "name", rename_all = "lowercase")]
|
||||||
pub enum Resource {
|
pub enum Resource {
|
||||||
@@ -295,28 +311,21 @@ pub enum Resource {
|
|||||||
/// the upstream connection
|
/// the upstream connection
|
||||||
#[serde(rename = "connection-info")]
|
#[serde(rename = "connection-info")]
|
||||||
ConnectionInfo,
|
ConnectionInfo,
|
||||||
|
|
||||||
/// Mount the single page app
|
|
||||||
///
|
|
||||||
/// This is deprecated and will be removed in a future release.
|
|
||||||
#[deprecated = "This resource is deprecated and will be removed in a future release"]
|
|
||||||
Spa,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration of a listener
|
/// Configuration of a listener
|
||||||
#[skip_serializing_none]
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
||||||
pub struct ListenerConfig {
|
pub struct ListenerConfig {
|
||||||
/// A unique name for this listener which will be shown in traces and in
|
/// A unique name for this listener which will be shown in traces and in
|
||||||
/// metrics labels
|
/// metrics labels
|
||||||
#[serde(default)]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
|
||||||
/// List of resources to mount
|
/// List of resources to mount
|
||||||
pub resources: Vec<Resource>,
|
pub resources: Vec<Resource>,
|
||||||
|
|
||||||
/// HTTP prefix to mount the resources on
|
/// HTTP prefix to mount the resources on
|
||||||
#[serde(default)]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub prefix: Option<String>,
|
pub prefix: Option<String>,
|
||||||
|
|
||||||
/// List of sockets to bind
|
/// List of sockets to bind
|
||||||
@@ -327,7 +336,7 @@ pub struct ListenerConfig {
|
|||||||
pub proxy_protocol: bool,
|
pub proxy_protocol: bool,
|
||||||
|
|
||||||
/// If set, makes the listener use TLS with the provided certificate and key
|
/// If set, makes the listener use TLS with the provided certificate and key
|
||||||
#[serde(default)]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub tls: Option<TlsConfig>,
|
pub tls: Option<TlsConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,6 +412,68 @@ impl ConfigurationSection for HttpConfig {
|
|||||||
Ok(Self::default())
|
Ok(Self::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
|
||||||
|
for (index, listener) in self.listeners.iter().enumerate() {
|
||||||
|
let annotate = |mut error: figment::Error| {
|
||||||
|
error.metadata = figment
|
||||||
|
.find_metadata(&format!("{root}.listeners", root = Self::PATH.unwrap()))
|
||||||
|
.cloned();
|
||||||
|
error.profile = Some(figment::Profile::Default);
|
||||||
|
error.path = vec![
|
||||||
|
Self::PATH.unwrap().to_owned(),
|
||||||
|
"listeners".to_owned(),
|
||||||
|
index.to_string(),
|
||||||
|
];
|
||||||
|
Err(error)
|
||||||
|
};
|
||||||
|
|
||||||
|
if listener.resources.is_empty() {
|
||||||
|
return annotate(figment::Error::from("listener has no resources".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if listener.binds.is_empty() {
|
||||||
|
return annotate(figment::Error::from(
|
||||||
|
"listener does not bind to any address".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tls_config) = &listener.tls {
|
||||||
|
if tls_config.certificate.is_some() && tls_config.certificate_file.is_some() {
|
||||||
|
return annotate(figment::Error::from(
|
||||||
|
"Only one of `certificate` or `certificate_file` can be set at a time"
|
||||||
|
.to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if tls_config.certificate.is_none() && tls_config.certificate_file.is_none() {
|
||||||
|
return annotate(figment::Error::from(
|
||||||
|
"TLS configuration is missing a certificate".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if tls_config.key.is_some() && tls_config.key_file.is_some() {
|
||||||
|
return annotate(figment::Error::from(
|
||||||
|
"Only one of `key` or `key_file` can be set at a time".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if tls_config.key.is_none() && tls_config.key_file.is_none() {
|
||||||
|
return annotate(figment::Error::from(
|
||||||
|
"TLS configuration is missing a private key".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if tls_config.password.is_some() && tls_config.password_file.is_some() {
|
||||||
|
return annotate(figment::Error::from(
|
||||||
|
"Only one of `password` or `password_file` can be set at a time".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn test() -> Self {
|
fn test() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
@@ -836,22 +836,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Mount the single page app\n\nThis is deprecated and will be removed in a future release.",
|
|
||||||
"deprecated": true,
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"spa"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -955,32 +939,32 @@
|
|||||||
"TlsConfig": {
|
"TlsConfig": {
|
||||||
"description": "Configuration related to TLS on a listener",
|
"description": "Configuration related to TLS on a listener",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"oneOf": [
|
"properties": {
|
||||||
{
|
"certificate": {
|
||||||
"type": "object",
|
"description": "PEM-encoded X509 certificate chain\n\nExactly one of `certificate` or `certificate_file` must be set.",
|
||||||
"required": [
|
"type": "string"
|
||||||
"certificate"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"certificate": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
{
|
"certificate_file": {
|
||||||
"type": "object",
|
"description": "File containing the PEM-encoded X509 certificate chain\n\nExactly one of `certificate` or `certificate_file` must be set.",
|
||||||
"required": [
|
"type": "string"
|
||||||
"certificate_file"
|
},
|
||||||
],
|
"key": {
|
||||||
"properties": {
|
"description": "PEM-encoded private key\n\nExactly one of `key` or `key_file` must be set.",
|
||||||
"certificate_file": {
|
"type": "string"
|
||||||
"type": "string"
|
},
|
||||||
}
|
"key_file": {
|
||||||
},
|
"description": "File containing a PEM or DER-encoded private key\n\nExactly one of `key` or `key_file` must be set.",
|
||||||
"additionalProperties": false
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"description": "Password used to decode the private key\n\nOne of `password` or `password_file` must be set if the key is encrypted.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password_file": {
|
||||||
|
"description": "Password file used to decode the private key\n\nOne of `password` or `password_file` must be set if the key is encrypted.",
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"IpNetwork": {
|
"IpNetwork": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
|
Reference in New Issue
Block a user