fix: allow security group cidr have multiple prefixes (#25122)

Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
This commit is contained in:
Jian Qiu
2026-07-13 13:58:46 +08:00
committed by GitHub
parent 40b3c82ab4
commit 35b100b03c
7 changed files with 222 additions and 49 deletions
+21 -2
View File
@@ -16,6 +16,7 @@ package compute
import (
"fmt"
"strings"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/regutils"
@@ -136,6 +137,24 @@ type SSecgroupRuleUpdateInput struct {
Description string `json:"description"`
}
func IsValidSecgroupRuleCIDR(cidr string) bool {
isInvalidCidr := func(cidr string) bool {
return !regutils.MatchCIDR(cidr) && !regutils.MatchIP4Addr(cidr) && !regutils.MatchCIDR6(cidr) && !regutils.MatchIP6Addr(cidr)
}
if strings.Contains(cidr, ",") {
cidrs := strings.Split(cidr, ",")
for i := range cidrs {
if isInvalidCidr(cidrs[i]) {
return false
}
}
} else if isInvalidCidr(cidr) {
return false
}
return true
}
func (input *SSecgroupRuleResource) Check() error {
priority := 1
if input.Priority != nil {
@@ -159,8 +178,8 @@ func (input *SSecgroupRuleResource) Check() error {
}
if len(input.CIDR) > 0 {
if !regutils.MatchCIDR(input.CIDR) && !regutils.MatchIP4Addr(input.CIDR) && !regutils.MatchCIDR6(input.CIDR) && !regutils.MatchIP6Addr(input.CIDR) {
return fmt.Errorf("invalid ip address: %s", input.CIDR)
if !IsValidSecgroupRuleCIDR(input.CIDR) {
return fmt.Errorf("invalid cidr: %s", input.CIDR)
}
} else {
// empty CIDR means both IPv4 and IPv6
+2 -2
View File
@@ -3500,7 +3500,7 @@ func (self *SGuest) getSecurityGroupsRules() string {
}
rules := []string{}
for _, rule := range secrules {
rules = append(rules, rule.String())
rules = append(rules, rule.Strings()...)
}
return strings.Join(rules, SECURITY_GROUP_SEPARATOR)
}
@@ -3520,7 +3520,7 @@ func (self *SGuest) getNetworkSecurityGroupsRules(networkIndex int) string {
}
rules := []string{}
for _, rule := range secrules {
rules = append(rules, rule.String())
rules = append(rules, rule.Strings()...)
}
return strings.Join(rules, SECURITY_GROUP_SEPARATOR)
}
+30 -42
View File
@@ -16,7 +16,6 @@ package models
import (
"context"
"net"
"strings"
"yunion.io/x/cloudmux/pkg/cloudprovider"
@@ -25,7 +24,6 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/compare"
"yunion.io/x/pkg/util/rbacscope"
"yunion.io/x/pkg/util/regutils"
"yunion.io/x/pkg/util/secrules"
"yunion.io/x/pkg/util/stringutils"
"yunion.io/x/sqlchemy"
@@ -39,6 +37,7 @@ import (
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/onecloud/pkg/util/netutils2"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
@@ -319,10 +318,6 @@ func (self *SSecurityGroupRule) ValidateUpdateData(ctx context.Context, userCred
return nil, err
}
if input.CIDR == nil {
// input.CIDR = &self.CIDR
}
driver, err := secgrp.GetRegionDriver()
if err != nil {
return nil, err
@@ -341,15 +336,19 @@ func (self *SSecurityGroupRule) ValidateUpdateData(ctx context.Context, userCred
return input, nil
}
func (self *SSecurityGroupRule) String() string {
rule, err := self.toRule()
func (self *SSecurityGroupRule) Strings() []string {
rules, err := self.toRules()
if err != nil {
return ""
return nil
}
return rule.String()
ruleStrs := make([]string, len(rules))
for i := range rules {
ruleStrs[i] = rules[i].String()
}
return ruleStrs
}
func (self *SSecurityGroupRule) toRule() (*secrules.SecurityRule, error) {
func (self *SSecurityGroupRule) toRules() ([]*secrules.SecurityRule, error) {
rule := secrules.SecurityRule{
Priority: int(self.Priority),
Direction: secrules.TSecurityRuleDirection(self.Direction),
@@ -357,33 +356,31 @@ func (self *SSecurityGroupRule) toRule() (*secrules.SecurityRule, error) {
Protocol: self.Protocol,
Description: self.Description,
}
if regutils.MatchCIDR(self.CIDR) || regutils.MatchCIDR6(self.CIDR) {
_, rule.IPNet, _ = net.ParseCIDR(self.CIDR)
} else if regutils.MatchIP4Addr(self.CIDR) {
rule.IPNet = &net.IPNet{
IP: net.ParseIP(self.CIDR),
Mask: net.CIDRMask(32, 32),
{
err := rule.ParsePorts(self.Ports)
if err != nil {
return nil, errors.Wrap(err, "ParsePorts")
}
} else if regutils.MatchIP6Addr(self.CIDR) {
rule.IPNet = &net.IPNet{
IP: net.ParseIP(self.CIDR),
Mask: net.CIDRMask(128, 128),
}
{
err := rule.ValidateRule()
if err != nil {
return nil, errors.Wrap(err, "ValidateRule")
}
} else {
// any
}
ipnets := netutils2.Str2IPNets(self.CIDR)
if len(ipnets) == 0 {
rule.IPNet = nil
/* &net.IPNet{
IP: net.IPv4zero,
Mask: net.CIDRMask(0, 32),
} */
return []*secrules.SecurityRule{&rule}, nil
}
err := rule.ParsePorts(self.Ports)
if err != nil {
return nil, err
rules := make([]*secrules.SecurityRule, len(ipnets))
for i := range ipnets {
ruleClone := rule
ruleClone.IPNet = ipnets[i]
rules[i] = &ruleClone
}
return &rule, rule.ValidateRule()
return rules, nil
}
func (self *SSecurityGroupRule) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
@@ -448,15 +445,6 @@ func (self *SSecurityGroup) StartSecurityGroupRuleUpdateTask(ctx context.Context
return task.ScheduleRun(nil)
}
func (manager *SSecurityGroupRuleManager) getRulesBySecurityGroup(secgroup *SSecurityGroup) ([]SSecurityGroupRule, error) {
rules := make([]SSecurityGroupRule, 0)
q := manager.Query().Equals("secgroup_id", secgroup.Id)
if err := db.FetchModelObjects(manager, q, &rules); err != nil {
return nil, err
}
return rules, nil
}
func (self *SSecurityGroupRule) GetOwnerId() mcclient.IIdentityProvider {
secgrp, _ := self.GetSecGroup()
if secgrp != nil {
+1 -1
View File
@@ -592,7 +592,7 @@ func (self *SSecurityGroup) getSecurityRuleString() (string, error) {
}
var rules []string
for _, rule := range secgrouprules {
rules = append(rules, rule.String())
rules = append(rules, rule.Strings()...)
}
return strings.Join(rules, SECURITY_GROUP_SEPARATOR), nil
}
+1 -2
View File
@@ -28,7 +28,6 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/httputils"
randutil "yunion.io/x/pkg/util/rand"
"yunion.io/x/pkg/util/regutils"
"yunion.io/x/pkg/util/secrules"
"yunion.io/x/pkg/util/sets"
"yunion.io/x/pkg/utils"
@@ -1602,7 +1601,7 @@ func (self *SKVMRegionDriver) ValidateUpdateSecurityGroupRuleInput(ctx context.C
}
}
if input.CIDR != nil && len(*input.CIDR) > 0 && !regutils.MatchCIDR(*input.CIDR) && !regutils.MatchIP4Addr(*input.CIDR) && !regutils.MatchCIDR6(*input.CIDR) && !regutils.MatchIP6Addr(*input.CIDR) {
if input.CIDR != nil && len(*input.CIDR) > 0 && !api.IsValidSecgroupRuleCIDR(*input.CIDR) {
return nil, httperrors.NewInputParameterError("invalid cidr %s", *input.CIDR)
}
+103
View File
@@ -0,0 +1,103 @@
// Copyright 2019 Yunion
//
// Licensed 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 netutils2
import (
"net"
"strings"
"yunion.io/x/pkg/util/netutils"
"yunion.io/x/pkg/util/regutils"
)
func str2ipRange(cidr string) (*netutils.IPV4AddrRange, *netutils.IPV6AddrRange) {
if regutils.MatchCIDR(cidr) {
v4prefix, err := netutils.NewIPV4Prefix(cidr)
if err != nil {
return nil, nil
}
v4range := v4prefix.ToIPRange()
return &v4range, nil
} else if regutils.MatchCIDR6(cidr) {
v6prefix, err := netutils.NewIPV6Prefix(cidr)
if err != nil {
return nil, nil
}
v6range := v6prefix.ToIPRange()
return nil, &v6range
} else if regutils.MatchIP4Addr(cidr) {
v4addr, err := netutils.NewIPV4Addr(cidr)
if err != nil {
return nil, nil
}
v4range := netutils.NewIPV4AddrRange(v4addr, v4addr)
return &v4range, nil
} else if regutils.MatchIP6Addr(cidr) {
v6addr, err := netutils.NewIPV6Addr(cidr)
if err != nil {
return nil, nil
}
v6range := netutils.NewIPV6AddrRange(v6addr, v6addr)
return nil, &v6range
}
return nil, nil
}
func str2ipRangeList(cidr string) ([]netutils.IPV4AddrRange, []netutils.IPV6AddrRange) {
v4ranges := []netutils.IPV4AddrRange{}
v6ranges := []netutils.IPV6AddrRange{}
if strings.Contains(cidr, ",") {
cidrStrs := strings.Split(cidr, ",")
for _, cidrStr := range cidrStrs {
cidrStr = strings.TrimSpace(cidrStr)
v4range, v6range := str2ipRange(cidrStr)
if v4range != nil {
v4ranges = append(v4ranges, *v4range)
}
if v6range != nil {
v6ranges = append(v6ranges, *v6range)
}
}
} else {
v4range, v6range := str2ipRange(cidr)
if v4range != nil {
v4ranges = append(v4ranges, *v4range)
}
if v6range != nil {
v6ranges = append(v6ranges, *v6range)
}
}
if len(v4ranges) > 0 {
v4ranges = netutils.IPV4AddrRangeList(v4ranges).Merge()
}
if len(v6ranges) > 0 {
v6ranges = netutils.IPV6AddrRangeList(v6ranges).Merge()
}
return v4ranges, v6ranges
}
func Str2IPNets(cidr string) []*net.IPNet {
v4ranges, v6ranges := str2ipRangeList(cidr)
ipnets := []*net.IPNet{}
for i := range v4ranges {
v4nets := v4ranges[i].ToIPNets()
ipnets = append(ipnets, v4nets...)
}
for i := range v6ranges {
v6nets := v6ranges[i].ToIPNets()
ipnets = append(ipnets, v6nets...)
}
return ipnets
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2019 Yunion
//
// Licensed 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 netutils2
import (
"fmt"
"net"
"testing"
)
func TestStr2IPNets(t *testing.T) {
tests := []struct {
cidr string
want []*net.IPNet
}{
{
cidr: "192.168.1.0/24",
want: []*net.IPNet{
{IP: net.IPv4(192, 168, 1, 0), Mask: net.CIDRMask(24, 32)},
},
},
{
cidr: "192.168.1.0/24,192.168.2.0/24",
want: []*net.IPNet{
{IP: net.IPv4(192, 168, 1, 0), Mask: net.CIDRMask(24, 32)},
{IP: net.IPv4(192, 168, 2, 0), Mask: net.CIDRMask(24, 32)},
},
},
{
cidr: "192.168.1.0/24,192.168.2.0/24,192.168.3.0/24",
want: []*net.IPNet{
{IP: net.IPv4(192, 168, 1, 0), Mask: net.CIDRMask(24, 32)},
{IP: net.IPv4(192, 168, 2, 0), Mask: net.CIDRMask(23, 32)},
},
},
{
cidr: "192.168.2.0/24,192.168.2.0/24,192.168.3.0/24",
want: []*net.IPNet{
{IP: net.IPv4(192, 168, 2, 0), Mask: net.CIDRMask(23, 32)},
},
},
}
for _, test := range tests {
got := Str2IPNets(test.cidr)
gotStr := fmt.Sprintf("%v", got)
wantStr := fmt.Sprintf("%v", test.want)
if gotStr != wantStr {
t.Errorf("Str2IPNets(%s) = %s, want %s", test.cidr, gotStr, wantStr)
}
}
}