diff --git a/cmd/skopeo/layers.go b/cmd/skopeo/layers.go index e697f0d7..313d3558 100644 --- a/cmd/skopeo/layers.go +++ b/cmd/skopeo/layers.go @@ -37,7 +37,10 @@ var layersCmd = cli.Command{ if err != nil { return err } - tmpDirRef := directory.NewReference(tmpDir) + tmpDirRef, err := directory.NewReference(tmpDir) + if err != nil { + return err + } dest, err := tmpDirRef.NewImageDestination("", true) if err != nil { return err diff --git a/vendor/github.com/containers/image/directory/directory_transport.go b/vendor/github.com/containers/image/directory/directory_transport.go index 821273c1..34800e34 100644 --- a/vendor/github.com/containers/image/directory/directory_transport.go +++ b/vendor/github.com/containers/image/directory/directory_transport.go @@ -1,6 +1,11 @@ package directory import ( + "errors" + "fmt" + "strings" + + "github.com/containers/image/directory/explicitfilepath" "github.com/containers/image/image" "github.com/containers/image/types" "github.com/docker/docker/reference" @@ -17,13 +22,35 @@ func (t dirTransport) Name() string { // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference. func (t dirTransport) ParseReference(reference string) (types.ImageReference, error) { - return NewReference(reference), nil + return NewReference(reference) +} + +// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys +// (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value). +// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion. +// scope passed to this function will not be "", that value is always allowed. +func (t dirTransport) ValidatePolicyConfigurationScope(scope string) error { + if !strings.HasPrefix(scope, "/") { + return fmt.Errorf("Invalid scope %s: must be an absolute path", scope) + } + // Refuse also "/", otherwise "/" and "" would have the same semantics, + // and "" could be unexpectedly shadowed by the "/" entry. + if scope == "/" { + return errors.New(`Invalid scope "/": Use the generic default scope ""`) + } + return nil } // dirReference is an ImageReference for directory paths. type dirReference struct { // Note that the interpretation of paths below depends on the underlying filesystem state, which may change under us at any time! - path string // As specified by the user. May be relative, contain symlinks, etc. + // Either of the paths may point to a different, or no, inode over time. resolvedPath may contain symbolic links, and so on. + + // Generally we follow the intent of the user, and use the "path" member for filesystem operations (e.g. the user can use a relative path to avoid + // being exposed to symlinks and renames in the parent directories to the working directory). + // (But in general, we make no attempt to be completely safe against concurrent hostile filesystem modifications.) + path string // As specified by the user. May be relative, contain symlinks, etc. + resolvedPath string // Absolute path with no symlinks, at least at the time of its creation. Primarily used for policy namespaces. } // There is no directory.ParseReference because it is rather pointless. @@ -32,8 +59,15 @@ type dirReference struct { // can use directory.NewReference. // NewReference returns a directory reference for a specified path. -func NewReference(path string) types.ImageReference { - return dirReference{path: path} +// +// We do not expose an API supplying the resolvedPath; we could, but recomputing it +// is generally cheap enough that we prefer being confident about the properties of resolvedPath. +func NewReference(path string) (types.ImageReference, error) { + resolved, err := explicitfilepath.ResolvePathToFullyExplicit(path) + if err != nil { + return nil, err + } + return dirReference{path: path, resolvedPath: resolved}, nil } func (ref dirReference) Transport() types.ImageTransport { @@ -56,6 +90,38 @@ 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 ref.resolvedPath +} + +// 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 { + res := []string{} + path := ref.resolvedPath + for { + lastSlash := strings.LastIndex(path, "/") + if lastSlash == -1 || lastSlash == 0 { + break + } + path = path[:lastSlash] + res = append(res, path) + } + // Note that we do not include "/"; it is redundant with the default "" global default, + // and rejected by dirTransport.ValidatePolicyConfigurationScope above. + return res +} + // NewImage returns a types.Image for this reference. func (ref dirReference) NewImage(certPath string, tlsVerify bool) (types.Image, error) { src := newImageSource(ref) diff --git a/vendor/github.com/containers/image/directory/explicitfilepath/path.go b/vendor/github.com/containers/image/directory/explicitfilepath/path.go new file mode 100644 index 00000000..b4ff4d08 --- /dev/null +++ b/vendor/github.com/containers/image/directory/explicitfilepath/path.go @@ -0,0 +1,55 @@ +package explicitfilepath + +import ( + "fmt" + "os" + "path/filepath" +) + +// ResolvePathToFullyExplicit returns the input path converted to an absolute, no-symlinks, cleaned up path. +// To do so, all elements of the input path must exist; as a special case, the final component may be +// a non-existent name (but not a symlink pointing to a non-existent name) +// This is intended as a a helper for implementations of types.ImageReference.PolicyConfigurationIdentity etc. +func ResolvePathToFullyExplicit(path string) (string, error) { + switch _, err := os.Lstat(path); { + case err == nil: + return resolveExistingPathToFullyExplicit(path) + case os.IsNotExist(err): + parent, file := filepath.Split(path) + resolvedParent, err := resolveExistingPathToFullyExplicit(parent) + if err != nil { + return "", err + } + if file == "." || file == ".." { + // Coverage: This can happen, but very rarely: if we have successfully resolved the parent, both "." and ".." in it should have been resolved as well. + // This can still happen if there is a filesystem race condition, causing the Lstat() above to fail but the later resolution to succeed. + // We do not care to promise anything if such filesystem race conditions can happen, but we definitely don't want to return "."/".." components + // in the resulting path, and especially not at the end. + return "", fmt.Errorf("Unexpectedly missing special filename component in %s", path) + } + resolvedPath := filepath.Join(resolvedParent, file) + // As a sanity check, ensure that there are no "." or ".." components. + cleanedResolvedPath := filepath.Clean(resolvedPath) + if cleanedResolvedPath != resolvedPath { + // Coverage: This should never happen. + return "", fmt.Errorf("Internal inconsistency: Path %s resolved to %s still cleaned up to %s", path, resolvedPath, cleanedResolvedPath) + } + return resolvedPath, nil + default: // err != nil, unrecognized + return "", err + } +} + +// resolveExistingPathToFullyExplicit is the same as ResolvePathToFullyExplicit, +// but without the special case for missing final component. +func resolveExistingPathToFullyExplicit(path string) (string, error) { + resolved, err := filepath.Abs(path) + if err != nil { + return "", err // Coverage: This can fail only if os.Getwd() fails. + } + resolved, err = filepath.EvalSymlinks(resolved) + if err != nil { + return "", err + } + return filepath.Clean(resolved), nil +} diff --git a/vendor/github.com/containers/image/docker/docker_client.go b/vendor/github.com/containers/image/docker/docker_client.go index ca52ad10..66c1e9c6 100644 --- a/vendor/github.com/containers/image/docker/docker_client.go +++ b/vendor/github.com/containers/image/docker/docker_client.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/homedir" @@ -39,7 +40,7 @@ type dockerClient struct { password string wwwAuthenticate string // Cache of a value set by ping() if scheme is not empty scheme string // Cache of a value returned by a successful ping() if not empty - transport *http.Transport + client *http.Client } // newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry) @@ -70,11 +71,17 @@ func newDockerClient(refHostname, certPath string, tlsVerify bool) (*dockerClien TLSClientConfig: tlsc, } } + client := &http.Client{ + Timeout: 1 * time.Minute, + } + if tr != nil { + client.Transport = tr + } return &dockerClient{ - registry: registry, - username: username, - password: password, - transport: tr, + registry: registry, + username: username, + password: password, + client: client, }, nil } @@ -112,12 +119,8 @@ func (c *dockerClient) makeRequestToResolvedURL(method, url string, headers map[ return nil, err } } - client := &http.Client{} - if c.transport != nil { - client.Transport = c.transport - } logrus.Debugf("%s %s", method, url) - res, err := client.Do(req) + res, err := c.client.Do(req) if err != nil { return nil, err } @@ -134,11 +137,7 @@ func (c *dockerClient) setupRequestAuth(req *http.Request) error { req.SetBasicAuth(c.username, c.password) return nil case "Bearer": - client := &http.Client{} - if c.transport != nil { - client.Transport = c.transport - } - res, err := client.Do(req) + res, err := c.client.Do(req) if err != nil { return err } @@ -294,13 +293,9 @@ type pingResponse struct { } func (c *dockerClient) ping() (*pingResponse, error) { - client := &http.Client{} - if c.transport != nil { - client.Transport = c.transport - } ping := func(scheme string) (*pingResponse, error) { url := fmt.Sprintf(baseURL, scheme, c.registry) - resp, err := client.Get(url) + resp, err := c.client.Get(url) logrus.Debugf("Ping %s err %#v", url, err) if err != nil { return nil, err diff --git a/vendor/github.com/containers/image/docker/docker_transport.go b/vendor/github.com/containers/image/docker/docker_transport.go index d20b2a54..0a88741e 100644 --- a/vendor/github.com/containers/image/docker/docker_transport.go +++ b/vendor/github.com/containers/image/docker/docker_transport.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/containers/image/docker/policyconfiguration" "github.com/containers/image/types" "github.com/docker/docker/reference" ) @@ -22,6 +23,17 @@ func (t dockerTransport) ParseReference(reference string) (types.ImageReference, return ParseReference(reference) } +// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys +// (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value). +// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion. +// scope passed to this function will not be "", that value is always allowed. +func (t dockerTransport) ValidatePolicyConfigurationScope(scope string) error { + // FIXME? We could be verifying the various character set and length restrictions + // from docker/distribution/reference.regexp.go, but other than that there + // are few semantically invalid strings. + return nil +} + // dockerReference is an ImageReference for Docker images. type dockerReference struct { ref reference.Named // By construction we know that !reference.IsNameOnly(ref) @@ -79,6 +91,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) diff --git a/vendor/github.com/containers/image/docker/policyconfiguration/naming.go b/vendor/github.com/containers/image/docker/policyconfiguration/naming.go new file mode 100644 index 00000000..ace300e4 --- /dev/null +++ b/vendor/github.com/containers/image/docker/policyconfiguration/naming.go @@ -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 +} diff --git a/vendor/github.com/containers/image/oci/oci_transport.go b/vendor/github.com/containers/image/oci/oci_transport.go index ab069586..101361e7 100644 --- a/vendor/github.com/containers/image/oci/oci_transport.go +++ b/vendor/github.com/containers/image/oci/oci_transport.go @@ -6,6 +6,7 @@ import ( "regexp" "strings" + "github.com/containers/image/directory/explicitfilepath" "github.com/containers/image/types" "github.com/docker/docker/reference" ) @@ -24,14 +25,53 @@ func (t ociTransport) ParseReference(reference string) (types.ImageReference, er return ParseReference(reference) } +var refRegexp = regexp.MustCompile(`^([A-Za-z0-9._-]+)+$`) + +// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys +// (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value). +// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion. +// scope passed to this function will not be "", that value is always allowed. +func (t ociTransport) ValidatePolicyConfigurationScope(scope string) error { + var dir string + sep := strings.LastIndex(scope, ":") + if sep == -1 { + dir = scope + } else { + dir = scope[:sep] + tag := scope[sep+1:] + if !refRegexp.MatchString(tag) { + return fmt.Errorf("Invalid tag %s", tag) + } + } + + if strings.Contains(dir, ":") { + return fmt.Errorf("Invalid OCI reference %s: path contains a colon", scope) + } + + if !strings.HasPrefix(dir, "/") { + return fmt.Errorf("Invalid scope %s: must be an absolute path", scope) + } + // Refuse also "/", otherwise "/" and "" would have the same semantics, + // and "" could be unexpectedly shadowed by the "/" entry. + // (Note: we do allow "/:sometag", a bit ridiculous but why refuse it?) + if scope == "/" { + return errors.New(`Invalid scope "/": Use the generic default scope ""`) + } + return nil +} + // ociReference is an ImageReference for OCI directory paths. type ociReference struct { // Note that the interpretation of paths below depends on the underlying filesystem state, which may change under us at any time! - dir string // As specified by the user. May be relative, contain symlinks, etc. - tag string -} + // Either of the paths may point to a different, or no, inode over time. resolvedDir may contain symbolic links, and so on. -var refRegexp = regexp.MustCompile(`^([A-Za-z0-9._-]+)+$`) + // Generally we follow the intent of the user, and use the "dir" member for filesystem operations (e.g. the user can use a relative path to avoid + // being exposed to symlinks and renames in the parent directories to the working directory). + // (But in general, we make no attempt to be completely safe against concurrent hostile filesystem modifications.) + dir string // As specified by the user. May be relative, contain symlinks, etc. + resolvedDir string // Absolute path with no symlinks, at least at the time of its creation. Primarily used for policy namespaces. + tag string +} // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference. func ParseReference(reference string) (types.ImageReference, error) { @@ -47,12 +87,24 @@ func ParseReference(reference string) (types.ImageReference, error) { return nil, fmt.Errorf("Invalid tag %s", tag) } } - return NewReference(dir, tag), nil + return NewReference(dir, tag) } // NewReference returns an OCI reference for a directory and a tag. -func NewReference(dir, tag string) types.ImageReference { - return ociReference{dir: dir, tag: tag} +// +// We do not expose an API supplying the resolvedDir; we could, but recomputing it +// is generally cheap enough that we prefer being confident about the properties of resolvedDir. +func NewReference(dir, tag string) (types.ImageReference, error) { + resolved, err := explicitfilepath.ResolvePathToFullyExplicit(dir) + if err != nil { + return nil, err + } + // This is necessary to prevent directory paths returned by PolicyConfigurationNamespaces + // from being ambiguous with values of PolicyConfigurationIdentity. + if strings.Contains(resolved, ":") { + return nil, fmt.Errorf("Invalid OCI reference %s:%s: path %s contains a colon", dir, tag, resolved) + } + return ociReference{dir: dir, resolvedDir: resolved, tag: tag}, nil } func (ref ociReference) Transport() types.ImageTransport { @@ -75,6 +127,38 @@ 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 fmt.Sprintf("%s:%s", ref.resolvedDir, ref.tag) +} + +// 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 { + res := []string{} + path := ref.resolvedDir + for { + lastSlash := strings.LastIndex(path, "/") + // Note that we do not include "/"; it is redundant with the default "" global default, + // and rejected by ociTransport.ValidatePolicyConfigurationScope above. + if lastSlash == -1 || path == "/" { + break + } + res = append(res, path) + path = path[:lastSlash] + } + return res +} + // 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") diff --git a/vendor/github.com/containers/image/openshift/openshift.go b/vendor/github.com/containers/image/openshift/openshift.go index 60bea41e..e2311747 100644 --- a/vendor/github.com/containers/image/openshift/openshift.go +++ b/vendor/github.com/containers/image/openshift/openshift.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net/http" "strings" + "time" "github.com/Sirupsen/logrus" "github.com/containers/image/docker" @@ -52,6 +53,7 @@ func newOpenshiftClient(ref openshiftReference) (*openshiftClient, error) { if *baseURL != *ref.baseURL { return nil, fmt.Errorf("Unexpected baseURL mismatch: default %#v, reference %#v", *baseURL, *ref.baseURL) } + httpClient.Timeout = 1 * time.Minute return &openshiftClient{ ref: ref, diff --git a/vendor/github.com/containers/image/openshift/openshift_transport.go b/vendor/github.com/containers/image/openshift/openshift_transport.go index c9c92072..13f99c6b 100644 --- a/vendor/github.com/containers/image/openshift/openshift_transport.go +++ b/vendor/github.com/containers/image/openshift/openshift_transport.go @@ -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" ) @@ -25,6 +26,22 @@ func (t openshiftTransport) ParseReference(reference string) (types.ImageReferen return ParseReference(reference) } +// Note that imageNameRegexp is namespace/stream:tag, this +// is HOSTNAME/namespace/stream:tag or parent prefixes. +// Keep this in sync with imageNameRegexp! +var scopeRegexp = regexp.MustCompile("^[^/]*(/[^:/]*(/[^:/]*(:[^:/]*)?)?)?$") + +// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys +// (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value). +// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion. +// scope passed to this function will not be "", that value is always allowed. +func (t openshiftTransport) ValidatePolicyConfigurationScope(scope string) error { + if scopeRegexp.FindStringIndex(scope) == nil { + return fmt.Errorf("Invalid scope name %s", scope) + } + return nil +} + // openshiftReference is an ImageReference for OpenShift images. type openshiftReference struct { baseURL *url.URL @@ -35,6 +52,7 @@ type openshiftReference struct { } // FIXME: Is imageName like this a good way to refer to OpenShift images? +// Keep this in sync with scopeRegexp! var imageNameRegexp = regexp.MustCompile("^([^:/]*)/([^:/]*):([^:/]*)$") // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OpenShift ImageReference. @@ -111,6 +129,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") diff --git a/vendor/github.com/containers/image/signature/json.go b/vendor/github.com/containers/image/signature/json.go index a612db08..12d14c02 100644 --- a/vendor/github.com/containers/image/signature/json.go +++ b/vendor/github.com/containers/image/signature/json.go @@ -59,7 +59,7 @@ func stringField(m map[string]interface{}, fieldName string) (string, error) { // (including duplicated keys, unrecognized keys, and non-matching types). Uses fieldResolver to // determine the destination for a field value, which should return a pointer to the destination if valid, or nil if the key is rejected. // -// The fieldResolver approach is useful for decoding the Policy.Specific map; using it for structs is a bit lazy, +// The fieldResolver approach is useful for decoding the Policy.Transports map; using it for structs is a bit lazy, // we could use reflection to automate this. Later? func paranoidUnmarshalJSONObject(data []byte, fieldResolver func(string) interface{}) error { seenKeys := map[string]struct{}{} diff --git a/vendor/github.com/containers/image/signature/policy_config.go b/vendor/github.com/containers/image/signature/policy_config.go index 7a57559d..1f74e19d 100644 --- a/vendor/github.com/containers/image/signature/policy_config.go +++ b/vendor/github.com/containers/image/signature/policy_config.go @@ -15,9 +15,12 @@ package signature import ( "encoding/json" + "errors" "fmt" "io/ioutil" + "github.com/containers/image/transports" + "github.com/containers/image/types" "github.com/docker/docker/reference" ) @@ -55,13 +58,13 @@ var _ json.Unmarshaler = (*Policy)(nil) // UnmarshalJSON implements the json.Unmarshaler interface. func (p *Policy) UnmarshalJSON(data []byte) error { *p = Policy{} - specific := policySpecificMap{} + transports := policyTransportsMap{} if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} { switch key { case "default": return &p.Default - case "specific": - return &specific + case "transports": + return &transports default: return nil } @@ -72,29 +75,79 @@ func (p *Policy) UnmarshalJSON(data []byte) error { if p.Default == nil { return InvalidPolicyFormatError("Default policy is missing") } - p.Specific = map[string]PolicyRequirements(specific) + p.Transports = map[string]PolicyTransportScopes(transports) return nil } -// policySpecificMap is a specialization of this map type for the strict JSON parsing semantics appropriate for the Policy.Specific member. -type policySpecificMap map[string]PolicyRequirements +// policyTransportsMap is a specialization of this map type for the strict JSON parsing semantics appropriate for the Policy.Transports member. +type policyTransportsMap map[string]PolicyTransportScopes -// Compile-time check that policySpecificMap implements json.Unmarshaler. -var _ json.Unmarshaler = (*policySpecificMap)(nil) +// Compile-time check that policyTransportsMap implements json.Unmarshaler. +var _ json.Unmarshaler = (*policyTransportsMap)(nil) // UnmarshalJSON implements the json.Unmarshaler interface. -func (m *policySpecificMap) UnmarshalJSON(data []byte) error { +func (m *policyTransportsMap) UnmarshalJSON(data []byte) error { + // We can't unmarshal directly into map values because it is not possible to take an address of a map value. + // So, use a temporary map of pointers-to-slices and convert. + tmpMap := map[string]*PolicyTransportScopes{} + if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} { + transport, ok := transports.KnownTransports[key] + if !ok { + return nil + } + // paranoidUnmarshalJSONObject detects key duplication for us, check just to be safe. + if _, ok := tmpMap[key]; ok { + return nil + } + ptsWithTransport := policyTransportScopesWithTransport{ + transport: transport, + dest: &PolicyTransportScopes{}, // This allocates a new instance on each call. + } + tmpMap[key] = ptsWithTransport.dest + return &ptsWithTransport + }); err != nil { + return err + } + for key, ptr := range tmpMap { + (*m)[key] = *ptr + } + return nil +} + +// Compile-time check that PolicyTransportScopes "implements"" json.Unmarshaler. +// we want to only use policyTransportScopesWithTransport +var _ json.Unmarshaler = (*PolicyTransportScopes)(nil) + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (m *PolicyTransportScopes) UnmarshalJSON(data []byte) error { + return errors.New("Do not try to unmarshal PolicyTransportScopes directly") +} + +// policyTransportScopesWithTransport is a way to unmarshal a PolicyTransportScopes +// while validating using a specific ImageTransport. +type policyTransportScopesWithTransport struct { + transport types.ImageTransport + dest *PolicyTransportScopes +} + +// Compile-time check that policyTransportScopesWithTransport implements json.Unmarshaler. +var _ json.Unmarshaler = (*policyTransportScopesWithTransport)(nil) + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (m *policyTransportScopesWithTransport) UnmarshalJSON(data []byte) error { // We can't unmarshal directly into map values because it is not possible to take an address of a map value. // So, use a temporary map of pointers-to-slices and convert. tmpMap := map[string]*PolicyRequirements{} if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} { - // FIXME? We might want to validate the scope format. - // Note that reference.ParseNamed is unsuitable; it would understand "example.com" as - // "docker.io/library/example.com" // paranoidUnmarshalJSONObject detects key duplication for us, check just to be safe. if _, ok := tmpMap[key]; ok { return nil } + if key != "" { + if err := m.transport.ValidatePolicyConfigurationScope(key); err != nil { + return nil + } + } ptr := &PolicyRequirements{} // This allocates a new instance on each call. tmpMap[key] = ptr return ptr @@ -102,7 +155,7 @@ func (m *policySpecificMap) UnmarshalJSON(data []byte) error { return err } for key, ptr := range tmpMap { - (*m)[key] = *ptr + (*m.dest)[key] = *ptr } return nil } diff --git a/vendor/github.com/containers/image/signature/policy_eval.go b/vendor/github.com/containers/image/signature/policy_eval.go index 6594b665..ab3a50f9 100644 --- a/vendor/github.com/containers/image/signature/policy_eval.go +++ b/vendor/github.com/containers/image/signature/policy_eval.go @@ -7,12 +7,9 @@ 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,75 +124,41 @@ 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. - } - return res, nil +// policyIdentityLogName returns a string description of the image identity for policy purposes. +// ONLY use this for log messages, not for any decisions! +func policyIdentityLogName(ref types.ImageReference) string { + return ref.Transport().Name() + ":" + ref.PolicyConfigurationIdentity() } -// 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) - 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 { - if req, ok := pc.Policy.Specific[name]; ok { - logrus.Debugf(" Using specific policy section %s", name) - return req, nil +// requirementsForImageRef selects the appropriate requirements for ref. +func (pc *PolicyContext) requirementsForImageRef(ref types.ImageReference) PolicyRequirements { + // Do we have a PolicyTransportScopes for this transport? + transportName := ref.Transport().Name() + if transportScopes, ok := pc.Policy.Transports[transportName]; ok { + // Look for a full match. + identity := ref.PolicyConfigurationIdentity() + if req, ok := transportScopes[identity]; ok { + logrus.Debugf(` Using transport "%s" policy section %s`, transportName, identity) + return req } - lastSlash := strings.LastIndex(name, "/") - if lastSlash == -1 { - break + // Look for a match of the possible parent namespaces. + for _, name := range ref.PolicyConfigurationNamespaces() { + if req, ok := transportScopes[name]; ok { + logrus.Debugf(` Using transport "%s" specific policy section %s`, transportName, name) + return req + } + } + + // Look for a default match for the transport. + if req, ok := transportScopes[""]; ok { + logrus.Debugf(` Using transport "%s" policy section ""`, transportName) + return req } - name = name[:lastSlash] } logrus.Debugf(" Using default policy section") - return pc.Policy.Default, nil + return pc.Policy.Default } // GetSignaturesWithAcceptedAuthor returns those signatures from an image @@ -222,12 +185,8 @@ func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(image types.Image) (sig } }() - logrus.Debugf("GetSignaturesWithAcceptedAuthor for image %s", image.Reference().DockerReference()) - - reqs, err := pc.requirementsForImage(image) - if err != nil { - return nil, err - } + logrus.Debugf("GetSignaturesWithAcceptedAuthor for image %s", policyIdentityLogName(image.Reference())) + reqs := pc.requirementsForImageRef(image.Reference()) // FIXME: rename Signatures to UnverifiedSignatures unverifiedSignatures, err := image.Signatures() @@ -306,12 +265,8 @@ func (pc *PolicyContext) IsRunningImageAllowed(image types.Image) (res bool, fin } }() - logrus.Debugf("IsRunningImageAllowed for image %s", image.Reference().DockerReference()) - - reqs, err := pc.requirementsForImage(image) - if err != nil { - return false, err - } + logrus.Debugf("IsRunningImageAllowed for image %s", policyIdentityLogName(image.Reference())) + reqs := pc.requirementsForImageRef(image.Reference()) if len(reqs) == 0 { return false, PolicyRequirementError("List of verification policy requirements must not be empty") diff --git a/vendor/github.com/containers/image/signature/policy_types.go b/vendor/github.com/containers/image/signature/policy_types.go index 8e614cce..ef870601 100644 --- a/vendor/github.com/containers/image/signature/policy_types.go +++ b/vendor/github.com/containers/image/signature/policy_types.go @@ -8,18 +8,22 @@ package signature // Policy defines requirements for considering a signature valid. type Policy struct { - // Default applies to any image which does not have a matching policy in Specific. - Default PolicyRequirements `json:"default"` - // Specific applies to images matching scope, the map key. - // Scope is hostname[/zero/or/more/namespaces[/repository[:tag|@digest]]]; note that in order to be - // unambiguous, this must use a fully expanded format, e.g. "docker.io/library/busybox" or - // "docker.io/library", not "busybox" or "library". - // FIXME: Scope syntax - should it be namespaced docker:something ? Or, in the worst case, a composite object (we couldn't use a JSON map) - // Most specific scope wins, duplication is prohibited (hard failure). - // Defaults to an empty map if not specified. - Specific map[string]PolicyRequirements `json:"specific"` + // Default applies to any image which does not have a matching policy in Transports. + // Note that this can happen even if a matching PolicyTransportScopes exists in Transports + // if the image matches none of the scopes. + Default PolicyRequirements `json:"default"` + Transports map[string]PolicyTransportScopes `json:"transports"` } +// PolicyTransportScopes defines policies for images for a specific transport, +// for various scopes, the map keys. +// Scopes are defined by the transport (types.ImageReference.PolicyConfigurationIdentity etc.); +// there is one scope precisely matching to a single image, and namespace scopes as prefixes +// of the single-image scope. (e.g. hostname[/zero[/or[/more[/namespaces[/individualimage]]]]]) +// The empty scope, if exists, is considered a parent namespace of all other scopes. +// Most specific scope wins, duplication is prohibited (hard failure). +type PolicyTransportScopes map[string]PolicyRequirements + // PolicyRequirements is a set of requirements applying to a set of images; each of them must be satisfied (though perhaps each by a different signature). // Must not be empty, frequently will only contain a single element. type PolicyRequirements []PolicyRequirement diff --git a/vendor/github.com/containers/image/types/types.go b/vendor/github.com/containers/image/types/types.go index 29860f88..086e0080 100644 --- a/vendor/github.com/containers/image/types/types.go +++ b/vendor/github.com/containers/image/types/types.go @@ -24,6 +24,11 @@ type ImageTransport interface { Name() string // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference. ParseReference(reference string) (ImageReference, error) + // ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys + // (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value). + // It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion. + // scope passed to this function will not be "", that value is always allowed. + ValidatePolicyConfigurationScope(scope string) error } // ImageReference is an abstracted way to refer to an image location, namespaced within an ImageTransport. @@ -49,6 +54,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.