github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/internal/commands/tunnel_up.go (about) 1 package commands 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "log" 11 "net" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "regexp" 16 "strconv" 17 "strings" 18 "text/template" 19 20 "github.com/Masterminds/semver" 21 "github.com/aws/aws-sdk-go/aws" 22 "github.com/aws/aws-sdk-go/aws/session" 23 "github.com/aws/aws-sdk-go/service/ec2instanceconnect" 24 "github.com/aws/aws-sdk-go/service/ssm" 25 "github.com/aws/aws-sdk-go/service/ssm/ssmiface" 26 "github.com/hazelops/ize/internal/config" 27 "github.com/hazelops/ize/internal/requirements" 28 "github.com/hazelops/ize/pkg/term" 29 "github.com/pterm/pterm" 30 "github.com/sirupsen/logrus" 31 "github.com/spf13/cobra" 32 "golang.org/x/crypto/ssh" 33 "golang.org/x/crypto/ssh/terminal" 34 ) 35 36 const sshConfig = `# SSH over Session Manager 37 host i-* mi-* 38 ServerAliveInterval 180 39 ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'" 40 41 {{range $k := .}}LocalForward {{$k}} 42 {{end}} 43 ` 44 45 var explainTunnelUpTmpl = ` 46 # Set variables 47 SSH_CONFIG={{.EnvDir}}/ssh.config 48 SSH_PUBLIC_KEY=$(cat ~/.ssh/id_rsa.pub) 49 50 # Get bastion instance id 51 BASTION_INSTANCE_ID=$(aws ssm get-parameter --name "/{{.Env}}/terraform-output" --with-decryption | jq -r '.Parameter.Value' | base64 -d | jq -r '.bastion_instance_id.value' 52 53 # Get ssh config 54 aws ssm get-parameter --name "/{{.Env}}/terraform-output" --with-decryption | jq -r '.Parameter.Value' | base64 -d | jq -r '.ssh_forward_config.value[]' > $SSH_CONFIG 55 56 # Send ssh public key to instance 57 aws ssm send-command --instance-ids $BASTION_INSTANCE_ID --document-name AWS-RunShellScript --comment 'Add an SSH public key to authorized_keys' --parameters '{"commands": ["grep -qR \"$(SSH_PUBLIC_KEY)\" /home/ubuntu/.ssh/authorized_keys || echo \"$(SSH_PUBLIC_KEY)\" >> /home/ubuntu/.ssh/authorized_keys"]}' 1> /dev/null) 58 59 # Change to the dir and up tunnel 60 (cd {{.EnvDir}} && $(aws ssm get-parameter --name "/{{.Env}}/terraform-output" --with-decryption | jq -r '.Parameter.Value' | base64 -d | jq -r '.cmd.value.tunnel.up') -F $SSH_CONFIG) 61 ` 62 63 type TunnelUpOptions struct { 64 Config *config.Project 65 PrivateKeyFile string 66 PublicKeyFile string 67 BastionHostID string 68 ForwardHost []string 69 StrictHostKeyChecking bool 70 Metadata bool 71 Explain bool 72 } 73 74 func NewTunnelUpFlags(project *config.Project) *TunnelUpOptions { 75 return &TunnelUpOptions{ 76 Config: project, 77 } 78 } 79 80 func NewCmdTunnelUp(project *config.Project) *cobra.Command { 81 o := NewTunnelUpFlags(project) 82 83 cmd := &cobra.Command{ 84 Use: "up", 85 Short: "Open tunnel with sending ssh key", 86 Long: "Open tunnel with sending ssh key to remote server", 87 RunE: func(cmd *cobra.Command, args []string) error { 88 cmd.SilenceUsage = true 89 90 if o.Explain { 91 err := o.Config.Generate(explainTunnelUpTmpl, nil) 92 if err != nil { 93 return err 94 } 95 96 return nil 97 } 98 99 err := o.Complete() 100 if err != nil { 101 return err 102 } 103 104 err = o.Validate() 105 if err != nil { 106 return err 107 } 108 109 err = o.Run() 110 if err != nil { 111 return err 112 } 113 114 return nil 115 }, 116 } 117 118 cmd.Flags().StringVar(&o.BastionHostID, "bastion-instance-id", "", "set bastion host instance id (i-xxxxxxxxxxxxxxxxx)") 119 cmd.Flags().StringSliceVar(&o.ForwardHost, "forward-host", nil, "set forward hosts for redirect with next format: <remote-host>:<remote-port>, <remote-host>:<remote-port>, <remote-host>:<remote-port>. In this case a free local port will be selected automatically. It's possible to set local manually using <remote-host>:<remote-port>:<local-port>") 120 cmd.Flags().StringVar(&o.PublicKeyFile, "ssh-public-key", "", "set ssh key public path") 121 cmd.Flags().StringVar(&o.PrivateKeyFile, "ssh-private-key", "", "set ssh key private path") 122 cmd.PersistentFlags().BoolVar(&o.StrictHostKeyChecking, "strict-host-key-checking", false, "set strict host key checking") 123 cmd.PersistentFlags().BoolVar(&o.Metadata, "use-ec2-metadata", false, "send ssh key to EC2 metadata (work only for Ubuntu versions > 20.0)") 124 cmd.Flags().BoolVar(&o.Explain, "explain", false, "bash alternative shown") 125 126 return cmd 127 } 128 129 func (o *TunnelUpOptions) Complete() error { 130 if err := requirements.CheckRequirements(requirements.WithSSMPlugin()); err != nil { 131 return err 132 } 133 134 isUp, err := checkTunnel(o.Config.EnvDir) 135 if err != nil { 136 return fmt.Errorf("can't run tunnel up: %w", err) 137 } 138 if isUp { 139 os.Exit(0) 140 } 141 142 if o.PrivateKeyFile == "" && o.Config.Tunnel != nil { 143 o.PrivateKeyFile = o.Config.Tunnel.SSHPrivateKey 144 } 145 146 if o.PublicKeyFile == "" && o.Config.Tunnel != nil { 147 o.PublicKeyFile = o.Config.Tunnel.SSHPublicKey 148 } 149 150 if o.PrivateKeyFile == "" { 151 home, _ := os.UserHomeDir() 152 o.PrivateKeyFile = fmt.Sprintf("%s/.ssh/id_rsa", home) 153 } 154 155 if o.PublicKeyFile == "" { 156 home, _ := os.UserHomeDir() 157 o.PublicKeyFile = fmt.Sprintf("%s/.ssh/id_rsa.pub", home) 158 } 159 160 if len(o.BastionHostID) == 0 && len(o.ForwardHost) != 0 { 161 return fmt.Errorf("can't load options for a command: --forward-host parameter requires --bastion-instance-id") 162 } 163 164 if len(o.ForwardHost) == 0 && len(o.BastionHostID) != 0 { 165 return fmt.Errorf("can't load options for a command: --bastion-instance-id requires --forward-host parameter") 166 } 167 168 if len(o.BastionHostID) == 0 && len(o.ForwardHost) == 0 { 169 if o.Config.Tunnel != nil { 170 o.ForwardHost = o.Config.Tunnel.ForwardHost 171 o.BastionHostID = o.Config.Tunnel.BastionInstanceID 172 } 173 } 174 175 if len(o.BastionHostID) == 0 && len(o.ForwardHost) == 0 { 176 wr := new(SSMWrapper) 177 wr.Api = ssm.New(o.Config.Session) 178 bastionHostID, forwardHost, err := writeSSHConfigFromSSM(wr, o.Config.Env, o.Config.EnvDir) 179 if err != nil { 180 return err 181 } 182 183 o.BastionHostID = bastionHostID 184 o.ForwardHost = forwardHost 185 pterm.Success.Println("Tunnel forwarding configuration obtained from SSM") 186 } else { 187 err := writeSSHConfigFromConfig(o.ForwardHost, o.Config.EnvDir) 188 if err != nil { 189 return err 190 } 191 pterm.Success.Println("Tunnel forwarding configuration obtained from the config file") 192 } 193 194 return nil 195 } 196 197 func (o *TunnelUpOptions) Validate() error { 198 if len(o.Config.Env) == 0 { 199 return fmt.Errorf("env must be specified") 200 } 201 202 for _, h := range o.ForwardHost { 203 p, _ := strconv.Atoi(strings.Split(h, ":")[2]) 204 if err := checkPort(p, o.Config.EnvDir); err != nil { 205 return fmt.Errorf("tunnel forwarding config validation failed: %w", err) 206 } 207 } 208 209 return nil 210 } 211 212 func (o *TunnelUpOptions) Run() error { 213 logrus.Debugf("public key path: %s", o.PublicKeyFile) 214 logrus.Debugf("private key path: %s", o.PrivateKeyFile) 215 216 err := o.checkOsVersion() 217 if err != nil { 218 return err 219 } 220 221 pk, err := getPublicKey(o.PublicKeyFile) 222 if err != nil { 223 return fmt.Errorf("can't get public key: %s", err) 224 } 225 226 logrus.Debugf("public key:\n%s", pk) 227 228 if o.Metadata { 229 err = sendSSHPublicKey(o.BastionHostID, pk, o.Config.Session) 230 if err != nil { 231 return fmt.Errorf("can't run tunnel: %s", err) 232 } 233 } else { 234 err = sendSSHPublicKeyLegacy(o.BastionHostID, pk, o.Config.Session) 235 if err != nil { 236 return fmt.Errorf("can't run tunnel: %s", err) 237 } 238 } 239 240 forwardConfig, err := o.upTunnel() 241 if err != nil { 242 return err 243 } 244 245 pterm.Success.Println("Tunnel is up! Forwarded ports:") 246 pterm.Println(forwardConfig) 247 248 return nil 249 } 250 251 func (o *TunnelUpOptions) checkOsVersion() error { 252 diio, err := o.Config.AWSClient.SSMClient.DescribeInstanceInformation(&ssm.DescribeInstanceInformationInput{ 253 Filters: []*ssm.InstanceInformationStringFilter{ 254 { 255 Key: aws.String("InstanceIds"), 256 Values: aws.StringSlice([]string{o.BastionHostID}), 257 }, 258 }, 259 }) 260 if err != nil { 261 return fmt.Errorf("can't get instance '%s' information: %s", o.BastionHostID, err) 262 } 263 264 if len(diio.InstanceInformationList) == 0 { 265 return fmt.Errorf("can't get instance '%s' information", o.BastionHostID) 266 } 267 268 osName := *diio.InstanceInformationList[0].PlatformName 269 osVersion := *diio.InstanceInformationList[0].PlatformVersion 270 271 switch osName { 272 case "Ubuntu": 273 if semver.MustParse(osVersion).LessThan(semver.MustParse("20.04")) { 274 pterm.Warning.Printfln("Your bastion host AMI is Ubuntu %s, Instance Connect is not installed by default on that version of OS. Please upgrade to at least v20.04", osVersion) 275 } 276 case "Amazon Linux AMI": 277 if semver.MustParse(osVersion).LessThan(semver.MustParse("2.0.20190618")) { 278 pterm.Warning.Printfln("Your bastion host AMI is Amazon Linux AMI %s, Instance Connect is not installed by default on that version of OS. Please upgrade your AMI to at least 2.0.20190618", osVersion) 279 } 280 } 281 282 return nil 283 } 284 285 func (o *TunnelUpOptions) upTunnel() (string, error) { 286 sshConfigPath := fmt.Sprintf("%s/ssh.config", o.Config.EnvDir) 287 logrus.Debugf("ssh config path: %s", sshConfigPath) 288 289 if err := setAWSCredentials(o.Config.Session); err != nil { 290 return "", fmt.Errorf("can't run tunnel: %w", err) 291 } 292 293 args := o.getSSHCommandArgs(sshConfigPath) 294 295 err := o.runSSH(args) 296 if err != nil { 297 return "", err 298 } 299 300 var forwardConfig string 301 for _, h := range o.ForwardHost { 302 ss := strings.Split(h, ":") 303 forwardConfig += fmt.Sprintf("%s:%s ➡ localhost:%s\n", ss[0], ss[1], ss[2]) 304 } 305 return forwardConfig, nil 306 } 307 308 func (o *TunnelUpOptions) runSSH(args []string) error { 309 c := exec.Command("ssh", args...) 310 311 c.Dir = o.Config.EnvDir 312 os.Setenv("AWS_REGION", o.Config.AwsRegion) 313 314 runner := term.New(term.WithStdin(os.Stdin)) 315 _, _, code, err := runner.Run(c) 316 if err != nil { 317 return err 318 } 319 320 if code != 0 { 321 return fmt.Errorf("exit status: %d", code) 322 } 323 return nil 324 } 325 326 func (o *TunnelUpOptions) getSSHCommandArgs(sshConfigPath string) []string { 327 args := []string{"-M", "-t", "-S", "bastion.sock", "-fN"} 328 if !o.StrictHostKeyChecking { 329 args = append(args, "-o", "StrictHostKeyChecking=no") 330 } 331 args = append(args, fmt.Sprintf("ubuntu@%s", o.BastionHostID)) 332 args = append(args, "-F", sshConfigPath) 333 334 if _, err := os.Stat(o.PrivateKeyFile); !os.IsNotExist(err) { 335 args = append(args, "-i", o.PrivateKeyFile) 336 } 337 338 if o.Config.LogLevel == "debug" { 339 args = append(args, "-vvv") 340 } 341 342 return args 343 } 344 345 type SSMWrapper struct { 346 Api ssmiface.SSMAPI 347 } 348 349 func getTerraformOutput(wr *SSMWrapper, env string) (terraformOutput, error) { 350 resp, err := wr.Api.GetParameter(&ssm.GetParameterInput{ 351 Name: aws.String(fmt.Sprintf("/%s/terraform-output", env)), 352 WithDecryption: aws.Bool(true), 353 }) 354 if err != nil { 355 return terraformOutput{}, fmt.Errorf("can't get terraform output: %w", err) 356 } 357 358 var value []byte 359 360 value, err = base64.StdEncoding.DecodeString(*resp.Parameter.Value) 361 if err != nil { 362 return terraformOutput{}, fmt.Errorf("can't get terraform output: %w", err) 363 } 364 365 logrus.Debugf("decoded terrafrom output: \n%s", value) 366 367 var output terraformOutput 368 369 err = json.Unmarshal(value, &output) 370 if err != nil { 371 return terraformOutput{}, fmt.Errorf("can't get terraform output: %w", err) 372 } 373 374 logrus.Debugf("output: %s", output) 375 376 return output, nil 377 } 378 379 type terraformOutput struct { 380 BastionInstanceID struct { 381 Value string `json:"value,omitempty"` 382 } `json:"bastion_instance_id,omitempty"` 383 SSHForwardConfig struct { 384 Value []string `json:"value,omitempty"` 385 } `json:"ssh_forward_config,omitempty"` 386 } 387 388 func sendSSHPublicKey(bastionID string, key string, sess *session.Session) error { 389 _, err := ec2instanceconnect.New(sess).SendSSHPublicKey(&ec2instanceconnect.SendSSHPublicKeyInput{ 390 InstanceId: aws.String(bastionID), 391 InstanceOSUser: aws.String("ubuntu"), 392 SSHPublicKey: aws.String(key), 393 }) 394 if err != nil { 395 return err 396 } 397 398 return nil 399 } 400 401 func sendSSHPublicKeyLegacy(bastionID string, key string, sess *session.Session) error { 402 // This command is executed in the bastion host and it checks if our public key is present. If it's not it uploads it to _authorized_keys file. 403 command := fmt.Sprintf( 404 `grep -qR "%s" /home/ubuntu/.ssh/authorized_keys || echo "%s" >> /home/ubuntu/.ssh/authorized_keys`, 405 strings.TrimSpace(key), strings.TrimSpace(key), 406 ) 407 408 logrus.Debugf("send command: \n%s", command) 409 410 _, err := ssm.New(sess).SendCommand(&ssm.SendCommandInput{ 411 InstanceIds: []*string{&bastionID}, 412 DocumentName: aws.String("AWS-RunShellScript"), 413 Comment: aws.String("Add an SSH public key to authorized_keys"), 414 Parameters: map[string][]*string{ 415 "commands": {&command}, 416 }, 417 }) 418 if err != nil { 419 return fmt.Errorf("can't send SSH public key: %w", err) 420 } 421 422 return nil 423 } 424 425 func getPublicKey(path string) (string, error) { 426 if !filepath.IsAbs(path) { 427 var err error 428 path, err = filepath.Abs(path) 429 if err != nil { 430 return "", err 431 } 432 } 433 434 if _, err := os.Stat(path); err != nil { 435 return "", fmt.Errorf("%s does not exist", path) 436 } 437 438 f, err := ioutil.ReadFile(path) 439 if err != nil { 440 return "", err 441 } 442 443 _, _, _, _, err = ssh.ParseAuthorizedKey(f) 444 if err != nil { 445 return "", err 446 } 447 448 return string(f), nil 449 } 450 451 func getHosts(config string) [][]string { 452 // This regexp reads ssh.conf configuration, so we can display it nicely in the UI 453 re, err := regexp.Compile(`LocalForward\s(?P<localPort>\d+)\s(?P<remoteHost>.+):(?P<remotePort>\d+)`) 454 if err != nil { 455 log.Fatal(fmt.Errorf("can't get forward config: %w", err)) 456 } 457 458 hosts := re.FindAllStringSubmatch( 459 config, 460 -1, 461 ) 462 463 return hosts 464 } 465 466 func getSSHConfig(path string) (string, error) { 467 f, err := os.Open(path) 468 if err != nil { 469 return "", fmt.Errorf("can't get ssh config: %w", err) 470 } 471 472 b, err := io.ReadAll(f) 473 if err != nil { 474 return "", fmt.Errorf("can't get ssh config: %w", err) 475 } 476 477 return string(b), nil 478 } 479 480 func getFreePort() (int, error) { 481 addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 482 if err != nil { 483 return 0, err 484 } 485 486 l, err := net.ListenTCP("tcp", addr) 487 if err != nil { 488 return 0, err 489 } 490 defer func(l *net.TCPListener) { 491 err := l.Close() 492 if err != nil { 493 log.Fatal(err) 494 } 495 }(l) 496 return l.Addr().(*net.TCPAddr).Port, nil 497 } 498 499 func checkPort(port int, dir string) error { 500 addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", port)) 501 if err != nil { 502 return fmt.Errorf("can't check address %s: %w", fmt.Sprintf("127.0.0.1:%d", port), err) 503 } 504 505 l, err := net.ListenTCP("tcp", addr) 506 if err != nil { 507 command := fmt.Sprintf("lsof -i tcp:%d | grep LISTEN | awk '{print $1, $2}'", port) 508 stdout, stderr, code, err := term.New(term.WithStdout(io.Discard), term.WithStderr(io.Discard)).Run(exec.Command("bash", "-c", command)) 509 if err != nil { 510 return fmt.Errorf("can't run command '%s': %w", command, err) 511 } 512 if code == 0 { 513 stdout = strings.TrimSpace(stdout) 514 processName := strings.Split(stdout, " ")[0] 515 processPid, err := strconv.Atoi(strings.Split(stdout, " ")[1]) 516 if err != nil { 517 return fmt.Errorf("can't get pid: %w", err) 518 } 519 pterm.Info.Printfln("Can't start tunnel on port %d. It seems like it's take by a process '%s'.", port, processName) 520 proc, err := os.FindProcess(processPid) 521 if err != nil { 522 return fmt.Errorf("can't find process: %w", err) 523 } 524 525 _, err = os.Stat(filepath.Join(dir, "bastion.sock")) 526 if processName == "ssh" && os.IsNotExist(err) { 527 return fmt.Errorf("it could be another ize tunnel, but we can't find a socket. Something went wrong. We suggest terminating it and starting it up again") 528 } 529 isContinue := false 530 if terminal.IsTerminal(int(os.Stdout.Fd())) { 531 isContinue, err = pterm.DefaultInteractiveConfirm.WithDefaultText("Would you like to terminate it?").Show() 532 if err != nil { 533 return err 534 } 535 } else { 536 isContinue = true 537 } 538 539 if !isContinue { 540 return fmt.Errorf("destroying was canceled") 541 } 542 err = proc.Kill() 543 if err != nil { 544 return fmt.Errorf("can't kill process: %w", err) 545 } 546 547 pterm.Info.Printfln("Process '%s' (pid %d) was killed", processName, processPid) 548 549 return nil 550 } 551 return fmt.Errorf("error during run command: %s (exit code: %d, stderr: %s)", command, code, stderr) 552 } 553 554 err = l.Close() 555 if err != nil { 556 return err 557 } 558 559 return nil 560 } 561 562 func writeSSHConfigFromSSM(wr *SSMWrapper, env string, dir string) (string, []string, error) { 563 var bastionHostID string 564 var forwardHost []string 565 566 to, err := getTerraformOutput(wr, env) 567 if err != nil { 568 return "", []string{}, fmt.Errorf("can't write SSH config: %w", err) 569 } 570 571 sshConfigPath := fmt.Sprintf("%s/ssh.config", dir) 572 573 f, err := os.Create(sshConfigPath) 574 if err != nil { 575 return "", []string{}, fmt.Errorf("can't write SSH config: %w", err) 576 } 577 578 sshConfig := strings.Join(to.SSHForwardConfig.Value, "\n") 579 _, err = io.WriteString(f, sshConfig) 580 if err != nil { 581 return "", []string{}, fmt.Errorf("can't write SSH config: %w", err) 582 } 583 if err = f.Close(); err != nil { 584 return "", []string{}, fmt.Errorf("can't write SSH config: %w", err) 585 } 586 587 hosts := getHosts(sshConfig) 588 if len(hosts) == 0 { 589 errMsg := "can't write SSH config: forwarding config is not valid" 590 if logrus.GetLevel() == logrus.DebugLevel { 591 errMsg += fmt.Sprintf(". Config in SSM: \n%s", sshConfig) 592 } 593 return "", []string{}, fmt.Errorf(errMsg) 594 } 595 596 bastionHostID = to.BastionInstanceID.Value 597 598 for _, h := range hosts { 599 forwardHost = append(forwardHost, fmt.Sprintf("%s:%s:%s", h[2], h[3], h[1])) 600 } 601 602 return bastionHostID, forwardHost, nil 603 } 604 605 func writeSSHConfigFromConfig(forwardHost []string, dir string) error { 606 sshConfigPath := fmt.Sprintf("%s/ssh.config", dir) 607 f, err := os.Create(sshConfigPath) 608 if err != nil { 609 return fmt.Errorf("can't run tunnel up: %w", err) 610 } 611 612 var tmplData []string 613 for k, v := range forwardHost { 614 ss := strings.Split(v, ":") 615 if len(ss) < 2 || len(ss) > 3 { 616 return fmt.Errorf("can't load options for a command: invalid format for forward host (should be host:port:localport)") 617 } 618 if len(ss) == 2 { 619 p, err := getFreePort() 620 if err != nil { 621 return fmt.Errorf("can't load options for a command: %w", err) 622 } 623 forwardHost[k] = forwardHost[k] + ":" + strconv.Itoa(p) 624 ss = append(ss, strconv.Itoa(p)) 625 } else if len(ss[2]) == 0 { 626 return fmt.Errorf("can't load options for a command: invalid format for forward host (should be host:port:localport)") 627 } 628 tmplData = append(tmplData, fmt.Sprintf("%s %s:%s", ss[2], ss[0], ss[1])) 629 } 630 t := template.New("sshConfig") 631 t, err = t.Parse(sshConfig) 632 if err != nil { 633 return err 634 } 635 err = t.Execute(f, tmplData) 636 if err != nil { 637 return err 638 } 639 if err = f.Close(); err != nil { 640 return fmt.Errorf("can't run tunnel up: %w", err) 641 } 642 643 return nil 644 } 645 646 func checkTunnel(dir string) (bool, error) { 647 pathToSocket := filepath.Join(dir, "bastion.sock") 648 if _, err := os.Stat(pathToSocket); !os.IsNotExist(err) { 649 pterm.Info.Printfln("A socket file from another tunnel has been detected: %s", pathToSocket) 650 c := exec.Command( 651 "ssh", "-S", "bastion.sock", "-O", "check", "", 652 ) 653 out := &bytes.Buffer{} 654 c.Stdout = out 655 c.Stderr = out 656 c.Dir = dir 657 658 err := c.Run() 659 if err == nil { 660 sshConfigPath := fmt.Sprintf("%s/ssh.config", dir) 661 sshConfig, err := getSSHConfig(sshConfigPath) 662 if err != nil { 663 return false, fmt.Errorf("can't check tunnel: %w", err) 664 } 665 666 pterm.Success.Println("Tunnel is up. Forwarding config:") 667 hosts := getHosts(sshConfig) 668 var forwardConfig string 669 for _, h := range hosts { 670 forwardConfig += fmt.Sprintf("%s:%s ➡ localhost:%s\n", h[2], h[3], h[1]) 671 } 672 pterm.Println(forwardConfig) 673 674 return true, nil 675 } else { 676 pterm.Warning.Println("Tunnel socket file seems to be not useable. We have deleted it") 677 err := os.Remove(pathToSocket) 678 if err != nil { 679 return false, err 680 } 681 return false, nil 682 } 683 } 684 685 return false, nil 686 } 687 688 func setAWSCredentials(sess *session.Session) error { 689 v, err := sess.Config.Credentials.Get() 690 if err != nil { 691 return fmt.Errorf("can't set AWS credentials: %w", err) 692 } 693 694 err = os.Setenv("AWS_SECRET_ACCESS_KEY", v.SecretAccessKey) 695 if err != nil { 696 return err 697 } 698 err = os.Setenv("AWS_ACCESS_KEY_ID", v.AccessKeyID) 699 if err != nil { 700 return err 701 } 702 err = os.Setenv("AWS_SESSION_TOKEN", v.SessionToken) 703 if err != nil { 704 return err 705 } 706 707 return nil 708 }