Skip to content

Commit

Permalink
add security test suite to eden
Browse files Browse the repository at this point in the history
Signed-off-by: Shahriyar Jalayeri <[email protected]>
  • Loading branch information
shjala committed Nov 3, 2023
1 parent 79f876e commit d359bfb
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 1 deletion.
75 changes: 75 additions & 0 deletions tests/sec/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
DEBUG ?= "debug"

# HOSTARCH is the host architecture
# ARCH is the target architecture
# we need to keep track of them separately
HOSTARCH ?= $(shell uname -m)
HOSTOS ?= $(shell uname -s | tr A-Z a-z)

# canonicalized names for host architecture
override HOSTARCH := $(subst aarch64,arm64,$(subst x86_64,amd64,$(HOSTARCH)))

# unless otherwise set, I am building for my own architecture, i.e. not cross-compiling
# and for my OS
ARCH ?= $(HOSTARCH)
OS ?= $(HOSTOS)

# canonicalized names for target architecture
override ARCH := $(subst aarch64,arm64,$(subst x86_64,amd64,$(ARCH)))

WORKDIR ?= $(CURDIR)/../../dist
TESTDIR := tests/$(shell basename $(CURDIR))
BINDIR := $(WORKDIR)/bin
DATADIR := $(WORKDIR)/$(TESTDIR)/
BIN := eden
LOCALBIN := $(BINDIR)/$(BIN)-$(OS)-$(ARCH)
TESTNAME := eden.sec
TESTBIN := $(TESTNAME).test
TESTSCN := $(TESTNAME).tests.txt
LOCALTESTBIN := $(TESTBIN)-$(OS)-$(ARCH)
LINKDIR := ../../tests/sec

.DEFAULT_GOAL := help

clean:
rm -rf $(LOCALTESTBIN) $(BINDIR)/$(TESTBIN) $(WORKDIR)/$(TESTSCN) $(CURDIR)/$(TESTBIN) $(BINDIR)/$(TESTBIN)

$(BINDIR):
mkdir -p $@
$(DATADIR):
mkdir -p $@

test_sec:
go test sec_test.go common.go -v -count=1 -timeout 3000s

test:
$(LOCALBIN) test $(CURDIR) -v $(DEBUG)

build: setup

testbin: $(TESTBIN)
$(LOCALTESTBIN): $(BINDIR) *.go
CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go test -c -ldflags "-s -w" -o $@ *.go

$(TESTBIN): $(LOCALTESTBIN)
ln -sf $(LOCALTESTBIN) $(CURDIR)/$(TESTBIN)

setup: testbin $(BINDIR) $(DATADIR)
cp -a $(LOCALTESTBIN) $(CURDIR)/$(TESTBIN) $(BINDIR)
cp -a *.yml $(TESTSCN) $(DATADIR)

.PHONY: test build setup clean all testbin

help:
@echo "EDEN is the harness for testing EVE and ADAM"
@echo
@echo "This Makefile automates commons tasks of EDEN testing"
@echo
@echo "Commonly used maintenance and development targets:"
@echo " build build test-binary (OS and ARCH options supported, for ex. OS=linux ARCH=arm64)"
@echo " setup setup of test environment"
@echo " test run tests"
@echo " clean cleanup of test harness"
@echo
@echo "You need install requirements for EVE (look at https://github.com/lf-edge/eve#install-dependencies)."
@echo "You need access to docker socket and installed qemu packages."
7 changes: 7 additions & 0 deletions tests/sec/eden-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
eden:
# test binary
test-bin: "eden.sec.test"

# test scenario
test-scenario: "eden.sec.tests.txt"
1 change: 1 addition & 0 deletions tests/sec/eden.sec.tests.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eden.sec.test
88 changes: 88 additions & 0 deletions tests/sec/remote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package sec_test

import (
"fmt"
"io"
"os"
"strings"

"github.com/lf-edge/eden/pkg/defaults"
"github.com/lf-edge/eden/pkg/openevec"
"github.com/lf-edge/eden/pkg/utils"
)

type remoteNode struct {
openEVEC *openevec.OpenEVEC
}

func getOpenEVEC() *openevec.OpenEVEC {
edenConfigEnv := os.Getenv(defaults.DefaultConfigEnv)
configName := utils.GetConfig(edenConfigEnv)

viperCfg, err := openevec.FromViper(configName, "debug")
if err != nil {
return nil
}

return openevec.CreateOpenEVEC(viperCfg)
}

func createRemoteNode() *remoteNode {
evec := getOpenEVEC()
if evec == nil {
return nil
}

return &remoteNode{openEVEC: evec}
}

func (node *remoteNode) runCommand(command string) ([]byte, error) {
realStdout := os.Stdout
r, w, err := os.Pipe()
if err != nil {
return nil, err
}

os.Stdout = w

// unfortunately, we can't capture command return value from SSHEve
err = node.openEVEC.SSHEve(command)

os.Stdout = realStdout
w.Close()

if err != nil {
return nil, err
}

out, _ := io.ReadAll(r)
return out, nil
}

