Update for docker-references PR and API changes

Pull in https://github.com/containers/image/pull/37 , and
update for CanonicalDockerReference() returning a reference.Named
This commit is contained in:
Miloslav Trmač 2016-07-11 21:35:46 +02:00
parent 29d76eb5ca
commit 8adb5f56de
13 changed files with 162 additions and 109 deletions

View File

@ -58,12 +58,12 @@ func copyHandler(context *cli.Context) error {
if err != nil {
return fmt.Errorf("Error initializing GPG: %v", err)
}
dockerReference, err := dest.CanonicalDockerReference()
if err != nil {
return fmt.Errorf("Error determining canonical Docker reference: %v", err)
dockerReference := dest.CanonicalDockerReference()
if dockerReference == nil {
return errors.New("Destination image does not have an associated Docker reference")
}
newSig, err := signature.SignDockerManifest(manifest, dockerReference, mech, signBy)
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, signBy)
if err != nil {
return fmt.Errorf("Error creating signature: %v", err)
}

View File

@ -1,12 +1,12 @@
package directory
import (
"fmt"
"io"
"io/ioutil"
"os"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
type dirImageDestination struct {
@ -18,8 +18,8 @@ func NewImageDestination(dir string) types.ImageDestination {
return &dirImageDestination{dir}
}
func (d *dirImageDestination) CanonicalDockerReference() (string, error) {
return "", fmt.Errorf("Can not determine canonical Docker reference for a local directory")
func (d *dirImageDestination) CanonicalDockerReference() reference.Named {
return nil
}
func (d *dirImageDestination) SupportedManifestMIMETypes() []string {

View File

@ -7,6 +7,7 @@ import (
"os"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
type dirImageSource struct {
@ -18,11 +19,12 @@ func NewImageSource(dir string) types.ImageSource {
return &dirImageSource{dir}
}
// IntendedDockerReference returns the full, unambiguous, Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
// May be "" if unknown.
func (s *dirImageSource) IntendedDockerReference() string {
return ""
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
func (s *dirImageSource) IntendedDockerReference() reference.Named {
return nil
}
// it's up to the caller to determine the MIME type of the returned manifest's bytes

View File

@ -15,13 +15,12 @@ import (
type dockerImageDestination struct {
ref reference.Named
tag string
c *dockerClient
}
// NewImageDestination creates a new ImageDestination for the specified image and connection specification.
func NewImageDestination(img, certPath string, tlsVerify bool) (types.ImageDestination, error) {
ref, tag, err := parseImageName(img)
ref, err := parseImageName(img)
if err != nil {
return nil, err
}
@ -31,7 +30,6 @@ func NewImageDestination(img, certPath string, tlsVerify bool) (types.ImageDesti
}
return &dockerImageDestination{
ref: ref,
tag: tag,
c: c,
}, nil
}
@ -45,8 +43,8 @@ func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
}
}
func (d *dockerImageDestination) CanonicalDockerReference() (string, error) {
return fmt.Sprintf("%s:%s", d.ref.Name(), d.tag), nil
func (d *dockerImageDestination) CanonicalDockerReference() reference.Named {
return d.ref
}
func (d *dockerImageDestination) PutManifest(m []byte) error {

View File

@ -25,13 +25,12 @@ func (e errFetchManifest) Error() string {
type dockerImageSource struct {
ref reference.Named
tag string
c *dockerClient
}
// newDockerImageSource is the same as NewImageSource, only it returns the more specific *dockerImageSource type.
func newDockerImageSource(img, certPath string, tlsVerify bool) (*dockerImageSource, error) {
ref, tag, err := parseImageName(img)
ref, err := parseImageName(img)
if err != nil {
return nil, err
}
@ -41,7 +40,6 @@ func newDockerImageSource(img, certPath string, tlsVerify bool) (*dockerImageSou
}
return &dockerImageSource{
ref: ref,
tag: tag,
c: c,
}, nil
}
@ -51,11 +49,12 @@ func NewImageSource(img, certPath string, tlsVerify bool) (types.ImageSource, er
return newDockerImageSource(img, certPath, tlsVerify)
}
// IntendedDockerReference returns the full, unambiguous, Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
// May be "" if unknown.
func (s *dockerImageSource) IntendedDockerReference() string {
return fmt.Sprintf("%s:%s", s.ref.Name(), s.tag)
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
func (s *dockerImageSource) IntendedDockerReference() reference.Named {
return s.ref
}
// simplifyContentType drops parameters from a HTTP media type (see https://tools.ietf.org/html/rfc7231#section-3.1.1.1)
@ -72,7 +71,11 @@ func simplifyContentType(contentType string) string {
}
func (s *dockerImageSource) GetManifest(mimetypes []string) ([]byte, string, error) {
url := fmt.Sprintf(manifestURL, s.ref.RemoteName(), s.tag)
reference, err := tagOrDigest(s.ref)
if err != nil {
return nil, "", err
}
url := fmt.Sprintf(manifestURL, s.ref.RemoteName(), reference)
// TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1
// TODO(runcom) NO, switch on the resulter manifest like Docker is doing
headers := make(map[string][]string)
@ -123,7 +126,11 @@ func (s *dockerImageSource) Delete() error {
headers := make(map[string][]string)
headers["Accept"] = []string{manifest.DockerV2Schema2MIMEType}
getURL := fmt.Sprintf(manifestURL, s.ref.RemoteName(), s.tag)
reference, err := tagOrDigest(s.ref)
if err != nil {
return err
}
getURL := fmt.Sprintf(manifestURL, s.ref.RemoteName(), reference)
get, err := s.c.makeRequest("GET", getURL, headers, nil)
if err != nil {
return err

View File

@ -1,20 +1,32 @@
package docker
import "github.com/docker/docker/reference"
import (
"fmt"
// parseImageName converts a string into a reference and tag value.
func parseImageName(img string) (reference.Named, string, error) {
"github.com/docker/docker/reference"
)
// parseImageName converts a string into a reference.
// It is guaranteed that reference.IsNameOnly is false for the returned value.
func parseImageName(img string) (reference.Named, error) {
ref, err := reference.ParseNamed(img)
if err != nil {
return nil, "", err
return nil, err
}
ref = reference.WithDefaultTag(ref)
var tag string
switch x := ref.(type) {
case reference.Canonical:
tag = x.Digest().String()
case reference.NamedTagged:
tag = x.Tag()
if reference.IsNameOnly(ref) { // Sanity check that we are fulfulling our contract
return nil, fmt.Errorf("Internal inconsistency: reference.IsNameOnly for reference %s (parsed from %s)", ref.String(), img)
}
return ref, tag, nil
return ref, nil
}
// tagOrDigest returns a tag or digest from a reference for which !reference.IsNameOnly.
func tagOrDigest(ref reference.Named) (string, error) {
if ref, ok := ref.(reference.Canonical); ok {
return ref.Digest().String(), nil
}
if ref, ok := ref.(reference.NamedTagged); ok {
return ref.Tag(), nil
}
return "", fmt.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", ref.String())
}

View File

@ -13,6 +13,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
var (
@ -50,10 +51,11 @@ func FromSource(src types.ImageSource, requestedManifestMIMETypes []string) type
return &genericImage{src: src, requestedManifestMIMETypes: requestedManifestMIMETypes}
}
// IntendedDockerReference returns the full, unambiguous, Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
// May be "" if unknown.
func (i *genericImage) IntendedDockerReference() string {
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
func (i *genericImage) IntendedDockerReference() reference.Named {
return i.src.IntendedDockerReference()
}

View File

@ -12,6 +12,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
type ociManifest struct {
@ -53,8 +54,8 @@ func NewImageDestination(dest string) (types.ImageDestination, error) {
}, nil
}
func (d *ociImageDestination) CanonicalDockerReference() (string, error) {
return "", fmt.Errorf("Can not determine canonical Docker reference for an OCI image")
func (d *ociImageDestination) CanonicalDockerReference() reference.Named {
return nil
}
func createManifest(m []byte) ([]byte, string, error) {

View File

@ -17,6 +17,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/containers/image/version"
"github.com/docker/docker/reference"
)
// openshiftClient is configuration for dealing with a single image stream, for reading or writing.
@ -28,9 +29,10 @@ type openshiftClient struct {
username string // "" if not used
password string // if username != ""
// Values specific to this image
namespace string
stream string
tag string
namespace string
stream string
tag string
canonicalDockerReference reference.Named // Computed from the above in advance, so that later references can not fail.
}
// FIXME: Is imageName like this a good way to refer to OpenShift images?
@ -58,7 +60,7 @@ func newOpenshiftClient(imageName string) (*openshiftClient, error) {
return nil, fmt.Errorf("Invalid image reference %s, %#v", imageName, m)
}
return &openshiftClient{
c := &openshiftClient{
baseURL: baseURL,
httpClient: httpClient,
bearerToken: restConfig.BearerToken,
@ -68,7 +70,21 @@ func newOpenshiftClient(imageName string) (*openshiftClient, error) {
namespace: m[1],
stream: m[2],
tag: m[3],
}, nil
}
// Precompute also c.canonicalDockerReference so that later references can not fail.
// FIXME: This is, strictly speaking, a namespace conflict with images placed in a Docker registry running on the same host.
// Do we need to do something else, perhaps disambiguate (port number?) or namespace Docker and OpenShift separately?
dockerRef, err := reference.WithName(fmt.Sprintf("%s/%s/%s", c.baseURL.Host, c.namespace, c.stream))
if err != nil {
return nil, err
}
c.canonicalDockerReference, err = reference.WithTag(dockerRef, c.tag)
if err != nil {
return nil, err
}
return c, nil
}
// doRequest performs a correctly authenticated request to a specified path, and returns response body or an error object.
@ -133,13 +149,6 @@ func (c *openshiftClient) doRequest(method, path string, requestBody []byte) ([]
return body, nil
}
// canonicalDockerReference returns a canonical reference we use for signing OpenShift images.
// FIXME: This is, strictly speaking, a namespace conflict with images placed in a Docker registry running on the same host.
// Do we need to do something else, perhaps disambiguate (port number?) or namespace Docker and OpenShift separately?
func (c *openshiftClient) canonicalDockerReference() string {
return fmt.Sprintf("%s/%s/%s:%s", c.baseURL.Host, c.namespace, c.stream, c.tag)
}
// convertDockerImageReference takes an image API DockerImageReference value and returns a reference we can actually use;
// currently OpenShift stores the cluster-internal service IPs here, which are unusable from the outside.
func (c *openshiftClient) convertDockerImageReference(ref string) (string, error) {
@ -186,11 +195,12 @@ func NewImageSource(imageName, certPath string, tlsVerify bool) (types.ImageSour
}, nil
}
// IntendedDockerReference returns the full, unambiguous, Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
// May be "" if unknown.
func (s *openshiftImageSource) IntendedDockerReference() string {
return s.client.canonicalDockerReference()
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
func (s *openshiftImageSource) IntendedDockerReference() reference.Named {
return s.client.canonicalDockerReference
}
func (s *openshiftImageSource) GetManifest(mimetypes []string) ([]byte, string, error) {
@ -290,8 +300,8 @@ func (d *openshiftImageDestination) SupportedManifestMIMETypes() []string {
}
}
func (d *openshiftImageDestination) CanonicalDockerReference() (string, error) {
return d.client.canonicalDockerReference(), nil
func (d *openshiftImageDestination) CanonicalDockerReference() reference.Named {
return d.client.canonicalDockerReference
}
func (d *openshiftImageDestination) PutManifest(m []byte) error {

View File

@ -10,9 +10,8 @@ import (
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/reference"
"github.com/containers/image/types"
distreference "github.com/docker/distribution/reference"
"github.com/docker/docker/reference"
)
// PolicyRequirementError is an explanatory text for rejecting a signature or an image.
@ -71,7 +70,8 @@ type PolicyRequirement interface {
// The type is public, but its implementation is private.
type PolicyReferenceMatch interface {
// matchesDockerReference decides whether a specific image identity is accepted for an image
// (or, usually, for the image's IntendedDockerReference()),
// (or, usually, for the image's IntendedDockerReference()). Note that
// image.IntendedDockerReference() may be nil.
matchesDockerReference(image types.Image, signatureDockerReference string) bool
}
@ -132,14 +132,15 @@ func (pc *PolicyContext) Destroy() error {
// FIXME? This feels like it should be provided by skopeo/reference.
func fullyExpandedDockerReference(ref reference.Named) (string, error) {
res := ref.FullName()
tagged, isTagged := ref.(distreference.Tagged)
digested, isDigested := ref.(distreference.Digested)
tagged, isTagged := ref.(reference.NamedTagged)
digested, isDigested := ref.(reference.Canonical)
// A github.com/distribution/reference value can have a tag and a digest at the same time!
// skopeo/reference does not handle that, so fail.
// FIXME? Should we support that?
// github.com/docker/reference does not handle that, so fail.
// (Even if it were supported, the semantics of policy namespaces are unclear - should we drop
// the tag or the digest first?)
switch {
case isTagged && isDigested:
// Coverage: This should currently not happen, the way skopeo/reference sets up types,
// Coverage: This should currently not happen, the way docker/reference sets up types,
// isTagged and isDigested is mutually exclusive.
return "", fmt.Errorf("Names with both a tag and digest are not currently supported")
case isTagged:
@ -154,15 +155,12 @@ func fullyExpandedDockerReference(ref reference.Named) (string, error) {
// requirementsForImage selects the appropriate requirements for image.
func (pc *PolicyContext) requirementsForImage(image types.Image) (PolicyRequirements, error) {
imageIdentity := image.IntendedDockerReference()
// We don't technically need to parse it first in order to match the full name:tag,
// but do so anyway to ensure that the intended identity really does follow that
// format, or at least that it is not demonstrably wrong.
ref, err := reference.ParseNamed(imageIdentity)
if err != nil {
return nil, err
ref := image.IntendedDockerReference()
if ref == nil {
// FIXME: Tell the user which image this is.
return nil, fmt.Errorf("Can not determine policy for an image with no known Docker reference identity")
}
ref = reference.WithDefaultTag(ref)
ref = reference.WithDefaultTag(ref) // This should not be needed, but if we did receive a name-only reference, this is a reasonable thing to do.
// Look for a full match.
fullyExpanded, err := fullyExpandedDockerReference(ref)

View File

@ -7,6 +7,40 @@ import (
"github.com/containers/image/types"
)
// parseImageAndDockerReference converts an image and a reference string into two parsed entities, failing on any error and handling unidentified images.
func parseImageAndDockerReference(image types.Image, s2 string) (reference.Named, reference.Named, error) {
r1 := image.IntendedDockerReference()
if r1 == nil {
// FIXME: Tell the user which image this is.
return nil, nil, PolicyRequirementError("Docker reference match attempted on an image with no known Docker reference identity")
}
r2, err := reference.ParseNamed(s2)
if err != nil {
return nil, nil, err
}
return r1, r2, nil
}
func (prm *prmMatchExact) matchesDockerReference(image types.Image, signatureDockerReference string) bool {
intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference)
if err != nil {
return false
}
// Do not add default tags: image.IntendedDockerReference() has it added already per its construction, and signatureDockerReference should be exact; so, verify that now.
if reference.IsNameOnly(intended) || reference.IsNameOnly(signature) {
return false
}
return signature.String() == intended.String()
}
func (prm *prmMatchRepository) matchesDockerReference(image types.Image, signatureDockerReference string) bool {
intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference)
if err != nil {
return false
}
return signature.Name() == intended.Name()
}
// parseDockerReferences converts two reference strings into parsed entities, failing on any error
func parseDockerReferences(s1, s2 string) (reference.Named, reference.Named, error) {
r1, err := reference.ParseNamed(s1)
@ -20,26 +54,6 @@ func parseDockerReferences(s1, s2 string) (reference.Named, reference.Named, err
return r1, r2, nil
}
func (prm *prmMatchExact) matchesDockerReference(image types.Image, signatureDockerReference string) bool {
intended, signature, err := parseDockerReferences(image.IntendedDockerReference(), signatureDockerReference)
if err != nil {
return false
}
// Do not add default tags: image.IntendedDockerReference() has it added already per its construction, and signatureDockerReference should be exact; so, verify that now.
if reference.IsNameOnly(intended) || reference.IsNameOnly(signature) {
return false
}
return signature.String() == intended.String()
}
func (prm *prmMatchRepository) matchesDockerReference(image types.Image, signatureDockerReference string) bool {
intended, signature, err := parseDockerReferences(image.IntendedDockerReference(), signatureDockerReference)
if err != nil {
return false
}
return signature.Name() == intended.Name()
}
func (prm *prmExactReference) matchesDockerReference(image types.Image, signatureDockerReference string) bool {
intended, signature, err := parseDockerReferences(prm.DockerReference, signatureDockerReference)
if err != nil {

View File

@ -3,16 +3,19 @@ package types
import (
"io"
"time"
"github.com/docker/docker/reference"
)
// ImageSource is a service, possibly remote (= slow), to download components of a single image.
// This is primarily useful for copying images around; for examining their properties, Image (below)
// is usually more useful.
type ImageSource interface {
// IntendedDockerReference returns the full, unambiguous, Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
// May be "" if unknown.
IntendedDockerReference() string
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
IntendedDockerReference() reference.Named
// GetManifest returns the image's manifest along with its MIME type. The empty string is returned if the MIME type is unknown. The slice parameter indicates the supported mime types the manifest should be when getting it.
// It may use a remote (= slow) service.
GetManifest([]string) ([]byte, string, error)
@ -27,8 +30,9 @@ type ImageSource interface {
// ImageDestination is a service, possibly remote (= slow), to store components of a single image.
type ImageDestination interface {
// CanonicalDockerReference returns the full, unambiguous, Docker reference for this image (even if the user referred to the image using some shorthand notation).
CanonicalDockerReference() (string, error)
// CanonicalDockerReference returns the Docker reference for this image (fully expanded, i.e. !reference.IsNameOnly, but
// reflecting user intent, not e.g. after redirect or alias processing), or nil if unknown.
CanonicalDockerReference() reference.Named
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
PutManifest([]byte) error
// Note: Calling PutBlob() and other methods may have ordering dependencies WRT other methods of this type. FIXME: Figure out and document.
@ -42,10 +46,11 @@ type ImageDestination interface {
// Image is the primary API for inspecting properties of images.
type Image interface {
// ref to repository?
// IntendedDockerReference returns the full, unambiguous, Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
// May be "" if unknown.
IntendedDockerReference() string
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
IntendedDockerReference() reference.Named
// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
// NOTE: It is essential for signature verification that Manifest returns the manifest from which BlobDigests is computed.
Manifest() ([]byte, string, error)

View File

@ -1,5 +1,9 @@
# libtrust
> **WARNING** this library is no longer actively developed, and will be integrated
> in the [docker/distribution][https://www.github.com/docker/distribution]
> repository in future.
Libtrust is library for managing authentication and authorization using public key cryptography.
Authentication is handled using the identity attached to the public key.