Skip to content

Commit

Permalink
Start to sketch internal/oci/remote.SignedImage[Index].
Browse files Browse the repository at this point in the history
This follows the pattern GGCR uses for functional `remote.Options`, and starts to define `SignedImage[Index]` methods, which build upon the `Signatures` implementation from a prior change.

This also tries to start adopting this pattern a few places, which drops bits of boilerplate (or changes them to functional options).

As part of this, I'm combining `FetchSignaturesForImage[Digest]` and tweaking it's signature a bit (no pun intended!), but I think long term we'll just want folks to consume the `remote` package directly themselves.

Signed-off-by: Matt Moore <[email protected]>
  • Loading branch information
mattmoor committed Sep 17, 2021
1 parent a1c1e42 commit 54e7fe1
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 43 deletions.
7 changes: 2 additions & 5 deletions cmd/cosign/cli/download/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/peterbourgon/ff/v3/ffcli"

"github.com/sigstore/cosign/cmd/cosign/cli"
ociremote "github.com/sigstore/cosign/internal/oci/remote"
"github.com/sigstore/cosign/pkg/cosign"
)

Expand Down Expand Up @@ -53,12 +54,8 @@ func SignatureCmd(ctx context.Context, regOpts cli.RegistryOpts, imageRef string
if err != nil {
return err
}
sigRepo, err := cli.TargetRepositoryForImage(ref)
if err != nil {
return err
}
regClientOpts := regOpts.GetRegistryClientOpts(ctx)
signatures, err := cosign.FetchSignaturesForImage(ctx, ref, sigRepo, cosign.SignatureTagSuffix, regClientOpts...)
signatures, err := cosign.FetchSignaturesForImage(ctx, ref, ociremote.WithRemoteOptions(regClientOpts...))
if err != nil {
return err
}
Expand Down
5 changes: 3 additions & 2 deletions cmd/cosign/cli/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ import (
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/kms"

oremote "github.com/sigstore/cosign/internal/oci/remote"
)

const (
ExperimentalEnv = "COSIGN_EXPERIMENTAL"
repoEnv = "COSIGN_REPOSITORY"
)

func EnableExperimental() bool {
Expand All @@ -47,7 +48,7 @@ func EnableExperimental() bool {
}

func TargetRepositoryForImage(img name.Reference) (name.Repository, error) {
wantRepo := os.Getenv(repoEnv)
wantRepo := os.Getenv(oremote.RepoOverrideKey)
if wantRepo == "" {
return img.Context(), nil
}
Expand Down
5 changes: 3 additions & 2 deletions cmd/cosign/cli/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"testing"

"github.com/google/go-containerregistry/pkg/name"
ociremote "github.com/sigstore/cosign/internal/oci/remote"
)

func TestTargetRepositoryForImage(t *testing.T) {
Expand Down Expand Up @@ -68,8 +69,8 @@ func TestTargetRepositoryForImage(t *testing.T) {

for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
os.Setenv(repoEnv, test.envRepo)
defer os.Unsetenv(repoEnv)
os.Setenv(ociremote.RepoOverrideKey, test.envRepo)
defer os.Unsetenv(ociremote.RepoOverrideKey)

got, err := TargetRepositoryForImage(test.image)
if err != nil {
Expand Down
7 changes: 2 additions & 5 deletions copasetic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (

"github.com/sigstore/cosign/cmd/cosign/cli"
"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
ociremote "github.com/sigstore/cosign/internal/oci/remote"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/options"
Expand Down Expand Up @@ -136,16 +137,12 @@ func main() {
if err != nil {
return nil, err
}
sigRepo, err := cli.TargetRepositoryForImage(ref)
if err != nil {
return nil, err
}
registryOpts := []remote.Option{
remote.WithAuthFromKeychain(authn.DefaultKeychain),
remote.WithContext(bctx.Context),
}

sps, err := cosign.FetchSignaturesForImage(bctx.Context, ref, sigRepo, cosign.SignatureTagSuffix, registryOpts...)
sps, err := cosign.FetchSignaturesForImage(bctx.Context, ref, ociremote.WithRemoteOptions(registryOpts...))
if err != nil {
return nil, err
}
Expand Down
118 changes: 118 additions & 0 deletions internal/oci/remote/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//
// Copyright 2021 The Sigstore 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 remote

import (
"os"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
)

const (
SignatureTagSuffix = ".sig"
SBOMTagSuffix = ".sbom"
AttestationTagSuffix = ".att"

RepoOverrideKey = "COSIGN_REPOSITORY"
)

// Option is a functional option for remote operations.
type Option func(*options) error

type options struct {
signatureSuffix string
attestationSuffix string
sbomSuffix string
targetRepository name.Repository
ropt []remote.Option
}

func makeOptions(target name.Repository, opts ...Option) (*options, error) {
o := &options{
signatureSuffix: SignatureTagSuffix,
attestationSuffix: AttestationTagSuffix,
sbomSuffix: SBOMTagSuffix,
targetRepository: target,
ropt: []remote.Option{
remote.WithAuthFromKeychain(authn.DefaultKeychain),
// TODO(mattmoor): Incorporate user agent.
},
}

// Before applying options, allow the environment to override things.
if ro := os.Getenv(RepoOverrideKey); ro != "" {
repo, err := name.NewRepository(ro)
if err != nil {
return nil, err
}
o.targetRepository = repo
}

for _, option := range opts {
if err := option(o); err != nil {
return nil, err
}
}

return o, nil
}

// WithSignatureSuffix is a functional option for overriding the default
// signature tag suffix.
func WithSignatureSuffix(suffix string) Option {
return func(o *options) error {
o.signatureSuffix = suffix
return nil
}
}

// WithAttestationSuffix is a functional option for overriding the default
// attestation tag suffix.
func WithAttestationSuffix(suffix string) Option {
return func(o *options) error {
o.attestationSuffix = suffix
return nil
}
}

// WithSBOMSuffix is a functional option for overriding the default
// SBOM tag suffix.
func WithSBOMSuffix(suffix string) Option {
return func(o *options) error {
o.sbomSuffix = suffix
return nil
}
}

// WithRemoteOptions is a functional option for overriding the default
// remote options passed to GGCR.
func WithRemoteOptions(opts ...remote.Option) Option {
return func(o *options) error {
o.ropt = opts
return nil
}
}

// WithTargetRepository is a functional option for overriding the default
// target repository hosting the signature and attestation tags.
func WithTargetRepository(repo name.Repository) Option {
return func(o *options) error {
o.targetRepository = repo
return nil
}
}
145 changes: 143 additions & 2 deletions internal/oci/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/pkg/errors"
"github.com/sigstore/cosign/internal/oci"
"github.com/sigstore/sigstore/pkg/cryptoutils"
Expand All @@ -37,8 +38,148 @@ const (
BundleKey = "dev.sigstore.cosign/bundle"
)

// This enables mocking for unit testing without faking an entire registry.
var remoteImage = remote.Image
// These enable mocking for unit testing without faking an entire registry.
var (
remoteImage = remote.Image
remoteIndex = remote.Index
)

// SignedEntity provides access to a remote reference, and its signatures.
// The SignedEntity will be one of SignedImage or SignedImageIndex.
func SignedEntity(ref name.Reference, options ...Option) (oci.SignedEntity, error) {
o, err := makeOptions(ref.Context(), options...)
if err != nil {
return nil, err
}

got, err := remote.Get(ref, o.ropt...)
if err != nil {
return nil, err
}

switch got.MediaType {
case types.OCIImageIndex, types.DockerManifestList:
ii, err := got.ImageIndex()
if err != nil {
return nil, err
}
return &index{
v1Index: ii,
ref: ref.Context().Digest(got.Digest.String()),
opt: o,
}, nil
case types.OCIManifestSchema1, types.DockerManifestSchema2:
i, err := got.Image()
if err != nil {
return nil, err
}
return &image{
Image: i,
opt: o,
}, nil
default:
return nil, fmt.Errorf("unknown mime type: %v", got.MediaType)
}
}

// SignedImageIndex provides access to a remote index reference, and its signatures.
func SignedImageIndex(ref name.Reference, options ...Option) (oci.SignedImageIndex, error) {
o, err := makeOptions(ref.Context(), options...)
if err != nil {
return nil, err
}
ri, err := remoteIndex(ref, o.ropt...)
if err != nil {
return nil, err
}
return &index{
v1Index: ri,
ref: ref,
opt: o,
}, nil
}

// We alias ImageIndex so that we can inline it without the type
// name colliding with the name of a method it had to implement.
type v1Index v1.ImageIndex

type index struct {
v1Index
ref name.Reference
opt *options
}

var _ oci.SignedImageIndex = (*index)(nil)

// signatures is a shared implementation of the oci.Signed* Signatures method.
func signatures(digestable interface{ Digest() (v1.Hash, error) }, o *options) (oci.Signatures, error) {
h, err := digestable.Digest()
if err != nil {
return nil, err
}

// sha256:d34db33f -> sha256-d34db33f.suffix
tagStr := strings.ReplaceAll(h.String(), ":", "-") + o.signatureSuffix
ref := o.targetRepository.Tag(tagStr)
return Signatures(ref, o.ropt...)
}

// Signatures implements oic.SignedImageIndex
func (i *index) Signatures() (oci.Signatures, error) {
return signatures(i, i.opt)
}

// Attestations implements oic.SignedImageIndex
func (i *index) Attestations() (oci.Attestations, error) {
// TODO(mattmoor): allow accessing attestations.
return nil, errors.New("NYI")
}

// Attestations implements oic.SignedImageIndex
func (i *index) SignedImage(v1.Hash) (oci.SignedImage, error) {
// TODO(mattmoor): allow accessing child images as SignedImage
return nil, errors.New("NYI")
}

// Attestations implements oic.SignedImageIndex
func (i *index) SignedImageIndex(v1.Hash) (oci.SignedImageIndex, error) {
// TODO(mattmoor): allow accessing child indices as SignedImageIndex
return nil, errors.New("NYI")
}

// SignedImage provides access to a remote image reference, and its signatures.
func SignedImage(ref name.Reference, options ...Option) (oci.SignedImage, error) {
o, err := makeOptions(ref.Context(), options...)
if err != nil {
return nil, err
}
ri, err := remote.Image(ref, o.ropt...)
if err != nil {
return nil, err
}
return &image{
Image: ri,
opt: o,
}, nil
}

type image struct {
v1.Image
opt *options
}

var _ oci.SignedImage = (*image)(nil)

// Signatures implements oic.SignedImage
func (i *image) Signatures() (oci.Signatures, error) {
return signatures(i, i.opt)
}

// Attestations implements oic.SignedImage
func (i *image) Attestations() (oci.Attestations, error) {
// TODO(mattmoor): allow accessing attestations.
return nil, errors.New("NYI")
}

// Signatures fetches the signatures image represented by the named reference.
func Signatures(ref name.Reference, opts ...remote.Option) (oci.Signatures, error) {
Expand Down
13 changes: 3 additions & 10 deletions pkg/cosign/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (

"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/pkg/errors"
"knative.dev/pkg/pool"

Expand Down Expand Up @@ -67,19 +66,13 @@ func AttachedImageTag(repo name.Repository, digest v1.Hash, tagSuffix string) na
return repo.Tag(tagStr)
}

func FetchSignaturesForImage(ctx context.Context, signedImgRef name.Reference, sigRepo name.Repository, sigTagSuffix string, registryOpts ...remote.Option) ([]SignedPayload, error) {
// TODO(mattmoor): If signedImageRef is a digest, this is an unnecessary fetch.
signedImgDesc, err := remote.Get(signedImgRef, registryOpts...)
func FetchSignaturesForImage(ctx context.Context, ref name.Reference, opts ...ociremote.Option) ([]SignedPayload, error) {
simg, err := ociremote.SignedEntity(ref, opts...)
if err != nil {
return nil, err
}
return FetchSignaturesForImageDigest(ctx, signedImgDesc.Descriptor.Digest, sigRepo, sigTagSuffix, registryOpts...)
}

func FetchSignaturesForImageDigest(ctx context.Context, signedImageDigest v1.Hash, sigRepo name.Repository, sigTagSuffix string, registryOpts ...remote.Option) ([]SignedPayload, error) {
tag := AttachedImageTag(sigRepo, signedImageDigest, sigTagSuffix)

sigs, err := ociremote.Signatures(tag, registryOpts...)
sigs, err := simg.Signatures()
if err != nil {
return nil, errors.Wrap(err, "remote image")
}
Expand Down
Loading

0 comments on commit 54e7fe1

Please sign in to comment.