Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6d7f428
Adds support for v2 registry login
Dec 12, 2014
964763b
Add trust key creation on client
dmcgowan Oct 22, 2014
b94732e
Push flow
dmcgowan Oct 1, 2014
a3ed289
Update manifest format for push
dmcgowan Oct 10, 2014
83e4eb9
Use tarsum dev version to fix mtime issue
dmcgowan Oct 10, 2014
fa006d6
Update push to use mount blob endpoint
dmcgowan Nov 15, 2014
914be97
Update token response handling
Dec 12, 2014
0c66109
Registry V2 HTTP route and error code definitions
stevvooe Dec 12, 2014
9e8b9de
Remove dependencies on registry packages
stevvooe Dec 15, 2014
2f554a7
Update push and pull to registry 2.1 specification
dmcgowan Dec 17, 2014
78b6d89
Allow private V2 registry endpoints
dmcgowan Dec 19, 2014
e4823e8
Get token on each request
dmcgowan Dec 20, 2014
450b5eb
Correctly check and propagate errors in v2 session
stevvooe Dec 22, 2014
00dd323
Fix tests
dmcgowan Dec 23, 2014
ef9cf88
Add Tarsum Calculation during v2 Pull operation
Dec 23, 2014
2e4bb3b
Refactor from feedback
dmcgowan Jan 2, 2015
a75c701
Update push to sign with the daemon's key when no manifest is given
dmcgowan Jan 7, 2015
4667641
Fix list tags
dmcgowan Jan 7, 2015
2d8971a
Fix integration test failures
dmcgowan Jan 12, 2015
d9f5757
Install registry V2 in image
LK4D4 Jan 12, 2015
0154068
RegistryV2 datastructure for tests
LK4D4 Jan 12, 2015
018edd3
Tests for push to registry v2
LK4D4 Jan 12, 2015
f652e54
Add some push test coverage
Jan 13, 2015
93b41d6
Test pulling image with aliases
Jan 13, 2015
d4df9d9
Refactor push and pull to move code out of cmd function
dmcgowan Jan 13, 2015
43e1d96
Identify push operation by command http header
stevvooe Jan 15, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ RUN set -x \
&& git clone -b v1.2 https://github.com/russross/blackfriday.git /go/src/github.com/russross/blackfriday \
&& go install -v github.com/cpuguy83/go-md2man

# Install registry
COPY pkg/tarsum /go/src/github.com/docker/docker/pkg/tarsum
# REGISTRY_COMMIT gives us the repeatability guarantees we need
# (so that we're all testing the same version of the registry)
ENV REGISTRY_COMMIT 21a69f53b5c7986b831f33849d551cd59ec8cbd1
RUN set -x \
&& git clone https://github.com/docker/distribution.git /go/src/github.com/docker/distribution \
&& (cd /go/src/github.com/docker/distribution && git checkout -q $REGISTRY_COMMIT) \
&& go get -d github.com/docker/distribution/cmd/registry \
&& go build -o /go/bin/registry-v2 github.com/docker/distribution/cmd/registry

# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]

Expand Down
23 changes: 22 additions & 1 deletion api/client/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
"github.com/docker/libtrust"
)

const (
Expand Down Expand Up @@ -1195,6 +1196,26 @@ func (cli *DockerCli) CmdPush(args ...string) error {

v := url.Values{}
v.Set("tag", tag)

body, _, err := readBody(cli.call("GET", "/images/"+remote+"/manifest?"+v.Encode(), nil, false))
if err != nil {
return err
}

js, err := libtrust.NewJSONSignature(body)
if err != nil {
return err
}
err = js.Sign(cli.key)
if err != nil {
return err
}

signedBody, err := js.PrettySignature("signatures")
if err != nil {
return err
}

push := func(authConfig registry.AuthConfig) error {
buf, err := json.Marshal(authConfig)
if err != nil {
Expand All @@ -1204,7 +1225,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
base64.URLEncoding.EncodeToString(buf),
}

return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), bytes.NewReader(signedBody), cli.out, map[string][]string{
"X-Registry-Auth": registryAuthHeader,
})
}
Expand Down
19 changes: 19 additions & 0 deletions api/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,18 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons
return job.Run()
}

