From d42727c38e8894d55c6f2ff68ce81df52964862c Mon Sep 17 00:00:00 2001 From: AdamKorcz Date: Fri, 1 Oct 2021 18:53:35 +0100 Subject: [PATCH 1/2] Fuzzing: Initial commit Signed-off-by: AdamKorcz --- fuzz/Dockerfile | 22 ++++ fuzz/fuzz.go | 316 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 338 insertions(+) create mode 100644 fuzz/Dockerfile create mode 100644 fuzz/fuzz.go diff --git a/fuzz/Dockerfile b/fuzz/Dockerfile new file mode 100644 index 00000000..4b44fab3 --- /dev/null +++ b/fuzz/Dockerfile @@ -0,0 +1,22 @@ +# Build the manager binary +FROM golang:1.16-buster as builder + +RUN apt-get update && apt-get install -y git clang +RUN git clone https://github.com/fluxcd/image-reflector-controller /image-reflector-controller +RUN mkdir /image-reflector-controller/fuzz +COPY fuzz.go /image-reflector-controller/controllers/ + +RUN cd /image-reflector-controller/controllers \ + && go get github.com/AdaLogics/go-fuzz-headers \ + && go get go.uber.org/zap/zapcore@v1.18.1 + +RUN cd / \ + && go get -u github.com/mdempsky/go114-fuzz-build \ + && go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest + +RUN mkdir /fuzzers +RUN cd /image-reflector-controller/controllers \ + && go114-fuzz-build -o Fuzz.a -func Fuzz . \ + && clang -o /fuzzers/Fuzz Fuzz.a -fsanitize=fuzzer + +RUN cd /image-reflector-controller/controllers && /fuzzers/Fuzz diff --git a/fuzz/fuzz.go b/fuzz/fuzz.go new file mode 100644 index 00000000..96faa8d2 --- /dev/null +++ b/fuzz/fuzz.go @@ -0,0 +1,316 @@ +//go:build gofuzz +// +build gofuzz + +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "time" + + "go.uber.org/zap/zapcore" + + "github.com/dgraph-io/badger/v3" + . "github.com/onsi/ginkgo" + + //. "github.com/onsi/gomega" + "github.com/google/go-containerregistry/pkg/registry" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + fuzz "github.com/AdaLogics/go-fuzz-headers" + + imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" + "github.com/fluxcd/image-reflector-controller/internal/database" +) + +const ( + timeout = time.Second * 30 + contextTimeout = time.Second * 10 + interval = time.Second * 1 + reconciliationInterval = time.Second * 2 +) + +var registryServer *httptest.Server +var cfg *rest.Config +var k8sClient client.Client +var k8sMgr ctrl.Manager +var stopManager func() +var imageRepoReconciler *ImageRepositoryReconciler +var imagePolicyReconciler *ImagePolicyReconciler +var testEnv *envtest.Environment +var badgerDir string +var badgerDB *badger.DB +var initter sync.Once + +// createKUBEBUILDER_ASSETS runs "setup-envtest use" +// and returns the path of the 3 binaries +func createKUBEBUILDER_ASSETS() string { + out, err := exec.Command("setup-envtest", "use").Output() + if err != nil { + panic(err) + } + + // split the output to get the path: + splitString := strings.Split(string(out), " ") + binPath := strings.TrimSuffix(splitString[len(splitString)-1], "\n") + if err != nil { + panic(err) + } + return binPath +} + +// initFunc is an init function that is invoked by +// way of sync.Do. +func initFunc() { + kubebuilder_assets := createKUBEBUILDER_ASSETS() + os.Setenv("KUBEBUILDER_ASSETS", kubebuilder_assets) + + ctrl.SetLogger( + zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true), zap.Level(zapcore.PanicLevel)), + ) + + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + } + + var err error + cfg, err = testEnv.Start() + if err != nil { + panic(err) + } + if cfg == nil { + panic("cfg is nill") + } + + err = imagev1.AddToScheme(scheme.Scheme) + if err != nil { + panic(err) + } + + k8sMgr, err = ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) + if err != nil { + panic(err) + } + + badgerDir, err = ioutil.TempDir(os.TempDir(), "badger") + if err != nil { + panic(err) + } + badgerDB, err = badger.Open(badger.DefaultOptions(badgerDir)) + if err != nil { + panic(err) + } + + imageRepoReconciler = &ImageRepositoryReconciler{ + Client: k8sMgr.GetClient(), + Scheme: scheme.Scheme, + Database: database.NewBadgerDatabase(badgerDB), + } + err = imageRepoReconciler.SetupWithManager(k8sMgr, ImageRepositoryReconcilerOptions{}) + if err != nil { + panic(err) + } + + imagePolicyReconciler = &ImagePolicyReconciler{ + Client: k8sMgr.GetClient(), + Scheme: scheme.Scheme, + Database: database.NewBadgerDatabase(badgerDB), + } + err = imagePolicyReconciler.SetupWithManager(k8sMgr, ImagePolicyReconcilerOptions{}) + if err != nil { + panic(err) + } + + mgrContext, cancel := context.WithCancel(ctrl.SetupSignalHandler()) + go func() { + err = k8sMgr.Start(mgrContext) + if err != nil { + panic(err) + } + }() + stopManager = cancel + + k8sClient = k8sMgr.GetClient() + if k8sClient == nil { + panic("k8sClient is nil") + } +} + +func registryName(srv *httptest.Server) string { + if strings.HasPrefix(srv.URL, "https://") { + return strings.TrimPrefix(srv.URL, "https://") + } // else assume HTTP + return strings.TrimPrefix(srv.URL, "http://") +} + +// Fuzz implements a fuzzer that creates pseudo-random objects. +func Fuzz(data []byte) int { + initter.Do(initFunc) + registryServer = newRegistryServer() + defer registryServer.Close() + f := fuzz.NewConsumer(data) + + imgRepo := registryName(registryServer) + repo := imagev1.ImageRepository{} + err := f.GenerateStruct(&repo) + if err != nil { + return 0 + } + repo.Spec.Image = imgRepo + + objectName, err := f.GetStringFrom("abcdefghijklmnopqrstuvwxyz123456789", 59) + if err != nil { + return 0 + } + imageObjectName := types.NamespacedName{ + Name: objectName, + Namespace: "default", + } + repo.Name = imageObjectName.Name + repo.Namespace = imageObjectName.Namespace + + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*200) + defer cancel() + + r := imageRepoReconciler + if r == nil { + panic("r is nil") + } + err = r.Create(ctx, &repo) + if err != nil { + return 0 + } + time.Sleep(30 * time.Millisecond) + err = r.Get(ctx, imageObjectName, &repo) + if err != nil || repo.Status.LastScanResult != nil { + panic("Failed1") + } + + polNs, err := f.GetStringFrom("abcdefghijklmnopqrstuvwxyz123456789", 59) + if err != nil { + return 0 + } + polName := types.NamespacedName{ + Name: polNs, + Namespace: imageObjectName.Namespace, + } + pol := imagev1.ImagePolicy{} + err = f.GenerateStruct(&pol) + if err != nil { + return 0 + } + pol.Spec.ImageRepositoryRef.Name = imageObjectName.Name + + pol.Namespace = polName.Namespace + pol.Name = polName.Name + + ctx, cancel = context.WithTimeout(context.Background(), time.Millisecond*200) + defer cancel() + + err = r.Create(ctx, &pol) + if err != nil { + return 0 + } + time.Sleep(time.Millisecond * 30) + err = r.Get(ctx, polName, &pol) + if err != nil { + panic(err) + } + return 1 +} + +// Taken from here: https://github.com/fluxcd/image-reflector-controller/blob/main/controllers/registry_test.go#L62 +func newRegistryServer() *httptest.Server { + regHandler := registry.New() + srv := httptest.NewServer(&tagListHandler{ + registryHandler: regHandler, + imagetags: convenientTags, + }) + return srv +} + +// tje tagListHandler is taken from here: +// https://github.com/fluxcd/image-reflector-controller/blob/main/controllers/registry_test.go#L62 + +type tagListHandler struct { + registryHandler http.Handler + imagetags map[string][]string +} + +type tagListResult struct { + Name string `json:"name"` + Tags []string `json:"tags"` +} + +// Take from here: https://github.com/fluxcd/image-reflector-controller/blob/main/controllers/registry_test.go#L126 +// and modified to not include any of the BDD APIs +func (h *tagListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if withoutTagsList := strings.TrimSuffix(r.URL.Path, "/tags/list"); r.Method == "GET" && withoutTagsList != r.URL.Path { + repo := strings.TrimPrefix(withoutTagsList, "/v2/") + if tags, ok := h.imagetags[repo]; ok { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + result := tagListResult{ + Name: repo, + Tags: tags, + } + err := json.NewEncoder(w).Encode(result) + if err != nil { + panic(err) + } + println("Requested tags", repo, strings.Join(tags, ", ")) + return + } + w.WriteHeader(http.StatusNotFound) + return + } + + // record the fact of a PUT to a tag; the path looks like: /v2//manifests/ + h.registryHandler.ServeHTTP(w, r) + if r.Method == "PUT" { + pathElements := strings.Split(r.URL.Path, "/") + if len(pathElements) == 5 && pathElements[1] == "v2" && pathElements[3] == "manifests" { + repo, tag := pathElements[2], pathElements[4] + println("Recording tag", repo, tag) + h.imagetags[repo] = append(h.imagetags[repo], tag) + } + } +} + +var convenientTags = map[string][]string{ + "convenient": []string{ + "tag1", "tag2", + }, +} From e41e4b6c41c326353c72c7375a2b85b84b971cff Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Tue, 18 Jan 2022 11:13:44 +0000 Subject: [PATCH 2/2] Refactor fuzzing Structure the fuzz implementation to be closer to what go native will support. Add Makefile target to enable smoketesting fuzzers. Add smoketest as CI workflow. Signed-off-by: Paulo Gomes --- .github/workflows/cifuzz.yaml | 20 ++ .gitignore | 3 +- Makefile | 23 ++- fuzz/Dockerfile | 22 --- tests/fuzz/Dockerfile.builder | 6 + tests/fuzz/README.md | 45 +++++ .../fuzz.go => tests/fuzz/fuzz_controllers.go | 176 ++++++++---------- tests/fuzz/go.mod | 5 + tests/fuzz/oss_fuzz_build.sh | 46 +++++ tests/fuzz/oss_fuzz_run.sh | 20 ++ 10 files changed, 245 insertions(+), 121 deletions(-) create mode 100644 .github/workflows/cifuzz.yaml delete mode 100644 fuzz/Dockerfile create mode 100644 tests/fuzz/Dockerfile.builder create mode 100644 tests/fuzz/README.md rename fuzz/fuzz.go => tests/fuzz/fuzz_controllers.go (61%) create mode 100644 tests/fuzz/go.mod create mode 100755 tests/fuzz/oss_fuzz_build.sh create mode 100755 tests/fuzz/oss_fuzz_run.sh diff --git a/.github/workflows/cifuzz.yaml b/.github/workflows/cifuzz.yaml new file mode 100644 index 00000000..202ce966 --- /dev/null +++ b/.github/workflows/cifuzz.yaml @@ -0,0 +1,20 @@ +name: CIFuzz +on: + pull_request: + branches: + - main +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Restore Go cache + uses: actions/cache@v1 + with: + path: /home/runner/work/_temp/_github_home/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Smoke test Fuzzers + run: make fuzz-smoketest diff --git a/.gitignore b/.gitignore index c46c3907..f7f53314 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ notes *.so *.dylib bin -testbin # Test binary, build with `go test -c` *.test @@ -24,3 +23,5 @@ testbin *.swp *.swo *~ + +build/ diff --git a/Makefile b/Makefile index 6dc6b7eb..e383bb27 100644 --- a/Makefile +++ b/Makefile @@ -114,7 +114,7 @@ ENVTEST = $(shell pwd)/bin/setup-envtest setup-envtest: ## Download envtest-setup locally if necessary. $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) -ENVTEST_ASSETS_DIR=$(shell pwd)/testbin +ENVTEST_ASSETS_DIR=$(shell pwd)/build/testbin ENVTEST_KUBERNETES_VERSION?=latest install-envtest: setup-envtest mkdir -p ${ENVTEST_ASSETS_DIR} @@ -133,3 +133,24 @@ GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\ rm -rf $$TMP_DIR ;\ } endef + +# Build fuzzers +fuzz-build: + rm -rf $(shell pwd)/build/fuzz/ + mkdir -p $(shell pwd)/build/fuzz/out/ + + docker build . --tag local-fuzzing:latest -f tests/fuzz/Dockerfile.builder + docker run --rm \ + -e FUZZING_LANGUAGE=go -e SANITIZER=address \ + -e CIFUZZ_DEBUG='True' -e OSS_FUZZ_PROJECT_NAME=fluxcd \ + -v "$(shell pwd)/build/fuzz/out":/out \ + local-fuzzing:latest + +# Run each fuzzer once to ensure they are working +fuzz-smoketest: fuzz-build + docker run --rm \ + -v "$(shell pwd)/build/fuzz/out":/out \ + -v "$(shell pwd)/tests/fuzz/oss_fuzz_run.sh":/runner.sh \ + -e ENVTEST_KUBERNETES_VERSION="$(ENVTEST_KUBERNETES_VERSION)" \ + local-fuzzing:latest \ + bash -c "/runner.sh" diff --git a/fuzz/Dockerfile b/fuzz/Dockerfile deleted file mode 100644 index 4b44fab3..00000000 --- a/fuzz/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -# Build the manager binary -FROM golang:1.16-buster as builder - -RUN apt-get update && apt-get install -y git clang -RUN git clone https://github.com/fluxcd/image-reflector-controller /image-reflector-controller -RUN mkdir /image-reflector-controller/fuzz -COPY fuzz.go /image-reflector-controller/controllers/ - -RUN cd /image-reflector-controller/controllers \ - && go get github.com/AdaLogics/go-fuzz-headers \ - && go get go.uber.org/zap/zapcore@v1.18.1 - -RUN cd / \ - && go get -u github.com/mdempsky/go114-fuzz-build \ - && go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest - -RUN mkdir /fuzzers -RUN cd /image-reflector-controller/controllers \ - && go114-fuzz-build -o Fuzz.a -func Fuzz . \ - && clang -o /fuzzers/Fuzz Fuzz.a -fsanitize=fuzzer - -RUN cd /image-reflector-controller/controllers && /fuzzers/Fuzz diff --git a/tests/fuzz/Dockerfile.builder b/tests/fuzz/Dockerfile.builder new file mode 100644 index 00000000..f99f4be3 --- /dev/null +++ b/tests/fuzz/Dockerfile.builder @@ -0,0 +1,6 @@ +FROM gcr.io/oss-fuzz-base/base-builder-go + +COPY ./ $GOPATH/src/github.com/fluxcd/image-reflector-controller/ +COPY ./tests/fuzz/oss_fuzz_build.sh $SRC/build.sh + +WORKDIR $SRC diff --git a/tests/fuzz/README.md b/tests/fuzz/README.md new file mode 100644 index 00000000..f2d23396 --- /dev/null +++ b/tests/fuzz/README.md @@ -0,0 +1,45 @@ +# fuzz testing + +Flux is part of Google's [oss fuzz] program which provides continuous fuzzing for +open source projects. + +The long running fuzzing execution is configured in the [oss-fuzz repository]. +Shorter executions are done on a per-PR basis, configured as a [github workflow]. + +For fuzzers to be called, they must be compiled within [oss_fuzz_build.sh](./oss_fuzz_build.sh). + +### Testing locally + +Build fuzzers: + +```bash +make fuzz-build +``` +All fuzzers will be built into `./build/fuzz/out`. + +Smoke test fuzzers: + +```bash +make fuzz-smoketest +``` + +The smoke test runs each fuzzer once to ensure they are fully functional. + +Run fuzzer locally: +```bash +./build/fuzz/out/fuzz_conditions_match +``` + +Run fuzzer inside a container: + +```bash + docker run --rm -ti \ + -v "$(pwd)/build/fuzz/out":/out \ + gcr.io/oss-fuzz/fluxcd \ + /out/fuzz_conditions_match +``` + + +[oss fuzz]: https://github.com/google/oss-fuzz +[oss-fuzz repository]: https://github.com/google/oss-fuzz/tree/master/projects/fluxcd +[github workflow]: .github/workflows/cifuzz.yaml diff --git a/fuzz/fuzz.go b/tests/fuzz/fuzz_controllers.go similarity index 61% rename from fuzz/fuzz.go rename to tests/fuzz/fuzz_controllers.go index 96faa8d2..3d998669 100644 --- a/fuzz/fuzz.go +++ b/tests/fuzz/fuzz_controllers.go @@ -21,14 +21,14 @@ package controllers import ( "context" - "encoding/json" + "embed" + "fmt" + "io/fs" "io/ioutil" - "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" - "strings" "sync" "time" @@ -38,7 +38,7 @@ import ( . "github.com/onsi/ginkgo" //. "github.com/onsi/gomega" - "github.com/google/go-containerregistry/pkg/registry" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -72,35 +72,87 @@ var badgerDir string var badgerDB *badger.DB var initter sync.Once -// createKUBEBUILDER_ASSETS runs "setup-envtest use" -// and returns the path of the 3 binaries -func createKUBEBUILDER_ASSETS() string { - out, err := exec.Command("setup-envtest", "use").Output() - if err != nil { - panic(err) +const defaultBinVersion = "1.23" + +func envtestBinVersion() string { + if binVersion := os.Getenv("ENVTEST_KUBERNETES_VERSION"); binVersion != "" { + return binVersion } + return defaultBinVersion +} - // split the output to get the path: - splitString := strings.Split(string(out), " ") - binPath := strings.TrimSuffix(splitString[len(splitString)-1], "\n") - if err != nil { - panic(err) +//go:embed testdata/crd/*.yaml +var testFiles embed.FS + +// ensureDependencies ensure that: +// a) setup-envtest is installed and a specific version of envtest is deployed. +// b) the embedded crd files are exported onto the "runner container". +// +// The steps above are important as the fuzzers tend to be built in an +// environment (or container) and executed in other. +func ensureDependencies() error { + // only install dependencies when running inside a container + if _, err := os.Stat("/.dockerenv"); os.IsNotExist(err) { + return nil + } + + if os.Getenv("KUBEBUILDER_ASSETS") == "" { + binVersion := envtestBinVersion() + cmd := exec.Command("/usr/bin/bash", "-c", fmt.Sprintf(`go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest && \ + /root/go/bin/setup-envtest use -p path %s`, binVersion)) + + cmd.Env = append(os.Environ(), "GOPATH=/root/go") + assetsPath, err := cmd.Output() + if err != nil { + return err + } + os.Setenv("KUBEBUILDER_ASSETS", string(assetsPath)) + } + + // Output all embedded testdata files to disk. + embedDirs := []string{"testdata/crd"} + for _, dir := range embedDirs { + err := os.MkdirAll(dir, 0o755) + if err != nil { + return fmt.Errorf("mkdir %s: %v", dir, err) + } + + templates, err := fs.ReadDir(testFiles, dir) + if err != nil { + return fmt.Errorf("reading embedded dir: %v", err) + } + + for _, template := range templates { + fileName := fmt.Sprintf("%s/%s", dir, template.Name()) + fmt.Println(fileName) + + data, err := testFiles.ReadFile(fileName) + if err != nil { + return fmt.Errorf("reading embedded file %s: %v", fileName, err) + } + + os.WriteFile(fileName, data, 0o644) + if err != nil { + return fmt.Errorf("writing %s: %v", fileName, err) + } + } } - return binPath + + return nil } -// initFunc is an init function that is invoked by -// way of sync.Do. +// initFunc is an init function that is invoked by way of sync.Do. func initFunc() { - kubebuilder_assets := createKUBEBUILDER_ASSETS() - os.Setenv("KUBEBUILDER_ASSETS", kubebuilder_assets) + if err := ensureDependencies(); err != nil { + panic(fmt.Sprintf("Failed to ensure dependencies: %v", err)) + } ctrl.SetLogger( zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true), zap.Level(zapcore.PanicLevel)), ) testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("testdata", "crd")}, } var err error @@ -168,15 +220,9 @@ func initFunc() { } } -func registryName(srv *httptest.Server) string { - if strings.HasPrefix(srv.URL, "https://") { - return strings.TrimPrefix(srv.URL, "https://") - } // else assume HTTP - return strings.TrimPrefix(srv.URL, "http://") -} - -// Fuzz implements a fuzzer that creates pseudo-random objects. -func Fuzz(data []byte) int { +// Fuzz implements a fuzzer that creates pseudo-random objects +// for the ImageRepositoryController to reconcile. +func FuzzImageRepositoryController(data []byte) int { initter.Do(initFunc) registryServer = newRegistryServer() defer registryServer.Close() @@ -206,7 +252,7 @@ func Fuzz(data []byte) int { r := imageRepoReconciler if r == nil { - panic("r is nil") + return 0 } err = r.Create(ctx, &repo) if err != nil { @@ -215,7 +261,7 @@ func Fuzz(data []byte) int { time.Sleep(30 * time.Millisecond) err = r.Get(ctx, imageObjectName, &repo) if err != nil || repo.Status.LastScanResult != nil { - panic("Failed1") + return 0 } polNs, err := f.GetStringFrom("abcdefghijklmnopqrstuvwxyz123456789", 59) @@ -246,71 +292,7 @@ func Fuzz(data []byte) int { time.Sleep(time.Millisecond * 30) err = r.Get(ctx, polName, &pol) if err != nil { - panic(err) + return 0 } return 1 } - -// Taken from here: https://github.com/fluxcd/image-reflector-controller/blob/main/controllers/registry_test.go#L62 -func newRegistryServer() *httptest.Server { - regHandler := registry.New() - srv := httptest.NewServer(&tagListHandler{ - registryHandler: regHandler, - imagetags: convenientTags, - }) - return srv -} - -// tje tagListHandler is taken from here: -// https://github.com/fluxcd/image-reflector-controller/blob/main/controllers/registry_test.go#L62 - -type tagListHandler struct { - registryHandler http.Handler - imagetags map[string][]string -} - -type tagListResult struct { - Name string `json:"name"` - Tags []string `json:"tags"` -} - -// Take from here: https://github.com/fluxcd/image-reflector-controller/blob/main/controllers/registry_test.go#L126 -// and modified to not include any of the BDD APIs -func (h *tagListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if withoutTagsList := strings.TrimSuffix(r.URL.Path, "/tags/list"); r.Method == "GET" && withoutTagsList != r.URL.Path { - repo := strings.TrimPrefix(withoutTagsList, "/v2/") - if tags, ok := h.imagetags[repo]; ok { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - result := tagListResult{ - Name: repo, - Tags: tags, - } - err := json.NewEncoder(w).Encode(result) - if err != nil { - panic(err) - } - println("Requested tags", repo, strings.Join(tags, ", ")) - return - } - w.WriteHeader(http.StatusNotFound) - return - } - - // record the fact of a PUT to a tag; the path looks like: /v2//manifests/ - h.registryHandler.ServeHTTP(w, r) - if r.Method == "PUT" { - pathElements := strings.Split(r.URL.Path, "/") - if len(pathElements) == 5 && pathElements[1] == "v2" && pathElements[3] == "manifests" { - repo, tag := pathElements[2], pathElements[4] - println("Recording tag", repo, tag) - h.imagetags[repo] = append(h.imagetags[repo], tag) - } - } -} - -var convenientTags = map[string][]string{ - "convenient": []string{ - "tag1", "tag2", - }, -} diff --git a/tests/fuzz/go.mod b/tests/fuzz/go.mod new file mode 100644 index 00000000..e8383933 --- /dev/null +++ b/tests/fuzz/go.mod @@ -0,0 +1,5 @@ +module github.com/fluxcd/image-reflector-controller/tests/fuzz +// This module is used only to avoid polluting the main module +// with fuzz dependencies. + +go 1.17 diff --git a/tests/fuzz/oss_fuzz_build.sh b/tests/fuzz/oss_fuzz_build.sh new file mode 100755 index 00000000..311c18d8 --- /dev/null +++ b/tests/fuzz/oss_fuzz_build.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# Copyright 2022 The Flux authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euxo pipefail + +GOPATH="${GOPATH:-/root/go}" +GO_SRC="${GOPATH}/src" +PROJECT_PATH="github.com/fluxcd/image-reflector-controller" + +cd "${GO_SRC}" + +# Move fuzzer to their respective directories. +# This removes dependency noises from the modules' go.mod and go.sum files. +cp "${PROJECT_PATH}/tests/fuzz/fuzz_controllers.go" "${PROJECT_PATH}/controllers/" + +# Some private functions within suite_test.go are extremly useful for testing. +# Instead of duplicating them here, or refactoring them away, this simply renames +# the file to make it available to "non-testing code". +# This is a temporary fix, which will cease once the implementation is migrated to +# the built-in fuzz support in golang 1.18. +cp "${PROJECT_PATH}/controllers/registry_test.go" "${PROJECT_PATH}/controllers/fuzzer_helper.go" + +# compile fuzz tests for the runtime module +pushd "${PROJECT_PATH}" + +# Setup files to be embedded into controllers_fuzzer.go's testFiles variable. +mkdir -p controllers/testdata/crd +cp config/crd/bases/*.yaml controllers/testdata/crd + +go mod tidy +compile_go_fuzzer "${PROJECT_PATH}/controllers/" FuzzImageRepositoryController fuzz_imagerepositorycontroller + +popd diff --git a/tests/fuzz/oss_fuzz_run.sh b/tests/fuzz/oss_fuzz_run.sh new file mode 100755 index 00000000..4c87f489 --- /dev/null +++ b/tests/fuzz/oss_fuzz_run.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# Copyright 2022 The Flux authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euxo pipefail + +# run each fuzzer once to ensure they are working properly +find /out -type f -name "fuzz*" -exec echo {} -runs=1 \; | bash -e