Skip to content

Commit

Permalink
Use Data field when fetching in remote (#1076)
Browse files Browse the repository at this point in the history
* Add Data to descriptor

* Use Data field when fetching in remote

Small optimization to use the Data field, if present.

* Update mutate package Data field interactions

* ./hack/update-codegen.sh

* Actually verify things

* verify test
  • Loading branch information
jonjohnsonjr committed Jul 28, 2021
1 parent 596751a commit 45aaa6c
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 3 deletions.
24 changes: 24 additions & 0 deletions internal/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package verify

import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"hash"
"io"
Expand Down Expand Up @@ -81,3 +83,25 @@ func ReadCloser(r io.ReadCloser, size int64, h v1.Hash) (io.ReadCloser, error) {
CloseFunc: r.Close,
}, nil
}

// Descriptor verifies that the embedded Data field matches the Size and Digest
// fields of the given v1.Descriptor, returning an error if the Data field is
// missing or if it contains incorrect data.
func Descriptor(d v1.Descriptor) error {
if d.Data == nil {
return errors.New("error verifying descriptor; Data == nil")
}

h, sz, err := v1.SHA256(bytes.NewReader(d.Data))
if err != nil {
return err
}
if h != d.Digest {
return fmt.Errorf("error verifying Digest; got %q, want %q", h, d.Digest)
}
if sz != d.Size {
return fmt.Errorf("error verifying Size; got %d, want %d", sz, d.Size)
}

return nil
}
45 changes: 45 additions & 0 deletions internal/verify/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package verify

import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"strings"
Expand Down Expand Up @@ -86,3 +87,47 @@ func TestBadSize(t *testing.T) {
})
}
}

