Skip to content

Commit

Permalink
Check index or plugin name safety in cmd (#583)
Browse files Browse the repository at this point in the history
* Check index or plugin name safety in cmd

Moving plugin name or index name safety checks to cmd/ where they are given by
the user to the program. This way we shift the need for validation from being
unit tests of low-level machinery to the integration_test which tests
user-facing concerns.

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>

* code review feedback

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>

* address code review feedback

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>

* fix bug in install

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
  • Loading branch information
ahmetb authored Apr 4, 2020
1 parent f958833 commit da05925
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 14 deletions.
12 changes: 10 additions & 2 deletions cmd/krew/cmd/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import (
)

var (
forceIndexDelete *bool
forceIndexDelete *bool
errInvalidIndexName = errors.New("invalid index name")
)

// indexCmd represents the index command
Expand Down Expand Up @@ -70,7 +71,11 @@ var indexAddCmd = &cobra.Command{
Example: "kubectl krew index add default " + constants.IndexURI,
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
return indexoperations.AddIndex(paths, args[0], args[1])
name := args[0]
if !indexoperations.IsValidIndexName(name) {
return errInvalidIndexName
}
return indexoperations.AddIndex(paths, name, args[1])
},
}

Expand All @@ -89,6 +94,9 @@ option is used ( not recommended).`,

func indexDelete(_ *cobra.Command, args []string) error {
name := args[0]
if !indexoperations.IsValidIndexName(name) {
return errInvalidIndexName
}

ps, err := installation.InstalledPluginsFromIndex(paths.InstallReceiptsPath(), name)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions cmd/krew/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

"sigs.k8s.io/krew/cmd/krew/cmd/internal"
"sigs.k8s.io/krew/internal/index/indexscanner"
"sigs.k8s.io/krew/internal/index/validation"
"sigs.k8s.io/krew/internal/installation"
"sigs.k8s.io/krew/internal/pathutil"
"sigs.k8s.io/krew/pkg/index"
Expand Down Expand Up @@ -98,6 +99,10 @@ Remarks:
var install []pluginEntry
for _, name := range pluginNames {
indexName, pluginName := pathutil.CanonicalPluginName(name)
if !validation.IsSafePluginName(pluginName) {
return unsafePluginNameErr(pluginName)
}

plugin, err := indexscanner.LoadPluginByName(paths.IndexPluginsPath(indexName), pluginName)
if err != nil {
if os.IsNotExist(err) {
Expand Down
6 changes: 6 additions & 0 deletions cmd/krew/cmd/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/klog"

"sigs.k8s.io/krew/internal/index/validation"
"sigs.k8s.io/krew/internal/installation"
)

Expand All @@ -38,6 +39,9 @@ Remarks:
Failure to uninstall a plugin will result in an error and exit immediately.`,
RunE: func(cmd *cobra.Command, args []string) error {
for _, name := range args {
if !validation.IsSafePluginName(name) {
return unsafePluginNameErr(name)
}
klog.V(4).Infof("Going to uninstall plugin %s\n", name)
if err := installation.Uninstall(paths, name); err != nil {
return errors.Wrapf(err, "failed to uninstall plugin %s", name)
Expand All @@ -51,6 +55,8 @@ Remarks:
Aliases: []string{"remove"},
}

func unsafePluginNameErr(n string) error { return errors.Errorf("plugin name %q not allowed", n) }

func init() {
rootCmd.AddCommand(uninstallCmd)
}
4 changes: 4 additions & 0 deletions cmd/krew/cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"sigs.k8s.io/krew/cmd/krew/cmd/internal"
"sigs.k8s.io/krew/internal/index/indexscanner"
"sigs.k8s.io/krew/internal/index/validation"
"sigs.k8s.io/krew/internal/installation"
"sigs.k8s.io/krew/internal/pathutil"
)
Expand Down Expand Up @@ -64,6 +65,9 @@ kubectl krew upgrade foo bar"`,
var nErrors int
for _, name := range pluginNames {
indexName, pluginName := pathutil.CanonicalPluginName(name)
if !validation.IsSafePluginName(pluginName) {
return unsafePluginNameErr(pluginName)
}
plugin, err := indexscanner.LoadPluginByName(paths.IndexPluginsPath(indexName), pluginName)
if err != nil {
if !os.IsNotExist(err) {
Expand Down
38 changes: 38 additions & 0 deletions integration_test/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package integrationtest

import (
"os"
"strings"
"testing"

"sigs.k8s.io/krew/pkg/constants"
Expand Down Expand Up @@ -45,6 +46,25 @@ func TestKrewIndexAdd(t *testing.T) {
}
}

func TestKrewIndexAddUnsafe(t *testing.T) {
skipShort(t)
test, cleanup := NewTest(t)
defer cleanup()
test = test.WithEnv(constants.EnableMultiIndexSwitch, 1).WithIndex()

cases := []string{"a/b", `a\b`, "../a", `..\a`}
expected := "invalid index name"

for _, c := range cases {
b, err := test.Krew("index", "add", c, constants.IndexURI).Run()
if err == nil {
t.Fatalf("%q: expected error", c)
} else if !strings.Contains(string(b), expected) {
t.Fatalf("%q: output doesn't contain %q: %q", c, expected, string(b))
}
}
}

func TestKrewIndexList(t *testing.T) {
skipShort(t)

Expand Down Expand Up @@ -109,6 +129,24 @@ func TestKrewIndexRemove_ok(t *testing.T) {
test.Krew("index", "remove", "foo").RunOrFail()
}

func TestKrewIndexRemove_unsafe(t *testing.T) {
skipShort(t)
test, cleanup := NewTest(t)
test = test.WithEnv(constants.EnableMultiIndexSwitch, 1).WithIndex()
defer cleanup()

expected := "invalid index name"
cases := []string{"a/b", `a\b`, "../a", `..\a`}
for _, c := range cases {
b, err := test.Krew("index", "remove", c).Run()
if err == nil {
t.Fatalf("%q: expected error", c)
} else if !strings.Contains(string(b), expected) {
t.Fatalf("%q: output doesn't contain %q: %q", c, expected, string(b))
}
}
}

func TestKrewIndexRemoveFailsWhenPluginsInstalled(t *testing.T) {
skipShort(t)
test, cleanup := NewTest(t)
Expand Down
25 changes: 25 additions & 0 deletions integration_test/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,31 @@ func TestKrewInstallReRun(t *testing.T) {
test.AssertExecutableInPATH("kubectl-" + validPlugin)
}

func TestKrewInstallUnsafe(t *testing.T) {
skipShort(t)
test, cleanup := NewTest(t)
defer cleanup()
test.WithEnv(constants.EnableMultiIndexSwitch, 1).WithIndex()

cases := []string{
`../index/` + validPlugin,
`..\index\` + validPlugin,
`../default/` + validPlugin,
`..\default\` + validPlugin,
`index-name/sub-directory/plugin-name`,
}

expectedErr := `not allowed`
for _, c := range cases {
b, err := test.Krew("install", c).Run()
if err == nil {
t.Fatalf("%q expected failure", c)
} else if !strings.Contains(string(b), expectedErr) {
t.Fatalf("%q does not contain err %q: %q", c, expectedErr, string(b))
}
}
}

func TestKrewInstall_MultiplePositionalArgs(t *testing.T) {
skipShort(t)

Expand Down
28 changes: 28 additions & 0 deletions integration_test/uninstall_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package integrationtest
import (
"os"
"path/filepath"
"strings"
"testing"

"sigs.k8s.io/krew/internal/environment"
Expand Down Expand Up @@ -75,3 +76,30 @@ func TestKrewRemove_ManifestRemovedFromIndex(t *testing.T) {
}
test.Krew("remove", validPlugin).RunOrFail()
}

func TestKrewRemove_Unsafe(t *testing.T) {
skipShort(t)
test, cleanup := NewTest(t)
defer cleanup()
test.WithEnv(constants.EnableMultiIndexSwitch, 1).WithIndex()
test.Krew("install", validPlugin).RunOrFailOutput()

cases := []string{
`../index/` + validPlugin,
`..\index\` + validPlugin,
`../default/` + validPlugin,
`..\default\` + validPlugin,
`../receipts/` + validPlugin,
`..\receipts\` + validPlugin,
`default/subdir/plugin-name`,
}
expectedErr := `not allowed`
for _, c := range cases {
b, err := test.Krew("uninstall", c).Run()
if err == nil {
t.Fatalf("%q expected failure", c)
} else if !strings.Contains(string(b), expectedErr) {
t.Fatalf("%q does not contain err %q: %q", c, expectedErr, string(b))
}
}
}
25 changes: 25 additions & 0 deletions integration_test/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@ func TestKrewUpgrade(t *testing.T) {
}
}

func TestKrewUpgradeUnsafe(t *testing.T) {
skipShort(t)
test, cleanup := NewTest(t)
defer cleanup()
test.WithEnv(constants.EnableMultiIndexSwitch, 1).WithIndex()

cases := []string{
`../index/` + validPlugin,
`..\index\` + validPlugin,
`../default/` + validPlugin,
`..\default\` + validPlugin,
`index-name/sub-directory/plugin-name`,
}

expectedErr := `not allowed`
for _, c := range cases {
b, err := test.Krew("upgrade", c).Run()
if err == nil {
t.Fatalf("%q expected failure", c)
} else if !strings.Contains(string(b), expectedErr) {
t.Fatalf("%q does not contain err %q: %q", c, expectedErr, string(b))
}
}
}

func TestKrewUpgradeWhenPlatformNoLongerMatches(t *testing.T) {
skipShort(t)

Expand Down
9 changes: 1 addition & 8 deletions internal/index/indexoperations/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ func ListIndexes(paths environment.Paths) ([]Index, error) {

// AddIndex initializes a new index to install plugins from.
func AddIndex(paths environment.Paths, name, url string) error {
if !IsValidIndexName(name) {
return errors.New("invalid index name")
}

dir := paths.IndexPath(name)
if _, err := os.Stat(dir); os.IsNotExist(err) {
return gitutil.EnsureCloned(url, dir)
Expand All @@ -74,14 +70,11 @@ func AddIndex(paths environment.Paths, name, url string) error {

// DeleteIndex removes specified index name. If index does not exist, returns an error that can be tested by os.IsNotExist.
func DeleteIndex(paths environment.Paths, name string) error {
if !IsValidIndexName(name) {
return errors.New("invalid index name")
}

dir := paths.IndexPath(name)
if _, err := os.Stat(dir); err != nil {
return err
}

return os.RemoveAll(dir)
}

Expand Down
5 changes: 5 additions & 0 deletions internal/index/indexoperations/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ func TestIsValidIndexName(t *testing.T) {
index: "foo/bar",
want: false,
},
{
name: "relative path",
index: "../foo",
want: false,
},
{
name: "with back slash",
index: "foo\\bar",
Expand Down
4 changes: 0 additions & 4 deletions internal/index/indexscanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,6 @@ func LoadPluginListFromFS(indexDir string) ([]index.Plugin, error) {
// LoadPluginByName loads a plugins index file by its name. When plugin
// file not found, it returns an error that can be checked with os.IsNotExist.
func LoadPluginByName(pluginsDir, pluginName string) (index.Plugin, error) {
if !validation.IsSafePluginName(pluginName) {
return index.Plugin{}, errors.Errorf("plugin name %q not allowed", pluginName)
}

klog.V(4).Infof("Reading plugin %q from %s", pluginName, pluginsDir)
return ReadPluginFromFile(filepath.Join(pluginsDir, pluginName+constants.ManifestExtension))
}
Expand Down

0 comments on commit da05925

Please sign in to comment.