github.com/grailbio/bigslice@v0.0.0-20230519005545-30c4c12152ad/cmd/bigslice/ec2.go (about)

     1  // Copyright 2019 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"flag"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"log"
    14  	"os"
    15  	"path/filepath"
    16  	"strings"
    17  
    18  	"github.com/aws/aws-sdk-go/aws"
    19  	"github.com/aws/aws-sdk-go/aws/session"
    20  	"github.com/aws/aws-sdk-go/service/ec2"
    21  	"github.com/grailbio/base/config"
    22  	"github.com/grailbio/base/must"
    23  
    24  	// We bring these in so we can show the user all the defaults when
    25  	// writing the profile.
    26  	_ "github.com/grailbio/base/config/aws"
    27  	_ "github.com/grailbio/bigmachine/ec2system"
    28  	_ "github.com/grailbio/bigslice/exec"
    29  	"github.com/grailbio/bigslice/sliceconfig"
    30  )
    31  
    32  func setupEc2Usage(flags *flag.FlagSet) {
    33  	fmt.Fprint(os.Stderr, `usage: bigslice setup-ec2 [-securitygroup name]
    34  
    35  Command setup-ec2 sets up a security group so that Bigslice programs
    36  can run on AWS EC2. Once complete, the resulting configuration is
    37  written to the Bigslice configuration file at `, sliceconfig.Path, `.
    38  If a configuration file already exists, then it is modified in place.
    39  
    40  Setup-ec2 tags the security group with the name "bigslice"; if a
    41  previously set up security group already exists, no new group is
    42  created, but the configuration is modified to include that security
    43  group.
    44  
    45  The Bigslice security group is set up with the following rules:
    46  
    47  	allowed: all traffic within the default VPC
    48  	allowed: all outbound
    49  	allowed: inbound SSH connections
    50  	allowed: inbound HTTPS connections
    51  
    52  The flags are:
    53  `)
    54  	flags.PrintDefaults()
    55  	os.Exit(2)
    56  }
    57  
    58  func setupEc2Cmd(args []string) {
    59  	var (
    60  		flags         = flag.NewFlagSet("bigslice setup-ec2", flag.ExitOnError)
    61  		securityGroup = flags.String("securitygroup", "bigslice", "name of the security group to set up")
    62  	)
    63  	flags.Usage = func() { setupEc2Usage(flags) }
    64  	must.Nil(flags.Parse(args), "parsing flags")
    65  	if flags.NArg() != 0 {
    66  		flags.Usage()
    67  	}
    68  
    69  	profile := config.New()
    70  	f, err := os.Open(sliceconfig.Path)
    71  	if err == nil {
    72  		must.Nil(profile.Parse(f))
    73  		must.Nil(f.Close())
    74  	} else {
    75  		must.True(os.IsNotExist(err), err)
    76  	}
    77  
    78  	if region, ok := profile.Get("aws/env.region"); ok && len(region) > 0 {
    79  		must.Nil(profile.Set("bigmachine/ec2system.default-region", strings.Trim(region, `"`)))
    80  	}
    81  
    82  	if v, ok := profile.Get("bigmachine/ec2system.security-group"); ok && v != `""` {
    83  		log.Print("ec2 security group ", v, " already configured")
    84  	} else {
    85  		sess, err := session.NewSession()
    86  		must.Nil(err, "setting up AWS session")
    87  		ident, err := setupEC2SecurityGroup(ec2.New(sess), *securityGroup)
    88  		must.Nil(err, "setting up security group")
    89  		must.Nil(profile.Set("bigmachine/ec2system.security-group", ident))
    90  		log.Print("set up new security group ", ident)
    91  	}
    92  
    93  	must.Nil(profile.Set("bigslice.system", "bigmachine/ec2system"))
    94  	// Set up a more appropriate instance type for Bigslice.
    95  	must.Nil(profile.Set("bigmachine/ec2system.instance", "m5.xlarge"))
    96  	var buf bytes.Buffer
    97  	must.Nil(profile.PrintTo(&buf))
    98  	must.Nil(os.MkdirAll(filepath.Dir(sliceconfig.Path), 0777))
    99  	must.Nil(ioutil.WriteFile(sliceconfig.Path+".setup-ec2", buf.Bytes(), 0777))
   100  	must.Nil(os.Rename(sliceconfig.Path+".setup-ec2", sliceconfig.Path))
   101  	log.Print("wrote configuration to ", sliceconfig.Path)
   102  }
   103  
   104  func setupEC2SecurityGroup(svc *ec2.EC2, name string) (string, error) {
   105  	describeResp, err := svc.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{
   106  		Filters: []*ec2.Filter{
   107  			{
   108  				Name:   aws.String("group-name"),
   109  				Values: []*string{aws.String(name)},
   110  			},
   111  		},
   112  	})
   113  	if err != nil {
   114  		if len(name) > 0 {
   115  			return "", fmt.Errorf("unable to query existing security group: %v: %v", name, err)
   116  		}
   117  		return "", fmt.Errorf("no security group configured, and unable to query existing security groups: %v", err)
   118  	}
   119  	if len(describeResp.SecurityGroups) > 0 {
   120  		id := aws.StringValue(describeResp.SecurityGroups[0].GroupId)
   121  		log.Printf("found existing bigslice security group %s", id)
   122  		return id, nil
   123  	}
   124  	log.Println("no existing bigslice security group found; creating new")
   125  	// We are going to be launching into the default VPC, so find it.
   126  	vpcResp, err := svc.DescribeVpcs(&ec2.DescribeVpcsInput{
   127  		Filters: []*ec2.Filter{{
   128  			Name:   aws.String("isDefault"),
   129  			Values: []*string{aws.String("true")},
   130  		}},
   131  	})
   132  	if err != nil {
   133  		return "", fmt.Errorf("error retrieving default VPC while creating new security group:% v", err)
   134  	}
   135  	if len(vpcResp.Vpcs) == 0 {
   136  		return "", errors.New(
   137  			"AWS account does not have a default VPC and requires manual setup.\n" +
   138  				"See https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html#create-default-vpc")
   139  	} else if len(vpcResp.Vpcs) > 1 {
   140  		// I'm not sure this is possible. But keep it as a sanity check.
   141  		return "", errors.New("AWS account has multiple default VPCs; needs manual setup")
   142  	}
   143  	vpc := vpcResp.Vpcs[0]
   144  	log.Printf("found default VPC %s", aws.StringValue(vpc.VpcId))
   145  	resp, err := svc.CreateSecurityGroup(&ec2.CreateSecurityGroupInput{
   146  		GroupName:   aws.String(name),
   147  		Description: aws.String("security group automatically created by bigslice setup-ec2"),
   148  		VpcId:       vpc.VpcId,
   149  	})
   150  	if err != nil {
   151  		return "", fmt.Errorf("error creating security group %s: %v", name, err)
   152  	}
   153  
   154  	id := aws.StringValue(resp.GroupId)
   155  	log.Printf("authorizing ingress traffic for security group %s", id)
   156  	_, err = svc.AuthorizeSecurityGroupIngress(&ec2.AuthorizeSecurityGroupIngressInput{
   157  		GroupName: aws.String(name),
   158  		IpPermissions: []*ec2.IpPermission{
   159  			// Allow all internal traffic.
   160  			{
   161  				IpProtocol: aws.String("-1"),
   162  				IpRanges:   []*ec2.IpRange{{CidrIp: vpc.CidrBlock}},
   163  				FromPort:   aws.Int64(0),
   164  				ToPort:     aws.Int64(0),
   165  			},
   166  			// Allow incoming SSH connections.
   167  			{
   168  				IpProtocol: aws.String("tcp"),
   169  				IpRanges:   []*ec2.IpRange{{CidrIp: aws.String("0.0.0.0/0")}},
   170  				FromPort:   aws.Int64(22),
   171  				ToPort:     aws.Int64(22),
   172  			},
   173  			// Allow incoming bigslice executor connections. (HTTPS)
   174  			{
   175  				IpProtocol: aws.String("tcp"),
   176  				IpRanges:   []*ec2.IpRange{{CidrIp: aws.String("0.0.0.0/0")}},
   177  				FromPort:   aws.Int64(443),
   178  				ToPort:     aws.Int64(443),
   179  			},
   180  		},
   181  	})
   182  	if err != nil {
   183  		return "", fmt.Errorf("failed to authorize security group %s for ingress traffic: %v", id, err)
   184  	}
   185  	// The default egress rules are to permit all outgoing traffic.
   186  	log.Printf("tagging security group %s", id)
   187  	_, err = svc.CreateTags(&ec2.CreateTagsInput{
   188  		Resources: []*string{aws.String(id)},
   189  		Tags: []*ec2.Tag{
   190  			{Key: aws.String("bigslice-sg"), Value: aws.String("true")},
   191  			{Key: aws.String("Name"), Value: aws.String("bigslice")},
   192  		},
   193  	})
   194  	if err != nil {
   195  		log.Printf("tag security group %s: %v", id, err)
   196  	}
   197  	log.Printf("created security group %v", id)
   198  	return id, nil
   199  }