diff --git a/cmd/local.go b/cmd/local.go index 536e8836..da0af36f 100644 --- a/cmd/local.go +++ b/cmd/local.go @@ -7,23 +7,21 @@ import ( "github.com/spf13/viper" "log/slog" "os" - "os/user" - "runtime" ) type localCmdOptions struct { - useDKMS bool - srcDir string - envMap map[string]string + useDKMS bool + downloadHeaders 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.", - PersistentPreRunE: persistentPreRunFunc(rootCommand, rootOpts), + Use: "local", + Short: "Build Falco kernel modules and eBPF probes in local env with local kernel sources and gcc/clang.", Run: func(c *cobra.Command, args []string) { slog.With("processor", c.Name()).Info("driver building, it will take a few seconds") if !configOptions.DryRun { @@ -31,18 +29,11 @@ func NewLocalCmd(rootCommand *RootCmd, rootOpts *RootOptions, rootFlags *pflag.F if !b.HasOutputs() { return } - 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 { + if err := driverbuilder.NewLocalBuildProcessor(viper.GetInt("timeout"), + opts.useDKMS, + opts.downloadHeaders, + opts.srcDir, + opts.envMap).Start(b); err != nil { slog.With("err", err.Error()).Error("exiting") os.Exit(1) } @@ -52,7 +43,6 @@ func NewLocalCmd(rootCommand *RootCmd, rootOpts *RootOptions, rootFlags *pflag.F // Add root flags, but not the ones unneeded unusedFlagsSet := map[string]struct{}{ "architecture": {}, - "target": {}, "kernelurls": {}, "builderrepo": {}, "builderimage": {}, @@ -71,18 +61,9 @@ func NewLocalCmd(rootCommand *RootCmd, rootOpts *RootOptions, rootFlags *pflag.F } }) flagSet.BoolVar(&opts.useDKMS, "dkms", false, "Enforce usage of DKMS to build the kernel module.") + flagSet.BoolVar(&opts.downloadHeaders, "download-headers", false, "Try to automatically download kernel headers.") flagSet.StringVar(&opts.srcDir, "src-dir", "", "Enforce usage of local source dir to build drivers.") flagSet.StringToStringVar(&opts.envMap, "env", make(map[string]string), "Env variables to be enforced during the driver build.") localCmd.PersistentFlags().AddFlagSet(flagSet) return localCmd } - -// Partially overrides rootCmd.persistentPreRunFunc setting some defaults before config init/validation stage. -func persistentPreRunFunc(rootCommand *RootCmd, rootOpts *RootOptions) func(c *cobra.Command, args []string) error { - return func(c *cobra.Command, args []string) error { - // Default values - rootOpts.Target = "local" - rootOpts.Architecture = runtime.GOARCH - return rootCommand.c.PersistentPreRunE(c, args) - } -} diff --git a/pkg/driverbuilder/local.go b/pkg/driverbuilder/local.go index d36f675b..280ad7e2 100644 --- a/pkg/driverbuilder/local.go +++ b/pkg/driverbuilder/local.go @@ -4,31 +4,39 @@ import ( "bufio" "context" _ "embed" + "errors" "fmt" "github.com/falcosecurity/driverkit/pkg/driverbuilder/builder" "io" "log/slog" "os" "os/exec" + "os/user" "path/filepath" + "strings" "time" ) -const LocalBuildProcessorName = "local" +const ( + LocalBuildProcessorName = "local" + kernelDirEnv = "KERNELDIR" +) type LocalBuildProcessor struct { - timeout int - useDKMS bool - srcDir string - envMap map[string]string + timeout int + useDKMS bool + downloadHeaders bool + srcDir string + envMap map[string]string } -func NewLocalBuildProcessor(timeout int, useDKMS bool, srcDir string, envMap map[string]string) *LocalBuildProcessor { +func NewLocalBuildProcessor(timeout int, useDKMS, downloadHeaders bool, srcDir string, envMap map[string]string) *LocalBuildProcessor { return &LocalBuildProcessor{ - timeout: timeout, - useDKMS: useDKMS, - srcDir: srcDir, - envMap: envMap, + timeout: timeout, + useDKMS: useDKMS, + srcDir: srcDir, + envMap: envMap, + downloadHeaders: downloadHeaders, } } @@ -38,10 +46,51 @@ func (lbp *LocalBuildProcessor) String() string { func (lbp *LocalBuildProcessor) Start(b *builder.Build) error { slog.Debug("doing a new local build") - + + if lbp.useDKMS { + currentUser, err := user.Current() + if err != nil { + return err + } + if currentUser.Username != "root" { + return errors.New("must be run as root for DKMS build") + } + } + // We don't want to download headers kr := b.KernelReleaseFromBuildConfig() + if lbp.downloadHeaders { + // Download headers for current distro + realBuilder, err := builder.Factory(b.TargetType) + // Since this can be used by external projects, it is not an issue + // if an unsupported target is passed. + // Go on skipping automatic kernel headers download. + if err == nil { + slog.Info("Trying automatic kernel headers download.") + kernelDownloadScript, err := builder.KernelDownloadScript(realBuilder, nil, kr) + if err == nil { + out, err := exec.Command("bash", "-c", kernelDownloadScript).Output() + if err == nil { + path := strings.TrimSuffix(string(out), "\n") + // add the kerneldir path to env + lbp.envMap[kernelDirEnv] = path + defer func() { + _ = os.RemoveAll("/tmp/kernel-download") + _ = os.RemoveAll(path) + }() + } else { + slog.Warn("Failed to download headers.", "err", err) + } + } + } else { + slog.Info("Skipping kernel headers automatic download.", "err", err) + } + } + + // From now on, we use the local builder + b.TargetType = LocalBuildProcessorName + // create a builder based on the choosen build type v, err := builder.Factory(b.TargetType) if err != nil { @@ -88,6 +137,7 @@ func (lbp *LocalBuildProcessor) Start(b *builder.Build) error { srcProbePath := vv.GetProbeFullPath(c) if len(lbp.srcDir) == 0 { + slog.Info("Downloading driver sources.") // Download src! libsDownloadScript, err := builder.LibsDownloadScript(c) if err != nil { @@ -117,11 +167,11 @@ func (lbp *LocalBuildProcessor) Start(b *builder.Build) error { } stdout, err := cmd.StdoutPipe() - cmd.Stderr = cmd.Stdout // redirect stderr to stdout so that we catch it if err != nil { slog.Warn("Failed to pipe stdout. Trying without piping.", "err", err) _, err = cmd.CombinedOutput() } else { + cmd.Stderr = cmd.Stdout // redirect stderr to stdout so that we catch it defer stdout.Close() err = cmd.Start() if err != nil {