mirror of https://github.com/knative/client.git
229 lines
6.3 KiB
Go
229 lines
6.3 KiB
Go
// Copyright © 2018 The Knative Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package plugin
|
|
|
|
import (
|
|
"errors"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"knative.dev/client/pkg/kn/commands"
|
|
"knative.dev/client/pkg/util"
|
|
|
|
"github.com/spf13/cobra"
|
|
"gotest.tools/assert"
|
|
)
|
|
|
|
func TestPluginVerifier(t *testing.T) {
|
|
|
|
var (
|
|
pluginPath string
|
|
rootCmd *cobra.Command
|
|
verifier *pluginVerifier
|
|
)
|
|
|
|
setup := func(t *testing.T) {
|
|
knParams := &commands.KnParams{}
|
|
rootCmd, _, _ = commands.CreateTestKnCommand(NewPluginCommand(knParams), knParams)
|
|
verifier = newPluginVerifier(rootCmd)
|
|
}
|
|
|
|
cleanup := func(t *testing.T) {
|
|
if pluginPath != "" {
|
|
DeleteTestPlugin(t, pluginPath)
|
|
}
|
|
}
|
|
|
|
t.Run("with nil root command", func(t *testing.T) {
|
|
t.Run("returns error verifying path", func(t *testing.T) {
|
|
setup(t)
|
|
defer cleanup(t)
|
|
verifier.root = nil
|
|
eaw := errorsAndWarnings{}
|
|
eaw = verifier.verify(eaw, pluginPath)
|
|
assert.Assert(t, len(eaw.errors) == 1)
|
|
assert.Assert(t, len(eaw.warnings) == 0)
|
|
assert.Assert(t, util.ContainsAll(eaw.errors[0], "nil root"))
|
|
})
|
|
})
|
|
|
|
t.Run("with root command", func(t *testing.T) {
|
|
t.Run("whether plugin in path is executable (unix only)", func(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("Skip test for windows as the permission check are for Unix only")
|
|
return
|
|
}
|
|
|
|
setup(t)
|
|
defer cleanup(t)
|
|
|
|
pluginDir, err := ioutil.TempDir("", "plugin")
|
|
assert.NilError(t, err)
|
|
defer os.RemoveAll(pluginDir)
|
|
pluginPath := filepath.Join(pluginDir, "kn-execution-test")
|
|
err = ioutil.WriteFile(pluginPath, []byte("#!/bin/sh\ntrue"), 0644)
|
|
assert.NilError(t, err, "can't create test plugin")
|
|
|
|
for _, uid := range getExecTestUids() {
|
|
for _, gid := range getExecTestGids() {
|
|
for _, userPerm := range []int{0, UserExecute} {
|
|
for _, groupPerm := range []int{0, GroupExecute} {
|
|
for _, otherPerm := range []int{0, OtherExecute} {
|
|
perm := os.FileMode(userPerm | groupPerm | otherPerm + 0444)
|
|
assert.NilError(t, prepareFile(pluginPath, uid, gid, perm), "prepare plugin file, uid: %d, gid: %d, perm: %03o", uid, gid, perm)
|
|
|
|
eaw := errorsAndWarnings{}
|
|
eaw = newPluginVerifier(rootCmd).verify(eaw, pluginPath)
|
|
|
|
if isExecutable(pluginPath) {
|
|
assert.Assert(t, len(eaw.warnings) == 0, "executable: perm %03o | uid %d | gid %d | %v", perm, uid, gid, eaw.warnings)
|
|
assert.Assert(t, len(eaw.errors) == 0)
|
|
} else {
|
|
assert.Assert(t, len(eaw.warnings) == 1, "not executable: perm %03o | uid %d | gid %d | %v", perm, uid, gid, eaw.warnings)
|
|
assert.Assert(t, len(eaw.errors) == 0)
|
|
assert.Assert(t, util.ContainsAll(eaw.warnings[0], pluginPath))
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("when kn plugin in path is executable", func(t *testing.T) {
|
|
setup(t)
|
|
defer cleanup(t)
|
|
pluginPath = CreateTestPlugin(t, KnTestPluginName, KnTestPluginScript, FileModeExecutable)
|
|
|
|
t.Run("when kn plugin in path shadows another", func(t *testing.T) {
|
|
var shadowPluginPath = CreateTestPlugin(t, KnTestPluginName, KnTestPluginScript, FileModeExecutable)
|
|
verifier.seenPlugins[KnTestPluginName] = pluginPath
|
|
defer DeleteTestPlugin(t, shadowPluginPath)
|
|
|
|
t.Run("fails with overshadowed error", func(t *testing.T) {
|
|
eaw := errorsAndWarnings{}
|
|
eaw = verifier.verify(eaw, shadowPluginPath)
|
|
assert.Assert(t, len(eaw.errors) == 0)
|
|
assert.Assert(t, len(eaw.warnings) == 1)
|
|
assert.Assert(t, util.ContainsAll(eaw.warnings[0], "shadowed", "ignored"))
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("when kn plugin in path overwrites existing command", func(t *testing.T) {
|
|
setup(t)
|
|
defer cleanup(t)
|
|
var overwritingPluginPath = CreateTestPlugin(t, "kn-plugin", KnTestPluginScript, FileModeExecutable)
|
|
defer DeleteTestPlugin(t, overwritingPluginPath)
|
|
|
|
t.Run("fails with overwrites error", func(t *testing.T) {
|
|
eaw := errorsAndWarnings{}
|
|
eaw = verifier.verify(eaw, overwritingPluginPath)
|
|
assert.Assert(t, len(eaw.errors) == 1)
|
|
assert.Assert(t, len(eaw.warnings) == 0)
|
|
assert.Assert(t, util.ContainsAll(eaw.errors[0], "overwrite", "kn-plugin"))
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
func isExecutable(plugin string) bool {
|
|
_, err := exec.Command(plugin).Output()
|
|
return err == nil
|
|
}
|
|
|
|
func getExecTestUids() []int {
|
|
currentUser := os.Getuid()
|
|
// Only root can switch ownership of a file
|
|
if currentUser == 0 {
|
|
foreignUser, err := lookupForeignUser()
|
|
if err == nil {
|
|
return []int{currentUser, foreignUser}
|
|
}
|
|
}
|
|
return []int{currentUser}
|
|
}
|
|
|
|
func getExecTestGids() []int {
|
|
currentUser := os.Getuid()
|
|
currentGroup := os.Getgid()
|
|
// Only root can switch group of a file
|
|
if currentUser == 0 {
|
|
foreignGroup, err := lookupForeignGroup()
|
|
if err == nil {
|
|
return []int{currentGroup, foreignGroup}
|
|
}
|
|
}
|
|
return []int{currentGroup}
|
|
}
|
|
|
|
func lookupForeignUser() (int, error) {
|
|
for _, probe := range []string{"daemon", "nobody", "_unknown"} {
|
|
u, err := user.Lookup(probe)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
uid, err := strconv.Atoi(u.Uid)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if uid != os.Getuid() {
|
|
return uid, nil
|
|
}
|
|
}
|
|
return 0, errors.New("could not find foreign user")
|
|
}
|
|
|
|
func lookupForeignGroup() (int, error) {
|
|
gids, err := os.Getgroups()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
OUTER:
|
|
for _, probe := range []string{"daemon", "wheel", "nobody", "nogroup", "admin"} {
|
|
group, err := user.LookupGroup(probe)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
gid, err := strconv.Atoi(group.Gid)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for _, g := range gids {
|
|
if gid == g {
|
|
continue OUTER
|
|
}
|
|
}
|
|
return gid, nil
|
|
}
|
|
return 0, errors.New("could not find a foreign group")
|
|
}
|
|
|
|
func prepareFile(file string, uid int, gid int, perm os.FileMode) error {
|
|
err := os.Chown(file, uid, gid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.Chmod(file, perm)
|
|
}
|