1package daemon // import "github.com/docker/docker/daemon"
2
3import (
4	"fmt"
5	"net"
6	"runtime"
7	"strings"
8	"time"
9
10	"github.com/docker/docker/api/types"
11	containertypes "github.com/docker/docker/api/types/container"
12	networktypes "github.com/docker/docker/api/types/network"
13	"github.com/docker/docker/container"
14	"github.com/docker/docker/errdefs"
15	"github.com/docker/docker/image"
16	"github.com/docker/docker/pkg/idtools"
17	"github.com/docker/docker/pkg/system"
18	"github.com/docker/docker/runconfig"
19	"github.com/opencontainers/selinux/go-selinux/label"
20	"github.com/pkg/errors"
21	"github.com/sirupsen/logrus"
22)
23
24// CreateManagedContainer creates a container that is managed by a Service
25func (daemon *Daemon) CreateManagedContainer(params types.ContainerCreateConfig) (containertypes.ContainerCreateCreatedBody, error) {
26	return daemon.containerCreate(params, true)
27}
28
29// ContainerCreate creates a regular container
30func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (containertypes.ContainerCreateCreatedBody, error) {
31	return daemon.containerCreate(params, false)
32}
33
34func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, managed bool) (containertypes.ContainerCreateCreatedBody, error) {
35	start := time.Now()
36	if params.Config == nil {
37		return containertypes.ContainerCreateCreatedBody{}, errdefs.InvalidParameter(errors.New("Config cannot be empty in order to create a container"))
38	}
39
40	os := runtime.GOOS
41	if params.Config.Image != "" {
42		img, err := daemon.imageService.GetImage(params.Config.Image)
43		if err == nil {
44			os = img.OS
45		}
46	} else {
47		// This mean scratch. On Windows, we can safely assume that this is a linux
48		// container. On other platforms, it's the host OS (which it already is)
49		if runtime.GOOS == "windows" && system.LCOWSupported() {
50			os = "linux"
51		}
52	}
53
54	warnings, err := daemon.verifyContainerSettings(os, params.HostConfig, params.Config, false)
55	if err != nil {
56		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err)
57	}
58
59	err = verifyNetworkingConfig(params.NetworkingConfig)
60	if err != nil {
61		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err)
62	}
63
64	if params.HostConfig == nil {
65		params.HostConfig = &containertypes.HostConfig{}
66	}
67	err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares)
68	if err != nil {
69		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err)
70	}
71
72	container, err := daemon.create(params, managed)
73	if err != nil {
74		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
75	}
76	containerActions.WithValues("create").UpdateSince(start)
77
78	return containertypes.ContainerCreateCreatedBody{ID: container.ID, Warnings: warnings}, nil
79}
80
81// Create creates a new container from the given configuration with a given name.
82func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (retC *container.Container, retErr error) {
83	var (
84		container *container.Container
85		img       *image.Image
86		imgID     image.ID
87		err       error
88	)
89
90	os := runtime.GOOS
91	if params.Config.Image != "" {
92		img, err = daemon.imageService.GetImage(params.Config.Image)
93		if err != nil {
94			return nil, err
95		}
96		if img.OS != "" {
97			os = img.OS
98		} else {
99			// default to the host OS except on Windows with LCOW
100			if runtime.GOOS == "windows" && system.LCOWSupported() {
101				os = "linux"
102			}
103		}
104		imgID = img.ID()
105
106		if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() {
107			return nil, errors.New("operating system on which parent image was created is not Windows")
108		}
109	} else {
110		if runtime.GOOS == "windows" {
111			os = "linux" // 'scratch' case.
112		}
113	}
114
115	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
116		return nil, errdefs.InvalidParameter(err)
117	}
118
119	if err := daemon.mergeAndVerifyLogConfig(&params.HostConfig.LogConfig); err != nil {
120		return nil, errdefs.InvalidParameter(err)
121	}
122
123	if container, err = daemon.newContainer(params.Name, os, params.Config, params.HostConfig, imgID, managed); err != nil {
124		return nil, err
125	}
126	defer func() {
127		if retErr != nil {
128			if err := daemon.cleanupContainer(container, true, true); err != nil {
129				logrus.Errorf("failed to cleanup container on create error: %v", err)
130			}
131		}
132	}()
133
134	if err := daemon.setSecurityOptions(container, params.HostConfig); err != nil {
135		return nil, err
136	}
137
138	container.HostConfig.StorageOpt = params.HostConfig.StorageOpt
139
140	// Fixes: https://github.com/moby/moby/issues/34074 and
141	// https://github.com/docker/for-win/issues/999.
142	// Merge the daemon's storage options if they aren't already present. We only
143	// do this on Windows as there's no effective sandbox size limit other than
144	// physical on Linux.
145	if runtime.GOOS == "windows" {
146		if container.HostConfig.StorageOpt == nil {
147			container.HostConfig.StorageOpt = make(map[string]string)
148		}
149		for _, v := range daemon.configStore.GraphOptions {
150			opt := strings.SplitN(v, "=", 2)
151			if _, ok := container.HostConfig.StorageOpt[opt[0]]; !ok {
152				container.HostConfig.StorageOpt[opt[0]] = opt[1]
153			}
154		}
155	}
156
157	// Set RWLayer for container after mount labels have been set
158	rwLayer, err := daemon.imageService.CreateLayer(container, setupInitLayer(daemon.idMappings))
159	if err != nil {
160		return nil, errdefs.System(err)
161	}
162	container.RWLayer = rwLayer
163
164	rootIDs := daemon.idMappings.RootPair()
165	if err := idtools.MkdirAndChown(container.Root, 0700, rootIDs); err != nil {
166		return nil, err
167	}
168	if err := idtools.MkdirAndChown(container.CheckpointDir(), 0700, rootIDs); err != nil {
169		return nil, err
170	}
171
172	if err := daemon.setHostConfig(container, params.HostConfig); err != nil {
173		return nil, err
174	}
175
176	if err := daemon.createContainerOSSpecificSettings(container, params.Config, params.HostConfig); err != nil {
177		return nil, err
178	}
179
180	var endpointsConfigs map[string]*networktypes.EndpointSettings
181	if params.NetworkingConfig != nil {
182		endpointsConfigs = params.NetworkingConfig.EndpointsConfig
183	}
184	// Make sure NetworkMode has an acceptable value. We do this to ensure
185	// backwards API compatibility.
186	runconfig.SetDefaultNetModeIfBlank(container.HostConfig)
187
188	daemon.updateContainerNetworkSettings(container, endpointsConfigs)
189	if err := daemon.Register(container); err != nil {
190		return nil, err
191	}
192	stateCtr.set(container.ID, "stopped")
193	daemon.LogContainerEvent(container, "create")
194	return container, nil
195}
196
197func toHostConfigSelinuxLabels(labels []string) []string {
198	for i, l := range labels {
199		labels[i] = "label=" + l
200	}
201	return labels
202}
203
204func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig) ([]string, error) {
205	for _, opt := range hostConfig.SecurityOpt {
206		con := strings.Split(opt, "=")
207		if con[0] == "label" {
208			// Caller overrode SecurityOpts
209			return nil, nil
210		}
211	}
212	ipcMode := hostConfig.IpcMode
213	pidMode := hostConfig.PidMode
214	privileged := hostConfig.Privileged
215	if ipcMode.IsHost() || pidMode.IsHost() || privileged {
216		return toHostConfigSelinuxLabels(label.DisableSecOpt()), nil
217	}
218
219	var ipcLabel []string
220	var pidLabel []string
221	ipcContainer := ipcMode.Container()
222	pidContainer := pidMode.Container()
223	if ipcContainer != "" {
224		c, err := daemon.GetContainer(ipcContainer)
225		if err != nil {
226			return nil, err
227		}
228		ipcLabel = label.DupSecOpt(c.ProcessLabel)
229		if pidContainer == "" {
230			return toHostConfigSelinuxLabels(ipcLabel), err
231		}
232	}
233	if pidContainer != "" {
234		c, err := daemon.GetContainer(pidContainer)
235		if err != nil {
236			return nil, err
237		}
238
239		pidLabel = label.DupSecOpt(c.ProcessLabel)
240		if ipcContainer == "" {
241			return toHostConfigSelinuxLabels(pidLabel), err
242		}
243	}
244
245	if pidLabel != nil && ipcLabel != nil {
246		for i := 0; i < len(pidLabel); i++ {
247			if pidLabel[i] != ipcLabel[i] {
248				return nil, fmt.Errorf("--ipc and --pid containers SELinux labels aren't the same")
249			}
250		}
251		return toHostConfigSelinuxLabels(pidLabel), nil
252	}
253	return nil, nil
254}
255
256func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *image.Image) error {
257	if img != nil && img.Config != nil {
258		if err := merge(config, img.Config); err != nil {
259			return err
260		}
261	}
262	// Reset the Entrypoint if it is [""]
263	if len(config.Entrypoint) == 1 && config.Entrypoint[0] == "" {
264		config.Entrypoint = nil
265	}
266	if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 {
267		return fmt.Errorf("No command specified")
268	}
269	return nil
270}
271
272// Checks if the client set configurations for more than one network while creating a container
273// Also checks if the IPAMConfig is valid
274func verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
275	if nwConfig == nil || len(nwConfig.EndpointsConfig) == 0 {
276		return nil
277	}
278	if len(nwConfig.EndpointsConfig) == 1 {
279		for k, v := range nwConfig.EndpointsConfig {
280			if v == nil {
281				return errdefs.InvalidParameter(errors.Errorf("no EndpointSettings for %s", k))
282			}
283			if v.IPAMConfig != nil {
284				if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil {
285					return errors.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address)
286				}
287				if v.IPAMConfig.IPv6Address != "" {
288					n := net.ParseIP(v.IPAMConfig.IPv6Address)
289					// if the address is an invalid network address (ParseIP == nil) or if it is
290					// an IPv4 address (To4() != nil), then it is an invalid IPv6 address
291					if n == nil || n.To4() != nil {
292						return errors.Errorf("invalid IPv6 address: %s", v.IPAMConfig.IPv6Address)
293					}
294				}
295			}
296		}
297		return nil
298	}
299	l := make([]string, 0, len(nwConfig.EndpointsConfig))
300	for k := range nwConfig.EndpointsConfig {
301		l = append(l, k)
302	}
303	return errors.Errorf("Container cannot be connected to network endpoints: %s", strings.Join(l, ", "))
304}
305