From cc6443fcabed8f05b7dc9112146c852004bff755 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Wed, 15 Nov 2023 11:10:23 +0100 Subject: [PATCH] chore(cmd/driver,pkg/driver,internal/config): use a context for Download and Build long-running actions. Use an Enum to wrap supported drivertypes in driver.config command. Signed-off-by: Federico Di Pierro Co-authored-by: alacuku --- cmd/driver/config/config.go | 105 +++++++++++++++++++++------------- cmd/driver/install/install.go | 6 +- go.mod | 2 +- internal/config/config.go | 5 +- pkg/driver/distro/distro.go | 19 ++++-- pkg/driver/type/bpf.go | 6 +- pkg/driver/type/kmod.go | 9 +-- pkg/driver/type/modernbpf.go | 3 +- pkg/driver/type/type.go | 13 +++-- pkg/options/common.go | 4 +- pkg/options/driver.go | 32 +++++++++++ pkg/options/enum.go | 5 ++ 12 files changed, 145 insertions(+), 64 deletions(-) create mode 100644 pkg/options/driver.go diff --git a/cmd/driver/config/config.go b/cmd/driver/config/config.go index 1cc58755f..b1d556ba5 100644 --- a/cmd/driver/config/config.go +++ b/cmd/driver/config/config.go @@ -16,17 +16,18 @@ package driverconfig import ( + "encoding/json" "fmt" - "os" "strings" "github.com/pterm/pterm" "github.com/spf13/cobra" "golang.org/x/net/context" - corev1 "k8s.io/api/core/v1" 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" @@ -36,18 +37,24 @@ import ( "github.com/falcosecurity/falcoctl/pkg/options" ) +const ( + configMapDriverTypeKey = "driver_mode" +) + type driverConfigOptions struct { *options.Common - Type string - Version string - Repos []string - Name string - HostRoot string - Update bool + 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(_ context.Context, opt *options.Common) *cobra.Command { +func NewDriverConfigCmd(ctx context.Context, opt *options.Common) *cobra.Command { o := driverConfigOptions{ Common: opt, } @@ -58,22 +65,23 @@ func NewDriverConfigCmd(_ context.Context, opt *options.Common) *cobra.Command { 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(cmd) + return o.RunDriverConfig(ctx, cmd) }, } - cmd.Flags().StringVar(&o.Type, "type", config.DefaultDriver.Type, - "Driver type to be configured. Allowed: "+strings.Join(drivertype.GetDriverTypes(), ",")+",auto") + 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().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(cmd *cobra.Command) error { +func (o *driverConfigOptions) RunDriverConfig(ctx context.Context, cmd *cobra.Command) error { var ( dType drivertype.DriverType err error @@ -118,9 +126,9 @@ func (o *driverConfigOptions) RunDriverConfig(cmd *cobra.Command) error { Key: "driver type", Value: o.Type, }) - if o.Type != "auto" { + if o.Type.String() != "auto" { // Ok driver type was enforced by the user - dType, err = drivertype.Parse(o.Type) + dType, err = drivertype.Parse(o.Type.String()) if err != nil { return err } @@ -152,7 +160,7 @@ func (o *driverConfigOptions) RunDriverConfig(cmd *cobra.Command) error { o.Printer.Logger.Info("Running falcoctl driver config", loggerArgs) if o.Update { - err = commit(driverCfg.HostRoot, dType) + err = commit(ctx, dType, driverCfg.HostRoot, o.Namespace, o.KubeConfig) if err != nil { return err } @@ -160,25 +168,21 @@ func (o *driverConfigOptions) RunDriverConfig(cmd *cobra.Command) error { return config.StoreDriver(&driverCfg, o.ConfigFile) } -func replaceDriverTypeInFalcoConfig(filePath string, driverType drivertype.DriverType) error { - return utils.ReplaceLineInFile(filePath, "driver_mode:", "driver_mode: "+driverType.String(), 1) +func replaceDriverTypeInFalcoConfig(hostRoot string, driverType drivertype.DriverType) error { + return utils.ReplaceLineInFile(hostRoot+"/etc/falco/falco.yaml", "driver_mode:", "driver_mode: "+driverType.String(), 1) } -// commit saves the updated driver type to Falco config, -// either to the local falco.yaml or updating the deployment configmap. -func commit(hostRoot string, driverType drivertype.DriverType) error { - err := replaceDriverTypeInFalcoConfig(hostRoot+"/etc/falco/falco.yaml", driverType) - if err == nil { - return nil - } - if !os.IsNotExist(err) { - return err - } - // falco config does not exist; try K8S. +func replaceDriverTypeInK8SConfigMap(ctx context.Context, namespace, kubeconfig string, driverType drivertype.DriverType) error { + var ( + err error + cfg *rest.Config + ) - // TODO double check - // creates the in-cluster config - cfg, err := rest.InClusterConfig() + if kubeconfig != "" { + cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig) + } else { + cfg, err = rest.InClusterConfig() + } if err != nil { return err } @@ -188,7 +192,7 @@ func commit(hostRoot string, driverType drivertype.DriverType) error { return err } - configMapList, err := cl.CoreV1().ConfigMaps(corev1.NamespaceAll).List(context.TODO(), metav1.ListOptions{ + configMapList, err := cl.CoreV1().ConfigMaps(namespace).List(ctx, metav1.ListOptions{ LabelSelector: "app.kubernetes.io/instance: falco", }) if err != nil { @@ -198,6 +202,18 @@ func commit(hostRoot string, driverType drivertype.DriverType) error { 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 @@ -205,16 +221,25 @@ func commit(hostRoot string, driverType drivertype.DriverType) error { // 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["driver_mode"]; !ok || val == "plugin" { + if val, ok := configMap.Data[configMapDriverTypeKey]; !ok || val == "none" { continue } - configMap.Data["driver_mode"] = driverType.String() - // Update the ConfigMap - _, loopErr := cl.CoreV1().ConfigMaps(configMap.Namespace).Update(context.TODO(), &configMap, metav1.UpdateOptions{}) - if loopErr != nil { - return loopErr + // 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/install/install.go b/cmd/driver/install/install.go index 7e493df8e..16f9ecdba 100644 --- a/cmd/driver/install/install.go +++ b/cmd/driver/install/install.go @@ -133,7 +133,7 @@ func setDefaultHTTPClientOpts(downloadOptions driverDownloadOptions) { } // RunDriverInstall implements the driver install command. -func (o *driverInstallOptions) RunDriverInstall(_ context.Context, driver *config.Driver) (string, error) { +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 @@ -180,7 +180,7 @@ func (o *driverInstallOptions) RunDriverInstall(_ context.Context, driver *confi var dest string if o.Download { - dest, err = driverdistro.Download(d, o.Printer, kr, driver.Name, driver.Type, driver.Version, driver.Repos) + dest, err = driverdistro.Download(ctx, d, o.Printer, kr, driver.Name, driver.Type, driver.Version, driver.Repos) if err == nil { return dest, nil } @@ -190,7 +190,7 @@ func (o *driverInstallOptions) RunDriverInstall(_ context.Context, driver *confi } if o.Compile { - dest, err = driverdistro.Build(d, o.Printer, kr, driver.Name, driver.Type, driver.Version, driver.HostRoot) + dest, err = driverdistro.Build(ctx, d, o.Printer, kr, driver.Name, driver.Type, driver.Version, driver.HostRoot) if err == nil { return dest, nil } diff --git a/go.mod b/go.mod index cd539002a..fcef76cf2 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,6 @@ require ( google.golang.org/api v0.149.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.28.3 k8s.io/apimachinery v0.28.3 k8s.io/client-go v0.28.3 k8s.io/utils v0.0.0-20230726121419-3b25d923346b @@ -284,6 +283,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // 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/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/internal/config/config.go b/internal/config/config.go index c29610c56..5cb06208e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -124,7 +124,8 @@ const ( // 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" + DriverHostRootKey = "driver.hostRoot" + falcoHostRootEnvKey = "HOST_ROOT" ) // Index represents a configured index. @@ -562,7 +563,7 @@ func Driverer() (Driver, error) { // Bind FALCOCTL_DRIVER_HOSTROOT key to HOST_ROOT, // so that we manage Falco HOST_ROOT variable too. - _ = viper.BindEnv(DriverHostRootKey, "HOST_ROOT") + _ = viper.BindEnv(DriverHostRootKey, falcoHostRootEnvKey) drvCfg := Driver{ Type: drvType, diff --git a/pkg/driver/distro/distro.go b/pkg/driver/distro/distro.go index ac1e98893..c0a1e4451 100644 --- a/pkg/driver/distro/distro.go +++ b/pkg/driver/distro/distro.go @@ -28,6 +28,7 @@ import ( "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" @@ -146,7 +147,8 @@ func copyDataToLocalPath(destination string, src io.Reader) error { // 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(d Distro, +func Build(ctx context.Context, + d Distro, printer *output.Printer, kr kernelrelease.KernelRelease, driverName string, @@ -158,7 +160,7 @@ func Build(d Distro, if err != nil { return "", err } - path, err := driverType.Build(printer, d.FixupKernel(kr), driverName, driverVer, env) + path, err := driverType.Build(ctx, printer, d.FixupKernel(kr), driverName, driverVer, env) if err != nil { return "", err } @@ -179,7 +181,8 @@ func Build(d Distro, // 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(d Distro, +func Download(ctx context.Context, + d Distro, printer *output.Printer, kr kernelrelease.KernelRelease, driverName string, @@ -198,13 +201,19 @@ func Download(d Distro, // 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)) + printer.Logger.Info("Trying to download a driver.", printer.Logger.Args("url", url)) - resp, err := http.Get(url) //nolint:gosec,noctx // false positive + 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) diff --git a/pkg/driver/type/bpf.go b/pkg/driver/type/bpf.go index bd6baa1fe..3afd13da8 100644 --- a/pkg/driver/type/bpf.go +++ b/pkg/driver/type/bpf.go @@ -20,6 +20,7 @@ import ( "os/exec" "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" "k8s.io/utils/mount" "github.com/falcosecurity/falcoctl/pkg/output" @@ -61,7 +62,8 @@ func (b *bpf) HasArtifacts() bool { } //nolint:gocritic // the method shall not be able to modify kr -func (b *bpf) Build(printer *output.Printer, +func (b *bpf) Build(ctx context.Context, + printer *output.Printer, _ kernelrelease.KernelRelease, driverName, driverVersion string, env map[string]string, @@ -71,7 +73,7 @@ func (b *bpf) Build(printer *output.Printer, srcPath := fmt.Sprintf("/usr/src/%s-%s/bpf", driverName, driverVersion) makeCmdArgs := fmt.Sprintf(`make -C %q`, srcPath) - makeCmd := exec.Command("bash", "-c", makeCmdArgs) //nolint:gosec // false positive + 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)) diff --git a/pkg/driver/type/kmod.go b/pkg/driver/type/kmod.go index 754919b4d..ff2c04387 100644 --- a/pkg/driver/type/kmod.go +++ b/pkg/driver/type/kmod.go @@ -26,6 +26,7 @@ import ( "time" "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" "github.com/falcosecurity/falcoctl/pkg/output" ) @@ -183,7 +184,8 @@ func createDKMSMakeFile(gcc string) error { } //nolint:gocritic // the method shall not be able to modify kr -func (k *kmod) Build(printer *output.Printer, +func (k *kmod) Build(ctx context.Context, + printer *output.Printer, kr kernelrelease.KernelRelease, driverName, driverVersion string, _ map[string]string, @@ -212,7 +214,6 @@ func (k *kmod) Build(printer *output.Printer, // 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 } @@ -227,7 +228,7 @@ func (k *kmod) Build(printer *output.Printer, driverName, driverVersion, kr.String()) // Try the build through dkms - dkmsCmd := exec.Command("bash", "-c", dkmsCmdArgs) //nolint:gosec // false positive + 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) @@ -251,7 +252,7 @@ func (k *kmod) Build(printer *output.Printer, scanner := bufio.NewScanner(logBuf) for scanner.Scan() { m := scanner.Text() - fmt.Println(m) + printer.DefaultText.Println(m) } } } diff --git a/pkg/driver/type/modernbpf.go b/pkg/driver/type/modernbpf.go index 739ee36c5..3663090f6 100644 --- a/pkg/driver/type/modernbpf.go +++ b/pkg/driver/type/modernbpf.go @@ -17,6 +17,7 @@ package drivertype import ( "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" "github.com/falcosecurity/falcoctl/pkg/output" ) @@ -51,6 +52,6 @@ func (m *modernBpf) HasArtifacts() bool { } //nolint:gocritic // the method shall not be able to modify kr -func (m *modernBpf) Build(_ *output.Printer, _ kernelrelease.KernelRelease, _, _ string, _ map[string]string) (string, error) { +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 index 0a2a0a3a2..8a17f341b 100644 --- a/pkg/driver/type/type.go +++ b/pkg/driver/type/type.go @@ -22,6 +22,7 @@ import ( "os/exec" "github.com/falcosecurity/driverkit/pkg/kernelrelease" + "golang.org/x/net/context" "github.com/falcosecurity/falcoctl/pkg/output" ) @@ -35,15 +36,19 @@ type DriverType interface { Load(printer *output.Printer, driverName string, fallback bool) error Extension() string HasArtifacts() bool - Build(printer *output.Printer, kr kernelrelease.KernelRelease, driverName, driverVersion string, env map[string]string) (string, error) + Build(ctx context.Context, printer *output.Printer, kr kernelrelease.KernelRelease, + driverName, driverVersion string, env map[string]string) (string, error) } -// GetDriverTypes return the list of supported driver types. -func GetDriverTypes() []string { +// 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 } @@ -70,7 +75,7 @@ func runCmdPipingStdout(printer *output.Printer, cmd *exec.Cmd) error { scanner := bufio.NewScanner(stdout) for scanner.Scan() { m := scanner.Text() - fmt.Println(m) + printer.DefaultText.Println(m) } err = cmd.Wait() } 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) {