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/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/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/VERSION b/VERSION index 0ef074f2e..27f9cd322 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.0-dev +1.8.0 diff --git a/api/client/build.go b/api/client/build.go index 6dac4a3f3..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` @@ -302,6 +316,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 @@ -467,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() @@ -483,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{ @@ -509,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) @@ -531,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/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/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()) + } +} 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/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/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/api/types/types.go b/api/types/types.go index 0e7078bc7..329ee96ce 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 @@ -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" @@ -215,14 +214,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/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/contrib/apparmor/docker-engine b/contrib/apparmor/docker-engine index 07b5dd864..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, @@ -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 /sbin/iptables { - 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, + profile /bin/cat (complain) { + /etc/ld.so.cache r, + /lib/** r, + /dev/null rw, + /proc r, + /bin/cat mr, - @{DOCKER_GRAPH_PATH}/aufs/** 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, + # For reading in 'docker stats': + /proc/[0-9]*/net/dev r, } - profile /sbin/modprobe { - signal (receive) peer=/usr/bin/docker, - capability sys_module, - file, + profile /bin/ps (complain) { + /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 (complain) { + signal (receive) peer=/usr/bin/docker, + capability net_admin, + } + profile /sbin/auplink flags=(attach_disconnected, complain) { + signal (receive) peer=/usr/bin/docker, + capability sys_admin, + capability dac_override, + + @{DOCKER_GRAPH_PATH}/aufs/** rw, + @{DOCKER_GRAPH_PATH}/tmp/** rw, + # For user namespaces: + @{DOCKER_GRAPH_PATH}/[0-9]*.[0-9]*/** 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 /bin/kmod (complain) { + 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, + profile /usr/bin/xz (complain) { + 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, complain) { + /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, complain) { + file, + capability, } } 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/contrib/completion/bash/docker b/contrib/completion/bash/docker index 273d9b701..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() { @@ -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() { @@ -312,49 +316,24 @@ __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 + $global_boolean_options --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) + --config) _filedir -d return ;; - --log-driver) - __docker_log_drivers - return - ;; --log-level|-l) - COMPREPLY=( $( compgen -W "debug info warn error fatal" -- "$cur" ) ) + __docker_log_levels 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,10 +342,10 @@ _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)" + 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 @@ -478,6 +457,84 @@ _docker_create() { _docker_run } +_docker_daemon() { + local boolean_options=" + $global_boolean_options + --help -h + --icc=false + --ip-forward=false + --ip-masq=false + --iptables=false + --ipv6 + --selinux-enabled + --userland-proxy=false + " + local options_with_args=" + $global_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-level|-l) + __docker_log_levels + return + ;; + --log-opt) + __docker_log_driver_options + return + ;; + $(__docker_to_extglob "$options_with_args") ) + return + ;; + esac + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) ) + ;; + esac +} + _docker_diff() { case "$cur" in -*) @@ -685,8 +742,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 } @@ -1287,6 +1353,7 @@ _docker() { commit cp create + daemon diff events exec @@ -1323,41 +1390,23 @@ _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 + # 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=" + --config --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 + local host config COMPREPLY=() local cur prev words cword @@ -1372,7 +1421,12 @@ _docker() { (( counter++ )) host="${words[$counter]}" ;; - $main_options_with_args_glob ) + # save config so that completion can use custom configuration directories + --config) + (( counter++ )) + config="${words[$counter]}" + ;; + $(__docker_to_extglob "$global_options_with_args") ) (( counter++ )) ;; -*) 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 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/daemon/container.go b/daemon/container.go index 0b19034b0..49c1f41e3 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 @@ -1080,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", @@ -1090,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", @@ -1099,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", 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/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{} } 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/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/daemon/execdriver/native/apparmor.go b/daemon/execdriver/native/apparmor.go new file mode 100644 index 000000000..30d49b37b --- /dev/null +++ b/daemon/execdriver/native/apparmor.go @@ -0,0 +1,145 @@ +// +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}/kcore 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, +} +` + +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/create.go b/daemon/execdriver/native/create.go index 0f0a6a12d..bbc83fedb 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 { @@ -200,7 +200,6 @@ func (d *driver) setPrivileged(container *configs.Config) (err error) { if apparmor.IsEnabled() { container.AppArmorProfile = "unconfined" } - return nil } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index a94de3d18..c5d4d964c 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 { + 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 apparmorProfiles { + 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/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 } 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/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/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 } 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..99b4a3583 100644 --- a/daemon/logger/logger.go +++ b/daemon/logger/logger.go @@ -64,7 +64,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 diff --git a/docker/daemon.go b/docker/daemon.go index e11b98d8f..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) } }() @@ -226,7 +227,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/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/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/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 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/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/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. 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 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 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/fedora.md b/docs/installation/fedora.md index eb8176296..b1bdb19cb 100644 --- a/docs/installation/fedora.md +++ b/docs/installation/fedora.md @@ -206,6 +206,24 @@ 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 `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 +`/usr/lib/systemd/network/` on your Docker host (ex: `/usr/lib/systemd/network/80-container-host0.network`) add the following block: + +``` +[Network] +... +IPForward=kernel +# OR +IPForward=true +... +``` + +This configuration allows IP forwarding from the container as expected. ## Uninstall 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/ubuntulinux.md b/docs/installation/ubuntulinux.md index 7f2e1fa19..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. @@ -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. @@ -339,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/docs/installation/windows.md b/docs/installation/windows.md index 64ed93939..0737fa571 100644 --- a/docs/installation/windows.md +++ b/docs/installation/windows.md @@ -9,165 +9,357 @@ 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 -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 -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/). 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/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)** diff --git a/docs/misc/faq.md b/docs/misc/faq.md index 2108ddc16..38a54f1a6 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, @@ -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/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 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/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/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index 6ed93afb2..ba0dc3712 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!** @@ -297,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 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 49934708a..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 } @@ -1109,7 +1117,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 +1128,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/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/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 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/security/apparmor.md b/docs/security/apparmor.md new file mode 100644 index 000000000..1e82200b6 --- /dev/null +++ b/docs/security/apparmor.md @@ -0,0 +1,41 @@ +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 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/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 000000000..9a1f9062b Binary files /dev/null and b/docs/security/trust/images/tag_signing.png differ diff --git a/docs/security/trust/images/trust_.gliffy b/docs/security/trust/images/trust_.gliffy new file mode 100644 index 000000000..9298984bb --- /dev/null +++ b/docs/security/trust/images/trust_.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":1029,"height":814,"nodeIndex":315,"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":159,"y":120.286},"max":{"x":1029,"y":814}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":465.5822784810126,"y":531.0,"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":733.0,"y":578.0,"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":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 000000000..039dfc8cf Binary files /dev/null and b/docs/security/trust/images/trust_components.png differ 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 000000000..4a941be19 Binary files /dev/null and b/docs/security/trust/images/trust_signing.png differ 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 000000000..71eb26ce3 Binary files /dev/null and b/docs/security/trust/images/trust_view.png differ diff --git a/docs/security/trust/index.md b/docs/security/trust/index.md new file mode 100644 index 000000000..2264c5dfe --- /dev/null +++ b/docs/security/trust/index.md @@ -0,0 +1,21 @@ + + +# 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 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 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 diff --git a/experimental/README.md b/experimental/README.md index 05ef03edf..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. @@ -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) 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/pull_v2.go b/graph/pull_v2.go index a70529754..c4b18e7c4 100644 --- a/graph/pull_v2.go +++ b/graph/pull_v2.go @@ -1,6 +1,7 @@ package graph import ( + "errors" "fmt" "io" "io/ioutil" @@ -102,13 +103,13 @@ 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 + out io.Writer // Download progress is written here. } type errVerification struct{} @@ -118,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 { @@ -176,9 +177,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)) @@ -190,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 @@ -203,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 } @@ -211,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)) @@ -241,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]) } @@ -252,7 +277,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 +392,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 +435,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/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/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/hack/install.sh b/hack/install.sh index e3820503e..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) @@ -119,41 +145,79 @@ 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")" 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 fi if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then lsb_dist='fedora' - dist_version="$(rpm -qa \*-release | cut -d"-" -f3 | head -n1)" + 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 -qa \*-release | cut -d"-" -f3 | head -n1)" 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 + + # Check if this is a forked Linux distro + check_forked + + # Run setup for each distro accordingly case "$lsb_dist" in amzn) ( @@ -237,8 +301,8 @@ do_install() { exit 0 ;; - fedora|centos) - cat >/etc/yum.repos.d/docker-${repo}.repo <<-EOF + fedora|centos|oraclelinux) + $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} 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 1d830232f..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_apparmor --profile-name=docker -pdocker-engine - 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/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" diff --git a/hack/make/ubuntu b/hack/make/ubuntu index 76c3f2905..0421dc367 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -72,11 +72,6 @@ bundle_ubuntu() { done done - # 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/" - # Copy the binary # This will fail if the binary bundle hasn't been built mkdir -p "$DIR/usr/bin" @@ -94,11 +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 - /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 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 diff --git a/hack/vendor.sh b/hack/vendor.sh index 27c1a73d3..912419141 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 bd3eecc96f3c05a4acef1bedcf74397bc6850d22 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4 @@ -35,11 +35,11 @@ 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 7dc8d4a26b689bd4892f2f2322dbce0b7119d686 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 a592b03b28b02bb29bb5878308fb1abed63383b5 clone git github.com/tent/canonical-json-go 96e4ba3a7613a1216cbd1badca4efe382adea337 clone git github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index b0e9b0eed..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" @@ -1687,3 +1689,45 @@ 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) +} + +// 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_build_test.go b/integration-cli/docker_cli_build_test.go index 72f796ed6..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) { @@ -5373,3 +5380,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) + } +} diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index cbc6dc1ba..71f8b1a83 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 non-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/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/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_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 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_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) +} 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) +} 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 ee9570a27..ed4d24a85 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) @@ -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 4a2545892..92889574a 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) } } @@ -2397,7 +2397,10 @@ 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 { + if code != 0 { + return + } + if err != nil { c.Fatal(err) } if strings.Trim(out, "\n ") != "" { @@ -2414,7 +2417,10 @@ func (s *DockerSuite) TestRunReadProcLatency(c *check.C) { return } out, code, err := dockerCmdWithError(c, "run", "busybox", "cat", "/proc/latency_stats") - if err != nil || code != 0 { + if code != 0 { + return + } + if err != nil { c.Fatal(err) } if strings.Trim(out, "\n ") != "" { @@ -2422,6 +2428,28 @@ 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) + + 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) + } + } +} + func (s *DockerSuite) TestMountIntoProc(c *check.C) { testRequires(c, NativeExecDriver) _, code, err := dockerCmdWithError(c, "run", "-v", "/proc//sys", "busybox", "true") @@ -2515,13 +2543,17 @@ 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) 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) } } @@ -2600,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 @@ -2704,3 +2737,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(c, "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(c, "run", "busybox", "readlink", "/proc/1/ns/net") + if exitCode != 0 { + c.Fatal("ptrace of self failed.") + } +} 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()) + } +} 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...) } 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)) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 04e40a94f..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 { @@ -633,8 +639,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 +662,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/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) } 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 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 { 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 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..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" @@ -49,6 +50,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 { @@ -194,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 diff --git a/registry/service.go b/registry/service.go index fa35e3132..8d301b4fa 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) { @@ -125,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{ 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(), 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/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/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 ee895b722..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 @@ -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, + }, + }, + }, }, }, }, @@ -580,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/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") + } +} 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 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..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 @@ -8,7 +8,6 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/libnetwork/netutils" - "github.com/docker/libnetwork/types" "github.com/vishvananda/netlink" ) @@ -32,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}) } } @@ -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/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) } } 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 } 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/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. 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..9cb910717 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 @@ -84,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 @@ -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 {