Skip to content

Commit

Permalink
refactor: add prompt for ssh hostkey warnings with override flag
Browse files Browse the repository at this point in the history
  • Loading branch information
shreddedbacon committed Aug 12, 2024
1 parent 7229fbc commit 20a1a04
Show file tree
Hide file tree
Showing 160 changed files with 2,573 additions and 2,354 deletions.
13 changes: 13 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,16 @@ var configFeatureSwitch = &cobra.Command{
case "false":
lagoonCLIConfig.EnvironmentFromDirectory = false
}
strictHostKeyChecking, err := cmd.Flags().GetString("strict-host-key-checking")
if err != nil {
output.RenderError(err.Error(), outputOptions)
os.Exit(1)
}
strictHostKeyCheckingProvided := cmd.Flags().Lookup("strict-host-key-checking").Changed
if strictHostKeyCheckingProvided {
lagoonCLIConfig.StrictHostKeyChecking = strictHostKeyChecking
}

if err := writeLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)); err != nil {
output.RenderError(err.Error(), outputOptions)
os.Exit(1)
Expand Down Expand Up @@ -299,6 +309,7 @@ var configLagoonVersionCmd = &cobra.Command{

var updateCheck string
var environmentFromDirectory string
var strictHostKeyChecking string
var fullConfigList bool

func init() {
Expand Down Expand Up @@ -333,6 +344,8 @@ func init() {
"Enable or disable checking of updates (true/false)")
configFeatureSwitch.Flags().StringVarP(&environmentFromDirectory, "enable-local-dir-check", "", "",
"Enable or disable checking of local directory for Lagoon project (true/false)")
configFeatureSwitch.Flags().StringVar(&strictHostKeyChecking, "strict-host-key-checking", "",
"Enable or disable StrictHostKeyChecking (yes, no, ignore)")
}

// readLagoonConfig reads the lagoon config from specified file.
Expand Down
15 changes: 11 additions & 4 deletions cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"github.com/spf13/cobra"
lagoonssh "github.com/uselagoon/lagoon-cli/pkg/lagoon/ssh"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
terminal "golang.org/x/term"
Expand Down Expand Up @@ -139,19 +140,25 @@ func retrieveTokenViaSsh() (string, error) {
privateKey = cmdSSHKey
skipAgent = true
}
ignoreHostKey, acceptNewHostKey := lagoonssh.CheckStrictHostKey(strictHostKeyCheck)
sshHost := fmt.Sprintf("%s:%s",
lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].HostName,
lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].Port)
hkcb, hkalgo, err := lagoonssh.InteractiveKnownHosts(userPath, sshHost, ignoreHostKey, acceptNewHostKey)
if err != nil {
return "", fmt.Errorf("couldn't get ~/.ssh/known_hosts: %v", err)
}
authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].PublicKeyIdentities, skipAgent)
config := &ssh.ClientConfig{
User: "lagoon",
Auth: []ssh.AuthMethod{
authMethod,
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
HostKeyCallback: hkcb,
HostKeyAlgorithms: hkalgo,
}
defer closeSSHAgent()

sshHost := fmt.Sprintf("%s:%s",
lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].HostName,
lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].Port)
conn, err := ssh.Dial("tcp", sshHost, config)
if err != nil {
return "", fmt.Errorf("unable to authenticate or connect to host %s\nthere may be an issue determining which ssh-key to use, or there may be an issue establishing a connection to the host\nthe error returned was: %v", sshHost, err)
Expand Down
29 changes: 16 additions & 13 deletions cmd/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"os"
"path"
"time"

"github.com/spf13/cobra"
Expand All @@ -13,7 +12,6 @@ import (
"github.com/uselagoon/machinery/api/lagoon"
lclient "github.com/uselagoon/machinery/api/lagoon/client"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)

var (
Expand Down Expand Up @@ -58,12 +56,13 @@ func generateLogsCommand(service, container string, lines uint,
return argv, nil
}

func getSSHHostPort(environmentName string, debug bool) (string, string, error) {
func getSSHHostPort(environmentName string, debug bool) (string, string, bool, error) {
current := lagoonCLIConfig.Current
// set the default ssh host and port to the core ssh endpoint
sshHost := lagoonCLIConfig.Lagoons[current].HostName
sshPort := lagoonCLIConfig.Lagoons[current].Port
token := lagoonCLIConfig.Lagoons[current].Token
portal := false

// get SSH Portal endpoint if required
lc := lclient.New(
Expand All @@ -76,7 +75,7 @@ func getSSHHostPort(environmentName string, debug bool) (string, string, error)
defer cancel()
project, err := lagoon.GetSSHEndpointsByProject(ctx, cmdProjectName, lc)
if err != nil {
return "", "", fmt.Errorf("couldn't get SSH endpoint by project: %v", err)
return "", "", portal, fmt.Errorf("couldn't get SSH endpoint by project: %v", err)
}
// check all the environments for this project
for _, env := range project.Environments {
Expand All @@ -86,13 +85,14 @@ func getSSHHostPort(environmentName string, debug bool) (string, string, error)
if env.DeployTarget.SSHHost != "" && env.DeployTarget.SSHPort != "" {
sshHost = env.DeployTarget.SSHHost
sshPort = env.DeployTarget.SSHPort
portal = true
}
}
}
return sshHost, sshPort, nil
return sshHost, sshPort, portal, nil
}

func getSSHClientConfig(environmentName string) (*ssh.ClientConfig,
func getSSHClientConfig(environmentName, host string, ignoreHostKey, acceptNewHostKey bool) (*ssh.ClientConfig,
func() error, error) {
skipAgent := false
privateKey := fmt.Sprintf("%s/.ssh/id_rsa", userPath)
Expand All @@ -107,17 +107,19 @@ func getSSHClientConfig(environmentName string) (*ssh.ClientConfig,
skipAgent = true
}
// parse known_hosts
kh, err := knownhosts.New(path.Join(userPath, ".ssh/known_hosts"))
hkcb, hkalgo, err := lagoonssh.InteractiveKnownHosts(userPath, host, ignoreHostKey, acceptNewHostKey)
if err != nil {
return nil, nil, fmt.Errorf("couldn't get ~/.ssh/known_hosts: %v", err)
}

// configure an SSH client session
authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].PublicKeyIdentities, skipAgent)
return &ssh.ClientConfig{
User: cmdProjectName + "-" + environmentName,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: kh,
Timeout: connTimeout,
User: cmdProjectName + "-" + environmentName,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: hkcb,
HostKeyAlgorithms: hkalgo,
Timeout: connTimeout,
}, closeSSHAgent, nil
}

Expand All @@ -136,6 +138,7 @@ var logsCmd = &cobra.Command{
if err != nil {
return fmt.Errorf("couldn't get debug value: %v", err)
}
ignoreHostKey, acceptNewHostKey := lagoonssh.CheckStrictHostKey(strictHostKeyCheck)
argv, err := generateLogsCommand(logsService, logsContainer, logsTailLines,
logsFollow)
if err != nil {
Expand All @@ -145,12 +148,12 @@ var logsCmd = &cobra.Command{
environmentName := makeSafe(
shortenEnvironment(cmdProjectName, cmdProjectEnvironment))
// query the Lagoon API for the environment's SSH endpoint
sshHost, sshPort, err := getSSHHostPort(environmentName, debug)
sshHost, sshPort, _, err := getSSHHostPort(environmentName, debug)
if err != nil {
return fmt.Errorf("couldn't get SSH endpoint: %v", err)
}
// configure SSH client session
sshConfig, closeSSHAgent, err := getSSHClientConfig(environmentName)
sshConfig, closeSSHAgent, err := getSSHClientConfig(environmentName, fmt.Sprintf("%s:%s", sshHost, sshPort), ignoreHostKey, acceptNewHostKey)
if err != nil {
return fmt.Errorf("couldn't get SSH client config: %v", err)
}
Expand Down
6 changes: 6 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ var verboseOutput bool

var skipUpdateCheck bool

var strictHostKeyCheck string

// global for the lagoon config that the cli uses
// @TODO: when lagoon-cli rewrite happens, do this a bit better
var lagoonCLIConfig lagooncli.Config
Expand All @@ -61,6 +63,9 @@ var rootCmd = &cobra.Command{
if lagoonCLIConfig.UpdateCheckDisable {
skipUpdateCheck = true
}
if lagoonCLIConfig.StrictHostKeyChecking != "" {
strictHostKeyCheck = lagoonCLIConfig.StrictHostKeyChecking
}
if !skipUpdateCheck {
// Using code from https://github.com/drud/ddev/
updateFile := filepath.Join(userPath, ".lagoon.update")
Expand Down Expand Up @@ -142,6 +147,7 @@ func init() {
rootCmd.PersistentFlags().BoolVarP(&debugEnable, "debug", "", false, "Enable debugging output (if supported)")
rootCmd.PersistentFlags().BoolVarP(&skipUpdateCheck, "skip-update-check", "", false, "Skip checking for updates")
rootCmd.PersistentFlags().BoolVarP(&verboseOutput, "verbose", "v", false, "Enable verbose output to stderr (if supported)")
rootCmd.PersistentFlags().StringVar(&strictHostKeyCheck, "strict-host-key-checking", "accept-new", "Similar to SSH StrictHostKeyChecking (accept-new, no, ignore)")

// get config-file from flag
rootCmd.PersistentFlags().StringP("config-file", "", "", "Path to the config file to use (must be *.yml or *.yaml)")
Expand Down
46 changes: 9 additions & 37 deletions cmd/ssh.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
package cmd

import (
"context"
"fmt"
"os"

"github.com/uselagoon/machinery/api/lagoon"
lclient "github.com/uselagoon/machinery/api/lagoon/client"

"github.com/spf13/cobra"
lagoonssh "github.com/uselagoon/lagoon-cli/pkg/lagoon/ssh"
"github.com/uselagoon/lagoon-cli/pkg/output"
Expand All @@ -33,43 +29,16 @@ var sshEnvCmd = &cobra.Command{
if err != nil {
return err
}
ignoreHostKey, acceptNewHostKey := lagoonssh.CheckStrictHostKey(strictHostKeyCheck)

// allow the use of the `feature/branch` and standard `feature-branch` type environment names to be used
// since ssh requires the `feature-branch` type name to be used as the ssh username
// run the environment through the makesafe and shorted functions that lagoon uses
environmentName := makeSafe(shortenEnvironment(cmdProjectName, cmdProjectEnvironment))

current := lagoonCLIConfig.Current
// set the default ssh host and port to the core ssh endpoint
sshHost := lagoonCLIConfig.Lagoons[current].HostName
sshPort := lagoonCLIConfig.Lagoons[current].Port
isPortal := false

// if the config for this lagoon is set to use ssh portal support, handle that here
token := lagoonCLIConfig.Lagoons[current].Token
lc := lclient.New(
lagoonCLIConfig.Lagoons[current].GraphQL,
lagoonCLIVersion,
lagoonCLIConfig.Lagoons[current].Version,
&token,
debug)
project, err := lagoon.GetSSHEndpointsByProject(context.TODO(), cmdProjectName, lc)
sshHost, sshPort, isPortal, err := getSSHHostPort(environmentName, debug)
if err != nil {
return err
}
// check all the environments for this project
for _, env := range project.Environments {
// if the env name matches the requested environment then check if the deploytarget supports regional ssh endpoints
if env.Name == environmentName {
// if the deploytarget supports regional endpoints, then set these as the host and port for ssh
if env.DeployTarget.SSHHost != "" && env.DeployTarget.SSHPort != "" {
sshHost = env.DeployTarget.SSHHost
sshPort = env.DeployTarget.SSHPort
isPortal = true
}
}
return fmt.Errorf("couldn't get SSH endpoint: %v", err)
}

// get private key that the cli is using
skipAgent := false

Expand All @@ -93,18 +62,21 @@ var sshEnvCmd = &cobra.Command{
if sshConnString {
fmt.Println(generateSSHConnectionString(sshConfig, sshService, sshContainer, isPortal))
} else {

hkcb, hkalgo, err := lagoonssh.InteractiveKnownHosts(userPath, fmt.Sprintf("%s:%s", sshHost, sshPort), ignoreHostKey, acceptNewHostKey)
if err != nil {
return fmt.Errorf("couldn't get ~/.ssh/known_hosts: %v", err)
}
// start an interactive ssh session
authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].PublicKeyIdentities, skipAgent)
config := &ssh.ClientConfig{
User: sshConfig["username"],
Auth: []ssh.AuthMethod{
authMethod,
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
HostKeyCallback: hkcb,
HostKeyAlgorithms: hkalgo,
}
defer closeSSHAgent()
var err error
if sshCommand != "" {
err = lagoonssh.RunSSHCommand(sshConfig, sshService, sshContainer, sshCommand, config)
} else {
Expand Down
35 changes: 18 additions & 17 deletions docs/commands/lagoon.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,24 @@ lagoon [flags]
### Options

```
--config-file string Path to the config file to use (must be *.yml or *.yaml)
--debug Enable debugging output (if supported)
-e, --environment string Specify an environment to use
--force Force yes on prompts (if supported)
-h, --help help for lagoon
-l, --lagoon string The Lagoon instance to interact with
--no-header No header on table (if supported)
--output-csv Output as CSV (if supported)
--output-json Output as JSON (if supported)
--pretty Make JSON pretty (if supported)
-p, --project string Specify a project to use
--skip-update-check Skip checking for updates
-i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication
--ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent.
This will override any public key identities defined in configuration
-v, --verbose Enable verbose output to stderr (if supported)
--version Version information
--config-file string Path to the config file to use (must be *.yml or *.yaml)
--debug Enable debugging output (if supported)
-e, --environment string Specify an environment to use
--force Force yes on prompts (if supported)
-h, --help help for lagoon
-l, --lagoon string The Lagoon instance to interact with
--no-header No header on table (if supported)
--output-csv Output as CSV (if supported)
--output-json Output as JSON (if supported)
--pretty Make JSON pretty (if supported)
-p, --project string Specify a project to use
--skip-update-check Skip checking for updates
-i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication
--ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent.
This will override any public key identities defined in configuration
--strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new")
-v, --verbose Enable verbose output to stderr (if supported)
--version Version information
```

### SEE ALSO
Expand Down
31 changes: 16 additions & 15 deletions docs/commands/lagoon_add.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@ Add a project, or add notifications and variables to projects or environments
### Options inherited from parent commands

```
--config-file string Path to the config file to use (must be *.yml or *.yaml)
--debug Enable debugging output (if supported)
-e, --environment string Specify an environment to use
--force Force yes on prompts (if supported)
-l, --lagoon string The Lagoon instance to interact with
--no-header No header on table (if supported)
--output-csv Output as CSV (if supported)
--output-json Output as JSON (if supported)
--pretty Make JSON pretty (if supported)
-p, --project string Specify a project to use
--skip-update-check Skip checking for updates
-i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication
--ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent.
This will override any public key identities defined in configuration
-v, --verbose Enable verbose output to stderr (if supported)
--config-file string Path to the config file to use (must be *.yml or *.yaml)
--debug Enable debugging output (if supported)
-e, --environment string Specify an environment to use
--force Force yes on prompts (if supported)
-l, --lagoon string The Lagoon instance to interact with
--no-header No header on table (if supported)
--output-csv Output as CSV (if supported)
--output-json Output as JSON (if supported)
--pretty Make JSON pretty (if supported)
-p, --project string Specify a project to use
--skip-update-check Skip checking for updates
-i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication
--ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent.
This will override any public key identities defined in configuration
--strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new")
-v, --verbose Enable verbose output to stderr (if supported)
```

### SEE ALSO
Expand Down
Loading

0 comments on commit 20a1a04

Please sign in to comment.