chg: [logs] add access logs

otp
terrtia 2024-08-19 15:45:20 +02:00
parent b66859d639
commit ee443ff313
No known key found for this signature in database
GPG Key ID: 1E1B1F50D84613D0
5 changed files with 89 additions and 30 deletions

View File

@ -5,6 +5,7 @@ import os
import json import json
import sys import sys
import logging import logging
import logging.handlers
sys.path.append(os.environ['AIL_BIN']) sys.path.append(os.environ['AIL_BIN'])
################################## ##################################
@ -16,14 +17,37 @@ config_loader = ConfigLoader()
r_db = config_loader.get_db_conn("Kvrocks_DB") r_db = config_loader.get_db_conn("Kvrocks_DB")
config_loader = None config_loader = None
LOGGING_CONFIG = os.path.join(os.environ['AIL_HOME'], 'configs', 'logging.json') LOGGING_CONF_DIR = os.path.join(os.environ['AIL_HOME'], 'configs')
LOGS_DIR = os.path.join(os.environ['AIL_HOME'], 'logs')
def get_config(name=None): def get_config(name=None):
if not name: if not name:
name = 'ail.log' name = 'ail.log'
else: else:
name = f'{name}.log' name = f'{name}.log'
with open(LOGGING_CONFIG, 'r') as f: with open(os.path.join(LOGGING_CONF_DIR, f'logging.json'), 'r') as f:
config = json.load(f) config = json.load(f)
config['handlers']['file']['filename'] = os.path.join(os.environ['AIL_HOME'], 'logs', name) config['handlers']['file']['filename'] = os.path.join(LOGS_DIR, name)
return config return config
def get_access_config(create=False):
logger = logging.getLogger('access.log')
if create:
formatter = logging.Formatter('%(asctime)s - %(ip_address)s - %(levelname)s - %(user_id)s - %(message)s')
# STDOUT
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)
handler.setFormatter(formatter)
logger.addHandler(handler)
# FILE
handler = logging.handlers.RotatingFileHandler(filename=os.path.join(LOGS_DIR, f'access.log'),
maxBytes=10*1024*1024, backupCount=5)
handler.setLevel(logging.INFO)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.propagate = False
return logger

View File

