mirror of
https://github.com/containers/skopeo.git
synced 2025-06-29 16:17:44 +00:00
Merge pull request #170 from mtrmac/docker-lookaside
Implement a lookaside storage for signatures of images in Docker registries
This commit is contained in:
commit
d6be447ce9
@ -56,6 +56,11 @@ func createApp() *cli.App {
|
|||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Path to a trust policy file",
|
Usage: "Path to a trust policy file",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "registries.d",
|
||||||
|
Value: "",
|
||||||
|
Usage: "use registry configuration files in `DIR` (e.g. for docker signature storage)",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
app.Before = func(c *cli.Context) error {
|
app.Before = func(c *cli.Context) error {
|
||||||
if c.GlobalBool("debug") {
|
if c.GlobalBool("debug") {
|
||||||
|
@ -8,10 +8,10 @@ import (
|
|||||||
|
|
||||||
// contextFromGlobalOptions returns a types.SystemContext depending on c.
|
// contextFromGlobalOptions returns a types.SystemContext depending on c.
|
||||||
func contextFromGlobalOptions(c *cli.Context) *types.SystemContext {
|
func contextFromGlobalOptions(c *cli.Context) *types.SystemContext {
|
||||||
certPath := c.GlobalString("cert-path")
|
|
||||||
tlsVerify := c.GlobalBool("tls-verify") // FIXME!! defaults to false
|
tlsVerify := c.GlobalBool("tls-verify") // FIXME!! defaults to false
|
||||||
return &types.SystemContext{
|
return &types.SystemContext{
|
||||||
DockerCertPath: certPath,
|
RegistriesDirPath: c.GlobalString("registries.d"),
|
||||||
|
DockerCertPath: c.GlobalString("cert-path"),
|
||||||
DockerInsecureSkipTLSVerify: !tlsVerify,
|
DockerInsecureSkipTLSVerify: !tlsVerify,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,8 @@ Most commands refer to container images, using a _transport_`:`_details_ format.
|
|||||||
|
|
||||||
**--policy** _path-to-policy_ Path to a policy.json file to use for verifying signatures and deciding whether an image is trusted, overriding the default trust policy file.
|
**--policy** _path-to-policy_ Path to a policy.json file to use for verifying signatures and deciding whether an image is trusted, overriding the default trust policy file.
|
||||||
|
|
||||||
|
**--registries.d** _dir_ use registry configuration files in _dir_ (e.g. for docker signature storage), overriding the default path.
|
||||||
|
|
||||||
**--tls-verify** _bool-value_ Verify certificates
|
**--tls-verify** _bool-value_ Verify certificates
|
||||||
|
|
||||||
**--help**|**-h** Show help
|
**--help**|**-h** Show help
|
||||||
@ -139,6 +141,10 @@ show help for `skopeo`
|
|||||||
Default trust policy file, if **--policy** is not specified.
|
Default trust policy file, if **--policy** is not specified.
|
||||||
The policy format is documented in https://github.com/containers/image/blob/master/docs/policy.json.md .
|
The policy format is documented in https://github.com/containers/image/blob/master/docs/policy.json.md .
|
||||||
|
|
||||||
|
**/etc/containers/registries.d**
|
||||||
|
Default directory containing registry configuration, if **--registries.d** is not specified.
|
||||||
|
The contents of this directory are documented in https://github.com/containers/image/blob/master/docs/registries.d.md .
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
## skopeo copy
|
## skopeo copy
|
||||||
|
19
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
19
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
@ -42,17 +42,17 @@ type dockerClient struct {
|
|||||||
wwwAuthenticate string // Cache of a value set by ping() if scheme is not empty
|
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
|
scheme string // Cache of a value returned by a successful ping() if not empty
|
||||||
client *http.Client
|
client *http.Client
|
||||||
|
signatureBase signatureStorageBase
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
// newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
||||||
func newDockerClient(ctx *types.SystemContext, refHostname string) (*dockerClient, error) {
|
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
|
||||||
var registry string
|
func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool) (*dockerClient, error) {
|
||||||
if refHostname == dockerHostname {
|
registry := ref.ref.Hostname()
|
||||||
|
if registry == dockerHostname {
|
||||||
registry = dockerRegistry
|
registry = dockerRegistry
|
||||||
} else {
|
|
||||||
registry = refHostname
|
|
||||||
}
|
}
|
||||||
username, password, err := getAuth(refHostname)
|
username, password, err := getAuth(ref.ref.Hostname())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -78,11 +78,18 @@ func newDockerClient(ctx *types.SystemContext, refHostname string) (*dockerClien
|
|||||||
if tr != nil {
|
if tr != nil {
|
||||||
client.Transport = tr
|
client.Transport = tr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sigBase, err := configuredSignatureStorageBase(ctx, ref, write)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &dockerClient{
|
return &dockerClient{
|
||||||
registry: registry,
|
registry: registry,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
client: client,
|
client: client,
|
||||||
|
signatureBase: sigBase,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
91
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
91
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
@ -8,6 +8,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
@ -18,11 +21,13 @@ import (
|
|||||||
type dockerImageDestination struct {
|
type dockerImageDestination struct {
|
||||||
ref dockerReference
|
ref dockerReference
|
||||||
c *dockerClient
|
c *dockerClient
|
||||||
|
// State
|
||||||
|
manifestDigest string // or "" if not yet known.
|
||||||
}
|
}
|
||||||
|
|
||||||
// newImageDestination creates a new ImageDestination for the specified image reference.
|
// newImageDestination creates a new ImageDestination for the specified image reference.
|
||||||
func newImageDestination(ctx *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
func newImageDestination(ctx *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
||||||
c, err := newDockerClient(ctx, ref.ref.Hostname())
|
c, err := newDockerClient(ctx, ref, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -144,6 +149,7 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
d.manifestDigest = digest
|
||||||
url := fmt.Sprintf(manifestURL, d.ref.ref.RemoteName(), digest)
|
url := fmt.Sprintf(manifestURL, d.ref.ref.RemoteName(), digest)
|
||||||
|
|
||||||
headers := map[string][]string{}
|
headers := map[string][]string{}
|
||||||
@ -168,10 +174,89 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
|
func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
|
||||||
if len(signatures) != 0 {
|
// FIXME? This overwrites files one at a time, definitely not atomic.
|
||||||
return fmt.Errorf("Pushing signatures to a Docker Registry is not supported")
|
// A failure when updating signatures with a reordered copy could lose some of them.
|
||||||
|
|
||||||
|
// Skip dealing with the manifest digest if not necessary.
|
||||||
|
if len(signatures) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if d.c.signatureBase == nil {
|
||||||
|
return fmt.Errorf("Pushing signatures to a Docker Registry is not supported, and there is no applicable signature storage configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: This assumption that signatures are stored after the manifest rather breaks the model.
|
||||||
|
if d.manifestDigest == "" {
|
||||||
|
return fmt.Errorf("Unknown manifest digest, can't add signatures")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, signature := range signatures {
|
||||||
|
url := signatureStorageURL(d.c.signatureBase, d.manifestDigest, i)
|
||||||
|
if url == nil {
|
||||||
|
return fmt.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
|
||||||
|
}
|
||||||
|
err := d.putOneSignature(url, signature)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove any other signatures, if present.
|
||||||
|
// We stop at the first missing signature; if a previous deleting loop aborted
|
||||||
|
// prematurely, this may not clean up all of them, but one missing signature
|
||||||
|
// is enough for dockerImageSource to stop looking for other signatures, so that
|
||||||
|
// is sufficient.
|
||||||
|
for i := len(signatures); ; i++ {
|
||||||
|
url := signatureStorageURL(d.c.signatureBase, d.manifestDigest, i)
|
||||||
|
if url == nil {
|
||||||
|
return fmt.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
|
||||||
|
}
|
||||||
|
missing, err := d.c.deleteOneSignature(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if missing {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// putOneSignature stores one signature to url.
|
||||||
|
func (d *dockerImageDestination) putOneSignature(url *url.URL, signature []byte) error {
|
||||||
|
switch url.Scheme {
|
||||||
|
case "file":
|
||||||
|
logrus.Debugf("Writing to %s", url.Path)
|
||||||
|
err := os.MkdirAll(filepath.Dir(url.Path), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(url.Path, signature, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unsupported scheme when writing signature to %s", url.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteOneSignature deletes a signature from url, if it exists.
|
||||||
|
// If it successfully determines that the signature does not exist, returns (true, nil)
|
||||||
|
func (c *dockerClient) deleteOneSignature(url *url.URL) (missing bool, err error) {
|
||||||
|
switch url.Scheme {
|
||||||
|
case "file":
|
||||||
|
logrus.Debugf("Deleting %s", url.Path)
|
||||||
|
err := os.Remove(url.Path)
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("Unsupported scheme when deleting signature from %s", url.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
|
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
|
||||||
|
132
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
132
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
@ -6,6 +6,8 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
@ -26,6 +28,9 @@ type dockerImageSource struct {
|
|||||||
ref dockerReference
|
ref dockerReference
|
||||||
requestedManifestMIMETypes []string
|
requestedManifestMIMETypes []string
|
||||||
c *dockerClient
|
c *dockerClient
|
||||||
|
// State
|
||||||
|
cachedManifest []byte // nil if not loaded yet
|
||||||
|
cachedManifestMIMEType string // Only valid if cachedManifest != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newImageSource creates a new ImageSource for the specified image reference,
|
// newImageSource creates a new ImageSource for the specified image reference,
|
||||||
@ -33,7 +38,7 @@ type dockerImageSource struct {
|
|||||||
// nil requestedManifestMIMETypes means manifest.DefaultRequestedManifestMIMETypes.
|
// nil requestedManifestMIMETypes means manifest.DefaultRequestedManifestMIMETypes.
|
||||||
// The caller must call .Close() on the returned ImageSource.
|
// The caller must call .Close() on the returned ImageSource.
|
||||||
func newImageSource(ctx *types.SystemContext, ref dockerReference, requestedManifestMIMETypes []string) (*dockerImageSource, error) {
|
func newImageSource(ctx *types.SystemContext, ref dockerReference, requestedManifestMIMETypes []string) (*dockerImageSource, error) {
|
||||||
c, err := newDockerClient(ctx, ref.ref.Hostname())
|
c, err := newDockerClient(ctx, ref, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -71,10 +76,29 @@ func simplifyContentType(contentType string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *dockerImageSource) GetManifest() ([]byte, string, error) {
|
func (s *dockerImageSource) GetManifest() ([]byte, string, error) {
|
||||||
reference, err := s.ref.tagOrDigest()
|
err := s.ensureManifestIsLoaded()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
return s.cachedManifest, s.cachedManifestMIMEType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureManifestIsLoaded sets s.cachedManifest and s.cachedManifestMIMEType
|
||||||
|
//
|
||||||
|
// ImageSource implementations are not required or expected to do any caching,
|
||||||
|
// but because our signatures are “attached” to the manifest digest,
|
||||||
|
// we need to ensure that the digest of the manifest returned by GetManifest
|
||||||
|
// and used by GetSignatures are consistent, otherwise we would get spurious
|
||||||
|
// signature verification failures when pulling while a tag is being updated.
|
||||||
|
func (s *dockerImageSource) ensureManifestIsLoaded() error {
|
||||||
|
if s.cachedManifest != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reference, err := s.ref.tagOrDigest()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
url := fmt.Sprintf(manifestURL, s.ref.ref.RemoteName(), reference)
|
url := fmt.Sprintf(manifestURL, s.ref.ref.RemoteName(), reference)
|
||||||
// TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1
|
// 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
|
// TODO(runcom) NO, switch on the resulter manifest like Docker is doing
|
||||||
@ -82,18 +106,20 @@ func (s *dockerImageSource) GetManifest() ([]byte, string, error) {
|
|||||||
headers["Accept"] = s.requestedManifestMIMETypes
|
headers["Accept"] = s.requestedManifestMIMETypes
|
||||||
res, err := s.c.makeRequest("GET", url, headers, nil)
|
res, err := s.c.makeRequest("GET", url, headers, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
manblob, err := ioutil.ReadAll(res.Body)
|
manblob, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return err
|
||||||
}
|
}
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return nil, "", errFetchManifest{res.StatusCode, manblob}
|
return errFetchManifest{res.StatusCode, manblob}
|
||||||
}
|
}
|
||||||
// We might validate manblob against the Docker-Content-Digest header here to protect against transport errors.
|
// We might validate manblob against the Docker-Content-Digest header here to protect against transport errors.
|
||||||
return manblob, simplifyContentType(res.Header.Get("Content-Type")), nil
|
s.cachedManifest = manblob
|
||||||
|
s.cachedManifestMIMEType = simplifyContentType(res.Header.Get("Content-Type"))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
|
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
|
||||||
@ -116,12 +142,77 @@ func (s *dockerImageSource) GetBlob(digest string) (io.ReadCloser, int64, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *dockerImageSource) GetSignatures() ([][]byte, error) {
|
func (s *dockerImageSource) GetSignatures() ([][]byte, error) {
|
||||||
|
if s.c.signatureBase == nil { // Skip dealing with the manifest digest if not necessary.
|
||||||
return [][]byte{}, nil
|
return [][]byte{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := s.ensureManifestIsLoaded(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
manifestDigest, err := manifest.Digest(s.cachedManifest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signatures := [][]byte{}
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
url := signatureStorageURL(s.c.signatureBase, manifestDigest, i)
|
||||||
|
if url == nil {
|
||||||
|
return nil, fmt.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
|
||||||
|
}
|
||||||
|
signature, missing, err := s.getOneSignature(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if missing {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
signatures = append(signatures, signature)
|
||||||
|
}
|
||||||
|
return signatures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOneSignature downloads one signature from url.
|
||||||
|
// If it successfully determines that the signature does not exist, returns with missing set to true and error set to nil.
|
||||||
|
func (s *dockerImageSource) getOneSignature(url *url.URL) (signature []byte, missing bool, err error) {
|
||||||
|
switch url.Scheme {
|
||||||
|
case "file":
|
||||||
|
logrus.Debugf("Reading %s", url.Path)
|
||||||
|
sig, err := ioutil.ReadFile(url.Path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return sig, false, nil
|
||||||
|
|
||||||
|
case "http", "https":
|
||||||
|
logrus.Debugf("GET %s", url)
|
||||||
|
res, err := s.c.client.Get(url.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode == http.StatusNotFound {
|
||||||
|
return nil, true, nil
|
||||||
|
} else if res.StatusCode != http.StatusOK {
|
||||||
|
return nil, false, fmt.Errorf("Error reading signature from %s: status %d", url.String(), res.StatusCode)
|
||||||
|
}
|
||||||
|
sig, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return sig, false, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, false, fmt.Errorf("Unsupported scheme when reading signature from %s", url.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// deleteImage deletes the named image from the registry, if supported.
|
// deleteImage deletes the named image from the registry, if supported.
|
||||||
func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
||||||
c, err := newDockerClient(ctx, ref.ref.Hostname())
|
c, err := newDockerClient(ctx, ref, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -141,7 +232,7 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer get.Body.Close()
|
defer get.Body.Close()
|
||||||
body, err := ioutil.ReadAll(get.Body)
|
manifestBody, err := ioutil.ReadAll(get.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -150,7 +241,7 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||||||
case http.StatusNotFound:
|
case http.StatusNotFound:
|
||||||
return fmt.Errorf("Unable to delete %v. Image may not exist or is not stored with a v2 Schema in a v2 registry.", ref.ref)
|
return fmt.Errorf("Unable to delete %v. Image may not exist or is not stored with a v2 Schema in a v2 registry.", ref.ref)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Failed to delete %v: %s (%v)", ref.ref, string(body), get.Status)
|
return fmt.Errorf("Failed to delete %v: %s (%v)", ref.ref, manifestBody, get.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
digest := get.Header.Get("Docker-Content-Digest")
|
digest := get.Header.Get("Docker-Content-Digest")
|
||||||
@ -164,7 +255,7 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||||||
}
|
}
|
||||||
defer delete.Body.Close()
|
defer delete.Body.Close()
|
||||||
|
|
||||||
body, err = ioutil.ReadAll(delete.Body)
|
body, err := ioutil.ReadAll(delete.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -172,5 +263,26 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||||||
return fmt.Errorf("Failed to delete %v: %s (%v)", deleteURL, string(body), delete.Status)
|
return fmt.Errorf("Failed to delete %v: %s (%v)", deleteURL, string(body), delete.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.signatureBase != nil {
|
||||||
|
manifestDigest, err := manifest.Digest(manifestBody)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
url := signatureStorageURL(c.signatureBase, manifestDigest, i)
|
||||||
|
if url == nil {
|
||||||
|
return fmt.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
|
||||||
|
}
|
||||||
|
missing, err := c.deleteOneSignature(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if missing {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
198
vendor/github.com/containers/image/docker/lookaside.go
generated
vendored
Normal file
198
vendor/github.com/containers/image/docker/lookaside.go
generated
vendored
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containers/image/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// systemRegistriesDirPath is the path to registries.d, used for locating lookaside Docker signature storage.
|
||||||
|
// You can override this at build time with
|
||||||
|
// -ldflags '-X github.com/containers/image/docker.systemRegistriesDirPath=$your_path'
|
||||||
|
var systemRegistriesDirPath = builtinRegistriesDirPath
|
||||||
|
|
||||||
|
// builtinRegistriesDirPath is the path to registries.d.
|
||||||
|
// DO NOT change this, instead see systemRegistriesDirPath above.
|
||||||
|
const builtinRegistriesDirPath = "/etc/containers/registries.d"
|
||||||
|
|
||||||
|
// registryConfiguration is one of the files in registriesDirPath configuring lookaside locations, or the result of merging them all.
|
||||||
|
// NOTE: Keep this in sync with docs/registries.d.md!
|
||||||
|
type registryConfiguration struct {
|
||||||
|
DefaultDocker *registryNamespace `json:"default-docker"`
|
||||||
|
// The key is a namespace, using fully-expanded Docker reference format or parent namespaces (per dockerReference.PolicyConfiguration*),
|
||||||
|
Docker map[string]registryNamespace `json:"docker"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// registryNamespace defines lookaside locations for a single namespace.
|
||||||
|
type registryNamespace struct {
|
||||||
|
SigStore string `json:"sigstore"` // For reading, and if SigStoreWrite is not present, for writing.
|
||||||
|
SigStoreWrite string `json:"sigstore-write"` // For writing only.
|
||||||
|
}
|
||||||
|
|
||||||
|
// signatureStorageBase is an "opaque" type representing a lookaside Docker signature storage.
|
||||||
|
// Users outside of this file should use configuredSignatureStorageBase and signatureStorageURL below.
|
||||||
|
type signatureStorageBase *url.URL // The only documented value is nil, meaning storage is not supported.
|
||||||
|
|
||||||
|
// configuredSignatureStorageBase reads configuration to find an appropriate signature storage URL for ref, for write access if “write”.
|
||||||
|
func configuredSignatureStorageBase(ctx *types.SystemContext, ref dockerReference, write bool) (signatureStorageBase, error) {
|
||||||
|
// FIXME? Loading and parsing the config could be cached across calls.
|
||||||
|
dirPath := registriesDirPath(ctx)
|
||||||
|
logrus.Debugf(`Using registries.d directory %s for sigstore configuration`, dirPath)
|
||||||
|
config, err := loadAndMergeConfig(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
topLevel := config.signatureTopLevel(ref, write)
|
||||||
|
if topLevel == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := url.Parse(topLevel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid signature storage URL %s: %v", topLevel, err)
|
||||||
|
}
|
||||||
|
// FIXME? Restrict to explicitly supported schemes?
|
||||||
|
repo := ref.ref.FullName() // Note that this is without a tag or digest.
|
||||||
|
if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
|
||||||
|
return nil, fmt.Errorf("Unexpected path elements in Docker reference %s for signature storage", ref.ref.String())
|
||||||
|
}
|
||||||
|
url.Path = url.Path + "/" + repo
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// registriesDirPath returns a path to registries.d
|
||||||
|
func registriesDirPath(ctx *types.SystemContext) string {
|
||||||
|
if ctx != nil {
|
||||||
|
if ctx.RegistriesDirPath != "" {
|
||||||
|
return ctx.RegistriesDirPath
|
||||||
|
}
|
||||||
|
if ctx.RootForImplicitAbsolutePaths != "" {
|
||||||
|
return filepath.Join(ctx.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return systemRegistriesDirPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadAndMergeConfig loads configuration files in dirPath
|
||||||
|
func loadAndMergeConfig(dirPath string) (*registryConfiguration, error) {
|
||||||
|
mergedConfig := registryConfiguration{Docker: map[string]registryNamespace{}}
|
||||||
|
dockerDefaultMergedFrom := ""
|
||||||
|
nsMergedFrom := map[string]string{}
|
||||||
|
|
||||||
|
dir, err := os.Open(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return &mergedConfig, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
configNames, err := dir.Readdirnames(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, configName := range configNames {
|
||||||
|
if !strings.HasSuffix(configName, ".yaml") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
configPath := filepath.Join(dirPath, configName)
|
||||||
|
configBytes, err := ioutil.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config registryConfiguration
|
||||||
|
err = yaml.Unmarshal(configBytes, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error parsing %s: %v", configPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.DefaultDocker != nil {
|
||||||
|
if mergedConfig.DefaultDocker != nil {
|
||||||
|
return nil, fmt.Errorf(`Error parsing signature storage configuration: "default-docker" defined both in "%s" and "%s"`,
|
||||||
|
dockerDefaultMergedFrom, configPath)
|
||||||
|
}
|
||||||
|
mergedConfig.DefaultDocker = config.DefaultDocker
|
||||||
|
dockerDefaultMergedFrom = configPath
|
||||||
|
}
|
||||||
|
|
||||||
|
for nsName, nsConfig := range config.Docker { // includes config.Docker == nil
|
||||||
|
if _, ok := mergedConfig.Docker[nsName]; ok {
|
||||||
|
return nil, fmt.Errorf(`Error parsing signature storage configuration: "docker" namespace "%s" defined both in "%s" and "%s"`,
|
||||||
|
nsName, nsMergedFrom[nsName], configPath)
|
||||||
|
}
|
||||||
|
mergedConfig.Docker[nsName] = nsConfig
|
||||||
|
nsMergedFrom[nsName] = configPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mergedConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// config.signatureTopLevel returns an URL string configured in config for ref, for write access if “write”.
|
||||||
|
// (the top level of the storage, namespaced by repo.FullName etc.), or "" if no signature storage should be used.
|
||||||
|
func (config *registryConfiguration) signatureTopLevel(ref dockerReference, write bool) string {
|
||||||
|
if config.Docker != nil {
|
||||||
|
// Look for a full match.
|
||||||
|
identity := ref.PolicyConfigurationIdentity()
|
||||||
|
if ns, ok := config.Docker[identity]; ok {
|
||||||
|
logrus.Debugf(` Using "docker" namespace %s`, identity)
|
||||||
|
if url := ns.signatureTopLevel(write); url != "" {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for a match of the possible parent namespaces.
|
||||||
|
for _, name := range ref.PolicyConfigurationNamespaces() {
|
||||||
|
if ns, ok := config.Docker[name]; ok {
|
||||||
|
logrus.Debugf(` Using "docker" namespace %s`, name)
|
||||||
|
if url := ns.signatureTopLevel(write); url != "" {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Look for a default location
|
||||||
|
if config.DefaultDocker != nil {
|
||||||
|
logrus.Debugf(` Using "default-docker" configuration`)
|
||||||
|
if url := config.DefaultDocker.signatureTopLevel(write); url != "" {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logrus.Debugf(" No signature storage configuration found for %s", ref.PolicyConfigurationIdentity())
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ns.signatureTopLevel returns an URL string configured in ns for ref, for write access if “write”.
|
||||||
|
// or "" if nothing has been configured.
|
||||||
|
func (ns registryNamespace) signatureTopLevel(write bool) string {
|
||||||
|
if write && ns.SigStoreWrite != "" {
|
||||||
|
logrus.Debugf(` Using %s`, ns.SigStoreWrite)
|
||||||
|
return ns.SigStoreWrite
|
||||||
|
}
|
||||||
|
if ns.SigStore != "" {
|
||||||
|
logrus.Debugf(` Using %s`, ns.SigStore)
|
||||||
|
return ns.SigStore
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// signatureStorageURL returns an URL usable for acessing signature index in base with known manifestDigest, or nil if not applicable.
|
||||||
|
// Returns nil iff base == nil.
|
||||||
|
func signatureStorageURL(base signatureStorageBase, manifestDigest string, index int) *url.URL {
|
||||||
|
if base == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
url := *base
|
||||||
|
url.Path = fmt.Sprintf("%s@%s/signature-%d", url.Path, manifestDigest, index+1)
|
||||||
|
return &url
|
||||||
|
}
|
4
vendor/github.com/containers/image/types/types.go
generated
vendored
4
vendor/github.com/containers/image/types/types.go
generated
vendored
@ -188,12 +188,16 @@ type SystemContext struct {
|
|||||||
// If not "", prefixed to any absolute paths used by default by the library (e.g. in /etc/).
|
// If not "", prefixed to any absolute paths used by default by the library (e.g. in /etc/).
|
||||||
// Not used for any of the more specific path overrides available in this struct.
|
// Not used for any of the more specific path overrides available in this struct.
|
||||||
// Not used for any paths specified by users in config files (even if the location of the config file _was_ affected by it).
|
// Not used for any paths specified by users in config files (even if the location of the config file _was_ affected by it).
|
||||||
|
// NOTE: If this is set, environment-variable overrides of paths are ignored (to keep the semantics simple: to create an /etc replacement, just set RootForImplicitAbsolutePaths .
|
||||||
|
// and there is no need to worry about the environment.)
|
||||||
// NOTE: This does NOT affect paths starting by $HOME.
|
// NOTE: This does NOT affect paths starting by $HOME.
|
||||||
RootForImplicitAbsolutePaths string
|
RootForImplicitAbsolutePaths string
|
||||||
|
|
||||||
// === Global configuration overrides ===
|
// === Global configuration overrides ===
|
||||||
// If not "", overrides the system's default path for signature.Policy configuration.
|
// If not "", overrides the system's default path for signature.Policy configuration.
|
||||||
SignaturePolicyPath string
|
SignaturePolicyPath string
|
||||||
|
// If not "", overrides the system's default path for registries.d (Docker signature storage configuration)
|
||||||
|
RegistriesDirPath string
|
||||||
|
|
||||||
// === docker.Transport overrides ===
|
// === docker.Transport overrides ===
|
||||||
DockerCertPath string // If not "", a directory containing "cert.pem" and "key.pem" used when talking to a Docker Registry
|
DockerCertPath string // If not "", a directory containing "cert.pem" and "key.pem" used when talking to a Docker Registry
|
||||||
|
8
vendor/github.com/docker/distribution/reference/reference.go
generated
vendored
8
vendor/github.com/docker/distribution/reference/reference.go
generated
vendored
@ -24,6 +24,7 @@ package reference
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
)
|
)
|
||||||
@ -43,6 +44,9 @@ var (
|
|||||||
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
|
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
|
||||||
ErrDigestInvalidFormat = errors.New("invalid digest format")
|
ErrDigestInvalidFormat = errors.New("invalid digest format")
|
||||||
|
|
||||||
|
// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
|
||||||
|
ErrNameContainsUppercase = errors.New("repository name must be lowercase")
|
||||||
|
|
||||||
// ErrNameEmpty is returned for empty, invalid repository names.
|
// ErrNameEmpty is returned for empty, invalid repository names.
|
||||||
ErrNameEmpty = errors.New("repository name must have at least one component")
|
ErrNameEmpty = errors.New("repository name must have at least one component")
|
||||||
|
|
||||||
@ -149,7 +153,9 @@ func Parse(s string) (Reference, error) {
|
|||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, ErrNameEmpty
|
return nil, ErrNameEmpty
|
||||||
}
|
}
|
||||||
// TODO(dmcgowan): Provide more specific and helpful error
|
if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
|
||||||
|
return nil, ErrNameContainsUppercase
|
||||||
|
}
|
||||||
return nil, ErrReferenceInvalidFormat
|
return nil, ErrReferenceInvalidFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user