From 2c7d6f04aa36df0181910e4adddad302c5320dcb Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Wed, 22 Nov 2023 11:27:10 +0100 Subject: [PATCH] new(cmd,pkg/driverbuilder): support `env`,`src-dir` and `dkms` build options in local builder. Signed-off-by: Federico Di Pierro --- cmd/local.go | 24 ++++++++- pkg/driverbuilder/builder/local.go | 35 +++++++++++-- pkg/driverbuilder/builder/templates/local.sh | 15 ++++++ pkg/driverbuilder/local.go | 53 +++++++++++++++++--- 4 files changed, 116 insertions(+), 11 deletions(-) diff --git a/cmd/local.go b/cmd/local.go index 7a7e7128..4d7d78e8 100644 --- a/cmd/local.go +++ b/cmd/local.go @@ -9,12 +9,20 @@ import ( "golang.org/x/sys/unix" "log/slog" "os" + "os/user" "runtime" "strings" ) +type localCmdOptions struct { + useDKMS bool + srcDir string + envMap map[string]string +} + // NewLocalCmd creates the `driverkit local` command. func NewLocalCmd(rootCommand *RootCmd, rootOpts *RootOptions, rootFlags *pflag.FlagSet) *cobra.Command { + opts := localCmdOptions{} localCmd := &cobra.Command{ Use: "local", Short: "Build Falco kernel modules and eBPF probes in local env with local kernel sources and gcc/clang.", @@ -26,7 +34,18 @@ func NewLocalCmd(rootCommand *RootCmd, rootOpts *RootOptions, rootFlags *pflag.F if !b.HasOutputs() { return } - if err := driverbuilder.NewLocalBuildProcessor(viper.GetInt("timeout")).Start(b); err != nil { + if opts.useDKMS { + currentUser, err := user.Current() + if err != nil { + slog.With("err", err.Error()).Error("Failed to retrieve user. Exiting.") + os.Exit(1) + } + if currentUser.Username != "root" { + slog.Error("Must be run as root for DKMS build.") + os.Exit(1) + } + } + if err := driverbuilder.NewLocalBuildProcessor(viper.GetInt("timeout"), opts.useDKMS, opts.srcDir, opts.envMap).Start(b); err != nil { slog.With("err", err.Error()).Error("exiting") os.Exit(1) } @@ -56,6 +75,9 @@ func NewLocalCmd(rootCommand *RootCmd, rootOpts *RootOptions, rootFlags *pflag.F flagSet.AddFlag(flag) } }) + flagSet.BoolVar(&opts.useDKMS, "dkms", false, "Enforce usage of DKMS to build the kernel module.") + flagSet.StringVar(&opts.srcDir, "src-dir", "", "Enforce usage of local source dir to build drivers.") + flagSet.StringToStringVar(&opts.envMap, "env", nil, "Env variables to be enforced during the driver build.") localCmd.PersistentFlags().AddFlagSet(flagSet) return localCmd } diff --git a/pkg/driverbuilder/builder/local.go b/pkg/driverbuilder/builder/local.go index d2f65487..00e20778 100644 --- a/pkg/driverbuilder/builder/local.go +++ b/pkg/driverbuilder/builder/local.go @@ -15,6 +15,8 @@ var localTemplate string type LocalBuilder struct { GccPath string + UseDKMS bool + SrcDir string } func (l *LocalBuilder) Name() string { @@ -25,7 +27,7 @@ func (l *LocalBuilder) TemplateScript() string { return localTemplate } -func (l *LocalBuilder) URLs(kr kernelrelease.KernelRelease) ([]string, error) { +func (l *LocalBuilder) URLs(_ kernelrelease.KernelRelease) ([]string, error) { return nil, nil } @@ -36,18 +38,43 @@ func (l *LocalBuilder) MinimumURLs() int { type localTemplateData struct { commonTemplateData + UseDKMS bool + DownloadSrc bool + DriverVersion string + KernelRelease string } -func (l *LocalBuilder) TemplateData(c Config, _ kernelrelease.KernelRelease, _ []string) interface{} { +func (l *LocalBuilder) TemplateData(c Config, kr kernelrelease.KernelRelease, _ []string) interface{} { return localTemplateData{ commonTemplateData: commonTemplateData{ - DriverBuildDir: DriverDirectory, + DriverBuildDir: l.GetDriverBuildDir(), ModuleDownloadURL: fmt.Sprintf("%s/%s.tar.gz", c.DownloadBaseURL, c.DriverVersion), ModuleDriverName: c.DriverName, - ModuleFullPath: ModuleFullPath, + ModuleFullPath: l.GetModuleFullPath(c, kr), BuildModule: len(c.ModuleFilePath) > 0, BuildProbe: len(c.ProbeFilePath) > 0, GCCVersion: l.GccPath, }, + UseDKMS: l.UseDKMS, + DownloadSrc: len(l.SrcDir) == 0, // if no srcdir is provided, download src! + DriverVersion: c.DriverVersion, + KernelRelease: c.KernelRelease, } } + +func (l *LocalBuilder) GetModuleFullPath(c Config, kr kernelrelease.KernelRelease) string { + moduleFullPath := ModuleFullPath + if l.UseDKMS { + // When using dkms, we will use a GLOB to match the pattern; ModuleFullPath won't be used in the templated script anyway. + moduleFullPath = fmt.Sprintf("/var/lib/dkms/%s/%s/%s/%s/module/%s.*", c.DriverName, c.DriverVersion, kr.String(), kr.Architecture.ToNonDeb(), c.DriverName) + } + return moduleFullPath +} + +func (l *LocalBuilder) GetDriverBuildDir() string { + driverBuildDir := DriverDirectory + if len(l.SrcDir) > 0 { + driverBuildDir = l.SrcDir + } + return driverBuildDir +} diff --git a/pkg/driverbuilder/builder/templates/local.sh b/pkg/driverbuilder/builder/templates/local.sh index 2ddbd8bb..a7b533e0 100644 --- a/pkg/driverbuilder/builder/templates/local.sh +++ b/pkg/driverbuilder/builder/templates/local.sh @@ -22,6 +22,8 @@ # set -xeuo pipefail +{{ if .DownloadSrc }} +echo "* Downloading driver sources" rm -Rf {{ .DriverBuildDir }} mkdir {{ .DriverBuildDir }} rm -Rf /tmp/module-download @@ -32,8 +34,19 @@ mv /tmp/module-download/*/driver/* {{ .DriverBuildDir }} cp /tmp/module-Makefile {{ .DriverBuildDir }}/Makefile bash /tmp/fill-driver-config.sh {{ .DriverBuildDir }} +{{ end }} {{ if .BuildModule }} +{{ if .UseDKMS }} +echo "* Building kmod with DKMS" +# Build the module using DKMS +echo "#!/usr/bin/env bash" > "/tmp/falco-dkms-make" +echo "make CC={{ .GCCVersion }} \$@" >> "/tmp/falco-dkms-make" +chmod +x "/tmp/falco-dkms-make" +dkms install --directive="MAKE='/tmp/falco-dkms-make'" -m "{{ .ModuleDriverName }}" -v "{{ .DriverVersion }}" -k "{{ .KernelRelease }}" +rm -Rf "/tmp/falco-dkms-make" +{{ else }} +echo "* Building kmod" # Build the module cd {{ .DriverBuildDir }} make CC={{ .GCCVersion }} @@ -42,8 +55,10 @@ strip -g {{ .ModuleFullPath }} # Print results modinfo {{ .ModuleFullPath }} {{ end }} +{{ end }} {{ if .BuildProbe }} +echo "* Building eBPF probe" # Build the eBPF probe cd {{ .DriverBuildDir }}/bpf make diff --git a/pkg/driverbuilder/local.go b/pkg/driverbuilder/local.go index 5aa35e73..03c624c7 100644 --- a/pkg/driverbuilder/local.go +++ b/pkg/driverbuilder/local.go @@ -7,9 +7,11 @@ import ( _ "embed" "fmt" "github.com/falcosecurity/driverkit/pkg/driverbuilder/builder" + "io" "log/slog" "os" "os/exec" + "path" "path/filepath" "time" ) @@ -18,11 +20,17 @@ const LocalBuildProcessorName = "local" type LocalBuildProcessor struct { timeout int + useDKMS bool + srcDir string + envMap map[string]string } -func NewLocalBuildProcessor(timeout int) *LocalBuildProcessor { +func NewLocalBuildProcessor(timeout int, useDKMS bool, srcDir string, envMap map[string]string) *LocalBuildProcessor { return &LocalBuildProcessor{ timeout: timeout, + useDKMS: useDKMS, + srcDir: srcDir, + envMap: envMap, } } @@ -104,6 +112,11 @@ func (lbp *LocalBuildProcessor) Start(b *builder.Build) error { // Cannot fail vv, _ := v.(*builder.LocalBuilder) + vv.SrcDir = lbp.srcDir + vv.UseDKMS = lbp.useDKMS + + modulePath := vv.GetModuleFullPath(c, kr) + probePath := path.Join(vv.GetDriverBuildDir(), "bpf", builder.ProbeFileName) for _, gcc := range gccs { vv.GccPath = gcc @@ -115,6 +128,10 @@ func (lbp *LocalBuildProcessor) Start(b *builder.Build) error { ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(lbp.timeout)*time.Second) defer cancelFunc() cmd := exec.CommandContext(ctx, "/bin/bash", "-c", driverkitScript) + // Append requested env variables to the command env + for key, val := range lbp.envMap { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, val)) + } stdout, err := cmd.StdoutPipe() if err != nil { slog.Warn("Failed to pipe output. Trying without piping.", "err", err) @@ -139,24 +156,30 @@ func (lbp *LocalBuildProcessor) Start(b *builder.Build) error { } // If we received an error, perhaps we must just rebuilt the kmod. // Check if we were able to build anything. - if _, err = os.Stat(builder.ModuleFullPath); !os.IsNotExist(err) { - // we built the kmod; there is no need to loop again. + koFiles, err := filepath.Glob(modulePath) + if err == nil && len(koFiles) > 0 { break } - if _, err = os.Stat(builder.ProbeFullPath); !os.IsNotExist(err) { + if _, err = os.Stat(probePath); !os.IsNotExist(err) { c.ProbeFilePath = "" } } if len(b.ModuleFilePath) > 0 { - if err = os.Rename(builder.ModuleFullPath, b.ModuleFilePath); err != nil { + // If we received an error, perhaps we must just rebuilt the kmod. + // Check if we were able to build anything. + koFiles, err := filepath.Glob(modulePath) + if err != nil || len(koFiles) == 0 { + return fmt.Errorf("failed to find kernel module .ko file: %s", modulePath) + } + if err = copyDataToLocalPath(koFiles[0], b.ModuleFilePath); err != nil { return err } slog.With("path", b.ModuleFilePath).Info("kernel module available") } if len(b.ProbeFilePath) > 0 { - if err = os.Rename(builder.ProbeFullPath, b.ProbeFilePath); err != nil { + if err = copyDataToLocalPath(probePath, b.ProbeFilePath); err != nil { return err } slog.With("path", b.ProbeFilePath).Info("eBPF probe available") @@ -164,3 +187,21 @@ func (lbp *LocalBuildProcessor) Start(b *builder.Build) error { return nil } + +func copyDataToLocalPath(src, dest string) error { + in, err := os.Open(filepath.Clean(src)) + if err != nil { + return err + } + defer in.Close() + err = os.MkdirAll(filepath.Dir(dest), 0o755) + if err != nil { + return err + } + out, err := os.OpenFile(filepath.Clean(dest), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o755) + if err == nil { + defer out.Close() + _, err = io.Copy(out, in) + } + return err +}