diff --git a/cmd/common.go b/cmd/common.go index ed7c4dba..abd61e25 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -16,13 +16,30 @@ package cmd import ( "fmt" + "os" "regexp" "github.com/falcosecurity/client-go/pkg/client" "github.com/falcosecurity/event-generator/events" + "github.com/falcosecurity/event-generator/pkg/declarative" "github.com/spf13/pflag" + "gopkg.in/yaml.v2" ) +// This function parses the yaml file given and returns the tests data int it +func parseYamlFile(filepath string) (declarative.Tests, error) { + data, err := os.ReadFile(filepath) + if err != nil { + return declarative.Tests{}, fmt.Errorf("an error occurred while parsing file %s: %w", filepath, err) + } + var tests declarative.Tests + err = yaml.Unmarshal(data, &tests) + if err != nil { + return declarative.Tests{}, fmt.Errorf("an error occurred while unmarshalling yaml data: %w", err) + } + return tests, nil +} + func parseEventsArg(arg string) (map[string]events.Action, error) { reg, err := regexp.Compile(arg) if err != nil { diff --git a/cmd/declarative.go b/cmd/declarative.go new file mode 100644 index 00000000..1bced7f9 --- /dev/null +++ b/cmd/declarative.go @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco 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 cmd + +import ( + "fmt" + + "github.com/falcosecurity/event-generator/pkg/declarative" + "github.com/spf13/cobra" +) + +// NewDeclarative instantiates the declarative subcommand for run command. +func NewDeclarative() *cobra.Command { + c := &cobra.Command{ + Use: "declarative [yaml-file-path]", + Short: "Execute Falco tests using a declarative approach", + Long: `This command takes the path to a YAML file as an argument. + The YAML file defines tests that are parsed and executed which + triggers specific Falco rules.`, + Args: cobra.MaximumNArgs(1), + DisableAutoGenTag: true, + } + + c.RunE = func(c *cobra.Command, args []string) error { + tests, err := parseYamlFile(args[0]) + if err != nil { + return err + } + + var failedTests []error // stores the errors of failed tests + + // Execute each test mentioned in yaml file + for _, eachTest := range tests.Tests { + err := runTestSteps(eachTest) + if err != nil { + // Collect the errors if any test fails + failedTests = append(failedTests, fmt.Errorf("test %v failed with err: %v", eachTest.Rule, err)) + } + } + + // Print all errors + if len(failedTests) > 0 { + for _, ft := range failedTests { + fmt.Println(ft) + } + return fmt.Errorf("some tests failed, see previous logs") + } + + return nil + } + + return c +} + +// runTestSteps executes the steps, before and after scripts defined in the test. +func runTestSteps(test declarative.Test) error { + var runner declarative.Runner + + // Assign a runner based on test.Runner value + switch test.Runner { + case "HostRunner": + runner = &declarative.Hostrunner{} + default: + return fmt.Errorf("unsupported runner: %v", test.Runner) + } + + // Execute the "Before" script. + if err := runner.Setup(test.Before); err != nil { + return err + } + + // Execute each step in the test. + for _, step := range test.Steps { + err := runner.ExecuteStep(step) + if err != nil { + return fmt.Errorf("error executing steps for the rule %v : %v", test.Rule, err) + } + } + + // Execute the "After" script. + if err := runner.Cleanup(test.After); err != nil { + return err + } + return nil +} diff --git a/cmd/run.go b/cmd/run.go index c98c2c72..62e0cfbf 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -44,6 +44,7 @@ func NewRun() *cobra.Command { c.RunE = func(c *cobra.Command, args []string) error { return runEWithOpts(c, args) } + c.AddCommand(NewDeclarative()) return c } diff --git a/cmd/test.go b/cmd/test.go index d4aabcf2..1f24faf9 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -17,6 +17,7 @@ package cmd import ( // register event collections + "time" "github.com/falcosecurity/event-generator/pkg/runner" diff --git a/events/exampleyamlfile.yml b/events/exampleyamlfile.yml new file mode 100644 index 00000000..6ec150bf --- /dev/null +++ b/events/exampleyamlfile.yml @@ -0,0 +1,21 @@ +tests: + - rule: WriteBelowRoot + runner: HostRunner + before: "" + steps: + - syscall: "write" + args: + filepath: "/root/created-by-event-generator" + content: "" + after: "rm -f /root/created-by-event-generator" + + - rule: WriteBelowEtc + runner: HostRunner + before: "" + steps: + - syscall: "write" + args: + filepath: "/etc/created-by-event-generator" + content: "" + after: "rm -f /etc/created-by-event-generator" + diff --git a/pkg/declarative/helpers.go b/pkg/declarative/helpers.go new file mode 100644 index 00000000..1c824be7 --- /dev/null +++ b/pkg/declarative/helpers.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco 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 declarative + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +func WriteSyscall(filepath string, content string) error { + // Open the file using unix.Open syscall + fd, err := unix.Open(filepath, unix.O_WRONLY|unix.O_CREAT, 0644) + if err != nil { + return fmt.Errorf("error opening file: %v", err) + } + defer unix.Close(fd) + + // Write to the file using unix.Write + _, err = unix.Write(fd, []byte(content)) + if err != nil { + return fmt.Errorf("error writing to file: %v", err) + } + return nil +} diff --git a/pkg/declarative/runner.go b/pkg/declarative/runner.go new file mode 100644 index 00000000..c4f0ab4f --- /dev/null +++ b/pkg/declarative/runner.go @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco 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 declarative + +import ( + "fmt" + "os/exec" +) + +// Common runner interface for runners like hostrunner, container-runner etc.. +type Runner interface { + Setup(beforeScript string) error + ExecuteStep(step SyscallStep) error + Cleanup(afterScript string) error +} + +type Hostrunner struct{} + +func (r *Hostrunner) Setup(beforeScript string) error { + if beforeScript != "" { + if err := exec.Command("sh", "-c", beforeScript).Run(); err != nil { + return fmt.Errorf("error executing before script: %v", err) + } + } + return nil +} + +func (r *Hostrunner) ExecuteStep(step SyscallStep) error { + switch step.Syscall { + case "write": + if err := WriteSyscall(step.Args["filepath"], step.Args["content"]); err != nil { + return fmt.Errorf("write syscall failed with error: %v", err) + } + default: + return fmt.Errorf("unsupported syscall: %s", step.Syscall) + } + return nil +} + +func (r *Hostrunner) Cleanup(afterScript string) error { + if afterScript != "" { + if err := exec.Command("sh", "-c", afterScript).Run(); err != nil { + return fmt.Errorf("error executing after script: %v", err) + } + } + return nil +} diff --git a/pkg/declarative/yamltypes.go b/pkg/declarative/yamltypes.go new file mode 100644 index 00000000..bb966e8e --- /dev/null +++ b/pkg/declarative/yamltypes.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco 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 declarative + +// Yaml file structure +type SyscallStep struct { + Syscall string `yaml:"syscall"` + Args map[string]string `yaml:"args"` +} + +type Test struct { + Rule string `yaml:"rule"` + Runner string `yaml:"runner"` + Before string `yaml:"before"` + Steps []SyscallStep `yaml:"steps"` + After string `yaml:"after"` +} + +type Tests struct { + Tests []Test `yaml:"tests"` +}