From 90991ddb9b5ea31353b1360cc26aba509e33d61f Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Sat, 25 Jul 2015 14:02:47 -0700 Subject: [PATCH 01/79] fix deb packaging systemd files Signed-off-by: Jessica Frazelle (cherry picked from commit a2ea8f2ad8b93c53253b9c86537dad95e14bd599) --- hack/make/.build-deb/rules | 1 + 1 file changed, 1 insertion(+) diff --git a/hack/make/.build-deb/rules b/hack/make/.build-deb/rules index 1d830232f..ae2defcc0 100755 --- a/hack/make/.build-deb/rules +++ b/hack/make/.build-deb/rules @@ -33,6 +33,7 @@ override_dh_installudev: dh_installudev --priority=z80 override_dh_install: + dh_install dh_apparmor --profile-name=docker -pdocker-engine dh_apparmor --profile-name=docker-engine -pdocker-engine From 36b6e5884dd76d5518aa1d1e96607c116883f844 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 26 Jul 2015 15:00:53 +0200 Subject: [PATCH 02/79] Format times in inspect command with a template as RFC3339Nano In 1.6.2 we were decoding inspect API response into interface{}. time.Time fields were JSON encoded as RFC3339Nano in the response and when decoded into interface{} they were just strings so the inspect template treated them as just strings. From 1.7 we are decoding into types.ContainerJSON and when the template gets executed it now gets a time.Time and it's formatted as 2015-07-22 05:02:38.091530369 +0000 UTC. This patch brings back the old behavior by typing time.Time fields as string so they gets formatted as they were encoded in JSON -- RCF3339Nano Signed-off-by: Antonio Murdaca (cherry picked from commit c9207bc0aa57745876a3422d2cbc290be7c53da8) --- api/types/types.go | 8 +++---- daemon/inspect.go | 7 +++--- graph/service.go | 3 ++- integration-cli/docker_cli_inspect_test.go | 26 ++++++++++++++++++++++ 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/api/types/types.go b/api/types/types.go index 0e7078bc7..b02c88bb6 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -86,7 +86,7 @@ type ImageInspect struct { Id string Parent string Comment string - Created time.Time + Created string Container string ContainerConfig *runconfig.Config DockerVersion string @@ -215,14 +215,14 @@ type ContainerState struct { Pid int ExitCode int Error string - StartedAt time.Time - FinishedAt time.Time + StartedAt string + FinishedAt string } // GET "/containers/{name:.*}/json" type ContainerJSONBase struct { Id string - Created time.Time + Created string Path string Args []string State *ContainerState diff --git a/daemon/inspect.go b/daemon/inspect.go index 1fb73c43a..d38471a04 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -2,6 +2,7 @@ package daemon import ( "fmt" + "time" "github.com/docker/docker/api/types" ) @@ -91,13 +92,13 @@ func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSON Pid: container.State.Pid, ExitCode: container.State.ExitCode, Error: container.State.Error, - StartedAt: container.State.StartedAt, - FinishedAt: container.State.FinishedAt, + StartedAt: container.State.StartedAt.Format(time.RFC3339Nano), + FinishedAt: container.State.FinishedAt.Format(time.RFC3339Nano), } contJSONBase := &types.ContainerJSONBase{ Id: container.ID, - Created: container.Created, + Created: container.Created.Format(time.RFC3339Nano), Path: container.Path, Args: container.Args, State: containerState, diff --git a/graph/service.go b/graph/service.go index 4a8c3d3b8..a7b9b4935 100644 --- a/graph/service.go +++ b/graph/service.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "runtime" + "time" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" @@ -34,7 +35,7 @@ func (s *TagStore) Lookup(name string) (*types.ImageInspect, error) { Id: image.ID, Parent: image.Parent, Comment: image.Comment, - Created: image.Created, + Created: image.Created.Format(time.RFC3339Nano), Container: image.Container, ContainerConfig: &image.ContainerConfig, DockerVersion: image.DockerVersion, diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go index f90419eee..3e42d0c37 100644 --- a/integration-cli/docker_cli_inspect_test.go +++ b/integration-cli/docker_cli_inspect_test.go @@ -5,6 +5,7 @@ import ( "os/exec" "strconv" "strings" + "time" "github.com/docker/docker/api/types" "github.com/go-check/check" @@ -260,3 +261,28 @@ func (s *DockerSuite) TestInspectBindMountPoint(c *check.C) { c.Fatalf("Expected rw to be false") } } + +// #14947 +func (s *DockerSuite) TestInspectTimesAsRFC3339Nano(c *check.C) { + out, _ := dockerCmd(c, "run", "-d", "busybox", "true") + id := strings.TrimSpace(out) + startedAt, err := inspectField(id, "State.StartedAt") + c.Assert(err, check.IsNil) + finishedAt, err := inspectField(id, "State.FinishedAt") + c.Assert(err, check.IsNil) + created, err := inspectField(id, "Created") + c.Assert(err, check.IsNil) + + _, err = time.Parse(time.RFC3339Nano, startedAt) + c.Assert(err, check.IsNil) + _, err = time.Parse(time.RFC3339Nano, finishedAt) + c.Assert(err, check.IsNil) + _, err = time.Parse(time.RFC3339Nano, created) + c.Assert(err, check.IsNil) + + created, err = inspectField("busybox", "Created") + c.Assert(err, check.IsNil) + + _, err = time.Parse(time.RFC3339Nano, created) + c.Assert(err, check.IsNil) +} From fa85dc003014e984f67e68739eeb3149b2d2a9ed Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Mon, 27 Jul 2015 09:37:38 -0700 Subject: [PATCH 03/79] Update vendored distribution repo to new version This version includes a fix that avoids checking against specific HTTP status codes. The previous behavior violated the registry API spec. Fixes #14975 Signed-off-by: Aaron Lehmann (cherry picked from commit 091dbc103434eafa22afaed374032c8b3ecb8001) --- hack/vendor.sh | 2 +- .../github.com/docker/distribution/blobs.go | 14 ++- .../registry/api/v2/descriptors.go | 27 +++++ .../registry/client/auth/session.go | 3 +- .../registry/client/blob_writer.go | 12 +- .../distribution/registry/client/errors.go | 6 + .../registry/client/repository.go | 103 ++++++++++-------- .../registry/client/transport/http_reader.go | 7 +- .../cache/cachedblobdescriptorstore.go | 27 ++++- .../registry/storage/cache/memory/memory.go | 20 ++++ .../registry/storage/cache/suite.go | 37 +++++++ 11 files changed, 199 insertions(+), 59 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 27c1a73d3..686afeedf 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -35,7 +35,7 @@ clone git github.com/coreos/go-etcd v2.0.0 clone git github.com/hashicorp/consul v0.5.2 # get graph and distribution packages -clone git github.com/docker/distribution cd8ff553b6b1911be23dfeabb73e33108bcbf147 +clone git github.com/docker/distribution e83345626608aa943d5c8a027fddcf54814d9545 clone git github.com/vbatts/tar-split v0.9.4 clone git github.com/docker/notary 77bced079e83d80f40c1f0a544b1a8a3b97fb052 diff --git a/vendor/src/github.com/docker/distribution/blobs.go b/vendor/src/github.com/docker/distribution/blobs.go index b0c89d1f3..ffec41e8a 100644 --- a/vendor/src/github.com/docker/distribution/blobs.go +++ b/vendor/src/github.com/docker/distribution/blobs.go @@ -27,6 +27,9 @@ var ( // ErrBlobInvalidLength returned when the blob has an expected length on // commit, meaning mismatched with the descriptor or an invalid value. ErrBlobInvalidLength = errors.New("blob invalid length") + + // ErrUnsupported returned when an unsupported operation is attempted + ErrUnsupported = errors.New("unsupported operation") ) // ErrBlobInvalidDigest returned when digest check fails. @@ -70,6 +73,11 @@ type BlobStatter interface { Stat(ctx context.Context, dgst digest.Digest) (Descriptor, error) } +// BlobDeleter enables deleting blobs from storage. +type BlobDeleter interface { + Delete(ctx context.Context, dgst digest.Digest) error +} + // BlobDescriptorService manages metadata about a blob by digest. Most // implementations will not expose such an interface explicitly. Such mappings // should be maintained by interacting with the BlobIngester. Hence, this is @@ -87,6 +95,9 @@ type BlobDescriptorService interface { // the restriction that the algorithm of the descriptor must match the // canonical algorithm (ie sha256) of the annotator. SetDescriptor(ctx context.Context, dgst digest.Digest, desc Descriptor) error + + // Clear enables descriptors to be unlinked + Clear(ctx context.Context, dgst digest.Digest) error } // ReadSeekCloser is the primary reader type for blob data, combining @@ -183,8 +194,9 @@ type BlobService interface { } // BlobStore represent the entire suite of blob related operations. Such an -// implementation can access, read, write and serve blobs. +// implementation can access, read, write, delete and serve blobs. type BlobStore interface { BlobService BlobServer + BlobDeleter } diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go index ee895b722..635cb7f90 100644 --- a/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go @@ -398,6 +398,8 @@ var routeDescriptors = []RouteDescriptor{ Description: "Fetch the tags under the repository identified by `name`.", Requests: []RequestDescriptor{ { + Name: "Tags", + Description: "Return all tags for the repository", Headers: []ParameterDescriptor{ hostHeader, authHeader, @@ -455,6 +457,7 @@ var routeDescriptors = []RouteDescriptor{ }, }, { + Name: "Tags Paginated", Description: "Return a portion of the tags for the specified repository.", PathParameters: []ParameterDescriptor{nameParameterDescriptor}, QueryParameters: paginationParameters, @@ -483,6 +486,30 @@ var routeDescriptors = []RouteDescriptor{ }, }, }, + Failures: []ResponseDescriptor{ + { + StatusCode: http.StatusNotFound, + Description: "The repository is not known to the registry.", + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameUnknown, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Description: "The client does not have access to the repository.", + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeUnauthorized, + }, + }, + }, }, }, }, diff --git a/vendor/src/github.com/docker/distribution/registry/client/auth/session.go b/vendor/src/github.com/docker/distribution/registry/client/auth/session.go index 27e1d9e35..27a2aa719 100644 --- a/vendor/src/github.com/docker/distribution/registry/client/auth/session.go +++ b/vendor/src/github.com/docker/distribution/registry/client/auth/session.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/docker/distribution/registry/client" "github.com/docker/distribution/registry/client/transport" ) @@ -209,7 +210,7 @@ func (th *tokenHandler) fetchToken(params map[string]string) (token string, err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { + if !client.SuccessStatus(resp.StatusCode) { return "", fmt.Errorf("token auth attempt for registry: %s request failed with status: %d %s", req.URL, resp.StatusCode, http.StatusText(resp.StatusCode)) } diff --git a/vendor/src/github.com/docker/distribution/registry/client/blob_writer.go b/vendor/src/github.com/docker/distribution/registry/client/blob_writer.go index 9ebd41839..5f6f01f7f 100644 --- a/vendor/src/github.com/docker/distribution/registry/client/blob_writer.go +++ b/vendor/src/github.com/docker/distribution/registry/client/blob_writer.go @@ -44,7 +44,7 @@ func (hbu *httpBlobUpload) ReadFrom(r io.Reader) (n int64, err error) { return 0, err } - if resp.StatusCode != http.StatusAccepted { + if !SuccessStatus(resp.StatusCode) { return 0, hbu.handleErrorResponse(resp) } @@ -79,7 +79,7 @@ func (hbu *httpBlobUpload) Write(p []byte) (n int, err error) { return 0, err } - if resp.StatusCode != http.StatusAccepted { + if !SuccessStatus(resp.StatusCode) { return 0, hbu.handleErrorResponse(resp) } @@ -142,7 +142,7 @@ func (hbu *httpBlobUpload) Commit(ctx context.Context, desc distribution.Descrip } defer resp.Body.Close() - if resp.StatusCode != http.StatusCreated { + if !SuccessStatus(resp.StatusCode) { return distribution.Descriptor{}, hbu.handleErrorResponse(resp) } @@ -160,12 +160,10 @@ func (hbu *httpBlobUpload) Cancel(ctx context.Context) error { } defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusNoContent, http.StatusNotFound: + if resp.StatusCode == http.StatusNotFound || SuccessStatus(resp.StatusCode) { return nil - default: - return hbu.handleErrorResponse(resp) } + return hbu.handleErrorResponse(resp) } func (hbu *httpBlobUpload) Close() error { diff --git a/vendor/src/github.com/docker/distribution/registry/client/errors.go b/vendor/src/github.com/docker/distribution/registry/client/errors.go index 2c168400a..ebd1c36c4 100644 --- a/vendor/src/github.com/docker/distribution/registry/client/errors.go +++ b/vendor/src/github.com/docker/distribution/registry/client/errors.go @@ -61,3 +61,9 @@ func handleErrorResponse(resp *http.Response) error { } return &UnexpectedHTTPStatusError{Status: resp.Status} } + +// SuccessStatus returns true if the argument is a successful HTTP response +// code (in the range 200 - 399 inclusive). +func SuccessStatus(status int) bool { + return status >= 200 && status <= 399 +} diff --git a/vendor/src/github.com/docker/distribution/registry/client/repository.go b/vendor/src/github.com/docker/distribution/registry/client/repository.go index 29effcce8..d0079f092 100644 --- a/vendor/src/github.com/docker/distribution/registry/client/repository.go +++ b/vendor/src/github.com/docker/distribution/registry/client/repository.go @@ -70,8 +70,7 @@ func (r *registry) Repositories(ctx context.Context, entries []string, last stri } defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK: + if SuccessStatus(resp.StatusCode) { var ctlg struct { Repositories []string `json:"repositories"` } @@ -90,8 +89,7 @@ func (r *registry) Repositories(ctx context.Context, entries []string, last stri if link == "" { returnErr = io.EOF } - - default: + } else { return 0, handleErrorResponse(resp) } @@ -199,8 +197,7 @@ func (ms *manifests) Tags() ([]string, error) { } defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK: + if SuccessStatus(resp.StatusCode) { b, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err @@ -214,11 +211,10 @@ func (ms *manifests) Tags() ([]string, error) { } return tagsResponse.Tags, nil - case http.StatusNotFound: + } else if resp.StatusCode == http.StatusNotFound { return nil, nil - default: - return nil, handleErrorResponse(resp) } + return nil, handleErrorResponse(resp) } func (ms *manifests) Exists(dgst digest.Digest) (bool, error) { @@ -238,14 +234,12 @@ func (ms *manifests) ExistsByTag(tag string) (bool, error) { return false, err } - switch resp.StatusCode { - case http.StatusOK: + if SuccessStatus(resp.StatusCode) { return true, nil - case http.StatusNotFound: + } else if resp.StatusCode == http.StatusNotFound { return false, nil - default: - return false, handleErrorResponse(resp) } + return false, handleErrorResponse(resp) } func (ms *manifests) Get(dgst digest.Digest) (*manifest.SignedManifest, error) { @@ -254,13 +248,14 @@ func (ms *manifests) Get(dgst digest.Digest) (*manifest.SignedManifest, error) { return ms.GetByTag(dgst.String()) } -// AddEtagToTag allows a client to supply an eTag to GetByTag which will -// be used for a conditional HTTP request. If the eTag matches, a nil -// manifest and nil error will be returned. -func AddEtagToTag(tagName, dgst string) distribution.ManifestServiceOption { +// AddEtagToTag allows a client to supply an eTag to GetByTag which will be +// used for a conditional HTTP request. If the eTag matches, a nil manifest +// and nil error will be returned. etag is automatically quoted when added to +// this map. +func AddEtagToTag(tag, etag string) distribution.ManifestServiceOption { return func(ms distribution.ManifestService) error { if ms, ok := ms.(*manifests); ok { - ms.etags[tagName] = dgst + ms.etags[tag] = fmt.Sprintf(`"%s"`, etag) return nil } return fmt.Errorf("etag options is a client-only option") @@ -293,8 +288,9 @@ func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServic } defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK: + if resp.StatusCode == http.StatusNotModified { + return nil, nil + } else if SuccessStatus(resp.StatusCode) { var sm manifest.SignedManifest decoder := json.NewDecoder(resp.Body) @@ -302,11 +298,8 @@ func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServic return nil, err } return &sm, nil - case http.StatusNotModified: - return nil, nil - default: - return nil, handleErrorResponse(resp) } + return nil, handleErrorResponse(resp) } func (ms *manifests) Put(m *manifest.SignedManifest) error { @@ -328,13 +321,11 @@ func (ms *manifests) Put(m *manifest.SignedManifest) error { } defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusAccepted: + if SuccessStatus(resp.StatusCode) { // TODO(dmcgowan): make use of digest header return nil - default: - return handleErrorResponse(resp) } + return handleErrorResponse(resp) } func (ms *manifests) Delete(dgst digest.Digest) error { @@ -353,12 +344,10 @@ func (ms *manifests) Delete(dgst digest.Digest) error { } defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK: + if SuccessStatus(resp.StatusCode) { return nil - default: - return handleErrorResponse(resp) } + return handleErrorResponse(resp) } type blobs struct { @@ -366,7 +355,8 @@ type blobs struct { ub *v2.URLBuilder client *http.Client - statter distribution.BlobStatter + statter distribution.BlobDescriptorService + distribution.BlobDeleter } func sanitizeLocation(location, source string) (string, error) { @@ -459,8 +449,7 @@ func (bs *blobs) Create(ctx context.Context) (distribution.BlobWriter, error) { } defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusAccepted: + if SuccessStatus(resp.StatusCode) { // TODO(dmcgowan): Check for invalid UUID uuid := resp.Header.Get("Docker-Upload-UUID") location, err := sanitizeLocation(resp.Header.Get("Location"), u) @@ -475,15 +464,18 @@ func (bs *blobs) Create(ctx context.Context) (distribution.BlobWriter, error) { startedAt: time.Now(), location: location, }, nil - default: - return nil, handleErrorResponse(resp) } + return nil, handleErrorResponse(resp) } func (bs *blobs) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { panic("not implemented") } +func (bs *blobs) Delete(ctx context.Context, dgst digest.Digest) error { + return bs.statter.Clear(ctx, dgst) +} + type blobStatter struct { name string ub *v2.URLBuilder @@ -502,8 +494,7 @@ func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distributi } defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK: + if SuccessStatus(resp.StatusCode) { lengthHeader := resp.Header.Get("Content-Length") length, err := strconv.ParseInt(lengthHeader, 10, 64) if err != nil { @@ -515,11 +506,10 @@ func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distributi Size: length, Digest: dgst, }, nil - case http.StatusNotFound: + } else if resp.StatusCode == http.StatusNotFound { return distribution.Descriptor{}, distribution.ErrBlobUnknown - default: - return distribution.Descriptor{}, handleErrorResponse(resp) } + return distribution.Descriptor{}, handleErrorResponse(resp) } func buildCatalogValues(maxEntries int, last string) url.Values { @@ -535,3 +525,30 @@ func buildCatalogValues(maxEntries int, last string) url.Values { return values } + +func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error { + blobURL, err := bs.ub.BuildBlobURL(bs.name, dgst) + if err != nil { + return err + } + + req, err := http.NewRequest("DELETE", blobURL, nil) + if err != nil { + return err + } + + resp, err := bs.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if SuccessStatus(resp.StatusCode) { + return nil + } + return handleErrorResponse(resp) +} + +func (bs *blobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { + return nil +} diff --git a/vendor/src/github.com/docker/distribution/registry/client/transport/http_reader.go b/vendor/src/github.com/docker/distribution/registry/client/transport/http_reader.go index e351bdfe3..b2e74ddb8 100644 --- a/vendor/src/github.com/docker/distribution/registry/client/transport/http_reader.go +++ b/vendor/src/github.com/docker/distribution/registry/client/transport/http_reader.go @@ -154,10 +154,11 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) { return nil, err } - switch { - case resp.StatusCode == 200: + // Normally would use client.SuccessStatus, but that would be a cyclic + // import + if resp.StatusCode >= 200 && resp.StatusCode <= 399 { hrs.rc = resp.Body - default: + } else { defer resp.Body.Close() return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status) } diff --git a/vendor/src/github.com/docker/distribution/registry/storage/cache/cachedblobdescriptorstore.go b/vendor/src/github.com/docker/distribution/registry/storage/cache/cachedblobdescriptorstore.go index a095b19a5..94ca8a90c 100644 --- a/vendor/src/github.com/docker/distribution/registry/storage/cache/cachedblobdescriptorstore.go +++ b/vendor/src/github.com/docker/distribution/registry/storage/cache/cachedblobdescriptorstore.go @@ -26,13 +26,13 @@ type MetricsTracker interface { type cachedBlobStatter struct { cache distribution.BlobDescriptorService - backend distribution.BlobStatter + backend distribution.BlobDescriptorService tracker MetricsTracker } // NewCachedBlobStatter creates a new statter which prefers a cache and // falls back to a backend. -func NewCachedBlobStatter(cache distribution.BlobDescriptorService, backend distribution.BlobStatter) distribution.BlobStatter { +func NewCachedBlobStatter(cache distribution.BlobDescriptorService, backend distribution.BlobDescriptorService) distribution.BlobDescriptorService { return &cachedBlobStatter{ cache: cache, backend: backend, @@ -41,7 +41,7 @@ func NewCachedBlobStatter(cache distribution.BlobDescriptorService, backend dist // NewCachedBlobStatterWithMetrics creates a new statter which prefers a cache and // falls back to a backend. Hits and misses will send to the tracker. -func NewCachedBlobStatterWithMetrics(cache distribution.BlobDescriptorService, backend distribution.BlobStatter, tracker MetricsTracker) distribution.BlobStatter { +func NewCachedBlobStatterWithMetrics(cache distribution.BlobDescriptorService, backend distribution.BlobDescriptorService, tracker MetricsTracker) distribution.BlobStatter { return &cachedBlobStatter{ cache: cache, backend: backend, @@ -77,4 +77,25 @@ fallback: } return desc, err + +} + +func (cbds *cachedBlobStatter) Clear(ctx context.Context, dgst digest.Digest) error { + err := cbds.cache.Clear(ctx, dgst) + if err != nil { + return err + } + + err = cbds.backend.Clear(ctx, dgst) + if err != nil { + return err + } + return nil +} + +func (cbds *cachedBlobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { + if err := cbds.cache.SetDescriptor(ctx, dgst, desc); err != nil { + context.GetLogger(ctx).Errorf("error adding descriptor %v to cache: %v", desc.Digest, err) + } + return nil } diff --git a/vendor/src/github.com/docker/distribution/registry/storage/cache/memory/memory.go b/vendor/src/github.com/docker/distribution/registry/storage/cache/memory/memory.go index cdd9abe89..120a6572d 100644 --- a/vendor/src/github.com/docker/distribution/registry/storage/cache/memory/memory.go +++ b/vendor/src/github.com/docker/distribution/registry/storage/cache/memory/memory.go @@ -44,6 +44,10 @@ func (imbdcp *inMemoryBlobDescriptorCacheProvider) Stat(ctx context.Context, dgs return imbdcp.global.Stat(ctx, dgst) } +func (imbdcp *inMemoryBlobDescriptorCacheProvider) Clear(ctx context.Context, dgst digest.Digest) error { + return imbdcp.global.Clear(ctx, dgst) +} + func (imbdcp *inMemoryBlobDescriptorCacheProvider) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { _, err := imbdcp.Stat(ctx, dgst) if err == distribution.ErrBlobUnknown { @@ -80,6 +84,14 @@ func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) Stat(ctx context.Co return rsimbdcp.repository.Stat(ctx, dgst) } +func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) Clear(ctx context.Context, dgst digest.Digest) error { + if rsimbdcp.repository == nil { + return distribution.ErrBlobUnknown + } + + return rsimbdcp.repository.Clear(ctx, dgst) +} + func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { if rsimbdcp.repository == nil { // allocate map since we are setting it now. @@ -133,6 +145,14 @@ func (mbdc *mapBlobDescriptorCache) Stat(ctx context.Context, dgst digest.Digest return desc, nil } +func (mbdc *mapBlobDescriptorCache) Clear(ctx context.Context, dgst digest.Digest) error { + mbdc.mu.Lock() + defer mbdc.mu.Unlock() + + delete(mbdc.descriptors, dgst) + return nil +} + func (mbdc *mapBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { if err := dgst.Validate(); err != nil { return err diff --git a/vendor/src/github.com/docker/distribution/registry/storage/cache/suite.go b/vendor/src/github.com/docker/distribution/registry/storage/cache/suite.go index f74d9f9e7..b5a2f6431 100644 --- a/vendor/src/github.com/docker/distribution/registry/storage/cache/suite.go +++ b/vendor/src/github.com/docker/distribution/registry/storage/cache/suite.go @@ -139,3 +139,40 @@ func checkBlobDescriptorCacheSetAndRead(t *testing.T, ctx context.Context, provi t.Fatalf("unexpected descriptor: %#v != %#v", desc, expected) } } + +func checkBlobDescriptorClear(t *testing.T, ctx context.Context, provider BlobDescriptorCacheProvider) { + localDigest := digest.Digest("sha384:abc") + expected := distribution.Descriptor{ + Digest: "sha256:abc", + Size: 10, + MediaType: "application/octet-stream"} + + cache, err := provider.RepositoryScoped("foo/bar") + if err != nil { + t.Fatalf("unexpected error getting scoped cache: %v", err) + } + + if err := cache.SetDescriptor(ctx, localDigest, expected); err != nil { + t.Fatalf("error setting descriptor: %v", err) + } + + desc, err := cache.Stat(ctx, localDigest) + if err != nil { + t.Fatalf("unexpected error statting fake2:abc: %v", err) + } + + if expected != desc { + t.Fatalf("unexpected descriptor: %#v != %#v", expected, desc) + } + + err = cache.Clear(ctx, localDigest) + if err != nil { + t.Fatalf("unexpected error deleting descriptor") + } + + nonExistantDigest := digest.Digest("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + err = cache.Clear(ctx, nonExistantDigest) + if err == nil { + t.Fatalf("expected error deleting unknown descriptor") + } +} From 546a704c63139890f4f4a34b0fd7b6e095405887 Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Mon, 27 Jul 2015 10:21:36 -0700 Subject: [PATCH 04/79] [api/client] Fix build when context dir is symlink Symbolic links in the context directory path are now evaluated. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) (cherry picked from commit 01d570ad30a794f2736b679700af91625e61bc85) --- api/client/build.go | 16 ++++++++++ integration-cli/docker_cli_build_test.go | 38 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/api/client/build.go b/api/client/build.go index 6dac4a3f3..10b0e727d 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -302,6 +302,22 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi return "", "", fmt.Errorf("unable to get absolute context directory: %v", err) } + // The context dir might be a symbolic link, so follow it to the actual + // target directory. + absContextDir, err = filepath.EvalSymlinks(absContextDir) + if err != nil { + return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err) + } + + stat, err := os.Lstat(absContextDir) + if err != nil { + return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err) + } + + if !stat.IsDir() { + return "", "", fmt.Errorf("context must be a directory: %s", absContextDir) + } + absDockerfile := givenDockerfile if absDockerfile == "" { // No -f/--file was specified so use the default relative to the diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 72f796ed6..560fcb88e 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5373,3 +5373,41 @@ func (s *DockerTrustSuite) TestTrustedBuildUntrustedTag(c *check.C) { c.Fatalf("Unexpected output on trusted build with untrusted tag:\n%s", out) } } + +func (s *DockerTrustSuite) TestBuildContextDirIsSymlink(c *check.C) { + tempDir, err := ioutil.TempDir("", "test-build-dir-is-symlink-") + if err != nil { + c.Fatal(err) + } + defer os.RemoveAll(tempDir) + + // Make a real context directory in this temp directory with a simple + // Dockerfile. + realContextDirname := filepath.Join(tempDir, "context") + if err := os.Mkdir(realContextDirname, os.FileMode(0755)); err != nil { + c.Fatal(err) + } + + if err = ioutil.WriteFile( + filepath.Join(realContextDirname, "Dockerfile"), + []byte(` + FROM busybox + RUN echo hello world + `), + os.FileMode(0644), + ); err != nil { + c.Fatal(err) + } + + // Make a symlink to the real context directory. + contextSymlinkName := filepath.Join(tempDir, "context_link") + if err := os.Symlink(realContextDirname, contextSymlinkName); err != nil { + c.Fatal(err) + } + + // Executing the build with the symlink as the specified context should + // *not* fail. + if out, exitStatus := dockerCmd(c, "build", contextSymlinkName); exitStatus != 0 { + c.Fatalf("build failed with exit status %d: %s", exitStatus, out) + } +} From 56d859d0523b5de753f774bd0117a148b5cf1cf7 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 27 Jul 2015 13:11:08 -0700 Subject: [PATCH 05/79] Use math.MaxInt32 instead of math.MaxUint32 I think it was original intention, because even half of a comment was about MaxInt32. Fix #15038 Signed-off-by: Alexander Morozov (cherry picked from commit eb45602d2fa0fac8a694d2afb1c59ef60b0e1f77) --- daemon/logger/fluentd/fluentd.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/logger/fluentd/fluentd.go b/daemon/logger/fluentd/fluentd.go index 726d2be75..97205ddee 100644 --- a/daemon/logger/fluentd/fluentd.go +++ b/daemon/logger/fluentd/fluentd.go @@ -93,9 +93,9 @@ func New(ctx logger.Context) (logger.Logger, error) { } logrus.Debugf("logging driver fluentd configured for container:%s, host:%s, port:%d, tag:%s.", ctx.ContainerID, host, port, tag) - // logger tries to recoonect 2**64 - 1 times + // logger tries to recoonect 2**32 - 1 times // failed (and panic) after 204 years [ 1.5 ** (2**32 - 1) - 1 seconds] - log, err := fluent.New(fluent.Config{FluentPort: port, FluentHost: host, RetryWait: 1000, MaxRetry: math.MaxUint32}) + log, err := fluent.New(fluent.Config{FluentPort: port, FluentHost: host, RetryWait: 1000, MaxRetry: math.MaxInt32}) if err != nil { return nil, err } From 50f65742efe6ebc3641ed691e2e9a3b6473d90b1 Mon Sep 17 00:00:00 2001 From: Avi Miller Date: Fri, 24 Jul 2015 07:38:28 +1000 Subject: [PATCH 06/79] Switch to using only the RPM command to determine the distro version. Signed-off-by: Avi Miller (cherry picked from commit a43199f14314b3d966e5723cd336f9747a0f1ca0) --- hack/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/install.sh b/hack/install.sh index e3820503e..dd9df94a2 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -140,12 +140,12 @@ do_install() { fi if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then lsb_dist='fedora' - dist_version="$(rpm -qa \*-release | cut -d"-" -f3 | head -n1)" + dist_version="$(rpm -q --whatprovides redhat-release --queryformat "%{VERSION\n")" fi if [ -z "$lsb_dist" ]; then if [ -r /etc/centos-release ] || [ -r /etc/redhat-release ]; then lsb_dist='centos' - dist_version="$(rpm -qa \*-release | cut -d"-" -f3 | head -n1)" + dist_version="$(rpm -q --whatprovides redhat-release --queryformat "%{VERSION}\n")" fi fi if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then From 37e886eb7b1f71c927a3695a4a39d19a25fb0f02 Mon Sep 17 00:00:00 2001 From: Avi Miller Date: Fri, 24 Jul 2015 08:14:06 +1000 Subject: [PATCH 07/79] Fix typo. Signed-off-by: Avi Miller (cherry picked from commit 59e48b346848a131d85a808fa1e21aea8c4f9833) --- hack/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/install.sh b/hack/install.sh index dd9df94a2..bbe9f70ed 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -140,7 +140,7 @@ do_install() { fi if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then lsb_dist='fedora' - dist_version="$(rpm -q --whatprovides redhat-release --queryformat "%{VERSION\n")" + dist_version="$(rpm -q --whatprovides redhat-release --queryformat "%{VERSION}\n")" fi if [ -z "$lsb_dist" ]; then if [ -r /etc/centos-release ] || [ -r /etc/redhat-release ]; then From b537508f8c52dff3699668fe7fdf78da1137e310 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 27 Jul 2015 16:26:35 -0700 Subject: [PATCH 08/79] only write distributions file if dne Signed-off-by: Jessica Frazelle (cherry picked from commit 2eee19236650c5146274172e08751cfce52c2fbe) --- hack/make/release-deb | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/hack/make/release-deb b/hack/make/release-deb index 1832b5b3f..5fd824bd0 100755 --- a/hack/make/release-deb +++ b/hack/make/release-deb @@ -21,17 +21,19 @@ APTDIR=$DOCKER_RELEASE_DIR/apt/repo mkdir -p "$APTDIR/conf" "$APTDIR/db" # create/update distributions file -for suite in $(exec contrib/reprepro/suites.sh); do - cat <<-EOF - Origin: Docker - Suite: $suite - Codename: $suite - Architectures: amd64 i386 - Components: main testing experimental - Description: Docker APT Repository +if [[ ! -f "$APTDIR/conf/distributions" ]]; then + for suite in $(exec contrib/reprepro/suites.sh); do + cat <<-EOF + Origin: Docker + Suite: $suite + Codename: $suite + Architectures: amd64 i386 + Components: main testing experimental + Description: Docker APT Repository - EOF -done > "$APTDIR/conf/distributions" + EOF + done > "$APTDIR/conf/distributions" +fi # set the component and priority for the version being released component="main" From e0e852ee6f665c52df287e64a61d42ddec144e77 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Tue, 28 Jul 2015 14:48:18 -0400 Subject: [PATCH 09/79] Restore AppArmor profile generation Will attempt to load profiles automatically. If loading fails but the profiles are already loaded, execution will continue. A hard failure will only occur if Docker cannot load the profiles *and* they have not already been loaded via some other means. Also introduces documentation for AppArmor. Signed-off-by: Eric Windisch (cherry picked from commit 3edc88f76df6a3bc9d887de8157ec71730c9057a) --- contrib/apparmor/docker | 25 ----- daemon/execdriver/native/apparmor.go | 146 +++++++++++++++++++++++++ daemon/execdriver/native/driver.go | 15 +++ docs/security/apparmor.md | 45 ++++++++ integration-cli/docker_cli_run_test.go | 33 +++++- 5 files changed, 235 insertions(+), 29 deletions(-) delete mode 100644 contrib/apparmor/docker create mode 100644 daemon/execdriver/native/apparmor.go create mode 100644 docs/security/apparmor.md diff --git a/contrib/apparmor/docker b/contrib/apparmor/docker deleted file mode 100644 index 4674ecf6e..000000000 --- a/contrib/apparmor/docker +++ /dev/null @@ -1,25 +0,0 @@ -#include - -profile docker-default flags=(attach_disconnected,mediate_deleted) { - #include - - network, - capability, - file, - umount, - - deny @{PROC}/sys/fs/** wklx, - deny @{PROC}/sysrq-trigger rwklx, - deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx, - deny @{PROC}/sys/kernel/*/** wklx, - - deny mount, - - deny /sys/[^f]*/** wklx, - deny /sys/f[^s]*/** wklx, - deny /sys/fs/[^c]*/** wklx, - deny /sys/fs/c[^g]*/** wklx, - deny /sys/fs/cg[^r]*/** wklx, - deny /sys/firmware/efi/efivars/** rwklx, - deny /sys/kernel/security/** rwklx, -} diff --git a/daemon/execdriver/native/apparmor.go b/daemon/execdriver/native/apparmor.go new file mode 100644 index 000000000..ecbaf3351 --- /dev/null +++ b/daemon/execdriver/native/apparmor.go @@ -0,0 +1,146 @@ +// +build linux + +package native + +import ( + "bufio" + "fmt" + "io" + "os" + "os/exec" + "path" + "strings" + "text/template" + + "github.com/opencontainers/runc/libcontainer/apparmor" +) + +const ( + apparmorProfilePath = "/etc/apparmor.d/docker" +) + +type data struct { + Name string + Imports []string + InnerImports []string +} + +const baseTemplate = ` +{{range $value := .Imports}} +{{$value}} +{{end}} + +profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { +{{range $value := .InnerImports}} + {{$value}} +{{end}} + + network, + capability, + file, + umount, + + deny @{PROC}/sys/fs/** wklx, + deny @{PROC}/fs/** wklx, + deny @{PROC}/sysrq-trigger rwklx, + deny @{PROC}/mem rwklx, + deny @{PROC}/kmem rwklx, + deny @{PROC}/kore rwklx, + deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx, + deny @{PROC}/sys/kernel/*/** wklx, + + deny mount, + deny ptrace, + + deny /sys/[^f]*/** wklx, + deny /sys/f[^s]*/** wklx, + deny /sys/fs/[^c]*/** wklx, + deny /sys/fs/c[^g]*/** wklx, + deny /sys/fs/cg[^r]*/** wklx, + deny /sys/firmware/efi/efivars/** rwklx, + deny /sys/kernel/security/** rwklx, +} +` + +func generateProfile(out io.Writer) error { + compiled, err := template.New("apparmor_profile").Parse(baseTemplate) + if err != nil { + return err + } + data := &data{ + Name: "docker-default", + } + if tunablesExists() { + data.Imports = append(data.Imports, "#include ") + } else { + data.Imports = append(data.Imports, "@{PROC}=/proc/") + } + if abstractionsExists() { + data.InnerImports = append(data.InnerImports, "#include ") + } + if err := compiled.Execute(out, data); err != nil { + return err + } + return nil +} + +// check if the tunables/global exist +func tunablesExists() bool { + _, err := os.Stat("/etc/apparmor.d/tunables/global") + return err == nil +} + +// check if abstractions/base exist +func abstractionsExists() bool { + _, err := os.Stat("/etc/apparmor.d/abstractions/base") + return err == nil +} + +func installAppArmorProfile() error { + if !apparmor.IsEnabled() { + return nil + } + + // Make sure /etc/apparmor.d exists + if err := os.MkdirAll(path.Dir(apparmorProfilePath), 0755); err != nil { + return err + } + + f, err := os.OpenFile(apparmorProfilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + if err := generateProfile(f); err != nil { + f.Close() + return err + } + f.Close() + + cmd := exec.Command("/sbin/apparmor_parser", "-r", "-W", "docker") + // to use the parser directly we have to make sure we are in the correct + // dir with the profile + cmd.Dir = "/etc/apparmor.d" + + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("Error loading docker apparmor profile: %s (%s)", err, output) + } + return nil +} + +func hasAppArmorProfileLoaded(profile string) error { + file, err := os.Open("/sys/kernel/security/apparmor/profiles") + if err != nil { + return err + } + r := bufio.NewReader(file) + for { + p, err := r.ReadString('\n') + if err != nil { + return err + } + if strings.HasPrefix(p, profile+" ") { + return nil + } + } +} diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index a94de3d18..351f26794 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -21,6 +21,7 @@ import ( sysinfo "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/term" "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/cgroups/systemd" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/system" @@ -51,6 +52,20 @@ func NewDriver(root, initPath string, options []string) (*driver, error) { return nil, err } + if apparmor.IsEnabled() { + if err := installAppArmorProfile(); err != nil { + apparmor_profiles := []string{"docker-default", "docker-unconfined"} + + // Allow daemon to run if loading failed, but are active + // (possibly through another run, manually, or via system startup) + for _, policy := range apparmor_profiles { + if err := hasAppArmorProfileLoaded(policy); err != nil { + return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy) + } + } + } + } + // choose cgroup manager // this makes sure there are no breaking changes to people // who upgrade from versions without native.cgroupdriver opt diff --git a/docs/security/apparmor.md b/docs/security/apparmor.md new file mode 100644 index 000000000..7cd88d314 --- /dev/null +++ b/docs/security/apparmor.md @@ -0,0 +1,45 @@ +AppArmor security profiles for Docker +-------------------------------------- + +AppArmor (Application Armor) is a security module that allows a system +administrator to associate a security profile with each program. Docker +expects to find an AppArmor policy loaded and enforced. + +Container profiles are loaded automatically by Docker. A profile +for the Docker Engine itself also exists and is installed +with the official *.deb* packages. Advanced users and package +managers may find the profile for */usr/bin/docker* underneath +[contrib/apparmor](https://github.com/docker/docker/tree/master/contrib/apparmor) +in the Docker Engine source repository. + + +Understand the policies +------------------------ + +The `docker-default` profile the default for running +containers. It is moderately protective while +providing wide application compatability. + +The `docker-unconfined` profile is intended for +privileged applications and is the default when runing +a container with the *--privileged* flag. + +The system's standard `unconfined` profile inherits all +system-wide policies, applying path-based policies +intended for the host system inside of containers. +This was the default for privileged containers +prior to Docker 1.8. + + +Overriding the profile for a container +--------------------------------------- + +Users may override the AppArmor profile using the +`security-opt` option (per-container). + +For example, the following explicitly specifies the default policy: + +``` +$ docker run --rm -it --security-opt apparmor:docker-default hello-world +``` + diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 4a2545892..841480e9f 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2396,8 +2396,11 @@ func (s *DockerSuite) TestRunWriteToProcAsound(c *check.C) { func (s *DockerSuite) TestRunReadProcTimer(c *check.C) { testRequires(c, NativeExecDriver) - out, code, err := dockerCmdWithError(c, "run", "busybox", "cat", "/proc/timer_stats") - if err != nil || code != 0 { + out, code, err := dockerCmdWithError("run", "busybox", "cat", "/proc/timer_stats") + if code != 0 { + return + } + if err != nil { c.Fatal(err) } if strings.Trim(out, "\n ") != "" { @@ -2413,8 +2416,11 @@ func (s *DockerSuite) TestRunReadProcLatency(c *check.C) { c.Skip("kernel doesnt have latency_stats configured") return } - out, code, err := dockerCmdWithError(c, "run", "busybox", "cat", "/proc/latency_stats") - if err != nil || code != 0 { + out, code, err := dockerCmdWithError("run", "busybox", "cat", "/proc/latency_stats") + if code != 0 { + return + } + if err != nil { c.Fatal(err) } if strings.Trim(out, "\n ") != "" { @@ -2422,6 +2428,24 @@ func (s *DockerSuite) TestRunReadProcLatency(c *check.C) { } } +func (s *DockerSuite) TestRunReadFilteredProc(c *check.C) { + testRequires(c, Apparmor) + + testReadPaths := []string{ + "/proc/latency_stats", + "/proc/timer_stats", + "/proc/kcore", + } + for i, filePath := range testReadPaths { + name := fmt.Sprintf("procsieve-%d", i) + shellCmd := fmt.Sprintf("exec 3<%s", filePath) + + if out, exitCode, err := dockerCmdWithError("run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd); err == nil || exitCode == 0 { + c.Fatalf("Open FD for read should have failed with permission denied, got: %s, %v", out, err) + } + } +} + func (s *DockerSuite) TestMountIntoProc(c *check.C) { testRequires(c, NativeExecDriver) _, code, err := dockerCmdWithError(c, "run", "-v", "/proc//sys", "busybox", "true") @@ -2515,6 +2539,7 @@ func (s *DockerSuite) TestRunWriteFilteredProc(c *check.C) { "/proc/sys/kernel/modprobe", "/proc/sys/kernel/core_pattern", "/proc/sysrq-trigger", + "/proc/kcore", } for i, filePath := range testWritePaths { name := fmt.Sprintf("writeprocsieve-%d", i) From edb60b950a6ae3afbf995f866d7142deacd7c90f Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Thu, 23 Jul 2015 12:01:24 -0400 Subject: [PATCH 10/79] AA: Eliminate 'file' permission Implements the policies for the remaining binaries called by the Docker engine and eliminates the giant whitelisted 'all files' permission in favor of granular whitelisting and child-specific policies. It should be possible now to remove the 'file' permission, but for the sake of keeping Docker unbroken, we'll try to gradually tighten the policy. Signed-off-by: Eric Windisch (cherry picked from commit 8b2fcddcd251e58473abf6c4949573e03f44bb96) --- contrib/apparmor/docker-engine | 128 ++++++++++++++++++++++++++------- 1 file changed, 104 insertions(+), 24 deletions(-) diff --git a/contrib/apparmor/docker-engine b/contrib/apparmor/docker-engine index 07b5dd864..a174ee440 100644 --- a/contrib/apparmor/docker-engine +++ b/contrib/apparmor/docker-engine @@ -21,51 +21,131 @@ profile /usr/bin/docker (attach_disconnected) { ipc rw, network, capability, - file, + owner /** rw, + /var/lib/docker/** rwl, + + # For non-root client use: + /dev/urandom r, + /run/docker.sock rw, + /proc/** r, + /sys/kernel/mm/hugepages/ r, + /etc/localtime r, ptrace peer=@{profile_name}, + ptrace (read) peer=docker-default, + deny ptrace (trace) peer=docker-default, + deny ptrace peer=/usr/bin/docker///bin/ps, /usr/bin/docker pix, - /sbin/xtables-multi rCix, + /sbin/xtables-multi rCx, /sbin/iptables rCx, /sbin/modprobe rCx, /sbin/auplink rCx, + /bin/kmod rCx, /usr/bin/xz rCx, + /bin/ps rCx, + /bin/cat rCx, + /sbin/zfs rCx, # Transitions change_profile -> docker-*, change_profile -> unconfined, + profile /bin/cat { + /etc/ld.so.cache r, + /lib/** r, + /dev/null rw, + /proc r, + /bin/cat mr, + + # For reading in 'docker stats': + /proc/[0-9]*/net/dev r, + } + profile /bin/ps { + /etc/ld.so.cache r, + /etc/localtime r, + /etc/passwd r, + /etc/nsswitch.conf r, + /lib/** r, + /proc/[0-9]*/** r, + /dev/null rw, + /bin/ps mr, + + # We don't need ptrace so we'll deny and ignore the error. + deny ptrace (read, trace), + + # Quiet dac_override denials + deny capability dac_override, + deny capability dac_read_search, + deny capability sys_ptrace, + + /dev/tty r, + /proc/stat r, + /proc/cpuinfo r, + /proc/meminfo r, + /proc/uptime r, + /sys/devices/system/cpu/online r, + /proc/sys/kernel/pid_max r, + /proc/ r, + /proc/tty/drivers r, + } profile /sbin/iptables { - signal (receive) peer=/usr/bin/docker, - capability net_admin, + signal (receive) peer=/usr/bin/docker, + capability net_admin, } profile /sbin/auplink flags=(attach_disconnected) { - signal (receive) peer=/usr/bin/docker, - capability sys_admin, - capability dac_override, + signal (receive) peer=/usr/bin/docker, + capability sys_admin, + capability dac_override, - @{DOCKER_GRAPH_PATH}/aufs/** rw, - # For user namespaces: - @{DOCKER_GRAPH_PATH}/[0-9]*.[0-9]*/** rw, + @{DOCKER_GRAPH_PATH}/aufs/** rw, + @{DOCKER_GRAPH_PATH}/tmp/** rw, + # For user namespaces: + @{DOCKER_GRAPH_PATH}/[0-9]*.[0-9]*/** rw, - # The following may be removed via delegates - /sys/fs/aufs/** r, - /lib/** r, - /apparmor/.null r, - /dev/null rw, - /etc/ld.so.cache r, - /sbin/auplink rm, - /proc/fs/aufs/** rw, - /proc/[0-9]*/mounts rw, + /sys/fs/aufs/** r, + /lib/** r, + /apparmor/.null r, + /dev/null rw, + /etc/ld.so.cache r, + /sbin/auplink rm, + /proc/fs/aufs/** rw, + /proc/[0-9]*/mounts rw, } - profile /sbin/modprobe { - signal (receive) peer=/usr/bin/docker, - capability sys_module, - file, + profile /sbin/modprobe /bin/kmod { + signal (receive) peer=/usr/bin/docker, + capability sys_module, + /etc/ld.so.cache r, + /lib/** r, + /dev/null rw, + /apparmor/.null rw, + /sbin/modprobe rm, + /bin/kmod rm, + /proc/cmdline r, + /sys/module/** r, + /etc/modprobe.d{/,/**} r, } # xz works via pipes, so we do not need access to the filesystem. profile /usr/bin/xz { - signal (receive) peer=/usr/bin/docker, + signal (receive) peer=/usr/bin/docker, + /etc/ld.so.cache r, + /lib/** r, + /usr/bin/xz rm, + deny /proc/** rw, + deny /sys/** rw, + } + profile /sbin/xtables-multi (attach_disconnected) { + /etc/ld.so.cache r, + /lib/** r, + /sbin/xtables-multi rm, + /apparmor/.null w, + /dev/null rw, + capability net_raw, + capability net_admin, + network raw, + } + profile /sbin/zfs (attach_disconnected) { + file, + capability, } } From 1d02be1c7ad3809283c04162298aade796e6c31f Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Thu, 23 Jul 2015 23:23:05 -0400 Subject: [PATCH 11/79] Mark engine AA policy as complain-only The engine policy will now only complain as a temporary measure to ensure we do not cause breakages while users exercise this policy. This is NOT the policy for containers, but for the newly-introduced policy for the daemon itself. Signed-off-by: Eric Windisch (cherry picked from commit 6c887be76951e802900a07e16aeaf0a079ac4534) --- contrib/apparmor/docker-engine | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/contrib/apparmor/docker-engine b/contrib/apparmor/docker-engine index a174ee440..bdfc20756 100644 --- a/contrib/apparmor/docker-engine +++ b/contrib/apparmor/docker-engine @@ -1,6 +1,6 @@ @{DOCKER_GRAPH_PATH}=/var/lib/docker -profile /usr/bin/docker (attach_disconnected) { +profile /usr/bin/docker (attach_disconnected, complain) { # Prevent following links to these files during container setup. deny /etc/** mkl, deny /dev/** kl, @@ -51,7 +51,7 @@ profile /usr/bin/docker (attach_disconnected) { change_profile -> docker-*, change_profile -> unconfined, - profile /bin/cat { + profile /bin/cat (complain) { /etc/ld.so.cache r, /lib/** r, /dev/null rw, @@ -61,7 +61,7 @@ profile /usr/bin/docker (attach_disconnected) { # For reading in 'docker stats': /proc/[0-9]*/net/dev r, } - profile /bin/ps { + profile /bin/ps (complain) { /etc/ld.so.cache r, /etc/localtime r, /etc/passwd r, @@ -89,11 +89,11 @@ profile /usr/bin/docker (attach_disconnected) { /proc/ r, /proc/tty/drivers r, } - profile /sbin/iptables { + profile /sbin/iptables (complain) { signal (receive) peer=/usr/bin/docker, capability net_admin, } - profile /sbin/auplink flags=(attach_disconnected) { + profile /sbin/auplink flags=(attach_disconnected, complain) { signal (receive) peer=/usr/bin/docker, capability sys_admin, capability dac_override, @@ -112,7 +112,7 @@ profile /usr/bin/docker (attach_disconnected) { /proc/fs/aufs/** rw, /proc/[0-9]*/mounts rw, } - profile /sbin/modprobe /bin/kmod { + profile /sbin/modprobe /bin/kmod (complain) { signal (receive) peer=/usr/bin/docker, capability sys_module, /etc/ld.so.cache r, @@ -126,7 +126,7 @@ profile /usr/bin/docker (attach_disconnected) { /etc/modprobe.d{/,/**} r, } # xz works via pipes, so we do not need access to the filesystem. - profile /usr/bin/xz { + profile /usr/bin/xz (complain) { signal (receive) peer=/usr/bin/docker, /etc/ld.so.cache r, /lib/** r, @@ -134,7 +134,7 @@ profile /usr/bin/docker (attach_disconnected) { deny /proc/** rw, deny /sys/** rw, } - profile /sbin/xtables-multi (attach_disconnected) { + profile /sbin/xtables-multi (attach_disconnected, complain) { /etc/ld.so.cache r, /lib/** r, /sbin/xtables-multi rm, @@ -144,7 +144,7 @@ profile /usr/bin/docker (attach_disconnected) { capability net_admin, network raw, } - profile /sbin/zfs (attach_disconnected) { + profile /sbin/zfs (attach_disconnected, complain) { file, capability, } From 686fe02020bb739e751e6e64467bd3784bbc9152 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 26 Jul 2015 20:06:33 +0200 Subject: [PATCH 12/79] Fix install script to handle debian 8.1 apt repo string Fix #14911 Signed-off-by: Antonio Murdaca (cherry picked from commit 98f15cae89ab528d60d8df8b22da2747cc9e67bc) --- hack/install.sh | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/hack/install.sh b/hack/install.sh index bbe9f70ed..902a07c33 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -51,6 +51,19 @@ echo_docker_as_nonroot() { EOF } +get_debian_dist_version() { + dist_version='' + case "$@" in + 8) + dist_version="jessie" + ;; + 7) + dist_version="wheezy" + ;; + esac + echo "$dist_version" +} + do_install() { case "$(uname -m)" in *64) @@ -124,19 +137,32 @@ do_install() { if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")" dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")" + if [ -z "$dist_version" ]; then + dist_version="$(. /etc/lsb-release && echo "$DISTRIB_RELEASE")" + case "$lsb_dist" in + "Debian") + dist_version="$(get_debian_dist_version "$dist_version")" + ;; + "Ubuntu") + case "$dist_version" in + "15.04") + dist_version="vivid" + ;; + "14.04") + dist_version="trusty" + ;; + "12.04") + dist_version="precise" + ;; + esac + ;; + esac + fi fi if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then lsb_dist='debian' dist_version="$(cat /etc/debian_version | sed 's/\/.*//' | sed 's/\..*//')" - case "$dist_version" in - 8) - dist_version="jessie" - ;; - - 7) - dist_version="wheezy" - ;; - esac + dist_version="$(get_debian_dist_version "$dist_version")" fi if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then lsb_dist='fedora' From 43da1adedbeedac1267a9d809da6116846da519b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 29 Jul 2015 13:04:12 -0700 Subject: [PATCH 13/79] Use *int64 for MemorySwappiness. So we marshal/unmarshal its value properly when it's empty. Signed-off-by: David Calavera (cherry picked from commit 4e25d2982b3af25bf55b8bc788d4604f7b539ea4) --- daemon/container_unix.go | 6 +++++- daemon/daemon_unix.go | 11 +++++++---- runconfig/hostconfig.go | 6 +++--- runconfig/parse.go | 2 +- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/daemon/container_unix.go b/daemon/container_unix.go index 6ae56cb8c..ff62de9c5 100644 --- a/daemon/container_unix.go +++ b/daemon/container_unix.go @@ -272,7 +272,11 @@ func populateCommand(c *Container, env []string) error { BlkioWeight: c.hostConfig.BlkioWeight, Rlimits: rlimits, OomKillDisable: c.hostConfig.OomKillDisable, - MemorySwappiness: c.hostConfig.MemorySwappiness, + MemorySwappiness: -1, + } + + if c.hostConfig.MemorySwappiness != nil { + resources.MemorySwappiness = *c.hostConfig.MemorySwappiness } processConfig := execdriver.ProcessConfig{ diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index d3197e3c3..1bc394c45 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -167,13 +167,16 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig, if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.") } - if hostConfig.MemorySwappiness != -1 && !daemon.SystemConfig().MemorySwappiness { + if hostConfig.MemorySwappiness != nil && !daemon.SystemConfig().MemorySwappiness { warnings = append(warnings, "Your kernel does not support memory swappiness capabilities, memory swappiness discarded.") logrus.Warnf("Your kernel does not support memory swappiness capabilities, memory swappiness discarded.") - hostConfig.MemorySwappiness = -1 + hostConfig.MemorySwappiness = nil } - if hostConfig.MemorySwappiness != -1 && (hostConfig.MemorySwappiness < 0 || hostConfig.MemorySwappiness > 100) { - return warnings, fmt.Errorf("Invalid value: %d, valid memory swappiness range is 0-100.", hostConfig.MemorySwappiness) + if hostConfig.MemorySwappiness != nil { + swappiness := *hostConfig.MemorySwappiness + if swappiness < -1 || swappiness > 100 { + return warnings, fmt.Errorf("Invalid value: %v, valid memory swappiness range is 0-100.", swappiness) + } } if hostConfig.CpuPeriod > 0 && !daemon.SystemConfig().CpuCfsPeriod { warnings = append(warnings, "Your kernel does not support CPU cfs period. Period discarded.") diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 21b40dc10..38255574a 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -231,9 +231,9 @@ type HostConfig struct { CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1 CpuQuota int64 - BlkioWeight int64 // Block IO weight (relative weight vs. other containers) - OomKillDisable bool // Whether to disable OOM Killer or not - MemorySwappiness int64 // Tuning container memory swappiness behaviour + BlkioWeight int64 // Block IO weight (relative weight vs. other containers) + OomKillDisable bool // Whether to disable OOM Killer or not + MemorySwappiness *int64 // Tuning container memory swappiness behaviour Privileged bool PortBindings nat.PortMap Links []string diff --git a/runconfig/parse.go b/runconfig/parse.go index c83d5bea1..b5ca0b8f8 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -351,7 +351,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe CpuQuota: *flCpuQuota, BlkioWeight: *flBlkioWeight, OomKillDisable: *flOomKillDisable, - MemorySwappiness: swappiness, + MemorySwappiness: flSwappiness, Privileged: *flPrivileged, PortBindings: portBindings, Links: flLinks.GetAll(), From 689c4e60750d3ea1aa11f65febbc51aefd047373 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 28 Jul 2015 11:36:38 -0700 Subject: [PATCH 14/79] Keep backwards compatibility in kill api. Return an error when the container is stopped only in api versions equal or greater than 1.20 (docker 1.8). Signed-off-by: David Calavera (cherry picked from commit 621e3d8587bbee86b4e36d0b7822662bfbedd76c) --- api/server/server.go | 8 +++++++- daemon/container.go | 18 +++++++++++++----- daemon/kill.go | 9 +++------ integration-cli/docker_cli_kill_test.go | 11 +++++++++++ 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 1ad1249a5..22bcc376b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -298,7 +298,13 @@ func (s *Server) postContainersKill(version version.Version, w http.ResponseWrit } if err := s.daemon.ContainerKill(name, sig); err != nil { - return err + _, isStopped := err.(daemon.ErrContainerNotRunning) + // Return error that's not caused because the container is stopped. + // Return error if the container is not running and the api is >= 1.20 + // to keep backwards compatibility. + if version.GreaterThanOrEqualTo("1.20") || !isStopped { + return fmt.Errorf("Cannot kill container %s: %v", name, err) + } } w.WriteHeader(http.StatusNoContent) diff --git a/daemon/container.go b/daemon/container.go index 0b19034b0..51a6bfd2c 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -41,6 +41,14 @@ var ( ErrContainerRootfsReadonly = errors.New("container rootfs is marked read-only") ) +type ErrContainerNotRunning struct { + id string +} + +func (e ErrContainerNotRunning) Error() string { + return fmt.Sprintf("Container %s is not running", e.id) +} + type StreamConfig struct { stdout *broadcastwriter.BroadcastWriter stderr *broadcastwriter.BroadcastWriter @@ -371,7 +379,7 @@ func (container *Container) KillSig(sig int) error { } if !container.Running { - return fmt.Errorf("Container %s is not running", container.ID) + return ErrContainerNotRunning{container.ID} } // signal to the monitor that it should not restart the container @@ -408,7 +416,7 @@ func (container *Container) Pause() error { // We cannot Pause the container which is not running if !container.Running { - return fmt.Errorf("Container %s is not running, cannot pause a non-running container", container.ID) + return ErrContainerNotRunning{container.ID} } // We cannot Pause the container which is already paused @@ -430,7 +438,7 @@ func (container *Container) Unpause() error { // We cannot unpause the container which is not running if !container.Running { - return fmt.Errorf("Container %s is not running, cannot unpause a non-running container", container.ID) + return ErrContainerNotRunning{container.ID} } // We cannot unpause the container which is not paused @@ -448,7 +456,7 @@ func (container *Container) Unpause() error { func (container *Container) Kill() error { if !container.IsRunning() { - return fmt.Errorf("Container %s is not running", container.ID) + return ErrContainerNotRunning{container.ID} } // 1. Send SIGKILL @@ -530,7 +538,7 @@ func (container *Container) Restart(seconds int) error { func (container *Container) Resize(h, w int) error { if !container.IsRunning() { - return fmt.Errorf("Cannot resize container %s, container is not running", container.ID) + return ErrContainerNotRunning{container.ID} } if err := container.command.ProcessConfig.Terminal.Resize(h, w); err != nil { return err diff --git a/daemon/kill.go b/daemon/kill.go index 3f7bb9bcf..7a4d9ce8a 100644 --- a/daemon/kill.go +++ b/daemon/kill.go @@ -1,9 +1,6 @@ package daemon -import ( - "fmt" - "syscall" -) +import "syscall" // ContainerKill send signal to the container // If no signal is given (sig 0), then Kill with SIGKILL and wait @@ -18,12 +15,12 @@ func (daemon *Daemon) ContainerKill(name string, sig uint64) error { // If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait()) if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL { if err := container.Kill(); err != nil { - return fmt.Errorf("Cannot kill container %s: %s", name, err) + return err } } else { // Otherwise, just send the requested signal if err := container.KillSig(int(sig)); err != nil { - return fmt.Errorf("Cannot kill container %s: %s", name, err) + return err } } return nil diff --git a/integration-cli/docker_cli_kill_test.go b/integration-cli/docker_cli_kill_test.go index 2c65fd344..685f4f5e6 100644 --- a/integration-cli/docker_cli_kill_test.go +++ b/integration-cli/docker_cli_kill_test.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + "net/http" "strings" "github.com/go-check/check" @@ -87,3 +89,12 @@ func (s *DockerSuite) TestKillWithInvalidSignal(c *check.C) { c.Fatal("Container should be in running state after an invalid signal") } } + +func (s *DockerSuite) TestKillofStoppedContainerAPIPre120(c *check.C) { + dockerCmd(c, "run", "--name", "docker-kill-test-api", "-d", "busybox", "top") + dockerCmd(c, "stop", "docker-kill-test-api") + + status, _, err := sockRequest("POST", fmt.Sprintf("/v1.19/containers/%s/kill", "docker-kill-test-api"), nil) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusNoContent) +} From 589922adf01efdcf89e887aa58abff2a712f2269 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 28 Jul 2015 10:36:57 -0700 Subject: [PATCH 15/79] Fix login and search TLS configuration Currently login and search do not load per registry certificates. This is a regression caused by the last refactor since this was recently fixed. Signed-off-by: Derek McGowan (github: dmcgowan) (cherry picked from commit e863a07b89599fd4a03d34491d67c09c6bc84444) --- registry/endpoint.go | 10 +++++----- registry/registry.go | 17 +++++++++++++++++ registry/service.go | 19 +------------------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/registry/endpoint.go b/registry/endpoint.go index c6361346a..b7aaedaaa 100644 --- a/registry/endpoint.go +++ b/registry/endpoint.go @@ -13,7 +13,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/registry/api/v2" "github.com/docker/distribution/registry/client/transport" - "github.com/docker/docker/pkg/tlsconfig" ) // for mocking in unit tests @@ -45,10 +44,11 @@ func scanForAPIVersion(address string) (string, APIVersion) { // NewEndpoint parses the given address to return a registry endpoint. func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) { - // *TODO: Allow per-registry configuration of endpoints. - tlsConfig := tlsconfig.ServerDefault - tlsConfig.InsecureSkipVerify = !index.Secure - endpoint, err := newEndpoint(index.GetAuthConfigKey(), &tlsConfig, metaHeaders) + tlsConfig, err := newTLSConfig(index.Name, index.Secure) + if err != nil { + return nil, err + } + endpoint, err := newEndpoint(index.GetAuthConfigKey(), tlsConfig, metaHeaders) if err != nil { return nil, err } diff --git a/registry/registry.go b/registry/registry.go index 09143ba8c..74f731bdc 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -49,6 +49,23 @@ func init() { dockerUserAgent = useragent.AppendVersions("", httpVersion...) } +func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) { + // PreferredServerCipherSuites should have no effect + tlsConfig := tlsconfig.ServerDefault + + tlsConfig.InsecureSkipVerify = !isSecure + + if isSecure { + hostDir := filepath.Join(CertsDir, hostname) + logrus.Debugf("hostDir: %s", hostDir) + if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil { + return nil, err + } + } + + return &tlsConfig, nil +} + func hasFile(files []os.FileInfo, name string) bool { for _, f := range files { if f.Name() == name { diff --git a/registry/service.go b/registry/service.go index fa35e3132..f4ea42ef9 100644 --- a/registry/service.go +++ b/registry/service.go @@ -5,10 +5,8 @@ import ( "fmt" "net/http" "net/url" - "path/filepath" "strings" - "github.com/Sirupsen/logrus" "github.com/docker/distribution/registry/client/auth" "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/tlsconfig" @@ -99,22 +97,7 @@ func (e APIEndpoint) ToV1Endpoint(metaHeaders http.Header) (*Endpoint, error) { // TLSConfig constructs a client TLS configuration based on server defaults func (s *Service) TLSConfig(hostname string) (*tls.Config, error) { - // PreferredServerCipherSuites should have no effect - tlsConfig := tlsconfig.ServerDefault - - isSecure := s.Config.isSecureIndex(hostname) - - tlsConfig.InsecureSkipVerify = !isSecure - - if isSecure { - hostDir := filepath.Join(CertsDir, hostname) - logrus.Debugf("hostDir: %s", hostDir) - if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil { - return nil, err - } - } - - return &tlsConfig, nil + return newTLSConfig(hostname, s.Config.isSecureIndex(hostname)) } func (s *Service) tlsConfigForMirror(mirror string) (*tls.Config, error) { From e6844381f00b2ef4058bf138a307903ec5da3baa Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 27 Jul 2015 09:46:20 -0400 Subject: [PATCH 16/79] archive, chrootarchive: split out decompression In `ApplyLayer` and `Untar`, the stream is magically decompressed. Since this is not able to be toggled, rather than break this ./pkg/ API, add an `ApplyUncompressedLayer` and `UntarUncompressed` that does not magically decompress the layer stream. Signed-off-by: Vincent Batts (cherry picked from commit 56bf275e32fc27bd31c5dba6b715336b43981695) --- pkg/archive/archive.go | 32 ++++++++++++++++++++++------- pkg/archive/diff.go | 28 +++++++++++++++++++------ pkg/chrootarchive/archive.go | 27 +++++++++++++++++++----- pkg/chrootarchive/archive_unix.go | 2 +- pkg/chrootarchive/diff_unix.go | 34 +++++++++++++++++++++++-------- 5 files changed, 95 insertions(+), 28 deletions(-) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 04e40a94f..11a707d20 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -633,8 +633,20 @@ loop: // The archive may be compressed with one of the following algorithms: // identity (uncompressed), gzip, bzip2, xz. // FIXME: specify behavior when target path exists vs. doesn't exist. -func Untar(archive io.Reader, dest string, options *TarOptions) error { - if archive == nil { +func Untar(tarArchive io.Reader, dest string, options *TarOptions) error { + return untarHandler(tarArchive, dest, options, true) +} + +// Untar reads a stream of bytes from `archive`, parses it as a tar archive, +// and unpacks it into the directory at `dest`. +// The archive must be an uncompressed stream. +func UntarUncompressed(tarArchive io.Reader, dest string, options *TarOptions) error { + return untarHandler(tarArchive, dest, options, false) +} + +// Handler for teasing out the automatic decompression +func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decompress bool) error { + if tarArchive == nil { return fmt.Errorf("Empty archive") } dest = filepath.Clean(dest) @@ -644,12 +656,18 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error { if options.ExcludePatterns == nil { options.ExcludePatterns = []string{} } - decompressedArchive, err := DecompressStream(archive) - if err != nil { - return err + + var r io.Reader = tarArchive + if decompress { + decompressedArchive, err := DecompressStream(tarArchive) + if err != nil { + return err + } + defer decompressedArchive.Close() + r = decompressedArchive } - defer decompressedArchive.Close() - return Unpack(decompressedArchive, dest, options) + + return Unpack(r, dest, options) } func (archiver *Archiver) TarUntar(src, dst string) error { diff --git a/pkg/archive/diff.go b/pkg/archive/diff.go index aed8542d7..d310a17a5 100644 --- a/pkg/archive/diff.go +++ b/pkg/archive/diff.go @@ -173,10 +173,24 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { return size, nil } -// ApplyLayer parses a diff in the standard layer format from `layer`, and -// applies it to the directory `dest`. Returns the size in bytes of the -// contents of the layer. +// ApplyLayer parses a diff in the standard layer format from `layer`, +// and applies it to the directory `dest`. The stream `layer` can be +// compressed or uncompressed. +// Returns the size in bytes of the contents of the layer. func ApplyLayer(dest string, layer ArchiveReader) (int64, error) { + return applyLayerHandler(dest, layer, true) +} + +// ApplyUncompressedLayer parses a diff in the standard layer format from +// `layer`, and applies it to the directory `dest`. The stream `layer` +// can only be uncompressed. +// Returns the size in bytes of the contents of the layer. +func ApplyUncompressedLayer(dest string, layer ArchiveReader) (int64, error) { + return applyLayerHandler(dest, layer, false) +} + +// do the bulk load of ApplyLayer, but allow for not calling DecompressStream +func applyLayerHandler(dest string, layer ArchiveReader, decompress bool) (int64, error) { dest = filepath.Clean(dest) // We need to be able to set any perms @@ -186,9 +200,11 @@ func ApplyLayer(dest string, layer ArchiveReader) (int64, error) { } defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform - layer, err = DecompressStream(layer) - if err != nil { - return 0, err + if decompress { + layer, err = DecompressStream(layer) + if err != nil { + return 0, err + } } return UnpackLayer(dest, layer) } diff --git a/pkg/chrootarchive/archive.go b/pkg/chrootarchive/archive.go index dffbec16b..8e8e15977 100644 --- a/pkg/chrootarchive/archive.go +++ b/pkg/chrootarchive/archive.go @@ -3,6 +3,7 @@ package chrootarchive import ( "fmt" "io" + "io/ioutil" "os" "path/filepath" @@ -17,6 +18,18 @@ var chrootArchiver = &archive.Archiver{Untar: Untar} // The archive may be compressed with one of the following algorithms: // identity (uncompressed), gzip, bzip2, xz. func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error { + return untarHandler(tarArchive, dest, options, true) +} + +// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive, +// and unpacks it into the directory at `dest`. +// The archive must be an uncompressed stream. +func UntarUncompressed(tarArchive io.Reader, dest string, options *archive.TarOptions) error { + return untarHandler(tarArchive, dest, options, false) +} + +// Handler for teasing out the automatic decompression +func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool) error { if tarArchive == nil { return fmt.Errorf("Empty archive") @@ -35,13 +48,17 @@ func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error } } - decompressedArchive, err := archive.DecompressStream(tarArchive) - if err != nil { - return err + r := ioutil.NopCloser(tarArchive) + if decompress { + decompressedArchive, err := archive.DecompressStream(tarArchive) + if err != nil { + return err + } + defer decompressedArchive.Close() + r = decompressedArchive } - defer decompressedArchive.Close() - return invokeUnpack(decompressedArchive, dest, options) + return invokeUnpack(r, dest, options) } // TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. diff --git a/pkg/chrootarchive/archive_unix.go b/pkg/chrootarchive/archive_unix.go index d60718dc8..83331425f 100644 --- a/pkg/chrootarchive/archive_unix.go +++ b/pkg/chrootarchive/archive_unix.go @@ -49,7 +49,7 @@ func untar() { os.Exit(0) } -func invokeUnpack(decompressedArchive io.ReadCloser, dest string, options *archive.TarOptions) error { +func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions) error { // We can't pass a potentially large exclude list directly via cmd line // because we easily overrun the kernel's max argument/environment size diff --git a/pkg/chrootarchive/diff_unix.go b/pkg/chrootarchive/diff_unix.go index f8678ab2d..bec85a0de 100644 --- a/pkg/chrootarchive/diff_unix.go +++ b/pkg/chrootarchive/diff_unix.go @@ -65,20 +65,36 @@ func applyLayer() { os.Exit(0) } -// ApplyLayer parses a diff in the standard layer format from `layer`, and -// applies it to the directory `dest`. Returns the size in bytes of the -// contents of the layer. +// ApplyLayer parses a diff in the standard layer format from `layer`, +// and applies it to the directory `dest`. The stream `layer` can only be +// uncompressed. +// Returns the size in bytes of the contents of the layer. func ApplyLayer(dest string, layer archive.ArchiveReader) (size int64, err error) { + return applyLayerHandler(dest, layer, true) +} + +// ApplyUncompressedLayer parses a diff in the standard layer format from +// `layer`, and applies it to the directory `dest`. The stream `layer` +// can only be uncompressed. +// Returns the size in bytes of the contents of the layer. +func ApplyUncompressedLayer(dest string, layer archive.ArchiveReader) (int64, error) { + return applyLayerHandler(dest, layer, false) +} + +func applyLayerHandler(dest string, layer archive.ArchiveReader, decompress bool) (size int64, err error) { dest = filepath.Clean(dest) - decompressed, err := archive.DecompressStream(layer) - if err != nil { - return 0, err + if decompress { + decompressed, err := archive.DecompressStream(layer) + if err != nil { + return 0, err + } + defer decompressed.Close() + + layer = decompressed } - defer decompressed.Close() - cmd := reexec.Command("docker-applyLayer", dest) - cmd.Stdin = decompressed + cmd.Stdin = layer outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer) cmd.Stdout, cmd.Stderr = outBuf, errBuf From d167338876c4c803d4f40234ba64d7b9451cf35f Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 27 Jul 2015 13:23:52 -0400 Subject: [PATCH 17/79] graphdriver/*: expect uncompressed tar for ApplyDiff The `ApplyDiff` function takes a tar archive stream that is automagically decompressed later. This was causing a double decompression, and when the layer was empty, that causes an early EOF. Signed-off-by: Vincent Batts (cherry picked from commit 273f50c741e82a0be3e9f9d4c975cc18801dfe38) --- daemon/graphdriver/aufs/aufs.go | 2 +- daemon/graphdriver/driver.go | 1 + daemon/graphdriver/fsdiff.go | 2 +- daemon/graphdriver/overlay/overlay.go | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index 893801a36..eec4deee6 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -323,7 +323,7 @@ func (a *Driver) Diff(id, parent string) (archive.Archive, error) { } func (a *Driver) applyDiff(id string, diff archive.ArchiveReader) error { - return chrootarchive.Untar(diff, path.Join(a.rootPath(), "diff", id), nil) + return chrootarchive.UntarUncompressed(diff, path.Join(a.rootPath(), "diff", id), nil) } // DiffSize calculates the changes between the specified id diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index b7e35e4c0..2f44fddcb 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -77,6 +77,7 @@ type Driver interface { // ApplyDiff extracts the changeset from the given diff into the // layer with the specified id and parent, returning the size of the // new layer in bytes. + // The archive.ArchiveReader must be an uncompressed stream. ApplyDiff(id, parent string, diff archive.ArchiveReader) (size int64, err error) // DiffSize calculates the changes between the specified id // and its parent and returns the size in bytes of the changes diff --git a/daemon/graphdriver/fsdiff.go b/daemon/graphdriver/fsdiff.go index e091e619b..bee9682e7 100644 --- a/daemon/graphdriver/fsdiff.go +++ b/daemon/graphdriver/fsdiff.go @@ -121,7 +121,7 @@ func (gdw *naiveDiffDriver) ApplyDiff(id, parent string, diff archive.ArchiveRea start := time.Now().UTC() logrus.Debugf("Start untar layer") - if size, err = chrootarchive.ApplyLayer(layerFs, diff); err != nil { + if size, err = chrootarchive.ApplyUncompressedLayer(layerFs, diff); err != nil { return } logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds()) diff --git a/daemon/graphdriver/overlay/overlay.go b/daemon/graphdriver/overlay/overlay.go index 9cde62ae9..fc04057c3 100644 --- a/daemon/graphdriver/overlay/overlay.go +++ b/daemon/graphdriver/overlay/overlay.go @@ -411,7 +411,7 @@ func (d *Driver) ApplyDiff(id string, parent string, diff archive.ArchiveReader) return 0, err } - if size, err = chrootarchive.ApplyLayer(tmpRootDir, diff); err != nil { + if size, err = chrootarchive.ApplyUncompressedLayer(tmpRootDir, diff); err != nil { return 0, err } From 47d52fb872fc35e3a605922f720c25eb20277cfe Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Fri, 24 Jul 2015 16:35:11 -0700 Subject: [PATCH 18/79] [api/client] Tag resolved digest from Dockerfile Builds where the base images have been resolved to trusted digest references will now be tagged with the original tag reference from the Dockerfile on a successful build. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) (cherry picked from commit bb2e6c72d2fb3f1b64755bdf6d6269dbc6767f87) --- api/client/build.go | 50 +++++++++++++++++++----- integration-cli/docker_cli_build_test.go | 11 +++++- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index 10b0e727d..bc5172d15 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -115,8 +115,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } // Resolve the FROM lines in the Dockerfile to trusted digest references - // using Notary. - newDockerfile, err := rewriteDockerfileFrom(filepath.Join(contextDir, relDockerfile), cli.trustedReference) + // using Notary. On a successful build, we must tag the resolved digests + // to the original name specified in the Dockerfile. + newDockerfile, resolvedTags, err := rewriteDockerfileFrom(filepath.Join(contextDir, relDockerfile), cli.trustedReference) if err != nil { return fmt.Errorf("unable to process Dockerfile: %v", err) } @@ -291,7 +292,20 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code} } - return err + + if err != nil { + return err + } + + // Since the build was successful, now we must tag any of the resolved + // images from the above Dockerfile rewrite. + for _, resolved := range resolvedTags { + if err := cli.tagTrusted(resolved.repoInfo, resolved.digestRef, resolved.tagRef); err != nil { + return err + } + } + + return nil } // getDockerfileRelPath uses the given context directory for a `docker build` @@ -483,14 +497,21 @@ func (td *trustedDockerfile) Close() error { return os.Remove(td.File.Name()) } +// resolvedTag records the repository, tag, and resolved digest reference +// from a Dockerfile rewrite. +type resolvedTag struct { + repoInfo *registry.RepositoryInfo + digestRef, tagRef registry.Reference +} + // rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in // "FROM " instructions to a digest reference. `translator` is a // function that takes a repository name and tag reference and returns a // trusted digest reference. -func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, err error) { +func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, resolvedTags []*resolvedTag, err error) { dockerfile, err := os.Open(dockerfileName) if err != nil { - return nil, fmt.Errorf("unable to open Dockerfile: %v", err) + return nil, nil, fmt.Errorf("unable to open Dockerfile: %v", err) } defer dockerfile.Close() @@ -499,7 +520,7 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist // Make a tempfile to store the rewritten Dockerfile. tempFile, err := ioutil.TempFile("", "trusted-dockerfile-") if err != nil { - return nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err) + return nil, nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err) } trustedFile := &trustedDockerfile{ @@ -525,21 +546,32 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist if tag == "" { tag = tags.DEFAULTTAG } + + repoInfo, err := registry.ParseRepositoryInfo(repo) + if err != nil { + return nil, nil, fmt.Errorf("unable to parse repository info: %v", err) + } + ref := registry.ParseReference(tag) if !ref.HasDigest() && isTrusted() { trustedRef, err := translator(repo, ref) if err != nil { - return nil, err + return nil, nil, err } line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.ImageName(repo))) + resolvedTags = append(resolvedTags, &resolvedTag{ + repoInfo: repoInfo, + digestRef: trustedRef, + tagRef: ref, + }) } } n, err := fmt.Fprintln(tempFile, line) if err != nil { - return nil, err + return nil, nil, err } trustedFile.size += int64(n) @@ -547,7 +579,7 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist tempFile.Seek(0, os.SEEK_SET) - return trustedFile, scanner.Err() + return trustedFile, resolvedTags, scanner.Err() } // replaceDockerfileTarWrapper wraps the given input tar archive stream and diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 560fcb88e..6dc24df96 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5349,8 +5349,15 @@ func (s *DockerTrustSuite) TestTrustedBuild(c *check.C) { c.Fatalf("Unexpected output on trusted build:\n%s", out) } - // Build command does not create untrusted tag - //dockerCmd(c, "rmi", repoName) + // We should also have a tag reference for the image. + if out, exitCode := dockerCmd(c, "inspect", repoName); exitCode != 0 { + c.Fatalf("unexpected exit code inspecting image %q: %d: %s", repoName, exitCode, out) + } + + // We should now be able to remove the tag reference. + if out, exitCode := dockerCmd(c, "rmi", repoName); exitCode != 0 { + c.Fatalf("unexpected exit code inspecting image %q: %d: %s", repoName, exitCode, out) + } } func (s *DockerTrustSuite) TestTrustedBuildUntrustedTag(c *check.C) { From ebf396c6e8899ab88ce47fdb039c59fea4c5024c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 27 Jul 2015 14:28:05 -0700 Subject: [PATCH 19/79] Log each release step. Signed-off-by: David Calavera (cherry picked from commit 776600fabbf52d21a061a9021011774d23df9b67) --- hack/release.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hack/release.sh b/hack/release.sh index 4a712873a..b56d69e88 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -70,6 +70,7 @@ BUCKET=$AWS_S3_BUCKET # GPG_KEY="740B314AE3941731B942C66ADF4FD13717AAD7D6" setup_s3() { + echo "Setting up S3" # Try creating the bucket. Ignore errors (it might already exist). s3cmd mb "s3://$BUCKET" 2>/dev/null || true # Check access to the bucket. @@ -102,6 +103,7 @@ s3_url() { } build_all() { + echo "Building release" if ! ./hack/make.sh "${RELEASE_BUNDLES[@]}"; then echo >&2 echo >&2 'The build or tests appear to have failed.' @@ -162,6 +164,7 @@ upload_release_build() { } release_build() { + echo "Releasing binaries" GOOS=$1 GOARCH=$2 @@ -246,6 +249,7 @@ release_build() { # 1. A full APT repository is published at $BUCKET/ubuntu/ # 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/index release_ubuntu() { + echo "Releasing ubuntu" [ -e "bundles/$VERSION/ubuntu" ] || { echo >&2 './hack/make.sh must be run before release_ubuntu' exit 1 @@ -338,16 +342,19 @@ EOF # Upload the index script release_index() { + echo "Releasing index" sed "s,url='https://get.docker.com/',url='$(s3_url)/'," hack/install.sh | write_to_s3 "s3://$BUCKET/index" } release_test() { + echo "Releasing tests" if [ -e "bundles/$VERSION/test" ]; then s3cmd --acl-public sync "bundles/$VERSION/test/" "s3://$BUCKET/test/" fi } setup_gpg() { + echo "Setting up GPG" # Make sure that we have our keys mkdir -p "$HOME/.gnupg/" s3cmd sync "s3://$BUCKET/ubuntu/.gnupg/" "$HOME/.gnupg/" || true From ba8abcb3dd6537ca04101b34764dfaf08ce65c35 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Sun, 14 Jun 2015 12:03:41 -0700 Subject: [PATCH 20/79] Add `docker daemon` to bash completion Signed-off-by: Harald Albers Signed-off-by: Tibor Vass (cherry picked from commit 8cc8ee9254ead6ba5e4f708d7a04ac88a74ccb0a) --- contrib/completion/bash/docker | 137 +++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 58 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 273d9b701..50b32aca2 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -312,49 +312,22 @@ __docker_signals() { COMPREPLY=( $( compgen -W "${signals[*]} ${signals[*]#SIG}" -- "$( echo $cur | tr '[:lower:]' '[:upper:]')" ) ) } +# global options that may appear after the docker command _docker_docker() { local boolean_options=" - --daemon -d --debug -D --help -h - --icc - --ip-forward - --ip-masq - --iptables - --ipv6 - --selinux-enabled --tls --tlsverify - --userland-proxy=false --version -v " case "$prev" in - --exec-root|--graph|-g) - _filedir -d - return - ;; - --log-driver) - __docker_log_drivers - return - ;; --log-level|-l) COMPREPLY=( $( compgen -W "debug info warn error fatal" -- "$cur" ) ) return ;; - --log-opt) - __docker_log_driver_options - return - ;; - --pidfile|-p|--tlscacert|--tlscert|--tlskey) - _filedir - return - ;; - --storage-driver|-s) - COMPREPLY=( $( compgen -W "aufs devicemapper btrfs overlay" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) ) - return - ;; - $main_options_with_args_glob ) + $(__docker_to_extglob "$global_options_with_args") ) return ;; esac @@ -363,7 +336,7 @@ _docker_docker() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "$boolean_options $main_options_with_args" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "$boolean_options $global_options_with_args" -- "$cur" ) ) ;; *) local counter="$(__docker_pos_first_nonflag $main_options_with_args_glob)" @@ -478,6 +451,79 @@ _docker_create() { _docker_run } +_docker_daemon() { + local boolean_options=" + --help -h + --icc=false + --ip-forward=false + --ip-masq=false + --iptables=false + --ipv6 + --selinux-enabled + --userland-proxy=false + " + local options_with_args=" + --api-cors-header + --bip + --bridge -b + --default-gateway + --default-gateway-v6 + --default-ulimit + --dns + --dns-search + --exec-driver -e + --exec-opt + --exec-root + --fixed-cidr + --fixed-cidr-v6 + --graph -g + --group -G + --insecure-registry + --ip + --label + --log-driver + --log-opt + --mtu + --pidfile -p + --registry-mirror + --storage-driver -s + --storage-opt + " + + case "$prev" in + --exec-root|--graph|-g) + _filedir -d + return + ;; + --log-driver) + __docker_log_drivers + return + ;; + --pidfile|-p|--tlscacert|--tlscert|--tlskey) + _filedir + return + ;; + --storage-driver|-s) + COMPREPLY=( $( compgen -W "aufs devicemapper btrfs overlay" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) ) + return + ;; + --log-opt) + __docker_log_driver_options + return + ;; + $(__docker_to_extglob "$options_with_args") ) + return + ;; + $main_options_with_args_glob ) + esac + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) ) + ;; + esac +} + _docker_diff() { case "$cur" in -*) @@ -1287,6 +1333,7 @@ _docker() { commit cp create + daemon diff events exec @@ -1323,40 +1370,14 @@ _docker() { wait ) - local main_options_with_args=" - --api-cors-header - --bip - --bridge -b - --default-gateway - --default-gateway-v6 - --default-ulimit - --dns - --dns-search - --exec-driver -e - --exec-opt - --exec-root - --fixed-cidr - --fixed-cidr-v6 - --graph -g - --group -G + local global_options_with_args=" --host -H - --insecure-registry - --ip - --label - --log-driver --log-level -l - --log-opt - --mtu - --pidfile -p - --registry-mirror - --storage-driver -s - --storage-opt --tlscacert --tlscert --tlskey " - local main_options_with_args_glob=$(__docker_to_extglob "$main_options_with_args") local host COMPREPLY=() @@ -1372,7 +1393,7 @@ _docker() { (( counter++ )) host="${words[$counter]}" ;; - $main_options_with_args_glob ) + $(__docker_to_extglob "$global_options_with_args") ) (( counter++ )) ;; -*) From 559043b953d6a07a810e173398a0b81d7c3212dc Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Thu, 30 Jul 2015 14:15:41 -0400 Subject: [PATCH 21/79] tlsconfig: better format for error message in tlsconfig Signed-off-by: Tibor Vass (cherry picked from commit c7a04fda2ad804601385f054c19b69cf43fcfe46) --- docker/daemon.go | 2 +- pkg/tlsconfig/config.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/daemon.go b/docker/daemon.go index e11b98d8f..24607042c 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -226,7 +226,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { } tlsConfig, err := tlsconfig.Server(*commonFlags.TLSOptions) if err != nil { - logrus.Fatalf("foobar: %v", err) + logrus.Fatal(err) } serverConfig.TLSConfig = tlsConfig } diff --git a/pkg/tlsconfig/config.go b/pkg/tlsconfig/config.go index 88f768ae2..9f7f33694 100644 --- a/pkg/tlsconfig/config.go +++ b/pkg/tlsconfig/config.go @@ -72,10 +72,10 @@ func certPool(caFile string) (*x509.CertPool, error) { certPool := x509.NewCertPool() pem, err := ioutil.ReadFile(caFile) if err != nil { - return nil, fmt.Errorf("Could not read CA certificate %s: %v", caFile, err) + return nil, fmt.Errorf("Could not read CA certificate %q: %v", caFile, err) } if !certPool.AppendCertsFromPEM(pem) { - return nil, fmt.Errorf("failed to append certificates from PEM file: %s", caFile) + return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile) } s := certPool.Subjects() subjects := make([]string, len(s)) @@ -116,9 +116,9 @@ func Server(options Options) (*tls.Config, error) { tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) if err != nil { if os.IsNotExist(err) { - return nil, fmt.Errorf("Could not load X509 key pair (%s, %s): %v", options.CertFile, options.KeyFile, err) + return nil, fmt.Errorf("Could not load X509 key pair (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err) } - return nil, fmt.Errorf("Error reading X509 key pair (%s, %s): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err) + return nil, fmt.Errorf("Error reading X509 key pair (cert: %q, key: %q): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err) } tlsConfig.Certificates = []tls.Certificate{tlsCert} if options.ClientAuth >= tls.VerifyClientCertIfGiven { From f156fb7be5e23128a1dc682f2c86a7f2f2907e98 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Thu, 30 Jul 2015 12:54:21 -0400 Subject: [PATCH 22/79] Only explicitly deny ptrace for container-originated procs The 'deny ptrace' statement was supposed to only ignore ptrace failures in the AUDIT log. However, ptrace was implicitly allowed from unconfined processes (such as the docker daemon and its integration tests) due to the abstractions/base include. This rule narrows the definition such that it will only ignore the failures originating inside of the container and will not cause denials when the daemon or its tests ptrace inside processes. Introduces positive and negative tests for ptrace /w apparmor. Signed-off-by: Eric Windisch (cherry picked from commit f5c388b35a9ddd699b3dbbe85b80fa02234f8355) --- daemon/execdriver/native/apparmor.go | 2 +- integration-cli/docker_cli_run_test.go | 39 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/daemon/execdriver/native/apparmor.go b/daemon/execdriver/native/apparmor.go index ecbaf3351..9d166fb93 100644 --- a/daemon/execdriver/native/apparmor.go +++ b/daemon/execdriver/native/apparmor.go @@ -50,7 +50,7 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { deny @{PROC}/sys/kernel/*/** wklx, deny mount, - deny ptrace, + deny ptrace (trace) peer=docker-default, deny /sys/[^f]*/** wklx, deny /sys/f[^s]*/** wklx, diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 841480e9f..2c27cd9d7 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2729,3 +2729,42 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) { c.Fatalf("Missing expected output on trusted push:\n%s", out) } } + +func (s *DockerSuite) TestPtraceContainerProcsFromHost(c *check.C) { + testRequires(c, SameHostDaemon) + + out, _ := dockerCmd(c, "run", "-d", "busybox", "top") + id := strings.TrimSpace(out) + if err := waitRun(id); err != nil { + c.Fatal(err) + } + pid1, err := inspectField(id, "State.Pid") + c.Assert(err, check.IsNil) + + _, err = os.Readlink(fmt.Sprintf("/proc/%s/ns/net", pid1)) + if err != nil { + c.Fatal(err) + } +} + +func (s *DockerSuite) TestAppArmorDeniesPtrace(c *check.C) { + testRequires(c, SameHostDaemon) + testRequires(c, Apparmor) + + // Run through 'sh' so we are NOT pid 1. Pid 1 may be able to trace + // itself, but pid>1 should not be able to trace pid1. + _, exitCode, _ := dockerCmdWithError("run", "busybox", "sh", "-c", "readlink /proc/1/ns/net") + if exitCode == 0 { + c.Fatal("ptrace was not successfully restricted by AppArmor") + } +} + +func (s *DockerSuite) TestAppArmorTraceSelf(c *check.C) { + testRequires(c, SameHostDaemon) + testRequires(c, Apparmor) + + _, exitCode, _ := dockerCmdWithError("run", "busybox", "readlink", "/proc/1/ns/net") + if exitCode != 0 { + c.Fatal("ptrace of self failed.") + } +} From 2af7f63173e0af35db67c2dbc97759ac022d39bd Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 30 Jul 2015 10:26:45 -0700 Subject: [PATCH 23/79] Don't mount /proc as ro This caused a regression with LSM labeling. Signed-off-by: Michael Crosby (cherry picked from commit bfc51cf6605ebcf7a4ec791fb0f3b5ae7b05c6fd) --- daemon/execdriver/native/create.go | 2 +- integration-cli/docker_cli_exec_test.go | 7 +++++++ integration-cli/docker_cli_run_test.go | 12 ++++++------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 0f0a6a12d..0732a835d 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -85,7 +85,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*configs.Config, error) } /* These paths must be remounted as r/o */ - container.ReadonlyPaths = append(container.ReadonlyPaths, "/proc", "/dev") + container.ReadonlyPaths = append(container.ReadonlyPaths, "/dev") } if err := d.setupMounts(container, c); err != nil { diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index b90e159af..8e85988f1 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -536,3 +536,10 @@ func (s *DockerSuite) TestExecWithImageUser(c *check.C) { c.Fatalf("exec with user by id expected dockerio user got %s", out) } } + +func (s *DockerSuite) TestExecOnReadonlyContainer(c *check.C) { + dockerCmd(c, "run", "-d", "--read-only", "--name", "parent", "busybox", "top") + if _, status := dockerCmd(c, "exec", "parent", "true"); status != 0 { + c.Fatalf("exec into a read-only container failed with exit status %d", status) + } +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 2c27cd9d7..d76ca95b6 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2242,7 +2242,7 @@ func (s *DockerSuite) TestRunContainerWithWritableRootfs(c *check.C) { func (s *DockerSuite) TestRunContainerWithReadonlyRootfs(c *check.C) { testRequires(c, NativeExecDriver) - for _, f := range []string{"/file", "/etc/hosts", "/etc/resolv.conf", "/etc/hostname", "/proc/uptime", "/sys/kernel", "/dev/.dont.touch.me"} { + for _, f := range []string{"/file", "/etc/hosts", "/etc/resolv.conf", "/etc/hostname", "/sys/kernel", "/dev/.dont.touch.me"} { testReadOnlyFile(f, c) } } @@ -2396,7 +2396,7 @@ func (s *DockerSuite) TestRunWriteToProcAsound(c *check.C) { func (s *DockerSuite) TestRunReadProcTimer(c *check.C) { testRequires(c, NativeExecDriver) - out, code, err := dockerCmdWithError("run", "busybox", "cat", "/proc/timer_stats") + out, code, err := dockerCmdWithError(c, "run", "busybox", "cat", "/proc/timer_stats") if code != 0 { return } @@ -2416,7 +2416,7 @@ func (s *DockerSuite) TestRunReadProcLatency(c *check.C) { c.Skip("kernel doesnt have latency_stats configured") return } - out, code, err := dockerCmdWithError("run", "busybox", "cat", "/proc/latency_stats") + out, code, err := dockerCmdWithError(c, "run", "busybox", "cat", "/proc/latency_stats") if code != 0 { return } @@ -2440,7 +2440,7 @@ func (s *DockerSuite) TestRunReadFilteredProc(c *check.C) { name := fmt.Sprintf("procsieve-%d", i) shellCmd := fmt.Sprintf("exec 3<%s", filePath) - if out, exitCode, err := dockerCmdWithError("run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd); err == nil || exitCode == 0 { + if out, exitCode, err := dockerCmdWithError(c, "run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd); err == nil || exitCode == 0 { c.Fatalf("Open FD for read should have failed with permission denied, got: %s, %v", out, err) } } @@ -2753,7 +2753,7 @@ func (s *DockerSuite) TestAppArmorDeniesPtrace(c *check.C) { // Run through 'sh' so we are NOT pid 1. Pid 1 may be able to trace // itself, but pid>1 should not be able to trace pid1. - _, exitCode, _ := dockerCmdWithError("run", "busybox", "sh", "-c", "readlink /proc/1/ns/net") + _, exitCode, _ := dockerCmdWithError(c, "run", "busybox", "sh", "-c", "readlink /proc/1/ns/net") if exitCode == 0 { c.Fatal("ptrace was not successfully restricted by AppArmor") } @@ -2763,7 +2763,7 @@ func (s *DockerSuite) TestAppArmorTraceSelf(c *check.C) { testRequires(c, SameHostDaemon) testRequires(c, Apparmor) - _, exitCode, _ := dockerCmdWithError("run", "busybox", "readlink", "/proc/1/ns/net") + _, exitCode, _ := dockerCmdWithError(c, "run", "busybox", "readlink", "/proc/1/ns/net") if exitCode != 0 { c.Fatal("ptrace of self failed.") } From d394113dfecb8627dcb129053e6f7861e7bcaf34 Mon Sep 17 00:00:00 2001 From: Stephen Rust Date: Sat, 25 Jul 2015 07:39:13 -0400 Subject: [PATCH 24/79] Check for nil before using HostConfig to adjustCpuShares Fix #14915. Add unit test for #14915. Thanks @runcom for the test case: when the client calls 1.18 api version w/o hostconfig it results in a nil pointer dereference. Signed-off-by: Stephen Rust (cherry picked from commit c358a4cd3597ac330674c9d93b6038e8f455c8f7) --- api/server/server_linux.go | 2 +- integration-cli/docker_api_containers_test.go | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/api/server/server_linux.go b/api/server/server_linux.go index 2ab186e97..f6ad26a9b 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -109,7 +109,7 @@ func allocateDaemonPort(addr string) error { func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) { if version.LessThan("1.19") { - if hostConfig.CpuShares > 0 { + if hostConfig != nil && hostConfig.CpuShares > 0 { // Handle unsupported CpuShares if hostConfig.CpuShares < linuxMinCpuShares { logrus.Warnf("Changing requested CpuShares of %d to minimum allowed of %d", hostConfig.CpuShares, linuxMinCpuShares) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index b0e9b0eed..c9d5d9645 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -1687,3 +1687,13 @@ func (s *DockerSuite) TestPostContainersStartWithLinksInHostConfigIdLinked(c *ch c.Assert(res.StatusCode, check.Equals, http.StatusNoContent) b.Close() } + +// #14915 +func (s *DockerSuite) TestContainersApiCreateNoHostConfig118(c *check.C) { + config := struct { + Image string + }{"busybox"} + status, _, err := sockRequest("POST", "/v1.18/containers/create", config) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusCreated) +} From c109095a58d339700ea5223c51a108d0266da9ca Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Fri, 24 Jul 2015 14:12:55 -0700 Subject: [PATCH 25/79] Fix `docker cp` Behavior With Symlinks [pkg/archive] Update archive/copy path handling - Remove unused TarOptions.Name field. - Add new TarOptions.RebaseNames field. - Update some of the logic around path dir/base splitting. - Update some of the logic behind archive entry name rebasing. [api/types] Add LinkTarget field to PathStat [daemon] Fix stat, archive, extract of symlinks These operations *should* resolve symlinks that are in the path but if the resource itself is a symlink then it *should not* be resolved. This patch puts this logic into a common function `resolvePath` which resolves symlinks of the path's dir in scope of the container rootfs but does not resolve the final element of the path. Now archive, extract, and stat operations will return symlinks if the path is indeed a symlink. [api/client] Update cp path hanling [docs/reference/api] Update description of stat Add the linkTarget field to the header of the archive endpoint. Remove path field. [integration-cli] Fix/Add cp symlink test cases Copying a symlink should do just that: copy the symlink NOT copy the target of the symlink. Also, the resulting file from the copy should have the name of the symlink NOT the name of the target file. Copying to a symlink should copy to the symlink target and not modify the symlink itself. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) (cherry picked from commit 75f6929b449a59335572436862d644afacf55cdb) --- api/client/cp.go | 39 +++- api/types/types.go | 13 +- daemon/archive.go | 193 +++++++++------- docs/reference/api/docker_remote_api_v1.20.md | 6 +- integration-cli/docker_api_containers_test.go | 34 +++ .../docker_cli_cp_from_container_test.go | 108 +++++++++ integration-cli/docker_cli_cp_test.go | 174 +++++++++++++- .../docker_cli_cp_to_container_test.go | 112 +++++++++ integration-cli/docker_cli_cp_utils.go | 22 +- pkg/archive/archive.go | 26 ++- pkg/archive/archive_test.go | 2 +- pkg/archive/copy.go | 213 ++++++++++++------ pkg/archive/copy_test.go | 24 +- 13 files changed, 758 insertions(+), 208 deletions(-) diff --git a/api/client/cp.go b/api/client/cp.go index 99278adfc..a36212a79 100644 --- a/api/client/cp.go +++ b/api/client/cp.go @@ -232,6 +232,20 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (er // Prepare destination copy info by stat-ing the container path. dstInfo := archive.CopyInfo{Path: dstPath} dstStat, err := cli.statContainerPath(dstContainer, dstPath) + + // If the destination is a symbolic link, we should evaluate it. + if err == nil && dstStat.Mode&os.ModeSymlink != 0 { + linkTarget := dstStat.LinkTarget + if !filepath.IsAbs(linkTarget) { + // Join with the parent directory. + dstParent, _ := archive.SplitPathDirEntry(dstPath) + linkTarget = filepath.Join(dstParent, linkTarget) + } + + dstInfo.Path = linkTarget + dstStat, err = cli.statContainerPath(dstContainer, linkTarget) + } + // Ignore any error and assume that the parent directory of the destination // path exists, in which case the copy may still succeed. If there is any // type of conflict (e.g., non-directory overwriting an existing directory @@ -242,15 +256,26 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (er dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir() } - var content io.Reader + var ( + content io.Reader + resolvedDstPath string + ) + if srcPath == "-" { // Use STDIN. content = os.Stdin + resolvedDstPath = dstInfo.Path if !dstInfo.IsDir { return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath)) } } else { - srcArchive, err := archive.TarResource(srcPath) + // Prepare source copy info. + srcInfo, err := archive.CopyInfoSourcePath(srcPath) + if err != nil { + return err + } + + srcArchive, err := archive.TarResource(srcInfo) if err != nil { return err } @@ -262,12 +287,6 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (er // it to the specified directory in the container we get the disired // copy behavior. - // Prepare source copy info. - srcInfo, err := archive.CopyInfoStatPath(srcPath, true) - if err != nil { - return err - } - // See comments in the implementation of `archive.PrepareArchiveCopy` // for exactly what goes into deciding how and whether the source // archive needs to be altered for the correct copy behavior when it is @@ -280,12 +299,12 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (er } defer preparedArchive.Close() - dstPath = dstDir + resolvedDstPath = dstDir content = preparedArchive } query := make(url.Values, 2) - query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API. + query.Set("path", filepath.ToSlash(resolvedDstPath)) // Normalize the paths used in the API. // Do not allow for an existing directory to be overwritten by a non-directory and vice versa. query.Set("noOverwriteDirNonDir", "true") diff --git a/api/types/types.go b/api/types/types.go index b02c88bb6..329ee96ce 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -130,14 +130,13 @@ type CopyConfig struct { // ContainerPathStat is used to encode the header from // GET /containers/{name:.*}/archive -// "name" is the file or directory name. -// "path" is the absolute path to the resource in the container. +// "name" is basename of the resource. type ContainerPathStat struct { - Name string `json:"name"` - Path string `json:"path"` - Size int64 `json:"size"` - Mode os.FileMode `json:"mode"` - Mtime time.Time `json:"mtime"` + Name string `json:"name"` + Size int64 `json:"size"` + Mode os.FileMode `json:"mode"` + Mtime time.Time `json:"mtime"` + LinkTarget string `json:"linkTarget"` } // GET "/containers/{name:.*}/top" diff --git a/daemon/archive.go b/daemon/archive.go index f6b569835..0d675a702 100644 --- a/daemon/archive.go +++ b/daemon/archive.go @@ -70,6 +70,66 @@ func (daemon *Daemon) ContainerExtractToDir(name, path string, noOverwriteDirNon return container.ExtractToDir(path, noOverwriteDirNonDir, content) } +// resolvePath resolves the given path in the container to a resource on the +// host. Returns a resolved path (absolute path to the resource on the host), +// the absolute path to the resource relative to the container's rootfs, and +// a error if the path points to outside the container's rootfs. +func (container *Container) resolvePath(path string) (resolvedPath, absPath string, err error) { + // Consider the given path as an absolute path in the container. + absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path) + + // Split the absPath into its Directory and Base components. We will + // resolve the dir in the scope of the container then append the base. + dirPath, basePath := filepath.Split(absPath) + + resolvedDirPath, err := container.GetResourcePath(dirPath) + if err != nil { + return "", "", err + } + + // resolvedDirPath will have been cleaned (no trailing path separators) so + // we can manually join it with the base path element. + resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath + + return resolvedPath, absPath, nil +} + +// statPath is the unexported version of StatPath. Locks and mounts should +// be aquired before calling this method and the given path should be fully +// resolved to a path on the host corresponding to the given absolute path +// inside the container. +func (container *Container) statPath(resolvedPath, absPath string) (stat *types.ContainerPathStat, err error) { + lstat, err := os.Lstat(resolvedPath) + if err != nil { + return nil, err + } + + var linkTarget string + if lstat.Mode()&os.ModeSymlink != 0 { + // Fully evaluate the symlink in the scope of the container rootfs. + hostPath, err := container.GetResourcePath(absPath) + if err != nil { + return nil, err + } + + linkTarget, err = filepath.Rel(container.basefs, hostPath) + if err != nil { + return nil, err + } + + // Make it an absolute path. + linkTarget = filepath.Join(string(filepath.Separator), linkTarget) + } + + return &types.ContainerPathStat{ + Name: filepath.Base(absPath), + Size: lstat.Size(), + Mode: lstat.Mode(), + Mtime: lstat.ModTime(), + LinkTarget: linkTarget, + }, nil +} + // StatPath stats the filesystem resource at the specified path in this // container. Returns stat info about the resource. func (container *Container) StatPath(path string) (stat *types.ContainerPathStat, err error) { @@ -87,39 +147,12 @@ func (container *Container) StatPath(path string) (stat *types.ContainerPathStat return nil, err } - // Consider the given path as an absolute path in the container. - absPath := path - if !filepath.IsAbs(absPath) { - absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path) - } - - resolvedPath, err := container.GetResourcePath(absPath) + resolvedPath, absPath, err := container.resolvePath(path) if err != nil { return nil, err } - // A trailing "." or separator has important meaning. For example, if - // `"foo"` is a symlink to some directory `"dir"`, then `os.Lstat("foo")` - // will stat the link itself, while `os.Lstat("foo/")` will stat the link - // target. If the basename of the path is ".", it means to archive the - // contents of the directory with "." as the first path component rather - // than the name of the directory. This would cause extraction of the - // archive to *not* make another directory, but instead use the current - // directory. - resolvedPath = archive.PreserveTrailingDotOrSeparator(resolvedPath, absPath) - - lstat, err := os.Lstat(resolvedPath) - if err != nil { - return nil, err - } - - return &types.ContainerPathStat{ - Name: lstat.Name(), - Path: absPath, - Size: lstat.Size(), - Mode: lstat.Mode(), - Mtime: lstat.ModTime(), - }, nil + return container.statPath(resolvedPath, absPath) } // ArchivePath creates an archive of the filesystem resource at the specified @@ -154,41 +187,25 @@ func (container *Container) ArchivePath(path string) (content io.ReadCloser, sta return nil, nil, err } - // Consider the given path as an absolute path in the container. - absPath := path - if !filepath.IsAbs(absPath) { - absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path) - } - - resolvedPath, err := container.GetResourcePath(absPath) + resolvedPath, absPath, err := container.resolvePath(path) if err != nil { return nil, nil, err } - // A trailing "." or separator has important meaning. For example, if - // `"foo"` is a symlink to some directory `"dir"`, then `os.Lstat("foo")` - // will stat the link itself, while `os.Lstat("foo/")` will stat the link - // target. If the basename of the path is ".", it means to archive the - // contents of the directory with "." as the first path component rather - // than the name of the directory. This would cause extraction of the - // archive to *not* make another directory, but instead use the current - // directory. - resolvedPath = archive.PreserveTrailingDotOrSeparator(resolvedPath, absPath) - - lstat, err := os.Lstat(resolvedPath) + stat, err = container.statPath(resolvedPath, absPath) if err != nil { return nil, nil, err } - stat = &types.ContainerPathStat{ - Name: lstat.Name(), - Path: absPath, - Size: lstat.Size(), - Mode: lstat.Mode(), - Mtime: lstat.ModTime(), - } - - data, err := archive.TarResource(resolvedPath) + // We need to rebase the archive entries if the last element of the + // resolved path was a symlink that was evaluated and is now different + // than the requested path. For example, if the given path was "/foo/bar/", + // but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want + // to ensure that the archive entries start with "bar" and not "baz". This + // also catches the case when the root directory of the container is + // requested: we want the archive entries to start with "/" and not the + // container ID. + data, err := archive.TarResourceRebase(resolvedPath, filepath.Base(absPath)) if err != nil { return nil, nil, err } @@ -227,27 +244,21 @@ func (container *Container) ExtractToDir(path string, noOverwriteDirNonDir bool, return err } - // Consider the given path as an absolute path in the container. - absPath := path - if !filepath.IsAbs(absPath) { - absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path) - } + // The destination path needs to be resolved to a host path, with all + // symbolic links followed in the scope of the container's rootfs. Note + // that we do not use `container.resolvePath(path)` here because we need + // to also evaluate the last path element if it is a symlink. This is so + // that you can extract an archive to a symlink that points to a directory. + // Consider the given path as an absolute path in the container. + absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path) + + // This will evaluate the last path element if it is a symlink. resolvedPath, err := container.GetResourcePath(absPath) if err != nil { return err } - // A trailing "." or separator has important meaning. For example, if - // `"foo"` is a symlink to some directory `"dir"`, then `os.Lstat("foo")` - // will stat the link itself, while `os.Lstat("foo/")` will stat the link - // target. If the basename of the path is ".", it means to archive the - // contents of the directory with "." as the first path component rather - // than the name of the directory. This would cause extraction of the - // archive to *not* make another directory, but instead use the current - // directory. - resolvedPath = archive.PreserveTrailingDotOrSeparator(resolvedPath, absPath) - stat, err := os.Lstat(resolvedPath) if err != nil { return err @@ -257,23 +268,23 @@ func (container *Container) ExtractToDir(path string, noOverwriteDirNonDir bool, return ErrExtractPointNotDirectory } + // Need to check if the path is in a volume. If it is, it cannot be in a + // read-only volume. If it is not in a volume, the container cannot be + // configured with a read-only rootfs. + + // Use the resolved path relative to the container rootfs as the new + // absPath. This way we fully follow any symlinks in a volume that may + // lead back outside the volume. baseRel, err := filepath.Rel(container.basefs, resolvedPath) if err != nil { return err } - absPath = filepath.Join("/", baseRel) + // Make it an absolute path. + absPath = filepath.Join(string(filepath.Separator), baseRel) - // Need to check if the path is in a volume. If it is, it cannot be in a - // read-only volume. If it is not in a volume, the container cannot be - // configured with a read-only rootfs. - var toVolume bool - for _, mnt := range container.MountPoints { - if toVolume = mnt.hasResource(absPath); toVolume { - if mnt.RW { - break - } - return ErrVolumeReadonly - } + toVolume, err := checkIfPathIsInAVolume(container, absPath) + if err != nil { + return err } if !toVolume && container.hostConfig.ReadonlyRootfs { @@ -295,3 +306,19 @@ func (container *Container) ExtractToDir(path string, noOverwriteDirNonDir bool, return nil } + +// checkIfPathIsInAVolume checks if the path is in a volume. If it is, it +// cannot be in a read-only volume. If it is not in a volume, the container +// cannot be configured with a read-only rootfs. +func checkIfPathIsInAVolume(container *Container, absPath string) (bool, error) { + var toVolume bool + for _, mnt := range container.MountPoints { + if toVolume = mnt.hasResource(absPath); toVolume { + if mnt.RW { + break + } + return false, ErrVolumeReadonly + } + } + return toVolume, nil +} diff --git a/docs/reference/api/docker_remote_api_v1.20.md b/docs/reference/api/docker_remote_api_v1.20.md index 49934708a..40c58af0e 100644 --- a/docs/reference/api/docker_remote_api_v1.20.md +++ b/docs/reference/api/docker_remote_api_v1.20.md @@ -1109,7 +1109,7 @@ Query Parameters: HTTP/1.1 200 OK Content-Type: application/x-tar - X-Docker-Container-Path-Stat: eyJuYW1lIjoicm9vdCIsInBhdGgiOiIvcm9vdCIsInNpemUiOjQwOTYsIm1vZGUiOjIxNDc0ODQwOTYsIm10aW1lIjoiMjAxNC0wMi0yN1QyMDo1MToyM1oifQ== + X-Docker-Container-Path-Stat: eyJuYW1lIjoicm9vdCIsInNpemUiOjQwOTYsIm1vZGUiOjIxNDc0ODQwOTYsIm10aW1lIjoiMjAxNC0wMi0yN1QyMDo1MToyM1oiLCJsaW5rVGFyZ2V0IjoiIn0= {{ TAR STREAM }} @@ -1120,10 +1120,10 @@ JSON object (whitespace added for readability): { "name": "root", - "path": "/root", "size": 4096, "mode": 2147484096, - "mtime": "2014-02-27T20:51:23Z" + "mtime": "2014-02-27T20:51:23Z", + "linkTarget": "" } A `HEAD` request can also be made to this endpoint if only this information is diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index c9d5d9645..d8dc44833 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -4,9 +4,11 @@ import ( "archive/tar" "bytes" "encoding/json" + "fmt" "io" "net/http" "net/http/httputil" + "net/url" "os" "strconv" "strings" @@ -1697,3 +1699,35 @@ func (s *DockerSuite) TestContainersApiCreateNoHostConfig118(c *check.C) { c.Assert(err, check.IsNil) c.Assert(status, check.Equals, http.StatusCreated) } + +// Ensure an error occurs when you have a container read-only rootfs but you +// extract an archive to a symlink in a writable volume which points to a +// directory outside of the volume. +func (s *DockerSuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *check.C) { + testRequires(c, SameHostDaemon) // Requires local volume mount bind. + + testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-") + defer os.RemoveAll(testVol) + + makeTestContentInDir(c, testVol) + + cID := makeTestContainer(c, testContainerOptions{ + readOnly: true, + volumes: defaultVolumes(testVol), // Our bind mount is at /vol2 + }) + defer deleteContainer(cID) + + // Attempt to extract to a symlink in the volume which points to a + // directory outside the volume. This should cause an error because the + // rootfs is read-only. + query := make(url.Values, 1) + query.Set("path", "/vol2/symlinkToAbsDir") + urlPath := fmt.Sprintf("/v1.20/containers/%s/archive?%s", cID, query.Encode()) + + statusCode, body, err := sockRequest("PUT", urlPath, nil) + c.Assert(err, check.IsNil) + + if !isCpCannotCopyReadOnly(fmt.Errorf(string(body))) { + c.Fatalf("expected ErrContainerRootfsReadonly error, but got %d: %s", statusCode, string(body)) + } +} diff --git a/integration-cli/docker_cli_cp_from_container_test.go b/integration-cli/docker_cli_cp_from_container_test.go index 14536ce85..945a34f4b 100644 --- a/integration-cli/docker_cli_cp_from_container_test.go +++ b/integration-cli/docker_cli_cp_from_container_test.go @@ -130,6 +130,114 @@ func (s *DockerSuite) TestCpFromErrDstNotDir(c *check.C) { } } +// Check that copying from a container to a local symlink copies to the symlink +// target and does not overwrite the local symlink itself. +func (s *DockerSuite) TestCpFromSymlinkDestination(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{addContent: true}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-err-dst-not-dir") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + // First, copy a file from the container to a symlink to a file. This + // should overwrite the symlink target contents with the source contents. + srcPath := containerCpPath(cID, "/file2") + dstPath := cpPath(tmpDir, "symlinkToFile1") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // The symlink should not have been modified. + if err := symlinkTargetEquals(c, dstPath, "file1"); err != nil { + c.Fatal(err) + } + + // The file should have the contents of "file2" now. + if err := fileContentEquals(c, cpPath(tmpDir, "file1"), "file2\n"); err != nil { + c.Fatal(err) + } + + // Next, copy a file from the container to a symlink to a directory. This + // should copy the file into the symlink target directory. + dstPath = cpPath(tmpDir, "symlinkToDir1") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // The symlink should not have been modified. + if err := symlinkTargetEquals(c, dstPath, "dir1"); err != nil { + c.Fatal(err) + } + + // The file should have the contents of "file2" now. + if err := fileContentEquals(c, cpPath(tmpDir, "file2"), "file2\n"); err != nil { + c.Fatal(err) + } + + // Next, copy a file from the container to a symlink to a file that does + // not exist (a broken symlink). This should create the target file with + // the contents of the source file. + dstPath = cpPath(tmpDir, "brokenSymlinkToFileX") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // The symlink should not have been modified. + if err := symlinkTargetEquals(c, dstPath, "fileX"); err != nil { + c.Fatal(err) + } + + // The file should have the contents of "file2" now. + if err := fileContentEquals(c, cpPath(tmpDir, "fileX"), "file2\n"); err != nil { + c.Fatal(err) + } + + // Next, copy a directory from the container to a symlink to a local + // directory. This should copy the directory into the symlink target + // directory and not modify the symlink. + srcPath = containerCpPath(cID, "/dir2") + dstPath = cpPath(tmpDir, "symlinkToDir1") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // The symlink should not have been modified. + if err := symlinkTargetEquals(c, dstPath, "dir1"); err != nil { + c.Fatal(err) + } + + // The directory should now contain a copy of "dir2". + if err := fileContentEquals(c, cpPath(tmpDir, "dir1/dir2/file2-1"), "file2-1\n"); err != nil { + c.Fatal(err) + } + + // Next, copy a directory from the container to a symlink to a local + // directory that does not exist (a broken symlink). This should create + // the target as a directory with the contents of the source directory. It + // should not modify the symlink. + dstPath = cpPath(tmpDir, "brokenSymlinkToDirX") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // The symlink should not have been modified. + if err := symlinkTargetEquals(c, dstPath, "dirX"); err != nil { + c.Fatal(err) + } + + // The "dirX" directory should now be a copy of "dir2". + if err := fileContentEquals(c, cpPath(tmpDir, "dirX/file2-1"), "file2-1\n"); err != nil { + c.Fatal(err) + } +} + // Possibilities are reduced to the remaining 10 cases: // // case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index 03c0a4a63..64ae0b5d8 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -250,29 +250,185 @@ func (s *DockerSuite) TestCpAbsoluteSymlink(c *check.C) { c.Fatal(err) } - tmpname := filepath.Join(tmpdir, cpTestName) + tmpname := filepath.Join(tmpdir, "container_path") defer os.RemoveAll(tmpdir) path := path.Join("/", "container_path") dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) - file, _ := os.Open(tmpname) - defer file.Close() - - test, err := ioutil.ReadAll(file) + // We should have copied a symlink *NOT* the file itself! + linkTarget, err := os.Readlink(tmpname) if err != nil { c.Fatal(err) } - if string(test) == cpHostContents { - c.Errorf("output matched host file -- absolute symlink can escape container rootfs") + if linkTarget != filepath.FromSlash(cpFullPath) { + c.Errorf("symlink target was %q, but expected: %q", linkTarget, cpFullPath) + } +} + +// Check that symlinks to a directory behave as expected when copying one from +// a container. +func (s *DockerSuite) TestCpFromSymlinkToDirectory(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPathParent+" /dir_link") + if exitCode != 0 { + c.Fatal("failed to create a container", out) } - if string(test) != cpContainerContents { - c.Errorf("output doesn't match the input for absolute symlink") + cleanedContainerID := strings.TrimSpace(out) + + out, _ = dockerCmd(c, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + c.Fatal("failed to set up container", out) } + testDir, err := ioutil.TempDir("", "test-cp-from-symlink-to-dir-") + if err != nil { + c.Fatal(err) + } + defer os.RemoveAll(testDir) + + // This copy command should copy the symlink, not the target, into the + // temporary directory. + dockerCmd(c, "cp", cleanedContainerID+":"+"/dir_link", testDir) + + expectedPath := filepath.Join(testDir, "dir_link") + linkTarget, err := os.Readlink(expectedPath) + if err != nil { + c.Fatalf("unable to read symlink at %q: %v", expectedPath, err) + } + + if linkTarget != filepath.FromSlash(cpTestPathParent) { + c.Errorf("symlink target was %q, but expected: %q", linkTarget, cpTestPathParent) + } + + os.Remove(expectedPath) + + // This copy command should resolve the symlink (note the trailing + // seperator), copying the target into the temporary directory. + dockerCmd(c, "cp", cleanedContainerID+":"+"/dir_link/", testDir) + + // It *should not* have copied the directory using the target's name, but + // used the given name instead. + unexpectedPath := filepath.Join(testDir, cpTestPathParent) + if stat, err := os.Lstat(unexpectedPath); err == nil { + c.Fatalf("target name was copied: %q - %q", stat.Mode(), stat.Name()) + } + + // It *should* have copied the directory using the asked name "dir_link". + stat, err := os.Lstat(expectedPath) + if err != nil { + c.Fatalf("unable to stat resource at %q: %v", expectedPath, err) + } + + if !stat.IsDir() { + c.Errorf("should have copied a directory but got %q instead", stat.Mode()) + } +} + +// Check that symlinks to a directory behave as expected when copying one to a +// container. +func (s *DockerSuite) TestCpToSymlinkToDirectory(c *check.C) { + testRequires(c, SameHostDaemon) // Requires local volume mount bind. + + testVol, err := ioutil.TempDir("", "test-cp-to-symlink-to-dir-") + if err != nil { + c.Fatal(err) + } + defer os.RemoveAll(testVol) + + // Create a test container with a local volume. We will test by copying + // to the volume path in the container which we can then verify locally. + out, exitCode := dockerCmd(c, "create", "-v", testVol+":/testVol", "busybox") + if exitCode != 0 { + c.Fatal("failed to create a container", out) + } + + cleanedContainerID := strings.TrimSpace(out) + + // Create a temp directory to hold a test file nested in a direcotry. + testDir, err := ioutil.TempDir("", "test-cp-to-symlink-to-dir-") + if err != nil { + c.Fatal(err) + } + defer os.RemoveAll(testDir) + + // This file will be at "/testDir/some/path/test" and will be copied into + // the test volume later. + hostTestFilename := filepath.Join(testDir, cpFullPath) + if err := os.MkdirAll(filepath.Dir(hostTestFilename), os.FileMode(0700)); err != nil { + c.Fatal(err) + } + if err := ioutil.WriteFile(hostTestFilename, []byte(cpHostContents), os.FileMode(0600)); err != nil { + c.Fatal(err) + } + + // Now create another temp directory to hold a symlink to the + // "/testDir/some" directory. + linkDir, err := ioutil.TempDir("", "test-cp-to-symlink-to-dir-") + if err != nil { + c.Fatal(err) + } + defer os.RemoveAll(linkDir) + + // Then symlink "/linkDir/dir_link" to "/testdir/some". + linkTarget := filepath.Join(testDir, cpTestPathParent) + localLink := filepath.Join(linkDir, "dir_link") + if err := os.Symlink(linkTarget, localLink); err != nil { + c.Fatal(err) + } + + // Now copy that symlink into the test volume in the container. + dockerCmd(c, "cp", localLink, cleanedContainerID+":/testVol") + + // This copy command should have copied the symlink *not* the target. + expectedPath := filepath.Join(testVol, "dir_link") + actualLinkTarget, err := os.Readlink(expectedPath) + if err != nil { + c.Fatalf("unable to read symlink at %q: %v", expectedPath, err) + } + + if actualLinkTarget != linkTarget { + c.Errorf("symlink target was %q, but expected: %q", actualLinkTarget, linkTarget) + } + + // Good, now remove that copied link for the next test. + os.Remove(expectedPath) + + // This copy command should resolve the symlink (note the trailing + // seperator), copying the target into the test volume directory in the + // container. + dockerCmd(c, "cp", localLink+"/", cleanedContainerID+":/testVol") + + // It *should not* have copied the directory using the target's name, but + // used the given name instead. + unexpectedPath := filepath.Join(testVol, cpTestPathParent) + if stat, err := os.Lstat(unexpectedPath); err == nil { + c.Fatalf("target name was copied: %q - %q", stat.Mode(), stat.Name()) + } + + // It *should* have copied the directory using the asked name "dir_link". + stat, err := os.Lstat(expectedPath) + if err != nil { + c.Fatalf("unable to stat resource at %q: %v", expectedPath, err) + } + + if !stat.IsDir() { + c.Errorf("should have copied a directory but got %q instead", stat.Mode()) + } + + // And this directory should contain the file copied from the host at the + // expected location: "/testVol/dir_link/path/test" + expectedFilepath := filepath.Join(testVol, "dir_link/path/test") + fileContents, err := ioutil.ReadFile(expectedFilepath) + if err != nil { + c.Fatal(err) + } + + if string(fileContents) != cpHostContents { + c.Fatalf("file contains %q but expected %q", string(fileContents), cpHostContents) + } } // Test for #5619 diff --git a/integration-cli/docker_cli_cp_to_container_test.go b/integration-cli/docker_cli_cp_to_container_test.go index 4179553d1..341121d2c 100644 --- a/integration-cli/docker_cli_cp_to_container_test.go +++ b/integration-cli/docker_cli_cp_to_container_test.go @@ -146,6 +146,118 @@ func (s *DockerSuite) TestCpToErrDstNotDir(c *check.C) { } } +// Check that copying from a local path to a symlink in a container copies to +// the symlink target and does not overwrite the container symlink itself. +func (s *DockerSuite) TestCpToSymlinkDestination(c *check.C) { + testRequires(c, SameHostDaemon) // Requires local volume mount bind. + + testVol := getTestDir(c, "test-cp-to-symlink-destination-") + defer os.RemoveAll(testVol) + + makeTestContentInDir(c, testVol) + + cID := makeTestContainer(c, testContainerOptions{ + volumes: defaultVolumes(testVol), // Our bind mount is at /vol2 + }) + defer deleteContainer(cID) + + // First, copy a local file to a symlink to a file in the container. This + // should overwrite the symlink target contents with the source contents. + srcPath := cpPath(testVol, "file2") + dstPath := containerCpPath(cID, "/vol2/symlinkToFile1") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // The symlink should not have been modified. + if err := symlinkTargetEquals(c, cpPath(testVol, "symlinkToFile1"), "file1"); err != nil { + c.Fatal(err) + } + + // The file should have the contents of "file2" now. + if err := fileContentEquals(c, cpPath(testVol, "file1"), "file2\n"); err != nil { + c.Fatal(err) + } + + // Next, copy a local file to a symlink to a directory in the container. + // This should copy the file into the symlink target directory. + dstPath = containerCpPath(cID, "/vol2/symlinkToDir1") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // The symlink should not have been modified. + if err := symlinkTargetEquals(c, cpPath(testVol, "symlinkToDir1"), "dir1"); err != nil { + c.Fatal(err) + } + + // The file should have the contents of "file2" now. + if err := fileContentEquals(c, cpPath(testVol, "file2"), "file2\n"); err != nil { + c.Fatal(err) + } + + // Next, copy a file to a symlink to a file that does not exist (a broken + // symlink) in the container. This should create the target file with the + // contents of the source file. + dstPath = containerCpPath(cID, "/vol2/brokenSymlinkToFileX") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // The symlink should not have been modified. + if err := symlinkTargetEquals(c, cpPath(testVol, "brokenSymlinkToFileX"), "fileX"); err != nil { + c.Fatal(err) + } + + // The file should have the contents of "file2" now. + if err := fileContentEquals(c, cpPath(testVol, "fileX"), "file2\n"); err != nil { + c.Fatal(err) + } + + // Next, copy a local directory to a symlink to a directory in the + // container. This should copy the directory into the symlink target + // directory and not modify the symlink. + srcPath = cpPath(testVol, "/dir2") + dstPath = containerCpPath(cID, "/vol2/symlinkToDir1") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // The symlink should not have been modified. + if err := symlinkTargetEquals(c, cpPath(testVol, "symlinkToDir1"), "dir1"); err != nil { + c.Fatal(err) + } + + // The directory should now contain a copy of "dir2". + if err := fileContentEquals(c, cpPath(testVol, "dir1/dir2/file2-1"), "file2-1\n"); err != nil { + c.Fatal(err) + } + + // Next, copy a local directory to a symlink to a local directory that does + // not exist (a broken symlink) in the container. This should create the + // target as a directory with the contents of the source directory. It + // should not modify the symlink. + dstPath = containerCpPath(cID, "/vol2/brokenSymlinkToDirX") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // The symlink should not have been modified. + if err := symlinkTargetEquals(c, cpPath(testVol, "brokenSymlinkToDirX"), "dirX"); err != nil { + c.Fatal(err) + } + + // The "dirX" directory should now be a copy of "dir2". + if err := fileContentEquals(c, cpPath(testVol, "dirX/file2-1"), "file2-1\n"); err != nil { + c.Fatal(err) + } +} + // Possibilities are reduced to the remaining 10 cases: // // case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action diff --git a/integration-cli/docker_cli_cp_utils.go b/integration-cli/docker_cli_cp_utils.go index c04a50f6f..c26ebfd7e 100644 --- a/integration-cli/docker_cli_cp_utils.go +++ b/integration-cli/docker_cli_cp_utils.go @@ -74,8 +74,11 @@ var defaultFileData = []fileData{ {ftRegular, "dir4/file3-1", "file4-1"}, {ftRegular, "dir4/file3-2", "file4-2"}, {ftDir, "dir5", ""}, - {ftSymlink, "symlink1", "target1"}, - {ftSymlink, "symlink2", "target2"}, + {ftSymlink, "symlinkToFile1", "file1"}, + {ftSymlink, "symlinkToDir1", "dir1"}, + {ftSymlink, "brokenSymlinkToFileX", "fileX"}, + {ftSymlink, "brokenSymlinkToDirX", "dirX"}, + {ftSymlink, "symlinkToAbsDir", "/root"}, } func defaultMkContentCommand() string { @@ -268,6 +271,21 @@ func fileContentEquals(c *check.C, filename, contents string) (err error) { return } +func symlinkTargetEquals(c *check.C, symlink, expectedTarget string) (err error) { + c.Logf("checking that the symlink %q points to %q\n", symlink, expectedTarget) + + actualTarget, err := os.Readlink(symlink) + if err != nil { + return err + } + + if actualTarget != expectedTarget { + return fmt.Errorf("symlink target points to %q not %q", actualTarget, expectedTarget) + } + + return nil +} + func containerStartOutputEquals(c *check.C, cID, contents string) (err error) { c.Logf("checking that container %q start output contains %q\n", cID, contents) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 11a707d20..3f3c819ac 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -37,11 +37,13 @@ type ( Compression Compression NoLchown bool ChownOpts *TarChownOptions - Name string IncludeSourceDir bool // When unpacking, specifies whether overwriting a directory with a // non-directory is allowed and vice versa. NoOverwriteDirNonDir bool + // For each include when creating an archive, the included name will be + // replaced with the matching name from this map. + RebaseNames map[string]string } // Archiver allows the reuse of most utility functions of this package @@ -454,8 +456,9 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) seen := make(map[string]bool) - var renamedRelFilePath string // For when tar.Options.Name is set for _, include := range options.IncludeFiles { + rebaseName := options.RebaseNames[include] + // We can't use filepath.Join(srcPath, include) because this will // clean away a trailing "." or "/" which may be important. walkRoot := strings.Join([]string{srcPath, include}, string(filepath.Separator)) @@ -503,14 +506,17 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) } seen[relFilePath] = true - // TODO Windows: Verify if this needs to be os.Pathseparator - // Rename the base resource - if options.Name != "" && filePath == srcPath+"/"+filepath.Base(relFilePath) { - renamedRelFilePath = relFilePath - } - // Set this to make sure the items underneath also get renamed - if options.Name != "" { - relFilePath = strings.Replace(relFilePath, renamedRelFilePath, options.Name, 1) + // Rename the base resource. + if rebaseName != "" { + var replacement string + if rebaseName != string(filepath.Separator) { + // Special case the root directory to replace with an + // empty string instead so that we don't end up with + // double slashes in the paths. + replacement = rebaseName + } + + relFilePath = strings.Replace(relFilePath, include, replacement, 1) } if err := ta.addTarFile(filePath, relFilePath); err != nil { diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index b93c76cda..b9bfc2390 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -695,7 +695,7 @@ func TestTarWithOptions(t *testing.T) { {&TarOptions{ExcludePatterns: []string{"2"}}, 1}, {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2}, {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2}, - {&TarOptions{Name: "test", IncludeFiles: []string{"1"}}, 4}, + {&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4}, } for _, testCase := range cases { changes, err := tarUntar(t, origin, testCase.opts) diff --git a/pkg/archive/copy.go b/pkg/archive/copy.go index fee4a022b..39bb4fd7e 100644 --- a/pkg/archive/copy.go +++ b/pkg/archive/copy.go @@ -6,7 +6,6 @@ import ( "io" "io/ioutil" "os" - "path" "path/filepath" "strings" @@ -64,34 +63,33 @@ func SpecifiesCurrentDir(path string) bool { return filepath.Base(path) == "." } -// SplitPathDirEntry splits the given path between its -// parent directory and its basename in that directory. -func SplitPathDirEntry(localizedPath string) (dir, base string) { - normalizedPath := filepath.ToSlash(localizedPath) - vol := filepath.VolumeName(normalizedPath) - normalizedPath = normalizedPath[len(vol):] +// SplitPathDirEntry splits the given path between its directory name and its +// basename by first cleaning the path but preserves a trailing "." if the +// original path specified the current directory. +func SplitPathDirEntry(path string) (dir, base string) { + cleanedPath := filepath.Clean(path) - if normalizedPath == "/" { - // Specifies the root path. - return filepath.FromSlash(vol + normalizedPath), "." + if SpecifiesCurrentDir(path) { + cleanedPath += string(filepath.Separator) + "." } - trimmedPath := vol + strings.TrimRight(normalizedPath, "/") - - dir = filepath.FromSlash(path.Dir(trimmedPath)) - base = filepath.FromSlash(path.Base(trimmedPath)) - - return dir, base + return filepath.Dir(cleanedPath), filepath.Base(cleanedPath) } -// TarResource archives the resource at the given sourcePath into a Tar +// TarResource archives the resource described by the given CopyInfo to a Tar // archive. A non-nil error is returned if sourcePath does not exist or is // asserted to be a directory but exists as another type of file. // // This function acts as a convenient wrapper around TarWithOptions, which // requires a directory as the source path. TarResource accepts either a // directory or a file path and correctly sets the Tar options. -func TarResource(sourcePath string) (content Archive, err error) { +func TarResource(sourceInfo CopyInfo) (content Archive, err error) { + return TarResourceRebase(sourceInfo.Path, sourceInfo.RebaseName) +} + +// TarResourceRebase is like TarResource but renames the first path element of +// items in the resulting tar archive to match the given rebaseName if not "". +func TarResourceRebase(sourcePath, rebaseName string) (content Archive, err error) { if _, err = os.Lstat(sourcePath); err != nil { // Catches the case where the source does not exist or is not a // directory if asserted to be a directory, as this also causes an @@ -99,22 +97,6 @@ func TarResource(sourcePath string) (content Archive, err error) { return } - if len(sourcePath) > 1 && HasTrailingPathSeparator(sourcePath) { - // In the case where the source path is a symbolic link AND it ends - // with a path separator, we will want to evaluate the symbolic link. - trimmedPath := sourcePath[:len(sourcePath)-1] - stat, err := os.Lstat(trimmedPath) - if err != nil { - return nil, err - } - - if stat.Mode()&os.ModeSymlink != 0 { - if sourcePath, err = filepath.EvalSymlinks(trimmedPath); err != nil { - return nil, err - } - } - } - // Separate the source path between it's directory and // the entry in that directory which we are archiving. sourceDir, sourceBase := SplitPathDirEntry(sourcePath) @@ -127,32 +109,137 @@ func TarResource(sourcePath string) (content Archive, err error) { Compression: Uncompressed, IncludeFiles: filter, IncludeSourceDir: true, + RebaseNames: map[string]string{ + sourceBase: rebaseName, + }, }) } // CopyInfo holds basic info about the source // or destination path of a copy operation. type CopyInfo struct { - Path string - Exists bool - IsDir bool + Path string + Exists bool + IsDir bool + RebaseName string } -// CopyInfoStatPath stats the given path to create a CopyInfo -// struct representing that resource. If mustExist is true, then -// it is an error if there is no file or directory at the given path. -func CopyInfoStatPath(path string, mustExist bool) (CopyInfo, error) { - pathInfo := CopyInfo{Path: path} +// CopyInfoSourcePath stats the given path to create a CopyInfo +// struct representing that resource for the source of an archive copy +// operation. The given path should be an absolute local path. A source path +// has all symlinks evaluated that appear before the last path separator ("/" +// on Unix). As it is to be a copy source, the path must exist. +func CopyInfoSourcePath(path string) (CopyInfo, error) { + // Split the given path into its Directory and Base components. We will + // evaluate symlinks in the directory component then append the base. + dirPath, basePath := filepath.Split(path) - fileInfo, err := os.Lstat(path) - - if err == nil { - pathInfo.Exists, pathInfo.IsDir = true, fileInfo.IsDir() - } else if os.IsNotExist(err) && !mustExist { - err = nil + resolvedDirPath, err := filepath.EvalSymlinks(dirPath) + if err != nil { + return CopyInfo{}, err } - return pathInfo, err + // resolvedDirPath will have been cleaned (no trailing path separators) so + // we can manually join it with the base path element. + resolvedPath := resolvedDirPath + string(filepath.Separator) + basePath + + var rebaseName string + if HasTrailingPathSeparator(path) && filepath.Base(path) != filepath.Base(resolvedPath) { + // In the case where the path had a trailing separator and a symlink + // evaluation has changed the last path component, we will need to + // rebase the name in the archive that is being copied to match the + // originally requested name. + rebaseName = filepath.Base(path) + } + + stat, err := os.Lstat(resolvedPath) + if err != nil { + return CopyInfo{}, err + } + + return CopyInfo{ + Path: resolvedPath, + Exists: true, + IsDir: stat.IsDir(), + RebaseName: rebaseName, + }, nil +} + +// CopyInfoDestinationPath stats the given path to create a CopyInfo +// struct representing that resource for the destination of an archive copy +// operation. The given path should be an absolute local path. +func CopyInfoDestinationPath(path string) (info CopyInfo, err error) { + maxSymlinkIter := 10 // filepath.EvalSymlinks uses 255, but 10 already seems like a lot. + originalPath := path + + stat, err := os.Lstat(path) + + if err == nil && stat.Mode()&os.ModeSymlink == 0 { + // The path exists and is not a symlink. + return CopyInfo{ + Path: path, + Exists: true, + IsDir: stat.IsDir(), + }, nil + } + + // While the path is a symlink. + for n := 0; err == nil && stat.Mode()&os.ModeSymlink != 0; n++ { + if n > maxSymlinkIter { + // Don't follow symlinks more than this arbitrary number of times. + return CopyInfo{}, errors.New("too many symlinks in " + originalPath) + } + + // The path is a symbolic link. We need to evaluate it so that the + // destination of the copy operation is the link target and not the + // link itself. This is notably different than CopyInfoSourcePath which + // only evaluates symlinks before the last appearing path separator. + // Also note that it is okay if the last path element is a broken + // symlink as the copy operation should create the target. + var linkTarget string + + linkTarget, err = os.Readlink(path) + if err != nil { + return CopyInfo{}, err + } + + if !filepath.IsAbs(linkTarget) { + // Join with the parent directory. + dstParent, _ := SplitPathDirEntry(path) + linkTarget = filepath.Join(dstParent, linkTarget) + } + + path = linkTarget + stat, err = os.Lstat(path) + } + + if err != nil { + // It's okay if the destination path doesn't exist. We can still + // continue the copy operation if the parent directory exists. + if !os.IsNotExist(err) { + return CopyInfo{}, err + } + + // Ensure destination parent dir exists. + dstParent, _ := SplitPathDirEntry(path) + + parentDirStat, err := os.Lstat(dstParent) + if err != nil { + return CopyInfo{}, err + } + if !parentDirStat.IsDir() { + return CopyInfo{}, ErrNotDirectory + } + + return CopyInfo{Path: path}, nil + } + + // The path exists after resolving symlinks. + return CopyInfo{ + Path: path, + Exists: true, + IsDir: stat.IsDir(), + }, nil } // PrepareArchiveCopy prepares the given srcContent archive, which should @@ -210,6 +297,13 @@ func PrepareArchiveCopy(srcContent ArchiveReader, srcInfo, dstInfo CopyInfo) (ds // rebaseArchiveEntries rewrites the given srcContent archive replacing // an occurance of oldBase with newBase at the beginning of entry names. func rebaseArchiveEntries(srcContent ArchiveReader, oldBase, newBase string) Archive { + if oldBase == "/" { + // If oldBase specifies the root directory, use an empty string as + // oldBase instead so that newBase doesn't replace the path separator + // that all paths will start with. + oldBase = "" + } + rebased, w := io.Pipe() go func() { @@ -259,11 +353,11 @@ func CopyResource(srcPath, dstPath string) error { srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath) dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath) - if srcInfo, err = CopyInfoStatPath(srcPath, true); err != nil { + if srcInfo, err = CopyInfoSourcePath(srcPath); err != nil { return err } - content, err := TarResource(srcPath) + content, err := TarResource(srcInfo) if err != nil { return err } @@ -275,24 +369,13 @@ func CopyResource(srcPath, dstPath string) error { // CopyTo handles extracting the given content whose // entries should be sourced from srcInfo to dstPath. func CopyTo(content ArchiveReader, srcInfo CopyInfo, dstPath string) error { - dstInfo, err := CopyInfoStatPath(dstPath, false) + // The destination path need not exist, but CopyInfoDestinationPath will + // ensure that at least the parent directory exists. + dstInfo, err := CopyInfoDestinationPath(dstPath) if err != nil { return err } - if !dstInfo.Exists { - // Ensure destination parent dir exists. - dstParent, _ := SplitPathDirEntry(dstPath) - - dstStat, err := os.Lstat(dstParent) - if err != nil { - return err - } - if !dstStat.IsDir() { - return ErrNotDirectory - } - } - dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo) if err != nil { return err diff --git a/pkg/archive/copy_test.go b/pkg/archive/copy_test.go index d0cfa18bd..8acf1ecfd 100644 --- a/pkg/archive/copy_test.go +++ b/pkg/archive/copy_test.go @@ -138,13 +138,7 @@ func TestCopyErrSrcNotExists(t *testing.T) { tmpDirA, tmpDirB := getTestTempDirs(t) defer removeAllPaths(tmpDirA, tmpDirB) - content, err := TarResource(filepath.Join(tmpDirA, "file1")) - if err == nil { - content.Close() - t.Fatal("expected IsNotExist error, but got nil instead") - } - - if !os.IsNotExist(err) { + if _, err := CopyInfoSourcePath(filepath.Join(tmpDirA, "file1")); !os.IsNotExist(err) { t.Fatalf("expected IsNotExist error, but got %T: %s", err, err) } } @@ -158,13 +152,7 @@ func TestCopyErrSrcNotDir(t *testing.T) { // Load A with some sample files and directories. createSampleDir(t, tmpDirA) - content, err := TarResource(joinTrailingSep(tmpDirA, "file1")) - if err == nil { - content.Close() - t.Fatal("expected IsNotDir error, but got nil instead") - } - - if !isNotDir(err) { + if _, err := CopyInfoSourcePath(joinTrailingSep(tmpDirA, "file1")); !isNotDir(err) { t.Fatalf("expected IsNotDir error, but got %T: %s", err, err) } } @@ -181,7 +169,7 @@ func TestCopyErrDstParentNotExists(t *testing.T) { srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false} // Try with a file source. - content, err := TarResource(srcInfo.Path) + content, err := TarResource(srcInfo) if err != nil { t.Fatalf("unexpected error %T: %s", err, err) } @@ -199,7 +187,7 @@ func TestCopyErrDstParentNotExists(t *testing.T) { // Try with a directory source. srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true} - content, err = TarResource(srcInfo.Path) + content, err = TarResource(srcInfo) if err != nil { t.Fatalf("unexpected error %T: %s", err, err) } @@ -228,7 +216,7 @@ func TestCopyErrDstNotDir(t *testing.T) { // Try with a file source. srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false} - content, err := TarResource(srcInfo.Path) + content, err := TarResource(srcInfo) if err != nil { t.Fatalf("unexpected error %T: %s", err, err) } @@ -245,7 +233,7 @@ func TestCopyErrDstNotDir(t *testing.T) { // Try with a directory source. srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true} - content, err = TarResource(srcInfo.Path) + content, err = TarResource(srcInfo) if err != nil { t.Fatalf("unexpected error %T: %s", err, err) } From b0ac5df3676a1d93c02191478669f569fe5c1577 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Wed, 29 Jul 2015 09:43:06 -0400 Subject: [PATCH 26/79] Labels on network content need to be shared if shared network namespace If I run two containers with the same network they share the same /etc/resolv.conf. The current code changes the labels of the /etc/resolv.conf currently to the private label which causes it to be unusable in the first container. This patch changes the labels to a shared label if more then one container will use the content. Docker-DCO-1.1-Signed-off-by: Dan Walsh dwalsh@redhat.com (github: rhatdan) Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) (cherry picked from commit 90b8cebda65b9e45c75c44010833e181167dd4f6) --- daemon/container.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 51a6bfd2c..49c1f41e3 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1088,8 +1088,12 @@ func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) func (container *Container) networkMounts() []execdriver.Mount { var mounts []execdriver.Mount + mode := "Z" + if container.hostConfig.NetworkMode.IsContainer() { + mode = "z" + } if container.ResolvConfPath != "" { - label.SetFileLabel(container.ResolvConfPath, container.MountLabel) + label.Relabel(container.ResolvConfPath, container.MountLabel, mode) mounts = append(mounts, execdriver.Mount{ Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", @@ -1098,7 +1102,7 @@ func (container *Container) networkMounts() []execdriver.Mount { }) } if container.HostnamePath != "" { - label.SetFileLabel(container.HostnamePath, container.MountLabel) + label.Relabel(container.HostnamePath, container.MountLabel, mode) mounts = append(mounts, execdriver.Mount{ Source: container.HostnamePath, Destination: "/etc/hostname", @@ -1107,7 +1111,7 @@ func (container *Container) networkMounts() []execdriver.Mount { }) } if container.HostsPath != "" { - label.SetFileLabel(container.HostsPath, container.MountLabel) + label.Relabel(container.HostsPath, container.MountLabel, mode) mounts = append(mounts, execdriver.Mount{ Source: container.HostsPath, Destination: "/etc/hosts", From 117860577cc048cd7f9fe29e8937d182dfa58b11 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 28 Jul 2015 16:37:52 -0700 Subject: [PATCH 27/79] Fix a couple broken links Signed-off-by: Jay Kamat (cherry picked from commit 4ca0aad85557ae41c8f4eeb978287cd8657a50b6) --- README.md | 2 +- experimental/README.md | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 18a396c55..86f45a3d8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ databases, and backend services without depending on a particular stack or provider. Docker began as an open-source implementation of the deployment engine which -powers [dotCloud](https://dotcloud.com), a popular Platform-as-a-Service. +powers [dotCloud](https://www.dotcloud.com), a popular Platform-as-a-Service. It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands of applications and databases. diff --git a/experimental/README.md b/experimental/README.md index 05ef03edf..a067e839a 100644 --- a/experimental/README.md +++ b/experimental/README.md @@ -61,8 +61,6 @@ After downloading the appropriate binary, you can follow the instructions ## Current experimental features -* [Support for Docker plugins](plugins.md) -* [Volume plugins](plugins_volume.md) * [Network plugins](plugins_network.md) * [Native Multi-host networking](networking.md) * [Compose, Swarm and networking integration](compose_swarm_networking.md) From 33cdc7f2c4e8584ea6a292bfecb5dc474f25154a Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Thu, 30 Jul 2015 19:03:38 -0400 Subject: [PATCH 28/79] registry: allow fallback on unknown errors This patch fixes a bug where a user specifies a v1 mirror for --registry-mirror and pull an image from the Hub. It used to not fallback because of an unexpected error returned when trying to JSON marshal nginx output. We now ensure that any unexpected error falls back to the next endpoint in the list. Signed-off-by: Tibor Vass (cherry picked from commit a21ba12f4e7f11c17e6d665716f3060b9923b11c) --- registry/registry.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index 74f731bdc..9fb71d175 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -17,6 +17,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/registry/api/errcode" "github.com/docker/distribution/registry/api/v2" + "github.com/docker/distribution/registry/client" "github.com/docker/distribution/registry/client/transport" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/pkg/parsers/kernel" @@ -211,8 +212,14 @@ func ContinueOnError(err error) bool { return ContinueOnError(v.Err) case errcode.Error: return shouldV2Fallback(v) + case *client.UnexpectedHTTPResponseError: + return true } - return false + // let's be nice and fallback if the error is a completely + // unexpected one. + // If new errors have to be handled in some way, please + // add them to the switch above. + return true } // NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the From a819a60a94f48d3856f0d00b0eac96219f588f66 Mon Sep 17 00:00:00 2001 From: Avi Miller Date: Wed, 29 Jul 2015 07:41:14 +1000 Subject: [PATCH 29/79] Fix for #14924. Seperates lsb_dist detection from dist_version detection so that the latter can be distro specific. Signed-off-by: Avi Miller (cherry picked from commit 5c6446f335a9f9010cabe93104f6feced0166dd8) --- hack/install.sh | 80 +++++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/hack/install.sh b/hack/install.sh index 902a07c33..b3b752346 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -132,54 +132,76 @@ do_install() { dist_version='' if command_exists lsb_release; then lsb_dist="$(lsb_release -si)" - dist_version="$(lsb_release --codename | cut -f2)" fi if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")" - dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")" - if [ -z "$dist_version" ]; then - dist_version="$(. /etc/lsb-release && echo "$DISTRIB_RELEASE")" - case "$lsb_dist" in - "Debian") - dist_version="$(get_debian_dist_version "$dist_version")" - ;; - "Ubuntu") - case "$dist_version" in - "15.04") - dist_version="vivid" - ;; - "14.04") - dist_version="trusty" - ;; - "12.04") - dist_version="precise" - ;; - esac - ;; - esac - fi fi if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then lsb_dist='debian' - dist_version="$(cat /etc/debian_version | sed 's/\/.*//' | sed 's/\..*//')" - dist_version="$(get_debian_dist_version "$dist_version")" fi if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then lsb_dist='fedora' - dist_version="$(rpm -q --whatprovides redhat-release --queryformat "%{VERSION}\n")" + fi + if [ -z "$lsb_dist" ] && [ -r /etc/oracle-release ]; then + lsb_dist='oracleserver' fi if [ -z "$lsb_dist" ]; then if [ -r /etc/centos-release ] || [ -r /etc/redhat-release ]; then lsb_dist='centos' - dist_version="$(rpm -q --whatprovides redhat-release --queryformat "%{VERSION}\n")" fi fi if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then lsb_dist="$(. /etc/os-release && echo "$ID")" - dist_version="$(. /etc/os-release && echo "$VERSION_ID")" fi lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" + + case "$lsb_dist" in + + ubuntu) + if command_exists lsb_release; then + dist_version="$(lsb_release --codename | cut -f2)" + fi + if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then + dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")" + fi + ;; + + debian) + dist_version="$(cat /etc/debian_version | sed 's/\/.*//' | sed 's/\..*//')" + case "$dist_version" in + 8) + dist_version="jessie" + ;; + 7) + dist_version="wheezy" + ;; + esac + ;; + + oracleserver) + # need to switch lsb_dist to match yum repo URL + lsb_dist="oraclelinux" + dist_version="$(rpm -q --whatprovides redhat-release --queryformat "%{VERSION}\n" | sed 's/\/.*//' | sed 's/\..*//')" + ;; + + fedora|centos) + dist_version="$(rpm -q --whatprovides redhat-release --queryformat "%{VERSION}\n" | sed 's/\/.*//' | sed 's/\..*//')" + ;; + + *) + if command_exists lsb_release; then + dist_version="$(lsb_release --codename | cut -f2)" + fi + if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then + dist_version="$(. /etc/os-release && echo "$VERSION_ID")" + fi + ;; + + + esac + + case "$lsb_dist" in amzn) ( @@ -263,7 +285,7 @@ do_install() { exit 0 ;; - fedora|centos) + fedora|centos|oraclelinux) cat >/etc/yum.repos.d/docker-${repo}.repo <<-EOF [docker-${repo}-repo] name=Docker ${repo} Repository From b1f394a24751bd6cbbd4a90da916554be67ab075 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Thu, 30 Jul 2015 16:28:59 -0700 Subject: [PATCH 30/79] fix regression Signed-off-by: Jessica Frazelle (cherry picked from commit b0af811272c7cf9d7694ab5a21ded776a46a7d7a) --- hack/install.sh | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/hack/install.sh b/hack/install.sh index b3b752346..d40404a8c 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -51,19 +51,6 @@ echo_docker_as_nonroot() { EOF } -get_debian_dist_version() { - dist_version='' - case "$@" in - 8) - dist_version="jessie" - ;; - 7) - dist_version="wheezy" - ;; - esac - echo "$dist_version" -} - do_install() { case "$(uname -m)" in *64) @@ -286,7 +273,7 @@ do_install() { ;; fedora|centos|oraclelinux) - cat >/etc/yum.repos.d/docker-${repo}.repo <<-EOF + $sh_c "cat >/etc/yum.repos.d/docker-${repo}.repo" <<-EOF [docker-${repo}-repo] name=Docker ${repo} Repository baseurl=https://yum.dockerproject.org/repo/${repo}/${lsb_dist}/${dist_version} From 98855c863d695f3f162e105ea36d26785235f243 Mon Sep 17 00:00:00 2001 From: Alessandro Boch Date: Thu, 30 Jul 2015 17:28:12 -0700 Subject: [PATCH 31/79] Vendoring libnetwork 31139cdb513aea5ad1ed08b60d4350a68b4c96db Signed-off-by: Alessandro Boch (cherry picked from commit 91274625ba43b5d059ac239bd3ca53006158b7a7) --- hack/vendor.sh | 2 +- .../libnetwork/drivers/bridge/bridge.go | 48 ++++++++++--------- .../libnetwork/drivers/bridge/setup_device.go | 24 ++++++++-- .../libnetwork/drivers/bridge/setup_ipv4.go | 20 +++++--- .../libnetwork/ipallocator/allocator.go | 11 +++-- .../docker/libnetwork/netutils/utils.go | 22 +++++---- .../docker/libnetwork/sandboxdata.go | 14 ++++-- 7 files changed, 91 insertions(+), 50 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 686afeedf..94b179ccc 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -21,7 +21,7 @@ clone git golang.org/x/net 3cffabab72adf04f8e3b01c5baf775361837b5fe https://gith clone hg code.google.com/p/gosqlite 74691fb6f837 #get libnetwork packages -clone git github.com/docker/libnetwork f1c5671f1ee2133055144e566cd8b3a0ae4f0433 +clone git github.com/docker/libnetwork 31139cdb513aea5ad1ed08b60d4350a68b4c96db clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4 diff --git a/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go b/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go index 57a7f575d..8fc05ae64 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go @@ -596,21 +596,18 @@ func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) err // networks. This step is needed now because driver might have now set the bridge // name on this config struct. And because we need to check for possible address // conflicts, so we need to check against operationa lnetworks. - if err := config.conflictsWithNetworks(id, networkList); err != nil { + if err = config.conflictsWithNetworks(id, networkList); err != nil { return err } setupNetworkIsolationRules := func(config *networkConfiguration, i *bridgeInterface) error { - defer func() { - if err != nil { - if err := network.isolateNetwork(networkList, false); err != nil { - logrus.Warnf("Failed on removing the inter-network iptables rules on cleanup: %v", err) - } + if err := network.isolateNetwork(networkList, true); err != nil { + if err := network.isolateNetwork(networkList, false); err != nil { + logrus.Warnf("Failed on removing the inter-network iptables rules on cleanup: %v", err) } - }() - - err := network.isolateNetwork(networkList, true) - return err + return err + } + return nil } // Prepare the bridge setup configuration @@ -766,17 +763,26 @@ func (d *driver) DeleteNetwork(nid types.UUID) error { } func addToBridge(ifaceName, bridgeName string) error { - iface, err := net.InterfaceByName(ifaceName) + link, err := netlink.LinkByName(ifaceName) if err != nil { return fmt.Errorf("could not find interface %s: %v", ifaceName, err) } + if err = netlink.LinkSetMaster(link, + &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: bridgeName}}); err != nil { + logrus.Debugf("Failed to add %s to bridge via netlink.Trying ioctl: %v", ifaceName, err) + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + return fmt.Errorf("could not find network interface %s: %v", ifaceName, err) + } - master, err := net.InterfaceByName(bridgeName) - if err != nil { - return fmt.Errorf("could not find bridge %s: %v", bridgeName, err) + master, err := net.InterfaceByName(bridgeName) + if err != nil { + return fmt.Errorf("could not find bridge %s: %v", bridgeName, err) + } + + return ioctlAddToBridge(iface, master) } - - return ioctlAddToBridge(iface, master) + return nil } func setHairpinMode(link netlink.Link, enable bool) error { @@ -947,15 +953,14 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointIn } // v4 address for the sandbox side pipe interface - sub := types.GetIPNetCanonical(n.bridge.bridgeIPv4) - ip4, err := ipAllocator.RequestIP(sub, nil) + ip4, err := ipAllocator.RequestIP(n.bridge.bridgeIPv4, nil) if err != nil { return err } ipv4Addr := &net.IPNet{IP: ip4, Mask: n.bridge.bridgeIPv4.Mask} // Down the interface before configuring mac address. - if err := netlink.LinkSetDown(sbox); err != nil { + if err = netlink.LinkSetDown(sbox); err != nil { return fmt.Errorf("could not set link down for container interface %s: %v", containerIfName, err) } @@ -968,7 +973,7 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointIn endpoint.macAddress = mac // Up the host interface after finishing all netlink configuration - if err := netlink.LinkSetUp(host); err != nil { + if err = netlink.LinkSetUp(host); err != nil { return fmt.Errorf("could not set link up for host interface %s: %v", hostIfName, err) } @@ -1074,8 +1079,7 @@ func (d *driver) DeleteEndpoint(nid, eid types.UUID) error { n.releasePorts(ep) // Release the v4 address allocated to this endpoint's sandbox interface - sub := types.GetIPNetCanonical(n.bridge.bridgeIPv4) - err = ipAllocator.ReleaseIP(sub, ep.addr.IP) + err = ipAllocator.ReleaseIP(n.bridge.bridgeIPv4, ep.addr.IP) if err != nil { return err } diff --git a/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_device.go b/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_device.go index 96eeee552..22bf64b2f 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_device.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_device.go @@ -1,7 +1,11 @@ package bridge import ( + "fmt" + + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/parsers/kernel" + "github.com/docker/libnetwork/netutils" "github.com/vishvananda/netlink" ) @@ -25,11 +29,25 @@ func setupDevice(config *networkConfiguration, i *bridgeInterface) error { // Only set the bridge's MAC address if the kernel version is > 3.3, as it // was not supported before that. kv, err := kernel.GetKernelVersion() - if err == nil && (kv.Kernel >= 3 && kv.Major >= 3) { - setMac = true + if err != nil { + logrus.Errorf("Failed to check kernel versions: %v. Will not assign a MAC address to the bridge interface", err) + } else { + setMac = kv.Kernel > 3 || (kv.Kernel == 3 && kv.Major >= 3) } - return ioctlCreateBridge(config.BridgeName, setMac) + if err = netlink.LinkAdd(i.Link); err != nil { + logrus.Debugf("Failed to create bridge %s via netlink. Trying ioctl", config.BridgeName) + return ioctlCreateBridge(config.BridgeName, setMac) + } + + if setMac { + hwAddr := netutils.GenerateRandomMAC() + if err = netlink.LinkSetHardwareAddr(i.Link, hwAddr); err != nil { + return fmt.Errorf("failed to set bridge mac-address %s : %s", hwAddr, err.Error()) + } + logrus.Debugf("Setting bridge mac address to %s", hwAddr) + } + return err } // SetupDeviceUp ups the given bridge interface. diff --git a/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_ipv4.go b/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_ipv4.go index cca715e39..ac4535adb 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_ipv4.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_ipv4.go @@ -8,7 +8,6 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/libnetwork/netutils" - "github.com/docker/libnetwork/types" "github.com/vishvananda/netlink" ) @@ -76,8 +75,12 @@ func setupBridgeIPv4(config *networkConfiguration, i *bridgeInterface) error { } func allocateBridgeIP(config *networkConfiguration, i *bridgeInterface) error { - sub := types.GetIPNetCanonical(i.bridgeIPv4) - ipAllocator.RequestIP(sub, i.bridgeIPv4.IP) + // Because of the way ipallocator manages the container address space, + // reserve bridge address only if it belongs to the container network + // (if defined), no need otherwise + if config.FixedCIDR == nil || config.FixedCIDR.Contains(i.bridgeIPv4.IP) { + ipAllocator.RequestIP(i.bridgeIPv4, i.bridgeIPv4.IP) + } return nil } @@ -112,10 +115,13 @@ func setupGatewayIPv4(config *networkConfiguration, i *bridgeInterface) error { return &ErrInvalidGateway{} } - // Pass the real network subnet to ip allocator (no host bits set) - sub := types.GetIPNetCanonical(i.bridgeIPv4) - if _, err := ipAllocator.RequestIP(sub, config.DefaultGatewayIPv4); err != nil { - return err + // Because of the way ipallocator manages the container address space, + // reserve default gw address only if it belongs to the container network + // (if defined), no need otherwise + if config.FixedCIDR == nil || config.FixedCIDR.Contains(config.DefaultGatewayIPv4) { + if _, err := ipAllocator.RequestIP(i.bridgeIPv4, config.DefaultGatewayIPv4); err != nil { + return err + } } // Store requested default gateway diff --git a/vendor/src/github.com/docker/libnetwork/ipallocator/allocator.go b/vendor/src/github.com/docker/libnetwork/ipallocator/allocator.go index 156009993..06bc051c5 100644 --- a/vendor/src/github.com/docker/libnetwork/ipallocator/allocator.go +++ b/vendor/src/github.com/docker/libnetwork/ipallocator/allocator.go @@ -66,7 +66,8 @@ func (a *IPAllocator) RegisterSubnet(network *net.IPNet, subnet *net.IPNet) erro a.mutex.Lock() defer a.mutex.Unlock() - key := network.String() + nw := &net.IPNet{IP: network.IP.Mask(network.Mask), Mask: network.Mask} + key := nw.String() if _, ok := a.allocatedIPs[key]; ok { return ErrNetworkAlreadyRegistered } @@ -90,10 +91,11 @@ func (a *IPAllocator) RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) { a.mutex.Lock() defer a.mutex.Unlock() - key := network.String() + nw := &net.IPNet{IP: network.IP.Mask(network.Mask), Mask: network.Mask} + key := nw.String() allocated, ok := a.allocatedIPs[key] if !ok { - allocated = newAllocatedMap(network) + allocated = newAllocatedMap(nw) a.allocatedIPs[key] = allocated } @@ -109,7 +111,8 @@ func (a *IPAllocator) ReleaseIP(network *net.IPNet, ip net.IP) error { a.mutex.Lock() defer a.mutex.Unlock() - if allocated, exists := a.allocatedIPs[network.String()]; exists { + nw := &net.IPNet{IP: network.IP.Mask(network.Mask), Mask: network.Mask} + if allocated, exists := a.allocatedIPs[nw.String()]; exists { delete(allocated.p, ip.String()) } return nil diff --git a/vendor/src/github.com/docker/libnetwork/netutils/utils.go b/vendor/src/github.com/docker/libnetwork/netutils/utils.go index 0ef357ec4..cb430eb03 100644 --- a/vendor/src/github.com/docker/libnetwork/netutils/utils.go +++ b/vendor/src/github.com/docker/libnetwork/netutils/utils.go @@ -74,20 +74,22 @@ func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { // NetworkRange calculates the first and last IP addresses in an IPNet func NetworkRange(network *net.IPNet) (net.IP, net.IP) { - var netIP net.IP - if network.IP.To4() != nil { - netIP = network.IP.To4() - } else if network.IP.To16() != nil { - netIP = network.IP.To16() - } else { + if network == nil { return nil, nil } - lastIP := make([]byte, len(netIP), len(netIP)) - for i := 0; i < len(netIP); i++ { - lastIP[i] = netIP[i] | ^network.Mask[i] + firstIP := network.IP.Mask(network.Mask) + lastIP := types.GetIPCopy(firstIP) + for i := 0; i < len(firstIP); i++ { + lastIP[i] = firstIP[i] | ^network.Mask[i] } - return netIP.Mask(network.Mask), net.IP(lastIP) + + if network.IP.To4() != nil { + firstIP = firstIP.To4() + lastIP = lastIP.To4() + } + + return firstIP, lastIP } // GetIfaceAddr returns the first IPv4 address and slice of IPv6 addresses for the specified network interface diff --git a/vendor/src/github.com/docker/libnetwork/sandboxdata.go b/vendor/src/github.com/docker/libnetwork/sandboxdata.go index 6b217a87b..9b0d8ea1b 100644 --- a/vendor/src/github.com/docker/libnetwork/sandboxdata.go +++ b/vendor/src/github.com/docker/libnetwork/sandboxdata.go @@ -139,10 +139,15 @@ func (s *sandboxData) rmEndpoint(ep *endpoint) { } } - // We don't check if s.endpoints is empty here because - // it should never be empty during a rmEndpoint call and - // if it is we will rightfully panic here s.Lock() + if len(s.endpoints) == 0 { + // s.endpoints should never be empty and this is unexpected error condition + // We log an error message to note this down for debugging purposes. + logrus.Errorf("No endpoints in sandbox while trying to remove endpoint %s", ep.Name()) + s.Unlock() + return + } + highEpBefore := s.endpoints[0] var ( i int @@ -245,7 +250,10 @@ func (c *controller) LeaveAll(id string) error { } sData.sandbox().Destroy() + + c.Lock() delete(c.sandboxes, sandbox.GenerateKey(id)) + c.Unlock() return nil } From 290987fcb4c8074a3945f52048492e3b6f4a9f01 Mon Sep 17 00:00:00 2001 From: Alessandro Boch Date: Thu, 30 Jul 2015 17:29:02 -0700 Subject: [PATCH 32/79] Add test code to cover issue #14859 Signed-off-by: Alessandro Boch (cherry picked from commit 4964ab08218f3b40f5c3a6bf19fe74f7fb39562a) --- integration-cli/docker_cli_daemon_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 3cfabd9c1..992cd83e2 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -780,6 +780,18 @@ func (s *DockerDaemonSuite) TestDaemonDefaultGatewayIPv4Explicit(c *check.C) { deleteInterface(c, defaultNetworkBridge) } +func (s *DockerDaemonSuite) TestDaemonDefaultGatewayIPv4ExplicitOutsideContainerSubnet(c *check.C) { + defaultNetworkBridge := "docker0" + deleteInterface(c, defaultNetworkBridge) + + // Program a custom default gateway outside of the container subnet, daemon should accept it and start + err := s.d.StartWithBusybox("--bip", "172.16.0.10/16", "--fixed-cidr", "172.16.1.0/24", "--default-gateway", "172.16.0.254") + c.Assert(err, check.IsNil) + + deleteInterface(c, defaultNetworkBridge) + s.d.Restart() +} + func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) { d := s.d From 5ba75ac3436197702e378893a8d1540e58b60a7d Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Fri, 31 Jul 2015 20:02:03 +0200 Subject: [PATCH 33/79] Add completion of global options to `docker daemon` It's a bit confusing: the "global options" are valid as "global options" for all client commands (i.e. all but daemon). Example: `docker --log-level info run` For `docker daemon`, these "global options" are only valid as "command options". Example: `docker daemon --log-level info` As command completion cannot tell which command the user is going to type next, completion for the daemon command has to allow illegal syntaxes like `docker --log-level info daemon --log-level info` Signed-off-by: Harald Albers (cherry picked from commit e0dad9a153fb8aad44cc36aa4bd14e297b5f120c) --- contrib/completion/bash/docker | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 50b32aca2..1f397fa8c 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -295,6 +295,10 @@ __docker_complete_log_driver_options() { return 1 } +__docker_log_levels() { + COMPREPLY=( $( compgen -W "debug info warn error fatal" -- "$cur" ) ) +} + # a selection of the available signals that is most likely of interest in the # context of docker containers. __docker_signals() { @@ -315,16 +319,14 @@ __docker_signals() { # global options that may appear after the docker command _docker_docker() { local boolean_options=" - --debug -D + $global_boolean_options --help -h - --tls - --tlsverify --version -v " case "$prev" in --log-level|-l) - COMPREPLY=( $( compgen -W "debug info warn error fatal" -- "$cur" ) ) + __docker_log_levels return ;; $(__docker_to_extglob "$global_options_with_args") ) @@ -453,6 +455,7 @@ _docker_create() { _docker_daemon() { local boolean_options=" + $global_boolean_options --help -h --icc=false --ip-forward=false @@ -463,6 +466,7 @@ _docker_daemon() { --userland-proxy=false " local options_with_args=" + $global_options_with_args --api-cors-header --bip --bridge -b @@ -507,6 +511,10 @@ _docker_daemon() { COMPREPLY=( $( compgen -W "aufs devicemapper btrfs overlay" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) ) return ;; + --log-level|-l) + __docker_log_levels + return + ;; --log-opt) __docker_log_driver_options return @@ -514,7 +522,6 @@ _docker_daemon() { $(__docker_to_extglob "$options_with_args") ) return ;; - $main_options_with_args_glob ) esac case "$cur" in @@ -1370,6 +1377,13 @@ _docker() { wait ) + # These options are valid as global options for all client commands + # and valid as command options for `docker daemon` + local global_boolean_options=" + --debug -D + --tls + --tlsverify + " local global_options_with_args=" --host -H --log-level -l From 81efe1f32ef495c9aebd6f538b390be79df02b17 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Fri, 31 Jul 2015 19:25:42 +0200 Subject: [PATCH 34/79] Fix completion of commands after a global option with arg Without this fix, `docker -l info ` would not complete the commands. Signed-off-by: Harald Albers (cherry picked from commit aab82c5c2230fa328bfac3c156b482634a42b73c) --- contrib/completion/bash/docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 1f397fa8c..59d8a869e 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -341,7 +341,7 @@ _docker_docker() { COMPREPLY=( $( compgen -W "$boolean_options $global_options_with_args" -- "$cur" ) ) ;; *) - local counter="$(__docker_pos_first_nonflag $main_options_with_args_glob)" + local counter=$( __docker_pos_first_nonflag $(__docker_to_extglob "$global_options_with_args") ) if [ $cword -eq $counter ]; then COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) ) fi From 2f7145b1c58b465f4f4214ab13b2c7bc2e76cdff Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 31 Jul 2015 09:48:55 -0700 Subject: [PATCH 35/79] Remove read index that causes dead lock. Let the iterator to lock the index when it needs it. Signed-off-by: David Calavera (cherry picked from commit 5ed84009b33642ca1f1eac8b99001842d93e2494) --- pkg/truncindex/truncindex.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/truncindex/truncindex.go b/pkg/truncindex/truncindex.go index 8d8bee0c9..72d525a1d 100644 --- a/pkg/truncindex/truncindex.go +++ b/pkg/truncindex/truncindex.go @@ -111,8 +111,6 @@ func (idx *TruncIndex) Get(s string) (string, error) { // Iterates over all stored IDs, and passes each of them to the given handler func (idx *TruncIndex) Iterate(handler func(id string)) { - idx.RLock() - defer idx.RUnlock() idx.trie.Visit(func(prefix patricia.Prefix, item patricia.Item) error { handler(string(prefix)) return nil From 0f5e2fd479dfd60c2132a26e427d629efc1a2966 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 31 Jul 2015 16:49:07 -0400 Subject: [PATCH 36/79] Ensure reader position is at the end after tailing After tailing a file, if the number of lines requested is > the number of lines in the file, this would cause a json unmarshalling error to occur when we later try to go follow the file. So brute force set it to the end if any tailing occurred. There is potential that there could be some missing log messages if logs are being written very quickly, however I was not able to make this happen even with `while true; do echo hello; done`, so this is probably acceptable. While testing this I also found a panic in LogWatcher.Close can be called twice due to a race. Fix channel close to only close when there has been no signal to the channel. Signed-off-by: Brian Goff (cherry picked from commit c57faa91e2dab72a0a0905dc10e5cbdf55b545f5) --- daemon/logger/jsonfilelog/jsonfilelog.go | 3 ++- daemon/logger/logger.go | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/daemon/logger/jsonfilelog/jsonfilelog.go b/daemon/logger/jsonfilelog/jsonfilelog.go index 383aada82..4703f64b9 100644 --- a/daemon/logger/jsonfilelog/jsonfilelog.go +++ b/daemon/logger/jsonfilelog/jsonfilelog.go @@ -259,7 +259,8 @@ func (l *JSONFileLogger) readLogs(logWatcher *logger.LogWatcher, config logger.R if !config.Follow { return } - if config.Tail == 0 { + + if config.Tail >= 0 { latestFile.Seek(0, os.SEEK_END) } diff --git a/daemon/logger/logger.go b/daemon/logger/logger.go index 96421f4b9..39c1512de 100644 --- a/daemon/logger/logger.go +++ b/daemon/logger/logger.go @@ -2,6 +2,7 @@ package logger import ( "errors" + "sync" "time" "github.com/docker/docker/pkg/timeutils" @@ -51,6 +52,7 @@ type LogWatcher struct { // For sending error messages that occur while while reading logs Err chan error closeNotifier chan struct{} + closeOnce sync.Once } // NewLogWatcher returns a new LogWatcher. @@ -64,7 +66,12 @@ func NewLogWatcher() *LogWatcher { // Close notifies the underlying log reader to stop func (w *LogWatcher) Close() { - close(w.closeNotifier) + // only close if not already closed + select { + case <-w.closeNotifier: + default: + close(w.closeNotifier) + } } // WatchClose returns a channel receiver that receives notification when the watcher has been closed From 0fe5aad9844d6b6d7d3363f7916228504e27d17c Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Mon, 3 Aug 2015 09:14:52 +0800 Subject: [PATCH 37/79] Remove unused variable Introduced by #15209 unintentionally. Signed-off-by: Qiang Huang (cherry picked from commit f5557f4f43ba89a448131fbca8094f9cf8ddb097) --- daemon/logger/logger.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/daemon/logger/logger.go b/daemon/logger/logger.go index 39c1512de..99b4a3583 100644 --- a/daemon/logger/logger.go +++ b/daemon/logger/logger.go @@ -2,7 +2,6 @@ package logger import ( "errors" - "sync" "time" "github.com/docker/docker/pkg/timeutils" @@ -52,7 +51,6 @@ type LogWatcher struct { // For sending error messages that occur while while reading logs Err chan error closeNotifier chan struct{} - closeOnce sync.Once } // NewLogWatcher returns a new LogWatcher. From b3c3c4cddc71d240af2174673493387a9be85209 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Fri, 31 Jul 2015 14:54:55 -0700 Subject: [PATCH 38/79] Vendor latest notary Use updated notary to pick up updates from security review Signed-off-by: Derek McGowan (github: dmcgowan) (cherry picked from commit d594c6fcd8fbe295a87cfc2af70456be4e58c24d) --- Dockerfile | 2 +- hack/vendor.sh | 4 +- vendor/src/github.com/docker/notary/LICENSE | 201 ++++++++++++++++++ .../docker/notary/client/changelist/change.go | 16 +- .../notary/client/changelist/changelist.go | 5 + ...files_changelist.go => file_changelist.go} | 0 .../notary/client/changelist/interface.go | 8 +- .../github.com/docker/notary/client/client.go | 18 +- .../docker/notary/client/helpers.go | 39 ++-- .../notary/keystoremanager/import_export.go | 41 +++- .../notary/pkg/passphrase/passphrase.go | 73 +++++-- .../notary/trustmanager/keyfilestore.go | 83 ++------ .../docker/notary/trustmanager/keystore.go | 52 +++++ .../docker/notary/trustmanager/x509utils.go | 2 +- .../endophage/gotuf/client/client.go | 26 ++- .../endophage/gotuf/client/errors.go | 8 + .../github.com/endophage/gotuf/data/roles.go | 48 ++++- .../github.com/endophage/gotuf/data/types.go | 42 ++-- .../endophage/gotuf/signed/errors.go | 14 ++ .../endophage/gotuf/signed/verifiers.go | 53 ++++- .../endophage/gotuf/signed/verify.go | 21 +- 21 files changed, 584 insertions(+), 172 deletions(-) create mode 100644 vendor/src/github.com/docker/notary/LICENSE rename vendor/src/github.com/docker/notary/client/changelist/{files_changelist.go => file_changelist.go} (100%) create mode 100644 vendor/src/github.com/docker/notary/trustmanager/keystore.go diff --git a/Dockerfile b/Dockerfile index 51b6cf08f..951c0cf7f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -137,7 +137,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Install notary server -ENV NOTARY_COMMIT 77bced079e83d80f40c1f0a544b1a8a3b97fb052 +ENV NOTARY_COMMIT 8e8122eb5528f621afcd4e2854c47302f17392f7 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ diff --git a/hack/vendor.sh b/hack/vendor.sh index 94b179ccc..28997e922 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -38,8 +38,8 @@ clone git github.com/hashicorp/consul v0.5.2 clone git github.com/docker/distribution e83345626608aa943d5c8a027fddcf54814d9545 clone git github.com/vbatts/tar-split v0.9.4 -clone git github.com/docker/notary 77bced079e83d80f40c1f0a544b1a8a3b97fb052 -clone git github.com/endophage/gotuf 374908abc8af7e953a2813c5c2b3944ab625ca68 +clone git github.com/docker/notary 8e8122eb5528f621afcd4e2854c47302f17392f7 +clone git github.com/endophage/gotuf 89ceb27829b9353dfee5ccccf7a3a9bb77008b05 clone git github.com/tent/canonical-json-go 96e4ba3a7613a1216cbd1badca4efe382adea337 clone git github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c diff --git a/vendor/src/github.com/docker/notary/LICENSE b/vendor/src/github.com/docker/notary/LICENSE new file mode 100644 index 000000000..6daf85e9d --- /dev/null +++ b/vendor/src/github.com/docker/notary/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/src/github.com/docker/notary/client/changelist/change.go b/vendor/src/github.com/docker/notary/client/changelist/change.go index 77544dc66..867c23051 100644 --- a/vendor/src/github.com/docker/notary/client/changelist/change.go +++ b/vendor/src/github.com/docker/notary/client/changelist/change.go @@ -1,9 +1,19 @@ package changelist +// Scopes for TufChanges are simply the TUF roles. +// Unfortunately because of targets delegations, we can only +// cover the base roles. +const ( + ScopeRoot = "root" + ScopeTargets = "targets" + ScopeSnapshot = "snapshot" + ScopeTimestamp = "timestamp" +) + // TufChange represents a change to a TUF repo type TufChange struct { // Abbreviated because Go doesn't permit a field and method of the same name - Actn int `json:"action"` + Actn string `json:"action"` Role string `json:"role"` ChangeType string `json:"type"` ChangePath string `json:"path"` @@ -11,7 +21,7 @@ type TufChange struct { } // NewTufChange initializes a tufChange object -func NewTufChange(action int, role, changeType, changePath string, content []byte) *TufChange { +func NewTufChange(action string, role, changeType, changePath string, content []byte) *TufChange { return &TufChange{ Actn: action, Role: role, @@ -22,7 +32,7 @@ func NewTufChange(action int, role, changeType, changePath string, content []byt } // Action return c.Actn -func (c TufChange) Action() int { +func (c TufChange) Action() string { return c.Actn } diff --git a/vendor/src/github.com/docker/notary/client/changelist/changelist.go b/vendor/src/github.com/docker/notary/client/changelist/changelist.go index aef497011..80cd2461f 100644 --- a/vendor/src/github.com/docker/notary/client/changelist/changelist.go +++ b/vendor/src/github.com/docker/notary/client/changelist/changelist.go @@ -5,6 +5,11 @@ type memChangelist struct { changes []Change } +// NewMemChangelist instantiates a new in-memory changelist +func NewMemChangelist() Changelist { + return &memChangelist{} +} + // List returns a list of Changes func (cl memChangelist) List() []Change { return cl.changes diff --git a/vendor/src/github.com/docker/notary/client/changelist/files_changelist.go b/vendor/src/github.com/docker/notary/client/changelist/file_changelist.go similarity index 100% rename from vendor/src/github.com/docker/notary/client/changelist/files_changelist.go rename to vendor/src/github.com/docker/notary/client/changelist/file_changelist.go diff --git a/vendor/src/github.com/docker/notary/client/changelist/interface.go b/vendor/src/github.com/docker/notary/client/changelist/interface.go index fd24b65c5..a9b09b71f 100644 --- a/vendor/src/github.com/docker/notary/client/changelist/interface.go +++ b/vendor/src/github.com/docker/notary/client/changelist/interface.go @@ -22,17 +22,17 @@ type Changelist interface { const ( // ActionCreate represents a Create action - ActionCreate = iota + ActionCreate = "create" // ActionUpdate represents an Update action - ActionUpdate + ActionUpdate = "update" // ActionDelete represents a Delete action - ActionDelete + ActionDelete = "delete" ) // Change is the interface for a TUF Change type Change interface { // "create","update", or "delete" - Action() int + Action() string // Where the change should be made. // For TUF this will be the role diff --git a/vendor/src/github.com/docker/notary/client/client.go b/vendor/src/github.com/docker/notary/client/client.go index 6c8e3a8aa..6d59af720 100644 --- a/vendor/src/github.com/docker/notary/client/client.go +++ b/vendor/src/github.com/docker/notary/client/client.go @@ -250,7 +250,7 @@ func (r *NotaryRepository) AddTarget(target *Target) error { return err } - c := changelist.NewTufChange(changelist.ActionCreate, "targets", "target", target.Name, metaJSON) + c := changelist.NewTufChange(changelist.ActionCreate, changelist.ScopeTargets, "target", target.Name, metaJSON) err = cl.Add(c) if err != nil { return err @@ -258,6 +258,22 @@ func (r *NotaryRepository) AddTarget(target *Target) error { return cl.Close() } +// RemoveTarget creates a new changelist entry to remove a target from the repository +// when the changelist gets applied at publish time +func (r *NotaryRepository) RemoveTarget(targetName string) error { + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } + logrus.Debugf("Removing target \"%s\"", targetName) + c := changelist.NewTufChange(changelist.ActionDelete, changelist.ScopeTargets, "target", targetName, nil) + err = cl.Add(c) + if err != nil { + return err + } + return nil +} + // ListTargets lists all targets for the current repository func (r *NotaryRepository) ListTargets() ([]*Target, error) { c, err := r.bootstrapClient() diff --git a/vendor/src/github.com/docker/notary/client/helpers.go b/vendor/src/github.com/docker/notary/client/helpers.go index 003f73fa9..93040b41d 100644 --- a/vendor/src/github.com/docker/notary/client/helpers.go +++ b/vendor/src/github.com/docker/notary/client/helpers.go @@ -5,6 +5,7 @@ import ( "net/http" "time" + "github.com/Sirupsen/logrus" "github.com/docker/notary/client/changelist" "github.com/endophage/gotuf" "github.com/endophage/gotuf/data" @@ -26,13 +27,16 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error { changes := cl.List() - var err error + logrus.Debugf("applying %d changes", len(changes)) for _, c := range changes { - if c.Scope() == "targets" { - applyTargetsChange(repo, c) - } - if err != nil { - return err + switch c.Scope() { + case changelist.ScopeTargets: + err := applyTargetsChange(repo, c) + if err != nil { + return err + } + default: + logrus.Debug("scope not supported: ", c.Scope()) } } return nil @@ -40,16 +44,21 @@ func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error { func applyTargetsChange(repo *tuf.TufRepo, c changelist.Change) error { var err error - meta := &data.FileMeta{} - err = json.Unmarshal(c.Content(), meta) - if err != nil { - return nil - } - if c.Action() == changelist.ActionCreate { + switch c.Action() { + case changelist.ActionCreate: + logrus.Debug("changelist add: ", c.Path()) + meta := &data.FileMeta{} + err = json.Unmarshal(c.Content(), meta) + if err != nil { + return err + } files := data.Files{c.Path(): *meta} - _, err = repo.AddTargets("targets", files) - } else if c.Action() == changelist.ActionDelete { - err = repo.RemoveTargets("targets", c.Path()) + _, err = repo.AddTargets(c.Scope(), files) + case changelist.ActionDelete: + logrus.Debug("changelist remove: ", c.Path()) + err = repo.RemoveTargets(c.Scope(), c.Path()) + default: + logrus.Debug("action not yet supported: ", c.Action()) } if err != nil { return err diff --git a/vendor/src/github.com/docker/notary/keystoremanager/import_export.go b/vendor/src/github.com/docker/notary/keystoremanager/import_export.go index bd8724468..55c736cda 100644 --- a/vendor/src/github.com/docker/notary/keystoremanager/import_export.go +++ b/vendor/src/github.com/docker/notary/keystoremanager/import_export.go @@ -42,6 +42,39 @@ func (km *KeyStoreManager) ExportRootKey(dest io.Writer, keyID string) error { return err } +// ExportRootKeyReencrypt exports the specified root key to an io.Writer in +// PEM format. The key is reencrypted with a new passphrase. +func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error { + privateKey, alias, err := km.rootKeyStore.GetKey(keyID) + if err != nil { + return err + } + + // Create temporary keystore to use as a staging area + tempBaseDir, err := ioutil.TempDir("", "notary-key-export-") + defer os.RemoveAll(tempBaseDir) + + privRootKeysSubdir := filepath.Join(privDir, rootKeysSubdir) + tempRootKeysPath := filepath.Join(tempBaseDir, privRootKeysSubdir) + tempRootKeyStore, err := trustmanager.NewKeyFileStore(tempRootKeysPath, newPassphraseRetriever) + if err != nil { + return err + } + + err = tempRootKeyStore.AddKey(keyID, alias, privateKey) + if err != nil { + return err + } + + pemBytes, err := tempRootKeyStore.Get(keyID + "_" + alias) + if err != nil { + return err + } + + _, err = dest.Write(pemBytes) + return err +} + // checkRootKeyIsEncrypted makes sure the root key is encrypted. We have // internal assumptions that depend on this. func checkRootKeyIsEncrypted(pemBytes []byte) error { @@ -80,13 +113,13 @@ func (km *KeyStoreManager) ImportRootKey(source io.Reader, keyID string) error { func moveKeys(oldKeyStore, newKeyStore *trustmanager.KeyFileStore) error { // List all files but no symlinks - for _, f := range oldKeyStore.ListKeys() { - pemBytes, alias, err := oldKeyStore.GetKey(f) + for f := range oldKeyStore.ListKeys() { + privateKey, alias, err := oldKeyStore.GetKey(f) if err != nil { return err } - err = newKeyStore.AddKey(f, alias, pemBytes) + err = newKeyStore.AddKey(f, alias, privateKey) if err != nil { return err @@ -247,7 +280,7 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader) error { func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun string) error { // List all files but no symlinks - for _, relKeyPath := range oldKeyStore.ListKeys() { + for relKeyPath := range oldKeyStore.ListKeys() { // Skip keys that aren't associated with this GUN if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) { diff --git a/vendor/src/github.com/docker/notary/pkg/passphrase/passphrase.go b/vendor/src/github.com/docker/notary/pkg/passphrase/passphrase.go index aae28170b..e89092b89 100644 --- a/vendor/src/github.com/docker/notary/pkg/passphrase/passphrase.go +++ b/vendor/src/github.com/docker/notary/pkg/passphrase/passphrase.go @@ -22,28 +22,45 @@ import ( type Retriever func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) const ( - idBytesToDisplay = 5 + idBytesToDisplay = 7 tufRootAlias = "root" tufTargetsAlias = "targets" tufSnapshotAlias = "snapshot" - tufRootKeyGenerationWarning = `You are about to create a new root signing key passphrase. This passphrase will be used to protect -the most sensitive key in your signing system. Please choose a long, complex passphrase and be careful -to keep the password and the key file itself secure and backed up. It is highly recommended that you use -a password manager to generate the passphrase and keep it safe. There will be no way to recover this key. -You can find the key in your config directory.` + tufRootKeyGenerationWarning = `You are about to create a new root signing key passphrase. This passphrase +will be used to protect the most sensitive key in your signing system. Please +choose a long, complex passphrase and be careful to keep the password and the +key file itself secure and backed up. It is highly recommended that you use a +password manager to generate the passphrase and keep it safe. There will be no +way to recover this key. You can find the key in your config directory.` +) + +var ( + // ErrTooShort is returned if the passphrase entered for a new key is + // below the minimum length + ErrTooShort = errors.New("Passphrase too short") + + // ErrDontMatch is returned if the two entered passphrases don't match. + // new key is below the minimum length + ErrDontMatch = errors.New("The entered passphrases do not match") + + // ErrTooManyAttempts is returned if the maximum number of passphrase + // entry attempts is reached. + ErrTooManyAttempts = errors.New("Too many attempts") ) // PromptRetriever returns a new Retriever which will provide a prompt on stdin // and stdout to retrieve a passphrase. The passphrase will be cached such that // subsequent prompts will produce the same passphrase. func PromptRetriever() Retriever { - return PromptRetrieverWithInOut(os.Stdin, os.Stdout) + return PromptRetrieverWithInOut(os.Stdin, os.Stdout, nil) } // PromptRetrieverWithInOut returns a new Retriever which will provide a // prompt using the given in and out readers. The passphrase will be cached // such that subsequent prompts will produce the same passphrase. -func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { +// aliasMap can be used to specify display names for TUF key aliases. If aliasMap +// is nil, a sensible default will be used. +func PromptRetrieverWithInOut(in io.Reader, out io.Writer, aliasMap map[string]string) Retriever { userEnteredTargetsSnapshotsPass := false targetsSnapshotsPass := "" userEnteredRootsPass := false @@ -54,14 +71,20 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { fmt.Fprintln(out, tufRootKeyGenerationWarning) } if numAttempts > 0 { - if createNew { - fmt.Fprintln(out, "Passphrases do not match. Please retry.") - - } else { + if !createNew { fmt.Fprintln(out, "Passphrase incorrect. Please retry.") } } + // Figure out if we should display a different string for this alias + displayAlias := alias + if aliasMap != nil { + if val, ok := aliasMap[alias]; ok { + displayAlias = val + } + + } + // First, check if we have a password cached for this alias. if numAttempts == 0 { if userEnteredTargetsSnapshotsPass && (alias == tufSnapshotAlias || alias == tufTargetsAlias) { @@ -73,7 +96,7 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { } if numAttempts > 3 && !createNew { - return "", true, errors.New("Too many attempts") + return "", true, ErrTooManyAttempts } state, err := term.SaveState(0) @@ -86,15 +109,24 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { stdin := bufio.NewReader(in) indexOfLastSeparator := strings.LastIndex(keyName, string(filepath.Separator)) + if indexOfLastSeparator == -1 { + indexOfLastSeparator = 0 + } - if len(keyName) > indexOfLastSeparator+idBytesToDisplay+1 { - keyName = keyName[:indexOfLastSeparator+idBytesToDisplay+1] + if len(keyName) > indexOfLastSeparator+idBytesToDisplay { + if indexOfLastSeparator > 0 { + keyNamePrefix := keyName[:indexOfLastSeparator] + keyNameID := keyName[indexOfLastSeparator+1 : indexOfLastSeparator+idBytesToDisplay+1] + keyName = keyNamePrefix + " (" + keyNameID + ")" + } else { + keyName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay] + } } if createNew { - fmt.Fprintf(out, "Enter passphrase for new %s key with id %s: ", alias, keyName) + fmt.Fprintf(out, "Enter passphrase for new %s key with id %s: ", displayAlias, keyName) } else { - fmt.Fprintf(out, "Enter key passphrase for %s key with id %s: ", alias, keyName) + fmt.Fprintf(out, "Enter key passphrase for %s key with id %s: ", displayAlias, keyName) } passphrase, err := stdin.ReadBytes('\n') @@ -119,10 +151,10 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { if len(retPass) < 8 { fmt.Fprintln(out, "Please use a password manager to generate and store a good random passphrase.") - return "", false, errors.New("Passphrase too short") + return "", false, ErrTooShort } - fmt.Fprintf(out, "Repeat passphrase for new %s key with id %s: ", alias, keyName) + fmt.Fprintf(out, "Repeat passphrase for new %s key with id %s: ", displayAlias, keyName) confirmation, err := stdin.ReadBytes('\n') fmt.Fprintln(out) if err != nil { @@ -131,7 +163,8 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer) Retriever { confirmationStr := strings.TrimSpace(string(confirmation)) if retPass != confirmationStr { - return "", false, errors.New("The entered passphrases do not match") + fmt.Fprintln(out, "Passphrases do not match. Please retry.") + return "", false, ErrDontMatch } if alias == tufSnapshotAlias || alias == tufTargetsAlias { diff --git a/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go b/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go index b8d5fd175..fc68463db 100644 --- a/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go +++ b/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go @@ -5,65 +5,10 @@ import ( "strings" "sync" - "fmt" - "github.com/docker/notary/pkg/passphrase" "github.com/endophage/gotuf/data" ) -const ( - keyExtension = "key" -) - -// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key -type ErrAttemptsExceeded struct{} - -// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key -func (err ErrAttemptsExceeded) Error() string { - return "maximum number of passphrase attempts exceeded" -} - -// ErrPasswordInvalid is returned when signing fails. It could also mean the signing -// key file was corrupted, but we have no way to distinguish. -type ErrPasswordInvalid struct{} - -// ErrPasswordInvalid is returned when signing fails. It could also mean the signing -// key file was corrupted, but we have no way to distinguish. -func (err ErrPasswordInvalid) Error() string { - return "password invalid, operation has failed." -} - -// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key. -type ErrKeyNotFound struct { - KeyID string -} - -// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key. -func (err ErrKeyNotFound) Error() string { - return fmt.Sprintf("signing key not found: %s", err.KeyID) -} - -// KeyStore is a generic interface for private key storage -type KeyStore interface { - LimitedFileStore - - AddKey(name, alias string, privKey data.PrivateKey) error - GetKey(name string) (data.PrivateKey, string, error) - ListKeys() []string - RemoveKey(name string) error -} - -type cachedKey struct { - alias string - key data.PrivateKey -} - -// PassphraseRetriever is a callback function that should retrieve a passphrase -// for a given named key. If it should be treated as new passphrase (e.g. with -// confirmation), createNew will be true. Attempts is passed in so that implementers -// decide how many chances to give to a human, for example. -type PassphraseRetriever func(keyId, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) - // KeyFileStore persists and manages private keys on disk type KeyFileStore struct { sync.Mutex @@ -111,7 +56,7 @@ func (s *KeyFileStore) GetKey(name string) (data.PrivateKey, string, error) { // ListKeys returns a list of unique PublicKeys present on the KeyFileStore. // There might be symlinks associating Certificate IDs to Public Keys, so this // method only returns the IDs that aren't symlinks -func (s *KeyFileStore) ListKeys() []string { +func (s *KeyFileStore) ListKeys() map[string]string { return listKeys(s) } @@ -149,7 +94,7 @@ func (s *KeyMemoryStore) GetKey(name string) (data.PrivateKey, string, error) { // ListKeys returns a list of unique PublicKeys present on the KeyFileStore. // There might be symlinks associating Certificate IDs to Public Keys, so this // method only returns the IDs that aren't symlinks -func (s *KeyMemoryStore) ListKeys() []string { +func (s *KeyMemoryStore) ListKeys() map[string]string { return listKeys(s) } @@ -167,10 +112,10 @@ func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached } attempts := 0 - passphrase := "" + chosenPassphrase := "" giveup := false for { - passphrase, giveup, err = passphraseRetriever(name, alias, true, attempts) + chosenPassphrase, giveup, err = passphraseRetriever(name, alias, true, attempts) if err != nil { attempts++ continue @@ -184,8 +129,8 @@ func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached break } - if passphrase != "" { - pemPrivKey, err = EncryptPrivateKey(privKey, passphrase) + if chosenPassphrase != "" { + pemPrivKey, err = EncryptPrivateKey(privKey, chosenPassphrase) if err != nil { return err } @@ -261,18 +206,20 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached return privKey, keyAlias, nil } -// ListKeys returns a list of unique PublicKeys present on the KeyFileStore. +// ListKeys returns a map of unique PublicKeys present on the KeyFileStore and +// their corresponding aliases. // There might be symlinks associating Certificate IDs to Public Keys, so this // method only returns the IDs that aren't symlinks -func listKeys(s LimitedFileStore) []string { - var keyIDList []string +func listKeys(s LimitedFileStore) map[string]string { + keyIDMap := make(map[string]string) for _, f := range s.ListFiles(false) { - keyID := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f))) - keyID = keyID[:strings.LastIndex(keyID, "_")] - keyIDList = append(keyIDList, keyID) + keyIDFull := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f))) + keyID := keyIDFull[:strings.LastIndex(keyIDFull, "_")] + keyAlias := keyIDFull[strings.LastIndex(keyIDFull, "_")+1:] + keyIDMap[keyID] = keyAlias } - return keyIDList + return keyIDMap } // RemoveKey removes the key from the keyfilestore diff --git a/vendor/src/github.com/docker/notary/trustmanager/keystore.go b/vendor/src/github.com/docker/notary/trustmanager/keystore.go new file mode 100644 index 000000000..ba5fb1a1a --- /dev/null +++ b/vendor/src/github.com/docker/notary/trustmanager/keystore.go @@ -0,0 +1,52 @@ +package trustmanager + +import ( + "fmt" + + "github.com/endophage/gotuf/data" +) + +// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key +type ErrAttemptsExceeded struct{} + +// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key +func (err ErrAttemptsExceeded) Error() string { + return "maximum number of passphrase attempts exceeded" +} + +// ErrPasswordInvalid is returned when signing fails. It could also mean the signing +// key file was corrupted, but we have no way to distinguish. +type ErrPasswordInvalid struct{} + +// ErrPasswordInvalid is returned when signing fails. It could also mean the signing +// key file was corrupted, but we have no way to distinguish. +func (err ErrPasswordInvalid) Error() string { + return "password invalid, operation has failed." +} + +// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key. +type ErrKeyNotFound struct { + KeyID string +} + +// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key. +func (err ErrKeyNotFound) Error() string { + return fmt.Sprintf("signing key not found: %s", err.KeyID) +} + +const ( + keyExtension = "key" +) + +// KeyStore is a generic interface for private key storage +type KeyStore interface { + AddKey(name, alias string, privKey data.PrivateKey) error + GetKey(name string) (data.PrivateKey, string, error) + ListKeys() map[string]string + RemoveKey(name string) error +} + +type cachedKey struct { + alias string + key data.PrivateKey +} diff --git a/vendor/src/github.com/docker/notary/trustmanager/x509utils.go b/vendor/src/github.com/docker/notary/trustmanager/x509utils.go index 396bd052e..2661c7677 100644 --- a/vendor/src/github.com/docker/notary/trustmanager/x509utils.go +++ b/vendor/src/github.com/docker/notary/trustmanager/x509utils.go @@ -351,7 +351,7 @@ func GenerateECDSAKey(random io.Reader) (data.PrivateKey, error) { // PrivateKey. The serialization format we use is just the public key bytes // followed by the private key bytes func GenerateED25519Key(random io.Reader) (data.PrivateKey, error) { - pub, priv, err := ed25519.GenerateKey(rand.Reader) + pub, priv, err := ed25519.GenerateKey(random) if err != nil { return nil, err } diff --git a/vendor/src/github.com/endophage/gotuf/client/client.go b/vendor/src/github.com/endophage/gotuf/client/client.go index 7d7c63a36..a321587ae 100644 --- a/vendor/src/github.com/endophage/gotuf/client/client.go +++ b/vendor/src/github.com/endophage/gotuf/client/client.go @@ -50,15 +50,9 @@ func (c *Client) Update() error { logrus.Debug("updating TUF client") err := c.update() if err != nil { - switch err.(type) { - case signed.ErrRoleThreshold, signed.ErrExpired, tuf.ErrLocalRootExpired: - logrus.Debug("retryable error occurred. Root will be downloaded and another update attempted") - if err := c.downloadRoot(); err != nil { - logrus.Errorf("client Update (Root):", err) - return err - } - default: - logrus.Error("an unexpected error occurred while updating TUF client") + logrus.Debug("Error occurred. Root will be downloaded and another update attempted") + if err := c.downloadRoot(); err != nil { + logrus.Errorf("client Update (Root):", err) return err } // If we error again, we now have the latest root and just want to fail @@ -114,6 +108,20 @@ func (c Client) checkRoot() error { if !bytes.Equal(hash[:], hashSha256) { return fmt.Errorf("Cached root sha256 did not match snapshot root sha256") } + + if int64(len(raw)) != size { + return fmt.Errorf("Cached root size did not match snapshot size") + } + + root := &data.SignedRoot{} + err = json.Unmarshal(raw, root) + if err != nil { + return ErrCorruptedCache{file: "root.json"} + } + + if signed.IsExpired(root.Signed.Expires) { + return tuf.ErrLocalRootExpired{} + } return nil } diff --git a/vendor/src/github.com/endophage/gotuf/client/errors.go b/vendor/src/github.com/endophage/gotuf/client/errors.go index 92df3e2de..776e6a69e 100644 --- a/vendor/src/github.com/endophage/gotuf/client/errors.go +++ b/vendor/src/github.com/endophage/gotuf/client/errors.go @@ -104,3 +104,11 @@ type ErrInvalidURL struct { func (e ErrInvalidURL) Error() string { return fmt.Sprintf("tuf: invalid repository URL %s", e.URL) } + +type ErrCorruptedCache struct { + file string +} + +func (e ErrCorruptedCache) Error() string { + return fmt.Sprintf("cache is corrupted: %s", e.file) +} diff --git a/vendor/src/github.com/endophage/gotuf/data/roles.go b/vendor/src/github.com/endophage/gotuf/data/roles.go index d77529bb0..d3047d784 100644 --- a/vendor/src/github.com/endophage/gotuf/data/roles.go +++ b/vendor/src/github.com/endophage/gotuf/data/roles.go @@ -7,16 +7,27 @@ import ( "github.com/endophage/gotuf/errors" ) +// Canonical base role names +const ( + CanonicalRootRole = "root" + CanonicalTargetsRole = "targets" + CanonicalSnapshotRole = "snapshot" + CanonicalTimestampRole = "timestamp" +) + var ValidRoles = map[string]string{ - "root": "root", - "targets": "targets", - "snapshot": "snapshot", - "timestamp": "timestamp", + CanonicalRootRole: CanonicalRootRole, + CanonicalTargetsRole: CanonicalTargetsRole, + CanonicalSnapshotRole: CanonicalSnapshotRole, + CanonicalTimestampRole: CanonicalTimestampRole, } func SetValidRoles(rs map[string]string) { - for k, v := range rs { - ValidRoles[strings.ToLower(k)] = strings.ToLower(v) + // iterate ValidRoles + for k, _ := range ValidRoles { + if v, ok := rs[k]; ok { + ValidRoles[k] = v + } } } @@ -27,6 +38,27 @@ func RoleName(role string) string { return role } +func CanonicalRole(role string) string { + name := strings.ToLower(role) + if _, ok := ValidRoles[name]; ok { + // The canonical version is always lower case + // se ensure we return name, not role + return name + } + targetsBase := fmt.Sprintf("%s/", ValidRoles[CanonicalTargetsRole]) + if strings.HasPrefix(name, targetsBase) { + role = strings.TrimPrefix(role, targetsBase) + role = fmt.Sprintf("%s/%s", CanonicalTargetsRole, role) + return role + } + for r, v := range ValidRoles { + if role == v { + return r + } + } + return "" +} + // ValidRole only determines the name is semantically // correct. For target delegated roles, it does NOT check // the the appropriate parent roles exist. @@ -35,7 +67,7 @@ func ValidRole(name string) bool { if v, ok := ValidRoles[name]; ok { return name == v } - targetsBase := fmt.Sprintf("%s/", ValidRoles["targets"]) + targetsBase := fmt.Sprintf("%s/", ValidRoles[CanonicalTargetsRole]) if strings.HasPrefix(name, targetsBase) { return true } @@ -112,6 +144,6 @@ func (r Role) CheckPrefixes(hash string) bool { } func (r Role) IsDelegation() bool { - targetsBase := fmt.Sprintf("%s/", ValidRoles["targets"]) + targetsBase := fmt.Sprintf("%s/", ValidRoles[CanonicalTargetsRole]) return strings.HasPrefix(r.Name, targetsBase) } diff --git a/vendor/src/github.com/endophage/gotuf/data/types.go b/vendor/src/github.com/endophage/gotuf/data/types.go index 9d4667165..98d55f32b 100644 --- a/vendor/src/github.com/endophage/gotuf/data/types.go +++ b/vendor/src/github.com/endophage/gotuf/data/types.go @@ -43,10 +43,10 @@ const ( ) var TUFTypes = map[string]string{ - "targets": "Targets", - "root": "Root", - "snapshot": "Snapshot", - "timestamp": "Timestamp", + CanonicalRootRole: "Root", + CanonicalTargetsRole: "Targets", + CanonicalSnapshotRole: "Snapshot", + CanonicalTimestampRole: "Timestamp", } // SetTUFTypes allows one to override some or all of the default @@ -57,19 +57,25 @@ func SetTUFTypes(ts map[string]string) { } } -// Checks if type is correct. -func ValidTUFType(t string) bool { +func ValidTUFType(typ, role string) bool { + if ValidRole(role) { + // All targets delegation roles must have + // the valid type is for targets. + role = CanonicalRole(role) + if role == "" { + // role is unknown and does not map to + // a type + return false + } + if strings.HasPrefix(role, CanonicalTargetsRole+"/") { + role = CanonicalTargetsRole + } + } // most people will just use the defaults so have this optimal check // first. Do comparison just in case there is some unknown vulnerability // if a key and value in the map differ. - if v, ok := TUFTypes[t]; ok { - return t == v - } - // For people that feel the need to change the default type names. - for _, v := range TUFTypes { - if t == v { - return true - } + if v, ok := TUFTypes[role]; ok { + return typ == v } return false } @@ -138,10 +144,10 @@ func NewDelegations() *Delegations { // defines number of days in which something should expire var defaultExpiryTimes = map[string]int{ - "root": 365, - "targets": 90, - "snapshot": 7, - "timestamp": 1, + CanonicalRootRole: 365, + CanonicalTargetsRole: 90, + CanonicalSnapshotRole: 7, + CanonicalTimestampRole: 1, } // SetDefaultExpiryTimes allows one to change the default expiries. diff --git a/vendor/src/github.com/endophage/gotuf/signed/errors.go b/vendor/src/github.com/endophage/gotuf/signed/errors.go index 7aec7c723..09ecc9a71 100644 --- a/vendor/src/github.com/endophage/gotuf/signed/errors.go +++ b/vendor/src/github.com/endophage/gotuf/signed/errors.go @@ -27,3 +27,17 @@ type ErrRoleThreshold struct{} func (e ErrRoleThreshold) Error() string { return "valid signatures did not meet threshold" } + +type ErrInvalidKeyType struct{} + +func (e ErrInvalidKeyType) Error() string { + return "key type is not valid for signature" +} + +type ErrInvalidKeyLength struct { + msg string +} + +func (e ErrInvalidKeyLength) Error() string { + return fmt.Sprintf("key length is not supported: %s", e.msg) +} diff --git a/vendor/src/github.com/endophage/gotuf/signed/verifiers.go b/vendor/src/github.com/endophage/gotuf/signed/verifiers.go index fd919035d..e11eb4ad6 100644 --- a/vendor/src/github.com/endophage/gotuf/signed/verifiers.go +++ b/vendor/src/github.com/endophage/gotuf/signed/verifiers.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "crypto/x509" "encoding/pem" + "fmt" "math/big" "reflect" @@ -15,6 +16,11 @@ import ( "github.com/endophage/gotuf/data" ) +const ( + minRSAKeySizeBit = 2048 // 2048 bits = 256 bytes + minRSAKeySizeByte = minRSAKeySizeBit / 8 +) + // Verifiers serves as a map of all verifiers available on the system and // can be injected into a verificationService. For testing and configuration // purposes, it will not be used by default. @@ -47,15 +53,27 @@ func RegisterVerifier(algorithm data.SigAlgorithm, v Verifier) { type Ed25519Verifier struct{} func (v Ed25519Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) error { + if key.Algorithm() != data.ED25519Key { + return ErrInvalidKeyType{} + } var sigBytes [ed25519.SignatureSize]byte - if len(sig) != len(sigBytes) { + if len(sig) != ed25519.SignatureSize { logrus.Infof("signature length is incorrect, must be %d, was %d.", ed25519.SignatureSize, len(sig)) return ErrInvalid } copy(sigBytes[:], sig) var keyBytes [ed25519.PublicKeySize]byte - copy(keyBytes[:], key.Public()) + pub := key.Public() + if len(pub) != ed25519.PublicKeySize { + logrus.Errorf("public key is incorrect size, must be %d, was %d.", ed25519.PublicKeySize, len(pub)) + return ErrInvalidKeyLength{msg: fmt.Sprintf("ed25519 public key must be %d bytes.", ed25519.PublicKeySize)} + } + n := copy(keyBytes[:], key.Public()) + if n < ed25519.PublicKeySize { + logrus.Errorf("failed to copy the key, must have %d bytes, copied %d bytes.", ed25519.PublicKeySize, n) + return ErrInvalid + } if !ed25519.Verify(&keyBytes, msg, &sigBytes) { logrus.Infof("failed ed25519 verification") @@ -71,6 +89,16 @@ func verifyPSS(key interface{}, digest, sig []byte) error { return ErrInvalid } + if rsaPub.N.BitLen() < minRSAKeySizeBit { + logrus.Infof("RSA keys less than 2048 bits are not acceptable, provided key has length %d.", rsaPub.N.BitLen()) + return ErrInvalidKeyLength{msg: fmt.Sprintf("RSA key must be at least %d bits.", minRSAKeySizeBit)} + } + + if len(sig) < minRSAKeySizeByte { + logrus.Infof("RSA keys less than 2048 bits are not acceptable, provided signature has length %d.", len(sig)) + return ErrInvalid + } + opts := rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256} if err := rsa.VerifyPSS(rsaPub, crypto.SHA256, digest[:], sig, &opts); err != nil { logrus.Infof("failed RSAPSS verification: %s", err) @@ -104,8 +132,9 @@ func getRSAPubKey(key data.PublicKey) (crypto.PublicKey, error) { return nil, ErrInvalid } default: + // only accept RSA keys logrus.Infof("invalid key type for RSAPSS verifier: %s", algorithm) - return nil, ErrInvalid + return nil, ErrInvalidKeyType{} } return pubKey, nil @@ -116,6 +145,7 @@ type RSAPSSVerifier struct{} // Verify does the actual check. func (v RSAPSSVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error { + // will return err if keytype is not a recognized RSA type pubKey, err := getRSAPubKey(key) if err != nil { return err @@ -130,6 +160,7 @@ func (v RSAPSSVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error type RSAPKCS1v15Verifier struct{} func (v RSAPKCS1v15Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) error { + // will return err if keytype is not a recognized RSA type pubKey, err := getRSAPubKey(key) if err != nil { return err @@ -142,6 +173,16 @@ func (v RSAPKCS1v15Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) return ErrInvalid } + if rsaPub.N.BitLen() < minRSAKeySizeBit { + logrus.Infof("RSA keys less than 2048 bits are not acceptable, provided key has length %d.", rsaPub.N.BitLen()) + return ErrInvalidKeyLength{msg: fmt.Sprintf("RSA key must be at least %d bits.", minRSAKeySizeBit)} + } + + if len(sig) < minRSAKeySizeByte { + logrus.Infof("RSA keys less than 2048 bits are not acceptable, provided signature has length %d.", len(sig)) + return ErrInvalid + } + if err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA256, digest[:], sig); err != nil { logrus.Errorf("Failed verification: %s", err.Error()) return ErrInvalid @@ -157,6 +198,9 @@ type RSAPyCryptoVerifier struct{} // with PyCrypto. func (v RSAPyCryptoVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error { digest := sha256.Sum256(msg) + if key.Algorithm() != data.RSAKey { + return ErrInvalidKeyType{} + } k, _ := pem.Decode([]byte(key.Public())) if k == nil { @@ -203,8 +247,9 @@ func (v ECDSAVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error return ErrInvalid } default: + // only accept ECDSA keys. logrus.Infof("invalid key type for ECDSA verifier: %s", algorithm) - return ErrInvalid + return ErrInvalidKeyType{} } ecdsaPubKey, ok := pubKey.(*ecdsa.PublicKey) diff --git a/vendor/src/github.com/endophage/gotuf/signed/verify.go b/vendor/src/github.com/endophage/gotuf/signed/verify.go index f6b6d9167..fe79563f3 100644 --- a/vendor/src/github.com/endophage/gotuf/signed/verify.go +++ b/vendor/src/github.com/endophage/gotuf/signed/verify.go @@ -22,9 +22,9 @@ var ( ) type signedMeta struct { - Type string `json:"_type"` - Expires string `json:"expires"` - Version int `json:"version"` + Type string `json:"_type"` + Expires time.Time `json:"expires"` + Version int `json:"version"` } // VerifyRoot checks if a given root file is valid against a known set of keys. @@ -80,12 +80,12 @@ func verifyMeta(s *data.Signed, role string, minVersion int) error { if err := json.Unmarshal(s.Signed, sm); err != nil { return err } - if !data.ValidTUFType(sm.Type) { + if !data.ValidTUFType(sm.Type, role) { return ErrWrongType } if IsExpired(sm.Expires) { logrus.Errorf("Metadata for %s expired", role) - return ErrExpired{Role: role, Expired: sm.Expires} + return ErrExpired{Role: role, Expired: sm.Expires.Format("Mon Jan 2 15:04:05 MST 2006")} } if sm.Version < minVersion { return ErrLowVersion{sm.Version, minVersion} @@ -94,15 +94,8 @@ func verifyMeta(s *data.Signed, role string, minVersion int) error { return nil } -var IsExpired = func(t string) bool { - ts, err := time.Parse(time.RFC3339, t) - if err != nil { - ts, err = time.Parse("2006-01-02 15:04:05 MST", t) - if err != nil { - return false - } - } - return ts.Sub(time.Now()) <= 0 +var IsExpired = func(t time.Time) bool { + return t.Before(time.Now()) } func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error { From a16ab243e578fdefab6bdf89d0db201bb14be489 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Fri, 31 Jul 2015 15:01:50 -0700 Subject: [PATCH 39/79] Updated to use latest version of notary Update UX to use aliases for root, snapshot, and target key Signed-off-by: Derek McGowan (github: dmcgowan) (cherry picked from commit 6ce76cd9ed0e014e109ec632fa21b7e853f56951) --- api/client/trust.go | 37 +++++++++++++++++++------ integration-cli/docker_cli_push_test.go | 2 +- integration-cli/trust_server.go | 18 ++++++------ 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/api/client/trust.go b/api/client/trust.go index b07cb79dc..4d984cfa6 100644 --- a/api/client/trust.go +++ b/api/client/trust.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" "regexp" + "sort" "strconv" "strings" "time" @@ -176,11 +177,16 @@ func convertTarget(t client.Target) (target, error) { } func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever { - baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out) + aliasMap := map[string]string{ + "root": "offline", + "snapshot": "tagging", + "targets": "tagging", + } + baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap) env := map[string]string{ - "root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"), - "targets": os.Getenv("DOCKER_CONTENT_TRUST_TARGET_PASSPHRASE"), - "snapshot": os.Getenv("DOCKER_CONTENT_TRUST_SNAPSHOT_PASSPHRASE"), + "root": os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"), + "snapshot": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"), + "targets": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"), } return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) { if v := env[alias]; v != "" { @@ -311,6 +317,22 @@ func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registr return nil } +func selectKey(keys map[string]string) string { + if len(keys) == 0 { + return "" + } + + keyIDs := []string{} + for k := range keys { + keyIDs = append(keyIDs, k) + } + + // TODO(dmcgowan): let user choose if multiple keys, now pick consistently + sort.Strings(keyIDs) + + return keyIDs[0] +} + func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) { r, w := io.Pipe() out := io.MultiWriter(in, w) @@ -409,16 +431,13 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, ks := repo.KeyStoreManager keys := ks.RootKeyStore().ListKeys() - var rootKey string - if len(keys) == 0 { + rootKey := selectKey(keys) + if rootKey == "" { rootKey, err = ks.GenRootKey("ecdsa") if err != nil { return err } - } else { - // TODO(dmcgowan): let user choose - rootKey = keys[0] } cryptoService, err := ks.GetRootCryptoService(rootKey) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index ee9570a27..41c034684 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -275,7 +275,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithIncorrectPassphraseForNonRoot(c *c // Push with wrong passphrases pushCmd = exec.Command(dockerBinary, "push", repoName) - s.trustedCmdWithPassphrases(pushCmd, "12345678", "87654321", "87654321") + s.trustedCmdWithPassphrases(pushCmd, "12345678", "87654321") out, _, err = runCommandWithOutput(pushCmd) if err == nil { c.Fatalf("Error missing from trusted push with short targets passphrase: \n%s", out) diff --git a/integration-cli/trust_server.go b/integration-cli/trust_server.go index fbdb573f4..89d88a84b 100644 --- a/integration-cli/trust_server.go +++ b/integration-cli/trust_server.go @@ -32,7 +32,8 @@ func newTestNotary(c *check.C) (*testNotary, error) { "trust_service": { "type": "local", "hostname": "", - "port": "" + "port": "", + "key_algorithm": "ed25519" }, "logging": { "level": 5 @@ -116,25 +117,24 @@ func (t *testNotary) Close() { func (s *DockerTrustSuite) trustedCmd(cmd *exec.Cmd) { pwd := "12345678" - trustCmdEnv(cmd, s.not.address(), pwd, pwd, pwd) + trustCmdEnv(cmd, s.not.address(), pwd, pwd) } func (s *DockerTrustSuite) trustedCmdWithServer(cmd *exec.Cmd, server string) { pwd := "12345678" - trustCmdEnv(cmd, server, pwd, pwd, pwd) + trustCmdEnv(cmd, server, pwd, pwd) } -func (s *DockerTrustSuite) trustedCmdWithPassphrases(cmd *exec.Cmd, rootPwd, snapshotPwd, targetPwd string) { - trustCmdEnv(cmd, s.not.address(), rootPwd, snapshotPwd, targetPwd) +func (s *DockerTrustSuite) trustedCmdWithPassphrases(cmd *exec.Cmd, offlinePwd, taggingPwd string) { + trustCmdEnv(cmd, s.not.address(), offlinePwd, taggingPwd) } -func trustCmdEnv(cmd *exec.Cmd, server, rootPwd, snapshotPwd, targetPwd string) { +func trustCmdEnv(cmd *exec.Cmd, server, offlinePwd, taggingPwd string) { env := []string{ "DOCKER_CONTENT_TRUST=1", fmt.Sprintf("DOCKER_CONTENT_TRUST_SERVER=%s", server), - fmt.Sprintf("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE=%s", rootPwd), - fmt.Sprintf("DOCKER_CONTENT_TRUST_SNAPSHOT_PASSPHRASE=%s", snapshotPwd), - fmt.Sprintf("DOCKER_CONTENT_TRUST_TARGET_PASSPHRASE=%s", targetPwd), + fmt.Sprintf("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE=%s", offlinePwd), + fmt.Sprintf("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE=%s", taggingPwd), } cmd.Env = append(os.Environ(), env...) } From fbd2267e7dcdb339bbd2d485296ce8e4610da512 Mon Sep 17 00:00:00 2001 From: Charles Chan Date: Sun, 2 Aug 2015 09:50:25 -0700 Subject: [PATCH 40/79] Update docker_remote_api.md Minor fixes: * v1.19: GET /containers/(id)/logs - add missing '/' * v1.18: Break up POST /containers/create and POST /containers/(id)/start into separate lines. Signed-off-by: Charles Chan (cherry picked from commit 084d46408160cf309bb7730a0ccb7fdd2f71c84e) --- docs/reference/api/docker_remote_api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index 6ed93afb2..fd00a8f55 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -108,7 +108,7 @@ of a 404. You can now supply a `stream` bool to get only one set of stats and disconnect -`GET /containers(id)/logs` +`GET /containers/(id)/logs` **New!** @@ -138,6 +138,7 @@ In addition, the end point now returns the new boolean fields This endpoint now returns `Os`, `Arch` and `KernelVersion`. `POST /containers/create` + `POST /containers/(id)/start` **New!** From 183628388c929fd49edf733112b95a48f9f814fa Mon Sep 17 00:00:00 2001 From: evalle Date: Mon, 3 Aug 2015 12:01:25 +0200 Subject: [PATCH 41/79] Fix wrong path to YaST's 'Routing' menu for openSUSE Tumbleweed Signed-off-by: evalle (cherry picked from commit 5f1593c7b8cba267fb044156e152b7afcbcc2cd4) --- docs/installation/SUSE.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/installation/SUSE.md b/docs/installation/SUSE.md index 18c075733..b16e41749 100644 --- a/docs/installation/SUSE.md +++ b/docs/installation/SUSE.md @@ -64,8 +64,7 @@ a container. To exit the container type `exit`. If you want your containers to be able to access the external network you must enable the `net.ipv4.ip_forward` rule. This can be done using YaST by browsing to the -`Network Devices -> Network Settings -> Routing` menu and ensuring that the -`Enable IPv4 Forwarding` box is checked. +`System -> Network Settings -> Routing` menu (for openSUSE Tumbleweed and later) or `Network Devices -> Network Settings -> Routing` menu (for SUSE Linux Enterprise 12 and previous openSUSE versions) and ensuring that the `Enable IPv4 Forwarding` box is checked. This option cannot be changed when networking is handled by the Network Manager. In such cases the `/etc/sysconfig/SuSEfirewall2` file needs to be edited by From 60cbf4da6c775a8ffe834431c6e693030bed9b8c Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Thu, 30 Jul 2015 21:14:39 -0400 Subject: [PATCH 42/79] Vendor docker/distribution to 7dc8d4a26b689bd4892f2f2322dbce0b7119d686 Signed-off-by: Tibor Vass (cherry picked from commit 0cce1fb37ffbcb209826fdc98c3148fc5b1e2abd) --- hack/vendor.sh | 2 +- .../src/github.com/docker/distribution/Dockerfile | 1 + .../docker/distribution/context/context.go | 13 +++++++++++-- .../docker/distribution/context/logger.go | 7 ------- .../distribution/registry/api/v2/descriptors.go | 2 +- .../src/github.com/docker/distribution/uuid/uuid.go | 7 ++++--- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 28997e922..487616196 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -35,7 +35,7 @@ clone git github.com/coreos/go-etcd v2.0.0 clone git github.com/hashicorp/consul v0.5.2 # get graph and distribution packages -clone git github.com/docker/distribution e83345626608aa943d5c8a027fddcf54814d9545 +clone git github.com/docker/distribution 7dc8d4a26b689bd4892f2f2322dbce0b7119d686 clone git github.com/vbatts/tar-split v0.9.4 clone git github.com/docker/notary 8e8122eb5528f621afcd4e2854c47302f17392f7 diff --git a/vendor/src/github.com/docker/distribution/Dockerfile b/vendor/src/github.com/docker/distribution/Dockerfile index 66e568e42..5555606fa 100644 --- a/vendor/src/github.com/docker/distribution/Dockerfile +++ b/vendor/src/github.com/docker/distribution/Dockerfile @@ -10,6 +10,7 @@ ENV DOCKER_BUILDTAGS include_rados WORKDIR $DISTRIBUTION_DIR COPY . $DISTRIBUTION_DIR +COPY cmd/registry/config-dev.yml $DISTRIBUTION_DIR/cmd/registry/config.yml RUN make PREFIX=/go clean binaries VOLUME ["/var/lib/registry"] diff --git a/vendor/src/github.com/docker/distribution/context/context.go b/vendor/src/github.com/docker/distribution/context/context.go index 7a3a70e00..23cbf5b54 100644 --- a/vendor/src/github.com/docker/distribution/context/context.go +++ b/vendor/src/github.com/docker/distribution/context/context.go @@ -1,6 +1,8 @@ package context import ( + "sync" + "github.com/docker/distribution/uuid" "golang.org/x/net/context" ) @@ -14,11 +16,19 @@ type Context interface { // provided as the main background context. type instanceContext struct { Context - id string // id of context, logged as "instance.id" + id string // id of context, logged as "instance.id" + once sync.Once // once protect generation of the id } func (ic *instanceContext) Value(key interface{}) interface{} { if key == "instance.id" { + ic.once.Do(func() { + // We want to lazy initialize the UUID such that we don't + // call a random generator from the package initialization + // code. For various reasons random could not be available + // https://github.com/docker/distribution/issues/782 + ic.id = uuid.Generate().String() + }) return ic.id } @@ -27,7 +37,6 @@ func (ic *instanceContext) Value(key interface{}) interface{} { var background = &instanceContext{ Context: context.Background(), - id: uuid.Generate().String(), } // Background returns a non-nil, empty Context. The background context diff --git a/vendor/src/github.com/docker/distribution/context/logger.go b/vendor/src/github.com/docker/distribution/context/logger.go index b0f0c5084..78e4212a0 100644 --- a/vendor/src/github.com/docker/distribution/context/logger.go +++ b/vendor/src/github.com/docker/distribution/context/logger.go @@ -3,8 +3,6 @@ package context import ( "fmt" - "github.com/docker/distribution/uuid" - "github.com/Sirupsen/logrus" ) @@ -101,8 +99,3 @@ func getLogrusLogger(ctx Context, keys ...interface{}) *logrus.Entry { return logger.WithFields(fields) } - -func init() { - // inject a logger into the uuid library. - uuid.Loggerf = GetLogger(Background()).Warnf -} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go index 635cb7f90..74bdb9f2e 100644 --- a/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go @@ -607,7 +607,7 @@ var routeDescriptors = []RouteDescriptor{ Successes: []ResponseDescriptor{ { Description: "The manifest has been accepted by the registry and is stored under the specified `name` and `tag`.", - StatusCode: http.StatusAccepted, + StatusCode: http.StatusCreated, Headers: []ParameterDescriptor{ { Name: "Location", diff --git a/vendor/src/github.com/docker/distribution/uuid/uuid.go b/vendor/src/github.com/docker/distribution/uuid/uuid.go index 4bdd9700a..d433ccaf5 100644 --- a/vendor/src/github.com/docker/distribution/uuid/uuid.go +++ b/vendor/src/github.com/docker/distribution/uuid/uuid.go @@ -8,7 +8,6 @@ import ( "crypto/rand" "fmt" "io" - "log" "os" "syscall" "time" @@ -30,7 +29,7 @@ var ( // Loggerf can be used to override the default logging destination. Such // log messages in this library should be logged at warning or higher. - Loggerf = log.Printf + Loggerf = func(format string, args ...interface{}) {} ) // UUID represents a UUID value. UUIDs can be compared and set to other values @@ -49,6 +48,7 @@ func Generate() (u UUID) { var ( totalBackoff time.Duration + count int retries int ) @@ -60,9 +60,10 @@ func Generate() (u UUID) { time.Sleep(b) totalBackoff += b - _, err := io.ReadFull(rand.Reader, u[:]) + n, err := io.ReadFull(rand.Reader, u[count:]) if err != nil { if retryOnError(err) && retries < maxretries { + count += n retries++ Loggerf("error generating version 4 uuid, retrying: %v", err) continue From 53f5905379783135547fb15c74e0285cbc513ba8 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Thu, 30 Jul 2015 21:19:18 -0400 Subject: [PATCH 43/79] Add missing LICENSE files for docker/notary Signed-off-by: Tibor Vass (cherry picked from commit 084af30f39ce4f00d2e3c379b8c5cc4f4d345ee0) --- .../docker/notary/notarymysql/LICENSE | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 vendor/src/github.com/docker/notary/notarymysql/LICENSE diff --git a/vendor/src/github.com/docker/notary/notarymysql/LICENSE b/vendor/src/github.com/docker/notary/notarymysql/LICENSE new file mode 100644 index 000000000..c8476ac06 --- /dev/null +++ b/vendor/src/github.com/docker/notary/notarymysql/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Sameer Naik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From d19b1b927ba540aa865eef67c3041de948e0e977 Mon Sep 17 00:00:00 2001 From: jrabbit Date: Thu, 30 Jul 2015 21:16:45 -0400 Subject: [PATCH 44/79] Actually link to the information Signed-off-by: jrabbit (cherry picked from commit d2cd142ce1da96589902d07fddf3a12e63631d78) --- docs/articles/host_integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/host_integration.md b/docs/articles/host_integration.md index 531709619..673772172 100644 --- a/docs/articles/host_integration.md +++ b/docs/articles/host_integration.md @@ -12,7 +12,7 @@ weight = 99 # Automatically start containers As of Docker 1.2, -[restart policies](/reference/commandline/cli/#restart-policies) are the +[restart policies](/reference/run/#restart-policies-restart) are the built-in Docker mechanism for restarting containers when they exit. If set, restart policies will be used when the Docker daemon starts up, as typically happens after a system boot. Restart policies will ensure that linked containers From 948912f6923a69682fc6ce4252146b290698442e Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Sat, 1 Aug 2015 08:36:00 -0700 Subject: [PATCH 45/79] Filter completions of `docker inspect` by `--type` Completion now filters the images and containers by given `--type`. Signed-off-by: Harald Albers (cherry picked from commit 69cde5a3024b04a59125512ebe9c504ebea2c1ac) --- contrib/completion/bash/docker | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 59d8a869e..dcccd142d 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -738,8 +738,17 @@ _docker_inspect() { COMPREPLY=( $( compgen -W "--format -f --type --help" -- "$cur" ) ) ;; *) - __docker_containers_and_images - ;; + case $(__docker_value_of_option --type) in + '') + __docker_containers_and_images + ;; + container) + __docker_containers_all + ;; + image) + __docker_image_repos_and_tags_and_ids + ;; + esac esac } From c383ceaf37106e743ccc8683f3e8f2972693bcdc Mon Sep 17 00:00:00 2001 From: Stephen Rust Date: Sun, 2 Aug 2015 12:16:07 -0400 Subject: [PATCH 46/79] Docs: Add Blockbridge volume plugin Signed-off-by: Stephen Rust (cherry picked from commit 8b15b7958a9bb1298f8d82676c1ddef47fdc054d) --- docs/extend/plugins.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/extend/plugins.md b/docs/extend/plugins.md index 4f3af1546..6bfb0053c 100644 --- a/docs/extend/plugins.md +++ b/docs/extend/plugins.md @@ -31,6 +31,12 @@ Follow the instructions in the plugin's documentation. The following plugins exist: +* The [Blockbridge plugin](https://github.com/blockbridge/blockbridge-docker-volume) + is a volume plugin that provides access to an extensible set of + container-based persistent storage options. It supports single and multi-host Docker + environments with features that include tenant isolation, automated + provisioning, encryption, secure deletion, snapshots and QoS. + * The [Flocker plugin](https://clusterhq.com/docker-plugin/) is a volume plugin which provides multi-host portable volumes for Docker, enabling you to run databases and other stateful containers and move them around across a cluster From d9581e861dca55ce018b2e0f51807bdadd570fc3 Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Fri, 31 Jul 2015 23:27:19 -0700 Subject: [PATCH 47/79] [graph] Enforce manifest/layer digest verification We noticed a regression since the 1.7.1 patch after some refactoring. This patch corrects the behavior and adds integration tests for modified manifest and rootfs layer blobs. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) (cherry picked from commit de52a3bcaa1742054be184cc3465f0933f6d383a) --- graph/pull_v2.go | 60 +++++----- integration-cli/docker_cli_by_digest_test.go | 116 +++++++++++++++++-- integration-cli/registry.go | 48 ++++++++ 3 files changed, 187 insertions(+), 37 deletions(-) diff --git a/graph/pull_v2.go b/graph/pull_v2.go index a70529754..e5d760e68 100644 --- a/graph/pull_v2.go +++ b/graph/pull_v2.go @@ -102,13 +102,12 @@ func (p *v2Puller) pullV2Repository(tag string) (err error) { // downloadInfo is used to pass information from download to extractor type downloadInfo struct { - img *image.Image - tmpFile *os.File - digest digest.Digest - layer distribution.ReadSeekCloser - size int64 - err chan error - verified bool + img *image.Image + tmpFile *os.File + digest digest.Digest + layer distribution.ReadSeekCloser + size int64 + err chan error } type errVerification struct{} @@ -176,9 +175,11 @@ func (p *v2Puller) download(di *downloadInfo) { out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Verifying Checksum", nil)) - di.verified = verifier.Verified() - if !di.verified { - logrus.Infof("Image verification failed for layer %s", di.digest) + if !verifier.Verified() { + err = fmt.Errorf("filesystem layer verification failed for digest %s", di.digest) + logrus.Error(err) + di.err <- err + return } out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Download complete", nil)) @@ -252,7 +253,6 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) { return false, err } } - verified = verified && d.verified if d.layer != nil { // if tmpFile is empty assume download and extracted elsewhere defer os.Remove(d.tmpFile.Name()) @@ -368,6 +368,28 @@ func (p *v2Puller) verifyTrustedKeys(namespace string, keys []libtrust.PublicKey } func (p *v2Puller) validateManifest(m *manifest.SignedManifest, tag string) (verified bool, err error) { + // If pull by digest, then verify the manifest digest. NOTE: It is + // important to do this first, before any other content validation. If the + // digest cannot be verified, don't even bother with those other things. + if manifestDigest, err := digest.ParseDigest(tag); err == nil { + verifier, err := digest.NewDigestVerifier(manifestDigest) + if err != nil { + return false, err + } + payload, err := m.Payload() + if err != nil { + return false, err + } + if _, err := verifier.Write(payload); err != nil { + return false, err + } + if !verifier.Verified() { + err := fmt.Errorf("image verification failed for digest %s", manifestDigest) + logrus.Error(err) + return false, err + } + } + // TODO(tiborvass): what's the usecase for having manifest == nil and err == nil ? Shouldn't be the error be "DoesNotExist" ? if m == nil { return false, fmt.Errorf("image manifest does not exist for tag %q", tag) @@ -389,21 +411,5 @@ func (p *v2Puller) validateManifest(m *manifest.SignedManifest, tag string) (ver if err != nil { return false, fmt.Errorf("error verifying manifest keys: %v", err) } - localDigest, err := digest.ParseDigest(tag) - // if pull by digest, then verify - if err == nil { - verifier, err := digest.NewDigestVerifier(localDigest) - if err != nil { - return false, err - } - payload, err := m.Payload() - if err != nil { - return false, err - } - if _, err := verifier.Write(payload); err != nil { - return false, err - } - verified = verified && verifier.Verified() - } return verified, nil } diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index cbc6dc1ba..4d94d5b5d 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -1,25 +1,29 @@ package main import ( + "encoding/json" "fmt" "regexp" "strings" + "github.com/docker/distribution/digest" + "github.com/docker/distribution/manifest" "github.com/docker/docker/utils" "github.com/go-check/check" ) var ( - repoName = fmt.Sprintf("%v/dockercli/busybox-by-dgst", privateRegistryURL) + remoteRepoName = "dockercli/busybox-by-dgst" + repoName = fmt.Sprintf("%v/%s", privateRegistryURL, remoteRepoName) pushDigestRegex = regexp.MustCompile("[\\S]+: digest: ([\\S]+) size: [0-9]+") digestRegex = regexp.MustCompile("Digest: ([\\S]+)") ) -func setupImage(c *check.C) (string, error) { +func setupImage(c *check.C) (digest.Digest, error) { return setupImageWithTag(c, "latest") } -func setupImageWithTag(c *check.C, tag string) (string, error) { +func setupImageWithTag(c *check.C, tag string) (digest.Digest, error) { containerName := "busyboxbydigest" dockerCmd(c, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox") @@ -52,7 +56,7 @@ func setupImageWithTag(c *check.C, tag string) (string, error) { } pushDigest := matches[1] - return pushDigest, nil + return digest.Digest(pushDigest), nil } func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) { @@ -72,7 +76,7 @@ func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) { pullDigest := matches[1] // make sure the pushed and pull digests match - if pushDigest != pullDigest { + if pushDigest.String() != pullDigest { c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) } } @@ -95,7 +99,7 @@ func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) { pullDigest := matches[1] // make sure the pushed and pull digests match - if pushDigest != pullDigest { + if pushDigest.String() != pullDigest { c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) } } @@ -291,7 +295,7 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) { out, _ := dockerCmd(c, "images", "--digests") // make sure repo shown, tag=, digest = $digest1 - re1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1 + `\s`) + re1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1.String() + `\s`) if !re1.MatchString(out) { c.Fatalf("expected %q: %s", re1.String(), out) } @@ -319,7 +323,7 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) { } // make sure repo shown, tag=, digest = $digest2 - re2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2 + `\s`) + re2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2.String() + `\s`) if !re2.MatchString(out) { c.Fatalf("expected %q: %s", re2.String(), out) } @@ -332,7 +336,7 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) { // make sure image 1 has repo, tag, AND repo, , digest reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*\s`) - reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1 + `\s`) + reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1.String() + `\s`) if !reWithTag1.MatchString(out) { c.Fatalf("expected %q: %s", reWithTag1.String(), out) } @@ -357,7 +361,7 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) { // make sure image 2 has repo, tag, digest reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*\s`) - reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2 + `\s`) + reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2.String() + `\s`) if !reWithTag2.MatchString(out) { c.Fatalf("expected %q: %s", reWithTag2.String(), out) } @@ -401,3 +405,95 @@ func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) dockerCmd(c, "rmi", imageID) } + +// TestPullFailsWithAlteredManifest tests that a `docker pull` fails when +// we have modified a manifest blob and its digest cannot be verified. +func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) { + manifestDigest, err := setupImage(c) + if err != nil { + c.Fatalf("error setting up image: %v", err) + } + + // Load the target manifest blob. + manifestBlob := s.reg.readBlobContents(c, manifestDigest) + + var imgManifest manifest.Manifest + if err := json.Unmarshal(manifestBlob, &imgManifest); err != nil { + c.Fatalf("unable to decode image manifest from blob: %s", err) + } + + // Add a malicious layer digest to the list of layers in the manifest. + imgManifest.FSLayers = append(imgManifest.FSLayers, manifest.FSLayer{ + BlobSum: digest.Digest("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + }) + + // Move the existing data file aside, so that we can replace it with a + // malicious blob of data. NOTE: we defer the returned undo func. + undo := s.reg.tempMoveBlobData(c, manifestDigest) + defer undo() + + alteredManifestBlob, err := json.Marshal(imgManifest) + if err != nil { + c.Fatalf("unable to encode altered image manifest to JSON: %s", err) + } + + s.reg.writeBlobContents(c, manifestDigest, alteredManifestBlob) + + // Now try pulling that image by digest. We should get an error about + // digest verification for the manifest digest. + + // Pull from the registry using the @ reference. + imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest) + out, exitStatus, _ := dockerCmdWithError(c, "pull", imageReference) + if exitStatus == 0 { + c.Fatalf("expected a zero exit status but got %d: %s", exitStatus, out) + } + + expectedErrorMsg := fmt.Sprintf("image verification failed for digest %s", manifestDigest) + if !strings.Contains(out, expectedErrorMsg) { + c.Fatalf("expected error message %q in output: %s", expectedErrorMsg, out) + } +} + +// TestPullFailsWithAlteredLayer tests that a `docker pull` fails when +// we have modified a layer blob and its digest cannot be verified. +func (s *DockerRegistrySuite) TestPullFailsWithAlteredLayer(c *check.C) { + manifestDigest, err := setupImage(c) + if err != nil { + c.Fatalf("error setting up image: %v", err) + } + + // Load the target manifest blob. + manifestBlob := s.reg.readBlobContents(c, manifestDigest) + + var imgManifest manifest.Manifest + if err := json.Unmarshal(manifestBlob, &imgManifest); err != nil { + c.Fatalf("unable to decode image manifest from blob: %s", err) + } + + // Next, get the digest of one of the layers from the manifest. + targetLayerDigest := imgManifest.FSLayers[0].BlobSum + + // Move the existing data file aside, so that we can replace it with a + // malicious blob of data. NOTE: we defer the returned undo func. + undo := s.reg.tempMoveBlobData(c, targetLayerDigest) + defer undo() + + // Now make a fake data blob in this directory. + s.reg.writeBlobContents(c, targetLayerDigest, []byte("This is not the data you are looking for.")) + + // Now try pulling that image by digest. We should get an error about + // digest verification for the target layer digest. + + // Pull from the registry using the @ reference. + imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest) + out, exitStatus, _ := dockerCmdWithError(c, "pull", imageReference) + if exitStatus == 0 { + c.Fatalf("expected a zero exit status but got: %d", exitStatus) + } + + expectedErrorMsg := fmt.Sprintf("filesystem layer verification failed for digest %s", targetLayerDigest) + if !strings.Contains(out, expectedErrorMsg) { + c.Fatalf("expected error message %q in output: %s", expectedErrorMsg, out) + } +} diff --git a/integration-cli/registry.go b/integration-cli/registry.go index ab44f0525..35e1b4eb9 100644 --- a/integration-cli/registry.go +++ b/integration-cli/registry.go @@ -8,6 +8,7 @@ import ( "os/exec" "path/filepath" + "github.com/docker/distribution/digest" "github.com/go-check/check" ) @@ -70,3 +71,50 @@ func (t *testRegistryV2) Close() { t.cmd.Process.Kill() os.RemoveAll(t.dir) } + +func (t *testRegistryV2) getBlobFilename(blobDigest digest.Digest) string { + // Split the digest into it's algorithm and hex components. + dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Hex() + + // The path to the target blob data looks something like: + // baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data" + return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", t.dir, dgstAlg, dgstHex[:2], dgstHex) +} + +func (t *testRegistryV2) readBlobContents(c *check.C, blobDigest digest.Digest) []byte { + // Load the target manifest blob. + manifestBlob, err := ioutil.ReadFile(t.getBlobFilename(blobDigest)) + if err != nil { + c.Fatalf("unable to read blob: %s", err) + } + + return manifestBlob +} + +func (t *testRegistryV2) writeBlobContents(c *check.C, blobDigest digest.Digest, data []byte) { + if err := ioutil.WriteFile(t.getBlobFilename(blobDigest), data, os.FileMode(0644)); err != nil { + c.Fatalf("unable to write malicious data blob: %s", err) + } +} + +func (t *testRegistryV2) tempMoveBlobData(c *check.C, blobDigest digest.Digest) (undo func()) { + tempFile, err := ioutil.TempFile("", "registry-temp-blob-") + if err != nil { + c.Fatalf("unable to get temporary blob file: %s", err) + } + tempFile.Close() + + blobFilename := t.getBlobFilename(blobDigest) + + // Move the existing data file aside, so that we can replace it with a + // another blob of data. + if err := os.Rename(blobFilename, tempFile.Name()); err != nil { + os.Remove(tempFile.Name()) + c.Fatalf("unable to move data blob: %s", err) + } + + return func() { + os.Rename(tempFile.Name(), blobFilename) + os.Remove(tempFile.Name()) + } +} From dfd9f5989a56b97ef234e3a72e9a02a96836c29c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 3 Aug 2015 09:46:50 -0700 Subject: [PATCH 48/79] Add LXC built in support deprecation notice Signed-off-by: Michael Crosby (cherry picked from commit 06f6c0c7e50ea0df588d1d873783f326c23f195a) --- daemon/execdriver/execdrivers/execdrivers_linux.go | 2 ++ docs/misc/deprecated.md | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/daemon/execdriver/execdrivers/execdrivers_linux.go b/daemon/execdriver/execdrivers/execdrivers_linux.go index 89dedc762..bbad30483 100644 --- a/daemon/execdriver/execdrivers/execdrivers_linux.go +++ b/daemon/execdriver/execdrivers/execdrivers_linux.go @@ -6,6 +6,7 @@ import ( "fmt" "path" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/execdriver/lxc" "github.com/docker/docker/daemon/execdriver/native" @@ -18,6 +19,7 @@ func NewDriver(name string, options []string, root, libPath, initPath string, sy // we want to give the lxc driver the full docker root because it needs // to access and write config and template files in /var/lib/docker/containers/* // to be backwards compatible + logrus.Warn("LXC built-in support is deprecated.") return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor) case "native": return native.NewDriver(path.Join(root, "execdriver", "native"), initPath, options) diff --git a/docs/misc/deprecated.md b/docs/misc/deprecated.md index 7c327fde5..d943491a1 100644 --- a/docs/misc/deprecated.md +++ b/docs/misc/deprecated.md @@ -12,6 +12,14 @@ parent = "mn_use_docker" The following list of features are deprecated. +### LXC built-in exec driver +**Deprecated In Release: v1.8** + +**Target For Removal In Release: v1.10** + +The built-in LXC execution driver is deprecated for an external implementation. +The lxc-conf flag and API fields will also be removed. + ### Old Command Line Options **Deprecated In Release: [v1.8.0](/release-notes/#docker-engine-1-8-0)** From 1bf8954d0d28a82f713d5c3e9172ec97be2940b1 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 3 Aug 2015 15:29:54 -0700 Subject: [PATCH 49/79] Remove key file when migration fails. Signed-off-by: David Calavera (cherry picked from commit 07c45e499d5dc04b009d522ebbdcbfebe7e7aba9) --- docker/daemon.go | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/daemon.go b/docker/daemon.go index 24607042c..a1c7dafa4 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -100,6 +100,7 @@ func migrateKey() (err error) { err = os.Remove(oldPath) } else { logrus.Warnf("Key migration failed, key file not removed at %s", oldPath) + os.Remove(newPath) } }() From fc7697b050cdd325b26fa9d61239e1a1f12dc749 Mon Sep 17 00:00:00 2001 From: Alessandro Boch Date: Mon, 3 Aug 2015 16:37:01 -0700 Subject: [PATCH 50/79] Fix preallocated bridge networks - Because of a bug, all the statically preallocated bridge networks have /24 as network mask. Signed-off-by: Alessandro Boch (cherry picked from commit dab0447ae0c6d1355cf4708743973121cd55462a) --- hack/vendor.sh | 2 +- .../github.com/docker/libnetwork/drivers/bridge/setup_ipv4.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 487616196..9c7c68c69 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -21,7 +21,7 @@ clone git golang.org/x/net 3cffabab72adf04f8e3b01c5baf775361837b5fe https://gith clone hg code.google.com/p/gosqlite 74691fb6f837 #get libnetwork packages -clone git github.com/docker/libnetwork 31139cdb513aea5ad1ed08b60d4350a68b4c96db +clone git github.com/docker/libnetwork 78fc31ddc425fb379765c6b7ab5b96748bd8fc08 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4 diff --git a/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_ipv4.go b/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_ipv4.go index ac4535adb..91a9a6bcf 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_ipv4.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_ipv4.go @@ -31,9 +31,9 @@ func init() { bridgeNetworks = append(bridgeNetworks, &net.IPNet{IP: []byte{10, byte(i), 42, 1}, Mask: mask}) } // 192.168.[42-44].1/24 - mask[2] = 255 + mask24 := []byte{255, 255, 255, 0} for i := 42; i < 45; i++ { - bridgeNetworks = append(bridgeNetworks, &net.IPNet{IP: []byte{192, 168, byte(i), 1}, Mask: mask}) + bridgeNetworks = append(bridgeNetworks, &net.IPNet{IP: []byte{192, 168, byte(i), 1}, Mask: mask24}) } } From 9eff33735af2655fd5b404026b039b5a79e8d851 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Wed, 29 Jul 2015 16:57:14 -0400 Subject: [PATCH 51/79] Fix the proc integration test & include missing AA profile Integration tests were failing due to proc filter behavior changes with new apparmor policies. Also include the missing docker-unconfined policy resolving potential startup errors. This policy is complain-only so it should behave identically to the standard unconfined policy, but will not apply system path-based policies within containers. Signed-off-by: Eric Windisch (cherry picked from commit 5832715052e9e165cc40a5ac8178fa62685985aa) --- daemon/execdriver/native/apparmor.go | 18 ++++++++++++++++++ daemon/execdriver/native/create.go | 2 +- integration-cli/docker_cli_run_test.go | 13 ++++++++++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/daemon/execdriver/native/apparmor.go b/daemon/execdriver/native/apparmor.go index 9d166fb93..af8b4a4c6 100644 --- a/daemon/execdriver/native/apparmor.go +++ b/daemon/execdriver/native/apparmor.go @@ -40,6 +40,9 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { file, umount, + signal (receive) peer=/usr/bin/docker, + signal (receive) peer=docker-unconfined, + deny @{PROC}/sys/fs/** wklx, deny @{PROC}/fs/** wklx, deny @{PROC}/sysrq-trigger rwklx, @@ -60,6 +63,21 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { deny /sys/firmware/efi/efivars/** rwklx, deny /sys/kernel/security/** rwklx, } + +profile docker-unconfined flags=(attach_disconnected,mediate_deleted,complain) { + #include + + network, + capability, + file, + umount, + mount, + pivot_root, + change_profile -> *, + + ptrace, + signal, +} ` func generateProfile(out io.Writer) error { diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 0732a835d..a28681f2b 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -198,7 +198,7 @@ func (d *driver) setPrivileged(container *configs.Config) (err error) { container.Devices = hostDevices if apparmor.IsEnabled() { - container.AppArmorProfile = "unconfined" + container.AppArmorProfile = "docker-unconfined" } return nil diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index d76ca95b6..daea0ca10 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2440,7 +2440,11 @@ func (s *DockerSuite) TestRunReadFilteredProc(c *check.C) { name := fmt.Sprintf("procsieve-%d", i) shellCmd := fmt.Sprintf("exec 3<%s", filePath) - if out, exitCode, err := dockerCmdWithError(c, "run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd); err == nil || exitCode == 0 { + out, exitCode, err := dockerCmdWithError(c, "run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd) + if exitCode != 0 { + return + } + if err != nil { c.Fatalf("Open FD for read should have failed with permission denied, got: %s, %v", out, err) } } @@ -2545,8 +2549,11 @@ func (s *DockerSuite) TestRunWriteFilteredProc(c *check.C) { name := fmt.Sprintf("writeprocsieve-%d", i) shellCmd := fmt.Sprintf("exec 3>%s", filePath) - runCmd := exec.Command(dockerBinary, "run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd) - if out, exitCode, err := runCommandWithOutput(runCmd); err == nil || exitCode == 0 { + out, code, err := dockerCmdWithError(c, "run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd) + if code != 0 { + return + } + if err != nil { c.Fatalf("Open FD for write should have failed with permission denied, got: %s, %v", out, err) } } From 5851e2da600da4d5532beb76567f6509ca62422b Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Wed, 29 Jul 2015 17:45:32 -0400 Subject: [PATCH 52/79] Remove container AA profile from packaging Signed-off-by: Eric Windisch (cherry picked from commit 0f4e5f71491abc0e7c4f4effe1f3c59aa9bf6a33) --- hack/make/.build-deb/rules | 1 - hack/make/ubuntu | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/hack/make/.build-deb/rules b/hack/make/.build-deb/rules index ae2defcc0..be45676c1 100755 --- a/hack/make/.build-deb/rules +++ b/hack/make/.build-deb/rules @@ -34,7 +34,6 @@ override_dh_installudev: override_dh_install: dh_install - dh_apparmor --profile-name=docker -pdocker-engine dh_apparmor --profile-name=docker-engine -pdocker-engine %: diff --git a/hack/make/ubuntu b/hack/make/ubuntu index 76c3f2905..24246da76 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -74,8 +74,7 @@ bundle_ubuntu() { # Include contributed apparmor policy mkdir -p "$DIR/etc/apparmor.d/" - cp contrib/apparmor/docker "$DIR/etc/apparmor.d/" - cp contrib/apparmor/docker-engine "$DIR/etc/apparmor.d/" + cp contrib/apparmor/* "$DIR/etc/apparmor.d/" # Copy the binary # This will fail if the binary bundle hasn't been built @@ -95,7 +94,6 @@ if [ "$1" = 'configure' ] && [ -z "$2" ]; then fi if ( aa-status --enabled ); then - /sbin/apparmor_parser -r -W -T /etc/apparmor.d/docker /sbin/apparmor_parser -r -W -T /etc/apparmor.d/docker-engine fi From 5b06c94701f9dc1175f94da22acba6f2e33e9272 Mon Sep 17 00:00:00 2001 From: Charles Chan Date: Sun, 2 Aug 2015 11:47:13 -0700 Subject: [PATCH 53/79] Fix #15212: Add "Labels" key to output of /containers/json Applied retroactively from API v1.18 - v1.21. Signed-off-by: Charles Chan (cherry picked from commit b245bcd458492c9b348b790a9422dd7478f9af83) --- docs/reference/api/docker_remote_api_v1.18.md | 8 ++++++++ docs/reference/api/docker_remote_api_v1.19.md | 8 ++++++++ docs/reference/api/docker_remote_api_v1.20.md | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/docs/reference/api/docker_remote_api_v1.18.md b/docs/reference/api/docker_remote_api_v1.18.md index 1adf67650..8bdb98ff9 100644 --- a/docs/reference/api/docker_remote_api_v1.18.md +++ b/docs/reference/api/docker_remote_api_v1.18.md @@ -49,6 +49,11 @@ List containers "Created": 1367854155, "Status": "Exit 0", "Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "Labels": { + "com.example.vendor": "Acme", + "com.example.license": "GPL", + "com.example.version": "1.0" + }, "SizeRw": 12288, "SizeRootFs": 0 }, @@ -60,6 +65,7 @@ List containers "Created": 1367854155, "Status": "Exit 0", "Ports": [], + "Labels": {}, "SizeRw": 12288, "SizeRootFs": 0 }, @@ -71,6 +77,7 @@ List containers "Created": 1367854154, "Status": "Exit 0", "Ports":[], + "Labels": {}, "SizeRw":12288, "SizeRootFs":0 }, @@ -82,6 +89,7 @@ List containers "Created": 1367854152, "Status": "Exit 0", "Ports": [], + "Labels": {}, "SizeRw": 12288, "SizeRootFs": 0 } diff --git a/docs/reference/api/docker_remote_api_v1.19.md b/docs/reference/api/docker_remote_api_v1.19.md index d5832aa24..3068b102a 100644 --- a/docs/reference/api/docker_remote_api_v1.19.md +++ b/docs/reference/api/docker_remote_api_v1.19.md @@ -51,6 +51,11 @@ List containers "Created": 1367854155, "Status": "Exit 0", "Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "Labels": { + "com.example.vendor": "Acme", + "com.example.license": "GPL", + "com.example.version": "1.0" + }, "SizeRw": 12288, "SizeRootFs": 0 }, @@ -62,6 +67,7 @@ List containers "Created": 1367854155, "Status": "Exit 0", "Ports": [], + "Labels": {}, "SizeRw": 12288, "SizeRootFs": 0 }, @@ -73,6 +79,7 @@ List containers "Created": 1367854154, "Status": "Exit 0", "Ports":[], + "Labels": {}, "SizeRw":12288, "SizeRootFs":0 }, @@ -84,6 +91,7 @@ List containers "Created": 1367854152, "Status": "Exit 0", "Ports": [], + "Labels": {}, "SizeRw": 12288, "SizeRootFs": 0 } diff --git a/docs/reference/api/docker_remote_api_v1.20.md b/docs/reference/api/docker_remote_api_v1.20.md index 40c58af0e..73ea4bf49 100644 --- a/docs/reference/api/docker_remote_api_v1.20.md +++ b/docs/reference/api/docker_remote_api_v1.20.md @@ -51,6 +51,11 @@ List containers "Created": 1367854155, "Status": "Exit 0", "Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "Labels": { + "com.example.vendor": "Acme", + "com.example.license": "GPL", + "com.example.version": "1.0" + }, "SizeRw": 12288, "SizeRootFs": 0 }, @@ -62,6 +67,7 @@ List containers "Created": 1367854155, "Status": "Exit 0", "Ports": [], + "Labels": {}, "SizeRw": 12288, "SizeRootFs": 0 }, @@ -73,6 +79,7 @@ List containers "Created": 1367854154, "Status": "Exit 0", "Ports":[], + "Labels": {}, "SizeRw":12288, "SizeRootFs":0 }, @@ -84,6 +91,7 @@ List containers "Created": 1367854152, "Status": "Exit 0", "Ports": [], + "Labels": {}, "SizeRw": 12288, "SizeRootFs": 0 } From 7895ec25ea8cb673573a98c0f49a8c9a9d695cef Mon Sep 17 00:00:00 2001 From: Sally O'Malley Date: Mon, 3 Aug 2015 12:36:37 -0400 Subject: [PATCH 54/79] make man/docker.1.md consistent with docker --help "Options:" listed when you run "docker --help" and "docker daemon --help" do not match the options listed in "man/docker.1.md". This PR makes 'docker --help', 'docker daemon --help' and 'man docker' consistent. Also 2 typo fixes. Signed-off-by: Sally O'Malley (cherry picked from commit f3bea61c8011e177e9035325e2007865994d838d) --- man/docker.1.md | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/man/docker.1.md b/man/docker.1.md index 96f62bd2c..898ff08a5 100644 --- a/man/docker.1.md +++ b/man/docker.1.md @@ -50,9 +50,15 @@ To see the man page for a command run **man docker **. **--default-gateway-v6**="" IPv6 address of the container default gateway +**--default-ulimit**=[] + Set default ulimits for containers. + **--dns**="" Force Docker to use specific DNS servers +**--dns-search**=[] + DNS search domains to use. + **-e**, **--exec-driver**="" Force Docker to use specific exec driver. Default is `native`. @@ -60,7 +66,7 @@ To see the man page for a command run **man docker **. Set exec driver options. See EXEC DRIVER OPTIONS. **--exec-root**="" - Path to use as the root of the Docker execdriver. Default is `/var/run/docker`. + Path to use as the root of the Docker exec driver. Default is `/var/run/docker`. **--fixed-cidr**="" IPv4 subnet for fixed IPs (e.g., 10.20.0.0/16); this subnet must be nested in the bridge subnet (which is defined by \-b or \-\-bip) @@ -83,6 +89,9 @@ unix://[/path/to/socket] to use. **--icc**=*true*|*false* Allow unrestricted inter\-container and Docker daemon host communication. If disabled, containers can still be linked together using **--link** option (see **docker-run(1)**). Default is true. +**--insecure-registry**=[] + Enable insecure registry communication. + **--ip**="" Default IP address to use when binding container ports. Default is `0.0.0.0`. @@ -131,10 +140,19 @@ unix://[/path/to/socket] to use. **--storage-opt**=[] Set storage driver options. See STORAGE DRIVER OPTIONS. -**-tls**=*true*|*false* +**--tls**=*true*|*false* Use TLS; implied by --tlsverify. Default is false. -**-tlsverify**=*true*|*false* +**--tlscacert**=~/.docker/ca.pem + Trust certs signed only by this CA. + +**--tlscert**=~/.docker/cert.pem + Path to TLS certificate file. + +**--tlskey**=~/.docker/key.pem + Path to TLS key file. + +**--tlsverify**=*true*|*false* Use TLS and verify the remote (daemon: verify client, client: verify daemon). Default is false. @@ -242,6 +260,10 @@ inside it) Push an image or a repository to a Docker Registry See **docker-push(1)** for full documentation on the **push** command. +**rename** + Rename a container. + See **docker-rename(1)** for full documentation on the **rename** command. + **restart** Restart a running container See **docker-restart(1)** for full documentation on the **restart** command. @@ -411,7 +433,7 @@ Example use: `docker -d --storage-opt dm.loopdatasize=200G` **Note**: This option configures devicemapper loopback, which should not be used in production. Specifies the size to use when creating the loopback file for the -"metadadata" device which is used for the thin pool. The default size +"metadata" device which is used for the thin pool. The default size is 2G. The file is sparse, so it will not initially take up this much space. @@ -473,7 +495,7 @@ When `udev` sync support is `true`, then `devicemapper` and `udev` can coordinate the activation and deactivation of devices for containers. When `udev` sync support is `false`, a race condition occurs between -the`devicemapper` and `udev` during create and cleanup. The race +the `devicemapper` and `udev` during create and cleanup. The race condition results in errors and failures. (For information on these failures, see [docker#4036](https://github.com/docker/docker/issues/4036)) From c967dd289f2d24b588261eefb929e16027c51afd Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Tue, 4 Aug 2015 09:47:48 -0700 Subject: [PATCH 55/79] update systemd article to reference dropin file Signed-off-by: Jessica Frazelle (cherry picked from commit 35e7a7c3e2f0e7329ef666b0e5078b39626befd2) --- docs/articles/systemd.md | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/docs/articles/systemd.md b/docs/articles/systemd.md index 7082ca273..c8fe3db4e 100644 --- a/docs/articles/systemd.md +++ b/docs/articles/systemd.md @@ -33,17 +33,33 @@ If you want Docker to start at boot, you should also: There are a number of ways to configure the daemon flags and environment variables for your Docker daemon. -If the `docker.service` file is set to use an `EnvironmentFile` -(often pointing to `/etc/sysconfig/docker`) then you can modify the -referenced file. +The recommended way is to use a systemd drop-in file. These are local files in +the `/etc/systemd/system/docker.service.d` directory. This could also be +`/etc/systemd/system/docker.service`, which also works for overriding the +defaults from `/lib/systemd/system/docker.service`. -Check if the `docker.service` uses an `EnvironmentFile`: +However, if you had previously used a package which had an `EnvironmentFile` +(often pointing to `/etc/sysconfig/docker`) then for backwards compatibility, +you drop a file in the `/etc/systemd/system/docker.service.d` +directory including the following: + + [Service] + EnvironmentFile=-/etc/sysconfig/docker + EnvironmentFile=-/etc/sysconfig/docker-storage + EnvironmentFile=-/etc/sysconfig/docker-network + ExecStart= + ExecStart=/usr/bin/docker -d -H fd:// $OPTIONS \ + $DOCKER_STORAGE_OPTIONS \ + $DOCKER_NETWORK_OPTIONS \ + $BLOCK_REGISTRY \ + $INSECURE_REGISTRY + +To check if the `docker.service` uses an `EnvironmentFile`: $ sudo systemctl show docker | grep EnvironmentFile EnvironmentFile=-/etc/sysconfig/docker (ignore_errors=yes) -Alternatively, find out where the service file is located, and look for the -property: +Alternatively, find out where the service file is located: $ sudo systemctl status docker | grep Loaded Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled) @@ -69,18 +85,20 @@ In this example, we'll assume that your `docker.service` file looks something li [Service] Type=notify - EnvironmentFile=-/etc/sysconfig/docker - ExecStart=/usr/bin/docker daemon -H fd:// $OPTIONS + ExecStart=/usr/bin/docker daemon -H fd:// LimitNOFILE=1048576 LimitNPROC=1048576 [Install] Also=docker.socket -This will allow us to add extra flags to the `/etc/sysconfig/docker` file by -setting `OPTIONS`: +This will allow us to add extra flags via a drop-in file (mentioned above) by +placing a file containing the following in the `/etc/systemd/system/docker.service.d` +directory: - OPTIONS="--graph /mnt/docker-data --storage-driver btrfs" + [Service] + ExecStart= + ExecStart=/usr/bin/docker daemon -H fd:// --graph /mnt/docker-data --storage-driver btrfs You can also set other environment variables in this file, for example, the `HTTP_PROXY` environment variables described below. From cc6f6cb2e2a3f58973239b4f88d8ba687b1c15ec Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Mon, 3 Aug 2015 18:53:51 +0200 Subject: [PATCH 56/79] Add `--config` to bash completion The custom configuration will also be used in docker invocations made by the completion script itself, just like `-H`. Signed-off-by: Harald Albers (cherry picked from commit b898111d3aac00305e93cec15c5ba8fe8decd0a1) --- contrib/completion/bash/docker | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index dcccd142d..2cba086fe 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -27,7 +27,7 @@ # This order should be applied to lists, alternatives and code blocks. __docker_q() { - docker ${host:+-H "$host"} 2>/dev/null "$@" + docker ${host:+-H "$host"} ${config:+--config "$config"} 2>/dev/null "$@" } __docker_containers_all() { @@ -325,6 +325,10 @@ _docker_docker() { " case "$prev" in + --config) + _filedir -d + return + ;; --log-level|-l) __docker_log_levels return @@ -1394,6 +1398,7 @@ _docker() { --tlsverify " local global_options_with_args=" + --config --host -H --log-level -l --tlscacert @@ -1401,7 +1406,7 @@ _docker() { --tlskey " - local host + local host config COMPREPLY=() local cur prev words cword @@ -1416,6 +1421,11 @@ _docker() { (( counter++ )) host="${words[$counter]}" ;; + # save config so that completion can use custom configuration directories + --config) + (( counter++ )) + config="${words[$counter]}" + ;; $(__docker_to_extglob "$global_options_with_args") ) (( counter++ )) ;; From d3bbaa70cd36fdbc4076fd48abf421eb3ed5f33b Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Tue, 4 Aug 2015 22:04:11 +0200 Subject: [PATCH 57/79] Update some contributions documentations - Add a golint entry to coding-style.md Signed-off-by: Vincent Demeester (cherry picked from commit b30492002172a525513a2c411ebae22149dd4476) --- docs/project/coding-style.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/project/coding-style.md b/docs/project/coding-style.md index 224d2bc02..65a48612f 100644 --- a/docs/project/coding-style.md +++ b/docs/project/coding-style.md @@ -26,6 +26,9 @@ program code and documentation code. * Run `gofmt -s -w file.go` on each changed file before committing your changes. Most editors have plug-ins that do this automatically. +* Run `golint` on each changed file before + committing your changes. + * Update the documentation when creating or modifying features. * Commits that fix or close an issue should reference them in the commit message From e15f6fca3f2b7bf5e32d76ae32fa1df0784fb228 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 5 Aug 2015 13:00:20 -0700 Subject: [PATCH 58/79] Fail fail when the ps format template is invalid. Fixes error continuing execution when the parsing fails. Signed-off-by: David Calavera (cherry picked from commit 3d3db0d4af6cf483299a152133f19f9df760dc11) --- api/client/ps/custom.go | 11 +++++++---- api/client/ps/custom_test.go | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/api/client/ps/custom.go b/api/client/ps/custom.go index d9e8fe075..6d2518b5b 100644 --- a/api/client/ps/custom.go +++ b/api/client/ps/custom.go @@ -170,9 +170,11 @@ func customFormat(ctx Context, containers []types.Container) { format += "\t{{.Size}}" } - tmpl, err := template.New("ps template").Parse(format) + tmpl, err := template.New("").Parse(format) if err != nil { - buffer.WriteString(fmt.Sprintf("Invalid `docker ps` format: %v\n", err)) + buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err)) + buffer.WriteTo(ctx.Output) + return } for _, container := range containers { @@ -181,8 +183,9 @@ func customFormat(ctx Context, containers []types.Container) { c: container, } if err := tmpl.Execute(buffer, containerCtx); err != nil { - buffer = bytes.NewBufferString(fmt.Sprintf("Invalid `docker ps` format: %v\n", err)) - break + buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err)) + buffer.WriteTo(ctx.Output) + return } if table && len(header) == 0 { header = containerCtx.fullHeader() diff --git a/api/client/ps/custom_test.go b/api/client/ps/custom_test.go index d04c9597d..dba2e891c 100644 --- a/api/client/ps/custom_test.go +++ b/api/client/ps/custom_test.go @@ -1,6 +1,7 @@ package ps import ( + "bytes" "reflect" "strings" "testing" @@ -10,7 +11,7 @@ import ( "github.com/docker/docker/pkg/stringid" ) -func TestContainerContextID(t *testing.T) { +func TestContainerPsContext(t *testing.T) { containerId := stringid.GenerateRandomID() unix := time.Now().Unix() @@ -86,3 +87,16 @@ func TestContainerContextID(t *testing.T) { } } + +func TestContainerPsFormatError(t *testing.T) { + out := bytes.NewBufferString("") + ctx := Context{ + Format: "{{InvalidFunction}}", + Output: out, + } + + customFormat(ctx, make([]types.Container, 0)) + if out.String() != "Template parsing error: template: :1: function \"InvalidFunction\" not defined\n" { + t.Fatalf("Expected format error, got `%v`\n", out.String()) + } +} From 85f7f7cfc79d1b68c18c48ccbd193ff04447f2ea Mon Sep 17 00:00:00 2001 From: Filipe Oliveira Date: Tue, 4 Aug 2015 21:33:48 -0300 Subject: [PATCH 59/79] Adding support to forked distributions in installer script. Signed-off-by: Filipe Oliveira (cherry picked from commit f618de15436941c91cc1b82a56518a4e338c7b76) --- hack/install.sh | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/hack/install.sh b/hack/install.sh index d40404a8c..748ad321c 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -51,6 +51,32 @@ echo_docker_as_nonroot() { EOF } +# Check if this is a forked Linux distro +check_forked() { + # Check for lsb_release command existence, it usually exists in forked distros + if command_exists lsb_release; then + # Check if the `-u` option is supported + lsb_release -a -u > /dev/null 2>&1 + + # Check if the command has exited successfully, it means we're in a forked distro + if [ "$?" = "0" ]; then + # Print info about current distro + cat <<-EOF + You're using '$lsb_dist' version '$dist_version'. + EOF + + # Get the upstream release info + lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[[:space:]]') + dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[[:space:]]') + + # Print info about upstream distro + cat <<-EOF + Upstream release is '$lsb_dist' version '$dist_version'. + EOF + fi + fi +} + do_install() { case "$(uname -m)" in *64) @@ -187,8 +213,11 @@ do_install() { esac - + # Check if this is a forked Linux distro + check_forked + + # Run setup for each distro accordingly case "$lsb_dist" in amzn) ( From be40a48c12e766d8689e62a00cb04f3f6fa95c9d Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Wed, 5 Aug 2015 09:07:11 -0700 Subject: [PATCH 60/79] - Remove references to sudo in basics.md; see sudo instructions top of file - Removing references to Boot2Docker replacing with Docker Machine - Removing sudo warnings in instances where appropriate (no sudo in file) - Updating with comments Signed-off-by: Mary Anthony (cherry picked from commit cc375a1e48d0f4b3855e7f14b09ead244bbbadfb) --- docs/README.md | 14 ++++++----- docs/articles/basics.md | 4 --- docs/articles/https.md | 2 +- docs/installation/windows.md | 42 ++++++++++++++----------------- docs/misc/faq.md | 2 +- docs/project/set-up-dev-env.md | 2 +- docs/project/set-up-git.md | 4 +-- docs/project/test-and-docs.md | 4 +-- docs/reference/commandline/cli.md | 4 --- docs/userguide/dockerimages.md | 2 +- docs/userguide/dockerizing.md | 7 +++--- docs/userguide/dockervolumes.md | 14 +++++------ 12 files changed, 46 insertions(+), 55 deletions(-) diff --git a/docs/README.md b/docs/README.md index e4413c5cd..fcde0691e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -87,8 +87,8 @@ own. container with this image. The container exposes port 8000 on the localhost so that you can connect and - see your changes. If you are running Boot2Docker, use the `boot2docker ip` - to get the address of your server. + see your changes. If you use Docker Machine, the `docker-machine ip + ` command gives you the address of your server. 6. Check your writing for style and mechanical errors. @@ -158,18 +158,20 @@ update the root docs pages by running $ make AWS_S3_BUCKET=dowideit-docs BUILD_ROOT=yes docs-release -### Errors publishing using Boot2Docker +### Errors publishing using a Docker Machine VM -Sometimes, in a Boot2Docker environment, the publishing procedure returns this +Sometimes, in a Windows or Mac environment, the publishing procedure returns this error: Post http:///var/run/docker.sock/build?rm=1&t=docker-docs%3Apost-1.2.0-docs_update-2: dial unix /var/run/docker.sock: no such file or directory. -If this happens, set the Docker host. Run the following command to set the +If this happens, set the Docker host. Run the following command to get the variables in your shell: - $ eval "$(boot2docker shellinit)" + docker-machine env + +Then, set your environment accordingly. ## Cherry-picking documentation changes to update an existing release. diff --git a/docs/articles/basics.md b/docs/articles/basics.md index 62cff1de1..905266d0f 100644 --- a/docs/articles/basics.md +++ b/docs/articles/basics.md @@ -47,10 +47,6 @@ image cache. > characters of the full image ID - which can be found using > `docker inspect` or `docker images --no-trunc=true`. -> **Note:** if you are using a remote Docker daemon, such as Boot2Docker, -> then _do not_ type the `sudo` before the `docker` commands shown in the -> documentation's examples. - ## Running an interactive shell To run an interactive shell in the Ubuntu image: diff --git a/docs/articles/https.md b/docs/articles/https.md index 244162988..d7f016d8e 100644 --- a/docs/articles/https.md +++ b/docs/articles/https.md @@ -58,7 +58,7 @@ First generate CA private and public keys: State or Province Name (full name) [Some-State]:Queensland Locality Name (eg, city) []:Brisbane Organization Name (eg, company) [Internet Widgits Pty Ltd]:Docker Inc - Organizational Unit Name (eg, section) []:Boot2Docker + Organizational Unit Name (eg, section) []:Sales Common Name (e.g. server FQDN or YOUR name) []:$HOST Email Address []:Sven@home.org.au diff --git a/docs/installation/windows.md b/docs/installation/windows.md index 64ed93939..efaf0e08e 100644 --- a/docs/installation/windows.md +++ b/docs/installation/windows.md @@ -124,40 +124,36 @@ The Boot2Docker management tool provides several commands: ## Container port redirection -If you are curious, the username for the boot2docker default user is `docker` -and the password is `tcuser`. +If you are curious, the username for the Docker default user is `docker` and the +password is `tcuser`. The latest version of `docker-machine` sets up a host only +network adaptor which provides access to the container's ports. -The latest version of `boot2docker` sets up a host only network adaptor which -provides access to the container's ports. +If you run a container with a published port: -If you run a container with an exposed port: + $ docker run --rm -i -t -p 80:80 nginx - docker run --rm -i -t -p 80:80 nginx +Then you should be able to access that nginx server using the IP address +reported to you using: -Then you should be able to access that nginx server using the IP address reported -to you using: + $ docker-machine ip - boot2docker ip - -Typically, it is 192.168.59.103, but it could get changed by VirtualBox's DHCP -implementation. - -For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io) +Typically, the IP is 192.168.59.103, but it could get changed by VirtualBox's +DHCP implementation. ## Login with PUTTY instead of using the CMD -Boot2Docker generates and uses the public/private key pair in your `%USERPROFILE%\.ssh` -directory so to log in you need to use the private key from this same directory. - -The private key needs to be converted into the format PuTTY uses. - -You can do this with +Docker Machine generates and uses the public/private key pair in your +`%USERPROFILE%\.ssh` directory so to log in you need to use the private key from +this same directory. The private key needs to be converted into the format PuTTY +uses. You can do this with [puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html): -- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from +1. Open `puttygen.exe` and load ("File"->"Load" menu) the private key from `%USERPROFILE%\.ssh\id_boot2docker` -- then click: "Save Private Key". -- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`. + +2. Click "Save Private Key". + +3. Use the saved file to login with PuTTY using `docker@127.0.0.1:2022`. ## Uninstallation diff --git a/docs/misc/faq.md b/docs/misc/faq.md index 2108ddc16..06be72d7c 100644 --- a/docs/misc/faq.md +++ b/docs/misc/faq.md @@ -33,7 +33,7 @@ Docker currently runs only on Linux, but you can use VirtualBox to run Docker in a virtual machine on your box, and get the best of both worlds. Check out the [*Mac OS X*](../installation/mac/#macosx) and [*Microsoft Windows*](../installation/windows/#windows) installation guides. The small Linux -distribution boot2docker can be run inside virtual machines on these two +distribution Docker Machine can be run inside virtual machines on these two operating systems. > **Note:** if you are using a remote Docker daemon, such as Boot2Docker, diff --git a/docs/project/set-up-dev-env.md b/docs/project/set-up-dev-env.md index 4e711f009..4c70d18fe 100644 --- a/docs/project/set-up-dev-env.md +++ b/docs/project/set-up-dev-env.md @@ -29,7 +29,7 @@ you continue working with your fork on this branch. ## Clean your host of Docker artifacts -Docker developers run the latest stable release of the Docker software (with Boot2Docker if their machine is Mac OS X). They clean their local +Docker developers run the latest stable release of the Docker software (with Docker Machine if their machine is Mac OS X). They clean their local hosts of unnecessary Docker artifacts such as stopped containers or unused images. Cleaning unnecessary artifacts isn't strictly necessary, but it is good practice, so it is included here. diff --git a/docs/project/set-up-git.md b/docs/project/set-up-git.md index 93cd0b351..5d3ac8563 100644 --- a/docs/project/set-up-git.md +++ b/docs/project/set-up-git.md @@ -57,8 +57,8 @@ target="_blank">docker/docker repository. $ cd ~ - In Windows, you'll work in your Boot2Docker window instead of Powershell or - a `cmd` window. + In Windows, you'll work in your Docker Quickstart Terminal window instead of + Powershell or a `cmd` window. 6. Create a `repos` directory. diff --git a/docs/project/test-and-docs.md b/docs/project/test-and-docs.md index e58ebd296..554de7d7b 100644 --- a/docs/project/test-and-docs.md +++ b/docs/project/test-and-docs.md @@ -317,9 +317,9 @@ can browse the docs. 4. Enter the URL in your browser. - If you are running Boot2Docker, replace the default localhost address + If you are using Docker Machine, replace the default localhost address (0.0.0.0) with your DOCKERHOST value. You can get this value at any time by - entering `boot2docker ip` at the command line. + entering `docker-machine ip ` at the command line. 5. Once in the documentation, look for the red notice to verify you are seeing the correct build. diff --git a/docs/reference/commandline/cli.md b/docs/reference/commandline/cli.md index e9503c108..69d8e2709 100644 --- a/docs/reference/commandline/cli.md +++ b/docs/reference/commandline/cli.md @@ -10,10 +10,6 @@ parent = "smn_cli" # Using the command line -> **Note:** If you are using a remote Docker daemon, such as Boot2Docker, -> then _do not_ type the `sudo` before the `docker` commands shown in the -> documentation's examples. - To list available commands, either run `docker` with no parameters or execute `docker help`: diff --git a/docs/userguide/dockerimages.md b/docs/userguide/dockerimages.md index b4fd39aeb..795fff81a 100644 --- a/docs/userguide/dockerimages.md +++ b/docs/userguide/dockerimages.md @@ -256,7 +256,7 @@ Let's create a directory and a `Dockerfile` first. $ cd sinatra $ touch Dockerfile -If you are using Boot2Docker on Windows, you may access your host +If you are using Docker Machine on Windows, you may access your host directory by `cd` to `/c/Users/your_user_name`. Each instruction creates a new layer of the image. Let's look at a simple diff --git a/docs/userguide/dockerizing.md b/docs/userguide/dockerizing.md index 515c60111..3f9c730de 100644 --- a/docs/userguide/dockerizing.md +++ b/docs/userguide/dockerizing.md @@ -15,9 +15,10 @@ parent = "smn_applied" Docker allows you to run applications inside containers. Running an application inside a container takes a single command: `docker run`. -> **Note:** if you are using a remote Docker daemon, such as Boot2Docker, -> then _do not_ type the `sudo` before the `docker` commands shown in the -> documentation's examples. +>**Note**: Depending on your Docker system configuration, you may be required to +>preface each `docker` command on this page with `sudo`. To avoid this behavior, +>your system administrator can create a Unix group called `docker` and add users +>to it. ## Hello world diff --git a/docs/userguide/dockervolumes.md b/docs/userguide/dockervolumes.md index d0664637d..528aa1af6 100644 --- a/docs/userguide/dockervolumes.md +++ b/docs/userguide/dockervolumes.md @@ -90,13 +90,13 @@ You will notice in the above 'Volumes' is specifying the location on the host an In addition to creating a volume using the `-v` flag you can also mount a directory from your Docker daemon's host into a container. -> **Note:** -> If you are using Boot2Docker, your Docker daemon only has limited access to -> your OS X/Windows filesystem. Boot2Docker tries to auto-share your `/Users` -> (OS X) or `C:\Users` (Windows) directory - and so you can mount files or directories -> using `docker run -v /Users/:/ ...` (OS X) or -> `docker run -v /c/Users/:/ come from the Boot2Docker virtual machine's filesystem. +>**Note**: If you are using Docker Machine on Mac or Windows, your Docker daemon +>only has limited access to your OS X/Windows filesystem. Docker Machine tries +>to auto-share your `/Users` (OS X) or `C:\Users` (Windows) directory - and so +>you can mount files or directories using `docker run -v +>/Users/:/ ...` (OS X) or `docker run -v +>/c/Users/:/virtual machine's filesystem. $ docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py From 2c875215b1c1fa55605d05db87b3cbaf4fe6d2d4 Mon Sep 17 00:00:00 2001 From: Dimitri John Ledkov Date: Wed, 29 Jul 2015 13:59:36 +0100 Subject: [PATCH 61/79] systemd: set service type to notify. Currently the service type is 'simple', the default, meaning that docker.service is considered to be started straight after spawning. This is incorrect as there is significant amount of time between spawning and docker ready to accept connections on the passed sockets. Docker does implement systemd socket activate and notification protocol, and send the ready signal to systemd, once it is ready. However for systemd to take those notifications into account, the service file type should be set to notify. Signed-off-by: Dimitri John Ledkov (cherry picked from commit d3e5179c291a7646c71f1ca608d6700026756f7c) --- contrib/init/systemd/docker.service | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/init/systemd/docker.service b/contrib/init/systemd/docker.service index 5ceee65cd..f09c2d395 100644 --- a/contrib/init/systemd/docker.service +++ b/contrib/init/systemd/docker.service @@ -5,6 +5,7 @@ After=network.target docker.socket Requires=docker.socket [Service] +Type=notify ExecStart=/usr/bin/docker daemon -H fd:// MountFlags=slave LimitNOFILE=1048576 From 74df05ccaaec18800e43f5713b6c2be2673646ee Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Wed, 5 Aug 2015 17:47:37 -0700 Subject: [PATCH 62/79] [graph] Use a pipe for downloads to write progress The process of pulling an image spawns a new goroutine for each layer in the image manifest. If any of these downloads fail we would stop everything and return the error, even though other goroutines would still be running and writing output through a progress reader which is attached to an http response writer. Since the request handler had already returned from the first error, the http server panics when one of these download goroutines makes a write to the response writer buffer. This patch prevents this crash in the daemon http server by waiting for all of the download goroutines to complete, even if one of them fails. Only then does it return, terminating the request handler. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) (cherry picked from commit d80c4244d322fe0a9cbdd996d23e37fb5b089544) --- graph/pull_v2.go | 30 ++++++++++++++++++-- integration-cli/docker_cli_by_digest_test.go | 2 +- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/graph/pull_v2.go b/graph/pull_v2.go index e5d760e68..c4b18e7c4 100644 --- a/graph/pull_v2.go +++ b/graph/pull_v2.go @@ -1,6 +1,7 @@ package graph import ( + "errors" "fmt" "io" "io/ioutil" @@ -108,6 +109,7 @@ type downloadInfo struct { layer distribution.ReadSeekCloser size int64 err chan error + out io.Writer // Download progress is written here. } type errVerification struct{} @@ -117,7 +119,7 @@ func (errVerification) Error() string { return "verification failed" } func (p *v2Puller) download(di *downloadInfo) { logrus.Debugf("pulling blob %q to %s", di.digest, di.img.ID) - out := p.config.OutStream + out := di.out if c, err := p.poolAdd("pull", "img:"+di.img.ID); err != nil { if c != nil { @@ -191,7 +193,7 @@ func (p *v2Puller) download(di *downloadInfo) { di.err <- nil } -func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) { +func (p *v2Puller) pullV2Tag(tag, taggedName string) (verified bool, err error) { logrus.Debugf("Pulling tag from V2 registry: %q", tag) out := p.config.OutStream @@ -204,7 +206,7 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) { if err != nil { return false, err } - verified, err := p.validateManifest(manifest, tag) + verified, err = p.validateManifest(manifest, tag) if err != nil { return false, err } @@ -212,6 +214,27 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) { logrus.Printf("Image manifest for %s has been verified", taggedName) } + // By using a pipeWriter for each of the downloads to write their progress + // to, we can avoid an issue where this function returns an error but + // leaves behind running download goroutines. By splitting the writer + // with a pipe, we can close the pipe if there is any error, consequently + // causing each download to cancel due to an error writing to this pipe. + pipeReader, pipeWriter := io.Pipe() + go func() { + if _, err := io.Copy(out, pipeReader); err != nil { + logrus.Errorf("error copying from layer download progress reader: %s", err) + } + }() + defer func() { + if err != nil { + // All operations on the pipe are synchronous. This call will wait + // until all current readers/writers are done using the pipe then + // set the error. All successive reads/writes will return with this + // error. + pipeWriter.CloseWithError(errors.New("download canceled")) + } + }() + out.Write(p.sf.FormatStatus(tag, "Pulling from %s", p.repo.Name())) downloads := make([]downloadInfo, len(manifest.FSLayers)) @@ -242,6 +265,7 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) { out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pulling fs layer", nil)) downloads[i].err = make(chan error) + downloads[i].out = pipeWriter go p.download(&downloads[i]) } diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index 4d94d5b5d..71f8b1a83 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -446,7 +446,7 @@ func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) { imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest) out, exitStatus, _ := dockerCmdWithError(c, "pull", imageReference) if exitStatus == 0 { - c.Fatalf("expected a zero exit status but got %d: %s", exitStatus, out) + c.Fatalf("expected a non-zero exit status but got %d: %s", exitStatus, out) } expectedErrorMsg := fmt.Sprintf("image verification failed for digest %s", manifestDigest) From da4b336233b9a5c03bbc1f0d7b5e4219d26622cc Mon Sep 17 00:00:00 2001 From: Lei Date: Thu, 16 Jul 2015 09:28:41 +0800 Subject: [PATCH 63/79] Remove redundant ip_forward check Signed-off-by: Lei Jitang (cherry picked from commit 6a0050d0f0600cf17c0fe5dcca0d24d65e70d818) --- daemon/create.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/daemon/create.go b/daemon/create.go index 79001c6a0..a4a740f0e 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -66,9 +66,6 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos if err := daemon.mergeAndVerifyConfig(config, img); err != nil { return nil, nil, err } - if !config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled { - warnings = append(warnings, "IPv4 forwarding is disabled.") - } if hostConfig == nil { hostConfig = &runconfig.HostConfig{} } From 59166642200cc201604d22bccbcffaed9f8824f1 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 5 Aug 2015 20:08:00 -0700 Subject: [PATCH 64/79] revert apparmor changes back to how it was in 1.7.1, but keep tests Signed-off-by: Jessica Frazelle (cherry picked from commit ed248207d74ac59fb190be31dee96c6a8cef68da) --- contrib/builder/deb/debian-jessie/Dockerfile | 2 +- contrib/builder/deb/debian-stretch/Dockerfile | 2 +- contrib/builder/deb/debian-wheezy/Dockerfile | 2 +- contrib/builder/deb/generate.sh | 1 - contrib/builder/deb/ubuntu-debootstrap-precise/Dockerfile | 2 +- contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile | 2 +- contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile | 2 +- contrib/builder/deb/ubuntu-debootstrap-wily/Dockerfile | 2 +- daemon/execdriver/native/apparmor.go | 6 +----- daemon/execdriver/native/create.go | 3 +-- hack/make/.build-deb/docker-engine.install | 1 - hack/make/.build-deb/rules | 4 ---- hack/make/.integration-daemon-start | 2 -- hack/make/ubuntu | 8 -------- 14 files changed, 9 insertions(+), 30 deletions(-) diff --git a/contrib/builder/deb/debian-jessie/Dockerfile b/contrib/builder/deb/debian-jessie/Dockerfile index a725d3efa..de888a1a7 100644 --- a/contrib/builder/deb/debian-jessie/Dockerfile +++ b/contrib/builder/deb/debian-jessie/Dockerfile @@ -4,7 +4,7 @@ FROM debian:jessie -RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* ENV GO_VERSION 1.4.2 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local diff --git a/contrib/builder/deb/debian-stretch/Dockerfile b/contrib/builder/deb/debian-stretch/Dockerfile index 693a77138..ee4628247 100644 --- a/contrib/builder/deb/debian-stretch/Dockerfile +++ b/contrib/builder/deb/debian-stretch/Dockerfile @@ -4,7 +4,7 @@ FROM debian:stretch -RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* ENV GO_VERSION 1.4.2 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local diff --git a/contrib/builder/deb/debian-wheezy/Dockerfile b/contrib/builder/deb/debian-wheezy/Dockerfile index f850d674a..dc9c38809 100644 --- a/contrib/builder/deb/debian-wheezy/Dockerfile +++ b/contrib/builder/deb/debian-wheezy/Dockerfile @@ -5,7 +5,7 @@ FROM debian:wheezy RUN echo deb http://http.debian.net/debian wheezy-backports main > /etc/apt/sources.list.d/wheezy-backports.list -RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* ENV GO_VERSION 1.4.2 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local diff --git a/contrib/builder/deb/generate.sh b/contrib/builder/deb/generate.sh index 4ab31605a..4bb7320ea 100755 --- a/contrib/builder/deb/generate.sh +++ b/contrib/builder/deb/generate.sh @@ -50,7 +50,6 @@ for version in "${versions[@]}"; do build-essential # "essential for building Debian packages" curl ca-certificates # for downloading Go debhelper # for easy ".deb" building - dh-apparmor # for apparmor debhelper dh-systemd # for systemd debhelper integration git # for "git commit" info in "docker -v" libapparmor-dev # for "sys/apparmor.h" diff --git a/contrib/builder/deb/ubuntu-debootstrap-precise/Dockerfile b/contrib/builder/deb/ubuntu-debootstrap-precise/Dockerfile index a53b46eab..ae6f46478 100644 --- a/contrib/builder/deb/ubuntu-debootstrap-precise/Dockerfile +++ b/contrib/builder/deb/ubuntu-debootstrap-precise/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu-debootstrap:precise -RUN apt-get update && apt-get install -y bash-completion build-essential curl ca-certificates debhelper dh-apparmor git libapparmor-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y bash-completion build-essential curl ca-certificates debhelper git libapparmor-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* ENV GO_VERSION 1.4.2 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local diff --git a/contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile b/contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile index 5f4c35e3f..599a74f89 100644 --- a/contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile +++ b/contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu-debootstrap:trusty -RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* ENV GO_VERSION 1.4.2 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local diff --git a/contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile b/contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile index dacadae21..a8e238590 100644 --- a/contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile +++ b/contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu-debootstrap:vivid -RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* ENV GO_VERSION 1.4.2 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local diff --git a/contrib/builder/deb/ubuntu-debootstrap-wily/Dockerfile b/contrib/builder/deb/ubuntu-debootstrap-wily/Dockerfile index f59ea3b50..a40729e63 100644 --- a/contrib/builder/deb/ubuntu-debootstrap-wily/Dockerfile +++ b/contrib/builder/deb/ubuntu-debootstrap-wily/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu-debootstrap:wily -RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* ENV GO_VERSION 1.4.2 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local diff --git a/daemon/execdriver/native/apparmor.go b/daemon/execdriver/native/apparmor.go index af8b4a4c6..ee5b2743b 100644 --- a/daemon/execdriver/native/apparmor.go +++ b/daemon/execdriver/native/apparmor.go @@ -40,20 +40,16 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { file, umount, - signal (receive) peer=/usr/bin/docker, - signal (receive) peer=docker-unconfined, - deny @{PROC}/sys/fs/** wklx, deny @{PROC}/fs/** wklx, deny @{PROC}/sysrq-trigger rwklx, deny @{PROC}/mem rwklx, deny @{PROC}/kmem rwklx, - deny @{PROC}/kore rwklx, + deny @{PROC}/kcore rwklx, deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx, deny @{PROC}/sys/kernel/*/** wklx, deny mount, - deny ptrace (trace) peer=docker-default, deny /sys/[^f]*/** wklx, deny /sys/f[^s]*/** wklx, diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index a28681f2b..bbc83fedb 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -198,9 +198,8 @@ func (d *driver) setPrivileged(container *configs.Config) (err error) { container.Devices = hostDevices if apparmor.IsEnabled() { - container.AppArmorProfile = "docker-unconfined" + container.AppArmorProfile = "unconfined" } - return nil } diff --git a/hack/make/.build-deb/docker-engine.install b/hack/make/.build-deb/docker-engine.install index 9371ac873..a8857a96d 100644 --- a/hack/make/.build-deb/docker-engine.install +++ b/hack/make/.build-deb/docker-engine.install @@ -9,4 +9,3 @@ contrib/init/systemd/docker.socket lib/systemd/system/ contrib/mk* usr/share/docker-engine/contrib/ contrib/nuke-graph-directory.sh usr/share/docker-engine/contrib/ contrib/syntax/nano/Dockerfile.nanorc usr/share/nano/ -contrib/apparmor/* etc/apparmor.d/ diff --git a/hack/make/.build-deb/rules b/hack/make/.build-deb/rules index be45676c1..b4c8e2b4c 100755 --- a/hack/make/.build-deb/rules +++ b/hack/make/.build-deb/rules @@ -32,9 +32,5 @@ override_dh_installudev: # match our existing priority dh_installudev --priority=z80 -override_dh_install: - dh_install - dh_apparmor --profile-name=docker-engine -pdocker-engine - %: dh $@ --with=bash-completion $(shell command -v dh_systemd_enable > /dev/null 2>&1 && echo --with=systemd) diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index b4cdf86fe..dcc09fa92 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -35,8 +35,6 @@ if [ -z "$DOCKER_TEST_HOST" ]; then ( set -x /etc/init.d/apparmor start - - /sbin/apparmor_parser -r -W -T contrib/apparmor/ ) fi diff --git a/hack/make/ubuntu b/hack/make/ubuntu index 24246da76..0421dc367 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -72,10 +72,6 @@ bundle_ubuntu() { done done - # Include contributed apparmor policy - mkdir -p "$DIR/etc/apparmor.d/" - cp contrib/apparmor/* "$DIR/etc/apparmor.d/" - # Copy the binary # This will fail if the binary bundle hasn't been built mkdir -p "$DIR/usr/bin" @@ -93,10 +89,6 @@ if [ "$1" = 'configure' ] && [ -z "$2" ]; then fi fi -if ( aa-status --enabled ); then - /sbin/apparmor_parser -r -W -T /etc/apparmor.d/docker-engine -fi - if ! { [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | grep -q upstart; }; then # we only need to do this if upstart isn't in charge update-rc.d docker defaults > /dev/null || true From ed672d1609924dd6ab0033ce960b5604915193bd Mon Sep 17 00:00:00 2001 From: Alessandro Boch Date: Thu, 6 Aug 2015 14:23:25 -0700 Subject: [PATCH 65/79] Vendoring libnetwork bd3eecc96f3c05a4acef1bedcf74397bc6850d22 Signed-off-by: Alessandro Boch (cherry picked from commit e35a5ae463507debc25fc372622cf7b0b27b6d36) --- hack/vendor.sh | 2 +- vendor/src/github.com/docker/libnetwork/endpoint.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 9c7c68c69..da17b86b0 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -21,7 +21,7 @@ clone git golang.org/x/net 3cffabab72adf04f8e3b01c5baf775361837b5fe https://gith clone hg code.google.com/p/gosqlite 74691fb6f837 #get libnetwork packages -clone git github.com/docker/libnetwork 78fc31ddc425fb379765c6b7ab5b96748bd8fc08 +clone git github.com/docker/libnetwork bd3eecc96f3c05a4acef1bedcf74397bc6850d22 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4 diff --git a/vendor/src/github.com/docker/libnetwork/endpoint.go b/vendor/src/github.com/docker/libnetwork/endpoint.go index 6d757001b..d38a4fd12 100644 --- a/vendor/src/github.com/docker/libnetwork/endpoint.go +++ b/vendor/src/github.com/docker/libnetwork/endpoint.go @@ -415,7 +415,8 @@ func (ep *endpoint) Join(containerID string, options ...EndpointOption) error { } defer func() { if err != nil { - if err = driver.Leave(nid, epid); err != nil { + // Do not alter global err variable, it's needed by the previous defer + if err := driver.Leave(nid, epid); err != nil { log.Warnf("driver leave failed while rolling back join: %v", err) } } From 29ea36a880a2946c525037bae8ecf09c3edaca8a Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Thu, 6 Aug 2015 17:41:59 -0400 Subject: [PATCH 66/79] registry: Do not push to mirrors This patch splits LookupEndpoints into LookupPullEndpoints and LookupPushEndpoints so that mirrors added with --registry-mirror are skipped in the list returned by LookupPushEndpoints. Fixes https://github.com/docker/distribution/issues/823 Signed-off-by: Tibor Vass (cherry picked from commit b899977ee283d6bccf35ad9a079ed92a291ad34c) --- graph/pull.go | 2 +- graph/push.go | 2 +- registry/service.go | 43 ++++++++++++++++++++++++++++--------------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/graph/pull.go b/graph/pull.go index 5612480a3..56f226e5c 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -61,7 +61,7 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf return err } - endpoints, err := s.registryService.LookupEndpoints(repoInfo.CanonicalName) + endpoints, err := s.registryService.LookupPullEndpoints(repoInfo.CanonicalName) if err != nil { return err } diff --git a/graph/push.go b/graph/push.go index 6e844aada..fcb7e121d 100644 --- a/graph/push.go +++ b/graph/push.go @@ -60,7 +60,7 @@ func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) erro return err } - endpoints, err := s.registryService.LookupEndpoints(repoInfo.CanonicalName) + endpoints, err := s.registryService.LookupPushEndpoints(repoInfo.CanonicalName) if err != nil { return err } diff --git a/registry/service.go b/registry/service.go index f4ea42ef9..8d301b4fa 100644 --- a/registry/service.go +++ b/registry/service.go @@ -108,27 +108,40 @@ func (s *Service) tlsConfigForMirror(mirror string) (*tls.Config, error) { return s.TLSConfig(mirrorURL.Host) } -// LookupEndpoints creates an list of endpoints to try, in order of preference. +// LookupPullEndpoints creates an list of endpoints to try to pull from, in order of preference. // It gives preference to v2 endpoints over v1, mirrors over the actual // registry, and HTTPS over plain HTTP. -func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err error) { +func (s *Service) LookupPullEndpoints(repoName string) (endpoints []APIEndpoint, err error) { + return s.lookupEndpoints(repoName, false) +} + +// LookupPushEndpoints creates an list of endpoints to try to push to, in order of preference. +// It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP. +// Mirrors are not included. +func (s *Service) LookupPushEndpoints(repoName string) (endpoints []APIEndpoint, err error) { + return s.lookupEndpoints(repoName, true) +} + +func (s *Service) lookupEndpoints(repoName string, isPush bool) (endpoints []APIEndpoint, err error) { var cfg = tlsconfig.ServerDefault tlsConfig := &cfg if strings.HasPrefix(repoName, DefaultNamespace+"/") { - // v2 mirrors - for _, mirror := range s.Config.Mirrors { - mirrorTLSConfig, err := s.tlsConfigForMirror(mirror) - if err != nil { - return nil, err + if !isPush { + // v2 mirrors for pull only + for _, mirror := range s.Config.Mirrors { + mirrorTLSConfig, err := s.tlsConfigForMirror(mirror) + if err != nil { + return nil, err + } + endpoints = append(endpoints, APIEndpoint{ + URL: mirror, + // guess mirrors are v2 + Version: APIVersion2, + Mirror: true, + TrimHostname: true, + TLSConfig: mirrorTLSConfig, + }) } - endpoints = append(endpoints, APIEndpoint{ - URL: mirror, - // guess mirrors are v2 - Version: APIVersion2, - Mirror: true, - TrimHostname: true, - TLSConfig: mirrorTLSConfig, - }) } // v2 registry endpoints = append(endpoints, APIEndpoint{ From a97b89b585a2f6c5deb44cf1d614a327c32aa13c Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Thu, 6 Aug 2015 16:51:01 -0700 Subject: [PATCH 67/79] remove docker-unconfined profile we were not using it and it breaks apparmor on wheezy Signed-off-by: Jessica Frazelle (cherry picked from commit e542238f2a4ba9d77bf8ebc77e319dd6b321925f) --- daemon/execdriver/native/apparmor.go | 15 --------------- daemon/execdriver/native/driver.go | 4 ++-- docs/security/apparmor.md | 4 ---- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/daemon/execdriver/native/apparmor.go b/daemon/execdriver/native/apparmor.go index ee5b2743b..30d49b37b 100644 --- a/daemon/execdriver/native/apparmor.go +++ b/daemon/execdriver/native/apparmor.go @@ -59,21 +59,6 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { deny /sys/firmware/efi/efivars/** rwklx, deny /sys/kernel/security/** rwklx, } - -profile docker-unconfined flags=(attach_disconnected,mediate_deleted,complain) { - #include - - network, - capability, - file, - umount, - mount, - pivot_root, - change_profile -> *, - - ptrace, - signal, -} ` func generateProfile(out io.Writer) error { diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 351f26794..c5d4d964c 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -54,11 +54,11 @@ func NewDriver(root, initPath string, options []string) (*driver, error) { if apparmor.IsEnabled() { if err := installAppArmorProfile(); err != nil { - apparmor_profiles := []string{"docker-default", "docker-unconfined"} + apparmorProfiles := []string{"docker-default"} // Allow daemon to run if loading failed, but are active // (possibly through another run, manually, or via system startup) - for _, policy := range apparmor_profiles { + for _, policy := range apparmorProfiles { if err := hasAppArmorProfileLoaded(policy); err != nil { return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy) } diff --git a/docs/security/apparmor.md b/docs/security/apparmor.md index 7cd88d314..1e82200b6 100644 --- a/docs/security/apparmor.md +++ b/docs/security/apparmor.md @@ -20,10 +20,6 @@ The `docker-default` profile the default for running containers. It is moderately protective while providing wide application compatability. -The `docker-unconfined` profile is intended for -privileged applications and is the default when runing -a container with the *--privileged* flag. - The system's standard `unconfined` profile inherits all system-wide policies, applying path-based policies intended for the host system inside of containers. From 83f6dbe30adac62cd0cce3d07f562184ddb22711 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Wed, 29 Jul 2015 12:09:40 -0700 Subject: [PATCH 68/79] Skip notary tests which update system clock Currently some notary tests change the system clock to check for expiration. Skip these tests until the code can be refactored to not rely on updating the system clock. Signed-off-by: Derek McGowan (github: dmcgowan) (cherry picked from commit bf3c1e6a3afc951e41643b8d55d4ac25fa9cb06d) --- integration-cli/docker_cli_create_test.go | 1 + integration-cli/docker_cli_pull_test.go | 2 ++ integration-cli/docker_cli_push_test.go | 2 ++ integration-cli/docker_cli_run_test.go | 1 + 4 files changed, 6 insertions(+) diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index 13b841e44..482e96f9d 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -345,6 +345,7 @@ func (s *DockerTrustSuite) TestTrustedIsolatedCreate(c *check.C) { } func (s *DockerTrustSuite) TestCreateWhenCertExpired(c *check.C) { + c.Skip("Currently changes system time, causing instability") repoName := s.setupTrustedImage(c, "trusted-create-expired") // Certificates have 10 years of expiration diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 3e9da73f3..2ab2ca3fe 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -225,6 +225,7 @@ func (s *DockerTrustSuite) TestUntrustedPull(c *check.C) { } func (s *DockerTrustSuite) TestPullWhenCertExpired(c *check.C) { + c.Skip("Currently changes system time, causing instability") repoName := s.setupTrustedImage(c, "trusted-cert-expired") // Certificates have 10 years of expiration @@ -331,6 +332,7 @@ func (s *DockerTrustSuite) TestTrustedPullFromBadTrustServer(c *check.C) { } func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) { + c.Skip("Currently changes system time, causing instability") repoName := fmt.Sprintf("%v/dockercliexpiredtimestamppull/trusted:latest", privateRegistryURL) // tag the image and upload it to the private registry dockerCmd(c, "tag", "busybox", repoName) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 41c034684..ed4d24a85 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -287,6 +287,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithIncorrectPassphraseForNonRoot(c *c } func (s *DockerTrustSuite) TestTrustedPushWithExpiredSnapshot(c *check.C) { + c.Skip("Currently changes system time, causing instability") repoName := fmt.Sprintf("%v/dockercliexpiredsnapshot/trusted:latest", privateRegistryURL) // tag the image and upload it to the private registry dockerCmd(c, "tag", "busybox", repoName) @@ -322,6 +323,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithExpiredSnapshot(c *check.C) { } func (s *DockerTrustSuite) TestTrustedPushWithExpiredTimestamp(c *check.C) { + c.Skip("Currently changes system time, causing instability") repoName := fmt.Sprintf("%v/dockercliexpiredtimestamppush/trusted:latest", privateRegistryURL) // tag the image and upload it to the private registry dockerCmd(c, "tag", "busybox", repoName) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index daea0ca10..92889574a 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2632,6 +2632,7 @@ func (s *DockerTrustSuite) TestUntrustedRun(c *check.C) { } func (s *DockerTrustSuite) TestRunWhenCertExpired(c *check.C) { + c.Skip("Currently changes system time, causing instability") repoName := s.setupTrustedImage(c, "trusted-run-expired") // Certificates have 10 years of expiration From c1d9e7c6fbae0614da33ff5c2d9677a303cccfd7 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Fri, 7 Aug 2015 14:31:33 +0200 Subject: [PATCH 69/79] doc: add a notice about memory accounting overhead The documentation for Debian and Ubuntu explains how to enable memory and swap accounting but doesn't explain why it is disabled in the first place. The problem with those subsystems is that they incurs a performance hit even when not used at all. Add this explanation. The provided figure are quite vague. The memory overhead is easily verifiable. It is for example cited in [RedHat documentation][]. For the performance hit, maybe the performance is better now, but a few years ago, it was said to be [between 6 and 15%][]. The goal is that people don't just enable memory accounting if they don't have a use for it. [RedHat documentation]: https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide/sec-memory.html [between 6 and 15%]: https://lwn.net/Articles/517562/ Signed-off-by: Vincent Bernat (cherry picked from commit 56b33e9f27fa9dc50cec16392ca01bd4234390f9) --- docs/installation/ubuntulinux.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/installation/ubuntulinux.md b/docs/installation/ubuntulinux.md index 7f2e1fa19..bae68063c 100644 --- a/docs/installation/ubuntulinux.md +++ b/docs/installation/ubuntulinux.md @@ -197,9 +197,14 @@ When users run Docker, they may see these messages when working with an image: WARNING: Your kernel does not support cgroup swap limit. WARNING: Your kernel does not support swap limit capabilities. Limitation discarded. -To prevent these messages, enable memory and swap accounting on your system. To -enable these on system using GNU GRUB (GNU GRand Unified Bootloader), do the -following. +To prevent these messages, enable memory and swap accounting on your +system. Enabling memory and swap accounting does induce both a memory +overhead and a performance degradation even when Docker is not in +use. The memory overhead is about 1% of the total available +memory. The performance degradation is roughly 10%. + +To enable memory and swap on system using GNU GRUB (GNU GRand Unified +Bootloader), do the following: 1. Log into Ubuntu as a user with `sudo` privileges. From 783baec49c5f317c6c0d617b395f2ea881d01bfa Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Fri, 7 Aug 2015 11:37:05 -0700 Subject: [PATCH 70/79] Fixes #15349 too many apis Signed-off-by: Mary Anthony (cherry picked from commit 1d5f479e01388e1bffcd61f9c3896093ac82d5d0) --- docs/reference/api/docker_remote_api.md | 425 ------------------------ 1 file changed, 425 deletions(-) diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index fd00a8f55..ba0dc3712 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -298,429 +298,4 @@ The `fromImage` and `repo` parameters now supports the `repo:tag` format. Consequently, the `tag` parameter is now obsolete. Using the new format and the `tag` parameter at the same time will return an error. -## v1.13 -### Full documentation - -[*Docker Remote API v1.13*](/reference/api/docker_remote_api_v1.13/) - -### What's new - -`GET /containers/(name)/json` - -**New!** -The `HostConfig.Links` field is now filled correctly - -**New!** -`Sockets` parameter added to the `/info` endpoint listing all the sockets the -daemon is configured to listen on. - -`POST /containers/(name)/start` -`POST /containers/(name)/stop` - -**New!** -`start` and `stop` will now return 304 if the container's status is not modified - -`POST /commit` - -**New!** -Added a `pause` parameter (default `true`) to pause the container during commit - -## v1.12 - -### Full documentation - -[*Docker Remote API v1.12*](/reference/api/docker_remote_api_v1.12/) - -### What's new - -`POST /build` - -**New!** -Build now has support for the `forcerm` parameter to always remove containers - -`GET /containers/(name)/json` -`GET /images/(name)/json` - -**New!** -All the JSON keys are now in CamelCase - -**New!** -Trusted builds are now Automated Builds - `is_trusted` is now `is_automated`. - -**Removed Insert Endpoint** -The `insert` endpoint has been removed. - -## v1.11 - -### Full documentation - -[*Docker Remote API v1.11*](/reference/api/docker_remote_api_v1.11/) - -### What's new - -`GET /_ping` - -**New!** -You can now ping the server via the `_ping` endpoint. - -`GET /events` - -**New!** -You can now use the `-until` parameter to close connection -after timestamp. - -`GET /containers/(id)/logs` - -This url is preferred method for getting container logs now. - -## v1.10 - -### Full documentation - -[*Docker Remote API v1.10*](/reference/api/docker_remote_api_v1.10/) - -### What's new - -`DELETE /images/(name)` - -**New!** -You can now use the force parameter to force delete of an - image, even if it's tagged in multiple repositories. **New!** - You - can now use the noprune parameter to prevent the deletion of parent - images - -`DELETE /containers/(id)` - -**New!** -You can now use the force parameter to force delete a - container, even if it is currently running - -## v1.9 - -### Full documentation - -[*Docker Remote API v1.9*](/reference/api/docker_remote_api_v1.9/) - -### What's new - -`POST /build` - -**New!** -This endpoint now takes a serialized ConfigFile which it -uses to resolve the proper registry auth credentials for pulling the -base image. Clients which previously implemented the version -accepting an AuthConfig object must be updated. - -## v1.8 - -### Full documentation - -[*Docker Remote API v1.8*](/reference/api/docker_remote_api_v1.8/) - -### What's new - -`POST /build` - -**New!** -This endpoint now returns build status as json stream. In -case of a build error, it returns the exit status of the failed -command. - -`GET /containers/(id)/json` - -**New!** -This endpoint now returns the host config for the -container. - -`POST /images/create` - -`POST /images/(name)/insert` - -`POST /images/(name)/push` - -**New!** -progressDetail object was added in the JSON. It's now -possible to get the current value and the total of the progress -without having to parse the string. - -## v1.7 - -### Full documentation - -[*Docker Remote API v1.7*](/reference/api/docker_remote_api_v1.7/) - -### What's new - -`GET /images/json` - -The format of the json returned from this uri changed. Instead of an -entry for each repo/tag on an image, each image is only represented -once, with a nested attribute indicating the repo/tags that apply to -that image. - -Instead of: - - HTTP/1.1 200 OK - Content-Type: application/json - - [ - { - "VirtualSize": 131506275, - "Size": 131506275, - "Created": 1365714795, - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Tag": "12.04", - "Repository": "ubuntu" - }, - { - "VirtualSize": 131506275, - "Size": 131506275, - "Created": 1365714795, - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Tag": "latest", - "Repository": "ubuntu" - }, - { - "VirtualSize": 131506275, - "Size": 131506275, - "Created": 1365714795, - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Tag": "precise", - "Repository": "ubuntu" - }, - { - "VirtualSize": 180116135, - "Size": 24653, - "Created": 1364102658, - "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Tag": "12.10", - "Repository": "ubuntu" - }, - { - "VirtualSize": 180116135, - "Size": 24653, - "Created": 1364102658, - "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Tag": "quantal", - "Repository": "ubuntu" - } - ] - -The returned json looks like this: - - HTTP/1.1 200 OK - Content-Type: application/json - - [ - { - "RepoTags": [ - "ubuntu:12.04", - "ubuntu:precise", - "ubuntu:latest" - ], - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Created": 1365714795, - "Size": 131506275, - "VirtualSize": 131506275 - }, - { - "RepoTags": [ - "ubuntu:12.10", - "ubuntu:quantal" - ], - "ParentId": "27cf784147099545", - "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Created": 1364102658, - "Size": 24653, - "VirtualSize": 180116135 - } - ] - -`GET /images/viz` - -This URI no longer exists. The `images --viz` -output is now generated in the client, using the -`/images/json` data. - -## v1.6 - -### Full documentation - -[*Docker Remote API v1.6*](/reference/api/docker_remote_api_v1.6/) - -### What's new - -`POST /containers/(id)/attach` - -**New!** -You can now split stderr from stdout. This is done by -prefixing a header to each transmission. See -[`POST /containers/(id)/attach`]( -/reference/api/docker_remote_api_v1.9/#attach-to-a-container "POST /containers/(id)/attach"). -The WebSocket attach is unchanged. Note that attach calls on the -previous API version didn't change. Stdout and stderr are merged. - -## v1.5 - -### Full documentation - -[*Docker Remote API v1.5*](/reference/api/docker_remote_api_v1.5/) - -### What's new - -`POST /images/create` - -**New!** -You can now pass registry credentials (via an AuthConfig - object) through the X-Registry-Auth header - -`POST /images/(name)/push` - -**New!** -The AuthConfig object now needs to be passed through the - X-Registry-Auth header - -`GET /containers/json` - -**New!** -The format of the Ports entry has been changed to a list of -dicts each containing PublicPort, PrivatePort and Type describing a -port mapping. - -## v1.4 - -### Full documentation - -[*Docker Remote API v1.4*](/reference/api/docker_remote_api_v1.4/) - -### What's new - -`POST /images/create` - -**New!** -When pulling a repo, all images are now downloaded in parallel. - -`GET /containers/(id)/top` - -**New!** -You can now use ps args with docker top, like docker top - aux - -`GET /events` - -**New!** -Image's name added in the events - -## v1.3 - -docker v0.5.0 -[51f6c4a](https://github.com/docker/docker/commit/51f6c4a7372450d164c61e0054daf0223ddbd909) - -### Full documentation - -[*Docker Remote API v1.3*](/reference/api/docker_remote_api_v1.3/) - -### What's new - -`GET /containers/(id)/top` - -List the processes running inside a container. - -`GET /events` - -**New!** -Monitor docker's events via streaming or via polling - -Builder (/build): - - - Simplify the upload of the build context - - Simply stream a tarball instead of multipart upload with 4 - intermediary buffers - - Simpler, less memory usage, less disk usage and faster - -> **Warning**: -> The /build improvements are not reverse-compatible. Pre 1.3 clients will -> break on /build. - -List containers (/containers/json): - - - You can use size=1 to get the size of the containers - -Start containers (/containers//start): - - - You can now pass host-specific configuration (e.g., bind mounts) in - the POST body for start calls - -## v1.2 - -docker v0.4.2 -[2e7649b](https://github.com/docker/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168) - -### Full documentation - -[*Docker Remote API v1.2*](/reference/api/docker_remote_api_v1.2/) - -### What's new - -The auth configuration is now handled by the client. - -The client should send it's authConfig as POST on each call of -`/images/(name)/push` - -`GET /auth` - -**Deprecated.** - -`POST /auth` - -Only checks the configuration but doesn't store it on the server - - Deleting an image is now improved, will only untag the image if it - has children and remove all the untagged parents if has any. - -`POST /images//delete` - -Now returns a JSON structure with the list of images -deleted/untagged. - -## v1.1 - -docker v0.4.0 -[a8ae398](https://github.com/docker/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f) - -### Full documentation - -[*Docker Remote API v1.1*](/reference/api/docker_remote_api_v1.1/) - -### What's new - -`POST /images/create` - -`POST /images/(name)/insert` - -`POST /images/(name)/push` - -Uses json stream instead of HTML hijack, it looks like this: - - HTTP/1.1 200 OK - Content-Type: application/json - - {"status":"Pushing..."} - {"status":"Pushing", "progress":"1/? (n/a)"} - {"error":"Invalid..."} - ... - -## v1.0 - -docker v0.3.4 -[8d73740](https://github.com/docker/docker/commit/8d73740343778651c09160cde9661f5f387b36f4) - -### Full documentation - -[*Docker Remote API v1.0*](/reference/api/docker_remote_api_v1.0/) - -### What's new - -Initial version From b3f3abfc94bd6e77d237d0a2d641556ec07a20ae Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Fri, 7 Aug 2015 10:13:28 -0700 Subject: [PATCH 71/79] Make official install instructions use curl instead of wget YOU GO CURL Fixes #14650 Signed-off-by: Ankush Agarwal (cherry picked from commit c76fef1791106ca60b7f913a895a77251b695c0a) --- docs/installation/debian.md | 2 +- docs/installation/ubuntulinux.md | 16 ++++++++-------- experimental/README.md | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/installation/debian.md b/docs/installation/debian.md index ac45721ff..54526cf32 100644 --- a/docs/installation/debian.md +++ b/docs/installation/debian.md @@ -96,7 +96,7 @@ which is officially supported by Docker. >command fails for the Docker repo during installation. To work around this, >add the key directly using the following: > -> $ wget -qO- https://get.docker.com/gpg | sudo apt-key add - +> $ curl -sSL https://get.docker.com/gpg | sudo apt-key add - ### Uninstallation diff --git a/docs/installation/ubuntulinux.md b/docs/installation/ubuntulinux.md index bae68063c..e41c9e919 100644 --- a/docs/installation/ubuntulinux.md +++ b/docs/installation/ubuntulinux.md @@ -111,18 +111,18 @@ install Docker using the following: 1. Log into your Ubuntu installation as a user with `sudo` privileges. -2. Verify that you have `wget` installed. +2. Verify that you have `curl` installed. - $ which wget + $ which curl - If `wget` isn't installed, install it after updating your manager: + If `curl` isn't installed, install it after updating your manager: $ sudo apt-get update - $ sudo apt-get install wget + $ sudo apt-get install curl 3. Get the latest Docker package. - $ wget -qO- https://get.docker.com/ | sh + $ curl -sSL https://get.docker.com/ | sh The system prompts you for your `sudo` password. Then, it downloads and installs Docker and its dependencies. @@ -132,7 +132,7 @@ install Docker using the following: >command fails for the Docker repo during installation. To work around this, >add the key directly using the following: > -> $ wget -qO- https://get.docker.com/gpg | sudo apt-key add - +> $ curl -sSL https://get.docker.com/gpg | sudo apt-key add - 4. Verify `docker` is installed correctly. @@ -344,9 +344,9 @@ to start the docker daemon on boot ## Upgrade Docker -To install the latest version of Docker with `wget`: +To install the latest version of Docker with `curl`: - $ wget -qO- https://get.docker.com/ | sh + $ curl -sSL https://get.docker.com/ | sh ## Uninstallation diff --git a/experimental/README.md b/experimental/README.md index a067e839a..6c9b21687 100644 --- a/experimental/README.md +++ b/experimental/README.md @@ -13,18 +13,18 @@ please feel free to provide any feedback on these features you wish. Unlike the regular Docker binary, the experimental channels is built and updated nightly on TO.BE.ANNOUNCED. From one day to the next, new features may appear, while existing experimental features may be refined or entirely removed. -1. Verify that you have `wget` installed. +1. Verify that you have `curl` installed. - $ which wget + $ which curl - If `wget` isn't installed, install it after updating your manager: + If `curl` isn't installed, install it after updating your manager: $ sudo apt-get update - $ sudo apt-get install wget + $ sudo apt-get install curl 2. Get the latest Docker package. - $ wget -qO- https://experimental.docker.com/ | sh + $ curl -sSL https://experimental.docker.com/ | sh The system prompts you for your `sudo` password. Then, it downloads and installs Docker and its dependencies. @@ -34,7 +34,7 @@ Unlike the regular Docker binary, the experimental channels is built and updated >command fails for the Docker repo during installation. To work around this, >add the key directly using the following: > - > $ wget -qO- https://experimental.docker.com/gpg | sudo apt-key add - + > $ curl -sSL https://experimental.docker.com/gpg | sudo apt-key add - 3. Verify `docker` is installed correctly. From 3de34af5d125f1cb5fbed1f08e502c7b611dfb81 Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Fri, 7 Aug 2015 10:01:07 -0700 Subject: [PATCH 72/79] Remove note from docker version 1.3 on Env variables Fixes #14734 Signed-off-by: Ankush Agarwal (cherry picked from commit 89ec5399aff830ed0b00ff24610969fbf9ad4fdc) --- docs/reference/builder.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/reference/builder.md b/docs/reference/builder.md index b08ff584f..073fc6d5e 100644 --- a/docs/reference/builder.md +++ b/docs/reference/builder.md @@ -114,12 +114,6 @@ images. ### Environment replacement -> **Note**: prior to 1.3, `Dockerfile` environment variables were handled -> similarly, in that they would be replaced as described below. However, there -> was no formal definition on as to which instructions handled environment -> replacement at the time. After 1.3 this behavior will be preserved and -> canonical. - Environment variables (declared with [the `ENV` statement](#env)) can also be used in certain instructions as variables to be interpreted by the `Dockerfile`. Escapes are also handled for including variable-like syntax From 9705c349c5bf44324c07a3564e351ab7ec26a165 Mon Sep 17 00:00:00 2001 From: Youcef YEKHLEF Date: Mon, 3 Aug 2015 23:37:48 +0200 Subject: [PATCH 73/79] Add fedora 22 install docs warning about systemd-network Following @cles commit #6bd15a68c for Arch Signed-off-by: yyekhlef (cherry picked from commit f224a9f3d609eec8bff9c6be18582eb72cd0e7ef) --- docs/installation/fedora.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/installation/fedora.md b/docs/installation/fedora.md index eb8176296..2378320ab 100644 --- a/docs/installation/fedora.md +++ b/docs/installation/fedora.md @@ -206,6 +206,22 @@ If you need to add an HTTP Proxy, set a different directory or partition for the Docker runtime files, or make other customizations, read our Systemd article to learn how to [customize your Systemd Docker daemon options](/articles/systemd/). +## Running Docker with a manually-defined network + +If you manually configure your network using `systemd-network` with fedora version 22, containers you start with Docker may be unable to access your network. +Beginning with version 220, the forwarding setting for a given network (`net.ipv4.conf..forwarding`) defaults to *off*. This setting prevents IP forwarding. It also conflicts with Docker which enables the `net.ipv4.conf.all.forwarding` setting within a container. + +To work around this, edit the `.network` file in +`/usr/lib/systemd/network/` on your Docker host (ex: `/usr/lib/systemd/network/80-container-host0.network`) add the following block: + +``` +[Network] +... +IPForward=kernel +... +``` + +This configuration allows IP forwarding from the container as expected. ## Uninstall From 33b16fef43f00ff25941c1e5094b35922c15d776 Mon Sep 17 00:00:00 2001 From: Youcef YEKHLEF Date: Tue, 4 Aug 2015 17:02:59 +0200 Subject: [PATCH 74/79] Add fedora 22 install docs warning about systemd-network With systemd in version 219 IP forwarding should be forced in the interface configuration file. Signed-off-by: yyekhlef (cherry picked from commit 35aebdc803e2ffedf3208f0fd27a7652562fc007) --- docs/installation/fedora.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/installation/fedora.md b/docs/installation/fedora.md index 2378320ab..b1bdb19cb 100644 --- a/docs/installation/fedora.md +++ b/docs/installation/fedora.md @@ -208,7 +208,7 @@ learn how to [customize your Systemd Docker daemon options](/articles/systemd/). ## Running Docker with a manually-defined network -If you manually configure your network using `systemd-network` with fedora version 22, containers you start with Docker may be unable to access your network. +If you manually configure your network using `systemd-network` with `systemd` version 219 or higher, containers you start with Docker may be unable to access your network. Beginning with version 220, the forwarding setting for a given network (`net.ipv4.conf..forwarding`) defaults to *off*. This setting prevents IP forwarding. It also conflicts with Docker which enables the `net.ipv4.conf.all.forwarding` setting within a container. To work around this, edit the `.network` file in @@ -218,6 +218,8 @@ To work around this, edit the `.network` file in [Network] ... IPForward=kernel +# OR +IPForward=true ... ``` From b6f0f93c9435a60e66b54b5450bc547d7b7d4b4a Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Wed, 29 Jul 2015 18:16:43 -0700 Subject: [PATCH 75/79] Docker content trust documentation - started from Diogo's work - updated after discussions with team - Updating with new key names - fixing weight - adding in sandbox - adding in gliffy for images - backing out to old names for now - Copy edit pass - Entering comments from the content trust team - Update name of branch and image name - Removing the last diogo reference - Updating with Seb's comments Signed-off-by: Mary Anthony (cherry picked from commit 753bf40f154853c32dbd46150cfd964773110610) --- docs/security/trust/content_trust.md | 291 +++++++++++++++ docs/security/trust/images/tag_signing.png | Bin 0 -> 74416 bytes docs/security/trust/images/trust_.gliffy | 1 + .../trust/images/trust_components.gliffy | 1 + .../trust/images/trust_components.png | Bin 0 -> 124071 bytes .../trust/images/trust_signing.gliffy | 1 + docs/security/trust/images/trust_signing.png | Bin 0 -> 71621 bytes docs/security/trust/images/trust_view.gliffy | 1 + docs/security/trust/images/trust_view.png | Bin 0 -> 59533 bytes docs/security/trust/index.md | 21 ++ docs/security/trust/trust_automation.md | 79 +++++ docs/security/trust/trust_key_mng.md | 74 ++++ docs/security/trust/trust_sandbox.md | 331 ++++++++++++++++++ 13 files changed, 800 insertions(+) create mode 100644 docs/security/trust/content_trust.md create mode 100644 docs/security/trust/images/tag_signing.png create mode 100644 docs/security/trust/images/trust_.gliffy create mode 100644 docs/security/trust/images/trust_components.gliffy create mode 100644 docs/security/trust/images/trust_components.png create mode 100644 docs/security/trust/images/trust_signing.gliffy create mode 100644 docs/security/trust/images/trust_signing.png create mode 100644 docs/security/trust/images/trust_view.gliffy create mode 100644 docs/security/trust/images/trust_view.png create mode 100644 docs/security/trust/index.md create mode 100644 docs/security/trust/trust_automation.md create mode 100644 docs/security/trust/trust_key_mng.md create mode 100644 docs/security/trust/trust_sandbox.md diff --git a/docs/security/trust/content_trust.md b/docs/security/trust/content_trust.md new file mode 100644 index 000000000..ee76ffdca --- /dev/null +++ b/docs/security/trust/content_trust.md @@ -0,0 +1,291 @@ + + +# Content trust in Docker + +When transferring data among networked systems, *trust* is a central concern. In +particular, when communicating over an untrusted medium such as the internet, it +is critical to ensure the integrity and publisher of all the data a system +operates on. You use Docker to push and pull images (data) to a registry. Content trust +gives you the ability to both verify the integrity and the publisher of all the +data received from a registry over any channel. + +Content trust is currently only available for users of the public Docker Hub. It +is currently not available for the Docker Trusted Registry or for private +registries. + +## Understand trust in Docker + +Content trust allows operations with a remote Docker registry to enforce +client-side signing and verification of image tags. Content trust provides the +ability to use digital signatures for data sent to and received from remote +Docker registries. These signatures allow client-side verification of the +integrity and publisher of specific image tags. + +Currently, content trust is disabled by default. You must enabled it by setting +the `DOCKER_CONTENT_TRUST` environment variable. + +Once content trust is enabled, image publishers can sign their images. Image consumers can +ensure that the images they use are signed. publishers and consumers can be +individuals alone or in organizations. Docker's content trust supports users and +automated processes such as builds. + +### Image tags and content trust + +An individual image record has the following identifier: + +``` +[REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG] +``` + +A particular image `REPOSITORY` can have multiple tags. For example, `latest` and + `3.1.2` are both tags on the `mongo` image. An image publisher can build an image + and tag combination many times changing the image with each build. + +Content trust is associated with the `TAG` portion of an image. Each image +repository has a set of keys that image publishers use to sign an image tag. +Image publishers have discretion on which tags they sign. + +An image repository can contain an image with one tag that is signed and another +tag that is not. For example, consider [the Mongo image +repository](https://hub.docker.com/r/library/mongo/tags/). The `latest` +tag could be unsigned while the `3.1.6` tag could be signed. It is the +responsibility of the image publisher to decide if an image tag is signed or +not. In this representation, some image tags are signed, others are not: + +![Signed tags](../images/tag_signing.png) + +Publishers can choose to sign a specific tag or not. As a result, the content of +an unsigned tag and that of a signed tag with the same name may not match. For +example, a publisher can push a tagged image `someimage:latest` and sign it. +Later, the same publisher can push an unsigned `someimage:latest` image. This second +push replaces the last unsigned tag `latest` but does not affect the signed `latest` version. +The ability to choose which tags they can sign, allows publishers to iterate over +the unsigned version of an image before officially signing it. + +Image consumers can enable content trust to ensure that images they use were +signed. If a consumer enables content trust, they can only pull, run, or build +with trusted images. Enabling content trust is like wearing a pair of +rose-colored glasses. Consumers "see" only signed images tags and the less +desirable, unsigned image tags are "invisible" to them. + +![Trust view](../images/trust_view.png) + +To the consumer who does not enabled content trust, nothing about how they +work with Docker images changes. Every image is visible regardless of whether it +is signed or not. + + +### Content trust operations and keys + +When content trust is enabled, `docker` CLI commands that operate on tagged images must +either have content signatures or explicit content hashes. The commands that +operate with content trust are: + +* `push` +* `build` +* `create` +* `pull` +* `run` + +For example, with content trust enabled a `docker pull someimage:latest` only +succeeds if `someimage:latest` is signed. However, an operation with an explicit +content hash always succeeds as long as the hash exists: + +```bash +$ docker pull someimage@sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a +``` + +Trust for an image tag is managed through the use of signing keys. Docker's content +trust makes use four different keys: + +| Key | Description | +|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| offline key | Root of content trust for a image tag. When content trust is enabled, you create the offline key once. | +| target and snapshot | These two keys are known together as the "tagging" key. When content trust is enabled, you create this key when you add a new image repository. If you have the offline key, you can export the tagging key and allow other publishers to sign the image tags. | +| timestamp | This key applies to a repository. It allows Docker repositories to have freshness security guarantees without requiring periodic content refreshes on the client's side. | + +With the exception of the timestamp, all the keys are generated and stored locally +client-side. The timestamp is safely generated and stored in a signing server that +is deployed alongside the Docker registry. All keys are generated in a backend +service that isn't directly exposed to the internet and are encrypted at rest. + +The following image depicts the various signing keys and their relationships: + +![Content trust components](../images/trust_components.png) + +>**WARNING**: Loss of the offline key is **very difficult** to recover from. +>Correcting this loss requires intervention from [Docker +>Support](https://support.docker.com) to reset the repository state. This loss +>also requires **manual intervention** from every consumer that used a signed +>tag from this repository prior to the loss. + +You should backup the offline key somewhere safe. Given that it is only required +to create new repositories, it is a good idea to store it offline. Make sure you +read [Manage keys for content trust](/security/trust/trust_key_mng) information +for details on creating, securing, and backing up your keys. + +## Survey of typical content trust operations + +This section surveys the typical trusted operations users perform with Docker +images. + +### Enable content trust + +Enable content trust by setting the `DOCKER_CONTENT_TRUST` environment variable. +Enabling per-shell is useful because you can have one shell configured for +trusted operations and another terminal shell for untrusted operations. You can +also add this declaration to your shell profile to have it turned on always by +default. + +To enable content trust in a `bash` shell enter the following command: + +```bash +export DOCKER_CONTENT_TRUST=1 +``` + +Once set, each of the "tag" operations require key for trusted tag. All of these +commands also support the `--disable-content-trust` flag. This flag allows +publishers to run individual operations on tagged images without content trust on an +as-needed basis. + + +### Push trusted content + +To create signed content for a specific image tag, simply enable content trust and push +a tagged image. If this is the first time you have pushed an image using content trust +on your system, the session looks like this: + +```bash +$ docker push docker/trusttest:latest +The push refers to a repository [docker.io/docker/trusttest] (len: 1) +9a61b6b1315e: Image already exists +902b87aaaec9: Image already exists +latest: digest: sha256:d02adacee0ac7a5be140adb94fa1dae64f4e71a68696e7f8e7cbf9db8dd49418 size: 3220 +Signing and pushing trust metadata +You are about to create a new offline signing key passphrase. This passphrase +will be used to protect the most sensitive key in your signing system. Please +choose a long, complex passphrase and be careful to keep the password and the +key file itself secure and backed up. It is highly recommended that you use a +password manager to generate the passphrase and keep it safe. There will be no +way to recover this key. You can find the key in your config directory. +Enter passphrase for new offline key with id a1d96fb: +Repeat passphrase for new offline key with id a1d96fb: +Enter passphrase for new tagging key with id docker.io/docker/trusttest (3a932f1): +Repeat passphrase for new tagging key with id docker.io/docker/trusttest (3a932f1): +Finished initializing "docker.io/docker/trusttest" +``` +When you push your first tagged image with content trust enabled, the `docker` client +recognizes this is your first push and: + + - alerts you that it will create a new offline key + - requests a passphrase for the key + - generates an offline key in the `~/.docker/trust` directory + - generates a tagging key for in the `~/.docker/trust` directory + +The passphrase you chose for both the offline key and your content key-pair should +be randomly generated and stored in a *password manager*. + +It is important to note, if you had left off the `latest` tag, content trust is skipped. +This is true even if content trust is enabled and even if this is your first push. + +```bash +$ docker push docker/trusttest +The push refers to a repository [docker.io/docker/trusttest] (len: 1) +9a61b6b1315e: Image successfully pushed +902b87aaaec9: Image successfully pushed +latest: digest: sha256:a9a9c4402604b703bed1c847f6d85faac97686e48c579bd9c3b0fa6694a398fc size: 3220 +No tag specified, skipping trust metadata push +``` + +It is skipped because as the message states, you did not supply an image `TAG` +value. In Docker content trust, signatures are associated with tags. + +Once you have an offline key on your system, subsequent images repositories +you create can use that same offline key: + +```bash +$ docker push docker.io/docker/seaside:latest +The push refers to a repository [docker.io/docker/seaside] (len: 1) +a9539b34a6ab: Image successfully pushed +b3dbab3810fc: Image successfully pushed +latest: digest: sha256:d2ba1e603661a59940bfad7072eba698b79a8b20ccbb4e3bfb6f9e367ea43939 size: 3346 +Signing and pushing trust metadata +Enter key passphrase for offline key with id a1d96fb: +Enter passphrase for new tagging key with id docker.io/docker/seaside (bb045e3): +Repeat passphrase for new tagging key with id docker.io/docker/seaside (bb045e3): +Finished initializing "docker.io/docker/seaside" +``` + +The new image has its own tagging key and timestamp key. The `latest` tag is signed with both of +these. + + +### Pull image content + +A common way to consume an image is to `pull` it. With content trust enabled, the Docker +client only allows `docker pull` to retrieve signed images. + +``` +$ docker pull docker/seaside +Using default tag: latest +Pull (1 of 1): docker/trusttest:latest@sha256:d149ab53f871 +... +Tagging docker/trusttest@sha256:d149ab53f871 as docker/trusttest:latest +``` + +The `seaside:latest` image is signed. In the following example, the command does not specify a tag, so the system uses +the `latest` tag by default again and the `docker/cliffs:latest` tag is not signed. + +```bash +$ docker pull docker/cliffs +Using default tag: latest +no trust data available +``` + +Because the tag `docker/cliffs:latest` is not trusted, the `pull` fails. + + +### Disable content trust for specific operations + +A user that wants to disable content trust for a particular operation can use the +`--disable-content-trust` flag. **Warning: this flag disables content trust for +this operation**. With this flag, Docker will ignore content-trust and allow all +operations to be done without verifying any signatures. If we wanted the +previous untrusted build to succeed we could do: + +``` +$ cat Dockerfile +FROM docker/trusttest:notrust +RUN echo +$ docker build --disable-content-trust -t docker/trusttest:testing . +Sending build context to Docker daemon 42.84 MB +... +Successfully built f21b872447dc +``` + +The same is true for all the other commands, such as `pull` and `push`: + +``` +$ docker pull --disable-content-trust docker/trusttest:untrusted +... +$ docker push --disable-content-trust docker/trusttest:untrusted +... +``` + +## Related information + +* [Manage keys for content trust](/security/trust/trust_key_mng) +* [Automation with content trust](/security/trust/trust_automation) +* [Play in a content trust sandbox](/security/trust/trust_sandbox) + + + diff --git a/docs/security/trust/images/tag_signing.png b/docs/security/trust/images/tag_signing.png new file mode 100644 index 0000000000000000000000000000000000000000..9a1f9062b4dc6d30942ab7c4f47dca5f5df2be31 GIT binary patch literal 74416 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 Key

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":311.0,"y":147.0,"rotation":0.0,"id":268,"width":18.0,"height":53.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":178,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":152,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":264,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":17,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-3.417721518987321,-4.214000000000027],[9.708860759493689,-4.214000000000027],[9.708860759493689,50.74999999999994],[22.8354430379747,50.74999999999994]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":415.0,"y":313.0,"rotation":0.0,"id":250,"width":7.0,"height":413.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":172,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":79,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[3.5,-3.0],[9.5,497.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":290.0,"y":340.0,"rotation":0.0,"id":11,"width":63.0,"height":82.0,"uid":"com.gliffy.shape.network.network_v4.business.user","order":12,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.user","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":12,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Account

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":479.0,"y":330.0,"rotation":0.0,"id":2,"width":120.0,"height":80.0,"uid":"com.gliffy.shape.network.network_v4.business.user_group","order":9,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.user_group","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":3,"width":73.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Organization

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":159.0,"y":310.0,"rotation":0.0,"id":79,"width":531.0,"height":500.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#ffffff","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":159.00000000000003,"y":320.0,"rotation":0.0,"id":82,"width":108.99999999999999,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Registry

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":730.0,"y":340.0,"rotation":0.0,"id":86,"width":61.0,"height":79.0,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":59,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#ff0000","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":87,"width":62.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Offline key

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":730.0,"y":455.0,"rotation":0.0,"id":88,"width":61.0,"height":79.0,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":62,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":70.0,"height":14.0,"uid":null,"order":64,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Tagging key

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":360.4891500904159,"y":650.0,"rotation":0.0,"id":227,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":158,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":228,"width":16.0,"height":18.0,"uid":null,"order":160,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

X

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":185.1428571428571,"y":587.0,"rotation":0.0,"id":109,"width":187.85714285714286,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":81,"lockAspectRatio":false,"lockShape":false,"children":[{"x":7.142857142857139,"y":50.0,"rotation":0.0,"id":98,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":74,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":71.42857142857143,"height":50.0,"uid":null,"order":77,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":98}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":98}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":100,"width":50.0,"height":18.0,"uid":null,"order":80,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":98,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

working

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":7.571428571428527,"y":0.0,"rotation":0.0,"id":95,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":66,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":96,"width":71.42857142857143,"height":50.0,"uid":null,"order":69,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":95}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":95}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":97,"width":38.0,"height":18.0,"uid":null,"order":72,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":95,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":77.85714285714286,"y":8.0,"rotation":0.0,"id":30,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":24,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":31,"width":110.00000000000001,"height":25.0,"uid":null,"order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":32}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":32,"width":110.00000000000001,"height":25.0,"uid":null,"order":31,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":33,"width":110.00000000000001,"height":55.0,"uid":null,"order":34,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":30},{"magnitude":-1,"id":32}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":32,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":184.21428571428567,"y":450.0,"rotation":0.0,"id":253,"width":187.85714285714286,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":173,"lockAspectRatio":false,"lockShape":false,"children":[{"x":77.85714285714286,"y":8.0,"rotation":0.0,"id":125,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":83,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":126,"width":110.00000000000001,"height":25.0,"uid":null,"order":86,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":127}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":127,"width":110.00000000000001,"height":25.0,"uid":null,"order":90,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":128,"width":110.00000000000001,"height":55.0,"uid":null,"order":93,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":125},{"magnitude":-1,"id":127}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":127,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":7.571428571428527,"y":0.0,"rotation":0.0,"id":122,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":95,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":123,"width":71.42857142857143,"height":50.0,"uid":null,"order":98,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":122}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":122}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":124,"width":38.0,"height":18.0,"uid":null,"order":101,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":122,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":7.142857142857139,"y":50.0,"rotation":0.0,"id":119,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":103,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":120,"width":71.42857142857143,"height":50.0,"uid":null,"order":106,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":119}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":119}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":121,"width":26.0,"height":18.0,"uid":null,"order":109,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":119,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

2.0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":479.0,"y":120.74999999999994,"rotation":0.0,"id":261,"width":155.08307142857143,"height":168.072,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":174,"lockAspectRatio":false,"lockShape":false,"children":[{"x":85.65449999999998,"y":38.0,"rotation":0.0,"id":245,"width":28.0,"height":43.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":171,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":193,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":204,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":17,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.5108499095841808,-13.999999999999972],[16.0465641952984,-13.999999999999972],[16.0465641952984,39.0],[29.582278481012622,39.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":null},{"x":89.65449999999998,"y":25.0,"rotation":0.0,"id":244,"width":24.0,"height":1.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":169,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":193,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":192,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":17,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-1.4891500904158192,-0.9999999999999716],[7.534659433393699,-0.9999999999999716],[16.558468957203104,-0.9999999999999716],[25.582278481012622,-0.9999999999999716]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":null},{"x":115.2367784810126,"y":62.0,"rotation":0.0,"id":204,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":151,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":205,"width":15.0,"height":16.0,"uid":null,"order":154,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

C

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":null},{"x":115.2367784810126,"y":9.000000000000028,"rotation":0.0,"id":192,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":148,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":201,"width":15.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

A

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":null},{"x":65.0007929475588,"y":9.000000000000028,"rotation":0.0,"id":193,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":141,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#ff0000","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":194,"width":14.0,"height":18.0,"uid":null,"order":144,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

2

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":null},{"x":55.08307142857143,"y":0.0,"rotation":0.0,"id":195,"width":100.0,"height":133.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.speech_bubble_right","order":129,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"magnitude":1,"id":197},{"magnitude":1,"id":198}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":196,"width":100.0,"height":118.0,"uid":null,"order":132,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":195,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":195},{"magnitude":-1,"id":198}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":195}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.speech_bubble","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":197,"width":100.0,"height":29.0,"uid":null,"order":136,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":195}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

 

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":36.0,"y":117.0,"rotation":0.0,"id":198,"width":24.0,"height":15.0,"uid":null,"order":139,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":24}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":15}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":196,"px":1.0,"py":1.0,"xOffset":-64.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.speech_bubble_right","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":67.0,"rotation":0.0,"id":180,"width":67.309,"height":101.072,"uid":"com.gliffy.shape.cisco.cisco_v1.buildings.generic_building","order":126,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.buildings.generic_building","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":182,"width":56.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Company

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":231.1785714285715,"y":204.78599999999997,"rotation":0.0,"id":0,"width":63.0,"height":82.0,"uid":"com.gliffy.shape.network.network_v4.business.female_user","order":6,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.female_user","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":1,"width":43.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Person

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":272.07142857142856,"y":120.286,"rotation":0.0,"id":171,"width":100.0,"height":132.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.speech_bubble_right","order":112,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"magnitude":1,"id":173},{"magnitude":1,"id":174}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":172,"width":100.0,"height":117.0,"uid":null,"order":114,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":171,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":171},{"magnitude":-1,"id":174}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":171}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.speech_bubble","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":173,"width":100.0,"height":29.0,"uid":null,"order":117,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":171}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

 

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":36.0,"y":116.0,"rotation":0.0,"id":174,"width":24.0,"height":15.0,"uid":null,"order":119,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":24}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":15}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":172,"px":1.0,"py":1.0,"xOffset":-64.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.speech_bubble_right","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":310.5,"y":146.78599999999997,"rotation":0.0,"id":239,"width":20.0,"height":1.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":167,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":152,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":237,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":17,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-2.917721518987321,-4.0],[6.078661844484657,-4.0],[15.075045207956578,-4.0],[24.071428571428555,-4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":333.8354430379747,"y":182.74999999999994,"rotation":0.0,"id":264,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":175,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":265,"width":21.0,"height":18.0,"uid":null,"order":177,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

 N

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":284.4177215189874,"y":127.78599999999997,"rotation":0.0,"id":152,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":120,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#ff0000","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":153,"width":14.0,"height":18.0,"uid":null,"order":122,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

1

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":334.57142857142856,"y":127.78599999999997,"rotation":0.0,"id":237,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":164,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":238,"width":16.0,"height":18.0,"uid":null,"order":166,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

X

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":565.0,"y":500.0,"rotation":0.0,"id":40,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":1,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":41,"width":71.42857142857143,"height":50.0,"uid":null,"order":3,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":40}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":40}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285666,"y":0.0,"rotation":0.0,"id":42,"width":26.0,"height":18.0,"uid":null,"order":5,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":40,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

1.0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":454.99999999999994,"y":461.0,"rotation":0.0,"id":16,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":15,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":17,"width":110.00000000000001,"height":25.0,"uid":null,"order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":18}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":18,"width":110.00000000000001,"height":25.0,"uid":null,"order":20,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":19,"width":110.00000000000001,"height":55.0,"uid":null,"order":22,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":16},{"magnitude":-1,"id":18}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":18,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":565.0,"y":450.0,"rotation":0.0,"id":37,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":35,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":38,"width":71.42857142857143,"height":50.0,"uid":null,"order":37,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":37}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":37}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285666,"y":0.0,"rotation":0.0,"id":39,"width":38.0,"height":18.0,"uid":null,"order":39,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":37,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":443.4177215189873,"y":513.0,"rotation":0.0,"id":229,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":161,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":230,"width":15.0,"height":16.0,"uid":null,"order":163,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

A

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":565.0,"y":630.0,"rotation":0.0,"id":63,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":40,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":64,"width":71.42857142857143,"height":50.0,"uid":null,"order":42,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":63}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":63}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285666,"y":0.0,"rotation":0.0,"id":65,"width":68.0,"height":18.0,"uid":null,"order":44,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":63,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

producttion

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":454.99999999999994,"y":591.0,"rotation":0.0,"id":58,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":45,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":59,"width":110.00000000000001,"height":25.0,"uid":null,"order":47,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":60}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":60,"width":110.00000000000001,"height":25.0,"uid":null,"order":50,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":61,"width":110.00000000000001,"height":55.0,"uid":null,"order":52,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":58},{"magnitude":-1,"id":60}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":60,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":565.0,"y":580.0,"rotation":0.0,"id":55,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":53,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":56,"width":71.42857142857143,"height":50.0,"uid":null,"order":55,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":55}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":55}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285666,"y":0.0,"rotation":0.0,"id":57,"width":28.0,"height":18.0,"uid":null,"order":57,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":55,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

test

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":443.4177215189873,"y":646.0,"rotation":0.0,"id":221,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":155,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":222,"width":15.0,"height":16.0,"uid":null,"order":157,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

C

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":565.0,"y":745.0,"rotation":0.0,"id":281,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":179,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":282,"width":71.42857142857143,"height":50.0,"uid":null,"order":181,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":281}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":281}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285666,"y":0.0,"rotation":0.0,"id":283,"width":48.0,"height":18.0,"uid":null,"order":183,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":281,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

release

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":454.99999999999994,"y":706.0,"rotation":0.0,"id":277,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":184,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":278,"width":110.00000000000001,"height":25.0,"uid":null,"order":186,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":279}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":279,"width":110.00000000000001,"height":25.0,"uid":null,"order":189,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":280,"width":110.00000000000001,"height":55.0,"uid":null,"order":191,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":277},{"magnitude":-1,"id":279}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":279,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":565.0,"y":695.0,"rotation":0.0,"id":274,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":192,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":275,"width":71.42857142857143,"height":50.0,"uid":null,"order":194,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":274}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":274}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285666,"y":0.0,"rotation":0.0,"id":276,"width":26.0,"height":18.0,"uid":null,"order":196,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":274,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

7.5

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":360.4891500904159,"y":510.0,"rotation":0.0,"id":289,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":197,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":290,"width":21.0,"height":18.0,"uid":null,"order":199,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

 N

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":332.57142857142856,"y":532.0,"rotation":0.0,"id":301,"width":30.0,"height":30.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.events.timer_intermediate","order":205,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.timer_intermediate.bpmn_v1","strokeWidth":2.0,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":330.4177215189874,"y":670.0,"rotation":0.0,"id":302,"width":30.0,"height":30.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.events.timer_intermediate","order":206,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.timer_intermediate.bpmn_v1","strokeWidth":2.0,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":466.5822784810126,"y":667.0,"rotation":0.0,"id":303,"width":30.0,"height":30.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.events.timer_intermediate","order":207,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.timer_intermediate.bpmn_v1","strokeWidth":2.0,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":621.401335443038,"y":508.0,"rotation":0.0,"id":306,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":209,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":621.401335443038,"y":459.0,"rotation":0.0,"id":307,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":210,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":621.401335443038,"y":589.0,"rotation":0.0,"id":308,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":211,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":186.21428571428567,"y":594.0,"rotation":0.0,"id":309,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":212,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":189.21428571428567,"y":644.0,"rotation":0.0,"id":310,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":213,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":810.0,"y":358.5,"rotation":0.0,"id":164,"width":217.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":110,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

A offline key is used to create repository keys. Offline keys belong to a person or an organization. Resides client-side. You should store these in a safe place and back them up. 

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":810.0,"y":487.5,"rotation":0.0,"id":170,"width":217.0,"height":56.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":111,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

A tagging key is associated with an image repository. publishers with this key can push or pull any tag in this repository. This resides on client-side.

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":810.0,"y":587.0,"rotation":0.0,"id":298,"width":217.0,"height":42.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":203,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

A timestamp key is associated with an image repository. This is created by Docker and resides on the server.

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":743.3333333333334,"y":681.0,"rotation":0.0,"id":314,"width":283.66666666666663,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":215,"lockAspectRatio":false,"lockShape":false,"children":[{"x":66.66666666666663,"y":4.0,"rotation":0.0,"id":312,"width":217.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":214,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Signed tag.

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":0.0,"y":0.0,"rotation":0.0,"id":304,"width":33.333333333333336,"height":20.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":208,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"}],"layers":[{"guid":"dockVlz9GmcW","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":216}],"shapeStyles":{},"lineStyles":{"global":{"strokeWidth":1,"endArrow":17}},"textStyles":{"global":{"size":"16px","color":"#000000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.cisco.cisco_v1.buildings","com.gliffy.libraries.sitemap.sitemap_v2","com.gliffy.libraries.sitemap.sitemap_v1.default","com.gliffy.libraries.ui.ui_v3.containers_content","com.gliffy.libraries.table.table_v2.default","com.gliffy.libraries.ui.ui_v3.navigation","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.ui.ui_v3.icon_symbols","com.gliffy.libraries.ui.ui_v2.forms_components","com.gliffy.libraries.ui.ui_v2.content","com.gliffy.libraries.ui.ui_v2.miscellaneous","com.gliffy.libraries.network.network_v4.business","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.bpmn.bpmn_v1.events","com.gliffy.libraries.bpmn.bpmn_v1.activities","com.gliffy.libraries.bpmn.bpmn_v1.data_artifacts","com.gliffy.libraries.bpmn.bpmn_v1.gateways","com.gliffy.libraries.bpmn.bpmn_v1.connectors","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.images"],"lastSerialized":1439068390533},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file diff --git a/docs/security/trust/images/trust_components.gliffy b/docs/security/trust/images/trust_components.gliffy new file mode 100644 index 000000000..07c859bb1 --- /dev/null +++ b/docs/security/trust/images/trust_components.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":881,"height":704,"nodeIndex":316,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":null,"printShrinkToFit":false,"printPortrait":false,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":10,"y":10},"max":{"x":880.0000000000001,"y":703.7139999999999}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":10.0,"y":199.714,"rotation":0.0,"id":79,"width":531.0,"height":500.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#ffffff","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":389.714,"rotation":0.0,"id":40,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":1,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":41,"width":71.42857142857143,"height":50.0,"uid":null,"order":3,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":40}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":40}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":42,"width":26.0,"height":18.0,"uid":null,"order":5,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":40,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

1.0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":82.1785714285715,"y":94.49999999999997,"rotation":0.0,"id":0,"width":63.0,"height":82.0,"uid":"com.gliffy.shape.network.network_v4.business.female_user","order":6,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.female_user","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":1,"width":43.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Person

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":330.0,"y":219.714,"rotation":0.0,"id":2,"width":120.0,"height":80.0,"uid":"com.gliffy.shape.network.network_v4.business.user_group","order":9,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.user_group","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":3,"width":73.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Organization

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":141.0,"y":229.714,"rotation":0.0,"id":11,"width":63.0,"height":82.0,"uid":"com.gliffy.shape.network.network_v4.business.user","order":12,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.user","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":12,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Account

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":305.99999999999994,"y":350.714,"rotation":0.0,"id":16,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":15,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":17,"width":110.00000000000001,"height":25.0,"uid":null,"order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":18}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":18,"width":110.00000000000001,"height":25.0,"uid":null,"order":20,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":19,"width":110.00000000000001,"height":55.0,"uid":null,"order":22,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":16},{"magnitude":-1,"id":18}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":18,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":339.714,"rotation":0.0,"id":37,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":35,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":38,"width":71.42857142857143,"height":50.0,"uid":null,"order":37,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":37}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":37}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":39,"width":38.0,"height":18.0,"uid":null,"order":39,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":37,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":519.7139999999999,"rotation":0.0,"id":63,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":40,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":64,"width":71.42857142857143,"height":50.0,"uid":null,"order":42,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":63}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":63}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":65,"width":68.0,"height":18.0,"uid":null,"order":44,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":63,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

producttion

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":305.99999999999994,"y":480.71399999999994,"rotation":0.0,"id":58,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":45,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":59,"width":110.00000000000001,"height":25.0,"uid":null,"order":47,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":60}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":60,"width":110.00000000000001,"height":25.0,"uid":null,"order":50,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":61,"width":110.00000000000001,"height":55.0,"uid":null,"order":52,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":58},{"magnitude":-1,"id":60}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":60,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":469.714,"rotation":0.0,"id":55,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":53,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":56,"width":71.42857142857143,"height":50.0,"uid":null,"order":55,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":55}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":55}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":57,"width":28.0,"height":18.0,"uid":null,"order":57,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":55,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

test

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":10.000000000000036,"y":209.714,"rotation":0.0,"id":82,"width":108.99999999999999,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Registry

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":581.0,"y":229.714,"rotation":0.0,"id":86,"width":61.0,"height":79.0,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":59,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#ff0000","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":87,"width":62.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Offline key

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":581.0,"y":344.714,"rotation":0.0,"id":88,"width":61.0,"height":79.0,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":62,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":89,"width":70.0,"height":14.0,"uid":null,"order":64,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Tagging key

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":36.142857142857125,"y":476.71399999999994,"rotation":0.0,"id":109,"width":187.85714285714286,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":81,"lockAspectRatio":false,"lockShape":false,"children":[{"x":7.142857142857139,"y":50.0,"rotation":0.0,"id":98,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":74,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":71.42857142857143,"height":50.0,"uid":null,"order":77,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":98}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":98}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":100,"width":50.0,"height":18.0,"uid":null,"order":80,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":98,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

working

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":7.571428571428527,"y":0.0,"rotation":0.0,"id":95,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":66,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":96,"width":71.42857142857143,"height":50.0,"uid":null,"order":69,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":95}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":95}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":97,"width":38.0,"height":18.0,"uid":null,"order":72,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":95,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":77.85714285714286,"y":8.0,"rotation":0.0,"id":30,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":24,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":31,"width":110.00000000000001,"height":25.0,"uid":null,"order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":32}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":32,"width":110.00000000000001,"height":25.0,"uid":null,"order":31,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":33,"width":110.00000000000001,"height":55.0,"uid":null,"order":34,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":30},{"magnitude":-1,"id":32}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":32,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":661.0,"y":248.214,"rotation":0.0,"id":164,"width":217.0,"height":70.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":110,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

A offline key is used to create tagging keys. Offline keys belong to a person or an organization. Resides client-side. You should store these in a safe place and back them up. 

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":661.0,"y":377.214,"rotation":0.0,"id":170,"width":217.0,"height":56.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":111,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

A tagging key is associated with an image repository. Creators with this key can push or pull any tag in this repository. This resides on client-side.

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":123.07142857142856,"y":10.0,"rotation":0.0,"id":171,"width":100.0,"height":132.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.speech_bubble_right","order":112,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"magnitude":1,"id":173},{"magnitude":1,"id":174}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":172,"width":100.0,"height":117.0,"uid":null,"order":114,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":171,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":171},{"magnitude":-1,"id":174}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":171}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.speech_bubble","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":173,"width":100.0,"height":29.0,"uid":null,"order":117,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":171}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

 

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":36.0,"y":116.0,"rotation":0.0,"id":174,"width":24.0,"height":15.0,"uid":null,"order":119,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":24}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":15}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":172,"px":1.0,"py":1.0,"xOffset":-64.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.speech_bubble_right","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":135.41772151898738,"y":17.499999999999968,"rotation":0.0,"id":152,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":120,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#ff0000","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":153,"width":14.0,"height":18.0,"uid":null,"order":122,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

1

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":294.4177215189873,"y":535.7139999999999,"rotation":0.0,"id":221,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":155,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":222,"width":15.0,"height":16.0,"uid":null,"order":157,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

C

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":211.48915009041588,"y":539.7139999999999,"rotation":0.0,"id":227,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":158,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":228,"width":16.0,"height":18.0,"uid":null,"order":160,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

X

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":294.4177215189873,"y":402.714,"rotation":0.0,"id":229,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":161,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":230,"width":15.0,"height":16.0,"uid":null,"order":163,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

A

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":185.57142857142856,"y":17.499999999999968,"rotation":0.0,"id":237,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":164,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":238,"width":16.0,"height":18.0,"uid":null,"order":166,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

X

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":161.5,"y":36.49999999999997,"rotation":0.0,"id":239,"width":20.0,"height":1.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":167,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":152,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":237,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":17,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-2.9177215189872925,-4.0],[6.078661844484657,-4.0],[15.075045207956606,-4.0],[24.071428571428555,-4.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":266.0,"y":202.714,"rotation":0.0,"id":250,"width":7.0,"height":413.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":172,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":79,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[3.5,-3.0],[9.5,496.99999999999994]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":35.21428571428568,"y":339.714,"rotation":0.0,"id":253,"width":187.85714285714286,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":173,"lockAspectRatio":false,"lockShape":false,"children":[{"x":77.85714285714286,"y":8.0,"rotation":0.0,"id":125,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":83,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":126,"width":110.00000000000001,"height":25.0,"uid":null,"order":86,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":127}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":127,"width":110.00000000000001,"height":25.0,"uid":null,"order":90,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":128,"width":110.00000000000001,"height":55.0,"uid":null,"order":93,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":125},{"magnitude":-1,"id":127}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":127,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":7.571428571428527,"y":0.0,"rotation":0.0,"id":122,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":95,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":123,"width":71.42857142857143,"height":50.0,"uid":null,"order":98,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":122}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":122}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":124,"width":38.0,"height":18.0,"uid":null,"order":101,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":122,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":7.142857142857139,"y":50.0,"rotation":0.0,"id":119,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":103,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":120,"width":71.42857142857143,"height":50.0,"uid":null,"order":106,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":119}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":119}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":121,"width":26.0,"height":18.0,"uid":null,"order":109,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":119,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

2.0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":330.0,"y":10.463999999999942,"rotation":0.0,"id":261,"width":155.08307142857143,"height":168.072,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":174,"lockAspectRatio":false,"lockShape":false,"children":[{"x":85.65449999999998,"y":38.0,"rotation":0.0,"id":245,"width":28.0,"height":43.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":171,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":193,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":204,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":17,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.510849909584124,-13.999999999999972],[16.0465641952984,-13.999999999999972],[16.0465641952984,39.0],[29.582278481012622,39.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":null},{"x":89.65449999999998,"y":25.0,"rotation":0.0,"id":244,"width":24.0,"height":1.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":169,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":193,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":192,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":17,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-1.489150090415876,-0.9999999999999716],[7.534659433393642,-0.9999999999999716],[16.558468957203104,-0.9999999999999716],[25.582278481012622,-0.9999999999999716]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":null},{"x":115.2367784810126,"y":62.0,"rotation":0.0,"id":204,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":151,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":205,"width":15.0,"height":16.0,"uid":null,"order":154,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

C

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":null},{"x":115.2367784810126,"y":9.000000000000028,"rotation":0.0,"id":192,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":148,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":201,"width":15.0,"height":16.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

A

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":null},{"x":65.0007929475588,"y":9.000000000000028,"rotation":0.0,"id":193,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":141,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#ff0000","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":194,"width":14.0,"height":18.0,"uid":null,"order":144,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

2

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":null},{"x":55.08307142857143,"y":0.0,"rotation":0.0,"id":195,"width":100.0,"height":133.0,"uid":"com.gliffy.shape.ui.ui_v3.containers_content.speech_bubble_right","order":129,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"MinWidthConstraint","MinWidthConstraint":{"width":100}},{"type":"HeightConstraint","HeightConstraint":{"isMin":true,"heightInfo":[{"magnitude":1,"id":197},{"magnitude":1,"id":198}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":196,"width":100.0,"height":118.0,"uid":null,"order":132,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":195,"px":0.0,"py":0.0,"xOffset":0.0,"yOffset":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":195},{"magnitude":-1,"id":198}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":195}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.speech_bubble","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":197,"width":100.0,"height":29.0,"uid":null,"order":136,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":195}],"minWidth":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

 

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":36.0,"y":117.0,"rotation":0.0,"id":198,"width":24.0,"height":15.0,"uid":null,"order":139,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"ConstWidthConstraint","ConstWidthConstraint":{"width":24}},{"type":"ConstHeightConstraint","ConstHeightConstraint":{"height":15}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":196,"px":1.0,"py":1.0,"xOffset":-64.0,"yOffset":-1.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.containers_content.speech_bubble_right","strokeWidth":2.0,"strokeColor":"#BBBBBB","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":67.0,"rotation":0.0,"id":180,"width":67.309,"height":101.072,"uid":"com.gliffy.shape.cisco.cisco_v1.buildings.generic_building","order":126,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.buildings.generic_building","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":182,"width":56.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Company

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":184.8354430379747,"y":72.46399999999994,"rotation":0.0,"id":264,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":175,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":265,"width":21.0,"height":18.0,"uid":null,"order":177,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

 N

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":162.0,"y":36.714,"rotation":0.0,"id":268,"width":18.0,"height":53.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":178,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":152,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":264,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":17,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-3.4177215189872925,-4.214000000000027],[9.708860759493689,-4.214000000000027],[9.708860759493689,50.74999999999994],[22.8354430379747,50.74999999999994]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":634.7139999999999,"rotation":0.0,"id":281,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":179,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":282,"width":71.42857142857143,"height":50.0,"uid":null,"order":181,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":281}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":281}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":283,"width":48.0,"height":18.0,"uid":null,"order":183,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":281,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

release

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":305.99999999999994,"y":595.7139999999999,"rotation":0.0,"id":277,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":184,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":278,"width":110.00000000000001,"height":25.0,"uid":null,"order":186,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":279}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":279,"width":110.00000000000001,"height":25.0,"uid":null,"order":189,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":280,"width":110.00000000000001,"height":55.0,"uid":null,"order":191,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":277},{"magnitude":-1,"id":279}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":279,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":584.7139999999999,"rotation":0.0,"id":274,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":192,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":275,"width":71.42857142857143,"height":50.0,"uid":null,"order":194,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":274}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":274}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":276,"width":26.0,"height":18.0,"uid":null,"order":196,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":274,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

7.5

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":211.48915009041588,"y":399.714,"rotation":0.0,"id":289,"width":23.16455696202532,"height":30.000000000000007,"uid":"com.gliffy.shape.network.network_v4.business.encrypted","order":197,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.encrypted","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":290,"width":21.0,"height":18.0,"uid":null,"order":199,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

 N

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":584.0,"y":467.714,"rotation":0.0,"id":294,"width":54.0,"height":54.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.events.timer_intermediate","order":200,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.timer_intermediate.bpmn_v1","strokeWidth":2.0,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":297,"width":88.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Timestamp Key

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":661.0,"y":476.714,"rotation":0.0,"id":298,"width":217.0,"height":42.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":203,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

A timestamp key is associated with an image repository. This is created by Docker and resides on the server.

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":316.5822784810126,"y":420.714,"rotation":0.0,"id":299,"width":30.0,"height":30.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.events.timer_intermediate","order":204,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.timer_intermediate.bpmn_v1","strokeWidth":2.0,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":183.57142857142856,"y":421.714,"rotation":0.0,"id":301,"width":30.0,"height":30.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.events.timer_intermediate","order":205,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.timer_intermediate.bpmn_v1","strokeWidth":2.0,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":181.41772151898738,"y":559.7139999999999,"rotation":0.0,"id":302,"width":30.0,"height":30.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.events.timer_intermediate","order":206,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.timer_intermediate.bpmn_v1","strokeWidth":2.0,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":317.5822784810126,"y":556.7139999999999,"rotation":0.0,"id":303,"width":30.0,"height":30.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.events.timer_intermediate","order":207,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.timer_intermediate.bpmn_v1","strokeWidth":2.0,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":472.40133544303796,"y":397.714,"rotation":0.0,"id":306,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":209,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":472.40133544303796,"y":348.714,"rotation":0.0,"id":307,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":210,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":472.40133544303796,"y":478.714,"rotation":0.0,"id":308,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":211,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":37.214285714285666,"y":483.714,"rotation":0.0,"id":309,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":212,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":40.214285714285666,"y":533.7139999999999,"rotation":0.0,"id":310,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":213,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":594.3333333333335,"y":570.7139999999999,"rotation":0.0,"id":314,"width":283.66666666666663,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":215,"lockAspectRatio":false,"lockShape":false,"children":[{"x":66.66666666666663,"y":4.0,"rotation":0.0,"id":312,"width":217.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":214,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Signed tag.

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":0.0,"y":0.0,"rotation":0.0,"id":304,"width":33.333333333333336,"height":20.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":208,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"}],"layers":[{"guid":"dockVlz9GmcW","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":216}],"shapeStyles":{},"lineStyles":{"global":{"strokeWidth":1,"endArrow":17}},"textStyles":{"global":{"size":"16px","color":"#000000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.cisco.cisco_v1.buildings","com.gliffy.libraries.sitemap.sitemap_v2","com.gliffy.libraries.sitemap.sitemap_v1.default","com.gliffy.libraries.ui.ui_v3.containers_content","com.gliffy.libraries.table.table_v2.default","com.gliffy.libraries.ui.ui_v3.navigation","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.ui.ui_v3.icon_symbols","com.gliffy.libraries.ui.ui_v2.forms_components","com.gliffy.libraries.ui.ui_v2.content","com.gliffy.libraries.ui.ui_v2.miscellaneous","com.gliffy.libraries.network.network_v4.business","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.bpmn.bpmn_v1.events","com.gliffy.libraries.bpmn.bpmn_v1.activities","com.gliffy.libraries.bpmn.bpmn_v1.data_artifacts","com.gliffy.libraries.bpmn.bpmn_v1.gateways","com.gliffy.libraries.bpmn.bpmn_v1.connectors","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.images"],"lastSerialized":1439174260766},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file diff --git a/docs/security/trust/images/trust_components.png b/docs/security/trust/images/trust_components.png new file mode 100644 index 0000000000000000000000000000000000000000..039dfc8cf323cf1933ebb385a5b0edd2e6be37ab GIT binary patch literal 124071 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⁢TU`eop|-D&mDE^r8- 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�F@G@g&NL2I{H@a(~7fKAG%LonU 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 literal 0 HcmV?d00001 diff --git a/docs/security/trust/images/trust_signing.gliffy b/docs/security/trust/images/trust_signing.gliffy new file mode 100644 index 000000000..b21fa3665 --- /dev/null +++ b/docs/security/trust/images/trust_signing.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":881,"height":627,"nodeIndex":322,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":null,"printShrinkToFit":false,"printPortrait":false,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":10,"y":0},"max":{"x":880.0000000000001,"y":626.25}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":10.0,"y":122.25000000000006,"rotation":0.0,"id":79,"width":531.0,"height":500.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#ffffff","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":312.25000000000006,"rotation":0.0,"id":40,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":1,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":41,"width":71.42857142857143,"height":50.0,"uid":null,"order":3,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":40}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":40}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":42,"width":26.0,"height":18.0,"uid":null,"order":5,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":40,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

1.0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":82.1785714285715,"y":17.03600000000003,"rotation":0.0,"id":0,"width":63.0,"height":82.0,"uid":"com.gliffy.shape.network.network_v4.business.female_user","order":6,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.female_user","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":1,"width":43.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Person

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":330.0,"y":142.25000000000006,"rotation":0.0,"id":2,"width":120.0,"height":80.0,"uid":"com.gliffy.shape.network.network_v4.business.user_group","order":9,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.user_group","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":3,"width":73.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Organization

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":141.0,"y":152.25000000000006,"rotation":0.0,"id":11,"width":63.0,"height":82.0,"uid":"com.gliffy.shape.network.network_v4.business.user","order":12,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.user","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":12,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Account

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":305.99999999999994,"y":273.25000000000006,"rotation":0.0,"id":16,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":15,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":17,"width":110.00000000000001,"height":25.0,"uid":null,"order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":18}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":18,"width":110.00000000000001,"height":25.0,"uid":null,"order":20,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":19,"width":110.00000000000001,"height":55.0,"uid":null,"order":22,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":16},{"magnitude":-1,"id":18}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":18,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":262.25000000000006,"rotation":0.0,"id":37,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":35,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":38,"width":71.42857142857143,"height":50.0,"uid":null,"order":37,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":37}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":37}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":39,"width":38.0,"height":18.0,"uid":null,"order":39,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":37,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":442.25000000000006,"rotation":0.0,"id":63,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":40,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":64,"width":71.42857142857143,"height":50.0,"uid":null,"order":42,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":63}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":63}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":65,"width":68.0,"height":18.0,"uid":null,"order":44,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":63,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

producttion

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":305.99999999999994,"y":403.25000000000006,"rotation":0.0,"id":58,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":45,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":59,"width":110.00000000000001,"height":25.0,"uid":null,"order":47,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":60}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":60,"width":110.00000000000001,"height":25.0,"uid":null,"order":50,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":61,"width":110.00000000000001,"height":55.0,"uid":null,"order":52,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":58},{"magnitude":-1,"id":60}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":60,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":392.25000000000006,"rotation":0.0,"id":55,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":53,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":56,"width":71.42857142857143,"height":50.0,"uid":null,"order":55,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":55}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":55}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":57,"width":28.0,"height":18.0,"uid":null,"order":57,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":55,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

test

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":10.000000000000036,"y":132.25000000000006,"rotation":0.0,"id":82,"width":108.99999999999999,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Registry

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":36.142857142857125,"y":399.25000000000006,"rotation":0.0,"id":109,"width":187.85714285714286,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":81,"lockAspectRatio":false,"lockShape":false,"children":[{"x":7.142857142857139,"y":50.0,"rotation":0.0,"id":98,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":74,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":71.42857142857143,"height":50.0,"uid":null,"order":77,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":98}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":98}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":100,"width":50.0,"height":18.0,"uid":null,"order":80,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":98,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

working

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":7.571428571428527,"y":0.0,"rotation":0.0,"id":95,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":66,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":96,"width":71.42857142857143,"height":50.0,"uid":null,"order":69,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":95}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":95}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":97,"width":38.0,"height":18.0,"uid":null,"order":72,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":95,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":77.85714285714286,"y":8.0,"rotation":0.0,"id":30,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":24,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":31,"width":110.00000000000001,"height":25.0,"uid":null,"order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":32}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":32,"width":110.00000000000001,"height":25.0,"uid":null,"order":31,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":33,"width":110.00000000000001,"height":55.0,"uid":null,"order":34,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":30},{"magnitude":-1,"id":32}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":32,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":330.0,"y":0.0,"rotation":0.0,"id":180,"width":67.309,"height":101.072,"uid":"com.gliffy.shape.cisco.cisco_v1.buildings.generic_building","order":126,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.buildings.generic_building","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":182,"width":56.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Company

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":266.0,"y":125.25000000000006,"rotation":0.0,"id":250,"width":7.0,"height":413.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":172,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":79,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[3.5,-3.0],[9.5,496.99999999999994]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":35.21428571428568,"y":262.25000000000006,"rotation":0.0,"id":253,"width":187.85714285714286,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":173,"lockAspectRatio":false,"lockShape":false,"children":[{"x":77.85714285714286,"y":8.0,"rotation":0.0,"id":125,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":83,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":126,"width":110.00000000000001,"height":25.0,"uid":null,"order":86,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":127}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":127,"width":110.00000000000001,"height":25.0,"uid":null,"order":90,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":128,"width":110.00000000000001,"height":55.0,"uid":null,"order":93,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":125},{"magnitude":-1,"id":127}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":127,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":7.571428571428527,"y":0.0,"rotation":0.0,"id":122,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":95,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":123,"width":71.42857142857143,"height":50.0,"uid":null,"order":98,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":122}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":122}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":124,"width":38.0,"height":18.0,"uid":null,"order":101,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":122,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":7.142857142857139,"y":50.0,"rotation":0.0,"id":119,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":103,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":120,"width":71.42857142857143,"height":50.0,"uid":null,"order":106,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":119}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":119}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":121,"width":26.0,"height":18.0,"uid":null,"order":109,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":119,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

2.0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":557.25,"rotation":0.0,"id":281,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":179,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":282,"width":71.42857142857143,"height":50.0,"uid":null,"order":181,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":281}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":281}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":283,"width":48.0,"height":18.0,"uid":null,"order":183,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":281,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

release

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":305.99999999999994,"y":518.25,"rotation":0.0,"id":277,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":184,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":278,"width":110.00000000000001,"height":25.0,"uid":null,"order":186,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":279}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":279,"width":110.00000000000001,"height":25.0,"uid":null,"order":189,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":280,"width":110.00000000000001,"height":55.0,"uid":null,"order":191,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":277},{"magnitude":-1,"id":279}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":279,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":507.25,"rotation":0.0,"id":274,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":192,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":275,"width":71.42857142857143,"height":50.0,"uid":null,"order":194,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":274}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":274}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":276,"width":26.0,"height":18.0,"uid":null,"order":196,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":274,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

7.5

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":472.40133544303796,"y":320.25000000000006,"rotation":0.0,"id":306,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":209,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":472.40133544303796,"y":271.25000000000006,"rotation":0.0,"id":307,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":210,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":472.40133544303796,"y":401.25000000000006,"rotation":0.0,"id":308,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":211,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":37.214285714285666,"y":406.25000000000006,"rotation":0.0,"id":309,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":212,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":40.214285714285666,"y":456.25000000000006,"rotation":0.0,"id":310,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":213,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":594.3333333333335,"y":493.25000000000006,"rotation":0.0,"id":314,"width":283.66666666666663,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":215,"lockAspectRatio":false,"lockShape":false,"children":[{"x":66.66666666666663,"y":4.0,"rotation":0.0,"id":312,"width":217.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":214,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Signed tag.

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":0.0,"y":0.0,"rotation":0.0,"id":304,"width":33.333333333333336,"height":20.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":208,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"}],"layers":[{"guid":"dockVlz9GmcW","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":216}],"shapeStyles":{},"lineStyles":{"global":{"strokeWidth":1,"endArrow":17}},"textStyles":{"global":{"size":"16px","color":"#000000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.cisco.cisco_v1.buildings","com.gliffy.libraries.sitemap.sitemap_v2","com.gliffy.libraries.sitemap.sitemap_v1.default","com.gliffy.libraries.ui.ui_v3.containers_content","com.gliffy.libraries.table.table_v2.default","com.gliffy.libraries.ui.ui_v3.navigation","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.ui.ui_v3.icon_symbols","com.gliffy.libraries.ui.ui_v2.forms_components","com.gliffy.libraries.ui.ui_v2.content","com.gliffy.libraries.ui.ui_v2.miscellaneous","com.gliffy.libraries.network.network_v4.business","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.bpmn.bpmn_v1.events","com.gliffy.libraries.bpmn.bpmn_v1.activities","com.gliffy.libraries.bpmn.bpmn_v1.data_artifacts","com.gliffy.libraries.bpmn.bpmn_v1.gateways","com.gliffy.libraries.bpmn.bpmn_v1.connectors","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.images"],"lastSerialized":1439068922785},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file diff --git a/docs/security/trust/images/trust_signing.png b/docs/security/trust/images/trust_signing.png new file mode 100644 index 0000000000000000000000000000000000000000..4a941be19d7af808c74be2f67e75fb13de6de411 GIT binary patch literal 71621 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 literal 0 HcmV?d00001 diff --git a/docs/security/trust/images/trust_view.gliffy b/docs/security/trust/images/trust_view.gliffy new file mode 100644 index 000000000..b635e6576 --- /dev/null +++ b/docs/security/trust/images/trust_view.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":866,"height":537,"nodeIndex":323,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":null,"printShrinkToFit":false,"printPortrait":false,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":10,"y":0},"max":{"x":865.6666666666666,"y":536.25}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":10.0,"y":122.25000000000006,"rotation":0.0,"id":79,"width":531.0,"height":409.99999999999994,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#ffffff","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":312.25000000000006,"rotation":0.0,"id":40,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":1,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":41,"width":71.42857142857143,"height":50.0,"uid":null,"order":3,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":40}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":40}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":42,"width":26.0,"height":18.0,"uid":null,"order":5,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":40,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

1.0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":82.1785714285715,"y":17.03600000000003,"rotation":0.0,"id":0,"width":63.0,"height":82.0,"uid":"com.gliffy.shape.network.network_v4.business.female_user","order":6,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.female_user","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":1,"width":43.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Person

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":330.0,"y":142.25000000000006,"rotation":0.0,"id":2,"width":120.0,"height":80.0,"uid":"com.gliffy.shape.network.network_v4.business.user_group","order":9,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.user_group","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":3,"width":73.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Organization

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":141.0,"y":152.25000000000006,"rotation":0.0,"id":11,"width":63.0,"height":82.0,"uid":"com.gliffy.shape.network.network_v4.business.user","order":12,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.network.network_v4.business.user","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#3966A0","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":12,"width":48.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Account

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":305.99999999999994,"y":273.25000000000006,"rotation":0.0,"id":16,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":15,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":17,"width":110.00000000000001,"height":25.0,"uid":null,"order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":18}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":18,"width":110.00000000000001,"height":25.0,"uid":null,"order":20,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":19,"width":110.00000000000001,"height":55.0,"uid":null,"order":22,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":16},{"magnitude":-1,"id":18}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":18,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":262.25000000000006,"rotation":0.0,"id":37,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":35,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":38,"width":71.42857142857143,"height":50.0,"uid":null,"order":37,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":37}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":37}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":39,"width":38.0,"height":18.0,"uid":null,"order":39,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":37,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":442.25000000000006,"rotation":0.0,"id":63,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":40,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":64,"width":71.42857142857143,"height":50.0,"uid":null,"order":42,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":63}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":63}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":65,"width":68.0,"height":18.0,"uid":null,"order":44,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":63,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

producttion

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":305.99999999999994,"y":403.25000000000006,"rotation":0.0,"id":58,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":45,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":59,"width":110.00000000000001,"height":25.0,"uid":null,"order":47,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":60}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":60,"width":110.00000000000001,"height":25.0,"uid":null,"order":50,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":61,"width":110.00000000000001,"height":55.0,"uid":null,"order":52,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":58},{"magnitude":-1,"id":60}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":60,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":416.0,"y":392.25000000000006,"rotation":0.0,"id":55,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left","order":53,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":56,"width":71.42857142857143,"height":50.0,"uid":null,"order":55,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":55}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":55}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_left","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":10.714285714285722,"y":0.0,"rotation":0.0,"id":57,"width":28.0,"height":18.0,"uid":null,"order":57,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":55,"px":0.15,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

test

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":10.000000000000036,"y":132.25000000000006,"rotation":0.0,"id":82,"width":108.99999999999999,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Registry

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":36.142857142857125,"y":399.25000000000006,"rotation":0.0,"id":109,"width":187.85714285714286,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":81,"lockAspectRatio":false,"lockShape":false,"children":[{"x":7.142857142857139,"y":50.0,"rotation":0.0,"id":98,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":74,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":99,"width":71.42857142857143,"height":50.0,"uid":null,"order":77,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":98}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":98}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":100,"width":50.0,"height":18.0,"uid":null,"order":80,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":98,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

working

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":7.571428571428527,"y":0.0,"rotation":0.0,"id":95,"width":71.42857142857142,"height":50.0,"uid":"com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right","order":66,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"MinHeightConstraint","MinHeightConstraint":{"height":28}},{"type":"MinWidthConstraint","MinWidthConstraint":{"width":40}}]},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":96,"width":71.42857142857143,"height":50.0,"uid":null,"order":69,"lockAspectRatio":true,"lockShape":false,"constraints":{"constraints":[{"type":"WidthConstraint","WidthConstraint":{"isMin":false,"widthInfo":[{"magnitude":1,"id":95}],"minWidth":0.0,"growParent":false,"padding":0.0}},{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":95}],"minHeight":0.0,"growParent":false,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ui.ui_v3.icon_symbols.annotate_right","strokeWidth":1.0,"strokeColor":"#EA6624","fillColor":"#cfe2f3","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"hidden":false,"layerId":null},{"x":-7.142857142857139,"y":0.0,"rotation":0.0,"id":97,"width":38.0,"height":18.0,"uid":null,"order":72,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"PositionConstraint","PositionConstraint":{"nodeId":95,"px":-0.1,"py":0.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

latest

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":77.85714285714286,"y":8.0,"rotation":0.0,"id":30,"width":110.00000000000001,"height":80.0,"uid":"com.gliffy.shape.sitemap.sitemap_v2.photo","order":24,"lockAspectRatio":false,"lockShape":false,"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":31,"width":110.00000000000001,"height":25.0,"uid":null,"order":27,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":32}],"minHeight":0.0,"growParent":true,"padding":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.rounded_top","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":32,"width":110.00000000000001,"height":25.0,"uid":null,"order":31,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":6,"paddingRight":2,"paddingBottom":6,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Repository

","tid":null,"valign":"top","vposition":"none","hposition":"none"}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":25.0,"rotation":0.0,"id":33,"width":110.00000000000001,"height":55.0,"uid":null,"order":34,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[{"type":"HeightConstraint","HeightConstraint":{"isMin":false,"heightInfo":[{"magnitude":1,"id":30},{"magnitude":-1,"id":32}],"minHeight":0.0,"growParent":false,"padding":0.0}},{"type":"PositionConstraint","PositionConstraint":{"nodeId":32,"px":0.0,"py":1.0,"xOffset":0.0,"yOffset":0.0}}]},"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.sitemap.sitemap_v2.photo","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"hidden":false,"layerId":null}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":330.0,"y":0.0,"rotation":0.0,"id":180,"width":67.309,"height":101.072,"uid":"com.gliffy.shape.cisco.cisco_v1.buildings.generic_building","order":126,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.buildings.generic_building","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#0b5394","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":182,"width":56.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Company

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"dockVlz9GmcW"}],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":266.0,"y":125.25000000000006,"rotation":0.0,"id":250,"width":7.0,"height":413.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":172,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":79,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[3.5,-3.0],[9.5,406.99999999999994]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":472.40133544303796,"y":320.25000000000006,"rotation":0.0,"id":306,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":209,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":472.40133544303796,"y":271.25000000000006,"rotation":0.0,"id":307,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":210,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":472.40133544303796,"y":401.25000000000006,"rotation":0.0,"id":308,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":211,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":37.214285714285666,"y":406.25000000000006,"rotation":0.0,"id":309,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":212,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":40.214285714285666,"y":456.25000000000006,"rotation":0.0,"id":310,"width":20.0,"height":12.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":213,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"dockVlz9GmcW"},{"x":580.0,"y":418.25000000000006,"rotation":0.0,"id":314,"width":283.66666666666663,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":215,"lockAspectRatio":false,"lockShape":false,"children":[{"x":66.66666666666663,"y":4.0,"rotation":0.0,"id":312,"width":217.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":214,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Signed tag.

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":0.0,"y":0.0,"rotation":0.0,"id":304,"width":33.333333333333336,"height":20.0,"uid":"com.gliffy.shape.bpmn.bpmn_v1.activities.ad_hoc","order":208,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ad_hoc.bpmn_v1","strokeWidth":0.0,"strokeColor":"#38761d","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"dockVlz9GmcW"}],"layers":[{"guid":"dockVlz9GmcW","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":216}],"shapeStyles":{},"lineStyles":{"global":{"strokeWidth":1,"endArrow":17}},"textStyles":{"global":{"size":"16px","color":"#000000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.cisco.cisco_v1.buildings","com.gliffy.libraries.sitemap.sitemap_v2","com.gliffy.libraries.sitemap.sitemap_v1.default","com.gliffy.libraries.ui.ui_v3.containers_content","com.gliffy.libraries.table.table_v2.default","com.gliffy.libraries.ui.ui_v3.navigation","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.ui.ui_v3.icon_symbols","com.gliffy.libraries.ui.ui_v2.forms_components","com.gliffy.libraries.ui.ui_v2.content","com.gliffy.libraries.ui.ui_v2.miscellaneous","com.gliffy.libraries.network.network_v4.business","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.bpmn.bpmn_v1.events","com.gliffy.libraries.bpmn.bpmn_v1.activities","com.gliffy.libraries.bpmn.bpmn_v1.data_artifacts","com.gliffy.libraries.bpmn.bpmn_v1.gateways","com.gliffy.libraries.bpmn.bpmn_v1.connectors","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.images"],"lastSerialized":1439069097667},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file diff --git a/docs/security/trust/images/trust_view.png b/docs/security/trust/images/trust_view.png new file mode 100644 index 0000000000000000000000000000000000000000..71eb26ce3163d519403bd6f6e72b3347c48c347f GIT binary patch literal 59533 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 NYXR5Ot->`V^ ++++ +title = "Use trusted images" +description = "Use trusted images" +keywords = ["trust, security, docker, index"] +[menu.main] +identifier="smn_content_trust" +parent= "mn_docker_hub" +weight=4 ++++ + + +# Use trusted images + +The following topics are available: + +* [Content trust in Docker](/security/trust/content_trust) +* [Manage keys for content trust](/security/trust/trust_key_mng) +* [Automation with content trust](/security/trust/trust_automation) +* [Play in a content trust sandbox](/security/trust/trust_sandbox) + diff --git a/docs/security/trust/trust_automation.md b/docs/security/trust/trust_automation.md new file mode 100644 index 000000000..0808d8cef --- /dev/null +++ b/docs/security/trust/trust_automation.md @@ -0,0 +1,79 @@ + + +# Automation with content trust + +Your automation systems that pull or build images can also work with trust. Any automation environment must set `DOCKER_TRUST_ENABLED` either manually or in in a scripted fashion before processing images. + +## Bypass requests for passphrases + +To allow tools to wrap docker and push trusted content, there are two +environment variables that allow you to provide the passphrases without an +expect script, or typing them in: + + - `DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE` + - `DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE` + +Docker attempts to use the contents of these environment variables as passphrase +for the keys. For example, an image publisher can export the repository `target` +and `snapshot` passphrases: + +```bash +$ export DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE="u7pEQcGoebUHm6LHe6" +$ export DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE="l7pEQcTKJjUHm6Lpe4" +``` + +Then, when pushing a new tag the Docker client does not request these values but signs automatically: + +``bash +$ docker push docker/trusttest:latest +The push refers to a repository [docker.io/docker/trusttest] (len: 1) +a9539b34a6ab: Image already exists +b3dbab3810fc: Image already exists +latest: digest: sha256:d149ab53f871 size: 3355 +Signing and pushing trust metadata +``` + +## Building with content trust + +You can also build with content trust. Before running the `docker build` command, you should set the environment variable `DOCKER_CONTENT_TRUST` either manually or in in a scripted fashion. Consider the simple Dockerfile below. + +```Dockerfilea +FROM docker/trusttest:latest +RUN echo +``` + +The `FROM` tag is pulling a signed image. You cannot build an image that has a +`FROM` that is not either present locally or signed. Given that content trust +data exists for the tag `latest`, the following build should succeed: + +```bash +$ docker build -t docker/trusttest:testing . +Using default tag: latest +latest: Pulling from docker/trusttest + +b3dbab3810fc: Pull complete +a9539b34a6ab: Pull complete +Digest: sha256:d149ab53f871 +``` + +If content trust is enabled, building from a Dockerfile that relies on tag without trust data, causes the build command to fail: + +```bash +$ docker build -t docker/trusttest:testing . +unable to process Dockerfile: No trust data for notrust +``` + +## Related information + +* [Content trust in Docker](/security/trust/content_trust) +* [Manage keys for content trust](/security/trust/trust_key_mng) +* [Play in a content trust sandbox](/security/trust/trust_sandbox) + diff --git a/docs/security/trust/trust_key_mng.md b/docs/security/trust/trust_key_mng.md new file mode 100644 index 000000000..a9bd02b75 --- /dev/null +++ b/docs/security/trust/trust_key_mng.md @@ -0,0 +1,74 @@ + + +# Manage keys for content trust + +Trust for an image tag is managed through the use of keys. Docker's content +trust makes use four different keys: + +| Key | Description | +|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| offline key | Root of content trust for a image tag. When content trust is enabled, you create the offline key once. | +| target and snapshot | These two keys are known together as the "tagging" key. When content trust is enabled, you create this key when you add a new image repository. If you have the offline key, you can export the tagging key and allow other publishers to sign the image tags. | +| timestamp | This key applies to a repository. It allows Docker repositories to have freshness security guarantees without requiring periodic content refreshes on the client's side. | + +With the exception of the timestamp, all the keys are generated and stored locally +client-side. The timestamp is safely generated and stored in a signing server that +is deployed alongside the Docker registry. All keys are generated in a backend +service that isn't directly exposed to the internet and are encrypted at rest. + +## Choosing a passphrase + +The passphrases you chose for both the offline key and your tagging key should +be randomly generated and stored in a password manager. Having the tagging key +allow users to sign image tags on a repository. Passphrases are used to encrypt +your keys at rest and ensures that a lost laptop or an unintended backup doesn't +put the private key material at risk. + +## Back up your keys + +All the Docker trust keys are stored encrypted using the passphrase you provide +on creation. Even so, you should still take care of the location where you back them up. +Good practice is to create two encrypted USB keys. + +It is very important that you backup your keys to a safe, secure location. Loss +of the tagging key is recoverable; loss of the offline key is not. + +The Docker client stores the keys in the `~/.docker/trust/private` directory. +Before backing them up, you should `tar` them into an archive: + +```bash +$ tar -zcvf private_keys_backup.tar.gz ~/.docker/trust/private +$ chmod 600 private_keys_backup.tar.gz +``` + +## Lost keys + +If a publisher loses keys it means losing the ability to sign trusted content for +your repositories. If you lose a key, contact [Docker +Support](https://support.docker.com) (support@docker.com) to reset the repository +state. + +This loss also requires **manual intervention** from every consumer that pulled +the tagged image prior to the loss. Image consumers would get an error for +content that they already downloaded: + +``` +could not validate the path to a trusted root: failed to validate data with current trusted certificates +``` + +To correct this, they need to download a new image tag with that is signed with +the new key. + +## Related information + +* [Content trust in Docker](/security/trust/content_trust) +* [Automation with content trust](/security/trust/trust_automation) +* [Play in a content trust sandbox](/security/trust/trust_sandbox) diff --git a/docs/security/trust/trust_sandbox.md b/docs/security/trust/trust_sandbox.md new file mode 100644 index 000000000..68b149109 --- /dev/null +++ b/docs/security/trust/trust_sandbox.md @@ -0,0 +1,331 @@ + + +# Play in a content trust sandbox + +This page explains how to set up and use a sandbox for experimenting with trust. +The sandbox allows you to configure and try trust operations locally without +impacting your production images. + +Before working through this sandbox, you should have read through the [trust +overview](content_trust.md). + +### Prerequisites + +These instructions assume you are running in Linux or Mac OS X. You can run +this sandbox on a local machine or on a virtual machine. You will need to +have `sudo` privileges on your local machine or in the VM. + +This sandbox requires you to install two Docker tools: Docker Engine and Docker +Compose. To install the Docker Engine, choose from the [list of supported +platforms]({{< relref "installation.md" >}}). To install Docker Compose, see the +[detailed instructions here]({{< relref "compose/install" >}}). + +Finally, you'll need to have `git` installed on your local system or VM. + +## What is in the sandbox? + +If you are just using trust out-of-the-box you only need your Docker Engine +client and access to Docker's own public hub. The sandbox mimics a +production trust environment, and requires these additional components: + +| Container | Description | +|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| nostarysandbox | A container with the latest version of Docker Engine and with some preconfigured certifications. This is your sandbox where you can use the `docker` client to test trust operations. | +| Registry server | A local registry service. | +| Notary server | The service that does all the heavy-lifting of managing trust | +| Notary signer | A service that ensures that your keys are secure. | +| MySQL | The database where all of the trust information will be stored | + +The sandbox uses the Docker daemon on your local system. Within the `nostarysandbox` +you interact with a local registry rather than the public Docker Hub. This means +your everyday image repositories are not used. They are protected while you play. + +When you play in the sandbox, you'll also create root and tagging keys. The +sandbox is configured to store all the keys and files inside the `notarysandbox` +container. Since the keys you create in the sandbox are for play only, +destroying the container destroys them as well. + + +## Build the sandbox + +In this section, you build the Docker components for your trust sandbox. If you +work exclusively with the Docker Hub, you would not need with these components. +They are built into the Docker Hub for you. For the sandbox, however, you must +build your own entire, mock production environment and registry. + +### Configure /etc/hosts + +The sandbox' `notaryserver` and `sandboxregistry` run on your local server. The +client inside the `notarysandbox` container connects to them over your network. +So, you'll need an entry for both the servers in your local `/etc/hosts` file. + +1. Add an entry for the `notaryserver` to `/etc/hosts`. + + $ sudo sh -c 'echo "127.0.0.1 notaryserver" >> /etc/hosts' + +2. Add an entry for the `sandboxregistry` to `/etc/hosts`. + + $ sudo sh -c 'echo "127.0.0.1 sandboxregistry" >> /etc/hosts' + + +### Build the notarytest image + +1. Create a `notarytest` directory on your system. + + $ mkdir notarysandbox + +2. Change into your `notarysandbox` directory. + + $ cd notarysandbox + +3. Create a `notarytest` directory then change into that. + + $ mkdir notarytest + $ cd nostarytest + +4. Create a filed called `Dockerfile` with your favorite editor. + +5. Add the following to the new file. + + FROM debian:jessie + + ADD https://master.dockerproject.org/linux/amd64/docker /usr/bin/docker + RUN chmod +x /usr/bin/docker \ + && apt-get update \ + && apt-get install -y \ + tree \ + vim \ + git \ + ca-certificates \ + --no-install-recommends + + WORKDIR /root + RUN git clone -b trust-sandbox https://github.com/docker/notary.git + RUN cp /root/notary/fixtures/root-ca.crt /usr/local/share/ca-certificates/root-ca.crt + RUN update-ca-certificates + + ENTRYPOINT ["bash"] + +6. Save and close the file. + +7. Build the testing container. + + $ docker build -t nostarysandbox . + Sending build context to Docker daemon 2.048 kB + Step 0 : FROM debian:jessie + ... + Successfully built 5683f17e9d72 + + +### Build and start up the trust servers + +In this step, you get the source code for your notary and registry services. +Then, you'll use Docker Compose to build and start them on your local system. + +1. Change to back to the root of your `notarysandbox` directory. + + $ cd notarysandbox + +2. Clone the `notary` project. + + $ git clone -b trust-sandbox https://github.com/docker/notary.git + +3. Clone the `distribution` project. + + $ git clone https://github.com/docker/distribution.git + +4. Change to the `notary` project directory. + + $ cd notary + + The directory contains a `docker-compose` file that you'll use to run a + notary server together with a notary signer and the corresponding MySQL + databases. The databases store the trust information for an image. + +5. Build the server images. + + $ docker-compose build + + The first time you run this, the build takes some time. + +6. Run the server containers on your local system. + + $ docker-compose up -d + + Once the trust services are up, you'll setup a local version of the Docker + Registry v2. + +7. Change to the `nostarysandbox/distribution` directory. + +8. Build the `sandboxregistry` server. + + $ docker build -t sandboxregistry . + +9. Start the `sandboxregistry` server running. + + $ docker run -p 5000:5000 --name sandboxregistry sandboxregistry & + +## Playing in the sandbox + +Now that everything is setup, you can go into your `nostarysandbox` container and +start testing Docker content trust. + + +### Start the notarysandbox container + +In this procedure, you start the `notarysandbox` and link it to the running +`notary_notaryserver_1` and `sandboxregistry` containers. The links allow +communication among the containers. + +``` +$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock --link notary_notaryserver_1:notaryserver --link sandboxregistry:sandboxregistry nostarysandbox +root@0710762bb59a:/# +``` + +Mounting the `docker.sock` gives the `nostarysandbox` access to the `docker` +deamon on your host, while storing all the keys and files inside the sandbox +container. When you destroy the container, you destroy the "play" keys. + +### Test some trust operations + +Now, you'll pull some images. + +1. Download a `docker` image to test with. + + # docker pull docker/trusttest + docker pull docker/trusttest + Using default tag: latest + latest: Pulling from docker/trusttest + + b3dbab3810fc: Pull complete + a9539b34a6ab: Pull complete + Digest: sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a + Status: Downloaded newer image for docker/trusttest:latest + +2. Tag it to be pushed to our sandbox registry: + + # docker tag docker/trusttest sandboxregistry:5000/test/trusttest:latest + +3. Enable content trust. + + # export DOCKER_CONTENT_TRUST=1 + +4. Identify the trust server. + + # export DOCKER_CONTENT_TRUST_SERVER=https://notaryserver:4443 + + This step is only necessary because the sandbox is using its own server. + Normally, if you are using the Docker Public Hub this step isn't necessary. + +5. Pull the test image. + + # docker pull sandboxregistry:5000/test/trusttest + Using default tag: latest + no trust data available + + You see an error, because this content doesn't exist on the `sandboxregistry` yet. + +6. Push the trusted image. + + # docker push sandboxregistry:5000/test/trusttest:latest + The push refers to a repository [sandboxregistry:5000/test/trusttest] (len: 1) + a9539b34a6ab: Image successfully pushed + b3dbab3810fc: Image successfully pushed + latest: digest: sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c size: 3348 + Signing and pushing trust metadata + You are about to create a new root signing key passphrase. This passphrase + will be used to protect the most sensitive key in your signing system. Please + choose a long, complex passphrase and be careful to keep the password and the + key file itself secure and backed up. It is highly recommended that you use a + password manager to generate the passphrase and keep it safe. There will be no + way to recover this key. You can find the key in your config directory. + Enter passphrase for new offline key with id 8c69e04: + Repeat passphrase for new offline key with id 8c69e04: + Enter passphrase for new tagging key with id sandboxregistry:5000/test/trusttest (93c362a): + Repeat passphrase for new tagging key with id sandboxregistry:5000/test/trusttest (93c362a): + Finished initializing "sandboxregistry:5000/test/trusttest" + latest: digest: sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a size: 3355 + Signing and pushing trust metadata + +7. Try pulling the image you just pushed: + + # docker pull sandboxregistry:5000/test/trusttest + Using default tag: latest + Pull (1 of 1): sandboxregistry:5000/test/trusttest:latest@sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c + sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c: Pulling from test/trusttest + b3dbab3810fc: Already exists + a9539b34a6ab: Already exists + Digest: sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c + Status: Downloaded newer image for sandboxregistry:5000/test/trusttest@sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c + Tagging sandboxregistry:5000/test/trusttest@sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c as sandboxregistry:5000/test/trusttest:latest + + +### Test with malicious images + +What happens when data is corrupted and you try to pull it when trust is +enabled? In this section, you go into the `sandboxregistry` and tamper with some +data. Then, you try and pull it. + +1. Leave the sandbox container running. + +2. Open a new bash terminal from your host into the `sandboxregistry`. + + $ docker exec -it sandboxregistry bash + 296db6068327# + +3. Change into the registry storage. + + You'll need to provide the `sha` you received when you pushed the image. + + # cd /var/lib/registry/docker/registry/v2/blobs/sha256/aa/aac0c133338db2b18ff054943cee3267fe50c75cdee969aed88b1992539ed042 + +4. Add malicious data to one of the trusttest layers: + + # echo "Malicious data" > data + +5. Got back to your sandbox terminal. + +6. List the trusttest image. + + # docker images | grep trusttest + docker/trusttest latest a9539b34a6ab 7 weeks ago 5.025 MB + sandboxregistry:5000/test/trusttest latest a9539b34a6ab 7 weeks ago 5.025 MB + sandboxregistry:5000/test/trusttest a9539b34a6ab 7 weeks ago 5.025 MB + +7. Remove the `trusttest:latest` image. + + # docker rmi -f a9539b34a6ab + Untagged: docker/trusttest:latest + Untagged: sandboxregistry:5000/test/trusttest:latest + Untagged: sandboxregistry:5000/test/trusttest@sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c + Deleted: a9539b34a6aba01d3942605dfe09ab821cd66abf3cf07755b0681f25ad81f675 + Deleted: b3dbab3810fc299c21f0894d39a7952b363f14520c2f3d13443c669b63b6aa20 + +8. Pull the image again. + + # docker pull sandboxregistry:5000/test/trusttest + Using default tag: latest + ... + b3dbab3810fc: Verifying Checksum + a9539b34a6ab: Pulling fs layer + filesystem layer verification failed for digest sha256:aac0c133338db2b18ff054943cee3267fe50c75cdee969aed88b1992539ed042 + + You'll see the the pull did not complete because the trust system was + unable to verify the image. + +## More play in the sandbox + +Now, that you have a full Docker content trust sandbox on your local system, +feel free to play with it and see how it behaves. If you find any security +issues with Docker, feel free to send us an email at . + + +  \ No newline at end of file From 5745aaed2298f2f7a73eaf03b2829e1ab32529bf Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Mon, 10 Aug 2015 11:02:35 -0700 Subject: [PATCH 76/79] Initial work Removing references to regsitry 1.0, pointing to distribution Updating links and title Adding in comments Signed-off-by: Mary Anthony (cherry picked from commit d550d729e14d1a18d00b66a941da5b23b9a61bd3) --- docs/articles/certificates.md | 112 +--- docs/articles/registry_mirror.md | 83 +-- docs/introduction/understanding-docker.md | 15 +- docs/misc/faq.md | 2 +- docs/reference/api/docker-io_api.md | 499 +------------- docs/reference/api/hub_registry_spec.md | 761 ---------------------- docs/userguide/image_management.md | 53 ++ 7 files changed, 72 insertions(+), 1453 deletions(-) delete mode 100644 docs/reference/api/hub_registry_spec.md create mode 100644 docs/userguide/image_management.md diff --git a/docs/articles/certificates.md b/docs/articles/certificates.md index 16c73d1b2..da2ffcc9b 100644 --- a/docs/articles/certificates.md +++ b/docs/articles/certificates.md @@ -11,111 +11,7 @@ weight = 7 # Using certificates for repository client verification -In [Running Docker with HTTPS](/articles/https), you learned that, by default, -Docker runs via a non-networked Unix socket and TLS must be enabled in order -to have the Docker client and the daemon communicate securely over HTTPS. - -Now, you will see how to allow the Docker registry (i.e., *a server*) to -verify that the Docker daemon (i.e., *a client*) has the right to access the -images being hosted with *certificate-based client-server authentication*. - -We will show you how to install a Certificate Authority (CA) root certificate -for the registry and how to set the client TLS certificate for verification. - -## Understanding the configuration - -A custom certificate is configured by creating a directory under -`/etc/docker/certs.d` using the same name as the registry's hostname (e.g., -`localhost`). All `*.crt` files are added to this directory as CA roots. - -> **Note:** -> In the absence of any root certificate authorities, Docker -> will use the system default (i.e., host's root CA set). - -The presence of one or more `.key/cert` pairs indicates to Docker -that there are custom certificates required for access to the desired -repository. - -> **Note:** -> If there are multiple certificates, each will be tried in alphabetical -> order. If there is an authentication error (e.g., 403, 404, 5xx, etc.), Docker -> will continue to try with the next certificate. - -Our example is set up like this: - - /etc/docker/certs.d/ <-- Certificate directory - └── localhost <-- Hostname - ├── client.cert <-- Client certificate - ├── client.key <-- Client key - └── localhost.crt <-- Registry certificate - -## Creating the client certificates - -You will use OpenSSL's `genrsa` and `req` commands to first generate an RSA -key and then use the key to create the certificate. - - $ openssl genrsa -out client.key 4096 - $ openssl req -new -x509 -text -key client.key -out client.cert - -> **Warning:**: -> Using TLS and managing a CA is an advanced topic. -> You should be familiar with OpenSSL, x509, and TLS before -> attempting to use them in production. - -> **Warning:** -> These TLS commands will only generate a working set of certificates on Linux. -> The version of OpenSSL in Mac OS X is incompatible with the type of -> certificate Docker requires. - -## Testing the verification setup - -You can test this setup by using Apache to host a Docker registry. -For this purpose, you can copy a registry tree (containing images) inside -the Apache root. - -> **Note:** -> You can find such an example [here]( -> http://people.gnome.org/~alexl/v1.tar.gz) - which contains the busybox image. - -Once you set up the registry, you can use the following Apache configuration -to implement certificate-based protection. - - # This must be in the root context, otherwise it causes a re-negotiation - # which is not supported by the TLS implementation in go - SSLVerifyClient optional_no_ca - - - Action cert-protected /cgi-bin/cert.cgi - SetHandler cert-protected - - Header set x-docker-registry-version "0.6.2" - SetEnvIf Host (.*) custom_host=$1 - Header set X-Docker-Endpoints "%{custom_host}e" - - -Save the above content as `/etc/httpd/conf.d/registry.conf`, and -continue with creating a `cert.cgi` file under `/var/www/cgi-bin/`. - - #!/bin/bash - if [ "$HTTPS" != "on" ]; then - echo "Status: 403 Not using SSL" - echo "x-docker-registry-version: 0.6.2" - echo - exit 0 - fi - if [ "$SSL_CLIENT_VERIFY" == "NONE" ]; then - echo "Status: 403 Client certificate invalid" - echo "x-docker-registry-version: 0.6.2" - echo - exit 0 - fi - echo "Content-length: $(stat --printf='%s' $PATH_TRANSLATED)" - echo "x-docker-registry-version: 0.6.2" - echo "X-Docker-Endpoints: $SERVER_NAME" - echo "X-Docker-Size: 0" - echo - - cat $PATH_TRANSLATED - -This CGI script will ensure that all requests to `/v1` *without* a valid -certificate will be returned with a `403` (i.e., HTTP forbidden) error. +The orginal content was deprecated. For information about configuring +cerficates, see [deploying a registry +server](http://docs.docker.com/registry/deploying/). To reach an older version +of this content, refer to an older version of the documentation. diff --git a/docs/articles/registry_mirror.md b/docs/articles/registry_mirror.md index 6e309824e..97049e458 100644 --- a/docs/articles/registry_mirror.md +++ b/docs/articles/registry_mirror.md @@ -11,81 +11,8 @@ weight = 8 # Run a local registry mirror -## Why? - -If you have multiple instances of Docker running in your environment -(e.g., multiple physical or virtual machines, all running the Docker -daemon), each time one of them requires an image that it doesn't have -it will go out to the internet and fetch it from the public Docker -registry. By running a local registry mirror, you can keep most of the -image fetch traffic on your local network. - -## How does it work? - -The first time you request an image from your local registry mirror, -it pulls the image from the public Docker registry and stores it locally -before handing it back to you. On subsequent requests, the local registry -mirror is able to serve the image from its own storage. - -## How do I set up a local registry mirror? - -There are two steps to set up and use a local registry mirror. - -### Step 1: Configure your Docker daemons to use the local registry mirror - -You will need to pass the `--registry-mirror` option to your Docker daemon on -startup: - - docker daemon --registry-mirror=http:// - -For example, if your mirror is serving on `http://10.0.0.2:5000`, you would run: - - docker daemon --registry-mirror=http://10.0.0.2:5000 - -**NOTE:** -Depending on your local host setup, you may be able to add the -`--registry-mirror` options to the `DOCKER_OPTS` variable in -`/etc/default/docker`. - -### Step 2: Run the local registry mirror - -You will need to start a local registry mirror service. The -[`registry` image](https://registry.hub.docker.com/_/registry/) provides this -functionality. For example, to run a local registry mirror that serves on -port `5000` and mirrors the content at `registry-1.docker.io`: - - docker run -p 5000:5000 \ - -e STANDALONE=false \ - -e MIRROR_SOURCE=https://registry-1.docker.io \ - -e MIRROR_SOURCE_INDEX=https://index.docker.io \ - registry - -## Test it out - -With your mirror running, pull an image that you haven't pulled before (using -`time` to time it): - - $ time docker pull node:latest - Pulling repository node - [...] - - real 1m14.078s - user 0m0.176s - sys 0m0.120s - -Now, remove the image from your local machine: - - $ docker rmi node:latest - -Finally, re-pull the image: - - $ time docker pull node:latest - Pulling repository node - [...] - - real 0m51.376s - user 0m0.120s - sys 0m0.116s - -The second time around, the local registry mirror served the image from storage, -avoiding a trip out to the internet to refetch it. +The orginal content was deprecated. [An archived +version](https://docs.docker.com/v1.6/articles/registry_mirror) is available in +the 1.7 documentation. For information about configuring mirrors with the latest +Docker Registry version, please file a support request with [the Distribution +project](https://github.com/docker/distribution/issues). diff --git a/docs/introduction/understanding-docker.md b/docs/introduction/understanding-docker.md index 9c872efbe..d597c3ea3 100644 --- a/docs/introduction/understanding-docker.md +++ b/docs/introduction/understanding-docker.md @@ -116,11 +116,11 @@ images, or you can download Docker images that other people have already created Docker images are the **build** component of Docker. #### Docker registries -Docker registries hold images. These are public or private stores from which you upload -or download images. The public Docker registry is called -[Docker Hub](http://hub.docker.com). It provides a huge collection of existing -images for your use. These can be images you create yourself or you -can use images that others have previously created. Docker registries are the +Docker registries hold images. These are public or private stores from which you +upload or download images. The public Docker registry is provided with the +[Docker Hub](http://hub.docker.com). It serves a huge collection of existing +images for your use. These can be images you create yourself or you can use +images that others have previously created. Docker registries are the **distribution** component of Docker. #### Docker containers @@ -179,8 +179,9 @@ returns a final image. ### How does a Docker registry work? The Docker registry is the store for your Docker images. Once you build a Docker -image you can *push* it to a public registry [Docker Hub](https://hub.docker.com) or to -your own registry running behind your firewall. +image you can *push* it to a public registry such as the one provided by [Docker +Hub](https://hub.docker.com) or to your own registry running behind your +firewall. Using the Docker client, you can search for already published images and then pull them down to your Docker host to build containers from them. diff --git a/docs/misc/faq.md b/docs/misc/faq.md index 06be72d7c..38a54f1a6 100644 --- a/docs/misc/faq.md +++ b/docs/misc/faq.md @@ -97,7 +97,7 @@ with several powerful functionalities: applications. Your ideal Postgresql setup can be re-used for all your future projects. And so on. - - *Sharing.* Docker has access to a [public registry](https://hub.docker.com) + - *Sharing.* Docker has access to a public registry [on Docker Hub](https://registry.hub.docker.com/) where thousands of people have uploaded useful containers: anything from Redis, CouchDB, Postgres to IRC bouncers to Rails app servers to Hadoop to base images for various Linux distros. The diff --git a/docs/reference/api/docker-io_api.md b/docs/reference/api/docker-io_api.md index c6280742e..f798d3e35 100644 --- a/docs/reference/api/docker-io_api.md +++ b/docs/reference/api/docker-io_api.md @@ -10,502 +10,5 @@ parent = "smn_remoteapi" # Docker Hub API -- This is the REST API for [Docker Hub](https://hub.docker.com). -- Authorization is done with basic auth over SSL -- Not all commands require authentication, only those noted as such. +This API is deprecated as of 1.7. To view the old version, see the [Docker Hub API](https://docs.docker.com/v1.7/reference/api/docker-io_api/) in the 1.7 documentation. -# Repositories - -## User repository - -### Create a user repository - -`PUT /v1/repositories/(namespace)/(repo_name)/` - -Create a user repository with the given `namespace` and `repo_name`. - -**Example Request**: - - PUT /v1/repositories/foo/bar/ HTTP/1.1 - Host: index.docker.io - Accept: application/json - Content-Type: application/json - Authorization: Basic akmklmasadalkm== - X-Docker-Token: true - - [{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"}] - -Parameters: - -- **namespace** – the namespace for the repo -- **repo_name** – the name for the repo - -**Example Response**: - - HTTP/1.1 200 - Vary: Accept - Content-Type: application/json - WWW-Authenticate: Token signature=123abc,repository="foo/bar",access=write - X-Docker-Token: signature=123abc,repository="foo/bar",access=write - X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io] - - "" - -Status Codes: - -- **200** – Created -- **400** – Errors (invalid json, missing or invalid fields, etc) -- **401** – Unauthorized -- **403** – Account is not Active - -### Delete a user repository - -`DELETE /v1/repositories/(namespace)/(repo_name)/` - -Delete a user repository with the given `namespace` and `repo_name`. - -**Example Request**: - - DELETE /v1/repositories/foo/bar/ HTTP/1.1 - Host: index.docker.io - Accept: application/json - Content-Type: application/json - Authorization: Basic akmklmasadalkm== - X-Docker-Token: true - - "" - -Parameters: - -- **namespace** – the namespace for the repo -- **repo_name** – the name for the repo - -**Example Response**: - - HTTP/1.1 202 - Vary: Accept - Content-Type: application/json - WWW-Authenticate: Token signature=123abc,repository="foo/bar",access=delete - X-Docker-Token: signature=123abc,repository="foo/bar",access=delete - X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io] - - "" - -Status Codes: - -- **200** – Deleted -- **202** – Accepted -- **400** – Errors (invalid json, missing or invalid fields, etc) -- **401** – Unauthorized -- **403** – Account is not Active - -## Library repository - -### Create a library repository - -`PUT /v1/repositories/(repo_name)/` - -Create a library repository with the given `repo_name`. -This is a restricted feature only available to docker admins. - -> When namespace is missing, it is assumed to be `library` - - -**Example Request**: - - PUT /v1/repositories/foobar/ HTTP/1.1 - Host: index.docker.io - Accept: application/json - Content-Type: application/json - Authorization: Basic akmklmasadalkm== - X-Docker-Token: true - - [{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"}] - -Parameters: - -- **repo_name** – the library name for the repo - -**Example Response**: - - HTTP/1.1 200 - Vary: Accept - Content-Type: application/json - WWW-Authenticate: Token signature=123abc,repository="library/foobar",access=write - X-Docker-Token: signature=123abc,repository="foo/bar",access=write - X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io] - - "" - -Status Codes: - -- **200** – Created -- **400** – Errors (invalid json, missing or invalid fields, etc) -- **401** – Unauthorized -- **403** – Account is not Active - -### Delete a library repository - -`DELETE /v1/repositories/(repo_name)/` - -Delete a library repository with the given `repo_name`. -This is a restricted feature only available to docker admins. - -> When namespace is missing, it is assumed to be `library` - - -**Example Request**: - - DELETE /v1/repositories/foobar/ HTTP/1.1 - Host: index.docker.io - Accept: application/json - Content-Type: application/json - Authorization: Basic akmklmasadalkm== - X-Docker-Token: true - - "" - -Parameters: - -- **repo_name** – the library name for the repo - -**Example Response**: - - HTTP/1.1 202 - Vary: Accept - Content-Type: application/json - WWW-Authenticate: Token signature=123abc,repository="library/foobar",access=delete - X-Docker-Token: signature=123abc,repository="foo/bar",access=delete - X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io] - - "" - -Status Codes: - -- **200** – Deleted -- **202** – Accepted -- **400** – Errors (invalid json, missing or invalid fields, etc) -- **401** – Unauthorized -- **403** – Account is not Active - -# Repository images - -## User repository images - -### Update user repository images - -`PUT /v1/repositories/(namespace)/(repo_name)/images` - -Update the images for a user repo. - -**Example Request**: - - PUT /v1/repositories/foo/bar/images HTTP/1.1 - Host: index.docker.io - Accept: application/json - Content-Type: application/json - Authorization: Basic akmklmasadalkm== - - [{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f", - "checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"}] - -Parameters: - -- **namespace** – the namespace for the repo -- **repo_name** – the name for the repo - -**Example Response**: - - HTTP/1.1 204 - Vary: Accept - Content-Type: application/json - - "" - -Status Codes: - -- **204** – Created -- **400** – Errors (invalid json, missing or invalid fields, etc) -- **401** – Unauthorized -- **403** – Account is not Active or permission denied - -### List user repository images - -`GET /v1/repositories/(namespace)/(repo_name)/images` - -Get the images for a user repo. - -**Example Request**: - - GET /v1/repositories/foo/bar/images HTTP/1.1 - Host: index.docker.io - Accept: application/json - -Parameters: - -- **namespace** – the namespace for the repo -- **repo_name** – the name for the repo - -**Example Response**: - - HTTP/1.1 200 - Vary: Accept - Content-Type: application/json - - [{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f", - "checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"}, - {"id": "ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds", - "checksum": "34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew"}] - -Status Codes: - -- **200** – OK -- **404** – Not found - -## Library repository images - -### Update library repository images - -`PUT /v1/repositories/(repo_name)/images` - -Update the images for a library repo. - -**Example Request**: - - PUT /v1/repositories/foobar/images HTTP/1.1 - Host: index.docker.io - Accept: application/json - Content-Type: application/json - Authorization: Basic akmklmasadalkm== - - [{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f", - "checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"}] - -Parameters: - -- **repo_name** – the library name for the repo - -**Example Response**: - - HTTP/1.1 204 - Vary: Accept - Content-Type: application/json - - "" - -Status Codes: - -- **204** – Created -- **400** – Errors (invalid json, missing or invalid fields, etc) -- **401** – Unauthorized -- **403** – Account is not Active or permission denied - -### List library repository images - -`GET /v1/repositories/(repo_name)/images` - -Get the images for a library repo. - -**Example Request**: - - GET /v1/repositories/foobar/images HTTP/1.1 - Host: index.docker.io - Accept: application/json - -Parameters: - -- **repo_name** – the library name for the repo - -**Example Response**: - - HTTP/1.1 200 - Vary: Accept - Content-Type: application/json - - [{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f", - "checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"}, - {"id": "ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds", - "checksum": "34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew"}] - -Status Codes: - -- **200** – OK -- **404** – Not found - -# Repository authorization - -## Library repository - -### Authorize a token for a library - -`PUT /v1/repositories/(repo_name)/auth` - -Authorize a token for a library repo - -**Example Request**: - - PUT /v1/repositories/foobar/auth HTTP/1.1 - Host: index.docker.io - Accept: application/json - Authorization: Token signature=123abc,repository="library/foobar",access=write - -Parameters: - -- **repo_name** – the library name for the repo - -**Example Response**: - - HTTP/1.1 200 - Vary: Accept - Content-Type: application/json - - "OK" - -Status Codes: - -- **200** – OK -- **403** – Permission denied -- **404** – Not found - -## User repository - -### Authorize a token for a user repository - -`PUT /v1/repositories/(namespace)/(repo_name)/auth` - -Authorize a token for a user repo - -**Example Request**: - - PUT /v1/repositories/foo/bar/auth HTTP/1.1 - Host: index.docker.io - Accept: application/json - Authorization: Token signature=123abc,repository="foo/bar",access=write - -Parameters: - -- **namespace** – the namespace for the repo -- **repo_name** – the name for the repo - -**Example Response**: - - HTTP/1.1 200 - Vary: Accept - Content-Type: application/json - - "OK" - -Status Codes: - -- **200** – OK -- **403** – Permission denied -- **404** – Not found - -## Users - -### User login - -`GET /v1/users/` - -If you want to check your login, you can try this endpoint - -**Example Request**: - - GET /v1/users/ HTTP/1.1 - Host: index.docker.io - Accept: application/json - Authorization: Basic akmklmasadalkm== - -**Example Response**: - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - OK - -Status Codes: - -- **200** – no error -- **401** – Unauthorized -- **403** – Account is not Active - -### User register - -`POST /v1/users/` - -Registering a new account. - -**Example request**: - - POST /v1/users/ HTTP/1.1 - Host: index.docker.io - Accept: application/json - Content-Type: application/json - - {"email": "sam@docker.com", - "password": "toto42", - "username": "foobar"} - -Json Parameters: - -- **email** – valid email address, that needs to be confirmed -- **username** – min 4 character, max 30 characters, must match - the regular expression [a-z0-9_]. -- **password** – min 5 characters - -**Example Response**: - - HTTP/1.1 201 OK - Vary: Accept - Content-Type: application/json - - "User Created" - -Status Codes: - -- **201** – User Created -- **400** – Errors (invalid json, missing or invalid fields, etc) - -### Update user - -`PUT /v1/users/(username)/` - -Change a password or email address for given user. If you pass in an -email, it will add it to your account, it will not remove the old -one. Passwords will be updated. - -It is up to the client to verify that that password that is sent is -the one that they want. Common approach is to have them type it -twice. - -**Example Request**: - - PUT /v1/users/fakeuser/ HTTP/1.1 - Host: index.docker.io - Accept: application/json - Content-Type: application/json - Authorization: Basic akmklmasadalkm== - - {"email": "sam@docker.com", - "password": "toto42"} - -Parameters: - -- **username** – username for the person you want to update - -**Example Response**: - - HTTP/1.1 204 - Vary: Accept - Content-Type: application/json - - "" - -Status Codes: - -- **204** – User Updated -- **400** – Errors (invalid json, missing or invalid fields, etc) -- **401** – Unauthorized -- **403** – Account is not Active -- **404** – User not found diff --git a/docs/reference/api/hub_registry_spec.md b/docs/reference/api/hub_registry_spec.md deleted file mode 100644 index adcd9d582..000000000 --- a/docs/reference/api/hub_registry_spec.md +++ /dev/null @@ -1,761 +0,0 @@ - - -# The Docker Hub and the Registry v1 - -## The three roles - -There are three major components playing a role in the Docker ecosystem. - -### Docker Hub - -The Docker Hub is responsible for centralizing information about: - - - User accounts - - Checksums of the images - - Public namespaces - -The Docker Hub has different components: - - - Web UI - - Meta-data store (comments, stars, list public repositories) - - Authentication service - - Tokenization - -The Docker Hub is authoritative for that information. - -There is only one instance of the Docker Hub, run and -managed by Docker Inc. - -### Docker Registry 1.0 - -The 1.0 registry has the following characteristics: - - - It stores the images and the graph for a set of repositories - - It does not have user accounts data - - It has no notion of user accounts or authorization - - It delegates authentication and authorization to the Docker Hub Auth - service using tokens - - It supports different storage backends (S3, cloud files, local FS) - - It doesn't have a local database - - [Source Code](https://github.com/docker/docker-registry) - -We expect that there will be multiple registries out there. To help you -grasp the context, here are some examples of registries: - - - **sponsor registry**: such a registry is provided by a third-party - hosting infrastructure as a convenience for their customers and the - Docker community as a whole. Its costs are supported by the third - party, but the management and operation of the registry are - supported by Docker, Inc. It features read/write access, and delegates - authentication and authorization to the Docker Hub. - - **mirror registry**: such a registry is provided by a third-party - hosting infrastructure but is targeted at their customers only. Some - mechanism (unspecified to date) ensures that public images are - pulled from a sponsor registry to the mirror registry, to make sure - that the customers of the third-party provider can `docker pull` - those images locally. - - **vendor registry**: such a registry is provided by a software - vendor who wants to distribute docker images. It would be operated - and managed by the vendor. Only users authorized by the vendor would - be able to get write access. Some images would be public (accessible - for anyone), others private (accessible only for authorized users). - Authentication and authorization would be delegated to the Docker Hub. - The goal of vendor registries is to let someone do `docker pull - basho/riak1.3` and automatically push from the vendor registry - (instead of a sponsor registry); i.e., vendors get all the convenience of a - sponsor registry, while retaining control on the asset distribution. - - **private registry**: such a registry is located behind a firewall, - or protected by an additional security layer (HTTP authorization, - SSL client-side certificates, IP address authorization...). The - registry is operated by a private entity, outside of Docker's - control. It can optionally delegate additional authorization to the - Docker Hub, but it is not mandatory. - -> **Note:** The latter implies that while HTTP is the protocol -> of choice for a registry, multiple schemes are possible (and -> in some cases, trivial): -> -> - HTTP with GET (and PUT for read-write registries); -> - local mount point; -> - remote docker addressed through SSH. - -The latter would only require two new commands in Docker, e.g., -`registryget` and `registryput`, -wrapping access to the local filesystem (and optionally doing -consistency checks). Authentication and authorization are then delegated -to SSH (e.g., with public keys). - -### Docker - -On top of being a runtime for LXC, Docker is the Registry client. It -supports: - - - Push / Pull on the registry - - Client authentication on the Docker Hub - -## Workflow - -### Pull - -![](/static_files/docker_pull_chart.png) - -1. Contact the Docker Hub to know where I should download “samalba/busybox” -2. Docker Hub replies: a. `samalba/busybox` is on Registry A b. here are the - checksums for `samalba/busybox` (for all layers) c. token -3. Contact Registry A to receive the layers for `samalba/busybox` (all of - them to the base image). Registry A is authoritative for “samalba/busybox” - but keeps a copy of all inherited layers and serve them all from the same - location. -4. registry contacts Docker Hub to verify if token/user is allowed to download images -5. Docker Hub returns true/false lettings registry know if it should proceed or error - out -6. Get the payload for all layers - -It's possible to run: - - $ docker pull https:///repositories/samalba/busybox - -In this case, Docker bypasses the Docker Hub. However the security is not -guaranteed (in case Registry A is corrupted) because there won't be any -checksum checks. - -Currently registry redirects to s3 urls for downloads, going forward all -downloads need to be streamed through the registry. The Registry will -then abstract the calls to S3 by a top-level class which implements -sub-classes for S3 and local storage. - -Token is only returned when the `X-Docker-Token` -header is sent with request. - -Basic Auth is required to pull private repos. Basic auth isn't required -for pulling public repos, but if one is provided, it needs to be valid -and for an active account. - -**API (pulling repository foo/bar):** - -1. (Docker -> Docker Hub) GET /v1/repositories/foo/bar/images: - -**Headers**: - - Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== - X-Docker-Token: true - -**Action**: - - (looking up the foo/bar in db and gets images and checksums - for that repo (all if no tag is specified, if tag, only - checksums for those tags) see part 4.4.1) - -2. (Docker Hub -> Docker) HTTP 200 OK - -**Headers**: - - Authorization: Token - signature=123abc,repository=”foo/bar”,access=write - X-Docker-Endpoints: registry.docker.io [,registry2.docker.io] - -**Body**: - - Jsonified checksums (see part 4.4.1) - -3. (Docker -> Registry) GET /v1/repositories/foo/bar/tags/latest - -**Headers**: - - Authorization: Token - signature=123abc,repository=”foo/bar”,access=write - -4. (Registry -> Docker Hub) GET /v1/repositories/foo/bar/images - -**Headers**: - - Authorization: Token - signature=123abc,repository=”foo/bar”,access=read - -**Body**: - - - -**Action**: - - (Lookup token see if they have access to pull.) - - If good: - HTTP 200 OK Docker Hub will invalidate the token - - If bad: - HTTP 401 Unauthorized - -5. (Docker -> Registry) GET /v1/images/928374982374/ancestry - -**Action**: - - (for each image id returned in the registry, fetch /json + /layer) - -> **Note**: -> If someone makes a second request, then we will always give a new token, -> never reuse tokens. - -### Push - -![](/static_files/docker_push_chart.png) - -1. Contact the Docker Hub to allocate the repository name “samalba/busybox” - (authentication required with user credentials) -2. If authentication works and namespace available, “samalba/busybox” - is allocated and a temporary token is returned (namespace is marked - as initialized in Docker Hub) -3. Push the image on the registry (along with the token) -4. Registry A contacts the Docker Hub to verify the token (token must - corresponds to the repository name) -5. Docker Hub validates the token. Registry A starts reading the stream - pushed by docker and store the repository (with its images) -6. docker contacts the Docker Hub to give checksums for upload images - -> **Note:** -> **It's possible not to use the Docker Hub at all!** In this case, a deployed -> version of the Registry is deployed to store and serve images. Those -> images are not authenticated and the security is not guaranteed. - -> **Note:** -> **Docker Hub can be replaced!** For a private Registry deployed, a custom -> Docker Hub can be used to serve and validate token according to different -> policies. - -Docker computes the checksums and submit them to the Docker Hub at the end of -the push. When a repository name does not have checksums on the Docker Hub, -it means that the push is in progress (since checksums are submitted at -the end). - -**API (pushing repos foo/bar):** - -1. (Docker -> Docker Hub) PUT /v1/repositories/foo/bar/ - -**Headers**: - - Authorization: Basic sdkjfskdjfhsdkjfh== X-Docker-Token: - true - -**Action**: - -- in Docker Hub, we allocated a new repository, and set to - initialized - -**Body**: - -(The body contains the list of images that are going to be -pushed, with empty checksums. The checksums will be set at -the end of the push): - - [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}] - -2. (Docker Hub -> Docker) 200 Created - -**Headers**: - - WWW-Authenticate: Token - signature=123abc,repository=”foo/bar”,access=write - X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] - -3. (Docker -> Registry) PUT /v1/images/98765432_parent/json - -**Headers**: - - Authorization: Token - signature=123abc,repository=”foo/bar”,access=write - -4. (Registry->Docker Hub) GET /v1/repositories/foo/bar/images - -**Headers**: - - Authorization: Token - signature=123abc,repository=”foo/bar”,access=write - -**Action**: - -- Docker Hub: - will invalidate the token. -- Registry: - grants a session (if token is approved) and fetches - the images id - -5. (Docker -> Registry) PUT /v1/images/98765432_parent/json - -**Headers**: - - Authorization: Token - signature=123abc,repository=”foo/bar”,access=write - Cookie: (Cookie provided by the Registry) - -6. (Docker -> Registry) PUT /v1/images/98765432/json - -**Headers**: - - Cookie: (Cookie provided by the Registry) - -7. (Docker -> Registry) PUT /v1/images/98765432_parent/layer - -**Headers**: - - Cookie: (Cookie provided by the Registry) - -8. (Docker -> Registry) PUT /v1/images/98765432/layer - -**Headers**: - - X-Docker-Checksum: sha256:436745873465fdjkhdfjkgh - -9. (Docker -> Registry) PUT /v1/repositories/foo/bar/tags/latest - -**Headers**: - - Cookie: (Cookie provided by the Registry) - -**Body**: - - “98765432” - -10. (Docker -> Docker Hub) PUT /v1/repositories/foo/bar/images - -**Headers**: - - Authorization: Basic 123oislifjsldfj== X-Docker-Endpoints: - registry1.docker.io (no validation on this right now) - -**Body**: - - (The image, id`s, tags and checksums) - [{“id”: - “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, - “checksum”: - “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}] - -**Return**: - - HTTP 204 - -> **Note:** If push fails and they need to start again, what happens in the Docker Hub, -> there will already be a record for the namespace/name, but it will be -> initialized. Should we allow it, or mark as name already used? One edge -> case could be if someone pushes the same thing at the same time with two -> different shells. - -If it's a retry on the Registry, Docker has a cookie (provided by the -registry after token validation). So the Docker Hub won't have to provide a -new token. - -### Delete - -If you need to delete something from the Docker Hub or registry, we need a -nice clean way to do that. Here is the workflow. - -1. Docker contacts the Docker Hub to request a delete of a repository - `samalba/busybox` (authentication required with user credentials) -2. If authentication works and repository is valid, `samalba/busybox` - is marked as deleted and a temporary token is returned -3. Send a delete request to the registry for the repository (along with - the token) -4. Registry A contacts the Docker Hub to verify the token (token must - corresponds to the repository name) -5. Docker Hub validates the token. Registry A deletes the repository and - everything associated to it. -6. docker contacts the Docker Hub to let it know it was removed from the - registry, the Docker Hub removes all records from the database. - -> **Note**: -> The Docker client should present an "Are you sure?" prompt to confirm -> the deletion before starting the process. Once it starts it can't be -> undone. - -**API (deleting repository foo/bar):** - -1. (Docker -> Docker Hub) DELETE /v1/repositories/foo/bar/ - -**Headers**: - - Authorization: Basic sdkjfskdjfhsdkjfh== X-Docker-Token: - true - -**Action**: - -- in Docker Hub, we make sure it is a valid repository, and set - to deleted (logically) - -**Body**: - - Empty - -2. (Docker Hub -> Docker) 202 Accepted - -**Headers**: - - WWW-Authenticate: Token - signature=123abc,repository=”foo/bar”,access=delete - X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] - # list of endpoints where this repo lives. - -3. (Docker -> Registry) DELETE /v1/repositories/foo/bar/ - -**Headers**: - - Authorization: Token - signature=123abc,repository=”foo/bar”,access=delete - -4. (Registry->Docker Hub) PUT /v1/repositories/foo/bar/auth - -**Headers**: - - Authorization: Token - signature=123abc,repository=”foo/bar”,access=delete - -**Action**: - -- Docker Hub: - will invalidate the token. -- Registry: - deletes the repository (if token is approved) - -5. (Registry -> Docker) 200 OK - - 200 If success 403 if forbidden 400 if bad request 404 - if repository isn't found - -6. (Docker -> Docker Hub) DELETE /v1/repositories/foo/bar/ - -**Headers**: - - Authorization: Basic 123oislifjsldfj== X-Docker-Endpoints: - registry-1.docker.io (no validation on this right now) - -**Body**: - - Empty - -**Return**: - - HTTP 200 - -## How to use the Registry in standalone mode - -The Docker Hub has two main purposes (along with its fancy social features): - - - Resolve short names (to avoid passing absolute URLs all the time): - - username/projectname -> - https://registry.docker.io/users//repositories// - team/projectname -> - https://registry.docker.io/team//repositories// - - - Authenticate a user as a repos owner (for a central referenced - repository) - -### Without a Docker Hub - -Using the Registry without the Docker Hub can be useful to store the images -on a private network without having to rely on an external entity -controlled by Docker Inc. - -In this case, the registry will be launched in a special mode -(-standalone? ne? -no-index?). In this mode, the only thing which changes is -that Registry will never contact the Docker Hub to verify a token. It will be -the Registry owner responsibility to authenticate the user who pushes -(or even pulls) an image using any mechanism (HTTP auth, IP based, -etc...). - -In this scenario, the Registry is responsible for the security in case -of data corruption since the checksums are not delivered by a trusted -entity. - -As hinted previously, a standalone registry can also be implemented by -any HTTP server handling GET/PUT requests (or even only GET requests if -no write access is necessary). - -### With a Docker Hub - -The Docker Hub data needed by the Registry are simple: - - - Serve the checksums - - Provide and authorize a Token - -In the scenario of a Registry running on a private network with the need -of centralizing and authorizing, it's easy to use a custom Docker Hub. - -The only challenge will be to tell Docker to contact (and trust) this -custom Docker Hub. Docker will be configurable at some point to use a -specific Docker Hub, it'll be the private entity responsibility (basically -the organization who uses Docker in a private environment) to maintain -the Docker Hub and the Docker's configuration among its consumers. - -## The API - -The first version of the api is available here: -[https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md](https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md) - -### Images - -The format returned in the images is not defined here (for layer and -JSON), basically because Registry stores exactly the same kind of -information as Docker uses to manage them. - -The format of ancestry is a line-separated list of image ids, in age -order, i.e. the image's parent is on the last line, the parent of the -parent on the next-to-last line, etc.; if the image has no parent, the -file is empty. - - GET /v1/images//layer - PUT /v1/images//layer - GET /v1/images//json - PUT /v1/images//json - GET /v1/images//ancestry - PUT /v1/images//ancestry - -### Users - -### Create a user (Docker Hub) - - POST /v1/users: - -**Body**: - - {"email": "[sam@docker.com](mailto:sam%40docker.com)", - "password": "toto42", "username": "foobar"`} - -**Validation**: - -- **username**: min 4 character, max 30 characters, must match the - regular expression [a-z0-9_]. -- **password**: min 5 characters - -**Valid**: - - return HTTP 201 - -Errors: HTTP 400 (we should create error codes for possible errors) - -invalid json - missing field - wrong format (username, password, email, -etc) - forbidden name - name already exists - -> **Note**: -> A user account will be valid only if the email has been validated (a -> validation link is sent to the email address). - -### Update a user (Docker Hub) - - PUT /v1/users/ - -**Body**: - - {"password": "toto"} - -> **Note**: -> We can also update email address, if they do, they will need to reverify -> their new email address. - -### Login (Docker Hub) - -Does nothing else but asking for a user authentication. Can be used to -validate credentials. HTTP Basic Auth for now, maybe change in future. - -GET /v1/users - -**Return**: -- Valid: HTTP 200 -- Invalid login: HTTP 401 -- Account inactive: HTTP 403 Account is not Active - -### Tags (Registry) - -The Registry does not know anything about users. Even though -repositories are under usernames, it's just a namespace for the -registry. Allowing us to implement organizations or different namespaces -per user later, without modifying the Registry's API. - -The following naming restrictions apply: - - - Namespaces must match the same regular expression as usernames (See - 4.2.1.) - - Repository names must match the regular expression [a-zA-Z0-9-_.] - -### Get all tags: - - GET /v1/repositories///tags - - **Return**: HTTP 200 - [ - { - "layer": "9e89cc6f", - "name": "latest" - }, - { - "layer": "b486531f", - "name": "0.1.1", - } - ] - -**4.3.2 Read the content of a tag (resolve the image id):** - - GET /v1/repositories///tags/ - -**Return**: - - "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f" - -**4.3.3 Delete a tag (registry):** - - DELETE /v1/repositories///tags/ - -### 4.4 Images (Docker Hub) - -For the Docker Hub to “resolve” the repository name to a Registry location, -it uses the X-Docker-Endpoints header. In other terms, this requests -always add a `X-Docker-Endpoints` to indicate the -location of the registry which hosts this repository. - -**4.4.1 Get the images:** - - GET /v1/repositories///images - - **Return**: HTTP 200 - [{“id”: - “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, - “checksum”: - “[md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087](md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087)”}] - -### Add/update the images: - -You always add images, you never remove them. - - PUT /v1/repositories///images - -**Body**: - - [ {“id”: - “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, - “checksum”: - “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”} - ] - -**Return**: - - 204 - -### Repositories - -### Remove a Repository (Registry) - -DELETE /v1/repositories// - -Return 200 OK - -### Remove a Repository (Docker Hub) - -This starts the delete process. see 2.3 for more details. - -DELETE /v1/repositories// - -Return 202 OK - -## Chaining Registries - -It's possible to chain Registries server for several reasons: - - - Load balancing - - Delegate the next request to another server - -When a Registry is a reference for a repository, it should host the -entire images chain in order to avoid breaking the chain during the -download. - -The Docker Hub and Registry use this mechanism to redirect on one or the -other. - -Example with an image download: - -On every request, a special header can be returned: - - X-Docker-Endpoints: server1,server2 - -On the next request, the client will always pick a server from this -list. - -## Authentication and authorization - -### On the Docker Hub - -The Docker Hub supports both “Basic” and “Token” challenges. Usually when -there is a `401 Unauthorized`, the Docker Hub replies -this: - - 401 Unauthorized - WWW-Authenticate: Basic realm="auth required",Token - -You have 3 options: - -1. Provide user credentials and ask for a token - -**Header**: - - Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== - X-Docker-Token: true - -In this case, along with the 200 response, you'll get a new token -(if user auth is ok): If authorization isn't correct you get a 401 -response. If account isn't active you will get a 403 response. - -**Response**: - - 200 OK - X-Docker-Token: Token - signature=123abc,repository=”foo/bar”,access=read - - -2. Provide user credentials only - -**Header**: - - Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== - -3. Provide Token - -**Header**: - - Authorization: Token - signature=123abc,repository=”foo/bar”,access=read - -### 6.2 On the Registry - -The Registry only supports the Token challenge: - - 401 Unauthorized - WWW-Authenticate: Token - -The only way is to provide a token on `401 Unauthorized` -responses: - - Authorization: Token signature=123abc,repository="foo/bar",access=read - -Usually, the Registry provides a Cookie when a Token verification -succeeded. Every time the Registry passes a Cookie, you have to pass it -back the same cookie.: - - 200 OK - Set-Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="; Path=/; HttpOnly - -Next request: - - GET /(...) - Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4=" - -## Document version - - - 1.0 : May 6th 2013 : initial release - - 1.1 : June 1st 2013 : Added Delete Repository and way to handle new - source namespace. - diff --git a/docs/userguide/image_management.md b/docs/userguide/image_management.md new file mode 100644 index 000000000..28fef6c02 --- /dev/null +++ b/docs/userguide/image_management.md @@ -0,0 +1,53 @@ + + +# Image management + +The Docker Engine provides a client which you can use to create images on the command line or through a build process. You can run these images in a container or publish them for others to use. Storing the images you create, searching for images you might want, or publishing images others might use are all elements of image management. + +This section provides an overview of the major features and products Docker provides for image management. + + +## Docker Hub + +The [Docker Hub](https://docs.docker.com/docker-hub/) is responsible for centralizing information about user accounts, images, and public name spaces. It has different components: + + - Web UI + - Meta-data store (comments, stars, list public repositories) + - Authentication service + - Tokenization + +There is only one instance of the Docker Hub, run and managed by Docker Inc. This public Hub is useful for most individuals and smaller companies. + +## Docker Registry and the Docker Trusted Registry + +The Docker Registry is a component of Docker's ecosystem. A registry is a +storage and content delivery system, holding named Docker images, available in +different tagged versions. For example, the image `distribution/registry`, with +tags `2.0` and `latest`. Users interact with a registry by using docker push and +pull commands. For example, `docker pull myregistry.com/stevvooe/batman:voice`. + +The Docker Hub has its own registry which, like the Hub itself, is run and managed by Docker. There are other ways to obtain a registry. You can purchase the [Docker Trusted Registry](https://docs.docker.com/dockter-trusted-registry) product to run on your company's network. Alternatively, you can use the Docker Registry component to build a private registry. For information about using a registry, see overview for the [Docker Registry](https://docs.docker.com/registry). + + +## Content Trust + +When transferring data among networked systems, *trust* is a central concern. In +particular, when communicating over an untrusted medium such as the internet, it +is critical to ensure the integrity and publisher of the all the data a system +operates on. You use Docker to push and pull images (data) to a registry. +Content trust gives you the ability to both verify the integrity and the +publisher of all the data received from a registry over any channel. + +[Content trust](/security/trust) is currently only available for users of the +public Docker Hub. It is currently not available for the Docker Trusted Registry +or for private registries. \ No newline at end of file From c65afe6ba8d39cadb84d756e0db38e5abc656c98 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Mon, 10 Aug 2015 13:29:08 -0700 Subject: [PATCH 77/79] Updating new vm and with migrate Signed-off-by: Mary Anthony (cherry picked from commit e5c160e1d68fa7ed485827a88a577b9fff2077c7) --- docs/installation/mac.md | 406 +++++++++++++++++++++-------------- docs/installation/windows.md | 358 +++++++++++++++++++++++------- 2 files changed, 517 insertions(+), 247 deletions(-) diff --git a/docs/installation/mac.md b/docs/installation/mac.md index ee5fcaaef..c6ae0b37c 100644 --- a/docs/installation/mac.md +++ b/docs/installation/mac.md @@ -10,37 +10,34 @@ parent = "smn_engine" # Mac OS X -You can install Docker using Boot2Docker to run `docker` commands at your command-line. -Choose this installation if you are familiar with the command-line or plan to -contribute to the Docker project on GitHub. +> **Note**: This release of Docker deprecates the Boot2Docker command line in +> favor of Docker Machine. Use the Docker Toolbox to install Docker Machine as +> well as the other Docker tools. -[Download Kitematic](https://kitematic.com/download) +You install Docker using Docker Toolbox. Docker Toolbox includes the following Docker tools: -Alternatively, you may want to try
Kitematic, an application that lets you set up Docker and -run containers using a graphical user interface (GUI). - -## Command-line Docker with Boot2Docker +* Docker Machine for running the `docker-machine` binary +* Docker Engine for running the `docker` binary +* Docker Compose for running the `docker-compose` binary +* Kitematic, the Docker GUI +* a shell preconfigured for a Docker command-line environment +* Oracle VM VirtualBox Because the Docker daemon uses Linux-specific kernel features, you can't run -Docker natively in OS X. Instead, you must install the Boot2Docker application. -The application includes a VirtualBox Virtual Machine (VM), Docker itself, and the -Boot2Docker management tool. - -The Boot2Docker management tool is a lightweight Linux virtual machine made -specifically to run the Docker daemon on Mac OS X. The VirtualBox VM runs -completely from RAM, is a small ~24MB download, and boots in approximately 5s. +Docker natively in OS X. Instead, you must use `docker-machine` to create and +attach to a virtual machine (VM). This machine is a Linux VM that hosts Docker +for you on your Mac. **Requirements** -Your Mac must be running OS X 10.6 "Snow Leopard" or newer to run Boot2Docker. +Your Mac must be running OS X 10.8 "Mountain Lion" or newer to install the +Docker Toolbox. ### Learn the key concepts before installing -In a Docker installation on Linux, your machine is both the localhost and the -Docker host. In networking, localhost means your computer. The Docker host is -the machine on which the containers run. +In a Docker installation on Linux, your physical machine is both the localhost +and the Docker host. In networking, localhost means your computer. The Docker +host is the computer on which the containers run. On a typical Linux installation, the Docker client, the Docker daemon, and any containers run directly on your localhost. This means you can address ports on a @@ -49,135 +46,243 @@ Docker container using standard localhost addressing such as `localhost:8000` or ![Linux Architecture Diagram](/installation/images/linux_docker_host.svg) -In an OS X installation, the `docker` daemon is running inside a Linux virtual -machine provided by Boot2Docker. +In an OS X installation, the `docker` daemon is running inside a Linux VM called +`default`. The `default` is a lightweight Linux VM made specifically to run +the Docker daemon on Mac OS X. The VM runs completely from RAM, is a small ~24MB +download, and boots in approximately 5s. ![OSX Architecture Diagram](/installation/images/mac_docker_host.svg) -In OS X, the Docker host address is the address of the Linux VM. -When you start the `boot2docker` process, the VM is assigned an IP address. Under -`boot2docker` ports on a container map to ports on the VM. To see this in +In OS X, the Docker host address is the address of the Linux VM. When you start +the VM with `docker-machine` it is assigned an IP address. When you start a +container, the ports on a container map to ports on the VM. To see this in practice, work through the exercises on this page. ### Installation -1. Go to the [boot2docker/osx-installer ]( - https://github.com/boot2docker/osx-installer/releases/latest) release page. +If you have VirtualBox running, you must shut it down before running the +installer. -4. Download Boot2Docker by clicking `Boot2Docker-x.x.x.pkg` in the "Downloads" - section. +1. Go to the [Docker Toolbox](https://www.docker.com/toolbox) page. -3. Install Boot2Docker by double-clicking the package. +2. Click the installer link to download. - The installer places Boot2Docker and VirtualBox in your "Applications" folder. +3. Install Docker Toolbox by double-clicking the package or by right-clicking +and choosing "Open" from the pop-up menu. -The installation places the `docker` and `boot2docker` binaries in your -`/usr/local/bin` directory. + The installer launches the "Install Docker Toolbox" dialog. + + ![Install Docker Toolbox](/installation/images/mac-welcome-page.png) + +4. Press "Continue" to install the toolbox. + + The installer presents you with options to customize the standard + installation. + + ![Standard install](/installation/images/mac-page-two.png) + + By default, the standard Docker Toolbox installation: + + * installs binaries for the Docker tools in `/usr/local/bin` + * makes these binaries available to all users + * updates any existing VirtualBox installation + + Change these defaults by pressing "Customize" or "Change + Install Location." + +5. Press "Install" to perform the standard installation. + + The system prompts you for your password. + + ![Password prompt](/installation/images/mac-password-prompt.png) + +6. Provide your password to continue with the installation. + + When it completes, the installer provides you with some information you can + use to complete some common tasks. + + ![All finished](/installation/images/mac-page-finished.png) + +7. Press "Close" to exit. -## Start the Boot2Docker Application +## Running a Docker Container -To run a Docker container, you first start the `boot2docker` VM and then issue -`docker` commands to create, load, and manage containers. You can launch -`boot2docker` from your Applications folder or from the command line. +To run a Docker container, you: -> **NOTE**: Boot2Docker is designed as a development tool. You should not use -> it in production environments. +* create a new (or start an existing) Docker virtual machine +* switch your environment to your new VM +* use the `docker` client to create, load, and manage containers -### From the Applications folder +Once you create a machine, you can reuse it as often as you like. Like any +VirtualBox VM, it maintains its configuration between uses. -When you launch the "Boot2Docker" application from your "Applications" folder, the -application: +There are two ways to use the installed tools, from the Docker Quickstart Terminal or +[from your shell](#from-your-shell). -* opens a terminal window +### From the Docker Quickstart Terminal -* creates a $HOME/.boot2docker directory +1. Open the "Applications" folder or the "Launchpad". -* creates a VirtualBox ISO and certs +2. Find the Docker Quickstart Terminal and double-click to launch it. -* starts a VirtualBox VM running the `docker` daemon + The application: -Once the launch completes, you can run `docker` commands. A good way to verify -your setup succeeded is to run the `hello-world` container. + * opens a terminal window + * creates a VM called `default` if it doesn't exists, starts the VM if it does + * points the terminal environment to this VM - $ docker run hello-world - Unable to find image 'hello-world:latest' locally - 511136ea3c5a: Pull complete - 31cbccb51277: Pull complete - e45a5af57b00: Pull complete - hello-world:latest: The image you are pulling has been verified. - Important: image verification is a tech preview feature and should not be - relied on to provide security. - Status: Downloaded newer image for hello-world:latest - Hello from Docker. - This message shows that your installation appears to be working correctly. + Once the launch completes, the Docker Quickstart Terminal reports: - To generate this message, Docker took the following steps: - 1. The Docker client contacted the Docker daemon. - 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. - (Assuming it was not already locally available.) - 3. The Docker daemon created a new container from that image which runs the - executable that produces the output you are currently reading. - 4. The Docker daemon streamed that output to the Docker client, which sent it - to your terminal. + ![All finished](/installation/images/mac-success.png) - To try something more ambitious, you can run an Ubuntu container with: - $ docker run -it ubuntu bash + Now, you can run `docker` commands. - For more examples and ideas, visit: - http://docs.docker.com/userguide/ +3. Verify your setup succeeded by running the `hello-world` container. + + $ docker run hello-world + Unable to find image 'hello-world:latest' locally + 511136ea3c5a: Pull complete + 31cbccb51277: Pull complete + e45a5af57b00: Pull complete + hello-world:latest: The image you are pulling has been verified. + Important: image verification is a tech preview feature and should not be + relied on to provide security. + Status: Downloaded newer image for hello-world:latest + Hello from Docker. + This message shows that your installation appears to be working correctly. + + To generate this message, Docker took the following steps: + 1. The Docker client contacted the Docker daemon. + 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. + (Assuming it was not already locally available.) + 3. The Docker daemon created a new container from that image which runs the + executable that produces the output you are currently reading. + 4. The Docker daemon streamed that output to the Docker client, which sent it + to your terminal. + + To try something more ambitious, you can run an Ubuntu container with: + $ docker run -it ubuntu bash + + For more examples and ideas, visit: + http://docs.docker.com/userguide/ -A more typical way to start and stop `boot2docker` is using the command line. +A more typical way to interact with the Docker tools is from your regular shell command line. -### From your command line +### From your shell -Initialize and run `boot2docker` from the command line, do the following: +This section assumes you are running a Bash shell. You may be running a +different shell such as C Shell but the commands are the same. -1. Create a new Boot2Docker VM. +1. Create a new Docker VM. - $ boot2docker init + $ docker-machine create --driver virtualbox default + Creating VirtualBox VM... + Creating SSH key... + Starting VirtualBox VM... + Starting VM... + To see how to connect Docker to this machine, run: docker-machine env default - This creates a new virtual machine. You only need to run this command once. + This creates a new `default` in VirtualBox. -2. Start the `boot2docker` VM. + ![default](/installation/images/default.png) - $ boot2docker start + The command also creates a machine configuration in the + `~/.docker/machine/machines/default` directory. You only need to run the + `create` command once. Then, you can use `docker-machine` to start, stop, + query, and otherwise manage the VM from the command line. -3. Display the environment variables for the Docker client. +2. List your available machines. - $ boot2docker shellinit - Writing /Users/mary/.boot2docker/certs/boot2docker-vm/ca.pem - Writing /Users/mary/.boot2docker/certs/boot2docker-vm/cert.pem - Writing /Users/mary/.boot2docker/certs/boot2docker-vm/key.pem - export DOCKER_HOST=tcp://192.168.59.103:2376 - export DOCKER_CERT_PATH=/Users/mary/.boot2docker/certs/boot2docker-vm - export DOCKER_TLS_VERIFY=1 + $ docker-machine ls + NAME ACTIVE DRIVER STATE URL SWARM + default * virtualbox Running tcp://192.168.99.101:2376 - The specific paths and address on your machine will be different. + If you have previously installed the deprecated Boot2Docker application or + run the Docker Quickstart Terminal, you may have a `dev` VM as well. When you + created `default`, the `docker-machine` command provided instructions + for learning how to connect the VM. -4. To set the environment variables in your shell do the following: +3. Get the environment commands for your new VM. - $ eval "$(boot2docker shellinit)" + $ docker-machine env default + export DOCKER_TLS_VERIFY="1" + export DOCKER_HOST="tcp://192.168.99.101:2376" + export DOCKER_CERT_PATH="/Users/mary/.docker/machine/machines/default" + export DOCKER_MACHINE_NAME="default" + # Run this command to configure your shell: + # eval "$(docker-machine env default)" - You can also set them manually by using the `export` commands `boot2docker` - returns. +4. Connect your shell to the `default` machine. + + $ eval "$(docker-machine env default)" 5. Run the `hello-world` container to verify your setup. $ docker run hello-world -## Basic Boot2Docker exercises +## Learn about your Toolbox installation -At this point, you should have `boot2docker` running and the `docker` client -environment initialized. To verify this, run the following commands: +Toolbox installs the Docker Engine binary, the Docker binary on your system. When you +use the Docker Quickstart Terminal or create a `default` manually, Docker +Machine updates the `~/.docker/machine/machines/default` folder to your +system. This folder contains the configuration for the VM. - $ boot2docker status - $ docker version +You can create multiple VMs on your system with Docker Machine. So, you may have +more than one VM folder if you have more than one VM. To remove a VM, use the +`docker-machine rm ` command. -Work through this section to try some practical container tasks using `boot2docker` VM. +## Migrate from Boot2Docker + +If you were using Boot2Docker previously, you have a pre-existing Docker +`boot2docker-vm` VM on your local system. To allow Docker Machine to manage +this older VM, you can migrate it. + +1. Open a terminal or the Docker CLI on your system. + +2. Type the following command. + + $ docker-machine create -d virtualbox --virtualbox-import-boot2docker-vm boot2docker-vm docker-vm + +3. Use the `docker-machine` command to interact with the migrated VM. + +The `docker-machine` subcommands are slightly different than the `boot2docker` +subcommands. The table below lists the equivalent `docker-machine` subcommand +and what it does: + +| `boot2docker` | `docker-machine` | `docker-machine` description | +|----------------|------------------|----------------------------------------------------------| +| init | create | Creates a new docker host. | +| up | start | Starts a stopped machine. | +| ssh | ssh | Runs a command or interactive ssh session on the machine.| +| save | - | Not applicable. | +| down | stop | Stops a running machine. | +| poweroff | stop | Stops a running machine. | +| reset | restart | Restarts a running machine. | +| config | inspect | Prints machine configuration details. | +| status | ls | Lists all machines and their status. | +| info | inspect | Displays a machine's details. | +| ip | ip | Displays the machine's ip address. | +| shellinit | env | Displays shell commands needed to configure your shell to interact with a machine | +| delete | rm | Removes a machine. | +| download | - | Not applicable. | +| upgrade | upgrade | Upgrades a machine's Docker client to the latest stable release. | + + +## Example of Docker on Mac OS X + +Work through this section to try some practical container tasks on a VM. At this +point, you should have a VM running and be connected to it through your shell. +To verify this, run the following commands: + + $ docker-machine ls + NAME ACTIVE DRIVER STATE URL SWARM + dev * virtualbox Running tcp://192.168.99.100:2376 + +The `ACTIVE` machine, in this case `dev`, is the one your environment is pointing to. ### Access container ports @@ -212,11 +317,11 @@ Work through this section to try some practical container tasks using `boot2dock This didn't work. The reason it doesn't work is your `DOCKER_HOST` address is not the localhost address (0.0.0.0) but is instead the address of the - `boot2docker` VM. + your Docker VM. -5. Get the address of the `boot2docker` VM. +5. Get the address of the `dev` VM. - $ boot2docker ip + $ docker-machine ip dev 192.168.59.103 6. Enter the `http://192.168.59.103:49157` address in your browser: @@ -232,7 +337,7 @@ Work through this section to try some practical container tasks using `boot2dock ### Mount a volume on the container -When you start `boot2docker`, it automatically shares your `/Users` directory +When you start a container it automatically shares your `/Users/username` directory with the VM. You can use this share point to mount directories onto your container. The next exercise demonstrates how to do this. @@ -254,7 +359,8 @@ The next exercise demonstrates how to do this. 5. Start a new `nginx` container and replace the `html` folder with your `site` directory. - $ docker run -d -P -v $HOME/site:/usr/share/nginx/html --name mysite nginx + $ docker run -d -P -v $HOME/site:/usr/share/nginx/html \ + --name mysite nginx 6. Get the `mysite` container's port. @@ -274,85 +380,53 @@ The next exercise demonstrates how to do this. ![Cool page](/installation/images/cool_view.png) -9. Stop and then remove your running `mysite` container. +10. Stop and then remove your running `mysite` container. $ docker stop mysite $ docker rm mysite -## Upgrade Boot2Docker -If you running Boot2Docker 1.4.1 or greater, you can upgrade Boot2Docker from -the command line. If you are running an older version, you should use the -package provided by the `boot2docker` repository. +## Upgrade Docker Toolbox -### From the command line - -To upgrade from 1.4.1 or greater, you can do this: - -1. Open a terminal on your local machine. - -2. Stop the `boot2docker` application. - - $ boot2docker stop - -3. Run the upgrade command. - - $ boot2docker upgrade +To upgrade Docker Toolbox, download an re-run [the Docker Toolbox +installer](https://docker.com/toolbox/). -### Use the installer +## Uninstall Docker Toolbox -To upgrade any version of Boot2Docker, do this: +To uninstall, do the following: -1. Open a terminal on your local machine. +1. List your machines. -2. Stop the `boot2docker` application. + $ docker-machine ls + NAME ACTIVE DRIVER STATE URL SWARM + dev * virtualbox Running tcp://192.168.99.100:2376 + my-docker-machine virtualbox Stopped + default virtualbox Stopped - $ boot2docker stop +2. Remove each machine. -3. Go to the [boot2docker/osx-installer ]( - https://github.com/boot2docker/osx-installer/releases/latest) release page. + $ docker-machine rm dev + Successfully removed dev -4. Download Boot2Docker by clicking `Boot2Docker-x.x.x.pkg` in the "Downloads" - section. + Removing a machine deletes its VM from VirtualBox and from the + `~/.docker/machine/machines` directory. -2. Install Boot2Docker by double-clicking the package. +3. Remove the Docker Quickstart Terminal and Kitematic from your "Applications" folder. - The installer places Boot2Docker in your "Applications" folder. +4. Remove the `docker`, `docker-compose`, and `docker-machine` commands from the `/usr/local/bin` folder. + + $ rm /usr/local/bin/docker + +5. Delete the `~/.docker` folder from your system. -## Uninstallation +## Learning more -1. Go to the [boot2docker/osx-installer ]( - https://github.com/boot2docker/osx-installer/releases/latest) release page. +Use `docker-machine help` to list the full command line reference for Docker Machine. For more +information about using SSH or SCP to access a VM, see [the Docker Machine +documentation](https://docs.docker.com/machine/). -2. Download the source code by clicking `Source code (zip)` or - `Source code (tar.gz)` in the "Downloads" section. - -3. Extract the source code. - -4. Open a terminal on your local machine. - -5. Change to the directory where you extracted the source code: - - $ cd - -6. Make sure the uninstall.sh script is executable: - - $ chmod +x uninstall.sh - -7. Run the uninstall.sh script: - - $ ./uninstall.sh - - -## Learning more and acknowledgement - -Use `boot2docker help` to list the full command line reference. For more -information about using SSH or SCP to access the Boot2Docker VM, see the README -at [Boot2Docker repository](https://github.com/boot2docker/boot2docker). - -Thanks to Chris Jones whose [blog](http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide) -inspired me to redo this page. - -Continue with the [Docker User Guide](/userguide). +You can continue with the [Docker User Guide](/userguide). If you are +interested in using the Kitematic GUI, see the [Kitermatic user +guide](/kitematic/userguide/). diff --git a/docs/installation/windows.md b/docs/installation/windows.md index efaf0e08e..0737fa571 100644 --- a/docs/installation/windows.md +++ b/docs/installation/windows.md @@ -9,118 +9,315 @@ parent = "smn_engine" # Windows -> **Note:** -> Docker has been tested on Windows 7 and 8.1; it may also run on older versions. -> Your processor needs to support hardware virtualization. -The Docker Engine uses Linux-specific kernel features, so to run it on Windows -we need to use a lightweight virtual machine (VM). You use the **Windows Docker -Client** to control the virtualized Docker Engine to build, run, and manage -Docker containers. +> **Note**: This release of Docker deprecates the Boot2Docker command line in +> favor of Docker Machine. Use the Docker Toolbox to install Docker Machine as +> well as the other Docker tools. -To make this process easier, we've designed a helper application called -[Boot2Docker](https://github.com/boot2docker/boot2docker) which creates a Linux virtual -machine on Windows to run Docker on a Linux operating system. +You install Docker using Docker Toolbox. Docker Toolbox includes the following Docker tools: -Although you will be using Windows Docker client, the docker engine hosting the -containers will still be running on Linux. Until the Docker engine for Windows -is developed, you can launch only Linux containers from your Windows machine. +* Docker Machine for running the `docker-machine` binary +* Docker Engine for running the `docker` binary +* Kitematic, the Docker GUI +* a shell preconfigured for a Docker command-line environment +* Oracle VM VirtualBox + +Because the Docker daemon uses Linux-specific kernel features, you can't run +Docker natively in Windows. Instead, you must use `docker-machine` to create and attach to a Docker VM on your machine. This VM hosts Docker for you on your Windows system. + +The Docker VM is lightweight Linux virtual machine made specifically to run the +Docker daemon on Windows. The VirtualBox VM runs completely from RAM, is a +small ~24MB download, and boots in approximately 5s. + +## Requirements + +Your machine must be running Windows 7.1, 8/8.1 or newer to run Docker. Windows 10 is not currently supported. To find out what version of Windows you have: + +1. Right click the Windows message and choose **System**. + + ![Which version](/installation/images/win_ver.png) + + If you aren't using a supported version, you could consider upgrading your + operating system. + +2. Make sure your Windows system supports Hardware Virtualization Technology and that virtualization is enabled. + + #### For Windows 8 or 8.1 + + Choose **Start > Task Manager** and navigate to the **Performance** tab. + Under **CPU** you should see the following: + + ![Release page](/installation/images/virtualization.png) + + If virtualization is not enabled on your system, follow the manufacturer's instructions for enabling it. + + ### For Windows 7 + + Run the Microsoft® Hardware-Assisted Virtualization Detection + Tool and follow the on-screen instructions. + + +> **Note**: If you have Docker hosts running and you don't wish to do a Docker Toolbox +installation, you can install the `docker.exe` using the *unofficial* Windows package +manager Chocolately. For information on how to do this, see [Docker package on +Chocolatey](http://chocolatey.org/packages/docker). + +### Learn the key concepts before installing + +In a Docker installation on Linux, your machine is both the localhost and the +Docker host. In networking, localhost means your computer. The Docker host is +the machine on which the containers run. + +On a typical Linux installation, the Docker client, the Docker daemon, and any +containers run directly on your localhost. This means you can address ports on a +Docker container using standard localhost addressing such as `localhost:8000` or +`0.0.0.0:8376`. + +![Linux Architecture Diagram](/installation/images/linux_docker_host.svg) + +In an Windows installation, the `docker` daemon is running inside a Linux virtual +machine. You use the Windows Docker client to talk to the Docker host VM. Your +Docker containers run inside this host. ![Windows Architecture Diagram](/installation/images/win_docker_host.svg) -## Demonstration +In Windows, the Docker host address is the address of the Linux VM. When you +start the VM with `docker-machine` it is assigned an IP address. When you start +a container, the ports on a container map to ports on the VM. To see this in +practice, work through the exercises on this page. - -## Installation +### Installation -1. Download the latest release of the - [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest). -2. Run the installer, which will install Docker Client for Windows, VirtualBox, - Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker - management tool. - ![](/installation/images/windows-installer.png) -3. Run the **Boot2Docker Start** shortcut from your Desktop or “Program Files → - Boot2Docker for Windows”. - The Start script will ask you to enter an ssh key passphrase - the simplest - (but least secure) is to just hit [Enter]. +If you have VirtualBox running, you must shut it down before running the +installer. -4. The **Boot2Docker Start** will start a unix shell already configured to manage - Docker running inside the virtual machine. Run `docker version` to see - if it is working correctly: +1. Go to the [Docker Toolbox](https://www.docker.com/toolbox) page. -![](/installation/images/windows-boot2docker-start.png) +2. Click the installer link to download. -## Running Docker +3. Install Docker Toolbox by double-clicking the installer. -> **Note:** if you are using a remote Docker daemon, such as Boot2Docker, -> then _do not_ type the `sudo` before the `docker` commands shown in the -> documentation's examples. + The installer launches the "Setup - Docker Toolbox" dialog. -**Boot2Docker Start** will automatically start a shell with environment variables -correctly set so you can start using Docker right away: + ![Install Docker Toolbox](/installation/images/win-welcome.png) -Let's try the `hello-world` example image. Run +4. Press "Next" to install the toolbox. - $ docker run hello-world + The installer presents you with options to customize the standard + installation. By default, the standard Docker Toolbox installation: + + * installs executables for the Docker tools in `C:\Program Files\Docker Toolbox` + * updates any existing VirtualBox installation + * adds a Docker Inc. folder to your program shortcuts + * updates your `PATH` environment variable + * adds desktop icons for the Docker Quickstart Terminal and Kitematic + + This installation assumes the defaults are acceptable. + +5. Press "Next" until you reach the "Ready to Install" page. + + The system prompts you for your password. + + ![Install](/installation/images/win-page-6.png) + +6. Press "Install" to continue with the installation. + + When it completes, the installer provides you with some information you can + use to complete some common tasks. + + ![All finished](/installation/images/windows-finish.png) + +7. Press "Close" to exit. + +## Running a Docker Container + +To run a Docker container, you: + +* create a new (or start an existing) Docker virtual machine +* switch your environment to your new VM +* use the `docker` client to create, load, and manage containers + +Once you create a machine, you can reuse it as often as you like. Like any +VirtualBox VM, it maintains its configuration between uses. + +There are several ways to use the installed tools, from the Docker Quickstart Terminal or +[from your shell](#from-your-shell). + +### From the Docker Quickstart Terminal + +1. Find the Docker Quickstart Terminal icon on your Desktop and double-click to launch it. + + The application: + + * opens a terminal window + * creates a `default` if it doesn't exist, starts the VM if it does + * points the terminal environment to this VM + + Once the launch completes, you can run `docker` commands. + +3. Verify your setup succeeded by running the `hello-world` container. + + $ docker run hello-world + Unable to find image 'hello-world:latest' locally + 511136ea3c5a: Pull complete + 31cbccb51277: Pull complete + e45a5af57b00: Pull complete + hello-world:latest: The image you are pulling has been verified. + Important: image verification is a tech preview feature and should not be + relied on to provide security. + Status: Downloaded newer image for hello-world:latest + Hello from Docker. + This message shows that your installation appears to be working correctly. + + To generate this message, Docker took the following steps: + 1. The Docker client contacted the Docker daemon. + 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. + (Assuming it was not already locally available.) + 3. The Docker daemon created a new container from that image which runs the + executable that produces the output you are currently reading. + 4. The Docker daemon streamed that output to the Docker client, which sent it + to your terminal. + + To try something more ambitious, you can run an Ubuntu container with: + $ docker run -it ubuntu bash + + For more examples and ideas, visit: + http://docs.docker.com/userguide/ -This should download the very small `hello-world` image and print a -`Hello from Docker.` message. ## Using Docker from Windows Command Line Prompt (cmd.exe) -Launch a Windows Command Line Prompt (cmd.exe). +1. Launch a Windows Command Line Prompt (cmd.exe). -Boot2Docker command requires `ssh.exe` to be in the PATH, therefore we need to -include `bin` folder of the Git installation (which has ssh.exe) to the `%PATH%` -environment variable by running: + The `docker-machine` command requires `ssh.exe` in your `PATH` environment + variable. This `.exe` is in the MsysGit `bin` folder. - set PATH=%PATH%;"c:\Program Files (x86)\Git\bin" +2. Add this to the `%PATH%` environment variable by running: -and then we can run the `boot2docker start` command to start the Boot2Docker VM. -(Run `boot2docker init` command if you get an error saying machine does not -exist.) Then copy the instructions for cmd.exe to set the environment variables -to your console window and you are ready to run docker commands such as -`docker ps`: + set PATH=%PATH%;"c:\Program Files (x86)\Git\bin" -![](/installation/images/windows-boot2docker-cmd.png) +3. Create a new Docker VM. + + docker-machine create --driver virtualbox my-default + Creating VirtualBox VM... + Creating SSH key... + Starting VirtualBox VM... + Starting VM... + To see how to connect Docker to this machine, run: docker-machine env my-default + + The command also creates a machine configuration in the + `C:\USERS\USERNAME\.docker\machine\machines` directory. You only need to run the `create` + command once. Then, you can use `docker-machine` to start, stop, query, and + otherwise manage the VM from the command line. + +4. List your available machines. + + C:\Users\mary> docker-machine ls + NAME ACTIVE DRIVER STATE URL SWARM + my-default * virtualbox Running tcp://192.168.99.101:2376 + + If you have previously installed the deprecated Boot2Docker application or + run the Docker Quickstart Terminal, you may have a `dev` VM as well. + +5. Get the environment commands for your new VM. + + C:\Users\mary> docker-machine env --shell cmd my-default + +6. Connect your shell to the `my-default` machine. + + C:\Users\mary> eval "$(docker-machine env my-default)" + +7. Run the `hello-world` container to verify your setup. + + C:\Users\mary> docker run hello-world ## Using Docker from PowerShell -Launch a PowerShell window, then add `ssh.exe` to your PATH: +1. Launch a Windows PowerShell window. - $Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin" +2. Add `ssh.exe` to your PATH: -and after running the `boot2docker start` command it will print PowerShell -commands to set the environment variables to connect to the Docker daemon -running inside the VM. Run these commands and you are ready to run docker -commands such as `docker ps`: + PS C:\Users\mary> $Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin" -![](/installation/images/windows-boot2docker-powershell.png) +3. Create a new Docker VM. -> NOTE: You can alternatively run `boot2docker shellinit | Invoke-Expression` -> command to set the environment variables instead of copying and pasting on -> PowerShell. + PS C:\Users\mary> docker-machine create --driver virtualbox my-default -# Further Details +4. List your available machines. -The Boot2Docker management tool provides several commands: + C:\Users\mary> docker-machine ls + NAME ACTIVE DRIVER STATE URL SWARM + my-default * virtualbox Running tcp://192.168.99.101:2376 - $ boot2docker - Usage: boot2docker.exe [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|shellinit|delete|download|upgrade|version} [] +5. Get the environment commands for your new VM. -## Upgrading + C:\Users\mary> docker-machine env --shell powershell my-default -1. Download the latest release of the [Docker for Windows Installer]( - https://github.com/boot2docker/windows-installer/releases/latest) +6. Connect your shell to the `my-default` machine. -2. Run the installer, which will update the Boot2Docker management tool. + C:\Users\mary> eval "$(docker-machine env my-default)" -3. To upgrade your existing virtual machine, open a terminal and run: +7. Run the `hello-world` container to verify your setup. - boot2docker stop - boot2docker download - boot2docker start + C:\Users\mary> docker run hello-world + + +## Learn about your Toolbox installation + +Toolbox installs the Docker Engine binary in the `C:\Program Files\Docker +Toolbox` directory. When you use the Docker Quickstart Terminal or create a +`default` manually, Docker Machine updates the +`C:\USERS\USERNAME\.docker\machine\machines\default` folder to your +system. This folder contains the configuration for the VM. + +You can create multiple VMs on your system with Docker Machine. So, you may have +more than one VM folder if you have more than one VM. To remove a VM, use the +`docker-machine rm ` command. + +## Migrate from Boot2Docker + +If you were using Boot2Docker previously, you have a pre-existing Docker +`boot2docker-vm` VM on your local system. To allow Docker Machine to manage +this older VM, you can migrate it. + +1. Open a terminal or the Docker CLI on your system. + +2. Type the following command. + + $ docker-machine create -d virtualbox --virtualbox-import-boot2docker-vm boot2docker-vm docker-vm + +3. Use the `docker-machine` command to interact with the migrated VM. + +The `docker-machine` subcommands are slightly different than the `boot2docker` +subcommands. The table below lists the equivalent `docker-machine` subcommand +and what it does: + +| `boot2docker` | `docker-machine` | `docker-machine` description | +|----------------|------------------|----------------------------------------------------------| +| init | create | Creates a new docker host. | +| up | start | Starts a stopped machine. | +| ssh | ssh | Runs a command or interactive ssh session on the machine.| +| save | - | Not applicable. | +| down | stop | Stops a running machine. | +| poweroff | stop | Stops a running machine. | +| reset | restart | Restarts a running machine. | +| config | inspect | Prints machine configuration details. | +| status | ls | Lists all machines and their status. | +| info | inspect | Displays a machine's details. | +| ip | ip | Displays the machine's ip address. | +| shellinit | env | Displays shell commands needed to configure your shell to interact with a machine | +| delete | rm | Removes a machine. | +| download | - | Not applicable. | +| upgrade | upgrade | Upgrades a machine's Docker client to the latest stable release. | + + +## Upgrade Docker Toolbox + +To upgrade Docker Toolbox, download an re-run [the Docker Toolbox +installer](https://www.docker.com/toolbox). ## Container port redirection @@ -157,13 +354,12 @@ uses. You can do this with ## Uninstallation -You can uninstall Boot2Docker using Window's standard process for removing programs. -This process does not remove the `docker-install.exe` file. You must delete that file -yourself. +You can uninstall Docker Toolbox using Window's standard process for removing +programs. This process does not remove the `docker-install.exe` file. You must +delete that file yourself. -## References +## Learn more -If you have Docker hosts running and if you don't wish to do a -Boot2Docker installation, you can install the docker.exe using -unofficial Windows package manager Chocolately. For information -on how to do this, see [Docker package on Chocolatey](http://chocolatey.org/packages/docker). +You can continue with the [Docker User Guide](/userguide). If you are +interested in using the Kitematic GUI, see the [Kitermatic user +guide](/kitematic/userguide/). From 55e9551aaa5a6ff5928179e0c84b67ab261b3fe0 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 10 Aug 2015 21:54:03 -0700 Subject: [PATCH 78/79] minor cosmetic change to client output on pull Signed-off-by: Jessica Frazelle (cherry picked from commit 8bd632276069d7057406aa6ea151296cf5bbbd2f) --- hack/vendor.sh | 2 +- vendor/src/github.com/endophage/gotuf/client/client.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index da17b86b0..912419141 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -39,7 +39,7 @@ clone git github.com/docker/distribution 7dc8d4a26b689bd4892f2f2322dbce0b7119d68 clone git github.com/vbatts/tar-split v0.9.4 clone git github.com/docker/notary 8e8122eb5528f621afcd4e2854c47302f17392f7 -clone git github.com/endophage/gotuf 89ceb27829b9353dfee5ccccf7a3a9bb77008b05 +clone git github.com/endophage/gotuf a592b03b28b02bb29bb5878308fb1abed63383b5 clone git github.com/tent/canonical-json-go 96e4ba3a7613a1216cbd1badca4efe382adea337 clone git github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c diff --git a/vendor/src/github.com/endophage/gotuf/client/client.go b/vendor/src/github.com/endophage/gotuf/client/client.go index a321587ae..9cb910717 100644 --- a/vendor/src/github.com/endophage/gotuf/client/client.go +++ b/vendor/src/github.com/endophage/gotuf/client/client.go @@ -78,7 +78,7 @@ func (c *Client) update() error { if err != nil { // In this instance the root has not expired base on time, but is // expired based on the snapshot dictating a new root has been produced. - logrus.Info(err.Error()) + logrus.Debug(err) return tuf.ErrLocalRootExpired{} } // will always need top level targets at a minimum From 0d03096b65d12b11082e496ffdd824ff356f3fed Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 24 Jul 2015 18:06:40 -0700 Subject: [PATCH 79/79] Bump version to 1.8.0 Signed-off-by: David Calavera --- CHANGELOG.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++ VERSION | 2 +- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40a1de267..2324f9be6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,96 @@ # Changelog +## 1.8.0 (2015-08-11) + +### Distribution + ++ Trusted pull, push and build, disabled by default +* Make tar layers deterministic between registries +* Don't allow deleting the image of running containers +* Check if a tag name to load is a valid digest +* Allow one character repository names +* Add a more accurate error description for invalid tag name +* Make build cache ignore mtime + +### Cli + ++ Add support for DOCKER_CONFIG/--config to specify config file dir ++ Add --type flag for docker inspect command ++ Add formatting options to `docker ps` with `--format` ++ Replace `docker -d` with new subcommand `docker daemon` +* Zsh completion updates and improvements +* Add some missing events to bash completion +* Support daemon urls with base paths in `docker -H` +* Validate status= filter to docker ps +* Display when a container is in --net=host in docker ps +* Extend docker inspect to export image metadata related to graph driver +* Restore --default-gateway{,-v6} daemon options +* Add missing unpublished ports in docker ps +* Allow duration strings in `docker events` as --since/--until +* Expose more mounts information in `docker inspect` + +### Runtime + ++ Add new Fluentd logging driver ++ Allow `docker import` to load from local files ++ Add logging driver for GELF via UDP ++ Allow to copy files from host to containers with `docker cp` ++ Promote volume drivers from experimental to master ++ Add rollover log driver, and --log-driver-opts flag ++ Add memory swappiness tuning options +* Remove cgroup read-only flag when privileged +* Make /proc, /sys, & /dev readonly for readonly containers +* Add cgroup bind mount by default +* Overlay: Export metadata for container and image in `docker inspect` +* Devicemapper: external device activation +* Devicemapper: Compare uuid of base device on startup +* Remove RC4 from the list of registry cipher suites +* Add syslog-facility option +* LXC execdriver compatibility with recent LXC versions +* Mark LXC execriver as deprecated (to be removed with the migration to runc) + +### Plugins + +* Separate plugin sockets and specs locations +* Allow TLS connections to plugins + +### Bug fixes + +- Add missing 'Names' field to /containers/json API output +- Make `docker rmi --dangling` safe when pulling +- Devicemapper: Change default basesize to 100G +- Go Scheduler issue with sync.Mutex and gcc +- Fix issue where Search API endpoint would panic due to empty AuthConfig +- Set image canonical names correctly +- Check dockerinit only if lxc driver is used +- Fix ulimit usage of nproc +- Always attach STDIN if -i,--interactive is specified +- Show error messages when saving container state fails +- Fixed incorrect assumption on --bridge=none treated as disable network +- Check for invalid port specifications in host configuration +- Fix endpoint leave failure for --net=host mode +- Fix goroutine leak in the stats API if the container is not running +- Check for apparmor file before reading it +- Fix DOCKER_TLS_VERIFY being ignored +- Set umask to the default on startup +- Correct the message of pause and unpause a non-running container +- Adjust disallowed CpuShares in container creation +- ZFS: correctly apply selinux context +- Display empty string instead of when IP opt is nil +- `docker kill` returns error when container is not running +- Fix COPY/ADD quoted/json form +- Fix goroutine leak on logs -f with no output +- Remove panic in nat package on invalid hostport +- Fix container linking in Fedora 22 +- Fix error caused using default gateways outside of the allocated range +- Format times in inspect command with a template as RFC3339Nano +- Make registry client to accept 2xx and 3xx http status responses as successful +- Fix race issue that caused the daemon to crash with certain layer downloads failed in a specific order. +- Fix error when the docker ps format was not valid. +- Remove redundant ip forward check. +- Fix issue trying to push images to repository mirrors. +- Fix error cleaning up network entrypoints when there is an initialization issue. + ## 1.7.1 (2015-07-14) #### Runtime diff --git a/VERSION b/VERSION index 0ef074f2e..27f9cd322 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.0-dev +1.8.0