Skip to content
This repository was archived by the owner on Feb 16, 2025. It is now read-only.

Commit 309f5f0

Browse files
committed
Fail2ban-FastAPI
0 parents  commit 309f5f0

32 files changed

+1716
-0
lines changed

.gitignore

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
api/data/*.txt
2+
node_modules/
3+
npm-debug.log
4+
# Byte-compiled / optimized / DLL files
5+
__pycache__/
6+
*.py[cod]
7+
*$py.class
8+
9+
# C extensions
10+
*.so
11+
12+
# Distribution / packaging
13+
.Python
14+
develop-eggs/
15+
dist/
16+
downloads/
17+
eggs/
18+
.eggs/
19+
lib/
20+
lib64/
21+
parts/
22+
sdist/
23+
var/
24+
wheels/
25+
pip-wheel-metadata/
26+
share/python-wheels/
27+
*.egg-info/
28+
.installed.cfg
29+
*.egg
30+
MANIFEST
31+
32+
# PyInstaller
33+
# Usually these files are written by a python script from a template
34+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
35+
*.manifest
36+
*.spec
37+
38+
# Installer logs
39+
pip-log.txt
40+
pip-delete-this-directory.txt
41+
42+
# Unit test / coverage reports
43+
htmlcov/
44+
.tox/
45+
.nox/
46+
.coverage
47+
.coverage.*
48+
.cache
49+
nosetests.xml
50+
coverage.xml
51+
*.cover
52+
*.py,cover
53+
.hypothesis/
54+
.pytest_cache/
55+
cover/
56+
57+
# Translations
58+
*.mo
59+
*.pot
60+
61+
# Django stuff:
62+
*.log
63+
local_settings.py
64+
db.sqlite3
65+
db.sqlite3-journal
66+
67+
# Flask stuff:
68+
instance/
69+
.webassets-cache
70+
71+
# Scrapy stuff:
72+
.scrapy
73+
74+
# Sphinx documentation
75+
docs/_build/
76+
77+
# PyBuilder
78+
target/
79+
80+
# Jupyter Notebook
81+
.ipynb_checkpoints
82+
83+
# IPython
84+
profile_default/
85+
ipython_config.py
86+
87+
# pyenv
88+
# For a library or package, you might want to ignore these files since the code is
89+
# intended to run in multiple environments; otherwise, check them in:
90+
# .python-version
91+
92+
# pipenv
93+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
95+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
96+
# install all needed dependencies.
97+
#Pipfile.lock
98+
99+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
100+
__pypackages__/
101+
102+
# Celery stuff
103+
celerybeat-schedule
104+
celerybeat.pid
105+
106+
# SageMath parsed files
107+
*.sage.py
108+
109+
# Environments
110+
.env
111+
.venv
112+
env/
113+
venv/
114+
ENV/
115+
env.bak/
116+
venv.bak/
117+
118+
# Spyder project settings
119+
.spyderproject
120+
.spyproject
121+
122+
# Rope project settings
123+
.ropeproject
124+
125+
# mkdocs documentation
126+
/site
127+
128+
# mypy
129+
.mypy_cache/
130+
.dmypy.json
131+
dmypy.json
132+
133+
# Pyre type checker
134+
.pyre/
135+
136+
# pytype static type analyzer
137+
.pytype/
138+
node_modules

README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
fail2ban-fastapi
2+
----------
3+
4+
Fail2Ban operates by monitoring log files (e.g. `/var/log/auth.log`, `/var/log/apache/access.log`, etc.) for selected entries and running scripts based on them. Most commonly this is used to block selected IP addresses that may belong to hosts that are trying to breach the system's security.
5+
6+
**What is fail2ban-fastapi?**
7+
Frontend (vuejs) and backend (fastapi). FastAPI reads it's data from files created by this script (since Fail2ban's internal sqlite database is not human readable). The script user must have permissions to execute `$ fail2ban-client` to generate this data.
8+
```
9+
.
10+
├── api
11+
│ └── data/
12+
│ └── jails.txt
13+
│ └── jail_<jailname>.txt
14+
│ └── jail_<jailname>.txt
15+
```
16+
17+
![Fail2ban-FastAPI Demo](https://i.imgur.com/7sQhGeh.gif)
18+
19+
## Installation
20+
21+
### Fail2ban Setup
22+
23+
First of all, the script needs to be able to run `fail2ban-client` command. This is usually done with root permissions. To solve this, you need to create a new (non-root) user and run fail2ban as this user.
24+
25+
```bash
26+
$ sudo apt install fail2ban
27+
$ sudo adduser fail2ban
28+
```
29+
30+
Set `User` to "fail2ban" in the [Service] section in `/lib/systemd/system/fail2ban.service`
31+
```text
32+
...
33+
[Service]
34+
User=fail2ban
35+
...
36+
```
37+
38+
Change permissions of `/var/run/fail2ban`.
39+
```bash
40+
$ sudo chown -R fail2ban:root /var/run/fail2ban
41+
```
42+
43+
Verify permissions with `sudo ls -la /var/run/fail2ban/`. Make sure fail2ban user can read and write to this location.
44+
45+
Depending on your jails, the fail2ban user needs to be able read your logfiles (ex. `/var/log/auth.log`). An example configuration can be found in [jail.local.sample](./jail.local.sample).
46+
47+
So change permisions of your logfiles.
48+
```bash
49+
$ sudo chown fail2ban:root /var/log/auth.log
50+
```
51+
52+
Now you should be all set to reload `systemctl daemon` and restart fail2ban.
53+
54+
```bash
55+
$ sudo systemctl daemon-reload
56+
$ sudo systemctl restart fail2ban.service
57+
```
58+
59+
Check status of fail2ban.service to make sure we're all set and you have no errors.
60+
61+
## Install Fail2ban-FastAPI
62+
63+
Clone rep, create virtualenv and install requirements.
64+
```bash
65+
$ git clone [email protected]:dunderrrrrr/fail2ban-fastapi.git
66+
$ mkvirtualenv --python=/usr/bin/python3 fail2ban-fastapi
67+
$ pip install -r requirements.txt
68+
```
69+
70+
Before starting FastAPI backend we'll need to generate some Fail2ban data. This will only be necessary once when setting up Fail2ban-FastAPI for the first time on.
71+
```bash
72+
$ cd api/
73+
$ python first_init.py
74+
```
75+
76+
Start FastAPI backend.
77+
```bash
78+
$ python main.py
79+
```
80+
81+
Start vuejs frontend.
82+
```bash
83+
$ npm install
84+
$ npm run dev
85+
```
86+
87+
FastAPI backend can be access at `http://localhost:8000`.
88+
Frontend access at `http://localhost:8080`.

api/_defs.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import re
2+
3+
data_path = 'data/'
4+
data_format = '.txt'
5+
files = {
6+
"jails": "{}jails{}".format(data_path, data_format)
7+
}
8+
9+
def get_jaildata(jail, jail_path):
10+
data = {}
11+
f = open(jail_path, "r")
12+
l = []
13+
for line in f:
14+
l.append(line)
15+
data['fail_current'] = re.findall(r'\d+', l[2])[0]
16+
data['fail_total'] = re.findall(r'\d+', l[3])[0]
17+
data['bans_current'] = re.findall(r'\d+', l[6])[0]
18+
data['bans_total'] = re.findall(r'\d+', l[7])[0]
19+
data['log_file'] = l[4].split(':')[1].strip()
20+
data['bans_iplist'] = l[8].split(':')[1].strip().split(' ')
21+
return(data)
22+
23+
def summary(obj_jails, jailnums):
24+
tot_bans_current = []
25+
tot_bans_total = []
26+
for k,v in obj_jails['jail'].items():
27+
tot_bans_current.append(int(v['bans_current']))
28+
tot_bans_total.append(int(v['bans_total']))
29+
obj_jails['sum'] = {}
30+
obj_jails['sum']['jail_nums'] = int(jailnums)
31+
obj_jails['sum']['total_bans_current'] = sum(tot_bans_current)
32+
obj_jails['sum']['total_bans_total'] = sum(tot_bans_total)
33+
return(obj_jails)
34+
35+
def main():
36+
obj_jails = {}
37+
f = open(files['jails'], "r")
38+
l = []
39+
for line in f:
40+
l.append(line)
41+
jailnums = re.findall(r'\d+', l[1])[0]
42+
jail_names = l[2].split(':')[1].strip()
43+
obj_jails['jail'] = {}
44+
for jail in jail_names.split(', '):
45+
jail_path = data_path + 'jail_' + jail + data_format
46+
jail_data = get_jaildata(jail, jail_path)
47+
obj_jails['jail'][jail] = {
48+
"jail_name": jail,
49+
"fails_current": jail_data['fail_current'],
50+
"fails_total": jail_data['fail_total'],
51+
"bans_current": jail_data['bans_current'],
52+
"bans_total": jail_data['bans_total'],
53+
"log_file": jail_data['log_file'],
54+
"bans_iplist": jail_data['bans_iplist']
55+
}
56+
data = summary(obj_jails, jailnums)
57+
return(data)
58+
59+
def get_jail(jail):
60+
f = open(files['jails'], "r")
61+
l = []
62+
for line in f:
63+
l.append(line)
64+
jail_names = l[2].split(':')[1].strip().split(', ')
65+
if jail in jail_names:
66+
obj_jail = {
67+
jail: {}
68+
}
69+
jail_path = data_path + 'jail_' + jail + data_format
70+
jail_data = get_jaildata(jail, jail_path)
71+
obj_jail[jail] = {
72+
"fails_current": jail_data['fail_current'],
73+
"fails_total": jail_data['fail_total'],
74+
"bans_current": jail_data['bans_current'],
75+
"bans_total": jail_data['bans_total'],
76+
"log_file": jail_data['log_file'],
77+
"bans_iplist": jail_data['bans_iplist']
78+
}
79+
return(obj_jail)
80+
else:
81+
return {"error": 404, "msg": "Jail name '{}' not found".format(jail)}

api/data/.gitkeep

Whitespace-only changes.

api/f2b.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import os
2+
import time
3+
4+
data_path = os.path.dirname(os.path.realpath(__file__)) + "/data/"
5+
6+
def create_status():
7+
print("Running fail2ban-client status")
8+
filename = "jails.txt"
9+
data = os.popen('fail2ban-client status').read()
10+
file = open(data_path + filename, 'w')
11+
file.write(data)
12+
print("Wrote to {}{}\n".format(data_path, filename))
13+
14+
def get_jails():
15+
f = open(data_path + "jails.txt", "r")
16+
l = []
17+
for line in f:
18+
l.append(line)
19+
jail_names = l[2].split(':')[1].strip()
20+
jails = jail_names.split(', ')
21+
return(jails)
22+
23+
def create_jail(jail):
24+
print("Running fail2ban-client status {}".format(jail))
25+
data = os.popen('fail2ban-client status {}'.format(jail)).read()
26+
print("JAILDATA: ", data)
27+
file = open(data_path + "jail_" + jail + ".txt", 'w')
28+
file.write(data)
29+
print("wrote to {}jail_{}.txt\n".format(data_path, jail))
30+
31+
def ban_ip(ip, jail):
32+
print("Banning {} in jail {}".format(ip, jail))
33+
data = os.popen('fail2ban-client set {} banip {}'.format(jail, ip)).read()
34+
print(data)
35+
time.sleep(1.5) # takes time to refresh jail (got diff in vue frontend)
36+
create_jail(jail)
37+
38+
def unban_ip(ip, jail):
39+
print("Unbanning {} in jail {}".format(ip, jail))
40+
data = os.popen('fail2ban-client set {} unbanip {}'.format(jail, ip)).read()
41+
print(data)
42+
time.sleep(1.5) # takes time to refresh jail (got diff in vue frontend)
43+
create_jail(jail)
44+
45+
def generate_files():
46+
status = create_status()
47+
jails = get_jails()
48+
for jail in jails:
49+
create_jail(jail)
50+

api/first_init.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from f2b import generate_files
2+
3+
if __name__ == "__main__":
4+
generate_files()

0 commit comments

Comments
 (0)