diff --git a/Cargo.lock b/Cargo.lock index b2a47390..c5f2fa2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,6 +432,8 @@ dependencies = [ "tracing-flame", "tracing-opentelemetry", "tracing-subscriber", + "trust-dns-resolver", + "url", ] [[package]] @@ -3124,6 +3126,7 @@ dependencies = [ "form_urlencoded", "idna 0.5.0", "percent-encoding", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 88383391..eb7463ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,6 +107,9 @@ futures-util = { version = "0.3.28", default-features = false } # Used for reading the configuration from conduit.toml & environment variables figment = { version = "0.10.8", features = ["env", "toml"] } +# Validating urls in config +url = { version = "2", features = ["serde"] } + tikv-jemallocator = { version = "0.5.0", features = ["unprefixed_malloc_on_supported_platforms"], optional = true } async-trait = "0.1.68" diff --git a/book.toml b/book.toml index e25746ca..700ecda5 100644 --- a/book.toml +++ b/book.toml @@ -16,3 +16,7 @@ git-repository-icon = "fa-git-square" [output.html.search] limit-results = 15 + +[output.html.code.hidelines] +json = "~" + diff --git a/conduit-example.toml b/conduit-example.toml index c83bce74..ef7bd182 100644 --- a/conduit-example.toml +++ b/conduit-example.toml @@ -17,7 +17,7 @@ # https://matrix.org/docs/spec/client_server/latest#get-well-known-matrix-client # and # https://matrix.org/docs/spec/server_server/r0.1.4#get-well-known-matrix-server -# for more information +# for more information, or continue below to see how conduit can do this for you. # YOU NEED TO EDIT THIS #server_name = "your.server.name" @@ -65,3 +65,10 @@ trusted_servers = ["matrix.org"] address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy #address = "0.0.0.0" # If Conduit is running in a container, make sure the reverse proxy (ie. Traefik) can reach it. + +[global.well_known] +# Conduit handles the /.well-known/matrix/* endpoints, making both clients and servers try to access conduit with the host +# server_name and port 443 by default. +# If you want to override these defaults, uncomment and edit the following lines accordingly: +#server = your.server.name:443 +#client = https://your.server.name diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index f874bb21..afba3cca 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -3,6 +3,7 @@ - [Introduction](introduction.md) - [Configuration](configuration.md) +- [Delegation](delegation.md) - [Deploying](deploying.md) - [Generic](deploying/generic.md) - [Debian](deploying/debian.md) diff --git a/docs/configuration.md b/docs/configuration.md index efa080dc..d903a21e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -56,6 +56,7 @@ The `global` section contains the following fields: | `turn_secret` | `string` | The TURN secret | `""` | | `turn_ttl` | `integer` | The TURN TTL in seconds | `86400` | | `emergency_password` | `string` | Set a password to login as the `conduit` user in case of emergency | N/A | +| `well_known` | `table` | Used for [delegation](delegation.md) | See [delegation](delegation.md) | ### TLS diff --git a/docs/delegation.md b/docs/delegation.md new file mode 100644 index 00000000..c8e5391c --- /dev/null +++ b/docs/delegation.md @@ -0,0 +1,69 @@ +# Delegation + +You can run Conduit on a separate domain than the actual server name (what shows up in user ids, aliases, etc.). +For example you can have your users have IDs such as `@foo:example.org` and have aliases like `#bar:example.org`, +while actually having Conduit hosted on the `matrix.example.org` domain. This is called delegation. + +## Automatic (recommended) + +Conduit has support for hosting delegation files by itself, and by default uses it to serve federation traffic on port 443. + +With this method, you need to direct requests to `/.well-known/matrix/*` to Conduit in your reverse proxy. + +This is only recommended if Conduit is on the same physical server as the server which serves your server name (e.g. example.org) +as servers don't always seem to cache the response, leading to slower response times otherwise, but it should also work if you +are connected to the server running Conduit using something like a VPN. + +> **Note**: this will automatically allow you to use [sliding sync][0] without any extra configuration + +To configure it, use the following options in the `global.well_known` table: +| Field | Type | Description | Default | +| --- | --- | --- | --- | +| `client` | `String` | The URL that clients should use to connect to Conduit | `https://` | +| `server` | `String` | The hostname and port servers should use to connect to Conduit | `:443` | + +### Example + +```toml +[global.well_known] +client = "https://matrix.example.org" +server = "matrix.example.org:443" +``` + +## Manual + +Alternatively you can serve static JSON files to inform clients and servers how to connect to Conduit. + +### Servers + +For servers to discover how to access your domain, serve a response in the following format for `/.well-known/matrix/server`: + +```json +{ + "m.server": "matrix.example.org:443" +} +``` +Where `matrix.example.org` is the domain and `443` is the port Conduit is accessible at. + +### Clients + +For clients to discover how to access your domain, serve a response in the following format for `/.well-known/matrix/client`: +```json +{ + "m.homeserver": { + "base_url": "https://matrix.example.org" + } +} +``` +Where `matrix.example.org` is the URL Conduit is accessible at. + +To ensure that all clients can access this endpoint, it is recommended you set the following headers for this endpoint: +``` +Access-Control-Allow-Origin: * +Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS +Access-Control-Allow-Headers: X-Requested-With, Content-Type, Authorization +``` + +If you also want to be able to use [sliding sync][0], look [here](faq.md#how-do-i-setup-sliding-sync). + +[0]: https://matrix.org/blog/2023/09/matrix-2-0/#sliding-sync diff --git a/docs/faq.md b/docs/faq.md index ce84f818..4c23a25c 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -15,12 +15,16 @@ You can simply stop Conduit, make a copy or file system snapshot of the database ## How do I setup sliding sync? -You need to add a `org.matrix.msc3575.proxy` field to your `.well-known/matrix/client` response which points to Conduit. Here is an example: +If you use the [automatic method for delegation](delegation.md#automatic-recommended) or just proxy `.well-known/matrix/client` to Conduit, sliding sync should work with no extra configuration. +If you don't, continue below. + +You need to add a `org.matrix.msc3575.proxy` field to your `.well-known/matrix/client` response which contains a url which Conduit is accessible behind. +Here is an example: ```json { - "m.homeserver": { - "base_url": "https://matrix.example.org" - }, +~ "m.homeserver": { +~ "base_url": "https://matrix.example.org" +~ }, "org.matrix.msc3575.proxy": { "url": "https://matrix.example.org" } diff --git a/src/api/client_server/mod.rs b/src/api/client_server/mod.rs index 54c99aa0..afe5181e 100644 --- a/src/api/client_server/mod.rs +++ b/src/api/client_server/mod.rs @@ -32,6 +32,7 @@ mod typing; mod unversioned; mod user_directory; mod voip; +mod well_known; pub use account::*; pub use alias::*; @@ -67,6 +68,7 @@ pub use typing::*; pub use unversioned::*; pub use user_directory::*; pub use voip::*; +pub use well_known::*; pub const DEVICE_ID_LENGTH: usize = 10; pub const TOKEN_LENGTH: usize = 32; diff --git a/src/api/client_server/unversioned.rs b/src/api/client_server/unversioned.rs index 70e260ec..7706afee 100644 --- a/src/api/client_server/unversioned.rs +++ b/src/api/client_server/unversioned.rs @@ -1,9 +1,8 @@ use std::{collections::BTreeMap, iter::FromIterator}; -use axum::{response::IntoResponse, Json}; -use ruma::api::client::{discovery::get_supported_versions, error::ErrorKind}; +use ruma::api::client::discovery::get_supported_versions; -use crate::{services, Error, Result, Ruma}; +use crate::{Result, Ruma}; /// # `GET /_matrix/client/versions` /// @@ -33,18 +32,3 @@ pub async fn get_supported_versions_route( Ok(resp) } - -/// # `GET /.well-known/matrix/client` -pub async fn well_known_client_route( - _body: Ruma, -) -> Result { - let client_url = match services().globals.well_known_client() { - Some(url) => url.clone(), - None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), - }; - - Ok(Json(serde_json::json!({ - "m.homeserver": {"base_url": client_url}, - "org.matrix.msc3575.proxy": {"url": client_url} - }))) -} diff --git a/src/api/client_server/well_known.rs b/src/api/client_server/well_known.rs new file mode 100644 index 00000000..e7bc2a4a --- /dev/null +++ b/src/api/client_server/well_known.rs @@ -0,0 +1,22 @@ +use ruma::api::client::discovery::discover_homeserver::{ + self, HomeserverInfo, SlidingSyncProxyInfo, +}; + +use crate::{services, Result, Ruma}; + +/// # `GET /.well-known/matrix/client` +/// +/// Returns the client server discovery information. +pub async fn well_known_client( + _body: Ruma, +) -> Result { + let client_url = services().globals.well_known_client(); + + Ok(discover_homeserver::Response { + homeserver: HomeserverInfo { + base_url: client_url.clone(), + }, + identity_server: None, + sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }), + }) +} diff --git a/src/api/server_server.rs b/src/api/server_server.rs index ef6ab4af..d816a3e9 100644 --- a/src/api/server_server.rs +++ b/src/api/server_server.rs @@ -17,7 +17,10 @@ use ruma::{ backfill::get_backfill, device::get_devices::{self, v1::UserDevice}, directory::{get_public_rooms, get_public_rooms_filtered}, - discovery::{get_server_keys, get_server_version, ServerSigningKeys, VerifyKey}, + discovery::{ + discover_homeserver, get_server_keys, get_server_version, ServerSigningKeys, + VerifyKey, + }, event::{get_event, get_missing_events, get_room_state, get_room_state_ids}, keys::{claim_keys, get_keys}, membership::{create_invite, create_join_event, prepare_join_event}, @@ -1911,6 +1914,17 @@ pub async fn claim_keys_route( }) } +/// # `GET /.well-known/matrix/server` +/// +/// Returns the federation server discovery information. +pub async fn well_known_server( + _body: Ruma, +) -> Result { + Ok(discover_homeserver::Response { + server: services().globals.well_known_server(), + }) +} + #[cfg(test)] mod tests { use super::{add_port_to_hostname, get_ip_with_port, FedDest}; diff --git a/src/config/mod.rs b/src/config/mod.rs index fb1e2f31..652b3a4c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,6 +7,7 @@ use std::{ use ruma::{OwnedServerName, RoomVersionId}; use serde::{de::IgnoredAny, Deserialize}; use tracing::warn; +use url::Url; mod proxy; @@ -56,7 +57,8 @@ pub struct Config { pub allow_unstable_room_versions: bool, #[serde(default = "default_default_room_version")] pub default_room_version: RoomVersionId, - pub well_known_client: Option, + #[serde(default)] + pub well_known: WellKnownConfig, #[serde(default = "false_fn")] pub allow_jaeger: bool, #[serde(default = "false_fn")] @@ -91,6 +93,12 @@ pub struct TlsConfig { pub key: String, } +#[derive(Clone, Debug, Deserialize, Default)] +pub struct WellKnownConfig { + pub client: Option, + pub server: Option, +} + const DEPRECATED_KEYS: &[&str] = &["cache_capacity"]; impl Config { @@ -111,9 +119,35 @@ impl Config { } } +impl Config { + pub fn well_known_client(&self) -> String { + if let Some(url) = &self.well_known.client { + url.to_string() + } else { + format!("https://{}", self.server_name) + } + } + + pub fn well_known_server(&self) -> OwnedServerName { + match &self.well_known.server { + Some(server_name) => server_name.to_owned(), + None => { + if self.server_name.port().is_some() { + self.server_name.to_owned() + } else { + format!("{}:443", self.server_name.host()) + .try_into() + .expect("Host from valid hostname + :443 must be valid") + } + } + } + } +} + impl fmt::Display for Config { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Prepare a list of config values to show + let well_known_server = self.well_known_server(); let lines = [ ("Server name", self.server_name.host()), ("Database backend", &self.database_backend), @@ -194,6 +228,8 @@ impl fmt::Display for Config { } &lst.join(", ") }), + ("Well-known server name", well_known_server.as_str()), + ("Well-known client URL", &self.well_known_client()), ]; let mut msg: String = "Active config values:\n\n".to_owned(); diff --git a/src/main.rs b/src/main.rs index 7beeb8ba..84467543 100644 --- a/src/main.rs +++ b/src/main.rs @@ -390,6 +390,7 @@ fn routes(config: &Config) -> Router { .ruma_route(client_server::get_relating_events_with_rel_type_route) .ruma_route(client_server::get_relating_events_route) .ruma_route(client_server::get_hierarchy_route) + .ruma_route(client_server::well_known_client) .route( "/_matrix/client/r0/rooms/:room_id/initialSync", get(initial_sync), @@ -430,10 +431,12 @@ fn routes(config: &Config) -> Router { .ruma_route(server_server::get_profile_information_route) .ruma_route(server_server::get_keys_route) .ruma_route(server_server::claim_keys_route) + .ruma_route(server_server::well_known_server) } else { router .route("/_matrix/federation/*path", any(federation_disabled)) .route("/_matrix/key/*path", any(federation_disabled)) + .route("/.well-known/matrix/server", any(federation_disabled)) } } diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index ab66ed45..263463d7 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -417,8 +417,12 @@ impl Service { r } - pub fn well_known_client(&self) -> &Option { - &self.config.well_known_client + pub fn well_known_server(&self) -> OwnedServerName { + self.config.well_known_server() + } + + pub fn well_known_client(&self) -> String { + self.config.well_known_client() } pub fn shutdown(&self) {