Skip to content

Commit

Permalink
feat(gardener#161): adds a prompt when providing /32 CIDR ranges
Browse files Browse the repository at this point in the history
including an --force flag that hides such prompts for non-interactive/script usage
  • Loading branch information
sven-petersen committed Oct 26, 2022
1 parent 445e728 commit 05c988b
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 1 deletion.
8 changes: 8 additions & 0 deletions pkg/cmd/ssh/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"context"
"os"
"time"

"github.com/gardener/gardenctl-v2/internal/util"
)

func SetBastionAvailabilityChecker(f func(hostname string, privateKey []byte) error) {
Expand Down Expand Up @@ -42,3 +44,9 @@ func SetKeepAliveInterval(d time.Duration) {

keepAliveInterval = d
}

var Confirm = confirm

func SetConfirm(f func(ioStreams util.IOStreams, question string, defaultAnswer bool) bool) {
confirm = f
}
45 changes: 44 additions & 1 deletion pkg/cmd/ssh/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0
package ssh

import (
"bufio"
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
Expand Down Expand Up @@ -151,6 +153,34 @@ var (

return cmd.Run()
}

// Displays a prompt to the user to confirm (yes/no) something
confirm = func(ioStreams util.IOStreams, question string, defaultAnswer bool) bool {
reader := bufio.NewReader(ioStreams.In)

choices := "y/N"
if defaultAnswer {
choices = "n/Y"
}

for {
fmt.Fprint(ioStreams.Out, question+" ["+choices+"]: ")

str, _ := reader.ReadString('\n')

str = strings.TrimSpace(str)
str = strings.ToLower(str)

switch str {
case "":
return defaultAnswer
case "n", "no":
return false
case "y", "yes":
return true
}
}
}
)

// SSHOptions is a struct to support ssh command
Expand Down Expand Up @@ -199,6 +229,10 @@ type SSHOptions struct {
// bastion once it exits. By default it deletes it, but we allow the user to
// keep it for debugging purposes.
KeepBastion bool

// Force will silence warnings and interactive prompts. The latter happens if the user
// specifies a /32/large/ CIDR range which usually requires the users confirmation.
Force bool
}

// NewSSHOptions returns initialized SSHOptions
Expand Down Expand Up @@ -301,9 +335,18 @@ func (o *SSHOptions) Validate() error {
}

for _, cidr := range o.CIDRs {
if _, _, err := net.ParseCIDR(cidr); err != nil {
_, netIP, err := net.ParseCIDR(cidr)
if err != nil {
return fmt.Errorf("CIDR %q is invalid: %w", cidr, err)
}

if !o.Force && bytes.Compare(netIP.Mask, []byte{0, 0, 0, 0}) <= 0 {
question := fmt.Sprintf("Large CIDR range %s compromises security. Continue?", cidr)
if !confirm(o.IOStreams, question, false) {
// return fmt.Errorf("")
os.Exit(0)
}
}
}

content, err := os.ReadFile(o.SSHPublicKeyFile)
Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/ssh/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func NewCmdSSH(f util.Factory, o *SSHOptions) *cobra.Command {
cmd.Flags().StringVar(&o.SSHPublicKeyFile, "public-key-file", "", "Path to the file that contains a public SSH key. If not given, a temporary keypair will be generated.")
cmd.Flags().DurationVar(&o.WaitTimeout, "wait-timeout", o.WaitTimeout, "Maximum duration to wait for the bastion to become available.")
cmd.Flags().BoolVar(&o.KeepBastion, "keep-bastion", o.KeepBastion, "Do not delete immediately when gardenctl exits (Bastions will be garbage-collected after some time)")
cmd.Flags().BoolVar(&o.Force, "force", o.Force, "Do not show warnings and do not prompt for confirmation. Does not affect access control warnings.")

return cmd
}
44 changes: 44 additions & 0 deletions pkg/cmd/ssh/ssh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/gardener/gardener/pkg/utils/secrets"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -569,4 +570,47 @@ var _ = Describe("SSH Options", func() {

Expect(o.Validate()).NotTo(Succeed())
})

DescribeTable("Prompt should print question and should return...", func(defaultAnswer bool, answer string, expectedResult bool) {
question := "Is 42 the ultimate answer?"
expectedPrompt := question
if defaultAnswer {
expectedPrompt += " [n/Y]: "
} else {
expectedPrompt += " [y/N]: "
}
streams, stdin, stdout, _ := util.NewTestIOStreams()

stdin.Write([]byte(answer))
result := ssh.Confirm(streams, question, defaultAnswer)
stdoutStr := stdout.String()

Expect(stdoutStr).To(Equal(expectedPrompt))
Expect(result).To(Equal(expectedResult))
},
Entry("true (defaultAnswer: false, given answer: y )", false, "y", true),
Entry("false (defaultAnswer: false, given answer: n )", false, "n", false),
Entry("false (defaultAnswer: false, given answer: \\n)", false, "\n", false),
Entry("true (defaultAnswer: true , given answer: yEs )", true, "yEs", true),
Entry("false (defaultAnswer: true , given answer: nO )", true, "nO", false),
Entry("true (defaultAnswer: true , given answer: \\n)", true, "\n", true),
)

DescribeTable("Should prompt for confirmation in case of /0 CIDRs without force-flag", func(cidr string, forceFlag, shouldConfirm bool) {
o := ssh.NewSSHOptions(streams)
o.CIDRs = []string{cidr}
o.SSHPublicKeyFile = publicSSHKeyFile
o.Force = forceFlag
confirmCalled := false
ssh.SetConfirm(func(ioStreams util.IOStreams, question string, defaultAnswer bool) bool {
confirmCalled = true
return true
})
o.Validate()
Expect(confirmCalled).To(Equal(shouldConfirm))
},
Entry("testing CIDR 8.8.8.8/0 without force flag", "8.8.8.8/0", false, true),
Entry("testing CIDR 8.8.8.8/16 without force flag", "8.8.8.8/16", false, false),
Entry("testing CIDR 8.8.8.8/0 with force flag", "8.8.8.8/0", true, false),
)
})

0 comments on commit 05c988b

Please sign in to comment.