Skip to content

Commit

Permalink
Implement ExecuteStep method for the container runner
Browse files Browse the repository at this point in the history
Updated the file structure in declarative package
Added a helper function createTarReader which helps in copying files into the container
Copied event-generator executable into the container
Refactored setup and cleanup methods of the container runner

Signed-off-by: GLVS Kiriti <[email protected]>
  • Loading branch information
GLVSKiriti authored and LucaGuerra committed Aug 5, 2024
1 parent 339f11a commit 94493f2
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 162 deletions.
18 changes: 10 additions & 8 deletions cmd/declarative.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
package cmd

import (
"context"
"fmt"

"github.com/falcosecurity/event-generator/pkg/declarative"
Expand Down Expand Up @@ -67,33 +68,34 @@ func NewDeclarative() *cobra.Command {
// runTestSteps executes the steps, before and after scripts defined in the test.
func runTestSteps(test declarative.Test) error {
var runner declarative.Runner
var ctx context.Context

// Assign a runner based on test.Runner value
switch test.Runner {
case "HostRunner":
runner = &declarative.Hostrunner{}
ctx = context.TODO()
case "ContainerRunner":
// spawn an alpine container
runner = &declarative.Containerrunner{Image: "alpine"}
runner = &declarative.Containerrunner{Image: "golang"}
ctx = context.Background()
default:
return fmt.Errorf("unsupported runner: %v", test.Runner)
}

// Execute the "Before" script.
if err := runner.Setup(test.Before); err != nil {
if err := runner.Setup(ctx, 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)
}
err := runner.ExecuteStep(ctx, test)
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 {
if err := runner.Cleanup(ctx, test.After); err != nil {
return err
}
return nil
Expand Down
3 changes: 1 addition & 2 deletions events/exampleyamlfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ tests:

- rule: LaunchIngressRemoteFileCopyToolsInsideContainer
runner: ContainerRunner
before: "wget"
before: "wget example.com"
steps:
after: ""

191 changes: 191 additions & 0 deletions pkg/declarative/container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// 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 (
"context"
"fmt"
"io"
"log"
"os"
"path/filepath"

"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/client"
"gopkg.in/yaml.v2"
)

type Containerrunner struct {
ContainerId string
Image string
}

func (r *Containerrunner) Setup(ctx context.Context, beforeScript string) error {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return fmt.Errorf("error creating Docker Client: %v", err)
}

// Pull the image
pullRes, err := cli.ImagePull(ctx, r.Image, image.PullOptions{})
if err != nil {
return fmt.Errorf("error pulling Docker Image: %v", err)
}

// Read the response of body inorder to download the image
defer pullRes.Close()
_, err = io.Copy(io.Discard, pullRes)
if err != nil {
log.Fatal(err)
}

// Create the container with name "alpine-container-by-event-generator"
resp, err := cli.ContainerCreate(ctx,
&container.Config{
Image: r.Image,
Cmd: []string{"sleep", "1h"},
}, nil, nil, nil, "alpine-container-by-event-generator")

if err != nil {
return fmt.Errorf("error creating Docker Container: %v", err)
}

// Store created container id
r.ContainerId = resp.ID

// Path of the event-generator executable
path_eg, err := os.Executable()
if err != nil {
return fmt.Errorf("error extracting path of event-generator executable")
}

tarReader, err := CreateTarReader(path_eg)
if err != nil {
return fmt.Errorf("error creating tar reader: %v", err)
}

err = cli.CopyToContainer(ctx, r.ContainerId, "/", tarReader, container.CopyToContainerOptions{})
if err != nil {
return fmt.Errorf("error copying file to container: %v", err)
}

// Start the container
err = cli.ContainerStart(ctx, r.ContainerId, container.StartOptions{})
if err != nil {
return fmt.Errorf("error starting Docker container: %v", err)
}
return nil
}

func (r *Containerrunner) ExecuteStep(ctx context.Context, test Test) error {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return fmt.Errorf("error creating Docker Client: %v", err)
}

// Create a yaml structure for given step
test.Runner = "HostRunner" // Change container runner to host runner
testFile := Tests{
Tests: []Test{test},
}

// Marshall struct to yaml data
yamldata, err := yaml.Marshal(testFile)
if err != nil {
return fmt.Errorf("error marshalling syscallstep input to yaml: %v", err)
}

// Write the yaml data to temporary file
tempFile, err := os.CreateTemp("", "syscall-*.yaml")
if err != nil {
return fmt.Errorf("error creating temporary file: %v", err)
}
defer os.Remove(tempFile.Name())

if _, err := tempFile.Write(yamldata); err != nil {
return fmt.Errorf("error writing to temporary file: %v", err)
}
if err := tempFile.Close(); err != nil {
return fmt.Errorf("error closing temporary file: %w", err)
}

tarReader, err := CreateTarReader(tempFile.Name())
if err != nil {
return fmt.Errorf("error creating tar reader: %v", err)
}

err = cli.CopyToContainer(ctx, r.ContainerId, "/", tarReader, container.CopyToContainerOptions{})
if err != nil {
return fmt.Errorf("error copying yaml file to container: %v", err)
}

// Prepare the command to run the event-generator inside container
yamlFileName := filepath.Base(tempFile.Name())
cmd := []string{"/event-generator", "run", "declarative", "/" + yamlFileName}

execConfig := container.ExecOptions{
Cmd: cmd,
AttachStdout: true,
AttachStderr: true,
}

execID, err := cli.ContainerExecCreate(ctx, r.ContainerId, execConfig)
if err != nil {
return fmt.Errorf("error creating Docker exec: %v", err)
}

err = cli.ContainerExecStart(ctx, execID.ID, container.ExecStartOptions{})
if err != nil {
return fmt.Errorf("error starting Docker exec: %v", err)
}

// Use this channel to wait for the exec instance to complete
done := make(chan error, 1)
go func() {
for {
inspectResp, err := cli.ContainerExecInspect(ctx, execID.ID)
if err != nil {
done <- fmt.Errorf("error inspecting Docker exec: %v", err)
return
}

if !inspectResp.Running {
if inspectResp.ExitCode != 0 {
done <- fmt.Errorf("ExecuteStep failed with exit code %v", inspectResp.ExitCode)
} else {
done <- nil
}
return
}
}
}()

err = <-done
return err
}

func (r *Containerrunner) Cleanup(ctx context.Context, afterScript string) error {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return fmt.Errorf("error creating Docker Client: %v", err)
}

err = cli.ContainerRemove(ctx, r.ContainerId, container.RemoveOptions{Force: true})
if err != nil {
return fmt.Errorf("error removing Docker container: %v", err)
}
return nil
}
39 changes: 39 additions & 0 deletions pkg/declarative/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,50 @@ limitations under the License.
package declarative

