Browse code

Merge configs/secrets in unix implementation

On unix, merge secrets/configs handling. This is important because
configs can contain secrets (via templating) and potentially a config
could just simply have secret information "by accident" from the user.
This just make sure that configs are as secure as secrets and de-dups a
lot of code.
Generally this makes everything simpler and configs more secure.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2018/01/18 00:49:58
Showing 10 changed files
... ...
@@ -68,13 +68,6 @@ type ExitStatus struct {
68 68
 	ExitedAt time.Time
69 69
 }
70 70
 
71
-// ConfigReference wraps swarmtypes.ConfigReference to add a Sensitive flag.
72
-type ConfigReference struct {
73
-	*swarmtypes.ConfigReference
74
-	// Sensitive is set if this config should not be written to disk.
75
-	Sensitive bool
76
-}
77
-
78 71
 // Container holds the structure defining a container object.
79 72
 type Container struct {
80 73
 	StreamConfig *stream.Config
... ...
@@ -106,7 +99,7 @@ type Container struct {
106 106
 	ExecCommands           *exec.Store                `json:"-"`
107 107
 	DependencyStore        agentexec.DependencyGetter `json:"-"`
108 108
 	SecretReferences       []*swarmtypes.SecretReference
109
-	ConfigReferences       []*ConfigReference
109
+	ConfigReferences       []*swarmtypes.ConfigReference
110 110
 	// logDriver for closing
111 111
 	LogDriver      logger.Logger  `json:"-"`
112 112
 	LogCopier      *logger.Copier `json:"-"`
... ...
@@ -1056,31 +1049,6 @@ func getSecretTargetPath(r *swarmtypes.SecretReference) string {
1056 1056
 	return filepath.Join(containerSecretMountPath, r.File.Name)
1057 1057
 }
1058 1058
 
1059
-// ConfigsDirPath returns the path to the directory where configs are stored on
1060
-// disk.
1061
-func (container *Container) ConfigsDirPath() (string, error) {
1062
-	return container.GetRootResourcePath("configs")
1063
-}
1064
-
1065
-// ConfigFilePath returns the path to the on-disk location of a config.
1066
-func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference) (string, error) {
1067
-	configs, err := container.ConfigsDirPath()
1068
-	if err != nil {
1069
-		return "", err
1070
-	}
1071
-	return filepath.Join(configs, configRef.ConfigID), nil
1072
-}
1073
-
1074
-// SensitiveConfigFilePath returns the path to the location of a config mounted
1075
-// as a secret.
1076
-func (container *Container) SensitiveConfigFilePath(configRef swarmtypes.ConfigReference) (string, error) {
1077
-	secretMountPath, err := container.SecretMountPath()
1078
-	if err != nil {
1079
-		return "", err
1080
-	}
1081
-	return filepath.Join(secretMountPath, configRef.ConfigID+"c"), nil
1082
-}
1083
-
1084 1059
 // CreateDaemonEnvironment creates a new environment variable slice for this container.
1085 1060
 func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string) []string {
1086 1061
 	// Setup environment
... ...
@@ -5,11 +5,13 @@ package container // import "github.com/docker/docker/container"
5 5
 import (
6 6
 	"io/ioutil"
7 7
 	"os"
8
+	"path/filepath"
8 9
 
9 10
 	"github.com/containerd/continuity/fs"
10 11
 	"github.com/docker/docker/api/types"
11 12
 	containertypes "github.com/docker/docker/api/types/container"
12 13
 	mounttypes "github.com/docker/docker/api/types/mount"
14
+	swarmtypes "github.com/docker/docker/api/types/swarm"
13 15
 	"github.com/docker/docker/pkg/mount"
14 16
 	"github.com/docker/docker/pkg/stringid"
15 17
 	"github.com/docker/docker/volume"
... ...
@@ -234,10 +236,7 @@ func (container *Container) SecretMounts() ([]Mount, error) {
234 234
 		})
235 235
 	}
