Merge branch '244-support-well-known' into 'next'

feat: add .well-known support

Closes #244 and #378

See merge request famedly/conduit!332
This commit is contained in:
Matthias Ahouansou 2024-05-02 09:35:14 +00:00
commit 0074aca0ef
15 changed files with 184 additions and 27 deletions

3
Cargo.lock generated
View file

@ -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]]

View file

@ -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"

View file

@ -16,3 +16,7 @@ git-repository-icon = "fa-git-square"
[output.html.search]
limit-results = 15
[output.html.code.hidelines]
json = "~"

View file

@ -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

View file

@ -3,6 +3,7 @@
- [Introduction](introduction.md)
- [Configuration](configuration.md)
- [Delegation](delegation.md)
- [Deploying](deploying.md)
- [Generic](deploying/generic.md)
- [Debian](deploying/debian.md)

View file

@ -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

69
docs/delegation.md Normal file
View file

@ -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_name>` |
| `server` | `String` | The hostname and port servers should use to connect to Conduit | `<server_name>: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

View file

@ -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"
}

View file

@ -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;

View file

@ -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<get_supported_versions::Request>,
) -> Result<impl IntoResponse> {
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}
})))
}

View file

@ -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<discover_homeserver::Request>,
) -> Result<discover_homeserver::Response> {
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 }),
})
}

View file

@ -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<discover_homeserver::Request>,
) -> Result<discover_homeserver::Response> {
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};

View file

@ -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<String>,
#[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<Url>,
pub server: Option<OwnedServerName>,
}
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();

View file

@ -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))
}
}

View file

@ -417,8 +417,12 @@ impl Service {
r
}
pub fn well_known_client(&self) -> &Option<String> {
&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) {