Benjamin Renard commited on 2014-12-31 15:04:38
Showing 4 changed files, with 252 additions and 0 deletions.
... | ... |
@@ -0,0 +1 @@ |
1 |
+*~ |
... | ... |
@@ -0,0 +1,35 @@ |
1 |
+Fail2ban action script to stock banned IP in a JSON file |
|
2 |
+======================================================== |
|
3 |
+ |
|
4 |
+This script is design to be use as action script for fail2ban. |
|
5 |
+ |
|
6 |
+Installation |
|
7 |
+------------ |
|
8 |
+ |
|
9 |
+ - Put action.d/jsonfile.conf in /etc/fail2ban/action.d |
|
10 |
+ - Put bin/fail2ban-jsonfile in /usr/local/bin/fail2ban-jsonfile |
|
11 |
+ |
|
12 |
+Configuration |
|
13 |
+------------- |
|
14 |
+ |
|
15 |
+Jail example : |
|
16 |
+ |
|
17 |
+ [myapp] |
|
18 |
+ enabled = true |
|
19 |
+ filter = myapp |
|
20 |
+ action = jsonfile[jsonpath=/var/tmp/myapp-banned-ip.json] |
|
21 |
+ logpath = /var/log/myapp.log |
|
22 |
+ |
|
23 |
+Parameters : |
|
24 |
+ |
|
25 |
+ - bin : |
|
26 |
+ Specify the path to fail2ban-jsonfile script |
|
27 |
+ Default : /usr/local/bin/fail2ban-jsonfile |
|
28 |
+ |
|
29 |
+ - jsonpath : |
|
30 |
+ The path of JSON file |
|
31 |
+ Default : /tmp/fail2ban-jsonfile.json |
|
32 |
+ |
|
33 |
+ - umask : |
|
34 |
+ Umask use on JSON file creation (Integer) |
|
35 |
+ Default : 0077 |
... | ... |
@@ -0,0 +1,66 @@ |
1 |
+# Fail2Ban configuration file |
|
2 |
+# |
|
3 |
+# Author: Benjamin Renard |
|
4 |
+# |
|
5 |
+# $Revision$ |
|
6 |
+# |
|
7 |
+ |
|
8 |
+[Definition] |
|
9 |
+ |
|
10 |
+# Option: actionstart |
|
11 |
+# Notes.: command executed once at the start of Fail2Ban. |
|
12 |
+# Values: CMD |
|
13 |
+# |
|
14 |
+actionstart = <bin> -j <jsonpath> -a start -u <umask> |
|
15 |
+ |
|
16 |
+# Option: actionstop |
|
17 |
+# Notes.: command executed once at the end of Fail2Ban |
|
18 |
+# Values: CMD |
|
19 |
+# |
|
20 |
+actionstop = <bin> -j <jsonpath> -a stop |
|
21 |
+ |
|
22 |
+# Option: actioncheck |
|
23 |
+# Notes.: command executed once before each actionban command |
|
24 |
+# Values: CMD |
|
25 |
+# |
|
26 |
+ |
|
27 |
+# Option: actionban |
|
28 |
+# Notes.: command executed when banning an IP. Take care that the |
|
29 |
+# command is executed with Fail2Ban user rights. |
|
30 |
+# Tags: <ip> IP address |
|
31 |
+# <failures> number of failures |
|
32 |
+# <time> unix timestamp of the ban time |
|
33 |
+# Values: CMD |
|
34 |
+# |
|
35 |
+actionban = <bin> -j <jsonpath> -a ban -i "<ip>" -f "<failures>" -t "<time>" -u <umask> |
|
36 |
+ |
|
37 |
+# Option: actionunban |
|
38 |
+# Notes.: command executed when unbanning an IP. Take care that the |
|
39 |
+# command is executed with Fail2Ban user rights. |
|
40 |
+# Tags: <ip> IP address |
|
41 |
+# <failures> number of failures |
|
42 |
+# <time> unix timestamp of the ban time |
|
43 |
+# Values: CMD |
|
44 |
+# |
|
45 |
+actionunban = <bin> -j <jsonpath> -a unban -i "<ip>" -u <umask> |
|
46 |
+ |
|
47 |
+[Init] |
|
48 |
+ |
|
49 |
+# Option: bin |
|
50 |
+# Note Specify the path to fail2ban-jsonfile script |
|
51 |
+# |
|
52 |
+# Values: Path to fail2ban-jsonfile script Default : /usr/local/bin/fail2ban-jsonfile |
|
53 |
+bin = /usr/local/bin/fail2ban-jsonfile |
|
54 |
+ |
|
55 |
+# Option: jsonpath |
|
56 |
+# Note The path of JSON file |
|
57 |
+# |
|
58 |
+# Values: Path to JSON file Default : /tmp/fail2ban-jsonfile.json |
|
59 |
+jsonpath = /tmp/fail2ban-jsonfile.json |
|
60 |
+ |
|
61 |
+ |
|
62 |
+# Option: umask |
|
63 |
+# Note Umask use on JSON file creation |
|
64 |
+# |
|
65 |
+# Values: INTEGER (UMASK) Default : 0077 |
|
66 |
+umask = 0077 |
... | ... |
@@ -0,0 +1,150 @@ |
1 |
+#!/usr/bin/python |
|
2 |
+# -*- coding: utf-8 -*- |
|
3 |
+ |
|
4 |
+import sys, os |
|
5 |
+import json |
|
6 |
+from optparse import OptionParser |
|
7 |
+import logging |
|
8 |
+ |
|
9 |
+parser = OptionParser() |
|
10 |
+ |
|
11 |
+parser.add_option('-j', |
|
12 |
+ '--json', |
|
13 |
+ action="store", |
|
14 |
+ type="string", |
|
15 |
+ dest="jsonfile", |
|
16 |
+ help="JSON Storage file path") |
|
17 |
+ |
|
18 |
+parser.add_option('-i', |
|
19 |
+ '--ip', |
|
20 |
+ action="store", |
|
21 |
+ type="string", |
|
22 |
+ dest="ip", |
|
23 |
+ help="IP address") |
|
24 |
+ |
|
25 |
+parser.add_option('-a', |
|
26 |
+ '--action', |
|
27 |
+ action="store", |
|
28 |
+ type="string", |
|
29 |
+ dest="action", |
|
30 |
+ help="The action : start / stop / ban / unban / check") |
|
31 |
+ |
|
32 |
+parser.add_option('-f', |
|
33 |
+ '--failures', |
|
34 |
+ action="store", |
|
35 |
+ type="string", |
|
36 |
+ dest="failures", |
|
37 |
+ help="The number of failures") |
|
38 |
+ |
|
39 |
+parser.add_option('-t', |
|
40 |
+ '--time', |
|
41 |
+ action="store", |
|
42 |
+ type="string", |
|
43 |
+ dest="time", |
|
44 |
+ help="The unix timestamp of the ban time") |
|
45 |
+ |
|
46 |
+parser.add_option('-l', |
|
47 |
+ '--log-file', |
|
48 |
+ action="store", |
|
49 |
+ type="string", |
|
50 |
+ dest="logfile", |
|
51 |
+ help="Log file path") |
|
52 |
+ |
|
53 |
+parser.add_option('-u', |
|
54 |
+ '--umask', |
|
55 |
+ action="store", |
|
56 |
+ type="int", |
|
57 |
+ dest="umask", |
|
58 |
+ help="JSON file UMASK", |
|
59 |
+ default=0077) |
|
60 |
+ |
|
61 |
+parser.add_option('-v', |
|
62 |
+ '--verbose', |
|
63 |
+ action="store_true", |
|
64 |
+ dest="verbose", |
|
65 |
+ help="Enable verbose mode") |
|
66 |
+ |
|
67 |
+parser.add_option('-d', |
|
68 |
+ '--debug', |
|
69 |
+ action="store_true", |
|
70 |
+ dest="debug", |
|
71 |
+ help="Enable debug mode") |
|
72 |
+ |
|
73 |
+(options, args) = parser.parse_args() |
|
74 |
+ |
|
75 |
+logformat = '%(asctime)s - %(levelname)s - %(message)s' |
|
76 |
+if options.debug: |
|
77 |
+ loglevel = logging.DEBUG |
|
78 |
+elif options.verbose: |
|
79 |
+ loglevel = logging.INFO |
|
80 |
+else: |
|
81 |
+ loglevel = logging.WARNING |
|
82 |
+ |
|
83 |
+if options.logfile: |
|
84 |
+ logging.basicConfig(filename=options.logfile,level=loglevel,format=logformat) |
|
85 |
+else: |
|
86 |
+ logging.basicConfig(level=loglevel,format=logformat) |
|
87 |
+ |
|
88 |
+if options.action is None or options.jsonfile is None: |
|
89 |
+ logging.error('You must say what you want to do (-a) and the JSON file path (-j).') |
|
90 |
+ sys.exit(1) |
|
91 |
+ |
|
92 |
+if options.action in ['ban','unban','check'] and options.ip is None: |
|
93 |
+ logging.error('You must provide the IP address') |
|
94 |
+ sys.exit(1) |
|
95 |
+ |
|
96 |
+def load_jsonfile(filepath): |
|
97 |
+ try: |
|
98 |
+ json_data = open(filepath) |
|
99 |
+ data = json.load(json_data) |
|
100 |
+ return data |
|
101 |
+ except BaseException,e: |
|
102 |
+ logging.warning('Fail to load JSON file, return empty hash (Error : %s)' % e) |
|
103 |
+ return {} |
|
104 |
+ |
|
105 |
+def write_jsonfile(filepath,data): |
|
106 |
+ try: |
|
107 |
+ os.umask(options.umask) |
|
108 |
+ json_file = open(filepath, 'w') |
|
109 |
+ json.dump(data,json_file) |
|
110 |
+ return True |
|
111 |
+ except BaseException,e: |
|
112 |
+ logging.error('Fail to write JSON file : %s' % e) |
|
113 |
+ return False |
|
114 |
+ |
|
115 |
+if options.action=='start': |
|
116 |
+ # Purge data in JSON file |
|
117 |
+ if not write_jsonfile(options.jsonfile,{}): |
|
118 |
+ logging.error("Fail to purge JSON file") |
|
119 |
+ sys.exit(1) |
|
120 |
+elif options.action=='stop': |
|
121 |
+ # Remove JSON file |
|
122 |
+ try: |
|
123 |
+ os.unlink(options.jsonfile) |
|
124 |
+ except BaseException,e: |
|
125 |
+ logging.error('Fail to remove JSON file : %s' % e) |
|
126 |
+ sys.exit(1) |
|
127 |
+elif options.action=='ban': |
|
128 |
+ data=load_jsonfile(options.jsonfile) |
|
129 |
+ if options.ip not in data: |
|
130 |
+ data[options.ip]={ |
|
131 |
+ 'failures': options.failures, |
|
132 |
+ 'time': options.time |
|
133 |
+ } |
|
134 |
+ if not write_jsonfile(options.jsonfile,data): |
|
135 |
+ sys.exit(1) |
|
136 |
+elif options.action=='unban': |
|
137 |
+ data=load_jsonfile(options.jsonfile) |
|
138 |
+ if options.ip in data: |
|
139 |
+ del data[options.ip] |
|
140 |
+ if not write_jsonfile(options.jsonfile,data): |
|
141 |
+ sys.exit(1) |
|
142 |
+elif options.action=='check': |
|
143 |
+ data=load_jsonfile(options.jsonfile) |
|
144 |
+ if options.ip not in data: |
|
145 |
+ sys.exit(1) |
|
146 |
+else: |
|
147 |
+ logging.error('Invalid action %s. Use -h parameter to get help.' % options.action) |
|
148 |
+ sys.exit(1) |
|
149 |
+ |
|
150 |
+sys.exit(0) |
|
0 | 151 |