Public Access
fix(security): close the Steam test endpoint, /internal and Prometheus to the public
/api/steam/test/{steamId} let anyone query any Steam profile with our API key. /internal/** had no controller but was open for any future one. Actuator now exposes only health, without details. The management block was also indented under endpoints, so show-details never applied.
This commit is contained in:
1 parent
d96e682511
commit
b46e2badbd
4 files changed
+69
-44
No files matched your search
@@ -25,11 +25,8 @@ import java.util.List;
|
|||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private static final String[] WHITE_LIST_URLS = {
|
private static final String[] WHITE_LIST_URLS = {
|
||||||
"/internal/**", // Allow internal service communication
|
"/actuator/health", // Docker health check; details are off
|
||||||
"/actuator/health",
|
|
||||||
"/actuator/prometheus",
|
|
||||||
"/error",
|
"/error",
|
||||||
"/api/steam/test/**",
|
|
||||||
"/api/stats/public"
|
"/api/stats/public"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package core_service.modules.steam_adapter.api;
|
|
||||||
|
|
||||||
import core_service.modules.steam_adapter.api.dto.response.SteamProfileAndGamesResponse;
|
|
||||||
import core_service.modules.steam_adapter.service.SteamApiService;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/steam/test")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class SteamTestController {
|
|
||||||
|
|
||||||
private final SteamApiService steamApiService;
|
|
||||||
|
|
||||||
@GetMapping("/{steamId}")
|
|
||||||
public ResponseEntity<?> testSteamConnection(@PathVariable String steamId) {
|
|
||||||
|
|
||||||
Optional<SteamProfileAndGamesResponse> profileOpt = steamApiService.getFullPlayerProfile(steamId);
|
|
||||||
|
|
||||||
if (profileOpt.isPresent()) {
|
|
||||||
return ResponseEntity.ok(profileOpt.get());
|
|
||||||
} else {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,14 +26,10 @@ management:
|
|||||||
endpoints:
|
endpoints:
|
||||||
web:
|
web:
|
||||||
exposure:
|
exposure:
|
||||||
include: health, prometheus, metrics
|
include: health
|
||||||
endpoint:
|
endpoint:
|
||||||
health:
|
health:
|
||||||
show-details: always
|
show-details: never
|
||||||
prometheus:
|
|
||||||
metrics:
|
|
||||||
export:
|
|
||||||
enabled: true
|
|
||||||
security:
|
security:
|
||||||
jwt:
|
jwt:
|
||||||
public-key: ${JWT_PUBLIC_KEY}
|
public-key: ${JWT_PUBLIC_KEY}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package core_service.configuration.security;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||||
|
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs with the production security filter (not the dev profile, which injects a dummy user).
|
||||||
|
*/
|
||||||
|
@Tag("IntegrationTest")
|
||||||
|
@SpringBootTest
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@Testcontainers
|
||||||
|
class PublicEndpointsTest {
|
||||||
|
|
||||||
|
@Container
|
||||||
|
@ServiceConnection
|
||||||
|
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17-alpine");
|
||||||
|
|
||||||
|
@DynamicPropertySource
|
||||||
|
static void properties(DynamicPropertyRegistry registry) throws Exception {
|
||||||
|
byte[] publicKey = KeyPairGenerator.getInstance("Ed25519").generateKeyPair().getPublic().getEncoded();
|
||||||
|
registry.add("security.jwt.public-key", () -> Base64.getEncoder().encodeToString(publicKey));
|
||||||
|
registry.add("app.steam.api-key", () -> "test-key");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void healthIsPublicWithoutDetails() throws Exception {
|
||||||
|
mockMvc.perform(get("/actuator/health"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.status").value("UP"))
|
||||||
|
.andExpect(jsonPath("$.components").doesNotExist());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {"/actuator/prometheus", "/api/steam/test/76561198000000001", "/internal/anything"})
|
||||||
|
void endpointIsNotPublic(String path) throws Exception {
|
||||||
|
int status = mockMvc.perform(get(path)).andReturn().getResponse().getStatus();
|
||||||
|
assertTrue(Set.of(401, 403).contains(status), path + " answered " + status);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user