在配置fail2ban的时候遇到很多坑,现在记录下

先参考一篇博客的教程:

​https://atjason.com/IT/ss_fail2ban.html​

可以使用​​echo​​命令来模拟日志的写入,不需要搭建日志服务

echo '2016-07-17 18:05:03 ERROR can not parse header when handling connection from 112.10.137.6:20463' >> /var/log/shadowsocks1.log

其中需要注意的一点 是,​​2016-07-17 18:05:03​​时间不能随便设置,时间与当前时间不能相差太多,不然fail2ban 总是忽略.

如果按照上边的教程能够实现,再来尝试odoo服务.

odoo 用户登录使用odoo自带的功能即可,详情可参考上一篇博客
主要是数据库密码的防暴力破解,看了下源码,odoo并没有做相关的防范措施,要是被人恶意破解开,感觉就是灾难.
odoo 数据库密码可以尝试的方式有:备份,复制,删除,创建,还原,更改密码,所以要把控好这几入口
而且就算尝试密码错误后,后台日志没有记录,添加日志需要修改源码.

关于odoo 日志的时间

日志的时间使用的utc时区,与系统的时间总是相差8个小时,这点也需要做调整:
调整的方式参考:​​​http://chenliy.com/post/330​

调整的相关源码:

调整时区

import datetime

class DBFormatter(logging.Formatter):
def format(self, record):
record.pid = os.getpid()
record.dbname = getattr(threading.current_thread(), 'dbname', '?')
return logging.Formatter.format(self, record)

def formatTime(self, record, datefmt=None):
now = datetime.datetime.now() + datetime.timedelta(hours=8)
ct = time.localtime(time.mktime(now.timetuple()))
print(time.tzname)
if datefmt:
s = time.strftime(datefmt, ct)
else:
t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
s = "%s,%03d" % (t, record.msecs)
return s

添加日志打印

@http.route('/web/database/create', type='http', auth="none", methods=['POST'], csrf=False)
def create(self, master_pwd, name, lang, password, **post):
try:
if not re.match(DBNAME_PATTERN, name):
raise Exception(_('Invalid database name. Only alphanumerical characters, underscore, hyphen and dot are allowed.'))
# country code could be = "False" which is actually True in python
country_code = post.get('country_code') or False
dispatch_rpc('db', 'create_database', [master_pwd, name, bool(post.get('demo')), lang, password, post['login'], country_code, post['phone']])
request.session.authenticate(name, post['login'], password)
return http.local_redirect('/web/')
except Exception as e:
error = "Database creation error: %s" % (str(e) or repr(e))
_logger.info('{} -Action:database create, database auth failed'.format(request.httprequest.remote_addr))
return self._render_template(error=error)

@http.route('/web/database/duplicate', type='http', auth="none", methods=['POST'], csrf=False)
def duplicate(self, master_pwd, name, new_name):
try:
if not re.match(DBNAME_PATTERN, new_name):
raise Exception(_('Invalid database name. Only alphanumerical characters, underscore, hyphen and dot are allowed.'))
dispatch_rpc('db', 'duplicate_database', [master_pwd, name, new_name])
return http.local_redirect('/web/database/manager')
except Exception as e:
error = "Database duplication error: %s" % (str(e) or repr(e))
_logger.info('{} -Action:database duplicate, database auth failed'.format(request.httprequest.remote_addr))
return self._render_template(error=error)

@http.route('/web/database/drop', type='http', auth="none", methods=['POST'], csrf=False)
def drop(self, master_pwd, name):
try:
dispatch_rpc('db','drop', [master_pwd, name])
request._cr = None # dropping a database leads to an unusable cursor
return http.local_redirect('/web/database/manager')
except Exception as e:
error = "Database deletion error: %s" % (str(e) or repr(e))
_logger.info('{} -Action:database drop, database auth failed'.format(request.httprequest.remote_addr))

return self._render_template(error=error)

@http.route('/web/database/backup', type='http', auth="none", methods=['POST'], csrf=False)
def backup(self, master_pwd, name, backup_format = 'zip'):
try:
odoo.service.db.check_super(master_pwd)
ts = datetime.datetime.utcnow().strftime("%Y-%m-%d_%H-%M-%S")
filename = "%s_%s.%s" % (name, ts, backup_format)
headers = [
('Content-Type', 'application/octet-stream; charset=binary'),
('Content-Disposition', content_disposition(filename)),
]
dump_stream = odoo.service.db.dump_db(name, None, backup_format)
response = werkzeug.wrappers.Response(dump_stream, headers=headers, direct_passthrough=True)
return response
except Exception as e:
_logger.exception('Database.backup')
error = "Database backup error: %s" % (str(e) or repr(e))
_logger.info('{} - Action:database backup, database auth failed'.format(request.httprequest.remote_addr))

return self._render_template(error=error)

@http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False)
def restore(self, master_pwd, backup_file, name, copy=False):
try:
data_file = None
db.check_super(master_pwd)
with tempfile.NamedTemporaryFile(delete=False) as data_file:
backup_file.save(data_file)
db.restore_db(name, data_file.name, str2bool(copy))
return http.local_redirect('/web/database/manager')
except Exception as e:
error = "Database restore error: %s" % (str(e) or repr(e))
_logger.info('{} - Action:database restore, database auth failed'.format(request.httprequest.remote_addr))

return self._render_template(error=error)
finally:
if data_file:
os.unlink(data_file.name)

@http.route('/web/database/change_password', type='http', auth="none", methods=['POST'], csrf=False)
def change_password(self, master_pwd, master_pwd_new):
try:
dispatch_rpc('db', 'change_admin_password', [master_pwd, master_pwd_new])
return http.local_redirect('/web/database/manager')
except Exception as e:
error = "Master password update error: %s" % (str(e) or repr(e))
_logger.info('{} - Action:database change_password, database auth failed'.format(request.httprequest.remote_addr))

return self._render_template(error=error)

fail2ban 配置:

# /etc/fail2ban/jail.local
[odoo]
enabled = true
port = http,https
bantime = 8100 # 900s (15 min) + 7200s (2 hours diffecence in odoo log and systemtime)
maxretry = 4
findtime = 8100 # 900s (15 min) + 7200s (2 hours diffecence in odoo log and systemtime)
# logpath = /var/log/test.log
logpath = /opt/logs/odoo/odoo12_dev_stdout.log
# /etc/fail2ban/filter.d/odoo.conf
[INCLUDES]

# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf

[DEFAULT]

_daemon = odoo

[Definition]
failregex = odoo.addons.web.controllers.main: <HOST> -Action:database .*?, database auth
ignoreregex =

# maxlines = 1

# journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd

重启fail2ban :​​service fail2ban reload​​​ 到这里不出意外的话,可以看到fail2ban 的测试日志:​​fail2ban set loglevel TRACEDEBUG​

odoo 结合fail2ban_odoo

问题延申:

  1. odoo服务是由其他nginx 服务器反向代理的, 那么这么配置就没有太大的意义,目前觉得可行的办法是将当前的日志文件共享到nginx 所在的服务器中,然后使用nginx 服务器的fail2ban 来禁用ip

懂得,原来世界如此简单!