yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/utils.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 aws 16 17 import ( 18 "fmt" 19 "net" 20 "reflect" 21 "sort" 22 "strings" 23 24 "github.com/aws/aws-sdk-go/service/ec2" 25 26 "yunion.io/x/jsonutils" 27 "yunion.io/x/log" 28 "yunion.io/x/pkg/errors" 29 "yunion.io/x/pkg/util/secrules" 30 31 "yunion.io/x/cloudmux/pkg/cloudprovider" 32 ) 33 34 type portRange struct { 35 Start int64 36 End int64 37 } 38 39 type TagSpec struct { 40 ResourceType string // "customer-gateway"|"dedicated-host"|"dhcp-options"|"image"|"instance"|"internet-gateway"|"network-acl"|"network-interface"|"reserved-instances"|"route-table"|"snapshot"|"spot-instances-request"|"subnet"|"security-group"|"volume"|"vpc"|"vpn-connection"|"vpn-gateway" 41 Tags map[string]string 42 } 43 44 func (self *TagSpec) LoadingEc2Tags(tags []*ec2.Tag) { 45 for _, tag := range tags { 46 if tag.Key != nil && tag.Value != nil { 47 self.SetTag(*tag.Key, *tag.Value) 48 } 49 } 50 } 51 52 func (self *TagSpec) GetTagSpecifications() (*ec2.TagSpecification, error) { 53 if self.ResourceType == "" { 54 return nil, fmt.Errorf("ResourceType should not be empty") 55 } 56 57 spec := &ec2.TagSpecification{ResourceType: &self.ResourceType} 58 tags := []*ec2.Tag{} 59 for k, v := range self.Tags { 60 if len(v) > 255 { 61 return nil, fmt.Errorf("%s value length should less than 255", k) 62 } 63 64 tag := &ec2.Tag{} 65 tag.SetKey(k) 66 tag.SetValue(v) 67 tags = append(tags, tag) 68 } 69 70 spec.SetTags(tags) 71 return spec, nil 72 } 73 74 func (self *TagSpec) SetTag(k, v string) { 75 if self.Tags == nil { 76 self.Tags = make(map[string]string) 77 } 78 self.Tags[k] = v 79 } 80 81 func (self *TagSpec) GetTags() (map[string]string, error) { 82 ret := map[string]string{} 83 for k, v := range self.Tags { 84 if k == "Name" || k == "Description" { 85 continue 86 } 87 ret[k] = v 88 } 89 return ret, nil 90 } 91 92 func (self *TagSpec) SetNameTag(v string) { 93 self.SetTag("Name", v) 94 } 95 96 func (self *TagSpec) SetDescTag(v string) { 97 self.SetTag("Description", v) 98 } 99 100 func (self *TagSpec) GetTag(k string) (string, error) { 101 v, ok := self.Tags[k] 102 if !ok { 103 return "", fmt.Errorf("%s not found", k) 104 } 105 106 return v, nil 107 } 108 109 // 找不到的情况下返回传入的默认值 110 func (self *TagSpec) GetTagWithDefault(k, Default string) string { 111 v, ok := self.Tags[k] 112 if !ok { 113 return Default 114 } 115 116 return v 117 } 118 119 func (self *TagSpec) GetNameTag() string { 120 return self.GetTagWithDefault("Name", "") 121 } 122 123 func (self *TagSpec) GetDescTag() string { 124 return self.GetTagWithDefault("Description", "") 125 } 126 127 func AppendFilter(filters []*ec2.Filter, name string, values []string) []*ec2.Filter { 128 f := &ec2.Filter{} 129 v := make([]*string, len(values)) 130 for _, value := range values { 131 v = append(v, &value) 132 } 133 134 f.SetName(name) 135 f.SetValues(v) 136 return append(filters, f) 137 } 138 139 func AppendSingleValueFilter(filters []*ec2.Filter, name string, value string) []*ec2.Filter { 140 f := &ec2.Filter{} 141 f.SetName(name) 142 f.SetValues([]*string{&value}) 143 return append(filters, f) 144 } 145 146 func ConvertedList(list []string) []*string { 147 result := make([]*string, 0) 148 for i := range list { 149 if len(list[i]) > 0 { 150 result = append(result, &list[i]) 151 } 152 } 153 154 return result 155 } 156 157 func GetBucketName(regionId string, imageId string) string { 158 return fmt.Sprintf("imgcache-%s-%s", strings.ToLower(regionId), imageId) 159 } 160 161 func ConvertedPointList(list []*string) []string { 162 result := make([]string, len(list)) 163 for _, item := range list { 164 if item != nil { 165 result = append(result, *item) 166 } 167 } 168 169 return result 170 } 171 172 func StrVal(s *string) string { 173 if s != nil { 174 return *s 175 } 176 177 return "" 178 } 179 180 func IntVal(s *int64) int64 { 181 if s != nil { 182 return *s 183 } 184 185 return 0 186 } 187 188 // SecurityRuleSet to allow list 189 // 将安全组规则全部转换为等价的allow规则 190 func SecurityRuleSetToAllowSet(srs secrules.SecurityRuleSet) secrules.SecurityRuleSet { 191 inRuleSet := secrules.SecurityRuleSet{} 192 outRuleSet := secrules.SecurityRuleSet{} 193 194 for _, rule := range srs { 195 if rule.Direction == secrules.SecurityRuleIngress { 196 inRuleSet = append(inRuleSet, rule) 197 } 198 199 if rule.Direction == secrules.SecurityRuleEgress { 200 outRuleSet = append(outRuleSet, rule) 201 } 202 } 203 204 sort.Sort(inRuleSet) 205 sort.Sort(outRuleSet) 206 207 inRuleSet = inRuleSet.AllowList() 208 outRuleSet = outRuleSet.AllowList() 209 210 ret := secrules.SecurityRuleSet{} 211 ret = append(ret, inRuleSet...) 212 ret = append(ret, outRuleSet...) 213 return ret 214 } 215 216 func isAwsPermissionAllPorts(p ec2.IpPermission) bool { 217 if p.FromPort == nil || p.ToPort == nil { 218 return false 219 } 220 221 // 全部端口范围: TCP/UDP (0,65535) 其他:(-1,-1) 222 if (*p.IpProtocol == "tcp" || *p.IpProtocol == "udp") && *p.FromPort == 0 && *p.ToPort == 65535 { 223 return true 224 } else if *p.FromPort == -1 && *p.ToPort == -1 { 225 return true 226 } else { 227 return false 228 } 229 } 230 231 func awsProtocolToYunion(p ec2.IpPermission) string { 232 if p.IpProtocol != nil && *p.IpProtocol == "-1" { 233 return secrules.PROTO_ANY 234 } else { 235 return *p.IpProtocol 236 } 237 } 238 239 func yunionProtocolToAws(r cloudprovider.SecurityRule) string { 240 if r.Protocol == secrules.PROTO_ANY { 241 return "-1" 242 } else { 243 return r.Protocol 244 } 245 } 246 247 func isYunionRuleAllPorts(r secrules.SecurityRule) bool { 248 // 全部端口范围: TCP/UDP (0,65535) 其他:(-1,-1) 249 if (r.Protocol == "tcp" || r.Protocol == "udp") && r.PortStart == 0 && r.PortEnd == 65535 { 250 return true 251 } else if r.PortStart == -1 && r.PortEnd == -1 { 252 return true 253 } else { 254 return false 255 } 256 } 257 258 func yunionPortRangeToAws(r cloudprovider.SecurityRule) []portRange { 259 // port 0 / -1 都代表所有端口 260 portranges := []portRange{} 261 if len(r.Ports) == 0 { 262 var start, end = 0, 0 263 if r.PortStart <= 0 { 264 if r.Protocol == "tcp" || r.Protocol == "udp" { 265 start = 0 266 } else { 267 start = -1 268 } 269 } else { 270 start = r.PortStart 271 } 272 273 if r.PortEnd <= 0 { 274 if r.Protocol == "tcp" || r.Protocol == "udp" { 275 end = 65535 276 } else { 277 end = -1 278 } 279 } else { 280 end = r.PortEnd 281 } 282 283 portranges = append(portranges, portRange{int64(start), int64(end)}) 284 } 285 286 for i := range r.Ports { 287 port := r.Ports[i] 288 if port <= 0 && (r.Protocol == "tcp" || r.Protocol == "udp") { 289 portranges = append(portranges, portRange{0, 65535}) 290 } else if port <= 0 { 291 portranges = append(portranges, portRange{-1, -1}) 292 } else { 293 portranges = append(portranges, portRange{int64(port), int64(port)}) 294 } 295 } 296 297 return portranges 298 } 299 300 // Security Rule Transform 301 func AwsIpPermissionToYunion(direction secrules.TSecurityRuleDirection, p ec2.IpPermission) ([]cloudprovider.SecurityRule, error) { 302 303 if len(p.UserIdGroupPairs) > 0 { 304 return nil, fmt.Errorf("AwsIpPermissionToYunion not supported aws rule: UserIdGroupPairs specified") 305 } 306 307 if len(p.PrefixListIds) > 0 { 308 return nil, fmt.Errorf("AwsIpPermissionToYunion not supported aws rule: PrefixListIds specified") 309 } 310 311 if len(p.Ipv6Ranges) > 0 { 312 log.Debugf("AwsIpPermissionToYunion ignored IPV6 rule: %s", p.Ipv6Ranges) 313 } 314 315 rules := []cloudprovider.SecurityRule{} 316 isAllPorts := isAwsPermissionAllPorts(p) 317 protocol := awsProtocolToYunion(p) 318 for _, ip := range p.IpRanges { 319 _, ipNet, err := net.ParseCIDR(*ip.CidrIp) 320 if err != nil { 321 log.Errorf("ParseCIDR failed, ignored IPV4 rule: %s", *ip.CidrIp) 322 continue 323 } 324 325 var rule cloudprovider.SecurityRule 326 if isAllPorts { 327 rule = cloudprovider.SecurityRule{ 328 SecurityRule: secrules.SecurityRule{ 329 Action: secrules.SecurityRuleAllow, 330 IPNet: ipNet, 331 Protocol: protocol, 332 Direction: direction, 333 Priority: 1, 334 Description: StrVal(ip.Description), 335 }, 336 } 337 } else { 338 rule = cloudprovider.SecurityRule{ 339 SecurityRule: secrules.SecurityRule{ 340 Action: secrules.SecurityRuleAllow, 341 IPNet: ipNet, 342 Protocol: protocol, 343 Direction: direction, 344 Priority: 1, 345 Description: StrVal(ip.Description), 346 }, 347 } 348 349 if p.FromPort != nil { 350 rule.PortStart = int(*p.FromPort) 351 } 352 353 if p.ToPort != nil { 354 rule.PortEnd = int(*p.ToPort) 355 } 356 } 357 358 rules = append(rules, rule) 359 360 } 361 362 return rules, nil 363 } 364 365 // YunionSecRuleToAws 不能保证无损转换 366 // 规则描述如果包含中文等字符,将被丢弃掉 367 func YunionSecRuleToAws(rule cloudprovider.SecurityRule) ([]*ec2.IpPermission, error) { 368 iprange := rule.IPNet.String() 369 if iprange == "<nil>" { 370 return nil, fmt.Errorf("YunionSecRuleToAws ignored ipnet should not be empty") 371 } 372 373 ipranges := []*ec2.IpRange{} 374 ipranges = append(ipranges, &ec2.IpRange{CidrIp: &iprange}) 375 376 portranges := yunionPortRangeToAws(rule) 377 protocol := yunionProtocolToAws(rule) 378 permissions := []*ec2.IpPermission{} 379 if rule.Protocol != secrules.PROTO_ANY { 380 for i := range portranges { 381 port := portranges[i] 382 permission := ec2.IpPermission{ 383 FromPort: &port.Start, 384 IpProtocol: &protocol, 385 IpRanges: ipranges, 386 ToPort: &port.End, 387 } 388 389 permissions = append(permissions, &permission) 390 } 391 } else { 392 permissions = append(permissions, &ec2.IpPermission{ 393 IpProtocol: &protocol, 394 IpRanges: ipranges, 395 }) 396 } 397 398 return permissions, nil 399 } 400 401 // fill a pointer struct with zero value. 402 func FillZero(i interface{}) error { 403 V := reflect.Indirect(reflect.ValueOf(i)) 404 405 if !V.CanSet() { 406 return fmt.Errorf("input is not addressable: %#v", i) 407 } 408 409 if V.Kind() != reflect.Struct { 410 return fmt.Errorf("only accept struct type") 411 } 412 413 for i := 0; i < V.NumField(); i++ { 414 field := V.Field(i) 415 416 if field.Kind() == reflect.Ptr && field.IsNil() { 417 if field.CanSet() { 418 field.Set(reflect.New(field.Type().Elem())) 419 } 420 } 421 422 vField := reflect.Indirect(field) 423 switch vField.Kind() { 424 case reflect.Map: 425 vField.Set(reflect.MakeMap(vField.Type())) 426 case reflect.Struct: 427 if field.CanInterface() { 428 err := FillZero(field.Interface()) 429 if err != nil { 430 return err 431 } 432 } 433 } 434 } 435 436 return nil 437 } 438 439 func NextDeviceName(curDeviceNames []string) (string, error) { 440 currents := []string{} 441 for _, item := range curDeviceNames { 442 currents = append(currents, strings.ToLower(item)) 443 } 444 445 for i := 0; i < 25; i++ { 446 device := fmt.Sprintf("/dev/sd%c", byte(98+i)) 447 found := false 448 for _, item := range currents { 449 if strings.HasPrefix(item, device) { 450 found = true 451 } 452 } 453 454 if !found { 455 return device, nil 456 } 457 } 458 459 for i := 0; i < 25; i++ { 460 device := fmt.Sprintf("/dev/vxd%c", byte(98+i)) 461 found := false 462 for _, item := range currents { 463 if !strings.HasPrefix(item, device) { 464 return device, nil 465 } 466 } 467 468 if !found { 469 return device, nil 470 } 471 } 472 473 return "", fmt.Errorf("disk devicename out of index, current deivces: %s", currents) 474 } 475 476 // fetch tags 477 func FetchTags(client *ec2.EC2, resourceId string) (*jsonutils.JSONDict, error) { 478 result := jsonutils.NewDict() 479 params := &ec2.DescribeTagsInput{} 480 filters := []*ec2.Filter{} 481 if len(resourceId) == 0 { 482 return result, fmt.Errorf("resource id should not be empty") 483 } 484 // todo: add resource type filter 485 filters = AppendSingleValueFilter(filters, "resource-id", resourceId) 486 params.SetFilters(filters) 487 488 ret, err := client.DescribeTags(params) 489 if err != nil { 490 return result, err 491 } 492 493 for _, tag := range ret.Tags { 494 if tag.Key != nil && tag.Value != nil { 495 result.Set(*tag.Key, jsonutils.NewString(*tag.Value)) 496 } 497 } 498 499 return result, nil 500 } 501 502 // error 503 func parseNotFoundError(err error) error { 504 if err == nil { 505 return nil 506 } 507 508 if strings.Contains(err.Error(), ".NotFound") { 509 return errors.Wrap(cloudprovider.ErrNotFound, "parseNotFoundError") 510 } else { 511 return err 512 } 513 }