diff --git a/pkg/apis/compute/secgroup.go b/pkg/apis/compute/secgroup.go index c36573e7d3..a113145244 100644 --- a/pkg/apis/compute/secgroup.go +++ b/pkg/apis/compute/secgroup.go @@ -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 diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 1bf5c2df98..63265c2b2e 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -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) } diff --git a/pkg/compute/models/secgrouprules.go b/pkg/compute/models/secgrouprules.go index 3073bc9130..54aaa9c620 100644 --- a/pkg/compute/models/secgrouprules.go +++ b/pkg/compute/models/secgrouprules.go @@ -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 { diff --git a/pkg/compute/models/secgroups.go b/pkg/compute/models/secgroups.go index 784cdbd14e..c05eb7338e 100644 --- a/pkg/compute/models/secgroups.go +++ b/pkg/compute/models/secgroups.go @@ -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 } diff --git a/pkg/compute/regiondrivers/kvm.go b/pkg/compute/regiondrivers/kvm.go index 51cea61c4a..e09f31e75b 100644 --- a/pkg/compute/regiondrivers/kvm.go +++ b/pkg/compute/regiondrivers/kvm.go @@ -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) } diff --git a/pkg/util/netutils2/ipnets.go b/pkg/util/netutils2/ipnets.go new file mode 100644 index 0000000000..bbbcccdec9 --- /dev/null +++ b/pkg/util/netutils2/ipnets.go @@ -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 +} diff --git a/pkg/util/netutils2/ipnets_test.go b/pkg/util/netutils2/ipnets_test.go new file mode 100644 index 0000000000..d4255a2f7b --- /dev/null +++ b/pkg/util/netutils2/ipnets_test.go @@ -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) + } + } +}