diff --git a/cmd/artifact/install/install.go b/cmd/artifact/install/install.go index e0dfa09e6..36ecf655e 100644 --- a/cmd/artifact/install/install.go +++ b/cmd/artifact/install/install.go @@ -327,7 +327,7 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args [] return err } // Extract artifact and move it to its destination directory - _, err = utils.ExtractTarGz(f, destDir) + _, err = utils.ExtractTarGz(f, destDir, 0) if err != nil { return fmt.Errorf("cannot extract %q to %q: %w", result.Filename, destDir, err) } diff --git a/cmd/driver/cleanup/cleanup.go b/cmd/driver/cleanup/cleanup.go new file mode 100644 index 000000000..32b12092e --- /dev/null +++ b/cmd/driver/cleanup/cleanup.go @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 drivercleanup + +import ( + "github.com/spf13/cobra" + "golang.org/x/net/context" + + "github.com/falcosecurity/falcoctl/internal/config" + "github.com/falcosecurity/falcoctl/pkg/options" +) + +type driverCleanupOptions struct { + *options.Common +} + +// NewDriverCleanupCmd cleans a driver up. +func NewDriverCleanupCmd(ctx context.Context, opt *options.Common) *cobra.Command { + o := driverCleanupOptions{ + Common: opt, + } + + cmd := &cobra.Command{ + Use: "cleanup [flags]", + DisableFlagsInUseLine: true, + Short: "Cleanup a driver", + Long: "Cleans a driver up, eg for kmod, by removing it from dkms.", + RunE: func(cmd *cobra.Command, args []string) error { + return o.RunDriverCleanup(ctx) + }, + } + return cmd +} + +func (o *driverCleanupOptions) RunDriverCleanup(_ context.Context) error { + driver, err := config.Driverer() + if err != nil { + return err + } + o.Printer.Logger.Info("Running falcoctl driver cleanup", o.Printer.Logger.Args( + "driver type", driver.Type, + "driver name", driver.Name)) + return driver.Type.Cleanup(o.Printer, driver.Name) +} diff --git a/cmd/driver/cleanup/doc.go b/cmd/driver/cleanup/doc.go new file mode 100644 index 000000000..7badc1293 --- /dev/null +++ b/cmd/driver/cleanup/doc.go @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 drivercleanup defines the cleanup logic for the driver cmd. +package drivercleanup diff --git a/cmd/driver/config/config.go b/cmd/driver/config/config.go new file mode 100644 index 000000000..825a0a15b --- /dev/null +++ b/cmd/driver/config/config.go @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverconfig + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "golang.org/x/net/context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "github.com/falcosecurity/falcoctl/internal/config" + "github.com/falcosecurity/falcoctl/internal/utils" + driverdistro "github.com/falcosecurity/falcoctl/pkg/driver/distro" + driverkernel "github.com/falcosecurity/falcoctl/pkg/driver/kernel" + drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" + "github.com/falcosecurity/falcoctl/pkg/options" +) + +const ( + configMapDriverTypeKey = "driver_mode" +) + +type driverConfigOptions struct { + *options.Common + Type *options.DriverTypes + Version string + Repos []string + Name string + HostRoot string + Update bool + Namespace string + KubeConfig string +} + +// NewDriverConfigCmd configures a driver and stores it in config. +func NewDriverConfigCmd(ctx context.Context, opt *options.Common) *cobra.Command { + o := driverConfigOptions{ + Common: opt, + Type: options.NewDriverTypes(), + } + + cmd := &cobra.Command{ + Use: "config [flags]", + DisableFlagsInUseLine: true, + Short: "Configure a driver", + Long: "Configure a driver for future usages with other driver subcommands", + RunE: func(cmd *cobra.Command, args []string) error { + return o.RunDriverConfig(ctx, cmd) + }, + } + + cmd.Flags().Var(o.Type, "type", "Driver type to be configured "+o.Type.Allowed()) + cmd.Flags().StringVar(&o.Version, "version", config.DefaultDriver.Version, "Driver version to be configured.") + cmd.Flags().StringSliceVar(&o.Repos, "repo", config.DefaultDriver.Repos, "Driver repo to be configured.") + cmd.Flags().StringVar(&o.Name, "name", config.DefaultDriver.Name, "Driver name to be configured.") + cmd.Flags().StringVar(&o.HostRoot, "host-root", config.DefaultDriver.HostRoot, "Driver host root to be configured.") + cmd.Flags().BoolVar(&o.Update, "update-falco", true, "Whether to update Falco config/configmap.") + cmd.Flags().StringVar(&o.Namespace, "namespace", "", "Kubernetes namespace.") + cmd.Flags().StringVar(&o.KubeConfig, "kubeconfig", "", "Kubernetes config.") + return cmd +} + +// RunDriverConfig implements the driver configuration command. +func (o *driverConfigOptions) RunDriverConfig(ctx context.Context, cmd *cobra.Command) error { + var ( + dType drivertype.DriverType + err error + ) + + driverCfg, err := config.Driverer() + if err != nil { + return err + } + + loggerArgs := make([]pterm.LoggerArgument, 0) + if f := cmd.Flags().Lookup("version"); f != nil && f.Changed { + driverCfg.Version = o.Version + loggerArgs = append(loggerArgs, pterm.LoggerArgument{ + Key: "driver version", + Value: o.Version, + }) + } + if f := cmd.Flags().Lookup("repo"); f != nil && f.Changed { + driverCfg.Repos = o.Repos + loggerArgs = append(loggerArgs, pterm.LoggerArgument{ + Key: "driver repos", + Value: strings.Join(o.Repos, ","), + }) + } + if f := cmd.Flags().Lookup("name"); f != nil && f.Changed { + driverCfg.Name = o.Name + loggerArgs = append(loggerArgs, pterm.LoggerArgument{ + Key: "driver name", + Value: o.Name, + }) + } + if f := cmd.Flags().Lookup("host-root"); f != nil && f.Changed { + driverCfg.HostRoot = o.HostRoot + loggerArgs = append(loggerArgs, pterm.LoggerArgument{ + Key: "driver host root", + Value: o.HostRoot, + }) + } + if f := cmd.Flags().Lookup("type"); f != nil && f.Changed { + loggerArgs = append(loggerArgs, pterm.LoggerArgument{ + Key: "driver type", + Value: o.Type.String(), + }) + if o.Type.String() != "auto" { + // Ok driver type was enforced by the user + dType, err = drivertype.Parse(o.Type.String()) + if err != nil { + return err + } + } else { + // automatic logic + info, err := driverkernel.FetchInfo("", "") + if err != nil { + return err + } + o.Printer.Logger.Debug("Fetched kernel info", o.Printer.Logger.Args( + "arch", info.Architecture.ToNonDeb(), + "kernel release", info.String(), + "kernel version", info.KernelVersion)) + + d, err := driverdistro.DiscoverDistro(info, driverCfg.HostRoot) + if err != nil { + return err + } + o.Printer.Logger.Debug("Discovered distro", o.Printer.Logger.Args("target", d)) + + dType = d.PreferredDriver(info) + if dType == nil { + return fmt.Errorf("automatic driver selection failed") + } + } + driverCfg.Type = dType + } + + o.Printer.Logger.Info("Running falcoctl driver config", loggerArgs) + + if o.Update { + err = commit(ctx, dType, driverCfg.HostRoot, o.Namespace, o.KubeConfig) + if err != nil { + return err + } + } + return config.StoreDriver(&driverCfg, o.ConfigFile) +} + +func replaceDriverTypeInFalcoConfig(hostRoot string, driverType drivertype.DriverType) error { + return utils.ReplaceLineInFile(hostRoot+"/etc/falco/falco.yaml", "driver_mode:", "driver_mode: "+driverType.String(), 1) +} + +func replaceDriverTypeInK8SConfigMap(ctx context.Context, namespace, kubeconfig string, driverType drivertype.DriverType) error { + var ( + err error + cfg *rest.Config + ) + + if kubeconfig != "" { + cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig) + } else { + cfg, err = rest.InClusterConfig() + } + if err != nil { + return err + } + + cl, err := kubernetes.NewForConfig(cfg) + if err != nil { + return err + } + + configMapList, err := cl.CoreV1().ConfigMaps(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: "app.kubernetes.io/instance: falco", + }) + if err != nil { + return err + } + if configMapList.Size() == 0 { + return fmt.Errorf(`no configmaps matching "app.kubernetes.io/instance: falco" label were found`) + } + + type patchDriverTypeValue struct { + Op string `json:"op"` + Path string `json:"path"` + Value string `json:"value"` + } + payload := []patchDriverTypeValue{{ + Op: "replace", + Path: "/data/" + configMapDriverTypeKey, + Value: driverType.String(), + }} + plBytes, _ := json.Marshal(payload) + + for i := 0; i < configMapList.Size(); i++ { + configMap := configMapList.Items[i] + // Modify the data in the ConfigMap ONLY if driver_mode is NOT set to plugin + // TODO: we must be sure that we are modifying the configmap for a Falco + // that is running with drivers, and not plugins. + // Scenario: user has multiple Falco pods deployed in its cluster, one running with driver, + // other running with plugins. We must only touch the one running with driver. + if val, ok := configMap.Data[configMapDriverTypeKey]; !ok || val == "none" { + continue + } + + // Patch the configMap + if _, err = cl.CoreV1().ConfigMaps(configMap.Namespace).Patch( + ctx, configMap.Name, types.JSONPatchType, plBytes, metav1.PatchOptions{}); err != nil { + return err + } + } + return nil +} + +// commit saves the updated driver type to Falco config, +// either to the local falco.yaml or updating the deployment configmap. +func commit(ctx context.Context, driverType drivertype.DriverType, hostroot, namespace, kubeconfig string) error { + if namespace != "" { + // Ok we are on k8s + return replaceDriverTypeInK8SConfigMap(ctx, namespace, kubeconfig, driverType) + } + return replaceDriverTypeInFalcoConfig(hostroot, driverType) +} diff --git a/cmd/driver/config/doc.go b/cmd/driver/config/doc.go new file mode 100644 index 000000000..dfbb62b9c --- /dev/null +++ b/cmd/driver/config/doc.go @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverconfig defines the configure logic for the driver cmd. +package driverconfig diff --git a/cmd/driver/driver_linux.go b/cmd/driver/driver_linux.go new file mode 100644 index 000000000..945b05c2d --- /dev/null +++ b/cmd/driver/driver_linux.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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. + +//go:build linux + +// Package driver implements the driver related cmd line interface. +package driver + +import ( + "context" + + "github.com/spf13/cobra" + + drivercleanup "github.com/falcosecurity/falcoctl/cmd/driver/cleanup" + driverconfig "github.com/falcosecurity/falcoctl/cmd/driver/config" + driverinstall "github.com/falcosecurity/falcoctl/cmd/driver/install" + driverprintenv "github.com/falcosecurity/falcoctl/cmd/driver/printenv" + "github.com/falcosecurity/falcoctl/internal/config" + commonoptions "github.com/falcosecurity/falcoctl/pkg/options" +) + +// NewDriverCmd returns the driver command. +func NewDriverCmd(ctx context.Context, opt *commonoptions.Common) *cobra.Command { + cmd := &cobra.Command{ + Use: "driver", + DisableFlagsInUseLine: true, + Short: "Interact with falcosecurity driver", + Long: "Interact with falcosecurity driver", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + opt.Initialize() + return config.Load(opt.ConfigFile) + }, + } + + cmd.AddCommand(driverinstall.NewDriverInstallCmd(ctx, opt)) + cmd.AddCommand(driverconfig.NewDriverConfigCmd(ctx, opt)) + cmd.AddCommand(drivercleanup.NewDriverCleanupCmd(ctx, opt)) + cmd.AddCommand(driverprintenv.NewDriverPrintenvCmd(ctx, opt)) + return cmd +} diff --git a/cmd/driver/driver_others.go b/cmd/driver/driver_others.go new file mode 100644 index 000000000..871dbc3cf --- /dev/null +++ b/cmd/driver/driver_others.go @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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. + +//go:build !linux + +package driver + +import ( + "github.com/spf13/cobra" + "golang.org/x/net/context" + + commonoptions "github.com/falcosecurity/falcoctl/pkg/options" +) + +// NewDriverCmd returns an empty driver command since it is not supported on non linuxes +func NewDriverCmd(ctx context.Context, opt *commonoptions.Common) *cobra.Command { + return &cobra.Command{} +} diff --git a/cmd/driver/install/doc.go b/cmd/driver/install/doc.go new file mode 100644 index 000000000..2b2f6a9d2 --- /dev/null +++ b/cmd/driver/install/doc.go @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverinstall defines the installation logic for the driver cmd. +package driverinstall diff --git a/cmd/driver/install/install.go b/cmd/driver/install/install.go new file mode 100644 index 000000000..16f9ecdba --- /dev/null +++ b/cmd/driver/install/install.go @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverinstall + +import ( + "crypto/tls" + "errors" + "fmt" + "net/http" + "path/filepath" + "strings" + "time" + + "github.com/blang/semver" + "github.com/spf13/cobra" + "golang.org/x/net/context" + + "github.com/falcosecurity/falcoctl/internal/config" + driverdistro "github.com/falcosecurity/falcoctl/pkg/driver/distro" + driverkernel "github.com/falcosecurity/falcoctl/pkg/driver/kernel" + "github.com/falcosecurity/falcoctl/pkg/options" +) + +type driverDownloadOptions struct { + InsecureDownload bool + HTTPTimeout time.Duration +} + +type driverInstallOptions struct { + *options.Common + Download bool + Compile bool + DriverKernelRelease string + DriverKernelVersion string + driverDownloadOptions +} + +// NewDriverInstallCmd returns the driver install command. +func NewDriverInstallCmd(ctx context.Context, opt *options.Common) *cobra.Command { + o := driverInstallOptions{ + Common: opt, + // Defaults to downloading or building if needed + Download: true, + Compile: true, + } + + cmd := &cobra.Command{ + Use: "install [flags]", + DisableFlagsInUseLine: true, + Short: "Install previously configured driver", + Long: "Install previously configured driver, either downloading it or attempting a build", + RunE: func(cmd *cobra.Command, args []string) error { + driver, err := config.Driverer() + if err != nil { + return err + } + // If empty, try to load it automatically from /usr/src sub folders, + // using the most recent (ie: the one with greatest semver) driver version. + if driver.Version == "" { + driver.Version = loadDriverVersion() + } + dest, err := o.RunDriverInstall(ctx, &driver) + if dest != "" { + // We don't care about errors at this stage + // Fallback: try to load any available driver if leaving with an error. + // It is only useful for kmod, as it will try to + // modprobe a pre-existent version of the driver, + // hoping it will be compatible. + _ = driver.Type.Load(o.Printer, dest, err != nil) + } + return err + }, + } + + cmd.Flags().BoolVar(&o.Download, "download", true, "Whether to enable download of prebuilt drivers") + cmd.Flags().BoolVar(&o.Compile, "compile", true, "Whether to enable local compilation of drivers") + cmd.Flags().StringVar(&o.DriverKernelRelease, + "kernelrelease", + "", + "Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' "+ + "(e.g. '6.1.0-10-cloud-amd64')") + cmd.Flags().StringVar(&o.DriverKernelVersion, + "kernelversion", + "", + "Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' "+ + "(e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')") + cmd.Flags().BoolVar(&o.InsecureDownload, "http-insecure", false, "Whether you want to allow insecure downloads or not") + cmd.Flags().DurationVar(&o.HTTPTimeout, "http-timeout", 60*time.Second, "Timeout for each http try") + return cmd +} + +func loadDriverVersion() string { + isSet := false + greatestVrs := semver.Version{} + paths, _ := filepath.Glob("/usr/src/falco-*+driver") + for _, path := range paths { + drvVer := strings.TrimPrefix(filepath.Base(path), "falco-") + sv, err := semver.Parse(drvVer) + if err != nil { + continue + } + if sv.GT(greatestVrs) { + greatestVrs = sv + isSet = true + } + } + if isSet { + return greatestVrs.String() + } + return "" +} + +//nolint:gosec // this was an existent option in falco-driver-loader that we are porting. +func setDefaultHTTPClientOpts(downloadOptions driverDownloadOptions) { + // Skip insecure verify + if downloadOptions.InsecureDownload { + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } + http.DefaultClient.Timeout = downloadOptions.HTTPTimeout +} + +// RunDriverInstall implements the driver install command. +func (o *driverInstallOptions) RunDriverInstall(ctx context.Context, driver *config.Driver) (string, error) { + kr, err := driverkernel.FetchInfo(o.DriverKernelRelease, o.DriverKernelVersion) + if err != nil { + return "", err + } + + o.Printer.Logger.Info("Running falcoctl driver install", o.Printer.Logger.Args( + "driver version", driver.Version, + "driver type", driver.Type, + "driver name", driver.Name, + "compile", o.Compile, + "download", o.Download, + "arch", kr.Architecture.ToNonDeb(), + "kernel release", kr.String(), + "kernel version", kr.KernelVersion)) + + if !driver.Type.HasArtifacts() { + o.Printer.Logger.Info("No artifacts needed for the selected driver.") + return "", nil + } + + if !o.Download && !o.Compile { + o.Printer.Logger.Info("Nothing to do: download and compile disabled.") + return "", nil + } + + d, err := driverdistro.DiscoverDistro(kr, driver.HostRoot) + if err != nil { + if errors.Is(err, driverdistro.ErrUnsupported) && o.Compile { + o.Download = false + o.Printer.Logger.Info( + "Detected an unsupported target system, please get in touch with the Falco community. Trying to compile anyway.") + } else { + return "", fmt.Errorf("detected an unsupported target system, please get in touch with the Falco community") + } + } + o.Printer.Logger.Info("found distro", o.Printer.Logger.Args("target", d)) + + err = driver.Type.Cleanup(o.Printer, driver.Name) + if err != nil { + return "", err + } + + setDefaultHTTPClientOpts(o.driverDownloadOptions) + + var dest string + if o.Download { + dest, err = driverdistro.Download(ctx, d, o.Printer, kr, driver.Name, driver.Type, driver.Version, driver.Repos) + if err == nil { + return dest, nil + } + // Print the error but go on + // attempting a build if requested + o.Printer.Logger.Warn(err.Error()) + } + + if o.Compile { + dest, err = driverdistro.Build(ctx, d, o.Printer, kr, driver.Name, driver.Type, driver.Version, driver.HostRoot) + if err == nil { + return dest, nil + } + o.Printer.Logger.Warn(err.Error()) + } + + return driver.Name, fmt.Errorf("failed: %w", err) +} diff --git a/cmd/driver/printenv/doc.go b/cmd/driver/printenv/doc.go new file mode 100644 index 000000000..255e917c7 --- /dev/null +++ b/cmd/driver/printenv/doc.go @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverprintenv defines the logic to print driver-related variables as env vars. +package driverprintenv diff --git a/cmd/driver/printenv/printenv.go b/cmd/driver/printenv/printenv.go new file mode 100644 index 000000000..50b1ec9c0 --- /dev/null +++ b/cmd/driver/printenv/printenv.go @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverprintenv + +import ( + "strings" + + "github.com/spf13/cobra" + "golang.org/x/net/context" + + "github.com/falcosecurity/falcoctl/internal/config" + driverdistro "github.com/falcosecurity/falcoctl/pkg/driver/distro" + driverkernel "github.com/falcosecurity/falcoctl/pkg/driver/kernel" + "github.com/falcosecurity/falcoctl/pkg/options" +) + +type driverPrintenvOptions struct { + *options.Common +} + +// NewDriverPrintenvCmd print info about driver falcoctl config as env vars. +func NewDriverPrintenvCmd(ctx context.Context, opt *options.Common) *cobra.Command { + o := driverPrintenvOptions{ + Common: opt, + } + + cmd := &cobra.Command{ + Use: "printenv [flags]", + DisableFlagsInUseLine: true, + Short: "Print env vars", + Long: "Print variables used by driver as env vars.", + RunE: func(cmd *cobra.Command, args []string) error { + return o.RunDriverPrintenv(ctx) + }, + } + return cmd +} + +func (o *driverPrintenvOptions) RunDriverPrintenv(_ context.Context) error { + driver, err := config.Driverer() + if err != nil { + return err + } + o.Printer.DefaultText.Printf("DRIVER=%q\n", driver.Type.String()) + o.Printer.DefaultText.Printf("DRIVERS_REPO=%q\n", strings.Join(driver.Repos, ", ")) + o.Printer.DefaultText.Printf("DRIVER_VERSION=%q\n", driver.Version) + o.Printer.DefaultText.Printf("DRIVER_NAME=%q\n", driver.Name) + o.Printer.DefaultText.Printf("HOST_ROOT=%q\n", driver.HostRoot) + + kr, err := driverkernel.FetchInfo("", "") + if err != nil { + return err + } + + d, err := driverdistro.DiscoverDistro(kr, driver.HostRoot) + if err != nil { + return err + } + o.Printer.DefaultText.Printf("TARGET_ID=%q\n", d.String()) + + fixedKr := d.FixupKernel(kr) + o.Printer.DefaultText.Printf("ARCH=%q\n", fixedKr.Architecture.ToNonDeb()) + o.Printer.DefaultText.Printf("KERNEL_RELEASE=%q\n", fixedKr.String()) + o.Printer.DefaultText.Printf("KERNEL_VERSION=%q\n", fixedKr.KernelVersion) + + return nil +} diff --git a/cmd/root.go b/cmd/root.go index 7a2d6f084..4dbbc0e37 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/falcosecurity/falcoctl/cmd/artifact" + "github.com/falcosecurity/falcoctl/cmd/driver" "github.com/falcosecurity/falcoctl/cmd/index" "github.com/falcosecurity/falcoctl/cmd/registry" "github.com/falcosecurity/falcoctl/cmd/tls" @@ -68,6 +69,7 @@ func New(ctx context.Context, opt *options.Common) *cobra.Command { rootCmd.AddCommand(registry.NewRegistryCmd(ctx, opt)) rootCmd.AddCommand(index.NewIndexCmd(ctx, opt)) rootCmd.AddCommand(artifact.NewArtifactCmd(ctx, opt)) + rootCmd.AddCommand(driver.NewDriverCmd(ctx, opt)) return rootCmd } diff --git a/cmd/root_test.go b/cmd/root_test.go index d95cbd5f3..675675f9f 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -17,6 +17,7 @@ package cmd_test import ( "context" + "runtime" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -27,7 +28,7 @@ import ( commonoptions "github.com/falcosecurity/falcoctl/pkg/options" ) -var usage = ` +var usageLinux = ` __ _ _ _ / _| __ _| | ___ ___ ___| |_| | | |_ / _ | |/ __/ _ \ / __| __| | @@ -43,6 +44,7 @@ Usage: Available Commands: artifact Interact with Falco artifacts completion Generate the autocompletion script for the specified shell + driver Interact with falcosecurity driver help Help about any command index Interact with index registry Interact with OCI registries @@ -58,6 +60,44 @@ Flags: Use "falcoctl [command] --help" for more information about a command. ` +var usageOthers = ` + __ _ _ _ + / _| __ _| | ___ ___ ___| |_| | + | |_ / _ | |/ __/ _ \ / __| __| | + | _| (_| | | (_| (_) | (__| |_| | + |_| \__,_|_|\___\___/ \___|\__|_| + + +The official CLI tool for working with Falco and its ecosystem components + +Usage: + falcoctl [command] + +Available Commands: + artifact Interact with Falco artifacts + completion Generate the autocompletion script for the specified shell + help Help about any command + index Interact with index + registry Interact with OCI registries + tls Generate and install TLS material for Falco + version Print the falcoctl version information + +Flags: + --config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml") + -h, --help help for falcoctl + --log-format string Set formatting for logs (color, text, json) (default "color") + --log-level string Set level for logs (info, warn, debug, trace) (default "info") + +Use "falcoctl [command] --help" for more information about a command. +` + +func getUsage() string { + if runtime.GOOS == "linux" { + return usageLinux + } + return usageOthers +} + var _ = Describe("Root", func() { var ( rootCmd *cobra.Command @@ -93,7 +133,7 @@ var _ = Describe("Root", func() { It("Should print the usage message", func() { Expect(err).ShouldNot(HaveOccurred()) - Expect(string(outputBuf.Contents())).Should(Equal(usage)) + Expect(string(outputBuf.Contents())).Should(Equal(getUsage())) }) }) @@ -105,7 +145,7 @@ var _ = Describe("Root", func() { It("Should print the usage message", func() { Expect(err).ShouldNot(HaveOccurred()) - Expect(string(outputBuf.Contents())).Should(Equal(usage)) + Expect(string(outputBuf.Contents())).Should(Equal(getUsage())) }) }) @@ -117,7 +157,7 @@ var _ = Describe("Root", func() { It("Should print the usage message", func() { Expect(err).ShouldNot(HaveOccurred()) - Expect(string(outputBuf.Contents())).Should(Equal(usage)) + Expect(string(outputBuf.Contents())).Should(Equal(getUsage())) }) }) diff --git a/go.mod b/go.mod index e4a09b240..fcef76cf2 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/distribution/distribution/v3 v3.0.0-20230608105614-4501a6e06d3b github.com/docker/cli v24.0.7+incompatible github.com/docker/docker v24.0.7+incompatible + github.com/falcosecurity/driverkit v0.15.5-0.20231108173325-1babd00be84f github.com/go-oauth2/oauth2/v4 v4.5.2 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-containerregistry v0.16.1 @@ -27,8 +28,12 @@ require ( golang.org/x/crypto v0.14.0 golang.org/x/exp v0.0.0-20231006140011-7918f672742d google.golang.org/api v0.149.0 + gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 - oras.land/oras-go/v2 v2.2.1 + k8s.io/apimachinery v0.28.3 + k8s.io/client-go v0.28.3 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b + oras.land/oras-go/v2 v2.3.0 ) require ( @@ -276,15 +281,11 @@ require ( gopkg.in/DataDog/dd-trace-go.v1 v1.56.1 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect k8s.io/api v0.28.3 // indirect - k8s.io/apimachinery v0.28.3 // indirect - k8s.io/client-go v0.28.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/release-utils v0.7.6 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect @@ -296,8 +297,8 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-isatty v0.0.20 github.com/stretchr/testify v1.8.4 - golang.org/x/net v0.17.0 // indirect + golang.org/x/net v0.17.0 golang.org/x/oauth2 v0.13.0 - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.13.0 golang.org/x/term v0.13.0 ) diff --git a/go.sum b/go.sum index 1833a9b60..0696133a1 100644 --- a/go.sum +++ b/go.sum @@ -333,6 +333,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/falcosecurity/driverkit v0.15.5-0.20231108173325-1babd00be84f h1:J18YO8qW1vHbFpue+ga0KS8vjXmF7Wkqd2juqFotcB0= +github.com/falcosecurity/driverkit v0.15.5-0.20231108173325-1babd00be84f/go.mod h1:vGGEx4jQFuTCYdPn70Pb7d3PjrgBULCKhOlW/serJTw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= @@ -1548,6 +1550,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1571,8 +1575,8 @@ k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/A k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go/v2 v2.2.1 h1:3VJTYqy5KfelEF9c2jo1MLSpr+TM3mX8K42wzZcd6qE= -oras.land/oras-go/v2 v2.2.1/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= +oras.land/oras-go/v2 v2.3.0 h1:lqX1aXdN+DAmDTKjiDyvq85cIaI4RkIKp/PghWlAGIU= +oras.land/oras-go/v2 v2.3.0/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/config/config.go b/internal/config/config.go index e1ff5e64a..5cb06208e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -30,6 +30,7 @@ import ( "github.com/mitchellh/mapstructure" "github.com/spf13/viper" + drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" "github.com/falcosecurity/falcoctl/pkg/oci" ) @@ -48,6 +49,8 @@ var ( DefaultIndex Index // DefaultRegistryCredentialConfPath is the default path for the credential store configuration file. DefaultRegistryCredentialConfPath = filepath.Join(config.Dir(), "config.json") + // DefaultDriver is the default config for the falcosecurity organization. + DefaultDriver driver // Useful regexps for parsing. @@ -109,6 +112,20 @@ const ( ArtifactAllowedTypesKey = "artifact.allowedTypes" // ArtifactNoVerifyKey is the Viper key for skipping signature verification. ArtifactNoVerifyKey = "artifact.noVerify" + + // DriverKey is the Viper key for driver structure. + DriverKey = "driver" + // DriverTypeKey is the Viper key for the driver type. + DriverTypeKey = "driver.type" + // DriverVersionKey is the Viper key for the driver version. + DriverVersionKey = "driver.version" + // DriverReposKey is the Viper key for the driver repositories. + DriverReposKey = "driver.repos" + // DriverNameKey is the Viper key for the driver name. + DriverNameKey = "driver.name" + // DriverHostRootKey is the Viper key for the driver host root. + DriverHostRootKey = "driver.hostRoot" + falcoHostRootEnvKey = "HOST_ROOT" ) // Index represents a configured index. @@ -158,6 +175,24 @@ type Install struct { NoVerify bool `mapstructure:"noVerify"` } +// driver represents the internal driver configuration (with Type string). +type driver struct { + Type string `mapstructure:"type"` + Name string `mapstructure:"name"` + Repos []string `mapstructure:"repos"` + Version string `mapstructure:"version"` + HostRoot string `mapstructure:"hostRoot"` +} + +// Driver represents the resolved driver configuration, exposed to be consumed. +type Driver struct { + Type drivertype.DriverType + Name string + Repos []string + Version string + HostRoot string +} + func init() { ConfigDir = filepath.Join(homedir.Get(), ".config") FalcoctlPath = filepath.Join(ConfigDir, "falcoctl") @@ -168,6 +203,13 @@ func init() { Name: "falcosecurity", URL: "https://falcosecurity.github.io/falcoctl/index.yaml", } + DefaultDriver = driver{ + Type: drivertype.TypeKmod, + Name: "falco", + Repos: []string{"https://download.falco.org/driver"}, + Version: "", + HostRoot: "", + } } // Load is used to load the config file. @@ -188,6 +230,8 @@ func Load(path string) error { viper.SetDefault(IndexesKey, []Index{DefaultIndex}) // Set default registry auth config path viper.SetDefault(RegistryCredentialConfigKey, DefaultRegistryCredentialConfPath) + // Set default driver + viper.SetDefault(DriverKey, DefaultDriver) err = viper.ReadInConfig() if errors.As(err, &viper.ConfigFileNotFoundError{}) || os.IsNotExist(err) { @@ -500,6 +544,53 @@ func Installer() (Install, error) { }, nil } +// Driverer retrieves the driver section of the config file. +func Driverer() (Driver, error) { + drvTypeStr := viper.GetString(DriverTypeKey) + drvType, err := drivertype.Parse(drvTypeStr) + if err != nil { + return Driver{}, err + } + + // manage driver.Repos as ";" separated list. + repos := viper.GetStringSlice(DriverReposKey) + if len(repos) == 1 { // in this case it might come from the env + if !SemicolonSeparatedRegexp.MatchString(repos[0]) { + return Driver{}, fmt.Errorf("env variable not correctly set, should match %q, got %q", SemicolonSeparatedRegexp.String(), repos[0]) + } + repos = strings.Split(repos[0], ";") + } + + // Bind FALCOCTL_DRIVER_HOSTROOT key to HOST_ROOT, + // so that we manage Falco HOST_ROOT variable too. + _ = viper.BindEnv(DriverHostRootKey, falcoHostRootEnvKey) + + drvCfg := Driver{ + Type: drvType, + Name: viper.GetString(DriverNameKey), + Repos: repos, + Version: viper.GetString(DriverVersionKey), + HostRoot: viper.GetString(DriverHostRootKey), + } + return drvCfg, nil +} + +// StoreDriver stores a driver conf in config file. +func StoreDriver(driverCfg *Driver, configFile string) error { + drvCfg := driver{ + Type: driverCfg.Type.String(), + Name: driverCfg.Name, + Repos: driverCfg.Repos, + Version: driverCfg.Version, + HostRoot: driverCfg.HostRoot, + } + + if err := UpdateConfigFile(DriverKey, drvCfg, configFile); err != nil { + return fmt.Errorf("unable to update driver in the config file %q: %w", configFile, err) + } + return nil +} + // ArtifactAllowedTypes retrieves the allowed types section of the config file. func ArtifactAllowedTypes() (*oci.ArtifactTypeSlice, error) { allowedTypes := viper.GetStringSlice(ArtifactAllowedTypesKey) diff --git a/internal/cosign/verify.go b/internal/cosign/verify.go index 0db8a1dbb..ef0f195a3 100644 --- a/internal/cosign/verify.go +++ b/internal/cosign/verify.go @@ -1,6 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -// -// Copyright 2023 The Sigstore Authors. +// Copyright (C) 2023 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. diff --git a/internal/follower/follower.go b/internal/follower/follower.go index 4291bb2a1..354a759fe 100644 --- a/internal/follower/follower.go +++ b/internal/follower/follower.go @@ -208,7 +208,7 @@ func (f *Follower) follow(ctx context.Context) { dstPath := filepath.Join(dstDir, baseName) // Check if the file exists. f.logger.Debug("Checking if file already exists", f.logger.Args("followerName", f.ref, "fileName", baseName, "directory", dstDir)) - exists, err := fileExists(dstPath) + exists, err := utils.FileExists(dstPath) if err != nil { f.logger.Error("Unable to check existence for file", f.logger.Args("followerName", f.ref, "fileName", baseName, "reason", err.Error())) return @@ -289,7 +289,7 @@ func (f *Follower) pull(ctx context.Context) (filePaths []string, res *oci.Regis } // Extract artifact and move it to its destination directory - filePaths, err = utils.ExtractTarGz(file, f.tmpDir) + filePaths, err = utils.ExtractTarGz(file, f.tmpDir, 0) if err != nil { return filePaths, res, fmt.Errorf("unable to extract %q to %q: %w", res.Filename, f.tmpDir, err) } @@ -368,20 +368,6 @@ func (f *Follower) cleanUp() { } } -// fileExists checks if a file exists on disk. -func fileExists(filename string) (bool, error) { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false, nil - } - - if err != nil { - return false, err - } - - return !info.IsDir(), nil -} - // equal checks if the two files are equal by comparing their sha256 hashes. func equal(files []string) (bool, error) { var hashes []string diff --git a/internal/utils/extract.go b/internal/utils/extract.go index 404b14468..92e314211 100644 --- a/internal/utils/extract.go +++ b/internal/utils/extract.go @@ -28,7 +28,7 @@ import ( // ExtractTarGz extracts a *.tar.gz compressed archive and moves its content to destDir. // Returns a slice containing the full path of the extracted files. -func ExtractTarGz(gzipStream io.Reader, destDir string) ([]string, error) { +func ExtractTarGz(gzipStream io.Reader, destDir string, stripPathComponents int) ([]string, error) { var files []string uncompressedStream, err := gzip.NewReader(gzipStream) @@ -49,15 +49,21 @@ func ExtractTarGz(gzipStream io.Reader, destDir string) ([]string, error) { return nil, err } + if strings.Contains(header.Name, "..") { + return nil, fmt.Errorf("not allowed relative path in tar archive") + } + + strippedName := stripComponents(header.Name, stripPathComponents) + switch header.Typeflag { case tar.TypeDir: - return nil, fmt.Errorf("unexepected dir inside the archive, expected to find only files without any tree structure") - case tar.TypeReg: - if strings.Contains(header.Name, "..") { - return nil, fmt.Errorf("not allowed relative path in tar archive") + d := filepath.Join(destDir, strippedName) + if err = os.Mkdir(filepath.Clean(d), 0o750); err != nil { + return nil, err } - - f := filepath.Join(destDir, filepath.Clean(header.Name)) + files = append(files, d) + case tar.TypeReg: + f := filepath.Join(destDir, strippedName) outFile, err := os.Create(filepath.Clean(f)) if err != nil { return nil, err @@ -79,3 +85,16 @@ func ExtractTarGz(gzipStream io.Reader, destDir string) ([]string, error) { return files, nil } + +func stripComponents(headerName string, stripComponents int) string { + if stripComponents == 0 { + return headerName + } + names := strings.FieldsFunc(headerName, func(r rune) bool { + return r == os.PathSeparator + }) + if len(names) < stripComponents { + return headerName + } + return filepath.Clean(strings.Join(names[stripComponents:], "/")) +} diff --git a/internal/utils/file.go b/internal/utils/file.go new file mode 100644 index 000000000..2bac2aad3 --- /dev/null +++ b/internal/utils/file.go @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 utils + +import ( + "log" + "os" + "path/filepath" + "strings" +) + +// ReplaceLineInFile searches for occurrences of searchFor in the file pointed by filePath, +// and substitutes the matching line with the provided one. +// At most n substitutions are made. +// If n < 0, there is no limit on the number of replacements. +func ReplaceLineInFile(filePath, searchFor, newLine string, n int) error { + stat, err := os.Stat(filePath) + if err != nil { + return err + } + + input, err := os.ReadFile(filepath.Clean(filePath)) + if err != nil { + log.Fatalln(err) + } + + lines := strings.Split(string(input), "\n") + + replaced := 0 + for i, line := range lines { + if strings.Contains(line, searchFor) { + lines[i] = newLine + replaced++ + if replaced == n { + break + } + } + } + newContent := strings.Join(lines, "\n") + return os.WriteFile(filePath, []byte(newContent), stat.Mode()) +} + +// FileExists checks if a file exists on disk. +func FileExists(filename string) (bool, error) { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false, nil + } + + if err != nil { + return false, err + } + + return !info.IsDir(), nil +} diff --git a/pkg/driver/distro/amzn.go b/pkg/driver/distro/amzn.go new file mode 100644 index 000000000..4f3a487d6 --- /dev/null +++ b/pkg/driver/distro/amzn.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import ( + "fmt" + + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "gopkg.in/ini.v1" +) + +func init() { + distros["amzn"] = &amzn{generic: &generic{}} +} + +type amzn struct { + *generic +} + +//nolint:gocritic // the method shall not be able to modify kr +func (a *amzn) init(kr kernelrelease.KernelRelease, _ string, cfg *ini.File) error { + idKey := cfg.Section("").Key("VERSION_ID") + if idKey == nil { + // OS-release without `VERSION_ID` (can it happen?) + return fmt.Errorf("no VERSION_ID present for amzn") + } + // overwrite id + newID := "" + switch idKey.String() { + case "2": + newID = "amazonlinux2" + case "2022": + newID = "amazonlinux2022" + case "2023": + newID = "amazonlinux2023" + default: + newID = "amazonlinux" + } + return a.generic.init(kr, newID, cfg) +} diff --git a/pkg/driver/distro/bottlerocket.go b/pkg/driver/distro/bottlerocket.go new file mode 100644 index 000000000..7638b5c52 --- /dev/null +++ b/pkg/driver/distro/bottlerocket.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import ( + "fmt" + "strings" + + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "gopkg.in/ini.v1" +) + +func init() { + distros["bottlerocket"] = &bottlerocket{generic: &generic{}} +} + +type bottlerocket struct { + *generic + variantID string + versionID string +} + +//nolint:gocritic // the method shall not be able to modify kr +func (b *bottlerocket) init(kr kernelrelease.KernelRelease, id string, cfg *ini.File) error { + idKey := cfg.Section("").Key("VERSION_ID") + if idKey == nil { + // OS-release without `VERSION_ID` (can it happen?) + return fmt.Errorf("no VERSION_ID present for bottlerocket") + } + b.versionID = idKey.String() + + idKey = cfg.Section("").Key("VARIANT_ID") + if idKey == nil { + return fmt.Errorf("no VARIANT_ID present for bottlerocket") + } + b.variantID = strings.Split(idKey.String(), "-")[0] + + return b.generic.init(kr, id, cfg) +} + +//nolint:gocritic // the method shall not be able to modify kr +func (b *bottlerocket) FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease { + kr.KernelVersion = fmt.Sprintf("1_%s-%s", b.versionID, b.variantID) + return kr +} diff --git a/pkg/driver/distro/centos.go b/pkg/driver/distro/centos.go new file mode 100644 index 000000000..0cd5e3866 --- /dev/null +++ b/pkg/driver/distro/centos.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import "github.com/falcosecurity/falcoctl/internal/utils" + +func init() { + distros["centos"] = ¢os{generic: &generic{}} +} + +type centos struct { + *generic +} + +func (c *centos) check(hostRoot string) bool { + exist, _ := utils.FileExists(hostRoot + "/etc/centos-release") + return exist +} diff --git a/pkg/driver/distro/cos.go b/pkg/driver/distro/cos.go new file mode 100644 index 000000000..1dcd87df8 --- /dev/null +++ b/pkg/driver/distro/cos.go @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import ( + "fmt" + "os" + + "github.com/blang/semver" + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" + "gopkg.in/ini.v1" + + "github.com/falcosecurity/falcoctl/internal/utils" + drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" + "github.com/falcosecurity/falcoctl/pkg/output" +) + +const ( + kbuildExtraCppFlagsEnv = "KBUILD_EXTRA_CPPFLAGS" + enableCos73Workaround = "-DCOS_73_WORKAROUND" +) + +func init() { + distros["cos"] = &cos{generic: &generic{}} +} + +type cos struct { + *generic + buildID string +} + +//nolint:gocritic // the method shall not be able to modify kr +func (c *cos) init(kr kernelrelease.KernelRelease, _ string, cfg *ini.File) error { + idKey := cfg.Section("").Key("BUILD_ID") + if idKey == nil { + // OS-release without `VERSION_ID` (can it happen?) + return fmt.Errorf("no BUILD_ID present for COS") + } + return c.generic.init(kr, idKey.String(), cfg) +} + +//nolint:gocritic // the method shall not be able to modify kr +func (c *cos) customizeBuild(ctx context.Context, + printer *output.Printer, + driverType drivertype.DriverType, + kr kernelrelease.KernelRelease, + hostRoot string, +) (map[string]string, error) { + switch driverType.String() { + case drivertype.TypeBpf: + break + default: + // nothing to do + return nil, nil + } + printer.Logger.Info("COS detected, using COS kernel headers.", printer.Logger.Args("build ID", c.buildID)) + bpfKernelSrcURL := fmt.Sprintf("https://storage.googleapis.com/cos-tools/%s/kernel-headers.tgz", c.buildID) + kr.Extraversion = "+" + env, err := downloadKernelSrc(ctx, printer, &kr, bpfKernelSrcURL, hostRoot, 0) + if err != nil { + return nil, err + } + + currKernelDir := env[kernelDirEnv] + + cosKernelDir := currKernelDir + "usr/src/" + entries, err := os.ReadDir(cosKernelDir) + if err != nil { + return nil, err + } + if len(entries) == 0 { + return nil, fmt.Errorf("no COS kernel src found") + } + cosKernelDir = entries[0].Name() + // Override env key + env[kernelDirEnv] = cosKernelDir + + clangCompilerHeader := fmt.Sprintf("%s/include/linux/compiler-clang.h", cosKernelDir) + err = utils.ReplaceLineInFile(clangCompilerHeader, "#define randomized_struct_fields_start", "", 1) + if err != nil { + return nil, err + } + err = utils.ReplaceLineInFile(clangCompilerHeader, "#define randomized_struct_fields_end", "", 1) + if err != nil { + return nil, err + } + + baseVer, err := semver.Parse("11553.0.0") + if err != nil { + return nil, err + } + cosVer, err := semver.Parse(c.buildID) + if err != nil { + return nil, err + } + if cosVer.GT(baseVer) { + env[kbuildExtraCppFlagsEnv] = enableCos73Workaround + } + return env, nil +} diff --git a/pkg/driver/distro/debian.go b/pkg/driver/distro/debian.go new file mode 100644 index 000000000..4166bf86b --- /dev/null +++ b/pkg/driver/distro/debian.go @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import ( + "fmt" + "regexp" + + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + + "github.com/falcosecurity/falcoctl/internal/utils" + driverkernel "github.com/falcosecurity/falcoctl/pkg/driver/kernel" +) + +func init() { + distros["debian"] = &debian{generic: &generic{}} +} + +type debian struct { + *generic +} + +var debianKernelReleaseRegex = regexp.MustCompile(`-?(rt-|cloud-|)(amd64|arm64)`) +var debianKernelVersionRegex = regexp.MustCompile(`\d+\.\d+\.\d+-\d+`) + +func (d *debian) check(hostRoot string) bool { + exist, _ := utils.FileExists(hostRoot + "/etc/debian_version") + return exist +} + +//nolint:gocritic // the method shall not be able to modify kr +func (d *debian) FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease { + // Workaround: debian kernelreleases might not be actual kernel running; + // instead, they might be the Debian kernel package + // providing the compatible kernel ABI + // See https://lists.debian.org/debian-user/2017/03/msg00485.html + // Real kernel release is embedded inside the kernel version. + // Moreover, kernel arch, when present, is attached to the former, + // therefore make sure to properly take it and attach it to the latter. + // Moreover, we support 3 flavors for debian kernels: cloud, rt and normal. + // KERNEL-RELEASE will have a `-rt`, or `-cloud` if we are in one of these flavors. + // Manage it to download the correct driver. + // + // Example: KERNEL_RELEASE="5.10.0-0.deb10.22-rt-amd64" and `uname -v`="5.10.178-3" + // should lead to: KERNEL_RELEASE="5.10.178-3-rt-amd64" + archExtra := "" + if debianKernelReleaseRegex.MatchString(kr.FullExtraversion) { + matches := debianKernelReleaseRegex.FindStringSubmatch(kr.FullExtraversion) + // -rt-amd64 + archExtra = fmt.Sprintf("-%s%s", matches[1], matches[2]) + } + if debianKernelVersionRegex.MatchString(kr.KernelVersion) { + // Real kernel release becomes: "5.10.178-3-rt-amd64" + realKernelReleaseStr := fmt.Sprintf("%s%s", kr.KernelVersion, archExtra) + // Parse it once again to a KernelRelease struct + kr, _ = driverkernel.FetchInfo(realKernelReleaseStr, "1") + } + return kr +} diff --git a/pkg/driver/distro/distro.go b/pkg/driver/distro/distro.go new file mode 100644 index 000000000..43cfabacd --- /dev/null +++ b/pkg/driver/distro/distro.go @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro implements all the distro specific driver-related logic. +package driverdistro + +import ( + "archive/tar" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/homedir" + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" + "gopkg.in/ini.v1" + + "github.com/falcosecurity/falcoctl/internal/utils" + drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" + "github.com/falcosecurity/falcoctl/pkg/output" +) + +const ( + // DefaultFalcoRepo is the default repository provided by falcosecurity to download driver artifacts from. + kernelDirEnv = "KERNELDIR" + kernelSrcDownloadFolder = "kernel-sources" +) + +var distros = map[string]Distro{} + +// ErrUnsupported is the error returned when the target distro is not supported. +var ErrUnsupported = fmt.Errorf("failed to determine distro") + +// Distro is the common interface used by distro-specific implementations. +// Most of the distro-specific only partially override the default `generic` implementation. +type Distro interface { + init(kr kernelrelease.KernelRelease, id string, cfg *ini.File) error // private + FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease // private + customizeBuild(ctx context.Context, printer *output.Printer, driverType drivertype.DriverType, + kr kernelrelease.KernelRelease, hostRoot string) (map[string]string, error) + PreferredDriver(kr kernelrelease.KernelRelease) drivertype.DriverType + fmt.Stringer +} + +type checker interface { + check(hostRoot string) bool // private +} + +// DiscoverDistro tries to fetch the correct Distro by looking at /etc/os-release or +// by cycling on all supported distros and checking them one by one. +// +//nolint:gocritic // the method shall not be able to modify kr +func DiscoverDistro(kr kernelrelease.KernelRelease, hostRoot string) (Distro, error) { + distro, err := getOSReleaseDistro(&kr, hostRoot) + if err == nil { + return distro, nil + } + + // Fallback to check any distro checker + for id, d := range distros { + dd, ok := d.(checker) + if ok && dd.check(hostRoot) { + err = d.init(kr, id, nil) + return d, err + } + } + + // Return a generic distro to try the build + distro = &generic{} + if err = distro.init(kr, "undetermined", nil); err != nil { + return nil, err + } + return distro, ErrUnsupported +} + +func getOSReleaseDistro(kr *kernelrelease.KernelRelease, hostRoot string) (Distro, error) { + cfg, err := ini.Load(hostRoot + "/etc/os-release") + if err != nil { + return nil, err + } + idKey := cfg.Section("").Key("ID") + if idKey == nil { + // OS-release without `ID` (can it happen?) + return nil, nil + } + id := strings.ToLower(idKey.String()) + + // Overwrite the OS_ID if /etc/VERSION file is present. + // Not sure if there is a better way to detect minikube. + dd := distros["minikube"].(checker) + if dd.check(hostRoot) { + id = "minikube" + } + + distro, exist := distros[id] + if !exist { + distro = &generic{} + } + if err = distro.init(*kr, id, cfg); err != nil { + return nil, err + } + return distro, nil +} + +func toURL(repo, driverVer, fileName, arch string) string { + return fmt.Sprintf("%s/%s/%s/%s", repo, driverVer, arch, fileName) +} + +func toLocalPath(driverVer, fileName, arch string) string { + return fmt.Sprintf("%s/.falco/%s/%s/%s", homedir.Get(), driverVer, arch, fileName) +} + +func toFilename(d Distro, kr *kernelrelease.KernelRelease, driverName string, driverType drivertype.DriverType) string { + // Fixup kernel information before attempting to download + fixedKR := d.FixupKernel(*kr) + return fmt.Sprintf("%s_%s_%s_%s%s", driverName, d, fixedKR.String(), fixedKR.KernelVersion, driverType.Extension()) +} + +func copyDataToLocalPath(destination string, src io.Reader) error { + err := os.MkdirAll(filepath.Dir(destination), 0o750) + if err != nil { + return err + } + out, err := os.Create(filepath.Clean(destination)) + if err == nil { + defer out.Close() + _, err = io.Copy(out, src) + } + return err +} + +// Build will try to build the desired driver for the specified distro and kernel release. +// +//nolint:gocritic // the method shall not be able to modify kr +func Build(ctx context.Context, + d Distro, + printer *output.Printer, + kr kernelrelease.KernelRelease, + driverName string, + driverType drivertype.DriverType, + driverVer string, + hostRoot string, +) (string, error) { + env, err := d.customizeBuild(ctx, printer, driverType, kr, hostRoot) + if err != nil { + return "", err + } + path, err := driverType.Build(ctx, printer, d.FixupKernel(kr), driverName, driverVer, env) + if err != nil { + return "", err + } + // Copy the path to the expected location. + // NOTE: for kmod, this is not useful since the driver will + // be loaded directly by dkms. + driverFileName := toFilename(d, &kr, driverName, driverType) + filePath := toLocalPath(driverVer, driverFileName, kr.Architecture.ToNonDeb()) + printer.Logger.Info("Copying built driver to its destination.", printer.Logger.Args("src", path, "dst", filePath)) + f, err := os.Open(filepath.Clean(path)) + if err != nil { + return "", err + } + defer f.Close() + return filePath, copyDataToLocalPath(filePath, f) +} + +// Download will try to download drivers for a distro trying specified repos. +// +//nolint:gocritic // the method shall not be able to modify kr +func Download(ctx context.Context, + d Distro, + printer *output.Printer, + kr kernelrelease.KernelRelease, + driverName string, + driverType drivertype.DriverType, + driverVer string, repos []string, +) (string, error) { + driverFileName := toFilename(d, &kr, driverName, driverType) + // Skip if existent + destination := toLocalPath(driverVer, driverFileName, kr.Architecture.ToNonDeb()) + if exist, _ := utils.FileExists(destination); exist { + printer.Logger.Info("Skipping download, driver already present.", printer.Logger.Args("path", destination)) + return destination, nil + } + + // Try to download from any specified repository, + // stopping at first successful http GET. + for _, repo := range repos { + url := toURL(repo, driverVer, driverFileName, kr.Architecture.ToNonDeb()) + printer.Logger.Info("Trying to download a driver.", printer.Logger.Args("url", url)) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + printer.Logger.Warn("Error creating http request.", printer.Logger.Args("err", err)) + continue + } + resp, err := http.DefaultClient.Do(req) + if err != nil || resp.StatusCode != 200 { + if err == nil { + _ = resp.Body.Close() + } + printer.Logger.Warn("Error GETting url.", printer.Logger.Args("err", err)) + continue + } + return destination, copyDataToLocalPath(destination, resp.Body) + } + return destination, fmt.Errorf("unable to find a prebuilt driver") +} + +func customizeDownloadKernelSrcBuild(printer *output.Printer, kr *kernelrelease.KernelRelease) error { + printer.Logger.Info("Configuring kernel.") + if kr.Extraversion != "" { + err := utils.ReplaceLineInFile(".config", "LOCALVERSION=", "LOCALVERSION="+kr.Extraversion, 1) + if err != nil { + return err + } + } + _, err := exec.Command("bash", "-c", "make olddefconfig").Output() + if err == nil { + _, err = exec.Command("bash", "-c", "make modules_prepare").Output() + } + return err +} + +func getKernelConfig(printer *output.Printer, kr *kernelrelease.KernelRelease, hostRoot string) (string, error) { + bootConfig := fmt.Sprintf("/boot/config-%s", kr.String()) + hrBootConfig := fmt.Sprintf("%s%s", hostRoot, bootConfig) + ostreeConfig := fmt.Sprintf("/usr/lib/ostree-boot/config-%s", kr.String()) + hrostreeConfig := fmt.Sprintf("%s%s", hostRoot, ostreeConfig) + libModulesConfig := fmt.Sprintf("/lib/modules/%s/config", kr.String()) + + toBeChecked := []string{ + "/proc/config.gz", + bootConfig, + hrBootConfig, + ostreeConfig, + hrostreeConfig, + libModulesConfig, + } + + for _, path := range toBeChecked { + if exist, _ := utils.FileExists(path); exist { + printer.Logger.Info("Found kernel config.", printer.Logger.Args("path", path)) + return path, nil + } + } + return "", fmt.Errorf("cannot find kernel config") +} + +func downloadKernelSrc(ctx context.Context, + printer *output.Printer, + kr *kernelrelease.KernelRelease, + url string, hostRoot string, + stripComponents int, +) (map[string]string, error) { + env := make(map[string]string) + + printer.Logger.Info("Downloading kernel sources.", printer.Logger.Args("url", url)) + err := os.MkdirAll("/tmp/kernel", 0o750) + if err != nil { + return env, err + } + tempDir, err := os.MkdirTemp("/tmp/kernel", "") + if err != nil { + return env, err + } + + // Download the url + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody) + if err != nil { + return env, err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return env, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return env, fmt.Errorf("non-200 http GET status code") + } + + printer.Logger.Info("Extracting kernel sources.") + + fullKernelDir := filepath.Join(tempDir, kernelSrcDownloadFolder) + + err = os.Mkdir(fullKernelDir, 0o750) + if err != nil { + return env, err + } + + _, err = utils.ExtractTarGz(resp.Body, fullKernelDir, stripComponents) + if err != nil { + return env, err + } + + kernelConfigPath, err := getKernelConfig(printer, kr, hostRoot) + if err != nil { + return nil, err + } + dest, err := os.Create(".config") + if err != nil { + return nil, err + } + f, err := os.Open(filepath.Clean(kernelConfigPath)) + if err != nil { + return nil, err + } + var src io.Reader + if strings.HasSuffix(kernelConfigPath, ".gz") { + src = tar.NewReader(f) + } else { + src = f + } + fStat, err := f.Stat() + if err != nil { + return nil, err + } + _, err = io.CopyN(dest, src, fStat.Size()) + if err != nil { + return nil, err + } + env[kernelDirEnv] = fullKernelDir + return env, nil +} diff --git a/pkg/driver/distro/flatcar.go b/pkg/driver/distro/flatcar.go new file mode 100644 index 000000000..e0bd59919 --- /dev/null +++ b/pkg/driver/distro/flatcar.go @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import ( + "fmt" + "os/exec" + + "github.com/blang/semver" + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" + "gopkg.in/ini.v1" + + drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" + "github.com/falcosecurity/falcoctl/pkg/output" +) + +const flatcarRelocateScript = ` +local -a tools=( + scripts/basic/fixdep + scripts/mod/modpost + tools/objtool/objtool + ) +local -r hostld=$(ls /host/usr/lib64/ld-linux-*.so.*) +local -r kdir=/lib/modules/$(ls /lib/modules/)/build +echo "** Found host dl interpreter: ${hostld}" +for host_tool in ${tools[@]}; do + t=${host_tool} + tool=$(basename $t) + tool_dir=$(dirname $t) + host_tool=${kdir}/${host_tool} + if [ ! -f ${host_tool} ]; then + continue + fi + umount ${host_tool} 2>/dev/null || true + mkdir -p /tmp/${tool_dir}/ + cp -a ${host_tool} /tmp/${tool_dir}/ + echo "** Setting host dl interpreter for $host_tool" + patchelf --set-interpreter ${hostld} --set-rpath /host/usr/lib64 /tmp/${tool_dir}/${tool} + mount -o bind /tmp/${tool_dir}/${tool} ${host_tool} +done +` + +func init() { + distros["flatcar"] = &flatcar{generic: &generic{}} +} + +type flatcar struct { + *generic + versionID string +} + +//nolint:gocritic // the method shall not be able to modify kr +func (f *flatcar) init(kr kernelrelease.KernelRelease, id string, cfg *ini.File) error { + idKey := cfg.Section("").Key("VERSION_ID") + if idKey == nil { + // OS-release without `VERSION_ID` (can it happen?) + return fmt.Errorf("no VERSION_ID present for flatcar") + } + f.versionID = idKey.String() + return f.generic.init(kr, id, cfg) +} + +//nolint:gocritic // the method shall not be able to modify kr +func (f *flatcar) FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease { + kr.Version = semver.MustParse(f.versionID) + return kr +} + +//nolint:gocritic // the method shall not be able to modify kr +func (f *flatcar) customizeBuild(ctx context.Context, + printer *output.Printer, + driverType drivertype.DriverType, + _ kernelrelease.KernelRelease, + _ string, +) (map[string]string, error) { + switch driverType.String() { + case drivertype.TypeBpf, drivertype.TypeKmod: + break + default: + // nothing to do + return nil, nil + } + printer.Logger.Info("Flatcar detected; relocating kernel tools.", printer.Logger.Args("version", f.versionID)) + _, err := exec.CommandContext(ctx, "/bin/bash", "-c", flatcarRelocateScript).Output() + return nil, err +} diff --git a/pkg/driver/distro/generic.go b/pkg/driver/distro/generic.go new file mode 100644 index 000000000..0264b7007 --- /dev/null +++ b/pkg/driver/distro/generic.go @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import ( + "github.com/blang/semver" + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" + "gopkg.in/ini.v1" + + drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" + "github.com/falcosecurity/falcoctl/pkg/output" +) + +type generic struct { + targetID string +} + +//nolint:gocritic // the method shall not be able to modify kr +func (g *generic) init(_ kernelrelease.KernelRelease, id string, _ *ini.File) error { + g.targetID = id + return nil +} + +func (g *generic) String() string { + return g.targetID +} + +//nolint:gocritic // the method shall not be able to modify kr +func (g *generic) FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease { + return kr +} + +//nolint:gocritic // the method shall not be able to modify kr +func (g *generic) customizeBuild(_ context.Context, + _ *output.Printer, + _ drivertype.DriverType, + _ kernelrelease.KernelRelease, + _ string, +) (map[string]string, error) { + return nil, nil +} + +//nolint:gocritic // the method shall not be able to modify kr +func (g *generic) PreferredDriver(kr kernelrelease.KernelRelease) drivertype.DriverType { + // Deadly simple default automatic selection + var dType drivertype.DriverType + switch { + case kr.GTE(semver.MustParse("5.8.0")): + dType, _ = drivertype.Parse(drivertype.TypeModernBpf) + case kr.SupportsProbe(): + dType, _ = drivertype.Parse(drivertype.TypeBpf) + default: + dType, _ = drivertype.Parse(drivertype.TypeKmod) + } + return dType +} diff --git a/pkg/driver/distro/minikube.go b/pkg/driver/distro/minikube.go new file mode 100644 index 000000000..b43fc06ae --- /dev/null +++ b/pkg/driver/distro/minikube.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import ( + "fmt" + "io" + "os" + "path/filepath" + "regexp" + + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" + + drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" + "github.com/falcosecurity/falcoctl/pkg/output" +) + +func init() { + distros["minikube"] = &minikube{generic: &generic{}} +} + +type minikube struct { + *generic + version string +} + +var minikubeVersionRegex = regexp.MustCompile(`(\\d+(\\.\\d+){2})`) + +// check() will also load minikube version, because minikube has a different +// code path from other "checker" distros. +func (m *minikube) check(hostRoot string) bool { + file, err := os.Open(filepath.Clean(hostRoot + "/etc/VERSION")) + if err == nil { + defer func() { + _ = file.Close() + }() + + // Extract the minikube version. + // Eg: for minikube version "v1.26.0-1655407986-14197" + // the extracted version will be "1.26.0" + bytes, err := io.ReadAll(file) + if err != nil { + return false + } + matches := minikubeVersionRegex.FindStringSubmatch(string(bytes)) + if len(matches) == 0 { + return false + } + m.version = matches[1] + return true + } + return false +} + +//nolint:gocritic // the method shall not be able to modify kr +func (m *minikube) FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease { + kr.KernelVersion = fmt.Sprintf("1_%s", m.version) + return kr +} + +//nolint:gocritic // the method shall not be able to modify kr +func (m *minikube) customizeBuild(ctx context.Context, + printer *output.Printer, + driverType drivertype.DriverType, + kr kernelrelease.KernelRelease, + hostRoot string, +) (map[string]string, error) { + switch driverType.String() { + case drivertype.TypeBpf: + break + default: + // nothing to do + return nil, nil + } + + printer.Logger.Info("Minikube detected, using linux kernel sources for minikube kernel", + printer.Logger.Args("version", m.version)) + kernelVersionStr := fmt.Sprintf("%d.%d", kr.Major, kr.Minor) + if kr.Patch != 0 { + kernelVersionStr += fmt.Sprintf(".%d", kr.Patch) + } + bpfKernelSrcURL := fmt.Sprintf("http://mirrors.edge.kernel.org/pub/linux/kernel/v%d.x/linux-%s.tar.gz", kr.Major, kernelVersionStr) + env, err := downloadKernelSrc(ctx, printer, &kr, bpfKernelSrcURL, hostRoot, 1) + if err != nil { + return nil, err + } + return env, customizeDownloadKernelSrcBuild(printer, &kr) +} diff --git a/pkg/driver/distro/rhel.go b/pkg/driver/distro/rhel.go new file mode 100644 index 000000000..2b5295dca --- /dev/null +++ b/pkg/driver/distro/rhel.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import ( + "github.com/falcosecurity/falcoctl/internal/utils" +) + +func init() { + distros["rhel"] = &rhel{generic: &generic{}} +} + +type rhel struct { + *generic +} + +func (r *rhel) check(hostRoot string) bool { + exist, _ := utils.FileExists(hostRoot + "/etc/redhat-release") + return exist +} diff --git a/pkg/driver/distro/talos.go b/pkg/driver/distro/talos.go new file mode 100644 index 000000000..adbde48fb --- /dev/null +++ b/pkg/driver/distro/talos.go @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import ( + "fmt" + + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "gopkg.in/ini.v1" +) + +func init() { + distros["talos"] = &talos{generic: &generic{}} +} + +type talos struct { + *generic + versionID string +} + +//nolint:gocritic // the method shall not be able to modify kr +func (t *talos) init(kr kernelrelease.KernelRelease, id string, cfg *ini.File) error { + idKey := cfg.Section("").Key("VERSION_ID") + if idKey == nil { + // OS-release without `VERSION_ID` (can it happen?) + return fmt.Errorf("no VERSION_ID present for talos") + } + t.versionID = idKey.String() + + return t.generic.init(kr, id, cfg) +} + +//nolint:gocritic // the method shall not be able to modify kr +func (t *talos) FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease { + kr.KernelVersion = fmt.Sprintf("1_%s", t.versionID) + return kr +} diff --git a/pkg/driver/distro/ubuntu.go b/pkg/driver/distro/ubuntu.go new file mode 100644 index 000000000..b7710acca --- /dev/null +++ b/pkg/driver/distro/ubuntu.go @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverdistro + +import ( + "regexp" + "strings" + + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "gopkg.in/ini.v1" +) + +func init() { + distros["ubuntu"] = &ubuntu{generic: &generic{}} +} + +type ubuntu struct { + *generic +} + +var ubuntuTargetIDRegex = regexp.MustCompile(`-([a-zA-Z]+)(-.*)?$`) + +//nolint:gocritic // the method shall not be able to modify kr +func (u *ubuntu) init(kr kernelrelease.KernelRelease, _ string, f *ini.File) error { + // # Extract the flavor from the kernelrelease + // # Examples: + // # 5.0.0-1028-aws-5.0 -> ubuntu-aws + // # 5.15.0-1009-aws -> ubuntu-aws + flavor := "generic" + if ubuntuTargetIDRegex.MatchString(kr.FullExtraversion) { + flavor = ubuntuTargetIDRegex.FindStringSubmatch(kr.FullExtraversion)[1] + } + return u.generic.init(kr, "ubuntu-"+flavor, f) +} + +//nolint:gocritic // the method shall not be able to modify kr +func (u *ubuntu) FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease { + // In the case that the kernelversion isn't just a number + // we keep also the remaining part excluding `-Ubuntu`. + // E.g.: + // from the following `uname -v` result + // `#26~22.04.1-Ubuntu SMP Mon Apr 24 01:58:15 UTC 2023` + // we obtain the kernelversion`26~22.04.1`. + // NOTE: driverkernel.FetchInfo() already trims leading "#" + // and everything starting from the first whitespace, + // so that eg: we receive "26~22.04.1-Ubuntu", + // therefore we only need to drop "-Ubuntu" suffix + kr.KernelVersion = strings.TrimSuffix(kr.KernelVersion, "-Ubuntu") + return kr +} diff --git a/pkg/driver/kernel/kernel.go b/pkg/driver/kernel/kernel.go new file mode 100644 index 000000000..47a947d3e --- /dev/null +++ b/pkg/driver/kernel/kernel.go @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 driverkernel implements the kernel info fetching helpers. +package driverkernel + +import ( + "bytes" + "runtime" + "strings" + + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/sys/unix" +) + +// FetchInfo returns information about currently running kernel. +func FetchInfo(enforcedKR, enforcedKV string) (kernelrelease.KernelRelease, error) { + var ( + kv string + kr string + ) + if enforcedKR == "" || enforcedKV == "" { + u := unix.Utsname{} + if err := unix.Uname(&u); err != nil { + return kernelrelease.KernelRelease{}, err + } + + kr = string(bytes.Trim(u.Release[:], "\x00")) + kv = string(bytes.Trim(u.Version[:], "\x00")) + } else { + kr = enforcedKR + kv = enforcedKV + } + kernelRel := kernelrelease.FromString(kr) + kernelRel.KernelVersion = formatVersion(kv) + kernelRel.Architecture = kernelrelease.Architecture(runtime.GOARCH) + // we don't use this, it is used by bpf build to customize the kernel config LOCALVERSION. + // Expected value is empty. + kernelRel.Extraversion = "" + return kernelRel, nil +} + +// formatVersion takes a kernelversion string (as taken from `uname -v` +// and extracts the first part of the string. +// Eg: '#1 SMP PREEMPT_DYNAMIC Tue, 10 Oct 2023 21:10:21 +0000' -> '1'. +// Eg: '#26~22.04.1-Ubuntu SMP Mon Apr 24 01:58:15 UTC 2023' -> '26~22.04.1-Ubuntu'. +func formatVersion(kv string) string { + // Take eg: "#1 SMP PREEMPT_DYNAMIC Tue, 10 Oct 2023 21:10:21 +0000" and return "1". + kv = strings.Trim(kv, "#") + kv = strings.Split(kv, " ")[0] + return kv +} diff --git a/pkg/driver/type/bpf.go b/pkg/driver/type/bpf.go new file mode 100644 index 000000000..2f3b1e2fb --- /dev/null +++ b/pkg/driver/type/bpf.go @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 drivertype + +import ( + "fmt" + "os/exec" + "path/filepath" + + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" + "k8s.io/utils/mount" + + "github.com/falcosecurity/falcoctl/pkg/output" +) + +// TypeBpf is the string for the bpf driver type. +const TypeBpf = "bpf" + +func init() { + driverTypes[TypeBpf] = &bpf{} +} + +type bpf struct{} + +func (b *bpf) String() string { + return TypeBpf +} + +func (b *bpf) Cleanup(printer *output.Printer, _ string) error { + // Mount /sys/kernel/debug that is needed on old (pre 4.17) kernel releases, + // since these releases still did not support raw tracepoints. + // BPF_PROG_TYPE_RAW_TRACEPOINT was introduced in 4.17 indeed: + // https://github.com/torvalds/linux/commit/c4f6699dfcb8558d138fe838f741b2c10f416cf9 + printer.Logger.Info("Mounting debugfs for bpf driver.") + mounter := mount.New("/bin/mount") + return mounter.Mount("debugfs", "/sys/kernel/debug", "debugfs", []string{"nodev"}) +} + +func (b *bpf) Load(_ *output.Printer, _ string, _ bool) error { + return nil +} + +func (b *bpf) Extension() string { + return ".o" +} + +func (b *bpf) HasArtifacts() bool { + return true +} + +//nolint:gocritic // the method shall not be able to modify kr +func (b *bpf) Build(ctx context.Context, + printer *output.Printer, + _ kernelrelease.KernelRelease, + driverName, driverVersion string, + env map[string]string, +) (string, error) { + printer.Logger.Info("Trying to compile the eBPF probe") + + srcPath := fmt.Sprintf("/usr/src/%s-%s/bpf", driverName, driverVersion) + + makeCmdArgs := fmt.Sprintf(`make -C %q`, filepath.Clean(srcPath)) + makeCmd := exec.CommandContext(ctx, "bash", "-c", makeCmdArgs) //nolint:gosec // false positive + // Append requested env variables to the command env + for key, val := range env { + makeCmd.Env = append(makeCmd.Env, fmt.Sprintf("%s=%s", key, val)) + } + err := runCmdPipingStdout(printer, makeCmd) + outProbe := fmt.Sprintf("%s/probe.o", srcPath) + if err == nil { + printer.Logger.Info("Probe successfully built.", printer.Logger.Args("file", outProbe)) + } else { + printer.Logger.Info("Failed to build probe.", printer.Logger.Args("err", err)) + } + return outProbe, err +} diff --git a/pkg/driver/type/kmod.go b/pkg/driver/type/kmod.go new file mode 100644 index 000000000..ac87c4766 --- /dev/null +++ b/pkg/driver/type/kmod.go @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 drivertype + +import ( + "bufio" + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" + + "github.com/falcosecurity/falcoctl/pkg/output" +) + +const ( + // TypeKmod is the string for the bpf driver type. + TypeKmod = "kmod" + maxRmmodWait = 10 + rmmodWaitTime = 5 * time.Second +) + +type errMissingDep struct { + program string +} + +func (e *errMissingDep) Error() string { + return fmt.Sprintf("This program requires %s.", e.program) +} + +func init() { + driverTypes[TypeKmod] = &kmod{} +} + +type kmod struct{} + +func (k *kmod) String() string { + return TypeKmod +} + +// Cleanup does a cleanup of existing kernel modules. +// First thing, it tries to rmmod the loaded kmod, if present. +// Then, using dkms, it tries to fetch all +// dkms-installed versions of the module to clean them up. +func (k *kmod) Cleanup(printer *output.Printer, driverName string) error { + _, err := exec.Command("bash", "-c", "hash lsmod").Output() + if err != nil { + return &errMissingDep{program: "lsmod"} + } + _, err = exec.Command("bash", "-c", "hash rmmod").Output() + if err != nil { + return &errMissingDep{program: "rmmod"} + } + + kmodName := strings.ReplaceAll(driverName, "-", "_") + printer.Logger.Info("Check if kernel module is still loaded.") + lsmodCmdArgs := fmt.Sprintf(`lsmod | cut -d' ' -f1 | grep -qx %q`, kmodName) + _, err = exec.Command("bash", "-c", lsmodCmdArgs).Output() //nolint:gosec // false positive + if err == nil { + unloaded := false + // Module is still loaded, try to remove it + for i := 0; i < maxRmmodWait; i++ { + printer.Logger.Info("Kernel module is still loaded.") + printer.Logger.Info("Trying to unload it with 'rmmod'.") + if _, err = exec.Command("rmmod", kmodName).Output(); err == nil { //nolint:gosec // false positive + printer.Logger.Info("OK! Unloading module succeeded.") + unloaded = true + break + } + printer.Logger.Info("Nothing to do...'falcoctl' will wait until you remove the kernel module to have a clean termination.") + printer.Logger.Info("Check that no process is using the kernel module with 'lsmod'.") + printer.Logger.Info("Sleep 5 seconds...") + time.Sleep(rmmodWaitTime) + } + if !unloaded { + printer.Logger.Warn("Kernel module is still loaded, you could have incompatibility issues.") + } + } else { + printer.Logger.Info("OK! There is no module loaded.") + } + + _, err = exec.Command("bash", "-c", "hash dkms").Output() + if err != nil { + printer.Logger.Info("Skipping dkms remove (dkms not found).") + return nil + } + + printer.Logger.Info("Check all versions of kernel module in dkms.") + dkmsLsCmdArgs := fmt.Sprintf(`dkms status -m %q | tr -d "," | tr -d ":" | tr "/" " " | cut -d' ' -f2`, kmodName) + out, err := exec.Command("bash", "-c", dkmsLsCmdArgs).Output() //nolint:gosec // false positive + if err != nil { + printer.Logger.Warn("Listing kernel module versions failed.", printer.Logger.Args("reason", err)) + return nil + } + if len(out) == 0 { + printer.Logger.Info("OK! There are no module versions in dkms.") + } else { + printer.Logger.Info("There are some module versions in dkms.") + outBuffer := bytes.NewBuffer(out) + scanner := bufio.NewScanner(outBuffer) + for scanner.Scan() { + dVer := scanner.Text() + dkmsRmCmdArgs := fmt.Sprintf(`dkms remove -m %s -v %q --all`, kmodName, dVer) + _, err = exec.Command("bash", "-c", dkmsRmCmdArgs).Output() //nolint:gosec // false positive + if err == nil { + printer.Logger.Info("OK! Removing succeeded.", printer.Logger.Args("version", dVer)) + } else { + printer.Logger.Warn("Removing failed.", printer.Logger.Args("version", dVer)) + } + } + } + return nil +} + +func (k *kmod) Load(printer *output.Printer, driverName string, fallback bool) error { + if fallback { + // Try to modprobe any existent version of the kmod; this is a fallback + // when both download and build of kmod fail. + printer.Logger.Info("Trying to load a pre existent system module, if present.") + _, err := exec.Command("modprobe", driverName).Output() + if err == nil { + printer.Logger.Info("Success: module found and loaded with modprobe.") + } else { + printer.Logger.Warn("Consider compiling your own driver and loading it or getting in touch with the Falco community.") + } + return err + } + + chconCmdArgs := fmt.Sprintf(`chcon -t modules_object_t %q`, driverName) + // We don't want to catch any error from this call + // chcon(1): change file SELinux security context + _, _ = exec.Command("bash", "-c", chconCmdArgs).Output() //nolint:gosec // false positive + _, err := exec.Command("insmod", driverName).Output() + if err == nil { + printer.Logger.Info("Success: module found and loaded in dkms.", printer.Logger.Args("driver", driverName)) + } else { + printer.Logger.Warn("Unable to insmod module.", printer.Logger.Args("driver", driverName, "err", err)) + } + return err +} + +func (k *kmod) Extension() string { + return ".ko" +} + +func (k *kmod) HasArtifacts() bool { + return true +} + +func createDKMSMakeFile(gcc string) error { + file, err := os.OpenFile("/tmp/falco-dkms-make", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o777) //nolint:gosec // we need the file to be executable + if err != nil { + return err + } + defer file.Close() + + _, err = fmt.Fprintln(file, "#!/usr/bin/env bash") + if err == nil { + _, err = fmt.Fprintln(file, `make CC=`+gcc+` $@`) + } + return err +} + +//nolint:gocritic // the method shall not be able to modify kr +func (k *kmod) Build(ctx context.Context, + printer *output.Printer, + kr kernelrelease.KernelRelease, + driverName, driverVersion string, + _ map[string]string, +) (string, error) { + // Skip dkms on UEK hosts because it will always fail + if strings.Contains(kr.String(), "uek") { + printer.Logger.Warn("Skipping because the dkms install always fail (on UEK hosts).") + return "", fmt.Errorf("unsupported on uek hosts") + } + + printer.Logger.Info("Trying to compile the kernel module") + + out, err := exec.Command("which", "gcc").Output() + if err != nil { + return "", err + } + gccDir := filepath.Dir(string(out)) + + gccs, err := filepath.Glob(gccDir + "/gcc*") + if err != nil { + return "", err + } + + for _, gcc := range gccs { + // Filter away gcc-{ar,nm,...} + // Only gcc compiler has `-print-search-dirs` option. + gccSearchArgs := fmt.Sprintf(`%s -print-search-dirs 2>&1 | grep "install:"`, gcc) + _, err = exec.Command("bash", "-c", gccSearchArgs).Output() //nolint:gosec // false positive + if err != nil { + continue + } + + printer.Logger.Info("Trying to dkms install module.", printer.Logger.Args("gcc", gcc)) + err = createDKMSMakeFile(gcc) + if err != nil { + printer.Logger.Warn("Could not fill /tmp/falco-dkms-make content.") + continue + } + dkmsCmdArgs := fmt.Sprintf(`dkms install --directive="MAKE='/tmp/falco-dkms-make'" -m %q -v %q -k %q --verbose`, + driverName, driverVersion, kr.String()) + + // Try the build through dkms + dkmsCmd := exec.CommandContext(ctx, "bash", "-c", dkmsCmdArgs) //nolint:gosec // false positive + err = runCmdPipingStdout(printer, dkmsCmd) + if err == nil { + koGlob := fmt.Sprintf("/var/lib/dkms/%s/%s/%s/%s/module/%s", driverName, driverVersion, kr.String(), kr.Architecture.ToNonDeb(), driverName) + var koFiles []string + koFiles, err = filepath.Glob(koGlob + ".*") + if err != nil || len(koFiles) == 0 { + printer.Logger.Warn("Module file not found.") + continue + } + koFile := koFiles[0] + printer.Logger.Info("Module installed in dkms.", printer.Logger.Args("file", koFile)) + return koFile, nil + } + dkmsLogFile := fmt.Sprintf("/var/lib/dkms/$%s/%s/build/make.log", driverName, driverVersion) + logs, err := os.ReadFile(filepath.Clean(dkmsLogFile)) + if err != nil { + printer.Logger.Warn("Running dkms build failed, couldn't find dkms log", printer.Logger.Args("file", dkmsLogFile)) + } else { + printer.Logger.Warn("Running dkms build failed. Dumping dkms log.", printer.Logger.Args("file", dkmsLogFile)) + logBuf := bytes.NewBuffer(logs) + scanner := bufio.NewScanner(logBuf) + for scanner.Scan() { + m := scanner.Text() + printer.DefaultText.Println(m) + } + } + } + return "", fmt.Errorf("failed to compile the module") +} diff --git a/pkg/driver/type/modernbpf.go b/pkg/driver/type/modernbpf.go new file mode 100644 index 000000000..3663090f6 --- /dev/null +++ b/pkg/driver/type/modernbpf.go @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 drivertype + +import ( + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" + + "github.com/falcosecurity/falcoctl/pkg/output" +) + +// TypeModernBpf is the string for the bpf driver type. +const TypeModernBpf = "modern-bpf" + +func init() { + driverTypes[TypeModernBpf] = &modernBpf{} +} + +type modernBpf struct{} + +func (m *modernBpf) String() string { + return TypeModernBpf +} + +func (m *modernBpf) Cleanup(_ *output.Printer, _ string) error { + return nil +} + +func (m *modernBpf) Load(_ *output.Printer, _ string, _ bool) error { + return nil +} + +func (m *modernBpf) Extension() string { + return "" +} + +func (m *modernBpf) HasArtifacts() bool { + return false +} + +//nolint:gocritic // the method shall not be able to modify kr +func (m *modernBpf) Build(_ context.Context, _ *output.Printer, _ kernelrelease.KernelRelease, _, _ string, _ map[string]string) (string, error) { + return "", nil +} diff --git a/pkg/driver/type/type.go b/pkg/driver/type/type.go new file mode 100644 index 000000000..8a17f341b --- /dev/null +++ b/pkg/driver/type/type.go @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 drivertype implements all the driver type specific logic. +package drivertype + +import ( + "bufio" + "fmt" + "os/exec" + + "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" + + "github.com/falcosecurity/falcoctl/pkg/output" +) + +var driverTypes = map[string]DriverType{} + +// DriverType is the interface that wraps driver types. +type DriverType interface { + fmt.Stringer + Cleanup(printer *output.Printer, driverName string) error + Load(printer *output.Printer, driverName string, fallback bool) error + Extension() string + HasArtifacts() bool + Build(ctx context.Context, printer *output.Printer, kr kernelrelease.KernelRelease, + driverName, driverVersion string, env map[string]string) (string, error) +} + +// GetTypes return the list of supported driver types. +func GetTypes() []string { + driverTypesSlice := make([]string, 0) + for key := range driverTypes { + driverTypesSlice = append(driverTypesSlice, key) + } + // auto is a sentinel value to enable automatic driver selection logic, + // but it is not mapped to any DriverType + driverTypesSlice = append(driverTypesSlice, "auto") + return driverTypesSlice +} + +// Parse parses a driver type string and returns the corresponding DriverType object or an error. +func Parse(driverType string) (DriverType, error) { + if dType, ok := driverTypes[driverType]; ok { + return dType, nil + } + return nil, fmt.Errorf("wrong driver type specified: %s", driverType) +} + +func runCmdPipingStdout(printer *output.Printer, cmd *exec.Cmd) error { + stdout, err := cmd.StdoutPipe() + if err != nil { + printer.Logger.Warn("Failed to pipe output. Trying without piping.", printer.Logger.Args("err", err)) + _, err = cmd.Output() + } else { + defer stdout.Close() + err = cmd.Start() + if err != nil { + printer.Logger.Warn("Failed to execute command.", printer.Logger.Args("err", err)) + } else { + // print the output of the subprocess line by line + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + m := scanner.Text() + printer.DefaultText.Println(m) + } + err = cmd.Wait() + } + } + return err +} diff --git a/pkg/options/common.go b/pkg/options/common.go index dca5dfb49..df0c5c6e5 100644 --- a/pkg/options/common.go +++ b/pkg/options/common.go @@ -111,6 +111,6 @@ func (o *Common) AddFlags(flags *pflag.FlagSet) { // Mark the disableStyling as deprecated. _ = flags.MarkDeprecated("disable-styling", "please use --log-format") flags.StringVar(&o.ConfigFile, "config", config.ConfigPath, "config file to be used for falcoctl") - flags.Var(o.logFormat, "log-format", "Set formatting for logs (color, text, json)") - flags.Var(o.logLevel, "log-level", "Set level for logs (info, warn, debug, trace)") + flags.Var(o.logFormat, "log-format", "Set formatting for logs "+o.logFormat.Allowed()) + flags.Var(o.logLevel, "log-level", "Set level for logs "+o.logLevel.Allowed()) } diff --git a/pkg/options/driver.go b/pkg/options/driver.go new file mode 100644 index 000000000..ed058c828 --- /dev/null +++ b/pkg/options/driver.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 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 options + +import ( + drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" +) + +// DriverTypes data structure for driver types. +type DriverTypes struct { + *Enum +} + +// NewDriverTypes returns a new Enum configured for the driver types. +func NewDriverTypes() *DriverTypes { + return &DriverTypes{ + Enum: NewEnum(drivertype.GetTypes(), drivertype.TypeKmod), + } +} diff --git a/pkg/options/enum.go b/pkg/options/enum.go index a9e531431..0b8290b3b 100644 --- a/pkg/options/enum.go +++ b/pkg/options/enum.go @@ -43,6 +43,11 @@ func (e *Enum) String() string { return e.value } +// Allowed returns the list of allowed values enclosed in parenthesis. +func (e *Enum) Allowed() string { + return fmt.Sprintf("(%s)", strings.Join(e.allowed, ", ")) +} + // Set the value for the flag. func (e *Enum) Set(p string) error { if !slices.Contains(e.allowed, p) {