1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package oci
18
19import (
20	"context"
21	"path/filepath"
22	"runtime"
23
24	"github.com/containerd/containerd/namespaces"
25	"github.com/containerd/containerd/platforms"
26
27	"github.com/containerd/containerd/containers"
28	specs "github.com/opencontainers/runtime-spec/specs-go"
29)
30
31const (
32	rwm               = "rwm"
33	defaultRootfsPath = "rootfs"
34)
35
36var (
37	defaultUnixEnv = []string{
38		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
39	}
40)
41
42// Spec is a type alias to the OCI runtime spec to allow third part SpecOpts
43// to be created without the "issues" with go vendoring and package imports
44type Spec = specs.Spec
45
46// GenerateSpec will generate a default spec from the provided image
47// for use as a containerd container
48func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
49	return GenerateSpecWithPlatform(ctx, client, platforms.DefaultString(), c, opts...)
50}
51
52// GenerateSpecWithPlatform will generate a default spec from the provided image
53// for use as a containerd container in the platform requested.
54func GenerateSpecWithPlatform(ctx context.Context, client Client, platform string, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
55	var s Spec
56	if err := generateDefaultSpecWithPlatform(ctx, platform, c.ID, &s); err != nil {
57		return nil, err
58	}
59
60	return &s, ApplyOpts(ctx, client, c, &s, opts...)
61}
62
63func generateDefaultSpecWithPlatform(ctx context.Context, platform, id string, s *Spec) error {
64	plat, err := platforms.Parse(platform)
65	if err != nil {
66		return err
67	}
68
69	if plat.OS == "windows" {
70		err = populateDefaultWindowsSpec(ctx, s, id)
71	} else {
72		err = populateDefaultUnixSpec(ctx, s, id)
73		if err == nil && runtime.GOOS == "windows" {
74			// To run LCOW we have a Linux and Windows section. Add an empty one now.
75			s.Windows = &specs.Windows{}
76		}
77	}
78	return err
79}
80
81// ApplyOpts applies the options to the given spec, injecting data from the
82// context, client and container instance.
83func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *Spec, opts ...SpecOpts) error {
84	for _, o := range opts {
85		if err := o(ctx, client, c, s); err != nil {
86			return err
87		}
88	}
89
90	return nil
91}
92
93func defaultUnixCaps() []string {
94	return []string{
95		"CAP_CHOWN",
96		"CAP_DAC_OVERRIDE",
97		"CAP_FSETID",
98		"CAP_FOWNER",
99		"CAP_MKNOD",
100		"CAP_NET_RAW",
101		"CAP_SETGID",
102		"CAP_SETUID",
103		"CAP_SETFCAP",
104		"CAP_SETPCAP",
105		"CAP_NET_BIND_SERVICE",
106		"CAP_SYS_CHROOT",
107		"CAP_KILL",
108		"CAP_AUDIT_WRITE",
109	}
110}
111
112func defaultUnixNamespaces() []specs.LinuxNamespace {
113	return []specs.LinuxNamespace{
114		{
115			Type: specs.PIDNamespace,
116		},
117		{
118			Type: specs.IPCNamespace,
119		},
120		{
121			Type: specs.UTSNamespace,
122		},
123		{
124			Type: specs.MountNamespace,
125		},
126		{
127			Type: specs.NetworkNamespace,
128		},
129	}
130}
131
132func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error {
133	ns, err := namespaces.NamespaceRequired(ctx)
134	if err != nil {
135		return err
136	}
137
138	*s = Spec{
139		Version: specs.Version,
140		Root: &specs.Root{
141			Path: defaultRootfsPath,
142		},
143		Process: &specs.Process{
144			Cwd:             "/",
145			NoNewPrivileges: true,
146			User: specs.User{
147				UID: 0,
148				GID: 0,
149			},
150			Capabilities: &specs.LinuxCapabilities{
151				Bounding:    defaultUnixCaps(),
152				Permitted:   defaultUnixCaps(),
153				Inheritable: defaultUnixCaps(),
154				Effective:   defaultUnixCaps(),
155			},
156			Rlimits: []specs.POSIXRlimit{
157				{
158					Type: "RLIMIT_NOFILE",
159					Hard: uint64(1024),
160					Soft: uint64(1024),
161				},
162			},
163		},
164		Mounts: []specs.Mount{
165			{
166				Destination: "/proc",
167				Type:        "proc",
168				Source:      "proc",
169				Options:     []string{"nosuid", "noexec", "nodev"},
170			},
171			{
172				Destination: "/dev",
173				Type:        "tmpfs",
174				Source:      "tmpfs",
175				Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
176			},
177			{
178				Destination: "/dev/pts",
179				Type:        "devpts",
180				Source:      "devpts",
181				Options:     []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
182			},
183			{
184				Destination: "/dev/shm",
185				Type:        "tmpfs",
186				Source:      "shm",
187				Options:     []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
188			},
189			{
190				Destination: "/dev/mqueue",
191				Type:        "mqueue",
192				Source:      "mqueue",
193				Options:     []string{"nosuid", "noexec", "nodev"},
194			},
195			{
196				Destination: "/sys",
197				Type:        "sysfs",
198				Source:      "sysfs",
199				Options:     []string{"nosuid", "noexec", "nodev", "ro"},
200			},
201			{
202				Destination: "/run",
203				Type:        "tmpfs",
204				Source:      "tmpfs",
205				Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
206			},
207		},
208		Linux: &specs.Linux{
209			MaskedPaths: []string{
210				"/proc/acpi",
211				"/proc/asound",
212				"/proc/kcore",
213				"/proc/keys",
214				"/proc/latency_stats",
215				"/proc/timer_list",
216				"/proc/timer_stats",
217				"/proc/sched_debug",
218				"/sys/firmware",
219				"/proc/scsi",
220			},
221			ReadonlyPaths: []string{
222				"/proc/bus",
223				"/proc/fs",
224				"/proc/irq",
225				"/proc/sys",
226				"/proc/sysrq-trigger",
227			},
228			CgroupsPath: filepath.Join("/", ns, id),
229			Resources: &specs.LinuxResources{
230				Devices: []specs.LinuxDeviceCgroup{
231					{
232						Allow:  false,
233						Access: rwm,
234					},
235				},
236			},
237			Namespaces: defaultUnixNamespaces(),
238		},
239	}
240	return nil
241}
242
243func populateDefaultWindowsSpec(ctx context.Context, s *Spec, id string) error {
244	*s = Spec{
245		Version: specs.Version,
246		Root:    &specs.Root{},
247		Process: &specs.Process{
248			Cwd: `C:\`,
249		},
250		Windows: &specs.Windows{},
251	}
252	return nil
253}
254