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 }