mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-08 14:36:13 +00:00
fix(fail2ban): fix banning regression and Docker zero-jail issue
- DockerEntrypoint.sh: create jail.d/filter.d/action.d config files before starting fail2ban so Docker containers no longer start with 0 active jails (fixes #4134) - x-ui.sh create_iplimit_jails: lower maxretry from 2 to 1 so fail2ban bans on the first log entry; with maxretry=2 and the partitionLiveIps logic the second occurrence could arrive after the 32 s findtime window, silently preventing any ban (fixes #4163) - x-ui.sh: fix datepattern (%%Y -> %Y) so fail2ban parses the Go log timestamp correctly instead of looking for a literal %%Y string - x-ui.sh / DockerEntrypoint.sh: fix date command in actionban / actionunban echo (%%Y -> %Y) so the ban log records actual dates - check_client_ip_job.go: replace log.SetOutput / log.SetFlags on the global standard-library logger with a local log.New instance, eliminating the dangling closed-file-handle between calls and stopping unrelated stdlib log output from polluting 3xipl.log Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
# Ignore editor and IDE settings
|
# Ignore editor and IDE settings
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.claude/
|
||||||
.cache/
|
.cache/
|
||||||
.sync*
|
.sync*
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,61 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Start fail2ban
|
# Start fail2ban with the 3x-ipl jail
|
||||||
[ $XUI_ENABLE_FAIL2BAN == "true" ] && fail2ban-client -x start
|
if [ "$XUI_ENABLE_FAIL2BAN" = "true" ]; then
|
||||||
|
LOG_FOLDER="${XUI_LOG_FOLDER:-/var/log/x-ui}"
|
||||||
|
mkdir -p "$LOG_FOLDER"
|
||||||
|
touch "$LOG_FOLDER/3xipl.log" "$LOG_FOLDER/3xipl-banned.log"
|
||||||
|
|
||||||
|
mkdir -p /etc/fail2ban/jail.d /etc/fail2ban/filter.d /etc/fail2ban/action.d
|
||||||
|
|
||||||
|
cat > /etc/fail2ban/jail.d/3x-ipl.conf << EOF
|
||||||
|
[3x-ipl]
|
||||||
|
enabled=true
|
||||||
|
backend=auto
|
||||||
|
filter=3x-ipl
|
||||||
|
action=3x-ipl
|
||||||
|
logpath=$LOG_FOLDER/3xipl.log
|
||||||
|
maxretry=1
|
||||||
|
findtime=32
|
||||||
|
bantime=30m
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > /etc/fail2ban/filter.d/3x-ipl.conf << 'EOF'
|
||||||
|
[Definition]
|
||||||
|
datepattern = ^%Y/%m/%d %H:%M:%S
|
||||||
|
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*Disconnecting OLD IP\s*=\s*<ADDR>\s*\|\|\s*Timestamp\s*=\s*\d+
|
||||||
|
ignoreregex =
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > /etc/fail2ban/action.d/3x-ipl.conf << EOF
|
||||||
|
[INCLUDES]
|
||||||
|
before = iptables-allports.conf
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
actionstart = <iptables> -N f2b-<name>
|
||||||
|
<iptables> -A f2b-<name> -j <returntype>
|
||||||
|
<iptables> -I <chain> -p <protocol> -j f2b-<name>
|
||||||
|
|
||||||
|
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
|
||||||
|
<actionflush>
|
||||||
|
<iptables> -X f2b-<name>
|
||||||
|
|
||||||
|
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||||
|
|
||||||
|
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||||
|
echo "\$(date +"%Y/%m/%d %H:%M:%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> $LOG_FOLDER/3xipl-banned.log
|
||||||
|
|
||||||
|
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||||
|
echo "\$(date +"%Y/%m/%d %H:%M:%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> $LOG_FOLDER/3xipl-banned.log
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
name = default
|
||||||
|
protocol = tcp
|
||||||
|
chain = INPUT
|
||||||
|
EOF
|
||||||
|
|
||||||
|
fail2ban-client -x start
|
||||||
|
fi
|
||||||
|
|
||||||
# Run x-ui
|
# Run x-ui
|
||||||
exec /app/x-ui
|
exec /app/x-ui
|
||||||
|
|||||||
@@ -403,16 +403,6 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
|||||||
shouldCleanLog := false
|
shouldCleanLog := false
|
||||||
j.disAllowedIps = []string{}
|
j.disAllowedIps = []string{}
|
||||||
|
|
||||||
// Open log file
|
|
||||||
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("failed to open IP limit log file: %s", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer logIpFile.Close()
|
|
||||||
log.SetOutput(logIpFile)
|
|
||||||
log.SetFlags(log.LstdFlags)
|
|
||||||
|
|
||||||
// historical db-only ips are excluded from this count on purpose.
|
// historical db-only ips are excluded from this count on purpose.
|
||||||
var keptLive []IPWithTimestamp
|
var keptLive []IPWithTimestamp
|
||||||
if len(liveIps) > limitIp {
|
if len(liveIps) > limitIp {
|
||||||
@@ -422,13 +412,25 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
|||||||
keptLive = liveIps[:limitIp]
|
keptLive = liveIps[:limitIp]
|
||||||
bannedLive := liveIps[limitIp:]
|
bannedLive := liveIps[limitIp:]
|
||||||
|
|
||||||
|
// Open log file only when a ban entry needs to be written.
|
||||||
|
// Use a local logger to avoid mutating the global log.* state,
|
||||||
|
// which would redirect all standard-library logging to this file
|
||||||
|
// and leave a dangling closed-file handle after the defer fires.
|
||||||
|
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to open IP limit log file: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer logIpFile.Close()
|
||||||
|
ipLogger := log.New(logIpFile, "", log.LstdFlags)
|
||||||
|
|
||||||
// log format is load-bearing: x-ui.sh create_iplimit_jails builds
|
// log format is load-bearing: x-ui.sh create_iplimit_jails builds
|
||||||
// filter.d/3x-ipl.conf with
|
// filter.d/3x-ipl.conf with
|
||||||
// failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*Disconnecting OLD IP\s*=\s*<ADDR>\s*\|\|\s*Timestamp\s*=\s*\d+
|
// failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*Disconnecting OLD IP\s*=\s*<ADDR>\s*\|\|\s*Timestamp\s*=\s*\d+
|
||||||
// don't change the wording.
|
// don't change the wording.
|
||||||
for _, ipTime := range bannedLive {
|
for _, ipTime := range bannedLive {
|
||||||
j.disAllowedIps = append(j.disAllowedIps, ipTime.IP)
|
j.disAllowedIps = append(j.disAllowedIps, ipTime.IP)
|
||||||
log.Printf("[LIMIT_IP] Email = %s || Disconnecting OLD IP = %s || Timestamp = %d", clientEmail, ipTime.IP, ipTime.Timestamp)
|
ipLogger.Printf("[LIMIT_IP] Email = %s || Disconnecting OLD IP = %s || Timestamp = %d", clientEmail, ipTime.IP, ipTime.Timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// force xray to drop existing connections from banned ips
|
// force xray to drop existing connections from banned ips
|
||||||
|
|||||||
8
x-ui.sh
8
x-ui.sh
@@ -2034,14 +2034,14 @@ backend=auto
|
|||||||
filter=3x-ipl
|
filter=3x-ipl
|
||||||
action=3x-ipl
|
action=3x-ipl
|
||||||
logpath=${iplimit_log_path}
|
logpath=${iplimit_log_path}
|
||||||
maxretry=2
|
maxretry=1
|
||||||
findtime=32
|
findtime=32
|
||||||
bantime=${bantime}m
|
bantime=${bantime}m
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
|
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
|
||||||
[Definition]
|
[Definition]
|
||||||
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
|
datepattern = ^%Y/%m/%d %H:%M:%S
|
||||||
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*Disconnecting OLD IP\s*=\s*<ADDR>\s*\|\|\s*Timestamp\s*=\s*\d+
|
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*Disconnecting OLD IP\s*=\s*<ADDR>\s*\|\|\s*Timestamp\s*=\s*\d+
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
EOF
|
EOF
|
||||||
@@ -2062,10 +2062,10 @@ actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
|
|||||||
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||||
|
|
||||||
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||||
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
|
echo "\$(date +"%Y/%m/%d %H:%M:%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
|
||||||
|
|
||||||
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||||
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
|
echo "\$(date +"%Y/%m/%d %H:%M:%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
name = default
|
name = default
|
||||||
|
|||||||
Reference in New Issue
Block a user