236 236
 	for _, r := range container.ConfigReferences {
237
-		if !r.Sensitive || r.File == nil {
238
-			continue
239
-		}
240
-		fPath, err := container.SensitiveConfigFilePath(*r.ConfigReference)
237
+		fPath, err := container.ConfigFilePath(*r)
241 238
 		if err != nil {
242 239
 			return nil, err
243 240
 		}
... ...
@@ -267,27 +266,6 @@ func (container *Container) UnmountSecrets() error {
267 267
 	return mount.RecursiveUnmount(p)
268 268
 }
269 269
 
270
-// ConfigMounts returns the mounts for configs.
271
-func (container *Container) ConfigMounts() ([]Mount, error) {
272
-	var mounts []Mount
273
-	for _, configRef := range container.ConfigReferences {
274
-		if configRef.Sensitive || configRef.File == nil {
275
-			continue
276
-		}
277
-		src, err := container.ConfigFilePath(*configRef.ConfigReference)
278
-		if err != nil {
279
-			return nil, err
280
-		}
281
-		mounts = append(mounts, Mount{
282
-			Source:      src,
283
-			Destination: configRef.File.Name,
284
-			Writable:    false,
285
-		})
286
-	}
287
-
288
-	return mounts, nil
289
-}
290
-
291 270
 type conflictingUpdateOptions string
292 271
 
293 272
 func (e conflictingUpdateOptions) Error() string {
... ...
@@ -471,3 +449,13 @@ func (container *Container) GetMountPoints() []types.MountPoint {
471 471
 	}
472 472
 	return mountPoints
473 473
 }
474
+
475
+// ConfigFilePath returns the path to the on-disk location of a config.
476
+// On unix, configs are always considered secret
477
+func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference) (string, error) {
478
+	mounts, err := container.SecretMountPath()
479
+	if err != nil {
480
+		return "", err
481
+	}
482
+	return filepath.Join(mounts, configRef.ConfigID), nil
483
+}
... ...
@@ -7,6 +7,7 @@ import (
7 7
 
8 8
 	"github.com/docker/docker/api/types"
9 9
 	containertypes "github.com/docker/docker/api/types/container"
10
+	swarmtypes "github.com/docker/docker/api/types/swarm"
10 11
 	"github.com/docker/docker/pkg/system"
11 12
 )
12 13
 
... ...
@@ -102,23 +103,20 @@ func (container *Container) CreateConfigSymlinks() error {
102 102
 }
103 103
 
104 104
 // ConfigMounts returns the mount for configs.
105
-// All configs are stored in a single mount on Windows. Target symlinks are
106
-// created for each config, pointing to the files in this mount.
107
-func (container *Container) ConfigMounts() ([]Mount, error) {
105
+// TODO: Right now Windows doesn't really have a "secure" storage for secrets,
106
+// however some configs may contain secrets. Once secure storage is worked out,
107
+// configs and secret handling should be merged.
108
+func (container *Container) ConfigMounts() []Mount {
108 109
 	var mounts []Mount
109 110
 	if len(container.ConfigReferences) > 0 {
110
-		src, err := container.ConfigsDirPath()
111
-		if err != nil {
112
-			return nil, err
113
-		}
114 111
 		mounts = append(mounts, Mount{
115
-			Source:      src,
112
+			Source:      container.ConfigsDirPath(),
116 113
 			Destination: containerInternalConfigsDirPath,
117 114
 			Writable:    false,
118 115
 		})
119 116
 	}
120 117
 
121
-	return mounts, nil
118
+	return mounts
122 119
 }
123 120
 
124 121
 // DetachAndUnmount unmounts all volumes.
... ...
@@ -204,3 +202,12 @@ func (container *Container) GetMountPoints() []types.MountPoint {
204 204
 	}
205 205
 	return mountPoints
206 206
 }