func (node *remoteNode) fileExists(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 {
return false, err
}

if strings.TrimSpace(string(out)) == "0" {
return false, nil
}

return true, nil
}

func (node *remoteNode) readFile(fileName string) ([]byte, error) {
exist, err := node.fileExists(fileName)
if err != nil {
return nil, err
}

if !exist {
return nil, fmt.Errorf("file %s does not exist", fileName)
}

command := fmt.Sprintf("cat %s", fileName)
return node.runCommand(command)
}
110 changes: 110 additions & 0 deletions tests/sec/sec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package sec_test

import (
"fmt"
"os"
"strings"
"testing"
"time"

log "github.com/sirupsen/logrus"

Check failure on line 10 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 10,col 2] (gci)

"github.com/lf-edge/eden/pkg/device"
"github.com/lf-edge/eden/pkg/projects"
"github.com/lf-edge/eden/pkg/tests"
)

var (
tc *projects.TestContext
rnode *remoteNode
)

// TestMain is used to provide setup and teardown for the rest of the
// tests. As part of setup we make sure that context has a slice of
// EVE instances that we can operate on. For any action, if the instance
// is not specified explicitly it is assumed to be the first one in the slice
func TestMain(m *testing.M) {
log.Println("Security Test Suite started")

tests.TestArgsParse()

tc = projects.NewTestContext()

projectName := fmt.Sprintf("%s_%s", "TestSecurity", time.Now())

// Registering our own project namespace with controller for easy cleanup
tc.InitProject(projectName)

// Create representation of EVE instances (based on the names
// or UUIDs that were passed in) in the context. This is the first place
// where we're using zcli-like API:
for _, node := range tc.GetNodeDescriptions() {
edgeNode := node.GetEdgeNode(tc)
if edgeNode == nil {
// Couldn't find existing edgeNode record in the controller.
// Need to create it from scratch now:
// this is modeled after: zcli edge-node create <name>
// --project=<project> --model=<model> [--title=<title>]
// ([--edge-node-certificate=<certificate>] |
// [--onboarding-certificate=<certificate>] |
// [(--onboarding-key=<key> --serial=<serial-number>)])
// [--network=<network>...]
//
// XXX: not sure if struct (giving us optional fields) would be better
edgeNode = tc.NewEdgeNode(tc.WithNodeDescription(node), tc.WithCurrentProject())
} else {
// make sure to move EdgeNode to the project we created, again
// this is modeled after zcli edge-node update <name> [--title=<title>]
// [--lisp-mode=experimental|default] [--project=<project>]
// [--clear-onboarding-certs] [--config=<key:value>...] [--network=<network>...]
edgeNode.SetProject(projectName)
}

tc.ConfigSync(edgeNode)

// finally we need to make sure that the edgeNode is in a state that we need
// it to be, before the test can run -- this could be multiple checks on its
// status, but for example:
if edgeNode.GetState() == device.NotOnboarded {
log.Fatal("Node is not onboarded now")
}

// this is a good node -- lets add it to the test context
tc.AddNode(edgeNode)
}

tc.StartTrackingState(false)

// create a remote node
rnode = createRemoteNode()
if rnode == nil {
log.Fatal("Can't initlize the remote node")
}

// we now have a situation where TestContext has enough EVE nodes known
// for the rest of the tests to run. So run them:
res := m.Run()

// Finally, we need to cleanup whatever objects may be in in the
// project we created and then we can exit
os.Exit(res)
}

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)

out, err := rnode.readFile("/sys/module/apparmor/parameters/enabled")
if err != nil {
t.Fatal(err)
}

exits := strings.TrimSpace(string(out))
if exits != "Y" {
t.Fatal("AppArmor is not enabled")
}
}
4 changes: 3 additions & 1 deletion tests/workflow/smoke.tests.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Number of tests
{{$tests := 21}}
{{$tests := 22}}
# EDEN_TEST_SETUP env. var. -- "y"(default) performs the EDEN setup steps
{{$setup := "y"}}
{{$setup_env := EdenGetEnv "EDEN_TEST_SETUP"}}
Expand Down Expand Up @@ -77,3 +77,5 @@ eden.escript.test -testdata ../eclient/testdata/ -test.run TestEdenScripts/shutd
/bin/echo EVE reset (21/{{$tests}})
eden.escript.test -test.run TestEdenScripts/eden_reset

/bin/echo EVE security tests (22/{{$tests}})
eden.escript.test -test.run TestEdenScripts/sec_eden
1 change: 1 addition & 0 deletions tests/workflow/testdata/sec_eden.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test eden.sec.test

0 comments on commit d359bfb

Please sign in to comment.