import (
"archive/tar"
"bytes"
"fmt"
"io"
"os"

"golang.org/x/sys/unix"
)

// It creates a tar reader for the file in given path.
func CreateTarReader(filePath string) (io.Reader, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("error opening file: %v", err)
}

fileInfo, err := file.Stat()
if err != nil {
file.Close()
return nil, fmt.Errorf("error getting file info: %v", err)
}

// Create a tar archive in memory
tarBuffer := new(bytes.Buffer)
tw := tar.NewWriter(tarBuffer)
defer tw.Close()

header := &tar.Header{
Name: fileInfo.Name(),
Mode: 0755,
Size: fileInfo.Size(),
}

if err := tw.WriteHeader(header); err != nil {
return nil, fmt.Errorf("error writing tar header: %v", err)
}

if _, err := io.Copy(tw, file); err != nil {
return nil, fmt.Errorf("error copying file to tar writer: %v", err)
}

return tarBuffer, nil
}

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)
Expand Down
56 changes: 56 additions & 0 deletions pkg/declarative/host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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 (
"context"
"fmt"
"os/exec"
)

type Hostrunner struct{}

func (r *Hostrunner) Setup(ctx context.Context, 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(ctx context.Context, test Test) error {
steps := test.Steps
for _, step := range steps {
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(ctx context.Context, 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
}
24 changes: 24 additions & 0 deletions pkg/declarative/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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 "context"

// Common runner interface for runners like hostrunner, container-runner etc..
type Runner interface {
Setup(ctx context.Context, beforeScript string) error
ExecuteStep(ctx context.Context, test Test) error
Cleanup(ctx context.Context, afterScript string) error
}
Loading

0 comments on commit 94493f2

Please sign in to comment.