Skip to content

Commit 279fae0

Browse files
change: ssh host key verification prompts (#370)
1 parent 1301985 commit 279fae0

File tree

161 files changed

+2577
-2332
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

161 files changed

+2577
-2332
lines changed

cmd/config.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,16 @@ var configFeatureSwitch = &cobra.Command{
247247
case "false":
248248
lagoonCLIConfig.EnvironmentFromDirectory = false
249249
}
250+
strictHostKeyChecking, err := cmd.Flags().GetString("strict-host-key-checking")
251+
if err != nil {
252+
output.RenderError(err.Error(), outputOptions)
253+
os.Exit(1)
254+
}
255+
strictHostKeyCheckingProvided := cmd.Flags().Lookup("strict-host-key-checking").Changed
256+
if strictHostKeyCheckingProvided {
257+
lagoonCLIConfig.StrictHostKeyChecking = strictHostKeyChecking
258+
}
259+
250260
if err := writeLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)); err != nil {
251261
output.RenderError(err.Error(), outputOptions)
252262
os.Exit(1)
@@ -299,6 +309,7 @@ var configLagoonVersionCmd = &cobra.Command{
299309

300310
var updateCheck string
301311
var environmentFromDirectory string
312+
var strictHostKeyChecking string
302313
var fullConfigList bool
303314

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

338351
// readLagoonConfig reads the lagoon config from specified file.

cmd/login.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010

1111
"github.com/spf13/cobra"
12+
lagoonssh "github.com/uselagoon/lagoon-cli/pkg/lagoon/ssh"
1213
"golang.org/x/crypto/ssh"
1314
"golang.org/x/crypto/ssh/agent"
1415
terminal "golang.org/x/term"
@@ -139,19 +140,25 @@ func retrieveTokenViaSsh() (string, error) {
139140
privateKey = cmdSSHKey
140141
skipAgent = true
141142
}
143+
ignoreHostKey, acceptNewHostKey := lagoonssh.CheckStrictHostKey(strictHostKeyCheck)
144+
sshHost := fmt.Sprintf("%s:%s",
145+
lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].HostName,
146+
lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].Port)
147+
hkcb, hkalgo, err := lagoonssh.InteractiveKnownHosts(userPath, sshHost, ignoreHostKey, acceptNewHostKey)
148+
if err != nil {
149+
return "", fmt.Errorf("couldn't get ~/.ssh/known_hosts: %v", err)
150+
}
142151
authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].PublicKeyIdentities, skipAgent)
143152
config := &ssh.ClientConfig{
144153
User: "lagoon",
145154
Auth: []ssh.AuthMethod{
146155
authMethod,
147156
},
148-
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
157+
HostKeyCallback: hkcb,
158+
HostKeyAlgorithms: hkalgo,
149159
}
150160
defer closeSSHAgent()
151161

152-
sshHost := fmt.Sprintf("%s:%s",
153-
lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].HostName,
154-
lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].Port)
155162
conn, err := ssh.Dial("tcp", sshHost, config)
156163
if err != nil {
157164
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)

cmd/logs.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"os"
7-
"path"
87
"time"
98

109
"github.com/spf13/cobra"
@@ -13,7 +12,6 @@ import (
1312
"github.com/uselagoon/machinery/api/lagoon"
1413
lclient "github.com/uselagoon/machinery/api/lagoon/client"
1514
"golang.org/x/crypto/ssh"
16-
"golang.org/x/crypto/ssh/knownhosts"
1715
)
1816

