remove props, set ranges

This commit is contained in:
Anthony Stirling 2025-06-16 23:38:41 +01:00
parent 561003f9af
commit bb04361c77
7 changed files with 107 additions and 40 deletions

View File

@ -37,12 +37,13 @@ public class AuditAspect {
Method method = signature.getMethod(); Method method = signature.getMethod();
Audited auditedAnnotation = method.getAnnotation(Audited.class); Audited auditedAnnotation = method.getAnnotation(Audited.class);
// Use unified check to determine if we should audit // Fast path: use unified check to determine if we should audit
// This avoids all data collection if auditing is disabled
if (!AuditUtils.shouldAudit(method, auditConfig)) { if (!AuditUtils.shouldAudit(method, auditConfig)) {
return joinPoint.proceed(); return joinPoint.proceed();
} }
// Use AuditUtils to create the base audit data // Only create the map once we know we'll use it
Map<String, Object> auditData = AuditUtils.createBaseAuditData(joinPoint, auditedAnnotation.level()); Map<String, Object> auditData = AuditUtils.createBaseAuditData(joinPoint, auditedAnnotation.level());
// Add HTTP information if we're in a web context // Add HTTP information if we're in a web context
@ -80,7 +81,8 @@ public class AuditAspect {
auditConfig.getAuditLevel() == AuditLevel.VERBOSE); auditConfig.getAuditLevel() == AuditLevel.VERBOSE);
if (includeResult && result != null) { if (includeResult && result != null) {
auditData.put("result", result.toString()); // Use safe string conversion with size limiting
auditData.put("result", AuditUtils.safeToString(result, 1000));
} }
return result; return result;

View File

@ -65,12 +65,16 @@ public enum AuditLevel {
* @return The corresponding AuditLevel * @return The corresponding AuditLevel
*/ */
public static AuditLevel fromInt(int level) { public static AuditLevel fromInt(int level) {
// Ensure level is within valid bounds
int boundedLevel = Math.min(Math.max(level, 0), 3);
for (AuditLevel auditLevel : values()) { for (AuditLevel auditLevel : values()) {
if (auditLevel.level == level) { if (auditLevel.level == boundedLevel) {
return auditLevel; return auditLevel;
} }
} }
// Default to STANDARD if invalid level
// Default to STANDARD if somehow we didn't match
return STANDARD; return STANDARD;
} }
} }

View File

