"""安全评分服务 基于 Agent 上报的安全检查项(SecurityCheck)计算安全评分与加固建议。 评分按检查项加权:pass 满分、warn 半分、fail/unknown 零分。 """ from sqlalchemy import func from sqlmodel import Session, select from app.models.monitor import SecurityCheck # 检查项权重(总和约 100) CHECK_WEIGHTS = {"ssh_config": 40, "firewall": 35, "listening_ports": 25} DEFAULT_WEIGHT = 20 def latest_checks(session: Session, asset_id: int) -> list: """取每个检查项的最新一条""" stmt = ( select(SecurityCheck) .where(SecurityCheck.asset_id == asset_id) .order_by(SecurityCheck.ts.desc()) .limit(50) ) checks = session.exec(stmt).all() latest = {} for check in checks: if check.check_item not in latest: latest[check.check_item] = check return list(latest.values()) def compute_security_score(checks: list): """计算安全评分(0-100),无检查数据返回 None""" if not checks: return None total = 0 earned = 0.0 for check in checks: weight = CHECK_WEIGHTS.get(check.check_item, DEFAULT_WEIGHT) total += weight if check.status == "pass": earned += weight elif check.status == "warn": earned += weight * 0.5 return round(earned / total * 100) if total else None def score_level(score): """评分分级:good(>=80) / warning(>=50) / risk(<50) / unknown""" if score is None: return "unknown" if score >= 80: return "good" if score >= 50: return "warning" return "risk" def get_asset_security(session: Session, asset_id: int) -> dict: """单个服务器的安全评分与加固建议""" checks = latest_checks(session, asset_id) score = compute_security_score(checks) return { "asset_id": asset_id, "score": score, "level": score_level(score), "checks_count": len(checks), "suggestions": [c.suggestion for c in checks if c.suggestion], } def get_security_overview(session: Session) -> list: """所有有安全检查数据的服务器评分总览(按评分升序,风险高的在前) 性能优化:用子查询取每个 (asset_id, check_item) 的最新 ts,仅拉取最新记录, 避免全表扫描(历史数据量大时内存可控)。 """ latest_ts = ( select( SecurityCheck.asset_id, SecurityCheck.check_item, func.max(SecurityCheck.ts).label("max_ts"), ) .group_by(SecurityCheck.asset_id, SecurityCheck.check_item) .subquery() ) stmt = select(SecurityCheck).join( latest_ts, (SecurityCheck.asset_id == latest_ts.c.asset_id) & (SecurityCheck.check_item == latest_ts.c.check_item) & (SecurityCheck.ts == latest_ts.c.max_ts), ) all_checks = session.exec(stmt).all() # 按 asset_id 分组 latest_by_asset: dict = {} for check in all_checks: latest_by_asset.setdefault(check.asset_id, []).append(check) result = [] for asset_id, checks in latest_by_asset.items(): score = compute_security_score(checks) result.append({ "asset_id": asset_id, "score": score, "level": score_level(score), "checks_count": len(checks), "suggestions": [c.suggestion for c in checks if c.suggestion], }) result.sort(key=lambda x: (x["score"] is None, x["score"] if x["score"] is not None else 0)) return result