| | |
| | | import java.util.regex.Pattern; |
| | | |
| | | /** |
| | | * 规则缓存;入站/出站或关系;record_all 时白名单下也全记 |
| | | * 规则缓存;入站/出站或关系;record_all 仅在该条规则命中时对本条日志生效(默认 -1 不截断) |
| | | */ |
| | | @Slf4j |
| | | public class HttpAuditRuleServiceImpl extends ServiceImpl<HttpAuditRuleMapper, HttpAuditRule> implements HttpAuditRuleService { |
| | | |
| | | private final HttpAuditProperties props; |
| | | private final CopyOnWriteArrayList<HttpAuditRule> cache = new CopyOnWriteArrayList<>(); |
| | | private volatile long lastRefreshAt = 0L; |
| | | |
| | | public HttpAuditRuleServiceImpl(HttpAuditRuleMapper mapper, HttpAuditProperties props) { |
| | | this.baseMapper = mapper; |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Scheduled(fixedDelayString = "${http-audit.rule-cache-refresh-ms:60000}") |
| | | @Scheduled(fixedDelay = 5000) |
| | | public void refreshCache() { |
| | | long now = System.currentTimeMillis(); |
| | | long interval = Math.max(5000L, props.getRuleCacheRefreshMs()); |
| | | if (now - lastRefreshAt < interval) { |
| | | return; |
| | | } |
| | | lastRefreshAt = now; |
| | | try { |
| | | List<HttpAuditRule> list = list(new LambdaQueryWrapper<HttpAuditRule>() |
| | | .eq(HttpAuditRule::getDeleted, 0) |
| | |
| | | @Override |
| | | public HttpAuditDecision decideInbound(HttpServletRequest request, String requestBody) { |
| | | if (!props.isWhitelistOnly()) { |
| | | return HttpAuditDecision.yes(reqLimitFromRecordAllRow(), resLimitFromRecordAllRow()); |
| | | return HttpAuditDecision.yes(null, null); |
| | | } |
| | | if (cache.isEmpty()) { |
| | | return HttpAuditDecision.SKIP; |
| | | } |
| | | HttpAuditRule allRow = firstRecordAllRule(); |
| | | if (allRow != null) { |
| | | return HttpAuditDecision.yes(allRow.getRequestMaxChars(), allRow.getResponseMaxChars()); |
| | | } |
| | | String path = HttpAuditSupport.safePath(request); |
| | | String ip = HttpAuditSupport.clientIp(request); |
| | | String body = requestBody == null ? "" : requestBody; |
| | | for (HttpAuditRule r : cache) { |
| | | if (isRecordAll(r)) { |
| | | continue; |
| | | } |
| | | if (!appliesInbound(r)) { |
| | | continue; |
| | | } |
| | | try { |
| | | if (matchInbound(r, path, ip, body)) { |
| | | return HttpAuditDecision.yes(r.getRequestMaxChars(), r.getResponseMaxChars()); |
| | | return decisionForMatchedRule(r); |
| | | } |
| | | } catch (Exception e) { |
| | | log.debug("http-audit 规则 id={} 匹配异常:{}", r.getId(), e.getMessage()); |
| | |
| | | @Override |
| | | public HttpAuditDecision decideOutbound(String fullUrl, String method, String requestBody) { |
| | | if (!props.isWhitelistOnly()) { |
| | | return HttpAuditDecision.yes(reqLimitFromRecordAllRow(), resLimitFromRecordAllRow()); |
| | | return HttpAuditDecision.yes(null, null); |
| | | } |
| | | if (cache.isEmpty()) { |
| | | return HttpAuditDecision.SKIP; |
| | | } |
| | | HttpAuditRule allRow = firstRecordAllRule(); |
| | | if (allRow != null) { |
| | | return HttpAuditDecision.yes(allRow.getRequestMaxChars(), allRow.getResponseMaxChars()); |
| | | } |
| | | String body = requestBody == null ? "" : requestBody; |
| | | for (HttpAuditRule r : cache) { |
| | | if (isRecordAll(r)) { |
| | | continue; |
| | | } |
| | | if (!appliesOutbound(r)) { |
| | | continue; |
| | | } |
| | | try { |
| | | if (matchOutbound(r, fullUrl, body)) { |
| | | return HttpAuditDecision.yes(r.getRequestMaxChars(), r.getResponseMaxChars()); |
| | | return decisionForMatchedRule(r); |
| | | } |
| | | } catch (Exception e) { |
| | | log.debug("http-audit 出站规则 id={} 匹配异常:{}", r.getId(), e.getMessage()); |
| | |
| | | return HttpAuditDecision.SKIP; |
| | | } |
| | | |
| | | private Integer reqLimitFromRecordAllRow() { |
| | | HttpAuditRule row = firstRecordAllRule(); |
| | | return row == null ? null : row.getRequestMaxChars(); |
| | | } |
| | | |
| | | private Integer resLimitFromRecordAllRow() { |
| | | HttpAuditRule row = firstRecordAllRule(); |
| | | return row == null ? null : row.getResponseMaxChars(); |
| | | } |
| | | |
| | | private HttpAuditRule firstRecordAllRule() { |
| | | for (HttpAuditRule r : cache) { |
| | | if (isRecordAll(r)) { |
| | | return r; |
| | | } |
| | | private static HttpAuditDecision decisionForMatchedRule(HttpAuditRule r) { |
| | | if (isRecordAll(r)) { |
| | | Integer req = r.getRequestMaxChars() != null ? r.getRequestMaxChars() : -1; |
| | | Integer res = r.getResponseMaxChars() != null ? r.getResponseMaxChars() : -1; |
| | | return HttpAuditDecision.yes(req, res); |
| | | } |
| | | return null; |
| | | return HttpAuditDecision.yes(r.getRequestMaxChars(), r.getResponseMaxChars()); |
| | | } |
| | | |
| | | private static boolean isRecordAll(HttpAuditRule r) { |
| | |
| | | } |
| | | |
| | | private static boolean appliesInbound(HttpAuditRule r) { |
| | | if (isRecordAll(r)) { |
| | | return false; |
| | | } |
| | | String d = dir(r); |
| | | return HttpAuditRule.DIR_IN.equals(d) || HttpAuditRule.DIR_BOTH.equals(d); |
| | | } |
| | | |
| | | private static boolean appliesOutbound(HttpAuditRule r) { |
| | | if (isRecordAll(r)) { |
| | | return false; |
| | | } |
| | | String d = dir(r); |
| | | return HttpAuditRule.DIR_OUT.equals(d) || HttpAuditRule.DIR_BOTH.equals(d); |
| | | } |