@ -23,6 +23,8 @@ import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import org.apache.commons.lang3.StringUtils;
/** /**
* Shared utilities for audit aspects to ensure consistent behavior * Shared utilities for audit aspects to ensure consistent behavior
* across different audit mechanisms. * across different audit mechanisms.
@ -69,18 +71,25 @@ public class AuditUtils {
* @param auditLevel The current audit level * @param auditLevel The current audit level
*/ */
public static void addHttpData(Map<String, Object> data, String httpMethod, String path, AuditLevel auditLevel) { public static void addHttpData(Map<String, Object> data, String httpMethod, String path, AuditLevel auditLevel) {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (httpMethod == null || path == null) {
if (attrs == null) { return; // Skip if we don't have basic HTTP info
return;
} }
HttpServletRequest req = attrs.getRequest();
HttpServletResponse resp = attrs.getResponse();
// BASIC level HTTP data // BASIC level HTTP data
data.put("httpMethod", httpMethod); data.put("httpMethod", httpMethod);
data.put("path", path); data.put("path", path);
// Get request attributes safely
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs == null) {
return; // No request context available
}
HttpServletRequest req = attrs.getRequest();
if (req == null) {
return; // No request available
}
// STANDARD level HTTP data // STANDARD level HTTP data
if (auditLevel.includes(AuditLevel.STANDARD)) { if (auditLevel.includes(AuditLevel.STANDARD)) {
data.put("clientIp", req.getRemoteAddr()); data.put("clientIp", req.getRemoteAddr());
@ -150,11 +159,56 @@ public class AuditUtils {
Object[] vals = joinPoint.getArgs(); Object[] vals = joinPoint.getArgs();
if (names != null && vals != null) { if (names != null && vals != null) {
IntStream.range(0, names.length) IntStream.range(0, names.length)
.forEach(i -> data.put("arg_" + names[i], vals[i])); .forEach(i -> {
if (vals[i] != null) {
// Convert objects to safe string representation
data.put("arg_" + names[i], safeToString(vals[i], 500));
} else {
data.put("arg_" + names[i], null);
}
});
} }
} }
} }
/**
* Safely convert an object to string with size limiting
*
* @param obj The object to convert
* @param maxLength Maximum length of the resulting string
* @return A safe string representation, truncated if needed
*/
public static String safeToString(Object obj, int maxLength) {
if (obj == null) {
return "null";
}
String result;
try {
// Handle common types directly to avoid toString() overhead
if (obj instanceof String) {
result = (String) obj;
} else if (obj instanceof Number || obj instanceof Boolean) {
result = obj.toString();
} else if (obj instanceof byte[]) {
result = "[binary data length=" + ((byte[]) obj).length + "]";
} else {
// For complex objects, use toString but handle exceptions
result = obj.toString();
}
// Truncate if necessary
if (result != null && result.length() > maxLength) {
return StringUtils.truncate(result, maxLength - 3) + "...";
}
return result;
} catch (Exception e) {
// If toString() fails, return the class name
return "[" + obj.getClass().getName() + " - toString() failed]";
}
}
/** /**
* Determine if a method should be audited based on config and annotation * Determine if a method should be audited based on config and annotation
* *
@ -163,20 +217,19 @@ public class AuditUtils {
* @return true if the method should be audited * @return true if the method should be audited
*/ */
public static boolean shouldAudit(Method method, AuditConfigurationProperties auditConfig) { public static boolean shouldAudit(Method method, AuditConfigurationProperties auditConfig) {
// First check if audit is globally enabled // First check if audit is globally enabled - fast path
if (!auditConfig.isEnabled()) { if (!auditConfig.isEnabled()) {
return false; return false;
} }
// Check for annotation override // Check for annotation override
Audited auditedAnnotation = method.getAnnotation(Audited.class); Audited auditedAnnotation = method.getAnnotation(Audited.class);
if (auditedAnnotation != null) { AuditLevel requiredLevel = (auditedAnnotation != null)
// Method has @Audited - check if the specific level is enabled ? auditedAnnotation.level()
return auditConfig.isLevelEnabled(auditedAnnotation.level()); : AuditLevel.BASIC;
}
// Check if the required level is enabled
// No annotation - use global level for controllers return auditConfig.getAuditLevel().includes(requiredLevel);
return auditConfig.isLevelEnabled(AuditLevel.BASIC);
} }
/** /**
@ -198,7 +251,11 @@ public class AuditUtils {
// Add HTTP status code if available // Add HTTP status code if available
if (response != null) { if (response != null) {
data.put("statusCode", response.getStatus()); try {
data.put("statusCode", response.getStatus());
} catch (Exception e) {
// Ignore - response might be in an inconsistent state
}
} }
} }
} }
@ -220,7 +277,7 @@ public class AuditUtils {
} }
// For HTTP methods, infer based on controller and path // For HTTP methods, infer based on controller and path
if (httpMethod != null) { if (httpMethod != null && path != null) {
String cls = controller.getSimpleName().toLowerCase(); String cls = controller.getSimpleName().toLowerCase();
String pkg = controller.getPackage().getName().toLowerCase(); String pkg = controller.getPackage().getName().toLowerCase();

View File

@ -89,7 +89,8 @@ public class ControllerAuditAspect {
MethodSignature sig = (MethodSignature) joinPoint.getSignature(); MethodSignature sig = (MethodSignature) joinPoint.getSignature();
Method method = sig.getMethod(); Method method = sig.getMethod();
// Use unified check to determine if we should audit // Fast path: check if auditing is enabled before doing any work
// This avoids all data collection if auditing is disabled
if (!AuditUtils.shouldAudit(method, auditConfig)) { if (!AuditUtils.shouldAudit(method, auditConfig)) {
return joinPoint.proceed(); return joinPoint.proceed();
} }
@ -155,7 +156,8 @@ public class ControllerAuditAspect {
// Add result for VERBOSE level // Add result for VERBOSE level
if (level.includes(AuditLevel.VERBOSE) && result != null) { if (level.includes(AuditLevel.VERBOSE) && result != null) {
data.put("result", result.toString()); // Use safe string conversion with size limiting
data.put("result", AuditUtils.safeToString(result, 1000));
} }
// Resolve the event type using the unified method // Resolve the event type using the unified method

View File

@ -28,10 +28,15 @@ public class AuditConfigurationProperties {
applicationProperties.getPremium().getProFeatures().getAudit(); applicationProperties.getPremium().getProFeatures().getAudit();
// Read values directly from configuration // Read values directly from configuration
this.enabled = auditConfig.isEnabled(); this.enabled = auditConfig.isEnabled();
this.level = auditConfig.getLevel();
// Ensure level is within valid bounds (0-3)
int configLevel = auditConfig.getLevel();
this.level = Math.min(Math.max(configLevel, 0), 3);
// Retention days (0 means infinite)
this.retentionDays = auditConfig.getRetentionDays(); this.retentionDays = auditConfig.getRetentionDays();
log.debug("Initialized audit configuration: enabled={}, level={}, retentionDays={}", log.debug("Initialized audit configuration: enabled={}, level={}, retentionDays={} (0=infinite)",
this.enabled, this.level, this.retentionDays); this.enabled, this.level, this.retentionDays);
} }
@ -51,4 +56,13 @@ public class AuditConfigurationProperties {
public boolean isLevelEnabled(AuditLevel requiredLevel) { public boolean isLevelEnabled(AuditLevel requiredLevel) {
return enabled && getAuditLevel().includes(requiredLevel); return enabled && getAuditLevel().includes(requiredLevel);
} }
/**
* Get the effective retention period in days
* @return The number of days to retain audit records, or -1 for infinite retention
*/
public int getEffectiveRetentionDays() {
// 0 means infinite retention
return retentionDays <= 0 ? -1 : retentionDays;
}
} }

View File

@ -34,8 +34,8 @@ public class AuditService {
* @param level The minimum audit level required for this event to be logged * @param level The minimum audit level required for this event to be logged
*/ */
public void audit(AuditEventType type, Map<String, Object> data, AuditLevel level) { public void audit(AuditEventType type, Map<String, Object> data, AuditLevel level) {
// Skip auditing if this level is not enabled // Skip auditing if this level is not enabled - check first to avoid further processing
if (!auditConfig.isLevelEnabled(level)) { if (!auditConfig.isEnabled() || !auditConfig.getAuditLevel().includes(level)) {
return; return;
} }

View File

@ -1,12 +0,0 @@
# ── Actuator surface-area hardening ───────────────────────
# Enable Prometheus metrics endpoint
management.endpoints.web.exposure.include=health,info,metrics,prometheus
# Exclude auditevents from exposure
management.endpoints.web.exposure.exclude=auditevents
# Disable the audit events endpoint completely
management.endpoint.auditevents.enabled=false
# Configure endpoints
management.endpoints.web.base-path=/actuator
management.info.env.enabled=true
management.endpoint.health.show-details=when_authorized
management.endpoint.health.roles=ADMIN