328 lines
8.6 KiB
Go
328 lines
8.6 KiB
Go
|
/*
|
||
|
Copyright The containerd Authors.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package oci
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"runtime"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/containerd/containerd/containers"
|
||
|
"github.com/containerd/containerd/namespaces"
|
||
|
"github.com/containerd/containerd/pkg/testutil"
|
||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||
|
)
|
||
|
|
||
|
func TestGenerateSpec(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
||
|
s, err := GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if s == nil {
|
||
|
t.Fatal("GenerateSpec() returns a nil spec")
|
||
|
}
|
||
|
|
||
|
if runtime.GOOS != "windows" {
|
||
|
// check for matching caps
|
||
|
defaults := defaultUnixCaps()
|
||
|
for _, cl := range [][]string{
|
||
|
s.Process.Capabilities.Bounding,
|
||
|
s.Process.Capabilities.Permitted,
|
||
|
s.Process.Capabilities.Inheritable,
|
||
|
s.Process.Capabilities.Effective,
|
||
|
} {
|
||
|
for i := 0; i < len(defaults); i++ {
|
||
|
if cl[i] != defaults[i] {
|
||
|
t.Errorf("cap at %d does not match set %q != %q", i, defaults[i], cl[i])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check default namespaces
|
||
|
defaultNS := defaultUnixNamespaces()
|
||
|
for i, ns := range s.Linux.Namespaces {
|
||
|
if defaultNS[i] != ns {
|
||
|
t.Errorf("ns at %d does not match set %q != %q", i, defaultNS[i], ns)
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if s.Windows == nil {
|
||
|
t.Fatal("Windows section of spec not filled in on Windows platform")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// test that we don't have tty set
|
||
|
if s.Process.Terminal {
|
||
|
t.Error("terminal set on default process")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGenerateSpecWithPlatform(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
||
|
platforms := []string{"windows/amd64", "linux/amd64"}
|
||
|
for _, p := range platforms {
|
||
|
t.Logf("Testing platform: %s", p)
|
||
|
s, err := GenerateSpecWithPlatform(ctx, nil, p, &containers.Container{ID: t.Name()})
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to generate spec: %v", err)
|
||
|
}
|
||
|
|
||
|
if s.Root == nil {
|
||
|
t.Fatal("expected non nil Root section.")
|
||
|
}
|
||
|
if s.Process == nil {
|
||
|
t.Fatal("expected non nil Process section.")
|
||
|
}
|
||
|
if p == "windows/amd64" {
|
||
|
if s.Linux != nil {
|
||
|
t.Fatal("expected nil Linux section")
|
||
|
}
|
||
|
if s.Windows == nil {
|
||
|
t.Fatal("expected non nil Windows section")
|
||
|
}
|
||
|
} else {
|
||
|
if s.Linux == nil {
|
||
|
t.Fatal("expected non nil Linux section")
|
||
|
}
|
||
|
if runtime.GOOS == "windows" && s.Windows == nil {
|
||
|
t.Fatal("expected non nil Windows section for LCOW")
|
||
|
} else if runtime.GOOS != "windows" && s.Windows != nil {
|
||
|
t.Fatal("expected nil Windows section")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSpecWithTTY(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
||
|
s, err := GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, WithTTY)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if !s.Process.Terminal {
|
||
|
t.Error("terminal net set WithTTY()")
|
||
|
}
|
||
|
if runtime.GOOS != "windows" {
|
||
|
v := s.Process.Env[len(s.Process.Env)-1]
|
||
|
if v != "TERM=xterm" {
|
||
|
t.Errorf("xterm not set in env for TTY")
|
||
|
}
|
||
|
} else {
|
||
|
if len(s.Process.Env) != 0 {
|
||
|
t.Fatal("Windows process args should be empty by default")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestWithLinuxNamespace(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
||
|
replacedNS := specs.LinuxNamespace{Type: specs.NetworkNamespace, Path: "/var/run/netns/test"}
|
||
|
|
||
|
var s *specs.Spec
|
||
|
var err error
|
||
|
if runtime.GOOS != "windows" {
|
||
|
s, err = GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, WithLinuxNamespace(replacedNS))
|
||
|
} else {
|
||
|
s, err = GenerateSpecWithPlatform(ctx, nil, "linux/amd64", &containers.Container{ID: t.Name()}, WithLinuxNamespace(replacedNS))
|
||
|
}
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
defaultNS := defaultUnixNamespaces()
|
||
|
found := false
|
||
|
for i, ns := range s.Linux.Namespaces {
|
||
|
if ns == replacedNS && !found {
|
||
|
found = true
|
||
|
continue
|
||
|
}
|
||
|
if defaultNS[i] != ns {
|
||
|
t.Errorf("ns at %d does not match set %q != %q", i, defaultNS[i], ns)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestWithCapabilities(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
||
|
|
||
|
opts := []SpecOpts{
|
||
|
WithCapabilities([]string{"CAP_SYS_ADMIN"}),
|
||
|
}
|
||
|
var s *specs.Spec
|
||
|
var err error
|
||
|
if runtime.GOOS != "windows" {
|
||
|
s, err = GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, opts...)
|
||
|
} else {
|
||
|
s, err = GenerateSpecWithPlatform(ctx, nil, "linux/amd64", &containers.Container{ID: t.Name()}, opts...)
|
||
|
}
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if len(s.Process.Capabilities.Bounding) != 1 || s.Process.Capabilities.Bounding[0] != "CAP_SYS_ADMIN" {
|
||
|
t.Error("Unexpected capabilities set")
|
||
|
}
|
||
|
if len(s.Process.Capabilities.Effective) != 1 || s.Process.Capabilities.Effective[0] != "CAP_SYS_ADMIN" {
|
||
|
t.Error("Unexpected capabilities set")
|
||
|
}
|
||
|
if len(s.Process.Capabilities.Permitted) != 1 || s.Process.Capabilities.Permitted[0] != "CAP_SYS_ADMIN" {
|
||
|
t.Error("Unexpected capabilities set")
|
||
|
}
|
||
|
if len(s.Process.Capabilities.Inheritable) != 1 || s.Process.Capabilities.Inheritable[0] != "CAP_SYS_ADMIN" {
|
||
|
t.Error("Unexpected capabilities set")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestWithCapabilitiesNil(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
||
|
|
||
|
s, err := GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()},
|
||
|
WithCapabilities(nil),
|
||
|
)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if len(s.Process.Capabilities.Bounding) != 0 {
|
||
|
t.Errorf("Unexpected capabilities set: length is non zero (%d)", len(s.Process.Capabilities.Bounding))
|
||
|
}
|
||
|
if len(s.Process.Capabilities.Effective) != 0 {
|
||
|
t.Errorf("Unexpected capabilities set: length is non zero (%d)", len(s.Process.Capabilities.Effective))
|
||
|
}
|
||
|
if len(s.Process.Capabilities.Permitted) != 0 {
|
||
|
t.Errorf("Unexpected capabilities set: length is non zero (%d)", len(s.Process.Capabilities.Permitted))
|
||
|
}
|
||
|
if len(s.Process.Capabilities.Inheritable) != 0 {
|
||
|
t.Errorf("Unexpected capabilities set: length is non zero (%d)", len(s.Process.Capabilities.Inheritable))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestPopulateDefaultWindowsSpec(t *testing.T) {
|
||
|
var (
|
||
|
c = containers.Container{ID: "TestWithDefaultSpec"}
|
||
|
ctx = namespaces.WithNamespace(context.Background(), "test")
|
||
|
)
|
||
|
var expected Spec
|
||
|
|
||
|
populateDefaultWindowsSpec(ctx, &expected, c.ID)
|
||
|
if expected.Windows == nil {
|
||
|
t.Error("Cannot populate windows Spec")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestPopulateDefaultUnixSpec(t *testing.T) {
|
||
|
var (
|
||
|
c = containers.Container{ID: "TestWithDefaultSpec"}
|
||
|
ctx = namespaces.WithNamespace(context.Background(), "test")
|
||
|
)
|
||
|
var expected Spec
|
||
|
|
||
|
populateDefaultUnixSpec(ctx, &expected, c.ID)
|
||
|
if expected.Linux == nil {
|
||
|
t.Error("Cannot populate Unix Spec")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestWithPrivileged(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
if runtime.GOOS == "linux" {
|
||
|
// because WithPrivileged depends on CapEff in /proc/self/status
|
||
|
testutil.RequiresRoot(t)
|
||
|
}
|
||
|
|
||
|
ctx := namespaces.WithNamespace(context.Background(), "testing")
|
||
|
|
||
|
opts := []SpecOpts{
|
||
|
WithCapabilities(nil),
|
||
|
WithMounts([]specs.Mount{
|
||
|
{Type: "cgroup", Destination: "/sys/fs/cgroup", Options: []string{"ro"}},
|
||
|
}),
|
||
|
WithPrivileged,
|
||
|
}
|
||
|
var s *specs.Spec
|
||
|
var err error
|
||
|
if runtime.GOOS != "windows" {
|
||
|
s, err = GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, opts...)
|
||
|
} else {
|
||
|
s, err = GenerateSpecWithPlatform(ctx, nil, "linux/amd64", &containers.Container{ID: t.Name()}, opts...)
|
||
|
}
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if runtime.GOOS != "linux" {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if len(s.Process.Capabilities.Bounding) == 0 {
|
||
|
t.Error("Expected capabilities to be set with privileged")
|
||
|
}
|
||
|
|
||
|
var foundSys, foundCgroup bool
|
||
|
for _, m := range s.Mounts {
|
||
|
switch m.Type {
|
||
|
case "sysfs":
|
||
|
foundSys = true
|
||
|
var found bool
|
||
|
for _, o := range m.Options {
|
||
|
switch o {
|
||
|
case "ro":
|
||
|
t.Errorf("Found unexpected read only %s mount", m.Type)
|
||
|
case "rw":
|
||
|
found = true
|
||
|
}
|
||
|
}
|
||
|
if !found {
|
||
|
t.Errorf("Did not find rw mount option for %s", m.Type)
|
||
|
}
|
||
|
case "cgroup":
|
||
|
foundCgroup = true
|
||
|
var found bool
|
||
|
for _, o := range m.Options {
|
||
|
switch o {
|
||
|
case "ro":
|
||
|
t.Errorf("Found unexpected read only %s mount", m.Type)
|
||
|
case "rw":
|
||
|
found = true
|
||
|
}
|
||
|
}
|
||
|
if !found {
|
||
|
t.Errorf("Did not find rw mount option for %s", m.Type)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if !foundSys {
|
||
|
t.Error("Did not find mount for sysfs")
|
||
|
}
|
||
|
if !foundCgroup {
|
||
|
t.Error("Did not find mount for cgroupfs")
|
||
|
}
|
||
|
}
|