@ -7,7 +7,6 @@ import pyotp
import re import re
import secrets import secrets
import sys import sys
import segno import segno
from base64 import b64encode from base64 import b64encode
@ -20,8 +19,13 @@ sys.path.append(os.environ['AIL_BIN'])
################################## ##################################
# Import Project packages # Import Project packages
################################## ##################################
from lib import ail_logger
from lib.ConfigLoader import ConfigLoader from lib.ConfigLoader import ConfigLoader
# LOGS
access_logger = ail_logger.get_access_config()
# Config # Config
config_loader = ConfigLoader() config_loader = ConfigLoader()
r_serv_db = config_loader.get_db_conn("Kvrocks_DB") r_serv_db = config_loader.get_db_conn("Kvrocks_DB")
@ -295,7 +299,7 @@ def disable_user(user_id):
def enable_user(user_id): def enable_user(user_id):
r_serv_db.srem(f'ail:users:disabled', user_id) r_serv_db.srem(f'ail:users:disabled', user_id)
def create_user(user_id, password=None, admin_id=None, chg_passwd=True, role=None, otp=False): # TODO LOGS def create_user(user_id, password=None, admin_id=None, chg_passwd=True, role=None, otp=False):
# # TODO: check password strength # # TODO: check password strength
if password: if password:
new_password = password new_password = password
@ -568,15 +572,15 @@ def api_get_user_hotp(user_id):
hotp = get_user_hotp_code(user_id) hotp = get_user_hotp_code(user_id)
return hotp, 200 return hotp, 200
def api_logout_user(admin_id, user_id): # TODO LOG ADMIN ID def api_logout_user(admin_id, user_id, ip_address):
user = AILUser(user_id) user = AILUser(user_id)
if not user.exists(): if not user.exists():
return {'status': 'error', 'reason': 'User not found'}, 404 return {'status': 'error', 'reason': 'User not found'}, 404
print(admin_id) access_logger.info(f'Logout user {user_id}', extra={'user_id': admin_id, 'ip_address': ip_address})
return user.kill_session(), 200 return user.kill_session(), 200
def api_logout_users(admin_id): # TODO LOG ADMIN ID def api_logout_users(admin_id, ip_address):
print(admin_id) access_logger.info('Logout all users', extra={'user_id': admin_id, 'ip_address': ip_address})
return kill_sessions(), 200 return kill_sessions(), 200
def api_disable_user(admin_id, user_id): # TODO LOG ADMIN ID def api_disable_user(admin_id, user_id): # TODO LOG ADMIN ID
@ -597,7 +601,7 @@ def api_enable_user(admin_id, user_id): # TODO LOG ADMIN ID
print(admin_id) print(admin_id)
enable_user(user_id) enable_user(user_id)
def api_enable_user_otp(user_id): def api_enable_user_otp(user_id, ip_address):
user = AILUser(user_id) user = AILUser(user_id)
if not user.exists(): if not user.exists():
return {'status': 'error', 'reason': 'User not found'}, 404 return {'status': 'error', 'reason': 'User not found'}, 404
@ -607,7 +611,7 @@ def api_enable_user_otp(user_id):
enable_user_2fa(user_id) enable_user_2fa(user_id)
return user_id, 200 return user_id, 200
def api_disable_user_otp(user_id): def api_disable_user_otp(user_id, ip_address):
user = AILUser(user_id) user = AILUser(user_id)
if not user.exists(): if not user.exists():
return {'status': 'error', 'reason': 'User not found'}, 404 return {'status': 'error', 'reason': 'User not found'}, 404
@ -619,7 +623,7 @@ def api_disable_user_otp(user_id):
delete_user_otp(user_id) delete_user_otp(user_id)
return user_id, 200 return user_id, 200
def api_reset_user_otp(admin_id, user_id): def api_reset_user_otp(admin_id, user_id, ip_address): # TODO LOGS
user = AILUser(user_id) user = AILUser(user_id)
if not user.exists(): if not user.exists():
return {'status': 'error', 'reason': 'User not found'}, 404 return {'status': 'error', 'reason': 'User not found'}, 404
@ -632,24 +636,29 @@ def api_reset_user_otp(admin_id, user_id):
enable_user_2fa(user_id) enable_user_2fa(user_id)
return user_id, 200 return user_id, 200
def api_create_user_api_key_self(user_id): # TODO LOG USER ID def api_create_user_api_key_self(user_id, ip_address):
user = AILUser(user_id) user = AILUser(user_id)
if not user.exists(): if not user.exists():
return {'status': 'error', 'reason': 'User not found'}, 404 return {'status': 'error', 'reason': 'User not found'}, 404
access_logger.info('New api key', extra={'user_id': user_id, 'ip_address': ip_address})
return user.new_api_key(), 200 return user.new_api_key(), 200
def api_create_user_api_key(user_id, admin_id): # TODO LOG ADMIN ID def api_create_user_api_key(user_id, admin_id, ip_address):
user = AILUser(user_id) user = AILUser(user_id)
if not user.exists(): if not user.exists():
return {'status': 'error', 'reason': 'User not found'}, 404 return {'status': 'error', 'reason': 'User not found'}, 404
print(admin_id) access_logger.info(f'New api key for user {user_id}', extra={'user_id': admin_id, 'ip_address': ip_address})
return user.new_api_key(), 200 return user.new_api_key(), 200
def api_delete_user(user_id, admin_id): # TODO LOG ADMIN ID def api_create_user(admin_id, ip_address, user_id, password, role, otp):
create_user(user_id, password=password, admin_id=admin_id, role=role, otp=otp)
access_logger.info(f'Create user {user_id}', extra={'user_id': admin_id, 'ip_address': ip_address})
def api_delete_user(user_id, admin_id, ip_address):
user = AILUser(user_id) user = AILUser(user_id)
if not user.exists(): if not user.exists():
return {'status': 'error', 'reason': 'User not found'}, 404 return {'status': 'error', 'reason': 'User not found'}, 404
print(admin_id) access_logger.info(f'Delete user {user_id}', extra={'user_id': admin_id, 'ip_address': ip_address})
return user.delete(), 200 return user.delete(), 200
######################################################################################################################## ########################################################################################################################

View File

@ -83,6 +83,8 @@ if not os.path.isdir(log_dir):
# ========= LOGS =========# # ========= LOGS =========#
access_logger = ail_logger.get_access_config(create=True)
class FilterLogErrors(logging.Filter): class FilterLogErrors(logging.Filter):
def filter(self, record): def filter(self, record):
# print(dict(record.__dict__)) # print(dict(record.__dict__))

View File

