blob: b737ff7b35e05c22851db7281dd67b1d82ff1cda [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
#include <gtest/gtest.h>
#include <stout/gtest.hpp>
#include <stout/path.hpp>
#include <stout/stringify.hpp>
#include <stout/try.hpp>
#include "linux/capabilities.hpp"
#include "tests/cluster.hpp"
#include "tests/environment.hpp"
#include "tests/mesos.hpp"
using mesos::internal::capabilities::Capability;
using mesos::internal::capabilities::CHOWN;
using mesos::internal::capabilities::SYS_ADMIN;
using mesos::internal::slave::Containerizer;
using mesos::internal::slave::Fetcher;
using mesos::internal::slave::MesosContainerizer;
using mesos::internal::slave::state::SlaveState;
using mesos::slave::ContainerTermination;
using process::Future;
using process::Owned;
using std::list;
using std::map;
using std::ostream;
using std::set;
using std::string;
namespace mesos {
namespace internal {
namespace tests {
// This is a modified version of the Docker's default Seccomp profile:
// https://github.com/moby/moby/blob/master/profiles/seccomp/default.json
// This profile allows `pivot_root` system call which is used by the
// Mesos containerizer.
constexpr char TEST_SECCOMP_PROFILE[] = R"~(
{
"defaultAction": "SCMP_ACT_ERRNO",
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
]
},
{
"architecture": "SCMP_ARCH_AARCH64",
"subArchitectures": [
"SCMP_ARCH_ARM"
]
},
{
"architecture": "SCMP_ARCH_MIPS64",
"subArchitectures": [
"SCMP_ARCH_MIPS",
"SCMP_ARCH_MIPS64N32"
]
},
{
"architecture": "SCMP_ARCH_MIPS64N32",
"subArchitectures": [
"SCMP_ARCH_MIPS",
"SCMP_ARCH_MIPS64"
]
},
{
"architecture": "SCMP_ARCH_MIPSEL64",
"subArchitectures": [
"SCMP_ARCH_MIPSEL",
"SCMP_ARCH_MIPSEL64N32"
]
},
{
"architecture": "SCMP_ARCH_MIPSEL64N32",
"subArchitectures": [
"SCMP_ARCH_MIPSEL",
"SCMP_ARCH_MIPSEL64"
]
},
{
"architecture": "SCMP_ARCH_S390X",
"subArchitectures": [
"SCMP_ARCH_S390"
]
}
],
"syscalls": [
{
"names": [
"accept",
"accept4",
"access",
"adjtimex",
"alarm",
"bind",
"brk",
"capget",
"capset",
"chdir",
"chmod",
"chown",
"chown32",
"clock_getres",
"clock_gettime",
"clock_nanosleep",
"close",
"connect",
"copy_file_range",
"creat",
"dup",
"dup2",
"dup3",
"epoll_create",
"epoll_create1",
"epoll_ctl",
"epoll_ctl_old",
"epoll_pwait",
"epoll_wait",
"epoll_wait_old",
"eventfd",
"eventfd2",
"execve",
"execveat",
"exit",
"exit_group",
"faccessat",
"fadvise64",
"fadvise64_64",
"fallocate",
"fanotify_mark",
"fchdir",
"fchmod",
"fchmodat",
"fchown",
"fchown32",
"fchownat",
"fcntl",
"fcntl64",
"fdatasync",
"fgetxattr",
"flistxattr",
"flock",
"fork",
"fremovexattr",
"fsetxattr",
"fstat",
"fstat64",
"fstatat64",
"fstatfs",
"fstatfs64",
"fsync",
"ftruncate",
"ftruncate64",
"futex",
"futimesat",
"getcpu",
"getcwd",
"getdents",
"getdents64",
"getegid",
"getegid32",
"geteuid",
"geteuid32",
"getgid",
"getgid32",
"getgroups",
"getgroups32",
"getitimer",
"getpeername",
"getpgid",
"getpgrp",
"getpid",
"getppid",
"getpriority",
"getrandom",
"getresgid",
"getresgid32",
"getresuid",
"getresuid32",
"getrlimit",
"get_robust_list",
"getrusage",
"getsid",
"getsockname",
"getsockopt",
"get_thread_area",
"gettid",
"gettimeofday",
"getuid",
"getuid32",
"getxattr",
"inotify_add_watch",
"inotify_init",
"inotify_init1",
"inotify_rm_watch",
"io_cancel",
"ioctl",
"io_destroy",
"io_getevents",
"ioprio_get",
"ioprio_set",
"io_setup",
"io_submit",
"ipc",
"kill",
"lchown",
"lchown32",
"lgetxattr",
"link",
"linkat",
"listen",
"listxattr",
"llistxattr",
"_llseek",
"lremovexattr",
"lseek",
"lsetxattr",
"lstat",
"lstat64",
"madvise",
"memfd_create",
"mincore",
"mkdir",
"mkdirat",
"mknod",
"mknodat",
"mlock",
"mlock2",
"mlockall",
"mmap",
"mmap2",
"mprotect",
"mq_getsetattr",
"mq_notify",
"mq_open",
"mq_timedreceive",
"mq_timedsend",
"mq_unlink",
"mremap",
"msgctl",
"msgget",
"msgrcv",
"msgsnd",
"msync",
"munlock",
"munlockall",
"munmap",
"nanosleep",
"newfstatat",
"_newselect",
"open",
"openat",
"pause",
"pipe",
"pipe2",
"poll",
"ppoll",
"prctl",
"pread64",
"preadv",
"preadv2",
"prlimit64",
"pselect6",
"pwrite64",
"pwritev",
"pwritev2",
"read",
"readahead",
"readlink",
"readlinkat",
"readv",
"recv",
"recvfrom",
"recvmmsg",
"recvmsg",
"remap_file_pages",
"removexattr",
"rename",
"renameat",
"renameat2",
"restart_syscall",
"rmdir",
"rt_sigaction",
"rt_sigpending",
"rt_sigprocmask",
"rt_sigqueueinfo",
"rt_sigreturn",
"rt_sigsuspend",
"rt_sigtimedwait",
"rt_tgsigqueueinfo",
"sched_getaffinity",
"sched_getattr",
"sched_getparam",
"sched_get_priority_max",
"sched_get_priority_min",
"sched_getscheduler",
"sched_rr_get_interval",
"sched_setaffinity",
"sched_setattr",
"sched_setparam",
"sched_setscheduler",
"sched_yield",
"seccomp",
"select",
"semctl",
"semget",
"semop",
"semtimedop",
"send",
"sendfile",
"sendfile64",
"sendmmsg",
"sendmsg",
"sendto",
"setfsgid",
"setfsgid32",
"setfsuid",
"setfsuid32",
"setgid",
"setgid32",
"setgroups",
"setgroups32",
"setitimer",
"setpgid",
"setpriority",
"setregid",
"setregid32",
"setresgid",
"setresgid32",
"setresuid",
"setresuid32",
"setreuid",
"setreuid32",
"setrlimit",
"set_robust_list",
"setsid",
"setsockopt",
"set_thread_area",
"set_tid_address",
"setuid",
"setuid32",
"setxattr",
"shmat",
"shmctl",
"shmdt",
"shmget",
"shutdown",
"sigaltstack",
"signalfd",
"signalfd4",
"sigreturn",
"socket",
"socketcall",
"socketpair",
"splice",
"stat",
"stat64",
"statfs",
"statfs64",
"statx",
"symlink",
"symlinkat",
"sync",
"sync_file_range",
"syncfs",
"sysinfo",
"tee",
"tgkill",
"time",
"timer_create",
"timer_delete",
"timerfd_create",
"timerfd_gettime",
"timerfd_settime",
"timer_getoverrun",
"timer_gettime",
"timer_settime",
"times",
"tkill",
"truncate",
"truncate64",
"ugetrlimit",
"umask",
"uname",
"unlink",
"unlinkat",
"utime",
"utimensat",
"utimes",
"vfork",
"vmsplice",
"wait4",
"waitid",
"waitpid",
"write",
"writev"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {},
"excludes": {}
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 0,
"valueTwo": 0,
"op": "SCMP_CMP_EQ"
}
],
"comment": "",
"includes": {},
"excludes": {}
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 8,
"valueTwo": 0,
"op": "SCMP_CMP_EQ"
}
],
"comment": "",
"includes": {},
"excludes": {}
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 131072,
"valueTwo": 0,
"op": "SCMP_CMP_EQ"
}
],
"comment": "",
"includes": {},
"excludes": {}
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 131080,
"valueTwo": 0,
"op": "SCMP_CMP_EQ"
}
],
"comment": "",
"includes": {},
"excludes": {}
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 4294967295,
"valueTwo": 0,
"op": "SCMP_CMP_EQ"
}
],
"comment": "",
"includes": {},
"excludes": {}
},
{
"names": [
"sync_file_range2"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"arches": [
"ppc64le"
]
},
"excludes": {}
},
{
"names": [
"arm_fadvise64_64",
"arm_sync_file_range",
"sync_file_range2",
"breakpoint",
"cacheflush",
"set_tls"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"arches": [
"arm",
"arm64"
]
},
"excludes": {}
},
{
"names": [
"arch_prctl"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"arches": [
"amd64",
"x32"
]
},
"excludes": {}
},
{
"names": [
"modify_ldt"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"arches": [
"amd64",
"x32",
"x86"
]
},
"excludes": {}
},
{
"names": [
"s390_pci_mmio_read",
"s390_pci_mmio_write",
"s390_runtime_instr"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"arches": [
"s390",
"s390x"
]
},
"excludes": {}
},
{
"names": [
"open_by_handle_at"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_DAC_READ_SEARCH"
]
},
"excludes": {}
},
{
"names": [
"bpf",
"clone",
"fanotify_init",
"lookup_dcookie",
"mount",
"name_to_handle_at",
"perf_event_open",
"quotactl",
"setdomainname",
"sethostname",
"setns",
"syslog",
"umount",
"umount2",
"unshare"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYS_ADMIN"
]
},
"excludes": {}
},
{
"names": [
"clone"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 2080505856,
"valueTwo": 0,
"op": "SCMP_CMP_MASKED_EQ"
}
],
"comment": "",
"includes": {},
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
],
"arches": [
"s390",
"s390x"
]
}
},
{
"names": [
"clone"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 1,
"value": 2080505856,
"valueTwo": 0,
"op": "SCMP_CMP_MASKED_EQ"
}
],
"comment": "s390 parameter ordering for clone is different",
"includes": {
"arches": [
"s390",
"s390x"
]
},
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
]
}
},
{
"names": [
"reboot"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYS_BOOT"
]
},
"excludes": {}
},
{
"names": [
"chroot",
"pivot_root"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYS_CHROOT"
]
},
"excludes": {}
},
{
"names": [
"delete_module",
"init_module",
"finit_module",
"query_module"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYS_MODULE"
]
},
"excludes": {}
},
{
"names": [
"acct"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYS_PACCT"
]
},
"excludes": {}
},
{
"names": [
"kcmp",
"process_vm_readv",
"process_vm_writev",
"ptrace"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYS_PTRACE"
]
},
"excludes": {}
},
{
"names": [
"iopl",
"ioperm"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYS_RAWIO"
]
},
"excludes": {}
},
{
"names": [
"settimeofday",
"stime",
"clock_settime"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYS_TIME"
]
},
"excludes": {}
},
{
"names": [
"vhangup"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYS_TTY_CONFIG"
]
},
"excludes": {}
},
{
"names": [
"get_mempolicy",
"mbind",
"set_mempolicy"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYS_NICE"
]
},
"excludes": {}
},
{
"names": [
"syslog"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {
"caps": [
"CAP_SYSLOG"
]
},
"excludes": {}
}
]
})~";
class LinuxSeccompIsolatorTest
: public ContainerizerTest<slave::MesosContainerizer>
{
protected:
slave::Flags CreateSlaveFlags() override
{
slave::Flags flags = MesosTest::CreateSlaveFlags();
flags.isolation = "linux/seccomp";
flags.seccomp_config_dir = os::getcwd();
return flags;
}
string createProfile(const string& config) const
{
CHECK(!config.empty());
Try<string> profilePath = os::mktemp(path::join(os::getcwd(), "XXXXXX"));
EXPECT_SOME(profilePath);
EXPECT_SOME(os::write(profilePath.get(), config));
return Path(profilePath.get()).basename();
}
};
// This test verifies that the Seccomp isolator fails during initialization
// when `--seccomp_config_dir` flag is not provided.
TEST_F(LinuxSeccompIsolatorTest, ROOT_MissingConfigDir)
{
slave::Flags flags = CreateSlaveFlags();
flags.seccomp_config_dir = None();
Fetcher fetcher(flags);
Try<MesosContainerizer*> create =
MesosContainerizer::create(flags, false, &fetcher);
ASSERT_ERROR(create);
EXPECT_TRUE(strings::contains(
create.error(), "Missing required `--seccomp_config_dir` flag"));
}
// This test verifies that the Seccomp isolator fails during initialization
// when default Seccomp profile is invalid.
TEST_F(LinuxSeccompIsolatorTest, ROOT_InvalidDefaultProfile)
{
slave::Flags flags = CreateSlaveFlags();
flags.seccomp_profile_name = createProfile("{}");
Fetcher fetcher(flags);
Try<MesosContainerizer*> create =
MesosContainerizer::create(flags, false, &fetcher);
ASSERT_ERROR(create);
EXPECT_TRUE(strings::contains(
create.error(), "Failed to parse Seccomp profile"));
}
// This test verifies that we can launch shell commands when the default
// Seccomp profile is enabled.
TEST_F(LinuxSeccompIsolatorTest, ROOT_SECCOMP_LaunchWithDefaultProfile)
{
slave::Flags flags = CreateSlaveFlags();
flags.seccomp_profile_name = createProfile(TEST_SECCOMP_PROFILE);
Fetcher fetcher(flags);
Try<MesosContainerizer*> create =
MesosContainerizer::create(flags, false, &fetcher);
ASSERT_SOME(create);
Owned<MesosContainerizer> containerizer(create.get());
SlaveState state;
state.id = SlaveID();
AWAIT_READY(containerizer->recover(state));
ContainerID containerId;
containerId.set_value(id::UUID::random().toString());
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
const string command = "id && env && uname && hostname";
Future<Containerizer::LaunchResult> launch = containerizer->launch(
containerId,
createContainerConfig(
None(),
createExecutorInfo("executor", command, "cpus:1"),
directory.get()),
map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
Future<Option<ContainerTermination>> wait = containerizer->wait(containerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
EXPECT_WEXITSTATUS_EQ(0, wait.get()->status());
}
// This test verifies that OS kernel kills the task process on invocation of
// syscall that is disabled by Seccomp profile.
TEST_F(LinuxSeccompIsolatorTest, ROOT_SECCOMP_LaunchWithUnameDisabled)
{
const string config =
R"~(
{
"defaultAction": "SCMP_ACT_ALLOW",
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
]
}
],
"syscalls": [
{
"names": ["uname"],
"action": "SCMP_ACT_ERRNO",
"args": [],
"includes": {},
"excludes": {}
}
]
})~";
slave::Flags flags = CreateSlaveFlags();
flags.seccomp_profile_name = createProfile(config);
Fetcher fetcher(flags);
Try<MesosContainerizer*> create =
MesosContainerizer::create(flags, false, &fetcher);
ASSERT_SOME(create);
Owned<MesosContainerizer> containerizer(create.get());
SlaveState state;
state.id = SlaveID();
AWAIT_READY(containerizer->recover(state));
ContainerID containerId;
containerId.set_value(id::UUID::random().toString());
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
Future<Containerizer::LaunchResult> launch = containerizer->launch(
containerId,
createContainerConfig(
None(),
createExecutorInfo("executor", "uname", "cpus:1"),
directory.get()),
map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
Future<Option<ContainerTermination>> wait = containerizer->wait(containerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
EXPECT_WEXITSTATUS_NE(0, wait.get()->status());
}
// This test verifies that we can disable Seccomp filtering for a particular
// task.
TEST_F(LinuxSeccompIsolatorTest, ROOT_SECCOMP_LaunchWithSeccompDisabled)
{
const string config =
R"~(
{
"defaultAction": "SCMP_ACT_ALLOW",
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
]
}
],
"syscalls": [
{
"names": ["uname"],
"action": "SCMP_ACT_ERRNO",
"args": [],
"includes": {},
"excludes": {}
}
]
})~";
slave::Flags flags = CreateSlaveFlags();
flags.seccomp_profile_name = createProfile(config);
Fetcher fetcher(flags);
Try<MesosContainerizer*> create =
MesosContainerizer::create(flags, false, &fetcher);
ASSERT_SOME(create);
Owned<MesosContainerizer> containerizer(create.get());
SlaveState state;
state.id = SlaveID();
AWAIT_READY(containerizer->recover(state));
ContainerID containerId;
containerId.set_value(id::UUID::random().toString());
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
auto containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", "uname", "cpus:1"),
directory.get());
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
SeccompInfo* seccomp = container->mutable_linux_info()->mutable_seccomp();
seccomp->set_unconfined(true);
Future<Containerizer::LaunchResult> launch = containerizer->launch(
containerId,
containerConfig,
map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
Future<Option<ContainerTermination>> wait = containerizer->wait(containerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
EXPECT_WEXITSTATUS_EQ(0, wait.get()->status());
}
// This test verifies that we can launch a task container with overridden
// Seccomp profile.
TEST_F(LinuxSeccompIsolatorTest, ROOT_SECCOMP_LaunchWithOverriddenProfile)
{
const string config =
R"~(
{
"defaultAction": "SCMP_ACT_ALLOW",
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
]
}
],
"syscalls": [
{
"names": ["uname"],
"action": "SCMP_ACT_ERRNO",
"args": [],
"includes": {},
"excludes": {}
}
]
})~";
slave::Flags flags = CreateSlaveFlags();
flags.seccomp_profile_name = createProfile(TEST_SECCOMP_PROFILE);
Fetcher fetcher(flags);
Try<MesosContainerizer*> create =
MesosContainerizer::create(flags, false, &fetcher);
ASSERT_SOME(create);
Owned<MesosContainerizer> containerizer(create.get());
SlaveState state;
state.id = SlaveID();
AWAIT_READY(containerizer->recover(state));
ContainerID containerId;
containerId.set_value(id::UUID::random().toString());
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
auto containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", "uname", "cpus:1"),
directory.get());
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
// Set the Seccomp profile name for this particular task.
SeccompInfo* seccomp = container->mutable_linux_info()->mutable_seccomp();
seccomp->set_profile_name(createProfile(config));
Future<Containerizer::LaunchResult> launch = containerizer->launch(
containerId,
containerConfig,
map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
Future<Option<ContainerTermination>> wait = containerizer->wait(containerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
EXPECT_WEXITSTATUS_NE(0, wait.get()->status());
}
// This test verifies that launching a task with a non-existent Seccomp profile
// leads to failure.
TEST_F(
LinuxSeccompIsolatorTest,
ROOT_SECCOMP_LaunchWithOverriddenNonExistentProfile)
{
slave::Flags flags = CreateSlaveFlags();
flags.seccomp_profile_name = createProfile(TEST_SECCOMP_PROFILE);
Fetcher fetcher(flags);
Try<MesosContainerizer*> create =
MesosContainerizer::create(flags, false, &fetcher);
ASSERT_SOME(create);
Owned<MesosContainerizer> containerizer(create.get());
SlaveState state;
state.id = SlaveID();
AWAIT_READY(containerizer->recover(state));
ContainerID containerId;
containerId.set_value(id::UUID::random().toString());
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
auto containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", "exit 0", "cpus:1"),
directory.get());
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
// Set a non-existent Seccomp profile for this particular task.
SeccompInfo* seccomp = container->mutable_linux_info()->mutable_seccomp();
seccomp->set_profile_name("absent");
Future<Containerizer::LaunchResult> launch = containerizer->launch(
containerId,
containerConfig,
map<string, string>(),
None());
AWAIT_FAILED(launch);
}
// Param for the tests:
// 'includes_capabilities'
// List of capabilities that are set in the `includes` section within
// Seccomp profile for a syscall filtering rule.
// 'excludes_capabilities'
// List of capabilities that are set in the `excludes` section within
// Seccomp profile for a syscall filtering rule.
// 'container_capabilities'
// Container specified effective and bounding capabilities for the
// container.
// 'result'
// True if the task should finish normally.
struct SeccompTestParam
{
enum Result
{
FAILURE = 0,
SUCCESS = 1
};
SeccompTestParam(
const Option<set<Capability>>& _includes_capabilities,
const Option<set<Capability>>& _excludes_capabilities,
const Option<set<Capability>>& _container_capabilities,
Result _result)
: includes_capabilities(convertToList(_includes_capabilities)),
excludes_capabilities(convertToList(_excludes_capabilities)),
container_capabilities(convertToInfo(_container_capabilities)),
result(_result) {}
static const Option<CapabilityInfo> convertToInfo(
const Option<set<Capability>>& caps)
{
return caps.isSome()
? capabilities::convert(caps.get())
: Option<CapabilityInfo>::none();
}
static const Option<list<string>> convertToList(
const Option<set<Capability>>& caps)
{
if (caps.isSome()) {
list<string> capabilities;
foreach (const Capability& capability, caps.get()) {
capabilities.emplace_back(
strings::format("\"CAP_%s\"", stringify(capability)).get());
}
return capabilities;
}
return None();
}
const Option<list<string>> includes_capabilities;
const Option<list<string>> excludes_capabilities;
const Option<CapabilityInfo> container_capabilities;
const Result result;
};
ostream& operator<<(ostream& stream, const SeccompTestParam& param)
{
if (param.includes_capabilities.isSome()) {
stream << "includes_capabilities='"
<< stringify(param.includes_capabilities.get()) << "', ";
} else {
stream << "includes_capabilities='none', ";
}
if (param.excludes_capabilities.isSome()) {
stream << "excludes_capabilities='"
<< stringify(param.excludes_capabilities.get()) << "', ";
} else {
stream << "excludes_capabilities='none', ";
}
if (param.container_capabilities.isSome()) {
stream << "container_capabilities='"
<< JSON::protobuf(param.container_capabilities.get()) << "', ";
} else {
stream << "container_capabilities='none', ";
}
switch (param.result) {
case SeccompTestParam::FAILURE:
stream << "result=failure'";
break;
case SeccompTestParam::SUCCESS:
stream << "result=success'";
}
return stream;
}
class LinuxSeccompIsolatorWithCapabilitiesTest
: public LinuxSeccompIsolatorTest,
public ::testing::WithParamInterface<SeccompTestParam>
{
public:
LinuxSeccompIsolatorWithCapabilitiesTest()
: param(GetParam()) {}
protected:
SeccompTestParam param;
};
// Parameterized test confirming the filtering of Seccomp rules by capabilities.
// This test launches a container with enabled filtering of a Seccomp rule which
// disables `uname` system call depending on container's capabilities. E.g., if
// `includes` section contains `CAP_SYS_ADMIN` and the container is launched
// with `CAP_SYS_ADMIN`, then the rule is applied, so the `uname` command fails
// as expected.
TEST_P(LinuxSeccompIsolatorWithCapabilitiesTest, ROOT_SECCOMP_LaunchWithFilter)
{
string includesCaps = "{}";
string excludesCaps = "{}";
if (param.includes_capabilities.isSome()) {
includesCaps = strings::format(
"{\"caps\": %s}",
stringify(param.includes_capabilities.get())).get();
}
if (param.excludes_capabilities.isSome()) {
excludesCaps = strings::format(
"{\"caps\": %s}",
stringify(param.excludes_capabilities.get())).get();
}
const string config = strings::format(
R"~(
{
"defaultAction": "SCMP_ACT_ALLOW",
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
]
}
],
"syscalls": [
{
"names": ["uname"],
"action": "SCMP_ACT_ERRNO",
"args": [],
"includes": %s,
"excludes": %s
}
]
})~",
includesCaps,
excludesCaps).get();
slave::Flags flags = CreateSlaveFlags();
flags.isolation = "linux/capabilities,linux/seccomp";
flags.effective_capabilities = param.container_capabilities;
flags.bounding_capabilities = param.container_capabilities;
flags.seccomp_profile_name = createProfile(config);
Fetcher fetcher(flags);
Try<MesosContainerizer*> create =
MesosContainerizer::create(flags, false, &fetcher);
ASSERT_SOME(create);
Owned<MesosContainerizer> containerizer(create.get());
SlaveState state;
state.id = SlaveID();
AWAIT_READY(containerizer->recover(state));
ContainerID containerId;
containerId.set_value(id::UUID::random().toString());
Try<string> directory = environment->mkdtemp();
ASSERT_SOME(directory);
Future<Containerizer::LaunchResult> launch = containerizer->launch(
containerId,
createContainerConfig(
None(),
createExecutorInfo("executor", "uname", "cpus:1"),
directory.get()),
map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
Future<Option<ContainerTermination>> wait = containerizer->wait(containerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
ASSERT_TRUE(wait.get()->has_status());
switch (param.result) {
case SeccompTestParam::SUCCESS:
EXPECT_WEXITSTATUS_EQ(0, wait.get()->status());
break;
case SeccompTestParam::FAILURE:
EXPECT_WEXITSTATUS_NE(0, wait.get()->status());
break;
}
}
INSTANTIATE_TEST_CASE_P(
SeccompTestParam,
LinuxSeccompIsolatorWithCapabilitiesTest,
::testing::Values(
// Seccomp filter capabilities match container capabilities.
SeccompTestParam(
set<Capability>({SYS_ADMIN}),
None(),
set<Capability>({SYS_ADMIN}),
SeccompTestParam::FAILURE),
SeccompTestParam(
None(),
set<Capability>({SYS_ADMIN}),
set<Capability>({SYS_ADMIN}),
SeccompTestParam::SUCCESS),
// Seccomp filter capabilities does not match container capabilities.
SeccompTestParam(
set<Capability>({SYS_ADMIN}),
None(),
set<Capability>({CHOWN}),
SeccompTestParam::SUCCESS),
SeccompTestParam(
None(),
set<Capability>({SYS_ADMIN}),
set<Capability>({CHOWN}),
SeccompTestParam::FAILURE)));
} // namespace tests {
} // namespace internal {
} // namespace mesos {