func TestDescriptor(t *testing.T) {
for _, tc := range []struct {
err error
desc v1.Descriptor
}{{
err: errors.New("error verifying descriptor; Data == nil"),
}, {
err: errors.New(`error verifying Digest; got "sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", want ":"`),
desc: v1.Descriptor{
Data: []byte("abc"),
},
}, {
err: errors.New("error verifying Size; got 3, want 0"),
desc: v1.Descriptor{
Data: []byte("abc"),
Digest: v1.Hash{
Algorithm: "sha256",
Hex: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
},
},
}, {
desc: v1.Descriptor{
Data: []byte("abc"),
Size: 3,
Digest: v1.Hash{
Algorithm: "sha256",
Hex: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
},
},
}} {
got, want := Descriptor(tc.desc), tc.err

if got == nil {
if want != nil {
t.Errorf("Descriptor(): got nil, want %v", want)
}
} else if want == nil {
t.Errorf("Descriptor(): got %v, want nil", got)
} else if got, want := got.Error(), want.Error(); got != want {
t.Errorf("Descriptor(): got %q, want %q", got, want)
}
}
}
1 change: 1 addition & 0 deletions pkg/v1/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Descriptor struct {
MediaType types.MediaType `json:"mediaType"`
Size int64 `json:"size"`
Digest Hash `json:"digest"`
Data []byte `json:"data,omitempty"`
URLs []string `json:"urls,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
Platform *Platform `json:"platform,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions pkg/v1/mutate/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ func (i *image) compute() error {
manifest.Config.Digest = d
manifest.Config.Size = sz

// If Data was set in the base image, we need to update it in the mutated image.
if m.Config.Data != nil {
manifest.Config.Data = rcfg
}

// With OCI media types, this should not be set, see discussion:
// https://github.com/opencontainers/image-spec/pull/795
if i.mediaType != nil {
Expand Down
3 changes: 3 additions & 0 deletions pkg/v1/mutate/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ func computeDescriptor(ia IndexAddendum) (*v1.Descriptor, error) {
if len(ia.Descriptor.Annotations) != 0 {
desc.Annotations = ia.Descriptor.Annotations
}
if ia.Descriptor.Data != nil {
desc.Data = ia.Descriptor.Data
}

return desc, nil
}
Expand Down
13 changes: 13 additions & 0 deletions pkg/v1/remote/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package remote

import (
"bytes"
"io"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -100,6 +101,14 @@ func (r *remoteImage) RawConfigFile() ([]byte, error) {
return nil, err
}

if m.Config.Data != nil {
if err := verify.Descriptor(m.Config); err != nil {
return nil, err
}
r.config = m.Config.Data
return r.config, nil
}

body, err := r.fetchBlob(r.context, m.Config.Size, m.Config.Digest)
if err != nil {
return nil, err
Expand Down Expand Up @@ -143,6 +152,10 @@ func (rl *remoteImageLayer) Compressed() (io.ReadCloser, error) {
return nil, err
}

if d.Data != nil {
return verify.ReadCloser(ioutil.NopCloser(bytes.NewReader(d.Data)), d.Size, d.Digest)
}

// We don't want to log binary layers -- this can break terminals.
ctx := redact.NewContext(rl.ri.context, "omitting binary blobs from logs")

Expand Down
65 changes: 65 additions & 0 deletions pkg/v1/remote/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
Expand Down Expand Up @@ -403,6 +404,8 @@ func TestPullingManifestList(t *testing.T) {
manifest.Manifests[0].Platform = &fakePlatform
// Make sure the second manifest does.
manifest.Manifests[1].Platform = &defaultPlatform
// Do short-circuiting via Data.
manifest.Manifests[1].Data = mustRawManifest(t, child)
rawManifest, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -674,3 +677,65 @@ func TestPullingForeignLayer(t *testing.T) {
t.Errorf("failed to Write: %v", err)
}
}

func TestData(t *testing.T) {
img := randomImage(t)
manifest, err := img.Manifest()
if err != nil {
t.Fatal(err)
}
layers, err := img.Layers()
if err != nil {
t.Fatal(err)
}
cb, err := img.RawConfigFile()
if err != nil {
t.Fatal(err)
}

manifest.Config.Data = cb
rc, err := layers[0].Compressed()
if err != nil {
t.Fatal(err)
}
lb, err := ioutil.ReadAll(rc)
if err != nil {
t.Fatal(err)
}
manifest.Layers[0].Data = lb
rawManifest, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/v2/":
w.WriteHeader(http.StatusOK)
case "/v2/test/manifests/latest":
if r.Method != http.MethodGet {
t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
}
w.Write(rawManifest)
default:
// explode if we try to read blob or config
t.Fatalf("Unexpected path: %v", r.URL.Path)
}
}))
defer server.Close()
u, err := url.Parse(server.URL)
if err != nil {
t.Fatalf("url.Parse(%v) = %v", server.URL, err)
}
ref, err := newReference(u.Host, "test", "latest")
if err != nil {
t.Fatal(err)
}
rmt, err := Image(ref)
if err != nil {
t.Fatal(err)
}
if err := validate.Image(rmt); err != nil {
t.Fatal(err)
}
}
18 changes: 15 additions & 3 deletions pkg/v1/remote/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"sync"

"github.com/google/go-containerregistry/internal/verify"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial"
Expand Down Expand Up @@ -198,9 +199,20 @@ func (r *remoteIndex) childByHash(h v1.Hash) (*Descriptor, error) {
// Convert one of this index's child's v1.Descriptor into a remote.Descriptor, with the given platform option.
func (r *remoteIndex) childDescriptor(child v1.Descriptor, platform v1.Platform) (*Descriptor, error) {
ref := r.Ref.Context().Digest(child.Digest.String())
manifest, _, err := r.fetchManifest(ref, []types.MediaType{child.MediaType})
if err != nil {
return nil, err
var (
manifest []byte
err error
)
if child.Data != nil {
if err := verify.Descriptor(child); err != nil {
return nil, err
}
manifest = child.Data
} else {
manifest, _, err = r.fetchManifest(ref, []types.MediaType{child.MediaType})
if err != nil {
return nil, err
}
}
return &Descriptor{
fetcher: fetcher{
Expand Down
5 changes: 5 additions & 0 deletions pkg/v1/zz_deepcopy_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 45aaa6c

Please sign in to comment.