123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- // +build !windows
- /*
- Copyright The containerd Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package oci
- import (
- "context"
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "github.com/containerd/containerd/containers"
- "github.com/containerd/containerd/content"
- "github.com/containerd/containerd/images"
- "github.com/containerd/containerd/mount"
- "github.com/containerd/containerd/namespaces"
- "github.com/containerd/continuity/fs"
- "github.com/opencontainers/image-spec/specs-go/v1"
- "github.com/opencontainers/runc/libcontainer/user"
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/pkg/errors"
- "github.com/syndtr/gocapability/capability"
- )
- // WithTTY sets the information on the spec as well as the environment variables for
- // using a TTY
- func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setProcess(s)
- s.Process.Terminal = true
- s.Process.Env = append(s.Process.Env, "TERM=xterm")
- return nil
- }
- // setRoot sets Root to empty if unset
- func setRoot(s *Spec) {
- if s.Root == nil {
- s.Root = &specs.Root{}
- }
- }
- // setLinux sets Linux to empty if unset
- func setLinux(s *Spec) {
- if s.Linux == nil {
- s.Linux = &specs.Linux{}
- }
- }
- // setCapabilities sets Linux Capabilities to empty if unset
- func setCapabilities(s *Spec) {
- setProcess(s)
- if s.Process.Capabilities == nil {
- s.Process.Capabilities = &specs.LinuxCapabilities{}
- }
- }
- // WithHostNamespace allows a task to run inside the host's linux namespace
- func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setLinux(s)
- for i, n := range s.Linux.Namespaces {
- if n.Type == ns {
- s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
- return nil
- }
- }
- return nil
- }
- }
- // WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
- // spec, the existing namespace is replaced by the one provided.
- func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setLinux(s)
- for i, n := range s.Linux.Namespaces {
- if n.Type == ns.Type {
- before := s.Linux.Namespaces[:i]
- after := s.Linux.Namespaces[i+1:]
- s.Linux.Namespaces = append(before, ns)
- s.Linux.Namespaces = append(s.Linux.Namespaces, after...)
- return nil
- }
- }
- s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
- return nil
- }
- }
- // WithImageConfig configures the spec to from the configuration of an Image
- func WithImageConfig(image Image) SpecOpts {
- return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
- ic, err := image.Config(ctx)
- if err != nil {
- return err
- }
- var (
- ociimage v1.Image
- config v1.ImageConfig
- )
- switch ic.MediaType {
- case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
- p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
- if err != nil {
- return err
- }
- if err := json.Unmarshal(p, &ociimage); err != nil {
- return err
- }
- config = ociimage.Config
- default:
- return fmt.Errorf("unknown image config media type %s", ic.MediaType)
- }
- setProcess(s)
- s.Process.Env = append(s.Process.Env, config.Env...)
- cmd := config.Cmd
- s.Process.Args = append(config.Entrypoint, cmd...)
- cwd := config.WorkingDir
- if cwd == "" {
- cwd = "/"
- }
- s.Process.Cwd = cwd
- if config.User != "" {
- return WithUser(config.User)(ctx, client, c, s)
- }
- return nil
- }
- }
- // WithRootFSPath specifies unmanaged rootfs path.
- func WithRootFSPath(path string) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setRoot(s)
- s.Root.Path = path
- // Entrypoint is not set here (it's up to caller)
- return nil
- }
- }
- // WithRootFSReadonly sets specs.Root.Readonly to true
- func WithRootFSReadonly() SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setRoot(s)
- s.Root.Readonly = true
- return nil
- }
- }
- // WithNoNewPrivileges sets no_new_privileges on the process for the container
- func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setProcess(s)
- s.Process.NoNewPrivileges = true
- return nil
- }
- // WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
- func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- s.Mounts = append(s.Mounts, specs.Mount{
- Destination: "/etc/hosts",
- Type: "bind",
- Source: "/etc/hosts",
- Options: []string{"rbind", "ro"},
- })
- return nil
- }
- // WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
- func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- s.Mounts = append(s.Mounts, specs.Mount{
- Destination: "/etc/resolv.conf",
- Type: "bind",
- Source: "/etc/resolv.conf",
- Options: []string{"rbind", "ro"},
- })
- return nil
- }
- // WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
- func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- s.Mounts = append(s.Mounts, specs.Mount{
- Destination: "/etc/localtime",
- Type: "bind",
- Source: "/etc/localtime",
- Options: []string{"rbind", "ro"},
- })
- return nil
- }
- // WithUserNamespace sets the uid and gid mappings for the task
- // this can be called multiple times to add more mappings to the generated spec
- func WithUserNamespace(container, host, size uint32) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- var hasUserns bool
- setLinux(s)
- for _, ns := range s.Linux.Namespaces {
- if ns.Type == specs.UserNamespace {
- hasUserns = true
- break
- }
- }
- if !hasUserns {
- s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
- Type: specs.UserNamespace,
- })
- }
- mapping := specs.LinuxIDMapping{
- ContainerID: container,
- HostID: host,
- Size: size,
- }
- s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping)
- s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping)
- return nil
- }
- }
- // WithCgroup sets the container's cgroup path
- func WithCgroup(path string) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setLinux(s)
- s.Linux.CgroupsPath = path
- return nil
- }
- }
- // WithNamespacedCgroup uses the namespace set on the context to create a
- // root directory for containers in the cgroup with the id as the subcgroup
- func WithNamespacedCgroup() SpecOpts {
- return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
- namespace, err := namespaces.NamespaceRequired(ctx)
- if err != nil {
- return err
- }
- setLinux(s)
- s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
- return nil
- }
- }
- // WithUser sets the user to be used within the container.
- // It accepts a valid user string in OCI Image Spec v1.0.0:
- // user, uid, user:group, uid:gid, uid:group, user:gid
- func WithUser(userstr string) SpecOpts {
- return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
- setProcess(s)
- parts := strings.Split(userstr, ":")
- switch len(parts) {
- case 1:
- v, err := strconv.Atoi(parts[0])
- if err != nil {
- // if we cannot parse as a uint they try to see if it is a username
- return WithUsername(userstr)(ctx, client, c, s)
- }
- return WithUserID(uint32(v))(ctx, client, c, s)
- case 2:
- var (
- username string
- groupname string
- )
- var uid, gid uint32
- v, err := strconv.Atoi(parts[0])
- if err != nil {
- username = parts[0]
- } else {
- uid = uint32(v)
- }
- if v, err = strconv.Atoi(parts[1]); err != nil {
- groupname = parts[1]
- } else {
- gid = uint32(v)
- }
- if username == "" && groupname == "" {
- s.Process.User.UID, s.Process.User.GID = uid, gid
- return nil
- }
- f := func(root string) error {
- if username != "" {
- uid, _, err = getUIDGIDFromPath(root, func(u user.User) bool {
- return u.Name == username
- })
- if err != nil {
- return err
- }
- }
- if groupname != "" {
- gid, err = getGIDFromPath(root, func(g user.Group) bool {
- return g.Name == groupname
- })
- if err != nil {
- return err
- }
- }
- s.Process.User.UID, s.Process.User.GID = uid, gid
- return nil
- }
- if c.Snapshotter == "" && c.SnapshotKey == "" {
- if !isRootfsAbs(s.Root.Path) {
- return errors.New("rootfs absolute path is required")
- }
- return f(s.Root.Path)
- }
- if c.Snapshotter == "" {
- return errors.New("no snapshotter set for container")
- }
- if c.SnapshotKey == "" {
- return errors.New("rootfs snapshot not created for container")
- }
- snapshotter := client.SnapshotService(c.Snapshotter)
- mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
- if err != nil {
- return err
- }
- return mount.WithTempMount(ctx, mounts, f)
- default:
- return fmt.Errorf("invalid USER value %s", userstr)
- }
- }
- }
- // WithUIDGID allows the UID and GID for the Process to be set
- func WithUIDGID(uid, gid uint32) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setProcess(s)
- s.Process.User.UID = uid
- s.Process.User.GID = gid
- return nil
- }
- }
- // WithUserID sets the correct UID and GID for the container based
- // on the image's /etc/passwd contents. If /etc/passwd does not exist,
- // or uid is not found in /etc/passwd, it sets gid to be the same with
- // uid, and not returns error.
- func WithUserID(uid uint32) SpecOpts {
- return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
- setProcess(s)
- if c.Snapshotter == "" && c.SnapshotKey == "" {
- if !isRootfsAbs(s.Root.Path) {
- return errors.Errorf("rootfs absolute path is required")
- }
- uuid, ugid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
- return u.Uid == int(uid)
- })
- if err != nil {
- if os.IsNotExist(err) || err == errNoUsersFound {
- s.Process.User.UID, s.Process.User.GID = uid, uid
- return nil
- }
- return err
- }
- s.Process.User.UID, s.Process.User.GID = uuid, ugid
- return nil
- }
- if c.Snapshotter == "" {
- return errors.Errorf("no snapshotter set for container")
- }
- if c.SnapshotKey == "" {
- return errors.Errorf("rootfs snapshot not created for container")
- }
- snapshotter := client.SnapshotService(c.Snapshotter)
- mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
- if err != nil {
- return err
- }
- return mount.WithTempMount(ctx, mounts, func(root string) error {
- uuid, ugid, err := getUIDGIDFromPath(root, func(u user.User) bool {
- return u.Uid == int(uid)
- })
- if err != nil {
- if os.IsNotExist(err) || err == errNoUsersFound {
- s.Process.User.UID, s.Process.User.GID = uid, uid
- return nil
- }
- return err
- }
- s.Process.User.UID, s.Process.User.GID = uuid, ugid
- return nil
- })
- }
- }
- // WithUsername sets the correct UID and GID for the container
- // based on the the image's /etc/passwd contents. If /etc/passwd
- // does not exist, or the username is not found in /etc/passwd,
- // it returns error.
- func WithUsername(username string) SpecOpts {
- return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
- setProcess(s)
- if c.Snapshotter == "" && c.SnapshotKey == "" {
- if !isRootfsAbs(s.Root.Path) {
- return errors.Errorf("rootfs absolute path is required")
- }
- uid, gid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
- return u.Name == username
- })
- if err != nil {
- return err
- }
- s.Process.User.UID, s.Process.User.GID = uid, gid
- return nil
- }
- if c.Snapshotter == "" {
- return errors.Errorf("no snapshotter set for container")
- }
- if c.SnapshotKey == "" {
- return errors.Errorf("rootfs snapshot not created for container")
- }
- snapshotter := client.SnapshotService(c.Snapshotter)
- mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
- if err != nil {
- return err
- }
- return mount.WithTempMount(ctx, mounts, func(root string) error {
- uid, gid, err := getUIDGIDFromPath(root, func(u user.User) bool {
- return u.Name == username
- })
- if err != nil {
- return err
- }
- s.Process.User.UID, s.Process.User.GID = uid, gid
- return nil
- })
- }
- }
- // WithCapabilities sets Linux capabilities on the process
- func WithCapabilities(caps []string) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setCapabilities(s)
- s.Process.Capabilities.Bounding = caps
- s.Process.Capabilities.Effective = caps
- s.Process.Capabilities.Permitted = caps
- s.Process.Capabilities.Inheritable = caps
- return nil
- }
- }
- // WithAllCapabilities sets all linux capabilities for the process
- var WithAllCapabilities = WithCapabilities(getAllCapabilities())
- func getAllCapabilities() []string {
- last := capability.CAP_LAST_CAP
- // hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
- if last == capability.Cap(63) {
- last = capability.CAP_BLOCK_SUSPEND
- }
- var caps []string
- for _, cap := range capability.List() {
- if cap > last {
- continue
- }
- caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
- }
- return caps
- }
- var errNoUsersFound = errors.New("no users found")
- func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint32, err error) {
- ppath, err := fs.RootPath(root, "/etc/passwd")
- if err != nil {
- return 0, 0, err
- }
- users, err := user.ParsePasswdFileFilter(ppath, filter)
- if err != nil {
- return 0, 0, err
- }
- if len(users) == 0 {
- return 0, 0, errNoUsersFound
- }
- u := users[0]
- return uint32(u.Uid), uint32(u.Gid), nil
- }
- var errNoGroupsFound = errors.New("no groups found")
- func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
- gpath, err := fs.RootPath(root, "/etc/group")
- if err != nil {
- return 0, err
- }
- groups, err := user.ParseGroupFileFilter(gpath, filter)
- if err != nil {
- return 0, err
- }
- if len(groups) == 0 {
- return 0, errNoGroupsFound
- }
- g := groups[0]
- return uint32(g.Gid), nil
- }
- func isRootfsAbs(root string) bool {
- return filepath.IsAbs(root)
- }
- // WithMaskedPaths sets the masked paths option
- func WithMaskedPaths(paths []string) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setLinux(s)
- s.Linux.MaskedPaths = paths
- return nil
- }
- }
- // WithReadonlyPaths sets the read only paths option
- func WithReadonlyPaths(paths []string) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setLinux(s)
- s.Linux.ReadonlyPaths = paths
- return nil
- }
- }
- // WithWriteableSysfs makes any sysfs mounts writeable
- func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- for i, m := range s.Mounts {
- if m.Type == "sysfs" {
- var options []string
- for _, o := range m.Options {
- if o == "ro" {
- o = "rw"
- }
- options = append(options, o)
- }
- s.Mounts[i].Options = options
- }
- }
- return nil
- }
- // WithWriteableCgroupfs makes any cgroup mounts writeable
- func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- for i, m := range s.Mounts {
- if m.Type == "cgroup" {
- var options []string
- for _, o := range m.Options {
- if o == "ro" {
- o = "rw"
- }
- options = append(options, o)
- }
- s.Mounts[i].Options = options
- }
- }
- return nil
- }
- // WithSelinuxLabel sets the process SELinux label
- func WithSelinuxLabel(label string) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setProcess(s)
- s.Process.SelinuxLabel = label
- return nil
- }
- }
- // WithApparmorProfile sets the Apparmor profile for the process
- func WithApparmorProfile(profile string) SpecOpts {
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setProcess(s)
- s.Process.ApparmorProfile = profile
- return nil
- }
- }
- // WithSeccompUnconfined clears the seccomp profile
- func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
- setLinux(s)
- s.Linux.Seccomp = nil
- return nil
- }
- // WithPrivileged sets up options for a privileged container
- // TODO(justincormack) device handling
- var WithPrivileged = Compose(
- WithAllCapabilities,
- WithMaskedPaths(nil),
- WithReadonlyPaths(nil),
- WithWriteableSysfs,
- WithWriteableCgroupfs,
- WithSelinuxLabel(""),
- WithApparmorProfile(""),
- WithSeccompUnconfined,
- )
|