From 13edc7f4f17afa1ae25cd7506c06bbc84de2ce50 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 17 Feb 2021 13:26:25 +0100 Subject: [PATCH] Strip reserved (com.docker. io.docker, org.dockerproject) labels on docker commit Docker uses reserved label namespaces on containers to store runtime information. For example, when creating a service (`docker service create`), deploying a stack (`docker stack deploy`), or running a compose project (`docker-compose up`), docker respectively adds `com.docker.swarm`, `com.docker.stack`, and `com.docker.compose` labels to store metadata used at runtime. These labels are not set by users, but when commiting such a container, they currently end up in the image that was committed. This patch updates `CreateImageFromContainer` to remove labels in the reserved namespace. Some remarks should be made to this change: - This patch only accounts for `docker commit`; `docker build` still allows committing labels in the reserved namespaces - Because of the above, committing a container that was started from an image that has labels in the reserved namespaces, will strip these labels, and thus remove the labels from the image that is created. - Other actions (`docker run`, `docker create`) still allow these labels to be set, and also inherit these labels if they're started from an image that has labels in the reserved namespaces. Signed-off-by: Sebastiaan van Stijn --- daemon/commit.go | 12 ++++++++++-- daemon/create.go | 2 +- daemon/daemon_test.go | 17 +++++++++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/daemon/commit.go b/daemon/commit.go index 302e9a95d6..d7bbec1e53 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -10,15 +10,20 @@ import ( containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder/dockerfile" "github.com/docker/docker/errdefs" + "github.com/docker/docker/opts" "github.com/pkg/errors" ) +type mergeOptions struct { + keepReservedLabels bool +} + // merge merges two Config, the image container configuration (defaults values), // and the user container configuration, either passed by the API or generated // by the cli. // It will mutate the specified user configuration (userConf) with the image // configuration where the user configuration is incomplete. -func merge(userConf, imageConf *containertypes.Config) error { +func merge(userConf, imageConf *containertypes.Config, options mergeOptions) error { if userConf.User == "" { userConf.User = imageConf.User } @@ -60,6 +65,9 @@ func merge(userConf, imageConf *containertypes.Config) error { userConf.Labels = map[string]string{} } for l, v := range imageConf.Labels { + if !options.keepReservedLabels && opts.IsReservedLabelNamespace(l) { + continue + } if _, ok := userConf.Labels[l]; !ok { userConf.Labels[l] = v } @@ -150,7 +158,7 @@ func (daemon *Daemon) CreateImageFromContainer(name string, c *backend.CreateIma if err != nil { return "", err } - if err := merge(newConfig, container.Config); err != nil { + if err := merge(newConfig, container.Config, mergeOptions{}); err != nil { return "", err } diff --git a/daemon/create.go b/daemon/create.go index 1d77d237eb..b2a1291380 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -293,7 +293,7 @@ func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig) func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *image.Image) error { if img != nil && img.Config != nil { - if err := merge(config, img.Config); err != nil { + if err := merge(config, img.Config, mergeOptions{keepReservedLabels: true}); err != nil { return err } } diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go index c29b2705ba..5b394bf21f 100644 --- a/daemon/daemon_test.go +++ b/daemon/daemon_test.go @@ -237,6 +237,7 @@ func TestMerge(t *testing.T) { ExposedPorts: portsImage, Env: []string{"VAR1=1", "VAR2=2"}, Volumes: volumesImage, + Labels: map[string]string{"com.docker.foo": "bar", "my-label": "my-value"}, } portsUser := make(nat.PortSet) @@ -250,7 +251,7 @@ func TestMerge(t *testing.T) { Volumes: volumesUser, } - if err := merge(configUser, configImage); err != nil { + if err := merge(configUser, configImage, mergeOptions{keepReservedLabels: true}); err != nil { t.Error(err) } @@ -284,22 +285,30 @@ func TestMerge(t *testing.T) { if err != nil { t.Error(err) } + + assert.DeepEqual(t, configUser.Labels, configImage.Labels) + configImage2 := &containertypes.Config{ ExposedPorts: ports, + Labels: map[string]string{"com.docker.foo": "bar", "my-label": "my-value"}, + } + configUser2 := &containertypes.Config{ + ExposedPorts: portsUser, } - if err := merge(configUser, configImage2); err != nil { + if err := merge(configUser2, configImage2, mergeOptions{}); err != nil { t.Error(err) } - if len(configUser.ExposedPorts) != 4 { + if len(configUser2.ExposedPorts) != 4 { t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts)) } - for portSpecs := range configUser.ExposedPorts { + for portSpecs := range configUser2.ExposedPorts { if portSpecs.Port() != "0" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" { t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs) } } + assert.DeepEqual(t, configUser2.Labels, map[string]string{"my-label": "my-value"}) } func TestValidateContainerIsolation(t *testing.T) {