From 9d819d6f7aefb444c2b5292bd6e9a4e82719ad41 Mon Sep 17 00:00:00 2001 From: Michael Ducy Date: Mon, 26 Aug 2019 15:43:40 -0400 Subject: [PATCH] add init container module loader Signed-off-by: Michael Ducy --- docker/kernel/httploader/Dockerfile | 11 ++ .../httploader/falcoloader/loader.go | 148 ++++++++++++++++++ docker/kernel/httploader/httploader/main.go | 50 ++++++ docker/kernel/linuxkit/Dockerfile | 34 ++-- .../falco-daemonset-configmap-slim.yaml | 93 +++++++++++ 5 files changed, 313 insertions(+), 23 deletions(-) create mode 100644 docker/kernel/httploader/Dockerfile create mode 100644 docker/kernel/httploader/httploader/falcoloader/loader.go create mode 100644 docker/kernel/httploader/httploader/main.go create mode 100644 integrations/k8s-using-daemonset/k8s-with-rbac/falco-daemonset-configmap-slim.yaml diff --git a/docker/kernel/httploader/Dockerfile b/docker/kernel/httploader/Dockerfile new file mode 100644 index 00000000000..1bad4dcc2bd --- /dev/null +++ b/docker/kernel/httploader/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:alpine AS build + +RUN apk --no-cache add build-base git bzr mercurial gcc ca-certificates + +ADD ./httploader /src +RUN cd /src && go get golang.org/x/sys/unix && CGO_ENABLED=0 GOOS=linux go build -a -o httploader -ldflags '-extldflags "-static"' . + +FROM scratch +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=build /src/httploader / +ENTRYPOINT /httploader \ No newline at end of file diff --git a/docker/kernel/httploader/httploader/falcoloader/loader.go b/docker/kernel/httploader/httploader/falcoloader/loader.go new file mode 100644 index 00000000000..ece51ee338a --- /dev/null +++ b/docker/kernel/httploader/httploader/falcoloader/loader.go @@ -0,0 +1,148 @@ +package falcoloader + +import "io/ioutil" +import "io" +import "os" +import "crypto/md5" +import "encoding/hex" +import "strings" +import "net/http" +import "golang.org/x/sys/unix" +import "log" +import "unsafe" +import "compress/gzip" +import "bytes" + +func GetKernelVersion() string { + path := "/proc/version" + + file, err := ioutil.ReadFile(path) + if err != nil { + return "invaildVersion" + } + version_string := string(file) + + version_fields := strings.Split(version_string, " ") + + return version_fields[2] +} + +func GetKernelConfigHash() string { + var hash string + kernelConfigPath := getKernelConfigPath() + hash, _ = genKernelConfigHash(kernelConfigPath) + + return hash +} + +func getKernelConfigPath() string { + kernelConfigPath := "" + + version := GetKernelVersion() + paths := []string{ + "/proc/config.gz", + "/boot/config-" + version, + "/host/boot/config-" + version, + "/usr/lib/ostree-boot/config-" + version, + "/usr/lib/ostree-boot/config-" + version, + "/lib/modules/" + version + "/config" } + + for i := range paths { + _, err := os.Stat(paths[i]) + if err != nil { + continue; + } + log.Print("Found kernel config: " + paths[i]) + return paths[i] + } + log.Fatal("No kernel config found") + return kernelConfigPath +} + +func genKernelConfigHash(path string) (string, error) { + var md5hash string + var err error + var buf bytes.Buffer + + if strings.HasSuffix(path, "gz") { + log.Print("Kernel config " + path + " is gz compressed") + tmpfile, err := os.Open(path) + if err != nil { + return md5hash, err + } + defer tmpfile.Close() + + file, err := gzip.NewReader(tmpfile) + if err != nil { + return md5hash, err + } + defer file.Close() + io.Copy(&buf, file) + } else { + file, err := os.Open(path) + if err != nil { + return md5hash, err + } + defer file.Close() + io.Copy(&buf,file) + } + + hash := md5.New() + if _, err := io.Copy(hash, &buf); err != nil { + return md5hash, err + } + md5hash = hex.EncodeToString(hash.Sum(nil)) + log.Print("Hash calculated: " + md5hash) + + return md5hash, err + +} + +func FetchModule(url string, path string) error { + log.Printf("Downloading kernel module from %s", url) + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + log.Printf("Recevied HTTP Status Code: %d", resp.StatusCode) + if resp.StatusCode == 200 { + out, err := os.Create(path) + if err != nil { + log.Fatalf("Error creating file: %s", path) + return err + } + defer out.Close() + + _, err = io.Copy(out, resp.Body) + if err != nil { + log.Fatalf("Unable to write file: %s", path) + return err + } + log.Printf("Wrote kernel module: %s", path) + } else { + log.Fatal("Non-200 Status code received.") + } + return err + +} + +func LoadModule(path string) error { + + file, err := os.Open(path) + if err != nil { + log.Fatalf("Error opening kernel module: %s", path) + return err + } + + log.Print("Opened probe: " + path) + + _p0, err := unix.BytePtrFromString("") + + if _, _, err := unix.Syscall(313, file.Fd(), uintptr(unsafe.Pointer(_p0)), 0); err != 0 { + log.Fatalf("Error loading kernel module: %s. The module may already be loaded.", path) + return err + } + + return err +} diff --git a/docker/kernel/httploader/httploader/main.go b/docker/kernel/httploader/httploader/main.go new file mode 100644 index 00000000000..8094bc34b57 --- /dev/null +++ b/docker/kernel/httploader/httploader/main.go @@ -0,0 +1,50 @@ +package main + +import "os" +import "log" +import "fmt" +import "./falcoloader" + +// Default behavior: calculate kernel module and download from Falco hosted probe library +// ENV FALCO_PROBE_URL = URL to download probe.ko file +// ENV FALCO_PROBE_REPO = URL to download probe.ko, probe name derived from `uname -r` + +func main() { + falco_version := getEnv("FALCO_VERSION","0.17.0") + falco_probe_path := getEnv("FALCO_PROBE_PATH","/") + falco_probe_file := getEnv("FALCO_PROBE_FILE","falco-probe.ko") + falco_probe_fullpath := falco_probe_path + falco_probe_file + falco_probe_url := getEnv("FALCO_PROBE_URL","") + falco_probe_repo := getEnv("FALCO_PROBE_REPO","https://s3.amazonaws.com/download.draios.com/stable/sysdig-probe-binaries/") + falco_config_hash := falcoloader.GetKernelConfigHash() + falco_kernel_version := falcoloader.GetKernelVersion() + + log.Printf("FALCO_VERSION: %s", falco_version) + log.Printf("FALCO_PROBE_URL: %s", falco_probe_url) + log.Printf("FALCO_PROBE_REPO: %s", falco_probe_repo) + log.Printf("KERNEL_VERSION: %s", falco_kernel_version) + log.Printf("KERNEL_CONFIG_HASH: %s", falco_config_hash) + + // if FALCO_PROBE_URL not set, build it + if falco_probe_url == "" { + falco_probe_url = fmt.Sprintf("%sfalco-probe-%s-x86_64-%s-%s.ko", falco_probe_repo, falco_version, falco_kernel_version, falco_config_hash) + } + + // fetch module + if err := falcoloader.FetchModule(falco_probe_url, falco_probe_fullpath); err != nil { + panic(err) + } + + // load module + if err := falcoloader.LoadModule(falco_probe_fullpath); err != nil { + panic(err) + } +} + +func getEnv(key, def string) string { + value, isSet := os.LookupEnv(key) + if (isSet) { + return value + } + return def +} diff --git a/docker/kernel/linuxkit/Dockerfile b/docker/kernel/linuxkit/Dockerfile index 2ae87181ef3..e14c8918def 100644 --- a/docker/kernel/linuxkit/Dockerfile +++ b/docker/kernel/linuxkit/Dockerfile @@ -1,15 +1,14 @@ FROM linuxkit/kernel:4.9.184 AS ksrc -FROM alpine:3.4 AS probe-build -ARG FALCOVER=0.17.0 -ARG SYSDIGVER=0.26.2 +FROM falcosecurity/falco-minimal as falco +FROM alpine:3.10 AS probe-build +ARG FALCO_VERSION=0.17.0 COPY --from=ksrc /kernel-dev.tar / +COPY --from=falco /usr/src/falco-${FALCO_VERSION} /usr/src/falco-${FALCO_VERSION} -RUN apk add --no-cache --update wget ca-certificates \ +RUN apk add --no-cache --update \ build-base gcc abuild binutils \ bc \ - cmake \ - git \ autoconf && \ export KERNELVER=`uname -r | cut -d '-' -f 1` && \ export KERNELDIR=/usr/src/linux-headers-4.9.184-linuxkit/ && \ @@ -17,25 +16,14 @@ RUN apk add --no-cache --update wget ca-certificates \ cd $KERNELDIR && \ zcat /proc/1/root/proc/config.gz > .config && \ make olddefconfig && \ - mkdir -p /falco/build && \ - mkdir /src && \ - cd /src && \ - wget https://github.com/falcosecurity/falco/archive/$FALCOVER.tar.gz && \ - tar zxf $FALCOVER.tar.gz && \ - wget https://github.com/draios/sysdig/archive/$SYSDIGVER.tar.gz && \ - tar zxf $SYSDIGVER.tar.gz && \ - mv sysdig-$SYSDIGVER sysdig && \ - cd /falco/build && \ - cmake /src/falco-$FALCOVER && \ - make driver && \ - rm -rf /src && \ - apk del wget ca-certificates \ + cd /usr/src/falco-${FALCO_VERSION} && \ + make && \ + apk del \ build-base gcc abuild binutils \ bc \ - cmake \ - git \ autoconf -FROM alpine:3.4 -COPY --from=probe-build /falco/build/driver/falco-probe.ko / +FROM alpine:3.10 +ARG FALCO_VERSION=0.17.0 +COPY --from=probe-build /usr/src/falco-${FALCO_VERSION}/falco-probe.ko / CMD ["insmod","/falco-probe.ko"] diff --git a/integrations/k8s-using-daemonset/k8s-with-rbac/falco-daemonset-configmap-slim.yaml b/integrations/k8s-using-daemonset/k8s-with-rbac/falco-daemonset-configmap-slim.yaml new file mode 100644 index 00000000000..1671b67e875 --- /dev/null +++ b/integrations/k8s-using-daemonset/k8s-with-rbac/falco-daemonset-configmap-slim.yaml @@ -0,0 +1,93 @@ +apiVersion: extensions/v1beta1 +kind: DaemonSet +metadata: + name: falco-daemonset + labels: + app: falco-example + role: security +spec: + template: + metadata: + labels: + app: falco-example + role: security + spec: + serviceAccount: falco-account + initContainers: + - name: httploader + image: falcosecurity/httploader:latest + command: ['/httploader'] + securityContext: + privileged: true + #env: + # - name: FALCO_VERSION + # value: 0.17.0 + # - name: FALCO_PROBE_URL + # value: + # - name: FALCO_PROBE_REPO + # value: "https://s3.amazonaws.com/download.draios.com/stable/sysdig-probe-binaries/" + containers: + - name: falco + image: falcosecurity/falco-minimal:latest + securityContext: + privileged: true +# Uncomment the 3 lines below to enable eBPF support for Falco. +# This allows Falco to run on Google COS. +# Leave blank for the default probe location, or set to the path +# of a precompiled probe. +# env: +# - name: SYSDIG_BPF_PROBE +# value: "" + args: [ "/usr/bin/falco", "--cri", "/host/run/containerd/containerd.sock", "-K", "/var/run/secrets/kubernetes.io/serviceaccount/token", "-k", "https://$(KUBERNETES_SERVICE_HOST)", "-pk"] + volumeMounts: + - mountPath: /host/var/run/docker.sock + name: docker-socket + - mountPath: /host/run/containerd/containerd.sock + name: containerd-socket + - mountPath: /host/dev + name: dev-fs + - mountPath: /host/proc + name: proc-fs + readOnly: true + - mountPath: /host/boot + name: boot-fs + readOnly: true + - mountPath: /host/lib/modules + name: lib-modules + readOnly: true + - mountPath: /host/usr + name: usr-fs + readOnly: true + - mountPath: /host/etc/ + name: etc-fs + readOnly: true + - mountPath: /etc/falco + name: falco-config + volumes: + - name: docker-socket + hostPath: + path: /var/run/docker.sock + - name: containerd-socket + hostPath: + path: /run/containerd/containerd.sock + - name: dev-fs + hostPath: + path: /dev + - name: proc-fs + hostPath: + path: /proc + - name: boot-fs + hostPath: + path: /boot + - name: lib-modules + hostPath: + path: /lib/modules + - name: usr-fs + hostPath: + path: /usr + - name: etc-fs + hostPath: + path: /etc + - name: falco-config + configMap: + name: falco-config