207
+
208
+func (container *Container) ConfigsDirPath() string {
209
+	return filepath.Join(container.Root, "configs")
210
+}
211
+
212
+// ConfigFilePath returns the path to the on-disk location of a config.
213
+func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference) string {
214
+	return filepath.Join(container.ConfigsDirPath(), configRef.ConfigID)
215
+}
... ...
@@ -2,7 +2,6 @@ package daemon // import "github.com/docker/docker/daemon"
2 2
 
3 3
 import (
4 4
 	swarmtypes "github.com/docker/docker/api/types/swarm"
5
-	"github.com/docker/docker/container"
6 5
 	"github.com/sirupsen/logrus"
7 6
 )
8 7
 
... ...
@@ -17,10 +16,6 @@ func (daemon *Daemon) SetContainerConfigReferences(name string, refs []*swarmtyp
17 17
 	if err != nil {
18 18
 		return err
19 19
 	}
20
-
21
-	for _, ref := range refs {
22
-		c.ConfigReferences = append(c.ConfigReferences, &container.ConfigReference{ConfigReference: ref})
23
-	}
24
-
20
+	c.ConfigReferences = append(c.ConfigReferences, refs...)
25 21
 	return nil
26 22
 }
... ...
@@ -161,20 +161,16 @@ func (daemon *Daemon) setupIpcDirs(c *container.Container) error {
161 161
 }
162 162
 
163 163
 func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
164
-	if len(c.SecretReferences) == 0 {
164
+	if len(c.SecretReferences) == 0 && len(c.ConfigReferences) == 0 {
165 165
 		return nil
166 166
 	}
167 167
 
168
-	localMountPath, err := c.SecretMountPath()
169
-	if err != nil {
170
-		return errors.Wrap(err, "error getting secrets mount path for container")
171
-	}
172
-	if err := daemon.createSecretsDir(localMountPath); err != nil {
168
+	if err := daemon.createSecretsDir(c); err != nil {
173 169
 		return err
174 170
 	}
175 171
 	defer func() {
176 172
 		if setupErr != nil {
177
-			daemon.cleanupSecretDir(localMountPath)
173
+			daemon.cleanupSecretDir(c)
178 174
 		}
179 175
 	}()
180 176
 
... ...
@@ -231,88 +227,16 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
231 231
 		}
232 232
 	}
233 233
 
