From 744aa4f0e8233e673597521f44fe00f719e7192d Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Fri, 20 Jan 2023 11:40:13 +0100 Subject: [PATCH 01/46] fix: separate yaml testing from yaml release configuration --- .github/workflows/build-app.yml | 2 +- cr.yaml | 11 +++++++++++ ct.yaml | 6 +----- 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 cr.yaml diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml index 1317e0e..2661ca4 100644 --- a/.github/workflows/build-app.yml +++ b/.github/workflows/build-app.yml @@ -142,4 +142,4 @@ jobs: env: CR_TOKEN: "${{ env.GITHUB_TOKEN }}" with: - config: ct.yaml + config: cr.yaml diff --git a/cr.yaml b/cr.yaml new file mode 100644 index 0000000..278fd76 --- /dev/null +++ b/cr.yaml @@ -0,0 +1,11 @@ +# See https://github.com/helm/chart-testing#configuration +remote: origin +target-branch: main +chart-dirs: + - ./charts +chart-repos: + - kapparmor=https://tuxerrante.github.io/kapparmor/ +helm-extra-args: --timeout 600s +validate-maintainers: true +generate-release-notes: true +make-release-latest: false diff --git a/ct.yaml b/ct.yaml index 278fd76..7c8e870 100644 --- a/ct.yaml +++ b/ct.yaml @@ -1,11 +1,7 @@ -# See https://github.com/helm/chart-testing#configuration -remote: origin -target-branch: main +# See https://github.com/helm/chart-releaser chart-dirs: - ./charts chart-repos: - kapparmor=https://tuxerrante.github.io/kapparmor/ helm-extra-args: --timeout 600s validate-maintainers: true -generate-release-notes: true -make-release-latest: false From 75315ec29e53948324657f1de211fef6312d8685 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Fri, 20 Jan 2023 12:25:44 +0100 Subject: [PATCH 02/46] feature: release-notes-file CHANGELOG.md --- CHANGELOG.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ cr.yaml | 1 + 2 files changed, 50 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d52249e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,49 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +1. Go unit tests + - [ ] Create a new profile + - [ ] Update an existing profile + - [ ] Remove an existing profile + - [ ] Remove a non existing profile +1. Remove kubernetes Service and DaemonSet exposed ports if useless +1. Evaluate an automatic changelog generation from commits like [googleapis/release-please](https://github.com/googleapis/release-please) + +## [0.0.6]() - + +### Added + +Helm: + +Go: + +CI/CD: +- Explicit changelog to help users understanding the project features + - Automatic generation of release notes based on changelog file + + +## [0.0.5](https://github.com/tuxerrante/kapparmor/releases/tag/kapparmor-0.0.5-alpha) - 2023-01-23 + +### Added + +Helm: +- Helm Chart based mainly on a DaemonSet and a configmap. No operator needed. +- Load all AppArmor profiles in the configmap template + +Go: +- Possibility to load continuously the security profiles from a configmap with a configurable poll time + +CI/CD: +- Helm chart linting and testing before releasing +- Security vulnerability tests on Go dependencies and container file. +- Auto generation of [GitHub pages](https://tuxerrante.github.io/kapparmor/) +- Container image tag is set to current commit SHA for every release. + +### Fixed + +- Being still an alpha release I will add everything in the "Added" section diff --git a/cr.yaml b/cr.yaml index 278fd76..43f404c 100644 --- a/cr.yaml +++ b/cr.yaml @@ -9,3 +9,4 @@ helm-extra-args: --timeout 600s validate-maintainers: true generate-release-notes: true make-release-latest: false +release-notes-file: CHANGELOG.md \ No newline at end of file From cb94b51e57ee976bdca4b102eb7b005af7ee67ee Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Fri, 20 Jan 2023 12:26:49 +0100 Subject: [PATCH 03/46] refactor: readme chapters --- README.md | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index eb26e16..dd836b5 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,9 @@ # Kapparmor - [Kapparmor](#kapparmor) - - [Prerequisites](#prerequisites) - - [How to initialize this project again](#how-to-initialize-this-project-again) + - [Testing](#testing) + - [How to initialize this project](#how-to-initialize-this-project) - [Test the app locally](#test-the-app-locally) - - [TO-DO](#to-do) - [External useful links](#external-useful-links) - ----- Apparmor-loader project to deploy profiles through a kubernetes daemonset. @@ -24,10 +23,10 @@ This work is inspired by [kubernetes/apparmor-loader](https://github.com/kuberne - The name of the file should be the same as the name of the profile. 3. The configmap will be polled every POLL_TIME seconds to move them into PROFILES_DIR host path and then enable them. -## Prerequisites +## Testing [Set up a Microk8s environment](./docs/microk8s.md). -### How to initialize this project again +### How to initialize this project ```sh helm create kapparmor sudo usermod -aG docker $USER @@ -63,13 +62,6 @@ docker build --quiet -t test-kapparmor --build-arg POLL_TIME=60 --build-arg PROF ``` -## TO-DO -1. Go unit tests - - [ ] Create a new profile - - [ ] Update an existing profile - - [ ] Remove an existing profile - - [ ] Remove a non existing profile -1. Remove kubernetes Service and DaemonSet exposed ports if useless # External useful links From a76a68459335242c49315c292b70e777f6b004e4 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Fri, 20 Jan 2023 12:27:13 +0100 Subject: [PATCH 04/46] fix: chart annotations are not used --- charts/kapparmor/Chart.yaml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/charts/kapparmor/Chart.yaml b/charts/kapparmor/Chart.yaml index 85e7ba4..dc58875 100644 --- a/charts/kapparmor/Chart.yaml +++ b/charts/kapparmor/Chart.yaml @@ -17,13 +17,3 @@ keywords: maintainers: - name: tuxerrante url: https://github.com/sponsors/tuxerrante - -annotations: - artifacthub.io/containsSecurityUpdates: "false" - artifacthub.io/changes: | - - kind: added - description: Load new profiles in the configmap - - kind: added - description: Unload old profiles in the filesystem - - kind: added - description: Update profiles with same name and different content From a27fcb1e54f90e4a47e876451fb4eb56f8de70c5 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Fri, 20 Jan 2023 12:27:31 +0100 Subject: [PATCH 05/46] fix: user has to be root in the container --- Dockerfile | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9af11a1..abb3d26 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,15 +13,11 @@ LABEL Author="Affinito Alessandro" WORKDIR /app -RUN addgroup --system appgroup &&\ - adduser --system appuser -G appgroup &&\ - apk --no-cache update &&\ +RUN apk --no-cache update &&\ apk add apparmor -COPY --chown=appuser:appgroup --from=builder ./go/bin/app /app/ -COPY --chown=appuser:appgroup ./charts/kapparmor/profiles /app/profiles - -RUN chmod 550 app +COPY --from=builder ./go/bin/app /app/ +COPY ./charts/kapparmor/profiles /app/profiles ARG PROFILES_DIR ARG POLL_TIME @@ -29,5 +25,5 @@ ARG POLL_TIME ENV PROFILES_DIR=$PROFILES_DIR ENV POLL_TIME=$POLL_TIME -USER appuser +USER root CMD ./app \ No newline at end of file From 6f662aa15dc0f9a242f26066185997ce71de72d8 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Mon, 23 Jan 2023 20:48:57 +0100 Subject: [PATCH 06/46] feature: testing function for checking if two profiles have the same content --- go/src/app/hasTheSameContent_test.go | 80 ++++++++++++++++++++++++++++ go/src/app/main.go | 69 +++++++++++++----------- 2 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 go/src/app/hasTheSameContent_test.go diff --git a/go/src/app/hasTheSameContent_test.go b/go/src/app/hasTheSameContent_test.go new file mode 100644 index 0000000..45ad37c --- /dev/null +++ b/go/src/app/hasTheSameContent_test.go @@ -0,0 +1,80 @@ +package main + +import ( + "testing" + "testing/fstest" +) + +const ( + testProfileData = ` +#include + +# a comment naming the application to confine +/usr/bin/foo { +#include + +link /etc/sysconfig/foo -> /etc/foo.conf, +}` + + testProfileDataExtraNewLine = ` +#include + +# a comment naming the application to confine +/usr/bin/foo { +#include + +link /etc/sysconfig/foo -> /etc/foo.conf, +} +` + testProfileDataDifferent = ` +#include + +# a comment naming the application to confine +/usr/bin/bar { +#include + +link /etc/sysconfig/foo -> /etc/foo.conf, +} +` +) + +func TestHasTheSameContent(t *testing.T) { + + fs := fstest.MapFS{ + "foo.profile": {Data: []byte(testProfileData)}, + "foo.profile.copy": {Data: []byte(testProfileData)}, + "foo.newline.profile": {Data: []byte(testProfileDataExtraNewLine)}, + "bar.profile": {Data: []byte(testProfileDataDifferent)}, + } + + // The current directory should change to allow mocking the filesystem + got, err := HasTheSameContent(fs, "foo.profile", "foo.profile.copy") + want := true + ok(t, err) + assertBool(t, got, want) + + // We don't forgive newlines + got, err = HasTheSameContent(fs, "foo.profile", "foo.newline.profile") + want = false + ok(t, err) + assertBool(t, got, want) + + // Very different profiles + got, err = HasTheSameContent(fs, "foo.profile", "bar.profile") + want = false + ok(t, err) + assertBool(t, got, want) +} + +func ok(t testing.TB, err error) { + if err != nil { + t.Fatalf("Function call returned an error:\n %s", err) + } +} + +func assertBool(t *testing.T, got, want bool) { + t.Helper() + if got != want { + t.Fatalf("Bool check failed! Got %t, expected %t", got, want) + } +} diff --git a/go/src/app/main.go b/go/src/app/main.go index 897d6f6..37f74df 100644 --- a/go/src/app/main.go +++ b/go/src/app/main.go @@ -3,10 +3,11 @@ package main import ( "bufio" "bytes" - "crypto/sha256" "errors" "fmt" "io" + "io/fs" + "io/ioutil" "log" "os" "os/exec" @@ -141,14 +142,13 @@ func loadNewProfiles() ([]string, error) { // If the profile is exactly the same skip the apply // ERROR: it checks profiles still not applied - filePath2 := path.Join(ETC_APPARMORD, newProfileName) - contentIsTheSame, err := hasTheSameContent(filePath1, filePath2) + contentIsTheSame, err := HasTheSameContent(os.DirFS(ETC_APPARMORD), filePath1, newProfileName) if err != nil { - log.Printf(">> Error in checking the content of %q VS %q\n", filePath1, filePath2) + log.Printf(">> Error in checking the content of %q VS %q\n", filePath1, newProfileName) return nil, err } if contentIsTheSame { - log.Printf("Content of %q and %q seems the same, skipping.", filePath1, filePath2) + log.Printf("Content of %q and %q seems the same, skipping.", filePath1, newProfileName) continue } } @@ -240,48 +240,53 @@ func parseProfileName(profileLine string) string { return strings.TrimSpace(profileLine[:modeIndex]) } -func hasTheSameContent(filePath1, filePath2 string) (bool, error) { - // compare sizes - file1, openErr1 := os.Open(filePath1) - if openErr1 != nil { - return false, openErr1 - } - defer file1.Close() +func HasTheSameContent(fsys fs.FS, filePath1, filePath2 string) (bool, error) { - file1_info, err := file1.Stat() + var file1, file2 os.FileInfo + dir, err := fs.ReadDir(fsys, ".") if err != nil { - log.Fatal("> Error accessing stats from file ", filePath1) + return false, err } - file2, openErr2 := os.Open(filePath2) - if openErr2 != nil { - return false, openErr2 + for _, file := range dir { + if filePath1 == file.Name() { + file1, _ = file.Info() + } else if filePath2 == file.Name() { + file2, _ = file.Info() + } } - defer file2.Close() - file2_info, err := file2.Stat() - if err != nil { - log.Fatal("> Error accessing stats from file ", filePath2) + if file1 == nil || file2 == nil { + return false, fmt.Errorf("files not found") } - if file1_info.Size() != file2_info.Size() { + if file1.Size() != file2.Size() { return false, nil } - // compare content through a sha256 hash - h1 := sha256.New() - if _, err := io.Copy(h1, file1); err != nil { - log.Fatal("Error in generating a hash for ", filePath1) + f1, err := fsys.Open(file1.Name()) + if err != nil { + return false, err } + defer f1.Close() - h2 := sha256.New() - if _, err := io.Copy(h2, file2); err != nil { - log.Fatal("Error in generating a hash for ", filePath2) + data1, err := io.ReadAll(f1) + if err != nil { + return false, err + } + + f2, err := fsys.Open(file2.Name()) + if err != nil { + return false, err + } + defer f2.Close() + + data2, err := ioutil.ReadAll(f2) + if err != nil { + return false, err } - // Sum appends the current hash to nil and returns the resulting slice - if bytes.Equal(h1.Sum(nil), h2.Sum(nil)) { - log.Printf("> Hashes are different\n %s: %s\n %s: %s", filePath1, h1, filePath2, h2) + if !bytes.Equal(data1, data2) { return false, nil } From 17eee4e7c49ecbbcbcbee6437d8d8ffac74f4262 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 25 Jan 2023 15:10:45 +0100 Subject: [PATCH 07/46] feature: parallel tests --- go/src/app/hasTheSameContent_test.go | 41 ++++++++++++++++------------ 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/go/src/app/hasTheSameContent_test.go b/go/src/app/hasTheSameContent_test.go index 45ad37c..0ab10db 100644 --- a/go/src/app/hasTheSameContent_test.go +++ b/go/src/app/hasTheSameContent_test.go @@ -47,23 +47,30 @@ func TestHasTheSameContent(t *testing.T) { "bar.profile": {Data: []byte(testProfileDataDifferent)}, } - // The current directory should change to allow mocking the filesystem - got, err := HasTheSameContent(fs, "foo.profile", "foo.profile.copy") - want := true - ok(t, err) - assertBool(t, got, want) - - // We don't forgive newlines - got, err = HasTheSameContent(fs, "foo.profile", "foo.newline.profile") - want = false - ok(t, err) - assertBool(t, got, want) - - // Very different profiles - got, err = HasTheSameContent(fs, "foo.profile", "bar.profile") - want = false - ok(t, err) - assertBool(t, got, want) + t.Parallel() + + t.Run("Two profiles with same content", func(t *testing.T) { + got, err := HasTheSameContent(fs, "foo.profile", "foo.profile.copy") + want := true + ok(t, err) + assertBool(t, got, want) + }) + + t.Run("A profile with an extra newline", func(t *testing.T) { + // We don't forgive newlines + got, err := HasTheSameContent(fs, "foo.profile", "foo.newline.profile") + want := false + ok(t, err) + assertBool(t, got, want) + }) + + t.Run("Two different profiles", func(t *testing.T) { + // Very different profiles + got, err := HasTheSameContent(fs, "foo.profile", "bar.profile") + want := false + ok(t, err) + assertBool(t, got, want) + }) } func ok(t testing.TB, err error) { From 50ba2ba499d68f76e149bf8fec4eda7cc269d4f7 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 25 Jan 2023 15:11:26 +0100 Subject: [PATCH 08/46] refactoring: moved low-level file operations to a dedicated module --- go/src/app/filesystemOperations.go | 174 +++++++++++++++++++++++++++++ go/src/app/main.go | 166 +-------------------------- 2 files changed, 176 insertions(+), 164 deletions(-) create mode 100644 go/src/app/filesystemOperations.go diff --git a/go/src/app/filesystemOperations.go b/go/src/app/filesystemOperations.go new file mode 100644 index 0000000..f27715f --- /dev/null +++ b/go/src/app/filesystemOperations.go @@ -0,0 +1,174 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/fs" + "log" + "os" + "strconv" +) + +func preFlightChecks() { + + // Environment variable type check + POLL_TIME, err := strconv.Atoi(POLL_TIME_ARG) + if err != nil { + log.Fatalf(">> It was not possible to convert env var POLL_TIME %v to an integer.\n%v", POLL_TIME, err) + } + + // Check profiler binary + if _, err := os.Stat(PROFILER_BIN); os.IsNotExist(err) { + log.Fatal(err) + } + + // Check if custom directory exists + if _, err := os.Stat(ETC_APPARMORD); errors.Is(err, os.ErrNotExist) { + err := os.Mkdir(ETC_APPARMORD, os.ModePerm) + if err != nil { + log.Fatal(err) + } + log.Printf("> Directory %s created.", ETC_APPARMORD) + } +} + +func HasTheSameContent(fsys fs.FS, filePath1, filePath2 string) (bool, error) { + + var file1, file2 os.FileInfo + dir, err := fs.ReadDir(fsys, ".") + if err != nil { + return false, err + } + + for _, file := range dir { + if filePath1 == file.Name() { + file1, _ = file.Info() + } else if filePath2 == file.Name() { + file2, _ = file.Info() + } + } + + if file1 == nil || file2 == nil { + return false, fmt.Errorf("files not found") + } + + if file1.Size() != file2.Size() { + return false, nil + } + + f1, err := fsys.Open(file1.Name()) + if err != nil { + return false, err + } + defer f1.Close() + + data1, err := io.ReadAll(f1) + if err != nil { + return false, err + } + + f2, err := fsys.Open(file2.Name()) + if err != nil { + return false, err + } + defer f2.Close() + + data2, err := io.ReadAll(f2) + if err != nil { + return false, err + } + + if !bytes.Equal(data1, data2) { + return false, nil + } + + return true, nil +} + +func areProfilesReadable(FOLDER_NAME string) (bool, map[string]bool) { + + filenames := map[string]bool{} + files, err := os.ReadDir(FOLDER_NAME) + if err != nil { + log.Fatal(err.Error()) + } + + if len(files) == 0 { + log.Printf("No files were found in the given folder!\n") + return false, nil + } + + log.Printf("Found files in given folder:\n") + for _, file := range files { + if file.IsDir() { + log.Printf("Directory '%s' will be skipped.\n", file.Name()) + continue + } + log.Printf("- %s\n", file.Name()) + filenames[file.Name()] = true + } + + return true, filenames +} + +// CopyFile copies a file from src to dst. If src and dst files exist, and are +// the same, then return success. Otherwise, attempt to create a hard link +// between the two files. If that fail, copy the file contents from src to dst. +// Credits: https://stackoverflow.com/a/21067803/3673430 +func CopyFile(src, dst string) (err error) { + sfi, err := os.Stat(src) + if err != nil { + return + } + if !sfi.Mode().IsRegular() { + // cannot copy non-regular files (e.g., directories symlinks, devices, etc.) + return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String()) + } + dfi, err := os.Stat(dst) + if err != nil { + if !os.IsNotExist(err) { + return + } + } else { + if !(dfi.Mode().IsRegular()) { + return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String()) + } + if os.SameFile(sfi, dfi) { + return + } + } + if err = os.Link(src, dst); err == nil { + return + } + err = copyFileContents(src, dst) + return +} + +// copyFileContents copies the contents of the file named src to the file named +// by dst. The file will be created if it does not already exist. If the +// destination file exists, all it's contents will be replaced by the contents +// of the source file. +func copyFileContents(src, dst string) (err error) { + in, err := os.Open(src) + if err != nil { + return + } + defer in.Close() + out, err := os.Create(dst) + if err != nil { + return + } + defer func() { + cerr := out.Close() + if err == nil { + err = cerr + } + }() + if _, err = io.Copy(out, in); err != nil { + return + } + err = out.Sync() + return +} diff --git a/go/src/app/main.go b/go/src/app/main.go index 37f74df..6107cdf 100644 --- a/go/src/app/main.go +++ b/go/src/app/main.go @@ -3,17 +3,12 @@ package main import ( "bufio" "bytes" - "errors" "fmt" - "io" - "io/fs" - "io/ioutil" "log" "os" "os/exec" "path" "sort" - "strconv" "strings" "time" ) @@ -21,6 +16,7 @@ import ( var ( CONFIGMAP_PATH string = os.Getenv("PROFILES_DIR") POLL_TIME_ARG string = os.Getenv("POLL_TIME") + POLL_TIME int ) const ( @@ -32,58 +28,13 @@ const ( func main() { - // Type check - POLL_TIME, err := strconv.Atoi(POLL_TIME_ARG) - if err != nil { - log.Fatalf(">> It was not possible to convert env var POLL_TIME %v to an integer.\n%v", POLL_TIME, err) - } + preFlightChecks() log.Printf("> Polling directory %s every %d seconds.\n", CONFIGMAP_PATH, POLL_TIME) - // Check profiler binary - if _, err := os.Stat(PROFILER_BIN); os.IsNotExist(err) { - log.Fatal(err) - } - - // Check if custom directory exists - if _, err := os.Stat(ETC_APPARMORD); errors.Is(err, os.ErrNotExist) { - err := os.Mkdir(ETC_APPARMORD, os.ModePerm) - if err != nil { - log.Fatal(err) - } - log.Printf("> Directory %s created.", ETC_APPARMORD) - } - - // Poll configmap forever every POLL_TIME seconds pollProfiles(POLL_TIME) } -func areProfilesReadable(FOLDER_NAME string) (bool, map[string]bool) { - - filenames := map[string]bool{} - files, err := os.ReadDir(FOLDER_NAME) - if err != nil { - log.Fatal(err.Error()) - } - - if len(files) == 0 { - log.Printf("No files were found in the given folder!\n") - return false, nil - } - - log.Printf("Found files in given folder:\n") - for _, file := range files { - if file.IsDir() { - log.Printf("Directory '%s' will be skipped.\n", file.Name()) - continue - } - log.Printf("- %s\n", file.Name()) - filenames[file.Name()] = true - } - - return true, filenames -} - // Profiles will probably change content while keeping the same name, so a digest comparison // can be useful if we don't want to load everything every time. func pollProfiles(delay int) { @@ -240,59 +191,6 @@ func parseProfileName(profileLine string) string { return strings.TrimSpace(profileLine[:modeIndex]) } -func HasTheSameContent(fsys fs.FS, filePath1, filePath2 string) (bool, error) { - - var file1, file2 os.FileInfo - dir, err := fs.ReadDir(fsys, ".") - if err != nil { - return false, err - } - - for _, file := range dir { - if filePath1 == file.Name() { - file1, _ = file.Info() - } else if filePath2 == file.Name() { - file2, _ = file.Info() - } - } - - if file1 == nil || file2 == nil { - return false, fmt.Errorf("files not found") - } - - if file1.Size() != file2.Size() { - return false, nil - } - - f1, err := fsys.Open(file1.Name()) - if err != nil { - return false, err - } - defer f1.Close() - - data1, err := io.ReadAll(f1) - if err != nil { - return false, err - } - - f2, err := fsys.Open(file2.Name()) - if err != nil { - return false, err - } - defer f2.Close() - - data2, err := ioutil.ReadAll(f2) - if err != nil { - return false, err - } - - if !bytes.Equal(data1, data2) { - return false, nil - } - - return true, nil -} - func loadProfile(profilePath string) error { execApparmor("--verbose", "--replace", profilePath) // Copy the profile definition in the apparmor configuration standard directory @@ -321,63 +219,3 @@ func execApparmor(args ...string) error { return nil } - -// CopyFile copies a file from src to dst. If src and dst files exist, and are -// the same, then return success. Otherwise, attempt to create a hard link -// between the two files. If that fail, copy the file contents from src to dst. -// Credits: https://stackoverflow.com/a/21067803/3673430 -func CopyFile(src, dst string) (err error) { - sfi, err := os.Stat(src) - if err != nil { - return - } - if !sfi.Mode().IsRegular() { - // cannot copy non-regular files (e.g., directories symlinks, devices, etc.) - return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String()) - } - dfi, err := os.Stat(dst) - if err != nil { - if !os.IsNotExist(err) { - return - } - } else { - if !(dfi.Mode().IsRegular()) { - return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String()) - } - if os.SameFile(sfi, dfi) { - return - } - } - if err = os.Link(src, dst); err == nil { - return - } - err = copyFileContents(src, dst) - return -} - -// copyFileContents copies the contents of the file named src to the file named -// by dst. The file will be created if it does not already exist. If the -// destination file exists, all it's contents will be replaced by the contents -// of the source file. -func copyFileContents(src, dst string) (err error) { - in, err := os.Open(src) - if err != nil { - return - } - defer in.Close() - out, err := os.Create(dst) - if err != nil { - return - } - defer func() { - cerr := out.Close() - if err == nil { - err = cerr - } - }() - if _, err = io.Copy(out, in); err != nil { - return - } - err = out.Sync() - return -} From 1890a984360d8ed9082bb0c0c7c844d55fd6cf8f Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 25 Jan 2023 15:17:12 +0100 Subject: [PATCH 09/46] minor doc fix --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dd836b5..330c9c7 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ go mod init ./go/src/app/ ``` ### Test the app locally +Test Helm Chart creation ```sh # --- Check the Helm chart # https://github.com/helm/chart-testing/issues/464 @@ -51,7 +52,9 @@ docker run -it --network host --workdir=/data --volume ~/.kube/config:/root/.kub export GITHUB_SHA=42 helm install --dry-run --atomic --generate-name --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ - +``` +Test the app inside a container: +```sh # --- Build and run the container image docker build --quiet -t test-kapparmor --build-arg POLL_TIME=60 --build-arg PROFILES_DIR=/app/profiles -f Dockerfile . &&\ echo &&\ From 554d8c92bf9738467ee433ad88e4ba22debf7f6b Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 25 Jan 2023 15:26:44 +0100 Subject: [PATCH 10/46] fix: build from feature branches --- .github/workflows/build-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml index 2661ca4..bc17239 100644 --- a/.github/workflows/build-app.yml +++ b/.github/workflows/build-app.yml @@ -2,7 +2,7 @@ name: "1. Create app" on: push: - branches: [main,dev] + branches: [main,dev,feature/*] paths: - "go/src/app/**.go" - Dockerfile From 12c240a7e18b374d74d120cfa3f6504a503cd698 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 25 Jan 2023 15:46:59 +0100 Subject: [PATCH 11/46] version upgrade --- README.md | 2 +- charts/kapparmor/Chart.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 330c9c7..9b33e27 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ docker run -it --network host --workdir=/data --volume ~/.kube/config:/root/.kub --volume $(pwd):/data quay.io/helmpack/chart-testing:latest \ /bin/sh -c "git config --global --add safe.directory /data; ct lint --print-config --charts ./charts/kapparmor" +# Replace here a commit id being part of an image tag, like "sha-554d8c92bf9738467ee433ad88e4ba22debf7f6b" export GITHUB_SHA=42 helm install --dry-run --atomic --generate-name --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ @@ -63,7 +64,6 @@ docker build --quiet -t test-kapparmor --build-arg POLL_TIME=60 --build-arg PROF --mount type=bind,source='/etc',target='/etc'\ test-kapparmor - ``` diff --git a/charts/kapparmor/Chart.yaml b/charts/kapparmor/Chart.yaml index dc58875..654f2cd 100644 --- a/charts/kapparmor/Chart.yaml +++ b/charts/kapparmor/Chart.yaml @@ -5,8 +5,8 @@ type: application home: https://artifacthub.io kubeVersion: ">= 1.23.0-0" -version: "0.0.5-alpha" -appVersion: "0.0.1-alpha" +version: "0.0.6-alpha" +appVersion: "0.0.2" keywords: - kubernetes From f4d4b306c76edeb5088a9b85683b78c1b7dcc6fc Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 25 Jan 2023 17:03:39 +0100 Subject: [PATCH 12/46] extra microk8s steps --- docs/microk8s.md | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/docs/microk8s.md b/docs/microk8s.md index 0bcdb66..3cb4404 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -13,7 +13,7 @@ restart # Install microk8s and check the status sudo snap install microk8s --classic --channel=latest/stable microk8s inspect -aa-status |grep microk8s +sudo aa-status |grep microk8s # Check if the current machine results as an active node with apparmor enabled microk8s kubectl get nodes -o=jsonpath='{range .items[*]}{@.metadata.name}: {.status.conditions[?(@.reason=="KubeletReady")].message}{"\n"}{end}' @@ -34,6 +34,46 @@ Verify pods have some syscall blocked Blocked Syscalls (24): MSGRCV SYSLOG SETPGID SETSID VHANGUP PIVOT_ROOT ACCT SETTIMEOFDAY UMOUNT2 SWAPON SWAPOFF REBOOT SETHOSTNAME SETDOMAINNAME INIT_MODULE DELETE_MODULE LOOKUP_DCOOKIE KEXEC_LOAD PERF_EVENT_OPEN FANOTIFY_INIT OPEN_BY_HANDLE_AT FINIT_MODULE KEXEC_FILE_LOAD BPF +### HA setup +Microk8s doesn't support dynamic ips for nodes, so remember to fix it manually after machines reboot. +```sh +microk8s stop + +ip addr show enp0s8 |grep "inet " |awk '{print $2}' + +# Modify this with your ip config +cat >/tmp/00-netplan.yml < Date: Wed, 25 Jan 2023 18:17:09 +0100 Subject: [PATCH 13/46] feature: added profiles_dir and poll_time configurations --- README.md | 7 ++++- charts/kapparmor/Chart.yaml | 2 +- .../{configmap.yaml => cm-profiles.yaml} | 2 +- charts/kapparmor/templates/cm-settings.yaml | 7 +++++ charts/kapparmor/templates/daemonset.yaml | 15 ++++++++-- charts/kapparmor/values.yaml | 4 +++ docs/microk8s.md | 29 +++++++++++++------ 7 files changed, 52 insertions(+), 14 deletions(-) rename charts/kapparmor/templates/{configmap.yaml => cm-profiles.yaml} (68%) create mode 100644 charts/kapparmor/templates/cm-settings.yaml diff --git a/README.md b/README.md index 9b33e27..056a9d9 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ go mod init ./go/src/app/ ``` ### Test the app locally + Test Helm Chart creation ```sh # --- Check the Helm chart @@ -50,10 +51,11 @@ docker run -it --network host --workdir=/data --volume ~/.kube/config:/root/.kub /bin/sh -c "git config --global --add safe.directory /data; ct lint --print-config --charts ./charts/kapparmor" # Replace here a commit id being part of an image tag, like "sha-554d8c92bf9738467ee433ad88e4ba22debf7f6b" -export GITHUB_SHA=42 +export GITHUB_SHA="sha-554d8c92bf9738467ee433ad88e4ba22debf7f6b" helm install --dry-run --atomic --generate-name --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ ``` + Test the app inside a container: ```sh # --- Build and run the container image @@ -66,6 +68,9 @@ docker build --quiet -t test-kapparmor --build-arg POLL_TIME=60 --build-arg PROF ``` +To test Helm chart installation in a MicroK8s cluster, follow docs/microk8s.md instructions if you don't have any local cluster. + + # External useful links - [https://emn178.github.io/online-tools/sha256.html](https://emn178.github.io/online-tools/sha256.html) diff --git a/charts/kapparmor/Chart.yaml b/charts/kapparmor/Chart.yaml index 654f2cd..6d57107 100644 --- a/charts/kapparmor/Chart.yaml +++ b/charts/kapparmor/Chart.yaml @@ -5,7 +5,7 @@ type: application home: https://artifacthub.io kubeVersion: ">= 1.23.0-0" -version: "0.0.6-alpha" +version: "0.0.7" appVersion: "0.0.2" keywords: diff --git a/charts/kapparmor/templates/configmap.yaml b/charts/kapparmor/templates/cm-profiles.yaml similarity index 68% rename from charts/kapparmor/templates/configmap.yaml rename to charts/kapparmor/templates/cm-profiles.yaml index 0de540c..fa7635a 100644 --- a/charts/kapparmor/templates/configmap.yaml +++ b/charts/kapparmor/templates/cm-profiles.yaml @@ -1,6 +1,6 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ include "kapparmor.fullname" . }} + name: kapparmor-profiles data: {{ (.Files.Glob "profiles/*").AsConfig | indent 2 }} \ No newline at end of file diff --git a/charts/kapparmor/templates/cm-settings.yaml b/charts/kapparmor/templates/cm-settings.yaml new file mode 100644 index 0000000..9dc6749 --- /dev/null +++ b/charts/kapparmor/templates/cm-settings.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kapparmor-settings +data: + PROFILES_DIR: {{ Values.app.profiles_dir }} + POLL_TIME: {{ Values.app.poll_time }} \ No newline at end of file diff --git a/charts/kapparmor/templates/daemonset.yaml b/charts/kapparmor/templates/daemonset.yaml index 7304abb..7a80c9c 100644 --- a/charts/kapparmor/templates/daemonset.yaml +++ b/charts/kapparmor/templates/daemonset.yaml @@ -46,9 +46,20 @@ spec: {{- toYaml .Values.resources | nindent 12 }} # mount a configmap as read only in the container's filesystem. volumeMounts : - - name : {{ include "kapparmor.fullname" . }} - mountPath : /app/profiles + - name : kapparmor-profiles + mountPath : {{ .Values.app.profiles_dir }} readOnly : false + env: + - name: PROFILES_DIR + valueFrom: + configMapKeyRef: + name: kapparmor-settings + key: PROFILES_DIR + - name: POLL_TIME + valueFrom: + configMapKeyRef: + name: kapparmor-settings + key: POLL_TIME volumes: - name: {{ include "kapparmor.fullname" . }} diff --git a/charts/kapparmor/values.yaml b/charts/kapparmor/values.yaml index 86e6a80..9cfd15e 100644 --- a/charts/kapparmor/values.yaml +++ b/charts/kapparmor/values.yaml @@ -9,6 +9,10 @@ imagePullSecrets: [] nameOverride: "kapparmor" fullnameOverride: "" +app: + profiles_dir: "/app/profiles" + poll_time: 60 + serviceAccount: # Specifies whether a service account should be created create: false diff --git a/docs/microk8s.md b/docs/microk8s.md index 3cb4404..2736ec8 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -12,7 +12,12 @@ restart # Install microk8s and check the status sudo snap install microk8s --classic --channel=latest/stable +microk8s enable dns hostpath-storage + microk8s inspect + +microk8s config > $HOME/.kube/microk8s.config + sudo aa-status |grep microk8s # Check if the current machine results as an active node with apparmor enabled @@ -42,26 +47,24 @@ microk8s stop ip addr show enp0s8 |grep "inet " |awk '{print $2}' # Modify this with your ip config -cat >/tmp/00-netplan.yml </etc/netplan/00-microk8s.yaml < Date: Wed, 25 Jan 2023 18:26:23 +0100 Subject: [PATCH 14/46] fix: cm-settings values --- charts/kapparmor/templates/cm-settings.yaml | 4 ++-- docs/microk8s.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/charts/kapparmor/templates/cm-settings.yaml b/charts/kapparmor/templates/cm-settings.yaml index 9dc6749..5be99db 100644 --- a/charts/kapparmor/templates/cm-settings.yaml +++ b/charts/kapparmor/templates/cm-settings.yaml @@ -3,5 +3,5 @@ kind: ConfigMap metadata: name: kapparmor-settings data: - PROFILES_DIR: {{ Values.app.profiles_dir }} - POLL_TIME: {{ Values.app.poll_time }} \ No newline at end of file + PROFILES_DIR: {{ .Values.app.profiles_dir }} + POLL_TIME: {{ .Values.app.poll_time }} \ No newline at end of file diff --git a/docs/microk8s.md b/docs/microk8s.md index 2736ec8..1089435 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -80,9 +80,10 @@ microk8s start ## Install the helm chart ``` export GITHUB_SHA="sha-554d8c92bf9738467ee433ad88e4ba22debf7f6b" -helm install --atomic --generate-name --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ +helm upgrade --install --atomic --generate-name --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ kubectl get events --sort-by .LastTimestamp + ``` ## Test profiles From b77bf4129ee4b63e51e8c72408c3e0af8cfe1f51 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 25 Jan 2023 18:31:01 +0100 Subject: [PATCH 15/46] fix: cannot unmarshal number into string --- charts/kapparmor/templates/cm-settings.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/kapparmor/templates/cm-settings.yaml b/charts/kapparmor/templates/cm-settings.yaml index 5be99db..9b49a5f 100644 --- a/charts/kapparmor/templates/cm-settings.yaml +++ b/charts/kapparmor/templates/cm-settings.yaml @@ -3,5 +3,5 @@ kind: ConfigMap metadata: name: kapparmor-settings data: - PROFILES_DIR: {{ .Values.app.profiles_dir }} - POLL_TIME: {{ .Values.app.poll_time }} \ No newline at end of file + PROFILES_DIR: "{{ .Values.app.profiles_dir }}" + POLL_TIME: "{{ .Values.app.poll_time }}" \ No newline at end of file From 809c28ce6084927b22f423cdd1cb3caa40979b8c Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 25 Jan 2023 18:33:17 +0100 Subject: [PATCH 16/46] fix configmap name --- charts/kapparmor/templates/daemonset.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/kapparmor/templates/daemonset.yaml b/charts/kapparmor/templates/daemonset.yaml index 7a80c9c..a8ab657 100644 --- a/charts/kapparmor/templates/daemonset.yaml +++ b/charts/kapparmor/templates/daemonset.yaml @@ -62,9 +62,9 @@ spec: key: POLL_TIME volumes: - - name: {{ include "kapparmor.fullname" . }} + - name: kapparmor-profiles configMap: - name: {{ include "kapparmor.fullname" . }} + name: kapparmor-profiles {{- with .Values.nodeSelector }} nodeSelector: From 939b14b70f8d50d1ffda06881497d564c3ec106a Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 11:53:52 +0100 Subject: [PATCH 17/46] create /etc/apparmor.d/custom dir in dockerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index abb3d26..cc6d273 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,8 @@ LABEL Author="Affinito Alessandro" WORKDIR /app RUN apk --no-cache update &&\ - apk add apparmor + apk add apparmor &&\ + mkdir -p /etc/apparmor.d/custom COPY --from=builder ./go/bin/app /app/ COPY ./charts/kapparmor/profiles /app/profiles From 5acb1e89dd159ed036b08f7f897b9c4d6276021a Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 12:10:35 +0100 Subject: [PATCH 18/46] typo --- docs/microk8s.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/microk8s.md b/docs/microk8s.md index 1089435..a196119 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -82,7 +82,7 @@ microk8s start export GITHUB_SHA="sha-554d8c92bf9738467ee433ad88e4ba22debf7f6b" helm upgrade --install --atomic --generate-name --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ -kubectl get events --sort-by .LastTimestamp +kubectl get events --sort-by .lastTimestamp ``` From ce244fb27e856f2e5d57082e0226408765ed09bb Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 12:31:06 +0100 Subject: [PATCH 19/46] poll_time doesn't get a value --- CHANGELOG.md | 3 ++- go/src/app/filesystemOperations.go | 5 ++++- go/src/app/main.go | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d52249e..c88934b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [ ] Remove a non existing profile 1. Remove kubernetes Service and DaemonSet exposed ports if useless 1. Evaluate an automatic changelog generation from commits like [googleapis/release-please](https://github.com/googleapis/release-please) +1. Add daemonset commands for checking readiness ## [0.0.6]() - @@ -25,7 +26,7 @@ Go: CI/CD: - Explicit changelog to help users understanding the project features - Automatic generation of release notes based on changelog file - +- Configurable poll time and profiles directory in the helm values file ## [0.0.5](https://github.com/tuxerrante/kapparmor/releases/tag/kapparmor-0.0.5-alpha) - 2023-01-23 diff --git a/go/src/app/filesystemOperations.go b/go/src/app/filesystemOperations.go index f27715f..a28fabd 100644 --- a/go/src/app/filesystemOperations.go +++ b/go/src/app/filesystemOperations.go @@ -11,13 +11,14 @@ import ( "strconv" ) -func preFlightChecks() { +func preFlightChecks() int { // Environment variable type check POLL_TIME, err := strconv.Atoi(POLL_TIME_ARG) if err != nil { log.Fatalf(">> It was not possible to convert env var POLL_TIME %v to an integer.\n%v", POLL_TIME, err) } + log.Printf("POLL_TIME set to %d sec.", POLL_TIME) // Check profiler binary if _, err := os.Stat(PROFILER_BIN); os.IsNotExist(err) { @@ -32,6 +33,8 @@ func preFlightChecks() { } log.Printf("> Directory %s created.", ETC_APPARMORD) } + + return POLL_TIME } func HasTheSameContent(fsys fs.FS, filePath1, filePath2 string) (bool, error) { diff --git a/go/src/app/main.go b/go/src/app/main.go index 6107cdf..52a5de8 100644 --- a/go/src/app/main.go +++ b/go/src/app/main.go @@ -28,7 +28,7 @@ const ( func main() { - preFlightChecks() + POLL_TIME = preFlightChecks() log.Printf("> Polling directory %s every %d seconds.\n", CONFIGMAP_PATH, POLL_TIME) From 59e65e8ff95324f3205dd38ac1482dd2d7cb6ac1 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 12:57:36 +0100 Subject: [PATCH 20/46] mount filesystem dirs in the pod --- charts/kapparmor/templates/daemonset.yaml | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/charts/kapparmor/templates/daemonset.yaml b/charts/kapparmor/templates/daemonset.yaml index a8ab657..28eacd3 100644 --- a/charts/kapparmor/templates/daemonset.yaml +++ b/charts/kapparmor/templates/daemonset.yaml @@ -30,18 +30,7 @@ spec: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: http - containerPort: 80 - protocol: TCP - livenessProbe: - httpGet: - path: / - port: http - readinessProbe: - httpGet: - path: / - port: http + resources: {{- toYaml .Values.resources | nindent 12 }} # mount a configmap as read only in the container's filesystem. @@ -49,6 +38,11 @@ spec: - name : kapparmor-profiles mountPath : {{ .Values.app.profiles_dir }} readOnly : false + - name: profiles-kernel-path + mountPath: /sys/kernel/security/apparmor/profiles + - name: etc-apparmor + mountPath: /etc/apparmor.d/ + env: - name: PROFILES_DIR valueFrom: @@ -65,6 +59,12 @@ spec: - name: kapparmor-profiles configMap: name: kapparmor-profiles + - name: profiles-kernel-path + hostPath: + path: /sys/kernel/security/apparmor/profiles + - name: etc-apparmor + hostPath: + path: /etc/apparmor.d/ {{- with .Values.nodeSelector }} nodeSelector: From 6c5be3dd768c02d3a8292b247ff27da045973fd1 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 14:46:06 +0100 Subject: [PATCH 21/46] missing dirs in dockerfile --- Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index cc6d273..f7adf87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,9 +13,10 @@ LABEL Author="Affinito Alessandro" WORKDIR /app -RUN apk --no-cache update &&\ - apk add apparmor &&\ - mkdir -p /etc/apparmor.d/custom +RUN apk --no-cache update &&\ + apk add apparmor libapparmor &&\ + mkdir -p /etc/apparmor.d/custom &&\ + mkdir -p /sys/kernel/security/apparmor/profiles COPY --from=builder ./go/bin/app /app/ COPY ./charts/kapparmor/profiles /app/profiles From d298fe34b741ff7baa13d370885534acc507e519 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 14:52:37 +0100 Subject: [PATCH 22/46] testing commands --- docs/microk8s.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/microk8s.md b/docs/microk8s.md index a196119..d7ec734 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -80,9 +80,12 @@ microk8s start ## Install the helm chart ``` export GITHUB_SHA="sha-554d8c92bf9738467ee433ad88e4ba22debf7f6b" -helm upgrade --install --atomic --generate-name --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ -kubectl get events --sort-by .lastTimestamp +git pull &&\ + helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ --wait &&\ + kubectl get events --sort-by .lastTimestamp &&\ + kubectl get pods -l app.kubernetes.io/name=kapparmor &&\ + kubectl logs -l app.kubernetes.io/name=kapparmor ``` From 9c7f06295870426fb0d72dc9e0c11b23af0f61db Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 15:13:53 +0100 Subject: [PATCH 23/46] fix: missing dirs in dockerfile --- Dockerfile | 2 +- charts/kapparmor/templates/daemonset.yaml | 4 ++-- docs/microk8s.md | 10 ++++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index f7adf87..f5049e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ WORKDIR /app RUN apk --no-cache update &&\ apk add apparmor libapparmor &&\ mkdir -p /etc/apparmor.d/custom &&\ - mkdir -p /sys/kernel/security/apparmor/profiles + mkdir -p /sys/kernel/security/apparmor COPY --from=builder ./go/bin/app /app/ COPY ./charts/kapparmor/profiles /app/profiles diff --git a/charts/kapparmor/templates/daemonset.yaml b/charts/kapparmor/templates/daemonset.yaml index 28eacd3..8e1ae7d 100644 --- a/charts/kapparmor/templates/daemonset.yaml +++ b/charts/kapparmor/templates/daemonset.yaml @@ -39,7 +39,7 @@ spec: mountPath : {{ .Values.app.profiles_dir }} readOnly : false - name: profiles-kernel-path - mountPath: /sys/kernel/security/apparmor/profiles + mountPath: /sys/kernel/security/apparmor - name: etc-apparmor mountPath: /etc/apparmor.d/ @@ -61,7 +61,7 @@ spec: name: kapparmor-profiles - name: profiles-kernel-path hostPath: - path: /sys/kernel/security/apparmor/profiles + path: /sys/kernel/security/apparmor - name: etc-apparmor hostPath: path: /etc/apparmor.d/ diff --git a/docs/microk8s.md b/docs/microk8s.md index d7ec734..35d961a 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -78,14 +78,20 @@ Add this to the bottom of /var/snap/microk8s/current/args/kube-apiserver: microk8s start ## Install the helm chart -``` +Run this on your linux node: +```sh +# Assume last commit triggered a building pipeline, we'll have this as last docker image tag +git log --online --no-abbrev-commit |head 1 export GITHUB_SHA="sha-554d8c92bf9738467ee433ad88e4ba22debf7f6b" +# Move on the right branch before git pull &&\ helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ --wait &&\ + echo > EVENTS &&\ kubectl get events --sort-by .lastTimestamp &&\ kubectl get pods -l app.kubernetes.io/name=kapparmor &&\ - kubectl logs -l app.kubernetes.io/name=kapparmor + echo > POD LOGS &&\ + kubectl logs -l app.kubernetes.io/name=kapparmor --follow ``` From 0c11c266827b0052dd86ac1c8655f493bf3f6bd5 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 15:18:25 +0100 Subject: [PATCH 24/46] fix: missing dirs in dockerfile --- Dockerfile | 3 +-- docs/microk8s.md | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index f5049e5..9281d76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,8 +15,7 @@ WORKDIR /app RUN apk --no-cache update &&\ apk add apparmor libapparmor &&\ - mkdir -p /etc/apparmor.d/custom &&\ - mkdir -p /sys/kernel/security/apparmor + mkdir --parent --verbose /etc/apparmor.d/custom /sys/kernel/security/apparmor COPY --from=builder ./go/bin/app /app/ COPY ./charts/kapparmor/profiles /app/profiles diff --git a/docs/microk8s.md b/docs/microk8s.md index 35d961a..312075d 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -81,8 +81,7 @@ microk8s start Run this on your linux node: ```sh # Assume last commit triggered a building pipeline, we'll have this as last docker image tag -git log --online --no-abbrev-commit |head 1 -export GITHUB_SHA="sha-554d8c92bf9738467ee433ad88e4ba22debf7f6b" +export GITHUB_SHA="sha-$(git log --online --no-abbrev-commit |head 1 |cut -d' ' -f1)" # Move on the right branch before git pull &&\ From 6bdb7ccd8b42878ce2f5106a5bb6f943bcd3fcd0 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 15:26:47 +0100 Subject: [PATCH 25/46] fix: missing dirs in dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9281d76..7cba170 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ WORKDIR /app RUN apk --no-cache update &&\ apk add apparmor libapparmor &&\ - mkdir --parent --verbose /etc/apparmor.d/custom /sys/kernel/security/apparmor + mkdir --parent --verbose /etc/apparmor.d/custom COPY --from=builder ./go/bin/app /app/ COPY ./charts/kapparmor/profiles /app/profiles From b8b9c4dafb56729b2e01c40091cab6247eaad276 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 15:33:31 +0100 Subject: [PATCH 26/46] fix: missing dirs in dockerfile - /sys/kernel/security --- README.md | 5 ++++- charts/kapparmor/templates/daemonset.yaml | 4 ++-- docs/microk8s.md | 11 +++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 056a9d9..7cd9060 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ - ----- Apparmor-loader project to deploy profiles through a kubernetes daemonset. -This work is inspired by [kubernetes/apparmor-loader](https://github.com/kubernetes/kubernetes/tree/master/test/images/apparmor-loader). +This work was inspired by [kubernetes/apparmor-loader](https://github.com/kubernetes/kubernetes/tree/master/test/images/apparmor-loader). ![architecture](./docs/kapparmor-architecture.png) @@ -23,6 +23,9 @@ This work is inspired by [kubernetes/apparmor-loader](https://github.com/kuberne - The name of the file should be the same as the name of the profile. 3. The configmap will be polled every POLL_TIME seconds to move them into PROFILES_DIR host path and then enable them. +You can view which profiles are loaded on a node by checking the /sys/kernel/security/apparmor/profiles, so this will need to be mounted in the pod. + + ## Testing [Set up a Microk8s environment](./docs/microk8s.md). diff --git a/charts/kapparmor/templates/daemonset.yaml b/charts/kapparmor/templates/daemonset.yaml index 8e1ae7d..39a72f6 100644 --- a/charts/kapparmor/templates/daemonset.yaml +++ b/charts/kapparmor/templates/daemonset.yaml @@ -39,7 +39,7 @@ spec: mountPath : {{ .Values.app.profiles_dir }} readOnly : false - name: profiles-kernel-path - mountPath: /sys/kernel/security/apparmor + mountPath: /sys/kernel/security - name: etc-apparmor mountPath: /etc/apparmor.d/ @@ -61,7 +61,7 @@ spec: name: kapparmor-profiles - name: profiles-kernel-path hostPath: - path: /sys/kernel/security/apparmor + path: /sys/kernel/security - name: etc-apparmor hostPath: path: /etc/apparmor.d/ diff --git a/docs/microk8s.md b/docs/microk8s.md index 312075d..e0f332b 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -81,15 +81,14 @@ microk8s start Run this on your linux node: ```sh # Assume last commit triggered a building pipeline, we'll have this as last docker image tag -export GITHUB_SHA="sha-$(git log --online --no-abbrev-commit |head 1 |cut -d' ' -f1)" - # Move on the right branch before -git pull &&\ - helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ --wait &&\ - echo > EVENTS &&\ +git pull && export GITHUB_SHA="sha-$(git log --online --no-abbrev-commit |head 1 |cut -d' ' -f1)" + +helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ --wait &&\ + echo --- EVENTS &&\ kubectl get events --sort-by .lastTimestamp &&\ kubectl get pods -l app.kubernetes.io/name=kapparmor &&\ - echo > POD LOGS &&\ + echo --- POD LOGS &&\ kubectl logs -l app.kubernetes.io/name=kapparmor --follow ``` From c0e79ebc031935351e2ca6bafc859b9470d328c5 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 16:08:31 +0100 Subject: [PATCH 27/46] add capabilities to read /sys/kernel/security --- README.md | 2 +- charts/kapparmor/values.yaml | 7 ++++--- docs/microk8s.md | 5 ++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7cd9060..fc50959 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This work was inspired by [kubernetes/apparmor-loader](https://github.com/kubern - The name of the file should be the same as the name of the profile. 3. The configmap will be polled every POLL_TIME seconds to move them into PROFILES_DIR host path and then enable them. -You can view which profiles are loaded on a node by checking the /sys/kernel/security/apparmor/profiles, so this will need to be mounted in the pod. +You can view which profiles are loaded on a node by checking the /sys/kernel/security/apparmor/profiles, so its parent will need to be mounted in the pod. ## Testing diff --git a/charts/kapparmor/values.yaml b/charts/kapparmor/values.yaml index 9cfd15e..332a0a6 100644 --- a/charts/kapparmor/values.yaml +++ b/charts/kapparmor/values.yaml @@ -28,9 +28,10 @@ podSecurityContext: {} # fsGroup: 2000 securityContext: - # capabilities: - # drop: - # - ALL + capabilities: + add: + - SYS_ADMIN + allowPrivilegeEscalation: true readOnlyRootFilesystem: false runAsUser: 0 diff --git a/docs/microk8s.md b/docs/microk8s.md index e0f332b..c3f6f86 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -80,14 +80,17 @@ microk8s start ## Install the helm chart Run this on your linux node: ```sh -# Assume last commit triggered a building pipeline, we'll have this as last docker image tag +# Assume the last commit triggered a building pipeline, we'll have this as last docker image tag # Move on the right branch before git pull && export GITHUB_SHA="sha-$(git log --online --no-abbrev-commit |head 1 |cut -d' ' -f1)" helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ --wait &&\ + echo &&\ echo --- EVENTS &&\ + sleep 10 &&\ kubectl get events --sort-by .lastTimestamp &&\ kubectl get pods -l app.kubernetes.io/name=kapparmor &&\ + echo &&\ echo --- POD LOGS &&\ kubectl logs -l app.kubernetes.io/name=kapparmor --follow From 137cc2feb808ae5e6f04271a00185dac33c9bd4a Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 17:01:51 +0100 Subject: [PATCH 28/46] unconfined profile annotation --- charts/kapparmor/templates/daemonset.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/kapparmor/templates/daemonset.yaml b/charts/kapparmor/templates/daemonset.yaml index 39a72f6..1e60ab6 100644 --- a/charts/kapparmor/templates/daemonset.yaml +++ b/charts/kapparmor/templates/daemonset.yaml @@ -10,10 +10,10 @@ spec: {{- include "kapparmor.selectorLabels" . | nindent 6 }} template: metadata: - {{- with .Values.podAnnotations }} annotations: - {{- toYaml . | nindent 8 }} - {{- end }} + container.apparmor.security.beta.kubernetes.io/kapparmor: unconfined + {{- toYaml .Values.podAnnotations | nindent 8 }} + labels: {{- include "kapparmor.selectorLabels" . | nindent 8 }} spec: From 987af1ecb6437766dd4c2a66a9951fb0c1d55f26 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 17:27:52 +0100 Subject: [PATCH 29/46] fix: podAnnotations --- charts/kapparmor/templates/daemonset.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/charts/kapparmor/templates/daemonset.yaml b/charts/kapparmor/templates/daemonset.yaml index 1e60ab6..aafdb7e 100644 --- a/charts/kapparmor/templates/daemonset.yaml +++ b/charts/kapparmor/templates/daemonset.yaml @@ -12,10 +12,13 @@ spec: metadata: annotations: container.apparmor.security.beta.kubernetes.io/kapparmor: unconfined - {{- toYaml .Values.podAnnotations | nindent 8 }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} labels: {{- include "kapparmor.selectorLabels" . | nindent 8 }} + spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: @@ -24,6 +27,7 @@ spec: serviceAccountName: {{ include "kapparmor.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: - name: {{ .Chart.Name }} securityContext: From 65069bf0fbb51c143e00df9cbb8931fab980a07c Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Thu, 26 Jan 2023 17:35:09 +0100 Subject: [PATCH 30/46] changelog --- CHANGELOG.md | 12 +++++++++++- charts/kapparmor/Chart.yaml | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c88934b..f40f196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +1. **Unable to replace profiles**. Permission denied, app seems still confined. 1. Go unit tests - [ ] Create a new profile - [ ] Update an existing profile @@ -14,14 +16,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 1. Remove kubernetes Service and DaemonSet exposed ports if useless 1. Evaluate an automatic changelog generation from commits like [googleapis/release-please](https://github.com/googleapis/release-please) 1. Add daemonset commands for checking readiness +1. Add tests for all the main functions +1. Add test for checking current confinement state of the app +1. Test on multiple nodes cluster ## [0.0.6]() - ### Added - Helm: +- Added SYS_ADMIN capabilities to the daemonset +- Mounted needed folders in the Dockerfile and in the daemonset +- Added POLL_TIME and profiles files as configurable options through configmaps Go: +- Added first testing function +- Moved file operations functions to dedicated module + - Fixed POLL_TIME value passing from configmap CI/CD: - Explicit changelog to help users understanding the project features diff --git a/charts/kapparmor/Chart.yaml b/charts/kapparmor/Chart.yaml index 6d57107..1293c80 100644 --- a/charts/kapparmor/Chart.yaml +++ b/charts/kapparmor/Chart.yaml @@ -5,7 +5,7 @@ type: application home: https://artifacthub.io kubeVersion: ">= 1.23.0-0" -version: "0.0.7" +version: "0.0.6" appVersion: "0.0.2" keywords: From 93d0dc4c597a8ae8a9febe1d68e674daf1fa919a Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Mon, 30 Jan 2023 16:26:09 +0100 Subject: [PATCH 31/46] switched to ubuntu image --- .dockerignore | 1 - Dockerfile | 14 +++++---- Dockerfile.alpine | 30 ++++++++++++++++++++ docs/microk8s.md | 17 +++++++++-- go/bin/.gitkeep | 0 test/ubuntu_deploy.yml | 64 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 Dockerfile.alpine delete mode 100644 go/bin/.gitkeep create mode 100644 test/ubuntu_deploy.yml diff --git a/.dockerignore b/.dockerignore index 720e7a0..ddcf96b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -12,7 +12,6 @@ **/*.dbmdl **/*.jfm **/bin -**/charts **/docker-compose* **/compose* **/Dockerfile* diff --git a/Dockerfile b/Dockerfile index 7cba170..1ea8ce4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,18 +7,20 @@ RUN go get -d -v ./go/src/app/ RUN go build -o /go/bin/app -v ./go/src/app/ # --- -FROM alpine:latest +FROM ubuntu:latest LABEL Name=kapparmor Version=0.0.1 LABEL Author="Affinito Alessandro" WORKDIR /app -RUN apk --no-cache update &&\ - apk add apparmor libapparmor &&\ - mkdir --parent --verbose /etc/apparmor.d/custom +RUN apt-get update &&\ + apt-get upgrade -y &&\ + apt-get install --no-install-recommends --yes apparmor &&\ + rm -rf /var/lib/apt/lists/* &&\ + mkdir --parent --verbose /etc/apparmor.d/custom -COPY --from=builder ./go/bin/app /app/ -COPY ./charts/kapparmor/profiles /app/profiles +COPY --from=builder /go/bin/app /app/ +COPY ./charts/kapparmor/profiles /app/profiles/ ARG PROFILES_DIR ARG POLL_TIME diff --git a/Dockerfile.alpine b/Dockerfile.alpine new file mode 100644 index 0000000..ba0b34f --- /dev/null +++ b/Dockerfile.alpine @@ -0,0 +1,30 @@ +# --- build stage +FROM golang:1.19-alpine AS builder +RUN apk add --no-cache git +WORKDIR /go/src/app +COPY . . +RUN go get -d -v ./go/src/app/ +RUN go build -o /go/bin/app -v ./go/src/app/ + +# --- +FROM alpine:latest +LABEL Name=kapparmor Version=0.0.1 +LABEL Author="Affinito Alessandro" + +WORKDIR /app + +RUN apk --no-cache update &&\ + apk add apparmor libapparmor &&\ + mkdir --parent --verbose /etc/apparmor.d/custom + +COPY --from=builder ./go/bin/app /app/ +COPY ./charts/kapparmor/profiles /app/profiles + +ARG PROFILES_DIR +ARG POLL_TIME + +ENV PROFILES_DIR=$PROFILES_DIR +ENV POLL_TIME=$POLL_TIME + +USER root +CMD ./app \ No newline at end of file diff --git a/docs/microk8s.md b/docs/microk8s.md index c3f6f86..b0d39f8 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -84,9 +84,9 @@ Run this on your linux node: # Move on the right branch before git pull && export GITHUB_SHA="sha-$(git log --online --no-abbrev-commit |head 1 |cut -d' ' -f1)" -helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ --wait &&\ +helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ &&\ echo &&\ - echo --- EVENTS &&\ + echo "--- EVENTS (wait 10 sec..)"&&\ sleep 10 &&\ kubectl get events --sort-by .lastTimestamp &&\ kubectl get pods -l app.kubernetes.io/name=kapparmor &&\ @@ -96,6 +96,19 @@ helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag= ``` +If the pod is running you can go inside to run some extra command, like: +```sh +POD_NAME=kubectl get pods -l app.kubernetes.io/name=kapparmor --no-headers |cut -d' ' -f1 +kubectl exec -ti $POD_NAME -- sh + cat /proc/1/attr/current + ps -ef + apparmor_parser --replace --verbose profiles/custom.deny-write-outside-app + + +# Run a new pod for extra testing +kubectl run ubuntu --rm --privileged -v /lib/modules/:/lib/modules/:ro +``` + ## Test profiles deny_all_writes.profile diff --git a/go/bin/.gitkeep b/go/bin/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/ubuntu_deploy.yml b/test/ubuntu_deploy.yml new file mode 100644 index 0000000..ccacac0 --- /dev/null +++ b/test/ubuntu_deploy.yml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: ubuntu + name: ubuntu +spec: + replicas: 1 + selector: + matchLabels: + app: ubuntu + strategy: {} + template: + metadata: + labels: + app: ubuntu + annotations: + container.apparmor.security.beta.kubernetes.io/ubuntu: unconfined + spec: + containers: + - image: ubuntu:22.10 + name: ubuntu + resources: + requests: + cpu: "100m" + memory: "20Mi" + limits: + memory: "200Mi" + command: ["bash", "-c"] + args: ["sleep infinity"] + + volumeMounts : + - name : kapparmor-profiles + mountPath : /app/profiles + readOnly : false + - name: profiles-kernel-path + mountPath: /sys/kernel/security + - name: etc-apparmor + mountPath: /etc/apparmor.d/ + env: + - name: PROFILES_DIR + valueFrom: + configMapKeyRef: + name: kapparmor-settings + key: PROFILES_DIR + - name: POLL_TIME + valueFrom: + configMapKeyRef: + name: kapparmor-settings + key: POLL_TIME + securityContext: + privileged: true + readOnlyRootFilesystem: false + + volumes: + - name: kapparmor-profiles + configMap: + name: kapparmor-profiles + - name: profiles-kernel-path + hostPath: + path: /sys/kernel/security + - name: etc-apparmor + hostPath: + path: /etc/apparmor.d/ \ No newline at end of file From ff098b8ff58e976ef9f1065fcd17e7906cb7d9dd Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Mon, 30 Jan 2023 17:23:44 +0100 Subject: [PATCH 32/46] go build ubuntu image --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1ea8ce4..ea26a25 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # --- build stage -FROM golang:1.19-alpine AS builder -RUN apk add --no-cache git +FROM golang:1.19 AS builder + WORKDIR /go/src/app COPY . . RUN go get -d -v ./go/src/app/ From c6c9c4a51e255dfe88cf95bd2d81d3e93b7acfc7 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Mon, 30 Jan 2023 17:24:02 +0100 Subject: [PATCH 33/46] privileged container --- README.md | 4 ++-- charts/kapparmor/values.yaml | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fc50959..c5ccc6a 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ docker run -it --network host --workdir=/data --volume ~/.kube/config:/root/.kub --volume $(pwd):/data quay.io/helmpack/chart-testing:latest \ /bin/sh -c "git config --global --add safe.directory /data; ct lint --print-config --charts ./charts/kapparmor" -# Replace here a commit id being part of an image tag, like "sha-554d8c92bf9738467ee433ad88e4ba22debf7f6b" -export GITHUB_SHA="sha-554d8c92bf9738467ee433ad88e4ba22debf7f6b" +# Replace here a commit id being part of an image tag +export GITHUB_SHA="sha-93d0dc4c597a8ae8a9febe1d68e674daf1fa919a" helm install --dry-run --atomic --generate-name --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ ``` diff --git a/charts/kapparmor/values.yaml b/charts/kapparmor/values.yaml index 332a0a6..85abe56 100644 --- a/charts/kapparmor/values.yaml +++ b/charts/kapparmor/values.yaml @@ -31,9 +31,8 @@ securityContext: capabilities: add: - SYS_ADMIN - allowPrivilegeEscalation: true readOnlyRootFilesystem: false - runAsUser: 0 + privileged: true service: type: ClusterIP From edf029851a3ddf06e2eb36406223782c6c79a8d0 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Mon, 30 Jan 2023 17:51:53 +0100 Subject: [PATCH 34/46] removed sys_admin cap --- CHANGELOG.md | 1 + charts/kapparmor/values.yaml | 3 --- docs/microk8s.md | 3 +++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f40f196..06b695a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 1. Add tests for all the main functions 1. Add test for checking current confinement state of the app 1. Test on multiple nodes cluster +1. helm diff in CD ## [0.0.6]() - diff --git a/charts/kapparmor/values.yaml b/charts/kapparmor/values.yaml index 85abe56..c120db3 100644 --- a/charts/kapparmor/values.yaml +++ b/charts/kapparmor/values.yaml @@ -28,9 +28,6 @@ podSecurityContext: {} # fsGroup: 2000 securityContext: - capabilities: - add: - - SYS_ADMIN readOnlyRootFilesystem: false privileged: true diff --git a/docs/microk8s.md b/docs/microk8s.md index b0d39f8..cfa8858 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -84,6 +84,9 @@ Run this on your linux node: # Move on the right branch before git pull && export GITHUB_SHA="sha-$(git log --online --no-abbrev-commit |head 1 |cut -d' ' -f1)" +# https://github.com/databus23/helm-diff +helm diff upgrade kapparmor --install --debug --set image.tag=$GITHUB_SHA charts/kapparmor + helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ &&\ echo &&\ echo "--- EVENTS (wait 10 sec..)"&&\ From 612b0d5ee95d7fb9e33134e620c038bd5db76518 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Mon, 30 Jan 2023 18:26:05 +0100 Subject: [PATCH 35/46] skip system files like '..data' in configmap --- go/src/app/filesystemOperations.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/go/src/app/filesystemOperations.go b/go/src/app/filesystemOperations.go index a28fabd..200406c 100644 --- a/go/src/app/filesystemOperations.go +++ b/go/src/app/filesystemOperations.go @@ -9,6 +9,7 @@ import ( "log" "os" "strconv" + "strings" ) func preFlightChecks() int { @@ -105,12 +106,16 @@ func areProfilesReadable(FOLDER_NAME string) (bool, map[string]bool) { log.Printf("Found files in given folder:\n") for _, file := range files { + filename := file.Name() if file.IsDir() { - log.Printf("Directory '%s' will be skipped.\n", file.Name()) + log.Printf("Directory '%s' will be skipped.\n", filename) + continue + } else if strings.HasPrefix(filename, ".") { + log.Printf("'%s' will be skipped.\n", filename) continue } - log.Printf("- %s\n", file.Name()) - filenames[file.Name()] = true + log.Printf("- %s\n", filename) + filenames[filename] = true } return true, filenames From 18482690ce00cff5c50611d625f8304b76db7e73 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Mon, 30 Jan 2023 18:41:06 +0100 Subject: [PATCH 36/46] HasTheSameContent check second file in etc/apparmor.d --- go/src/app/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/src/app/main.go b/go/src/app/main.go index 52a5de8..3835252 100644 --- a/go/src/app/main.go +++ b/go/src/app/main.go @@ -92,9 +92,9 @@ func loadNewProfiles() ([]string, error) { if customLoadedProfiles[newProfileName] { // If the profile is exactly the same skip the apply - // ERROR: it checks profiles still not applied - contentIsTheSame, err := HasTheSameContent(os.DirFS(ETC_APPARMORD), filePath1, newProfileName) + contentIsTheSame, err := HasTheSameContent(os.DirFS(ETC_APPARMORD), filePath1, path.Join(ETC_APPARMORD, newProfileName)) if err != nil { + // Error in checking the content of "/app/profiles/custom.deny-write-outside-app" VS "custom.deny-write-outside-app" log.Printf(">> Error in checking the content of %q VS %q\n", filePath1, newProfileName) return nil, err } From 56ea8d67f11ab4943e6bbacb27861e87975a15ba Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Tue, 31 Jan 2023 19:31:58 +0100 Subject: [PATCH 37/46] push container to local registry --- docs/microk8s.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/microk8s.md b/docs/microk8s.md index cfa8858..ab55594 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -77,6 +77,19 @@ Add this to the bottom of /var/snap/microk8s/current/args/kube-apiserver: microk8s start +## Test the container locally +While working on the app code it could be convenient avoid the pipelines building and pushing overload by using a local container registry where we will push directly our testing container. +See [microk8s.io registry-built-in](https://microk8s.io/docs/registry-built-in) + +Run this in you VM linux host: +```sh +docker build -t localhost:32000/kapparmor-local --build-arg POLL_TIME=60 --build-arg PROFILES_DIR=/app/profiles -f Dockerfile . +docker push localhost:32000/kapparmor-local + +kubectl set image daemonset/kapparmor kapparmor=localhost:32000/kapparmor-local + +``` + ## Install the helm chart Run this on your linux node: ```sh From db4f0be3e6542263449370ea0f631cced4002e01 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Tue, 31 Jan 2023 19:32:12 +0100 Subject: [PATCH 38/46] managing empy results --- .gitignore | 1 + Dockerfile | 2 +- README.md | 2 +- go/src/app/main.go | 7 +++++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1eaddae..7c03081 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ # Editor configs .idea/ .vscode/ +.errors/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ea26a25..9dd519c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ WORKDIR /app RUN apt-get update &&\ apt-get upgrade -y &&\ - apt-get install --no-install-recommends --yes apparmor &&\ + apt-get install --no-install-recommends --yes apparmor apparmor-utils &&\ rm -rf /var/lib/apt/lists/* &&\ mkdir --parent --verbose /etc/apparmor.d/custom diff --git a/README.md b/README.md index c5ccc6a..7083bd0 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ docker build --quiet -t test-kapparmor --build-arg POLL_TIME=60 --build-arg PROF docker run --rm -it --privileged \ --mount type=bind,source='/sys/kernel/security',target='/sys/kernel/security' \ --mount type=bind,source='/etc',target='/etc'\ - test-kapparmor + --name kapparmor test-kapparmor ``` diff --git a/go/src/app/main.go b/go/src/app/main.go index 3835252..76b25d2 100644 --- a/go/src/app/main.go +++ b/go/src/app/main.go @@ -69,11 +69,16 @@ func loadNewProfiles() ([]string, error) { log.Fatalf(">> Error reading existing profiles.\n%v", err) } + // Clean eventually empty keys + delete(customLoadedProfiles, "") + delete(loadedProfiles, "") + // Sort alphabetically the profiles and print them log.Println("Profiles already on this node:") loadedProfileNames := make([]string, len(loadedProfiles)) for loadedProfileName := range loadedProfiles { loadedProfileNames = append(loadedProfileNames, loadedProfileName) + log.Println(loadedProfileNames) } sort.Strings(loadedProfileNames) for _, p := range loadedProfileNames { @@ -91,6 +96,8 @@ func loadNewProfiles() ([]string, error) { // It exists a loaded profile with the same name if customLoadedProfiles[newProfileName] { + log.Printf("Checking %s profile..", path.Join(ETC_APPARMORD, newProfileName)) + // If the profile is exactly the same skip the apply contentIsTheSame, err := HasTheSameContent(os.DirFS(ETC_APPARMORD), filePath1, path.Join(ETC_APPARMORD, newProfileName)) if err != nil { From cb8e4ef2ddc1e51b08d22f1e271b6df7a314ff77 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Tue, 31 Jan 2023 22:29:56 +0100 Subject: [PATCH 39/46] instructions to test code locally --- docs/microk8s.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/microk8s.md b/docs/microk8s.md index ab55594..999f96b 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -90,12 +90,23 @@ kubectl set image daemonset/kapparmor kapparmor=localhost:32000/kapparmor-local ``` +Otherwise you can run it directly as root on your bash. Remember to take a snapshot of the VM before ;) +```bash +sudo -i +export POLL_TIME=60 +export PROFILES_DIR=../../../charts/kapparmor/profiles/ +cd kapparmor/go/src/app +rm /etc/apparmor.d/custom/custom.* +apparmor_parser --remove --verbose $PROFILES_DIR +go run . +``` + ## Install the helm chart Run this on your linux node: ```sh # Assume the last commit triggered a building pipeline, we'll have this as last docker image tag # Move on the right branch before -git pull && export GITHUB_SHA="sha-$(git log --online --no-abbrev-commit |head 1 |cut -d' ' -f1)" +git pull && export GITHUB_SHA="sha-$(git log --oneline --no-abbrev-commit -n 1 |cut -d' ' -f1)" # https://github.com/databus23/helm-diff helm diff upgrade kapparmor --install --debug --set image.tag=$GITHUB_SHA charts/kapparmor @@ -105,6 +116,7 @@ helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag= echo "--- EVENTS (wait 10 sec..)"&&\ sleep 10 &&\ kubectl get events --sort-by .lastTimestamp &&\ + echo &&\ kubectl get pods -l app.kubernetes.io/name=kapparmor &&\ echo &&\ echo --- POD LOGS &&\ From 2057ae4e6fde62b92504e55b58da3370e83ce4c1 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Tue, 31 Jan 2023 22:30:57 +0100 Subject: [PATCH 40/46] fix: compare file contents and copy new profiles --- go/src/app/filesystemOperations.go | 79 +++++++++++++++++++++++------- go/src/app/main.go | 42 ++++++++++------ 2 files changed, 88 insertions(+), 33 deletions(-) diff --git a/go/src/app/filesystemOperations.go b/go/src/app/filesystemOperations.go index 200406c..d64c8b1 100644 --- a/go/src/app/filesystemOperations.go +++ b/go/src/app/filesystemOperations.go @@ -8,6 +8,8 @@ import ( "io/fs" "log" "os" + "path" + "path/filepath" "strconv" "strings" ) @@ -19,7 +21,7 @@ func preFlightChecks() int { if err != nil { log.Fatalf(">> It was not possible to convert env var POLL_TIME %v to an integer.\n%v", POLL_TIME, err) } - log.Printf("POLL_TIME set to %d sec.", POLL_TIME) + log.Printf("POLL_TIME=%d sec.\n CONFIGMAP_PATH='%s'", POLL_TIME, CONFIGMAP_PATH) // Check profiler binary if _, err := os.Stat(PROFILER_BIN); os.IsNotExist(err) { @@ -38,14 +40,37 @@ func preFlightChecks() int { return POLL_TIME } +// Compare the byte content of two given files +// The function supports also an external filesystem for testing and future usages func HasTheSameContent(fsys fs.FS, filePath1, filePath2 string) (bool, error) { var file1, file2 os.FileInfo + + // Checking files on current filesystem + if fsys == nil { + fileBytes1, err := os.ReadFile(filePath1) + if err != nil { + log.Fatal(err) + } + fileBytes2, err := os.ReadFile(filePath2) + if err != nil { + log.Fatal(err) + } + if !bytes.Equal(fileBytes1, fileBytes2) { + return false, nil + } + return true, nil + } + + // dir will contain the files in given filesystem dir, err := fs.ReadDir(fsys, ".") if err != nil { + log.Printf("ERROR in opening directory %v\n", fsys) return false, err } + log.Printf(" dir: %v, First file path: %v, Second file path: %v", dir, filePath1, filePath2) + for _, file := range dir { if filePath1 == file.Name() { file1, _ = file.Info() @@ -55,7 +80,7 @@ func HasTheSameContent(fsys fs.FS, filePath1, filePath2 string) (bool, error) { } if file1 == nil || file2 == nil { - return false, fmt.Errorf("files not found") + return false, fmt.Errorf("ERROR: files not found") } if file1.Size() != file2.Size() { @@ -68,16 +93,21 @@ func HasTheSameContent(fsys fs.FS, filePath1, filePath2 string) (bool, error) { } defer f1.Close() - data1, err := io.ReadAll(f1) + f2, err := fsys.Open(file2.Name()) if err != nil { return false, err } + defer f2.Close() - f2, err := fsys.Open(file2.Name()) + return compareBytes(f1, f2) +} + +func compareBytes(f1, f2 fs.File) (bool, error) { + + data1, err := io.ReadAll(f1) if err != nil { return false, err } - defer f2.Close() data2, err := io.ReadAll(f2) if err != nil { @@ -104,7 +134,7 @@ func areProfilesReadable(FOLDER_NAME string) (bool, map[string]bool) { return false, nil } - log.Printf("Found files in given folder:\n") + log.Printf("Found files in %s:\n", FOLDER_NAME) for _, file := range files { filename := file.Name() if file.IsDir() { @@ -125,33 +155,42 @@ func areProfilesReadable(FOLDER_NAME string) (bool, map[string]bool) { // the same, then return success. Otherwise, attempt to create a hard link // between the two files. If that fail, copy the file contents from src to dst. // Credits: https://stackoverflow.com/a/21067803/3673430 -func CopyFile(src, dst string) (err error) { +func CopyFile(src, dst string) error { + + // dst is the destination directory + srcFileName := filepath.Base(src) + dstCompleteFileName := path.Join(ETC_APPARMORD, srcFileName) + sfi, err := os.Stat(src) if err != nil { - return + log.Fatal(err) } + if !sfi.Mode().IsRegular() { // cannot copy non-regular files (e.g., directories symlinks, devices, etc.) return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String()) } - dfi, err := os.Stat(dst) + + dfi, err := os.Stat(dstCompleteFileName) if err != nil { - if !os.IsNotExist(err) { - return - } + log.Print(err) } else { if !(dfi.Mode().IsRegular()) { return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String()) } if os.SameFile(sfi, dfi) { - return + log.Printf("File %s is already present", dstCompleteFileName) + return nil } } - if err = os.Link(src, dst); err == nil { - return + + log.Print(" Creating link..") + if err = os.Link(src, dstCompleteFileName); err == nil { + log.Printf("Hard link creation failed in %s", dstCompleteFileName) } - err = copyFileContents(src, dst) - return + + log.Printf("Copying %s in %s", src, dstCompleteFileName) + return copyFileContents(src, dstCompleteFileName) } // copyFileContents copies the contents of the file named src to the file named @@ -161,11 +200,14 @@ func CopyFile(src, dst string) (err error) { func copyFileContents(src, dst string) (err error) { in, err := os.Open(src) if err != nil { + log.Print(err) return } defer in.Close() + out, err := os.Create(dst) if err != nil { + log.Print(err) return } defer func() { @@ -174,9 +216,12 @@ func copyFileContents(src, dst string) (err error) { err = cerr } }() + if _, err = io.Copy(out, in); err != nil { + log.Print(err) return } + err = out.Sync() return } diff --git a/go/src/app/main.go b/go/src/app/main.go index 76b25d2..0c8ecab 100644 --- a/go/src/app/main.go +++ b/go/src/app/main.go @@ -74,15 +74,16 @@ func loadNewProfiles() ([]string, error) { delete(loadedProfiles, "") // Sort alphabetically the profiles and print them - log.Println("Profiles already on this node:") + log.Printf("%d Profiles already on this node:", len(loadedProfiles)) loadedProfileNames := make([]string, len(loadedProfiles)) for loadedProfileName := range loadedProfiles { loadedProfileNames = append(loadedProfileNames, loadedProfileName) - log.Println(loadedProfileNames) } sort.Strings(loadedProfileNames) for _, p := range loadedProfileNames { - log.Printf("- %s\n", p) + if p != "" { + log.Printf("- %s\n", p) + } } // Check if it exists a profile already loaded with the same name @@ -96,17 +97,16 @@ func loadNewProfiles() ([]string, error) { // It exists a loaded profile with the same name if customLoadedProfiles[newProfileName] { - log.Printf("Checking %s profile..", path.Join(ETC_APPARMORD, newProfileName)) - // If the profile is exactly the same skip the apply - contentIsTheSame, err := HasTheSameContent(os.DirFS(ETC_APPARMORD), filePath1, path.Join(ETC_APPARMORD, newProfileName)) + log.Printf("Checking %s profile..", path.Join(CONFIGMAP_PATH, newProfileName)) + contentIsTheSame, err := HasTheSameContent(nil, filePath1, path.Join(CONFIGMAP_PATH, newProfileName)) if err != nil { // Error in checking the content of "/app/profiles/custom.deny-write-outside-app" VS "custom.deny-write-outside-app" log.Printf(">> Error in checking the content of %q VS %q\n", filePath1, newProfileName) return nil, err } if contentIsTheSame { - log.Printf("Content of %q and %q seems the same, skipping.", filePath1, newProfileName) + log.Print("Contents seems the same, skipping..") continue } } @@ -125,16 +125,21 @@ func loadNewProfiles() ([]string, error) { log.Println("============================================================") log.Println("> Apparmor replace and apply new profiles..") for _, profilePath := range newProfilesToApply { - loadProfile(profilePath) + err := loadProfile(profilePath) + if err != nil { + log.Printf("ERROR: %s", err) + } } // Execute apparmor_parser --remove obsoleteProfilePath - log.Println("============================================================") - log.Println("> AppArmor REMOVE orphans profiles..") - for _, profileFileName := range loadedProfilesToUnload { - err := unloadProfile(profileFileName) - if err != nil { - log.Fatal(err) + if len(loadedProfilesToUnload) > 0 { + log.Println("============================================================") + log.Println("> AppArmor REMOVE orphans profiles..") + for _, profileFileName := range loadedProfilesToUnload { + err := unloadProfile(profileFileName) + if err != nil { + log.Fatal(err) + } } } @@ -199,8 +204,13 @@ func parseProfileName(profileLine string) string { } func loadProfile(profilePath string) error { - execApparmor("--verbose", "--replace", profilePath) + err := execApparmor("--verbose", "--replace", profilePath) + if err != nil { + log.Fatal(err) + } + // Copy the profile definition in the apparmor configuration standard directory + log.Printf("Copying profile in %s", ETC_APPARMORD) return CopyFile(profilePath, ETC_APPARMORD) } @@ -216,7 +226,7 @@ func execApparmor(args ...string) error { cmd.Stderr = stderr out, err := cmd.Output() path := args[len(args)-1] - log.Printf(" Loading profiles from %s:\n%s", path, out) + log.Printf("Loading profiles from %s:\n%s", path, out) if err != nil { if stderr.Len() > 0 { log.Println(stderr.String()) From ad6ce584c5e4b519af67585b521704110db766b8 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 1 Feb 2023 10:26:12 +0100 Subject: [PATCH 41/46] fixed hard link creation message --- go/src/app/filesystemOperations.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/go/src/app/filesystemOperations.go b/go/src/app/filesystemOperations.go index d64c8b1..d398874 100644 --- a/go/src/app/filesystemOperations.go +++ b/go/src/app/filesystemOperations.go @@ -21,7 +21,6 @@ func preFlightChecks() int { if err != nil { log.Fatalf(">> It was not possible to convert env var POLL_TIME %v to an integer.\n%v", POLL_TIME, err) } - log.Printf("POLL_TIME=%d sec.\n CONFIGMAP_PATH='%s'", POLL_TIME, CONFIGMAP_PATH) // Check profiler binary if _, err := os.Stat(PROFILER_BIN); os.IsNotExist(err) { @@ -184,9 +183,9 @@ func CopyFile(src, dst string) error { } } - log.Print(" Creating link..") if err = os.Link(src, dstCompleteFileName); err == nil { - log.Printf("Hard link creation failed in %s", dstCompleteFileName) + log.Printf("Hard link created in %s", dstCompleteFileName) + return nil } log.Printf("Copying %s in %s", src, dstCompleteFileName) From f113625250e09fd202502d4c6c362b71c96ec3f2 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 1 Feb 2023 10:26:38 +0100 Subject: [PATCH 42/46] fix: manage execApparmor error --- go/src/app/main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/go/src/app/main.go b/go/src/app/main.go index 0c8ecab..5ec93ef 100644 --- a/go/src/app/main.go +++ b/go/src/app/main.go @@ -216,7 +216,11 @@ func loadProfile(profilePath string) error { func unloadProfile(fileName string) error { filePath := path.Join(ETC_APPARMORD, fileName) - execApparmor("--verbose", "--remove", filePath) + + err := execApparmor("--verbose", "--remove", filePath) + if err != nil { + return err + } return os.Remove(filePath) } From 970142dad499513c6407497fddb32164394523df Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 1 Feb 2023 10:26:53 +0100 Subject: [PATCH 43/46] typos --- docs/microk8s.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/microk8s.md b/docs/microk8s.md index 999f96b..3f91ecb 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -96,6 +96,7 @@ sudo -i export POLL_TIME=60 export PROFILES_DIR=../../../charts/kapparmor/profiles/ cd kapparmor/go/src/app + rm /etc/apparmor.d/custom/custom.* apparmor_parser --remove --verbose $PROFILES_DIR go run . From a6fc8297b87553780d3b4c358ce5e24a6b5b9413 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 1 Feb 2023 11:32:04 +0100 Subject: [PATCH 44/46] testing configmap --- test/cm-kapparmor-home-profile.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 test/cm-kapparmor-home-profile.yml diff --git a/test/cm-kapparmor-home-profile.yml b/test/cm-kapparmor-home-profile.yml new file mode 100644 index 0000000..129b216 --- /dev/null +++ b/test/cm-kapparmor-home-profile.yml @@ -0,0 +1,17 @@ +apiVersion: v1 +data: + custom.deny-write-outside-home: |- + profile custom.deny-write-outside-home flags=(attach_disconnected) { + file, # access all filesystem + /home/** rw, + deny /bin/** w, # deny writes in all subdirectories + deny /etc/** w, + deny /usr/** w, + } +kind: ConfigMap +metadata: + annotations: + meta.helm.sh/release-name: kapparmor + labels: + app.kubernetes.io/managed-by: Helm + name: kapparmor-profiles From a83629af33d2874fa112786f880b5a5be87df7f5 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 1 Feb 2023 11:32:45 +0100 Subject: [PATCH 45/46] preparing for PR --- CHANGELOG.md | 15 ++++++++++++--- README.md | 7 ++++++- charts/kapparmor/templates/daemonset.yaml | 5 ++++- docs/microk8s.md | 3 +++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b695a..6d1d972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -1. **Unable to replace profiles**. Permission denied, app seems still confined. 1. Go unit tests - [ ] Create a new profile - [ ] Update an existing profile @@ -19,9 +18,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 1. Add tests for all the main functions 1. Add test for checking current confinement state of the app 1. Test on multiple nodes cluster -1. helm diff in CD -## [0.0.6]() - + +## [0.1.0]() - 2023-02-01 +### Fixed +1. "Unable to replace profiles. Permission denied, app seems still confined." - Switched to ubuntu image +1. No need for SYS_ADMIN capabilities +1. Ignore hidden and system folders while scanning for profiles + +### Added +1. Instructions to test the app in a virtual machine directly running the go app or in microk8s pushing the built container to the local registry + + +## 0.0.6 - 2023-01-26 ### Added Helm: diff --git a/README.md b/README.md index 7083bd0..542cf80 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,14 @@ - ----- Apparmor-loader project to deploy profiles through a kubernetes daemonset. -This work was inspired by [kubernetes/apparmor-loader](https://github.com/kubernetes/kubernetes/tree/master/test/images/apparmor-loader). ![architecture](./docs/kapparmor-architecture.png) +This app provide dynamic loading and unloading of AppArmor profiles to a Kubernetes cluster through a configmap. +The app doesn't need an operator and it will be managed by a DaemonSet filtering the linux nodes to schedule the app pod. +The custom profiles deployed in the configmap will be copied in a directory (`/etc/apparmor.d/custom` by default) since apparmor_parser needs the profiles definitions also to remove them. Once you will deploy a configmap with different profiles, Kapparmor will notice the missing ones and it will remove them from the apparmor cache and from the node directory. +If you modify only the content of a profile leaving the same name, Kapparmor should notice it anyway since a byte comparison is done when configmap profiles names and local profiles names match. + 1. The CD pipeline will - deploy a configmap in the security namespace containing all the profiles versioned in the current project - it will apply a daemonset on the linux nodes @@ -25,6 +29,7 @@ This work was inspired by [kubernetes/apparmor-loader](https://github.com/kubern You can view which profiles are loaded on a node by checking the /sys/kernel/security/apparmor/profiles, so its parent will need to be mounted in the pod. +This work was inspired by [kubernetes/apparmor-loader](https://github.com/kubernetes/kubernetes/tree/master/test/images/apparmor-loader). ## Testing [Set up a Microk8s environment](./docs/microk8s.md). diff --git a/charts/kapparmor/templates/daemonset.yaml b/charts/kapparmor/templates/daemonset.yaml index aafdb7e..7d00e3c 100644 --- a/charts/kapparmor/templates/daemonset.yaml +++ b/charts/kapparmor/templates/daemonset.yaml @@ -37,13 +37,16 @@ spec: resources: {{- toYaml .Values.resources | nindent 12 }} - # mount a configmap as read only in the container's filesystem. + volumeMounts : + # Folder containing profiles files mounted from the configmap - name : kapparmor-profiles mountPath : {{ .Values.app.profiles_dir }} readOnly : false + # Folder used by the kernel to store loaded profiles names - name: profiles-kernel-path mountPath: /sys/kernel/security + # Folder used by the app to store custom profiles definitions - name: etc-apparmor mountPath: /etc/apparmor.d/ diff --git a/docs/microk8s.md b/docs/microk8s.md index 3f91ecb..8cc64e3 100644 --- a/docs/microk8s.md +++ b/docs/microk8s.md @@ -112,6 +112,9 @@ git pull && export GITHUB_SHA="sha-$(git log --oneline --no-abbrev-commit -n 1 | # https://github.com/databus23/helm-diff helm diff upgrade kapparmor --install --debug --set image.tag=$GITHUB_SHA charts/kapparmor +rm /etc/apparmor.d/custom/custom.* +apparmor_parser --remove --verbose $PROFILES_DIR + helm upgrade kapparmor --install --atomic --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/ &&\ echo &&\ echo "--- EVENTS (wait 10 sec..)"&&\ From 311b97fb75d7769608a56bb7a6965435f2425d29 Mon Sep 17 00:00:00 2001 From: Alessandro Affinito Date: Wed, 1 Feb 2023 11:38:35 +0100 Subject: [PATCH 46/46] release version --- charts/kapparmor/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/kapparmor/Chart.yaml b/charts/kapparmor/Chart.yaml index 1293c80..5b73adf 100644 --- a/charts/kapparmor/Chart.yaml +++ b/charts/kapparmor/Chart.yaml @@ -5,8 +5,8 @@ type: application home: https://artifacthub.io kubeVersion: ">= 1.23.0-0" -version: "0.0.6" -appVersion: "0.0.2" +version: "0.1.0" +appVersion: "0.1.0" keywords: - kubernetes