Repository: falcosecurity/driverkit
Pull request #131 (2022-04-01 13:50:49)

Add builder for Flatcar
<!--  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"
-->
**Example output**
```
# Requires the builderimage to be updated, so replicating this might need also a --builderimage argument
./_output/bin/driverkit docker -l debug -t flatcar --kernelrelease 3255.0.0  --output-module=/tmp/falco.ko --driverversion master

DEBU running without a configuration file         
DEBU running with options                          arch=amd64 driverversion=master kernelrelease=3255.0.0 kernelversion=1 output-module=/tmp/falco.ko target=flatcar
INFO driver building, it will take a few seconds   processor=docker
DEBU doing a new docker build                     
DEBU kernel header url found                       url="https://alpha.release.flatcar-linux.net/amd64-usr/3255.0.0/flatcar_production_image_packages.txt"
DEBU kernel header url found                       url="https://alpha.release.flatcar-linux.net/amd64-usr/3255.0.0/flatcar_production_image_kernel_config.txt"
DEBU kernel header url found                       url="https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.43.tar.xz"
...
DEBU make[1]: Leaving directory '/tmp/kernel' 
DEBU + mv falco.ko /tmp/driver/module.ko  
DEBU + strip -g /tmp/driver/module.ko     
DEBU + modinfo /tmp/driver/module.ko      
DEBU filename:       /tmp/driver/module.ko 
DEBU schema_version: 1.0.0                        
DEBU api_version:    1.0.0                        
DEBU build_commit:   master                       
DEBU version:        master                       
DEBU author:         the Falco authors            
DEBU license:        GPL                          
DEBU srcversion:     F2DE132E5F90A47892185E9      
DEBU depends:                                     
DEBU retpoline:      Y                            
DEBU name:           falco                        
DEBU vermagic:       5.15.43-flatcar SMP mod_unload  
DEBU parm:           max_consumers:Maximum number of consumers that can simultaneously open the devices (uint) 
DEBU parm:           verbose:Enable verbose logging (bool) 
DEBU log pipe close                                error=EOF
INFO kernel module available                       path=/tmp/falco.ko
DEBU context canceled
```

**What type of PR is this?**
/kind feature

<!--
Please remove the leading whitespace before the `/kind <>` you uncommented.
-->

**Any specific area of the project related to this PR?**
/area build

