From 0b53714603ff2160a4ce28c21e9cdfa969e457e0 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 21 May 2026 10:55:34 +0200 Subject: [PATCH 1/2] Root CA: support multiple management network CIDRs with comma separated --- .../ca/provider/RootCAProvider.java | 12 ++- .../ca/provider/RootCAProviderTest.java | 79 +++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java index 25c45ed2a102..5330b0a47a6e 100644 --- a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java +++ b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java @@ -401,15 +401,21 @@ private boolean loadManagementKeyStore() { protected void addConfiguredManagementIp(List ipList) { String msNetworkCidr = configDao.getValue(Config.ManagementNetwork.key()); + if (StringUtils.isEmpty(msNetworkCidr)) { + return; + } try { logger.debug(String.format("Trying to find management IP in CIDR range [%s].", msNetworkCidr)); Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.asIterator().forEachRemaining(networkInterface -> { networkInterface.getInetAddresses().asIterator().forEachRemaining(inetAddress -> { - if (NetUtils.isIpWithInCidrRange(inetAddress.getHostAddress(), msNetworkCidr)) { - ipList.add(inetAddress.getHostAddress()); - logger.debug(String.format("Added IP [%s] to the list of IPs in the management server's certificate.", inetAddress.getHostAddress())); + String[] msNetworkCidrs = msNetworkCidr.split(","); + for (String cidr : msNetworkCidrs) { + if (NetUtils.isIpWithInCidrRange(inetAddress.getHostAddress(), cidr)) { + ipList.add(inetAddress.getHostAddress()); + logger.debug(String.format("Added IP [%s] to the list of IPs in the management server's certificate.", inetAddress.getHostAddress())); + } } }); }); diff --git a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java index 8311f4d45abc..d01c89e3adad 100644 --- a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java +++ b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java @@ -20,6 +20,8 @@ package org.apache.cloudstack.ca.provider; import java.lang.reflect.Field; +import java.net.NetworkInterface; +import java.net.SocketException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; @@ -38,6 +40,7 @@ import org.apache.cloudstack.framework.ca.Certificate; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.utils.security.CertUtils; import org.apache.cloudstack.utils.security.SSLUtils; import org.bouncycastle.asn1.x509.GeneralName; @@ -47,10 +50,14 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import com.cloud.configuration.Config; +import com.cloud.utils.exception.CloudRuntimeException; + @RunWith(MockitoJUnitRunner.class) public class RootCAProviderTest { @@ -208,4 +215,76 @@ public void testIsManagementCertificateMatch() { Assert.fail(String.format("Exception occurred: %s", e.getMessage())); } } + + // --------------------------------------------------------------- + // Tests for addConfiguredManagementIp + // --------------------------------------------------------------- + + private ConfigurationDao mockConfigDao(String cidr) throws Exception { + ConfigurationDao mockDao = Mockito.mock(ConfigurationDao.class); + Mockito.when(mockDao.getValue(Config.ManagementNetwork.key())).thenReturn(cidr); + addField(provider, "configDao", mockDao); + return mockDao; + } + + @Test + public void testAddConfiguredManagementIpWithMatchingCidr() throws Exception { + // 127.0.0.0/8 covers the loopback address (127.0.0.1) that is always + // present on a Linux host, so the method must add it to the list. + mockConfigDao("127.0.0.0/8"); + + List ipList = new ArrayList<>(); + provider.addConfiguredManagementIp(ipList); + + Assert.assertTrue("127.0.0.1 should be included for CIDR 127.0.0.0/8", + ipList.contains("127.0.0.1")); + } + + @Test + public void testAddConfiguredManagementIpWithNonMatchingCidr() throws Exception { + // 192.0.2.0/24 is TEST-NET-1 (RFC 5737) and is never assigned to a real + // interface, so nothing should be added to the list. + mockConfigDao("192.0.2.0/24"); + + List ipList = new ArrayList<>(); + provider.addConfiguredManagementIp(ipList); + + Assert.assertTrue("No IP should be added when no interface matches the CIDR", + ipList.isEmpty()); + } + + @Test + public void testAddConfiguredManagementIpWithMultipleCidrs() throws Exception { + // First CIDR is a non-matching TEST-NET; second covers loopback. + // The method splits on "," and checks each CIDR individually, so + // 127.0.0.1 must still be found via the second CIDR. + mockConfigDao("192.0.2.0/24,127.0.0.0/8"); + + List ipList = new ArrayList<>(); + provider.addConfiguredManagementIp(ipList); + + Assert.assertTrue("127.0.0.1 should be included when the second comma-separated CIDR matches", + ipList.contains("127.0.0.1")); + } + + @Test + public void testAddConfiguredManagementIpSocketException() throws Exception { + mockConfigDao("127.0.0.0/8"); + + try (MockedStatic networkInterfaceMock = + Mockito.mockStatic(NetworkInterface.class)) { + networkInterfaceMock.when(NetworkInterface::getNetworkInterfaces) + .thenThrow(new SocketException("simulated network error")); + + try { + provider.addConfiguredManagementIp(new ArrayList<>()); + Assert.fail("Expected CloudRuntimeException to be thrown on SocketException"); + } catch (CloudRuntimeException e) { + Assert.assertTrue("Exception message should describe the failure", + e.getMessage().contains("Exception while trying to gather the management server's network interfaces.")); + Assert.assertTrue("Cause should be the original SocketException", + e.getCause() instanceof SocketException); + } + } + } } From f52077e92ab562aa66dc05b38bb3593be3241f82 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 21 May 2026 11:17:31 +0200 Subject: [PATCH 2/2] Apply suggestion from @weizhouapache --- .../java/org/apache/cloudstack/ca/provider/RootCAProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java index 5330b0a47a6e..472e7754600f 100644 --- a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java +++ b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java @@ -412,7 +412,7 @@ protected void addConfiguredManagementIp(List ipList) { networkInterface.getInetAddresses().asIterator().forEachRemaining(inetAddress -> { String[] msNetworkCidrs = msNetworkCidr.split(","); for (String cidr : msNetworkCidrs) { - if (NetUtils.isIpWithInCidrRange(inetAddress.getHostAddress(), cidr)) { + if (NetUtils.isIpWithInCidrRange(inetAddress.getHostAddress(), cidr.trim())) { ipList.add(inetAddress.getHostAddress()); logger.debug(String.format("Added IP [%s] to the list of IPs in the management server's certificate.", inetAddress.getHostAddress())); }