fix: scope admin infra pages to current tenant's tables and indices
Database tables filtered to current_schema(), active queries to current_database(), OpenSearch indices to configured index-prefix. Delete endpoint rejects indices outside application scope. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,13 +72,14 @@ public class DatabaseAdminController {
|
|||||||
@Operation(summary = "Get table sizes and row counts")
|
@Operation(summary = "Get table sizes and row counts")
|
||||||
public ResponseEntity<List<TableSizeResponse>> getTables() {
|
public ResponseEntity<List<TableSizeResponse>> getTables() {
|
||||||
var tables = jdbc.query("""
|
var tables = jdbc.query("""
|
||||||
SELECT schemaname || '.' || relname AS table_name,
|
SELECT relname AS table_name,
|
||||||
n_live_tup AS row_count,
|
n_live_tup AS row_count,
|
||||||
pg_size_pretty(pg_total_relation_size(relid)) AS data_size,
|
pg_size_pretty(pg_total_relation_size(relid)) AS data_size,
|
||||||
pg_total_relation_size(relid) AS data_size_bytes,
|
pg_total_relation_size(relid) AS data_size_bytes,
|
||||||
pg_size_pretty(pg_indexes_size(relid)) AS index_size,
|
pg_size_pretty(pg_indexes_size(relid)) AS index_size,
|
||||||
pg_indexes_size(relid) AS index_size_bytes
|
pg_indexes_size(relid) AS index_size_bytes
|
||||||
FROM pg_stat_user_tables
|
FROM pg_stat_user_tables
|
||||||
|
WHERE schemaname = current_schema()
|
||||||
ORDER BY pg_total_relation_size(relid) DESC
|
ORDER BY pg_total_relation_size(relid) DESC
|
||||||
""", (rs, row) -> new TableSizeResponse(
|
""", (rs, row) -> new TableSizeResponse(
|
||||||
rs.getString("table_name"), rs.getLong("row_count"),
|
rs.getString("table_name"), rs.getLong("row_count"),
|
||||||
@@ -94,7 +95,7 @@ public class DatabaseAdminController {
|
|||||||
SELECT pid, EXTRACT(EPOCH FROM (now() - query_start)) AS duration_seconds,
|
SELECT pid, EXTRACT(EPOCH FROM (now() - query_start)) AS duration_seconds,
|
||||||
state, query
|
state, query
|
||||||
FROM pg_stat_activity
|
FROM pg_stat_activity
|
||||||
WHERE state != 'idle' AND pid != pg_backend_pid()
|
WHERE state != 'idle' AND pid != pg_backend_pid() AND datname = current_database()
|
||||||
ORDER BY query_start ASC
|
ORDER BY query_start ASC
|
||||||
""", (rs, row) -> new ActiveQueryResponse(
|
""", (rs, row) -> new ActiveQueryResponse(
|
||||||
rs.getInt("pid"), rs.getDouble("duration_seconds"),
|
rs.getInt("pid"), rs.getDouble("duration_seconds"),
|
||||||
|
|||||||
@@ -48,17 +48,20 @@ public class OpenSearchAdminController {
|
|||||||
private final AuditService auditService;
|
private final AuditService auditService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final String opensearchUrl;
|
private final String opensearchUrl;
|
||||||
|
private final String indexPrefix;
|
||||||
|
|
||||||
public OpenSearchAdminController(OpenSearchClient client, RestClient restClient,
|
public OpenSearchAdminController(OpenSearchClient client, RestClient restClient,
|
||||||
SearchIndexerStats indexerStats, AuditService auditService,
|
SearchIndexerStats indexerStats, AuditService auditService,
|
||||||
ObjectMapper objectMapper,
|
ObjectMapper objectMapper,
|
||||||
@Value("${opensearch.url:http://localhost:9200}") String opensearchUrl) {
|
@Value("${opensearch.url:http://localhost:9200}") String opensearchUrl,
|
||||||
|
@Value("${opensearch.index-prefix:executions-}") String indexPrefix) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.restClient = restClient;
|
this.restClient = restClient;
|
||||||
this.indexerStats = indexerStats;
|
this.indexerStats = indexerStats;
|
||||||
this.auditService = auditService;
|
this.auditService = auditService;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.opensearchUrl = opensearchUrl;
|
this.opensearchUrl = opensearchUrl;
|
||||||
|
this.indexPrefix = indexPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/status")
|
@GetMapping("/status")
|
||||||
@@ -109,6 +112,9 @@ public class OpenSearchAdminController {
|
|||||||
List<IndexInfoResponse> allIndices = new ArrayList<>();
|
List<IndexInfoResponse> allIndices = new ArrayList<>();
|
||||||
for (JsonNode idx : indices) {
|
for (JsonNode idx : indices) {
|
||||||
String name = idx.path("index").asText("");
|
String name = idx.path("index").asText("");
|
||||||
|
if (!name.startsWith(indexPrefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!search.isEmpty() && !name.contains(search)) {
|
if (!search.isEmpty() && !name.contains(search)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -146,6 +152,9 @@ public class OpenSearchAdminController {
|
|||||||
@Operation(summary = "Delete an OpenSearch index")
|
@Operation(summary = "Delete an OpenSearch index")
|
||||||
public ResponseEntity<Void> deleteIndex(@PathVariable String name, HttpServletRequest request) {
|
public ResponseEntity<Void> deleteIndex(@PathVariable String name, HttpServletRequest request) {
|
||||||
try {
|
try {
|
||||||
|
if (!name.startsWith(indexPrefix)) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Cannot delete index outside application scope");
|
||||||
|
}
|
||||||
boolean exists = client.indices().exists(r -> r.index(name)).value();
|
boolean exists = client.indices().exists(r -> r.index(name)).value();
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Index not found: " + name);
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Index not found: " + name);
|
||||||
|
|||||||
Reference in New Issue
Block a user