Skip to content

Commit

Permalink
Add connect command to connect to an instance
Browse files Browse the repository at this point in the history
  • Loading branch information
Eugene Dementiev committed Oct 31, 2019
1 parent d426273 commit f9660b4
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 4 deletions.
35 changes: 35 additions & 0 deletions cmd/connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cmd

import (
"aws-ssh/lib"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var connectCmd = &cobra.Command{
Use: "connect [ssh command (ssh -tt user@instanceid)]",
Short: "SSH into the EC2 instance using ec2 connect feature",
Long: `aws-ssh connects to the EC2 instance using ec2 connect feature. It makes a special API call to upload
the first public key from your running ssh agent and then runs ssh command`,
Aliases: []string{"ssh"},
Run: func(cmd *cobra.Command, args []string) {
var profile string

profiles := viper.GetStringSlice("profiles")
if len(profiles) > 0 {
profile = profiles[0]
}
lib.ConnectEC2(profile, viper.GetString("instanceid"), viper.GetString("user"), args)
},
}

func init() {
connectCmd.Flags().StringP("instanceid", "i", "", "Instance ID to connect to")
connectCmd.Flags().StringP("user", "u", "ec2-user", "Existing user on the instance")
connectCmd.MarkFlagRequired("instanceid")

viper.BindPFlag("instanceid", connectCmd.Flags().Lookup("instanceid"))
viper.BindPFlag("user", connectCmd.Flags().Lookup("user"))
rootCmd.AddCommand(connectCmd)
}
3 changes: 3 additions & 0 deletions cmd/reconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ var reconfCmd = &cobra.Command{
Short: "Creates a new ssh config",
Long: `Reconfigures your ssh by creating a new config for it. Only one argument is required,
which is a filename. In case of any errors, the preexisting file won't be touched.`,
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
},
Run: func(cmd *cobra.Command, args []string) {
lib.Reconf(viper.GetStringSlice("profiles"), args[0])
},
Expand Down
9 changes: 6 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func Execute(version string) {
}

func init() {
cobra.OnInitialize(initConfig)
cobra.OnInitialize(initSettings)

rootCmd.PersistentFlags().BoolP("debug", "d", false, "Show debug output")
rootCmd.PersistentFlags().StringSliceP("profile", "p", []string{}, "Profiles to query. Can be specified multiple times. If not specified, goes through all profiles in ~/.aws/config and ~/.aws/credentials")
Expand All @@ -45,12 +45,15 @@ func init() {
viper.BindPFlag("profiles", rootCmd.PersistentFlags().Lookup("profile"))
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
func initSettings() {
log.SetHandler(cli.New(os.Stdout))
if viper.GetBool("debug") {
log.SetLevel(log.DebugLevel)
}
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
if len(viper.GetStringSlice("profiles")) == 0 {
profiles, err := getProfiles()
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ var testCmd = &cobra.Command{
Allows to identify permission issues early.
`,
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
},
Run: func(cmd *cobra.Command, args []string) {
summaries, err := lib.TraverseProfiles(viper.GetStringSlice("profiles"))
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module aws-ssh
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/apex/log v1.1.0
github.com/aws/aws-sdk-go v1.16.26
github.com/aws/aws-sdk-go v1.25.23
github.com/fatih/color v1.7.0 // indirect
github.com/go-ini/ini v1.48.0
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
Expand All @@ -18,6 +18,7 @@ require (
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/spf13/cobra v0.0.3
github.com/spf13/viper v1.3.1
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 // indirect
gopkg.in/ahmetb/go-linq.v3 v3.0.0
gopkg.in/ini.v1 v1.41.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ github.com/apex/log v1.1.0/go.mod h1:yA770aXIDQrhVOIGurT/pVdfCpSq1GQV/auzMN5fzvY
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-sdk-go v1.16.26 h1:GWkl3rkRO/JGRTWoLLIqwf7AWC4/W/1hMOUZqmX0js4=
github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.25.23 h1:EJx1uSb8E/HRkDa02pOb0r/73bkDbds7qg74s57qYgs=
github.com/aws/aws-sdk-go v1.25.23/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
Expand Down Expand Up @@ -68,6 +70,7 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down
79 changes: 79 additions & 0 deletions lib/connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package lib

import (
"fmt"
"net"
"os"
"os/exec"
"strings"
"syscall"

"github.com/apex/log"
"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/ec2instanceconnect"
"golang.org/x/crypto/ssh/agent"
)

// ConnectEC2 connects to an EC2 instance by pushing your public key onto it first
// using EC2 connect feature and then runs ssh.
func ConnectEC2(profile, instanceID, instanceUser string, args []string) {
localSession, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{},

SharedConfigState: session.SharedConfigEnable,
Profile: profile,
})
if err != nil {
log.WithError(err).Fatal("can't get aws session")
}
ec2Svc := ec2.New(localSession)
ec2Result, err := ec2Svc.DescribeInstances(&ec2.DescribeInstancesInput{
InstanceIds: aws.StringSlice([]string{instanceID}),
})
if err != nil {
log.WithError(err).Fatal("can't get ec2 instance")
}

ec2Instance := ec2Result.Reservations[0].Instances[0]
ec2ICSvc := ec2instanceconnect.New(localSession)

log.WithField("instance_id", aws.StringValue(ec2Instance.InstanceId)).Info("Pushing SSH key...")

sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))

keys, err := agent.NewClient(sshAgent).List()
if err != nil || len(keys) < 1 {
log.Fatal("Can't get public keys from ssh agent. Please ensure you have the ssh-agent running and have at least one identity added (with ssh-add)")
}
pubkey := keys[0].String()

if _, err := ec2ICSvc.SendSSHPublicKey(&ec2instanceconnect.SendSSHPublicKeyInput{
InstanceId: ec2Instance.InstanceId,
InstanceOSUser: aws.String(instanceUser),
AvailabilityZone: ec2Instance.Placement.AvailabilityZone,
SSHPublicKey: aws.String(pubkey),
}); err != nil {
log.WithError(err).Fatal("can't push ssh key")
}

if len(args) == 0 {
// construct default args
args = []string{
"ssh",
"-tt",
fmt.Sprintf("%s@%s", instanceUser, instanceID),
}
}

command, err := exec.LookPath(args[0])
if err != nil {
log.WithError(err).Fatal("Can't find the binary in the PATH")
}
log.WithField("instance_id", aws.StringValue(ec2Instance.InstanceId)).Infof("Connecting to the instance using '%s'", strings.Join(args, " "))

if err := syscall.Exec(command, args, os.Environ()); err != nil {
log.WithFields(log.Fields{"command": command}).WithError(err).Fatal("can't run the command")
}
}

0 comments on commit f9660b4

Please sign in to comment.