spec_opts_unix.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. // +build !windows
  2. /*
  3. Copyright The containerd Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package oci
  15. import (
  16. "context"
  17. "encoding/json"
  18. "fmt"
  19. "os"
  20. "path/filepath"
  21. "strconv"
  22. "strings"
  23. "github.com/containerd/containerd/containers"
  24. "github.com/containerd/containerd/content"
  25. "github.com/containerd/containerd/images"
  26. "github.com/containerd/containerd/mount"
  27. "github.com/containerd/containerd/namespaces"
  28. "github.com/containerd/continuity/fs"
  29. "github.com/opencontainers/image-spec/specs-go/v1"
  30. "github.com/opencontainers/runc/libcontainer/user"
  31. specs "github.com/opencontainers/runtime-spec/specs-go"
  32. "github.com/pkg/errors"
  33. "github.com/syndtr/gocapability/capability"
  34. )
  35. // WithTTY sets the information on the spec as well as the environment variables for
  36. // using a TTY
  37. func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  38. setProcess(s)
  39. s.Process.Terminal = true
  40. s.Process.Env = append(s.Process.Env, "TERM=xterm")
  41. return nil
  42. }
  43. // setRoot sets Root to empty if unset
  44. func setRoot(s *Spec) {
  45. if s.Root == nil {
  46. s.Root = &specs.Root{}
  47. }
  48. }
  49. // setLinux sets Linux to empty if unset
  50. func setLinux(s *Spec) {
  51. if s.Linux == nil {
  52. s.Linux = &specs.Linux{}
  53. }
  54. }
  55. // setCapabilities sets Linux Capabilities to empty if unset
  56. func setCapabilities(s *Spec) {
  57. setProcess(s)
  58. if s.Process.Capabilities == nil {
  59. s.Process.Capabilities = &specs.LinuxCapabilities{}
  60. }
  61. }
  62. // WithHostNamespace allows a task to run inside the host's linux namespace
  63. func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
  64. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  65. setLinux(s)
  66. for i, n := range s.Linux.Namespaces {
  67. if n.Type == ns {
  68. s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
  69. return nil
  70. }
  71. }
  72. return nil
  73. }
  74. }
  75. // WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
  76. // spec, the existing namespace is replaced by the one provided.
  77. func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
  78. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  79. setLinux(s)
  80. for i, n := range s.Linux.Namespaces {
  81. if n.Type == ns.Type {
  82. before := s.Linux.Namespaces[:i]
  83. after := s.Linux.Namespaces[i+1:]
  84. s.Linux.Namespaces = append(before, ns)
  85. s.Linux.Namespaces = append(s.Linux.Namespaces, after...)
  86. return nil
  87. }
  88. }
  89. s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
  90. return nil
  91. }
  92. }
  93. // WithImageConfig configures the spec to from the configuration of an Image
  94. func WithImageConfig(image Image) SpecOpts {
  95. return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
  96. ic, err := image.Config(ctx)
  97. if err != nil {
  98. return err
  99. }
  100. var (
  101. ociimage v1.Image
  102. config v1.ImageConfig
  103. )
  104. switch ic.MediaType {
  105. case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
  106. p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
  107. if err != nil {
  108. return err
  109. }
  110. if err := json.Unmarshal(p, &ociimage); err != nil {
  111. return err
  112. }
  113. config = ociimage.Config
  114. default:
  115. return fmt.Errorf("unknown image config media type %s", ic.MediaType)
  116. }
  117. setProcess(s)
  118. s.Process.Env = append(s.Process.Env, config.Env...)
  119. cmd := config.Cmd
  120. s.Process.Args = append(config.Entrypoint, cmd...)
  121. cwd := config.WorkingDir
  122. if cwd == "" {
  123. cwd = "/"
  124. }
  125. s.Process.Cwd = cwd
  126. if config.User != "" {
  127. return WithUser(config.User)(ctx, client, c, s)
  128. }
  129. return nil
  130. }
  131. }
  132. // WithRootFSPath specifies unmanaged rootfs path.
  133. func WithRootFSPath(path string) SpecOpts {
  134. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  135. setRoot(s)
  136. s.Root.Path = path
  137. // Entrypoint is not set here (it's up to caller)
  138. return nil
  139. }
  140. }
  141. // WithRootFSReadonly sets specs.Root.Readonly to true
  142. func WithRootFSReadonly() SpecOpts {
  143. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  144. setRoot(s)
  145. s.Root.Readonly = true
  146. return nil
  147. }
  148. }
  149. // WithNoNewPrivileges sets no_new_privileges on the process for the container
  150. func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  151. setProcess(s)
  152. s.Process.NoNewPrivileges = true
  153. return nil
  154. }
  155. // WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
  156. func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  157. s.Mounts = append(s.Mounts, specs.Mount{
  158. Destination: "/etc/hosts",
  159. Type: "bind",
  160. Source: "/etc/hosts",
  161. Options: []string{"rbind", "ro"},
  162. })
  163. return nil
  164. }
  165. // WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
  166. func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  167. s.Mounts = append(s.Mounts, specs.Mount{
  168. Destination: "/etc/resolv.conf",
  169. Type: "bind",
  170. Source: "/etc/resolv.conf",
  171. Options: []string{"rbind", "ro"},
  172. })
  173. return nil
  174. }
  175. // WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
  176. func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  177. s.Mounts = append(s.Mounts, specs.Mount{
  178. Destination: "/etc/localtime",
  179. Type: "bind",
  180. Source: "/etc/localtime",
  181. Options: []string{"rbind", "ro"},
  182. })
  183. return nil
  184. }
  185. // WithUserNamespace sets the uid and gid mappings for the task
  186. // this can be called multiple times to add more mappings to the generated spec
  187. func WithUserNamespace(container, host, size uint32) SpecOpts {
  188. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  189. var hasUserns bool
  190. setLinux(s)
  191. for _, ns := range s.Linux.Namespaces {
  192. if ns.Type == specs.UserNamespace {
  193. hasUserns = true
  194. break
  195. }
  196. }
  197. if !hasUserns {
  198. s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
  199. Type: specs.UserNamespace,
  200. })
  201. }
  202. mapping := specs.LinuxIDMapping{
  203. ContainerID: container,
  204. HostID: host,
  205. Size: size,
  206. }
  207. s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping)
  208. s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping)
  209. return nil
  210. }
  211. }
  212. // WithCgroup sets the container's cgroup path
  213. func WithCgroup(path string) SpecOpts {
  214. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  215. setLinux(s)
  216. s.Linux.CgroupsPath = path
  217. return nil
  218. }
  219. }
  220. // WithNamespacedCgroup uses the namespace set on the context to create a
  221. // root directory for containers in the cgroup with the id as the subcgroup
  222. func WithNamespacedCgroup() SpecOpts {
  223. return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
  224. namespace, err := namespaces.NamespaceRequired(ctx)
  225. if err != nil {
  226. return err
  227. }
  228. setLinux(s)
  229. s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
  230. return nil
  231. }
  232. }
  233. // WithUser sets the user to be used within the container.
  234. // It accepts a valid user string in OCI Image Spec v1.0.0:
  235. // user, uid, user:group, uid:gid, uid:group, user:gid
  236. func WithUser(userstr string) SpecOpts {
  237. return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
  238. setProcess(s)
  239. parts := strings.Split(userstr, ":")
  240. switch len(parts) {
  241. case 1:
  242. v, err := strconv.Atoi(parts[0])
  243. if err != nil {
  244. // if we cannot parse as a uint they try to see if it is a username
  245. return WithUsername(userstr)(ctx, client, c, s)
  246. }
  247. return WithUserID(uint32(v))(ctx, client, c, s)
  248. case 2:
  249. var (
  250. username string
  251. groupname string
  252. )
  253. var uid, gid uint32
  254. v, err := strconv.Atoi(parts[0])
  255. if err != nil {
  256. username = parts[0]
  257. } else {
  258. uid = uint32(v)
  259. }
  260. if v, err = strconv.Atoi(parts[1]); err != nil {
  261. groupname = parts[1]
  262. } else {
  263. gid = uint32(v)
  264. }
  265. if username == "" && groupname == "" {
  266. s.Process.User.UID, s.Process.User.GID = uid, gid
  267. return nil
  268. }
  269. f := func(root string) error {
  270. if username != "" {
  271. uid, _, err = getUIDGIDFromPath(root, func(u user.User) bool {
  272. return u.Name == username
  273. })
  274. if err != nil {
  275. return err
  276. }
  277. }
  278. if groupname != "" {
  279. gid, err = getGIDFromPath(root, func(g user.Group) bool {
  280. return g.Name == groupname
  281. })
  282. if err != nil {
  283. return err
  284. }
  285. }
  286. s.Process.User.UID, s.Process.User.GID = uid, gid
  287. return nil
  288. }
  289. if c.Snapshotter == "" && c.SnapshotKey == "" {
  290. if !isRootfsAbs(s.Root.Path) {
  291. return errors.New("rootfs absolute path is required")
  292. }
  293. return f(s.Root.Path)
  294. }
  295. if c.Snapshotter == "" {
  296. return errors.New("no snapshotter set for container")
  297. }
  298. if c.SnapshotKey == "" {
  299. return errors.New("rootfs snapshot not created for container")
  300. }
  301. snapshotter := client.SnapshotService(c.Snapshotter)
  302. mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
  303. if err != nil {
  304. return err
  305. }
  306. return mount.WithTempMount(ctx, mounts, f)
  307. default:
  308. return fmt.Errorf("invalid USER value %s", userstr)
  309. }
  310. }
  311. }
  312. // WithUIDGID allows the UID and GID for the Process to be set
  313. func WithUIDGID(uid, gid uint32) SpecOpts {
  314. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  315. setProcess(s)
  316. s.Process.User.UID = uid
  317. s.Process.User.GID = gid
  318. return nil
  319. }
  320. }
  321. // WithUserID sets the correct UID and GID for the container based
  322. // on the image's /etc/passwd contents. If /etc/passwd does not exist,
  323. // or uid is not found in /etc/passwd, it sets gid to be the same with
  324. // uid, and not returns error.
  325. func WithUserID(uid uint32) SpecOpts {
  326. return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
  327. setProcess(s)
  328. if c.Snapshotter == "" && c.SnapshotKey == "" {
  329. if !isRootfsAbs(s.Root.Path) {
  330. return errors.Errorf("rootfs absolute path is required")
  331. }
  332. uuid, ugid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
  333. return u.Uid == int(uid)
  334. })
  335. if err != nil {
  336. if os.IsNotExist(err) || err == errNoUsersFound {
  337. s.Process.User.UID, s.Process.User.GID = uid, uid
  338. return nil
  339. }
  340. return err
  341. }
  342. s.Process.User.UID, s.Process.User.GID = uuid, ugid
  343. return nil
  344. }
  345. if c.Snapshotter == "" {
  346. return errors.Errorf("no snapshotter set for container")
  347. }
  348. if c.SnapshotKey == "" {
  349. return errors.Errorf("rootfs snapshot not created for container")
  350. }
  351. snapshotter := client.SnapshotService(c.Snapshotter)
  352. mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
  353. if err != nil {
  354. return err
  355. }
  356. return mount.WithTempMount(ctx, mounts, func(root string) error {
  357. uuid, ugid, err := getUIDGIDFromPath(root, func(u user.User) bool {
  358. return u.Uid == int(uid)
  359. })
  360. if err != nil {
  361. if os.IsNotExist(err) || err == errNoUsersFound {
  362. s.Process.User.UID, s.Process.User.GID = uid, uid
  363. return nil
  364. }
  365. return err
  366. }
  367. s.Process.User.UID, s.Process.User.GID = uuid, ugid
  368. return nil
  369. })
  370. }
  371. }
  372. // WithUsername sets the correct UID and GID for the container
  373. // based on the the image's /etc/passwd contents. If /etc/passwd
  374. // does not exist, or the username is not found in /etc/passwd,
  375. // it returns error.
  376. func WithUsername(username string) SpecOpts {
  377. return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
  378. setProcess(s)
  379. if c.Snapshotter == "" && c.SnapshotKey == "" {
  380. if !isRootfsAbs(s.Root.Path) {
  381. return errors.Errorf("rootfs absolute path is required")
  382. }
  383. uid, gid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
  384. return u.Name == username
  385. })
  386. if err != nil {
  387. return err
  388. }
  389. s.Process.User.UID, s.Process.User.GID = uid, gid
  390. return nil
  391. }
  392. if c.Snapshotter == "" {
  393. return errors.Errorf("no snapshotter set for container")
  394. }
  395. if c.SnapshotKey == "" {
  396. return errors.Errorf("rootfs snapshot not created for container")
  397. }
  398. snapshotter := client.SnapshotService(c.Snapshotter)
  399. mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
  400. if err != nil {
  401. return err
  402. }
  403. return mount.WithTempMount(ctx, mounts, func(root string) error {
  404. uid, gid, err := getUIDGIDFromPath(root, func(u user.User) bool {
  405. return u.Name == username
  406. })
  407. if err != nil {
  408. return err
  409. }
  410. s.Process.User.UID, s.Process.User.GID = uid, gid
  411. return nil
  412. })
  413. }
  414. }
  415. // WithCapabilities sets Linux capabilities on the process
  416. func WithCapabilities(caps []string) SpecOpts {
  417. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  418. setCapabilities(s)
  419. s.Process.Capabilities.Bounding = caps
  420. s.Process.Capabilities.Effective = caps
  421. s.Process.Capabilities.Permitted = caps
  422. s.Process.Capabilities.Inheritable = caps
  423. return nil
  424. }
  425. }
  426. // WithAllCapabilities sets all linux capabilities for the process
  427. var WithAllCapabilities = WithCapabilities(getAllCapabilities())
  428. func getAllCapabilities() []string {
  429. last := capability.CAP_LAST_CAP
  430. // hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
  431. if last == capability.Cap(63) {
  432. last = capability.CAP_BLOCK_SUSPEND
  433. }
  434. var caps []string
  435. for _, cap := range capability.List() {
  436. if cap > last {
  437. continue
  438. }
  439. caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
  440. }
  441. return caps
  442. }
  443. var errNoUsersFound = errors.New("no users found")
  444. func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint32, err error) {
  445. ppath, err := fs.RootPath(root, "/etc/passwd")
  446. if err != nil {
  447. return 0, 0, err
  448. }
  449. users, err := user.ParsePasswdFileFilter(ppath, filter)
  450. if err != nil {
  451. return 0, 0, err
  452. }
  453. if len(users) == 0 {
  454. return 0, 0, errNoUsersFound
  455. }
  456. u := users[0]
  457. return uint32(u.Uid), uint32(u.Gid), nil
  458. }
  459. var errNoGroupsFound = errors.New("no groups found")
  460. func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
  461. gpath, err := fs.RootPath(root, "/etc/group")
  462. if err != nil {
  463. return 0, err
  464. }
  465. groups, err := user.ParseGroupFileFilter(gpath, filter)
  466. if err != nil {
  467. return 0, err
  468. }
  469. if len(groups) == 0 {
  470. return 0, errNoGroupsFound
  471. }
  472. g := groups[0]
  473. return uint32(g.Gid), nil
  474. }
  475. func isRootfsAbs(root string) bool {
  476. return filepath.IsAbs(root)
  477. }
  478. // WithMaskedPaths sets the masked paths option
  479. func WithMaskedPaths(paths []string) SpecOpts {
  480. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  481. setLinux(s)
  482. s.Linux.MaskedPaths = paths
  483. return nil
  484. }
  485. }
  486. // WithReadonlyPaths sets the read only paths option
  487. func WithReadonlyPaths(paths []string) SpecOpts {
  488. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  489. setLinux(s)
  490. s.Linux.ReadonlyPaths = paths
  491. return nil
  492. }
  493. }
  494. // WithWriteableSysfs makes any sysfs mounts writeable
  495. func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  496. for i, m := range s.Mounts {
  497. if m.Type == "sysfs" {
  498. var options []string
  499. for _, o := range m.Options {
  500. if o == "ro" {
  501. o = "rw"
  502. }
  503. options = append(options, o)
  504. }
  505. s.Mounts[i].Options = options
  506. }
  507. }
  508. return nil
  509. }
  510. // WithWriteableCgroupfs makes any cgroup mounts writeable
  511. func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  512. for i, m := range s.Mounts {
  513. if m.Type == "cgroup" {
  514. var options []string
  515. for _, o := range m.Options {
  516. if o == "ro" {
  517. o = "rw"
  518. }
  519. options = append(options, o)
  520. }
  521. s.Mounts[i].Options = options
  522. }
  523. }
  524. return nil
  525. }
  526. // WithSelinuxLabel sets the process SELinux label
  527. func WithSelinuxLabel(label string) SpecOpts {
  528. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  529. setProcess(s)
  530. s.Process.SelinuxLabel = label
  531. return nil
  532. }
  533. }
  534. // WithApparmorProfile sets the Apparmor profile for the process
  535. func WithApparmorProfile(profile string) SpecOpts {
  536. return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  537. setProcess(s)
  538. s.Process.ApparmorProfile = profile
  539. return nil
  540. }
  541. }
  542. // WithSeccompUnconfined clears the seccomp profile
  543. func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  544. setLinux(s)
  545. s.Linux.Seccomp = nil
  546. return nil
  547. }
  548. // WithPrivileged sets up options for a privileged container
  549. // TODO(justincormack) device handling
  550. var WithPrivileged = Compose(
  551. WithAllCapabilities,
  552. WithMaskedPaths(nil),
  553. WithReadonlyPaths(nil),
  554. WithWriteableSysfs,
  555. WithWriteableCgroupfs,
  556. WithSelinuxLabel(""),
  557. WithApparmorProfile(""),
  558. WithSeccompUnconfined,
  559. )