234
-	return daemon.remountSecretDir(c.MountLabel, localMountPath)
235
-}
236
-
237
-// createSecretsDir is used to create a dir suitable for storing container secrets.
238
-// In practice this is using a tmpfs mount and is used for both "configs" and "secrets"
239
-func (daemon *Daemon) createSecretsDir(dir string) error {
240
-	// retrieve possible remapped range start for root UID, GID
241
-	rootIDs := daemon.idMappings.RootPair()
242
-	// create tmpfs
243
-	if err := idtools.MkdirAllAndChown(dir, 0700, rootIDs); err != nil {
244
-		return errors.Wrap(err, "error creating secret local mount path")
245
-	}
246
-
247
-	tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID)
248
-	if err := mount.Mount("tmpfs", dir, "tmpfs", "nodev,nosuid,noexec,"+tmpfsOwnership); err != nil {
249
-		return errors.Wrap(err, "unable to setup secret mount")
250
-	}
251
-
252
-	return nil
253
-}
254
-
255
-func (daemon *Daemon) remountSecretDir(mountLabel, dir string) error {
256
-	if err := label.Relabel(dir, mountLabel, false); err != nil {
257
-		logrus.WithError(err).WithField("dir", dir).Warn("Error while attempting to set selinux label")
258
-	}
259
-	rootIDs := daemon.idMappings.RootPair()
260
-	tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID)
261
-
262
-	// remount secrets ro
263
-	if err := mount.Mount("tmpfs", dir, "tmpfs", "remount,ro,"+tmpfsOwnership); err != nil {
264
-		return errors.Wrap(err, "unable to remount dir as readonly")
265
-	}
266
-
267
-	return nil
268
-}
269
-
270
-func (daemon *Daemon) cleanupSecretDir(dir string) {
271
-	if err := mount.RecursiveUnmount(dir); err != nil {
272
-		logrus.WithField("dir", dir).WithError(err).Warn("Error while attmepting to unmount dir, this may prevent removal of container.")
273
-	}
274
-	if err := os.RemoveAll(dir); err != nil && !os.IsNotExist(err) {
275
-		logrus.WithField("dir", dir).WithError(err).Error("Error removing dir.")
276
-	}
277
-}
278
-
279
-func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
280
-	if len(c.ConfigReferences) == 0 {
281
-		return nil
282
-	}
283
-
284
-	localPath, err := c.ConfigsDirPath()
285
-	if err != nil {
286
-		return err
287
-	}
288
-	logrus.Debugf("configs: setting up config dir: %s", localPath)
289
-	if err := daemon.createSecretsDir(localPath); err != nil {
290
-		return err
291
-	}
292
-	defer func() {
293
-		if setupErr != nil {
294
-			daemon.cleanupSecretDir(localPath)
295
-		}
296
-	}()
297
-
298
-	if c.DependencyStore == nil {
299
-		return errors.New("config store is not initialized")
300
-	}
301
-
302
-	// retrieve possible remapped range start for root UID, GID
303
-	rootIDs := daemon.idMappings.RootPair()
304
-
305 234
 	for _, ref := range c.ConfigReferences {
306 235
 		// TODO (ehazlett): use type switch when more are supported
307 236
 		if ref.File == nil {
308 237
 			logrus.Error("config target type is not a file target")
309 238
 			continue
310 239
 		}
311
-		// configs are created in the ConfigsDirPath on the host, at a
312
-		// single level
313
-		fPath, err := c.ConfigFilePath(*ref.ConfigReference)
240
+
241
+		fPath, err := c.ConfigFilePath(*ref)
314 242
 		if err != nil {
315
-			return err
243
+			return errors.Wrap(err, "error getting config file path for container")
316 244
 		}
317 245
 		if err := idtools.MkdirAllAndChown(filepath.Dir(fPath), 0700, rootIDs); err != nil {
318 246
 			return errors.Wrap(err, "error creating config mount path")
... ...
@@ -342,14 +266,67 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
342 342
 		if err := os.Chown(fPath, rootIDs.UID+uid, rootIDs.GID+gid); err != nil {
343 343
 			return errors.Wrap(err, "error setting ownership for config")
344 344
 		}
345
-		if err := os.Chmod(fPath, configRef.File.Mode); err != nil {
345
+		if err := os.Chmod(fPath, ref.File.Mode); err != nil {
346 346
 			return errors.Wrap(err, "error setting file mode for config")
347 347
 		}
348
+	}
349
+
350
+	return daemon.remountSecretDir(c)
351
+}
352
+
353
+// createSecretsDir is used to create a dir suitable for storing container secrets.
354
+// In practice this is using a tmpfs mount and is used for both "configs" and "secrets"
355
+func (daemon *Daemon) createSecretsDir(c *container.Container) error {
356
+	// retrieve possible remapped range start for root UID, GID
357
+	rootIDs := daemon.idMappings.RootPair()
358
+	dir, err := c.SecretMountPath()
359
+	if err != nil {
360
+		return errors.Wrap(err, "error getting container secrets dir")
361
+	}
362
+
363
+	// create tmpfs
364
+	if err := idtools.MkdirAllAndChown(dir, 0700, rootIDs); err != nil {
365
+		return errors.Wrap(err, "error creating secret local mount path")
366
+	}
367
+
368
+	tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID)
369
+	if err := mount.Mount("tmpfs", dir, "tmpfs", "nodev,nosuid,noexec,"+tmpfsOwnership); err != nil {
370
+		return errors.Wrap(err, "unable to setup secret mount")
371
+	}
348 372
 
349
-		label.Relabel(fPath, c.MountLabel, false)
373
+	return nil
374
+}
375
+
376
+func (daemon *Daemon) remountSecretDir(c *container.Container) error {
377
+	dir, err := c.SecretMountPath()
378
+	if err != nil {
379
+		return errors.Wrap(err, "error getting container secrets path")
350 380
 	}
381
+	if err := label.Relabel(dir, c.MountLabel, false); err != nil {
382
+		logrus.WithError(err).WithField("dir", dir).Warn("Error while attempting to set selinux label")
383
+	}
384
+	rootIDs := daemon.idMappings.RootPair()
385
+	tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID)
351 386
 