@ -24,6 +24,7 @@ sys.path.append(os.environ['AIL_BIN'])
################################## ##################################
from lib.ail_users import AILUser, kill_sessions, create_user, check_password_strength, check_user_role_integrity from lib.ail_users import AILUser, kill_sessions, create_user, check_password_strength, check_user_role_integrity
from lib.ConfigLoader import ConfigLoader from lib.ConfigLoader import ConfigLoader
from lib import ail_logger
# Config # Config
@ -34,6 +35,9 @@ config_loader = None
# Kill previous sessions # Kill previous sessions
kill_sessions() kill_sessions()
# LOGS
access_logger = ail_logger.get_access_config()
# ============ BLUEPRINT ============ # ============ BLUEPRINT ============
@ -56,6 +60,10 @@ def login():
login_failed_ip = int(login_failed_ip) login_failed_ip = int(login_failed_ip)
if login_failed_ip >= 5: if login_failed_ip >= 5:
wait_time = r_cache.ttl(f'failed_login_ip:{current_ip}') wait_time = r_cache.ttl(f'failed_login_ip:{current_ip}')
username = request.form.get('username')
if not username:
username = ''
access_logger.warning(f'Brute Force', extra={'user_id': username, 'ip_address': current_ip})
logging_error = f'Max Connection Attempts reached, Please wait {wait_time}s' logging_error = f'Max Connection Attempts reached, Please wait {wait_time}s'
return render_template("login.html", error=logging_error) return render_template("login.html", error=logging_error)
@ -68,7 +76,7 @@ def login():
return render_template("login.html", error='Password is required.') return render_template("login.html", error='Password is required.')
if username is not None: if username is not None:
user = AILUser.get(username) # TODO ANONYMOUS USER user = AILUser.get(username)
# brute force by user_id # brute force by user_id
login_failed_user_id = r_cache.get(f'failed_login_user_id:{username}') login_failed_user_id = r_cache.get(f'failed_login_user_id:{username}')
@ -76,12 +84,14 @@ def login():
login_failed_user_id = int(login_failed_user_id) login_failed_user_id = int(login_failed_user_id)
if login_failed_user_id >= 5: if login_failed_user_id >= 5:
wait_time = r_cache.ttl(f'failed_login_user_id:{username}') wait_time = r_cache.ttl(f'failed_login_user_id:{username}')
access_logger.warning(f'Max login attempts reached', extra={'user_id': user.get_user_id(), 'ip_address': current_ip})
logging_error = f'Max Connection Attempts reached, Please wait {wait_time}s' logging_error = f'Max Connection Attempts reached, Please wait {wait_time}s'
return render_template("login.html", error=logging_error) return render_template("login.html", error=logging_error)
if user.exists() and user.check_password(password): if user.exists() and user.check_password(password):
if not check_user_role_integrity(user.get_user_id()): if not check_user_role_integrity(user.get_user_id()):
logging_error = 'Incorrect User ACL, Please contact your administrator' logging_error = 'Incorrect User ACL, Please contact your administrator'
access_logger.info(f'Login fail: Invalid ACL', extra={'user_id': user.get_user_id(), 'ip_address': current_ip})
return render_template("login.html", error=logging_error) return render_template("login.html", error=logging_error)
if user.is_2fa_enabled(): if user.is_2fa_enabled():
@ -92,6 +102,7 @@ def login():
if not user.is_2fa_setup(): if not user.is_2fa_setup():
return redirect(url_for('root.setup_2fa')) return redirect(url_for('root.setup_2fa'))
else: else:
access_logger.info(f'First Login', extra={'user_id': user.get_user_id(), 'ip_address': current_ip})
if next_page and next_page != 'None' and next_page != '/': if next_page and next_page != 'None' and next_page != '/':
return redirect(url_for('root.verify_2fa', next=next_page)) return redirect(url_for('root.verify_2fa', next=next_page))
else: else:
@ -102,6 +113,7 @@ def login():
user.rotate_session() user.rotate_session()
login_user(user) login_user(user)
user.update_last_login() user.update_last_login()
access_logger.info(f'Login', extra={'user_id': user.get_user_id(), 'ip_address': current_ip})
if user.request_password_change(): if user.request_password_change():
return redirect(url_for('root.change_password')) return redirect(url_for('root.change_password'))
@ -124,6 +136,8 @@ def login():
r_cache.expire(f'failed_login_user_id:{username}', 300) r_cache.expire(f'failed_login_user_id:{username}', 300)
# #
access_logger.info(f'Login Failed', extra={'user_id': user.get_user_id(), 'ip_address': request.remote_addr})
logging_error = 'Login/Password Incorrect' logging_error = 'Login/Password Incorrect'
return render_template("login.html", error=logging_error) return render_template("login.html", error=logging_error)
@ -150,6 +164,7 @@ def verify_2fa():
if otp_expire < int(time.time()): # TODO LOG if otp_expire < int(time.time()): # TODO LOG
session.pop('user_id', None) session.pop('user_id', None)
session.pop('otp_expire', None) session.pop('otp_expire', None)
access_logger.info(f'First Login Expired', extra={'user_id': user_id, 'ip_address': request.remote_addr})
error = "First Login Expired" error = "First Login Expired"
return redirect(url_for('root.login', error=error)) return redirect(url_for('root.login', error=error))
@ -171,6 +186,8 @@ def verify_2fa():
login_user(user) login_user(user)
user.update_last_login() user.update_last_login()
access_logger.info(f'2FA login', extra={'user_id': user.get_user_id(), 'ip_address': request.remote_addr})
if user.request_password_change(): if user.request_password_change():
return redirect(url_for('root.change_password')) return redirect(url_for('root.change_password'))
else: else:
@ -180,6 +197,7 @@ def verify_2fa():
return redirect(url_for('dashboard.index')) return redirect(url_for('dashboard.index'))
else: else:
htop_counter = user.get_htop_counter() htop_counter = user.get_htop_counter()
access_logger.info(f'Invalid OTP', extra={'user_id': user.get_user_id(), 'ip_address': request.remote_addr})
error = "The OTP is incorrect or has expired" error = "The OTP is incorrect or has expired"
return render_template("verify_otp.html", htop_counter=htop_counter, next_page=next_page, error=error) return render_template("verify_otp.html", htop_counter=htop_counter, next_page=next_page, error=error)
@ -200,6 +218,7 @@ def setup_2fa():
if otp_expire < int(time.time()): # TODO LOG if otp_expire < int(time.time()): # TODO LOG
session.pop('user_id', None) session.pop('user_id', None)
session.pop('otp_expire', None) session.pop('otp_expire', None)
access_logger.info(f'First Login Expired', extra={'user_id': user_id, 'ip_address': request.remote_addr})
error = "First Login Expired" error = "First Login Expired"
return redirect(url_for('root.login', error=error)) return redirect(url_for('root.login', error=error))
@ -222,11 +241,14 @@ def setup_2fa():
login_user(user) login_user(user)
user.update_last_login() user.update_last_login()
access_logger.info(f'2FA login', extra={'user_id': user.get_user_id(), 'ip_address': request.remote_addr})
if user.request_password_change(): if user.request_password_change():
return redirect(url_for('root.change_password')) return redirect(url_for('root.change_password'))
else: else:
return redirect(url_for('dashboard.index')) return redirect(url_for('dashboard.index'))
else: else:
access_logger.info(f'OTP Invalid', extra={'user_id': user.get_user_id(), 'ip_address': request.remote_addr})
error = "The OTP is incorrect or has expired" error = "The OTP is incorrect or has expired"
return redirect(url_for('root.setup_2fa', error=error)) return redirect(url_for('root.setup_2fa', error=error))
else: else:
@ -252,6 +274,7 @@ def change_password():
if check_password_strength(password1): if check_password_strength(password1):
user_id = current_user.get_user_id() user_id = current_user.get_user_id()
create_user(user_id, password=password1, chg_passwd=False) # TODO RENAME ME create_user(user_id, password=password1, chg_passwd=False) # TODO RENAME ME
access_logger.info(f'Password change', extra={'user_id': user_id, 'ip_address': request.remote_addr})
# update Note # update Note
# dashboard # dashboard
return redirect(url_for('dashboard.index', update_note=True)) return redirect(url_for('dashboard.index', update_note=True))
@ -268,6 +291,7 @@ def change_password():
@root.route('/logout') @root.route('/logout')
@login_required @login_required
def logout(): def logout():
access_logger.info(f'Logout', extra={'user_id': current_user.get_user_id(), 'ip_address': request.remote_addr})
current_user.kill_session() current_user.kill_session()
logout_user() logout_user()
return redirect(url_for('root.login')) return redirect(url_for('root.login'))

