fix(obs): wire Prometheus endpoint for Spring Boot 4.0
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m1s
CI / OCR Service Tests (pull_request) Successful in 16s
CI / Backend Unit Tests (pull_request) Successful in 2m41s
CI / fail2ban Regex (pull_request) Successful in 40s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m0s
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m1s
CI / OCR Service Tests (pull_request) Successful in 16s
CI / Backend Unit Tests (pull_request) Successful in 2m41s
CI / fail2ban Regex (pull_request) Successful in 40s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m0s
Four Spring Boot 4.0-specific issues prevented /actuator/prometheus from working: 1. spring-boot-starter-micrometer-metrics missing — Spring Boot 4.0 splits Micrometer metrics export (including the Prometheus scrape endpoint) out of spring-boot-starter-actuator into its own starter. Added dependency. 2. management.prometheus.metrics.export.enabled not set — Spring Boot 4.0 defaults metrics export to false (opt-in). Added the property to application.yaml. 3. SecurityConfig did not permit /actuator/prometheus — Spring Boot 4.0 with Jetty serves the management port (8081) via the same security filter chain as the main port (8080). The previous commit's exclusion of ManagementWebSecurityAutoConfiguration was a no-op (that class no longer exists in Spring Boot 4.0); removed it and added the correct permitAll() rule. Updated the architecture comment in application.yaml to reflect the true filter-chain behaviour. 4. Reverted invalid FamilienarchivApplication.java change from the prior commit (ManagementWebSecurityAutoConfiguration import compiled against a class that does not exist in the Spring Boot 4.0 BOM). Also adds ActuatorPrometheusIT — an integration test that asserts the /actuator/prometheus endpoint returns 200 with jvm_memory_used_bytes without credentials, serving as regression protection against future Spring Boot upgrades silently breaking metrics collection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,11 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<!-- Spring Boot 4.0 splits Micrometer metrics export (incl. Prometheus scrape endpoint) into its own starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-micrometer-metrics</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package org.raddatz.familienarchiv;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
// Excluded: management port (8081) is network-isolated inside archiv-net; no app-level auth needed.
|
||||
@SpringBootApplication(exclude = {ManagementWebSecurityAutoConfiguration.class})
|
||||
@SpringBootApplication
|
||||
public class FamilienarchivApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -54,8 +54,14 @@ public class SecurityConfig {
|
||||
.csrf(csrf -> csrf.disable())
|
||||
|
||||
.authorizeHttpRequests(auth -> {
|
||||
// Health endpoint must be open so CI/Docker health checks work without credentials
|
||||
auth.requestMatchers("/actuator/health").permitAll();
|
||||
// Both /actuator/health and /actuator/prometheus must be open.
|
||||
// In Spring Boot 4.0 the management server (port 8081) shares the security filter chain;
|
||||
// network isolation (port 8081 not published in docker-compose) is the security boundary.
|
||||
// Health and Prometheus must be open — no credentials for Docker health checks or Prometheus scraping.
|
||||
// Note: in Spring Boot 4.0 the management port shares the security filter chain,
|
||||
// so these paths must be explicitly permitted here even though they are served on port 8081.
|
||||
// Network isolation (port 8081 not published in docker-compose) is the outer security boundary.
|
||||
auth.requestMatchers("/actuator/health", "/actuator/prometheus").permitAll();
|
||||
// Password reset endpoints are unauthenticated by nature
|
||||
auth.requestMatchers("/api/auth/forgot-password", "/api/auth/reset-password").permitAll();
|
||||
// Invite-based registration endpoints are public
|
||||
|
||||
@@ -49,7 +49,8 @@ management:
|
||||
# Management port is separate from the app port so that:
|
||||
# (a) Caddy never proxies /actuator/* (it only routes :8080 → the app port)
|
||||
# (b) Prometheus scrapes backend:8081 directly inside archiv-net, not via Caddy
|
||||
# (c) Spring Security's session-authenticated filter chain on :8080 never sees actuator requests
|
||||
# Note: in Spring Boot 4.0 the management port shares the security filter chain; /actuator/health
|
||||
# and /actuator/prometheus must be explicitly permitted in SecurityConfig — see SecurityConfig.java.
|
||||
port: 8081
|
||||
endpoints:
|
||||
web:
|
||||
@@ -58,6 +59,11 @@ management:
|
||||
endpoint:
|
||||
prometheus:
|
||||
enabled: true
|
||||
# Spring Boot 4.0: metrics export is disabled by default — explicitly opt in for Prometheus
|
||||
prometheus:
|
||||
metrics:
|
||||
export:
|
||||
enabled: true
|
||||
health:
|
||||
mail:
|
||||
enabled: false
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.raddatz.familienarchiv;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalManagementPort;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("test")
|
||||
@Import(PostgresContainerConfig.class)
|
||||
class ActuatorPrometheusIT {
|
||||
|
||||
@LocalManagementPort
|
||||
private int managementPort;
|
||||
|
||||
@MockitoBean
|
||||
S3Client s3Client;
|
||||
|
||||
@Test
|
||||
void prometheus_endpoint_returns_jvm_metrics_without_credentials() {
|
||||
ResponseEntity<String> response = noThrowTemplate().getForEntity(
|
||||
"http://localhost:" + managementPort + "/actuator/prometheus", String.class);
|
||||
|
||||
assertThat(response.getStatusCode().value()).isEqualTo(200);
|
||||
assertThat(response.getBody()).contains("jvm_memory_used_bytes");
|
||||
}
|
||||
|
||||
private RestTemplate noThrowTemplate() {
|
||||
RestTemplate template = new RestTemplate();
|
||||
template.setErrorHandler(new DefaultResponseErrorHandler() {
|
||||
@Override
|
||||
public boolean hasError(org.springframework.http.client.ClientHttpResponse response) throws IOException {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return template;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user