Auto ban anonymous IP:

* New settings: "Ban anonymous user's IP when user is banned from a chatroom":
  * if enabled, every time a streamer bans an anonymous user, it will ban its IP on the chat server,
  * banned IPs are logged on disk, so server's admin can use them to feed fail2ban (for example),
  * option disabled by default, because could be used to create trapped-rooms on public servers
This commit is contained in:
John Livingston 2023-09-22 18:17:54 +02:00
parent 812eb89856
commit d80cedfee5
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
9 changed files with 176 additions and 3 deletions

View File

@ -14,6 +14,10 @@
* make it send pre-recorded messages every X minutes,
* respond message to custom commands.
* many other features will be available in future releases!
* New settings: "Ban anonymous user's IP when user is banned from a chatroom":
* if enabled, every time a streamer bans an anonymous user, it will ban its IP on the chat server,
* banned IPs are logged on disk, so server's admin can use them to feed fail2ban (for example),
* option disabled by default, because could be used to create trapped-rooms on public servers
### Minor changes and fixes

View File

@ -240,6 +240,8 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re
return options.formValues['converse-theme'] !== 'peertube'
case 'chat-per-live-video-warning':
return !(options.formValues['chat-all-lives'] === true && options.formValues['chat-per-live-video'] === true)
case 'auto-ban-anonymous-ip':
return options.formValues['chat-no-anonymous'] !== false
}
return false

View File

@ -122,6 +122,13 @@ no_anonymous_description: |
If you enabled it, it is highly recommended to also check "Don't publish chat information".
Otherwise, some third party tools could try to open the chat, and have unpredictable behaviours.
auto_ban_anonymous_ip_label: "Ban anonymous user's IP when user is banned from a chatroom"
auto_ban_anonymous_ip_description: |
By enabling this option, each time an anonymous user is banned from a chatroom, it's IP will also be banned from the chat server.
Warning: if your instance is open to registration, any user could create a trapped-room, invite users to join, and automatically ban all anonymous user's IPs.
The banned IP list is not stored, it will be cleared on server restart, or when you change some plugin's settings.
The banned IP are logged in the Prosody server log files, so server's administrators can eventually use some external tools (like fail2ban) to ban IPs more widely.
theming_advanced_description: "<h3>Theming</h3>"
converse_theme_label: "ConverseJS theme"

View File

