This is an automated email from the ASF dual-hosted git repository. tanxinyu pushed a commit to branch prometheus in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit fa3f582531742bb21acb1b78f649ba43ba031dc5 Author: Li Yu Heng <[email protected]> AuthorDate: Fri Apr 25 12:00:58 2025 +0800 Add authorization for metric prometheus report (#15363) * init * add test * Tan review * add base64 & optimize test --- .../it/env/cluster/config/MppConfigNodeConfig.java | 12 +++ .../it/env/cluster/config/MppDataNodeConfig.java | 12 +++ .../iotdb/it/env/cluster/env/AbstractEnv.java | 13 ++- .../env/remote/config/RemoteConfigNodeConfig.java | 10 +++ .../it/env/remote/config/RemoteDataNodeConfig.java | 10 +++ .../iotdb/it/env/remote/env/RemoteServerEnv.java | 13 ++- .../java/org/apache/iotdb/itbase/env/BaseEnv.java | 11 ++- .../apache/iotdb/itbase/env/ConfigNodeConfig.java | 5 ++ .../apache/iotdb/itbase/env/DataNodeConfig.java | 4 + .../apache/iotdb/db/it/metric/IoTDBMetricIT.java | 92 +++++++++++++++++++--- iotdb-core/metrics/interface/pom.xml | 5 ++ .../apache/iotdb/metrics/config/MetricConfig.java | 38 +++++++++ .../metrics/config/MetricConfigDescriptor.java | 19 +++++ .../reporter/prometheus/PrometheusReporter.java | 62 ++++++++++++++- .../conf/iotdb-system.properties.template | 11 +++ 15 files changed, 296 insertions(+), 21 deletions(-) diff --git a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppConfigNodeConfig.java b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppConfigNodeConfig.java index 62ccbb0aa4f..8e4a6def365 100644 --- a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppConfigNodeConfig.java +++ b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppConfigNodeConfig.java @@ -55,4 +55,16 @@ public class MppConfigNodeConfig extends MppBaseConfig implements ConfigNodeConf properties.setProperty("cn_metric_reporter_list", String.join(",", metricReporterTypes)); return this; } + + @Override + public ConfigNodeConfig setMetricPrometheusReporterUsername(String username) { + properties.setProperty("metric_prometheus_reporter_username", username); + return this; + } + + @Override + public ConfigNodeConfig setMetricPrometheusReporterPassword(String password) { + properties.setProperty("metric_prometheus_reporter_password", password); + return this; + } } diff --git a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppDataNodeConfig.java b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppDataNodeConfig.java index 0f22e0d4286..e6fa19367fd 100644 --- a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppDataNodeConfig.java +++ b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppDataNodeConfig.java @@ -56,6 +56,18 @@ public class MppDataNodeConfig extends MppBaseConfig implements DataNodeConfig { return this; } + @Override + public DataNodeConfig setMetricPrometheusReporterUsername(String username) { + properties.setProperty("metric_prometheus_reporter_username", username); + return this; + } + + @Override + public DataNodeConfig setMetricPrometheusReporterPassword(String password) { + properties.setProperty("metric_prometheus_reporter_password", password); + return this; + } + @Override public DataNodeConfig setEnableRestService(boolean enableRestService) { properties.setProperty("enable_rest_service", String.valueOf(enableRestService)); diff --git a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/env/AbstractEnv.java b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/env/AbstractEnv.java index da372c3640f..7358de70621 100644 --- a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/env/AbstractEnv.java +++ b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/env/AbstractEnv.java @@ -113,7 +113,7 @@ public abstract class AbstractEnv implements BaseEnv { } @Override - public List<String> getMetricPrometheusReporterContents() { + public List<String> getMetricPrometheusReporterContents(String authHeader) { final List<String> result = new ArrayList<>(); // get all report content of confignodes for (final ConfigNodeWrapper configNode : this.configNodeWrapperList) { @@ -123,7 +123,8 @@ public abstract class AbstractEnv implements BaseEnv { + configNode.getIp() + ":" + configNode.getMetricPort() - + "/metrics"); + + "/metrics", + authHeader); result.add(configNodeMetricContent); } // get all report content of datanodes @@ -134,7 +135,8 @@ public abstract class AbstractEnv implements BaseEnv { + dataNode.getIp() + ":" + dataNode.getMetricPort() - + "/metrics"); + + "/metrics", + authHeader); result.add(dataNodeMetricContent); } return result; @@ -937,6 +939,11 @@ public abstract class AbstractEnv implements BaseEnv { configNodeWrapperList.forEach(AbstractNodeWrapper::stop); } + @Override + public void shutdownForciblyAllConfigNodes() { + configNodeWrapperList.forEach(AbstractNodeWrapper::stopForcibly); + } + @Override public ConfigNodeWrapper getConfigNodeWrapper(final int index) { return configNodeWrapperList.get(index); diff --git a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteConfigNodeConfig.java b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteConfigNodeConfig.java index 33a6bc48afd..ae8645eff52 100644 --- a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteConfigNodeConfig.java +++ b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteConfigNodeConfig.java @@ -28,4 +28,14 @@ public class RemoteConfigNodeConfig implements ConfigNodeConfig { public ConfigNodeConfig setMetricReporterType(List<String> metricReporterTypes) { return this; } + + @Override + public ConfigNodeConfig setMetricPrometheusReporterUsername(String username) { + return this; + } + + @Override + public ConfigNodeConfig setMetricPrometheusReporterPassword(String password) { + return this; + } } diff --git a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteDataNodeConfig.java b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteDataNodeConfig.java index fe89997bc41..2c85a7e5ddf 100644 --- a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteDataNodeConfig.java +++ b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteDataNodeConfig.java @@ -28,6 +28,16 @@ public class RemoteDataNodeConfig implements DataNodeConfig { return this; } + @Override + public DataNodeConfig setMetricPrometheusReporterUsername(String username) { + return this; + } + + @Override + public DataNodeConfig setMetricPrometheusReporterPassword(String password) { + return this; + } + @Override public DataNodeConfig setEnableRestService(boolean enableRestService) { return this; diff --git a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java index b4696fa1204..efd883e41a9 100644 --- a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java +++ b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java @@ -108,14 +108,16 @@ public class RemoteServerEnv implements BaseEnv { } @Override - public List<String> getMetricPrometheusReporterContents() { + public List<String> getMetricPrometheusReporterContents(String authHeader) { List<String> result = new ArrayList<>(); result.add( getUrlContent( - Config.IOTDB_HTTP_URL_PREFIX + ip_addr + ":" + configNodeMetricPort + "/metrics")); + Config.IOTDB_HTTP_URL_PREFIX + ip_addr + ":" + configNodeMetricPort + "/metrics", + authHeader)); result.add( getUrlContent( - Config.IOTDB_HTTP_URL_PREFIX + ip_addr + ":" + dataNodeMetricPort + "/metrics")); + Config.IOTDB_HTTP_URL_PREFIX + ip_addr + ":" + dataNodeMetricPort + "/metrics", + authHeader)); return result; } @@ -299,6 +301,11 @@ public class RemoteServerEnv implements BaseEnv { throw new UnsupportedOperationException(); } + @Override + public void shutdownForciblyAllConfigNodes() { + throw new UnsupportedOperationException(); + } + @Override public void ensureNodeStatus(List<BaseNodeWrapper> nodes, List<NodeStatus> targetStatus) { throw new UnsupportedOperationException(); diff --git a/integration-test/src/main/java/org/apache/iotdb/itbase/env/BaseEnv.java b/integration-test/src/main/java/org/apache/iotdb/itbase/env/BaseEnv.java index 9abc2b8ba89..c3dc9a3eb32 100644 --- a/integration-test/src/main/java/org/apache/iotdb/itbase/env/BaseEnv.java +++ b/integration-test/src/main/java/org/apache/iotdb/itbase/env/BaseEnv.java @@ -31,6 +31,8 @@ import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper; import org.apache.iotdb.jdbc.Constant; import org.apache.iotdb.rpc.IoTDBConnectionException; +import reactor.util.annotation.Nullable; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -71,11 +73,14 @@ public interface BaseEnv { /** Return the {@link ClusterConfig} for developers to set values before test. */ ClusterConfig getConfig(); - default String getUrlContent(String urlStr) { + default String getUrlContent(String urlStr, @Nullable String authHeader) { StringBuilder sb = new StringBuilder(); try { URL url = new URL(urlStr); HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection(); + if (authHeader != null) { + httpConnection.setRequestProperty("Authorization", authHeader); + } if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { InputStream in = httpConnection.getInputStream(); InputStreamReader isr = new InputStreamReader(in); @@ -97,7 +102,7 @@ public interface BaseEnv { } /** Return the content of prometheus */ - List<String> getMetricPrometheusReporterContents(); + List<String> getMetricPrometheusReporterContents(String authHeader); default Connection getConnection() throws SQLException { return getConnection(SessionConfig.DEFAULT_USER, SessionConfig.DEFAULT_PASSWORD); @@ -187,6 +192,8 @@ public interface BaseEnv { /** Shutdown all existed ConfigNodes. */ void shutdownAllConfigNodes(); + void shutdownForciblyAllConfigNodes(); + /** * Ensure all the nodes being in the corresponding status. * diff --git a/integration-test/src/main/java/org/apache/iotdb/itbase/env/ConfigNodeConfig.java b/integration-test/src/main/java/org/apache/iotdb/itbase/env/ConfigNodeConfig.java index bf7179ef702..65a5a3271fc 100644 --- a/integration-test/src/main/java/org/apache/iotdb/itbase/env/ConfigNodeConfig.java +++ b/integration-test/src/main/java/org/apache/iotdb/itbase/env/ConfigNodeConfig.java @@ -23,5 +23,10 @@ import java.util.List; /** This interface is used to handle properties in iotdb-confignode.properties. */ public interface ConfigNodeConfig { + ConfigNodeConfig setMetricReporterType(List<String> metricReporterTypes); + + ConfigNodeConfig setMetricPrometheusReporterUsername(String username); + + ConfigNodeConfig setMetricPrometheusReporterPassword(String password); } diff --git a/integration-test/src/main/java/org/apache/iotdb/itbase/env/DataNodeConfig.java b/integration-test/src/main/java/org/apache/iotdb/itbase/env/DataNodeConfig.java index 2887b0a9871..980ab74c10c 100644 --- a/integration-test/src/main/java/org/apache/iotdb/itbase/env/DataNodeConfig.java +++ b/integration-test/src/main/java/org/apache/iotdb/itbase/env/DataNodeConfig.java @@ -25,6 +25,10 @@ import java.util.List; public interface DataNodeConfig { DataNodeConfig setMetricReporterType(List<String> metricReporterTypes); + DataNodeConfig setMetricPrometheusReporterUsername(String username); + + DataNodeConfig setMetricPrometheusReporterPassword(String password); + DataNodeConfig setEnableRestService(boolean enableRestService); DataNodeConfig setConnectionTimeoutInMS(int connectionTimeoutInMS); diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/metric/IoTDBMetricIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/metric/IoTDBMetricIT.java index 76e6ddce817..3e6f660d4f2 100644 --- a/integration-test/src/test/java/org/apache/iotdb/db/it/metric/IoTDBMetricIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/metric/IoTDBMetricIT.java @@ -23,16 +23,19 @@ import org.apache.iotdb.it.env.EnvFactory; import org.apache.iotdb.it.framework.IoTDBTestRunner; import org.apache.iotdb.itbase.category.ClusterIT; import org.apache.iotdb.itbase.category.LocalStandaloneIT; +import org.apache.iotdb.metrics.reporter.prometheus.PrometheusReporter; -import org.junit.AfterClass; +import org.junit.After; import org.junit.Assert; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; @@ -65,7 +68,13 @@ public class IoTDBMetricIT { private static final String VALID_LOG_STRING = "This line {} is invalid in prometheus line protocol"; - public static boolean isValidPrometheusTextFormat(String metrics) { + private static final String TEST_USERNAME = "good"; + private static final String TEST_PASSWORD = "??"; + + private static final String WRONG_USERNAME = "bad"; + private static final String WRONG_PASSWORD = "!!"; + + private static boolean isValidPrometheusTextFormat(String metrics) { String[] lines = metrics.split("\\n"); boolean valid = true; @@ -107,8 +116,8 @@ public class IoTDBMetricIT { return Pattern.matches(TYPE_REGEX, line.trim()); } - @BeforeClass - public static void setUp() throws Exception { + @Before + public void setUp() throws Exception { // Start ConfigNode with Prometheus reporter up EnvFactory.getEnv() .getConfig() @@ -119,21 +128,86 @@ public class IoTDBMetricIT { .getConfig() .getDataNodeConfig() .setMetricReporterType(Collections.singletonList("PROMETHEUS")); - EnvFactory.getEnv().initClusterEnvironment(); } - @AfterClass - public static void tearDown() throws Exception { + @After + public void tearDown() throws Exception { EnvFactory.getEnv().cleanClusterEnvironment(); } + @Test + public void testPrometheusReporterWithoutAuth() { + EnvFactory.getEnv().initClusterEnvironment(); + + List<String> metricContents = EnvFactory.getEnv().getMetricPrometheusReporterContents(null); + for (String metricContent : metricContents) { + Assert.assertNotNull(metricContent); + Assert.assertNotEquals(0, metricContent.length()); + Assert.assertTrue(isValidPrometheusTextFormat(metricContent)); + } + } + @Test public void testPrometheusReporter() { - List<String> metricContents = EnvFactory.getEnv().getMetricPrometheusReporterContents(); + EnvFactory.getEnv() + .getConfig() + .getConfigNodeConfig() + .setMetricPrometheusReporterUsername(base64Encode(TEST_USERNAME)) + .setMetricPrometheusReporterPassword(base64Encode(TEST_PASSWORD)); + EnvFactory.getEnv() + .getConfig() + .getDataNodeConfig() + .setMetricPrometheusReporterUsername(base64Encode(TEST_USERNAME)) + .setMetricPrometheusReporterPassword(base64Encode(TEST_PASSWORD)); + EnvFactory.getEnv().initClusterEnvironment(); + + wrongUsernameTest(); + wrongPasswordTest(); + correctUsernameAndPasswordTest(); + } + + private void wrongUsernameTest() { + List<String> metricContents = + EnvFactory.getEnv() + .getMetricPrometheusReporterContents( + buildPrometheusReporterAuthHeader(WRONG_USERNAME, TEST_PASSWORD)); + for (String metricContent : metricContents) { + Assert.assertNull(metricContent); + } + } + + private void wrongPasswordTest() { + List<String> metricContents = + EnvFactory.getEnv() + .getMetricPrometheusReporterContents( + buildPrometheusReporterAuthHeader(TEST_USERNAME, WRONG_PASSWORD)); + for (String metricContent : metricContents) { + Assert.assertNull(metricContent); + } + } + + private void correctUsernameAndPasswordTest() { + List<String> metricContents = + EnvFactory.getEnv() + .getMetricPrometheusReporterContents( + buildPrometheusReporterAuthHeader(TEST_USERNAME, TEST_PASSWORD)); for (String metricContent : metricContents) { Assert.assertNotNull(metricContent); Assert.assertNotEquals(0, metricContent.length()); Assert.assertTrue(isValidPrometheusTextFormat(metricContent)); } } + + private String buildPrometheusReporterAuthHeader(String username, String password) { + if (username == null || username.isEmpty()) { + return null; + } + String raw = username + PrometheusReporter.DIVIDER_BETWEEN_USERNAME_AND_DIVIDER + password; + String base64 = Base64.getEncoder().encodeToString(raw.getBytes(StandardCharsets.UTF_8)); + return PrometheusReporter.BASIC_AUTH_PREFIX + base64; + } + + private static String base64Encode(String raw) { + return Base64.getEncoder().encodeToString(raw.getBytes(StandardCharsets.UTF_8)); + } } diff --git a/iotdb-core/metrics/interface/pom.xml b/iotdb-core/metrics/interface/pom.xml index f5ff0c2a8f2..3512c582e6e 100644 --- a/iotdb-core/metrics/interface/pom.xml +++ b/iotdb-core/metrics/interface/pom.xml @@ -79,6 +79,11 @@ <groupId>io.netty</groupId> <artifactId>netty-transport</artifactId> </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-codec-http</artifactId> + <version>4.1.119.Final</version> + </dependency> <dependency> <groupId>org.reactivestreams</groupId> <artifactId>reactive-streams</artifactId> diff --git a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfig.java b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfig.java index 00f9cd6af9d..090ef3eae2c 100644 --- a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfig.java +++ b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfig.java @@ -29,7 +29,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.management.ManagementFactory; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -49,6 +51,10 @@ public class MetricConfig { /** The export port for prometheus to get metrics. */ private Integer prometheusReporterPort = 9091; + private String prometheusReporterUsername = ""; + + private String prometheusReporterPassword = ""; + /** The iotdb config for iotdb reporter to push metric data. */ private final IoTDBReporterConfig iotdbReporterConfig = new IoTDBReporterConfig(); @@ -127,6 +133,36 @@ public class MetricConfig { this.prometheusReporterPort = prometheusReporterPort; } + public boolean prometheusNeedAuth() { + return prometheusReporterUsername != null && !prometheusReporterUsername.isEmpty(); + } + + public String getPrometheusReporterUsername() { + return prometheusReporterUsername; + } + + public String getDecodedPrometheusReporterUsername() { + return new String( + Base64.getDecoder().decode(prometheusReporterUsername), StandardCharsets.UTF_8); + } + + public void setPrometheusReporterUsername(String prometheusReporterUsername) { + this.prometheusReporterUsername = prometheusReporterUsername; + } + + public String getPrometheusReporterPassword() { + return prometheusReporterPassword; + } + + public String getDecodedPrometheusReporterPassword() { + return new String( + Base64.getDecoder().decode(prometheusReporterPassword), StandardCharsets.UTF_8); + } + + public void setPrometheusReporterPassword(String prometheusReporterPassword) { + this.prometheusReporterPassword = prometheusReporterPassword; + } + public IoTDBReporterConfig getIoTDBReporterConfig() { return iotdbReporterConfig; } @@ -181,6 +217,8 @@ public class MetricConfig { metricLevel = newMetricConfig.getMetricLevel(); asyncCollectPeriodInSecond = newMetricConfig.getAsyncCollectPeriodInSecond(); prometheusReporterPort = newMetricConfig.getPrometheusReporterPort(); + prometheusReporterUsername = newMetricConfig.getPrometheusReporterUsername(); + prometheusReporterPassword = newMetricConfig.getPrometheusReporterPassword(); internalReporterType = newMetricConfig.getInternalReportType(); iotdbReporterConfig.copy(newMetricConfig.getIoTDBReporterConfig()); diff --git a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfigDescriptor.java b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfigDescriptor.java index 11decb83369..2cc5c2a986d 100644 --- a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfigDescriptor.java +++ b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfigDescriptor.java @@ -112,6 +112,18 @@ public class MetricConfigDescriptor { properties, isConfigNode))); + loadConfig.setPrometheusReporterUsername( + getPropertyWithoutPrefix( + "metric_prometheus_reporter_username", + loadConfig.getPrometheusReporterUsername(), + properties)); + + loadConfig.setPrometheusReporterPassword( + getPropertyWithoutPrefix( + "metric_prometheus_reporter_password", + loadConfig.getPrometheusReporterPassword(), + properties)); + IoTDBReporterConfig reporterConfig = loadConfig.getIoTDBReporterConfig(); reporterConfig.setHost( getProperty( @@ -181,6 +193,13 @@ public class MetricConfigDescriptor { .orElse(defaultValue); } + private String getPropertyWithoutPrefix( + String target, String defaultValue, Properties properties) { + return Optional.ofNullable(properties.getProperty(target, defaultValue)) + .map(String::trim) + .orElse(defaultValue); + } + private static class MetricConfigDescriptorHolder { private static final MetricConfigDescriptor INSTANCE = new MetricConfigDescriptor(); } diff --git a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporter.java b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporter.java index 612c3e074f9..9bc8f2d9cae 100644 --- a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporter.java +++ b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporter.java @@ -37,17 +37,23 @@ import org.apache.iotdb.metrics.utils.ReporterType; import io.netty.channel.ChannelOption; import io.netty.channel.group.DefaultChannelGroup; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.concurrent.GlobalEventExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; import reactor.netty.DisposableServer; import reactor.netty.http.server.HttpServer; +import reactor.netty.http.server.HttpServerRequest; +import reactor.netty.http.server.HttpServerResponse; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -59,6 +65,10 @@ public class PrometheusReporter implements Reporter { private final AbstractMetricManager metricManager; private DisposableServer httpServer; + private static final String REALM = "metrics"; + public static final String BASIC_AUTH_PREFIX = "Basic "; + public static final char DIVIDER_BETWEEN_USERNAME_AND_DIVIDER = ':'; + public PrometheusReporter(AbstractMetricManager metricManager) { this.metricManager = metricManager; } @@ -80,10 +90,14 @@ public class PrometheusReporter implements Reporter { routes -> routes.get( "/metrics", - (request, response) -> - response - .addHeader("Content-Type", "text/plain") - .sendString(Mono.just(scrape())))) + (req, res) -> { + if (!authenticate(req, res)) { + // authenticate not pass + return Mono.empty(); + } + return res.header(HttpHeaderNames.CONTENT_TYPE, "text/plain") + .sendString(Mono.just(scrape())); + })) .bindNow(); } catch (Throwable e) { // catch Throwable rather than Exception here because the code above might cause a @@ -97,6 +111,46 @@ public class PrometheusReporter implements Reporter { return true; } + private boolean authenticate(HttpServerRequest req, HttpServerResponse res) { + if (!METRIC_CONFIG.prometheusNeedAuth()) { + return true; + } + + String header = req.requestHeaders().get(HttpHeaderNames.AUTHORIZATION); + if (header == null || !header.startsWith(BASIC_AUTH_PREFIX)) { + return authenticateFailed(res); + } + + // base64 decoding + // base64String is expected as "Basic dXNlcjpwYXNzd29yZA==" + String base64String = header.substring(BASIC_AUTH_PREFIX.length()); + // decodedString is expected as "username:password" + String decodedString = + new String(Base64.getDecoder().decode(base64String), StandardCharsets.UTF_8); + int dividerIndex = decodedString.indexOf(DIVIDER_BETWEEN_USERNAME_AND_DIVIDER); + if (dividerIndex < 0) { + LOGGER.warn("Unexpected auth string: {}", decodedString); + return authenticateFailed(res); + } + + // check username and password + String username = decodedString.substring(0, dividerIndex); + String password = decodedString.substring(dividerIndex + 1); + if (!METRIC_CONFIG.getDecodedPrometheusReporterUsername().equals(username) + || !METRIC_CONFIG.getDecodedPrometheusReporterPassword().equals(password)) { + return authenticateFailed(res); + } + + return true; + } + + private boolean authenticateFailed(HttpServerResponse response) { + response + .status(HttpResponseStatus.UNAUTHORIZED) + .addHeader(HttpHeaderNames.WWW_AUTHENTICATE, "Basic realm=\"" + REALM + "\""); + return false; + } + private String scrape() { Writer writer = new StringWriter(); PrometheusTextWriter prometheusTextWriter = new PrometheusTextWriter(writer); diff --git a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template index 866238ec71b..8e718667646 100644 --- a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template +++ b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template @@ -357,6 +357,17 @@ pipe_consensus_receiver_file_dirs=data/datanode/system/pipe/consensus/receiver ### Metric Configuration #################### +# The base64 encoded username for the prometheus port of both the ConfigNode and the DataNode. +# If left unset, the prometheus port can be accessed without authentication. +# effectiveMode: restart +# Datatype: String +metric_prometheus_reporter_username= + +# The base64 encoded password for the prometheus port of both the ConfigNode and the DataNode. +# effectiveMode: restart +# Datatype: String +metric_prometheus_reporter_password= + # The reporters of metric module to report metrics # If there are more than one reporter, please separate them by commas ",". # Options: [JMX, PROMETHEUS]
