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 <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2021-02-17 13:26:25 +01:00
parent 693c2b0c43
commit 13edc7f4f1
No known key found for this signature in database
GPG key ID: 76698F39D527CE8C
3 changed files with 24 additions and 7 deletions

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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) {