Skip to content

Commit

Permalink
Refactor registry test code
Browse files Browse the repository at this point in the history
oss-fuzz in coverage mode was broken due to duplicate symbols.
Refactoring the registry test code and referencing it directly fixes it.

Signed-off-by: Paulo Gomes <paulo.gomes@weave.works>
  • Loading branch information
Paulo Gomes committed Feb 15, 2022
1 parent 5ce2c41 commit b4aff4f
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 224 deletions.
29 changes: 15 additions & 14 deletions controllers/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
aclapi "github.com/fluxcd/pkg/apis/acl"

imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
"github.com/fluxcd/image-reflector-controller/internal/test"
// +kubebuilder:scaffold:imports
)

Expand All @@ -42,7 +43,7 @@ var _ = Describe("ImagePolicy controller", func() {
var registryServer *httptest.Server

BeforeEach(func() {
registryServer = newRegistryServer()
registryServer = test.NewRegistryServer()
})

AfterEach(func() {
Expand All @@ -61,7 +62,7 @@ var _ = Describe("ImagePolicy controller", func() {
It("fails to reconcile an ImagePolicy with a cross-ns ref", func() {
// a bona fide image repo is needed so that it _would_ succeed if not for the disallowed cross-ns ref.
versions := []string{"1.0.1", "1.0.2", "1.1.0-alpha"}
imgRepo := loadImages(registryServer, "test-semver-policy-"+randStringRunes(5), versions)
imgRepo := test.LoadImages(registryServer, "test-semver-policy-"+randStringRunes(5), versions)

repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Expand Down Expand Up @@ -119,7 +120,7 @@ var _ = Describe("ImagePolicy controller", func() {
When("Using SemVerPolicy", func() {
It("calculates an image from a repository's tags", func() {
versions := []string{"0.1.0", "0.1.1", "0.2.0", "1.0.0", "1.0.1", "1.0.2", "1.1.0-alpha"}
imgRepo := loadImages(registryServer, "test-semver-policy-"+randStringRunes(5), versions)
imgRepo := test.LoadImages(registryServer, "test-semver-policy-"+randStringRunes(5), versions)

repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Expand Down Expand Up @@ -184,7 +185,7 @@ var _ = Describe("ImagePolicy controller", func() {
When("Using SemVerPolicy with invalid range", func() {
It("fails with invalid policy error", func() {
versions := []string{"0.1.0", "0.1.1", "0.2.0", "1.0.0", "1.0.1", "1.0.2", "1.1.0-alpha"}
imgRepo := loadImages(registryServer, "test-semver-policy-"+randStringRunes(5), versions)
imgRepo := test.LoadImages(registryServer, "test-semver-policy-"+randStringRunes(5), versions)

repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Expand Down Expand Up @@ -250,7 +251,7 @@ var _ = Describe("ImagePolicy controller", func() {
When("Usign AlphabeticalPolicy", func() {
It("calculates an image from a repository's tags", func() {
versions := []string{"xenial", "yakkety", "zesty", "artful", "bionic"}
imgRepo := loadImages(registryServer, "test-alphabetical-policy-"+randStringRunes(5), versions)
imgRepo := test.LoadImages(registryServer, "test-alphabetical-policy-"+randStringRunes(5), versions)

repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Expand Down Expand Up @@ -312,7 +313,7 @@ var _ = Describe("ImagePolicy controller", func() {
When("valid regex supplied", func() {
It("correctly filters the repo tags", func() {
versions := []string{"test-0.1.0", "test-0.1.1", "dev-0.2.0", "1.0.0", "1.0.1", "1.0.2", "1.1.0-alpha"}
imgRepo := loadImages(registryServer, "test-semver-policy-"+randStringRunes(5), versions)
imgRepo := test.LoadImages(registryServer, "test-semver-policy-"+randStringRunes(5), versions)
repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Expand Down Expand Up @@ -380,7 +381,7 @@ var _ = Describe("ImagePolicy controller", func() {
When("invalid regex supplied", func() {
It("fails to reconcile returning error", func() {
versions := []string{"test-0.1.0", "test-0.1.1", "dev-0.2.0", "1.0.0", "1.0.1", "1.0.2", "1.1.0-alpha"}
imgRepo := loadImages(registryServer, "test-semver-policy-"+randStringRunes(5), versions)
imgRepo := test.LoadImages(registryServer, "test-semver-policy-"+randStringRunes(5), versions)
repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Expand Down Expand Up @@ -455,7 +456,7 @@ var _ = Describe("ImagePolicy controller", func() {
It("grants access", func() {
versions := []string{"1.0.0", "1.0.1"}
imageName := "test-acl-" + randStringRunes(5)
imgRepo := loadImages(registryServer, imageName, versions)
imgRepo := test.LoadImages(registryServer, imageName, versions)

repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Expand Down Expand Up @@ -514,7 +515,7 @@ var _ = Describe("ImagePolicy controller", func() {
Expect(pol.Status.LatestImage).To(Equal(imgRepo + ":1.0.1"))

// Updating the image should reconcile the cross-namespace policy
imgRepo = loadImages(registryServer, imageName, []string{"1.0.2"})
imgRepo = test.LoadImages(registryServer, imageName, []string{"1.0.2"})
Eventually(func() bool {
err := r.Get(ctx, imageObjectName, &repo)
return err == nil && repo.Status.LastScanResult.TagCount == len(versions)+1
Expand Down Expand Up @@ -542,7 +543,7 @@ var _ = Describe("ImagePolicy controller", func() {
defer k8sClient.Delete(context.Background(), policyNamespace)

versions := []string{"1.0.0", "1.0.1"}
imgRepo := loadImages(registryServer, "acl-image-"+randStringRunes(5), versions)
imgRepo := test.LoadImages(registryServer, "acl-image-"+randStringRunes(5), versions)

repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Expand Down Expand Up @@ -615,7 +616,7 @@ var _ = Describe("ImagePolicy controller", func() {
defer k8sClient.Delete(context.Background(), policyNamespace)

versions := []string{"1.0.0", "1.0.1"}
imgRepo := loadImages(registryServer, "acl-image-"+randStringRunes(5), versions)
imgRepo := test.LoadImages(registryServer, "acl-image-"+randStringRunes(5), versions)

repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Expand Down Expand Up @@ -698,7 +699,7 @@ var _ = Describe("ImagePolicy controller", func() {

versions := []string{"1.0.0", "1.0.1"}
imageName := "acl-image-" + randStringRunes(5)
imgRepo := loadImages(registryServer, imageName, versions)
imgRepo := test.LoadImages(registryServer, imageName, versions)

repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Expand Down Expand Up @@ -771,7 +772,7 @@ var _ = Describe("ImagePolicy controller", func() {
Expect(pol.Status.LatestImage).To(Equal(imgRepo + ":1.0.1"))

// Updating the image should reconcile the cross-namespace policy
imgRepo = loadImages(registryServer, imageName, []string{"1.0.2"})
imgRepo = test.LoadImages(registryServer, imageName, []string{"1.0.2"})
Eventually(func() bool {
err := r.Get(ctx, repoObjectName, &repo)
return err == nil && repo.Status.LastScanResult.TagCount == len(versions)+1
Expand Down Expand Up @@ -799,7 +800,7 @@ var _ = Describe("ImagePolicy controller", func() {
defer k8sClient.Delete(context.Background(), policyNamespace)

versions := []string{"1.0.0", "1.0.1"}
imgRepo := loadImages(registryServer, "acl-image-"+randStringRunes(5), versions)
imgRepo := test.LoadImages(registryServer, "acl-image-"+randStringRunes(5), versions)

repo := imagev1.ImageRepository{
Spec: imagev1.ImageRepositorySpec{
Expand Down
170 changes: 7 additions & 163 deletions controllers/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,25 @@ limitations under the License.
package controllers

import (
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/registry"
"github.com/google/go-containerregistry/pkg/v1/random"
"github.com/google/go-containerregistry/pkg/v1/remote"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"github.com/fluxcd/image-reflector-controller/internal/test"
)

var _ = Context("Registry handler", func() {

It("serves a tag list", func() {
srv := newRegistryServer()
srv := test.NewRegistryServer()
defer srv.Close()

uploadedTags := []string{"tag1", "tag2"}
repoString := loadImages(srv, "testimage", uploadedTags)
repoString := test.LoadImages(srv, "testimage", uploadedTags)
repo, _ := name.NewRepository(repoString)

tags, err := remote.List(repo)
Expand All @@ -48,158 +44,6 @@ var _ = Context("Registry handler", func() {
})
})

// ---

// pre-populated db of tags, so it's not necessary to upload images to
// get results from remote.List.
var convenientTags = map[string][]string{
"convenient": []string{
"tag1", "tag2",
},
}

// set up a local registry for testing scanning
func newRegistryServer() *httptest.Server {
regHandler := registry.New()
srv := httptest.NewServer(&tagListHandler{
registryHandler: regHandler,
imagetags: convenientTags,
})
return srv
}

func newAuthenticatedRegistryServer(username, pass string) *httptest.Server {
regHandler := registry.New()
regHandler = &tagListHandler{
registryHandler: regHandler,
imagetags: convenientTags,
}
regHandler = &authHandler{
registryHandler: regHandler,
allowedUser: username,
allowedPass: pass,
}
srv := httptest.NewServer(regHandler)
return srv
}

// Get the registry part of an image from the registry server
func registryName(srv *httptest.Server) string {
if strings.HasPrefix(srv.URL, "https://") {
return strings.TrimPrefix(srv.URL, "https://")
} // else assume HTTP
return strings.TrimPrefix(srv.URL, "http://")
}

// loadImages uploads images to the local registry, and returns the
// image repo
// name. https://github.com/google/go-containerregistry/blob/v0.1.1/pkg/registry/compatibility_test.go
// has an example of loading a test registry with a random image.
func loadImages(srv *httptest.Server, imageName string, versions []string, options ...remote.Option) string {
imgRepo := registryName(srv) + "/" + imageName
for _, tag := range versions {
imgRef, err := name.NewTag(imgRepo + ":" + tag)
Expect(err).ToNot(HaveOccurred())
img, err := random.Image(512, 1)
Expect(err).ToNot(HaveOccurred())
Expect(remote.Write(imgRef, img, options...)).To(Succeed())
}
return imgRepo
}

// the go-containerregistry test registry implementation does not
// serve /myimage/tags/list. Until it does, I'm adding this handler.
// NB:
// - assumes repo name is a single element
// - assumes no overwriting tags

type tagListHandler struct {
registryHandler http.Handler
imagetags map[string][]string
}

type tagListResult struct {
Name string `json:"name"`
Tags []string `json:"tags"`
}

func (h *tagListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// a tag list request has a path like: /v2/<repo>/tags/list
if withoutTagsList := strings.TrimSuffix(r.URL.Path, "/tags/list"); r.Method == "GET" && withoutTagsList != r.URL.Path {
repo := strings.TrimPrefix(withoutTagsList, "/v2/")
if tags, ok := h.imagetags[repo]; ok {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
result := tagListResult{
Name: repo,
Tags: tags,
}
Expect(json.NewEncoder(w).Encode(result)).To(Succeed())
println("Requested tags", repo, strings.Join(tags, ", "))
return
}
w.WriteHeader(http.StatusNotFound)
return
}

// record the fact of a PUT to a tag; the path looks like: /v2/<repo>/manifests/<tag>
h.registryHandler.ServeHTTP(w, r)
if r.Method == "PUT" {
pathElements := strings.Split(r.URL.Path, "/")
if len(pathElements) == 5 && pathElements[1] == "v2" && pathElements[3] == "manifests" {
repo, tag := pathElements[2], pathElements[4]
println("Recording tag", repo, tag)
h.imagetags[repo] = append(h.imagetags[repo], tag)
}
}
}

// there's no authentication in go-containerregistry/pkg/registry;
// this wrapper adds basic auth to a registry handler. NB: the
// important thing is to be able to test that the credentials get from
// the secret to the registry API library; it's assumed that the
// registry API library does e.g., OAuth2 correctly. See
// https://tools.ietf.org/html/rfc7617 regarding basic authentication.

type authHandler struct {
allowedUser, allowedPass string
registryHandler http.Handler
}

// ServeHTTP serves a request which needs authentication.
func (h *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
w.Header().Add("WWW-Authenticate", `Basic realm="Registry"`)
w.WriteHeader(401)
return
}
if !strings.HasPrefix(authHeader, "Basic ") {
w.WriteHeader(403)
w.Write([]byte(`Authorization header does not being with "Basic "`))
return
}
namePass, err := base64.StdEncoding.DecodeString(authHeader[6:])
if err != nil {
w.WriteHeader(403)
w.Write([]byte(`Authorization header doesn't appear to be base64-encoded`))
return
}
namePassSlice := strings.SplitN(string(namePass), ":", 2)
if len(namePassSlice) != 2 {
w.WriteHeader(403)
w.Write([]byte(`Authorization header doesn't appear to be colon-separated value `))
w.Write(namePass)
return
}
if namePassSlice[0] != h.allowedUser || namePassSlice[1] != h.allowedPass {
w.WriteHeader(403)
w.Write([]byte(`Authorization failed: wrong username or password`))
return
}
h.registryHandler.ServeHTTP(w, r)
}

var _ = Context("Authentication handler", func() {

var registryServer *httptest.Server
Expand All @@ -208,22 +52,22 @@ var _ = Context("Authentication handler", func() {
BeforeEach(func() {
username = "user"
password = "password1"
registryServer = newAuthenticatedRegistryServer(username, password)
registryServer = test.NewAuthenticatedRegistryServer(username, password)
})

AfterEach(func() {
registryServer.Close()
})

It("rejects requests without authentication", func() {
repo, err := name.NewRepository(registryName(registryServer) + "/convenient")
repo, err := name.NewRepository(test.RegistryName(registryServer) + "/convenient")
Expect(err).ToNot(HaveOccurred())
_, err = remote.List(repo)
Expect(err).To(HaveOccurred())
})

It("accepts requests with correct authentication", func() {
repo, err := name.NewRepository(registryName(registryServer) + "/convenient")
repo, err := name.NewRepository(test.RegistryName(registryServer) + "/convenient")
Expect(err).ToNot(HaveOccurred())
auth := &authn.Basic{
Username: username,
Expand Down
Loading

0 comments on commit b4aff4f

Please sign in to comment.