func getImageManifest(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}

job := eng.Job("image_manifest", vars["name"])
job.Setenv("tag", r.Form.Get("tag"))
job.Stdout.Add(utils.NewWriteFlusher(w))

return job.Run()
}

func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
Expand Down Expand Up @@ -639,9 +651,15 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response
}
}

manifest, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}

job := eng.Job("push", vars["name"])
job.SetenvJson("metaHeaders", metaHeaders)
job.SetenvJson("authConfig", authConfig)
job.Setenv("manifest", string(manifest))
job.Setenv("tag", r.Form.Get("tag"))
if version.GreaterThan("1.0") {
job.SetenvBool("json", true)
Expand Down Expand Up @@ -1276,6 +1294,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
"/images/viz": getImagesViz,
"/images/search": getImagesSearch,
"/images/get": getImagesGet,
"/images/{name:.*}/manifest": getImageManifest,
"/images/{name:.*}/get": getImagesGet,
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,
Expand Down
12 changes: 6 additions & 6 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -897,8 +897,13 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
return nil, err
}

trustKey, err := api.LoadOrCreateTrustKey(config.TrustKeyPath)
if err != nil {
return nil, err
}

log.Debugf("Creating repository list")
repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g)
repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, trustKey)
if err != nil {
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
}
Expand Down Expand Up @@ -961,11 +966,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
return nil, err
}

trustKey, err := api.LoadOrCreateTrustKey(config.TrustKeyPath)
if err != nil {
return nil, err
}

daemon := &Daemon{
ID: trustKey.PublicKey().KeyID(),
repository: daemonRepo,
Expand Down
9 changes: 7 additions & 2 deletions docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ func main() {
}
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)

trustKey, err := api.LoadOrCreateTrustKey(*flTrustKey)
if err != nil {
log.Fatal(err)
}

var (
cli *client.DockerCli
tlsConfig tls.Config
Expand Down Expand Up @@ -118,9 +123,9 @@ func main() {
}

if *flTls || *flTlsVerify {
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, nil, protoAddrParts[0], protoAddrParts[1], &tlsConfig)
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, trustKey, protoAddrParts[0], protoAddrParts[1], &tlsConfig)
} else {
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, nil, protoAddrParts[0], protoAddrParts[1], nil)
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, trustKey, protoAddrParts[0], protoAddrParts[1], nil)
}

if err := cli.Cmd(flag.Args()...); err != nil {
Expand Down
196 changes: 196 additions & 0 deletions graph/manifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package graph

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"path"

log "github.com/Sirupsen/logrus"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/tarsum"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/libtrust"
)

func (s *TagStore) CmdManifest(job *engine.Job) engine.Status {
if len(job.Args) != 1 {
return job.Errorf("usage: %s NAME", job.Name)
}
name := job.Args[0]
tag := job.Getenv("tag")
if tag == "" {
tag = "latest"
}

// Resolve the Repository name from fqn to endpoint + name
repoInfo, err := registry.ParseRepositoryInfo(name)
if err != nil {
return job.Error(err)
}

manifestBytes, err := s.newManifest(name, repoInfo.RemoteName, tag)
if err != nil {
return job.Error(err)
}

_, err = job.Stdout.Write(manifestBytes)
if err != nil {
return job.Error(err)
}

return engine.StatusOK
}