@ -0,0 +1,63 @@
---
labels:
- 'Stage-Alpha'
summary: Ban users from chatrooms by their IP address
...
Note: this is a slightly modified version: the log level for IP bans is
set to info, instead of debug.
So we can use external tools (fail2ban for example) to block IPs more widely.
Introduction
============
One frequent complaint about XMPP chatrooms (MUCs) compared to IRC is
the inability for a room admin to ban a user based on their IP address.
This is because an XMPP user is not identified on the network by their
IP address, only their JID.
This means that it is possible to create a new account (usually quite
easily), and rejoin the room that you were banned from.
This module allows the **user's** server to enforce bans by IP address,
which is very desirable for server admins who want to prevent their
server being used for spamming and abusive behaviour.
Details
=======
An important point to note is that this module enforces the IP ban on
the banned user's server, not on the MUC server. This means that:
- The user's server MUST have this module loaded, however -
- The module works even when the MUC is on a different server to the
user
- The MUC server does not need this module (it only needs to support
the [standard ban
protocol](http://xmpp.org/extensions/xep-0045.html#ban))
- The module works for effectively banning [anonymous
users](http://prosody.im/doc/anonymous_logins)
Also note that IP bans are not saved permanently, and are reset upon a
server restart.
Configuration
=============
There is no extra configuration for this module except for loading it.
Remember... do not load it on the MUC host, simply add it to your global
`modules_enabled` list, or under a specific host like:
``` lua
VirtualHost "anon.example.com"
authentication = "anonymous"
modules_enabled = { "muc_ban_ip" }
```
Compatibility
=============
----- --------------
0.9 Works
0.8 Doesn't work
----- --------------

View File

@ -0,0 +1,80 @@
module:set_global();
local jid_bare, jid_host = require "util.jid".bare, require "util.jid".host;
local st = require "util.stanza";
local xmlns_muc_user = "http://jabber.org/protocol/muc#user";
local trusted_services = module:get_option_inherited_set("muc_ban_ip_trusted_services", {});
local trust_local_restricted_services = module:get_option_boolean("muc_ban_ip_trust_local_restricted_services", true);
local ip_bans = module:shared("bans");
local full_sessions = prosody.full_sessions;
local function is_local_restricted_service(host)
local muc_service = prosody.hosts[host] and prosody.hosts[host].modules.muc;
if muc_service and module:context(host):get_option("restrict_room_creation") ~= nil then -- COMPAT: May need updating post-0.12
return true;
end
return false;
end
local function ban_ip(session, from)
local ip = session.ip;
if not ip then
module:log("warn", "Failed to ban IP (IP unknown) for %s", session.full_jid);
return;
end
local from_host = jid_host(from);
if trusted_services:contains(from_host) or (trust_local_restricted_services and is_local_restricted_service(from_host)) then
from = from_host; -- Ban from entire host
end
local banned_from = ip_bans[ip];
if not banned_from then
banned_from = {};
ip_bans[ip] = banned_from;
end
banned_from[from] = true;
-- Specific to peertube-plugin-livechat: log level=info.
module:log("info", "Added ban for IP address %s from %s", ip, from);
end
local function check_for_incoming_ban(event)
local stanza = event.stanza;
local to_session = full_sessions[stanza.attr.to];
if to_session then
local directed = to_session.directed;
local from = stanza.attr.from;
if directed and directed[from] and stanza.attr.type == "unavailable" then
-- This is a stanza from somewhere we sent directed presence to (may be a MUC)
local x = stanza:get_child("x", xmlns_muc_user);
if x then
for status in x:childtags("status") do
if status.attr.code == '301' then
ban_ip(to_session, jid_bare(from));
end
end
end
end
end
end
local function check_for_ban(event)
local origin, stanza = event.origin, event.stanza;
local ip = origin.ip;
local to, to_host = jid_bare(stanza.attr.to), jid_host(stanza.attr.to);
if ip_bans[ip] and (ip_bans[ip][to] or ip_bans[ip][to_host]) then
(origin.log or module._log)("debug", "IP banned: %s is banned from %s", ip, to)
if stanza.attr.type ~= "error" then
origin.send(st.error_reply(stanza, "auth", "forbidden")
:tag("x", { xmlns = xmlns_muc_user })
:tag("status", { code = '301' }));
end
return true;
end
(origin.log or module._log)("debug", "IP not banned: %s from %s", ip, to)
end
function module.add_host(module)
module:hook("presence/full", check_for_incoming_ban, 100);
module:hook("pre-presence/full", check_for_ban, 100);
end

View File

@ -152,6 +152,7 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
'prosody-components-interfaces',
'prosody-components-list',
'chat-no-anonymous',
'auto-ban-anonymous-ip',
'federation-dont-publish-remotely',
'disable-channel-configuration'
])
@ -163,6 +164,7 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
}
const logByDefault = (settings['prosody-muc-log-by-default'] as boolean) ?? true
const disableAnon = (settings['chat-no-anonymous'] as boolean) || false
const autoBanIP = (settings['auto-ban-anonymous-ip'] as boolean) || false
const logExpirationSetting = (settings['prosody-muc-expiration'] as string) ?? DEFAULTLOGEXPIRATION
const enableC2S = (settings['prosody-c2s'] as boolean) || false
// enableRoomS2S: room can be joined from remote XMPP servers (Peertube or not)
@ -224,7 +226,7 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
const config = new ProsodyConfigContent(paths, prosodyDomain)
if (!disableAnon) {
config.useAnonymous()
config.useAnonymous(autoBanIP)
}
config.useHttpAuthentication(authApiUrl)
const useWS = !!options.registerWebSocketRoute // this comes with Peertube >=5.0.0, and is a prerequisite to websocket

View File

@ -207,10 +207,13 @@ class ProsodyConfigContent {
this.muc.set('muc_room_default_history_length', 20)
}
useAnonymous (): void {
useAnonymous (autoBanIP: boolean): void {
this.anon = new ProsodyConfigVirtualHost('anon.' + this.prosodyDomain)
this.anon.set('authentication', 'anonymous')
this.anon.set('modules_enabled', ['ping'])
if (autoBanIP) {
this.anon.add('modules_enabled', 'muc_ban_ip')
}
}
useHttpAuthentication (url: string): void {

View File

@ -83,7 +83,7 @@ Please read
private: true
})
// ********** Moderation and advances customization
// ********** Advanced channel customization
registerSetting({
type: 'html',
private: true,
@ -197,6 +197,14 @@ Please read
descriptionHTML: loc('no_anonymous_description'),
private: false
})
registerSetting({
name: 'auto-ban-anonymous-ip',
label: loc('auto_ban_anonymous_ip_label'),
type: 'input-checkbox',
default: false,
descriptionHTML: loc('auto_ban_anonymous_ip_description'),
private: true
})
// ********** Theming
registerSetting({

View File

@ -82,6 +82,10 @@ Note: for now this feature simply hide the chat.
In a future release, the chat will be replaced by a message saying «please log in to [...]».
See [v5.7.0 Release Notes](https://github.com/JohnXLivingston/peertube-plugin-livechat/blob/main/CHANGELOG.md#570) for more information.
### {{% livechat_label auto_ban_anonymous_ip_label %}}
{{% livechat_label auto_ban_anonymous_ip_description %}}
## Theming
### {{% livechat_label converse_theme_label %}}