github.com/cilium/cilium@v1.16.2/pkg/policy/groups/aws/aws.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package aws
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/netip"
    10  
    11  	"github.com/aws/aws-sdk-go-v2/aws"
    12  	"github.com/aws/aws-sdk-go-v2/service/ec2"
    13  	ec2_types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
    14  
    15  	cilium_ec2 "github.com/cilium/cilium/pkg/aws/ec2"
    16  	"github.com/cilium/cilium/pkg/policy/api"
    17  )
    18  
    19  var (
    20  	policySecurityGroupIDKey = aws.String("group-id")
    21  	policySecurityGroupName  = aws.String("group-name")
    22  	policyEC2Labelskey       = "tag"
    23  )
    24  
    25  func init() {
    26  	api.RegisterToGroupsProvider(api.AWSProvider, GetIPsFromGroup)
    27  }
    28  
    29  // GetIPsFromGroup will return the list of the IPs for the given group filter
    30  func GetIPsFromGroup(ctx context.Context, group *api.Groups) ([]netip.Addr, error) {
    31  	result := []netip.Addr{}
    32  	if group.AWS == nil {
    33  		return result, fmt.Errorf("no aws data available")
    34  	}
    35  
    36  	cfg, err := cilium_ec2.NewConfig(ctx)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	ec2Client := ec2.NewFromConfig(cfg)
    41  
    42  	// If the group has a security group filter, add the IPs from the network interfaces
    43  	if len(group.AWS.SecurityGroupsIds) > 0 || len(group.AWS.SecurityGroupsNames) > 0 {
    44  		ips, err := getNetworkInterfaceIpsFromFilter(ctx, group.AWS, ec2Client)
    45  		if err != nil {
    46  			return nil, err
    47  		}
    48  		result = append(result, ips...)
    49  	}
    50  
    51  	// If the group has a label filter, add the IPs from the instances
    52  	if len(group.AWS.Labels) > 0 {
    53  		ips, err := getInstancesIpsFromFilter(ctx, group.AWS, ec2Client)
    54  		if err != nil {
    55  			return nil, err
    56  		}
    57  		result = append(result, ips...)
    58  	}
    59  
    60  	return result, nil
    61  }
    62  
    63  // getNetworkInterfaceIpsFromFilter returns the IPs from the network interfaces for
    64  // the given security group filter
    65  func getNetworkInterfaceIpsFromFilter(ctx context.Context, filter *api.AWSGroup, ec2Client *ec2.Client) ([]netip.Addr, error) {
    66  	result := []netip.Addr{}
    67  	input := &ec2.DescribeNetworkInterfacesInput{}
    68  
    69  	if len(filter.SecurityGroupsIds) > 0 {
    70  		input.Filters = append(input.Filters, ec2_types.Filter{
    71  			Name:   policySecurityGroupIDKey,
    72  			Values: filter.SecurityGroupsIds,
    73  		})
    74  	}
    75  	if len(filter.SecurityGroupsNames) > 0 {
    76  		input.Filters = append(input.Filters, ec2_types.Filter{
    77  			Name:   policySecurityGroupName,
    78  			Values: filter.SecurityGroupsNames,
    79  		})
    80  	}
    81  
    82  	paginator := ec2.NewDescribeNetworkInterfacesPaginator(ec2Client, input)
    83  	for paginator.HasMorePages() {
    84  		output, err := paginator.NextPage(ctx)
    85  		if err != nil {
    86  			return nil, fmt.Errorf("cannot retrieve aws network interface information: %w", err)
    87  		}
    88  		// functionally equivalent to extractIPs, we return private IPs and associated public IPs
    89  		for _, iface := range output.NetworkInterfaces {
    90  			for _, ifaceIP := range iface.PrivateIpAddresses {
    91  				addr, err := netip.ParseAddr(aws.ToString(ifaceIP.PrivateIpAddress))
    92  				if err != nil {
    93  					continue
    94  				}
    95  				result = append(result, addr)
    96  				if ifaceIP.Association != nil {
    97  					addr, err = netip.ParseAddr(aws.ToString(ifaceIP.Association.PublicIp))
    98  					if err != nil {
    99  						continue
   100  					}
   101  					result = append(result, addr)
   102  				}
   103  			}
   104  		}
   105  	}
   106  	return result, nil
   107  }
   108  
   109  // getInstancesIpsFromFilter returns IPs from matching instances for the given
   110  // label filter
   111  func getInstancesIpsFromFilter(ctx context.Context, filter *api.AWSGroup, ec2Client *ec2.Client) ([]netip.Addr, error) {
   112  	var result []ec2_types.Reservation
   113  	input := &ec2.DescribeInstancesInput{}
   114  
   115  	for labelKey, labelValue := range filter.Labels {
   116  		newFilter := ec2_types.Filter{
   117  			Name:   aws.String(policyEC2Labelskey + ":" + labelKey),
   118  			Values: []string{labelValue},
   119  		}
   120  		input.Filters = append(input.Filters, newFilter)
   121  	}
   122  
   123  	paginator := ec2.NewDescribeInstancesPaginator(ec2Client, input)
   124  	for paginator.HasMorePages() {
   125  		output, err := paginator.NextPage(ctx)
   126  		if err != nil {
   127  			return nil, fmt.Errorf("cannot retrieve aws ec2 instance information: %w", err)
   128  		}
   129  		result = append(result, output.Reservations...)
   130  	}
   131  	return extractIPs(result), nil
   132  }
   133  
   134  // extractIPs returns the private and associated public IPs from the given reservations
   135  func extractIPs(reservations []ec2_types.Reservation) []netip.Addr {
   136  	result := []netip.Addr{}
   137  	for _, reservation := range reservations {
   138  		for _, instance := range reservation.Instances {
   139  			for _, iface := range instance.NetworkInterfaces {
   140  				for _, ifaceIP := range iface.PrivateIpAddresses {
   141  					addr, err := netip.ParseAddr(aws.ToString(ifaceIP.PrivateIpAddress))
   142  					if err != nil {
   143  						continue
   144  					}
   145  					result = append(result, addr)
   146  					if ifaceIP.Association != nil {
   147  						addr, err = netip.ParseAddr(aws.ToString(ifaceIP.Association.PublicIp))
   148  						if err != nil {
   149  							continue
   150  						}
   151  						result = append(result, addr)
   152  					}
   153  				}
   154  			}
   155  		}
   156  	}
   157  	return result
   158  }