Repository: falcosecurity/driverkit (go)

Issue:
Add proxy options to set http proxy in container
**Motivation**

In some special network environment, such as intranet, network restricted area (China). 
The RPM package or compilation scaffold cannot be downloaded or the download speed is very
slow in container

<!--
Is your feature request related to a problem?
Please describe what the problem is clearly and concisely.
Eg., I'm always frustrated when ...
-->

**Feature**

Set the HTTP proxy environment variable so that the download data in the container 
uses the proxy

<!--
Describe the solution you would like.
A clear and concise description of what you want to happen.
-->

Pull request:
  Add proxy option to set http proxy in container
  Signed-off-by: aiyijing <aiyijing@live.com>

<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines in the [CONTRIBUTING.md](https://github.com/falcosecurity/.github/CONTRIBUTING.md) file.
2. Please label this pull request according to what type of issue you are addressing.
3. Please add a release note!
4. If the PR is unfinished while opening it specify a wip in the title before the actual title, for example, "wip: my awesome feature"
-->

**What type of PR is this?**

> Uncomment one (or more) `/kind <>` lines:

> /kind bug

> /kind cleanup

> /kind design

> /kind documentation

> /kind failing-test

/kind feature

<!--
Please remove the leading whitespace before the `/kind <>` you uncommented.
-->

**Any specific area of the project related to this PR?**

> Uncomment one (or more) `/area <>` lines:

> /area build

/area cmd

/area pkg

> /area docs

/area tests


**What this PR does / why we need it**:

**Which issue(s) this PR fixes**:

<!--
Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
If PR is `kind/failing-tests` or `kind/flaky-test`, please post the related issues/tests in a comment and do not use `Fixes`.
-->

Fixes #65

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below.
If the PR requires additional action from users switching to the new release, prepend the string "action required:".
For example, `action required: change the API interface of the rule engine`.
-->

```release-note
new: proxy option to set http proxy in container/pod
```


Patch:
diff --git a/cmd/config_options.go b/cmd/config_options.go
index 8a1f114..f5053f9 100644
--- a/cmd/config_options.go
+++ b/cmd/config_options.go
@@ -18,7 +18,10 @@ type ConfigOptions struct {
 	ConfigFile string
 	LogLevel   string `validate:"logrus" name:"log level" default:"info"`
 	Timeout    int    `validate:"number,min=30" default:"60" name:"timeout"`
+	ProxyURL   string `validate:"omitempty,proxy" name:"proxy url"`
 	DryRun     bool
+
+	configErrors bool
 }
 
 // NewConfigOptions creates an instance of ConfigOptions.
@@ -39,6 +42,7 @@ func (co *ConfigOptions) Validate() []error {
 			// Translate each error one at a time
 			errArr = append(errArr, fmt.Errorf(e.Translate(validate.T)))
 		}
+		co.configErrors = true
 		return errArr
 	}
 	return nil