1917
var (
@@ -58,12 +56,13 @@ func generateLogsCommand(service, container string, lines uint,
5856
return argv, nil
5957
}
6058

61-
func getSSHHostPort(environmentName string, debug bool) (string, string, error) {
59+
func getSSHHostPort(environmentName string, debug bool) (string, string, bool, error) {
6260
current := lagoonCLIConfig.Current
6361
// set the default ssh host and port to the core ssh endpoint
6462
sshHost := lagoonCLIConfig.Lagoons[current].HostName
6563
sshPort := lagoonCLIConfig.Lagoons[current].Port
6664
token := lagoonCLIConfig.Lagoons[current].Token
65+
portal := false
6766

6867
// get SSH Portal endpoint if required
6968
lc := lclient.New(
@@ -76,7 +75,7 @@ func getSSHHostPort(environmentName string, debug bool) (string, string, error)
7675
defer cancel()
7776
project, err := lagoon.GetSSHEndpointsByProject(ctx, cmdProjectName, lc)
7877
if err != nil {
79-
return "", "", fmt.Errorf("couldn't get SSH endpoint by project: %v", err)
78+
return "", "", portal, fmt.Errorf("couldn't get SSH endpoint by project: %v", err)
8079
}
8180
// check all the environments for this project
8281
for _, env := range project.Environments {
@@ -86,13 +85,14 @@ func getSSHHostPort(environmentName string, debug bool) (string, string, error)
8685
if env.DeployTarget.SSHHost != "" && env.DeployTarget.SSHPort != "" {
8786
sshHost = env.DeployTarget.SSHHost
8887
sshPort = env.DeployTarget.SSHPort
88+
portal = true
8989
}
9090
}
9191
}
92-
return sshHost, sshPort, nil
92+
return sshHost, sshPort, portal, nil
9393
}
9494

95-
func getSSHClientConfig(environmentName string) (*ssh.ClientConfig,
95+
func getSSHClientConfig(environmentName, host string, ignoreHostKey, acceptNewHostKey bool) (*ssh.ClientConfig,
9696
func() error, error) {
9797
skipAgent := false
9898
privateKey := fmt.Sprintf("%s/.ssh/id_rsa", userPath)
@@ -107,17 +107,19 @@ func getSSHClientConfig(environmentName string) (*ssh.ClientConfig,
107107
skipAgent = true
108108
}
109109
// parse known_hosts
110-
kh, err := knownhosts.New(path.Join(userPath, ".ssh/known_hosts"))
110+
hkcb, hkalgo, err := lagoonssh.InteractiveKnownHosts(userPath, host, ignoreHostKey, acceptNewHostKey)
111111
if err != nil {
112112
return nil, nil, fmt.Errorf("couldn't get ~/.ssh/known_hosts: %v", err)
113113
}
114+
114115
// configure an SSH client session
115116
authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].PublicKeyIdentities, skipAgent)
116117
return &ssh.ClientConfig{
117-
User: cmdProjectName + "-" + environmentName,
118-
Auth: []ssh.AuthMethod{authMethod},
119-
HostKeyCallback: kh,
120-
Timeout: connTimeout,
118+
User: cmdProjectName + "-" + environmentName,
119+
Auth: []ssh.AuthMethod{authMethod},
120+
HostKeyCallback: hkcb,
121+
HostKeyAlgorithms: hkalgo,
122+
Timeout: connTimeout,
121123
}, closeSSHAgent, nil
122124
}
123125

@@ -136,6 +138,7 @@ var logsCmd = &cobra.Command{
136138
if err != nil {
137139
return fmt.Errorf("couldn't get debug value: %v", err)
138140
}
141+
ignoreHostKey, acceptNewHostKey := lagoonssh.CheckStrictHostKey(strictHostKeyCheck)
139142
argv, err := generateLogsCommand(logsService, logsContainer, logsTailLines,
140143
logsFollow)
141144
if err != nil {
@@ -145,12 +148,12 @@ var logsCmd = &cobra.Command{
145148
environmentName := makeSafe(
146149
shortenEnvironment(cmdProjectName, cmdProjectEnvironment))
147150
// query the Lagoon API for the environment's SSH endpoint
148-
sshHost, sshPort, err := getSSHHostPort(environmentName, debug)
151+
sshHost, sshPort, _, err := getSSHHostPort(environmentName, debug)
149152
if err != nil {
150153
return fmt.Errorf("couldn't get SSH endpoint: %v", err)
151154
}
152155
// configure SSH client session
153-
sshConfig, closeSSHAgent, err := getSSHClientConfig(environmentName)
156+
sshConfig, closeSSHAgent, err := getSSHClientConfig(environmentName, fmt.Sprintf("%s:%s", sshHost, sshPort), ignoreHostKey, acceptNewHostKey)
154157
if err != nil {
155158
return fmt.Errorf("couldn't get SSH client config: %v", err)
156159
}

cmd/root.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ var verboseOutput bool
4141

4242
var skipUpdateCheck bool
4343

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

146152
// get config-file from flag
147153
rootCmd.PersistentFlags().StringP("config-file", "", "", "Path to the config file to use (must be *.yml or *.yaml)")

cmd/ssh.go

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
package cmd
22

33
import (
4-
"context"
54
"fmt"
65
"os"
76

8-
"github.com/uselagoon/machinery/api/lagoon"
9-
lclient "github.com/uselagoon/machinery/api/lagoon/client"
10-
117
"github.com/spf13/cobra"
128
lagoonssh "github.com/uselagoon/lagoon-cli/pkg/lagoon/ssh"
139
"github.com/uselagoon/lagoon-cli/pkg/output"
@@ -33,43 +29,16 @@ var sshEnvCmd = &cobra.Command{
3329
if err != nil {
3430
return err
3531
}
32+
ignoreHostKey, acceptNewHostKey := lagoonssh.CheckStrictHostKey(strictHostKeyCheck)
3633

3734
// allow the use of the `feature/branch` and standard `feature-branch` type environment names to be used
3835
// since ssh requires the `feature-branch` type name to be used as the ssh username
3936
// run the environment through the makesafe and shorted functions that lagoon uses
4037
environmentName := makeSafe(shortenEnvironment(cmdProjectName, cmdProjectEnvironment))
41-
42-
current := lagoonCLIConfig.Current
43-
// set the default ssh host and port to the core ssh endpoint
44-
sshHost := lagoonCLIConfig.Lagoons[current].HostName
45-
sshPort := lagoonCLIConfig.Lagoons[current].Port
46-
isPortal := false
47-
48-
// if the config for this lagoon is set to use ssh portal support, handle that here
49-
token := lagoonCLIConfig.Lagoons[current].Token
50-
lc := lclient.New(
51-
lagoonCLIConfig.Lagoons[current].GraphQL,
52-
lagoonCLIVersion,
53-
lagoonCLIConfig.Lagoons[current].Version,
54-
&token,
55-
debug)
56-
project, err := lagoon.GetSSHEndpointsByProject(context.TODO(), cmdProjectName, lc)
38+
sshHost, sshPort, isPortal, err := getSSHHostPort(environmentName, debug)
5739
if err != nil {
58-
return err
59-
}
60-
// check all the environments for this project
61-
for _, env := range project.Environments {
62-
// if the env name matches the requested environment then check if the deploytarget supports regional ssh endpoints
63-
if env.Name == environmentName {
64-
// if the deploytarget supports regional endpoints, then set these as the host and port for ssh
65-
if env.DeployTarget.SSHHost != "" && env.DeployTarget.SSHPort != "" {
66-
sshHost = env.DeployTarget.SSHHost
67-
sshPort = env.DeployTarget.SSHPort
68-
isPortal = true
69-
}
70-
}
40+
return fmt.Errorf("couldn't get SSH endpoint: %v", err)
7141
}
72-
7342
// get private key that the cli is using
7443
skipAgent := false
7544

@@ -93,18 +62,21 @@ var sshEnvCmd = &cobra.Command{
9362
if sshConnString {
9463
fmt.Println(generateSSHConnectionString(sshConfig, sshService, sshContainer, isPortal))
9564
} else {
96-
65+
hkcb, hkalgo, err := lagoonssh.InteractiveKnownHosts(userPath, fmt.Sprintf("%s:%s", sshHost, sshPort), ignoreHostKey, acceptNewHostKey)
66+
if err != nil {
67+
return fmt.Errorf("couldn't get ~/.ssh/known_hosts: %v", err)
68+
}
9769
// start an interactive ssh session
9870
authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].PublicKeyIdentities, skipAgent)
9971
config := &ssh.ClientConfig{
10072
User: sshConfig["username"],
10173
Auth: []ssh.AuthMethod{
10274
authMethod,
10375
},
104-
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
76+
HostKeyCallback: hkcb,
77+
HostKeyAlgorithms: hkalgo,
10578
}
10679
defer closeSSHAgent()
107-
var err error
10880
if sshCommand != "" {
10981
err = lagoonssh.RunSSHCommand(sshConfig, sshService, sshContainer, sshCommand, config)
11082
} else {

docs/commands/lagoon.md

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,24 @@ lagoon [flags]
1313
### Options
1414

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

3536
### SEE ALSO

docs/commands/lagoon_add.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,22 @@ Add a project, or add notifications and variables to projects or environments
1111
### Options inherited from parent commands
1212

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

3132
### SEE ALSO

0 commit comments

Comments
 (0)