Merge pull request #2592 from jterry75/specs_with_platform
Merges the oci package for Linux and Windows
This commit is contained in:
commit
777cc50c72
@ -22,29 +22,12 @@ import (
|
|||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||||
"github.com/containerd/containerd/containers"
|
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func withTTY(terminal bool) oci.SpecOpts {
|
|
||||||
if !terminal {
|
|
||||||
return func(ctx gocontext.Context, client oci.Client, c *containers.Container, s *specs.Spec) error {
|
|
||||||
s.Process.Terminal = false
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
con := console.Current()
|
|
||||||
size, err := con.Size()
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Error("console size")
|
|
||||||
}
|
|
||||||
return oci.WithTTY(int(size.Width), int(size.Height))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContainer creates a new container
|
// NewContainer creates a new container
|
||||||
func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
|
func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
|
||||||
var (
|
var (
|
||||||
@ -73,7 +56,17 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli
|
|||||||
opts = append(opts, oci.WithImageConfig(image))
|
opts = append(opts, oci.WithImageConfig(image))
|
||||||
opts = append(opts, oci.WithEnv(context.StringSlice("env")))
|
opts = append(opts, oci.WithEnv(context.StringSlice("env")))
|
||||||
opts = append(opts, withMounts(context))
|
opts = append(opts, withMounts(context))
|
||||||
opts = append(opts, withTTY(context.Bool("tty")))
|
if context.Bool("tty") {
|
||||||
|
opts = append(opts, oci.WithTTY)
|
||||||
|
|
||||||
|
con := console.Current()
|
||||||
|
size, err := con.Size()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("console size")
|
||||||
|
}
|
||||||
|
opts = append(opts, oci.WithTTYSize(int(size.Width), int(size.Height)))
|
||||||
|
}
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
opts = append(opts, oci.WithProcessArgs(args...))
|
opts = append(opts, oci.WithProcessArgs(args...))
|
||||||
}
|
}
|
||||||
|
207
oci/spec.go
207
oci/spec.go
@ -18,11 +18,26 @@ package oci
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/namespaces"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rwm = "rwm"
|
||||||
|
defaultRootfsPath = "rootfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultUnixEnv = []string{
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Spec is a type alias to the OCI runtime spec to allow third part SpecOpts
|
// Spec is a type alias to the OCI runtime spec to allow third part SpecOpts
|
||||||
// to be created without the "issues" with go vendoring and package imports
|
// to be created without the "issues" with go vendoring and package imports
|
||||||
type Spec = specs.Spec
|
type Spec = specs.Spec
|
||||||
@ -30,12 +45,28 @@ type Spec = specs.Spec
|
|||||||
// GenerateSpec will generate a default spec from the provided image
|
// GenerateSpec will generate a default spec from the provided image
|
||||||
// for use as a containerd container
|
// for use as a containerd container
|
||||||
func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
|
func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
|
||||||
s, err := createDefaultSpec(ctx, c.ID)
|
return GenerateSpecWithPlatform(ctx, client, platforms.DefaultString(), c, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSpecWithPlatform will generate a default spec from the provided image
|
||||||
|
// for use as a containerd container in the platform requested.
|
||||||
|
func GenerateSpecWithPlatform(ctx context.Context, client Client, platform string, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
|
||||||
|
plat, err := platforms.Parse(platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, ApplyOpts(ctx, client, c, s, opts...)
|
var s Spec
|
||||||
|
if plat.OS == "windows" {
|
||||||
|
err = populateDefaultWindowsSpec(ctx, &s, c.ID)
|
||||||
|
} else {
|
||||||
|
err = populateDefaultUnixSpec(ctx, &s, c.ID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &s, ApplyOpts(ctx, client, c, &s, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyOpts applys the options to the given spec, injecting data from the
|
// ApplyOpts applys the options to the given spec, injecting data from the
|
||||||
@ -50,7 +81,173 @@ func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *S
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultSpec(ctx context.Context, id string) (*Spec, error) {
|
func defaultUnixCaps() []string {
|
||||||
var s Spec
|
return []string{
|
||||||
return &s, populateDefaultSpec(ctx, &s, id)
|
"CAP_CHOWN",
|
||||||
|
"CAP_DAC_OVERRIDE",
|
||||||
|
"CAP_FSETID",
|
||||||
|
"CAP_FOWNER",
|
||||||
|
"CAP_MKNOD",
|
||||||
|
"CAP_NET_RAW",
|
||||||
|
"CAP_SETGID",
|
||||||
|
"CAP_SETUID",
|
||||||
|
"CAP_SETFCAP",
|
||||||
|
"CAP_SETPCAP",
|
||||||
|
"CAP_NET_BIND_SERVICE",
|
||||||
|
"CAP_SYS_CHROOT",
|
||||||
|
"CAP_KILL",
|
||||||
|
"CAP_AUDIT_WRITE",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultUnixNamespaces() []specs.LinuxNamespace {
|
||||||
|
return []specs.LinuxNamespace{
|
||||||
|
{
|
||||||
|
Type: specs.PIDNamespace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: specs.IPCNamespace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: specs.UTSNamespace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: specs.MountNamespace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: specs.NetworkNamespace,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error {
|
||||||
|
ns, err := namespaces.NamespaceRequired(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = Spec{
|
||||||
|
Version: specs.Version,
|
||||||
|
Root: &specs.Root{
|
||||||
|
Path: defaultRootfsPath,
|
||||||
|
},
|
||||||
|
Process: &specs.Process{
|
||||||
|
Env: defaultUnixEnv,
|
||||||
|
Cwd: "/",
|
||||||
|
NoNewPrivileges: true,
|
||||||
|
User: specs.User{
|
||||||
|
UID: 0,
|
||||||
|
GID: 0,
|
||||||
|
},
|
||||||
|
Capabilities: &specs.LinuxCapabilities{
|
||||||
|
Bounding: defaultUnixCaps(),
|
||||||
|
Permitted: defaultUnixCaps(),
|
||||||
|
Inheritable: defaultUnixCaps(),
|
||||||
|
Effective: defaultUnixCaps(),
|
||||||
|
},
|
||||||
|
Rlimits: []specs.POSIXRlimit{
|
||||||
|
{
|
||||||
|
Type: "RLIMIT_NOFILE",
|
||||||
|
Hard: uint64(1024),
|
||||||
|
Soft: uint64(1024),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Mounts: []specs.Mount{
|
||||||
|
{
|
||||||
|
Destination: "/proc",
|
||||||
|
Type: "proc",
|
||||||
|
Source: "proc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/dev",
|
||||||
|
Type: "tmpfs",
|
||||||
|
Source: "tmpfs",
|
||||||
|
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/dev/pts",
|
||||||
|
Type: "devpts",
|
||||||
|
Source: "devpts",
|
||||||
|
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/dev/shm",
|
||||||
|
Type: "tmpfs",
|
||||||
|
Source: "shm",
|
||||||
|
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/dev/mqueue",
|
||||||
|
Type: "mqueue",
|
||||||
|
Source: "mqueue",
|
||||||
|
Options: []string{"nosuid", "noexec", "nodev"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/sys",
|
||||||
|
Type: "sysfs",
|
||||||
|
Source: "sysfs",
|
||||||
|
Options: []string{"nosuid", "noexec", "nodev", "ro"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/run",
|
||||||
|
Type: "tmpfs",
|
||||||
|
Source: "tmpfs",
|
||||||
|
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Linux: &specs.Linux{
|
||||||
|
MaskedPaths: []string{
|
||||||
|
"/proc/acpi",
|
||||||
|
"/proc/kcore",
|
||||||
|
"/proc/keys",
|
||||||
|
"/proc/latency_stats",
|
||||||
|
"/proc/timer_list",
|
||||||
|
"/proc/timer_stats",
|
||||||
|
"/proc/sched_debug",
|
||||||
|
"/sys/firmware",
|
||||||
|
"/proc/scsi",
|
||||||
|
},
|
||||||
|
ReadonlyPaths: []string{
|
||||||
|
"/proc/asound",
|
||||||
|
"/proc/bus",
|
||||||
|
"/proc/fs",
|
||||||
|
"/proc/irq",
|
||||||
|
"/proc/sys",
|
||||||
|
"/proc/sysrq-trigger",
|
||||||
|
},
|
||||||
|
CgroupsPath: filepath.Join("/", ns, id),
|
||||||
|
Resources: &specs.LinuxResources{
|
||||||
|
Devices: []specs.LinuxDeviceCgroup{
|
||||||
|
{
|
||||||
|
Allow: false,
|
||||||
|
Access: rwm,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Namespaces: defaultUnixNamespaces(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateDefaultWindowsSpec(ctx context.Context, s *Spec, id string) error {
|
||||||
|
*s = Spec{
|
||||||
|
Version: specs.Version,
|
||||||
|
Root: &specs.Root{},
|
||||||
|
Process: &specs.Process{
|
||||||
|
Cwd: `C:\`,
|
||||||
|
ConsoleSize: &specs.Box{
|
||||||
|
Width: 80,
|
||||||
|
Height: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Windows: &specs.Windows{
|
||||||
|
IgnoreFlushesDuringBoot: true,
|
||||||
|
Network: &specs.WindowsNetwork{
|
||||||
|
AllowUnqualifiedDNSQuery: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
831
oci/spec_opts.go
831
oci/spec_opts.go
@ -19,12 +19,26 @@ package oci
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"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/containerd/platforms"
|
||||||
|
"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"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/syndtr/gocapability/capability"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SpecOpts sets spec specific information to a newly generated OCI spec
|
// SpecOpts sets spec specific information to a newly generated OCI spec
|
||||||
@ -49,13 +63,56 @@ func setProcess(s *Spec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithDefaultSpec returns a SpecOpts that will populate the spec with default
|
// WithDefaultSpec returns a SpecOpts that will populate the spec with default
|
||||||
// values.
|
// values.
|
||||||
//
|
//
|
||||||
// Use as the first option to clear the spec, then apply options afterwards.
|
// Use as the first option to clear the spec, then apply options afterwards.
|
||||||
func WithDefaultSpec() SpecOpts {
|
func WithDefaultSpec() SpecOpts {
|
||||||
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
||||||
return populateDefaultSpec(ctx, s, c.ID)
|
if runtime.GOOS == "windows" {
|
||||||
|
return populateDefaultWindowsSpec(ctx, s, c.ID)
|
||||||
|
}
|
||||||
|
return populateDefaultUnixSpec(ctx, s, c.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultSpecForPlatform returns a SpecOpts that will populate the spec
|
||||||
|
// with default values for a given platform.
|
||||||
|
//
|
||||||
|
// Use as the first option to clear the spec, then apply options afterwards.
|
||||||
|
func WithDefaultSpecForPlatform(platform string) SpecOpts {
|
||||||
|
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
||||||
|
plat, err := platforms.Parse(platform)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if plat.OS == "windows" {
|
||||||
|
return populateDefaultWindowsSpec(ctx, s, c.ID)
|
||||||
|
}
|
||||||
|
return populateDefaultUnixSpec(ctx, s, c.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,32 +138,6 @@ func WithSpecFromFile(filename string) SpecOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithProcessArgs replaces the args on the generated spec
|
|
||||||
func WithProcessArgs(args ...string) SpecOpts {
|
|
||||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
||||||
setProcess(s)
|
|
||||||
s.Process.Args = args
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithProcessCwd replaces the current working directory on the generated spec
|
|
||||||
func WithProcessCwd(cwd string) SpecOpts {
|
|
||||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
||||||
setProcess(s)
|
|
||||||
s.Process.Cwd = cwd
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHostname sets the container's hostname
|
|
||||||
func WithHostname(name string) SpecOpts {
|
|
||||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
||||||
s.Hostname = name
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithEnv appends environment variables
|
// WithEnv appends environment variables
|
||||||
func WithEnv(environmentVariables []string) SpecOpts {
|
func WithEnv(environmentVariables []string) SpecOpts {
|
||||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
@ -118,14 +149,6 @@ func WithEnv(environmentVariables []string) SpecOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithMounts appends mounts
|
|
||||||
func WithMounts(mounts []specs.Mount) SpecOpts {
|
|
||||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
||||||
s.Mounts = append(s.Mounts, mounts...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceOrAppendEnvValues returns the defaults with the overrides either
|
// replaceOrAppendEnvValues returns the defaults with the overrides either
|
||||||
// replaced by env key or appended to the list
|
// replaced by env key or appended to the list
|
||||||
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
||||||
@ -163,3 +186,741 @@ func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
|||||||
|
|
||||||
return defaults
|
return defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithProcessArgs replaces the args on the generated spec
|
||||||
|
func WithProcessArgs(args ...string) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
setProcess(s)
|
||||||
|
s.Process.Args = args
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithProcessCwd replaces the current working directory on the generated spec
|
||||||
|
func WithProcessCwd(cwd string) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
setProcess(s)
|
||||||
|
s.Process.Cwd = cwd
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if s.Linux != nil {
|
||||||
|
s.Process.Env = append(s.Process.Env, "TERM=xterm")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTTYSize sets the information on the spec as well as the environment variables for
|
||||||
|
// using a TTY
|
||||||
|
func WithTTYSize(width, height int) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
setProcess(s)
|
||||||
|
if s.Process.ConsoleSize == nil {
|
||||||
|
s.Process.ConsoleSize = &specs.Box{}
|
||||||
|
}
|
||||||
|
s.Process.ConsoleSize.Width = uint(width)
|
||||||
|
s.Process.ConsoleSize.Height = uint(height)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHostname sets the container's hostname
|
||||||
|
func WithHostname(name string) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
s.Hostname = name
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMounts appends mounts
|
||||||
|
func WithMounts(mounts []specs.Mount) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
s.Mounts = append(s.Mounts, mounts...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 WithImageConfigArgs(image, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that
|
||||||
|
// replaces the CMD of the image
|
||||||
|
func WithImageConfigArgs(image Image, args []string) 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)
|
||||||
|
if s.Linux != nil {
|
||||||
|
s.Process.Env = append(s.Process.Env, config.Env...)
|
||||||
|
cmd := config.Cmd
|
||||||
|
if len(args) > 0 {
|
||||||
|
cmd = args
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
} else if s.Windows != nil {
|
||||||
|
s.Process.Env = config.Env
|
||||||
|
s.Process.Args = append(config.Entrypoint, config.Cmd...)
|
||||||
|
s.Process.User = specs.User{
|
||||||
|
Username: config.User,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("spec does not contain Linux or Windows section")
|
||||||
|
}
|
||||||
|
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 the requested uid,
|
||||||
|
// additionally sets the gid to 0, and does not return an 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, 0
|
||||||
|
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, 0
|
||||||
|
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 s.Linux != nil {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
} else if s.Windows != nil {
|
||||||
|
s.Process.User.Username = username
|
||||||
|
} else {
|
||||||
|
return errors.New("spec does not contain Linux or Windows section")
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAmbientCapabilities set the Linux ambient capabilities for the process
|
||||||
|
// Ambient capabilities should only be set for non-root users or the caller should
|
||||||
|
// understand how these capabilities are used and set
|
||||||
|
func WithAmbientCapabilities(caps []string) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
setCapabilities(s)
|
||||||
|
|
||||||
|
s.Process.Capabilities.Ambient = caps
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's
|
||||||
|
// allowed and denied devices
|
||||||
|
func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
setLinux(s)
|
||||||
|
if s.Linux.Resources == nil {
|
||||||
|
s.Linux.Resources = &specs.LinuxResources{}
|
||||||
|
}
|
||||||
|
s.Linux.Resources.Devices = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to
|
||||||
|
// the container's resource cgroup spec
|
||||||
|
func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
setLinux(s)
|
||||||
|
if s.Linux.Resources == nil {
|
||||||
|
s.Linux.Resources = &specs.LinuxResources{}
|
||||||
|
}
|
||||||
|
intptr := func(i int64) *int64 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
|
||||||
|
{
|
||||||
|
// "/dev/null",
|
||||||
|
Type: "c",
|
||||||
|
Major: intptr(1),
|
||||||
|
Minor: intptr(3),
|
||||||
|
Access: rwm,
|
||||||
|
Allow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "/dev/random",
|
||||||
|
Type: "c",
|
||||||
|
Major: intptr(1),
|
||||||
|
Minor: intptr(8),
|
||||||
|
Access: rwm,
|
||||||
|
Allow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "/dev/full",
|
||||||
|
Type: "c",
|
||||||
|
Major: intptr(1),
|
||||||
|
Minor: intptr(7),
|
||||||
|
Access: rwm,
|
||||||
|
Allow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "/dev/tty",
|
||||||
|
Type: "c",
|
||||||
|
Major: intptr(5),
|
||||||
|
Minor: intptr(0),
|
||||||
|
Access: rwm,
|
||||||
|
Allow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "/dev/zero",
|
||||||
|
Type: "c",
|
||||||
|
Major: intptr(1),
|
||||||
|
Minor: intptr(5),
|
||||||
|
Access: rwm,
|
||||||
|
Allow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "/dev/urandom",
|
||||||
|
Type: "c",
|
||||||
|
Major: intptr(1),
|
||||||
|
Minor: intptr(9),
|
||||||
|
Access: rwm,
|
||||||
|
Allow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "/dev/console",
|
||||||
|
Type: "c",
|
||||||
|
Major: intptr(5),
|
||||||
|
Minor: intptr(1),
|
||||||
|
Access: rwm,
|
||||||
|
Allow: true,
|
||||||
|
},
|
||||||
|
// /dev/pts/ - pts namespaces are "coming soon"
|
||||||
|
{
|
||||||
|
Type: "c",
|
||||||
|
Major: intptr(136),
|
||||||
|
Access: rwm,
|
||||||
|
Allow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "c",
|
||||||
|
Major: intptr(5),
|
||||||
|
Minor: intptr(2),
|
||||||
|
Access: rwm,
|
||||||
|
Allow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// tuntap
|
||||||
|
Type: "c",
|
||||||
|
Major: intptr(10),
|
||||||
|
Minor: intptr(200),
|
||||||
|
Access: rwm,
|
||||||
|
Allow: true,
|
||||||
|
},
|
||||||
|
}...)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
@ -108,7 +109,13 @@ func TestWithDefaultSpec(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected, err := createDefaultSpec(ctx, c.ID)
|
var expected Spec
|
||||||
|
var err error
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
err = populateDefaultWindowsSpec(ctx, &expected, c.ID)
|
||||||
|
} else {
|
||||||
|
err = populateDefaultUnixSpec(ctx, &expected, c.ID)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -117,7 +124,7 @@ func TestWithDefaultSpec(t *testing.T) {
|
|||||||
t.Fatalf("spec should not be empty")
|
t.Fatalf("spec should not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(&s, expected) {
|
if !reflect.DeepEqual(&s, &expected) {
|
||||||
t.Fatalf("spec from option differs from default: \n%#v != \n%#v", &s, expected)
|
t.Fatalf("spec from option differs from default: \n%#v != \n%#v", &s, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,12 +141,12 @@ func TestWithSpecFromFile(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer fp.Close()
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := os.Remove(fp.Name()); err != nil {
|
if err := os.Remove(fp.Name()); err != nil {
|
||||||
log.Printf("failed to remove tempfile %v: %v", fp.Name(), err)
|
log.Printf("failed to remove tempfile %v: %v", fp.Name(), err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
defer fp.Close()
|
||||||
|
|
||||||
expected, err := GenerateSpec(ctx, nil, &c)
|
expected, err := GenerateSpec(ctx, nil, &c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,733 +0,0 @@
|
|||||||
// +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 WithImageConfigArgs(image, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that
|
|
||||||
// replaces the CMD of the image
|
|
||||||
func WithImageConfigArgs(image Image, args []string) 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
|
|
||||||
if len(args) > 0 {
|
|
||||||
cmd = args
|
|
||||||
}
|
|
||||||
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 the requested uid,
|
|
||||||
// additionally sets the gid to 0, and does not return an 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, 0
|
|
||||||
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, 0
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAmbientCapabilities set the Linux ambient capabilities for the process
|
|
||||||
// Ambient capabilities should only be set for non-root users or the caller should
|
|
||||||
// understand how these capabilities are used and set
|
|
||||||
func WithAmbientCapabilities(caps []string) SpecOpts {
|
|
||||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
||||||
setCapabilities(s)
|
|
||||||
|
|
||||||
s.Process.Capabilities.Ambient = caps
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's
|
|
||||||
// allowed and denied devices
|
|
||||||
func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
||||||
setLinux(s)
|
|
||||||
if s.Linux.Resources == nil {
|
|
||||||
s.Linux.Resources = &specs.LinuxResources{}
|
|
||||||
}
|
|
||||||
s.Linux.Resources.Devices = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to
|
|
||||||
// the container's resource cgroup spec
|
|
||||||
func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
||||||
setLinux(s)
|
|
||||||
if s.Linux.Resources == nil {
|
|
||||||
s.Linux.Resources = &specs.LinuxResources{}
|
|
||||||
}
|
|
||||||
intptr := func(i int64) *int64 {
|
|
||||||
return &i
|
|
||||||
}
|
|
||||||
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
|
|
||||||
{
|
|
||||||
// "/dev/null",
|
|
||||||
Type: "c",
|
|
||||||
Major: intptr(1),
|
|
||||||
Minor: intptr(3),
|
|
||||||
Access: rwm,
|
|
||||||
Allow: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// "/dev/random",
|
|
||||||
Type: "c",
|
|
||||||
Major: intptr(1),
|
|
||||||
Minor: intptr(8),
|
|
||||||
Access: rwm,
|
|
||||||
Allow: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// "/dev/full",
|
|
||||||
Type: "c",
|
|
||||||
Major: intptr(1),
|
|
||||||
Minor: intptr(7),
|
|
||||||
Access: rwm,
|
|
||||||
Allow: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// "/dev/tty",
|
|
||||||
Type: "c",
|
|
||||||
Major: intptr(5),
|
|
||||||
Minor: intptr(0),
|
|
||||||
Access: rwm,
|
|
||||||
Allow: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// "/dev/zero",
|
|
||||||
Type: "c",
|
|
||||||
Major: intptr(1),
|
|
||||||
Minor: intptr(5),
|
|
||||||
Access: rwm,
|
|
||||||
Allow: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// "/dev/urandom",
|
|
||||||
Type: "c",
|
|
||||||
Major: intptr(1),
|
|
||||||
Minor: intptr(9),
|
|
||||||
Access: rwm,
|
|
||||||
Allow: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// "/dev/console",
|
|
||||||
Type: "c",
|
|
||||||
Major: intptr(5),
|
|
||||||
Minor: intptr(1),
|
|
||||||
Access: rwm,
|
|
||||||
Allow: true,
|
|
||||||
},
|
|
||||||
// /dev/pts/ - pts namespaces are "coming soon"
|
|
||||||
{
|
|
||||||
Type: "c",
|
|
||||||
Major: intptr(136),
|
|
||||||
Access: rwm,
|
|
||||||
Allow: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: "c",
|
|
||||||
Major: intptr(5),
|
|
||||||
Minor: intptr(2),
|
|
||||||
Access: rwm,
|
|
||||||
Allow: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// tuntap
|
|
||||||
Type: "c",
|
|
||||||
Major: intptr(10),
|
|
||||||
Minor: intptr(200),
|
|
||||||
Access: rwm,
|
|
||||||
Allow: true,
|
|
||||||
},
|
|
||||||
}...)
|
|
||||||
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,
|
|
||||||
)
|
|
@ -1,89 +0,0 @@
|
|||||||
// +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"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
|
||||||
"github.com/containerd/containerd/content"
|
|
||||||
"github.com/containerd/containerd/images"
|
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WithImageConfig configures the spec to from the configuration of an Image
|
|
||||||
func WithImageConfig(image Image) SpecOpts {
|
|
||||||
return func(ctx context.Context, client Client, _ *containers.Container, s *Spec) error {
|
|
||||||
setProcess(s)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
s.Process.Env = config.Env
|
|
||||||
s.Process.Args = append(config.Entrypoint, config.Cmd...)
|
|
||||||
s.Process.User = specs.User{
|
|
||||||
Username: config.User,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTTY sets the information on the spec as well as the environment variables for
|
|
||||||
// using a TTY
|
|
||||||
func WithTTY(width, height int) SpecOpts {
|
|
||||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
||||||
setProcess(s)
|
|
||||||
s.Process.Terminal = true
|
|
||||||
if s.Process.ConsoleSize == nil {
|
|
||||||
s.Process.ConsoleSize = &specs.Box{}
|
|
||||||
}
|
|
||||||
s.Process.ConsoleSize.Width = uint(width)
|
|
||||||
s.Process.ConsoleSize.Height = uint(height)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithUsername sets the username on the process
|
|
||||||
func WithUsername(username string) SpecOpts {
|
|
||||||
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
|
||||||
setProcess(s)
|
|
||||||
s.Process.User.Username = username
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,3 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
@ -20,6 +18,7 @@ package oci
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
@ -39,8 +38,9 @@ func TestGenerateSpec(t *testing.T) {
|
|||||||
t.Fatal("GenerateSpec() returns a nil spec")
|
t.Fatal("GenerateSpec() returns a nil spec")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
// check for matching caps
|
// check for matching caps
|
||||||
defaults := defaultCaps()
|
defaults := defaultUnixCaps()
|
||||||
for _, cl := range [][]string{
|
for _, cl := range [][]string{
|
||||||
s.Process.Capabilities.Bounding,
|
s.Process.Capabilities.Bounding,
|
||||||
s.Process.Capabilities.Permitted,
|
s.Process.Capabilities.Permitted,
|
||||||
@ -55,12 +55,17 @@ func TestGenerateSpec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check default namespaces
|
// check default namespaces
|
||||||
defaultNS := defaultNamespaces()
|
defaultNS := defaultUnixNamespaces()
|
||||||
for i, ns := range s.Linux.Namespaces {
|
for i, ns := range s.Linux.Namespaces {
|
||||||
if defaultNS[i] != ns {
|
if defaultNS[i] != ns {
|
||||||
t.Errorf("ns at %d does not match set %q != %q", i, defaultNS[i], ns)
|
t.Errorf("ns at %d does not match set %q != %q", i, defaultNS[i], ns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if s.Windows == nil {
|
||||||
|
t.Fatal("Windows section of spec not filled on on Windows platform")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// test that we don't have tty set
|
// test that we don't have tty set
|
||||||
if s.Process.Terminal {
|
if s.Process.Terminal {
|
||||||
@ -79,10 +84,16 @@ func TestSpecWithTTY(t *testing.T) {
|
|||||||
if !s.Process.Terminal {
|
if !s.Process.Terminal {
|
||||||
t.Error("terminal net set WithTTY()")
|
t.Error("terminal net set WithTTY()")
|
||||||
}
|
}
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
v := s.Process.Env[len(s.Process.Env)-1]
|
v := s.Process.Env[len(s.Process.Env)-1]
|
||||||
if v != "TERM=xterm" {
|
if v != "TERM=xterm" {
|
||||||
t.Errorf("xterm not set in env for TTY")
|
t.Errorf("xterm not set in env for TTY")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if len(s.Process.Env) != 0 {
|
||||||
|
t.Fatal("Windows process args should be empty by default")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWithLinuxNamespace(t *testing.T) {
|
func TestWithLinuxNamespace(t *testing.T) {
|
||||||
@ -91,12 +102,18 @@ func TestWithLinuxNamespace(t *testing.T) {
|
|||||||
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
||||||
replacedNS := specs.LinuxNamespace{Type: specs.NetworkNamespace, Path: "/var/run/netns/test"}
|
replacedNS := specs.LinuxNamespace{Type: specs.NetworkNamespace, Path: "/var/run/netns/test"}
|
||||||
|
|
||||||
s, err := GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, WithLinuxNamespace(replacedNS))
|
var s *specs.Spec
|
||||||
|
var err error
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
s, err = GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, WithLinuxNamespace(replacedNS))
|
||||||
|
} else {
|
||||||
|
s, err = GenerateSpecWithPlatform(ctx, nil, "linux/amd64", &containers.Container{ID: t.Name()}, WithLinuxNamespace(replacedNS))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultNS := defaultNamespaces()
|
defaultNS := defaultUnixNamespaces()
|
||||||
found := false
|
found := false
|
||||||
for i, ns := range s.Linux.Namespaces {
|
for i, ns := range s.Linux.Namespaces {
|
||||||
if ns == replacedNS && !found {
|
if ns == replacedNS && !found {
|
||||||
@ -114,9 +131,16 @@ func TestWithCapabilities(t *testing.T) {
|
|||||||
|
|
||||||
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
||||||
|
|
||||||
s, err := GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()},
|
opts := []SpecOpts{
|
||||||
WithCapabilities([]string{"CAP_SYS_ADMIN"}),
|
WithCapabilities([]string{"CAP_SYS_ADMIN"}),
|
||||||
)
|
}
|
||||||
|
var s *specs.Spec
|
||||||
|
var err error
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
s, err = GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, opts...)
|
||||||
|
} else {
|
||||||
|
s, err = GenerateSpecWithPlatform(ctx, nil, "linux/amd64", &containers.Container{ID: t.Name()}, opts...)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -166,13 +190,20 @@ func TestWithPrivileged(t *testing.T) {
|
|||||||
|
|
||||||
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
||||||
|
|
||||||
s, err := GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()},
|
opts := []SpecOpts{
|
||||||
WithCapabilities(nil),
|
WithCapabilities(nil),
|
||||||
WithMounts([]specs.Mount{
|
WithMounts([]specs.Mount{
|
||||||
{Type: "cgroup", Destination: "/sys/fs/cgroup", Options: []string{"ro"}},
|
{Type: "cgroup", Destination: "/sys/fs/cgroup", Options: []string{"ro"}},
|
||||||
}),
|
}),
|
||||||
WithPrivileged,
|
WithPrivileged,
|
||||||
)
|
}
|
||||||
|
var s *specs.Spec
|
||||||
|
var err error
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
s, err = GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, opts...)
|
||||||
|
} else {
|
||||||
|
s, err = GenerateSpecWithPlatform(ctx, nil, "linux/amd64", &containers.Container{ID: t.Name()}, opts...)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
188
oci/spec_unix.go
188
oci/spec_unix.go
@ -1,188 +0,0 @@
|
|||||||
// +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"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/namespaces"
|
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
rwm = "rwm"
|
|
||||||
defaultRootfsPath = "rootfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultEnv = []string{
|
|
||||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func defaultCaps() []string {
|
|
||||||
return []string{
|
|
||||||
"CAP_CHOWN",
|
|
||||||
"CAP_DAC_OVERRIDE",
|
|
||||||
"CAP_FSETID",
|
|
||||||
"CAP_FOWNER",
|
|
||||||
"CAP_MKNOD",
|
|
||||||
"CAP_NET_RAW",
|
|
||||||
"CAP_SETGID",
|
|
||||||
"CAP_SETUID",
|
|
||||||
"CAP_SETFCAP",
|
|
||||||
"CAP_SETPCAP",
|
|
||||||
"CAP_NET_BIND_SERVICE",
|
|
||||||
"CAP_SYS_CHROOT",
|
|
||||||
"CAP_KILL",
|
|
||||||
"CAP_AUDIT_WRITE",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultNamespaces() []specs.LinuxNamespace {
|
|
||||||
return []specs.LinuxNamespace{
|
|
||||||
{
|
|
||||||
Type: specs.PIDNamespace,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: specs.IPCNamespace,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: specs.UTSNamespace,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: specs.MountNamespace,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: specs.NetworkNamespace,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func populateDefaultSpec(ctx context.Context, s *Spec, id string) error {
|
|
||||||
ns, err := namespaces.NamespaceRequired(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*s = Spec{
|
|
||||||
Version: specs.Version,
|
|
||||||
Root: &specs.Root{
|
|
||||||
Path: defaultRootfsPath,
|
|
||||||
},
|
|
||||||
Process: &specs.Process{
|
|
||||||
Env: defaultEnv,
|
|
||||||
Cwd: "/",
|
|
||||||
NoNewPrivileges: true,
|
|
||||||
User: specs.User{
|
|
||||||
UID: 0,
|
|
||||||
GID: 0,
|
|
||||||
},
|
|
||||||
Capabilities: &specs.LinuxCapabilities{
|
|
||||||
Bounding: defaultCaps(),
|
|
||||||
Permitted: defaultCaps(),
|
|
||||||
Inheritable: defaultCaps(),
|
|
||||||
Effective: defaultCaps(),
|
|
||||||
},
|
|
||||||
Rlimits: []specs.POSIXRlimit{
|
|
||||||
{
|
|
||||||
Type: "RLIMIT_NOFILE",
|
|
||||||
Hard: uint64(1024),
|
|
||||||
Soft: uint64(1024),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Mounts: []specs.Mount{
|
|
||||||
{
|
|
||||||
Destination: "/proc",
|
|
||||||
Type: "proc",
|
|
||||||
Source: "proc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Destination: "/dev",
|
|
||||||
Type: "tmpfs",
|
|
||||||
Source: "tmpfs",
|
|
||||||
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Destination: "/dev/pts",
|
|
||||||
Type: "devpts",
|
|
||||||
Source: "devpts",
|
|
||||||
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Destination: "/dev/shm",
|
|
||||||
Type: "tmpfs",
|
|
||||||
Source: "shm",
|
|
||||||
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Destination: "/dev/mqueue",
|
|
||||||
Type: "mqueue",
|
|
||||||
Source: "mqueue",
|
|
||||||
Options: []string{"nosuid", "noexec", "nodev"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Destination: "/sys",
|
|
||||||
Type: "sysfs",
|
|
||||||
Source: "sysfs",
|
|
||||||
Options: []string{"nosuid", "noexec", "nodev", "ro"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Destination: "/run",
|
|
||||||
Type: "tmpfs",
|
|
||||||
Source: "tmpfs",
|
|
||||||
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Linux: &specs.Linux{
|
|
||||||
MaskedPaths: []string{
|
|
||||||
"/proc/acpi",
|
|
||||||
"/proc/kcore",
|
|
||||||
"/proc/keys",
|
|
||||||
"/proc/latency_stats",
|
|
||||||
"/proc/timer_list",
|
|
||||||
"/proc/timer_stats",
|
|
||||||
"/proc/sched_debug",
|
|
||||||
"/sys/firmware",
|
|
||||||
"/proc/scsi",
|
|
||||||
},
|
|
||||||
ReadonlyPaths: []string{
|
|
||||||
"/proc/asound",
|
|
||||||
"/proc/bus",
|
|
||||||
"/proc/fs",
|
|
||||||
"/proc/irq",
|
|
||||||
"/proc/sys",
|
|
||||||
"/proc/sysrq-trigger",
|
|
||||||
},
|
|
||||||
CgroupsPath: filepath.Join("/", ns, id),
|
|
||||||
Resources: &specs.LinuxResources{
|
|
||||||
Devices: []specs.LinuxDeviceCgroup{
|
|
||||||
{
|
|
||||||
Allow: false,
|
|
||||||
Access: rwm,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Namespaces: defaultNamespaces(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
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"
|
|
||||||
|
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
func populateDefaultSpec(ctx context.Context, s *Spec, id string) error {
|
|
||||||
*s = Spec{
|
|
||||||
Version: specs.Version,
|
|
||||||
Root: &specs.Root{},
|
|
||||||
Process: &specs.Process{
|
|
||||||
Cwd: `C:\`,
|
|
||||||
ConsoleSize: &specs.Box{
|
|
||||||
Width: 80,
|
|
||||||
Height: 20,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Windows: &specs.Windows{
|
|
||||||
IgnoreFlushesDuringBoot: true,
|
|
||||||
Network: &specs.WindowsNetwork{
|
|
||||||
AllowUnqualifiedDNSQuery: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user