diff --git a/cmd/docker.go b/cmd/docker.go
index cdd758e..3f839df 100644
--- a/cmd/docker.go
+++ b/cmd/docker.go
@@ -17,7 +17,7 @@ func NewDockerCmd(rootOpts *RootOptions, rootFlags *pflag.FlagSet) *cobra.Comman
 		Run: func(c *cobra.Command, args []string) {
 			logrus.WithField("processor", c.Name()).Info("driver building, it will take a few seconds")
 			if !configOptions.DryRun {
-				if err := driverbuilder.NewDockerBuildProcessor(viper.GetInt("timeout")).Start(rootOpts.toBuild()); err != nil {
+				if err := driverbuilder.NewDockerBuildProcessor(viper.GetInt("timeout"), viper.GetString("proxy")).Start(rootOpts.toBuild()); err != nil {
 					logger.WithError(err).Fatal("exiting")
 				}
 			}
diff --git a/cmd/kubernetes.go b/cmd/kubernetes.go
index 3638c59..d6a1b18 100644
--- a/cmd/kubernetes.go
+++ b/cmd/kubernetes.go
@@ -76,7 +76,7 @@ func kubernetesRun(cmd *cobra.Command, args []string, kubefactory factory.Factor
 		return err
 	}
 
-	buildProcessor := driverbuilder.NewKubernetesBuildProcessor(kc.CoreV1(), clientConfig, namespaceStr, viper.GetInt("timeout"))
+	buildProcessor := driverbuilder.NewKubernetesBuildProcessor(kc.CoreV1(), clientConfig, namespaceStr, viper.GetInt("timeout"), viper.GetString("proxy"))
 
 	return buildProcessor.Start(b)
 }
diff --git a/cmd/root.go b/cmd/root.go
index 27b978a..1fa83e1 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -19,12 +19,17 @@ import (
 
 func persistentValidateFunc(rootCommand *RootCmd, rootOpts *RootOptions) func(c *cobra.Command, args []string) error {
 	return func(c *cobra.Command, args []string) error {
+		// Early exit if detect some error into config flags
+		if configOptions.configErrors {
+			return fmt.Errorf("exiting for validation errors")
+		}
 		// Merge environment variables or config file values into the RootOptions instance
 		skip := map[string]bool{ // do not merge these
 			"config":   true,
 			"timeout":  true,
 			"loglevel": true,
 			"dryrun":   true,
+			"proxy":    true,
 		}
 		nested := map[string]string{ // handle nested options in config file
 			"output-module": "output.module",
@@ -101,6 +106,7 @@ func NewRootCmd() *RootCmd {
 	flags.StringVarP(&configOptions.LogLevel, "loglevel", "l", configOptions.LogLevel, "log level")
 	flags.IntVar(&configOptions.Timeout, "timeout", configOptions.Timeout, "timeout in seconds")
 	flags.BoolVar(&configOptions.DryRun, "dryrun", configOptions.DryRun, "do not actually perform the action")
+	flags.StringVar(&configOptions.ProxyURL, "proxy", configOptions.ProxyURL, "the proxy to use to download data")
 
 	flags.StringVar(&rootOpts.Output.Module, "output-module", rootOpts.Output.Module, "filepath where to save the resulting kernel module")
 	flags.StringVar(&rootOpts.Output.Probe, "output-probe", rootOpts.Output.Probe, "filepath where to save the resulting eBPF probe")
@@ -109,7 +115,6 @@ func NewRootCmd() *RootCmd {
 	flags.StringVar(&rootOpts.KernelRelease, "kernelrelease", rootOpts.KernelRelease, "kernel release to build the module for, it can be found by executing 'uname -v'")
 	flags.StringVarP(&rootOpts.Target, "target", "t", rootOpts.Target, "the system to target the build for")
 	flags.StringVar(&rootOpts.KernelConfigData, "kernelconfigdata", rootOpts.KernelConfigData, "base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc")
-
 	viper.BindPFlags(flags)
 
 	// Flag annotations and custom completions
@@ -193,7 +198,7 @@ func initConfig() {
 		for _, err := range errs {
 			logger.WithError(err).Error("error validating config options")
 		}
-		logger.Fatal("exiting for validation errors")
+		// configOptions.configErrors should be true here
 	}
 	if configOptions.ConfigFile != "" {
 		viper.SetConfigFile(configOptions.ConfigFile)
@@ -201,7 +206,8 @@ func initConfig() {
 		// Find home directory.
 		home, err := homedir.Dir()
 		if err != nil {
-			logger.WithError(err).Fatal("error getting the home directory")
+			logger.WithError(err).Debug("error getting the home directory")
+			// not setting configOptions.configErrors = true because we fallback to `$HOME/.driverkit.yaml` and try with it
 		}
 
 		viper.AddConfigPath(home)
@@ -221,7 +227,8 @@ func initConfig() {
 			logger.Debug("running without a configuration file")
 		} else {
 			// Config file was found but another error was produced
-			logger.WithField("file", viper.ConfigFileUsed()).WithError(err).Fatal("error running with config file")
+			logger.WithField("file", viper.ConfigFileUsed()).WithError(err).Debug("error running with config file")
+			configOptions.configErrors = true
 		}
 	}
 }
diff --git a/pkg/driverbuilder/docker.go b/pkg/driverbuilder/docker.go
index 50b3e24..5e2548b 100644
--- a/pkg/driverbuilder/docker.go
+++ b/pkg/driverbuilder/docker.go
@@ -25,17 +25,20 @@ import (
 	"k8s.io/apimachinery/pkg/util/uuid"
 )
 
+// DockerBuildProcessorName is a constant containing the docker name.
 const DockerBuildProcessorName = "docker"
 
 type DockerBuildProcessor struct {
 	clean   bool
 	timeout int
+	proxy   string
 }
 
 // NewDockerBuildProcessor ...
-func NewDockerBuildProcessor(timeout int) *DockerBuildProcessor {
+func NewDockerBuildProcessor(timeout int, proxy string) *DockerBuildProcessor {
 	return &DockerBuildProcessor{
 		timeout: timeout,
+		proxy:   proxy,
 	}
 }
 
@@ -157,6 +160,16 @@ func (bp *DockerBuildProcessor) Start(b *builder.Build) error {
 		return err
 	}
 
+	// Construct environment variable array of string
+	var envs []string
+	// Add http_proxy and https_proxy environment variable
+	if bp.proxy != "" {
+		envs = append(envs,
+			fmt.Sprintf("http_proxy=%s", bp.proxy),
+			fmt.Sprintf("https_proxy=%s", bp.proxy),
+		)
+	}
+
 	edata, err := cli.ContainerExecCreate(ctx, cdata.ID, types.ExecConfig{
 		Privileged:   false,
 		Tty:          false,
@@ -164,6 +177,7 @@ func (bp *DockerBuildProcessor) Start(b *builder.Build) error {
 		AttachStderr: true,
 		AttachStdout: true,
 		Detach:       true,
+		Env:          envs,
 		Cmd: []string{
 			"/bin/bash",
 			"/driverkit/driverkit.sh",
diff --git a/pkg/driverbuilder/kubernetes.go b/pkg/driverbuilder/kubernetes.go
index 0e2650b..38b34e6 100644
--- a/pkg/driverbuilder/kubernetes.go
+++ b/pkg/driverbuilder/kubernetes.go
@@ -33,18 +33,20 @@ type KubernetesBuildProcessor struct {
 	clientConfig *restclient.Config
 	namespace    string
 	timeout      int
+	proxy        string
 }
 
 // NewKubernetesBuildProcessor constructs a KubernetesBuildProcessor
 // starting from a kubernetes.Clientset. bufferSize represents the length of the
 // channel we use to do the builds. A bigger bufferSize will mean that we can save more Builds
 // for processing, however setting this to a big value will have impacts
-func NewKubernetesBuildProcessor(corev1Client v1.CoreV1Interface, clientConfig *restclient.Config, namespace string, timeout int) *KubernetesBuildProcessor {
+func NewKubernetesBuildProcessor(corev1Client v1.CoreV1Interface, clientConfig *restclient.Config, namespace string, timeout int, proxy string) *KubernetesBuildProcessor {
 	return &KubernetesBuildProcessor{
 		coreV1Client: corev1Client,
 		clientConfig: clientConfig,
 		namespace:    namespace,
 		timeout:      timeout,
+		proxy:        proxy,
 	}
 }
 
@@ -131,6 +133,21 @@ func (bp *KubernetesBuildProcessor) buildModule(build *builder.Build) error {
 			"module-downloader.sh":   waitForModuleAndCat,
 		},
 	}
+	// Construct environment variable array of corev1.EnvVar
+	var envs []corev1.EnvVar
+	// Add http_porxy and https_proxy environment variable
+	if bp.proxy != "" {
+		envs = append(envs,
+			corev1.EnvVar{
+				Name:  "http_proxy",
+				Value: bp.proxy,
+			},
+			corev1.EnvVar{
+				Name:  "https_proxy",
+				Value: bp.proxy,
+			},
+		)
+	}
 
 	pod := &corev1.Pod{
 		ObjectMeta: commonMeta,
@@ -142,6 +159,7 @@ func (bp *KubernetesBuildProcessor) buildModule(build *builder.Build) error {
 					Name:            name,
 					Image:           builderBaseImage,
 					Command:         buildCmd,
+					Env:             envs,
 					ImagePullPolicy: corev1.PullIfNotPresent,
 
 					Resources: corev1.ResourceRequirements{
diff --git a/validate/isproxy.go b/validate/isproxy.go
new file mode 100644
index 0000000..c946daf
--- /dev/null
+++ b/validate/isproxy.go
@@ -0,0 +1,22 @@
+package validate
+
+import (
+	"fmt"
+	"reflect"
+	"regexp"
+
+	"github.com/go-playground/validator/v10"
+)
+
+var proxyRegex = regexp.MustCompile("^(http://|https://|socks5://)")
+
+func isProxy(fl validator.FieldLevel) bool {
+	field := fl.Field()
+
+	switch field.Kind() {
+	case reflect.String:
+		return proxyRegex.MatchString(field.String())
+	}
+
+	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
+}
diff --git a/validate/validate.go b/validate/validate.go
index 01dd263..2d2661e 100644
--- a/validate/validate.go
+++ b/validate/validate.go
@@ -37,6 +37,7 @@ func init() {
 	V.RegisterValidation("sha1", isSHA1)
 	V.RegisterValidation("target", isTargetSupported)
 	V.RegisterValidation("semver", isSemVer)
+	V.RegisterValidation("proxy", isProxy)
 
 	eng := en.New()
 	uni := ut.New(eng, eng)
@@ -159,4 +160,17 @@ func init() {
 			return t
 		},
 	)
+
+	V.RegisterTranslation(
+		"proxy",
+		T,
+		func(ut ut.Translator) error {
+			return ut.Add("proxy", "{0} must start with http:// or https:// or socks5:// prefix", true)
+		},
+		func(ut ut.Translator, fe validator.FieldError) string {
+			t, _ := ut.T(fe.Tag(), fe.Field(), fe.Param())
+
+			return t
+		},
+	)
 }


Test patch:
diff --git a/cmd/cli_test.go b/cmd/cli_test.go
index e1c0cc8..95fc3f4 100644
--- a/cmd/cli_test.go
+++ b/cmd/cli_test.go
@@ -54,6 +54,17 @@ var tests = []testCase{
 			err: `invalid argument "abc" for "driverkit"`,
 		},
 	},
+	{
+		descr: "invalid/config/proxy",
+		args: []string{
+			"--proxy",
+			"wrong",
+		},
+		expect: expect{
+			out: "testdata/invalid-proxyconfig.txt",
+			err: "exiting for validation errors",
+		},
+	},
 	{
 		descr: "docker/all-flags",
 		args: []string{
diff --git a/cmd/testdata/autohelp.txt b/cmd/testdata/autohelp.txt
index a66ef9c..244f7a1 100644
--- a/cmd/testdata/autohelp.txt
+++ b/cmd/testdata/autohelp.txt
@@ -22,6 +22,7 @@ Flags:
   -l, --loglevel string           log level (default "info")
       --output-module string      filepath where to save the resulting kernel module
       --output-probe string       filepath where to save the resulting eBPF probe
+      --proxy string              the proxy to use to download data
   -t, --target string             the system to target the build for
       --timeout int               timeout in seconds (default 60)
   -v, --version                   version for driverkit
diff --git a/cmd/testdata/dockernoopts.txt b/cmd/testdata/dockernoopts.txt
index 466097c..2b58975 100644
--- a/cmd/testdata/dockernoopts.txt
+++ b/cmd/testdata/dockernoopts.txt
@@ -17,6 +17,7 @@ Flags:
   -l, --loglevel string           log level (default "info")
       --output-module string      filepath where to save the resulting kernel module
       --output-probe string       filepath where to save the resulting eBPF probe
+      --proxy string              the proxy to use to download data
   -t, --target string             the system to target the build for
       --timeout int               timeout in seconds (default 60)
 
diff --git a/cmd/testdata/help-flag.txt b/cmd/testdata/help-flag.txt
index 2225df3..d058ff1 100644
--- a/cmd/testdata/help-flag.txt
+++ b/cmd/testdata/help-flag.txt
@@ -21,6 +21,7 @@ Flags:
   -l, --loglevel string           log level (default "info")
       --output-module string      filepath where to save the resulting kernel module
       --output-probe string       filepath where to save the resulting eBPF probe
+      --proxy string              the proxy to use to download data
   -t, --target string             the system to target the build for
       --timeout int               timeout in seconds (default 60)
   -v, --version                   version for driverkit
diff --git a/cmd/testdata/help.txt b/cmd/testdata/help.txt
index 2152e50..73c7c27 100644
--- a/cmd/testdata/help.txt
+++ b/cmd/testdata/help.txt
@@ -21,6 +21,7 @@ Flags:
   -l, --loglevel string           log level (default "info")
       --output-module string      filepath where to save the resulting kernel module
       --output-probe string       filepath where to save the resulting eBPF probe
+      --proxy string              the proxy to use to download data
   -t, --target string             the system to target the build for
       --timeout int               timeout in seconds (default 60)
 
diff --git a/cmd/testdata/invalid-proxyconfig.txt b/cmd/testdata/invalid-proxyconfig.txt
new file mode 100644
index 0000000..f34a57c
--- /dev/null
+++ b/cmd/testdata/invalid-proxyconfig.txt
@@ -0,0 +1,30 @@
+ERRO error validating config options               error="proxy url must start with http:// or https:// or socks5:// prefix"
+Error: exiting for validation errors
+Usage:
+  driverkit
+  driverkit [command]
+
+Available Commands:
+  completion  Generates completion scripts.
+  docker      Build Falco kernel modules and eBPF probes against a docker daemon.
+  help        Help about any command
+  kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
+
+Flags:
+  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
+      --driverversion string      driver version as a git commit hash or as a git tag (default "dev")
+      --dryrun                    do not actually perform the action
+  -h, --help                      help for driverkit
+      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
+      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
+      --kernelversion uint16      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default 1)
+  -l, --loglevel string           log level (default "info")
+      --output-module string      filepath where to save the resulting kernel module
+      --output-probe string       filepath where to save the resulting eBPF probe
+      --proxy string              the proxy to use to download data
+  -t, --target string             the system to target the build for
+      --timeout int               timeout in seconds (default 60)
+  -v, --version                   version for driverkit
+
+Use "driverkit [command] --help" for more information about a command.
+
diff --git a/cmd/testdata/non-existent-processor.txt b/cmd/testdata/non-existent-processor.txt
index 142cc51..658622d 100644
--- a/cmd/testdata/non-existent-processor.txt
+++ b/cmd/testdata/non-existent-processor.txt
@@ -20,6 +20,7 @@ Flags:
   -l, --loglevel string           log level (default "info")
       --output-module string      filepath where to save the resulting kernel module
       --output-probe string       filepath where to save the resulting eBPF probe
+      --proxy string              the proxy to use to download data
   -t, --target string             the system to target the build for
       --timeout int               timeout in seconds (default 60)
   -v, --version                   version for driverkit