yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/securitygroup.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package azure
    16  
    17  import (
    18  	"fmt"
    19  	"net"
    20  	"net/url"
    21  	"strconv"
    22  	"strings"
    23  	"unicode"
    24  
    25  	"yunion.io/x/jsonutils"
    26  	"yunion.io/x/log"
    27  	"yunion.io/x/pkg/errors"
    28  	"yunion.io/x/pkg/util/regutils"
    29  	"yunion.io/x/pkg/util/secrules"
    30  	"yunion.io/x/pkg/utils"
    31  
    32  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    33  	"yunion.io/x/cloudmux/pkg/multicloud"
    34  )
    35  
    36  type SecurityRulePropertiesFormat struct {
    37  	Description                string   `json:"description,omitempty"`
    38  	Protocol                   string   `json:"protocol,omitempty"`
    39  	SourcePortRange            string   `json:"sourcePortRange,omitempty"`
    40  	DestinationPortRange       string   `json:"destinationPortRange,omitempty"`
    41  	SourceAddressPrefix        string   `json:"sourceAddressPrefix,omitempty"`
    42  	SourceAddressPrefixes      []string `json:"sourceAddressPrefixes,omitempty"`
    43  	DestinationAddressPrefix   string   `json:"destinationAddressPrefix,omitempty"`
    44  	DestinationAddressPrefixes []string `json:"destinationAddressPrefixes,omitempty"`
    45  	SourcePortRanges           []string `json:"sourcePortRanges,omitempty"`
    46  	DestinationPortRanges      []string `json:"destinationPortRanges,omitempty"`
    47  	Access                     string   `json:"access,omitempty"` // Allow or Deny
    48  	Priority                   int32    `json:"priority,omitempty"`
    49  	Direction                  string   `json:"direction,omitempty"` //Inbound or Outbound
    50  	ProvisioningState          string   `json:"-"`
    51  }
    52  type SecurityRules struct {
    53  	Properties SecurityRulePropertiesFormat
    54  	Name       string
    55  	ID         string
    56  }
    57  
    58  type Interface struct {
    59  	ID string
    60  }
    61  
    62  type SecurityGroupPropertiesFormat struct {
    63  	SecurityRules        []SecurityRules `json:"securityRules,omitempty"`
    64  	DefaultSecurityRules []SecurityRules `json:"defaultSecurityRules,omitempty"`
    65  	NetworkInterfaces    *[]Interface    `json:"networkInterfaces,omitempty"`
    66  	Subnets              *[]SNetwork     `json:"subnets,omitempty"`
    67  	ProvisioningState    string          //Possible values are: 'Updating', 'Deleting', and 'Failed'
    68  }
    69  type SSecurityGroup struct {
    70  	multicloud.SSecurityGroup
    71  	AzureTags
    72  
    73  	region     *SRegion
    74  	Properties *SecurityGroupPropertiesFormat `json:"properties,omitempty"`
    75  	ID         string
    76  	Name       string
    77  	Location   string
    78  	Type       string
    79  }
    80  
    81  func parseCIDR(cidr string) (*net.IPNet, error) {
    82  	if cidr == "*" || strings.ToLower(cidr) == "internet" {
    83  		cidr = "0.0.0.0/0"
    84  	}
    85  	if strings.Index(cidr, "/") > 0 {
    86  		_, ipnet, err := net.ParseCIDR(cidr)
    87  		return ipnet, err
    88  	}
    89  	ip := net.ParseIP(cidr)
    90  	if ip == nil {
    91  		return nil, fmt.Errorf("Parse ip %s error", cidr)
    92  	}
    93  	return &net.IPNet{IP: ip, Mask: net.CIDRMask(32, 32)}, nil
    94  }
    95  
    96  type rulePorts struct {
    97  	ports     []int
    98  	portStart int
    99  	portEnd   int
   100  }
   101  
   102  func parsePorts(ports string) (rulePorts, error) {
   103  	result := rulePorts{
   104  		portStart: -1,
   105  		portEnd:   -1,
   106  		ports:     []int{},
   107  	}
   108  	if ports == "*" {
   109  		return result, nil
   110  	} else if strings.Index(ports, ",") > 0 {
   111  		for _, _port := range strings.Split(ports, ",") {
   112  			port, err := strconv.Atoi(_port)
   113  			if err != nil {
   114  				msg := fmt.Sprintf("parse rule port %s error: %v", ports, err)
   115  				log.Errorf(msg)
   116  				return result, fmt.Errorf(msg)
   117  			}
   118  			result.ports = append(result.ports, port)
   119  		}
   120  	} else if strings.Index(ports, "-") > 0 {
   121  		_ports := strings.Split(ports, "-")
   122  		if len(_ports) == 2 {
   123  			portStart, err := strconv.Atoi(_ports[0])
   124  			if err != nil {
   125  				msg := fmt.Sprintf("parse rule port %s error: %v", ports, err)
   126  				log.Errorf(msg)
   127  				return result, fmt.Errorf(msg)
   128  			}
   129  			result.portStart = portStart
   130  			portEnd, err := strconv.Atoi(_ports[1])
   131  			if err != nil {
   132  				msg := fmt.Sprintf("parse rule port %s error: %v", ports, err)
   133  				log.Errorf(msg)
   134  				return result, fmt.Errorf(msg)
   135  			}
   136  			result.portEnd = portEnd
   137  		}
   138  	} else {
   139  		_port, err := strconv.Atoi(ports)
   140  		if err != nil {
   141  			msg := fmt.Sprintf("parse rule port %s error: %v", ports, err)
   142  			log.Errorf(msg)
   143  			return result, fmt.Errorf(msg)
   144  		}
   145  		result.ports = append(result.ports, _port)
   146  	}
   147  	return result, nil
   148  }
   149  
   150  func paresPortsWithIpNet(port string, ports []string, ip string, ips []string) ([]rulePorts, []*net.IPNet, error) {
   151  	portsResult, ipResult := []rulePorts{}, []*net.IPNet{}
   152  	if len(port) > 0 {
   153  		_ports, err := parsePorts(port)
   154  		if err != nil {
   155  			return nil, nil, err
   156  		}
   157  		portsResult = append(portsResult, _ports)
   158  	} else if len(ports) > 0 {
   159  		for i := 0; i < len(ports); i++ {
   160  			_ports, err := parsePorts(ports[i])
   161  			if err != nil {
   162  				return nil, nil, err
   163  			}
   164  			portsResult = append(portsResult, _ports)
   165  		}
   166  	}
   167  
   168  	if len(ip) > 0 {
   169  		ipnet, err := parseCIDR(ip)
   170  		if err != nil {
   171  			return nil, nil, err
   172  		}
   173  		ipResult = append(ipResult, ipnet)
   174  	} else if len(ips) > 0 {
   175  		for i := 0; i < len(ips); i++ {
   176  			ipnet, err := parseCIDR(ips[i])
   177  			if err != nil {
   178  				return nil, nil, err
   179  			}
   180  			ipResult = append(ipResult, ipnet)
   181  		}
   182  	}
   183  	return portsResult, ipResult, nil
   184  }
   185  
   186  func (self *SecurityRulePropertiesFormat) toRules() ([]cloudprovider.SecurityRule, error) {
   187  	result := []cloudprovider.SecurityRule{}
   188  	rule := cloudprovider.SecurityRule{
   189  		SecurityRule: secrules.SecurityRule{
   190  			Action:      secrules.TSecurityRuleAction(strings.ToLower(self.Access)),
   191  			Direction:   secrules.TSecurityRuleDirection(strings.Replace(strings.ToLower(self.Direction), "bound", "", -1)),
   192  			Protocol:    strings.ToLower(self.Protocol),
   193  			Priority:    int(self.Priority),
   194  			Description: self.Description,
   195  		}}
   196  
   197  	if rule.Protocol == "*" {
   198  		rule.Protocol = "any"
   199  	}
   200  
   201  	addressPrefix, addressPrefixes := "", []string{}
   202  	if rule.Direction == secrules.DIR_IN {
   203  		addressPrefix, addressPrefixes = self.SourceAddressPrefix, self.SourceAddressPrefixes
   204  	} else {
   205  		addressPrefix, addressPrefixes = self.DestinationAddressPrefix, self.DestinationAddressPrefixes
   206  	}
   207  
   208  	if strings.ToLower(addressPrefix) == "internet" || addressPrefix == "*" {
   209  		addressPrefix = "0.0.0.0/0"
   210  	}
   211  
   212  	if !regutils.MatchIPAddr(addressPrefix) && !regutils.MatchCIDR(addressPrefix) && len(addressPrefixes) == 0 {
   213  		return nil, nil
   214  	}
   215  
   216  	ports, ips, err := paresPortsWithIpNet(self.DestinationPortRange, self.DestinationPortRanges, addressPrefix, addressPrefixes)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	for i := 0; i < len(ips); i++ {
   222  		rule.IPNet = ips[i]
   223  		for j := 0; j < len(ports); j++ {
   224  			rule.Ports = ports[j].ports
   225  			rule.PortStart = ports[j].portStart
   226  			rule.PortEnd = ports[j].portEnd
   227  			err := rule.ValidateRule()
   228  			if err != nil && err != secrules.ErrInvalidPriority {
   229  				return nil, err
   230  			}
   231  			result = append(result, rule)
   232  		}
   233  	}
   234  
   235  	return result, nil
   236  }
   237  
   238  func (self *SecurityRulePropertiesFormat) String() string {
   239  	rules, err := self.toRules()
   240  	if err != nil {
   241  		log.Errorf("convert secrules error: %v", err)
   242  		return ""
   243  	}
   244  	result := []string{}
   245  	for i := 0; i < len(rules); i++ {
   246  		result = append(result, rules[i].String())
   247  	}
   248  	return strings.Join(result, ";")
   249  }
   250  
   251  func (self *SSecurityGroup) GetId() string {
   252  	return self.ID
   253  }
   254  
   255  func (self *SSecurityGroup) GetGlobalId() string {
   256  	return strings.ToLower(self.ID)
   257  }
   258  
   259  func (self *SSecurityGroup) GetDescription() string {
   260  	return ""
   261  }
   262  
   263  func (self *SSecurityGroup) GetName() string {
   264  	return self.Name
   265  }
   266  
   267  func (self *SSecurityGroup) GetRules() ([]cloudprovider.SecurityRule, error) {
   268  	rules := make([]cloudprovider.SecurityRule, 0)
   269  	if self.Properties == nil || self.Properties.SecurityRules == nil {
   270  		return rules, nil
   271  	}
   272  	for _, _rule := range self.Properties.SecurityRules {
   273  		secRules, err := _rule.Properties.toRules()
   274  		if err != nil {
   275  			return nil, errors.Wrap(err, "_rule.Properties.toRules")
   276  		}
   277  		for i := range secRules {
   278  			secRules[i].Name = _rule.Name
   279  			rules = append(rules, secRules[i])
   280  		}
   281  	}
   282  	return rules, nil
   283  }
   284  
   285  func (self *SSecurityGroup) GetStatus() string {
   286  	return ""
   287  }
   288  
   289  func (self *SSecurityGroup) IsEmulated() bool {
   290  	return false
   291  }
   292  
   293  func (self *SSecurityGroup) GetVpcId() string {
   294  	return "normal"
   295  }
   296  
   297  func (region *SRegion) CreateSecurityGroup(secName string) (*SSecurityGroup, error) {
   298  	if secName == "Default" {
   299  		secName = "Default-copy"
   300  	}
   301  	secgroup := &SSecurityGroup{region: region}
   302  	params := map[string]string{
   303  		"Name":     secName,
   304  		"Type":     "Microsoft.Network/networkSecurityGroups",
   305  		"Location": region.Name,
   306  	}
   307  	return secgroup, region.create("", jsonutils.Marshal(params), secgroup)
   308  }
   309  
   310  func (region *SRegion) ListSecgroups() ([]SSecurityGroup, error) {
   311  	secgroups := []SSecurityGroup{}
   312  	err := region.list("Microsoft.Network/networkSecurityGroups", url.Values{}, &secgroups)
   313  	if err != nil {
   314  		return nil, errors.Wrapf(err, "list")
   315  	}
   316  	return secgroups, nil
   317  }
   318  
   319  func (region *SRegion) GetSecurityGroupDetails(secgroupId string) (*SSecurityGroup, error) {
   320  	secgroup := SSecurityGroup{region: region}
   321  	return &secgroup, region.get(secgroupId, url.Values{}, &secgroup)
   322  }
   323  
   324  func (self *SSecurityGroup) Refresh() error {
   325  	sec, err := self.region.GetSecurityGroupDetails(self.ID)
   326  	if err != nil {
   327  		return err
   328  	}
   329  	return jsonutils.Update(self, sec)
   330  }
   331  
   332  func convertRulePort(rule cloudprovider.SecurityRule) []string {
   333  	ports := []string{}
   334  	if len(rule.Ports) > 0 {
   335  		for i := 0; i < len(rule.Ports); i++ {
   336  			ports = append(ports, fmt.Sprintf("%d", rule.Ports[i]))
   337  		}
   338  		return ports
   339  	}
   340  	if rule.PortStart > 0 && rule.PortEnd < 65535 {
   341  		if rule.PortStart == rule.PortEnd {
   342  			return []string{fmt.Sprintf("%d", rule.PortStart)}
   343  		}
   344  		ports = append(ports, fmt.Sprintf("%d-%d", rule.PortStart, rule.PortEnd))
   345  	}
   346  	return ports
   347  }
   348  
   349  func convertSecurityGroupRule(rule cloudprovider.SecurityRule) *SecurityRules {
   350  	if len(rule.Name) == 0 {
   351  		rule.Name = fmt.Sprintf("%s_%d", rule.String(), rule.Priority)
   352  	}
   353  	rule.Name = func(name string) string {
   354  		// 名称必须以字母或数字开头,以字母、数字或下划线结尾,并且只能包含字母、数字、下划线、句点或连字符
   355  		for _, s := range name {
   356  			if !(unicode.IsDigit(s) || unicode.IsLetter(s) || s == '.' || s == '-' || s == '_') {
   357  				name = strings.ReplaceAll(name, string(s), "_")
   358  			}
   359  		}
   360  		if !unicode.IsDigit(rune(name[0])) && !unicode.IsLetter(rune(name[0])) {
   361  			name = fmt.Sprintf("r_%s", name)
   362  		}
   363  		last := len(name) - 1
   364  		if !unicode.IsDigit(rune(name[last])) && !unicode.IsLetter(rune(name[last])) && name[last] != '_' {
   365  			name = fmt.Sprintf("%s_", name)
   366  		}
   367  		return name
   368  	}(rule.Name)
   369  	destRule := SecurityRules{
   370  		Name: rule.Name,
   371  		Properties: SecurityRulePropertiesFormat{
   372  			Access:                   utils.Capitalize(string(rule.Action)),
   373  			Priority:                 int32(rule.Priority),
   374  			Protocol:                 "*",
   375  			Direction:                utils.Capitalize((string(rule.Direction) + "bound")),
   376  			Description:              rule.Description,
   377  			DestinationAddressPrefix: "*",
   378  			DestinationPortRanges:    convertRulePort(rule),
   379  			SourcePortRange:          "*",
   380  			SourceAddressPrefix:      "*",
   381  			DestinationPortRange:     "*",
   382  		},
   383  	}
   384  	if rule.Protocol != secrules.PROTO_ANY {
   385  		destRule.Properties.Protocol = utils.Capitalize(rule.Protocol)
   386  	}
   387  
   388  	if len(destRule.Properties.DestinationPortRanges) > 0 {
   389  		destRule.Properties.DestinationPortRange = ""
   390  	}
   391  
   392  	ipAddr := "*"
   393  	if rule.IPNet != nil {
   394  		ipAddr = rule.IPNet.String()
   395  	}
   396  	if rule.Direction == secrules.DIR_IN {
   397  		destRule.Properties.SourceAddressPrefix = ipAddr
   398  	} else {
   399  		destRule.Properties.DestinationAddressPrefix = ipAddr
   400  	}
   401  	return &destRule
   402  }
   403  
   404  func (region *SRegion) AttachSecurityToInterfaces(secgroupId string, nicIds []string) error {
   405  	for _, nicId := range nicIds {
   406  		nic, err := region.GetNetworkInterface(nicId)
   407  		if err != nil {
   408  			return err
   409  		}
   410  		nic.Properties.NetworkSecurityGroup = SSecurityGroup{ID: secgroupId}
   411  		if err := region.update(jsonutils.Marshal(nic), nil); err != nil {
   412  			return err
   413  		}
   414  	}
   415  	return nil
   416  }
   417  
   418  func (region *SRegion) SetSecurityGroup(instanceId, secgroupId string) error {
   419  	instance, err := region.GetInstance(instanceId)
   420  	if err != nil {
   421  		return err
   422  	}
   423  	nicIds := []string{}
   424  	for _, nic := range instance.Properties.NetworkProfile.NetworkInterfaces {
   425  		nicIds = append(nicIds, nic.ID)
   426  	}
   427  	return region.AttachSecurityToInterfaces(secgroupId, nicIds)
   428  }
   429  
   430  func (self *SSecurityGroup) GetProjectId() string {
   431  	return getResourceGroup(self.ID)
   432  }
   433  
   434  func (self *SSecurityGroup) Delete() error {
   435  	if self.Properties != nil && self.Properties.NetworkInterfaces != nil {
   436  		for _, nic := range *self.Properties.NetworkInterfaces {
   437  			nic, err := self.region.GetNetworkInterface(nic.ID)
   438  			if err != nil {
   439  				return err
   440  			}
   441  			if err := self.region.update(jsonutils.Marshal(nic), nil); err != nil {
   442  				return err
   443  			}
   444  		}
   445  	}
   446  	return self.region.del(self.ID)
   447  }
   448  
   449  func (self *SSecurityGroup) SetRules(rules []cloudprovider.SecurityRule) error {
   450  	names := []string{}
   451  	securityRules := []SecurityRules{}
   452  	for i := 0; i < len(rules); i++ {
   453  		rule := convertSecurityGroupRule(rules[i])
   454  		if rule != nil {
   455  			for {
   456  				if !utils.IsInStringArray(rule.Name, names) {
   457  					names = append(names, rule.Name)
   458  					break
   459  				}
   460  				rule.Name = fmt.Sprintf("%s_", rule.Name)
   461  			}
   462  			securityRules = append(securityRules, *rule)
   463  		}
   464  	}
   465  	if self.Properties == nil {
   466  		self.Properties = &SecurityGroupPropertiesFormat{}
   467  	}
   468  	self.Properties.SecurityRules = securityRules
   469  	self.Properties.ProvisioningState = ""
   470  	return self.region.update(jsonutils.Marshal(self), nil)
   471  }
   472  
   473  func (self *SSecurityGroup) SyncRules(common, inAdds, outAdds, inDels, outDels []cloudprovider.SecurityRule) error {
   474  	for i := range common {
   475  		switch common[i].Direction {
   476  		case secrules.DIR_IN:
   477  			inAdds = append(inAdds, common[i])
   478  		case secrules.DIR_OUT:
   479  			outAdds = append(outAdds, common[i])
   480  		default:
   481  			return fmt.Errorf("invalid rule %s direction %s", common[i].String(), common[i].Direction)
   482  		}
   483  	}
   484  	// Azure 不允许同方向的规则优先级相同
   485  	inRules := cloudprovider.SortUniqPriority(inAdds)
   486  	outRules := cloudprovider.SortUniqPriority(outAdds)
   487  	rules := append(inRules, outRules...)
   488  	return self.SetRules(rules)
   489  }