View File

@ -92,7 +92,7 @@ def user_hotp():
@login_read_only @login_read_only
def user_otp_enable_self(): def user_otp_enable_self():
user_id = current_user.get_user_id() user_id = current_user.get_user_id()
r = ail_users.api_enable_user_otp(user_id) r = ail_users.api_enable_user_otp(user_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
current_user.kill_session() current_user.kill_session()
@ -103,7 +103,7 @@ def user_otp_enable_self():
@login_read_only @login_read_only
def user_otp_disable_self(): def user_otp_disable_self():
user_id = current_user.get_user_id() user_id = current_user.get_user_id()
r = ail_users.api_disable_user_otp(user_id) r = ail_users.api_disable_user_otp(user_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
current_user.kill_session() current_user.kill_session()
@ -114,7 +114,7 @@ def user_otp_disable_self():
@login_admin @login_admin
def user_otp_reset_self(): # TODO ask for password ? def user_otp_reset_self(): # TODO ask for password ?
user_id = current_user.get_user_id() user_id = current_user.get_user_id()
r = ail_users.api_reset_user_otp(user_id, user_id) r = ail_users.api_reset_user_otp(user_id, user_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
else: else:
@ -126,7 +126,7 @@ def user_otp_reset_self(): # TODO ask for password ?
@login_admin @login_admin
def user_otp_enable(): def user_otp_enable():
user_id = request.args.get('user_id') user_id = request.args.get('user_id')
r = ail_users.api_enable_user_otp(user_id) r = ail_users.api_enable_user_otp(user_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
user = ail_users.AILUser.get(user_id) user = ail_users.AILUser.get(user_id)
@ -138,7 +138,7 @@ def user_otp_enable():
@login_admin @login_admin
def user_otp_disable(): def user_otp_disable():
user_id = request.args.get('user_id') user_id = request.args.get('user_id')
r = ail_users.api_disable_user_otp(user_id) r = ail_users.api_disable_user_otp(user_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
user = ail_users.AILUser.get(user_id) user = ail_users.AILUser.get(user_id)
@ -151,7 +151,7 @@ def user_otp_disable():
def user_otp_reset(): # TODO ask for password ? def user_otp_reset(): # TODO ask for password ?
user_id = request.args.get('user_id') user_id = request.args.get('user_id')
admin_id = current_user.get_user_id() admin_id = current_user.get_user_id()
r = ail_users.api_reset_user_otp(admin_id, user_id) r = ail_users.api_reset_user_otp(admin_id, user_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
else: else:
@ -164,7 +164,7 @@ def user_otp_reset(): # TODO ask for password ?
@login_read_only @login_read_only
def new_token_user_self(): def new_token_user_self():
user_id = current_user.get_user_id() user_id = current_user.get_user_id()
r = ail_users.api_create_user_api_key_self(user_id) r = ail_users.api_create_user_api_key_self(user_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
else: else:
@ -176,7 +176,7 @@ def new_token_user_self():
def new_token_user(): def new_token_user():
user_id = request.args.get('user_id') user_id = request.args.get('user_id')
admin_id = current_user.get_user_id() admin_id = current_user.get_user_id()
r = ail_users.api_create_user_api_key(user_id, admin_id) r = ail_users.api_create_user_api_key(user_id, admin_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
else: else:
@ -188,7 +188,7 @@ def new_token_user():
def user_logout(): def user_logout():
user_id = request.args.get('user_id') # TODO LOGS user_id = request.args.get('user_id') # TODO LOGS
admin_id = current_user.get_user_id() admin_id = current_user.get_user_id()
r = ail_users.api_logout_user(admin_id, user_id) r = ail_users.api_logout_user(admin_id, user_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
else: else:
@ -199,7 +199,7 @@ def user_logout():
@login_admin @login_admin
def users_logout(): def users_logout():
admin_id = current_user.get_user_id() # TODO LOGS admin_id = current_user.get_user_id() # TODO LOGS
r = ail_users.api_logout_users(admin_id) r = ail_users.api_logout_users(admin_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
else: else:
@ -271,7 +271,7 @@ def create_user_post():
if not password1 and not password2: if not password1 and not password2:
password = None password = None
str_password = 'Password not changed' str_password = 'Password not changed'
ail_users.create_user(email, password=password, admin_id=admin_id, role=role, otp=enable_2_fa) ail_users.api_create_user(admin_id, request.remote_addr, email, password, role, enable_2_fa)
new_user = {'email': email, 'password': str_password, 'otp': enable_2_fa} new_user = {'email': email, 'password': str_password, 'otp': enable_2_fa}
return render_template("create_user.html", new_user=new_user, meta={}, all_roles=all_roles, acl_admin=True) return render_template("create_user.html", new_user=new_user, meta={}, all_roles=all_roles, acl_admin=True)
@ -288,7 +288,7 @@ def create_user_post():
def delete_user(): def delete_user():
user_id = request.args.get('user_id') user_id = request.args.get('user_id')
admin_id = current_user.get_user_id() admin_id = current_user.get_user_id()
r = ail_users.api_delete_user(user_id, admin_id) r = ail_users.api_delete_user(user_id, admin_id, request.remote_addr)
if r[1] != 200: if r[1] != 200:
return create_json_response(r[0], r[1]) return create_json_response(r[0], r[1])
else: else: