Add and implement IsolationChroot

Add an IsolationChroot that trades flexibility and isolation for being
able to do what it does in a host environment that's already isolated to
the point where we're not allowed to set up some of that isolation,
producing a result that leans more toward chroot(1) than runc(1) does.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #836
Approved by: rhatdan
This commit is contained in:
Nalin Dahyabhai 2018-05-11 13:08:18 -04:00 committed by Atomic Bot
parent a9fa129609
commit 38ef1231f2
19 changed files with 2480 additions and 32 deletions

View File

@ -17,7 +17,7 @@ LDFLAGS := -ldflags '-X main.gitCommit=${GIT_COMMIT} -X main.buildInfo=${BUILD_I
all: buildah imgtype docs
buildah: *.go imagebuildah/*.go bind/*.go cmd/buildah/*.go docker/*.go pkg/cli/*.go pkg/parse/*.go unshare/*.c unshare/*.go util/*.go
buildah: *.go imagebuildah/*.go bind/*.go chroot/*.go cmd/buildah/*.go docker/*.go pkg/cli/*.go pkg/parse/*.go unshare/*.c unshare/*.go util/*.go
$(GO) build $(LDFLAGS) -o buildah $(BUILDFLAGS) ./cmd/buildah
darwin:
@ -104,9 +104,12 @@ test-integration:
ginkgo -v tests/e2e/.
cd tests; ./test_runner.sh
tests/testreport/testreport: tests/testreport/testreport.go
$(GO) build -ldflags "-linkmode external -extldflags -static" -tags "$(AUTOTAGS) $(TAGS)" -o tests/testreport/testreport ./tests/testreport
.PHONY: test-unit
test-unit:
$(GO) test -v -race $(shell go list ./... | grep -v vendor | grep -v tests | grep -v cmd)
test-unit: tests/testreport/testreport
$(GO) test -v -tags "$(AUTOTAGS) $(TAGS)" -race $(shell go list ./... | grep -v vendor | grep -v tests | grep -v cmd)
tmp=$(shell mktemp -d) ; \
mkdir -p $$tmp/root $$tmp/runroot; \
$(GO) test -v -tags "$(AUTOTAGS) $(TAGS)" ./cmd/buildah -args -root $$tmp/root -runroot $$tmp/runroot -storage-driver vfs -signature-policy $(shell pwd)/tests/policy.json -registries-conf $(shell pwd)/tests/registries.conf

1211
chroot/run.go Normal file

File diff suppressed because it is too large Load Diff

442
chroot/run_test.go Normal file
View File

@ -0,0 +1,442 @@
// +build linux
package chroot
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"github.com/containers/storage/pkg/reexec"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/projectatomic/buildah/tests/testreport/types"
"github.com/projectatomic/buildah/util"
)
const (
reportCommand = "testreport"
)
func TestMain(m *testing.M) {
if reexec.Init() {
return
}
os.Exit(m.Run())
}
func testMinimal(t *testing.T, modify func(g *generate.Generator, rootDir, bundleDir string), verify func(t *testing.T, report *types.TestReport)) {
g, err := generate.New("linux")
if err != nil {
t.Fatalf("generate.New(%q): %v", "linux", err)
}
tempDir, err := ioutil.TempDir("", "chroot-test")
if err != nil {
t.Fatalf("ioutil.TempDir(%q, %q): %v", "", "chrootTest", err)
}
defer os.RemoveAll(tempDir)
info, err := os.Stat(tempDir)
if err != nil {
t.Fatalf("error checking permissions on %q: %v", tempDir, err)
}
if err = os.Chmod(tempDir, info.Mode()|0111); err != nil {
t.Fatalf("error loosening permissions on %q: %v", tempDir, err)
}
rootDir := filepath.Join(tempDir, "root")
if err := os.Mkdir(rootDir, 0711); err != nil {
t.Fatalf("os.Mkdir(%q): %v", rootDir, err)
}
specPath := filepath.Join("..", "tests", reportCommand, reportCommand)
specBinarySource, err := os.Open(specPath)
if err != nil {
t.Fatalf("open(%q): %v", specPath, err)
}
defer specBinarySource.Close()
specBinary, err := os.OpenFile(filepath.Join(rootDir, reportCommand), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0711)
if err != nil {
t.Fatalf("open(%q): %v", filepath.Join(rootDir, reportCommand), err)
}
io.Copy(specBinary, specBinarySource)
specBinary.Close()
g.SetRootPath(rootDir)
g.SetProcessArgs([]string{"/" + reportCommand})
bundleDir := filepath.Join(tempDir, "bundle")
if err := os.Mkdir(bundleDir, 0700); err != nil {
t.Fatalf("os.Mkdir(%q): %v", bundleDir, err)
}
if modify != nil {
modify(&g, rootDir, bundleDir)
}
uid, gid, err := util.GetHostRootIDs(g.Spec())
if err != nil {
t.Fatalf("GetHostRootIDs: %v", err)
}
if err := os.Chown(rootDir, int(uid), int(gid)); err != nil {
t.Fatalf("os.Chown(%q): %v", rootDir, err)
}
output := new(bytes.Buffer)
if err := RunUsingChroot(g.Spec(), bundleDir, new(bytes.Buffer), output, output); err != nil {
t.Fatalf("run: %v: %s", err, output.String())
}
var report types.TestReport
if json.Unmarshal(output.Bytes(), &report) != nil {
t.Fatalf("decode: %v", err)
}
if verify != nil {
verify(t, &report)
}
}
func testNoop(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
testMinimal(t, nil, nil)
}
func testMinimalSkeleton(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
},
func(t *testing.T, report *types.TestReport) {
})
}
func TestProcessTerminal(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
for _, terminal := range []bool{false, true} {
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.SetProcessTerminal(terminal)
},
func(t *testing.T, report *types.TestReport) {
if report.Spec.Process.Terminal != terminal {
t.Fatalf("expected terminal = %v, got %v", terminal, report.Spec.Process.Terminal)
}
})
}
}
func TestProcessConsoleSize(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
for _, size := range [][2]uint{{80, 25}, {132, 50}} {
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.SetProcessTerminal(true)
g.SetProcessConsoleSize(size[0], size[1])
},
func(t *testing.T, report *types.TestReport) {
if report.Spec.Process.ConsoleSize.Width != size[0] {
t.Fatalf("expected console width = %v, got %v", size[0], report.Spec.Process.ConsoleSize.Width)
}
if report.Spec.Process.ConsoleSize.Height != size[1] {
t.Fatalf("expected console height = %v, got %v", size[1], report.Spec.Process.ConsoleSize.Height)
}
})
}
}
func TestProcessUser(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
for _, id := range []uint32{0, 1000} {
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.SetProcessUID(id)
g.SetProcessGID(id + 1)
g.AddProcessAdditionalGid(id + 2)
},
func(t *testing.T, report *types.TestReport) {
if report.Spec.Process.User.UID != id {
t.Fatalf("expected UID %v, got %v", id, report.Spec.Process.User.UID)
}
if report.Spec.Process.User.GID != id+1 {
t.Fatalf("expected GID %v, got %v", id+1, report.Spec.Process.User.GID)
}
})
}
}
func TestProcessEnv(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
e := fmt.Sprintf("PARENT_TEST_PID=%d", syscall.Getpid())
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.ClearProcessEnv()
g.AddProcessEnv("PARENT_TEST_PID", fmt.Sprintf("%d", syscall.Getpid()))
},
func(t *testing.T, report *types.TestReport) {
for _, ev := range report.Spec.Process.Env {
if ev == e {
return
}
}
t.Fatalf("expected environment variable %q", e)
})
}
func TestProcessCwd(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
if err := os.Mkdir(filepath.Join(rootDir, "/no-such-directory"), 0700); err != nil {
t.Fatalf("mkdir(%q): %v", filepath.Join(rootDir, "/no-such-directory"), err)
}
g.SetProcessCwd("/no-such-directory")
},
func(t *testing.T, report *types.TestReport) {
if report.Spec.Process.Cwd != "/no-such-directory" {
t.Fatalf("expected %q, got %q", "/no-such-directory", report.Spec.Process.Cwd)
}
})
}
func TestProcessCapabilities(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.ClearProcessCapabilities()
},
func(t *testing.T, report *types.TestReport) {
if len(report.Spec.Process.Capabilities.Permitted) != 0 {
t.Fatalf("expected no permitted capabilities, got %#v", report.Spec.Process.Capabilities.Permitted)
}
})
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.ClearProcessCapabilities()
g.AddProcessCapabilityEffective("CAP_IPC_LOCK")
g.AddProcessCapabilityPermitted("CAP_IPC_LOCK")
g.AddProcessCapabilityInheritable("CAP_IPC_LOCK")
g.AddProcessCapabilityBounding("CAP_IPC_LOCK")
g.AddProcessCapabilityAmbient("CAP_IPC_LOCK")
},
func(t *testing.T, report *types.TestReport) {
if len(report.Spec.Process.Capabilities.Permitted) != 1 {
t.Fatalf("expected one permitted capability, got %#v", report.Spec.Process.Capabilities.Permitted)
}
if report.Spec.Process.Capabilities.Permitted[0] != "CAP_IPC_LOCK" {
t.Fatalf("expected one capability CAP_IPC_LOCK, got %#v", report.Spec.Process.Capabilities.Permitted)
}
})
}
func TestProcessRlimits(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
for _, limit := range []int64{100 * 1024 * 1024 * 1024, 200 * 1024 * 1024 * 1024, syscall.RLIM_INFINITY} {
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.ClearProcessRlimits()
if limit != syscall.RLIM_INFINITY {
g.AddProcessRlimits("rlimit_as", uint64(limit), uint64(limit))
}
},
func(t *testing.T, report *types.TestReport) {
var rlim *specs.POSIXRlimit
for i := range report.Spec.Process.Rlimits {
if strings.ToUpper(report.Spec.Process.Rlimits[i].Type) == "RLIMIT_AS" {
rlim = &report.Spec.Process.Rlimits[i]
}
}
if limit == syscall.RLIM_INFINITY && !(rlim == nil || (int64(rlim.Soft) == syscall.RLIM_INFINITY && int64(rlim.Hard) == syscall.RLIM_INFINITY)) {
t.Fatalf("wasn't supposed to set limit on number of open files: %#v", rlim)
}
if limit != syscall.RLIM_INFINITY && rlim == nil {
t.Fatalf("was supposed to set limit on number of open files")
}
if rlim != nil {
if int64(rlim.Soft) != limit {
t.Fatalf("soft limit was set to %d, not %d", rlim.Soft, limit)
}
if int64(rlim.Hard) != limit {
t.Fatalf("hard limit was set to %d, not %d", rlim.Hard, limit)
}
}
})
}
}
func TestProcessNoNewPrivileges(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
for _, nope := range []bool{false, true} {
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.SetProcessNoNewPrivileges(nope)
},
func(t *testing.T, report *types.TestReport) {
if report.Spec.Process.NoNewPrivileges != nope {
t.Fatalf("expected no-new-prives to be %v, got %v", nope, report.Spec.Process.NoNewPrivileges)
}
})
}
}
func TestProcessOOMScoreAdj(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
for _, adj := range []int{0, 1, 2, 3} {
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.SetProcessOOMScoreAdj(adj)
},
func(t *testing.T, report *types.TestReport) {
adjusted := 0
if report.Spec.Process.OOMScoreAdj != nil {
adjusted = *report.Spec.Process.OOMScoreAdj
}
if adjusted != adj {
t.Fatalf("expected oom-score-adj to be %v, got %v", adj, adjusted)
}
})
}
}
func TestHostname(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
hostname := fmt.Sprintf("host%d", syscall.Getpid())
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.SetHostname(hostname)
},
func(t *testing.T, report *types.TestReport) {
if report.Spec.Hostname != hostname {
t.Fatalf("expected %q, got %q", hostname, report.Spec.Hostname)
}
})
}
func TestMounts(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.AddMount(specs.Mount{
Source: "tmpfs",
Destination: "/was-not-there-before",
Type: "tmpfs",
Options: []string{"ro,size=0"},
})
},
func(t *testing.T, report *types.TestReport) {
found := false
for _, mount := range report.Spec.Mounts {
if mount.Destination == "/was-not-there-before" && mount.Type == "tmpfs" {
found = true
}
}
if !found {
t.Fatal("added mount not found")
}
})
}
func TestLinuxIDMapping(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.ClearLinuxUIDMappings()
g.ClearLinuxGIDMappings()
g.AddLinuxUIDMapping(uint32(syscall.Getuid()), 0, 1)
g.AddLinuxGIDMapping(uint32(syscall.Getgid()), 0, 1)
},
func(t *testing.T, report *types.TestReport) {
if len(report.Spec.Linux.UIDMappings) != 1 {
t.Fatalf("expected 1 uid mapping, got %q", len(report.Spec.Linux.UIDMappings))
}
if report.Spec.Linux.UIDMappings[0].HostID != uint32(syscall.Getuid()) {
t.Fatalf("expected host uid mapping to be %d, got %d", syscall.Getuid(), report.Spec.Linux.UIDMappings[0].HostID)
}
if report.Spec.Linux.UIDMappings[0].ContainerID != 0 {
t.Fatalf("expected container uid mapping to be 0, got %d", report.Spec.Linux.UIDMappings[0].ContainerID)
}
if report.Spec.Linux.UIDMappings[0].Size != 1 {
t.Fatalf("expected container uid map size to be 1, got %d", report.Spec.Linux.UIDMappings[0].Size)
}
if report.Spec.Linux.GIDMappings[0].HostID != uint32(syscall.Getgid()) {
t.Fatalf("expected host uid mapping to be %d, got %d", syscall.Getgid(), report.Spec.Linux.GIDMappings[0].HostID)
}
if report.Spec.Linux.GIDMappings[0].ContainerID != 0 {
t.Fatalf("expected container gid mapping to be 0, got %d", report.Spec.Linux.GIDMappings[0].ContainerID)
}
if report.Spec.Linux.GIDMappings[0].Size != 1 {
t.Fatalf("expected container gid map size to be 1, got %d", report.Spec.Linux.GIDMappings[0].Size)
}
})
}
func TestLinuxIDMappingShift(t *testing.T) {
if syscall.Getuid() != 0 {
t.Skip("tests need to be run as root")
}
testMinimal(t,
func(g *generate.Generator, rootDir, bundleDir string) {
g.ClearLinuxUIDMappings()
g.ClearLinuxGIDMappings()
g.AddLinuxUIDMapping(uint32(syscall.Getuid())+1, 0, 1)
g.AddLinuxGIDMapping(uint32(syscall.Getgid())+1, 0, 1)
},
func(t *testing.T, report *types.TestReport) {
if len(report.Spec.Linux.UIDMappings) != 1 {
t.Fatalf("expected 1 uid mapping, got %q", len(report.Spec.Linux.UIDMappings))
}
if report.Spec.Linux.UIDMappings[0].HostID != uint32(syscall.Getuid()+1) {
t.Fatalf("expected host uid mapping to be %d, got %d", syscall.Getuid()+1, report.Spec.Linux.UIDMappings[0].HostID)
}
if report.Spec.Linux.UIDMappings[0].ContainerID != 0 {
t.Fatalf("expected container uid mapping to be 0, got %d", report.Spec.Linux.UIDMappings[0].ContainerID)
}
if report.Spec.Linux.UIDMappings[0].Size != 1 {
t.Fatalf("expected container uid map size to be 1, got %d", report.Spec.Linux.UIDMappings[0].Size)
}
if report.Spec.Linux.GIDMappings[0].HostID != uint32(syscall.Getgid()+1) {
t.Fatalf("expected host uid mapping to be %d, got %d", syscall.Getgid()+1, report.Spec.Linux.GIDMappings[0].HostID)
}
if report.Spec.Linux.GIDMappings[0].ContainerID != 0 {
t.Fatalf("expected container gid mapping to be 0, got %d", report.Spec.Linux.GIDMappings[0].ContainerID)
}
if report.Spec.Linux.GIDMappings[0].Size != 1 {
t.Fatalf("expected container gid map size to be 1, got %d", report.Spec.Linux.GIDMappings[0].Size)
}
})
}

142
chroot/seccomp.go Normal file
View File

@ -0,0 +1,142 @@
// +build linux,seccomp
package chroot
import (
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
libseccomp "github.com/seccomp/libseccomp-golang"
"github.com/sirupsen/logrus"
)
// setSeccomp sets the seccomp filter for ourselves and any processes that we'll start.
func setSeccomp(spec *specs.Spec) error {
logrus.Debugf("setting seccomp configuration")
if spec.Linux.Seccomp == nil {
return nil
}
mapAction := func(specAction specs.LinuxSeccompAction) libseccomp.ScmpAction {
switch specAction {
case specs.ActKill:
return libseccomp.ActKill
case specs.ActTrap:
return libseccomp.ActTrap
case specs.ActErrno:
return libseccomp.ActErrno
case specs.ActTrace:
return libseccomp.ActTrace
case specs.ActAllow:
return libseccomp.ActAllow
}
return libseccomp.ActInvalid
}
mapArch := func(specArch specs.Arch) libseccomp.ScmpArch {
switch specArch {
case specs.ArchX86:
return libseccomp.ArchX86
case specs.ArchX86_64:
return libseccomp.ArchAMD64
case specs.ArchX32:
return libseccomp.ArchX32
case specs.ArchARM:
return libseccomp.ArchARM
case specs.ArchAARCH64:
return libseccomp.ArchARM64
case specs.ArchMIPS:
return libseccomp.ArchMIPS
case specs.ArchMIPS64:
return libseccomp.ArchMIPS64
case specs.ArchMIPS64N32:
return libseccomp.ArchMIPS64N32
case specs.ArchMIPSEL:
return libseccomp.ArchMIPSEL
case specs.ArchMIPSEL64:
return libseccomp.ArchMIPSEL64
case specs.ArchMIPSEL64N32:
return libseccomp.ArchMIPSEL64N32
case specs.ArchPPC:
return libseccomp.ArchPPC
case specs.ArchPPC64:
return libseccomp.ArchPPC64
case specs.ArchPPC64LE:
return libseccomp.ArchPPC64LE
case specs.ArchS390:
return libseccomp.ArchS390
case specs.ArchS390X:
return libseccomp.ArchS390X
case specs.ArchPARISC:
/* fallthrough */ /* for now */
case specs.ArchPARISC64:
/* fallthrough */ /* for now */
}
return libseccomp.ArchInvalid
}
mapOp := func(op specs.LinuxSeccompOperator) libseccomp.ScmpCompareOp {
switch op {
case specs.OpNotEqual:
return libseccomp.CompareNotEqual
case specs.OpLessThan:
return libseccomp.CompareLess
case specs.OpLessEqual:
return libseccomp.CompareLessOrEqual
case specs.OpEqualTo:
return libseccomp.CompareEqual
case specs.OpGreaterEqual:
return libseccomp.CompareGreaterEqual
case specs.OpGreaterThan:
return libseccomp.CompareGreater
case specs.OpMaskedEqual:
return libseccomp.CompareMaskedEqual
}
return libseccomp.CompareInvalid
}
filter, err := libseccomp.NewFilter(mapAction(spec.Linux.Seccomp.DefaultAction))
if err != nil {
return errors.Wrapf(err, "error creating seccomp filter with default action %q", spec.Linux.Seccomp.DefaultAction)
}
for _, arch := range spec.Linux.Seccomp.Architectures {
if err = filter.AddArch(mapArch(arch)); err != nil {
return errors.Wrapf(err, "error adding architecture %q(%q) to seccomp filter", arch, mapArch(arch))
}
}
for _, rule := range spec.Linux.Seccomp.Syscalls {
scnames := make(map[libseccomp.ScmpSyscall]string)
for _, name := range rule.Names {
scnum, err := libseccomp.GetSyscallFromName(name)
if err != nil {
logrus.Debugf("error mapping syscall %q to a syscall, ignoring %q rule for %q", name, rule.Action)
continue
}
scnames[scnum] = name
}
for scnum := range scnames {
if len(rule.Args) == 0 {
if err = filter.AddRule(scnum, mapAction(rule.Action)); err != nil {
return errors.Wrapf(err, "error adding a rule (%q:%q) to seccomp filter", scnames[scnum], rule.Action)
}
continue
}
var conditions []libseccomp.ScmpCondition
for _, arg := range rule.Args {
condition, err := libseccomp.MakeCondition(arg.Index, mapOp(arg.Op), arg.Value, arg.ValueTwo)
if err != nil {
return errors.Wrapf(err, "error building a seccomp condition %d:%v:%d:%d", arg.Index, arg.Op, arg.Value, arg.ValueTwo)
}
conditions = append(conditions, condition)
}
if err = filter.AddRuleConditional(scnum, mapAction(rule.Action), conditions); err != nil {
return errors.Wrapf(err, "error adding a conditional rule (%q:%q) to seccomp filter", scnames[scnum], rule.Action)
}
}
}
if err = filter.SetNoNewPrivsBit(spec.Process.NoNewPrivileges); err != nil {
return errors.Wrapf(err, "error setting no-new-privileges bit to %v", spec.Process.NoNewPrivileges)
}
err = filter.Load()
filter.Release()
if err != nil {
return errors.Wrapf(err, "error activating seccomp filter")
}
return nil
}

