Hello community,
here is the log from the commit of package docker-distribution for
openSUSE:Factory checked in at 2016-03-18 21:42:38
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/docker-distribution (Old)
and /work/SRC/openSUSE:Factory/.docker-distribution.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "docker-distribution"
Changes:
--------
--- /work/SRC/openSUSE:Factory/docker-distribution/docker-distribution.changes
2016-02-17 12:10:41.000000000 +0100
+++
/work/SRC/openSUSE:Factory/.docker-distribution.new/docker-distribution.changes
2016-03-18 21:42:39.000000000 +0100
@@ -1,0 +2,12 @@
+Thu Mar 10 11:14:26 UTC 2016 - [email protected]
+
+- Ugraded to 2.3.1. The changelog is as follows:
+
+- Allow uppercase characters in hostnames
(https://github.com/docker/distribution/commit/34c3acf8a8d800524ac06444290b26ed96d4dac0)
+- Fix schema1 manifest etag and docker content digest header
(https://github.com/docker/distribution/commit/d7eb5d118a6a14a6f320062296b1808506c35241)
+- Add option to disable signatures
(https://github.com/docker/distribution/commit/d1c173078fe47f45c7f7b96098410d4f13dd68ab)
+- To avoid any network use unless necessary, delay establishing authorization
(https://github.com/docker/distribution/commit/740ed699f40e1522faacb2a706e44fa1560af8ea)
+- Extend authChallenger interface to remove type cast.
(https://github.com/docker/distribution/commit/16445b67679e91eab408a40a34625aa1f4dabfd1)
+- Enable proxying registries to downgrade fetched manifests to Schema 1.
(https://github.com/docker/distribution/commit/36936218c2e6a4527fc99a5c04126bb1f4c7d55c)
+
+-------------------------------------------------------------------
Old:
----
v2.3.0.tar.gz
New:
----
v2.3.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ docker-distribution.spec ++++++
--- /var/tmp/diff_new_pack.YRbZye/_old 2016-03-18 21:42:40.000000000 +0100
+++ /var/tmp/diff_new_pack.YRbZye/_new 2016-03-18 21:42:40.000000000 +0100
@@ -17,7 +17,7 @@
Name: docker-distribution
-Version: 2.3.0
+Version: 2.3.1
Release: 0
Summary: The Docker toolset to pack, ship, store, and deliver content
License: Apache-2.0
++++++ v2.3.0.tar.gz -> v2.3.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/configuration/configuration.go
new/distribution-2.3.1/configuration/configuration.go
--- old/distribution-2.3.0/configuration/configuration.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/configuration/configuration.go 2016-02-23
23:39:19.000000000 +0100
@@ -145,6 +145,21 @@
Health Health `yaml:"health,omitempty"`
Proxy Proxy `yaml:"proxy,omitempty"`
+
+ // Compatibility is used for configurations of working with older or
deprecated features.
+ Compatibility struct {
+ // Schema1 configures how schema1 manifests will be handled
+ Schema1 struct {
+ // TrustKey is the signing key to use for adding the
signature to
+ // schema1 manifests.
+ TrustKey string `yaml:"signingkeyfile,omitempty"`
+
+ // DisableSignatureStore will cause all signatures
attached to schema1 manifests
+ // to be ignored. Signatures will be generated on all
schema1 manifest requests
+ // rather than only requests which converted schema2 to
schema1.
+ DisableSignatureStore bool
`yaml:"disablesignaturestore,omitempty"`
+ } `yaml:"schema1,omitempty"`
+ } `yaml:"compatibility,omitempty"`
}
// LogHook is composed of hook Level and Type.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/manifest/schema1/manifest.go
new/distribution-2.3.1/manifest/schema1/manifest.go
--- old/distribution-2.3.0/manifest/schema1/manifest.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/manifest/schema1/manifest.go 2016-02-23
23:39:19.000000000 +0100
@@ -102,7 +102,7 @@
Canonical []byte `json:"-"`
// all contains the byte representation of the Manifest including
signatures
- // and is retuend by Payload()
+ // and is returned by Payload()
all []byte
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/reference/reference.go
new/distribution-2.3.1/reference/reference.go
--- old/distribution-2.3.0/reference/reference.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/reference/reference.go 2016-02-23
23:39:19.000000000 +0100
@@ -6,7 +6,7 @@
// reference := repository [ ":" tag ] [ "@" digest ]
// name := [hostname '/'] component ['/'
component]*
// hostname := hostcomponent ['.' hostcomponent]*
[':' port-number]
-// hostcomponent :=
/([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])/
+// hostcomponent :=
/([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
// port-number := /[0-9]+/
// component := alpha-numeric [separator
alpha-numeric]*
// alpha-numeric := /[a-z0-9]+/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/reference/regexp.go
new/distribution-2.3.1/reference/regexp.go
--- old/distribution-2.3.0/reference/regexp.go 2016-02-04 20:11:33.000000000
+0100
+++ new/distribution-2.3.1/reference/regexp.go 2016-02-23 23:39:19.000000000
+0100
@@ -22,7 +22,7 @@
// hostnameComponentRegexp restricts the registry hostname component of
a
// repository name to start with a component as defined by
hostnameRegexp
// and followed by an optional port.
- hostnameComponentRegexp =
match(`(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])`)
+ hostnameComponentRegexp =
match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
// hostnameRegexp defines the structure of potential hostname components
// that may be part of image names. This is purposely a subset of what
is
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/reference/regexp_test.go
new/distribution-2.3.1/reference/regexp_test.go
--- old/distribution-2.3.0/reference/regexp_test.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/reference/regexp_test.go 2016-02-23
23:39:19.000000000 +0100
@@ -111,6 +111,10 @@
input: "xn--n3h.com", // ☃.com in punycode
match: true,
},
+ {
+ input: "Asdf.com", // uppercase character
+ match: true,
+ },
}
r := regexp.MustCompile(`^` + hostnameRegexp.String() + `$`)
for i := range hostcases {
@@ -399,6 +403,14 @@
match: true,
subs: []string{"registry.io",
"foo/project--id.module--name.ver---sion--name"},
},
+ {
+ input: "Asdf.com/foo/bar", // uppercase character in
hostname
+ match: true,
+ },
+ {
+ input: "Foo/FarB", // uppercase characters in remote
name
+ match: false,
+ },
}
for i := range testcases {
checkRegexp(t, anchoredNameRegexp, testcases[i])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/registry/client/repository.go
new/distribution-2.3.1/registry/client/repository.go
--- old/distribution-2.3.0/registry/client/repository.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/client/repository.go 2016-02-23
23:39:19.000000000 +0100
@@ -257,9 +257,18 @@
if err != nil {
return distribution.Descriptor{}, err
}
- var attempts int
- resp, err := t.client.Head(u)
+ req, err := http.NewRequest("HEAD", u, nil)
+ if err != nil {
+ return distribution.Descriptor{}, err
+ }
+
+ for _, t := range distribution.ManifestMediaTypes() {
+ req.Header.Add("Accept", t)
+ }
+
+ var attempts int
+ resp, err := t.client.Do(req)
check:
if err != nil {
return distribution.Descriptor{}, err
@@ -269,7 +278,16 @@
case resp.StatusCode >= 200 && resp.StatusCode < 400:
return descriptorFromResponse(resp)
case resp.StatusCode == http.StatusMethodNotAllowed:
- resp, err = t.client.Get(u)
+ req, err = http.NewRequest("GET", u, nil)
+ if err != nil {
+ return distribution.Descriptor{}, err
+ }
+
+ for _, t := range distribution.ManifestMediaTypes() {
+ req.Header.Add("Accept", t)
+ }
+
+ resp, err = t.client.Do(req)
attempts++
if attempts > 1 {
return distribution.Descriptor{}, err
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/registry/handlers/api_test.go
new/distribution-2.3.1/registry/handlers/api_test.go
--- old/distribution-2.3.0/registry/handlers/api_test.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/handlers/api_test.go 2016-02-23
23:39:19.000000000 +0100
@@ -1378,19 +1378,28 @@
}
defer resp.Body.Close()
+ manifestBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ t.Fatalf("error reading response body: %v", err)
+ }
+
checkResponse(t, "fetching uploaded manifest as schema1", resp,
http.StatusOK)
- checkHeaders(t, resp, http.Header{
- "Docker-Content-Digest": []string{dgst.String()},
- "ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
- })
- var fetchedSchema1Manifest schema1.SignedManifest
- dec = json.NewDecoder(resp.Body)
+ m, desc, err :=
distribution.UnmarshalManifest(schema1.MediaTypeManifest, manifestBytes)
+ if err != nil {
+ t.Fatalf("unexpected error unmarshalling manifest: %v", err)
+ }
- if err := dec.Decode(&fetchedSchema1Manifest); err != nil {
- t.Fatalf("error decoding fetched schema1 manifest: %v", err)
+ fetchedSchema1Manifest, ok := m.(*schema1.SignedManifest)
+ if !ok {
+ t.Fatalf("expecting schema1 manifest")
}
+ checkHeaders(t, resp, http.Header{
+ "Docker-Content-Digest": []string{desc.Digest.String()},
+ "ETag": []string{fmt.Sprintf(`"%s"`,
desc.Digest)},
+ })
+
if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
t.Fatal("wrong schema version")
}
@@ -1603,19 +1612,28 @@
}
defer resp.Body.Close()
+ manifestBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ t.Fatalf("error reading response body: %v", err)
+ }
+
checkResponse(t, "fetching uploaded manifest list as schema1", resp,
http.StatusOK)
- checkHeaders(t, resp, http.Header{
- "Docker-Content-Digest": []string{dgst.String()},
- "ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
- })
- var fetchedSchema1Manifest schema1.SignedManifest
- dec = json.NewDecoder(resp.Body)
+ m, desc, err :=
distribution.UnmarshalManifest(schema1.MediaTypeManifest, manifestBytes)
+ if err != nil {
+ t.Fatalf("unexpected error unmarshalling manifest: %v", err)
+ }
- if err := dec.Decode(&fetchedSchema1Manifest); err != nil {
- t.Fatalf("error decoding fetched schema1 manifest: %v", err)
+ fetchedSchema1Manifest, ok := m.(*schema1.SignedManifest)
+ if !ok {
+ t.Fatalf("expecting schema1 manifest")
}
+ checkHeaders(t, resp, http.Header{
+ "Docker-Content-Digest": []string{desc.Digest.String()},
+ "ETag": []string{fmt.Sprintf(`"%s"`,
desc.Digest)},
+ })
+
if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
t.Fatal("wrong schema version")
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/registry/handlers/app.go
new/distribution-2.3.1/registry/handlers/app.go
--- old/distribution-2.3.0/registry/handlers/app.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/handlers/app.go 2016-02-23
23:39:19.000000000 +0100
@@ -146,11 +146,18 @@
app.configureRedis(configuration)
app.configureLogHook(configuration)
- // Generate an ephemeral key to be used for signing converted manifests
- // for clients that don't support schema2.
- app.trustKey, err = libtrust.GenerateECP256PrivateKey()
- if err != nil {
- panic(err)
+ if configuration.Compatibility.Schema1.TrustKey != "" {
+ app.trustKey, err =
libtrust.LoadKeyFile(configuration.Compatibility.Schema1.TrustKey)
+ if err != nil {
+ panic(fmt.Sprintf(`could not load schema1 "signingkey"
parameter: %v`, err))
+ }
+ } else {
+ // Generate an ephemeral key to be used for signing converted
manifests
+ // for clients that don't support schema2.
+ app.trustKey, err = libtrust.GenerateECP256PrivateKey()
+ if err != nil {
+ panic(err)
+ }
}
if configuration.HTTP.Host != "" {
@@ -167,6 +174,11 @@
options = append(options, storage.DisableDigestResumption)
}
+ if configuration.Compatibility.Schema1.DisableSignatureStore {
+ options = append(options, storage.DisableSchema1Signatures)
+ options = append(options,
storage.Schema1SigningKey(app.trustKey))
+ }
+
// configure deletion
if d, ok := configuration.Storage["delete"]; ok {
e, ok := d["enabled"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/registry/handlers/images.go
new/distribution-2.3.1/registry/handlers/images.go
--- old/distribution-2.3.0/registry/handlers/images.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/handlers/images.go 2016-02-23
23:39:19.000000000 +0100
@@ -196,6 +196,7 @@
imh.Errors = append(imh.Errors,
v2.ErrorCodeManifestInvalid.WithDetail(err))
return nil, err
}
+ imh.Digest =
digest.FromBytes(manifest.(*schema1.SignedManifest).Canonical)
return manifest, nil
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/registry/proxy/proxyauth.go
new/distribution-2.3.1/registry/proxy/proxyauth.go
--- old/distribution-2.3.0/registry/proxy/proxyauth.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/proxy/proxyauth.go 2016-02-23
23:39:19.000000000 +0100
@@ -8,6 +8,7 @@
)
const tokenURL = "https://auth.docker.io/token"
+const challengeHeader = "Docker-Distribution-Api-Version"
type userpass struct {
username string
@@ -24,12 +25,8 @@
return up.username, up.password
}
-// ConfigureAuth authorizes with the upstream registry
-func ConfigureAuth(remoteURL, username, password string, cm
auth.ChallengeManager) (auth.CredentialStore, error) {
- if err := ping(cm, remoteURL+"/v2/",
"Docker-Distribution-Api-Version"); err != nil {
- return nil, err
- }
-
+// configureAuth stores credentials for challenge responses
+func configureAuth(username, password string) (auth.CredentialStore, error) {
creds := map[string]userpass{
tokenURL: {
username: username,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/registry/proxy/proxyblobstore.go
new/distribution-2.3.1/registry/proxy/proxyblobstore.go
--- old/distribution-2.3.0/registry/proxy/proxyblobstore.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/proxy/proxyblobstore.go 2016-02-23
23:39:19.000000000 +0100
@@ -22,6 +22,7 @@
remoteStore distribution.BlobService
scheduler *scheduler.TTLExpirationScheduler
repositoryName reference.Named
+ authChallenger authChallenger
}
var _ distribution.BlobStore = &proxyBlobStore{}
@@ -121,6 +122,10 @@
return nil
}
+ if err := pbs.authChallenger.tryEstablishChallenges(ctx); err != nil {
+ return err
+ }
+
mu.Lock()
_, ok := inflight[dgst]
if ok {
@@ -162,9 +167,35 @@
return distribution.Descriptor{}, err
}
+ if err := pbs.authChallenger.tryEstablishChallenges(ctx); err != nil {
+ return distribution.Descriptor{}, err
+ }
+
return pbs.remoteStore.Stat(ctx, dgst)
}
+func (pbs *proxyBlobStore) Get(ctx context.Context, dgst digest.Digest)
([]byte, error) {
+ blob, err := pbs.localStore.Get(ctx, dgst)
+ if err == nil {
+ return blob, nil
+ }
+
+ if err := pbs.authChallenger.tryEstablishChallenges(ctx); err != nil {
+ return []byte{}, err
+ }
+
+ blob, err = pbs.remoteStore.Get(ctx, dgst)
+ if err != nil {
+ return []byte{}, err
+ }
+
+ _, err = pbs.localStore.Put(ctx, "", blob)
+ if err != nil {
+ return []byte{}, err
+ }
+ return blob, nil
+}
+
// Unsupported functions
func (pbs *proxyBlobStore) Put(ctx context.Context, mediaType string, p
[]byte) (distribution.Descriptor, error) {
return distribution.Descriptor{}, distribution.ErrUnsupported
@@ -186,10 +217,6 @@
return nil, distribution.ErrUnsupported
}
-func (pbs *proxyBlobStore) Get(ctx context.Context, dgst digest.Digest)
([]byte, error) {
- return nil, distribution.ErrUnsupported
-}
-
func (pbs *proxyBlobStore) Delete(ctx context.Context, dgst digest.Digest)
error {
return distribution.ErrUnsupported
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/distribution-2.3.0/registry/proxy/proxyblobstore_test.go
new/distribution-2.3.1/registry/proxy/proxyblobstore_test.go
--- old/distribution-2.3.0/registry/proxy/proxyblobstore_test.go
2016-02-04 20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/proxy/proxyblobstore_test.go
2016-02-23 23:39:19.000000000 +0100
@@ -168,6 +168,7 @@
remoteStore: truthBlobs,
localStore: localBlobs,
scheduler: s,
+ authChallenger: &mockChallenger{},
}
te := &testEnv{
@@ -217,6 +218,40 @@
te.inRemote = inRemote
te.numUnique = numUnique
}
+func TestProxyStoreGet(t *testing.T) {
+ te := makeTestEnv(t, "foo/bar")
+
+ localStats := te.LocalStats()
+ remoteStats := te.RemoteStats()
+
+ populate(t, te, 1, 10, 1)
+ _, err := te.store.Get(te.ctx, te.inRemote[0].Digest)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if (*localStats)["get"] != 1 && (*localStats)["put"] != 1 {
+ t.Errorf("Unexpected local counts")
+ }
+
+ if (*remoteStats)["get"] != 1 {
+ t.Errorf("Unexpected remote get count")
+ }
+
+ _, err = te.store.Get(te.ctx, te.inRemote[0].Digest)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if (*localStats)["get"] != 2 && (*localStats)["put"] != 1 {
+ t.Errorf("Unexpected local counts")
+ }
+
+ if (*remoteStats)["get"] != 1 {
+ t.Errorf("Unexpected remote get count")
+ }
+
+}
func TestProxyStoreStat(t *testing.T) {
te := makeTestEnv(t, "foo/bar")
@@ -242,6 +277,11 @@
if (*remoteStats)["stat"] != remoteBlobCount {
t.Errorf("Unexpected remote stat count")
}
+
+ if te.store.authChallenger.(*mockChallenger).count != len(te.inRemote) {
+ t.Fatalf("Unexpected auth challenge count, got %#v",
te.store.authChallenger)
+ }
+
}
func TestProxyStoreServeHighConcurrency(t *testing.T) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/distribution-2.3.0/registry/proxy/proxymanifeststore.go
new/distribution-2.3.1/registry/proxy/proxymanifeststore.go
--- old/distribution-2.3.0/registry/proxy/proxymanifeststore.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/proxy/proxymanifeststore.go 2016-02-23
23:39:19.000000000 +0100
@@ -19,6 +19,7 @@
remoteManifests distribution.ManifestService
repositoryName reference.Named
scheduler *scheduler.TTLExpirationScheduler
+ authChallenger authChallenger
}
var _ distribution.ManifestService = &proxyManifestStore{}
@@ -31,7 +32,9 @@
if exists {
return true, nil
}
-
+ if err := pms.authChallenger.tryEstablishChallenges(ctx); err != nil {
+ return false, err
+ }
return pms.remoteManifests.Exists(ctx, dgst)
}
@@ -41,6 +44,10 @@
var fromRemote bool
manifest, err := pms.localManifests.Get(ctx, dgst, options...)
if err != nil {
+ if err := pms.authChallenger.tryEstablishChallenges(ctx); err
!= nil {
+ return nil, err
+ }
+
manifest, err = pms.remoteManifests.Get(ctx, dgst, options...)
if err != nil {
return nil, err
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/distribution-2.3.0/registry/proxy/proxymanifeststore_test.go
new/distribution-2.3.1/registry/proxy/proxymanifeststore_test.go
--- old/distribution-2.3.0/registry/proxy/proxymanifeststore_test.go
2016-02-04 20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/proxy/proxymanifeststore_test.go
2016-02-23 23:39:19.000000000 +0100
@@ -2,6 +2,7 @@
import (
"io"
+ "sync"
"testing"
"github.com/docker/distribution"
@@ -10,6 +11,7 @@
"github.com/docker/distribution/manifest"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/reference"
+ "github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/proxy/scheduler"
"github.com/docker/distribution/registry/storage"
"github.com/docker/distribution/registry/storage/cache/memory"
@@ -64,6 +66,28 @@
}
*/
+type mockChallenger struct {
+ sync.Mutex
+ count int
+}
+
+// Called for remote operations only
+func (m *mockChallenger) tryEstablishChallenges(context.Context) error {
+ m.Lock()
+ defer m.Unlock()
+
+ m.count++
+ return nil
+}
+
+func (m *mockChallenger) credentialStore() auth.CredentialStore {
+ return nil
+}
+
+func (m *mockChallenger) challengeManager() auth.ChallengeManager {
+ return nil
+}
+
func newManifestStoreTestEnv(t *testing.T, name, tag string)
*manifestStoreTestEnv {
nameRef, err := reference.ParseNamed(name)
if err != nil {
@@ -120,6 +144,7 @@
remoteManifests: truthManifests,
scheduler: s,
repositoryName: nameRef,
+ authChallenger: &mockChallenger{},
},
}
}
@@ -198,6 +223,10 @@
t.Errorf("Unexpected exists count : \n%v \n%v", localStats,
remoteStats)
}
+ if env.manifests.authChallenger.(*mockChallenger).count != 1 {
+ t.Fatalf("Expected 1 auth challenge, got %#v",
env.manifests.authChallenger)
+ }
+
// Get - should succeed and pull manifest into local
_, err = env.manifests.Get(ctx, env.manifestDigest)
if err != nil {
@@ -212,6 +241,10 @@
t.Errorf("Expected local put")
}
+ if env.manifests.authChallenger.(*mockChallenger).count != 2 {
+ t.Fatalf("Expected 2 auth challenges, got %#v",
env.manifests.authChallenger)
+ }
+
// Stat - should only go to local
exists, err = env.manifests.Exists(ctx, env.manifestDigest)
if err != nil {
@@ -225,17 +258,18 @@
t.Errorf("Unexpected exists count")
}
- // Get - should get from remote, to test freshness
+ if env.manifests.authChallenger.(*mockChallenger).count != 2 {
+ t.Fatalf("Expected 2 auth challenges, got %#v",
env.manifests.authChallenger)
+ }
+
+ // Get proxied - won't require another authchallenge
_, err = env.manifests.Get(ctx, env.manifestDigest)
if err != nil {
t.Fatal(err)
}
- if (*remoteStats)["get"] != 2 && (*remoteStats)["exists"] != 1 &&
(*localStats)["put"] != 1 {
- t.Errorf("Unexpected get count")
+ if env.manifests.authChallenger.(*mockChallenger).count != 2 {
+ t.Fatalf("Expected 2 auth challenges, got %#v",
env.manifests.authChallenger)
}
-}
-
-func TestProxyTagService(t *testing.T) {
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/registry/proxy/proxyregistry.go
new/distribution-2.3.1/registry/proxy/proxyregistry.go
--- old/distribution-2.3.0/registry/proxy/proxyregistry.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/proxy/proxyregistry.go 2016-02-23
23:39:19.000000000 +0100
@@ -1,10 +1,11 @@
package proxy
import (
+ "fmt"
"net/http"
"net/url"
+ "sync"
- "fmt"
"github.com/docker/distribution"
"github.com/docker/distribution/configuration"
"github.com/docker/distribution/context"
@@ -19,13 +20,10 @@
// proxyingRegistry fetches content from a remote registry and caches it
locally
type proxyingRegistry struct {
- embedded distribution.Namespace // provides local registry functionality
-
- scheduler *scheduler.TTLExpirationScheduler
-
- remoteURL string
- credentialStore auth.CredentialStore
- challengeManager auth.ChallengeManager
+ embedded distribution.Namespace // provides local registry
functionality
+ scheduler *scheduler.TTLExpirationScheduler
+ remoteURL string
+ authChallenger authChallenger
}
// NewRegistryPullThroughCache creates a registry acting as a pull through
cache
@@ -93,18 +91,20 @@
return nil, err
}
- challengeManager := auth.NewSimpleChallengeManager()
- cs, err := ConfigureAuth(config.RemoteURL, config.Username,
config.Password, challengeManager)
+ cs, err := configureAuth(config.Username, config.Password)
if err != nil {
return nil, err
}
return &proxyingRegistry{
- embedded: registry,
- scheduler: s,
- challengeManager: challengeManager,
- credentialStore: cs,
- remoteURL: config.RemoteURL,
+ embedded: registry,
+ scheduler: s,
+ remoteURL: config.RemoteURL,
+ authChallenger: &remoteAuthChallenger{
+ remoteURL: config.RemoteURL,
+ cm: auth.NewSimpleChallengeManager(),
+ cs: cs,
+ },
}, nil
}
@@ -117,8 +117,10 @@
}
func (pr *proxyingRegistry) Repository(ctx context.Context, name
reference.Named) (distribution.Repository, error) {
+ c := pr.authChallenger
+
tr := transport.NewTransport(http.DefaultTransport,
- auth.NewAuthorizer(pr.challengeManager,
auth.NewTokenHandler(http.DefaultTransport, pr.credentialStore, name.Name(),
"pull")))
+ auth.NewAuthorizer(c.challengeManager(),
auth.NewTokenHandler(http.DefaultTransport, c.credentialStore(), name.Name(),
"pull")))
localRepo, err := pr.embedded.Repository(ctx, name)
if err != nil {
@@ -145,6 +147,7 @@
remoteStore: remoteRepo.Blobs(ctx),
scheduler: pr.scheduler,
repositoryName: name,
+ authChallenger: pr.authChallenger,
},
manifests: &proxyManifestStore{
repositoryName: name,
@@ -152,15 +155,63 @@
remoteManifests: remoteManifests,
ctx: ctx,
scheduler: pr.scheduler,
+ authChallenger: pr.authChallenger,
},
name: name,
tags: &proxyTagService{
- localTags: localRepo.Tags(ctx),
- remoteTags: remoteRepo.Tags(ctx),
+ localTags: localRepo.Tags(ctx),
+ remoteTags: remoteRepo.Tags(ctx),
+ authChallenger: pr.authChallenger,
},
}, nil
}
+// authChallenger encapsulates a request to the upstream to establish
credential challenges
+type authChallenger interface {
+ tryEstablishChallenges(context.Context) error
+ challengeManager() auth.ChallengeManager
+ credentialStore() auth.CredentialStore
+}
+
+type remoteAuthChallenger struct {
+ remoteURL string
+ sync.Mutex
+ cm auth.ChallengeManager
+ cs auth.CredentialStore
+}
+
+func (r *remoteAuthChallenger) credentialStore() auth.CredentialStore {
+ return r.cs
+}
+
+func (r *remoteAuthChallenger) challengeManager() auth.ChallengeManager {
+ return r.cm
+}
+
+// tryEstablishChallenges will attempt to get a challenge type for the
upstream if none currently exist
+func (r *remoteAuthChallenger) tryEstablishChallenges(ctx context.Context)
error {
+ r.Lock()
+ defer r.Unlock()
+
+ remoteURL := r.remoteURL + "/v2/"
+ challenges, err := r.cm.GetChallenges(remoteURL)
+ if err != nil {
+ return err
+ }
+
+ if len(challenges) > 0 {
+ return nil
+ }
+
+ // establish challenge type with upstream
+ if err := ping(r.cm, remoteURL, challengeHeader); err != nil {
+ return err
+ }
+
+ context.GetLogger(ctx).Infof("Challenge established with upstream : %s
%s", remoteURL, r.cm)
+ return nil
+}
+
// proxiedRepository uses proxying blob and manifest services to serve content
// locally, or pulling it through from a remote and caching it locally if it
doesn't
// already exist
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/registry/proxy/proxytagservice.go
new/distribution-2.3.1/registry/proxy/proxytagservice.go
--- old/distribution-2.3.0/registry/proxy/proxytagservice.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/proxy/proxytagservice.go 2016-02-23
23:39:19.000000000 +0100
@@ -7,8 +7,9 @@
// proxyTagService supports local and remote lookup of tags.
type proxyTagService struct {
- localTags distribution.TagService
- remoteTags distribution.TagService
+ localTags distribution.TagService
+ remoteTags distribution.TagService
+ authChallenger authChallenger
}
var _ distribution.TagService = proxyTagService{}
@@ -17,16 +18,19 @@
// tag service first and then caching it locally. If the remote is unavailable
// the local association is returned
func (pt proxyTagService) Get(ctx context.Context, tag string)
(distribution.Descriptor, error) {
- desc, err := pt.remoteTags.Get(ctx, tag)
+ err := pt.authChallenger.tryEstablishChallenges(ctx)
if err == nil {
- err := pt.localTags.Tag(ctx, tag, desc)
- if err != nil {
- return distribution.Descriptor{}, err
+ desc, err := pt.remoteTags.Get(ctx, tag)
+ if err == nil {
+ err := pt.localTags.Tag(ctx, tag, desc)
+ if err != nil {
+ return distribution.Descriptor{}, err
+ }
+ return desc, nil
}
- return desc, nil
}
- desc, err = pt.localTags.Get(ctx, tag)
+ desc, err := pt.localTags.Get(ctx, tag)
if err != nil {
return distribution.Descriptor{}, err
}
@@ -46,9 +50,12 @@
}
func (pt proxyTagService) All(ctx context.Context) ([]string, error) {
- tags, err := pt.remoteTags.All(ctx)
+ err := pt.authChallenger.tryEstablishChallenges(ctx)
if err == nil {
- return tags, err
+ tags, err := pt.remoteTags.All(ctx)
+ if err == nil {
+ return tags, err
+ }
}
return pt.localTags.All(ctx)
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/distribution-2.3.0/registry/proxy/proxytagservice_test.go
new/distribution-2.3.1/registry/proxy/proxytagservice_test.go
--- old/distribution-2.3.0/registry/proxy/proxytagservice_test.go
2016-02-04 20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/proxy/proxytagservice_test.go
2016-02-23 23:39:19.000000000 +0100
@@ -69,8 +69,9 @@
remote = make(map[string]distribution.Descriptor)
}
return &proxyTagService{
- localTags: &mockTagStore{mapping: local},
- remoteTags: &mockTagStore{mapping: remote},
+ localTags: &mockTagStore{mapping: local},
+ remoteTags: &mockTagStore{mapping: remote},
+ authChallenger: &mockChallenger{},
}
}
@@ -87,6 +88,10 @@
t.Fatal(err)
}
+ if proxyTags.authChallenger.(*mockChallenger).count != 1 {
+ t.Fatalf("Expected 1 auth challenge call, got %#v",
proxyTags.authChallenger)
+ }
+
if d != remoteDesc {
t.Fatal("unable to get put tag")
}
@@ -112,6 +117,10 @@
t.Fatal(err)
}
+ if proxyTags.authChallenger.(*mockChallenger).count != 2 {
+ t.Fatalf("Expected 2 auth challenge calls, got %#v",
proxyTags.authChallenger)
+ }
+
if d != newRemoteDesc {
t.Fatal("unable to get put tag")
}
@@ -142,7 +151,11 @@
t.Fatal("untagged tag should be pulled through")
}
- // Add another tag. Ensure both tags appear in enumerate
+ if proxyTags.authChallenger.(*mockChallenger).count != 3 {
+ t.Fatalf("Expected 3 auth challenge calls, got %#v",
proxyTags.authChallenger)
+ }
+
+ // Add another tag. Ensure both tags appear in 'All'
err = proxyTags.remoteTags.Tag(ctx, "funtag",
distribution.Descriptor{Size: 42})
if err != nil {
t.Fatal(err)
@@ -161,4 +174,8 @@
if all[0] != "funtag" && all[1] != "remote" {
t.Fatalf("Unexpected tags returned from All() : %v ", all)
}
+
+ if proxyTags.authChallenger.(*mockChallenger).count != 4 {
+ t.Fatalf("Expected 4 auth challenge calls, got %#v",
proxyTags.authChallenger)
+ }
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/distribution-2.3.0/registry/storage/manifeststore_test.go
new/distribution-2.3.1/registry/storage/manifeststore_test.go
--- old/distribution-2.3.0/registry/storage/manifeststore_test.go
2016-02-04 20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/storage/manifeststore_test.go
2016-02-23 23:39:19.000000000 +0100
@@ -28,11 +28,10 @@
tag string
}
-func newManifestStoreTestEnv(t *testing.T, name reference.Named, tag string)
*manifestStoreTestEnv {
+func newManifestStoreTestEnv(t *testing.T, name reference.Named, tag string,
options ...RegistryOption) *manifestStoreTestEnv {
ctx := context.Background()
driver := inmemory.New()
- registry, err := NewRegistry(ctx, driver, BlobDescriptorCacheProvider(
- memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete,
EnableRedirect)
+ registry, err := NewRegistry(ctx, driver, options...)
if err != nil {
t.Fatalf("error creating registry: %v", err)
}
@@ -53,13 +52,26 @@
}
func TestManifestStorage(t *testing.T) {
+ testManifestStorage(t,
BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()),
EnableDelete, EnableRedirect)
+}
+
+func TestManifestStorageDisabledSignatures(t *testing.T) {
+ k, err := libtrust.GenerateECP256PrivateKey()
+ if err != nil {
+ t.Fatal(err)
+ }
+ testManifestStorage(t,
BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()),
EnableDelete, EnableRedirect, DisableSchema1Signatures, Schema1SigningKey(k))
+}
+
+func testManifestStorage(t *testing.T, options ...RegistryOption) {
repoName, _ := reference.ParseNamed("foo/bar")
- env := newManifestStoreTestEnv(t, repoName, "thetag")
+ env := newManifestStoreTestEnv(t, repoName, "thetag", options...)
ctx := context.Background()
ms, err := env.repository.Manifests(ctx)
if err != nil {
t.Fatal(err)
}
+ equalSignatures := env.registry.(*registry).schema1SignaturesEnabled
m := schema1.Manifest{
Versioned: manifest.Versioned{
@@ -159,8 +171,14 @@
t.Fatalf("unexpected manifest type from signedstore")
}
- if !reflect.DeepEqual(fetchedManifest, sm) {
- t.Fatalf("fetched manifest not equal: %#v != %#v",
fetchedManifest, sm)
+ if !bytes.Equal(fetchedManifest.Canonical, sm.Canonical) {
+ t.Fatalf("fetched payload does not match original payload: %q
!= %q", fetchedManifest.Canonical, sm.Canonical)
+ }
+
+ if equalSignatures {
+ if !reflect.DeepEqual(fetchedManifest, sm) {
+ t.Fatalf("fetched manifest not equal: %#v != %#v",
fetchedManifest.Manifest, sm.Manifest)
+ }
}
_, pl, err := fetchedManifest.Payload()
@@ -196,8 +214,19 @@
t.Fatalf("unexpected error fetching manifest by digest: %v",
err)
}
- if !reflect.DeepEqual(fetchedByDigest, fetchedManifest) {
- t.Fatalf("fetched manifest not equal: %#v != %#v",
fetchedByDigest, fetchedManifest)
+ byDigestManifest, ok := fetchedByDigest.(*schema1.SignedManifest)
+ if !ok {
+ t.Fatalf("unexpected manifest type from signedstore")
+ }
+
+ if !bytes.Equal(byDigestManifest.Canonical, fetchedManifest.Canonical) {
+ t.Fatalf("fetched manifest not equal: %q != %q",
byDigestManifest.Canonical, fetchedManifest.Canonical)
+ }
+
+ if equalSignatures {
+ if !reflect.DeepEqual(fetchedByDigest, fetchedManifest) {
+ t.Fatalf("fetched manifest not equal: %#v != %#v",
fetchedByDigest, fetchedManifest)
+ }
}
sigs, err := fetchedJWS.Signatures()
@@ -286,14 +315,16 @@
t.Fatalf("payloads are not equal")
}
- receivedSigs, err := receivedJWS.Signatures()
- if err != nil {
- t.Fatalf("error getting signatures: %v", err)
- }
+ if equalSignatures {
+ receivedSigs, err := receivedJWS.Signatures()
+ if err != nil {
+ t.Fatalf("error getting signatures: %v", err)
+ }
- for i, sig := range receivedSigs {
- if !bytes.Equal(sig, expectedSigs[i]) {
- t.Fatalf("mismatched signatures from remote: %v != %v",
string(sig), string(expectedSigs[i]))
+ for i, sig := range receivedSigs {
+ if !bytes.Equal(sig, expectedSigs[i]) {
+ t.Fatalf("mismatched signatures from remote: %v
!= %v", string(sig), string(expectedSigs[i]))
+ }
}
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/distribution-2.3.0/registry/storage/registry.go
new/distribution-2.3.1/registry/storage/registry.go
--- old/distribution-2.3.0/registry/storage/registry.go 2016-02-04
20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/storage/registry.go 2016-02-23
23:39:19.000000000 +0100
@@ -6,6 +6,7 @@
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/storage/cache"
storagedriver "github.com/docker/distribution/registry/storage/driver"
+ "github.com/docker/libtrust"
)
// registry is the top-level implementation of Registry for use in the storage
@@ -17,6 +18,8 @@
blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider
deleteEnabled bool
resumableDigestEnabled bool
+ schema1SignaturesEnabled bool
+ schema1SigningKey libtrust.PrivateKey
}
// RegistryOption is the type used for functional options for NewRegistry.
@@ -43,6 +46,24 @@
return nil
}
+// DisableSchema1Signatures is a functional option for NewRegistry. It disables
+// signature storage and ensures all schema1 manifests will only be returned
+// with a signature from a provided signing key.
+func DisableSchema1Signatures(registry *registry) error {
+ registry.schema1SignaturesEnabled = false
+ return nil
+}
+
+// Schema1SigningKey returns a functional option for NewRegistry. It sets the
+// signing key for adding a signature to all schema1 manifests. This should be
+// used in conjunction with disabling signature store.
+func Schema1SigningKey(key libtrust.PrivateKey) RegistryOption {
+ return func(registry *registry) error {
+ registry.schema1SigningKey = key
+ return nil
+ }
+}
+
// BlobDescriptorCacheProvider returns a functional option for
// NewRegistry. It creates a cached blob statter for use by the
// registry.
@@ -85,8 +106,9 @@
statter: statter,
pathFn: bs.path,
},
- statter: statter,
- resumableDigestEnabled: true,
+ statter: statter,
+ resumableDigestEnabled: true,
+ schema1SignaturesEnabled: true,
}
for _, option := range options {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/distribution-2.3.0/registry/storage/signedmanifesthandler.go
new/distribution-2.3.1/registry/storage/signedmanifesthandler.go
--- old/distribution-2.3.0/registry/storage/signedmanifesthandler.go
2016-02-04 20:11:33.000000000 +0100
+++ new/distribution-2.3.1/registry/storage/signedmanifesthandler.go
2016-02-23 23:39:19.000000000 +0100
@@ -25,10 +25,17 @@
func (ms *signedManifestHandler) Unmarshal(ctx context.Context, dgst
digest.Digest, content []byte) (distribution.Manifest, error) {
context.GetLogger(ms.ctx).Debug("(*signedManifestHandler).Unmarshal")
- // Fetch the signatures for the manifest
- signatures, err := ms.signatures.Get(dgst)
- if err != nil {
- return nil, err
+
+ var (
+ signatures [][]byte
+ err error
+ )
+ if ms.repository.schema1SignaturesEnabled {
+ // Fetch the signatures for the manifest
+ signatures, err = ms.signatures.Get(dgst)
+ if err != nil {
+ return nil, err
+ }
}
jsig, err := libtrust.NewJSONSignature(content, signatures...)
@@ -36,6 +43,14 @@
return nil, err
}
+ if ms.repository.schema1SigningKey != nil {
+ if err := jsig.Sign(ms.repository.schema1SigningKey); err !=
nil {
+ return nil, err
+ }
+ } else if !ms.repository.schema1SignaturesEnabled {
+ return nil, fmt.Errorf("missing signing key with signature
store disabled")
+ }
+
// Extract the pretty JWS
raw, err := jsig.PrettySignature("signatures")
if err != nil {
@@ -75,14 +90,16 @@
return "", err
}
- // Grab each json signature and store them.
- signatures, err := sm.Signatures()
- if err != nil {
- return "", err
- }
-
- if err := ms.signatures.Put(revision.Digest, signatures...); err != nil
{
- return "", err
+ if ms.repository.schema1SignaturesEnabled {
+ // Grab each json signature and store them.
+ signatures, err := sm.Signatures()
+ if err != nil {
+ return "", err
+ }
+
+ if err := ms.signatures.Put(revision.Digest, signatures...);
err != nil {
+ return "", err
+ }
}
return revision.Digest, nil