Mercurial > prosody-modules
changeset 6490:771944f2a7c7
mod_s2s_v6mesh: Update configuration syntax and associated documentation
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Tue, 24 Mar 2026 13:20:51 +0000 |
| parents | dff042647892 |
| children | 0a9b516e388e |
| files | mod_s2s_v6mesh/README.md mod_s2s_v6mesh/mod_s2s_v6mesh.lua |
| diffstat | 2 files changed, 118 insertions(+), 52 deletions(-) [+] |
line wrap: on
line diff
--- a/mod_s2s_v6mesh/README.md Mon Mar 23 19:47:03 2026 +0100 +++ b/mod_s2s_v6mesh/README.md Tue Mar 24 13:20:51 2026 +0000 @@ -63,29 +63,37 @@ ### Connection security -By default, Prosody requires TLS for server-to-server connections. However, it +Prosody typically requires TLS for server-to-server connections. However, it is not possible to obtain CA-issued certificates for `.v6.alt` domains. This -module disables certificate validation (it treats all certificates as trusted) -for all `v6.alt` domains where the IP address matches the domain name that the -remote server is claiming to be (the actual certificate contents are -irrelevant and never checked, so self-signed certificates are fine). +raises challenges around authentication of peers. -Additionally, if the IP is within one of the CIDR ranges listed in the -`s2s_v6mesh_secure_ranges` configuration option, then the module will tell -Prosody to disable TLS encryption for the connection. +By default, this module treats all certificates as trusted for all `v6.alt` +domains where the IP address matches the domain name that the remote server is +claiming to be (the actual certificate contents are irrelevant and never +checked, so self-signed certificates are fine). This provides a decent balance +between convenience and security. + +Connections using v6.alt are automatically safe from whole categories of +attacks such as DNS spoofing/poisoning (because such domains do not use DNS +resolvers). However, any attack which enables an attacker to route an IP +address to their own machine, for example, may be susceptible to a +machine-in-the-middle attack, which cannot be ruled out on the internet. -**Warning:** Prosody will treat connections within any configured ranges as -already totally secure, and **TLS will be disabled**. Be *very* sure you trust -the underlying network's security layer before adding an IP range to this -list, and never add IPs that use the public internet. If you want similar -behaviour for other (non-`v6.alt`) domains, look at [mod_secure_interfaces](https://modules.prosody.im/mod_secure_interfaces) -and configure it with your mesh interface's address. If in doubt, just don't -set any secure ranges, and use self-signed certificates with TLS instead. +For ultimate security across untrusted networks, use self-signed certificates +and pin fingerprints in the configuration on both sides using +[mod_s2s_auth_fingerprint](https://modules.prosody.im/mod_s2s_auth_fingerprint). + +If you do this, or if you only want v6.alt certificates to be trusted within +explicitly configured network ranges, you can disable the default certificate +trusting behaviour using `s2s_v6mesh_trust_all_certs = false`. -Finally, because of the above mechanisms in place, it is strongly recommended -to keep Prosody's default settings of `s2s_require_encryption = true` and -`s2s_secure_auth = true` so that connections not covered by this module are -always encrypted and authenticated properly. +You can configure connection security and trust settings per range, using the +`s2s_v6mesh_ranges` configuration option (more details below). + +Regardless of any other options, we always recommend keeping Prosody's default +settings of `s2s_require_encryption = true` and `s2s_secure_auth = true` so +that connections not covered by this module are always encrypted and +authenticated properly. ### Connecting from clients @@ -110,9 +118,27 @@ ## Configuration - | Name | Default| Description - |--------------------------|--------|------------------------------------------------------------------------- - | s2s_v6mesh_secure_ranges | `{}` | A list of IP ranges to treat as secure (bypassing TLS) + | Name | Default | Description + |------------------------------|-------- |------------------------------------------------------------------------- + | s2s_v6mesh_ranges | `{}` | A list of IP ranges and associated configuration (see below) + | s2s_v6mesh_trust_all_certs | `true` | Whether to trust all certs from verified v6.alt addresses (even in unconfigured ranges) + +### Range configuration + +By default, this module will use TLS, and trust any certificate from a v6.alt +domain if the IP address matches. You can override this behaviour on a +per-range basis: + +```lua +s2s_v6mesh_ranges = { + ["200::/7"] = { + -- disable TLS requirement for this IP range (overrides s2s_require_encryption) + use_tls = false; + -- trust all certificates (even self-signed) within this range (overrides s2s_secure_auth) + trust_all_certs = true; + }; +} +``` ## Compatibility
--- a/mod_s2s_v6mesh/mod_s2s_v6mesh.lua Mon Mar 23 19:47:03 2026 +0100 +++ b/mod_s2s_v6mesh/mod_s2s_v6mesh.lua Tue Mar 24 13:20:51 2026 +0000 @@ -9,15 +9,16 @@ local ip = require "prosody.util.ip"; local parse_cidr = require "prosody.util.ip".parse_cidr; -local secure_ranges = module:get_option_set("s2s_v6mesh_secure_ranges", {}); +local mesh_ranges = module:get_option("s2s_v6mesh_ranges", {}); +local trust_all_certs = module:get_option_boolean("s2s_v6mesh_trust_all_certs", true); local function is_secure_range(remote_ip) - for range in secure_ranges do + for range, range_config in pairs(mesh_ranges) do if ip.match(remote_ip, parse_cidr(range)) then - return true, range; + return range, range_config; end end - return false; + return nil; end function lookup(dom) @@ -38,6 +39,30 @@ return net.ntop(raw), raw; end +local function apply_range_config(host_module, host, session, target_ip) + local range, range_config = is_secure_range(target_ip); + + if range then + if range_config.use_tls == false then + host_module:log("debug", "Treating non-TLS connection to %s as secure because of range configuration", host); + session.secure = true; + end + if range_config.trust_all_certs ~= false then + module:log("debug", "Auto-trusting certificate for %s because of range configuration", host); + session.authenticated_remote = true; + session.cert_chain_status = "valid"; + session.cert_identity_status = "valid"; + else + module:log("debug", "Not auto-trusting certificate for $s (IP in unconfigured range): %s", host); + end + elseif trust_all_certs then + module:log("debug", "Auto-trusting certificate for %s because of default policy", host); + session.authenticated_remote = true; + session.cert_chain_status = "valid"; + session.cert_identity_status = "valid"; + end +end + function module.add_host(host_module) host_module:hook("s2sout-pre-connect", function(event) local session = event.session; @@ -54,13 +79,8 @@ event.resolver = basic_resolver.new(target_ip, 5269, "tcp", {}); - if is_secure_range(ip.new_ip(target_ip, "IPv6")) then - host_module:log("debug", "Treating non-TLS connection to %s as secure because it's in a secure IP range", host); - session.secure = true; - session.authenticated_remote = true; - session.cert_chain_status = "valid"; - session.cert_identity_status = "valid"; - end + local range_ip = ip.new_ip(target_ip, "IPv6"); + apply_range_config(host_module, host, session, range_ip); end); host_module:hook("s2s-stream-features", function (event) @@ -80,13 +100,7 @@ return; end - if is_secure_range(remote_ip) then - host_module:log("debug", "Treating non-TLS connection from %s as secure because it's in a secure IP range", host); - session.secure = true; - session.authenticated_remote = true; - session.cert_chain_status = "valid"; - session.cert_identity_status = "valid"; - end + apply_range_config(host_module, host, session, remote_ip); end, 200); end @@ -105,10 +119,29 @@ return; end - module:log("debug", "Trusting certificate for %s because of IP match", host); - session.cert_chain_status = "valid"; - session.cert_identity_status = "valid"; - return true; + local range, range_config = is_secure_range(remote_ip); + if range then + if range_config.trust_all_certs ~= false then + module:log("debug", "Auto-trusting certificate for %s because of IP match", host); + session.cert_chain_status = "valid"; + session.cert_identity_status = "valid"; + else + module:log("debug", "Not auto-trusting certificate for $s (IP in unconfigured range): %s", host); + end + elseif trust_all_certs then + module:log("debug", "Auto-trusting certificate for %s because of default policy", host); + session.authenticated_remote = true; + session.cert_chain_status = "valid"; + session.cert_identity_status = "valid"; + return true; + end + + if not session.conn.extra then + session.conn.extra = {}; + end + -- This can be used by mod_s2s_auth_certs to validate IP certificates, + -- if supported. + session.conn.extra.secure_hostname = tostring(remote_ip); end, 600); module:add_item("shell-command", { @@ -160,23 +193,30 @@ module:add_item("shell-command", { section = "v6alt"; section_desc = module.name.." utility commands"; - name = "is_secure_range"; - desc = "Check whether an IP address is considered secure"; + name = "range"; + desc = "Check whether a host/IP address is within a configured range"; args = { { name = "ip", type = "string" }; }; - handler = function(self, ip_str) --luacheck: ignore 212/self 212/host + handler = function(self, str) --luacheck: ignore 212/self 212/host + local encoded = str:match("([%w-]+)%.v6%.alt$"); + + local ip_str = encoded and lookup(encoded) or str; local user_ip = ip.new_ip(ip_str, "IPv6"); if not user_ip then - return nil, "Invalid IP address: "..tostring(ip_str); + return nil, "Invalid domain or IP: "..tostring(str); end - local secure, range = is_secure_range(user_ip); + local range, range_config = is_secure_range(user_ip); - if not secure then - return true, "Not secure"; + if not range then + return true, "Not configured"; end - return true, ("In secure range: %s"):format(range); + self.session.print(("In range: %s"):format(range)); + self.session.print(("Use TLS: %s"):format(range_config.use_tls == false and "no" or "yes")); + self.session.print(("Trust certs: %s"):format(range_config.trust_all_certs == false and "no" or "yes")); + + return true, "IP is in configured range"; end; });
