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 <paulo.gomes@weave.works>
This commit is contained in:
		
							parent
							
								
									7f8441672e
								
							
						
					
					
						commit
						50c043eb4f
					
				|  | @ -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 | ||||||
|  | @ -13,8 +13,9 @@ | ||||||
| 
 | 
 | ||||||
| # Dependency directories (remove the comment below to include it) | # Dependency directories (remove the comment below to include it) | ||||||
| # vendor/ | # vendor/ | ||||||
| testbin/ |  | ||||||
| bin/ | bin/ | ||||||
| config/release/ | config/release/ | ||||||
| config/crd/bases/gitrepositories.yaml | config/crd/bases/gitrepositories.yaml | ||||||
| config/crd/bases/buckets.yaml | config/crd/bases/buckets.yaml | ||||||
|  | 
 | ||||||
|  | build/ | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										21
									
								
								Makefile
								
								
								
								
							|  | @ -22,7 +22,7 @@ ENVTEST_ARCH ?= amd64 | ||||||
| all: manager | all: manager | ||||||
| 
 | 
 | ||||||
| # Download the envtest binaries to testbin
 | # Download the envtest binaries to testbin
 | ||||||
| ENVTEST_ASSETS_DIR=$(shell pwd)/testbin | ENVTEST_ASSETS_DIR=$(shell pwd)/build/testbin | ||||||
| ENVTEST_KUBERNETES_VERSION?=latest | ENVTEST_KUBERNETES_VERSION?=latest | ||||||
| install-envtest: setup-envtest | install-envtest: setup-envtest | ||||||
| 	mkdir -p ${ENVTEST_ASSETS_DIR} | 	mkdir -p ${ENVTEST_ASSETS_DIR} | ||||||
|  | @ -147,3 +147,22 @@ GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\ | ||||||
| rm -rf $$TMP_DIR ;\ | rm -rf $$TMP_DIR ;\ | ||||||
| } | } | ||||||
| endef | 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 | ||||||
|  | 
 | ||||||
|  | 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 \
 | ||||||
|  | 		local-fuzzing:latest \
 | ||||||
