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
c4d512399c
commit
5572b8a553
|
@ -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
|
|
@ -5,7 +5,6 @@
|
|||
*.so
|
||||
*.dylib
|
||||
bin
|
||||
testbin
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
@ -22,3 +21,5 @@ testbin
|
|||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
build/
|
||||
|
|
22
Makefile
22
Makefile
|
@ -113,7 +113,7 @@ GEN_CRD_API_REFERENCE_DOCS = $(shell pwd)/bin/gen-crd-api-reference-docs
|
|||
gen-crd-api-reference-docs:
|
||||
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/gen-crd-api-reference-docs@v0.3.0)
|
||||
|
||||
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}
|
||||
|
@ -137,3 +137,23 @@ GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
|
|||
rm -rf $$TMP_DIR ;\
|
||||
}
|
||||
endef
|
||||
|
||||
# Build fuzzers
|
||||
fuzz-build:
|
||||
rm -rf $(shell pwd)/build/fuzz/
|
||||
mkdir -p $(shell pwd)/build/fuzz/out/
|
||||
|
||||
docker build . --tag local-fuzzing:latest -f tests/fuzz/Dockerfile.builder
|
||||
docker run --rm \
|
||||
-e FUZZING_LANGUAGE=go -e SANITIZER=address \
|
||||
-e CIFUZZ_DEBUG='True' -e OSS_FUZZ_PROJECT_NAME=fluxcd \
|
||||
-v "$(shell pwd)/build/fuzz/out":/out \
|
||||
local-fuzzing:latest
|
||||
|
||||
# Run each fuzzer once to ensure they are working
|
||||
fuzz-smoketest: fuzz-build
|
||||
docker run --rm \
|
||||
-v "$(shell pwd)/build/fuzz/out":/out \
|
||||
-v "$(shell pwd)/tests/fuzz/oss_fuzz_run.sh":/runner.sh \
|
||||
local-fuzzing:latest \
|
||||
bash -c "/runner.sh"
|
||||
|
|
|
@ -820,8 +820,10 @@ func (in HelmReleaseStatus) GetHelmChart() (string, string) {
|
|||
if in.HelmChart == "" {
|
||||
return "", ""
|
||||
}
|
||||
split := strings.Split(in.HelmChart, string(types.Separator))
|
||||
return split[0], split[1]
|
||||
if split := strings.Split(in.HelmChart, string(types.Separator)); len(split) > 1 {
|
||||
return split[0], split[1]
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// HelmReleaseProgressing resets any failures and registers progress toward
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
FROM golang:1.16-buster as builder
|
||||
|
||||
RUN apt-get update && apt-get install -y clang git
|
||||
RUN git clone --branch fuzz1 --depth 1 https://github.com/AdamKorcz/helm-controller /helm-controller
|
||||
COPY fuzz.go /helm-controller/controllers/
|
||||
RUN cd /helm-controller/controllers \
|
||||
&& go get github.com/AdaLogics/go-fuzz-headers
|
||||
|
||||
RUN cd / \
|
||||
&& go get -u github.com/mdempsky/go114-fuzz-build
|
||||
|
||||
RUN mkdir /fuzzers
|
||||
RUN cd /helm-controller/controllers \
|
||||
&& go114-fuzz-build -o FuzzHelmreleaseComposeValues.a -func FuzzHelmreleaseComposeValues . \
|
||||
&& clang -o /fuzzers/FuzzHelmreleaseComposeValues FuzzHelmreleaseComposeValues.a -fsanitize=fuzzer
|
||||
RUN cd /helm-controller/controllers \
|
||||
&& go114-fuzz-build -o FuzzHelmreleasereconcile.a -func FuzzHelmreleasereconcile . \
|
||||
&& clang -o /fuzzers/FuzzHelmreleasereconcile FuzzHelmreleasereconcile.a -fsanitize=fuzzer
|
||||
|
||||
# Uncomment here to run the fuzzer
|
||||
#RUN /fuzzers/FuzzHelmreleaseComposeValues
|
147
fuzz/fuzz.go
147
fuzz/fuzz.go
|
@ -1,147 +0,0 @@
|
|||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
/*
|
||||
Copyright 2020 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"
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sync"
|
||||
|
||||
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
||||
)
|
||||
|
||||
var (
|
||||
initter sync.Once
|
||||
scheme *runtime.Scheme
|
||||
)
|
||||
|
||||
// An init function that is invoked by way of sync.Do
|
||||
func initFunc() {
|
||||
scheme = runtime.NewScheme()
|
||||
err := corev1.AddToScheme(scheme)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = v2.AddToScheme(scheme)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// FuzzHelmreleaseComposeValues implements a fuzzer
|
||||
// that targets HelmReleaseReconciler.composeValues()
|
||||
func FuzzHelmreleaseComposeValues(data []byte) int {
|
||||
initter.Do(initFunc)
|
||||
|
||||
f := fuzz.NewConsumer(data)
|
||||
|
||||
hr := v2.HelmRelease{}
|
||||
err := f.GenerateStruct(&hr)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
r, err := createReconciler(f)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
_, _ = r.composeValues(logr.NewContext(context.TODO(), log.NullLogger{}), hr)
|
||||
return 1
|
||||
}
|
||||
|
||||
// FuzzHelmreleasereconcile implements a fuzzer
|
||||
// that targets HelmReleaseReconciler.reconcile()
|
||||
func FuzzHelmreleasereconcile(data []byte) int {
|
||||
initter.Do(initFunc)
|
||||
|
||||
f := fuzz.NewConsumer(data)
|
||||
|
||||
hr := v2.HelmRelease{}
|
||||
err := f.GenerateStruct(&hr)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
r, err := createReconciler(f)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
_, _, _ = r.reconcile(logr.NewContext(context.TODO(), log.NullLogger{}), hr)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Allows the fuzzer to create a reconciler
|
||||
func createReconciler(f *fuzz.ConsumeFuzzer) (*HelmReleaseReconciler, error) {
|
||||
// Get the type of object:
|
||||
var resources []runtime.Object
|
||||
r := &HelmReleaseReconciler{}
|
||||
getSecret, err := f.GetBool()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
name, err := f.GetString()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if getSecret {
|
||||
inputMap := make(map[string][]byte)
|
||||
err = f.FuzzMap(&inputMap)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
resources = []runtime.Object{
|
||||
valuesSecret(name, inputMap),
|
||||
}
|
||||
} else {
|
||||
inputMap := make(map[string]string)
|
||||
err = f.FuzzMap(&inputMap)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
resources = []runtime.Object{
|
||||
valuesConfigMap(name, inputMap),
|
||||
}
|
||||
}
|
||||
c := fake.NewFakeClientWithScheme(scheme, resources...)
|
||||
r.Client = c
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Taken from
|
||||
// https://github.com/fluxcd/helm-controller/blob/main/controllers/helmrelease_controller_test.go#L282
|
||||
func valuesSecret(name string, data map[string][]byte) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from
|
||||
// https://github.com/fluxcd/helm-controller/blob/main/controllers/helmrelease_controller_test.go#L289
|
||||
func valuesConfigMap(name string, data map[string]string) *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||
Data: data,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
FROM gcr.io/oss-fuzz-base/base-builder-go
|
||||
|
||||
COPY ./ $GOPATH/src/github.com/fluxcd/helm-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,139 @@
|
|||
//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 controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
||||
)
|
||||
|
||||
var (
|
||||
doOnce sync.Once
|
||||
scheme = runtime.NewScheme()
|
||||
)
|
||||
|
||||
// An init function that is invoked by way of sync.Do
|
||||
func initFunc() {
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
_ = v2.AddToScheme(scheme)
|
||||
_ = sourcev1.AddToScheme(scheme)
|
||||
}
|
||||
|
||||
// FuzzHelmreleaseComposeValues implements a fuzzer
|
||||
// that targets HelmReleaseReconciler.composeValues().
|
||||
func FuzzHelmreleaseComposeValues(data []byte) int {
|
||||
doOnce.Do(initFunc)
|
||||
|
||||
f := fuzz.NewConsumer(data)
|
||||
|
||||
resources, err := getResources(f)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
c := fake.NewFakeClientWithScheme(scheme, resources...)
|
||||
r := &HelmReleaseReconciler{Client: c}
|
||||
|
||||
hr := v2.HelmRelease{}
|
||||
err = f.GenerateStruct(&hr)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
_, _ = r.composeValues(logr.NewContext(context.TODO(), logr.Discard()), hr)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// FuzzHelmreleaseComposeValues implements a fuzzer
|
||||
// that targets HelmReleaseReconciler.reconcile().
|
||||
func FuzzHelmreleaseReconcile(data []byte) int {
|
||||
doOnce.Do(initFunc)
|
||||
|
||||
f := fuzz.NewConsumer(data)
|
||||
|
||||
resources, err := getResources(f)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
hr := v2.HelmRelease{}
|
||||
err = f.GenerateStruct(&hr)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
hc := sourcev1.HelmChart{}
|
||||
err = f.GenerateStruct(&hc)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
hc.ObjectMeta.Name = hr.GetHelmChartName()
|
||||
hc.ObjectMeta.Namespace = hr.Spec.Chart.GetNamespace(hr.Namespace)
|
||||
resources = append(resources, &hc)
|
||||
|
||||
c := fake.NewFakeClientWithScheme(scheme, resources...)
|
||||
r := &HelmReleaseReconciler{Client: c}
|
||||
|
||||
_, _, _ = r.reconcile(logr.NewContext(context.TODO(), logr.Discard()), hr)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
func getResources(f *fuzz.ConsumeFuzzer) ([]runtime.Object, error) {
|
||||
resources := make([]runtime.Object, 0)
|
||||
|
||||
name, err := f.GetString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if createSecret, _ := f.GetBool(); createSecret {
|
||||
inputByte := make(map[string][]byte)
|
||||
f.FuzzMap(&inputByte) // ignore error, as empty is still valid
|
||||
resources = append(resources,
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||
Data: inputByte,
|
||||
})
|
||||
}
|
||||
|
||||
if createConfigMap, _ := f.GetBool(); createConfigMap {
|
||||
inputString := make(map[string]string)
|
||||
f.FuzzMap(&inputString) // ignore error, as empty is still valid
|
||||
resources = append(resources,
|
||||
&corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||
Data: inputString,
|
||||
})
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
module github.com/fluxcd/helm-controller/tests/fuzz
|
||||
// This module is used only to avoid polluting the main module
|
||||
// with fuzz dependencies.
|
||||
|
||||
go 1.17
|
|
@ -0,0 +1,37 @@
|
|||
#!/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/helm-controller"
|
||||
|
||||
cd "${GO_SRC}"
|
||||
|
||||
# Move fuzzer to their respective directories.
|
||||
# This removes dependency noises from the modules' go.mod and go.sum files.
|
||||
cp "${PROJECT_PATH}/tests/fuzz/fuzz_controllers.go" "${PROJECT_PATH}/controllers/"
|
||||
|
||||
|
||||
# compile fuzz tests for the runtime module
|
||||
pushd "${PROJECT_PATH}"
|
||||
|
||||
go mod tidy
|
||||
compile_go_fuzzer "${PROJECT_PATH}/controllers/" FuzzHelmreleaseComposeValues fuzz_helmrelease_composevalues
|
||||
compile_go_fuzzer "${PROJECT_PATH}/controllers/" FuzzHelmreleaseReconcile fuzz_helmrelease_reconcile
|
||||
|
||||
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
|
Loading…
Reference in New Issue