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  }