func (s *TagStore) newManifest(localName, remoteName, tag string) ([]byte, error) {
manifest := &registry.ManifestData{
Name: remoteName,
Tag: tag,
SchemaVersion: 1,
}
localRepo, err := s.Get(localName)
if err != nil {
return nil, err
}
if localRepo == nil {
return nil, fmt.Errorf("Repo does not exist: %s", localName)
}

// Get the top-most layer id which the tag points to
layerId, exists := localRepo[tag]
if !exists {
return nil, fmt.Errorf("Tag does not exist for %s: %s", localName, tag)
}
layersSeen := make(map[string]bool)

layer, err := s.graph.Get(layerId)
if err != nil {
return nil, err
}
if layer.Config == nil {
return nil, errors.New("Missing layer configuration")
}
manifest.Architecture = layer.Architecture
manifest.FSLayers = make([]*registry.FSLayer, 0, 4)
manifest.History = make([]*registry.ManifestHistory, 0, 4)
var metadata runconfig.Config
metadata = *layer.Config

for ; layer != nil; layer, err = layer.GetParent() {
if err != nil {
return nil, err
}

if layersSeen[layer.ID] {
break
}
if layer.Config != nil && metadata.Image != layer.ID {
err = runconfig.Merge(&metadata, layer.Config)
if err != nil {
return nil, err
}
}

archive, err := layer.TarLayer()
if err != nil {
return nil, err
}

tarSum, err := tarsum.NewTarSum(archive, true, tarsum.Version1)
if err != nil {
return nil, err
}
if _, err := io.Copy(ioutil.Discard, tarSum); err != nil {
return nil, err
}

tarId := tarSum.Sum(nil)

manifest.FSLayers = append(manifest.FSLayers, &registry.FSLayer{BlobSum: tarId})

layersSeen[layer.ID] = true
jsonData, err := ioutil.ReadFile(path.Join(s.graph.Root, layer.ID, "json"))
if err != nil {
return nil, fmt.Errorf("Cannot retrieve the path for {%s}: %s", layer.ID, err)
}
manifest.History = append(manifest.History, &registry.ManifestHistory{V1Compatibility: string(jsonData)})
}

manifestBytes, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return nil, err
}

return manifestBytes, nil
}

func (s *TagStore) verifyManifest(eng *engine.Engine, manifestBytes []byte) (*registry.ManifestData, bool, error) {
sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures")
if err != nil {
return nil, false, fmt.Errorf("error parsing payload: %s", err)
}

keys, err := sig.Verify()
if err != nil {
return nil, false, fmt.Errorf("error verifying payload: %s", err)
}

payload, err := sig.Payload()
if err != nil {
return nil, false, fmt.Errorf("error retrieving payload: %s", err)
}

var manifest registry.ManifestData
if err := json.Unmarshal(payload, &manifest); err != nil {
return nil, false, fmt.Errorf("error unmarshalling manifest: %s", err)
}
if manifest.SchemaVersion != 1 {
return nil, false, fmt.Errorf("unsupported schema version: %d", manifest.SchemaVersion)
}

var verified bool
for _, key := range keys {
job := eng.Job("trust_key_check")
b, err := key.MarshalJSON()
if err != nil {
return nil, false, fmt.Errorf("error marshalling public key: %s", err)
}
namespace := manifest.Name
if namespace[0] != '/' {
namespace = "/" + namespace
}
stdoutBuffer := bytes.NewBuffer(nil)

job.Args = append(job.Args, namespace)
job.Setenv("PublicKey", string(b))
// Check key has read/write permission (0x03)
job.SetenvInt("Permission", 0x03)
job.Stdout.Add(stdoutBuffer)
if err = job.Run(); err != nil {
return nil, false, fmt.Errorf("error running key check: %s", err)
}
result := engine.Tail(stdoutBuffer, 1)
log.Debugf("Key check result: %q", result)
if result == "verified" {
verified = true
}
}

return &manifest, verified, nil
}

func checkValidManifest(manifest *registry.ManifestData) error {
if len(manifest.FSLayers) != len(manifest.History) {
return fmt.Errorf("length of history not equal to number of layers")
}

if len(manifest.FSLayers) == 0 {
return fmt.Errorf("no FSLayers in manifest")
}

return nil
}
Loading