package main import ( "fmt" "sort" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2/ec2iface" "github.com/racker/janus-passport/server/log" ) func explicitPublicAssocations(svc ec2iface.EC2API, vpcFilter *ec2.Filter) ([]*ec2.RouteTableAssociation, error) { var assocations []*ec2.RouteTableAssociation filters := []*ec2.Filter{ vpcFilter, &ec2.Filter{ Name: aws.String("route.destination-cidr-block"), Values: []*string{aws.String("0.0.0.0/0")}, }, &ec2.Filter{ Name: aws.String("route.gateway-id"), Values: []*string{aws.String("^igw-.*")}, }, } params := &ec2.DescribeRouteTablesInput{} params = params.SetFilters(filters) // If a subnet is not explicitly associated with any route table, // it is implicitly associated with the main route table. // This command does not return the subnet ID for implicit associations. tables, err := svc.DescribeRouteTables(params) if err != nil { return nil, err } for _, table := range tables.RouteTables { log.Info(fmt.Sprintf("Found %d public assocations", len(table.Associations))) assocations = append(assocations, table.Associations...) } return assocations, nil } // find the subnets that are not explicitly associated with any route table // **assuming the Main route table is internet enabled** func implicitMainSubnets(svc ec2iface.EC2API, vpcFilter *ec2.Filter) ([]*ec2.Subnet, error) { // 1. list available subnets in this vpc // 2. call describe route tables with each subnet // 3. return the subnets that return zero assocations filters := []*ec2.Filter{ vpcFilter, &ec2.Filter{ Name: aws.String("state"), Values: []*string{aws.String("available")}, }, } params := &ec2.DescribeSubnetsInput{} params = params.SetFilters(filters) allSubnets, err := svc.DescribeSubnets(params) if err != nil { return nil, err } var subnets []*ec2.Subnet for _, subnet := range allSubnets.Subnets { filters := []*ec2.Filter{ vpcFilter, &ec2.Filter{ Name: aws.String("association.subnet-id"), Values: []*string{aws.String(*subnet.SubnetId)}, }, } // If a subnet is not explicitly associated with any route table, // it is implicitly associated with the main route table. // This command does not return the subnet ID for implicit associations. // // NB: If this chunk of logic gets moved out of this function, // Calling DescribeRouteTables with an _invalid_ subnet-id // in the filter will *also* return an empty array for RouteTables // i.e. DescribeRouteTables will not validate your subnet id // if that is used in the filter. No danger here in its initial form // but if the subnet id used is not guaranteed to be legit, we // might end up thinking a bogus subnet ID is implicitly associated // with the Main route table, since the return value looks the same. params := &ec2.DescribeRouteTablesInput{} params = params.SetFilters(filters) tables, err := svc.DescribeRouteTables(params) if err != nil { return nil, err } if len(tables.RouteTables) == 0 { // this subnet is implicitly associated with Main log.Info("Found subnet implicitly associated with Main route table: ", subnet) subnets = append(subnets, subnet) } } return subnets, nil } func discoverPublicSubnets(svc ec2iface.EC2API) ([]*string, error) { var subnetIDs []*string vpcFilter := &ec2.Filter{ Name: aws.String("vpc-id"), Values: []*string{aws.String("vpc-38fa1a5c"), aws.String("vpc-a93675cc"), aws.String("vpc-7efb0b1a")}, } publicAssocations, err := explicitPublicAssocations(svc, vpcFilter) if err != nil { return nil, err } // Determine whether the Main route table is public // If the main route table is not internet enabled // it does not matter if there are implicit assocations // To determine implicit associations, see which subnets have no // explicit route table associations // List subnets mainAssocPublicIndex := sort.Search( len(publicAssocations), func(i int) bool { return *publicAssocations[i].Main == true }, ) if mainAssocPublicIndex < len(publicAssocations) { // lookup implicit assocations subnets, err := implicitMainSubnets(svc, vpcFilter) if err != nil { return nil, err } for _, subnet := range subnets { subnetIDs = append(subnetIDs, subnet.SubnetId) } } for _, assoc := range publicAssocations { subnetIDs = append(subnetIDs, assoc.SubnetId) } return subnetIDs, nil } func main() { log.Info("Hello") sess, err := session.NewSession(&aws.Config{ Region: aws.String("us-west-2"), }) if err != nil { panic(err) } ec2svc := ec2.New(sess) subnets, err := discoverPublicSubnets(ec2svc) if err != nil { panic(err) } log.Info("Subnets --> ", subnets) }