mirror of https://github.com/containers/image.git
Add types.ImageReference.PolicyConfiguration{Identity,Namespaces}
This makes the core policy lookup logic Docker-independent, with the underlying Docker-specific implementation moved to docker/policyconfiguration/naming.go. Does not change behavior, only docker: and atomic: transports currently implement the policy configuration naming, in a compatible way, and the policy configuration is still not transport-aware. That happens next. Signed-off-by: Miloslav Trmač <mitr@redhat.com>
This commit is contained in:
parent
2cfa81f4c1
commit
be1e012a0f
|
|
@ -56,6 +56,26 @@ func (ref dirReference) DockerReference() reference.Named {
|
|||
return nil
|
||||
}
|
||||
|
||||
// PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup.
|
||||
// This MUST reflect user intent, not e.g. after processing of third-party redirects or aliases;
|
||||
// The value SHOULD be fully explicit about its semantics, with no hidden defaults, AND canonical
|
||||
// (i.e. various references with exactly the same semantics should return the same configuration identity)
|
||||
// It is fine for the return value to be equal to StringWithinTransport(), and it is desirable but
|
||||
// not required/guaranteed that it will be a valid input to Transport().ParseReference().
|
||||
// Returns "" if configuration identities for these references are not supported.
|
||||
func (ref dirReference) PolicyConfigurationIdentity() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
|
||||
// for if explicit configuration for PolicyConfigurationIdentity() is not set. The list will be processed
|
||||
// in order, terminating on first match, and an implicit "" is always checked at the end.
|
||||
// It is STRONGLY recommended for the first element, if any, to be a prefix of PolicyConfigurationIdentity(),
|
||||
// and each following element to be a prefix of the element preceding it.
|
||||
func (ref dirReference) PolicyConfigurationNamespaces() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewImage returns a types.Image for this reference.
|
||||
func (ref dirReference) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
|
||||
src := newImageSource(ref)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,18 @@ func TestReferenceDockerReference(t *testing.T) {
|
|||
assert.Nil(t, ref.DockerReference())
|
||||
}
|
||||
|
||||
func TestReferencePolicyConfigurationIdentity(t *testing.T) {
|
||||
ref, tmpDir := refToTempDir(t)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
assert.Equal(t, "", ref.PolicyConfigurationIdentity())
|
||||
}
|
||||
|
||||
func TestReferencePolicyConfigurationNamespaces(t *testing.T) {
|
||||
ref, tmpDir := refToTempDir(t)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
assert.Nil(t, ref.PolicyConfigurationNamespaces())
|
||||
}
|
||||
|
||||
func TestReferenceNewImage(t *testing.T) {
|
||||
ref, tmpDir := refToTempDir(t)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/docker/policyconfiguration"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/docker/reference"
|
||||
)
|
||||
|
|
@ -79,6 +80,30 @@ func (ref dockerReference) DockerReference() reference.Named {
|
|||
return ref.ref
|
||||
}
|
||||
|
||||
// PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup.
|
||||
// This MUST reflect user intent, not e.g. after processing of third-party redirects or aliases;
|
||||
// The value SHOULD be fully explicit about its semantics, with no hidden defaults, AND canonical
|
||||
// (i.e. various references with exactly the same semantics should return the same configuration identity)
|
||||
// It is fine for the return value to be equal to StringWithinTransport(), and it is desirable but
|
||||
// not required/guaranteed that it will be a valid input to Transport().ParseReference().
|
||||
// Returns "" if configuration identities for these references are not supported.
|
||||
func (ref dockerReference) PolicyConfigurationIdentity() string {
|
||||
res, err := policyconfiguration.DockerReferenceIdentity(ref.ref)
|
||||
if res == "" || err != nil { // Coverage: Should never happen, NewReference above should refuse values which could cause a failure.
|
||||
panic(fmt.Sprintf("Internal inconsistency: policyconfiguration.DockerReferenceIdentity returned %#v, %v", res, err))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
|
||||
// for if explicit configuration for PolicyConfigurationIdentity() is not set. The list will be processed
|
||||
// in order, terminating on first match, and an implicit "" is always checked at the end.
|
||||
// It is STRONGLY recommended for the first element, if any, to be a prefix of PolicyConfigurationIdentity(),
|
||||
// and each following element to be a prefix of the element preceding it.
|
||||
func (ref dockerReference) PolicyConfigurationNamespaces() []string {
|
||||
return policyconfiguration.DockerReferenceNamespaces(ref.ref)
|
||||
}
|
||||
|
||||
// NewImage returns a types.Image for this reference.
|
||||
func (ref dockerReference) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
|
||||
return newImage(ref, certPath, tlsVerify)
|
||||
|
|
|
|||
|
|
@ -126,6 +126,24 @@ func TestReferenceDockerReference(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestReferencePolicyConfigurationIdentity(t *testing.T) {
|
||||
// Just a smoke test, the substance is tested in policyconfiguration.TestDockerReference.
|
||||
ref, err := ParseReference("//busybox")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "docker.io/library/busybox:latest", ref.PolicyConfigurationIdentity())
|
||||
}
|
||||
|
||||
func TestReferencePolicyConfigurationNamespaces(t *testing.T) {
|
||||
// Just a smoke test, the substance is tested in policyconfiguration.TestDockerReference.
|
||||
ref, err := ParseReference("//busybox")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{
|
||||
"docker.io/library/busybox",
|
||||
"docker.io/library",
|
||||
"docker.io",
|
||||
}, ref.PolicyConfigurationNamespaces())
|
||||
}
|
||||
|
||||
func TestReferenceNewImage(t *testing.T) {
|
||||
ref, err := ParseReference("//busybox")
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
package policyconfiguration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/reference"
|
||||
)
|
||||
|
||||
// DockerReferenceIdentity returns a string representation of the reference, suitable for policy lookup,
|
||||
// as a backend for ImageReference.PolicyConfigurationIdentity.
|
||||
// The reference must satisfy !reference.IsNameOnly().
|
||||
func DockerReferenceIdentity(ref reference.Named) (string, error) {
|
||||
res := ref.FullName()
|
||||
tagged, isTagged := ref.(reference.NamedTagged)
|
||||
digested, isDigested := ref.(reference.Canonical)
|
||||
switch {
|
||||
case isTagged && isDigested: // This should not happen, docker/reference.ParseNamed drops the tag.
|
||||
return "", fmt.Errorf("Unexpected Docker reference %s with both a name and a digest", ref.String())
|
||||
case !isTagged && !isDigested: // This should not happen, the caller is expected to ensure !reference.IsNameOnly()
|
||||
return "", fmt.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", ref.String())
|
||||
case isTagged:
|
||||
res = res + ":" + tagged.Tag()
|
||||
case isDigested:
|
||||
res = res + "@" + digested.Digest().String()
|
||||
default: // Coverage: The above was supposed to be exhaustive.
|
||||
return "", errors.New("Internal inconsistency, unexpected default branch")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// DockerReferenceNamespaces returns a list of other policy configuration namespaces to search,
|
||||
// as a backend for ImageReference.PolicyConfigurationIdentity.
|
||||
// The reference must satisfy !reference.IsNameOnly().
|
||||
func DockerReferenceNamespaces(ref reference.Named) []string {
|
||||
// Look for a match of the repository, and then of the possible parent
|
||||
// namespaces. Note that this only happens on the expanded host names
|
||||
// and repository names, i.e. "busybox" is looked up as "docker.io/library/busybox",
|
||||
// then in its parent "docker.io/library"; in none of "busybox",
|
||||
// un-namespaced "library" nor in "" supposedly implicitly representing "library/".
|
||||
//
|
||||
// ref.FullName() == ref.Hostname() + "/" + ref.RemoteName(), so the last
|
||||
// iteration matches the host name (for any namespace).
|
||||
res := []string{}
|
||||
name := ref.FullName()
|
||||
for {
|
||||
res = append(res, name)
|
||||
|
||||
lastSlash := strings.LastIndex(name, "/")
|
||||
if lastSlash == -1 {
|
||||
break
|
||||
}
|
||||
name = name[:lastSlash]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package policyconfiguration
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestDockerReference tests DockerReferenceIdentity and DockerReferenceNamespaces simulatenously
|
||||
// to ensure they are consistent.
|
||||
func TestDockerReference(t *testing.T) {
|
||||
sha256Digest := "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
// Test both that DockerReferenceIdentity returns the expected value (fullName+suffix),
|
||||
// and that DockerReferenceNamespaces starts with the expected value (fullName), i.e. that the two functions are
|
||||
// consistent.
|
||||
for inputName, expectedNS := range map[string][]string{
|
||||
"example.com/ns/repo": {"example.com/ns/repo", "example.com/ns", "example.com"},
|
||||
"example.com/repo": {"example.com/repo", "example.com"},
|
||||
"localhost/ns/repo": {"localhost/ns/repo", "localhost/ns", "localhost"},
|
||||
// Note that "localhost" is special here: notlocalhost/repo is parsed as docker.io/notlocalhost.repo:
|
||||
"localhost/repo": {"localhost/repo", "localhost"},
|
||||
"notlocalhost/repo": {"docker.io/notlocalhost/repo", "docker.io/notlocalhost", "docker.io"},
|
||||
"docker.io/ns/repo": {"docker.io/ns/repo", "docker.io/ns", "docker.io"},
|
||||
"docker.io/library/repo": {"docker.io/library/repo", "docker.io/library", "docker.io"},
|
||||
"docker.io/repo": {"docker.io/library/repo", "docker.io/library", "docker.io"},
|
||||
"ns/repo": {"docker.io/ns/repo", "docker.io/ns", "docker.io"},
|
||||
"library/repo": {"docker.io/library/repo", "docker.io/library", "docker.io"},
|
||||
"repo": {"docker.io/library/repo", "docker.io/library", "docker.io"},
|
||||
} {
|
||||
for inputSuffix, mappedSuffix := range map[string]string{
|
||||
":tag": ":tag",
|
||||
sha256Digest: sha256Digest,
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
// github.com/docker/reference handles that by dropping the tag. That is not obviously the
|
||||
// right thing to do, but it is at least reasonable, so test that we keep behaving reasonably.
|
||||
// This test case should not be construed to make this an API promise.
|
||||
":tag" + sha256Digest: sha256Digest,
|
||||
} {
|
||||
fullInput := inputName + inputSuffix
|
||||
ref, err := reference.ParseNamed(fullInput)
|
||||
require.NoError(t, err, fullInput)
|
||||
|
||||
identity, err := DockerReferenceIdentity(ref)
|
||||
require.NoError(t, err, fullInput)
|
||||
assert.Equal(t, expectedNS[0]+mappedSuffix, identity, fullInput)
|
||||
|
||||
ns := DockerReferenceNamespaces(ref)
|
||||
require.NotNil(t, ns, fullInput)
|
||||
require.Len(t, ns, len(expectedNS), fullInput)
|
||||
moreSpecific := identity
|
||||
for i := range expectedNS {
|
||||
assert.Equal(t, ns[i], expectedNS[i], fmt.Sprintf("%s item %d", fullInput, i))
|
||||
assert.True(t, strings.HasPrefix(moreSpecific, ns[i]))
|
||||
moreSpecific = ns[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time.
|
||||
type refWithTagAndDigest struct{ reference.Canonical }
|
||||
|
||||
func (ref refWithTagAndDigest) Tag() string {
|
||||
return "notLatest"
|
||||
}
|
||||
|
||||
func TestDockerReferenceIdentity(t *testing.T) {
|
||||
// TestDockerReference above has tested the core of the functionality, this tests only the failure cases.
|
||||
|
||||
// Neither a tag nor digest
|
||||
parsed, err := reference.ParseNamed("busybox")
|
||||
require.NoError(t, err)
|
||||
id, err := DockerReferenceIdentity(parsed)
|
||||
assert.Equal(t, "", id)
|
||||
assert.Error(t, err)
|
||||
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
parsed, err = reference.ParseNamed("busybox@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
|
||||
require.NoError(t, err)
|
||||
refDigested, ok := parsed.(reference.Canonical)
|
||||
require.True(t, ok)
|
||||
tagDigestRef := refWithTagAndDigest{refDigested}
|
||||
id, err = DockerReferenceIdentity(tagDigestRef)
|
||||
assert.Equal(t, "", id)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
@ -75,6 +75,26 @@ func (ref ociReference) DockerReference() reference.Named {
|
|||
return nil
|
||||
}
|
||||
|
||||
// PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup.
|
||||
// This MUST reflect user intent, not e.g. after processing of third-party redirects or aliases;
|
||||
// The value SHOULD be fully explicit about its semantics, with no hidden defaults, AND canonical
|
||||
// (i.e. various references with exactly the same semantics should return the same configuration identity)
|
||||
// It is fine for the return value to be equal to StringWithinTransport(), and it is desirable but
|
||||
// not required/guaranteed that it will be a valid input to Transport().ParseReference().
|
||||
// Returns "" if configuration identities for these references are not supported.
|
||||
func (ref ociReference) PolicyConfigurationIdentity() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
|
||||
// for if explicit configuration for PolicyConfigurationIdentity() is not set. The list will be processed
|
||||
// in order, terminating on first match, and an implicit "" is always checked at the end.
|
||||
// It is STRONGLY recommended for the first element, if any, to be a prefix of PolicyConfigurationIdentity(),
|
||||
// and each following element to be a prefix of the element preceding it.
|
||||
func (ref ociReference) PolicyConfigurationNamespaces() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewImage returns a types.Image for this reference.
|
||||
func (ref ociReference) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
|
||||
return nil, errors.New("Full Image support not implemented for oci: image names")
|
||||
|
|
|
|||
|
|
@ -118,6 +118,18 @@ func TestReferenceDockerReference(t *testing.T) {
|
|||
assert.Nil(t, ref.DockerReference())
|
||||
}
|
||||
|
||||
func TestReferencePolicyConfigurationIdentity(t *testing.T) {
|
||||
ref, tmpDir := refToTempOCI(t)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
assert.Equal(t, "", ref.PolicyConfigurationIdentity())
|
||||
}
|
||||
|
||||
func TestReferencePolicyConfigurationNamespaces(t *testing.T) {
|
||||
ref, tmpDir := refToTempOCI(t)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
assert.Nil(t, ref.PolicyConfigurationNamespaces())
|
||||
}
|
||||
|
||||
func TestReferenceNewImage(t *testing.T) {
|
||||
ref, tmpDir := refToTempOCI(t)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"regexp"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/containers/image/docker/policyconfiguration"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/docker/reference"
|
||||
)
|
||||
|
|
@ -111,6 +112,30 @@ func (ref openshiftReference) DockerReference() reference.Named {
|
|||
return ref.dockerReference
|
||||
}
|
||||
|
||||
// PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup.
|
||||
// This MUST reflect user intent, not e.g. after processing of third-party redirects or aliases;
|
||||
// The value SHOULD be fully explicit about its semantics, with no hidden defaults, AND canonical
|
||||
// (i.e. various references with exactly the same semantics should return the same configuration identity)
|
||||
// It is fine for the return value to be equal to StringWithinTransport(), and it is desirable but
|
||||
// not required/guaranteed that it will be a valid input to Transport().ParseReference().
|
||||
// Returns "" if configuration identities for these references are not supported.
|
||||
func (ref openshiftReference) PolicyConfigurationIdentity() string {
|
||||
res, err := policyconfiguration.DockerReferenceIdentity(ref.dockerReference)
|
||||
if res == "" || err != nil { // Coverage: Should never happen, NewReference constructs a valid tagged reference.
|
||||
panic(fmt.Sprintf("Internal inconsistency: policyconfiguration.DockerReferenceIdentity returned %#v, %v", res, err))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
|
||||
// for if explicit configuration for PolicyConfigurationIdentity() is not set. The list will be processed
|
||||
// in order, terminating on first match, and an implicit "" is always checked at the end.
|
||||
// It is STRONGLY recommended for the first element, if any, to be a prefix of PolicyConfigurationIdentity(),
|
||||
// and each following element to be a prefix of the element preceding it.
|
||||
func (ref openshiftReference) PolicyConfigurationNamespaces() []string {
|
||||
return policyconfiguration.DockerReferenceNamespaces(ref.dockerReference)
|
||||
}
|
||||
|
||||
// NewImage returns a types.Image for this reference.
|
||||
func (ref openshiftReference) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
|
||||
return nil, errors.New("Full Image support not implemented for atomic: image names")
|
||||
|
|
|
|||
|
|
@ -71,6 +71,24 @@ func TestReferenceStringWithinTransport(t *testing.T) {
|
|||
// but that is untested because it depends on per-user configuration.
|
||||
}
|
||||
|
||||
func TestReferencePolicyConfigurationIdentity(t *testing.T) {
|
||||
// Just a smoke test, the substance is tested in policyconfiguration.TestDockerReference.
|
||||
ref, err := NewReference(testBaseURL, "ns", "stream", "notlatest")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "registry.example.com:8443/ns/stream:notlatest", ref.PolicyConfigurationIdentity())
|
||||
}
|
||||
|
||||
func TestReferencePolicyConfigurationNamespaces(t *testing.T) {
|
||||
// Just a smoke test, the substance is tested in policyconfiguration.TestDockerReference.
|
||||
ref, err := NewReference(testBaseURL, "ns", "stream", "notlatest")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{
|
||||
"registry.example.com:8443/ns/stream",
|
||||
"registry.example.com:8443/ns",
|
||||
"registry.example.com:8443",
|
||||
}, ref.PolicyConfigurationNamespaces())
|
||||
}
|
||||
|
||||
func TestReferenceNewImage(t *testing.T) {
|
||||
ref, err := NewReference(testBaseURL, "ns", "stream", "notlatest")
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -7,12 +7,10 @@ package signature
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/docker/reference"
|
||||
)
|
||||
|
||||
// PolicyRequirementError is an explanatory text for rejecting a signature or an image.
|
||||
|
|
@ -127,71 +125,24 @@ func (pc *PolicyContext) Destroy() error {
|
|||
return pc.changeState(pcDestroying, pcDestroyed)
|
||||
}
|
||||
|
||||
// fullyExpandedDockerReference converts a reference.Named into a fully expanded format;
|
||||
// i.e. soft of an opposite to ref.String(), which is a fully canonicalized/minimized format.
|
||||
// This is guaranteed to be the same as reference.FullName(), with a tag or digest appended, if available.
|
||||
// FIXME? This feels like it should be provided by skopeo/reference.
|
||||
func fullyExpandedDockerReference(ref reference.Named) (string, error) {
|
||||
res := ref.FullName()
|
||||
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!
|
||||
// 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 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:
|
||||
res = res + ":" + tagged.Tag()
|
||||
case isDigested:
|
||||
res = res + "@" + digested.Digest().String()
|
||||
default:
|
||||
// res is already OK.
|
||||
// requirementsForImageRef selects the appropriate requirements for ref.
|
||||
func (pc *PolicyContext) requirementsForImageRef(ref types.ImageReference) (PolicyRequirements, error) {
|
||||
identity := ref.PolicyConfigurationIdentity()
|
||||
if identity == "" {
|
||||
return nil, fmt.Errorf("Can not determine policy for image %s with undefined policy configuration identity", transports.ImageName(ref))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// requirementsForImage selects the appropriate requirements for image.
|
||||
func (pc *PolicyContext) requirementsForImage(image types.Image) (PolicyRequirements, error) {
|
||||
ref := image.Reference().DockerReference()
|
||||
if ref == nil {
|
||||
return nil, fmt.Errorf("Can not determine policy for image %s with no known Docker reference identity", transports.ImageName(image.Reference()))
|
||||
}
|
||||
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)
|
||||
if err != nil { // Coverage: This cannot currently happen.
|
||||
return nil, err
|
||||
}
|
||||
if req, ok := pc.Policy.Specific[fullyExpanded]; ok {
|
||||
logrus.Debugf(" Using specific policy section %s", fullyExpanded)
|
||||
if req, ok := pc.Policy.Specific[identity]; ok {
|
||||
logrus.Debugf(" Using specific policy section %s", identity)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Look for a match of the repository, and then of the possible parent
|
||||
// namespaces. Note that this only happens on the expanded host names
|
||||
// and repository names, i.e. "busybox" is looked up as "docker.io/library/busybox",
|
||||
// then in its parent "docker.io/library"; in none of "busybox",
|
||||
// un-namespaced "library" nor in "" implicitly representing "library/".
|
||||
//
|
||||
// ref.FullName() == ref.Hostname() + "/" + ref.RemoteName(), so the last
|
||||
// iteration matches the host name (for any namespace).
|
||||
name := ref.FullName()
|
||||
for {
|
||||
// Look for a match of the possible parent namespaces.
|
||||
for _, name := range ref.PolicyConfigurationNamespaces() {
|
||||
if req, ok := pc.Policy.Specific[name]; ok {
|
||||
logrus.Debugf(" Using specific policy section %s", name)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
lastSlash := strings.LastIndex(name, "/")
|
||||
if lastSlash == -1 {
|
||||
break
|
||||
}
|
||||
name = name[:lastSlash]
|
||||
}
|
||||
|
||||
logrus.Debugf(" Using default policy section")
|
||||
|
|
@ -222,9 +173,9 @@ func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(image types.Image) (sig
|
|||
}
|
||||
}()
|
||||
|
||||
logrus.Debugf("GetSignaturesWithAcceptedAuthor for image %s", image.Reference().DockerReference())
|
||||
logrus.Debugf("GetSignaturesWithAcceptedAuthor for image %s:%s", image.Reference().DockerReference())
|
||||
|
||||
reqs, err := pc.requirementsForImage(image)
|
||||
reqs, err := pc.requirementsForImageRef(image.Reference())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -308,7 +259,7 @@ func (pc *PolicyContext) IsRunningImageAllowed(image types.Image) (res bool, fin
|
|||
|
||||
logrus.Debugf("IsRunningImageAllowed for image %s", image.Reference().DockerReference())
|
||||
|
||||
reqs, err := pc.requirementsForImage(image)
|
||||
reqs, err := pc.requirementsForImageRef(image.Reference())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@ func (ref nameOnlyImageReferenceMock) StringWithinTransport() string {
|
|||
func (ref nameOnlyImageReferenceMock) DockerReference() reference.Named {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) PolicyConfigurationIdentity() string {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) PolicyConfigurationNamespaces() []string {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/image/docker/policyconfiguration"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -59,47 +60,8 @@ func TestPolicyContextNewDestroy(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFullyExpandedDockerReference(t *testing.T) {
|
||||
sha256Digest := "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
// Test both that fullyExpandedDockerReference returns the expected value (fullName+suffix),
|
||||
// and that .FullName returns the expected value (fullName), i.e. that the two functions are
|
||||
// consistent.
|
||||
for inputName, fullName := range map[string]string{
|
||||
"example.com/ns/repo": "example.com/ns/repo",
|
||||
"example.com/repo": "example.com/repo",
|
||||
"localhost/ns/repo": "localhost/ns/repo",
|
||||
// Note that "localhost" is special here: notlocalhost/repo is be parsed as docker.io/notlocalhost.repo:
|
||||
"localhost/repo": "localhost/repo",
|
||||
"notlocalhost/repo": "docker.io/notlocalhost/repo",
|
||||
"docker.io/ns/repo": "docker.io/ns/repo",
|
||||
"docker.io/library/repo": "docker.io/library/repo",
|
||||
"docker.io/repo": "docker.io/library/repo",
|
||||
"ns/repo": "docker.io/ns/repo",
|
||||
"library/repo": "docker.io/library/repo",
|
||||
"repo": "docker.io/library/repo",
|
||||
} {
|
||||
for inputSuffix, mappedSuffix := range map[string]string{
|
||||
":tag": ":tag",
|
||||
sha256Digest: sha256Digest,
|
||||
"": "",
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
// github.com/docker/reference handles that by dropping the tag. That is not obviously the
|
||||
// right thing to do, but it is at least reasonable, so test that we keep behaving reasonably.
|
||||
// This test case should not be construed to make this an API promise.
|
||||
":tag" + sha256Digest: sha256Digest,
|
||||
} {
|
||||
fullInput := inputName + inputSuffix
|
||||
ref, err := reference.ParseNamed(fullInput)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, fullName, ref.FullName(), fullInput)
|
||||
expanded, err := fullyExpandedDockerReference(ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, fullName+mappedSuffix, expanded, fullInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pcImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference
|
||||
// and handles PolicyConfigurationIdentity and PolicyConfigurationReference consistently.
|
||||
type pcImageReferenceMock struct{ ref reference.Named }
|
||||
|
||||
func (ref pcImageReferenceMock) Transport() types.ImageTransport {
|
||||
|
|
@ -113,6 +75,22 @@ func (ref pcImageReferenceMock) StringWithinTransport() string {
|
|||
func (ref pcImageReferenceMock) DockerReference() reference.Named {
|
||||
return ref.ref
|
||||
}
|
||||
func (ref pcImageReferenceMock) PolicyConfigurationIdentity() string {
|
||||
if ref.ref == nil {
|
||||
return ""
|
||||
}
|
||||
res, err := policyconfiguration.DockerReferenceIdentity(ref.ref)
|
||||
if res == "" || err != nil {
|
||||
panic(fmt.Sprintf("Internal inconsistency: policyconfiguration.DockerReferenceIdentity returned %#v, %v", res, err))
|
||||
}
|
||||
return res
|
||||
}
|
||||
func (ref pcImageReferenceMock) PolicyConfigurationNamespaces() []string {
|
||||
if ref.ref == nil {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
return policyconfiguration.DockerReferenceNamespaces(ref.ref)
|
||||
}
|
||||
func (ref pcImageReferenceMock) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
|
|
@ -123,7 +101,7 @@ func (ref pcImageReferenceMock) NewImageDestination(certPath string, tlsVerify b
|
|||
panic("unexpected call to a mock function")
|
||||
}
|
||||
|
||||
func TestPolicyContextRequirementsForImage(t *testing.T) {
|
||||
func TestPolicyContextRequirementsForImageRef(t *testing.T) {
|
||||
ktGPG := SBKeyTypeGPGKeys
|
||||
prm := NewPRMMatchExact()
|
||||
|
||||
|
|
@ -135,35 +113,12 @@ func TestPolicyContextRequirementsForImage(t *testing.T) {
|
|||
// distinct so that we can compare the values and show them when debugging the tests.
|
||||
for _, scope := range []string{
|
||||
"unmatched",
|
||||
"hostname.com",
|
||||
"hostname.com/namespace",
|
||||
"hostname.com/namespace/repo",
|
||||
"hostname.com/namespace/repo:latest",
|
||||
"hostname.com/namespace/repo:tag2",
|
||||
"localhost",
|
||||
"localhost/namespace",
|
||||
"localhost/namespace/repo",
|
||||
"localhost/namespace/repo:latest",
|
||||
"localhost/namespace/repo:tag2",
|
||||
"deep.com",
|
||||
"deep.com/n1",
|
||||
"deep.com/n1/n2",
|
||||
"deep.com/n1/n2/n3",
|
||||
"deep.com/n1/n2/n3/repo",
|
||||
"deep.com/n1/n2/n3/repo:tag2",
|
||||
"docker.io",
|
||||
"docker.io/library",
|
||||
"docker.io/library/busybox",
|
||||
"docker.io/namespaceindocker",
|
||||
"docker.io/namespaceindocker/repo",
|
||||
"docker.io/namespaceindocker/repo:tag2",
|
||||
// Note: these non-fully-expanded repository names are not matched against canonical (shortened)
|
||||
// Docker names; they are instead parsed as starting with hostnames.
|
||||
"busybox",
|
||||
"library/busybox",
|
||||
"namespaceindocker",
|
||||
"namespaceindocker/repo",
|
||||
"namespaceindocker/repo:tag2",
|
||||
} {
|
||||
policy.Specific[scope] = PolicyRequirements{xNewPRSignedByKeyData(ktGPG, []byte(scope), prm)}
|
||||
}
|
||||
|
|
@ -173,46 +128,16 @@ func TestPolicyContextRequirementsForImage(t *testing.T) {
|
|||
|
||||
for input, matched := range map[string]string{
|
||||
// Full match
|
||||
"hostname.com/namespace/repo:latest": "hostname.com/namespace/repo:latest",
|
||||
"hostname.com/namespace/repo:tag2": "hostname.com/namespace/repo:tag2",
|
||||
"hostname.com/namespace/repo": "hostname.com/namespace/repo:latest",
|
||||
"localhost/namespace/repo:latest": "localhost/namespace/repo:latest",
|
||||
"localhost/namespace/repo:tag2": "localhost/namespace/repo:tag2",
|
||||
"localhost/namespace/repo": "localhost/namespace/repo:latest",
|
||||
"deep.com/n1/n2/n3/repo:tag2": "deep.com/n1/n2/n3/repo:tag2",
|
||||
// Repository match
|
||||
"hostname.com/namespace/repo:notlatest": "hostname.com/namespace/repo",
|
||||
"localhost/namespace/repo:notlatest": "localhost/namespace/repo",
|
||||
"deep.com/n1/n2/n3/repo:nottag2": "deep.com/n1/n2/n3/repo",
|
||||
// Namespace match
|
||||
"hostname.com/namespace/notrepo:latest": "hostname.com/namespace",
|
||||
"localhost/namespace/notrepo:latest": "localhost/namespace",
|
||||
"deep.com/n1/n2/n3/notrepo:tag2": "deep.com/n1/n2/n3",
|
||||
"deep.com/n1/n2/notn3/repo:tag2": "deep.com/n1/n2",
|
||||
"deep.com/n1/notn2/n3/repo:tag2": "deep.com/n1",
|
||||
"deep.com/n1/n2/n3/repo:tag2": "deep.com/n1/n2/n3/repo:tag2",
|
||||
// Namespace matches
|
||||
"deep.com/n1/n2/n3/repo:nottag2": "deep.com/n1/n2/n3/repo",
|
||||
"deep.com/n1/n2/n3/notrepo:tag2": "deep.com/n1/n2/n3",
|
||||
"deep.com/n1/n2/notn3/repo:tag2": "deep.com/n1/n2",
|
||||
"deep.com/n1/notn2/n3/repo:tag2": "deep.com/n1",
|
||||
// Host name match
|
||||
"hostname.com/notnamespace/repo:latest": "hostname.com",
|
||||
"localhost/notnamespace/repo:latest": "localhost",
|
||||
"deep.com/notn1/n2/n3/repo:tag2": "deep.com",
|
||||
"deep.com/notn1/n2/n3/repo:tag2": "deep.com",
|
||||
// Default
|
||||
"this.doesnt/match:anything": "",
|
||||
"this.doesnt/match-anything/defaulttag": "",
|
||||
|
||||
// docker.io canonizalication effects
|
||||
"docker.io/library/busybox": "docker.io/library/busybox",
|
||||
"library/busybox": "docker.io/library/busybox",
|
||||
"busybox": "docker.io/library/busybox",
|
||||
"docker.io/library/somethinginlibrary": "docker.io/library",
|
||||
"library/somethinginlibrary": "docker.io/library",
|
||||
"somethinginlibrary": "docker.io/library",
|
||||
"docker.io/namespaceindocker/repo:tag2": "docker.io/namespaceindocker/repo:tag2",
|
||||
"namespaceindocker/repo:tag2": "docker.io/namespaceindocker/repo:tag2",
|
||||
"docker.io/namespaceindocker/repo:nottag2": "docker.io/namespaceindocker/repo",
|
||||
"namespaceindocker/repo:nottag2": "docker.io/namespaceindocker/repo",
|
||||
"docker.io/namespaceindocker/notrepo:tag2": "docker.io/namespaceindocker",
|
||||
"namespaceindocker/notrepo:tag2": "docker.io/namespaceindocker",
|
||||
"docker.io/notnamespaceindocker/repo:tag2": "docker.io",
|
||||
"notnamespaceindocker/repo:tag2": "docker.io",
|
||||
"this.doesnt/match:anything": "",
|
||||
} {
|
||||
var expected PolicyRequirements
|
||||
if matched != "" {
|
||||
|
|
@ -223,12 +148,12 @@ func TestPolicyContextRequirementsForImage(t *testing.T) {
|
|||
expected = policy.Default
|
||||
}
|
||||
|
||||
inputRef, err := reference.ParseNamed(input)
|
||||
ref, err := reference.ParseNamed(input)
|
||||
require.NoError(t, err)
|
||||
reqs, err := pc.requirementsForImage(refImageMock{inputRef})
|
||||
reqs, err := pc.requirementsForImageRef(pcImageReferenceMock{ref})
|
||||
require.NoError(t, err)
|
||||
comment := fmt.Sprintf("case %s: %#v", input, reqs[0])
|
||||
// Do not sue assert.Equal, which would do a deep contents comparison; we want to compare
|
||||
// Do not use assert.Equal, which would do a deep contents comparison; we want to compare
|
||||
// the pointers. Also, == does not work on slices; so test that the slices start at the
|
||||
// same element and have the same length.
|
||||
assert.True(t, &(reqs[0]) == &(expected[0]), comment)
|
||||
|
|
@ -236,7 +161,7 @@ func TestPolicyContextRequirementsForImage(t *testing.T) {
|
|||
}
|
||||
|
||||
// Image without a Docker reference identity
|
||||
_, err = pc.requirementsForImage(refImageMock{nil})
|
||||
_, err = pc.requirementsForImageRef(pcImageReferenceMock{nil})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,6 +95,12 @@ func (ref refImageReferenceMock) StringWithinTransport() string {
|
|||
func (ref refImageReferenceMock) DockerReference() reference.Named {
|
||||
return ref.Named
|
||||
}
|
||||
func (ref refImageReferenceMock) PolicyConfigurationIdentity() string {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageReferenceMock) PolicyConfigurationNamespaces() []string {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageReferenceMock) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,22 @@ type ImageReference interface {
|
|||
// not e.g. after redirect or alias processing), or nil if unknown/not applicable.
|
||||
DockerReference() reference.Named
|
||||
|
||||
// PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup.
|
||||
// This MUST reflect user intent, not e.g. after processing of third-party redirects or aliases;
|
||||
// The value SHOULD be fully explicit about its semantics, with no hidden defaults, AND canonical
|
||||
// (i.e. various references with exactly the same semantics should return the same configuration identity)
|
||||
// It is fine for the return value to be equal to StringWithinTransport(), and it is desirable but
|
||||
// not required/guaranteed that it will be a valid input to Transport().ParseReference().
|
||||
// Returns "" if configuration identities for these references are not supported.
|
||||
PolicyConfigurationIdentity() string
|
||||
|
||||
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
|
||||
// for if explicit configuration for PolicyConfigurationIdentity() is not set. The list will be processed
|
||||
// in order, terminating on first match, and an implicit "" is always checked at the end.
|
||||
// It is STRONGLY recommended for the first element, if any, to be a prefix of PolicyConfigurationIdentity(),
|
||||
// and each following element to be a prefix of the element preceding it.
|
||||
PolicyConfigurationNamespaces() []string
|
||||
|
||||
// NewImage returns a types.Image for this reference.
|
||||
NewImage(certPath string, tlsVerify bool) (Image, error)
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
|
|
|
|||
Loading…
Reference in New Issue