Document new QUIC and outgoing support

This commit is contained in:
Travis Burtrum 2021-05-12 00:51:53 -04:00
parent 8e612cf02e
commit 62a97d3721
7 changed files with 103 additions and 26 deletions

View File

@ -3,7 +3,7 @@ name = "xmpp-proxy"
version = "1.0.0" version = "1.0.0"
authors = ["moparisthebest <admin@moparisthebest.com>"] authors = ["moparisthebest <admin@moparisthebest.com>"]
description = "Reverse XMPP proxy." description = "XMPP reverse proxy and outgoing proxy"
repository = "https://code.moparisthebest.com/moparisthebest/xmpp-proxy" repository = "https://code.moparisthebest.com/moparisthebest/xmpp-proxy"
keywords = ["xmpp", "proxy"] keywords = ["xmpp", "proxy"]
@ -49,5 +49,5 @@ default = ["incoming", "outgoing", "quic"]
#default = ["outgoing"] #default = ["outgoing"]
#default = ["incoming"] #default = ["incoming"]
incoming = ["tokio-rustls"] 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"] quic = ["quinn"]

View File

@ -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/) [![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 xmpp-proxy is a reverse proxy and outgoing proxy for XMPP servers and clients, providing STARTTLS,
and limiting stanza sizes without an XML parser. [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) xmpp-proxy in reverse proxy (incoming) mode will:
c2s or s2s connections, terminate TLS, and connect them to a real XMPP server, limiting stanza sizes as configured. 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 #### Installation
* `cargo install xmpp-proxy` * `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? #### 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"; "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) 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. which also works for s2s.
@ -49,11 +114,21 @@ proxy_port_mappings = {
c2s_ports = {} c2s_ports = {}
legacy_ssl_ports = {} legacy_ssl_ports = {}
-- you MUST have at least one s2s_ports defined if you want outgoing S2S to work, don't ask.. -- 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 #### Customize the build
`xmpp-proxy.toml` configuration as-is.
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 #### License
GNU/AGPLv3 - Check LICENSE.md for details GNU/AGPLv3 - Check LICENSE.md for details

View File

@ -79,7 +79,7 @@ macro_rules! debug {
struct Config { struct Config {
tls_key: String, tls_key: String,
tls_cert: String, tls_cert: String,
listen: Option<Vec<String>>, incoming_listen: Option<Vec<String>>,
quic_listen: Option<Vec<String>>, quic_listen: Option<Vec<String>>,
outgoing_listen: Option<Vec<String>>, outgoing_listen: Option<Vec<String>>,
max_stanza_size_bytes: usize, max_stanza_size_bytes: usize,
@ -272,7 +272,7 @@ async fn main() {
let mut handles: Vec<JoinHandle<Result<()>>> = Vec::new(); let mut handles: Vec<JoinHandle<Result<()>>> = Vec::new();
#[cfg(feature = "incoming")] #[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 ?"); let acceptor = main_config.tls_acceptor().die("invalid cert/key ?");
for listener in listeners { for listener in listeners {
handles.push(spawn_tls_listener(listener.parse().die("invalid listener address"), config.clone(), acceptor.clone())); handles.push(spawn_tls_listener(listener.parse().die("invalid listener address"), config.clone(), acceptor.clone()));

View File

@ -1,7 +1,7 @@
use crate::*; use crate::*;
async fn handle_outgoing_connection(stream: tokio::net::TcpStream, client_addr: SocketAddr, max_stanza_size_bytes: usize) -> Result<()> { 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); 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... // 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"\""))?); 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?; 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 // send server response to client
in_wr.write_all(&stream_open).await?; in_wr.write_all(&stream_open).await?;
@ -35,7 +35,7 @@ async fn handle_outgoing_connection(stream: tokio::net::TcpStream, client_addr:
match buf { match buf {
None => break, None => break,
Some(buf) => { 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.write_all(buf).await?;
in_wr.flush().await?; in_wr.flush().await?;
} }
@ -47,14 +47,14 @@ async fn handle_outgoing_connection(stream: tokio::net::TcpStream, client_addr:
if n == 0 { if n == 0 {
break; 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.write_all(&out_buf[0..n]).await?;
out_wr.flush().await?; out_wr.flush().await?;
}, },
} }
} }
println!("INFO: {} disconnected", client_addr); println!("INFO: out {} disconnected", client_addr);
Ok(()) Ok(())
} }

View File

@ -15,14 +15,14 @@ pub async fn quic_connect(target: SocketAddr, server_name: &str, is_c2s: bool) -
endpoint_builder.default_client_config(client_cfg); endpoint_builder.default_client_config(client_cfg);
let (endpoint, _incoming) = endpoint_builder.bind(&bind_addr)?; let (endpoint, _incoming) = endpoint_builder.bind(&bind_addr)?;
// connect to server // 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()); debug!("[client] connected: addr={}", connection.remote_address());
if is_c2s { 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))) Ok((Box::new(wrt), Box::new(rd)))
} else { } else {
let wrt = connection.open_uni().await.unwrap(); let wrt = connection.open_uni().await?;
Ok((Box::new(wrt), Box::new(NoopIo))) Ok((Box::new(wrt), Box::new(NoopIo)))
} }
} }

View File

@ -44,7 +44,7 @@ impl XmppConnection {
let ips = RESOLVER.lookup_ip(self.target.clone()).await?; let ips = RESOLVER.lookup_ip(self.target.clone()).await?;
debug!("trying 1 domain {}, SRV: {:?}", domain, self); debug!("trying 1 domain {}, SRV: {:?}", domain, self);
for ip in ips.iter() { 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 { match self.conn_type {
XmppConnectionType::StartTLS => match crate::starttls_connect(SocketAddr::new(ip, self.port), domain, is_c2s, &stream_open, &mut in_filter).await { 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)), Ok((wr, rd)) => return Ok((wr, rd)),

View File

@ -1,8 +1,10 @@
# interfaces to listen for external XMPP connections on # interfaces to listen for reverse proxy STARTTLS/Direct TLS XMPP connections on, should be open to the internet
listen = [ "0.0.0.0:5222", "0.0.0.0:5269" ] 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" ] 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 # 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 # prosody module: https://modules.prosody.im/mod_secure_interfaces.html