feat: SOC2 audit log completeness — hybrid interceptor + explicit calls
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 54s
CI / docker (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s

Add AuditInterceptor as a safety net that auto-audits any POST/PUT/DELETE
without an explicit audit call (excludes data ingestion + heartbeat).
AuditService sets a request attribute so the interceptor skips when
explicit logging already happened.

New explicit audit calls:
- ApplicationConfigController: view/update app config
- AgentCommandController: send/broadcast commands (AGENT category)
- AgentRegistrationController: agent register + token refresh
- UiAuthController: UI token refresh
- OidcAuthController: OIDC callback failure
- AuditLogController: view audit log (sensitive read)
- UserAdminController: view users (sensitive read)
- OidcConfigAdminController: view OIDC config (sensitive read)

New AuditCategory.AGENT added. Frontend audit log filter updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-26 16:41:10 +01:00
parent 0e6de69cd9
commit 0d94132c98
13 changed files with 152 additions and 18 deletions

View File

@@ -159,6 +159,9 @@ public class OidcAuthController {
throw e;
} catch (Exception e) {
log.error("OIDC callback failed: {}", e.getMessage(), e);
auditService.log("unknown", "login_oidc", AuditCategory.AUTH, null,
Map.of("reason", e.getMessage() != null ? e.getMessage() : "unknown"),
AuditResult.FAILURE, httpRequest);
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED,
"OIDC authentication failed: " + e.getMessage());
}

View File

@@ -123,7 +123,8 @@ public class UiAuthController {
@ApiResponse(responseCode = "200", description = "Token refreshed")
@ApiResponse(responseCode = "401", description = "Invalid refresh token",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
public ResponseEntity<AuthTokenResponse> refresh(@RequestBody RefreshRequest request) {
public ResponseEntity<AuthTokenResponse> refresh(@RequestBody RefreshRequest request,
HttpServletRequest httpRequest) {
try {
JwtValidationResult result = jwtService.validateRefreshToken(request.refreshToken());
if (!result.subject().startsWith("user:")) {
@@ -138,6 +139,7 @@ public class UiAuthController {
String displayName = userRepository.findById(result.subject())
.map(UserInfo::displayName)
.orElse(result.subject());
auditService.log(result.subject(), "token_refresh", AuditCategory.AUTH, null, null, AuditResult.SUCCESS, httpRequest);
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken, displayName, null));
} catch (ResponseStatusException e) {
throw e;