This is an automated email from the ASF dual-hosted git repository.
weizhouapache pushed a commit to branch 4.20
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.20 by this push:
new 376c1b40174 server: scope IPv6 security group member rules to the
exact host (#14037)
376c1b40174 is described below
commit 376c1b40174ab4b8d7f9aced5401143cbc09d587
Author: Ramgopal Nagaboina <[email protected]>
AuthorDate: Wed Oct 7 02:18:41 2026 -0400
server: scope IPv6 security group member rules to the exact host (#14037)
* server: scope IPv6 security group member rules to the exact host
When a security group rule references another security group, each member VM
should be authorized as an exact host. The IPv4 address is correctly pinned
to
a /32, but the IPv6 address was expanded to /64, opening the whole subnet
the
member sits in rather than just that member. Pin the IPv6 member to /128 to
match the IPv4 behaviour.
* server: scope the Impl2 IPv6 security group member rule to the exact host
---
.../network/security/SecurityGroupManagerImpl.java | 2 +-
.../security/SecurityGroupManagerImpl2.java | 2 +-
.../SecurityGroupManagerImpl2Ipv6RuleTest.java | 82 ++++++++++++++++++++
.../SecurityGroupManagerImplIpv6RuleTest.java | 90 ++++++++++++++++++++++
4 files changed, 174 insertions(+), 2 deletions(-)
diff --git
a/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl.java
b/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl.java
index f0e3b32ebaa..8b938775265 100644
---
a/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl.java
+++
b/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl.java
@@ -356,7 +356,7 @@ public class SecurityGroupManagerImpl extends ManagerBase
implements SecurityGro
cidr = cidr + "/32";
cidrs.add(cidr);
if (defaultNic.getIPv6Address() != null) {
- cidrs.add(defaultNic.getIPv6Address() + "/64");
+ cidrs.add(defaultNic.getIPv6Address() +
"/128");
}
}
}
diff --git
a/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl2.java
b/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl2.java
index bd532f8162f..28ec7c0d384 100644
---
a/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl2.java
+++
b/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl2.java
@@ -250,7 +250,7 @@ public class SecurityGroupManagerImpl2 extends
SecurityGroupManagerImpl {
String cidr = ngmapVO.getGuestIpAddress() + "/32";
cidrs.add(cidr);
if (ngmapVO.getGuestIpv6Address() != null) {
- cidrs.add(ngmapVO.getGuestIpv6Address() + "/64");
+ cidrs.add(ngmapVO.getGuestIpv6Address() + "/128");
}
}
} else if (rule.getAllowedSourceIpCidr() != null) {
diff --git
a/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImpl2Ipv6RuleTest.java
b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImpl2Ipv6RuleTest.java
new file mode 100644
index 00000000000..b8cbd1534d3
--- /dev/null
+++
b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImpl2Ipv6RuleTest.java
@@ -0,0 +1,82 @@
+// 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 com.cloud.network.security;
+
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.cloud.network.security.SecurityGroupManagerImpl.PortAndProto;
+import com.cloud.network.security.SecurityRule.SecurityRuleType;
+import com.cloud.network.security.dao.SecurityGroupRuleDao;
+import com.cloud.network.security.dao.SecurityGroupVMMapDao;
+import com.cloud.vm.VirtualMachine.State;
+
+@RunWith(MockitoJUnitRunner.Silent.class)
+public class SecurityGroupManagerImpl2Ipv6RuleTest {
+
+ @Mock
+ SecurityGroupRuleDao _securityGroupRuleDao;
+ @Mock
+ SecurityGroupVMMapDao _securityGroupVMMapDao;
+
+ @InjectMocks
+ SecurityGroupManagerImpl2 manager = new SecurityGroupManagerImpl2();
+
+ @Test
+ public void securityGroupMemberRuleUsesExactIpv6HostCidr() {
+ Long vmId = 1L;
+ SecurityRuleType type = SecurityRuleType.IngressRule;
+
+ // The VM belongs to security group 10, which has one rule referencing
another security group (20).
+ SecurityGroupVMMapVO groupMap =
Mockito.mock(SecurityGroupVMMapVO.class);
+ when(groupMap.getSecurityGroupId()).thenReturn(10L);
+
when(_securityGroupVMMapDao.listByInstanceId(vmId)).thenReturn(Collections.singletonList(groupMap));
+
+ SecurityGroupRuleVO rule = Mockito.mock(SecurityGroupRuleVO.class);
+ when(rule.getProtocol()).thenReturn("tcp");
+ when(rule.getStartPort()).thenReturn(80);
+ when(rule.getEndPort()).thenReturn(80);
+ when(rule.getAllowedNetworkId()).thenReturn(20L);
+ when(_securityGroupRuleDao.listBySecurityGroupId(10L,
type)).thenReturn(Collections.singletonList(rule));
+
+ // Unlike the superclass, Impl2 reads the member addresses straight
from the VO join, not the nics table.
+ SecurityGroupVMMapVO memberMap =
Mockito.mock(SecurityGroupVMMapVO.class);
+ when(memberMap.getGuestIpAddress()).thenReturn("10.1.1.5");
+ when(memberMap.getGuestIpv6Address()).thenReturn("2001:db8::5");
+ when(_securityGroupVMMapDao.listBySecurityGroup(20L,
State.Running)).thenReturn(Collections.singletonList(memberMap));
+
+ Map<PortAndProto, Set<String>> allowed =
manager.generateRulesForVM(vmId, type);
+
+ Assert.assertEquals(1, allowed.size());
+ Set<String> cidrs = allowed.values().iterator().next();
+ // The member must be authorized as an exact host, matching the IPv4
/32 behaviour.
+ Assert.assertTrue("IPv4 member should be pinned to /32",
cidrs.contains("10.1.1.5/32"));
+ Assert.assertTrue("IPv6 member should be pinned to the exact /128
host", cidrs.contains("2001:db8::5/128"));
+ Assert.assertFalse("IPv6 member must not open the whole /64 subnet",
cidrs.contains("2001:db8::5/64"));
+ }
+}
diff --git
a/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImplIpv6RuleTest.java
b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImplIpv6RuleTest.java
new file mode 100644
index 00000000000..e2b7e96ff26
--- /dev/null
+++
b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImplIpv6RuleTest.java
@@ -0,0 +1,90 @@
+// 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 com.cloud.network.security;
+
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.cloud.network.NetworkModel;
+import com.cloud.network.security.SecurityGroupManagerImpl.PortAndProto;
+import com.cloud.network.security.SecurityRule.SecurityRuleType;
+import com.cloud.network.security.dao.SecurityGroupRuleDao;
+import com.cloud.network.security.dao.SecurityGroupVMMapDao;
+import com.cloud.vm.Nic;
+import com.cloud.vm.VirtualMachine.State;
+
+@RunWith(MockitoJUnitRunner.Silent.class)
+public class SecurityGroupManagerImplIpv6RuleTest {
+
+ @Mock
+ SecurityGroupRuleDao _securityGroupRuleDao;
+ @Mock
+ SecurityGroupVMMapDao _securityGroupVMMapDao;
+ @Mock
+ NetworkModel _networkModel;
+
+ @InjectMocks
+ SecurityGroupManagerImpl manager = new SecurityGroupManagerImpl();
+
+ @Test
+ public void securityGroupMemberRuleUsesExactIpv6HostCidr() {
+ Long vmId = 1L;
+ SecurityRuleType type = SecurityRuleType.IngressRule;
+
+ // The VM belongs to security group 10, which has one rule referencing
another security group (20).
+ SecurityGroupVMMapVO groupMap =
Mockito.mock(SecurityGroupVMMapVO.class);
+ when(groupMap.getSecurityGroupId()).thenReturn(10L);
+
when(_securityGroupVMMapDao.listByInstanceId(vmId)).thenReturn(Collections.singletonList(groupMap));
+
+ SecurityGroupRuleVO rule = Mockito.mock(SecurityGroupRuleVO.class);
+ when(rule.getProtocol()).thenReturn("tcp");
+ when(rule.getStartPort()).thenReturn(80);
+ when(rule.getEndPort()).thenReturn(80);
+ when(rule.getAllowedNetworkId()).thenReturn(20L);
+ when(_securityGroupRuleDao.listBySecurityGroupId(10L,
type)).thenReturn(Collections.singletonList(rule));
+
+ // Group 20 has one running member VM with both an IPv4 and an IPv6
address.
+ SecurityGroupVMMapVO memberMap =
Mockito.mock(SecurityGroupVMMapVO.class);
+ when(memberMap.getInstanceId()).thenReturn(2L);
+ when(_securityGroupVMMapDao.listBySecurityGroup(20L,
State.Running)).thenReturn(Collections.singletonList(memberMap));
+
+ Nic nic = Mockito.mock(Nic.class);
+ when(nic.getIPv4Address()).thenReturn("10.1.1.5");
+ when(nic.getIPv6Address()).thenReturn("2001:db8::5");
+ when(_networkModel.getDefaultNic(2L)).thenReturn(nic);
+
+ Map<PortAndProto, Set<String>> allowed =
manager.generateRulesForVM(vmId, type);
+
+ Assert.assertEquals(1, allowed.size());
+ Set<String> cidrs = allowed.values().iterator().next();
+ // The member must be authorized as an exact host, matching the IPv4
/32 behaviour.
+ Assert.assertTrue("IPv4 member should be pinned to /32",
cidrs.contains("10.1.1.5/32"));
+ Assert.assertTrue("IPv6 member should be pinned to the exact /128
host", cidrs.contains("2001:db8::5/128"));
+ Assert.assertFalse("IPv6 member must not open the whole /64 subnet",
cidrs.contains("2001:db8::5/64"));
+ }
+}