Skip to content

Commit 3b3ebbb

Browse files
committed
prometheus: skip blank/non-IP allowlist entries, avoid DNS, cache parsed list
1 parent ee399dd commit 3b3ebbb

2 files changed

Lines changed: 97 additions & 12 deletions

File tree

‎plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterServerImpl.java‎

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.cloudstack.metrics;
1818

1919
import com.cloud.utils.component.ManagerBase;
20+
import com.cloud.utils.net.NetUtils;
2021
import com.sun.net.httpserver.HttpExchange;
2122
import com.sun.net.httpserver.HttpHandler;
2223
import com.sun.net.httpserver.HttpServer;
@@ -31,7 +32,9 @@
3132
import java.net.UnknownHostException;
3233
import java.nio.charset.StandardCharsets;
3334
import java.util.Arrays;
34-
import java.util.List;
35+
import java.util.Collections;
36+
import java.util.Objects;
37+
import java.util.Set;
3538
import java.util.stream.Collectors;
3639

3740
public class PrometheusExporterServerImpl extends ManagerBase implements PrometheusExporterServer, Configurable {
@@ -41,6 +44,9 @@ public class PrometheusExporterServerImpl extends ManagerBase implements Prometh
4144
@Inject
4245
private PrometheusExporter prometheusExporter;
4346

47+
private String allowedAddressesConfig;
48+
private Set<String> allowedAddresses = Collections.emptySet();
49+
4450
private final class ExporterHandler implements HttpHandler {
4551
private PrometheusExporter prometheusExporter;
4652

@@ -55,17 +61,7 @@ public void handle(final HttpExchange httpExchange) throws IOException {
5561
logger.debug("Prometheus exporter received client request from: " + remoteClientAddress);
5662
String response = "Forbidden";
5763
int responseCode = 403;
58-
final List<String> allowedAddresses = Arrays.stream(PrometheusExporterAllowedAddresses.value().split(","))
59-
.map(addr -> {
60-
try {
61-
return InetAddress.getByName(addr.trim()).getHostAddress();
62-
} catch (UnknownHostException e) {
63-
logger.warn("Invalid IP address in prometheus.exporter.allowed.ips: " + addr.trim());
64-
return addr.trim();
65-
}
66-
})
67-
.collect(Collectors.toList());
68-
if (allowedAddresses.contains(remoteClientAddress)) {
64+
if (getAllowedAddresses().contains(remoteClientAddress)) {
6965
prometheusExporter.updateMetrics();
7066
response = prometheusExporter.getMetrics();
7167
responseCode = 200;
@@ -88,6 +84,42 @@ public void handle(final HttpExchange httpExchange) throws IOException {
8884
}
8985
}
9086

87+
private synchronized Set<String> getAllowedAddresses() {
88+
final String config = PrometheusExporterAllowedAddresses.value();
89+
if (!config.equals(allowedAddressesConfig)) {
90+
allowedAddresses = normalizeAddresses(config);
91+
allowedAddressesConfig = config;
92+
}
93+
return allowedAddresses;
94+
}
95+
96+
/**
97+
* Normalizes a comma separated list of IP addresses to their canonical text form, so that
98+
* equivalent notations (e.g. ::1 and 0:0:0:0:0:0:0:1) match the remote client address.
99+
* Blank entries and anything that is not an IP literal are ignored, so no DNS resolution takes place.
100+
*/
101+
protected Set<String> normalizeAddresses(final String addresses) {
102+
return Arrays.stream(addresses.split(","))
103+
.map(String::trim)
104+
.filter(addr -> !addr.isEmpty())
105+
.map(this::normalizeAddress)
106+
.filter(Objects::nonNull)
107+
.collect(Collectors.toSet());
108+
}
109+
110+
private String normalizeAddress(final String address) {
111+
if (!NetUtils.isValidIp4(address) && !NetUtils.isValidIp6(address)) {
112+
logger.warn("Ignoring invalid IP address [{}] in {}", address, PrometheusExporterAllowedAddresses.key());
113+
return null;
114+
}
115+
try {
116+
return InetAddress.getByName(address).getHostAddress();
117+
} catch (UnknownHostException e) {
118+
logger.warn("Ignoring IP address [{}] in {} that could not be parsed", address, PrometheusExporterAllowedAddresses.key(), e);
119+
return null;
120+
}
121+
}
122+
91123
@Override
92124
public boolean start() {
93125
if (EnablePrometheusExporter.value()) {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.metrics;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertTrue;
21+
22+
import java.util.Set;
23+
24+
import org.junit.Test;
25+
26+
public class PrometheusExporterServerImplTest {
27+
28+
private final PrometheusExporterServerImpl server = new PrometheusExporterServerImpl();
29+
30+
@Test
31+
public void testNormalizeAddressesIpv6LoopbackForms() {
32+
Set<String> addresses = server.normalizeAddresses("::1");
33+
assertEquals(server.normalizeAddresses("0:0:0:0:0:0:0:1"), addresses);
34+
assertTrue(addresses.contains("0:0:0:0:0:0:0:1"));
35+
}
36+
37+
@Test
38+
public void testNormalizeAddressesTrimsEntries() {
39+
Set<String> addresses = server.normalizeAddresses(" 127.0.0.1 , 10.1.1.1");
40+
assertEquals(Set.of("127.0.0.1", "10.1.1.1"), addresses);
41+
}
42+
43+
@Test
44+
public void testNormalizeAddressesIgnoresBlankEntries() {
45+
assertTrue(server.normalizeAddresses("").isEmpty());
46+
assertEquals(Set.of("10.1.1.1"), server.normalizeAddresses(",10.1.1.1,, ,"));
47+
}
48+
49+
@Test
50+
public void testNormalizeAddressesIgnoresNonIpEntries() {
51+
assertEquals(Set.of("10.1.1.1"), server.normalizeAddresses("localhost,10.1.1.1,not-an-ip"));
52+
}
53+
}

0 commit comments

Comments
 (0)