From e07513a04a63c0cfe1898b7e3204acbce45e428c Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Wed, 12 May 2021 00:51:53 -0400 Subject: [PATCH] Document new QUIC and outgoing support --- Cargo.toml | 4 +-- README.md | 93 ++++++++++++++++++++++++++++++++++++++++++++----- src/main.rs | 4 +-- src/outgoing.rs | 12 +++---- src/quic.rs | 6 ++-- src/srv.rs | 2 +- xmpp-proxy.toml | 8 +++-- 7 files changed, 103 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28467bb..8018a83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "xmpp-proxy" version = "1.0.0" authors = ["moparisthebest "] -description = "Reverse XMPP proxy." +description = "XMPP reverse proxy and outgoing proxy" repository = "https://code.moparisthebest.com/moparisthebest/xmpp-proxy" keywords = ["xmpp", "proxy"] @@ -49,5 +49,5 @@ default = ["incoming", "outgoing", "quic"] #default = ["outgoing"] #default = ["incoming"] incoming = ["tokio-rustls"] -outgoing = ["trust-dns-resolver", "webpki-roots", "lazy_static", "tokio-rustls"] +outgoing = ["tokio-rustls", "trust-dns-resolver", "webpki-roots", "lazy_static"] quic = ["quinn"] diff --git a/README.md b/README.md index 6e0801c..61f56c6 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,27 @@ [![Build Status](https://ci.moparisthe.best/job/moparisthebest/job/xmpp-proxy/job/master/badge/icon%3Fstyle=plastic)](https://ci.moparisthe.best/job/moparisthebest/job/xmpp-proxy/job/master/) -xmpp-proxy is a reverse proxy for XMPP servers, providing STARTTLS and TLS over plain-text XMPP connections -and limiting stanza sizes without an XML parser. +xmpp-proxy is a reverse proxy and outgoing proxy for XMPP servers and clients, providing STARTTLS, +[Direct TLS](https://xmpp.org/extensions/xep-0368.html), and [QUIC](https://datatracker.ietf.org/doc/html/draft-ietf-quic-transport) +connectivity to plain-text XMPP servers and clients and limiting stanza sizes without an XML parser. -xmpp-proxy will listen on any number of interfaces/ports and accept any STARTTLS or [Direct TLS](https://xmpp.org/extensions/xep-0368.html) -c2s or s2s connections, terminate TLS, and connect them to a real XMPP server, limiting stanza sizes as configured. +xmpp-proxy in reverse proxy (incoming) mode will: + 1. listen on any number of interfaces/ports + 2. accept any STARTTLS, Direct TLS, or QUIC c2s or s2s connections from the internet + 3. terminate TLS + 4. connect them to a local real XMPP server over plain-text TCP + 5. send the [PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) v1 header if configured, so the + XMPP server knows the real client IP + 6. limit incoming stanza sizes as configured + +xmpp-proxy in outgoing mode will: + 1. listen on any number of interfaces/ports + 2. accept any plain-text TCP connection from a local XMPP server or client + 3. look up the required SRV records + 4. connect to a real XMPP server across the internet over STARTTLS, Direct TLS, or QUIC + 5. fallback to next SRV target or defaults as required to fully connect + 6. perform all the proper required certificate validation logic + 7. limit incoming stanza sizes as configured #### Installation * `cargo install xmpp-proxy` @@ -25,10 +41,59 @@ c2s or s2s connections, terminate TLS, and connect them to a real XMPP server, l #### How do I adapt my running Prosody config to use this instead? -Add these to modules_enabled: +You have 2 options here, use xmpp-proxy as only a reverse proxy, or as both reverse and outgoing proxy, I'll cover both: + +###### Reverse proxy and outgoing proxy + +In this mode both prosody doesn't need to do any TLS at all, so it needs no certs. xmpp-proxy need proper TLS +certificates, move prosody's TLS key to `/etc/xmpp-proxy/le.key` and TLS cert to `/etc/xmpp-proxy/fullchain.cer`, and +use the provided `xmpp-proxy.toml` configuration as-is. + +Edit `/etc/prosody/prosody.cfg.lua`, Add these to modules_enabled: ``` -"secure_interfaces"; "net_proxy"; +"s2s_outgoing_proxy"; +``` +Until prosody-modules is updated, use my new module [mod_s2s_outgoing_proxy.lua](https://www.moparisthebest.com/mod_s2s_outgoing_proxy.lua). + +Add this config: +``` +-- only need to listen on localhost +interfaces = { "127.0.0.1" } + +-- we don't need prosody doing any encryption, xmpp-proxy does this now +-- these are likely set to true somewhere in your file, find them, make them false +-- you can also remove all certificates from your config +c2s_require_encryption = false +s2s_require_encryption = false +s2s_secure_auth = false + +-- xmpp-proxy outgoing is listening on this port, make all outgoing s2s connections directly to here +s2s_outgoing_proxy = { "127.0.0.1", 15270 } + +-- handle PROXY protocol on these ports +proxy_port_mappings = { + [15222] = "c2s", + [15269] = "s2s" +} + +-- don't listen on any normal c2s/s2s ports (xmpp-proxy listens on these now) +-- you might need to comment these out further down in your config file if you set them +c2s_ports = {} +legacy_ssl_ports = {} +-- you MUST have at least one s2s_ports defined if you want outgoing S2S to work, don't ask.. +s2s_ports = {15268} +``` + +###### Reverse proxy only, prosody makes outgoing connections directly itself + +In this mode both prosody and xmpp-proxy need proper TLS certificates, copy prosody's TLS key to `/etc/xmpp-proxy/le.key` +and TLS cert to `/etc/xmpp-proxy/fullchain.cer`, and use the provided `xmpp-proxy.toml` configuration as-is. + +Edit `/etc/prosody/prosody.cfg.lua`, Add these to modules_enabled: +``` +"net_proxy"; +"secure_interfaces"; ``` Until prosody-modules is updated, use my patched version of [mod_secure_interfaces.lua](https://www.moparisthebest.com/mod_secure_interfaces.lua) which also works for s2s. @@ -49,11 +114,21 @@ proxy_port_mappings = { c2s_ports = {} legacy_ssl_ports = {} -- you MUST have at least one s2s_ports defined if you want outgoing S2S to work, don't ask.. -s2s_ports = {15269} +s2s_ports = {15268} ``` -Copy prosody's TLS key to `/etc/xmpp-proxy/le.key` and TLS cert to `/etc/xmpp-proxy/fullchain.cer`, and use the provided -`xmpp-proxy.toml` configuration as-is. +#### Customize the build + +If you are a grumpy power user who wants to build xmpp-proxy with exactly the features you want, nothing less, nothing +more, this section is for you! + +xmpp-proxy has 3 compile-time features: + 1. `incoming` - enables `incoming_listen` config option for reverse proxy STARTTLS/TLS + 2. `outgoing` - enables `outgoing_listen` config option for outgoing proxy STARTTLS/TLS + 3. `quic` - enables `quic_listen` config option for reverse proxy QUIC, and QUIC support for `outgoing` if it is enabled + +So to build only supporting reverse proxy STARTTLS/TLS, no QUIC, run: `cargo build --release --no-default-features --features incoming` +To build a reverse proxy only, but supporting all of STARTTLS/TLS/QUIC, run: `cargo build --release --no-default-features --features incoming,quic` #### License GNU/AGPLv3 - Check LICENSE.md for details diff --git a/src/main.rs b/src/main.rs index 071955e..9274744 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,7 +79,7 @@ macro_rules! debug { struct Config { tls_key: String, tls_cert: String, - listen: Option>, + incoming_listen: Option>, quic_listen: Option>, outgoing_listen: Option>, max_stanza_size_bytes: usize, @@ -272,7 +272,7 @@ async fn main() { let mut handles: Vec>> = Vec::new(); #[cfg(feature = "incoming")] - if let Some(ref listeners) = main_config.listen { + if let Some(ref listeners) = main_config.incoming_listen { let acceptor = main_config.tls_acceptor().die("invalid cert/key ?"); for listener in listeners { handles.push(spawn_tls_listener(listener.parse().die("invalid listener address"), config.clone(), acceptor.clone())); diff --git a/src/outgoing.rs b/src/outgoing.rs index 3448116..b435068 100644 --- a/src/outgoing.rs +++ b/src/outgoing.rs @@ -1,7 +1,7 @@ use crate::*; async fn handle_outgoing_connection(stream: tokio::net::TcpStream, client_addr: SocketAddr, max_stanza_size_bytes: usize) -> Result<()> { - println!("INFO: outgoing {} connected", client_addr); + println!("INFO: out {} connected", client_addr); let in_filter = StanzaFilter::new(max_stanza_size_bytes); @@ -18,9 +18,9 @@ async fn handle_outgoing_connection(stream: tokio::net::TcpStream, client_addr: // todo: unsure how legit changing to a string here is... let domain = to_str(stream_open.extract_between(b" to='", b"'").or_else(|_| stream_open.extract_between(b" to=\"", b"\""))?); - println!("INFO: {} is_c2s: {}, domain: {}", client_addr, is_c2s, domain); + println!("INFO: out {} is_c2s: {}, domain: {}", client_addr, is_c2s, domain); - debug!("< {} {} '{}'", client_addr, c2s(is_c2s), to_str(&stream_open)); + debug!("out < {} {} '{}'", client_addr, c2s(is_c2s), to_str(&stream_open)); let (mut out_wr, mut out_rd, stream_open) = srv_connect(&domain, is_c2s, &stream_open, &mut in_filter).await?; // send server response to client in_wr.write_all(&stream_open).await?; @@ -35,7 +35,7 @@ async fn handle_outgoing_connection(stream: tokio::net::TcpStream, client_addr: match buf { None => break, Some(buf) => { - debug!("< {} {} '{}'", client_addr, c2s(is_c2s), to_str(buf)); + debug!("out < {} {} '{}'", domain, c2s(is_c2s), to_str(buf)); in_wr.write_all(buf).await?; in_wr.flush().await?; } @@ -47,14 +47,14 @@ async fn handle_outgoing_connection(stream: tokio::net::TcpStream, client_addr: if n == 0 { break; } - debug!("> {} {} '{}'", client_addr, c2s(is_c2s), to_str(&out_buf[0..n])); + debug!("out > {} {} '{}'", domain, c2s(is_c2s), to_str(&out_buf[0..n])); out_wr.write_all(&out_buf[0..n]).await?; out_wr.flush().await?; }, } } - println!("INFO: {} disconnected", client_addr); + println!("INFO: out {} disconnected", client_addr); Ok(()) } diff --git a/src/quic.rs b/src/quic.rs index b797601..41c2de1 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -15,14 +15,14 @@ pub async fn quic_connect(target: SocketAddr, server_name: &str, is_c2s: bool) - endpoint_builder.default_client_config(client_cfg); let (endpoint, _incoming) = endpoint_builder.bind(&bind_addr)?; // connect to server - let quinn::NewConnection { connection, .. } = endpoint.connect(&target, server_name).unwrap().await.unwrap(); + let quinn::NewConnection { connection, .. } = endpoint.connect(&target, server_name).unwrap().await?; debug!("[client] connected: addr={}", connection.remote_address()); if is_c2s { - let (wrt, rd) = connection.open_bi().await.unwrap(); + let (wrt, rd) = connection.open_bi().await?; Ok((Box::new(wrt), Box::new(rd))) } else { - let wrt = connection.open_uni().await.unwrap(); + let wrt = connection.open_uni().await?; Ok((Box::new(wrt), Box::new(NoopIo))) } } diff --git a/src/srv.rs b/src/srv.rs index 9d21f3c..b3ebc76 100644 --- a/src/srv.rs +++ b/src/srv.rs @@ -44,7 +44,7 @@ impl XmppConnection { let ips = RESOLVER.lookup_ip(self.target.clone()).await?; debug!("trying 1 domain {}, SRV: {:?}", domain, self); for ip in ips.iter() { - debug!("trying domain {}, ip {}, SRV: {:?}", domain, ip, self); + debug!("trying domain {}, ip {}, is_c2s: {}, SRV: {:?}", domain, ip, is_c2s, self); match self.conn_type { XmppConnectionType::StartTLS => match crate::starttls_connect(SocketAddr::new(ip, self.port), domain, is_c2s, &stream_open, &mut in_filter).await { Ok((wr, rd)) => return Ok((wr, rd)), diff --git a/xmpp-proxy.toml b/xmpp-proxy.toml index c617ac2..032d4b7 100644 --- a/xmpp-proxy.toml +++ b/xmpp-proxy.toml @@ -1,8 +1,10 @@ -# interfaces to listen for external XMPP connections on -listen = [ "0.0.0.0:5222", "0.0.0.0:5269" ] +# interfaces to listen for reverse proxy STARTTLS/Direct TLS XMPP connections on, should be open to the internet +incoming_listen = [ "0.0.0.0:5222", "0.0.0.0:5269" ] +# interfaces to listen for reverse proxy QUIC XMPP connections on, should be open to the internet quic_listen = [ "0.0.0.0:443" ] -outgoing_listen = [ "0.0.0.0:5252" ] +# interfaces to listen for outgoing proxy TCP XMPP connections on, should be localhost +outgoing_listen = [ "127.0.0.1:15270" ] # these ports shouldn't do any TLS, but should assume any connection from xmpp-proxy is secure # prosody module: https://modules.prosody.im/mod_secure_interfaces.html