View File

@ -0,0 +1,15 @@
// +build !linux !seccomp
package chroot
import (
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
func setSeccomp(spec *specs.Spec) error {
if spec.Linux.Seccomp != nil {
return errors.New("configured a seccomp filter without seccomp support?")
}
return nil
}

22
chroot/selinux.go Normal file
View File

@ -0,0 +1,22 @@
// +build linux,selinux
package chroot
import (
"github.com/opencontainers/runtime-spec/specs-go"
selinux "github.com/opencontainers/selinux/go-selinux"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// setSelinuxLabel sets the process label for child processes that we'll start.
func setSelinuxLabel(spec *specs.Spec) error {
logrus.Debugf("setting selinux label")
if spec.Process.SelinuxLabel != "" && selinux.EnforceMode() != selinux.Disabled {
if err := label.SetProcessLabel(spec.Process.SelinuxLabel); err != nil {
return errors.Wrapf(err, "error setting process label to %q", spec.Process.SelinuxLabel)
}
}
return nil
}

View File

@ -0,0 +1,18 @@
// +build !linux !selinux
package chroot
import (
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
func setSelinuxLabel(spec *specs.Spec) error {
if spec.Linux.MountLabel != "" {
return errors.New("configured an SELinux mount label without SELinux support?")
}
if spec.Process.SelinuxLabel != "" {
return errors.New("configured an SELinux process label without SELinux support?")
}
return nil
}

12
chroot/unsupported.go Normal file
View File

@ -0,0 +1,12 @@
// +build !linux
package chroot
import (
"github.com/pkg/errors"
)
// RunUsingChroot is not supported.
func RunUsingChroot(spec *specs.Spec, bundlePath string, stdin io.Reader, stdout, stderr io.Writer) (err error) {
return errors.Errorf("--isolation chroot is not supported on this platform")
}

15
chroot/util.go Normal file
View File

@ -0,0 +1,15 @@
// +build linux
package chroot
func dedupeStringSlice(slice []string) []string {
done := make([]string, 0, len(slice))
m := make(map[string]struct{})
for _, s := range slice {
if _, present := m[s]; !present {
m[s] = struct{}{}
done = append(done, s)
}
}
return done
}

View File

@ -216,7 +216,9 @@ another process.
**--isolation** *type*
Controls what type of isolation is used when processing RUN instructions.
Recognized types include *oci* (OCI-compatible runtime, the default).
Recognized types include *oci* (OCI-compatible runtime, the default) and
*chroot* (an internal wrapper that leans more toward chroot(1) than container
technology).
Note: You can also override the default isolation type by setting the
BUILDAH\_ISOLATION environment variable. `export BUILDAH_ISOLATION=oci`

View File

@ -181,7 +181,9 @@ another process.
**--isolation** *type*
Controls what type of isolation will be used by default by `buildah run`.
Recognized types include *oci* (OCI-compatible runtime, the default).
Recognized types include *oci* (OCI-compatible runtime, the default) and
*chroot* (an internal wrapper that leans more toward chroot(1) than container
technology).
Note: You can also override the default isolation type by setting the
BUILDAH\_ISOLATION environment variable. `export BUILDAH_ISOLATION=oci`

View File

@ -66,7 +66,9 @@ process.
**--isolation** *type*
Controls what type of isolation is used for running the process.
Recognized types include *oci* (OCI-compatible runtime, the default).
Recognized types include *oci* (OCI-compatible runtime, the default) and
*chroot* (an internal wrapper that leans more toward chroot(1) than container
technology).
Note: You can also override the default isolation type by setting the
BUILDAH\_ISOLATION environment variable. `export BUILDAH_ISOLATION=oci`

View File

@ -531,12 +531,17 @@ func NamespaceOptions(c *cli.Context) (namespaceOptions buildah.NamespaceOptions
return options, policy, nil
}
func defaultIsolation() buildah.Isolation {
isolation := os.Getenv("BUILDAH_ISOLATION")
if strings.HasPrefix(strings.ToLower(isolation), "oci") {
return buildah.IsolationOCI
func defaultIsolation() (buildah.Isolation, error) {
isolation, isSet := os.LookupEnv("BUILDAH_ISOLATION")
if isSet {
if strings.HasPrefix(strings.ToLower(isolation), "oci") {
return buildah.IsolationOCI, nil
} else if strings.HasPrefix(strings.ToLower(isolation), "chroot") {
return buildah.IsolationChroot, nil
}
return 0, errors.Errorf("unrecognized $BUILDAH_ISOLATION value %q", isolation)
}
return buildah.IsolationDefault
return buildah.IsolationDefault, nil
}
// IsolationOption parses the --isolation flag.
@ -544,9 +549,11 @@ func IsolationOption(c *cli.Context) (buildah.Isolation, error) {
if c.String("isolation") != "" {
if strings.HasPrefix(strings.ToLower(c.String("isolation")), "oci") {
return buildah.IsolationOCI, nil
} else if strings.HasPrefix(strings.ToLower(c.String("isolation")), "chroot") {
return buildah.IsolationChroot, nil
} else {
return buildah.IsolationDefault, errors.Errorf("unrecognized isolation type %q", c.String("isolation"))
}
}
return defaultIsolation(), nil
return defaultIsolation()
}

22
run.go
View File

@ -29,6 +29,7 @@ import (
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/projectatomic/buildah/bind"
"github.com/projectatomic/buildah/chroot"
"github.com/projectatomic/buildah/util"
"github.com/projectatomic/libpod/pkg/secrets"
"github.com/sirupsen/logrus"
@ -40,7 +41,7 @@ const (
// DefaultWorkingDir is used if none was specified.
DefaultWorkingDir = "/"
// runUsingRuntimeCommand is a command we use as a key for reexec
runUsingRuntimeCommand = Package + "-runtime"
runUsingRuntimeCommand = Package + "-oci-runtime"
)
// TerminalPolicy takes the value DefaultTerminal, WithoutTerminal, or WithTerminal.
@ -112,6 +113,9 @@ const (
IsolationDefault Isolation = iota
// IsolationOCI is a proper OCI runtime.
IsolationOCI
// IsolationChroot is a more chroot-like environment: less isolation,
// but with fewer requirements.
IsolationChroot
)
// String converts a Isolation into a string.
@ -121,6 +125,8 @@ func (i Isolation) String() string {
return "IsolationDefault"
case IsolationOCI:
return "IsolationOCI"
case IsolationChroot:
return "IsolationChroot"
}
return fmt.Sprintf("unrecognized isolation type %d", i)
}
@ -129,10 +135,10 @@ func (i Isolation) String() string {
type RunOptions struct {
// Hostname is the hostname we set for the running container.
Hostname string
// Isolation is either IsolationDefault or IsolationOCI.
// Isolation is either IsolationDefault, IsolationOCI, or IsolationChroot.
Isolation Isolation
// Runtime is the name of the command to run. It should accept the same arguments
// that runc does, and produce similar output.
// Runtime is the name of the runtime to run. It should accept the
// same arguments that runc does, and produce similar output.
Runtime string
// Args adds global arguments for the runtime.
Args []string
@ -974,8 +980,8 @@ func (b *Builder) Run(command []string, options RunOptions) error {
return err
}
defer func() {
if err2 := b.Unmount(); err2 != nil {
logrus.Errorf("error unmounting container: %v", err2)
if err := b.Unmount(); err != nil {
logrus.Errorf("error unmounting container: %v", err)
}
}()
g.SetRootPath(mountPoint)
@ -1074,6 +1080,8 @@ func (b *Builder) Run(command []string, options RunOptions) error {
switch isolation {
case IsolationOCI:
err = b.runUsingRuntimeSubproc(options, configureNetwork, configureNetworks, spec, mountPoint, path, Package+"-"+filepath.Base(path))
case IsolationChroot:
err = chroot.RunUsingChroot(spec, path, options.Stdin, options.Stdout, options.Stderr)
default:
err = errors.Errorf("don't know how to run this command")
}
@ -1682,7 +1690,7 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy
}
// If the descriptor was closed elsewhere, remove it from our list.
if pollFd.Revents&unix.POLLNVAL != 0 {
logrus.Debugf("error polling descriptor %d: closed?", pollFd.Fd)
logrus.Debugf("error polling descriptor %s: closed?", readDesc[int(pollFd.Fd)])
removes[int(pollFd.Fd)] = struct{}{}
}
// If the POLLIN flag isn't set, then there's no data to be read from this descriptor.

View File

@ -193,8 +193,14 @@ load helpers
}
@test "from cpu-period test" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
if ! which runc ; then
skip
fi
cid=$(buildah from --cpu-period=5000 --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah run $cid cat /sys/fs/cgroup/cpu/cpu.cfs_period_us
run buildah --debug=false run $cid cat /sys/fs/cgroup/cpu/cpu.cfs_period_us
echo $output
[ "$status" -eq 0 ]
[[ "$output" =~ "5000" ]]
@ -202,8 +208,14 @@ load helpers
}
@test "from cpu-quota test" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
if ! which runc ; then
skip
fi
cid=$(buildah from --cpu-quota=5000 --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah run $cid cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
run buildah --debug=false run $cid cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ 5000 ]]
@ -211,8 +223,14 @@ load helpers
}
@test "from cpu-shares test" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
if ! which runc ; then
skip
fi
cid=$(buildah from --cpu-shares=2 --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah run $cid cat /sys/fs/cgroup/cpu/cpu.shares
run buildah --debug=false run $cid cat /sys/fs/cgroup/cpu/cpu.shares
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ 2 ]]
@ -220,8 +238,14 @@ load helpers
}
@test "from cpuset-cpus test" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
if ! which runc ; then
skip
fi
cid=$(buildah from --cpuset-cpus=0 --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah run $cid cat /sys/fs/cgroup/cpuset/cpuset.cpus
run buildah --debug=false run $cid cat /sys/fs/cgroup/cpuset/cpuset.cpus
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ 0 ]]
@ -229,8 +253,14 @@ load helpers
}
@test "from cpuset-mems test" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
if ! which runc ; then
skip
fi
cid=$(buildah from --cpuset-mems=0 --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah run $cid cat /sys/fs/cgroup/cpuset/cpuset.mems
run buildah --debug=false run $cid cat /sys/fs/cgroup/cpuset/cpuset.mems
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ 0 ]]
@ -238,8 +268,14 @@ load helpers
}
@test "from memory test" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
if ! which runc ; then
skip
fi
cid=$(buildah from --memory=40m --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah run $cid cat /sys/fs/cgroup/memory/memory.limit_in_bytes
run buildah --debug=false run $cid cat /sys/fs/cgroup/memory/memory.limit_in_bytes
echo $output
[ "$status" -eq 0 ]
[[ "$output" =~ 41943040 ]]
@ -247,8 +283,11 @@ load helpers
}
@test "from volume test" {
if ! which runc ; then
skip
fi
cid=$(buildah from --volume=${TESTDIR}:/myvol --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah run $cid -- cat /proc/mounts
run buildah --debug=false run $cid -- cat /proc/mounts
echo $output
[ "$status" -eq 0 ]
[[ "$output" =~ /myvol ]]
@ -256,8 +295,11 @@ load helpers
}
@test "from volume ro test" {
if ! which runc ; then
skip
fi
cid=$(buildah from --volume=${TESTDIR}:/myvol:ro --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah run $cid -- cat /proc/mounts
run buildah --debug=false run $cid -- cat /proc/mounts
echo $output
[ "$status" -eq 0 ]
[[ "$output" =~ /myvol ]]
@ -265,8 +307,14 @@ load helpers
}
@test "from shm-size test" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
if ! which runc ; then
skip
fi
cid=$(buildah from --shm-size=80m --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah run $cid -- df -h
run buildah --debug=false run $cid -- df -h
echo $output
[ "$status" -eq 0 ]
[[ "$output" =~ 80 ]]
@ -274,6 +322,9 @@ load helpers
}
@test "from add-host test" {
if ! which runc ; then
skip
fi
cid=$(buildah from --add-host=localhost:127.0.0.1 --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah run $cid -- cat /etc/hosts
echo $output

View File

@ -3,6 +3,9 @@
load helpers
@test "user-and-network-namespace" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
mkdir -p $TESTDIR/no-cni-configs
RUNOPTS="--cni-config-dir=${TESTDIR}/no-cni-configs ${RUNC_BINARY:+--runtime $RUNC_BINARY}"
# Check if we're running in an environment that can even test this.
@ -170,7 +173,9 @@ load helpers
[ "$output" != "" ]
case x"$map" in
x)
[ "$output" == "$mynamespace" ]
if test "$BUILDAH_ISOLATION" != "chroot" ; then
[ "$output" == "$mynamespace" ]
fi
;;
*)
[ "$output" != "$mynamespace" ]
@ -300,30 +305,51 @@ general_namespace() {
}
@test "ipc-namespace" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
general_namespace ipc
}
@test "net-namespace" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
general_namespace net
}
@test "network-namespace" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
general_namespace net network
}
@test "pid-namespace" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
general_namespace pid
}
@test "user-namespace" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
general_namespace user userns
}
@test "uts-namespace" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
general_namespace uts
}
@test "combination-namespaces" {
if test "$BUILDAH_ISOLATION" = "chroot" ; then
skip
fi
# mnt is always per-container, cgroup isn't a thing runc lets us configure
for ipc in host container ; do
for net in host container ; do

View File

@ -13,21 +13,21 @@ load helpers
# Create a container and read its context as a baseline.
cid=$(buildah --debug=false from --quiet --signature-policy ${TESTSDIR}/policy.json $image)
run buildah --debug=false run $cid sh -c 'tr \\0 \\n < /proc/1/attr/current'
run buildah --debug=false run $cid sh -c 'tr \\0 \\n < /proc/self/attr/current'
echo "$output"
[ "$status" -eq 0 ]
[ "$output" != "" ]
firstlabel="$output"
# Ensure that we label the same container consistently across multiple "run" instructions.
run buildah --debug=false run $cid sh -c 'tr \\0 \\n < /proc/1/attr/current'
run buildah --debug=false run $cid sh -c 'tr \\0 \\n < /proc/self/attr/current'
echo "$output"
[ "$status" -eq 0 ]
[ "$output" == "$firstlabel" ]
# Ensure that different containers get different labels.
cid1=$(buildah --debug=false from --quiet --signature-policy ${TESTSDIR}/policy.json $image)
run buildah --debug=false run $cid1 sh -c 'tr \\0 \\n < /proc/1/attr/current'
run buildah --debug=false run $cid1 sh -c 'tr \\0 \\n < /proc/self/attr/current'
echo "$output"
[ "$status" -eq 0 ]
[ "$output" != "$firstlabel" ]

View File

@ -0,0 +1,460 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/containers/storage/pkg/mount"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/projectatomic/buildah/tests/testreport/types"
"github.com/sirupsen/logrus"
"github.com/syndtr/gocapability/capability"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/sys/unix"
)
func getVersion(r *types.TestReport) error {
r.Spec.Version = fmt.Sprintf("%d.%d.%d%s", specs.VersionMajor, specs.VersionMinor, specs.VersionPatch, specs.VersionDev)
return nil
}
func getHostname(r *types.TestReport) error {
hostname, err := os.Hostname()
if err != nil {
return errors.Wrapf(err, "error reading hostname")
}
r.Spec.Hostname = hostname
return nil
}
func getProcessTerminal(r *types.TestReport) error {
r.Spec.Process.Terminal = terminal.IsTerminal(unix.Stdin)
return nil
}
func getProcessConsoleSize(r *types.TestReport) error {
if terminal.IsTerminal(unix.Stdin) {
winsize, err := unix.IoctlGetWinsize(unix.Stdin, unix.TIOCGWINSZ)
if err != nil {
return errors.Wrapf(err, "error reading size of terminal on stdin")
}
if r.Spec.Process.ConsoleSize == nil {
r.Spec.Process.ConsoleSize = new(specs.Box)
}
r.Spec.Process.ConsoleSize.Height = uint(winsize.Row)
r.Spec.Process.ConsoleSize.Width = uint(winsize.Col)
}
return nil
}
func getProcessUser(r *types.TestReport) error {
r.Spec.Process.User.UID = uint32(unix.Getuid())
r.Spec.Process.User.GID = uint32(unix.Getgid())
groups, err := unix.Getgroups()
if err != nil {
return errors.Wrapf(err, "error reading supplemental groups list")
}
for _, gid := range groups {
r.Spec.Process.User.AdditionalGids = append(r.Spec.Process.User.AdditionalGids, uint32(gid))
}
return nil
}
func getProcessArgs(r *types.TestReport) error {
r.Spec.Process.Args = append([]string{}, os.Args...)
return nil
}
func getProcessEnv(r *types.TestReport) error {
r.Spec.Process.Env = append([]string{}, os.Environ()...)
return nil
}
func getProcessCwd(r *types.TestReport) error {
cwd := make([]byte, 8192)
n, err := unix.Getcwd(cwd)
if err != nil {
return errors.Wrapf(err, "error determining current working directory")
}
for n > 0 && cwd[n-1] == 0 {
n--
}
r.Spec.Process.Cwd = string(cwd[:n])
return nil
}
func getProcessCapabilities(r *types.TestReport) error {
capabilities, err := capability.NewPid(0)
if err != nil {
return errors.Wrapf(err, "error reading current capabilities")
}
if r.Spec.Process.Capabilities == nil {
r.Spec.Process.Capabilities = new(specs.LinuxCapabilities)
}
caplistMap := map[capability.CapType]*[]string{
capability.EFFECTIVE: &r.Spec.Process.Capabilities.Effective,
capability.PERMITTED: &r.Spec.Process.Capabilities.Permitted,
capability.INHERITABLE: &r.Spec.Process.Capabilities.Inheritable,
capability.BOUNDING: &r.Spec.Process.Capabilities.Bounding,
capability.AMBIENT: &r.Spec.Process.Capabilities.Ambient,
}
for capType, capList := range caplistMap {
for _, cap := range capability.List() {
if capabilities.Get(capType, cap) {
*capList = append(*capList, strings.ToUpper("cap_"+cap.String()))
}
}
}
return nil
}
func getProcessRLimits(r *types.TestReport) error {
limitsMap := map[string]int{
"RLIMIT_AS": unix.RLIMIT_AS,
"RLIMIT_CORE": unix.RLIMIT_CORE,
"RLIMIT_CPU": unix.RLIMIT_CPU,
"RLIMIT_DATA": unix.RLIMIT_DATA,
"RLIMIT_FSIZE": unix.RLIMIT_FSIZE,
"RLIMIT_LOCKS": unix.RLIMIT_LOCKS,
"RLIMIT_MEMLOCK": unix.RLIMIT_MEMLOCK,
"RLIMIT_MSGQUEUE": unix.RLIMIT_MSGQUEUE,
"RLIMIT_NICE": unix.RLIMIT_NICE,
"RLIMIT_NOFILE": unix.RLIMIT_NOFILE,
"RLIMIT_NPROC": unix.RLIMIT_NPROC,
"RLIMIT_RSS": unix.RLIMIT_RSS,
"RLIMIT_RTPRIO": unix.RLIMIT_RTPRIO,
"RLIMIT_RTTIME": unix.RLIMIT_RTTIME,
"RLIMIT_SIGPENDING": unix.RLIMIT_SIGPENDING,
"RLIMIT_STACK": unix.RLIMIT_STACK,
}
for resourceName, resource := range limitsMap {
var rlim unix.Rlimit
if err := unix.Getrlimit(resource, &rlim); err != nil {
return errors.Wrapf(err, "error reading %s limit", resourceName)
}
if rlim.Cur == unix.RLIM_INFINITY && rlim.Max == unix.RLIM_INFINITY {
continue
}
rlimit := specs.POSIXRlimit{
Type: resourceName,
Soft: rlim.Cur,
Hard: rlim.Max,
}
found := false
for i := range r.Spec.Process.Rlimits {
if r.Spec.Process.Rlimits[i].Type == resourceName {
r.Spec.Process.Rlimits[i] = rlimit
found = true
}
}
if !found {
r.Spec.Process.Rlimits = append(r.Spec.Process.Rlimits, rlimit)
}
}
return nil
}
func getProcessNoNewPrivileges(r *types.TestReport) error {
// We'd scan /proc/self/status here, but the "NoNewPrivs" line wasn't added until 4.10,
// and we want to succeed on older kernels.
r1, _, err := unix.Syscall(unix.SYS_PRCTL, unix.PR_GET_NO_NEW_PRIVS, 0, 0)
if err != 0 {
return errors.Wrapf(err, "error reading no-new-privs bit")
}
r.Spec.Process.NoNewPrivileges = (r1 != 0)
return nil
}
func getProcessAppArmorProfile(r *types.TestReport) error {
// TODO
return nil
}
func getProcessOOMScoreAdjust(r *types.TestReport) error {
node := "/proc/self/oom_score_adj"
score, err := ioutil.ReadFile(node)
if err != nil {
return errors.Wrapf(err, "error reading %q", node)
}
fields := strings.Fields(string(score))
if len(fields) != 1 {
return errors.Wrapf(err, "badly formatted line %q in %q", string(score), node)
}
oom, err := strconv.Atoi(fields[0])
if err != nil {
return errors.Wrapf(err, "error parsing %q in line %q in %q", fields[0], string(score), node)
}
if oom != 0 {
r.Spec.Process.OOMScoreAdj = &oom
}
return nil
}
func getProcessSeLinuxLabel(r *types.TestReport) error {
// TODO
return nil
}
func getProcess(r *types.TestReport) error {
if r.Spec.Process == nil {
r.Spec.Process = new(specs.Process)
}
if err := getProcessTerminal(r); err != nil {
return err
}
if err := getProcessConsoleSize(r); err != nil {
return err
}
if err := getProcessUser(r); err != nil {
return err
}
if err := getProcessArgs(r); err != nil {
return err
}
if err := getProcessEnv(r); err != nil {
return err
}
if err := getProcessCwd(r); err != nil {
return err
}
if err := getProcessCapabilities(r); err != nil {
return err
}
if err := getProcessRLimits(r); err != nil {
return err
}
if err := getProcessNoNewPrivileges(r); err != nil {
return err
}
if err := getProcessAppArmorProfile(r); err != nil {
return err
}
if err := getProcessOOMScoreAdjust(r); err != nil {
return err
}
if err := getProcessSeLinuxLabel(r); err != nil {
return err
}
return nil
}
func getMounts(r *types.TestReport) error {
infos, err := mount.GetMounts()
if err != nil {
return errors.Wrapf(err, "reading current list of mounts")
}
for _, info := range infos {
mount := specs.Mount{
Destination: info.Mountpoint,
Type: info.Fstype,
Source: info.Source,
Options: strings.Split(info.Opts, ","),
}
r.Spec.Mounts = append(r.Spec.Mounts, mount)
}
return nil
}
func getLinuxIDMappings(r *types.TestReport) error {
getIDMapping := func(node string) ([]specs.LinuxIDMapping, error) {
var mappings []specs.LinuxIDMapping
mapfile, err := os.Open(node)
if err != nil {
return nil, errors.Wrapf(err, "error opening %q", node)
}
defer mapfile.Close()
scanner := bufio.NewScanner(mapfile)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) != 3 {
return nil, errors.Wrapf(err, "badly formatted line %q in %q", line, node)
}
cid, err := strconv.ParseUint(fields[0], 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "error parsing %q in line %q in %q", fields[0], line, node)
}
hid, err := strconv.ParseUint(fields[1], 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "error parsing %q in line %q in %q", fields[1], line, node)
}
size, err := strconv.ParseUint(fields[2], 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "error parsing %q in line %q in %q", fields[2], line, node)
}
mappings = append(mappings, specs.LinuxIDMapping{ContainerID: uint32(cid), HostID: uint32(hid), Size: uint32(size)})
}
return mappings, nil
}
uidmap, err := getIDMapping("/proc/self/uid_map")
if err != nil {
return err
}
gidmap, err := getIDMapping("/proc/self/gid_map")
if err != nil {
return err
}
r.Spec.Linux.UIDMappings = uidmap
r.Spec.Linux.GIDMappings = gidmap
return nil
}
func getLinuxSysctl(r *types.TestReport) error {
if r.Spec.Linux.Sysctl == nil {
r.Spec.Linux.Sysctl = make(map[string]string)
}
walk := func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
value, err := ioutil.ReadFile(path)
if err != nil {
if pe, ok := err.(*os.PathError); ok {
if errno, ok := pe.Err.(syscall.Errno); ok {
switch errno {
case syscall.EACCES, syscall.EINVAL, syscall.EIO, syscall.EPERM:
return nil
}
}
}
return errors.Wrapf(err, "error reading sysctl %q", path)
}
if strings.HasPrefix(path, "/proc/sys/") {
path = path[10:]
}
sysctl := strings.Replace(path, "/", ".", -1)
val := strings.TrimRight(string(value), "\r\n")
if strings.ContainsAny(val, "\r\n") {
val = string(value)
}
r.Spec.Linux.Sysctl[sysctl] = val
return nil
}
if err := filepath.Walk("/proc/sys", walk); err != nil {
return err
}
return nil
}
func getLinuxResources(r *types.TestReport) error {
// TODO
return nil
}
func getLinuxCgroupsPath(r *types.TestReport) error {
// TODO
return nil
}
func getLinuxNamespaces(r *types.TestReport) error {
// TODO
return nil
}
func getLinuxDevices(r *types.TestReport) error {
// TODO
return nil
}
func getLinuxRootfsPropagation(r *types.TestReport) error {
// TODO
return nil
}
func getLinuxMaskedPaths(r *types.TestReport) error {
// TODO
return nil
}
func getLinuxReadOnlyPaths(r *types.TestReport) error {
// TODO
return nil
}
func getLinuxMountLabel(r *types.TestReport) error {
// TODO
return nil
}
func getLinuxIntelRdt(r *types.TestReport) error {
// TODO
return nil
}
func getLinux(r *types.TestReport) error {
if r.Spec.Linux == nil {
r.Spec.Linux = new(specs.Linux)
}
if err := getLinuxIDMappings(r); err != nil {
return err
}
if err := getLinuxSysctl(r); err != nil {
return err
}
if err := getLinuxResources(r); err != nil {
return err
}
if err := getLinuxCgroupsPath(r); err != nil {
return err
}
if err := getLinuxNamespaces(r); err != nil {
return err
}
if err := getLinuxDevices(r); err != nil {
return err
}
if err := getLinuxRootfsPropagation(r); err != nil {
return err
}
if err := getLinuxMaskedPaths(r); err != nil {
return err
}
if err := getLinuxReadOnlyPaths(r); err != nil {
return err
}
if err := getLinuxMountLabel(r); err != nil {
return err
}
if err := getLinuxIntelRdt(r); err != nil {
return err
}
return nil
}
func main() {
var r types.TestReport
if r.Spec == nil {
r.Spec = new(specs.Spec)
}
if err := getVersion(&r); err != nil {
logrus.Errorf("%v", err)
os.Exit(1)
}
if err := getProcess(&r); err != nil {
logrus.Errorf("%v", err)
os.Exit(1)
}
if err := getHostname(&r); err != nil {
logrus.Errorf("%v", err)
os.Exit(1)
}
if err := getMounts(&r); err != nil {
logrus.Errorf("%v", err)
os.Exit(1)
}
if err := getLinux(&r); err != nil {
logrus.Errorf("%v", err)
os.Exit(1)
}
json.NewEncoder(os.Stdout).Encode(r)
}

View File

@ -0,0 +1,10 @@
package types
import (
"github.com/opencontainers/runtime-spec/specs-go"
)
// TestReport is an internal type used for testing.
type TestReport struct {
Spec *specs.Spec
}