From cf238b5cd902bda7f0121e6c10253bc56a194cb2 Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 26 Nov 2024 09:26:31 +0100 Subject: [PATCH 1/8] chore(decl/loader): improve container context definition Add container context fields documentation and the env field. Signed-off-by: Leonardo Di Giovanna Co-authored-by: Aldo Lacuku --- pkg/test/loader/loader.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/test/loader/loader.go b/pkg/test/loader/loader.go index dcfdc671..79977397 100644 --- a/pkg/test/loader/loader.go +++ b/pkg/test/loader/loader.go @@ -161,14 +161,19 @@ func (r *TestRunnerType) UnmarshalYAML(node *yaml.Node) error { // TestContext contains information regarding the running context of a test. type TestContext struct { - Container *ContainerContext `yaml:"container,omitempty"` + Container *ContainerContext `yaml:"container,omitempty" validate:"omitempty"` Processes []ProcessContext `yaml:"processes,omitempty" validate:"omitempty,dive"` } // ContainerContext contains information regarding the container instance that will run a test. type ContainerContext struct { - Image string `yaml:"image" validate:"required"` - Name *string `yaml:"name,omitempty" validate:"omitempty,min=1"` + // Image is the name the base event-generator image must be tagged with before being used to spawn the container. If + // omitted, it defaults to the name of the base event-generator image. + Image *string `yaml:"image" validate:"omitempty,min=1"` + // Name is the name that must be used to identify the container. If omitted, it defaults to "event-generator". + Name *string `yaml:"name,omitempty" validate:"omitempty,min=1"` + // Env is the set of environment variables that must be provided to the container (in addition to the default ones). + Env map[string]string `yaml:"env,omitempty" validate:"omitempty,min=1"` } // ProcessContext contains information regarding the process that will run a test, or information about one of its From 82a176b02bb0b708d836cafd373edcbf125cd2f2 Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 26 Nov 2024 11:01:21 +0100 Subject: [PATCH 2/8] chore(decl): add container and container builder declaration Signed-off-by: Leonardo Di Giovanna Co-authored-by: Aldo Lacuku --- pkg/container/container.go | 51 ++++++++++++++++++++++++++++++++++++++ pkg/container/doc.go | 17 +++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 pkg/container/container.go create mode 100644 pkg/container/doc.go diff --git a/pkg/container/container.go b/pkg/container/container.go new file mode 100644 index 00000000..6c11d29c --- /dev/null +++ b/pkg/container/container.go @@ -0,0 +1,51 @@ +// 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 container + +import ( + "context" + + "github.com/go-logr/logr" +) + +// Builder allows to build a new container. +type Builder interface { + // SetLogger sets the container interface logger. + SetLogger(logger logr.Logger) + // SetNamespaceName sets the container namespace. + SetNamespaceName(name string) + // SetImageName sets the name used to tag the base image. The new tagged image is the one used to spawn the + // container in place of the base one. + SetImageName(name string) + // SetContainerName sets the container name. + SetContainerName(name string) + // SetEnv sets the list of environment variable that must be provided to the container, in addition to the default + // ones. + SetEnv(env []string) + // SetEntrypoint sets the container entrypoint. + SetEntrypoint(entrypoint []string) + // Build builds the container. + Build() Container +} + +// Container represents a container. +type Container interface { + // Start starts the container. It returns an error if the container was already started. + Start(ctx context.Context) error + // Wait waits for container termination. It returns an error if the container was not started or the container + // process returned with an exit code different from zero. + Wait(ctx context.Context) error +} diff --git a/pkg/container/doc.go b/pkg/container/doc.go new file mode 100644 index 00000000..b95b1edf --- /dev/null +++ b/pkg/container/doc.go @@ -0,0 +1,17 @@ +// 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 container provides the definition of a Container and a container Builder. +package container From 428a404bd4a192fd6923d5488a9d74a2556cc4f6 Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 26 Nov 2024 11:03:52 +0100 Subject: [PATCH 3/8] feat(decl/containers): add containerd container builder Signed-off-by: Leonardo Di Giovanna Co-authored-by: Aldo Lacuku --- go.mod | 36 +++ go.sum | 130 ++++++++++ pkg/container/builder/builder.go | 428 +++++++++++++++++++++++++++++++ pkg/container/builder/doc.go | 17 ++ 4 files changed, 611 insertions(+) create mode 100644 pkg/container/builder/builder.go create mode 100644 pkg/container/builder/doc.go diff --git a/go.mod b/go.mod index c950549f..86752d1d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/falcosecurity/event-generator go 1.23.1 require ( + github.com/containerd/containerd v1.7.24 github.com/creasty/defaults v1.8.0 github.com/dustin/go-humanize v1.0.1 github.com/falcosecurity/client-go v0.6.1 @@ -45,22 +46,40 @@ require ( replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16 require ( + github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect + github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Microsoft/hcsshim v0.11.7 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/chai2010/gettext-go v1.0.3 // indirect + github.com/containerd/cgroups v1.1.0 // indirect + github.com/containerd/containerd/api v1.7.19 // indirect + github.com/containerd/continuity v0.4.2 // indirect + github.com/containerd/errdefs v0.3.0 // indirect + github.com/containerd/fifo v1.1.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/containerd/ttrpc v1.2.5 // indirect + github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/go-errors/errors v1.5.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect @@ -74,19 +93,30 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/sys/mountinfo v0.6.2 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/signal v0.7.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/runtime-spec v1.1.0 // indirect + github.com/opencontainers/selinux v1.11.0 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect @@ -101,6 +131,11 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect go.starlark.net v0.0.0-20240725214946-42030a7cedce // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.28.0 // indirect @@ -111,6 +146,7 @@ require ( golang.org/x/term v0.25.0 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.6.0 // indirect + google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect diff --git a/go.sum b/go.sum index d2ceceae..ccfa54ef 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,17 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= +github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= +github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -14,10 +22,32 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80= github.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/containerd v1.7.24 h1:zxszGrGjrra1yYJW/6rhm9cJ1ZQ8rkKBR48brqsa7nA= +github.com/containerd/containerd v1.7.24/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= +github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= +github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= +github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= +github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU= +github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= +github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -36,14 +66,24 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/falcosecurity/client-go v0.6.1 h1:zDiz6PHGPdg3sScWe+4juc5n7FLJGCHI9ohAnuRSMFg= github.com/falcosecurity/client-go v0.6.1/go.mod h1:zVjGk4GLCoBKCo+lGMmdiiL0Ag7ZVVtVt/ldubjghvk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -59,8 +99,11 @@ github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -86,11 +129,23 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -99,6 +154,12 @@ github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76 github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -109,6 +170,7 @@ github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQu github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -139,6 +201,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -165,8 +229,20 @@ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTS github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= +github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -191,6 +267,14 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= +github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= +github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= @@ -206,6 +290,7 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -251,12 +336,17 @@ github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -278,6 +368,16 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.starlark.net v0.0.0-20240725214946-42030a7cedce h1:YyGqCjZtGZJ+mRPaenEiB87afEO2MFRzLiJNZ0Z0bPw= go.starlark.net v0.0.0-20240725214946-42030a7cedce/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -295,22 +395,27 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -334,6 +439,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -351,7 +458,9 @@ golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -362,13 +471,33 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -398,6 +527,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= diff --git a/pkg/container/builder/builder.go b/pkg/container/builder/builder.go new file mode 100644 index 00000000..3e2b3779 --- /dev/null +++ b/pkg/container/builder/builder.go @@ -0,0 +1,428 @@ +// 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 builder + +import ( + "context" + "fmt" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/cio" + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/oci" + "github.com/go-logr/logr" + + "github.com/falcosecurity/event-generator/pkg/container" +) + +// config stores the container builder underlying configuration. +type config struct { + unixSocketPath string + baseImageName string + baseImagePullPolicy ImagePullPolicy +} + +// ImagePullPolicy is the policy defining how to pull an image. +type ImagePullPolicy int + +const ( + // ImagePullPolicyAlways specifies that the image must always be pulled from remote. + ImagePullPolicyAlways ImagePullPolicy = iota + // ImagePullPolicyNever specifies that the image must always be searched from the locally available images. + ImagePullPolicyNever + // ImagePullPolicyIfNotPresent specifies that the image must be pull from remote only if it not locally available. + ImagePullPolicyIfNotPresent +) + +// Option for configuring the container builder. +type Option interface { + apply(*config) error +} + +// funcOption is an implementation of Option storing a function that implements the requested apply method behavior. +type funcOption struct { + f func(*config) error +} + +func (cfo *funcOption) apply(c *config) error { + return cfo.f(c) +} + +// newFuncOption is a helper function to create a new funcOption from a function. +func newFuncOption(f func(*config) error) *funcOption { + return &funcOption{f: f} +} + +// WithUnixSocketPath allows to specify the unix socket path of the local container runtime server. +func WithUnixSocketPath(unixSocketPath string) Option { + return newFuncOption(func(c *config) error { + c.unixSocketPath = unixSocketPath + return nil + }) +} + +// WithBaseImageName allows to specify the name of the base image used to create the container. +func WithBaseImageName(baseImage string) Option { + return newFuncOption(func(c *config) error { + c.baseImageName = baseImage + return nil + }) +} + +// WithBaseImagePullPolicy allows to specify the pull policy for obtaining the base image. +func WithBaseImagePullPolicy(policy ImagePullPolicy) Option { + return newFuncOption(func(c *config) error { + c.baseImagePullPolicy = policy + return nil + }) +} + +var defaultConfig = &config{ + unixSocketPath: "/run/containerd/containerd.sock", + baseImageName: "docker.io/falcosecurity/event-generator:latest", + baseImagePullPolicy: ImagePullPolicyAlways, +} + +// builder is an implementation of container.Builder leveraging containerd. +type builder struct { + config + logger logr.Logger + namespaceName string + imageName string + containerName string + env []string + entrypoint []string +} + +// Verify that builder implements container.Builder interface. +var _ container.Builder = (*builder)(nil) + +// New creates a new containerd container builder. +func New(options ...Option) (container.Builder, error) { + b := &builder{config: *defaultConfig} + for _, opt := range options { + if err := opt.apply(&b.config); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + return b, nil +} + +func (b *builder) SetLogger(logger logr.Logger) { + b.logger = logger +} + +func (b *builder) SetNamespaceName(name string) { + b.namespaceName = name +} + +func (b *builder) SetImageName(name string) { + b.imageName = name +} + +func (b *builder) SetContainerName(name string) { + b.containerName = name +} + +func (b *builder) SetEnv(env []string) { + b.env = env +} + +func (b *builder) SetEntrypoint(entrypoint []string) { + b.entrypoint = entrypoint +} + +const ( + defaultNamespaceName = "event-generator" + defaultContainerName = "event-generator" +) + +var ( + defaultEntrypoint = []string{"/bin/event-generator"} +) + +func (b *builder) Build() container.Container { + // If the namespace is not provided, default it. + var namespaceName string + if b.namespaceName != "" { + namespaceName = b.namespaceName + } else { + namespaceName = defaultNamespaceName + } + + baseImageName := b.baseImageName + + // If the image name is not provided, default it to the base image name. + var imageName string + if b.imageName != "" { + imageName = b.imageName + } else { + imageName = baseImageName + } + + // If the container name is not provided, default it. + var containerName string + if b.containerName != "" { + containerName = b.containerName + } else { + containerName = defaultContainerName + } + + // If the container entrypoint is not provided, default it. + var entrypoint []string + if b.entrypoint != nil { + entrypoint = b.entrypoint + } else { + entrypoint = defaultEntrypoint + } + + cont := &containerdContainer{ + logger: b.logger, + unixSocketPath: b.unixSocketPath, + namespaceName: namespaceName, + baseImageName: baseImageName, + baseImagePullPolicy: b.baseImagePullPolicy, + imageName: imageName, + containerName: containerName, + env: b.env, + entrypoint: entrypoint, + } + return cont +} + +// containerdContainer is an implementation of a containerd container.Container. +type containerdContainer struct { + logger logr.Logger + unixSocketPath string + namespaceName string + baseImageName string + baseImagePullPolicy ImagePullPolicy + imageName string + containerName string + env []string + entrypoint []string + + // started is true if the container has been started; false otherwise. + started bool + // client is the client used to interact with the containerd runtime. + client *containerd.Client + // createdImage is the image created if imageName is different from baseImageName. If they are equal, createdImage + // is nil. + createdImage containerd.Image + // container is the containerd container. + container containerd.Container + // task is the task associated with the container. + task containerd.Task +} + +var ( + errContainerAlreadyStarted = fmt.Errorf("container already started") + errContainerNotStarted = fmt.Errorf("container not started") +) + +func (c *containerdContainer) Start(ctx context.Context) (err error) { + if c.started { + return errContainerAlreadyStarted + } + + defer func() { + if err != nil { + c.teardown(ctx) + } + }() + + logger := c.logger + namespace := c.namespaceName + + // Create containerd client. + client, err := containerd.New(c.unixSocketPath, containerd.WithDefaultNamespace(namespace)) + if err != nil { + return fmt.Errorf("error client containerd client: %w", err) + } + c.client = client + + // Set namespace into context to default it into subsequent requests to client. + ctx = namespaces.WithNamespace(ctx, namespace) + + // Retrieve base image. + baseImageName := c.baseImageName + image, err := getImage(ctx, client, baseImageName, c.baseImagePullPolicy) + if err != nil { + return fmt.Errorf("error getting image %q: %w", baseImageName, err) + } + logger.V(1).Info("Retrieved base image", "imageName", baseImageName) + + imageName := c.imageName + // If the base image name is different from the required name for the image that is going to be used for the + // container, create a new image with the required name from the base image. + if imageName != baseImageName { + metadata := image.Metadata() + metadata.Name = imageName + imageService := client.ImageService() + var createdImage images.Image + createdImage, err = imageService.Create(ctx, metadata) + if err != nil { + return fmt.Errorf("error tagging image %q as %q: %w", baseImageName, imageName, err) + } + logger.V(1).Info("Tagged image", "baseImageName", baseImageName, "imageName", imageName) + + // Set image to the new created image. + image = containerd.NewImage(client, createdImage) + c.createdImage = image + } + + // Create the container. + cont, err := c.createContainer(ctx, client, image) + if err != nil { + return fmt.Errorf("error creating container: %w", err) + } + c.container = cont + logger.V(1).Info("Created container", "containerName", c.containerName) + + // Create a new task for the container. + task, err := cont.NewTask(ctx, cio.NewCreator(cio.WithStdio), containerd.WithRuntimePath("io.containerd.runc.v1")) + if err != nil { + return fmt.Errorf("error creating new task: %w", err) + } + c.task = task + logger.V(1).Info("Created task") + + if err := task.Start(ctx); err != nil { + return fmt.Errorf("error starting task: %w", err) + } + + c.started = true + return nil +} + +// createContainer creates the container using the provided image and containerd client, configuring it as specified in +// the underlying configuration. +func (c *containerdContainer) createContainer(ctx context.Context, client *containerd.Client, + image containerd.Image) (containerd.Container, error) { + specOpts := []oci.SpecOpts{ + oci.WithPrivileged, + oci.WithEnv(c.env), + } + if entrypoint := c.entrypoint; entrypoint != nil { + specOpts = append(specOpts, oci.WithProcessArgs(entrypoint...)) + } + containerOptions := []containerd.NewContainerOpts{ + containerd.WithImage(image), + containerd.WithNewSnapshot(fmt.Sprintf("%s-rootfs", image.Name()), image), + containerd.WithNewSpec(specOpts...), + } + return client.NewContainer(ctx, c.containerName, containerOptions...) +} + +// teardown reverts all operations performed during Start execution. +func (c *containerdContainer) teardown(ctx context.Context) { + defer func() { + c.started = false + }() + + client := c.client + // If the client is nil, we are sure that the other fields are uninitialized, so simply return. + if client == nil { + return + } + + logger := c.logger + + if task := c.task; task != nil { + if _, err := task.Delete(ctx); err != nil { + logger.Error(err, "Error deleting container task") + } else { + logger.V(1).Info("Deleted container task") + } + c.task = nil + } + + if cont := c.container; cont != nil { + logger := logger.WithValues("containerName", cont.ID()) + if err := cont.Delete(ctx, containerd.WithSnapshotCleanup); err != nil { + logger.Error(err, "Error deleting container") + } else { + logger.V(1).Info("Deleted container") + } + c.container = nil + } + + if createdImage := c.createdImage; createdImage != nil { + imageService := client.ImageService() + imageName := createdImage.Name() + logger := logger.WithValues("imageName", imageName) + if err := imageService.Delete(ctx, imageName); err != nil { + logger.Error(err, "Error deleting image") + } else { + logger.V(1).Info("Deleted image") + } + c.createdImage = nil + } + + if err := client.Close(); err != nil { + logger.Error(err, "Error closing containerd client") + } else { + logger.V(1).Info("Closed containerd client") + } + c.client = nil +} + +// getImage returns the image with the provided image name, leveraging the provided containerd client and using the +// provided image pull policy. +func getImage(ctx context.Context, client *containerd.Client, imageName string, + policy ImagePullPolicy) (containerd.Image, error) { + switch policy { + case ImagePullPolicyAlways: + return client.Pull(ctx, imageName) + case ImagePullPolicyNever: + return client.GetImage(ctx, imageName) + case ImagePullPolicyIfNotPresent: + image, err := client.GetImage(ctx, imageName) + if errdefs.IsNotFound(err) { + return client.Pull(ctx, imageName) + } + return image, err + default: + panic(fmt.Sprintf("unknown image pull policy %q", policy)) + } +} + +func (c *containerdContainer) Wait(ctx context.Context) error { + if !c.started { + return errContainerNotStarted + } + + defer c.teardown(ctx) + + // Wait for the task to exit and get the exit status. + exitStatusCh, err := c.task.Wait(ctx) + if err != nil { + return fmt.Errorf("error waiting for task: %w", err) + } + + exitStatus := <-exitStatusCh + if err := exitStatus.Error(); err != nil { + return fmt.Errorf("error waiting for process: %w", err) + } + + if exitCode := exitStatus.ExitCode(); exitCode != 0 { + return fmt.Errorf("container exited with non-zero exit code (%d)", exitCode) + } + + return nil +} diff --git a/pkg/container/builder/doc.go b/pkg/container/builder/doc.go new file mode 100644 index 00000000..2f90b3ff --- /dev/null +++ b/pkg/container/builder/doc.go @@ -0,0 +1,17 @@ +// 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 builder provides an implementation of container.Builder and container.Container leveraging containerd. +package builder From b39294005ea215aa70477e0bd802612b76a448d5 Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 26 Nov 2024 11:08:30 +0100 Subject: [PATCH 4/8] chore(Dockerfile): replace alpine base images with debian-based ones Signed-off-by: Leonardo Di Giovanna Co-authored-by: Aldo Lacuku --- Dockerfile | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7e7faf60..fb1dc36e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM golang:1.23.1-alpine3.20 AS builder +FROM golang:1.23.1-bookworm AS builder LABEL maintainer="cncf-falco-dev@lists.cncf.io" -RUN apk add --no-cache make bash +RUN apt-get -y update && apt-get install -y make bash && apt clean -y && rm -rf /var/lib/apt/lists/* WORKDIR /event-generator @@ -11,10 +11,13 @@ COPY . . RUN make -FROM alpine:3.20 +FROM debian:bookworm-slim -RUN apk add --no-cache sudo polkit libcap e2fsprogs-extra openssh nmap netcat-openbsd wget curl +RUN apt-get -y update && \ + apt-get -y install policykit-1 libcap-dev e2fsprogs openssh-client openssh-server nmap netcat-openbsd wget \ + curl && \ + apt clean -y && rm -rf /var/lib/apt/lists/* -COPY --from=builder /event-generator/event-generator /bin/event-generator +COPY --from=builder --chmod=0755 /event-generator/event-generator /bin/event-generator ENTRYPOINT ["/bin/event-generator"] From 1c04efff495acb0a7d1024a6257bba9bd0ccc634 Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 26 Nov 2024 11:58:31 +0100 Subject: [PATCH 5/8] feat(decl/containers): add containerized test support Signed-off-by: Leonardo Di Giovanna Co-authored-by: Aldo Lacuku --- cmd/declarative/config/config.go | 26 +++- cmd/declarative/test/test.go | 14 +- pkg/test/runner/builder/builder.go | 16 ++- pkg/test/runner/host/host.go | 201 +++++++++++++++++++---------- 4 files changed, 185 insertions(+), 72 deletions(-) diff --git a/cmd/declarative/config/config.go b/cmd/declarative/config/config.go index 1171a35f..c0ca0de7 100644 --- a/cmd/declarative/config/config.go +++ b/cmd/declarative/config/config.go @@ -21,6 +21,9 @@ import ( "time" "github.com/spf13/cobra" + "github.com/thediveo/enumflag" + + "github.com/falcosecurity/event-generator/pkg/container/builder" ) const ( @@ -67,12 +70,24 @@ type Config struct { // - the last process in the process chain has the test ID in the form // A process having a test ID in the form (i.e.: the leaf process) is the only one that is monitored. TestID string - // ProcLabel is the process label in the form test.child. It is used for logging purposes + // ProcLabel is the process label in the form test,child. 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 + // ContainerRuntimeUnixSocketPath is the unix socket path of the local container runtime. + ContainerRuntimeUnixSocketPath string + // ContainerBaseImageName is the event-generator base image to generate new containers. + ContainerBaseImageName string + // ContainerImagePullPolicy is container image pull policy. + ContainerImagePullPolicy builder.ImagePullPolicy +} + +var containerImagePullPolicies = map[builder.ImagePullPolicy][]string{ + builder.ImagePullPolicyAlways: {"always"}, + builder.ImagePullPolicyNever: {"never"}, + builder.ImagePullPolicyIfNotPresent: {"ifnotpresent"}, } // New creates a new config linked to the provided command. @@ -112,6 +127,15 @@ func (c *Config) initFlags(cmd *cobra.Command) { 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") + + // Container runtime flags. + flags.StringVar(&c.ContainerRuntimeUnixSocketPath, "container-runtime-unix-socket", + "/run/containerd/containerd.sock", "The unix socket path of the local container runtime") + flags.StringVar(&c.ContainerBaseImageName, "container-base-image", + "docker.io/falcosecurity/event-generator:latest", "The event-generator base image to generate new containers") + 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'") } // envKeyFromFlagName converts the provided flag name into the corresponding environment variable key. diff --git a/cmd/declarative/test/test.go b/cmd/declarative/test/test.go index 604e8e8d..ba13818c 100644 --- a/cmd/declarative/test/test.go +++ b/cmd/declarative/test/test.go @@ -35,6 +35,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/test" testbuilder "github.com/falcosecurity/event-generator/pkg/test/builder" "github.com/falcosecurity/event-generator/pkg/test/loader" @@ -217,7 +218,18 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) { exitAndCancel() } - runnerBuilder, err := runnerbuilder.New(testBuilder) + containerBuilderOptions := []containerbuilder.Option{ + containerbuilder.WithUnixSocketPath(cw.ContainerRuntimeUnixSocketPath), + containerbuilder.WithBaseImageName(cw.ContainerBaseImageName), + containerbuilder.WithBaseImagePullPolicy(cw.ContainerImagePullPolicy), + } + containerBuilder, err := containerbuilder.New(containerBuilderOptions...) + if err != nil { + logger.Error(err, "Error creating container builder") + exitAndCancel() + } + + runnerBuilder, err := runnerbuilder.New(testBuilder, containerBuilder) if err != nil { logger.Error(err, "Error creating runner builder") exitAndCancel() diff --git a/pkg/test/runner/builder/builder.go b/pkg/test/runner/builder/builder.go index dbe3c955..48c13079 100644 --- a/pkg/test/runner/builder/builder.go +++ b/pkg/test/runner/builder/builder.go @@ -20,6 +20,7 @@ import ( "github.com/go-logr/logr" + "github.com/falcosecurity/event-generator/pkg/container" "github.com/falcosecurity/event-generator/pkg/test" "github.com/falcosecurity/event-generator/pkg/test/loader" "github.com/falcosecurity/event-generator/pkg/test/runner" @@ -30,18 +31,27 @@ import ( type builder struct { // testBuilder is the builder used to build a test. testBuilder test.Builder + // containerBuilder is the builder used to build a container. + containerBuilder container.Builder } // Verify that builder implements runner.Builder interface. var _ runner.Builder = (*builder)(nil) // New creates a new builder. -func New(testBuilder test.Builder) (runner.Builder, error) { +func New(testBuilder test.Builder, containerBuilder container.Builder) (runner.Builder, error) { if testBuilder == nil { return nil, fmt.Errorf("test builder must not be nil") } - b := &builder{testBuilder: testBuilder} + if containerBuilder == nil { + return nil, fmt.Errorf("container builder must not be nil") + } + + b := &builder{ + testBuilder: testBuilder, + containerBuilder: containerBuilder, + } return b, nil } @@ -50,7 +60,7 @@ func (b *builder) Build(runnerType loader.TestRunnerType, logger logr.Logger, logger = logger.WithValues("runnerType", runnerType) switch runnerType { case loader.TestRunnerTypeHost: - return host.New(logger, b.testBuilder, description) + return host.New(logger, b.testBuilder, b.containerBuilder, description) default: return nil, fmt.Errorf("unknown test runner type %q", runnerType) } diff --git a/pkg/test/runner/host/host.go b/pkg/test/runner/host/host.go index c7b402b7..5aa07886 100644 --- a/pkg/test/runner/host/host.go +++ b/pkg/test/runner/host/host.go @@ -24,6 +24,7 @@ import ( "github.com/go-logr/logr" + "github.com/falcosecurity/event-generator/pkg/container" "github.com/falcosecurity/event-generator/pkg/process" "github.com/falcosecurity/event-generator/pkg/test" "github.com/falcosecurity/event-generator/pkg/test/loader" @@ -36,6 +37,8 @@ type hostRunner struct { logger logr.Logger // testBuilder is the builder used to build a test. testBuilder test.Builder + // containerBuilder is the builder used to build a container. + containerBuilder container.Builder *runner.Description } @@ -43,11 +46,16 @@ type hostRunner struct { var _ runner.Runner = (*hostRunner)(nil) // New creates a new host runner. -func New(logger logr.Logger, testBuilder test.Builder, description *runner.Description) (runner.Runner, error) { +func New(logger logr.Logger, testBuilder test.Builder, containerBuilder container.Builder, + description *runner.Description) (runner.Runner, error) { if testBuilder == nil { return nil, fmt.Errorf("test builder must not be nil") } + if containerBuilder == nil { + return nil, fmt.Errorf("container builder must not be nil") + } + if description.TestDescriptionEnvKey == "" { return nil, fmt.Errorf("description.TestDescriptionEnvKey must not be empty") } @@ -69,21 +77,33 @@ func New(logger logr.Logger, testBuilder test.Builder, description *runner.Descr } r := &hostRunner{ - logger: logger, - testBuilder: testBuilder, - Description: description, + logger: logger, + testBuilder: testBuilder, + containerBuilder: containerBuilder, + Description: description, } return r, nil } func (r *hostRunner) Run(ctx context.Context, testID string, testIndex int, testDesc *loader.Test) error { - // Delegate to child process if we are not at the end of the chain. - if testDesc.Context != nil && len(testDesc.Context.Processes) != 0 { - if err := r.delegateToChild(ctx, testID, testIndex, testDesc); err != nil { - return fmt.Errorf("error delegating to child process: %w", err) + if testContext := testDesc.Context; testContext != nil { + // Delegate to container if the user specified a container context. + if testContext.Container != nil { + if err := r.delegateToContainer(ctx, testID, testIndex, testDesc); err != nil { + return fmt.Errorf("error delegating to container: %w", err) + } + + return nil } - return nil + // Delegate to child process if we are not at the end of the process chain. + if len(testDesc.Context.Processes) != 0 { + if err := r.delegateToProcess(ctx, testID, testIndex, testDesc); err != nil { + return fmt.Errorf("error delegating to child process: %w", err) + } + + return nil + } } // Build test. @@ -102,74 +122,53 @@ func (r *hostRunner) Run(ctx context.Context, testID string, testIndex int, test return nil } -// delegateToChild delegates the execution of the test to a child process, created and tuned as per test specification. -func (r *hostRunner) delegateToChild(ctx context.Context, testID string, testIndex int, testDesc *loader.Test) error { - firstProcess := popFirstProcessContext(testDesc.Context) - isLastProcess := len(testDesc.Context.Processes) == 0 +// 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 { + containerContext := popContainer(testDesc.Context) - // Evaluate process environment variables. - procEnv, err := r.buildEnv(testID, testIndex, testDesc, firstProcess.Env, isLastProcess) - if err != nil { - return fmt.Errorf("error building process environment variables set: %w", err) - } + // Configure the container. + containerBuilder := r.containerBuilder - // Get current process executable path. - currentExePath, err := getCurrentExePath() - if err != nil { - return fmt.Errorf("error retrieving the current process executable path: %w", err) - } + containerBuilder.SetLogger(r.logger.WithName("container")) - var simExePath, name, arg0, args, username, capabilities string - if firstProcess.ExePath != nil { - simExePath = *firstProcess.ExePath - } - if firstProcess.Name != nil { - name = *firstProcess.Name + if imageName := containerContext.Image; imageName != nil { + containerBuilder.SetImageName(*imageName) } - if firstProcess.Exe != nil { - arg0 = *firstProcess.Exe - } - if firstProcess.Args != nil { - args = *firstProcess.Args - } - if firstProcess.User != nil { - username = *firstProcess.User - } - if firstProcess.Capabilities != nil { - capabilities = *firstProcess.Capabilities + + if containerName := containerContext.Name; containerName != nil { + containerBuilder.SetContainerName(*containerName) } - procDesc := &process.Description{ - Logger: r.logger.WithName("process"), - Command: currentExePath, - SimExePath: simExePath, - Name: name, - Arg0: arg0, - Args: args, - Env: procEnv, - Username: username, - Capabilities: capabilities, + + containerEnv, err := r.buildEnv(testID, testIndex, testDesc, containerContext.Env, false) + if err != nil { + return fmt.Errorf("error building container environment variables set: %w", err) } - proc := process.New(ctx, procDesc) - // Run the child process and wait for it. - if err := proc.Start(); err != nil { - return fmt.Errorf("error starting child process: %w", err) + containerBuilder.SetEnv(containerEnv) + + cont := containerBuilder.Build() + + // Run the container and wait for it. + if err := cont.Start(ctx); err != nil { + return fmt.Errorf("error starting container: %w", err) } - if err := proc.Wait(); err != nil { - return fmt.Errorf("error waiting for child process: %w", err) + if err := cont.Wait(ctx); err != nil { + return fmt.Errorf("error waiting for container: %w", err) } return nil } -// popFirstProcessContext removes and returns the first process context from the provided testContext. -func popFirstProcessContext(testContext *loader.TestContext) *loader.ProcessContext { - processes := testContext.Processes - firstProcess := processes[0] - testContext.Processes = processes[1:] - return &firstProcess +// popContainer removes and returns the container context from the provided testContext. +func popContainer(testContext *loader.TestContext) *loader.ContainerContext { + containerContext := testContext.Container + testContext.Container = nil + return 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) { env := r.Environ @@ -207,11 +206,6 @@ func (r *hostRunner) buildEnv(testID string, testIndex int, testDesc *loader.Tes return env, nil } -// buildEnvVar creates an environment variable string in the form "=". -func buildEnvVar(envKey, envValue string) string { - return fmt.Sprintf("%s=%s", envKey, envValue) -} - // marshalTestDescription returns the serialized content of a test description object containing only the provided test. func marshalTestDescription(testDesc *loader.Test) (string, error) { desc := &loader.Description{Tests: []loader.Test{*testDesc}} @@ -223,6 +217,11 @@ func marshalTestDescription(testDesc *loader.Test) (string, error) { return sb.String(), nil } +// buildEnvVar creates an environment variable string in the form "=". +func buildEnvVar(envKey, envValue string) string { + return fmt.Sprintf("%s=%s", envKey, envValue) +} + // getTestUID extracts the test UID from the test ID by removing the ignore prefix. func (r *hostRunner) getTestUID(testID string) string { return strings.TrimPrefix(testID, r.TestIDIgnorePrefix) @@ -251,6 +250,74 @@ func (r *hostRunner) buildProcLabel(testIndex int) (string, error) { return fmt.Sprintf("%s,child%d", testName, childIndex+1), 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 + + // Evaluate process environment variables. + procEnv, err := r.buildEnv(testID, testIndex, testDesc, firstProcess.Env, isLastProcess) + if err != nil { + return fmt.Errorf("error building process environment variables set: %w", err) + } + + // Get current process executable path. + currentExePath, err := getCurrentExePath() + if err != nil { + return fmt.Errorf("error retrieving the current process executable path: %w", err) + } + + var simExePath, name, arg0, args, username, capabilities string + if firstProcess.ExePath != nil { + simExePath = *firstProcess.ExePath + } + if firstProcess.Name != nil { + name = *firstProcess.Name + } + if firstProcess.Exe != nil { + arg0 = *firstProcess.Exe + } + if firstProcess.Args != nil { + args = *firstProcess.Args + } + if firstProcess.User != nil { + username = *firstProcess.User + } + if firstProcess.Capabilities != nil { + capabilities = *firstProcess.Capabilities + } + procDesc := &process.Description{ + Logger: r.logger.WithName("process"), + Command: currentExePath, + SimExePath: simExePath, + Name: name, + Arg0: arg0, + Args: args, + Env: procEnv, + Username: username, + Capabilities: capabilities, + } + proc := process.New(ctx, procDesc) + // Run the child process and wait for it. + if err := proc.Start(); err != nil { + return fmt.Errorf("error starting child process: %w", err) + } + + if err := proc.Wait(); err != nil { + return fmt.Errorf("error waiting for child process: %w", err) + } + + return nil +} + +// popFirstProcessContext removes and returns the first process context from the provided testContext. +func popFirstProcessContext(testContext *loader.TestContext) *loader.ProcessContext { + processes := testContext.Processes + firstProcess := processes[0] + testContext.Processes = processes[1:] + return &firstProcess +} + // getCurrentExePath retrieves the current process executable path. func getCurrentExePath() (string, error) { return os.Readlink(fmt.Sprintf("/proc/%d/exe", os.Getpid())) From a6f5d4415a6bcc3b7913f33d29ed0002bc53585a Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 26 Nov 2024 16:24:37 +0100 Subject: [PATCH 6/8] chore(decl): add label package supporting labels handling Signed-off-by: Leonardo Di Giovanna Co-authored-by: Aldo Lacuku --- pkg/label/doc.go | 20 ++++++++ pkg/label/label.go | 112 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 pkg/label/doc.go create mode 100644 pkg/label/label.go diff --git a/pkg/label/doc.go b/pkg/label/doc.go new file mode 100644 index 00000000..5937eaba --- /dev/null +++ b/pkg/label/doc.go @@ -0,0 +1,20 @@ +// 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 label provides support for parsing a set of supported labels from a comma-separated list of values in the +// form =. The Set container is used to store the supported labels. A Set can be serialized (using +// the pre-defined aforementioned comma-separated format) and written to a generic destination through the Set.Write +// method. +package label diff --git a/pkg/label/label.go b/pkg/label/label.go new file mode 100644 index 00000000..81e47761 --- /dev/null +++ b/pkg/label/label.go @@ -0,0 +1,112 @@ +// 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 label + +import ( + "fmt" + "io" + "strconv" + "strings" +) + +const ( + testIndexKey = "testIndex" + procIndexKey = "procIndex" + isContainerKey = "isContainer" + imageNameKey = "imageName" + containerNameKey = "containerName" +) + +// Set stores values for the supported labels. +type Set struct { + TestIndex int + ProcIndex int + IsContainer bool + ImageName string + ContainerName string +} + +// Write writes the set to the provided writer. +func (s *Set) Write(w io.Writer) error { + _, err := fmt.Fprintf(w, "%s=%v,%s=%v,%s=%v,%s=%v,%s=%v", + testIndexKey, s.TestIndex, + procIndexKey, s.ProcIndex, + isContainerKey, s.IsContainer, + imageNameKey, s.ImageName, + containerNameKey, s.ContainerName) + return err +} + +// Clone creates and returns a clone of the set. +func (s *Set) Clone() *Set { + set := &Set{ + TestIndex: s.TestIndex, + ProcIndex: s.ProcIndex, + IsContainer: s.IsContainer, + ImageName: s.ImageName, + ContainerName: s.ContainerName, + } + return set +} + +// ParseSet parses the provided labels and returns the parsed set. If an empty string is provided, the function returns +// nil. +func ParseSet(labels string) (*Set, error) { + if labels == "" { + return nil, nil + } + + set := &Set{} + for _, label := range strings.Split(labels, ",") { + parts := strings.Split(label, "=") + if len(parts) != 2 { + continue + } + + key, value := parts[0], parts[1] + switch key { + case testIndexKey: + testIndex, err := strconv.Atoi(value) + if err != nil { + return nil, fmt.Errorf("error parsing testIndex: %w", err) + } + + set.TestIndex = testIndex + case procIndexKey: + testIndex, err := strconv.Atoi(value) + if err != nil { + return nil, fmt.Errorf("error parsing procIndex: %w", err) + } + + set.ProcIndex = testIndex + case isContainerKey: + isContainer, err := strconv.ParseBool(value) + if err != nil { + return nil, fmt.Errorf("error parsing isContainer: %w", err) + } + + set.IsContainer = isContainer + case imageNameKey: + set.ImageName = value + case containerNameKey: + set.ContainerName = value + default: + return nil, fmt.Errorf("unknown label %q", key) + } + } + + return set, nil +} From 638067bd8620a48e41d0a307dff8e9ec3d44dcc1 Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 26 Nov 2024 17:17:29 +0100 Subject: [PATCH 7/8] feat(decl): enhance process labels propagation system Enhance process labels propagation system to propagate container image and name information. Signed-off-by: Leonardo Di Giovanna Co-authored-by: Aldo Lacuku --- cmd/declarative/config/config.go | 43 ++++++++------- cmd/declarative/test/test.go | 89 ++++++++++++-------------------- pkg/test/runner/host/host.go | 69 +++++++++++++------------ pkg/test/runner/runner.go | 10 ++-- 4 files changed, 97 insertions(+), 114 deletions(-) diff --git a/cmd/declarative/config/config.go b/cmd/declarative/config/config.go index c0ca0de7..8f2b5a87 100644 --- a/cmd/declarative/config/config.go +++ b/cmd/declarative/config/config.go @@ -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" ) @@ -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 @@ -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 // - the last process in the process chain has the test ID in the form - // A process having a test ID in the form (i.e.: the leaf process) is the only one that is monitored. - TestID string - // ProcLabel is the process label in the form test,child. 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 @@ -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 (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 =. It is used + // for logging purposes and to potentially generate the child process/container labels. + Labels string } var containerImagePullPolicies = map[builder.ImagePullPolicy][]string{ @@ -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) @@ -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 . 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,child. "+ - "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") @@ -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 . 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 =. "+ + "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. diff --git a/cmd/declarative/test/test.go b/cmd/declarative/test/test.go index ba13818c..073827fb 100644 --- a/cmd/declarative/test/test.go +++ b/cmd/declarative/test/test.go @@ -21,8 +21,6 @@ import ( "fmt" "os" "path/filepath" - "regexp" - "strconv" "strings" "sync" "time" @@ -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" @@ -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 { @@ -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 @@ -292,6 +293,7 @@ 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, @@ -299,8 +301,8 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) { 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 { @@ -308,12 +310,6 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) { 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 { @@ -352,45 +348,26 @@ 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 +// enrichLogger creates a new logger, starting from the provided one, with the information extracted from the provided +// labels. +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 diff --git a/pkg/test/runner/host/host.go b/pkg/test/runner/host/host.go index 5aa07886..78c2e422 100644 --- a/pkg/test/runner/host/host.go +++ b/pkg/test/runner/host/host.go @@ -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" @@ -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{ @@ -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. @@ -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) } @@ -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. @@ -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 } @@ -227,27 +237,13 @@ 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, it returns -// testName,child. -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. @@ -255,8 +251,15 @@ func (r *hostRunner) delegateToProcess(ctx context.Context, testID string, testI 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) } diff --git a/pkg/test/runner/runner.go b/pkg/test/runner/runner.go index 37c057fd..5431d2a5 100644 --- a/pkg/test/runner/runner.go +++ b/pkg/test/runner/runner.go @@ -20,6 +20,7 @@ import ( "github.com/go-logr/logr" + "github.com/falcosecurity/event-generator/pkg/label" "github.com/falcosecurity/event-generator/pkg/test/loader" ) @@ -50,9 +51,8 @@ type Description struct { TestIDEnvKey string // TestIDIgnorePrefix is the optional testID prefix value. TestIDIgnorePrefix string - // ProcLabelEnvKey is the key identifying the environment variable used to store the process label in the form - // "test,child". - ProcLabelEnvKey string - // ProcLabel is the current process label. - ProcLabel string + // LabelsEnvKey is the key identifying the environment variable used to store the labels. + LabelsEnvKey string + // Labels is the set of process labels. + Labels *label.Set } From d70a938ae01e918197afcae7b07a14e554fc6a44 Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 26 Nov 2024 18:07:25 +0100 Subject: [PATCH 8/8] feat(decl/cmd/test): omit default process in containerized tests Omit default process injection if the test must be run inside a container, since spawning a container already involves a process creation. Signed-off-by: Leonardo Di Giovanna Co-authored-by: Aldo Lacuku --- cmd/declarative/test/test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/declarative/test/test.go b/cmd/declarative/test/test.go index 073827fb..00be7e5d 100644 --- a/cmd/declarative/test/test.go +++ b/cmd/declarative/test/test.go @@ -279,7 +279,7 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) { if testDesc.Context == nil { testDesc.Context = &loader.TestContext{} } - if len(testDesc.Context.Processes) == 0 { + if len(testDesc.Context.Processes) == 0 && testDesc.Context.Container == nil { testDesc.Context.Processes = []loader.ProcessContext{{}} } } else {