Prevent push and pull to v1 registries by filtering the available endpoints.

Add a daemon flag to control this behaviour.  Add a warning message when pulling
an image from a v1 registry.  The default order of pull is slightly altered
with this changset.

Previously it was:
https v2, https v1, http v2, http v1

now it is:
https v2, http v2, https v1, http v1

Prevent login to v1 registries by explicitly setting the version before ping to
prevent fallback to v1.

Add unit tests for v2 only mode.  Create a mock server that can register
handlers for various endpoints.  Assert no v1 endpoints are hit with legacy
registries disabled for the following commands:  pull, push, build, run and
login.  Assert the opposite when legacy registries are not disabled.

Signed-off-by: Richard Scothern <richard.scothern@gmail.com>
This commit is contained in:
Richard Scothern
2015-10-06 18:26:17 -07:00
committed by Jessica Frazelle
parent 6321ac9182
commit 668a1758bd
12 changed files with 400 additions and 104 deletions
+9
View File
@@ -31,15 +31,24 @@ func init() {
type DockerRegistrySuite struct {
ds *DockerSuite
reg *testRegistryV2
d *Daemon
}
func (s *DockerRegistrySuite) SetUpTest(c *check.C) {
s.reg = setupRegistry(c)
s.d = NewDaemon(c)
}
func (s *DockerRegistrySuite) TearDownTest(c *check.C) {
s.reg.Close()
s.ds.TearDownTest(c)
if s.reg != nil {
s.reg.Close()
}
if s.ds != nil {
s.ds.TearDownTest(c)
}
s.d.Stop()
}
func init() {
+147
View File
@@ -0,0 +1,147 @@
package main
import (
"fmt"
"github.com/go-check/check"
"io/ioutil"
"net/http"
"os"
)
func makefile(contents string) (string, func(), error) {
cleanup := func() {
}
f, err := ioutil.TempFile(".", "tmp")
if err != nil {
return "", cleanup, err
}
err = ioutil.WriteFile(f.Name(), []byte(contents), os.ModePerm)
if err != nil {
return "", cleanup, err
}
cleanup = func() {
err := os.Remove(f.Name())
if err != nil {
fmt.Println("Error removing tmpfile")
}
}
return f.Name(), cleanup, nil
}
// TestV2Only ensures that a daemon in v2-only mode does not
// attempt to contact any v1 registry endpoints.
func (s *DockerRegistrySuite) TestV2Only(c *check.C) {
reg, err := newTestRegistry(c)
if err != nil {
c.Fatal(err.Error())
}
reg.registerHandler("/v2/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
})
reg.registerHandler("/v1/.*", func(w http.ResponseWriter, r *http.Request) {
c.Fatal("V1 registry contacted")
})
repoName := fmt.Sprintf("%s/busybox", reg.hostport)
err = s.d.Start("--insecure-registry", reg.hostport, "--no-legacy-registry=true")
if err != nil {
c.Fatalf("Error starting daemon: %s", err.Error())
}
dockerfileName, cleanup, err := makefile(fmt.Sprintf("FROM %s/busybox", reg.hostport))
if err != nil {
c.Fatalf("Unable to create test dockerfile")
}
defer cleanup()
s.d.Cmd("build", "--file", dockerfileName, ".")
s.d.Cmd("run", repoName)
s.d.Cmd("login", "-u", "richard", "-p", "testtest", "-e", "testuser@testdomain.com", reg.hostport)
s.d.Cmd("tag", "busybox", repoName)
s.d.Cmd("push", repoName)
s.d.Cmd("pull", repoName)
}
// TestV1 starts a daemon in 'normal' mode
// and ensure v1 endpoints are hit for the following operations:
// login, push, pull, build & run
func (s *DockerRegistrySuite) TestV1(c *check.C) {
reg, err := newTestRegistry(c)
if err != nil {
c.Fatal(err.Error())
}
v2Pings := 0
reg.registerHandler("/v2/", func(w http.ResponseWriter, r *http.Request) {
v2Pings++
// V2 ping 404 causes fallback to v1
w.WriteHeader(404)
})
v1Pings := 0
reg.registerHandler("/v1/_ping", func(w http.ResponseWriter, r *http.Request) {
v1Pings++
})
v1Logins := 0
reg.registerHandler("/v1/users/", func(w http.ResponseWriter, r *http.Request) {
v1Logins++
})
v1Repo := 0
reg.registerHandler("/v1/repositories/busybox/", func(w http.ResponseWriter, r *http.Request) {
v1Repo++
})
reg.registerHandler("/v1/repositories/busybox/images", func(w http.ResponseWriter, r *http.Request) {
v1Repo++
})
err = s.d.Start("--insecure-registry", reg.hostport, "--no-legacy-registry=false")
if err != nil {
c.Fatalf("Error starting daemon: %s", err.Error())
}
dockerfileName, cleanup, err := makefile(fmt.Sprintf("FROM %s/busybox", reg.hostport))
if err != nil {
c.Fatalf("Unable to create test dockerfile")
}
defer cleanup()
s.d.Cmd("build", "--file", dockerfileName, ".")
if v1Repo == 0 {
c.Errorf("Expected v1 repository access after build")
}
repoName := fmt.Sprintf("%s/busybox", reg.hostport)
s.d.Cmd("run", repoName)
if v1Repo == 1 {
c.Errorf("Expected v1 repository access after run")
}
s.d.Cmd("login", "-u", "richard", "-p", "testtest", "-e", "testuser@testdomain.com", reg.hostport)
if v1Logins == 0 {
c.Errorf("Expected v1 login attempt")
}
s.d.Cmd("tag", "busybox", repoName)
s.d.Cmd("push", repoName)
if v1Repo != 2 || v1Pings != 1 {
c.Error("Not all endpoints contacted after push", v1Repo, v1Pings)
}
s.d.Cmd("pull", repoName)
if v1Repo != 3 {
c.Errorf("Expected v1 repository access after pull")
}
}
+56
View File
@@ -0,0 +1,56 @@
package main
import (
"net/http"
"net/http/httptest"
"regexp"
"strings"
"sync"
"github.com/go-check/check"
)
type handlerFunc func(w http.ResponseWriter, r *http.Request)
type testRegistry struct {
server *httptest.Server
hostport string
handlers map[string]handlerFunc
mu sync.Mutex
}
func (tr *testRegistry) registerHandler(path string, h handlerFunc) {
tr.mu.Lock()
defer tr.mu.Unlock()
tr.handlers[path] = h
}
func newTestRegistry(c *check.C) (*testRegistry, error) {
testReg := &testRegistry{handlers: make(map[string]handlerFunc)}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
url := r.URL.String()
var matched bool
var err error
for re, function := range testReg.handlers {
matched, err = regexp.MatchString(re, url)
if err != nil {
c.Fatalf("Error with handler regexp")
return
}
if matched {
function(w, r)
break
}
}
if !matched {
c.Fatal("Unable to match", url, "with regexp")
}
}))
testReg.server = ts
testReg.hostport = strings.Replace(ts.URL, "http://", "", 1)
return testReg, nil
}