|  | 		bash -c "/runner.sh" | ||||||
|  |  | ||||||
|  | @ -69,7 +69,7 @@ var ( | ||||||
| 	debugMode    = os.Getenv("DEBUG_TEST") != "" | 	debugMode    = os.Getenv("DEBUG_TEST") != "" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestMain(m *testing.M) { | func runInContext(registerControllers func(*testenv.Environment), run func() error, crdPath string) error { | ||||||
| 	var err error | 	var err error | ||||||
| 	utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme)) | 	utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme)) | ||||||
| 	utilruntime.Must(kustomizev1.AddToScheme(scheme.Scheme)) | 	utilruntime.Must(kustomizev1.AddToScheme(scheme.Scheme)) | ||||||
|  | @ -78,9 +78,7 @@ func TestMain(m *testing.M) { | ||||||
| 		controllerLog.SetLogger(zap.New(zap.WriteTo(os.Stderr), zap.UseDevMode(false))) | 		controllerLog.SetLogger(zap.New(zap.WriteTo(os.Stderr), zap.UseDevMode(false))) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	testEnv = testenv.New(testenv.WithCRDPath( | 	testEnv = testenv.New(testenv.WithCRDPath(crdPath)) | ||||||
| 		filepath.Join("..", "config", "crd", "bases"), |  | ||||||
| 	)) |  | ||||||
| 
 | 
 | ||||||
| 	testServer, err = testserver.NewTempArtifactServer() | 	testServer, err = testserver.NewTempArtifactServer() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -89,18 +87,7 @@ func TestMain(m *testing.M) { | ||||||
| 	fmt.Println("Starting the test storage server") | 	fmt.Println("Starting the test storage server") | ||||||
| 	testServer.Start() | 	testServer.Start() | ||||||
| 
 | 
 | ||||||
| 	controllerName := "kustomize-controller" | 	registerControllers(testEnv) | ||||||
| 	testEventsH = controller.MakeEvents(testEnv, controllerName, nil) |  | ||||||
| 	testMetricsH = controller.MustMakeMetrics(testEnv) |  | ||||||
| 	reconciler := &KustomizationReconciler{ |  | ||||||
| 		ControllerName:  controllerName, |  | ||||||
| 		Client:          testEnv, |  | ||||||
| 		EventRecorder:   testEventsH.EventRecorder, |  | ||||||
| 		MetricsRecorder: testMetricsH.MetricsRecorder, |  | ||||||
| 	} |  | ||||||
| 	if err := (reconciler).SetupWithManager(testEnv, KustomizationReconcilerOptions{MaxConcurrentReconciles: 4}); err != nil { |  | ||||||
| 		panic(fmt.Sprintf("Failed to start GitRepositoryReconciler: %v", err)) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	go func() { | 	go func() { | ||||||
| 		fmt.Println("Starting the test environment") | 		fmt.Println("Starting the test environment") | ||||||
|  | @ -129,7 +116,7 @@ func TestMain(m *testing.M) { | ||||||
| 		panic(fmt.Sprintf("Failed to create k8s client: %v", err)) | 		panic(fmt.Sprintf("Failed to create k8s client: %v", err)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	code := m.Run() | 	runErr := run() | ||||||
| 
 | 
 | ||||||
| 	if debugMode { | 	if debugMode { | ||||||
| 		events := &corev1.EventList{} | 		events := &corev1.EventList{} | ||||||
|  | @ -152,6 +139,30 @@ func TestMain(m *testing.M) { | ||||||
| 		panic(fmt.Sprintf("Failed to remove storage server dir: %v", err)) | 		panic(fmt.Sprintf("Failed to remove storage server dir: %v", err)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	return runErr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestMain(m *testing.M) { | ||||||
|  | 	code := 0 | ||||||
|  | 
 | ||||||
|  | 	runInContext(func(testEnv *testenv.Environment) { | ||||||
|  | 		controllerName := "kustomize-controller" | ||||||
|  | 		testEventsH = controller.MakeEvents(testEnv, controllerName, nil) | ||||||
|  | 		testMetricsH = controller.MustMakeMetrics(testEnv) | ||||||
|  | 		reconciler := &KustomizationReconciler{ | ||||||
|  | 			ControllerName:  controllerName, | ||||||
|  | 			Client:          testEnv, | ||||||
|  | 			EventRecorder:   testEventsH.EventRecorder, | ||||||
|  | 			MetricsRecorder: testMetricsH.MetricsRecorder, | ||||||
|  | 		} | ||||||
|  | 		if err := (reconciler).SetupWithManager(testEnv, KustomizationReconcilerOptions{MaxConcurrentReconciles: 4}); err != nil { | ||||||
|  | 			panic(fmt.Sprintf("Failed to start KustomizationReconciler: %v", err)) | ||||||
|  | 		} | ||||||
|  | 	}, func() error { | ||||||
|  | 		code = m.Run() | ||||||
|  | 		return nil | ||||||
|  | 	}, filepath.Join("..", "config", "crd", "bases")) | ||||||
|  | 
 | ||||||
| 	os.Exit(code) | 	os.Exit(code) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,29 +0,0 @@ | ||||||
| FROM golang:1.16-buster as builder |  | ||||||
| 
 |  | ||||||
| RUN apt-get update && apt-get install -y git vim clang |  | ||||||
| RUN git clone https://github.com/fluxcd/kustomize-controller /kustomize-controller |  | ||||||
| 
 |  | ||||||
| WORKDIR /kustomize-controller |  | ||||||
| 
 |  | ||||||
| # fillippo.io/age v1.0.0-beta7 throws an error |  | ||||||
| RUN sed 's/filippo.io\/age v1.0.0-beta7/filippo.io\/age v1.0.0/g' -i /kustomize-controller/go.mod |  | ||||||
| RUN make download-crd-deps |  | ||||||
| RUN mkdir /kustomize-controller/fuzz  |  | ||||||
| COPY fuzz.go /kustomize-controller/controllers/ |  | ||||||
| 
 |  | ||||||
| RUN go mod tidy |  | ||||||
| 
 |  | ||||||
| RUN cd / \ |  | ||||||
| 	&&  go get -u github.com/dvyukov/go-fuzz/go-fuzz@latest github.com/dvyukov/go-fuzz/go-fuzz-build@latest |  | ||||||
| RUN go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest |  | ||||||
| RUN go get github.com/AdaLogics/go-fuzz-headers |  | ||||||
| RUN go mod download golang.org/x/sync |  | ||||||
| RUN go mod download github.com/dvyukov/go-fuzz |  | ||||||
| 
 |  | ||||||
| RUN mkdir /fuzzers |  | ||||||
| RUN cd /kustomize-controller/controllers \ |  | ||||||
| 	&& go-fuzz-build -libfuzzer -func=Fuzz \ |  | ||||||
| 	&& clang -o /fuzzers/Fuzz reflect-fuzz.a \ |  | ||||||
| 	-fsanitize=fuzzer |  | ||||||
| 
 |  | ||||||
| RUN cd /kustomize-controller/controllers && /fuzzers/Fuzz |  | ||||||
							
								
								
									
										591
									
								
								fuzz/fuzz.go
								
								
								
								
							
							
						
						
									
										591
									
								
								fuzz/fuzz.go
								
								
								
								
							|  | @ -1,591 +0,0 @@ | ||||||
| // +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 ( |  | ||||||
| 	"archive/tar" |  | ||||||
| 	"bytes" |  | ||||||
| 	"compress/gzip" |  | ||||||
| 	"context" |  | ||||||
| 	"crypto/sha1" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os" |  | ||||||
| 	"os/exec" |  | ||||||
| 	"sync" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1" |  | ||||||
| 	"github.com/fluxcd/pkg/runtime/controller" |  | ||||||
| 	"github.com/fluxcd/pkg/testserver" |  | ||||||
| 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" |  | ||||||
| 	corev1 "k8s.io/api/core/v1" |  | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |  | ||||||
| 	"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" |  | ||||||
| 	"k8s.io/apimachinery/pkg/types" |  | ||||||
| 	"github.com/fluxcd/pkg/apis/meta" |  | ||||||
| 
 |  | ||||||
| 	fuzz "github.com/AdaLogics/go-fuzz-headers" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| var cfg *rest.Config |  | ||||||
| var k8sClient client.Client |  | ||||||
| var k8sManager ctrl.Manager |  | ||||||
| var testEnv *envtest.Environment |  | ||||||
| var kubeConfig []byte |  | ||||||
| var testServer *testserver.ArtifactServer |  | ||||||
| var	testEventsH  controller.Events |  | ||||||
| var	testMetricsH controller.Metrics |  | ||||||
| var	ctx = ctrl.SetupSignalHandler() |  | ||||||
| var initter sync.Once |  | ||||||
| var runningInOssfuzz = false |  | ||||||
| var ( |  | ||||||
| 	localCRDpath = []string{filepath.Join("..", "config", "crd", "bases")} |  | ||||||
| 
 |  | ||||||
| 	// Variables for the OSS-fuzz environment:
 |  | ||||||
| 	downloadLink = "https://storage.googleapis.com/kubebuilder-tools/kubebuilder-tools-1.19.2-linux-amd64.tar.gz" |  | ||||||
| 	downloadPath = "/tmp/envtest-bins.tar.gz" |  | ||||||
| 	binariesDir  = "/tmp/test-binaries" |  | ||||||
| 	ossFuzzCrdPath = []string{filepath.Join(".", "bases")} |  | ||||||
| 	ossFuzzCrdYaml = "https://raw.githubusercontent.com/fluxcd/kustomize-controller/main/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml" |  | ||||||
| 	ossFuzzGitYaml = "https://raw.githubusercontent.com/fluxcd/source-controller/v0.15.4/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml" |  | ||||||
| 	ossFuzzBucYaml = "https://raw.githubusercontent.com/fluxcd/source-controller/v0.15.4/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml" |  | ||||||
| 	crdPath []string |  | ||||||
| 	pgpAscFile = "https://raw.githubusercontent.com/fluxcd/kustomize-controller/main/controllers/testdata/sops/pgp.asc" |  | ||||||
| 	ageTxtFile = "https://raw.githubusercontent.com/fluxcd/kustomize-controller/main/controllers/testdata/sops/age.txt" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // createKUBEBUILDER_ASSETS runs "setup-envtest use" and
 |  | ||||||
| // returns the path of the 3 binaries that are created.
 |  | ||||||
| // If this one fails, it means that setup-envtest is not
 |  | ||||||
| // available in the runtime environment, and that means
 |  | ||||||
| // that the fuzzer is being run by OSS-fuzz. In that case
 |  | ||||||
| // we set "runningInOssfuzz" to true which will later trigger
 |  | ||||||
| // download of all required files so the OSS-fuzz can run
 |  | ||||||
| // the fuzzer.
 |  | ||||||
| func createKUBEBUILDER_ASSETS() string { |  | ||||||
| 	out, err := exec.Command("setup-envtest", "use").Output() |  | ||||||
| 	if err != nil { |  | ||||||
| 		// If there is an error here, the fuzzer is running
 |  | ||||||
| 		// in OSS-fuzz where the binary setup-envtest is
 |  | ||||||
| 		// not available, so we have to get them in an
 |  | ||||||
| 		// alternative way
 |  | ||||||
| 		runningInOssfuzz = true |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// split the output:
 |  | ||||||
| 	splitString := strings.Split(string(out), " ") |  | ||||||
| 	binPath := strings.TrimSuffix(splitString[len(splitString)-1], "\n") |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 	return binPath |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Downloads a file to a path. This is only needed when 
 |  | ||||||
| // the fuzzer is running in OSS-fuzz. 
 |  | ||||||
| func DownloadFile(filepath string, url string) error { |  | ||||||
| 	resp, err := http.Get(url) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer resp.Body.Close() |  | ||||||
| 
 |  | ||||||
| 	// Create the file
 |  | ||||||
| 	out, err := os.Create(filepath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer out.Close() |  | ||||||
| 
 |  | ||||||
| 	// Write the body to file
 |  | ||||||
| 	_, err = io.Copy(out, resp.Body) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // When OSS-fuzz runs the fuzzer a few files need to
 |  | ||||||
| // be download during initialization. No files from the
 |  | ||||||
| // kustomize-controller repo are available at runtime,
 |  | ||||||
| // and each of the files that are needed must be downloaded
 |  | ||||||
| // manually.
 |  | ||||||
| func downloadFilesForOssFuzz() error {	 |  | ||||||
| 	err := DownloadFile(downloadPath, downloadLink) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err = os.MkdirAll(binariesDir, 0777) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	cmd := exec.Command("tar", "xvf", downloadPath, "-C", binariesDir) |  | ||||||
| 	err = cmd.Run() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Download crds
 |  | ||||||
| 	err = os.MkdirAll("bases", 0777) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err = DownloadFile("./bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml", ossFuzzCrdYaml) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err = DownloadFile("./bases/source.toolkit.fluxcd.io_gitrepositories.yaml", ossFuzzGitYaml) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err = DownloadFile("./bases/source.toolkit.fluxcd.io_buckets.yaml", ossFuzzBucYaml) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Download sops files
 |  | ||||||
| 	err = os.MkdirAll("testdata/sops", 0777) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err = DownloadFile("./testdata/sops/pgp.asc", pgpAscFile) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err = DownloadFile("./testdata/sops/age.txt", ageTxtFile) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // customInit implements an init function that
 |  | ||||||
| // is invoked by way of sync.Do
 |  | ||||||
| func customInit() { |  | ||||||
| 	kubebuilder_assets := createKUBEBUILDER_ASSETS() |  | ||||||
| 	os.Setenv("KUBEBUILDER_ASSETS", kubebuilder_assets) |  | ||||||
| 
 |  | ||||||
| 	// Set up things for fuzzing in the OSS-fuzz environment:
 |  | ||||||
| 	if runningInOssfuzz { |  | ||||||
| 		err := downloadFilesForOssFuzz() |  | ||||||
| 		if err != nil { |  | ||||||
| 			panic(err) |  | ||||||
| 		} |  | ||||||
| 		os.Setenv("KUBEBUILDER_ASSETS", binariesDir+"/kubebuilder/bin") |  | ||||||
| 	    crdPath = ossFuzzCrdPath |  | ||||||
| 		runningInOssfuzz = false |  | ||||||
| 	} else { |  | ||||||
| 		crdPath = localCRDpath |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var err error |  | ||||||
| 	err = sourcev1.AddToScheme(scheme.Scheme) |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 	err = kustomizev1.AddToScheme(scheme.Scheme) |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	testEnv = &envtest.Environment{ |  | ||||||
| 		CRDDirectoryPaths: crdPath, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	testServer, err = testserver.NewTempArtifactServer() |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(fmt.Sprintf("Failed to create a temporary storage server: %v", err)) |  | ||||||
| 	} |  | ||||||
| 	fmt.Println("Starting the test storage server") |  | ||||||
| 	testServer.Start() |  | ||||||
| 
 |  | ||||||
| 	cfg, err = testEnv.Start() |  | ||||||
| 
 |  | ||||||
| 	user, err := testEnv.ControlPlane.AddUser(envtest.User{ |  | ||||||
| 		Name:   "envtest-admin", |  | ||||||
| 		Groups: []string{"system:masters"}, |  | ||||||
| 	}, nil) |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(fmt.Sprintf("Failed to create envtest-admin user: %v", err)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	kubeConfig, err = user.KubeConfig() |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(fmt.Sprintf("Failed to create the envtest-admin user kubeconfig: %v", err)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// client with caching disabled
 |  | ||||||
| 	k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) |  | ||||||
| 
 |  | ||||||
| 	k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ |  | ||||||
| 		Scheme: scheme.Scheme, |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	if err := (&KustomizationReconciler{ |  | ||||||
| 		Client:          k8sManager.GetClient(), |  | ||||||
| 	}).SetupWithManager(k8sManager, KustomizationReconcilerOptions{MaxConcurrentReconciles: 1}); err != nil { |  | ||||||
| 		panic(fmt.Sprintf("Failed to start GitRepositoryReconciler: %v", err)) |  | ||||||
| 	} |  | ||||||
| 	time.Sleep(1*time.Second) |  | ||||||
| 
 |  | ||||||
| 	go func() { |  | ||||||
| 		fmt.Println("Starting the test environment") |  | ||||||
| 		if err := k8sManager.Start(ctx); err != nil { |  | ||||||
| 			panic(fmt.Sprintf("Failed to start the test environment manager: %v", err)) |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 	time.Sleep(time.Second*1) |  | ||||||
| 	<-k8sManager.Elected() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Fuzz implements the fuzz harness
 |  | ||||||
| func Fuzz(data []byte) int { |  | ||||||
| 	initter.Do(customInit) |  | ||||||
| 	f := fuzz.NewConsumer(data) |  | ||||||
| 	dname, err := os.MkdirTemp("", "artifact-dir") |  | ||||||
|     if err != nil { |  | ||||||
|     	return 0 |  | ||||||
|     } |  | ||||||
|     defer os.RemoveAll(dname) |  | ||||||
|     err = f.CreateFiles(dname) |  | ||||||
|     if err != nil { |  | ||||||
|     	return 0 |  | ||||||
|     } |  | ||||||
| 	id, err := randString(f) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	namespace, err := createNamespaceForFuzzing(id) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	defer k8sClient.Delete(context.Background(), namespace) |  | ||||||
| 
 |  | ||||||
| 	fmt.Println("createKubeConfigSecretForFuzzing...") |  | ||||||
| 	secret, err := createKubeConfigSecretForFuzzing(id) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Println(err) |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	defer k8sClient.Delete(context.Background(), secret) |  | ||||||
| 	 |  | ||||||
| 	artifactFile, err := randString(f) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	fmt.Println("createArtifact...") |  | ||||||
| 	artifactChecksum, err := createArtifact(testServer, dname, artifactFile) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Println(err) |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	_ = artifactChecksum |  | ||||||
| 	fmt.Println("testServer.URLForFile...") |  | ||||||
| 	artifactURL, err := testServer.URLForFile(artifactFile) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Println("URLForFile error: ", err) |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	_ = artifactURL |  | ||||||
| 	repName, err := randString(f) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	repositoryName := types.NamespacedName{ |  | ||||||
| 		Name:      repName, |  | ||||||
| 		Namespace: id, |  | ||||||
| 	} |  | ||||||
| 	fmt.Println("applyGitRepository...") |  | ||||||
| 	err = applyGitRepository(repositoryName, artifactURL, "main/"+artifactChecksum, artifactChecksum) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Println(err) |  | ||||||
| 	} |  | ||||||
| 	pgpKey, err := ioutil.ReadFile("testdata/sops/pgp.asc") |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	ageKey, err := ioutil.ReadFile("testdata/sops/age.txt") |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	sskName, err := randString(f) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	sopsSecretKey := types.NamespacedName{ |  | ||||||
| 		Name:      sskName, |  | ||||||
| 		Namespace: id, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	sopsSecret := &corev1.Secret{ |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:      sopsSecretKey.Name, |  | ||||||
| 			Namespace: sopsSecretKey.Namespace, |  | ||||||
| 		}, |  | ||||||
| 		StringData: map[string]string{ |  | ||||||
| 			"pgp.asc":    string(pgpKey), |  | ||||||
| 			"age.agekey": string(ageKey), |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	err = k8sClient.Create(context.Background(), sopsSecret) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	defer k8sClient.Delete(context.Background(), sopsSecret) |  | ||||||
| 
 |  | ||||||
| 	kkName, err := randString(f) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	kustomizationKey := types.NamespacedName{ |  | ||||||
| 		Name:      kkName, |  | ||||||
| 		Namespace: id, |  | ||||||
| 	} |  | ||||||
| 	kustomization := &kustomizev1.Kustomization{ |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:      kustomizationKey.Name, |  | ||||||
| 			Namespace: kustomizationKey.Namespace, |  | ||||||
| 		}, |  | ||||||
| 		Spec: kustomizev1.KustomizationSpec{ |  | ||||||
| 			Path: "./", |  | ||||||
| 			KubeConfig: &kustomizev1.KubeConfig{ |  | ||||||
| 				SecretRef: meta.LocalObjectReference{ |  | ||||||
| 					Name: "kubeconfig", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			SourceRef: kustomizev1.CrossNamespaceSourceReference{ |  | ||||||
| 				Name:      repositoryName.Name, |  | ||||||
| 				Namespace: repositoryName.Namespace, |  | ||||||
| 				Kind:      sourcev1.GitRepositoryKind, |  | ||||||
| 			}, |  | ||||||
| 			Decryption: &kustomizev1.Decryption{ |  | ||||||
| 				Provider: "sops", |  | ||||||
| 				SecretRef: &meta.LocalObjectReference{ |  | ||||||
| 					Name: sopsSecretKey.Name, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			TargetNamespace: id, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = k8sClient.Create(context.TODO(), kustomization) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Println(err) |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	defer k8sClient.Delete(context.TODO(), kustomization) |  | ||||||
| 	return 1 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Allows the fuzzer to create a random lowercase string
 |  | ||||||
| func randString(f *fuzz.ConsumeFuzzer) (string, error) { |  | ||||||
| 	stringLength, err := f.GetInt() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	maxLength := 50 |  | ||||||
| 	var buffer bytes.Buffer |  | ||||||
| 	for i:=0;i<stringLength%maxLength;i++ { |  | ||||||
| 		getByte, err := f.GetByte() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return "", nil |  | ||||||
| 		} |  | ||||||
| 		if getByte < 'a' || getByte > 'z' { |  | ||||||
| 			return "", errors.New("Not a good char") |  | ||||||
| 		} |  | ||||||
| 		buffer.WriteByte(getByte) |  | ||||||
| 	} |  | ||||||
| 	return buffer.String(), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Creates a namespace.
 |  | ||||||
| // Caller must delete the created namespace.
 |  | ||||||
| func createNamespaceForFuzzing(name string) (*corev1.Namespace, error) { |  | ||||||
| 	namespace := &corev1.Namespace{ |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{Name: name}, |  | ||||||
| 	} |  | ||||||
| 	err := k8sClient.Create(context.Background(), namespace) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return namespace, err |  | ||||||
| 	} |  | ||||||
| 	return namespace, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Creates a secret.
 |  | ||||||
| // Caller must delete the created secret.
 |  | ||||||
| func createKubeConfigSecretForFuzzing(namespace string) (*corev1.Secret, error) { |  | ||||||
| 	secret := &corev1.Secret{ |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:      "kubeconfig", |  | ||||||
| 			Namespace: namespace, |  | ||||||
| 		}, |  | ||||||
| 		Data: map[string][]byte{ |  | ||||||
| 			"value.yaml": kubeConfig, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	err := k8sClient.Create(context.Background(), secret) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return secret, err |  | ||||||
| 	} |  | ||||||
| 	return secret, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // taken from https://github.com/fluxcd/kustomize-controller/blob/main/controllers/suite_test.go#L222
 |  | ||||||
| func createArtifact(artifactServer *testserver.ArtifactServer, fixture, path string) (string, error) { |  | ||||||
| 	if f, err := os.Stat(fixture); os.IsNotExist(err) || !f.IsDir() { |  | ||||||
| 		return "", fmt.Errorf("invalid fixture path: %s", fixture) |  | ||||||
| 	} |  | ||||||
| 	f, err := os.Create(filepath.Join(artifactServer.Root(), path)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	defer func() { |  | ||||||
| 		if err != nil { |  | ||||||
| 			os.Remove(f.Name()) |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	h := sha1.New() |  | ||||||
| 
 |  | ||||||
| 	mw := io.MultiWriter(h, f) |  | ||||||
| 	gw := gzip.NewWriter(mw) |  | ||||||
| 	tw := tar.NewWriter(gw) |  | ||||||
| 
 |  | ||||||
| 	if err = filepath.Walk(fixture, func(p string, fi os.FileInfo, err error) error { |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Ignore anything that is not a file (directories, symlinks)
 |  | ||||||
| 		if !fi.Mode().IsRegular() { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Ignore dotfiles
 |  | ||||||
| 		if strings.HasPrefix(fi.Name(), ".") { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		header, err := tar.FileInfoHeader(fi, p) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		relFilePath := p |  | ||||||
| 		if filepath.IsAbs(fixture) { |  | ||||||
| 			relFilePath, err = filepath.Rel(fixture, p) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		header.Name = relFilePath |  | ||||||
| 
 |  | ||||||
| 		if err := tw.WriteHeader(header); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		f, err := os.Open(p) |  | ||||||
| 		if err != nil { |  | ||||||
| 			f.Close() |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		if _, err := io.Copy(tw, f); err != nil { |  | ||||||
| 			f.Close() |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		return f.Close() |  | ||||||
| 	}); err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := tw.Close(); err != nil { |  | ||||||
| 		gw.Close() |  | ||||||
| 		f.Close() |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	if err := gw.Close(); err != nil { |  | ||||||
| 		f.Close() |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	if err := f.Close(); err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := os.Chmod(f.Name(), 0644); err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return fmt.Sprintf("%x", h.Sum(nil)), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Taken from https://github.com/fluxcd/kustomize-controller/blob/main/controllers/suite_test.go#L171
 |  | ||||||
| func applyGitRepository(objKey client.ObjectKey, artifactURL, artifactRevision, artifactChecksum string) error { |  | ||||||
| 	repo := &sourcev1.GitRepository{ |  | ||||||
| 		TypeMeta: metav1.TypeMeta{ |  | ||||||
| 			Kind:       sourcev1.GitRepositoryKind, |  | ||||||
| 			APIVersion: sourcev1.GroupVersion.String(), |  | ||||||
| 		}, |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:      objKey.Name, |  | ||||||
| 			Namespace: objKey.Namespace, |  | ||||||
| 		}, |  | ||||||
| 		Spec: sourcev1.GitRepositorySpec{ |  | ||||||
| 			URL:      "https://github.com/test/repository", |  | ||||||
| 			Interval: metav1.Duration{Duration: time.Minute}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	status := sourcev1.GitRepositoryStatus{ |  | ||||||
| 		Conditions: []metav1.Condition{ |  | ||||||
| 			{ |  | ||||||
| 				Type:               meta.ReadyCondition, |  | ||||||
| 				Status:             metav1.ConditionTrue, |  | ||||||
| 				LastTransitionTime: metav1.Now(), |  | ||||||
| 				Reason:             sourcev1.GitOperationSucceedReason, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		Artifact: &sourcev1.Artifact{ |  | ||||||
| 			Path:           artifactURL, |  | ||||||
| 			URL:            artifactURL, |  | ||||||
| 			Revision:       artifactRevision, |  | ||||||
| 			Checksum:       artifactChecksum, |  | ||||||
| 			LastUpdateTime: metav1.Now(), |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opt := []client.PatchOption{ |  | ||||||
| 		client.ForceOwnership, |  | ||||||
| 		client.FieldOwner("kustomize-controller"), |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := k8sClient.Patch(context.Background(), repo, client.Apply, opt...); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	repo.ManagedFields = nil |  | ||||||
| 	repo.Status = status |  | ||||||
| 	if err := k8sClient.Status().Patch(context.Background(), repo, client.Apply, opt...); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | FROM gcr.io/oss-fuzz-base/base-builder-go | ||||||
|  | 
 | ||||||
|  | COPY ./ $GOPATH/src/github.com/fluxcd/kustomize-controller/ | ||||||
|  | COPY ./tests/fuzz/oss_fuzz_build.sh $SRC/build.sh | ||||||
|  | 
 | ||||||
|  | WORKDIR $SRC | ||||||
|  | @ -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 | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | //go:build gofuzz
 | ||||||
|  | // +build gofuzz
 | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  | 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. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package age | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	fuzz "github.com/AdaLogics/go-fuzz-headers" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // FuzzAge implements a fuzzer that targets functions within age/keysource.go.
 | ||||||
|  | func FuzzAge(data []byte) int { | ||||||
|  | 	f := fuzz.NewConsumer(data) | ||||||
|  | 	masterKey := MasterKey{} | ||||||
|  | 
 | ||||||
|  | 	if err := f.GenerateStruct(&masterKey); err != nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	_ = masterKey.Encrypt(data) | ||||||
|  | 	_ = masterKey.EncryptIfNeeded(data) | ||||||
|  | 
 | ||||||
|  | 	receipts, err := f.GetString() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	_, _ = MasterKeysFromRecipients(receipts) | ||||||
|  | 	_, _ = MasterKeyFromRecipient(receipts) | ||||||
|  | 
 | ||||||
|  | 	return 1 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,441 @@ | ||||||
|  | //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" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"embed" | ||||||
|  | 	"io/fs" | ||||||
|  | 
 | ||||||
|  | 	securejoin "github.com/cyphar/filepath-securejoin" | ||||||
|  | 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" | ||||||
|  | 	"github.com/fluxcd/kustomize-controller/controllers" | ||||||
|  | 	"github.com/fluxcd/pkg/apis/meta" | ||||||
|  | 	"github.com/fluxcd/pkg/runtime/testenv" | ||||||
|  | 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" | ||||||
|  | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	"k8s.io/apimachinery/pkg/types" | ||||||
|  | 
 | ||||||
|  | 	fuzz "github.com/AdaLogics/go-fuzz-headers" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var doOnce sync.Once | ||||||
|  | 
 | ||||||
|  | const defaultBinVersion = "1.23" | ||||||
|  | 
 | ||||||
|  | func envtestBinVersion() string { | ||||||
|  | 	if binVersion := os.Getenv("ENVTEST_BIN_VERSION"); binVersion != "" { | ||||||
|  | 		return binVersion | ||||||
|  | 	} | ||||||
|  | 	return defaultBinVersion | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //go:embed testdata/crd/*.yaml
 | ||||||
|  | //go:embed testdata/sops/pgp.asc
 | ||||||
|  | //go:embed testdata/sops/age.txt
 | ||||||
|  | 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
 | ||||||
|  | 	// "testdata/sops" does not need to be saved in disk
 | ||||||
|  | 	// as it is being consumed directly from the embed.FS.
 | ||||||
|  | 	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 nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FuzzControllers implements a fuzzer that targets the Kustomize controller.
 | ||||||
|  | //
 | ||||||
|  | // The test must ensure a valid test state around Kubernetes objects, as the
 | ||||||
|  | // focus is to ensure the controller behaves properly, not Kubernetes nor
 | ||||||
|  | // testing infrastructure.
 | ||||||
|  | func FuzzControllers(data []byte) int { | ||||||
|  | 	// Fuzzing has to be deterministic, so that input A can be
 | ||||||
|  | 	// associated with crash B consistently. The current approach
 | ||||||
|  | 	// taken by go-fuzz-headers to achieve that uses the data input
 | ||||||
|  | 	// as a buffer which is used until its end (i.e. EOF).
 | ||||||
|  | 	//
 | ||||||
|  | 	// The problem with this approach is when the test setup requires
 | ||||||
|  | 	// a higher amount of input than the available in the buffer,
 | ||||||
|  | 	// resulting in an invalid test state.
 | ||||||
|  | 	//
 | ||||||
|  | 	// This is currently being countered by openning two consumers on
 | ||||||
|  | 	// the data input, and requiring at least a length of 1000 to
 | ||||||
|  | 	// run the tests.
 | ||||||
|  | 	//
 | ||||||
|  | 	// During the migration to the native fuzz feature in go we should
 | ||||||
|  | 	// review this approach.
 | ||||||
|  | 	if len(data) < 1000 { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	fmt.Printf("Data input length: %d\n", len(data)) | ||||||
|  | 
 | ||||||
|  | 	f := fuzz.NewConsumer(data) | ||||||
|  | 	ff := fuzz.NewConsumer(data) | ||||||
|  | 
 | ||||||
|  | 	doOnce.Do(func() { | ||||||
|  | 		if err := ensureDependencies(); err != nil { | ||||||
|  | 			panic(fmt.Sprintf("Failed to ensure dependencies: %v", err)) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	err := runInContext(func(testEnv *testenv.Environment) { | ||||||
|  | 		controllerName := "kustomize-controller" | ||||||
|  | 		reconciler := &controllers.KustomizationReconciler{ | ||||||
|  | 			ControllerName: controllerName, | ||||||
|  | 			Client:         testEnv, | ||||||
|  | 		} | ||||||
|  | 		if err := (reconciler).SetupWithManager(testEnv, controllers.KustomizationReconcilerOptions{MaxConcurrentReconciles: 1}); err != nil { | ||||||
|  | 			panic(fmt.Sprintf("Failed to start GitRepositoryReconciler: %v", err)) | ||||||
|  | 		} | ||||||
|  | 	}, func() error { | ||||||
|  | 		dname, err := os.MkdirTemp("", "artifact-dir") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer os.RemoveAll(dname) | ||||||
|  | 
 | ||||||
|  | 		if err = createFiles(ff, dname); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		namespaceName, err := randStringRange(f, 1, 63) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		namespace, err := createNamespaceForFuzzing(namespaceName) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer k8sClient.Delete(context.Background(), namespace) | ||||||
|  | 
 | ||||||
|  | 		fmt.Println("createKubeConfigSecretForFuzzing...") | ||||||
|  | 		secret, err := createKubeConfigSecretForFuzzing(namespaceName) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println(err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer k8sClient.Delete(context.Background(), secret) | ||||||
|  | 
 | ||||||
|  | 		artifactFile, err := randStringRange(f, 1, 63) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		fmt.Println("createArtifact...") | ||||||
|  | 		artifactChecksum, err := createArtifact(testServer, dname, artifactFile) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println(err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		repName, err := randStringRange(f, 1, 63) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		repositoryName := types.NamespacedName{ | ||||||
|  | 			Name:      repName, | ||||||
|  | 			Namespace: namespaceName, | ||||||
|  | 		} | ||||||
|  | 		fmt.Println("ApplyGitRepository...") | ||||||
|  | 		err = applyGitRepository(repositoryName, artifactFile, "main/"+artifactChecksum) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		pgpKey, err := testFiles.ReadFile("testdata/sops/pgp.asc") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		ageKey, err := testFiles.ReadFile("testdata/sops/age.txt") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		sskName, err := randStringRange(f, 1, 63) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		sopsSecretKey := types.NamespacedName{ | ||||||
|  | 			Name:      sskName, | ||||||
|  | 			Namespace: namespaceName, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		sopsSecret := &corev1.Secret{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name:      sopsSecretKey.Name, | ||||||
|  | 				Namespace: sopsSecretKey.Namespace, | ||||||
|  | 			}, | ||||||
|  | 			StringData: map[string]string{ | ||||||
|  | 				"pgp.asc":    string(pgpKey), | ||||||
|  | 				"age.agekey": string(ageKey), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if err = k8sClient.Create(context.Background(), sopsSecret); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer k8sClient.Delete(context.Background(), sopsSecret) | ||||||
|  | 
 | ||||||
|  | 		kkName, err := randStringRange(f, 1, 63) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		kustomizationKey := types.NamespacedName{ | ||||||
|  | 			Name:      kkName, | ||||||
|  | 			Namespace: namespaceName, | ||||||
|  | 		} | ||||||
|  | 		kustomization := &kustomizev1.Kustomization{ | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name:      kustomizationKey.Name, | ||||||
|  | 				Namespace: kustomizationKey.Namespace, | ||||||
|  | 			}, | ||||||
|  | 			Spec: kustomizev1.KustomizationSpec{ | ||||||
|  | 				Path: "./", | ||||||
|  | 				KubeConfig: &kustomizev1.KubeConfig{ | ||||||
|  | 					SecretRef: meta.LocalObjectReference{ | ||||||
|  | 						Name: "kubeconfig", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				SourceRef: kustomizev1.CrossNamespaceSourceReference{ | ||||||
|  | 					Name:      repositoryName.Name, | ||||||
|  | 					Namespace: repositoryName.Namespace, | ||||||
|  | 					Kind:      sourcev1.GitRepositoryKind, | ||||||
|  | 				}, | ||||||
|  | 				Decryption: &kustomizev1.Decryption{ | ||||||
|  | 					Provider: "sops", | ||||||
|  | 					SecretRef: &meta.LocalObjectReference{ | ||||||
|  | 						Name: sopsSecretKey.Name, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				TargetNamespace: namespaceName, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if err = k8sClient.Create(context.TODO(), kustomization); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// ensure reconciliation of the kustomization above took place before moving on
 | ||||||
|  | 		time.Sleep(10 * time.Second) | ||||||
|  | 
 | ||||||
|  | 		if err = k8sClient.Delete(context.TODO(), kustomization); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// ensure the deferred deletion of all objects (namespace, secret, sopSecret) and
 | ||||||
|  | 		// the kustomization above were reconciled before moving on. This avoids unneccessary
 | ||||||
|  | 		// errors whilst tearing down the testing infrastructure.
 | ||||||
|  | 		time.Sleep(10 * time.Second) | ||||||
|  | 
 | ||||||
|  | 		return nil | ||||||
|  | 	}, "testdata/crd") | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println(err) | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Allows the fuzzer to create a random lowercase string within a given range
 | ||||||
|  | func randStringRange(f *fuzz.ConsumeFuzzer, minLen, maxLen int) (string, error) { | ||||||
|  | 	stringLength, err := f.GetInt() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	len := stringLength % maxLen | ||||||
|  | 	if len < minLen { | ||||||
|  | 		len += minLen | ||||||
|  | 	} | ||||||
|  | 	return f.GetStringFrom(string(letterRunes), len) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Creates a namespace and returns the created object.
 | ||||||
|  | func createNamespaceForFuzzing(name string) (*corev1.Namespace, error) { | ||||||
|  | 	namespace := &corev1.Namespace{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{Name: name}, | ||||||
|  | 	} | ||||||
|  | 	err := k8sClient.Create(context.Background(), namespace) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return namespace, err | ||||||
|  | 	} | ||||||
|  | 	return namespace, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Creates a secret and returns the created object.
 | ||||||
|  | func createKubeConfigSecretForFuzzing(namespace string) (*corev1.Secret, error) { | ||||||
|  | 	secret := &corev1.Secret{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name:      "kubeconfig", | ||||||
|  | 			Namespace: namespace, | ||||||
|  | 		}, | ||||||
|  | 		Data: map[string][]byte{ | ||||||
|  | 			"value.yaml": kubeConfig, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	err := k8sClient.Create(context.Background(), secret) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return secret, err | ||||||
|  | 	} | ||||||
|  | 	return secret, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Creates pseudo-random files in rootDir.
 | ||||||
|  | // Will create subdirs and place the files there.
 | ||||||
|  | // It is the callers responsibility to ensure that
 | ||||||
|  | // rootDir exists.
 | ||||||
|  | //
 | ||||||
|  | // Original source:
 | ||||||
|  | // https://github.com/AdaLogics/go-fuzz-headers/blob/9f22f86e471065b8d56861991dc885e27b1ae7de/consumer.go#L345
 | ||||||
|  | //
 | ||||||
|  | // The change assures that as long as the f buffer has
 | ||||||
|  | // enough length to set numberOfFiles and the first fileName,
 | ||||||
|  | // this is returned without errors.
 | ||||||
|  | // Effectively making subDir and FileContent optional.
 | ||||||
|  | func createFiles(f *fuzz.ConsumeFuzzer, rootDir string) error { | ||||||
|  | 	noOfCreatedFiles := 0 | ||||||
|  | 	numberOfFiles, err := f.GetInt() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	maxNoFiles := numberOfFiles % 10000 //
 | ||||||
|  | 
 | ||||||
|  | 	for i := 0; i <= maxNoFiles; i++ { | ||||||
|  | 		fileName, err := f.GetString() | ||||||
|  | 		if err != nil { | ||||||
|  | 			if noOfCreatedFiles > 0 { | ||||||
|  | 				return nil | ||||||
|  | 			} else { | ||||||
|  | 				return errors.New("Could not get fileName") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if fileName == "" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// leave subDir empty if no more strings are available.
 | ||||||
|  | 		subDir, _ := f.GetString() | ||||||
|  | 
 | ||||||
|  | 		// Avoid going outside the root dir
 | ||||||
|  | 		if strings.Contains(subDir, "../") || (len(subDir) > 0 && subDir[0] == 47) || strings.Contains(subDir, "\\") { | ||||||
|  | 			continue // continue as this is not a permanent error
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		dirPath, err := securejoin.SecureJoin(rootDir, subDir) | ||||||
|  | 		if err != nil { | ||||||
|  | 			continue // some errors here are not permanent, so we can try again with different values
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		err = os.MkdirAll(dirPath, 0o755) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if noOfCreatedFiles > 0 { | ||||||
|  | 				return nil | ||||||
|  | 			} else { | ||||||
|  | 				return errors.New("Could not create the subDir") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		fullFilePath, err := securejoin.SecureJoin(dirPath, fileName) | ||||||
|  | 		if err != nil { | ||||||
|  | 			continue // potentially not a permanent error
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// leave fileContents empty if no more bytes are available,
 | ||||||
|  | 		// afterall empty files is a valid test case.
 | ||||||
|  | 		fileContents, _ := f.GetBytes() | ||||||
|  | 
 | ||||||
|  | 		createdFile, err := os.Create(fullFilePath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			createdFile.Close() | ||||||
|  | 			continue // some errors here are not permanent, so we can try again with different values
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		_, err = createdFile.Write(fileContents) | ||||||
|  | 		if err != nil { | ||||||
|  | 			createdFile.Close() | ||||||
|  | 			if noOfCreatedFiles > 0 { | ||||||
|  | 				return nil | ||||||
|  | 			} else { | ||||||
|  | 				return errors.New("Could not write the file") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		createdFile.Close() | ||||||
|  | 		noOfCreatedFiles++ | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | module github.com/fluxcd/kustomize-controller/tests/fuzz | ||||||
|  | // This module is used only to avoid polluting the main module | ||||||
|  | // with fuzz dependencies. | ||||||
|  | 
 | ||||||
|  | go 1.17 | ||||||
|  | @ -0,0 +1,61 @@ | ||||||
|  | #!/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/kustomize-controller" | ||||||
|  | 
 | ||||||
|  | cd "${GO_SRC}" | ||||||
|  | 
 | ||||||
|  | # Move fuzzer to their respective directories.  | ||||||
|  | # This removes dependency noises from the modules' go.mod and go.sum files. | ||||||
|  | mv "${PROJECT_PATH}/tests/fuzz/age_fuzzer.go" "${PROJECT_PATH}/internal/sops/age/" | ||||||
|  | mv "${PROJECT_PATH}/tests/fuzz/pgp_fuzzer.go" "${PROJECT_PATH}/internal/sops/pgp/" | ||||||
|  | 
 | ||||||
|  | # 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/suite_test.go" "${PROJECT_PATH}/tests/fuzz/fuzzer_helper.go" | ||||||
|  | sed -i 's;KustomizationReconciler;abc.KustomizationReconciler;g' "${PROJECT_PATH}/tests/fuzz/fuzzer_helper.go" | ||||||
|  | sed -i 's;import (;import(\n	abc "github.com/fluxcd/kustomize-controller/controllers";g' "${PROJECT_PATH}/tests/fuzz/fuzzer_helper.go" | ||||||
|  | 
 | ||||||
|  | pushd "${PROJECT_PATH}" | ||||||
|  | 
 | ||||||
|  | go mod tidy | ||||||
|  | 
 | ||||||
|  | compile_go_fuzzer "${PROJECT_PATH}/internal/sops/age/" FuzzAge fuzz_age | ||||||
|  | compile_go_fuzzer "${PROJECT_PATH}/internal/sops/pgp/" FuzzPgp fuzz_pgp | ||||||
|  | 
 | ||||||
|  | popd | ||||||
|  | 
 | ||||||
|  | pushd "${PROJECT_PATH}/tests/fuzz" | ||||||
|  | 
 | ||||||
|  | # Setup files to be embedded into controllers_fuzzer.go's testFiles variable. | ||||||
|  | mkdir -p testdata/crd | ||||||
|  | mkdir -p testdata/sops | ||||||
|  | cp ../../config/crd/bases/*.yaml testdata/crd | ||||||
|  | cp ../../controllers/testdata/sops/age.txt testdata/sops | ||||||
|  | cp ../../controllers/testdata/sops/pgp.asc testdata/sops | ||||||
|  | 
 | ||||||
|  | go mod tidy | ||||||
|  | 
 | ||||||
|  | compile_go_fuzzer "${PROJECT_PATH}/tests/fuzz/" FuzzControllers fuzz_controllers | ||||||
|  | 
 | ||||||
|  | popd | ||||||
|  | @ -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 | ||||||
|  | @ -0,0 +1,39 @@ | ||||||
|  | //go:build gofuzz
 | ||||||
|  | // +build gofuzz
 | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  | 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. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package pgp | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	fuzz "github.com/AdaLogics/go-fuzz-headers" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // FuzzPgp implements a fuzzer that targets functions within pgp/keysource.go.
 | ||||||
|  | func FuzzPgp(data []byte) int { | ||||||
|  | 	f := fuzz.NewConsumer(data) | ||||||
|  | 	masterKey := MasterKey{} | ||||||
|  | 
 | ||||||
|  | 	if err := f.GenerateStruct(&masterKey); err != nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	_ = masterKey.Encrypt(data) | ||||||
|  | 	_ = masterKey.EncryptIfNeeded(data) | ||||||
|  | 
 | ||||||
|  | 	return 1 | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue