odoo 结合fail2ban

在配置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

image

问题延申:

  1. odoo服务是由其他nginx 服务器反向代理的, 那么这么配置就没有太大的意义,目前觉得可行的办法是将当前的日志文件共享到nginx 所在的服务器中,然后使用nginx 服务器的fail2ban 来禁用ip
posted @ 2022-02-18 15:52  那时一个人  阅读(291)  评论(0)    收藏  举报