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:
parent
a9fa129609
commit
38ef1231f2
9
Makefile
9
Makefile
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
22
run.go
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" ]
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue