mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
Update file-headers and add a testing geoip_notify script
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Fail2ban UI
|
# Fail2ban UI
|
||||||
|
|
||||||
A **Go**-powered, **single-page** web interface for [Fail2ban](https://www.fail2ban.org/).
|
A Swissmade, management interface for [Fail2ban](https://www.fail2ban.org/).
|
||||||
It provides a modern dashboard to currently:
|
It provides a modern dashboard to currently:
|
||||||
|
|
||||||
- View all Fail2ban jails and banned IPs
|
- View all Fail2ban jails and banned IPs
|
||||||
@@ -8,6 +8,7 @@ It provides a modern dashboard to currently:
|
|||||||
- Edit and save jail/filter configs
|
- Edit and save jail/filter configs
|
||||||
- Reload Fail2ban when needed
|
- Reload Fail2ban when needed
|
||||||
- See recent ban events
|
- See recent ban events
|
||||||
|
- More to come...
|
||||||
|
|
||||||
Built by [Swissmakers GmbH](https://swissmakers.ch).
|
Built by [Swissmakers GmbH](https://swissmakers.ch).
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
// Fail2ban UI - A Swiss made, management interface for Fail2ban.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2025 Swissmakers GmbH (https://swissmakers.ch)
|
||||||
|
//
|
||||||
|
// Licensed under the GNU General Public License, Version 3 (GPL-3.0)
|
||||||
|
// You may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -252,20 +268,7 @@ norestored = 1
|
|||||||
# Option: actionban
|
# Option: actionban
|
||||||
# This executes the Python script with <ip> and the list of allowed countries.
|
# This executes the Python script with <ip> and the list of allowed countries.
|
||||||
# If the country matches the allowed list, it sends the email.
|
# If the country matches the allowed list, it sends the email.
|
||||||
actionban = /etc/fail2ban/scripts/check_geoip.py <ip> "%s" && (
|
actionban = /etc/fail2ban/action.d/geoip_notify.py <ip> "%s" 'name=<name>;ip=<ip>;fq-hostname=<fq-hostname>;sendername=<sendername>;sender=<sender>;dest=<dest>;failures=<failures>;_whois_command=%%(_whois_command)s;_grep_logs=%%(_grep_logs)s;grepmax=<grepmax>;mailcmd=<mailcmd>'
|
||||||
printf %%%%b "Subject: [Fail2Ban] <name>: banned <ip> from <fq-hostname>
|
|
||||||
Date: `+"`LC_ALL=C date +\"%%%%a, %%%%d %%%%h %%%%Y %%%%T %%%%z\"`"+`
|
|
||||||
From: <sendername> <<sender>>
|
|
||||||
To: <dest>\n
|
|
||||||
Hi,\n
|
|
||||||
The IP <ip> has just been banned by Fail2Ban after <failures> attempts against <name>.\n\n
|
|
||||||
Here is more information about <ip>:\n"
|
|
||||||
%%(_whois_command)s;
|
|
||||||
printf %%%%b "\nLines containing failures of <ip> (max <grepmax>)\n";
|
|
||||||
%%(_grep_logs)s;
|
|
||||||
printf %%%%b "\n
|
|
||||||
Regards,\n
|
|
||||||
Fail2Ban" ) | <mailcmd>
|
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
@@ -281,7 +284,6 @@ logpath = /dev/null
|
|||||||
`, countriesFormatted)
|
`, countriesFormatted)
|
||||||
|
|
||||||
// Write the action file
|
// Write the action file
|
||||||
//actionFilePath := "/etc/fail2ban/action.d/ui-custom-action.conf"
|
|
||||||
err := os.WriteFile(actionFile, []byte(actionConfig), 0644)
|
err := os.WriteFile(actionFile, []byte(actionConfig), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write action file: %w", err)
|
return fmt.Errorf("failed to write action file: %w", err)
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
// Fail2ban UI - A Swiss made, management interface for Fail2ban.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2025 Swissmakers GmbH (https://swissmakers.ch)
|
||||||
|
//
|
||||||
|
// Licensed under the GNU General Public License, Version 3 (GPL-3.0)
|
||||||
|
// You may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package fail2ban
|
package fail2ban
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -21,7 +37,7 @@ func GetJails() ([]string, error) {
|
|||||||
cmd := exec.Command("fail2ban-client", "status")
|
cmd := exec.Command("fail2ban-client", "status")
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not run 'fail2ban-client status': %v", err)
|
return nil, fmt.Errorf("could not get jail information. is fail2ban running? error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var jails []string
|
var jails []string
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
// Fail2ban UI - A Swiss made, management interface for Fail2ban.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2025 Swissmakers GmbH (https://swissmakers.ch)
|
||||||
|
//
|
||||||
|
// Licensed under the GNU General Public License, Version 3 (GPL-3.0)
|
||||||
|
// You may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package fail2ban
|
package fail2ban
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
132
internal/geoip_notify.py
Normal file
132
internal/geoip_notify.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/python3.9
|
||||||
|
"""
|
||||||
|
Fail2ban UI - A Swiss made, management interface for Fail2ban.
|
||||||
|
|
||||||
|
Copyright (C) 2025 Swissmakers GmbH
|
||||||
|
|
||||||
|
Licensed under the GNU General Public License, Version 3 (GPL-3.0)
|
||||||
|
You may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This file is for testing purposes only.
|
||||||
|
# (Must be copied to "/etc/fail2ban/action.d/geoip_notify.py")
|
||||||
|
#python3.9 -c "import maxminddb; print('maxminddb is installed successfully')"
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
# Manually set Python path where maxminddb is installed
|
||||||
|
sys.path.append("/usr/local/lib64/python3.9/site-packages/")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import maxminddb
|
||||||
|
except ImportError:
|
||||||
|
print("Error: maxminddb module not found, even after modifying PYTHONPATH.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# Path to MaxMind GeoIP2 database
|
||||||
|
GEOIP_DB_PATH = "/usr/share/GeoIP/GeoLite2-Country.mmdb"
|
||||||
|
|
||||||
|
def get_country(ip):
|
||||||
|
"""
|
||||||
|
Perform a GeoIP lookup to get the country code from an IP address.
|
||||||
|
Returns the country code (e.g., "CH") or None if lookup fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with maxminddb.open_database(GEOIP_DB_PATH) as reader:
|
||||||
|
geo_data = reader.get(ip)
|
||||||
|
if geo_data and "country" in geo_data and "iso_code" in geo_data["country"]:
|
||||||
|
return geo_data["country"]["iso_code"]
|
||||||
|
except Exception as e:
|
||||||
|
print(f"GeoIP lookup failed: {e}", file=sys.stderr)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def parse_placeholders(placeholder_str):
|
||||||
|
"""
|
||||||
|
Parses Fail2Ban placeholders passed as a string in "key=value" format.
|
||||||
|
Returns a dictionary.
|
||||||
|
"""
|
||||||
|
placeholders = {}
|
||||||
|
for item in placeholder_str.split(";"):
|
||||||
|
key_value = item.split("=", 1)
|
||||||
|
if len(key_value) == 2:
|
||||||
|
key, value = key_value
|
||||||
|
placeholders[key.strip()] = value.strip()
|
||||||
|
return placeholders
|
||||||
|
|
||||||
|
def send_email(placeholders):
|
||||||
|
"""
|
||||||
|
Generates and sends the email alert using sendmail.
|
||||||
|
"""
|
||||||
|
email_content = f"""Subject: [Fail2Ban] {placeholders['name']}: banned {placeholders['ip']} from {placeholders['fq-hostname']}
|
||||||
|
Date: $(LC_ALL=C date +"%a, %d %h %Y %T %z")
|
||||||
|
From: {placeholders['sendername']} <{placeholders['sender']}>
|
||||||
|
To: {placeholders['dest']}
|
||||||
|
|
||||||
|
Hi,
|
||||||
|
|
||||||
|
The IP {placeholders['ip']} has just been banned by Fail2Ban after {placeholders['failures']} attempts against {placeholders['name']}.
|
||||||
|
|
||||||
|
Here is more information about {placeholders['ip']}:
|
||||||
|
{subprocess.getoutput(placeholders['_whois_command'])}
|
||||||
|
|
||||||
|
Lines containing failures of {placeholders['ip']} (max {placeholders['grepmax']}):
|
||||||
|
{subprocess.getoutput(placeholders['_grep_logs'])}
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
Fail2Ban"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["/usr/sbin/sendmail", "-f", placeholders["sender"], placeholders["dest"]],
|
||||||
|
input=email_content,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
print("Email sent successfully.")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Failed to send email: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
def main(ip, allowed_countries, placeholder_str):
|
||||||
|
"""
|
||||||
|
Main function to check the IP's country and send an email if it matches the allowed list.
|
||||||
|
"""
|
||||||
|
allowed_countries = allowed_countries.split(",")
|
||||||
|
placeholders = parse_placeholders(placeholder_str)
|
||||||
|
|
||||||
|
# Perform GeoIP lookup
|
||||||
|
country = get_country(ip)
|
||||||
|
if not country:
|
||||||
|
print(f"Could not determine country for IP {ip}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"IP {ip} belongs to country: {country}")
|
||||||
|
|
||||||
|
# If the country is in the allowed list or "ALL" is selected, send the email
|
||||||
|
if "ALL" in allowed_countries or country in allowed_countries:
|
||||||
|
print(f"IP {ip} is in the alert countries list. Sending email...")
|
||||||
|
send_email(placeholders)
|
||||||
|
else:
|
||||||
|
print(f"IP {ip} is NOT in the alert countries list. No email sent.")
|
||||||
|
sys.exit(0) # Exit normally without error
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) != 4:
|
||||||
|
print("Usage: geoip_notify.py <ip> <allowed_countries> <placeholders>", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
ip = sys.argv[1]
|
||||||
|
allowed_countries = sys.argv[2]
|
||||||
|
placeholders = sys.argv[3]
|
||||||
|
|
||||||
|
main(ip, allowed_countries, placeholders)
|
||||||
@@ -1,3 +1,19 @@
|
|||||||
|
// Fail2ban UI - A Swiss made, management interface for Fail2ban.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2025 Swissmakers GmbH (https://swissmakers.ch)
|
||||||
|
//
|
||||||
|
// Licensed under the GNU General Public License, Version 3 (GPL-3.0)
|
||||||
|
// You may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
// Fail2ban UI - A Swiss made, management interface for Fail2ban.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2025 Swissmakers GmbH (https://swissmakers.ch)
|
||||||
|
//
|
||||||
|
// Licensed under the GNU General Public License, Version 3 (GPL-3.0)
|
||||||
|
// You may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
<!--
|
||||||
|
Fail2ban UI - A Swiss made, management interface for Fail2ban.
|
||||||
|
|
||||||
|
Copyright (C) 2025 Swissmakers GmbH
|
||||||
|
|
||||||
|
Licensed under the GNU General Public License, Version 3 (GPL-3.0)
|
||||||
|
You may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user