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

[WIP] security : add more tests #919

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 70 additions & 2 deletions tests/sec/remote.go → tests/sec/rutils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sec_test

import (
"encoding/json"
"fmt"
"io"
"os"
Expand All @@ -11,6 +12,20 @@ import (
"github.com/lf-edge/eden/pkg/utils"
)

type mount struct {
Path string `json:"path"`
Type string `json:"type"`
Options string `json:"options"`
}

type perm struct {
uid int
gid int
user string
group string
perms string
}

type remoteNode struct {
openEVEC *openevec.OpenEVEC
}
Expand Down Expand Up @@ -59,7 +74,7 @@ func (node *remoteNode) runCommand(command string) ([]byte, error) {
return out, nil
}

func (node *remoteNode) fileExists(fileName string) (bool, error) {
func (node *remoteNode) pathExists(fileName string) (bool, error) {
command := fmt.Sprintf("if stat \"%s\"; then echo \"1\"; else echo \"0\"; fi", fileName)
out, err := node.runCommand(command)
if err != nil {
Expand All @@ -74,7 +89,7 @@ func (node *remoteNode) fileExists(fileName string) (bool, error) {
}

func (node *remoteNode) readFile(fileName string) ([]byte, error) {
exist, err := node.fileExists(fileName)
exist, err := node.pathExists(fileName)
if err != nil {
return nil, err
}
Expand All @@ -86,3 +101,56 @@ func (node *remoteNode) readFile(fileName string) ([]byte, error) {
command := fmt.Sprintf("cat %s", fileName)
return node.runCommand(command)
}

func (node *remoteNode) getPathPerm(path string, perm *perm) error {
exist, err := node.pathExists(path)
if err != nil {
return err
}

if !exist {
return fmt.Errorf("file/dir %s does not exist", path)
}

command := fmt.Sprintf("stat -c \"%%u %%g %%U %%G %%A\" %s", path)
out, err := node.runCommand(command)
if err != nil {
return err
}

_, err = fmt.Sscanf(string(out), "%d %d %s %s %s", &perm.uid, &perm.gid, &perm.user, &perm.group, &perm.perms)
if err != nil {
return err
}

return nil
}

func (node *remoteNode) getMountPoints(mtype string) ([]mount, error) {
mountCommand := "mount -l"
if mtype != "" {
mountCommand = fmt.Sprintf("mount -l -t %s", mtype)
}

command := mountCommand + ` | awk '
BEGIN { print " [ "}
{
printf " %s {\"path\": \"%s\", \"type\": \"%s\", \"options\": \"%s\"}", separator, $3, $5, $6;
separator = ",";
}
END { print " ] " }
'`

out, err := node.runCommand(command)
if err != nil {
return nil, err
}

var mounts []mount
if err := json.Unmarshal(out, &mounts); err != nil {
return nil, err

}

return mounts, nil
}
269 changes: 268 additions & 1 deletion tests/sec/sec_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package sec_test

import (
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"time"

log "github.com/sirupsen/logrus"

Check failure on line 11 in tests/sec/sec_test.go

View workflow job for this annotation

GitHub Actions / yetus

golangcilint: Expected '"', Found 'l' at tests/sec/sec_test.go[line 11,col 2] (gci)

"github.com/lf-edge/eden/pkg/device"
"github.com/lf-edge/eden/pkg/projects"
Expand Down Expand Up @@ -90,10 +91,159 @@
os.Exit(res)
}

//nolint:paralleltest
func TestKernelModuleSigning(t *testing.T) {
log.Println("TestKernelModuleSigning started")
defer log.Println("TestKernelModuleSigning finished")

edgeNode := tc.GetEdgeNode(tc.WithTest(t))
tc.WaitForState(edgeNode, 60)

out, err := rnode.runCommand("cat /proc/config.gz | gunzip > /tmp/running.config && cat /tmp/running.config | grep CONFIG_MODULE_SIG_FORCE")
if err != nil {
t.Fatal(err)
}

status := strings.TrimSpace(string(out))
if status != "CONFIG_MODULE_SIG_FORCE=y" {
t.Fatal("Kernel module signing is not enabled")
}
}

//nolint:paralleltest
func TestUnconfinedProcesses(t *testing.T) {
log.Println("TestUnconfinedProcesses started")
defer log.Println("TestUnconfinedProcesses finished")

edgeNode := tc.GetEdgeNode(tc.WithTest(t))
tc.WaitForState(edgeNode, 60)

// check if there are any processes with capabilities
command := `ps -eZ | awk '
BEGIN { print " [ "}
/LABEL/ {next}
{
printf " %s {\"label\": \"%s\", \"cmd\": \"%s\"}", separator, $1, $5;
separator = ",";
}
END { print " ] " }
'`

out, err := rnode.runCommand(command)
if err != nil {
t.Fatal(err)
}

processes := []struct {
Label string `json:"label"`
Cmd string `json:"cmd"`
}{}

err = json.Unmarshal(out, &processes)
if err != nil {
t.Fatal(err)
}

fail := false
for _, process := range processes {
if process.Label == "unconfined" {
t.Logf("Unconfined process found: %s", process.Cmd)
fail = true
}
}

if fail {
// XXX : this not a proper way to check, but good for now
t.Fatal("There are unconfined processes running on the system")
}
}

//nolint:paralleltest
func TestUmask(t *testing.T) {
log.Println("TestUmask started")
defer log.Println("TestUmask finished")

edgeNode := tc.GetEdgeNode(tc.WithTest(t))
tc.WaitForState(edgeNode, 60)

// check if umask is set to 077 (600 on files, 700 on directories)
out, err := rnode.runCommand("umask")
if err != nil {
t.Fatal(err)
}

if string(out) != "077" {
t.Fatal("Umask is not set to 077")
}
}

//nolint:paralleltest
func TestNoHiddenExectuableExists(t *testing.T) {
log.Println("TestNoHiddenExectuableExists started")
defer log.Println("TestNoHiddenExectuableExists finished")

edgeNode := tc.GetEdgeNode(tc.WithTest(t))
tc.WaitForState(edgeNode, 60)

// check if there are any hidden binaries
out, err := rnode.runCommand("find / -name '.*' -executable -type f 2>/dev/null")
if err != nil {
t.Fatal(err)
}

if len(out) > 0 {
log.Println("Hidden executables found: ")
log.Println(string(out))

t.Fatal("There are hidden executables on the system")
}
}

//nolint:paralleltest
func TestCordumpDisabled(t *testing.T) {
log.Println("TestCordumpDisabled started")
defer log.Println("TestCordumpDisabled finished")

edgeNode := tc.GetEdgeNode(tc.WithTest(t))
tc.WaitForState(edgeNode, 60)

// check if coredump is disabled
out, err := rnode.runCommand("sysctl kernel.core_pattern")
if err != nil {
t.Fatal(err)
}

log.Println(string(out))
if strings.Contains(string(out), "core") {
t.Fatal("Core dumps are enabled")
}
}

//nolint:paralleltest
func TestProcessRunningAsRoot(t *testing.T) {
// XXX : this is not a proper way to check, but good for now
log.Println("TestProcessRunningAsRoot started")
defer log.Println("TestProcessRunningAsRoot finished")

edgeNode := tc.GetEdgeNode(tc.WithTest(t))
tc.WaitForState(edgeNode, 60)

// check if there are any processes running as root
out, err := rnode.runCommand("ps aux -U root -u root")
if err != nil {
t.Fatal(err)
}

if len(out) > 0 {
log.Println(string(out))
t.Fatal("There are processes running as root on the system")
}
}

//nolint:paralleltest
func TestAppArmorEnabled(t *testing.T) {
log.Println("TestAppArmorEnabled started")
defer log.Println("TestAppArmorEnabled finished")
t.Parallel()

edgeNode := tc.GetEdgeNode(tc.WithTest(t))
tc.WaitForState(edgeNode, 60)
Expand All @@ -108,3 +258,120 @@
t.Fatal("AppArmor is not enabled")
}
}

//nolint:paralleltest
func TestCheckMountOptions(t *testing.T) {
log.Println("TestCheckMountOptions started")
defer log.Println("TestCheckMountOptions finished")

edgeNode := tc.GetEdgeNode(tc.WithTest(t))
tc.WaitForState(edgeNode, 60)

fail := false
mounts, err := rnode.getMountPoints("")
if err != nil {
t.Fatal(err)
}

// checl of mounts of type proc are secure
misconfig := checkMountOptionsByType("proc", mounts, []string{"nosuid", "nodev", "noexec"})
if len(misconfig) > 0 {
for _, msg := range misconfig {
t.Logf("[FAIL] %s", msg)
}
fail = true
}

// XXX: set hidepid=2 on /proc and this to the above list
misconfig = checkMountOptionsByType("proc", mounts, []string{"hidepid=2"})
if len(misconfig) > 0 {
for _, msg := range misconfig {
t.Logf("[FAIL] %s", msg)
}
}

// check of mounts of type tmpfs are secure
misconfig = checkMountOptionsByType("tmpfs", mounts, []string{"nosuid", "nodev", "noexec"})
if len(misconfig) > 0 {
for _, msg := range misconfig {
t.Logf("[FAIL] %s", msg)
}
fail = true
}

if fail {
t.Fatal("Some mount options are not secure, see logs above")
}
}

//nolint:paralleltest
func TestCheckTmpIsSecure(t *testing.T) {
log.Println("TestCheckTempIsSecure started")
defer log.Println("TestCheckTempIsSecure finished")

edgeNode := tc.GetEdgeNode(tc.WithTest(t))
tc.WaitForState(edgeNode, 60)

mounts, err := rnode.getMountPoints("tmpfs")
if err != nil {
t.Fatal(err)
}

fail := false
for _, mount := range mounts {
p := perm{}
if err := rnode.getPathPerm(mount.Path, &p); err != nil {
t.Fatal(err)
}

if p.user != "root" || p.group != "root" {
t.Logf("[FAIL] %s is not owned by root:root", mount.Path)
fail = true
}

if !strings.Contains(p.perms, "t") {
t.Logf("[FAIL] %s is not sticky", mount.Path)
fail = true
}
}

if fail {
t.Fatal("Some tmpfs mounts are not secure, see logs above")
}
}

func checkMountSecurityOptions(mount mount, secureOptions []string) []string {
secOptNotFound := make([]string, 0)

for _, option := range secureOptions {
if !strings.Contains(mount.Options, option) {
secOptNotFound = append(secOptNotFound, fmt.Sprintf("'%s' option is not set on %s", option, mount.Path))
}
}

return secOptNotFound
}

func checkMountOptionsByType(mountType string, mounts []mount, options []string) []string {
secOptNotFound := make([]string, 0)
for _, mount := range mounts {
if mount.Type == mountType {
misses := checkMountSecurityOptions(mount, options)
secOptNotFound = append(secOptNotFound, misses...)
}
}

return secOptNotFound
}

func checkMountOptionsByPath(mountPath string, mounts []mount, options []string) []string {

Check failure on line 367 in tests/sec/sec_test.go

View workflow job for this annotation

GitHub Actions / yetus

golangcilint: `checkMountOptionsByPath` is unused (deadcode)
secOptNotFound := make([]string, 0)
for _, mount := range mounts {
if mount.Path == mountPath {
misses := checkMountSecurityOptions(mount, options)
secOptNotFound = append(secOptNotFound, misses...)
}
}

return secOptNotFound
}
Loading