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

refactor: safe exec commands #3699

Merged
5 changes: 3 additions & 2 deletions pkg/csi/plugins/nodeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/fluid-cloudnative/fluid/pkg/common"
"github.com/fluid-cloudnative/fluid/pkg/ddc/base"
"github.com/fluid-cloudnative/fluid/pkg/utils"
"github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
"github.com/fluid-cloudnative/fluid/pkg/utils/dataset/volume"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -161,7 +162,7 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
} else {
args = append(args, mountPath, targetPath)
}
command, err := utils.SimpleCommand("mount", args...)
command, err := cmdguard.Command("mount", args...)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
Expand Down Expand Up @@ -511,7 +512,7 @@ func checkMountInUse(volumeName string) (bool, error) {

// TODO: refer to https://github.com/kubernetes-sigs/alibaba-cloud-csi-driver/blob/4fcb743220371de82d556ab0b67b08440b04a218/pkg/oss/utils.go#L72
// for a better implementation
command, err := utils.SimpleCommand("/usr/local/bin/check_bind_mounts.sh", volumeName)
command, err := cmdguard.Command("/usr/local/bin/check_bind_mounts.sh", volumeName)
if err != nil {
return inUse, err
}
Expand Down
8 changes: 5 additions & 3 deletions pkg/ddc/alluxio/operations/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import (
"strings"
"time"

"github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
securityutil "github.com/fluid-cloudnative/fluid/pkg/utils/security"
securityutils "github.com/fluid-cloudnative/fluid/pkg/utils/security"
"github.com/go-logr/logr"

"github.com/fluid-cloudnative/fluid/pkg/utils"
Expand Down Expand Up @@ -505,7 +506,7 @@ func (a AlluxioFileUtils) exec(command []string, verbose bool) (stdout string, s

select {
case <-ch:
a.log.Info("execute in time", "command", securityutil.FilterCommand(command))
a.log.Info("execute in time", "command", securityutils.FilterCommand(command))
case <-ctx.Done():
err = fmt.Errorf("timeout when executing %v", command)
}
Expand All @@ -515,7 +516,8 @@ func (a AlluxioFileUtils) exec(command []string, verbose bool) (stdout string, s

// execWithoutTimeout
func (a AlluxioFileUtils) execWithoutTimeout(command []string, verbose bool) (stdout string, stderr string, err error) {
err = utils.ValidateCommandSlice(command)
err = cmdguard.ValidateCommandSlice(command)

if err != nil {
return
}
Expand Down
10 changes: 8 additions & 2 deletions pkg/ddc/efc/operations/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
"time"

"github.com/fluid-cloudnative/fluid/pkg/common"
"github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
securityutil "github.com/fluid-cloudnative/fluid/pkg/utils/security"
securityutils "github.com/fluid-cloudnative/fluid/pkg/utils/security"
"github.com/go-logr/logr"
)

Expand Down Expand Up @@ -57,7 +58,7 @@

select {
case <-ch:
a.log.Info("execute in time", "command", securityutil.FilterCommand(command))
a.log.Info("execute in time", "command", securityutils.FilterCommand(command))
case <-ctx.Done():
err = fmt.Errorf("timeout when executing %v", command)
}
Expand All @@ -67,6 +68,11 @@

// execWithoutTimeout
func (a EFCFileUtils) execWithoutTimeout(command []string, verbose bool) (stdout string, stderr string, err error) {
err = cmdguard.ValidateCommandSlice(command)
if err != nil {
return

Check warning on line 73 in pkg/ddc/efc/operations/base.go

View check run for this annotation

Codecov / codecov/patch

pkg/ddc/efc/operations/base.go#L71-L73

Added lines #L71 - L73 were not covered by tests
}

stdout, stderr, err = kubeclient.ExecCommandInContainer(a.podName, a.container, a.namespace, command)
if err != nil {
a.log.Info("Stdout", "Command", command, "Stdout", stdout)
Expand Down
4 changes: 2 additions & 2 deletions pkg/ddc/goosefs/operations/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
"strings"
"time"

"github.com/fluid-cloudnative/fluid/pkg/utils"
"github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
"github.com/go-logr/logr"
)
Expand Down Expand Up @@ -502,7 +502,7 @@ func (a GooseFSFileUtils) exec(command []string, verbose bool) (stdout string, s

// execWithoutTimeout
func (a GooseFSFileUtils) execWithoutTimeout(command []string, verbose bool) (stdout string, stderr string, err error) {
err = utils.ValidateCommandSlice(command)
err = cmdguard.ValidateCommandSlice(command)
if err != nil {
return
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/ddc/jindo/operations/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"strings"
"time"

"github.com/fluid-cloudnative/fluid/pkg/utils"
"github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
"github.com/go-logr/logr"
)
Expand Down Expand Up @@ -67,7 +67,7 @@

// execWithoutTimeout
func (a JindoFileUtils) execWithoutTimeout(command []string, verbose bool) (stdout string, stderr string, err error) {
err = utils.ValidateCommandSlice(command)
err = cmdguard.ValidateCommandSlice(command)

Check warning on line 70 in pkg/ddc/jindo/operations/base.go

View check run for this annotation

Codecov / codecov/patch

pkg/ddc/jindo/operations/base.go#L70

Added line #L70 was not covered by tests
if err != nil {
return
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/ddc/jindocache/operations/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"strings"
"time"

"github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
"github.com/go-logr/logr"
)
Expand Down Expand Up @@ -87,6 +88,11 @@

// execWithoutTimeout
func (a JindoFileUtils) execWithoutTimeout(command []string, verbose bool) (stdout string, stderr string, err error) {
err = cmdguard.ValidateCommandSlice(command)
if err != nil {
return

Check warning on line 93 in pkg/ddc/jindocache/operations/base.go

View check run for this annotation

Codecov / codecov/patch

pkg/ddc/jindocache/operations/base.go#L91-L93

Added lines #L91 - L93 were not covered by tests
}

stdout, stderr, err = kubeclient.ExecCommandInContainer(a.podName, a.container, a.namespace, command)
if err != nil {
a.log.Info("Stdout", "Command", command, "Stdout", stdout)
Expand Down
4 changes: 2 additions & 2 deletions pkg/ddc/jindofsx/operations/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"strings"
"time"

"github.com/fluid-cloudnative/fluid/pkg/utils"
"github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
"github.com/go-logr/logr"
)
Expand Down Expand Up @@ -88,7 +88,7 @@

// execWithoutTimeout
func (a JindoFileUtils) execWithoutTimeout(command []string, verbose bool) (stdout string, stderr string, err error) {
err = utils.ValidateCommandSlice(command)
err = cmdguard.ValidateCommandSlice(command)

Check warning on line 91 in pkg/ddc/jindofsx/operations/base.go

View check run for this annotation

Codecov / codecov/patch

pkg/ddc/jindofsx/operations/base.go#L91

Added line #L91 was not covered by tests
if err != nil {
return
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/ddc/juicefs/operations/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

"github.com/go-logr/logr"

"github.com/fluid-cloudnative/fluid/pkg/utils"
"github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
)

Expand Down Expand Up @@ -239,7 +239,7 @@
// GetStatus get status of volume
func (j JuiceFileUtils) GetStatus(source string) (status string, err error) {
var (
command = []string{"/bin/sh", "-c", fmt.Sprintf("juicefs status %s", source)}
command = []string{"sh", "-c", fmt.Sprintf("juicefs status %s", source)}
stdout string
stderr string
)
Expand Down Expand Up @@ -327,7 +327,7 @@
// execWithoutTimeout
func (j JuiceFileUtils) execWithoutTimeout(command []string) (stdout string, stderr string, err error) {
// validate the pipe command with white list
err = utils.ValidateCommandSlice(command)
err = cmdguard.ValidateCommandSlice(command)

Check warning on line 330 in pkg/ddc/juicefs/operations/base.go

View check run for this annotation

Codecov / codecov/patch

pkg/ddc/juicefs/operations/base.go#L330

Added line #L330 was not covered by tests
if err != nil {
return
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/ddc/thin/operations/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"strings"
"time"

"github.com/fluid-cloudnative/fluid/pkg/utils"
"github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
"github.com/go-logr/logr"
)
Expand Down Expand Up @@ -157,7 +157,7 @@
// execWithoutTimeout
func (t ThinFileUtils) execWithoutTimeout(command []string, verbose bool) (stdout string, stderr string, err error) {
// validate the pipe command with white list
err = utils.ValidateCommandSlice(command)
err = cmdguard.ValidateCommandSlice(command)

Check warning on line 160 in pkg/ddc/thin/operations/base.go

View check run for this annotation

Codecov / codecov/patch

pkg/ddc/thin/operations/base.go#L160

Added line #L160 was not covered by tests
if err != nil {
return
}
Expand Down
66 changes: 47 additions & 19 deletions pkg/utils/exec.go → pkg/utils/cmdguard/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,42 @@
limitations under the License.
*/

package utils
package cmdguard

import (
"fmt"
"os/exec"
"path/filepath"
"strings"

"github.com/go-logr/logr"
ctrl "sigs.k8s.io/controller-runtime"
)

func init() {
allowPathlist = buildPathList(allowPathlist)
}
var log logr.Logger

// allowPathlist of safe commands
var allowPathlist = map[string]bool{
// allowedPathList is a whitelist of safe commands
var allowedPathList = map[string]bool{
// "helm": true,
"kubectl": true,
"ddc-helm": true,
// add other commands as needed
}

var shellCommandList = map[string]bool{
"bash": true,
"sh": true,
}

// illegalChars to check
// var illegalChars = []string{"&", "|", ";", "$", "'", "`", "(", ")", ">>"}
var illegalChars = []rune{'&', '|', ';', '$', '\'', '`', '(', ')', '>'}

func init() {
allowedPathList = buildPathList(allowedPathList)
shellCommandList = buildPathList(shellCommandList)
log = ctrl.Log.WithName("utils.security")
}

// buildPathList is a function that builds a map of paths for the given pathList.
func buildPathList(pathList map[string]bool) (targetPath map[string]bool) {
targetPath = make(map[string]bool)
Expand All @@ -47,7 +58,6 @@
path, err := exec.LookPath(name)
if err != nil {
log.Info("Failed to find path %s due to %v", path, err)

} else {
targetPath[path] = enabled
}
Expand All @@ -57,19 +67,37 @@
return
}

// SimpleCommand checks the args before creating *exec.Cmd
func SimpleCommand(name string, arg ...string) (cmd *exec.Cmd, err error) {
if allowPathlist[name] {
cmd = exec.Command(name, arg...)
} else {
err = checkCommandArgs(arg...)
if err != nil {
return
}
cmd = exec.Command(name, arg...)
// Command checks the args before creating *exec.Cmd
func Command(name string, arg ...string) (cmd *exec.Cmd, err error) {
commandSlice := append([]string{name}, arg...)

if err = ValidateCommandSlice(commandSlice); err != nil {
return
}

return
return exec.Command(name, arg...), nil
}

// ValidateCommandSlice validates all the commands in the commandSlice.
// - For command in allowedPathList, it passes validation without further checks.
// - For a possible shell command, it calls ValidateShellCommandSlice() for detailed checks.
// - For any other command, it checks all the command args to ensure no illegal chars exists.
func ValidateCommandSlice(commandSlice []string) (err error) {
if len(commandSlice) == 0 {
return nil

Check warning on line 87 in pkg/utils/cmdguard/exec.go

View check run for this annotation

Codecov / codecov/patch

pkg/utils/cmdguard/exec.go#L87

Added line #L87 was not covered by tests
}

mainCmd := commandSlice[0]
if allowedPathList[mainCmd] {
return nil
}

if shellCommandList[mainCmd] {
return ValidateShellCommandSlice(commandSlice)
}

// For any other mainCmd, check illegal chars in command args
return checkCommandArgs(commandSlice[1:]...)
}

// CheckCommandArgs is check string is valid in args
Expand Down
Loading
Loading