Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change: ssh host key verification prompts #370

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading