diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..1e68185 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,129 @@ +name: tests + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + unit-test: + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # ratchet:actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install Dependencies + run: | + sudo apt update + sudo apt install -y clang + sudo apt install -y libbpf-dev + sudo apt install -y libseccomp-dev + + - name: Build coverage-instrumented binary + run: | + make build-static-libbpfgo + make build-bpf + + - name: Run Unit-Test + run: | + mkdir /tmp/unit/ + # test packages excluding the ones with libbpfgo + go test \ + -cover \ + -v \ + $(go list ./... | grep -v "github.com/alegrey91/harpoon$" | grep -v ebpf | grep -v cmd) \ + -skip TestHarpoon \ + -args -test.gocoverdir=/tmp/unit/ + + - name: Upload cover profiles + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # ratchet:actions/upload-artifact@v3 + with: + name: unit-test + path: /tmp/unit/ + + integration-test: + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # ratchet:actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install Dependencies + run: | + sudo apt update + sudo apt install -y clang + sudo apt install -y libbpf-dev + sudo apt install -y libseccomp-dev + + - name: Build coverage-instrumented binary + run: | + make build-static-libbpfgo + make build-bpf + make build-go-cover && sudo make -B install + + - name: Run integration test + run: | + mkdir -p /tmp/integration + # we have to run integration tests one-by-one + # otherwhise they will run in parallel. + # since harpoon apply network forwards, these could + # interact with each other and make the test fail. + go test \ + -exec sudo \ + -cover \ + -v main_test.go \ + -args -test.gocoverdir=/tmp/integration/ + + - name: Upload cover profiles + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # ratchet:actions/upload-artifact@v3 + with: + name: integration-test + path: /tmp/integration/ + + code-coverage: + + runs-on: ubuntu-latest + needs: [unit-test,integration-test] + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3 + + - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3 + with: + name: unit-test + path: /tmp/unit-test + + - uses: actions/download-artifact@v3 + with: + name: integration-test + path: /tmp/integration-test + + - name: list files + run: | + ls -lah /tmp/unit-test + ls -lah /tmp/integration-test + + - name: Set up Go + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # ratchet:actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Calculate total coverage + run: | + go tool \ + covdata \ + textfmt \ + -i=/tmp/unit-test,/tmp/integration-test \ + -o code-coverage + go tool \ + cover \ + -func code-coverage diff --git a/Makefile b/Makefile index ee3a4ae..4c68316 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,19 @@ build-go: create-bin-dir -o ${BINARY_DIR}/${BINARY_NAME} \ . +build-go-cover: create-bin-dir + go mod download + export CURRENT_DIR=$(shell pwd); \ + CC=gcc \ + CGO_CFLAGS="-I $$CURRENT_DIR/libbpfgo/output" \ + CGO_LDFLAGS="-lelf -lz $$CURRENT_DIR/libbpfgo/output/libbpf/libbpf.a" \ + go build \ + -tags core,ebpf \ + -v \ + -cover \ + -o ${BINARY_DIR}/${BINARY_NAME} \ + . + build: create-bin-dir vmlinux.h build-static-libbpfgo build-bpf go mod download export CURRENT_DIR=$(shell pwd); \ @@ -60,6 +73,9 @@ create-bin-dir: create-output-dir: mkdir -p ${OUTPUT_DIR} +install: + cp ${BINARY_DIR}/${BINARY_NAME} /usr/local/bin/ + clean: rm -rf ${OUTPUT_DIR} rm -rf ${BINARY_DIR} diff --git a/cmd/analyze.go b/cmd/analyze.go index 75d045f..fb1b89d 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -28,9 +28,11 @@ import ( "github.com/spf13/cobra" ) -var excludedPaths []string -var exclude string -var saveAnalysis bool +var ( + excludedPaths []string + exclude string + saveAnalysis bool +) // analyzeCmd represents the create args var analyzeCmd = &cobra.Command{ @@ -38,7 +40,7 @@ var analyzeCmd = &cobra.Command{ Short: "Analyze infers the symbols of functions that are tested by unit-tests", Long: ` `, - Example: " harpoon analyze --exclude vendor/ /path/to/repo/", + Example: " harpoon analyze --exclude vendor/ -s", RunE: func(cmd *cobra.Command, args []string) error { if exclude != "" { excludedPaths = strings.Split(exclude, ",") @@ -85,12 +87,12 @@ var analyzeCmd = &cobra.Command{ pkgPath := getPackagePath(path) testFile := filepath.Base(path) testFile = strings.ReplaceAll(testFile, "_test.go", ".test") - _, err = executor.Build(pkgPath, ".harpoon/"+testFile) + _, err = executor.Build(pkgPath, filepath.Join(".harpoon", testFile)) if err != nil { return fmt.Errorf("failed to build test file: %v", err) } - symbolsOrig := metadata.NewSymbolsOrigin(".harpoon/" + testFile) + symbolsOrig := metadata.NewSymbolsOrigin(filepath.Join(".harpoon", testFile)) fmt.Println("test: .harpoon/" + testFile) for _, symbol := range symbolNames { @@ -125,13 +127,17 @@ var analyzeCmd = &cobra.Command{ } // store to file - file, err = os.Create(".harpoon.yml") - if err != nil { - return fmt.Errorf("failed to create symbols list file: %w", err) + if saveAnalysis { + file, err = os.Create("harpoon-report.yml") + if err != nil { + return fmt.Errorf("failed to create symbols list file: %w", err) + } + mw := io.Writer(file) + fmt.Fprintln(mw, symbolsList.String()) + fmt.Println("file harpoon-report.yml is ready") + } else { + fmt.Println(symbolsList.String()) } - mw := io.Writer(file) - fmt.Fprintln(mw, symbolsList.String()) - fmt.Println("file .harpoon.yml is ready") return nil }, } @@ -140,7 +146,7 @@ func init() { rootCmd.AddCommand(analyzeCmd) analyzeCmd.Flags().StringVarP(&exclude, "exclude", "e", "", "Skip directories specified in the comma separated list") - analyzeCmd.Flags().BoolVarP(&saveAnalysis, "save", "s", false, "Save the result of analysis into a file") + analyzeCmd.Flags().BoolVarP(&saveAnalysis, "save", "S", false, "Save the result of analysis into a file") } func shouldSkipPath(path string) bool { @@ -171,6 +177,7 @@ func getPackagePath(inputPath string) string { // Adjust this according to your specific requirements dirPath = strings.TrimPrefix(dirPath, "../") dirPath = strings.TrimPrefix(dirPath, "./") + //dirPath = strings.TrimPrefix(inputPath, rootPath) // Add "./" at the start again if necessary dirPath = "./" + dirPath diff --git a/cmd/build.go b/cmd/build.go index 734afae..bb0065f 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -96,9 +96,9 @@ var buildCmd = &cobra.Command{ func init() { rootCmd.AddCommand(buildCmd) - buildCmd.Flags().StringVarP(&inputDirectory, "directory", "D", "", "Directory containing harpoon's files") + buildCmd.Flags().StringVarP(&inputDirectory, "directory", "D", "", "Directory containing harpoon's metadata files") buildCmd.MarkFlagRequired("directory") - buildCmd.Flags().BoolVarP(&saveProfile, "save-profile", "s", false, "Save profile to a file") - buildCmd.Flags().StringVarP(&profileName, "name", "n", profileName, "Save profile to a file") + buildCmd.Flags().BoolVarP(&saveProfile, "save", "S", false, "Save profile to a file") + buildCmd.Flags().StringVarP(&profileName, "name", "n", profileName, "Specify a name for the seccomp profile") } diff --git a/cmd/capture.go b/cmd/capture.go index ad888bc..29572c5 100644 --- a/cmd/capture.go +++ b/cmd/capture.go @@ -19,7 +19,7 @@ import ( "fmt" "strings" - "github.com/alegrey91/harpoon/internal/captor" + "github.com/alegrey91/harpoon/internal/ebpf/probesfacade/captor" "github.com/alegrey91/harpoon/internal/writer" "github.com/spf13/cobra" ) diff --git a/cmd/hunt.go b/cmd/hunt.go index d8a0c44..c3cf245 100644 --- a/cmd/hunt.go +++ b/cmd/hunt.go @@ -20,7 +20,7 @@ import ( "io" "os" - "github.com/alegrey91/harpoon/internal/captor" + "github.com/alegrey91/harpoon/internal/ebpf/probesfacade/captor" meta "github.com/alegrey91/harpoon/internal/metadata" "github.com/alegrey91/harpoon/internal/writer" "github.com/spf13/cobra" @@ -37,7 +37,7 @@ var huntCmd = &cobra.Command{ Short: "Hunt is like capture but gets a list of functions to be traced", Long: ` `, - Example: " harpoon hunt --file .harpoon.yaml", + Example: " harpoon hunt --file harpoon-report.yml", RunE: func(cmd *cobra.Command, args []string) error { file, err := os.Open(harpoonFile) if err != nil { @@ -91,7 +91,7 @@ var huntCmd = &cobra.Command{ func init() { rootCmd.AddCommand(huntCmd) - huntCmd.Flags().StringVarP(&harpoonFile, "file", "F", ".harpoon.yaml", "File with the result of analysis") + huntCmd.Flags().StringVarP(&harpoonFile, "file", "F", "harpoon-report.yml", "File with the result of analysis") huntCmd.MarkFlagRequired("file") huntCmd.Flags().BoolVarP(&commandOutput, "include-cmd-output", "c", false, "Include the executed command output") diff --git a/docs/commands.md b/docs/commands.md index 67f7e2c..918f377 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -4,11 +4,11 @@ Harpoon has several commands that you can use. The common way of using `harpoon` is to execute the available commands as follow: -* [`harpoon analyze`](#analyze-) to analyze the project to infer symbols to be traced. This will create a `.harpoon.yml` file. +* [`harpoon analyze`](#analyze) to infer symbols to be traced from the project root directory. This will create the `harpoon-report.yml` file. -* [`harpoon hunt`](#hunt-) by passing the `.harpoon.yml` file to trace the functions and get their system calls. This will generate the `./harpoon/` directory with the metadata that contain the system calls traced. +* [`harpoon hunt`](#hunt) by passing the `harpoon-report.yml` file to trace the functions and get their system calls. This will generate the `./harpoon/` directory with the metadata that contain the system calls traced. -* [`harpoon build`](#build-️) to read the metadata files and provide the **seccomp** profile. +* [`harpoon build`](#build) to read the metadata files and provide the **seccomp** profile. ## Analyze @@ -16,12 +16,12 @@ The `analyze` command is used to analyze the project's folder and get the list o Additionally it automatically build the test binary and place them into the `harpoon/` directory. -The result of this command is the `.harpoon.yml` file with the list of test binaries followed by their function symbols that are currently tested. +The result of this command is the `harpoon-report.yml` file with the list of test binaries followed by their function symbols that are currently tested. Run it on your project folder: ```sh -sudo harpoon analyze --exclude .git/ . +sudo harpoon analyze --exclude .git/ ``` ## Build @@ -51,7 +51,7 @@ The command needs a file as input paramenter that is the result of the `analyze` This will loop over the entries of the file, capturing the system calls of each entry. ```sh -harpoon hunt --file .harpoon.yml -S +harpoon hunt --file harpoon-report.yml -S ``` -This will create the directory `harpoon/` with the list of system calls traced from the execution of the different test binaries present in the `.harpoon.yml` file. \ No newline at end of file +This will create the directory `harpoon/` with the list of system calls traced from the execution of the different test binaries present in the `harpoon-report.yml` file. \ No newline at end of file diff --git a/go.mod b/go.mod index f22a794..52279f3 100644 --- a/go.mod +++ b/go.mod @@ -14,9 +14,11 @@ require ( require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/tools v0.1.12 // indirect ) require ( + github.com/rogpeppe/go-internal v1.12.0 github.com/spf13/cobra v1.8.0 golang.org/x/sys v0.16.0 // indirect gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 85e15a4..df5e340 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY= github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= @@ -22,6 +24,8 @@ golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/captor/capture.go b/internal/ebpf/probesfacade/captor/capture.go similarity index 98% rename from internal/captor/capture.go rename to internal/ebpf/probesfacade/captor/capture.go index 0df28f6..e6da14d 100644 --- a/internal/captor/capture.go +++ b/internal/ebpf/probesfacade/captor/capture.go @@ -9,9 +9,9 @@ import ( "sync" "unsafe" + probes "github.com/alegrey91/harpoon/internal/ebpf/probesfacade" embedded "github.com/alegrey91/harpoon/internal/embeddable" "github.com/alegrey91/harpoon/internal/executor" - probes "github.com/alegrey91/harpoon/internal/probesfacade" bpf "github.com/aquasecurity/libbpfgo" ) diff --git a/internal/probesfacade/probesfacade.go b/internal/ebpf/probesfacade/probesfacade.go similarity index 87% rename from internal/probesfacade/probesfacade.go rename to internal/ebpf/probesfacade/probesfacade.go index 33b0cf1..af82125 100644 --- a/internal/probesfacade/probesfacade.go +++ b/internal/ebpf/probesfacade/probesfacade.go @@ -12,11 +12,11 @@ import ( func AttachUProbe(binPath, functionSymbol string, probe *bpf.BPFProg) (uint32, error) { offset, err := helpers.SymbolToOffset(binPath, functionSymbol) if err != nil { - return 0, fmt.Errorf("error finding function (%s) offset: %v\n", functionSymbol, err) + return 0, fmt.Errorf("error finding function (%s) offset: %v", functionSymbol, err) } _, err = probe.AttachUprobe(-1, binPath, offset) if err != nil { - return 0, fmt.Errorf("error attaching uprobe at function (%s) offset: %d, error: %v\n", functionSymbol, offset, err) + return 0, fmt.Errorf("error attaching uprobe at function (%s) offset: %d, error: %v", functionSymbol, offset, err) } return offset, nil } @@ -28,12 +28,12 @@ func AttachUProbe(binPath, functionSymbol string, probe *bpf.BPFProg) (uint32, e func AttachURETProbe(binPath, functionSymbol string, probe *bpf.BPFProg, offset uint32) error { functionRetOffsets, err := elfreader.GetFunctionRetOffsets(binPath, functionSymbol) if err != nil { - return fmt.Errorf("error finding function (%s) RET offsets: %v\n", err) + return fmt.Errorf("error finding function (%s) RET offsets: %v", functionSymbol, err) } for _, offsetRet := range functionRetOffsets { _, err := probe.AttachUprobe(-1, binPath, offset+uint32(offsetRet)) if err != nil { - return fmt.Errorf("error attaching uprobe at function (%s) RET: %d, error: %v\n", functionSymbol, offset+uint32(offsetRet), err) + return fmt.Errorf("error attaching uprobe at function (%s) RET: %d, error: %v", functionSymbol, offset+uint32(offsetRet), err) } } return nil diff --git a/internal/executor/exec.go b/internal/executor/exec.go index 1ff6fb1..94907d3 100644 --- a/internal/executor/exec.go +++ b/internal/executor/exec.go @@ -28,14 +28,14 @@ func Build(packagePath, outputFile string) (string, error) { cmd := exec.Command( "go", "test", - "-gcflags", "-N -l", // disable optimization + "-gcflags=-N -l", // disable optimization "-c", packagePath, // build test binary "-o", outputFile, // save it in a dedicated directory ) - stdout, err := cmd.Output() + stdout, err := cmd.Output() if err != nil { - return "", fmt.Errorf("failed to execute build command: %v", err) + return "", fmt.Errorf("failed to execute build command '%s': %v", cmd.String(), err) } return string(stdout), nil diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..f38da99 --- /dev/null +++ b/main_test.go @@ -0,0 +1,66 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/rogpeppe/go-internal/testscript" +) + +func TestHarpoon(t *testing.T) { + testscript.Run(t, testscript.Params{ + Dir: "tests", + //Cmds: customCommands(), + RequireExplicitExec: true, + Setup: func(env *testscript.Env) error { + existingDir := filepath.Join("tests", "testcases") + destDir := filepath.Join(env.WorkDir, "testcases") + // Copy the directory to the test environment + err := copyDir(existingDir, destDir) + if err != nil { + return err + } + + env.Setenv("GOCOVERDIR", "/tmp/integration") + return nil + }, + }) +} + +// copyDir copies the contents of src to dst +func copyDir(src string, dst string) error { + entries, err := os.ReadDir(src) + if err != nil { + return err + } + + if err := os.MkdirAll(dst, 0755); err != nil { + return err + } + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + if entry.IsDir() { + err = copyDir(srcPath, dstPath) + if err != nil { + return err + } + continue + } + + data, err := os.ReadFile(srcPath) + if err != nil { + return err + } + + err = os.WriteFile(dstPath, data, 0644) + if err != nil { + return err + } + } + + return nil +} diff --git a/tests/integration.txtar b/tests/integration.txtar new file mode 100644 index 0000000..1e9a7a0 --- /dev/null +++ b/tests/integration.txtar @@ -0,0 +1,57 @@ +# this testscript test the 'analyze' command + +# skip if go is not installed +[!exec:go] skip + +# move on the testcases directory +cd testcases/example-app/ + +# test usage +exec harpoon analyze -h +stdout 'Usage:' + +exec harpoon analyze --save +exists harpoon-report.yml +exists .harpoon/ +cmp harpoon-report.yml harpoon-expected-report.yml + +exec harpoon hunt -S -D harpoon/ -F harpoon-report.yml +exists harpoon/github_com_alegrey91_seccomp-test-coverage_pkg_randomic_DoSomethingSpecial +exists harpoon/github_com_alegrey91_seccomp-test-coverage_pkg_randomic_FlipCoin +exists harpoon/github_com_alegrey91_seccomp-test-coverage_pkg_randomic_RockPaperScissors +exists harpoon/github_com_alegrey91_seccomp-test-coverage_pkg_randomic_ThrowDice + +exec harpoon build -D harpoon/ -S --name profile.json +exists profile.json +cmp profile.json expected-profile.json + +-- testcases/example-app/harpoon-expected-report.yml -- +--- +symbolsOrigins: + - testBinaryPath: .harpoon/randomic.test + symbols: + - github.com/alegrey91/seccomp-test-coverage/pkg/randomic.RockPaperScissors + - github.com/alegrey91/seccomp-test-coverage/pkg/randomic.ThrowDice + - github.com/alegrey91/seccomp-test-coverage/pkg/randomic.FlipCoin + - github.com/alegrey91/seccomp-test-coverage/pkg/randomic.DoSomethingSpecial + +-- testcases/example-app/expected-profile.json -- +{ + "defaultAction": "SCMP_ACT_ERRNO", + "architectures": [ + "SCMP_ARCH_X86_64", + "SCMP_ARCH_X86", + "SCMP_ARCH_X32" + ], + "syscalls": [ + { + "names": [ + "futex", + "gettid", + "nanosleep", + "write" + ], + "action": "SCMP_ACT_ALLOW" + } + ] +} diff --git a/tests/testcases/example-app/LICENSE b/tests/testcases/example-app/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/tests/testcases/example-app/Makefile b/tests/testcases/example-app/Makefile new file mode 100644 index 0000000..c833813 --- /dev/null +++ b/tests/testcases/example-app/Makefile @@ -0,0 +1,41 @@ +BINARY_NAME=example-app +BINARY_DIR=bin +UNIT_TEST_DIR=/tmp/unit + +build: + go mod download + go build \ + -v \ + -o ${BINARY_DIR}/${BINARY_NAME} \ + main.go + +test-build: + go build \ + -v \ + -cover \ + -o ${BINARY_DIR}/${BINARY_NAME} \ + main.go + +run: + go run main.go + +test: unit-test + +# here we are compiling the _test package(s) we want to test, +# so we are able to run manually unsing the `strace` utility. +unit-test: + rm -rf ${UNIT_TEST_DIR} + mkdir ${UNIT_TEST_DIR} + # loop over each package + go test \ + -cover \ + -c github.com/alegrey91/${BINARY_NAME}/pkg/randomic \ + -o ${BINARY_DIR}/randomic.test; + +clean: + rm -rf ${BINARY_DIR}/* + rm -rf ${UNIT_TEST_DIR} + rm -rf ${INTEGRATION_TEST_DIR} + rm -rf ${STRACE_LOG_DIR} + +all: build diff --git a/tests/testcases/example-app/cmd/coin.go b/tests/testcases/example-app/cmd/coin.go new file mode 100644 index 0000000..fe00f0a --- /dev/null +++ b/tests/testcases/example-app/cmd/coin.go @@ -0,0 +1,41 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/alegrey91/seccomp-test-coverage/pkg/randomic" +) + +// coinCmd represents the coin command +var coinCmd = &cobra.Command{ + Use: "coin", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(randomic.FlipCoin()) + }, +} + +func init() { + rootCmd.AddCommand(coinCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // coinCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // coinCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/tests/testcases/example-app/cmd/dice.go b/tests/testcases/example-app/cmd/dice.go new file mode 100644 index 0000000..090bb86 --- /dev/null +++ b/tests/testcases/example-app/cmd/dice.go @@ -0,0 +1,43 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/alegrey91/seccomp-test-coverage/pkg/randomic" +) + +var faces int + +// diceCmd represents the dice command +var diceCmd = &cobra.Command{ + Use: "dice", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(randomic.ThrowDice(faces)) + }, +} + +func init() { + rootCmd.AddCommand(diceCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // diceCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + diceCmd.Flags().IntVarP(&faces, "nfaces", "n", 6, "Number of the dice faces") +} diff --git a/tests/testcases/example-app/cmd/root.go b/tests/testcases/example-app/cmd/root.go new file mode 100644 index 0000000..1ca35ae --- /dev/null +++ b/tests/testcases/example-app/cmd/root.go @@ -0,0 +1,34 @@ +/* +Copyright © 2023 Alessio Greggi alessiog@armosec.io +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "seccomp-test-coverage", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/tests/testcases/example-app/cmd/rps.go b/tests/testcases/example-app/cmd/rps.go new file mode 100644 index 0000000..0372cc2 --- /dev/null +++ b/tests/testcases/example-app/cmd/rps.go @@ -0,0 +1,41 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/alegrey91/seccomp-test-coverage/pkg/randomic" +) + +// rpsCmd represents the rps command +var rpsCmd = &cobra.Command{ + Use: "rps", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(randomic.RockPaperScissors()) + }, +} + +func init() { + rootCmd.AddCommand(rpsCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // rpsCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // rpsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/tests/testcases/example-app/go.mod b/tests/testcases/example-app/go.mod new file mode 100644 index 0000000..dafe80f --- /dev/null +++ b/tests/testcases/example-app/go.mod @@ -0,0 +1,10 @@ +module github.com/alegrey91/seccomp-test-coverage + +go 1.20 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/seccomp/libseccomp-golang v0.10.0 // indirect + github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) diff --git a/tests/testcases/example-app/go.sum b/tests/testcases/example-app/go.sum new file mode 100644 index 0000000..b055e44 --- /dev/null +++ b/tests/testcases/example-app/go.sum @@ -0,0 +1,12 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY= +github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tests/testcases/example-app/main.go b/tests/testcases/example-app/main.go new file mode 100644 index 0000000..ba10f58 --- /dev/null +++ b/tests/testcases/example-app/main.go @@ -0,0 +1,38 @@ +/* +Copyright © 2023 Alessio Greggi alessiog@armosec.io + +*/ +package main + +import ( + "fmt" + "syscall" + + libseccomp "github.com/seccomp/libseccomp-golang" + + "github.com/alegrey91/seccomp-test-coverage/cmd" +) + +func whiteList(syscalls []string) { + + filter, err := libseccomp.NewFilter(libseccomp.ActErrno.SetReturnCode(int16(syscall.EPERM))) + if err != nil { + fmt.Printf("Error creating filter: %s\n", err) + } + for _, element := range syscalls { + fmt.Printf("[+] Whitelisting: %s\n",element) + syscallID, err := libseccomp.GetSyscallFromName(element) + if err != nil { + panic(err) + } + filter.AddRule(syscallID, libseccomp.ActAllow) + } + filter.Load() +} + +func main() { + + //var syscalls = []string{"access", "brk", "clone", "close", "execve", "fcntl", "futex", "getpid", "getrandom", "getrlimit", "gettid", "lseek", "madvise", "mmap", "mprotect", "munmap", "newfstatat", "openat", "read", "renameat", "rseq", "setrlimit", "sigaltstack", "write", "exit_group"} + //whiteList(syscalls) + cmd.Execute() +} diff --git a/tests/testcases/example-app/pkg/randomic/randomic.go b/tests/testcases/example-app/pkg/randomic/randomic.go new file mode 100644 index 0000000..c8cb9ea --- /dev/null +++ b/tests/testcases/example-app/pkg/randomic/randomic.go @@ -0,0 +1,47 @@ +package randomic + +import ( + "fmt" + "math/rand" + "syscall" + "time" +) + +// RockPaperScissors returns a random choice among "rock", "paper", and "scissors". +func RockPaperScissors() string { + choices := []string{"rock", "paper", "scissors"} + rand.Seed(time.Now().UnixNano()) + fmt.Println("[rock paper scissors]") + return choices[rand.Intn(len(choices))] +} + +// ThrowDice returns a random number between 1 and 6. +func ThrowDice(facesNumber int) (int, error) { + allowedNumberOfFaces := map[int]bool{ + 4: true, + 6: true, + 8: true, + 10: true, + 12: true, + 20: true, + } + if !allowedNumberOfFaces[facesNumber] { + return 0, fmt.Errorf("invalid number of faces") + } + rand.Seed(time.Now().UnixNano()) + fmt.Println("[throw dice]") + return rand.Intn(facesNumber) + 1, nil +} + +// FlipCoin returns either "head" or "tail" randomly. +func FlipCoin() string { + choices := []string{"head", "tail"} + rand.Seed(time.Now().UnixNano()) + fmt.Println("[flip coin]") + return choices[rand.Intn(len(choices))] +} + +func DoSomethingSpecial(value int) bool { + syscall.Gettid() + return true +} diff --git a/tests/testcases/example-app/pkg/randomic/randomic_test.go b/tests/testcases/example-app/pkg/randomic/randomic_test.go new file mode 100644 index 0000000..fb2ba2f --- /dev/null +++ b/tests/testcases/example-app/pkg/randomic/randomic_test.go @@ -0,0 +1,134 @@ +package randomic + +import ( + "testing" +) + +func TestRockPaperScissors(t *testing.T) { + tests := []struct { + name string + expected []string + }{ + {"Valid Choice", []string{"rock", "paper", "scissors"}}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := RockPaperScissors() + isValid := false + for _, validChoice := range test.expected { + if result == validChoice { + isValid = true + break + } + } + if !isValid { + t.Errorf("Got %s, but expected one of %v", result, test.expected) + } + }) + } +} + +func TestThrowDice(t *testing.T) { + tests := []struct { + name string + facesNumber int + expectedMin int + expectedMax int + expectedError bool + }{ + { + "test_1", + 4, + 1, + 4, + false, + }, + { + "test_2", + 6, + 1, + 6, + false, + }, + { + "test_3", + 5, + 0, + 0, + true, + }, + { + "test_4", + 10, + 1, + 10, + false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := ThrowDice(test.facesNumber) + if err != nil && !test.expectedError { + t.Errorf("Got unexpected error: %v", err) + } + if result < test.expectedMin || result > test.expectedMax { + t.Errorf("Got %d, but expected a value between %d and %d", result, test.expectedMin, test.expectedMax) + } + }) + } +} + +func TestFlipCoin(t *testing.T) { + tests := []struct { + name string + expected []string + }{ + {"Valid Choice", []string{"head", "tail"}}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := FlipCoin() + isValid := false + for _, validChoice := range test.expected { + if result == validChoice { + isValid = true + break + } + } + if !isValid { + t.Errorf("Got %s, but expected one of %v", result, test.expected) + } + }) + } +} + +func TestDoSomethingSpecial(t *testing.T) { + tests := []struct { + name string + value int + expected bool + }{ + { + name: "test1", + value: 1000, + expected: true, + }, + { + name: "test2", + value: 0, + expected: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + DoSomethingSpecial(test.value) + if !test.expected { + t.Errorf("Got an error") + } + }) + } +}