This is an automated email from the ASF dual-hosted git repository. liyuheng pushed a commit to branch Working/prometheus-add-auth in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 18db34aeb7254c029545e4137ae2eb2da860cc0e Author: liyuheng <[email protected]> AuthorDate: Thu Apr 17 17:43:35 2025 +0800 init --- .../apache/iotdb/metrics/config/MetricConfig.java | 26 ++++++++++ .../metrics/config/MetricConfigDescriptor.java | 4 ++ .../reporter/prometheus/PrometheusReporter.java | 57 +++++++++++++++++++--- .../conf/iotdb-system.properties.template | 7 +++ 4 files changed, 86 insertions(+), 8 deletions(-) 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..fcd1d8b3f14 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 @@ -49,6 +49,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 +131,26 @@ public class MetricConfig { this.prometheusReporterPort = prometheusReporterPort; } + public boolean prometheusNeedAuth() { + return !Objects.equals(prometheusReporterUsername, "") || !Objects.equals(prometheusReporterPassword, ""); + } + + public String getPrometheusReporterUsername() { + return prometheusReporterUsername; + } + + public void setPrometheusReporterUsername(String prometheusReporterUsername) { + this.prometheusReporterUsername = prometheusReporterUsername; + } + + public String getPrometheusReporterPassword() { + return prometheusReporterPassword; + } + + public void setPrometheusReporterPassword(String prometheusReporterPassword) { + this.prometheusReporterPassword = prometheusReporterPassword; + } + public IoTDBReporterConfig getIoTDBReporterConfig() { return iotdbReporterConfig; } @@ -181,6 +205,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..b881ef31bd4 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,10 @@ public class MetricConfigDescriptor { properties, isConfigNode))); + loadConfig.setPrometheusReporterUsername(properties.getProperty("metric_prometheus_reporter_username", loadConfig.getPrometheusReporterUsername())); + + loadConfig.setPrometheusReporterPassword(properties.getProperty("metric_prometheus_reporter_password", loadConfig.getPrometheusReporterPassword())); + IoTDBReporterConfig reporterConfig = loadConfig.getIoTDBReporterConfig(); reporterConfig.setHost( getProperty( 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..07ffc147600 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 @@ -19,6 +19,8 @@ package org.apache.iotdb.metrics.reporter.prometheus; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; import org.apache.iotdb.metrics.AbstractMetricManager; import org.apache.iotdb.metrics.config.MetricConfig; import org.apache.iotdb.metrics.config.MetricConfigDescriptor; @@ -43,11 +45,15 @@ 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,8 @@ public class PrometheusReporter implements Reporter { private final AbstractMetricManager metricManager; private DisposableServer httpServer; + private static final String REALM = "metrics"; + public PrometheusReporter(AbstractMetricManager metricManager) { this.metricManager = metricManager; } @@ -76,14 +84,13 @@ public class PrometheusReporter implements Reporter { .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000) .channelGroup(new DefaultChannelGroup(GlobalEventExecutor.INSTANCE)) .port(METRIC_CONFIG.getPrometheusReporterPort()) - .route( - routes -> - routes.get( - "/metrics", - (request, response) -> - response - .addHeader("Content-Type", "text/plain") - .sendString(Mono.just(scrape())))) + .route(routes -> routes.get("/metrics", (req, res) -> { + if (!authenticate(req, res)) { + return Mono.empty(); // authenticate not pass + } + 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 +104,40 @@ 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 ")) { + return authenticateFailed(res); + } + + // base64String is expected like "Basic dXNlcjpwYXNzd29yZA==" + String base64String = header.substring("Basic ".length()); + // decodedString is expected like "user:123456" + String decodedString = new String(Base64.getDecoder().decode(base64String), StandardCharsets.UTF_8); + int idx = decodedString.indexOf(':'); + if (idx < 0) { + LOGGER.info("Unexpected auth string: {}", decodedString); + return authenticateFailed(res); + } + + String username = decodedString.substring(0, idx); + String password = decodedString.substring(idx + 1); + if (!METRIC_CONFIG.getPrometheusReporterUsername().equals(username) || !METRIC_CONFIG.getPrometheusReporterPassword().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 a7f00234c06..e85d431595e 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 @@ -394,6 +394,13 @@ cn_metric_async_collect_period=5 # Datatype: int cn_metric_prometheus_reporter_port=9091 +# The username for the Prometheus port of both the ConfigNode and the DataNode. +# If left unset, the Prometheus port can be accessed without authentication. +metric_prometheus_reporter_username= + +# The password for the Prometheus port of both the ConfigNode and the DataNode. +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]
