mirror of
https://github.com/containers/skopeo.git
synced 2025-09-19 08:51:53 +00:00
Merge pull request #322 from mtrmac/x-registry-supports-signatures
Add support for the X-Registry-Supports-Signatures API extension
This commit is contained in:
@@ -473,6 +473,64 @@ func (s *CopySuite) TestCopyDockerSigstore(c *check.C) {
|
|||||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--policy", policy, "--registries.d", registriesDir, "copy", ourRegistry+"public/busybox", dirDest)
|
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--policy", policy, "--registries.d", registriesDir, "copy", ourRegistry+"public/busybox", dirDest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// atomic: and docker: X-Registry-Supports-Signatures works and interoperates
|
||||||
|
func (s *CopySuite) TestCopyAtomicExtension(c *check.C) {
|
||||||
|
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
defer mech.Close()
|
||||||
|
if err := mech.SupportsSigning(); err != nil { // FIXME? Test that the reading/writing works using signatures from fixtures
|
||||||
|
c.Skip(fmt.Sprintf("Signing not supported: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
topDir, err := ioutil.TempDir("", "atomic-extension")
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
defer os.RemoveAll(topDir)
|
||||||
|
for _, subdir := range []string{"dirAA", "dirAD", "dirDA", "dirDD", "registries.d"} {
|
||||||
|
err := os.MkdirAll(filepath.Join(topDir, subdir), 0755)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
}
|
||||||
|
registriesDir := filepath.Join(topDir, "registries.d")
|
||||||
|
dirDest := "dir:" + topDir
|
||||||
|
policy := fileFromFixture(c, "fixtures/policy.json", map[string]string{"@keydir@": s.gpgHome})
|
||||||
|
defer os.Remove(policy)
|
||||||
|
|
||||||
|
// Get an image to work with to an atomic: destination. Also verifies that we can use Docker repositories without X-Registry-Supports-Signatures
|
||||||
|
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--registries.d", registriesDir, "copy", "docker://busybox", "atomic:localhost:5000/myns/extension:unsigned")
|
||||||
|
// Pulling an unsigned image using atomic: fails.
|
||||||
|
assertSkopeoFails(c, ".*Source image rejected: A signature was required, but no signature exists.*",
|
||||||
|
"--tls-verify=false", "--policy", policy,
|
||||||
|
"copy", "atomic:localhost:5000/myns/extension:unsigned", dirDest+"/dirAA")
|
||||||
|
// The same when pulling using docker:
|
||||||
|
assertSkopeoFails(c, ".*Source image rejected: A signature was required, but no signature exists.*",
|
||||||
|
"--tls-verify=false", "--policy", policy, "--registries.d", registriesDir,
|
||||||
|
"copy", "docker://localhost:5000/myns/extension:unsigned", dirDest+"/dirAD")
|
||||||
|
|
||||||
|
// Sign the image using atomic:
|
||||||
|
assertSkopeoSucceeds(c, "", "--tls-verify=false",
|
||||||
|
"copy", "--sign-by", "personal@example.com", "atomic:localhost:5000/myns/extension:unsigned", "atomic:localhost:5000/myns/extension:atomic")
|
||||||
|
// Pulling the image using atomic: now succeeds.
|
||||||
|
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--policy", policy,
|
||||||
|
"copy", "atomic:localhost:5000/myns/extension:atomic", dirDest+"/dirAA")
|
||||||
|
// The same when pulling using docker:
|
||||||
|
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--policy", policy, "--registries.d", registriesDir,
|
||||||
|
"copy", "docker://localhost:5000/myns/extension:atomic", dirDest+"/dirAD")
|
||||||
|
// Both access methods result in the same data.
|
||||||
|
destructiveCheckDirImagesAreEqual(c, filepath.Join(topDir, "dirAA"), filepath.Join(topDir, "dirAD"))
|
||||||
|
|
||||||
|
// Get another image (different so that they don't share signatures, and sign it using docker://)
|
||||||
|
assertSkopeoSucceeds(c, "", "--tls-verify=false", "--registries.d", registriesDir,
|
||||||
|
"copy", "--sign-by", "personal@example.com", "docker://estesp/busybox:ppc64le", "atomic:localhost:5000/myns/extension:extension")
|
||||||
|
c.Logf("%s", combinedOutputOfCommand(c, "oc", "get", "istag", "extension:extension", "-o", "json"))
|
||||||
|
// Pulling the image using atomic: succeeds.
|
||||||
|
assertSkopeoSucceeds(c, "", "--debug", "--tls-verify=false", "--policy", policy,
|
||||||
|
"copy", "atomic:localhost:5000/myns/extension:extension", dirDest+"/dirDA")
|
||||||
|
// The same when pulling using docker:
|
||||||
|
assertSkopeoSucceeds(c, "", "--debug", "--tls-verify=false", "--policy", policy, "--registries.d", registriesDir,
|
||||||
|
"copy", "docker://localhost:5000/myns/extension:extension", dirDest+"/dirDD")
|
||||||
|
// Both access methods result in the same data.
|
||||||
|
destructiveCheckDirImagesAreEqual(c, filepath.Join(topDir, "dirDA"), filepath.Join(topDir, "dirDD"))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SkopeoSuite) TestCopySrcWithAuth(c *check.C) {
|
func (s *SkopeoSuite) TestCopySrcWithAuth(c *check.C) {
|
||||||
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--dest-creds=testuser:testpassword", "docker://busybox", fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url))
|
assertSkopeoSucceeds(c, "", "--tls-verify=false", "copy", "--dest-creds=testuser:testpassword", "docker://busybox", fmt.Sprintf("docker://%s/busybox:latest", s.regV2WithAuth.url))
|
||||||
dir1, err := ioutil.TempDir("", "copy-1")
|
dir1, err := ioutil.TempDir("", "copy-1")
|
||||||
|
@@ -13,6 +13,13 @@
|
|||||||
"keyPath": "@keydir@/personal-pubkey.gpg"
|
"keyPath": "@keydir@/personal-pubkey.gpg"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"localhost:5000/myns/extension": [
|
||||||
|
{
|
||||||
|
"type": "signedBy",
|
||||||
|
"keyType": "GPGKeys",
|
||||||
|
"keyPath": "@keydir@/personal-pubkey.gpg"
|
||||||
|
}
|
||||||
|
],
|
||||||
"docker.io/openshift": [
|
"docker.io/openshift": [
|
||||||
{
|
{
|
||||||
"type": "insecureAcceptAnything"
|
"type": "insecureAcceptAnything"
|
||||||
@@ -85,6 +92,13 @@
|
|||||||
"keyType": "GPGKeys",
|
"keyType": "GPGKeys",
|
||||||
"keyPath": "@keydir@/personal-pubkey.gpg"
|
"keyPath": "@keydir@/personal-pubkey.gpg"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"localhost:5000/myns/extension": [
|
||||||
|
{
|
||||||
|
"type": "signedBy",
|
||||||
|
"keyType": "GPGKeys",
|
||||||
|
"keyPath": "@keydir@/personal-pubkey.gpg"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
108
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
108
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
@@ -18,8 +18,10 @@ import (
|
|||||||
"github.com/containers/image/docker/reference"
|
"github.com/containers/image/docker/reference"
|
||||||
"github.com/containers/image/types"
|
"github.com/containers/image/types"
|
||||||
"github.com/containers/storage/pkg/homedir"
|
"github.com/containers/storage/pkg/homedir"
|
||||||
|
"github.com/docker/distribution/registry/client"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,20 +34,38 @@ const (
|
|||||||
dockerCfgFileName = "config.json"
|
dockerCfgFileName = "config.json"
|
||||||
dockerCfgObsolete = ".dockercfg"
|
dockerCfgObsolete = ".dockercfg"
|
||||||
|
|
||||||
baseURL = "%s://%s/v2/"
|
resolvedPingV2URL = "%s://%s/v2/"
|
||||||
baseURLV1 = "%s://%s/v1/_ping"
|
resolvedPingV1URL = "%s://%s/v1/_ping"
|
||||||
tagsURL = "%s/tags/list"
|
tagsPath = "/v2/%s/tags/list"
|
||||||
manifestURL = "%s/manifests/%s"
|
manifestPath = "/v2/%s/manifests/%s"
|
||||||
blobsURL = "%s/blobs/%s"
|
blobsPath = "/v2/%s/blobs/%s"
|
||||||
blobUploadURL = "%s/blobs/uploads/"
|
blobUploadPath = "/v2/%s/blobs/uploads/"
|
||||||
|
extensionsSignaturePath = "/extensions/v2/%s/signatures/%s"
|
||||||
|
|
||||||
minimumTokenLifetimeSeconds = 60
|
minimumTokenLifetimeSeconds = 60
|
||||||
|
|
||||||
|
extensionSignatureSchemaVersion = 2 // extensionSignature.Version
|
||||||
|
extensionSignatureTypeAtomic = "atomic" // extensionSignature.Type
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrV1NotSupported is returned when we're trying to talk to a
|
// ErrV1NotSupported is returned when we're trying to talk to a
|
||||||
// docker V1 registry.
|
// docker V1 registry.
|
||||||
var ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
|
var ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
|
||||||
|
|
||||||
|
// extensionSignature and extensionSignatureList come from github.com/openshift/origin/pkg/dockerregistry/server/signaturedispatcher.go:
|
||||||
|
// signature represents a Docker image signature.
|
||||||
|
type extensionSignature struct {
|
||||||
|
Version int `json:"schemaVersion"` // Version specifies the schema version
|
||||||
|
Name string `json:"name"` // Name must be in "sha256:<digest>@signatureName" format
|
||||||
|
Type string `json:"type"` // Type is optional, of not set it will be defaulted to "AtomicImageV1"
|
||||||
|
Content []byte `json:"content"` // Content contains the signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// signatureList represents list of Docker image signatures.
|
||||||
|
type extensionSignatureList struct {
|
||||||
|
Signatures []extensionSignature `json:"signatures"`
|
||||||
|
}
|
||||||
|
|
||||||
type bearerToken struct {
|
type bearerToken struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
@@ -54,15 +74,20 @@ type bearerToken struct {
|
|||||||
|
|
||||||
// dockerClient is configuration for dealing with a single Docker registry.
|
// dockerClient is configuration for dealing with a single Docker registry.
|
||||||
type dockerClient struct {
|
type dockerClient struct {
|
||||||
ctx *types.SystemContext
|
// The following members are set by newDockerClient and do not change afterwards.
|
||||||
registry string
|
ctx *types.SystemContext
|
||||||
username string
|
registry string
|
||||||
password string
|
username string
|
||||||
scheme string // Cache of a value returned by a successful ping() if not empty
|
password string
|
||||||
client *http.Client
|
client *http.Client
|
||||||
signatureBase signatureStorageBase
|
signatureBase signatureStorageBase
|
||||||
challenges []challenge
|
scope authScope
|
||||||
scope authScope
|
// The following members are detected registry properties:
|
||||||
|
// They are set after a successful detectProperties(), and never change afterwards.
|
||||||
|
scheme string // Empty value also used to indicate detectProperties() has not yet succeeded.
|
||||||
|
challenges []challenge
|
||||||
|
supportsSignatures bool
|
||||||
|
// The following members are private state for setupRequestAuth, both are valid if token != nil.
|
||||||
token *bearerToken
|
token *bearerToken
|
||||||
tokenExpiration time.Time
|
tokenExpiration time.Time
|
||||||
}
|
}
|
||||||
@@ -209,15 +234,13 @@ func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
|
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
|
||||||
// url is NOT an absolute URL, but a path relative to the /v2/ top-level API path. The host name and schema is taken from the client or autodetected.
|
// The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/.
|
||||||
func (c *dockerClient) makeRequest(method, url string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
|
func (c *dockerClient) makeRequest(method, path string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
|
||||||
if c.scheme == "" {
|
if err := c.detectProperties(); err != nil {
|
||||||
if err := c.ping(); err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
url = fmt.Sprintf(baseURL, c.scheme, c.registry) + url
|
url := fmt.Sprintf("%s://%s%s", c.scheme, c.registry, path)
|
||||||
return c.makeRequestToResolvedURL(method, url, headers, stream, -1, true)
|
return c.makeRequestToResolvedURL(method, url, headers, stream, -1, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,21 +421,28 @@ func getAuth(ctx *types.SystemContext, registry string) (string, string, error)
|
|||||||
return "", "", nil
|
return "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dockerClient) ping() error {
|
// detectProperties detects various properties of the registry.
|
||||||
|
// See the dockerClient documentation for members which are affected by this.
|
||||||
|
func (c *dockerClient) detectProperties() error {
|
||||||
|
if c.scheme != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
ping := func(scheme string) error {
|
ping := func(scheme string) error {
|
||||||
url := fmt.Sprintf(baseURL, scheme, c.registry)
|
url := fmt.Sprintf(resolvedPingV2URL, scheme, c.registry)
|
||||||
resp, err := c.makeRequestToResolvedURL("GET", url, nil, nil, -1, true)
|
resp, err := c.makeRequestToResolvedURL("GET", url, nil, nil, -1, true)
|
||||||
logrus.Debugf("Ping %s err %#v", url, err)
|
logrus.Debugf("Ping %s err %#v", url, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
logrus.Debugf("Ping %s status %d", scheme+"://"+c.registry+"/v2/", resp.StatusCode)
|
logrus.Debugf("Ping %s status %d", url, resp.StatusCode)
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
||||||
return errors.Errorf("error pinging repository, response code %d", resp.StatusCode)
|
return errors.Errorf("error pinging repository, response code %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
c.challenges = parseAuthHeader(resp.Header)
|
c.challenges = parseAuthHeader(resp.Header)
|
||||||
c.scheme = scheme
|
c.scheme = scheme
|
||||||
|
c.supportsSignatures = resp.Header.Get("X-Registry-Supports-Signatures") == "1"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := ping("https")
|
err := ping("https")
|
||||||
@@ -426,14 +456,14 @@ func (c *dockerClient) ping() error {
|
|||||||
}
|
}
|
||||||
// best effort to understand if we're talking to a V1 registry
|
// best effort to understand if we're talking to a V1 registry
|
||||||
pingV1 := func(scheme string) bool {
|
pingV1 := func(scheme string) bool {
|
||||||
url := fmt.Sprintf(baseURLV1, scheme, c.registry)
|
url := fmt.Sprintf(resolvedPingV1URL, scheme, c.registry)
|
||||||
resp, err := c.makeRequestToResolvedURL("GET", url, nil, nil, -1, true)
|
resp, err := c.makeRequestToResolvedURL("GET", url, nil, nil, -1, true)
|
||||||
logrus.Debugf("Ping %s err %#v", url, err)
|
logrus.Debugf("Ping %s err %#v", url, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
logrus.Debugf("Ping %s status %d", scheme+"://"+c.registry+"/v1/_ping", resp.StatusCode)
|
logrus.Debugf("Ping %s status %d", url, resp.StatusCode)
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -450,6 +480,30 @@ func (c *dockerClient) ping() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getExtensionsSignatures returns signatures from the X-Registry-Supports-Signatures API extension,
|
||||||
|
// using the original data structures.
|
||||||
|
func (c *dockerClient) getExtensionsSignatures(ref dockerReference, manifestDigest digest.Digest) (*extensionSignatureList, error) {
|
||||||
|
path := fmt.Sprintf(extensionsSignaturePath, reference.Path(ref.ref), manifestDigest)
|
||||||
|
res, err := c.makeRequest("GET", path, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return nil, client.HandleErrorResponse(res)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedBody extensionSignatureList
|
||||||
|
if err := json.Unmarshal(body, &parsedBody); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "Error decoding signature list")
|
||||||
|
}
|
||||||
|
return &parsedBody, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getDefaultConfigDir(confPath string) string {
|
func getDefaultConfigDir(confPath string) string {
|
||||||
return filepath.Join(homedir.Get(), confPath)
|
return filepath.Join(homedir.Get(), confPath)
|
||||||
}
|
}
|
||||||
|
4
vendor/github.com/containers/image/docker/docker_image.go
generated
vendored
4
vendor/github.com/containers/image/docker/docker_image.go
generated
vendored
@@ -40,8 +40,8 @@ func (i *Image) SourceRefFullName() string {
|
|||||||
|
|
||||||
// GetRepositoryTags list all tags available in the repository. Note that this has no connection with the tag(s) used for this specific image, if any.
|
// GetRepositoryTags list all tags available in the repository. Note that this has no connection with the tag(s) used for this specific image, if any.
|
||||||
func (i *Image) GetRepositoryTags() ([]string, error) {
|
func (i *Image) GetRepositoryTags() ([]string, error) {
|
||||||
url := fmt.Sprintf(tagsURL, reference.Path(i.src.ref.ref))
|
path := fmt.Sprintf(tagsPath, reference.Path(i.src.ref.ref))
|
||||||
res, err := i.src.c.makeRequest("GET", url, nil, nil)
|
res, err := i.src.c.makeRequest("GET", path, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
133
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
133
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
@@ -2,6 +2,8 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -70,7 +72,17 @@ func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
|
|||||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||||
func (d *dockerImageDestination) SupportsSignatures() error {
|
func (d *dockerImageDestination) SupportsSignatures() error {
|
||||||
return errors.Errorf("Pushing signatures to a Docker Registry is not supported")
|
if err := d.c.detectProperties(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case d.c.signatureBase != nil:
|
||||||
|
return nil
|
||||||
|
case d.c.supportsSignatures:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errors.Errorf("X-Registry-Supports-Signatures extension not supported, and lookaside is not configured")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
||||||
@@ -111,16 +123,16 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME? Chunked upload, progress reporting, etc.
|
// FIXME? Chunked upload, progress reporting, etc.
|
||||||
uploadURL := fmt.Sprintf(blobUploadURL, reference.Path(d.ref.ref))
|
uploadPath := fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref))
|
||||||
logrus.Debugf("Uploading %s", uploadURL)
|
logrus.Debugf("Uploading %s", uploadPath)
|
||||||
res, err := d.c.makeRequest("POST", uploadURL, nil, nil)
|
res, err := d.c.makeRequest("POST", uploadPath, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.BlobInfo{}, err
|
return types.BlobInfo{}, err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusAccepted {
|
if res.StatusCode != http.StatusAccepted {
|
||||||
logrus.Debugf("Error initiating layer upload, response %#v", *res)
|
logrus.Debugf("Error initiating layer upload, response %#v", *res)
|
||||||
return types.BlobInfo{}, errors.Errorf("Error initiating layer upload to %s, status %d", uploadURL, res.StatusCode)
|
return types.BlobInfo{}, errors.Errorf("Error initiating layer upload to %s, status %d", uploadPath, res.StatusCode)
|
||||||
}
|
}
|
||||||
uploadLocation, err := res.Location()
|
uploadLocation, err := res.Location()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -167,10 +179,10 @@ func (d *dockerImageDestination) HasBlob(info types.BlobInfo) (bool, int64, erro
|
|||||||
if info.Digest == "" {
|
if info.Digest == "" {
|
||||||
return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||||
}
|
}
|
||||||
checkURL := fmt.Sprintf(blobsURL, reference.Path(d.ref.ref), info.Digest.String())
|
checkPath := fmt.Sprintf(blobsPath, reference.Path(d.ref.ref), info.Digest.String())
|
||||||
|
|
||||||
logrus.Debugf("Checking %s", checkURL)
|
logrus.Debugf("Checking %s", checkPath)
|
||||||
res, err := d.c.makeRequest("HEAD", checkURL, nil, nil)
|
res, err := d.c.makeRequest("HEAD", checkPath, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
@@ -205,14 +217,14 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf(manifestURL, reference.Path(d.ref.ref), refTail)
|
path := fmt.Sprintf(manifestPath, reference.Path(d.ref.ref), refTail)
|
||||||
|
|
||||||
headers := map[string][]string{}
|
headers := map[string][]string{}
|
||||||
mimeType := manifest.GuessMIMEType(m)
|
mimeType := manifest.GuessMIMEType(m)
|
||||||
if mimeType != "" {
|
if mimeType != "" {
|
||||||
headers["Content-Type"] = []string{mimeType}
|
headers["Content-Type"] = []string{mimeType}
|
||||||
}
|
}
|
||||||
res, err := d.c.makeRequest("PUT", url, headers, bytes.NewReader(m))
|
res, err := d.c.makeRequest("PUT", path, headers, bytes.NewReader(m))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -223,12 +235,32 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
|
|||||||
logrus.Debugf("Error body %s", string(body))
|
logrus.Debugf("Error body %s", string(body))
|
||||||
}
|
}
|
||||||
logrus.Debugf("Error uploading manifest, status %d, %#v", res.StatusCode, res)
|
logrus.Debugf("Error uploading manifest, status %d, %#v", res.StatusCode, res)
|
||||||
return errors.Errorf("Error uploading manifest to %s, status %d", url, res.StatusCode)
|
return errors.Errorf("Error uploading manifest to %s, status %d", path, res.StatusCode)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
|
func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
|
||||||
|
// Do not fail if we don’t really need to support signatures.
|
||||||
|
if len(signatures) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := d.c.detectProperties(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case d.c.signatureBase != nil:
|
||||||
|
return d.putSignaturesToLookaside(signatures)
|
||||||
|
case d.c.supportsSignatures:
|
||||||
|
return d.putSignaturesToAPIExtension(signatures)
|
||||||
|
default:
|
||||||
|
return errors.Errorf("X-Registry-Supports-Signatures extension not supported, and lookaside is not configured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// putSignaturesToLookaside implements PutSignatures() from the lookaside location configured in s.c.signatureBase,
|
||||||
|
// which is not nil.
|
||||||
|
func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte) error {
|
||||||
// FIXME? This overwrites files one at a time, definitely not atomic.
|
// FIXME? This overwrites files one at a time, definitely not atomic.
|
||||||
// A failure when updating signatures with a reordered copy could lose some of them.
|
// A failure when updating signatures with a reordered copy could lose some of them.
|
||||||
|
|
||||||
@@ -236,9 +268,6 @@ func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
|
|||||||
if len(signatures) == 0 {
|
if len(signatures) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if d.c.signatureBase == nil {
|
|
||||||
return errors.Errorf("Pushing signatures to a Docker Registry is not supported, and there is no applicable signature storage configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.manifestDigest.String() == "" {
|
if d.manifestDigest.String() == "" {
|
||||||
// This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
|
// This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
|
||||||
@@ -318,6 +347,82 @@ func (c *dockerClient) deleteOneSignature(url *url.URL) (missing bool, err error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// putSignaturesToAPIExtension implements PutSignatures() using the X-Registry-Supports-Signatures API extension.
|
||||||
|
func (d *dockerImageDestination) putSignaturesToAPIExtension(signatures [][]byte) error {
|
||||||
|
// Skip dealing with the manifest digest, or reading the old state, if not necessary.
|
||||||
|
if len(signatures) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.manifestDigest.String() == "" {
|
||||||
|
// This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
|
||||||
|
return errors.Errorf("Unknown manifest digest, can't add signatures")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because image signatures are a shared resource in Atomic Registry, the default upload
|
||||||
|
// always adds signatures. Eventually we should also allow removing signatures,
|
||||||
|
// but the X-Registry-Supports-Signatures API extension does not support that yet.
|
||||||
|
|
||||||
|
existingSignatures, err := d.c.getExtensionsSignatures(d.ref, d.manifestDigest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
existingSigNames := map[string]struct{}{}
|
||||||
|
for _, sig := range existingSignatures.Signatures {
|
||||||
|
existingSigNames[sig.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
sigExists:
|
||||||
|
for _, newSig := range signatures {
|
||||||
|
for _, existingSig := range existingSignatures.Signatures {
|
||||||
|
if existingSig.Version == extensionSignatureSchemaVersion && existingSig.Type == extensionSignatureTypeAtomic && bytes.Equal(existingSig.Content, newSig) {
|
||||||
|
continue sigExists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The API expect us to invent a new unique name. This is racy, but hopefully good enough.
|
||||||
|
var signatureName string
|
||||||
|
for {
|
||||||
|
randBytes := make([]byte, 16)
|
||||||
|
n, err := rand.Read(randBytes)
|
||||||
|
if err != nil || n != 16 {
|
||||||
|
return errors.Wrapf(err, "Error generating random signature len %d", n)
|
||||||
|
}
|
||||||
|
signatureName = fmt.Sprintf("%s@%032x", d.manifestDigest.String(), randBytes)
|
||||||
|
if _, ok := existingSigNames[signatureName]; !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sig := extensionSignature{
|
||||||
|
Version: extensionSignatureSchemaVersion,
|
||||||
|
Name: signatureName,
|
||||||
|
Type: extensionSignatureTypeAtomic,
|
||||||
|
Content: newSig,
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(sig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf(extensionsSignaturePath, reference.Path(d.ref.ref), d.manifestDigest.String())
|
||||||
|
res, err := d.c.makeRequest("PUT", path, nil, bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != http.StatusCreated {
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err == nil {
|
||||||
|
logrus.Debugf("Error body %s", string(body))
|
||||||
|
}
|
||||||
|
logrus.Debugf("Error uploading signature, status %d, %#v", res.StatusCode, res)
|
||||||
|
return errors.Errorf("Error uploading signature to %s, status %d", path, res.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
// WARNING: This does not have any transactional semantics:
|
// WARNING: This does not have any transactional semantics:
|
||||||
// - Uploaded data MAY be visible to others before Commit() is called
|
// - Uploaded data MAY be visible to others before Commit() is called
|
||||||
|
58
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
58
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
@@ -93,10 +93,10 @@ func (s *dockerImageSource) GetManifest() ([]byte, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *dockerImageSource) fetchManifest(tagOrDigest string) ([]byte, string, error) {
|
func (s *dockerImageSource) fetchManifest(tagOrDigest string) ([]byte, string, error) {
|
||||||
url := fmt.Sprintf(manifestURL, reference.Path(s.ref.ref), tagOrDigest)
|
path := fmt.Sprintf(manifestPath, reference.Path(s.ref.ref), tagOrDigest)
|
||||||
headers := make(map[string][]string)
|
headers := make(map[string][]string)
|
||||||
headers["Accept"] = s.requestedManifestMIMETypes
|
headers["Accept"] = s.requestedManifestMIMETypes
|
||||||
res, err := s.c.makeRequest("GET", url, headers, nil)
|
res, err := s.c.makeRequest("GET", path, headers, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
@@ -179,9 +179,9 @@ func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64,
|
|||||||
return s.getExternalBlob(info.URLs)
|
return s.getExternalBlob(info.URLs)
|
||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf(blobsURL, reference.Path(s.ref.ref), info.Digest.String())
|
path := fmt.Sprintf(blobsPath, reference.Path(s.ref.ref), info.Digest.String())
|
||||||
logrus.Debugf("Downloading %s", url)
|
logrus.Debugf("Downloading %s", path)
|
||||||
res, err := s.c.makeRequest("GET", url, nil, nil)
|
res, err := s.c.makeRequest("GET", path, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -193,10 +193,22 @@ func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64,
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.
|
if err := s.c.detectProperties(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case s.c.signatureBase != nil:
|
||||||
|
return s.getSignaturesFromLookaside()
|
||||||
|
case s.c.supportsSignatures:
|
||||||
|
return s.getSignaturesFromAPIExtension()
|
||||||
|
default:
|
||||||
return [][]byte{}, nil
|
return [][]byte{}, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSignaturesFromLookaside implements GetSignatures() from the lookaside location configured in s.c.signatureBase,
|
||||||
|
// which is not nil.
|
||||||
|
func (s *dockerImageSource) getSignaturesFromLookaside() ([][]byte, error) {
|
||||||
if err := s.ensureManifestIsLoaded(); err != nil {
|
if err := s.ensureManifestIsLoaded(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -261,6 +273,30 @@ func (s *dockerImageSource) getOneSignature(url *url.URL) (signature []byte, mis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getSignaturesFromAPIExtension implements GetSignatures() using the X-Registry-Supports-Signatures API extension.
|
||||||
|
func (s *dockerImageSource) getSignaturesFromAPIExtension() ([][]byte, error) {
|
||||||
|
if err := s.ensureManifestIsLoaded(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
manifestDigest, err := manifest.Digest(s.cachedManifest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedBody, err := s.c.getExtensionsSignatures(s.ref, manifestDigest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var sigs [][]byte
|
||||||
|
for _, sig := range parsedBody.Signatures {
|
||||||
|
if sig.Version == extensionSignatureSchemaVersion && sig.Type == extensionSignatureTypeAtomic {
|
||||||
|
sigs = append(sigs, sig.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sigs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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, true, "push")
|
c, err := newDockerClient(ctx, ref, true, "push")
|
||||||
@@ -277,8 +313,8 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
getURL := fmt.Sprintf(manifestURL, reference.Path(ref.ref), refTail)
|
getPath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), refTail)
|
||||||
get, err := c.makeRequest("GET", getURL, headers, nil)
|
get, err := c.makeRequest("GET", getPath, headers, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -296,11 +332,11 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
digest := get.Header.Get("Docker-Content-Digest")
|
digest := get.Header.Get("Docker-Content-Digest")
|
||||||
deleteURL := fmt.Sprintf(manifestURL, reference.Path(ref.ref), digest)
|
deletePath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), digest)
|
||||||
|
|
||||||
// When retrieving the digest from a registry >= 2.3 use the following header:
|
// When retrieving the digest from a registry >= 2.3 use the following header:
|
||||||
// "Accept": "application/vnd.docker.distribution.manifest.v2+json"
|
// "Accept": "application/vnd.docker.distribution.manifest.v2+json"
|
||||||
delete, err := c.makeRequest("DELETE", deleteURL, headers, nil)
|
delete, err := c.makeRequest("DELETE", deletePath, headers, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -311,7 +347,7 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if delete.StatusCode != http.StatusAccepted {
|
if delete.StatusCode != http.StatusAccepted {
|
||||||
return errors.Errorf("Failed to delete %v: %s (%v)", deleteURL, string(body), delete.Status)
|
return errors.Errorf("Failed to delete %v: %s (%v)", deletePath, string(body), delete.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.signatureBase != nil {
|
if c.signatureBase != nil {
|
||||||
|
Reference in New Issue
Block a user