**What this PR does / why we need it**:
This PR introduces a builder for Flatcar. There have been several reports of vanilla builder not working well for Flatcar (https://github.com/falcosecurity/driverkit/issues?q=flatcar), we also have reports on our own issue tracker of the stock Falco build process not working since we shipped a newer GLIBC (https://github.com/flatcar-linux/Flatcar/issues/534). In slack there are occasional questions in slack regarding the kernel module build for Flatcar. So we know that there are plenty of users that would like this to work.
 
**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`.
-->
Example output:

 
Fixes #

**Special notes for your reviewer**:
I tried to not introduce an extra "osrelease" or "osversion" field but that's actually what we would need. I snuck the "osrelease" into the "kernelrelease" field instead, hope that's ok. Also: any thoughts on getting a GCC newer than 8 into the builder container?

**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
Add support for Flatcar Container Linux
```


poiana: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/falcosecurity/driverkit/pull/131#" title="Author self-approved">jepio</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **leodido** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @leodido` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://prow.falco.org/command-help?repo=falcosecurity%2Fdriverkit).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/falcosecurity/driverkit/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["leodido"]} -->
poiana: Welcome @jepio! It looks like this is your first PR to falcosecurity/driverkit 🎉
jepio: @leodido any chance you could have a look at this?
dwindsor: Hi! This looks fine to me. kernel-crawler already has [support](https://github.com/falcosecurity/kernel-crawler/blob/main/kernel_crawler/flatcar.py) for Flatcar, so it makes sense to add this.
dwindsor: > Thanks for the link to kernel-crawler, I checked what output it produces, and decided to drop the -flatcar from the kernelrelease value to be consistent with it. None of the package naming/formats/locations have changed, but I did catch 2 minor issues that I fixed: I was missing setting EXTRAVERSION correctly and we need dwarves/pahole in the builder container.

Nice! Thanks for the updates. Have you had a chance to try to load the built drivers/probes on Flatcar yet?
jepio: Yes, that's how I came across the issues 2 issues I found. The module now loads correctly on flatcar.
dwindsor: Could you update the description with an example command-line invocation building a flatcar driver with successful results as in [PR130](https://github.com/falcosecurity/driverkit/pull/130):

![Screen Shot 2022-06-03 at 1 40 20 PM](https://user-images.githubusercontent.com/934449/171917630-29ebd076-7316-40dc-9d1b-861de20ed312.png)


Patch:
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 588c706..08b20fa 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -9,7 +9,6 @@ builds:
     main: .
     flags:
       - -v 
-      - -buildmode=pie
       - "-tags={{ .Env.GOTAGS }}"
     ldflags: 
       - "{{.Env.LDFLAGS}}"
diff --git a/Makefile b/Makefile
index 89f60f5..021a4ec 100644
--- a/Makefile
+++ b/Makefile
@@ -49,7 +49,7 @@ driverkit_docgen ?= _output/bin/docgen
 build: clean ${driverkit}
 
 ${driverkit}:
-	CGO_ENABLED=1 go build -v -buildmode=pie -ldflags '${LDFLAGS}' -tags '${GOTAGS}' -o $@ .
+	CGO_ENABLED=1 go build -v -ldflags '${LDFLAGS}' -tags '${GOTAGS}' -o $@ .
 
 .PHONY: release
 release: clean
diff --git a/README.md b/README.md
index 9d411da..b5029d7 100644
--- a/README.md
+++ b/README.md
@@ -169,6 +169,20 @@ target: debian
 driverversion: master
 ```
 
+### flatcar
+
+Example configuration file to build both the Kernel module and eBPF probe for Flatcar.
+The Flatcar release version needs to be provided in the `kernelrelease` field instead of the kernel version.
+
+```yaml
+kernelrelease: 3185.0.0
+target: flatcar
+output:
+  module: /tmp/falco-flatcar-3185.0.0.ko
+  probe: /tmp/falco-flatcar-3185.0.0.o
+driverversion: master
+```
+
 ### vanilla
 
 In case of vanilla, you also need to pass the kernel config data in base64 format.
diff --git a/build/builder.Dockerfile b/build/builder.Dockerfile
index 15f439b..7bcba37 100644
--- a/build/builder.Dockerfile
+++ b/build/builder.Dockerfile
@@ -5,6 +5,7 @@ LABEL maintainer="cncf-falco-dev@lists.cncf.io"
 ARG TARGETARCH
 
 RUN cp /etc/skel/.bashrc /root && cp /etc/skel/.profile /root
+RUN echo 'deb http://deb.debian.org/debian buster-backports main' >>/etc/apt/sources.list
 
 RUN apt-get update \
 	&& apt-get install -y --no-install-recommends \
@@ -14,6 +15,7 @@ RUN apt-get update \
 	ca-certificates \
 	curl \
 	dkms \
+	dwarves/buster-backports \
 	gnupg2 \
 	gcc \
 	jq \
diff --git a/pkg/driverbuilder/builder/flatcar.go b/pkg/driverbuilder/builder/flatcar.go
new file mode 100644
index 0000000..64aaaa8
--- /dev/null
+++ b/pkg/driverbuilder/builder/flatcar.go
@@ -0,0 +1,234 @@
+package builder
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strconv"
+	"strings"
+	"text/template"
+
+	"github.com/falcosecurity/driverkit/pkg/kernelrelease"
+)
+
+// TargetTypeFlatcar identifies the Flatcar target.
+const TargetTypeFlatcar Type = "flatcar"
+
+func init() {
+	BuilderByTarget[TargetTypeFlatcar] = &flatcar{}
+}
+
+// flatcar is a driverkit target.
+type flatcar struct {
+}
+
+// Script compiles the script to build the kernel module and/or the eBPF probe.
+func (c flatcar) Script(cfg Config) (string, error) {
+	t := template.New(string(TargetTypeFlatcar))
+	parsed, err := t.Parse(flatcarTemplate)
+	if err != nil {
+		return "", err
+	}
+
+	kr :=  kernelReleaseFromBuildConfig(cfg.Build)
+	if kr.Extraversion != "" {
+		return "", fmt.Errorf("unexpected extraversion: %s", kr.Extraversion)
+	}
+
+	// convert string to int
+	version, err := strconv.Atoi(kr.Version)
+	if err != nil {
+		return "", err
+	}
+	if version < 1500 {
+		return "", fmt.Errorf("not a valid flatcar release version: %s", kr.Version)
+	}
+	flatcarVersion := kr.Fullversion
+	flatcarInfo, err := fetchFlatcarMetadata(kr)
+	if err != nil {
+		return "", err
+	}
+
+	kconfUrls, err := getResolvingURLs(fetchFlatcarKernelConfigURL(kr.Architecture, flatcarInfo.Channel, kr.Fullversion))
+	if err != nil {
+		return "", err
+	}
+
+	// Check (and filter) existing kernels before continuing
+	urls, err := getResolvingURLs(fetchFlatcarKernelURLS(flatcarInfo.KernelVersion))
+	if err != nil {
+		return "", err
+	}
+
+	td := flatcarTemplateData{
+		DriverBuildDir:    DriverDirectory,
+		ModuleDownloadURL: moduleDownloadURL(cfg),
+		KernelDownloadURL: urls[0],
+		GCCVersion:        flatcarGccVersion(flatcarInfo.GCCVersion),
+		FlatcarVersion:    flatcarVersion,
+		FlatcarChannel:    flatcarInfo.Channel,
+		KernelConfigURL:   kconfUrls[0],
+		ModuleDriverName:  cfg.DriverName,
+		ModuleFullPath:    ModuleFullPath,
+		BuildModule:       len(cfg.Build.ModuleFilePath) > 0,
+		BuildProbe:        len(cfg.Build.ProbeFilePath) > 0,
+	}
+
+	buf := bytes.NewBuffer(nil)
+	err = parsed.Execute(buf, td)
+	if err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
+
+func fetchFlatcarMetadata(kr kernelrelease.KernelRelease) (*flatcarReleaseInfo, error) {
+	flatcarInfo := flatcarReleaseInfo{}
+	flatcarVersion := kr.Fullversion
+	packageIndexUrl, err := getResolvingURLs(fetchFlatcarPackageListURL(kr.Architecture, flatcarVersion))
+	if err != nil {
+		return nil, err
+	}
+	// first part of the URL is the channel
+	flatcarInfo.Channel = strings.Split(packageIndexUrl[0], ".")[0][len("https://"):]
+	resp, err := http.Get(packageIndexUrl[0])
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	packageListBytes, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+	packageList := string(packageListBytes)
+	if len(packageListBytes) == 0 {
+		return nil, fmt.Errorf("missing package list for %s", flatcarVersion)
+	}
+
+	gccVersion := ""
+	kernelVersion := ""
+	// structure of a package line is: category/name-version(-revision)::repository
+	for _, pkg := range strings.Split(string(packageList), "\n") {
+		if strings.HasPrefix(pkg, "sys-devel/gcc") {
+			gccVersion = pkg[len("sys-devel/gcc-"):]
+			gccVersion = strings.Split(gccVersion, "::")[0]
+			gccVersion = strings.Split(gccVersion, "-")[0]
+		}
+		if strings.HasPrefix(pkg, "sys-kernel/coreos-kernel") {
+			kernelVersion = pkg[len("sys-kernel/coreos-kernel-"):]
+			kernelVersion = strings.Split(kernelVersion, "::")[0]
+			kernelVersion = strings.Split(kernelVersion, "-")[0]
+		}
+	}
+	flatcarInfo.GCCVersion = gccVersion
+	flatcarInfo.KernelVersion = kernelVersion
+
+	return &flatcarInfo, nil
+}
+
+func fetchFlatcarPackageListURL(architecture kernelrelease.Architecture, flatcarVersion string) []string {
+	pattern := "https://%s.release.flatcar-linux.net/%s-usr/%s/flatcar_production_image_packages.txt"
+	channels := []string{
+		"stable",
+		"beta",
+		"alpha",
+	}
+	urls := []string{}
+	for _, channel := range channels {
+		urls = append(urls, fmt.Sprintf(pattern, channel, architecture.String(), flatcarVersion))
+	}
+	return urls
+}
+
+func fetchFlatcarKernelConfigURL(architecture kernelrelease.Architecture, flatcarChannel, flatcarVersion string) []string {
+	return []string{fmt.Sprintf("https://%s.release.flatcar-linux.net/%s-usr/%s/flatcar_production_image_kernel_config.txt", flatcarChannel, architecture.String(), flatcarVersion)}
+}
+
+func fetchFlatcarKernelURLS(kernelVersion string) []string {
+	kv := kernelrelease.FromString(kernelVersion)
+	return []string{fmt.Sprintf("https://cdn.kernel.org/pub/linux/kernel/v%s.x/linux-%s.tar.xz", kv.Version, kv.Fullversion)}
+}
+
+type flatcarReleaseInfo struct {
+	Channel       string
+	GCCVersion    string
+	KernelVersion string
+}
+
+type flatcarTemplateData struct {
+	DriverBuildDir    string
+	ModuleDownloadURL string
+	KernelDownloadURL string
+	GCCVersion        string
+	FlatcarVersion    string
+	FlatcarChannel    string
+	KernelConfigURL   string
+	ModuleDriverName  string
+	ModuleFullPath    string
+	BuildModule       bool
+	BuildProbe        bool
+}
+
+const flatcarTemplate = `
+#!/bin/bash
+set -xeuo pipefail
+
+rm -Rf {{ .DriverBuildDir }}
+mkdir {{ .DriverBuildDir }}
+rm -Rf /tmp/module-download
+mkdir -p /tmp/module-download
+
+curl --silent -SL {{ .ModuleDownloadURL }} | tar -xzf - -C /tmp/module-download
+mv /tmp/module-download/*/driver/* {{ .DriverBuildDir }}
+
+cp /driverkit/module-Makefile {{ .DriverBuildDir }}/Makefile
+bash /driverkit/fill-driver-config.sh {{ .DriverBuildDir }}
+
+# Fetch the kernel
+mkdir /tmp/kernel-download
+cd /tmp/kernel-download
+curl --silent -SL {{ .KernelDownloadURL }} | tar -Jxf - -C /tmp/kernel-download
+rm -Rf /tmp/kernel
+mkdir -p /tmp/kernel
+mv /tmp/kernel-download/*/* /tmp/kernel
+
+# Change current gcc
+ln -sf /usr/bin/gcc-{{ .GCCVersion }} /usr/bin/gcc
+
+curl --silent -o /tmp/kernel.config -SL {{ .KernelConfigURL }}
+
+cd /tmp/kernel
+sed -i -e 's|^\(EXTRAVERSION =\).*|\1 -flatcar|' Makefile
+make KCONFIG_CONFIG=/tmp/kernel.config oldconfig
+make KCONFIG_CONFIG=/tmp/kernel.config modules_prepare
+
+{{ if .BuildModule }}
+# Build the module
+cd {{ .DriverBuildDir }}
+make KERNELDIR=/tmp/kernel
+mv {{ .ModuleDriverName }}.ko {{ .ModuleFullPath }}
+strip -g {{ .ModuleFullPath }}
+# Print results
+modinfo {{ .ModuleFullPath }}
+{{ end }}
+
+{{ if .BuildProbe }}
+# Build the eBPF probe
+cd {{ .DriverBuildDir }}/bpf
+make LLC=/usr/bin/llc-12 CLANG=/usr/bin/clang-12 CC=/usr/bin/gcc KERNELDIR=/tmp/kernel
+ls -l probe.o
+{{ end }}
+`
+
+func flatcarGccVersion(gccVersion string) string {
+	// reuse kernelrelease version parsing for gcc
+	gv := kernelrelease.FromString(gccVersion)
+	switch gv.Version {
+	case "7":
+		return "6"
+	default:
+		// builder doesn't support anything newer than 8 right now
+		return "8"
+	}
+}


Test patch:
diff --git a/cmd/testdata/completion-targets.txt b/cmd/testdata/completion-targets.txt
index cba01a5..6bfc973 100644
--- a/cmd/testdata/completion-targets.txt
+++ b/cmd/testdata/completion-targets.txt
@@ -2,6 +2,7 @@ amazonlinux
 amazonlinux2
 centos
 debian
+flatcar
 rocky
 ubuntu-aws
 ubuntu-generic