352
-	return daemon.remountSecretDir(c.MountLabel, localPath)
387
+	// remount secrets ro
388
+	if err := mount.Mount("tmpfs", dir, "tmpfs", "remount,ro,"+tmpfsOwnership); err != nil {
389
+		return errors.Wrap(err, "unable to remount dir as readonly")
390
+	}
391
+
392
+	return nil
393
+}
394
+
395
+func (daemon *Daemon) cleanupSecretDir(c *container.Container) {
396
+	dir, err := c.SecretMountPath()
397
+	if err != nil {
398
+		logrus.WithError(err).WithField("container", c.ID).Warn("error getting secrets mount path for container")
399
+	}
400
+	if err := mount.RecursiveUnmount(dir); err != nil {
401
+		logrus.WithField("dir", dir).WithError(err).Warn("Error while attmepting to unmount dir, this may prevent removal of container.")
402
+	}
403
+	if err := os.RemoveAll(dir); err != nil && !os.IsNotExist(err) {
404
+		logrus.WithField("dir", dir).WithError(err).Error("Error removing dir.")
405
+	}
353 406
 }
354 407
 
355 408
 func killProcessDirectly(cntr *container.Container) error {
... ...
@@ -21,10 +21,7 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
21 21
 		return nil
22 22
 	}
23 23
 
24
-	localPath, err := c.ConfigsDirPath()
25
-	if err != nil {
26
-		return err
27
-	}
24
+	localPath := c.ConfigsDirPath()
28 25
 	logrus.Debugf("configs: setting up config dir: %s", localPath)
29 26
 
30 27
 	// create local config root
... ...
@@ -51,11 +48,7 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
51 51
 			continue
52 52
 		}
53 53
 
54
-		fPath, err := c.ConfigFilePath(*configRef.ConfigReference)
55
-		if err != nil {
56
-			return err
57
-		}
58
-
54
+		fPath := c.ConfigFilePath(*configRef)
59 55
 		log := logrus.WithFields(logrus.Fields{"name": configRef.File.Name, "path": fPath})
60 56
 
61 57
 		log.Debug("injecting config")
... ...
@@ -842,18 +842,9 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e
842 842
 		return nil, err
843 843
 	}
844 844
 
845
-	secretMountPath, err := c.SecretMountPath()
846
-	if err != nil {
847
-		return nil, err
848
-	}
849
-	configsMountPath, err := c.ConfigsDirPath()
850
-	if err != nil {
851
-		return nil, err
852
-	}
853 845
 	defer func() {
854 846
 		if err != nil {
855
-			daemon.cleanupSecretDir(secretMountPath)
856
-			daemon.cleanupSecretDir(configsMountPath)
847
+			daemon.cleanupSecretDir(c)
857 848
 		}
858 849
 	}()
859 850
 
... ...
@@ -861,10 +852,6 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e
861 861
 		return nil, err
862 862
 	}
863 863
 
864
-	if err := daemon.setupConfigDir(c); err != nil {
865
-		return nil, err
866
-	}
867
-
868 864
 	ms, err := daemon.setupMounts(c)
