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)
|
||||
# vendor/
|
||||
testbin/
|
||||
bin/
|
||||
config/release/
|
||||
config/crd/bases/gitrepositories.yaml
|
||||
config/crd/bases/buckets.yaml
|
||||
|
||||
build/
|
||||
|
|
21
Makefile
21
Makefile
|
@ -22,7 +22,7 @@ ENVTEST_ARCH ?= amd64
|
|||
all: manager
|
||||
|
||||
# Download the envtest binaries to testbin
|
||||
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}
|
||||
|
@ -147,3 +147,22 @@ 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
|
||||
|
||||
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") != ""
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
func runInContext(registerControllers func(*testenv.Environment), run func() error, crdPath string) error {
|
||||
var err error
|
||||
utilruntime.Must(sourcev1.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)))
|
||||
}
|
||||
|
||||
testEnv = testenv.New(testenv.WithCRDPath(
|
||||
filepath.Join("..", "config", "crd", "bases"),
|
||||
))
|
||||
testEnv = testenv.New(testenv.WithCRDPath(crdPath))
|
||||
|
||||
testServer, err = testserver.NewTempArtifactServer()
|
||||
if err != nil {
|
||||
|
@ -89,18 +87,7 @@ func TestMain(m *testing.M) {
|
|||
fmt.Println("Starting the test storage server")
|
||||
testServer.Start()
|
||||
|
||||
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 GitRepositoryReconciler: %v", err))
|
||||
}
|
||||
registerControllers(testEnv)
|
||||
|
||||
go func() {
|
||||
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))
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
runErr := run()
|
||||
|
||||
if debugMode {
|
||||
events := &corev1.EventList{}
|
||||
|
@ -152,6 +139,30 @@ func TestMain(m *testing.M) {
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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