From 4566f62de3d2f4dabd017b934f1355de30014ad5 Mon Sep 17 00:00:00 2001 From: Duansg Date: Sun, 9 Aug 2026 21:07:48 +0800 Subject: [PATCH] [fix] restrict the actuator endpoints to admin (#4277) Co-authored-by: Claude Opus 5 (1M context) --- .../src/main/resources/sureness.yml | 2 + .../security/SurenessActuatorRuleTest.java | 86 +++++++++++++++++++ .../hertzbeat-mysql-iotdb/conf/sureness.yml | 2 + .../conf/sureness.yml | 2 + .../conf/sureness.yml | 2 + .../conf/sureness.yml | 2 + .../conf/sureness.yml | 2 + script/sureness.yml | 2 + 8 files changed, 100 insertions(+) create mode 100644 hertzbeat-startup/src/test/java/org/apache/hertzbeat/startup/security/SurenessActuatorRuleTest.java diff --git a/hertzbeat-startup/src/main/resources/sureness.yml b/hertzbeat-startup/src/main/resources/sureness.yml index da66fe4cea..ee29402442 100644 --- a/hertzbeat-startup/src/main/resources/sureness.yml +++ b/hertzbeat-startup/src/main/resources/sureness.yml @@ -99,6 +99,8 @@ resourceRole: - /api/account/token===get===[admin] - /api/account/token/**===post===[admin] - /api/account/token/**===delete===[admin] + # spring boot actuator exposes jvm, http and datasource internals for scraping + - /actuator/**===get===[admin] # config the resource restful api that need bypass auth protection # rule: api===method diff --git a/hertzbeat-startup/src/test/java/org/apache/hertzbeat/startup/security/SurenessActuatorRuleTest.java b/hertzbeat-startup/src/test/java/org/apache/hertzbeat/startup/security/SurenessActuatorRuleTest.java new file mode 100644 index 0000000000..919a08b740 --- /dev/null +++ b/hertzbeat-startup/src/test/java/org/apache/hertzbeat/startup/security/SurenessActuatorRuleTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.startup.security; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import com.usthe.sureness.matcher.util.TirePathTree; +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.yaml.snakeyaml.Yaml; + +/** + * Guards the rbac rule covering the spring boot actuator endpoints. + * + *

`application.yml` exposes `metrics`, `health` and `prometheus`, but `sureness.yml` + * neither listed nor excluded `/actuator/**`. A route with no rule leaves `supportRoles` + * null and `BaseProcessor.authorized` returns early, so every authenticated account + * including {@code guest} could read jvm heap, thread and gc counters, http call + * statistics and datasource health - useful for internal reconnaissance and as a + * feedback channel while probing for resource exhaustion. + */ +class SurenessActuatorRuleTest { + + private static final String SEPARATOR = "==="; + + private static TirePathTree roleTree; + + private static TirePathTree excludeTree; + + @BeforeAll + @SuppressWarnings("unchecked") + static void loadSurenessConfig() throws IOException { + List resourceRole; + List excludedResource; + try (InputStream in = SurenessActuatorRuleTest.class.getResourceAsStream("/sureness.yml")) { + assertNotNull(in, "sureness.yml must be on the classpath"); + Map document = new Yaml().load(in); + resourceRole = (List) document.get("resourceRole"); + excludedResource = (List) document.get("excludedResource"); + } + assertNotNull(resourceRole, "resourceRole must be present"); + assertNotNull(excludedResource, "excludedResource must be present"); + roleTree = new TirePathTree(); + roleTree.buildTree(new LinkedHashSet<>(resourceRole)); + excludeTree = new TirePathTree(); + excludeTree.buildTree(new LinkedHashSet<>(excludedResource)); + } + + @Test + void actuatorEndpointsAreRestrictedToAdmin() { + assertEquals("[admin]", roleTree.searchPathFilterRoles("/actuator/prometheus" + SEPARATOR + "get")); + assertEquals("[admin]", roleTree.searchPathFilterRoles("/actuator/health" + SEPARATOR + "get")); + assertEquals("[admin]", roleTree.searchPathFilterRoles("/actuator/metrics" + SEPARATOR + "get")); + } + + /** + * Sureness evaluates the exclusion tree before any credential check, so an actuator + * path landing there would hand these internals to anonymous callers instead. + */ + @Test + void actuatorEndpointsAreNotExcludedFromAuthentication() { + assertNull(excludeTree.searchPathFilterRoles("/actuator/prometheus" + SEPARATOR + "get")); + assertNull(excludeTree.searchPathFilterRoles("/actuator/health" + SEPARATOR + "get")); + } +} diff --git a/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml b/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml index f19414fb5b..fd283a60c6 100644 --- a/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml +++ b/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml @@ -92,6 +92,8 @@ resourceRole: - /api/ai/**===delete===[admin] - /api/logs/sse/**===get===[admin,user,guest] - /api/logs/ingest/**===post===[admin,user] + # spring boot actuator exposes jvm, http and datasource internals for scraping + - /actuator/**===get===[admin] # config the resource restful api that need bypass auth protection # rule: api===method diff --git a/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml b/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml index f19414fb5b..fd283a60c6 100644 --- a/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml +++ b/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml @@ -92,6 +92,8 @@ resourceRole: - /api/ai/**===delete===[admin] - /api/logs/sse/**===get===[admin,user,guest] - /api/logs/ingest/**===post===[admin,user] + # spring boot actuator exposes jvm, http and datasource internals for scraping + - /actuator/**===get===[admin] # config the resource restful api that need bypass auth protection # rule: api===method diff --git a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml index f19414fb5b..fd283a60c6 100644 --- a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml +++ b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml @@ -92,6 +92,8 @@ resourceRole: - /api/ai/**===delete===[admin] - /api/logs/sse/**===get===[admin,user,guest] - /api/logs/ingest/**===post===[admin,user] + # spring boot actuator exposes jvm, http and datasource internals for scraping + - /actuator/**===get===[admin] # config the resource restful api that need bypass auth protection # rule: api===method diff --git a/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml b/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml index e9c2f9f4a1..2c056a8448 100644 --- a/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml +++ b/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml @@ -96,6 +96,8 @@ resourceRole: - /api/ingestion/otlp/**===get===[admin,user,guest] - /api/logs/**===get===[admin,user,guest] - /api/traces/**===get===[admin,user,guest] + # spring boot actuator exposes jvm, http and datasource internals for scraping + - /actuator/**===get===[admin] # config the resource restful api that need bypass auth protection # rule: api===method diff --git a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml index f19414fb5b..fd283a60c6 100644 --- a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml +++ b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml @@ -92,6 +92,8 @@ resourceRole: - /api/ai/**===delete===[admin] - /api/logs/sse/**===get===[admin,user,guest] - /api/logs/ingest/**===post===[admin,user] + # spring boot actuator exposes jvm, http and datasource internals for scraping + - /actuator/**===get===[admin] # config the resource restful api that need bypass auth protection # rule: api===method diff --git a/script/sureness.yml b/script/sureness.yml index e9c2f9f4a1..2c056a8448 100644 --- a/script/sureness.yml +++ b/script/sureness.yml @@ -96,6 +96,8 @@ resourceRole: - /api/ingestion/otlp/**===get===[admin,user,guest] - /api/logs/**===get===[admin,user,guest] - /api/traces/**===get===[admin,user,guest] + # spring boot actuator exposes jvm, http and datasource internals for scraping + - /actuator/**===get===[admin] # config the resource restful api that need bypass auth protection # rule: api===method