attstore / api /syslog.py
dn-scribe's picture
fix: require auth, support logs csv
3acb8dd
Raw History Blame Contribute Delete
13 kB
from flask import Blueprint, request, Response, jsonify
from flask_jwt_extended import get_jwt_identity
import csv
from models import SystemLog, User
from app import db
from auth import jwt_required_with_logging, get_current_user_safe
from api.badges import is_admin_user
from logging_utils import log_event, parse_rfc5424_log
syslog_bp = Blueprint('syslog', __name__)
@syslog_bp.route('/syslog', methods=['POST'])
@jwt_required_with_logging
def receive_syslog():
"""Receive RFC 5424 syslog messages - requires JWT authentication per OpenAPI spec"""
try:
content_type = request.content_type or 'text/plain'
if content_type.startswith('application/json'):
# Handle JSON format
data = request.get_json()
if not data:
return jsonify({'error': 'JSON data required'}), 400
# Extract log messages from JSON
message = data.get('syslog_message', [])
if not message:
return jsonify({'error': 'syslog_message field required'}), 400
processed_count = 0
try:
# Parse RFC 5424 message
parsed = parse_rfc5424_log(message)
# Create log entry
log_entry = SystemLog(
facility=parsed['facility'],
severity=parsed['severity'],
version=parsed['version'],
timestamp=parsed['timestamp'],
hostname=parsed['hostname'],
app_name=parsed['app_name'],
proc_id=parsed['proc_id'],
msg_id=parsed['msg_id'],
structured_data=parsed['structured_data'],
message=parsed['message'],
user_id=int(get_jwt_identity()),
source_ip=request.remote_addr,
event_type='syslog_received'
)
db.session.add(log_entry)
processed_count += 1
except Exception as e:
# Log parsing error but continue processing
log_event(
severity=4,
message=f"Failed to parse syslog message: {str(e)}",
event_type="syslog_parse_error",
user_id=int(get_jwt_identity())
)
db.session.commit()
return jsonify({
'message': f'Processed {processed_count} log messages',
'processed_count': processed_count
})
else:
# Handle raw text format (single RFC 5424 message)
message = request.get_data(as_text=True).strip()
if not message:
return jsonify({'error': 'Log message required'}), 400
try:
# Parse RFC 5424 message
parsed = parse_rfc5424_log(message)
# Create log entry
log_entry = SystemLog(
facility=parsed['facility'],
severity=parsed['severity'],
version=parsed['version'],
timestamp=parsed['timestamp'],
hostname=parsed['hostname'],
app_name=parsed['app_name'],
proc_id=parsed['proc_id'],
msg_id=parsed['msg_id'],
structured_data=parsed['structured_data'],
message=parsed['message'],
user_id=int(get_jwt_identity()),
source_ip=request.remote_addr,
event_type='syslog_received'
)
db.session.add(log_entry)
db.session.commit()
return jsonify({
'message': 'Log message processed successfully'
})
except Exception as e:
log_event(
severity=4,
message=f"Failed to process syslog message: {str(e)}",
event_type="syslog_error",
user_id=int(get_jwt_identity())
)
return jsonify({'error': 'Failed to process log message'}), 500
except Exception as e:
try:
user_id = int(get_jwt_identity())
except:
user_id = None
log_event(
severity=3,
message=f"Syslog endpoint error: {str(e)}",
event_type="syslog_endpoint_error",
user_id=user_id
)
return jsonify({'error': 'Internal server error'}), 500
@syslog_bp.route('/logs', methods=['GET'])
@jwt_required_with_logging
def get_logs():
"""Get system logs with filtering"""
try:
# Get query parameters
page = int(request.args.get('page', 1))
per_page = min(int(request.args.get('per_page', 50)), 100)
severity = request.args.get('severity')
app_name = request.args.get('app_name')
event_type = request.args.get('event_type')
hostname = request.args.get('hostname')
search = request.args.get('search')
# Build query
query = SystemLog.query
# Get current user
current_user = get_current_user_safe()
# Filter by user_id unless user is admin
if current_user:
# Check if user is admin using consistent helper function
is_admin = is_admin_user(current_user)
if not is_admin:
# Only show logs for this user
query = query.filter(SystemLog.user_id == current_user.id)
# Apply filters
if severity is not None:
query = query.filter(SystemLog.severity == int(severity))
if app_name:
query = query.filter(SystemLog.app_name == app_name)
if event_type:
query = query.filter(SystemLog.event_type == event_type)
if hostname:
query = query.filter(SystemLog.hostname == hostname)
if search:
query = query.filter(SystemLog.message.contains(search))
# Order by timestamp descending
query = query.order_by(SystemLog.timestamp.desc())
# Get paginated results
pagination = query.paginate(
page=page,
per_page=per_page,
error_out=False
)
logs = []
for log in pagination.items:
log_dict = log.to_dict()
# Add username if user_id exists
if log.user_id:
from models import User
user = User.query.get(log.user_id)
log_dict["username"] = user.username if user else None
else:
log_dict["username"] = None
logs.append(log_dict)
return jsonify({
'logs': logs,
'total': pagination.total,
'pages': pagination.pages,
'current_page': page,
'per_page': per_page
})
except Exception as e:
log_event(
severity=3,
message=f"Failed to get logs: {str(e)}",
event_type="get_logs_error",
user_id=get_current_user_safe().id if get_current_user_safe() else None
)
return jsonify({'error': str(e)}), 500
@syslog_bp.route('/logs/stats', methods=['GET'])
@jwt_required_with_logging
def get_log_stats():
"""Get log statistics"""
try:
# Get current user
current_user = get_current_user_safe()
# Base query
base_query = SystemLog.query
# Filter by user_id unless user is admin
if current_user:
# Check if user is admin using consistent helper function
is_admin = is_admin_user(current_user)
if not is_admin:
# Only show logs for this user
base_query = base_query.filter(SystemLog.user_id == current_user.id)
# Get log counts by severity
severity_stats = db.session.query(
SystemLog.severity,
db.func.count(SystemLog.id).label('count')
)
# Apply user filter if not admin
if current_user and not is_admin_user(current_user):
severity_stats = severity_stats.filter(SystemLog.user_id == current_user.id)
severity_stats = severity_stats.group_by(SystemLog.severity).all()
# Get log counts by event type
event_stats = db.session.query(
SystemLog.event_type,
db.func.count(SystemLog.id).label('count')
)
# Apply user filter if not admin
if current_user and not is_admin_user(current_user):
event_stats = event_stats.filter(SystemLog.user_id == current_user.id)
event_stats = event_stats.group_by(SystemLog.event_type).limit(10).all()
# Get log counts by app name
app_stats = db.session.query(
SystemLog.app_name,
db.func.count(SystemLog.id).label('count')
)
# Apply user filter if not admin
if current_user and not is_admin_user(current_user):
app_stats = app_stats.filter(SystemLog.user_id == current_user.id)
app_stats = app_stats.group_by(SystemLog.app_name).limit(10).all()
# Total logs
total_logs = base_query.count()
return jsonify({
'total_logs': total_logs,
'severity_stats': [{'severity': s[0], 'count': s[1]} for s in severity_stats],
'event_stats': [{'event_type': e[0], 'count': e[1]} for e in event_stats],
'app_stats': [{'app_name': a[0], 'count': a[1]} for a in app_stats]
})
except Exception as e:
log_event(
severity=3,
message=f"Failed to get log stats: {str(e)}",
event_type="get_stats_error",
user_id=get_current_user_safe().id if get_current_user_safe() else None
)
return jsonify({'error': str(e)}), 500
@syslog_bp.route('/logs/csv', methods=['GET'])
@jwt_required_with_logging
def download_logs_csv():
"""Download all filtered logs as CSV (ignores paging)"""
try:
severity = request.args.get('severity')
app_name = request.args.get('app_name')
event_type = request.args.get('event_type')
hostname = request.args.get('hostname')
search = request.args.get('search')
query = SystemLog.query
current_user = get_current_user_safe()
if current_user:
is_admin = is_admin_user(current_user)
if not is_admin:
query = query.filter(SystemLog.user_id == current_user.id)
if severity is not None:
query = query.filter(SystemLog.severity == int(severity))
if app_name:
query = query.filter(SystemLog.app_name == app_name)
if event_type:
query = query.filter(SystemLog.event_type == event_type)
if hostname:
query = query.filter(SystemLog.hostname == hostname)
if search:
query = query.filter(SystemLog.message.contains(search))
logs = query.order_by(SystemLog.timestamp.desc()).all()
# Prepare CSV
output = csv.StringIO() if hasattr(csv, 'StringIO') else __import__('io').StringIO()
writer = csv.writer(output)
headers = [
'Timestamp', 'Severity', 'Event Type', 'App Name', 'Hostname', 'Message', 'User', 'Source IP'
]
writer.writerow(headers)
for log in logs:
username = None
if log.user_id:
user = User.query.get(log.user_id)
username = user.username if user else None
writer.writerow([
log.timestamp.isoformat() if log.timestamp else '',
log.severity,
log.event_type,
log.app_name,
log.hostname,
log.message,
username or '',
log.source_ip or ''
])
output.seek(0)
return Response(
output.getvalue(),
mimetype='text/csv',
headers={
'Content-Disposition': 'attachment; filename="logs.csv"'
}
)
except Exception as e:
log_event(
severity=3,
message=f"Failed to download logs CSV: {str(e)}",
event_type="download_logs_csv_error",
user_id=get_current_user_safe().id if get_current_user_safe() else None
)
return jsonify({'error': str(e)}), 500