This is an automated email from the ASF dual-hosted git repository.

nvazquez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 2a4d7d9036c Fix Prometheus exporter not recognizing ::1 as IPv6 
loopback (#13245)
2a4d7d9036c is described below

commit 2a4d7d9036ca72374054cf83853923e4282e6297
Author: dahn <[email protected]>
AuthorDate: Thu Nov 5 15:38:29 2026 +0100

    Fix Prometheus exporter not recognizing ::1 as IPv6 loopback (#13245)
    
    * Fix Prometheus exporter not recognizing ::1 as IPv6 loopback
    
    Normalize IPv6 addresses using InetAddress.getHostAddress() so that
    compressed forms like ::1 and full forms like 0:0:0:0:0:0:0:1 are
    treated as equivalent when checking the allowed IPs list.
    
    Fixes #6714
    
    * prometheus: skip blank/non-IP allowlist entries, avoid DNS, cache parsed 
list
---
 .../metrics/PrometheusExporterServerImpl.java      | 50 +++++++++++++++++++-
 .../metrics/PrometheusExporterServerImplTest.java  | 53 ++++++++++++++++++++++
 2 files changed, 101 insertions(+), 2 deletions(-)

diff --git 
a/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterServerImpl.java
 
b/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterServerImpl.java
index d9f25d2f577..5c51994a0ce 100644
--- 
a/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterServerImpl.java
+++ 
b/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterServerImpl.java
@@ -17,6 +17,7 @@
 package org.apache.cloudstack.metrics;
 
 import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.net.NetUtils;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
@@ -26,9 +27,15 @@ import org.apache.cloudstack.framework.config.Configurable;
 import javax.inject.Inject;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 public class PrometheusExporterServerImpl extends ManagerBase implements 
PrometheusExporterServer, Configurable {
 
@@ -37,6 +44,9 @@ public class PrometheusExporterServerImpl extends ManagerBase 
implements Prometh
     @Inject
     private PrometheusExporter prometheusExporter;
 
+    private String allowedAddressesConfig;
+    private Set<String> allowedAddresses = Collections.emptySet();
+
     private final class ExporterHandler implements HttpHandler {
         private PrometheusExporter prometheusExporter;
 
@@ -47,11 +57,11 @@ public class PrometheusExporterServerImpl extends 
ManagerBase implements Prometh
 
         @Override
         public void handle(final HttpExchange httpExchange) throws IOException 
{
-            final String remoteClientAddress = 
httpExchange.getRemoteAddress().getAddress().toString().replace("/", "");
+            final String remoteClientAddress = 
httpExchange.getRemoteAddress().getAddress().getHostAddress();
             logger.debug("Prometheus exporter received client request from: " 
+ remoteClientAddress);
             String response = "Forbidden";
             int responseCode = 403;
-            if 
(Arrays.asList(PrometheusExporterAllowedAddresses.value().split(",")).contains(remoteClientAddress))
 {
+            if (getAllowedAddresses().contains(remoteClientAddress)) {
                 prometheusExporter.updateMetrics();
                 response = prometheusExporter.getMetrics();
                 responseCode = 200;
@@ -74,6 +84,42 @@ public class PrometheusExporterServerImpl extends 
ManagerBase implements Prometh
         }
     }
 
+    private synchronized Set<String> getAllowedAddresses() {
+        final String config = PrometheusExporterAllowedAddresses.value();
+        if (!config.equals(allowedAddressesConfig)) {
+            allowedAddresses = normalizeAddresses(config);
+            allowedAddressesConfig = config;
+        }
+        return allowedAddresses;
+    }
+
+    /**
+     * Normalizes a comma separated list of IP addresses to their canonical 
text form, so that
+     * equivalent notations (e.g. ::1 and 0:0:0:0:0:0:0:1) match the remote 
client address.
+     * Blank entries and anything that is not an IP literal are ignored, so no 
DNS resolution takes place.
+     */
+    protected Set<String> normalizeAddresses(final String addresses) {
+        return Arrays.stream(addresses.split(","))
+                .map(String::trim)
+                .filter(addr -> !addr.isEmpty())
+                .map(this::normalizeAddress)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+    }
+
+    private String normalizeAddress(final String address) {
+        if (!NetUtils.isValidIp4(address) && !NetUtils.isValidIp6(address)) {
+            logger.warn("Ignoring invalid IP address [{}] in {}", address, 
PrometheusExporterAllowedAddresses.key());
+            return null;
+        }
+        try {
+            return InetAddress.getByName(address).getHostAddress();
+        } catch (UnknownHostException e) {
+            logger.warn("Ignoring IP address [{}] in {} that could not be 
parsed", address, PrometheusExporterAllowedAddresses.key(), e);
+            return null;
+        }
+    }
+
     @Override
     public boolean start() {
         if (EnablePrometheusExporter.value()) {
diff --git 
a/plugins/integrations/prometheus/src/test/java/org/apache/cloudstack/metrics/PrometheusExporterServerImplTest.java
 
b/plugins/integrations/prometheus/src/test/java/org/apache/cloudstack/metrics/PrometheusExporterServerImplTest.java
new file mode 100644
index 00000000000..835389f227f
--- /dev/null
+++ 
b/plugins/integrations/prometheus/src/test/java/org/apache/cloudstack/metrics/PrometheusExporterServerImplTest.java
@@ -0,0 +1,53 @@
+// 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.cloudstack.metrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.junit.Test;
+
+public class PrometheusExporterServerImplTest {
+
+    private final PrometheusExporterServerImpl server = new 
PrometheusExporterServerImpl();
+
+    @Test
+    public void testNormalizeAddressesIpv6LoopbackForms() {
+        Set<String> addresses = server.normalizeAddresses("::1");
+        assertEquals(server.normalizeAddresses("0:0:0:0:0:0:0:1"), addresses);
+        assertTrue(addresses.contains("0:0:0:0:0:0:0:1"));
+    }
+
+    @Test
+    public void testNormalizeAddressesTrimsEntries() {
+        Set<String> addresses = server.normalizeAddresses(" 127.0.0.1 , 
10.1.1.1");
+        assertEquals(Set.of("127.0.0.1", "10.1.1.1"), addresses);
+    }
+
+    @Test
+    public void testNormalizeAddressesIgnoresBlankEntries() {
+        assertTrue(server.normalizeAddresses("").isEmpty());
+        assertEquals(Set.of("10.1.1.1"), 
server.normalizeAddresses(",10.1.1.1,, ,"));
+    }
+
+    @Test
+    public void testNormalizeAddressesIgnoresNonIpEntries() {
+        assertEquals(Set.of("10.1.1.1"), 
server.normalizeAddresses("localhost,10.1.1.1,not-an-ip"));
+    }
+}

Reply via email to