869 865
 	if err != nil {
870 866
 		return nil, err
... ...
@@ -886,12 +873,6 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e
886 886
 	}
887 887
 	ms = append(ms, secretMounts...)
888 888
 
889
-	configMounts, err := c.ConfigMounts()
890
-	if err != nil {
891
-		return nil, err
892
-	}
893
-	ms = append(ms, configMounts...)
894
-
895 889
 	sort.Sort(mounts(ms))
896 890
 	if err := setMounts(daemon, &s, c, ms); err != nil {
897 891
 		return nil, fmt.Errorf("linux mounts: %v", err)
... ...
@@ -102,10 +102,7 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
102 102
 		mounts = append(mounts, secretMounts...)
103 103
 	}
104 104
 
105
-	configMounts, err := c.ConfigMounts()
106
-	if err != nil {
107
-		return nil, err
108
-	}
105
+	configMounts := c.ConfigMounts()
109 106
 	if configMounts != nil {
110 107
 		mounts = append(mounts, configMounts...)
111 108
 	}
... ...
@@ -292,15 +292,24 @@ func TestTemplatedConfig(t *testing.T) {
292 292
 		AttachStderr: true,
293 293
 	})
294 294
 
295
-	buf := bytes.NewBuffer(nil)
296
-	_, err = stdcopy.StdCopy(buf, buf, attach.Reader)
297
-	require.NoError(t, err)
298
-
299 295
 	expect := "SERVICE_NAME=svc\n" +
300 296
 		"this is a secret\n" +
301 297
 		"this is a config\n"
298
+	assertAttachedStream(t, attach, expect)
299
+
300
+	attach = swarm.ExecTask(t, d, task, types.ExecConfig{
301
+		Cmd:          []string{"mount"},
302
+		AttachStdout: true,
303
+		AttachStderr: true,
304
+	})
305
+	assertAttachedStream(t, attach, "tmpfs on /templated_config type tmpfs")
306
+}
302 307
 
303
-	assert.Equal(t, expect, buf.String())
308
+func assertAttachedStream(t *testing.T, attach types.HijackedResponse, expect string) {
309
+	buf := bytes.NewBuffer(nil)
310
+	_, err := stdcopy.StdCopy(buf, buf, attach.Reader)
311
+	require.NoError(t, err)
312
+	assert.Contains(t, buf.String(), expect)
304 313
 }
305 314
 
306 315
 func waitAndAssert(t *testing.T, timeout time.Duration, f func(*testing.T) bool) {
... ...
@@ -336,15 +336,24 @@ func TestTemplatedSecret(t *testing.T) {
336 336
 		AttachStderr: true,
337 337
 	})
338 338
 
339
-	buf := bytes.NewBuffer(nil)
340
-	_, err = stdcopy.StdCopy(buf, buf, attach.Reader)
341
-	require.NoError(t, err)
342
-
343 339
 	expect := "SERVICE_NAME=svc\n" +
344 340
 		"this is a secret\n" +
345 341
 		"this is a config\n"
342
+	assertAttachedStream(t, attach, expect)
343
+
344
+	attach = swarm.ExecTask(t, d, task, types.ExecConfig{
345
+		Cmd:          []string{"mount"},
346
+		AttachStdout: true,
347
+		AttachStderr: true,
348
+	})
349
+	assertAttachedStream(t, attach, "tmpfs on /run/secrets/templated_secret type tmpfs")
350
+}
346 351
 
347
-	assert.Equal(t, expect, buf.String())
352
+func assertAttachedStream(t *testing.T, attach types.HijackedResponse, expect string) {
353
+	buf := bytes.NewBuffer(nil)
354
+	_, err := stdcopy.StdCopy(buf, buf, attach.Reader)
355
+	require.NoError(t, err)
356
+	assert.Contains(t, buf.String(), expect)
348 357
 }
349 358
 
350 359
 func waitAndAssert(t *testing.T, timeout time.Duration, f func(*testing.T) bool) {