Skip to content

Commit

Permalink
feat(decl): enhance process labels propagation system
Browse files Browse the repository at this point in the history
Enhance process labels propagation system to propagate container
image and name information.

Signed-off-by: Leonardo Di Giovanna <[email protected]>
Co-authored-by: Aldo Lacuku <[email protected]>
  • Loading branch information
ekoops and alacuku committed Nov 26, 2024
1 parent a6f5d44 commit a272d85
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 114 deletions.
43 changes: 23 additions & 20 deletions cmd/declarative/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ const (
DescriptionFlagName = "description"
// TestIDFlagName is the name of the flag allowing to specify the test identifier.
TestIDFlagName = "test-id"
// ProcLabelFlagName is the name of the flag allowing to specify a process label.
ProcLabelFlagName = "proc-label"
// LabelsFlagName is the name of the flag allowing to specify labels.
LabelsFlagName = "labels"
// TimeoutFlagName is the name of the flag allowing to specify the test timeout.
TimeoutFlagName = "timeout"
)
Expand All @@ -51,8 +51,8 @@ type Config struct {
DescriptionEnvKey string
// TestIDEnvKey is the environment variable key corresponding to TestIDFlagName.
TestIDEnvKey string
// ProcLabelEnvKey is the environment variable key corresponding to ProcLabelFlagName.
ProcLabelEnvKey string
// LabelsEnvKey is the environment variable key corresponding to LabelsFlagName.
LabelsEnvKey string
// TimeoutEnvKey is the environment variable key corresponding to TimeoutFlagName.
TimeoutEnvKey string

Expand All @@ -68,11 +68,6 @@ type Config struct {
// - the root process has no test ID
// - the processes in the process chain but the last have the test ID in the form testIDIgnorePrefix<testUID>
// - the last process in the process chain has the test ID in the form <testUID>
// A process having a test ID in the form <testUID> (i.e.: the leaf process) is the only one that is monitored.
TestID string
// ProcLabel is the process label in the form test<testIndex>,child<childIndex>. It is used for logging purposes
// and to potentially generate the child process label.
ProcLabel string
// TestsTimeout is the maximal duration of the tests. If running tests lasts more than TestsTimeout, the execution
// of all pending tasks is canceled.
TestsTimeout time.Duration
Expand All @@ -82,6 +77,14 @@ type Config struct {
ContainerBaseImageName string
// ContainerImagePullPolicy is container image pull policy.
ContainerImagePullPolicy builder.ImagePullPolicy
//
// Hidden flags
//
// A process having a test ID in the form <testUID> (i.e.: the leaf process) is the only one that is monitored.
TestID string
// Labels is the string containing the comma-separated list of labels in the form <labelX>=<labelXValue>. It is used
// for logging purposes and to potentially generate the child process/container labels.
Labels string
}

var containerImagePullPolicies = map[builder.ImagePullPolicy][]string{
Expand All @@ -98,7 +101,7 @@ func New(cmd *cobra.Command, declarativeEnvKey, envKeysPrefix string) *Config {
DescriptionFileEnvKey: envKeyFromFlagName(envKeysPrefix, DescriptionFileFlagName),
DescriptionEnvKey: envKeyFromFlagName(envKeysPrefix, DescriptionFlagName),
TestIDEnvKey: envKeyFromFlagName(envKeysPrefix, TestIDFlagName),
ProcLabelEnvKey: envKeyFromFlagName(envKeysPrefix, ProcLabelFlagName),
LabelsEnvKey: envKeyFromFlagName(envKeysPrefix, LabelsFlagName),
TimeoutEnvKey: envKeyFromFlagName(envKeysPrefix, TimeoutFlagName),
}
commonConf.initFlags(cmd)
Expand All @@ -114,16 +117,6 @@ func (c *Config) initFlags(cmd *cobra.Command) {
flags.StringVarP(&c.TestsDescription, DescriptionFlagName, "d", "",
"The YAML-formatted tests description string specifying the tests to be run")
cmd.MarkFlagsMutuallyExclusive(DescriptionFileFlagName, DescriptionFlagName)

flags.StringVar(&c.TestID, TestIDFlagName, "",
"(used during process chain building) The test identifier in the form <ignorePrefix><testUID>. It is "+
"used to propagate the test UID to child processes in the process chain")
flags.StringVar(&c.ProcLabel, ProcLabelFlagName, "",
"(used during process chain building) The process label in the form test<testIndex>,child<childIndex>. "+
"It is used for logging purposes and to potentially generate the child process label")
_ = flags.MarkHidden(TestIDFlagName)
_ = flags.MarkHidden(ProcLabelFlagName)

flags.DurationVarP(&c.TestsTimeout, TimeoutFlagName, "t", time.Minute,
"The maximal duration of the tests. If running tests lasts more than testsTimeout, the execution of "+
"all pending tasks is canceled")
Expand All @@ -136,6 +129,16 @@ func (c *Config) initFlags(cmd *cobra.Command) {
flags.Var(enumflag.New(&c.ContainerImagePullPolicy, "container-image-pull-policy", containerImagePullPolicies,
enumflag.EnumCaseInsensitive), "container-image-pull-policy",
"The container image pull policy; can be 'always', 'never' or 'ifnotpresent'")

// Hidden flags.
flags.StringVar(&c.TestID, TestIDFlagName, "",
"(used during process chain building) The test identifier in the form <ignorePrefix><testUID>. It is "+
"used to propagate the test UID to child processes/container in the process chain")
flags.StringVar(&c.Labels, LabelsFlagName, "",
"(used during process chain building) The list of comma-separated labels in the form <labelX>=<labelXValue>. "+
"It is used for logging purposes and to potentially generate the child process/container labels")
_ = flags.MarkHidden(TestIDFlagName)
_ = flags.MarkHidden(LabelsFlagName)
}

// envKeyFromFlagName converts the provided flag name into the corresponding environment variable key.
Expand Down
87 changes: 31 additions & 56 deletions cmd/declarative/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"time"
Expand All @@ -36,6 +34,7 @@ import (
"github.com/falcosecurity/event-generator/cmd/declarative/config"
"github.com/falcosecurity/event-generator/pkg/alert/retriever/grpcretriever"
containerbuilder "github.com/falcosecurity/event-generator/pkg/container/builder"
"github.com/falcosecurity/event-generator/pkg/label"
"github.com/falcosecurity/event-generator/pkg/test"
testbuilder "github.com/falcosecurity/event-generator/pkg/test/builder"
"github.com/falcosecurity/event-generator/pkg/test/loader"
Expand Down Expand Up @@ -181,17 +180,13 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) {
testID := cw.TestID
isRootProcess := testID == ""

procLabelInfo, err := parseProcLabel(cw.ProcLabel)
labels, err := label.ParseSet(cw.Labels)
if err != nil {
logger.Error(err, "Error parsing process label")
logger.Error(err, "Error parsing labels")
exitAndCancel()
}

if procLabelInfo == nil {
logger = logger.WithName("root")
} else {
logger = logger.WithName(procLabelInfo.testName).WithName(procLabelInfo.childName)
}
logger = enrichLogger(logger, labels)

description, err := loadTestsDescription(logger, cw.TestsDescriptionFile, cw.TestsDescription)
if err != nil {
Expand Down Expand Up @@ -256,15 +251,21 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) {
// Prepare parameters shared by runners.
runnerLogger := logger.WithName("runner")
runnerEnviron := cw.buildRunnerEnviron(cmd)
var runnerProcLabel string
if procLabelInfo != nil {
runnerProcLabel = fmt.Sprintf("%s,%s", procLabelInfo.testName, procLabelInfo.childName)
var runnerLabels *label.Set
if labels != nil {
runnerLabels = labels.Clone()
}

// Build and run the tests.
for testIndex := range description.Tests {
testDesc := &description.Tests[testIndex]

// If this process belongs to a test process chain, override the logged test index in order to match its
// absolute index among all the test descriptions.
if labels != nil {
testIndex = labels.TestIndex
}

logger := logger.WithValues("testName", testDesc.Name, "testIndex", testIndex)

var testUID uuid.UUID
Expand Down Expand Up @@ -292,28 +293,23 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) {
}

logger = logger.WithValues("testUid", testUID)
runnerLogger := runnerLogger.WithValues("testName", testDesc.Name, "testIndex", testIndex, "testUid", testUID)

runnerDescription := &runner.Description{
Environ: runnerEnviron,
TestDescriptionEnvKey: cw.DescriptionEnvKey,
TestDescriptionFileEnvKey: cw.DescriptionFileEnvKey,
TestIDEnvKey: cw.TestIDEnvKey,
TestIDIgnorePrefix: testIDIgnorePrefix,
ProcLabelEnvKey: cw.ProcLabelEnvKey,
ProcLabel: runnerProcLabel,
LabelsEnvKey: cw.LabelsEnvKey,
Labels: runnerLabels,
}
runnerInstance, err := runnerBuilder.Build(testDesc.Runner, runnerLogger, runnerDescription)
if err != nil {
logger.Error(err, "Error creating runner")
exitAndCancel()
}

// If this process belongs to a test process chain, override the logged test index in order to match its
// absolute index among all the test descriptions.
if !isRootProcess {
testIndex = procLabelInfo.testIndex
}

logger.Info("Starting test execution...")

if err := runnerInstance.Run(ctx, testID, testIndex, testDesc); err != nil {
Expand Down Expand Up @@ -352,45 +348,24 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) {
testerWaitGroup.Wait()
}

var (
// procLabelRegex defines the process label format and allows to extract the embedded test and child indexes.
procLabelRegex = regexp.MustCompile(`^test(\d+),child(\d+)$`)
errProcLabelRegex = fmt.Errorf("process label must comply with %q regex", procLabelRegex.String())
)

// processLabelInfo contains information regarding the process label.
type processLabelInfo struct {
testName string
testIndex int
childName string
childIndex int
}

// parseProcLabel parses the process label and returns information on it.
func parseProcLabel(procLabel string) (*processLabelInfo, error) {
if procLabel == "" {
return nil, nil
}

match := procLabelRegex.FindStringSubmatch(procLabel)
if match == nil {
return nil, errProcLabelRegex
func enrichLogger(logger logr.Logger, labels *label.Set) logr.Logger {
if labels == nil {
return logger.WithName("root")
}

// No errors can occur, since we have already verified through regex that they are numbers.
testIndex, _ := strconv.Atoi(match[1])
childIndex, _ := strconv.Atoi(match[2])

parts := strings.Split(procLabel, ",")

procLabelInfo := &processLabelInfo{
testName: parts[0],
testIndex: testIndex,
childName: parts[1],
childIndex: childIndex,
testName := fmt.Sprintf("test%d", labels.TestIndex)
logger = logger.WithName(testName)
if labels.IsContainer {
logger = logger.WithName("cont")
if imageName := labels.ImageName; imageName != "" {
logger = logger.WithValues("imageName", imageName)
}
if containerName := labels.ContainerName; containerName != "" {
logger = logger.WithValues("containerName", containerName)
}
}

return procLabelInfo, nil
procName := fmt.Sprintf("proc%d", labels.ProcIndex)
return logger.WithName(procName)
}

// loadTestsDescription loads the YAML tests description from a different source, depending on the content of the
Expand Down
69 changes: 36 additions & 33 deletions pkg/test/runner/host/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (
"context"
"fmt"
"os"
"strconv"
"strings"

"github.com/go-logr/logr"

"github.com/falcosecurity/event-generator/pkg/container"
"github.com/falcosecurity/event-generator/pkg/label"
"github.com/falcosecurity/event-generator/pkg/process"
"github.com/falcosecurity/event-generator/pkg/test"
"github.com/falcosecurity/event-generator/pkg/test/loader"
Expand Down Expand Up @@ -72,8 +72,8 @@ func New(logger logr.Logger, testBuilder test.Builder, containerBuilder containe
return nil, fmt.Errorf("description.TestIDIgnorePrefix must not be empty")
}

if description.ProcLabelEnvKey == "" {
return nil, fmt.Errorf("description.ProcLabelEnvKey must not be empty")
if description.LabelsEnvKey == "" {
return nil, fmt.Errorf("description.LabelsEnvKey must not be empty")
}

r := &hostRunner{
Expand Down Expand Up @@ -125,6 +125,14 @@ func (r *hostRunner) Run(ctx context.Context, testID string, testIndex int, test
// delegateToContainer delegates the execution of the test to a container, created and tuned as per test specification.
func (r *hostRunner) delegateToContainer(ctx context.Context, testID string, testIndex int,
testDesc *loader.Test) error {
// Initialize labels for the container's process.
labels := r.Labels
if labels == nil {
labels = &label.Set{TestIndex: testIndex, ProcIndex: 0, IsContainer: true}
} else {
labels.ProcIndex++
}

containerContext := popContainer(testDesc.Context)

// Configure the container.
Expand All @@ -134,13 +142,15 @@ func (r *hostRunner) delegateToContainer(ctx context.Context, testID string, tes

if imageName := containerContext.Image; imageName != nil {
containerBuilder.SetImageName(*imageName)
labels.ImageName = *imageName
}

if containerName := containerContext.Name; containerName != nil {
containerBuilder.SetContainerName(*containerName)
labels.ContainerName = *containerName
}

containerEnv, err := r.buildEnv(testID, testIndex, testDesc, containerContext.Env, false)
containerEnv, err := r.buildEnv(testID, testDesc, containerContext.Env, false, labels)
if err != nil {
return fmt.Errorf("error building container environment variables set: %w", err)
}
Expand Down Expand Up @@ -168,9 +178,9 @@ func popContainer(testContext *loader.TestContext) *loader.ContainerContext {
}

// buildEnv builds the environment variable set for a given process, leveraging the provided test data and the
// additional user-provide environment variables.
func (r *hostRunner) buildEnv(testID string, testIndex int, testDesc *loader.Test, userEnv map[string]string,
isLastProcess bool) ([]string, error) {
// additional user-provide environment variables and labels.
func (r *hostRunner) buildEnv(testID string, testDesc *loader.Test, userEnv map[string]string,
isLastProcess bool, labels *label.Set) ([]string, error) {
env := r.Environ

// Add the user-provided environment variable.
Expand All @@ -191,18 +201,18 @@ func (r *hostRunner) buildEnv(testID string, testIndex int, testDesc *loader.Tes
}
testIDEnvVar := buildEnvVar(r.TestIDEnvKey, testID)

// Set process label environment variable.
procLabel, err := r.buildProcLabel(testIndex)
// Set labels environment variable.
labelsValue, err := marshalLabels(labels)
if err != nil {
return nil, fmt.Errorf("error building process label: %w", err)
return nil, fmt.Errorf("error serializing labels: %w", err)
}
procLabelEnvVar := buildEnvVar(r.ProcLabelEnvKey, procLabel)
labelsEnvVar := buildEnvVar(r.LabelsEnvKey, labelsValue)

// Override test description file environment variable to avoid conflicts with the test description environment
// variable.
descriptionFileEnvVar := buildEnvVar(r.TestDescriptionFileEnvKey, "")

env = append(env, descriptionEnvVar, testIDEnvVar, procLabelEnvVar, descriptionFileEnvVar)
env = append(env, descriptionEnvVar, testIDEnvVar, labelsEnvVar, descriptionFileEnvVar)
return env, nil
}

Expand All @@ -227,36 +237,29 @@ func (r *hostRunner) getTestUID(testID string) string {
return strings.TrimPrefix(testID, r.TestIDIgnorePrefix)
}

// buildProcLabel builds a process label. If the current process Label is not defined, it uses the provided testIndex to
// create a new one; otherwise, given the process label in the form testName,child<childIndex>, it returns
// testName,child<childIndex+1>.
func (r *hostRunner) buildProcLabel(testIndex int) (string, error) {
procLabel := r.ProcLabel
if procLabel == "" {
return fmt.Sprintf("test%d,child0", testIndex), nil
}

labelParts := strings.Split(procLabel, ",")
if len(labelParts) != 2 {
return "", fmt.Errorf("cannot parse process label")
}

testName, procName := labelParts[0], labelParts[1]
childIndex, err := strconv.Atoi(strings.TrimPrefix(procName, "child"))
if err != nil {
return "", fmt.Errorf("error parsing process name in process label %q: %w", procName, err)
// marshalLabels returns the serialized labels.
func marshalLabels(labels *label.Set) (string, error) {
sb := &strings.Builder{}
if err := labels.Write(sb); err != nil {
return "", err
}

return fmt.Sprintf("%s,child%d", testName, childIndex+1), nil
return sb.String(), nil
}

// delegateToProcess delegates the execution of the test to a process, created and tuned as per test specification.
func (r *hostRunner) delegateToProcess(ctx context.Context, testID string, testIndex int, testDesc *loader.Test) error {
firstProcess := popFirstProcessContext(testDesc.Context)
isLastProcess := len(testDesc.Context.Processes) == 0

labels := r.Labels
if labels == nil {
labels = &label.Set{TestIndex: testIndex, ProcIndex: 0, IsContainer: false}
} else {
labels.ProcIndex++
}

// Evaluate process environment variables.
procEnv, err := r.buildEnv(testID, testIndex, testDesc, firstProcess.Env, isLastProcess)
procEnv, err := r.buildEnv(testID, testDesc, firstProcess.Env, isLastProcess, labels)
if err != nil {
return fmt.Errorf("error building process environment variables set: %w", err)
}
Expand Down
Loading

0 comments on commit a272d85

Please sign in to comment.