From 1f09fd6ef05ad6b0e27f1456790ba2917425d4a2 Mon Sep 17 00:00:00 2001 From: Andrei Krasnitski Date: Tue, 9 Feb 2021 17:35:36 +0100 Subject: [PATCH 1/2] feat: add ability to specify a CA certificate Signed-off-by: Andrei Krasnitski --- README.md | 18 ++++++++++++++++++ commands/out.go | 23 ++++++++++++++++++++--- types.go | 34 ++++++++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bae3938..089e660 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,24 @@ differences: * `tls_key`: *Optional. Default `""`* TLS key for the notary server. * `tls_cert`: *Optional. Default `""`* TLS certificate for the notary server. +* `ca_certs`: *Optional.* An array of PEM-encoded CA certificates: + + ```yaml + ca_certs: + - | + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + - | + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + ``` + + Each entry specifies the x509 CA certificate for the trusted docker registry. + This is used to validate the certificate of the docker registry when the + registry's certificate is signed by a custom authority (or itself). + ### Signing with Docker Hub Configure Docker Content Trust for use with the [Docker Hub](https:/hub.docker.io) and Notary service by specifying the above source parameters as follows: diff --git a/commands/out.go b/commands/out.go index e87decc..938d573 100644 --- a/commands/out.go +++ b/commands/out.go @@ -193,8 +193,18 @@ func put(req resource.OutRequest, img v1.Image, tags []name.Tag) error { identifiers = append(identifiers, tag.Identifier()) } + repo, err := name.NewRepository(req.Source.Repository) + if err != nil { + return fmt.Errorf("resolve repository name: %w", err) + } + + opts, err := req.Source.AuthOptions(repo) + if err != nil { + return err + } + logrus.Infof("pushing tag(s) %s", strings.Join(identifiers, ", ")) - err := remote.MultiWrite(images, remote.WithAuth(createAuth(req))) + err = remote.MultiWrite(images, opts...) if err != nil { return fmt.Errorf("pushing tag(s): %w", err) } @@ -248,8 +258,15 @@ func createAuth(req resource.OutRequest) *authn.Basic { func aliasesToBump(req resource.OutRequest, repo name.Repository, ver *semver.Version) ([]name.Tag, error) { variant := req.Source.Variant - opts := []remote.Option{} - opts = append(opts, remote.WithAuth(createAuth(req))) + repo, err := name.NewRepository(req.Source.Repository) + if err != nil { + return nil, fmt.Errorf("resolve repository name: %w", err) + } + + opts, err := req.Source.AuthOptions(repo) + if err != nil { + return nil, err + } versions, err := remote.List(repo, opts...) if err != nil { diff --git a/types.go b/types.go index 4b867b3..7c868a2 100644 --- a/types.go +++ b/types.go @@ -1,6 +1,8 @@ package resource import ( + "crypto/tls" + "crypto/x509" "encoding/base64" "encoding/json" "fmt" @@ -87,6 +89,8 @@ type Source struct { ContentTrust *ContentTrust `json:"content_trust,omitempty"` + DomainCerts []string `json:"ca_certs,omitempty"` + Debug bool `json:"debug,omitempty"` } @@ -138,9 +142,35 @@ func (source Source) AuthOptions(repo name.Repository) ([]remote.Option, error) auth = authn.Anonymous } - opts := []remote.Option{remote.WithAuth(auth)} + tr := http.DefaultTransport.(*http.Transport) + // a cert was provided + if len(source.DomainCerts) > 0 { + rootCAs, err := x509.SystemCertPool() + if err != nil { + return nil, err + } + if rootCAs == nil { + rootCAs = x509.NewCertPool() + } + + for _, cert := range source.DomainCerts { + // append our cert to the system pool + if ok := rootCAs.AppendCertsFromPEM([]byte(cert)); !ok { + return nil, fmt.Errorf("failed to append registry certificate: %w", err) + } + } + + // trust the augmented cert pool in our client + config := &tls.Config{ + RootCAs: rootCAs, + } + + tr.TLSClientConfig = config + } + + opts := []remote.Option{remote.WithAuth(auth), remote.WithTransport(tr)} - rt, err := transport.New(repo.Registry, auth, http.DefaultTransport, []string{repo.Scope(transport.PullScope)}) + rt, err := transport.New(repo.Registry, auth, tr, []string{repo.Scope(transport.PullScope)}) if err != nil { return nil, fmt.Errorf("initialize transport: %w", err) } From 40734d20866202465015bbdc37c337542cc5ae2c Mon Sep 17 00:00:00 2001 From: Andrei Krasnitski Date: Thu, 18 Feb 2021 14:01:51 +0100 Subject: [PATCH 2/2] test: add tests for registry with self-signed certificate Signed-off-by: Andrei Krasnitski --- check_test.go | 110 +++++++++++++++++++++++++++++++++++++++-- in_test.go | 133 +++++++++++++++++++++++++++++++++++++++++++++++--- out_test.go | 94 +++++++++++++++++++++++++++++++++++ 3 files changed, 326 insertions(+), 11 deletions(-) diff --git a/check_test.go b/check_test.go index 94c8a29..b13eeca 100644 --- a/check_test.go +++ b/check_test.go @@ -3,6 +3,7 @@ package resource_test import ( "bytes" "encoding/json" + "encoding/pem" "fmt" "net/http" "os/exec" @@ -20,6 +21,8 @@ import ( ) var _ = Describe("Check", func() { + var actualErr error + var req struct { Source resource.Source Version *resource.Version @@ -47,11 +50,11 @@ var _ = Describe("Check", func() { cmd.Stdout = outBuf cmd.Stderr = GinkgoWriter - err = cmd.Run() - Expect(err).ToNot(HaveOccurred()) - - err = json.Unmarshal(outBuf.Bytes(), &res) - Expect(err).ToNot(HaveOccurred()) + actualErr = cmd.Run() + if actualErr == nil { + err = json.Unmarshal(outBuf.Bytes(), &res) + Expect(err).ToNot(HaveOccurred()) + } } Describe("tracking a single tag", func() { @@ -68,6 +71,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_STATIC_DIGEST}, })) @@ -89,6 +94,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: PRIVATE_LATEST_STATIC_DIGEST}, })) @@ -138,12 +145,69 @@ var _ = Describe("Check", func() { }) It("falls back on fetching the manifest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, })) }) }) + Context("using a registry with self-signed certificate", func() { + var registry *ghttp.Server + + BeforeEach(func() { + registry = ghttp.NewTLSServer() + + registry.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/"), + ghttp.RespondWith(http.StatusOK, `welcome to zombocom`), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/"), + ghttp.RespondWith(http.StatusOK, `welcome to zombocom`), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("HEAD", "/v2/some/fake-image/manifests/latest"), + ghttp.RespondWith(http.StatusOK, ``, LATEST_FAKE_HEADERS), + ), + ) + + req.Source.Repository = registry.Addr() + "/some/fake-image" + }) + + AfterEach(func() { + registry.Close() + }) + + When("the certificate is provided in 'source'", func() { + BeforeEach(func() { + certPem := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: registry.HTTPTestServer.Certificate().Raw, + }) + Expect(certPem).ToNot(BeEmpty()) + + req.Source.DomainCerts = []string{string(certPem)} + }) + + It("it checks and returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + + Expect(res).To(Equal([]resource.Version{ + {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, + })) + }) + }) + + When("the certificate is missing in 'source'", func() { + It("exits non-zero and returns an error", func() { + Expect(actualErr).To(HaveOccurred()) + }) + }) + }) + Context("against a mirror", func() { var mirror *ghttp.Server @@ -187,6 +251,8 @@ var _ = Describe("Check", func() { }) It("it checks and returns the current digest using the registry declared in the repository and not using the mirror", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, })) @@ -216,6 +282,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, })) @@ -244,6 +312,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "1.32.0", Digest: latestDigest(req.Source.Name())}, })) @@ -266,6 +336,8 @@ var _ = Describe("Check", func() { }) It("returns the given digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_STATIC_DIGEST}, })) @@ -277,6 +349,8 @@ var _ = Describe("Check", func() { }) It("includes the tag in the response version", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_STATIC_DIGEST}, })) @@ -304,6 +378,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: PRIVATE_LATEST_STATIC_DIGEST}, })) @@ -351,6 +427,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ *req.Version, })) @@ -384,6 +462,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ *req.Version, })) @@ -407,6 +487,8 @@ var _ = Describe("Check", func() { }) It("returns the previous digest and the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: OLDER_STATIC_DIGEST}, {Tag: "latest", Digest: LATEST_STATIC_DIGEST}, @@ -435,6 +517,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: PRIVATE_OLDER_STATIC_DIGEST}, {Tag: "latest", Digest: PRIVATE_LATEST_STATIC_DIGEST}, @@ -488,6 +572,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: OLDER_FAKE_DIGEST}, {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, @@ -520,6 +606,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "1.32.0", Digest: OLDER_LIBRARY_DIGEST}, {Tag: "1.32.0", Digest: latestDigest(req.Source.Name())}, @@ -543,6 +631,8 @@ var _ = Describe("Check", func() { }) It("returns only the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_STATIC_DIGEST}, })) @@ -564,6 +654,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: PRIVATE_LATEST_STATIC_DIGEST}, })) @@ -614,6 +706,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, })) @@ -642,6 +736,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "1.32.0", Digest: latestDigest(req.Source.Name())}, })) @@ -660,6 +756,8 @@ var _ = Describe("Check", func() { }) It("returns empty digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{})) }) @@ -679,6 +777,8 @@ var _ = Describe("Check", func() { }) It("returns empty digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{})) }) }) diff --git a/in_test.go b/in_test.go index bbdf94f..43ea2be 100644 --- a/in_test.go +++ b/in_test.go @@ -3,6 +3,7 @@ package resource_test import ( "bytes" "encoding/json" + "encoding/pem" "io/ioutil" "net/http" "os" @@ -23,7 +24,10 @@ import ( ) var _ = Describe("In", func() { - var destDir string + var ( + actualErr error + destDir string + ) var req struct { Source resource.Source `json:"source"` @@ -70,11 +74,11 @@ var _ = Describe("In", func() { cmd.Stdout = outBuf cmd.Stderr = GinkgoWriter - err = cmd.Run() - Expect(err).ToNot(HaveOccurred()) - - err = json.Unmarshal(outBuf.Bytes(), &res) - Expect(err).ToNot(HaveOccurred()) + actualErr = cmd.Run() + if actualErr == nil { + err = json.Unmarshal(outBuf.Bytes(), &res) + Expect(err).ToNot(HaveOccurred()) + } }) Describe("image metadata", func() { @@ -87,6 +91,8 @@ var _ = Describe("In", func() { }) It("captures the env and user", func() { + Expect(actualErr).ToNot(HaveOccurred()) + var meta struct { User string `json:"user"` Env []string `json:"env"` @@ -116,6 +122,8 @@ var _ = Describe("In", func() { }) It("returns metadata", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res.Version).To(Equal(req.Version)) Expect(res.Metadata).To(Equal([]resource.MetadataField{ { @@ -148,11 +156,15 @@ var _ = Describe("In", func() { }) It("keeps file permissions and file modified times", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(stat.Mode()).To(Equal(os.FileMode(0603))) Expect(stat.ModTime()).To(BeTemporally("==", time.Date(1991, 06, 03, 05, 30, 30, 0, time.UTC))) }) It("keeps file ownership", func() { + Expect(actualErr).ToNot(HaveOccurred()) + if os.Geteuid() != 0 { Skip("Must be run as root to validate file ownership") } @@ -174,6 +186,8 @@ var _ = Describe("In", func() { }) It("does not restore files that were removed in later layers", func() { + Expect(actualErr).ToNot(HaveOccurred()) + infos, err := ioutil.ReadDir(rootfsPath("top-dir-1")) Expect(err).ToNot(HaveOccurred()) Expect(infos).To(HaveLen(2)) @@ -220,6 +234,8 @@ var _ = Describe("In", func() { }) It("works", func() { + Expect(actualErr).ToNot(HaveOccurred()) + lstat, err := os.Lstat(rootfsPath("usr", "libexec", "git-core", "git")) Expect(err).ToNot(HaveOccurred()) Expect(lstat.Mode() & os.ModeSymlink).To(BeZero()) @@ -240,6 +256,8 @@ var _ = Describe("In", func() { }) It("removes the symlink and writes to a new file rather than trying to open and write to it (thereby overwriting its target)", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(cat(rootfsPath("a"))).To(Equal("symlinked\n")) Expect(cat(rootfsPath("b"))).To(Equal("replaced\n")) }) @@ -266,6 +284,8 @@ var _ = Describe("In", func() { }) It("works", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(cat(rootfsPath("Dockerfile"))).To(ContainSubstring("hello!")) }) }) @@ -282,6 +302,8 @@ var _ = Describe("In", func() { }) It("saves the tagged image as image.tar instead of saving the rootfs", func() { + Expect(actualErr).ToNot(HaveOccurred()) + _, err := os.Stat(filepath.Join(destDir, "rootfs")) Expect(os.IsNotExist(err)).To(BeTrue()) @@ -313,6 +335,8 @@ var _ = Describe("In", func() { }) It("saves the digest to a file", func() { + Expect(actualErr).ToNot(HaveOccurred()) + digest, err := ioutil.ReadFile(filepath.Join(destDir, "digest")) Expect(err).ToNot(HaveOccurred()) Expect(string(digest)).To(Equal(req.Version.Digest)) @@ -329,6 +353,8 @@ var _ = Describe("In", func() { }) It("saves the tag to a file", func() { + Expect(actualErr).ToNot(HaveOccurred()) + tag, err := ioutil.ReadFile(filepath.Join(destDir, "tag")) Expect(err).ToNot(HaveOccurred()) Expect(string(tag)).To(Equal("tagged")) @@ -343,6 +369,8 @@ var _ = Describe("In", func() { }) It("saves the repository string to a file", func() { + Expect(actualErr).ToNot(HaveOccurred()) + repository, err := ioutil.ReadFile(filepath.Join(destDir, "repository")) Expect(err).ToNot(HaveOccurred()) Expect(string(repository)).To(Equal("concourse/test-image-static")) @@ -358,6 +386,8 @@ var _ = Describe("In", func() { }) It("does not download the image", func() { + Expect(actualErr).ToNot(HaveOccurred()) + _, err := os.Stat(filepath.Join(destDir, "rootfs")) Expect(os.IsNotExist(err)).To(BeTrue()) @@ -369,6 +399,8 @@ var _ = Describe("In", func() { }) It("saves the tag and digest files", func() { + Expect(actualErr).ToNot(HaveOccurred()) + digest, err := ioutil.ReadFile(filepath.Join(destDir, "digest")) Expect(err).ToNot(HaveOccurred()) Expect(string(digest)).To(Equal(LATEST_STATIC_DIGEST)) @@ -480,10 +512,87 @@ var _ = Describe("In", func() { }) It("retries", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res.Version).To(Equal(req.Version)) }) }) + Describe("using a registry with self-signed certificate", func() { + var registry *ghttp.Server + + BeforeEach(func() { + registry = ghttp.NewTLSServer() + + fakeImage := empty.Image + + digest, err := fakeImage.Digest() + Expect(err).ToNot(HaveOccurred()) + + manifest, err := fakeImage.RawManifest() + Expect(err).ToNot(HaveOccurred()) + + config, err := fakeImage.RawConfigFile() + Expect(err).ToNot(HaveOccurred()) + + configDigest, err := fakeImage.ConfigName() + Expect(err).ToNot(HaveOccurred()) + + registry.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/"), + ghttp.RespondWith(http.StatusOK, `welcome to zombocom`), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/"), + ghttp.RespondWith(http.StatusOK, `welcome to zombocom`), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/some/fake-image/manifests/"+digest.String()), + ghttp.RespondWith(http.StatusOK, manifest), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/some/fake-image/blobs/"+configDigest.String()), + ghttp.RespondWith(http.StatusOK, config), + ), + ) + + req.Source = resource.Source{ + Repository: registry.Addr() + "/some/fake-image", + } + + req.Version.Digest = digest.String() + }) + + AfterEach(func() { + registry.Close() + }) + + When("the certificate is provided in 'source'", func() { + BeforeEach(func() { + certPem := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: registry.HTTPTestServer.Certificate().Raw, + }) + Expect(certPem).ToNot(BeEmpty()) + + req.Source.DomainCerts = []string{string(certPem)} + }) + + It("pulls the image from the registry", func() { + Expect(actualErr).ToNot(HaveOccurred()) + + Expect(res.Version).To(Equal(req.Version)) + }) + }) + + When("the certificate is missing in 'source'", func() { + It("exits non-zero and returns an error", func() { + Expect(actualErr).To(HaveOccurred()) + }) + }) + }) + Describe("using a mirror", func() { var mirror *ghttp.Server @@ -545,6 +654,8 @@ var _ = Describe("In", func() { }) It("pulls the image from the registry declared in the repository and not from the mirror", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res.Version).To(Equal(req.Version)) Expect(mirror.ReceivedRequests()).To(BeEmpty()) @@ -566,6 +677,8 @@ var _ = Describe("In", func() { }) It("saves the rootfs and metadata", func() { + Expect(actualErr).ToNot(HaveOccurred()) + _, err := os.Stat(rootfsPath("Dockerfile")) Expect(err).ToNot(HaveOccurred()) @@ -623,6 +736,8 @@ var _ = Describe("In", func() { }) It("pulls the image from the library", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res.Version).To(Equal(req.Version)) }) @@ -632,6 +747,8 @@ var _ = Describe("In", func() { }) It("names the image with the original repository and tag, not the mirror", func() { + Expect(actualErr).ToNot(HaveOccurred()) + tag, err := name.NewTag("fake-image:latest") Expect(err).ToNot(HaveOccurred()) @@ -673,6 +790,8 @@ var _ = Describe("In", func() { }) It("saves the rootfs and metadata", func() { + Expect(actualErr).ToNot(HaveOccurred()) + _, err := os.Stat(rootfsPath("Dockerfile")) Expect(err).ToNot(HaveOccurred()) @@ -708,6 +827,8 @@ var _ = Describe("In", func() { }) It("pulls the image from the library", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res.Version).To(Equal(req.Version)) }) }) diff --git a/out_test.go b/out_test.go index 7f97c3b..ad44a0a 100644 --- a/out_test.go +++ b/out_test.go @@ -3,6 +3,7 @@ package resource_test import ( "bytes" "encoding/json" + "encoding/pem" "fmt" "io" "io/ioutil" @@ -366,6 +367,99 @@ var _ = Describe("Out", func() { Expect(actualErr).ToNot(HaveOccurred()) }) }) + + Context("using a registry with self-signed certificate", func() { + var registry *ghttp.Server + var randomImage v1.Image + + BeforeEach(func() { + registry = ghttp.NewTLSServer() + + req.Source = resource.Source{ + Repository: registry.Addr() + "/fake-image", + Tag: "some-tag", + } + + tag, err := name.NewTag(req.Source.Name()) + Expect(err).ToNot(HaveOccurred()) + + randomImage, err = random.Image(1024, 1) + Expect(err).ToNot(HaveOccurred()) + + err = tarball.WriteToFile(filepath.Join(srcDir, "image.tar"), tag, randomImage) + Expect(err).ToNot(HaveOccurred()) + + req.Params.Image = "image.tar" + + layers, err := randomImage.Layers() + Expect(err).ToNot(HaveOccurred()) + + configDigest, err := randomImage.ConfigName() + Expect(err).ToNot(HaveOccurred()) + + registry.RouteToHandler("GET", "/v2/", func(w http.ResponseWriter, r *http.Request) { + ghttp.RespondWith(http.StatusOK, "welcome to zombocom")(w, r) + }) + + registry.RouteToHandler("HEAD", "/v2/fake-image/blobs/"+configDigest.String(), func(w http.ResponseWriter, r *http.Request) { + ghttp.RespondWith(http.StatusOK, "blob totally exists")(w, r) + }) + + registry.RouteToHandler("POST", "/v2/fake-image/blobs/uploads/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Location", "/upload/some-blob") + w.WriteHeader(http.StatusAccepted) + }) + + registry.RouteToHandler("PATCH", "/upload/some-blob", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Location", "/commit/some-blob") + w.WriteHeader(http.StatusAccepted) + }) + + registry.RouteToHandler("PUT", "/commit/some-blob", func(w http.ResponseWriter, r *http.Request) { + ghttp.RespondWith(http.StatusCreated, "upload complete")(w, r) + + }) + + for _, l := range layers { + layerDigest, err := l.Digest() + Expect(err).ToNot(HaveOccurred()) + + registry.RouteToHandler("HEAD", "/v2/fake-image/blobs/"+layerDigest.String(), func(w http.ResponseWriter, r *http.Request) { + ghttp.RespondWith(http.StatusNotFound, "needs upload")(w, r) + }) + } + + registry.RouteToHandler("PUT", "/v2/fake-image/manifests/some-tag", func(w http.ResponseWriter, r *http.Request) { + ghttp.RespondWith(http.StatusOK, "manifest updated")(w, r) + }) + }) + + AfterEach(func() { + registry.Close() + }) + + When("the certificate is provided in 'source'", func() { + BeforeEach(func() { + certPem := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: registry.HTTPTestServer.Certificate().Raw, + }) + Expect(certPem).ToNot(BeEmpty()) + + req.Source.DomainCerts = []string{string(certPem)} + }) + + It("should not error", func() { + Expect(actualErr).ToNot(HaveOccurred()) + }) + }) + + When("the certificate is missing in 'source'", func() { + It("exits non-zero and returns an error", func() { + Expect(actualErr).To(HaveOccurred()) + }) + }) + }) }) func parallelTag(tag string) string {