Skip to content

Commit

Permalink
experimental adaptation of CA bundle rotation test as fuzz target
Browse files Browse the repository at this point in the history
  • Loading branch information
vrutkovs committed Apr 5, 2024
1 parent 8b24f46 commit a84ce43
Showing 1 changed file with 201 additions and 0 deletions.
201 changes: 201 additions & 0 deletions pkg/operator/certrotation/cabundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,29 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"errors"
"fmt"
"io/ioutil"
"math/big"
mathrand "math/rand"
"strings"
"sync"
"testing"
"time"

"k8s.io/client-go/util/cert"

"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"

"github.com/openshift/library-go/pkg/crypto"
"github.com/openshift/library-go/pkg/operator/events"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
kubefake "k8s.io/client-go/kubernetes/fake"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
clienttesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
Expand Down Expand Up @@ -300,3 +307,197 @@ func signCertificate(template *x509.Certificate, requestKey gcrypto.PublicKey, i
}
return certs[0], nil
}

type cmgetter struct {
w *cmwrapped
}

func (g *cmgetter) ConfigMaps(string) corev1client.ConfigMapInterface {
return g.w
}

type cmwrapped struct {
corev1client.ConfigMapInterface
d *dispatcher
name string
t *testing.T
// the hooks are not invoked for every operation
hook func(controllerName, op string)
}

func (w cmwrapped) Create(ctx context.Context, cm *corev1.ConfigMap, opts metav1.CreateOptions) (*corev1.ConfigMap, error) {
w.t.Logf("[%s] op=Create, cm=%s/%s", w.name, cm.Namespace, cm.Name)
return w.ConfigMapInterface.Create(ctx, cm, opts)
}
func (w cmwrapped) Update(ctx context.Context, cm *corev1.ConfigMap, opts metav1.UpdateOptions) (*corev1.ConfigMap, error) {
w.t.Logf("[%s] op=Update, cm=%s/%s", w.name, cm.Namespace, cm.Name)
return w.ConfigMapInterface.Update(ctx, cm, opts)
}
func (w cmwrapped) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
w.t.Logf("[%s] op=Delete, cm=%s", w.name, name)
defer func() {
if w.hook != nil {
w.hook(w.name, operation(w.t, opts))
}
}()
return w.ConfigMapInterface.Delete(ctx, name, opts)
}
func (w cmwrapped) Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.ConfigMap, error) {
if w.hook != nil {
w.hook(w.name, operation(w.t, opts))
}
obj, err := w.ConfigMapInterface.Get(ctx, name, opts)
w.t.Logf("[%s] op=Get, cm=%s, err: %v", w.name, name, err)
return obj, err
}

type fakeCMLister struct {
who string
dispatcher *dispatcher
tracker clienttesting.ObjectTracker
}

func (l *fakeCMLister) List(selector labels.Selector) (ret []*corev1.ConfigMap, err error) {
return l.ConfigMaps("").List(selector)
}

func (l *fakeCMLister) ConfigMaps(namespace string) corev1listers.ConfigMapNamespaceLister {
return &fakeCMNamespaceLister{
who: l.who,
dispatcher: l.dispatcher,
tracker: l.tracker,
ns: namespace,
}
}

type fakeCMNamespaceLister struct {
who string
dispatcher *dispatcher
tracker clienttesting.ObjectTracker
ns string
}

func (l *fakeCMNamespaceLister) List(selector labels.Selector) (ret []*corev1.ConfigMap, err error) {
obj, err := l.tracker.List(
schema.GroupVersionResource{Version: "v1", Resource: "configmaps"},
schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
l.ns,
)
var cms []*corev1.ConfigMap
if l, ok := obj.(*corev1.ConfigMapList); ok {
for i := range l.Items {
cms = append(cms, &l.Items[i])
}
}
return cms, err
}

func (l *fakeCMNamespaceLister) Get(name string) (*corev1.ConfigMap, error) {
l.dispatcher.Sequence(l.who, "before-lister-get")
obj, err := l.tracker.Get(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, l.ns, name)
l.dispatcher.Sequence(l.who, "after-lister-get")
if cm, ok := obj.(*corev1.ConfigMap); ok {
return cm, err
}
return nil, err
}

func FuzzEnsureConfigMapCABundle(f *testing.F) {
const (
WorkerCount = 3
ConfigMapNamespace, ConfigMapName = "ns", "test-target"
)
existing := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: ConfigMapNamespace,
Name: ConfigMapName,
ResourceVersion: "10",
},
Data: map[string]string{"ca-bundle.crt": ""},
}
newCA, err := newTestCACertificate(pkix.Name{CommonName: "signer-tests"}, int64(1), metav1.Duration{Duration: time.Hour * 24 * 60}, time.Now)
if err != nil {
f.Fatal(err)
}
// give it a second so we have a unique signer name,
// and also unique not-after, and not-before values
<-time.After(2 * time.Second)

f.Fuzz(func(t *testing.T, seed int64) {
d := &dispatcher{
t: t,
source: mathrand.NewSource(seed),
requests: make(chan request, WorkerCount),
}
go d.Run()
defer d.Stop()

existing = existing.DeepCopy()

// get the original crt bytes to compare later
caCertWant, ok := existing.Data["ca-bundle.crt"]
if !ok || len(caCertWant) == 0 {
t.Fatalf("missing data in 'ca-bundle.crt' key of Data: %#v", existing.Data)
}

secretWant := existing.DeepCopy()

clientset := kubefake.NewSimpleClientset(existing)

options := events.RecommendedClusterSingletonCorrelatorOptions()
client := clientset.CoreV1().ConfigMaps(ConfigMapNamespace)

var wg sync.WaitGroup
for i := 1; i <= WorkerCount; i++ {
controllerName := fmt.Sprintf("controller-%d", i)
wg.Add(1)
d.Join(controllerName)

go func(controllerName string) {
defer func() {
d.Leave(controllerName)
wg.Done()
}()

recorder := events.NewKubeRecorderWithOptions(clientset.CoreV1().Events(ConfigMapNamespace), options, "operator", &corev1.ObjectReference{Name: controllerName, Namespace: ConfigMapNamespace})
wrapped := &cmwrapped{ConfigMapInterface: client, name: controllerName, t: t, d: d}
getter := &cmgetter{w: wrapped}
ctrl := &CABundleConfigMap{
Namespace: ConfigMapNamespace,
Name: ConfigMapName,
Client: getter,
Lister: &fakeCMLister{
who: controllerName,
dispatcher: d,
tracker: clientset.Tracker(),
},
AdditionalAnnotations: AdditionalAnnotations{JiraComponent: "test"},
Owner: &metav1.OwnerReference{Name: "operator"},
EventRecorder: recorder,
}

d.Sequence(controllerName, "begin")
_, err := ctrl.EnsureConfigMapCABundle(context.TODO(), newCA)
if err != nil {
t.Logf("error from %s: %v", controllerName, err)
}
}(controllerName)
}

wg.Wait()
t.Log("controllers done")
// controllers are done, we don't expect the signer to change
caCertGot, err := client.Get(context.TODO(), ConfigMapName, metav1.GetOptions{})
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if caCertGot, ok := caCertGot.Data["ca-bundle.crt"]; !ok || caCertWant != caCertGot {
t.Errorf("the ca has mutated unexpectedly")
}
if got, exists := caCertGot.Annotations["openshift.io/owning-component"]; !exists || got != "test" {
t.Errorf("owner annotation is missing: %#v", caCertGot.Annotations)
}
t.Logf("diff: %s", cmp.Diff(secretWant, caCertGot))
})
}

0 comments on commit a84ce43

Please sign in to comment.