inital seccomp support
Signed-off-by: Jessica Frazelle <acidburn@docker.com>
This commit is contained in:
parent
255004ef33
commit
6707f4b9b6
8 changed files with 173 additions and 2 deletions
|
@ -51,6 +51,7 @@ type Container struct {
|
|||
ShmPath string
|
||||
MqueuePath string
|
||||
ResolvConfPath string
|
||||
SeccompProfile string
|
||||
}
|
||||
|
||||
// CreateDaemonEnvironment returns the list of all environment variables given the list of
|
||||
|
|
|
@ -236,6 +236,7 @@ func (daemon *Daemon) populateCommand(c *container.Container, env []string) erro
|
|||
Pid: pid,
|
||||
ReadonlyRootfs: c.HostConfig.ReadonlyRootfs,
|
||||
RemappedRoot: remappedRoot,
|
||||
SeccompProfile: c.SeccompProfile,
|
||||
UIDMapping: uidMap,
|
||||
UTS: uts,
|
||||
}
|
||||
|
|
|
@ -152,6 +152,16 @@ func TestParseSecurityOpt(t *testing.T) {
|
|||
t.Fatalf("Unexpected AppArmorProfile, expected: \"test_profile\", got %q", container.AppArmorProfile)
|
||||
}
|
||||
|
||||
// test seccomp
|
||||
sp := "/path/to/seccomp_test.json"
|
||||
config.SecurityOpt = []string{"seccomp:" + sp}
|
||||
if err := parseSecurityOpt(container, config); err != nil {
|
||||
t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
|
||||
}
|
||||
if container.SeccompProfile != sp {
|
||||
t.Fatalf("Unexpected AppArmorProfile, expected: %q, got %q", sp, container.SeccompProfile)
|
||||
}
|
||||
|
||||
// test valid label
|
||||
config.SecurityOpt = []string{"label:user:USER"}
|
||||
if err := parseSecurityOpt(container, config); err != nil {
|
||||
|
|
2
daemon/daemon_unix.go
Normal file → Executable file
2
daemon/daemon_unix.go
Normal file → Executable file
|
@ -74,6 +74,8 @@ func parseSecurityOpt(container *container.Container, config *runconfig.HostConf
|
|||
labelOpts = append(labelOpts, con[1])
|
||||
case "apparmor":
|
||||
container.AppArmorProfile = con[1]
|
||||
case "seccomp":
|
||||
container.SeccompProfile = con[1]
|
||||
default:
|
||||
return fmt.Errorf("Invalid --security-opt: %q", opt)
|
||||
}
|
||||
|
|
|
@ -118,6 +118,7 @@ type Command struct {
|
|||
Pid *Pid `json:"pid"`
|
||||
ReadonlyRootfs bool `json:"readonly_rootfs"`
|
||||
RemappedRoot *User `json:"remap_root"`
|
||||
SeccompProfile string `json:"seccomp_profile"`
|
||||
UIDMapping []idtools.IDMap `json:"uidmapping"`
|
||||
UTS *UTS `json:"uts"`
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ import (
|
|||
|
||||
// createContainer populates and configures the container type with the
|
||||
// data provided by the execdriver.Command
|
||||
func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (*configs.Config, error) {
|
||||
container := execdriver.InitContainer(c)
|
||||
func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (container *configs.Config, err error) {
|
||||
container = execdriver.InitContainer(c)
|
||||
|
||||
if err := d.createIpc(container, c); err != nil {
|
||||
return nil, err
|
||||
|
@ -82,6 +82,12 @@ func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks)
|
|||
container.AppArmorProfile = c.AppArmorProfile
|
||||
}
|
||||
|
||||
if c.SeccompProfile != "" {
|
||||
container.Seccomp, err = loadSeccompProfile(c.SeccompProfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := execdriver.SetupCgroups(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
94
daemon/execdriver/native/seccomp.go
Normal file
94
daemon/execdriver/native/seccomp.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
// +build linux
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/seccomp"
|
||||
"github.com/opencontainers/specs"
|
||||
)
|
||||
|
||||
func loadSeccompProfile(path string) (*configs.Seccomp, error) {
|
||||
f, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Opening seccomp profile failed: %v", err)
|
||||
}
|
||||
|
||||
var config specs.Seccomp
|
||||
if err := json.Unmarshal(f, &config); err != nil {
|
||||
return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err)
|
||||
}
|
||||
|
||||
return setupSeccomp(&config)
|
||||
}
|
||||
|
||||
func setupSeccomp(config *specs.Seccomp) (newConfig *configs.Seccomp, err error) {
|
||||
if config == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// No default action specified, no syscalls listed, assume seccomp disabled
|
||||
if config.DefaultAction == "" && len(config.Syscalls) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
newConfig = new(configs.Seccomp)
|
||||
newConfig.Syscalls = []*configs.Syscall{}
|
||||
|
||||
// if config.Architectures == 0 then libseccomp will figure out the architecture to use
|
||||
if len(config.Architectures) > 0 {
|
||||
newConfig.Architectures = []string{}
|
||||
for _, arch := range config.Architectures {
|
||||
newArch, err := seccomp.ConvertStringToArch(string(arch))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newConfig.Architectures = append(newConfig.Architectures, newArch)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert default action from string representation
|
||||
newConfig.DefaultAction, err = seccomp.ConvertStringToAction(string(config.DefaultAction))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Loop through all syscall blocks and convert them to libcontainer format
|
||||
for _, call := range config.Syscalls {
|
||||
newAction, err := seccomp.ConvertStringToAction(string(call.Action))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newCall := configs.Syscall{
|
||||
Name: call.Name,
|
||||
Action: newAction,
|
||||
Args: []*configs.Arg{},
|
||||
}
|
||||
|
||||
// Loop through all the arguments of the syscall and convert them
|
||||
for _, arg := range call.Args {
|
||||
newOp, err := seccomp.ConvertStringToOperator(string(arg.Op))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newArg := configs.Arg{
|
||||
Index: arg.Index,
|
||||
Value: arg.Value,
|
||||
ValueTwo: arg.ValueTwo,
|
||||
Op: newOp,
|
||||
}
|
||||
|
||||
newCall.Args = append(newCall.Args, &newArg)
|
||||
}
|
||||
|
||||
newConfig.Syscalls = append(newConfig.Syscalls, &newCall)
|
||||
}
|
||||
|
||||
return newConfig, nil
|
||||
}
|
|
@ -3789,3 +3789,59 @@ func (s *DockerSuite) TestRunWithOomScoreAdjInvalidRange(c *check.C) {
|
|||
c.Fatalf("Expected output to contain %q, got %q instead", expected, out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunSeccompProfileDenyUnshare checks that 'docker run --security-opt seccomp:/tmp/profile.json jess/unshare unshare' exits with operation not permitted.
|
||||
func (s *DockerSuite) TestRunSeccompProfileDenyUnshare(c *check.C) {
|
||||
testRequires(c, SameHostDaemon)
|
||||
jsonData := `{
|
||||
"defaultAction": "SCMP_ACT_ALLOW",
|
||||
"syscalls": [
|
||||
{
|
||||
"name": "unshare",
|
||||
"action": "SCMP_ACT_ERRNO"
|
||||
}
|
||||
]
|
||||
}`
|
||||
tmpFile, err := ioutil.TempFile("", "profile.json")
|
||||
defer tmpFile.Close()
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := tmpFile.Write([]byte(jsonData)); err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
runCmd := exec.Command(dockerBinary, "run", "--security-opt", "seccomp:"+tmpFile.Name(), "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc")
|
||||
out, _, _ := runCommandWithOutput(runCmd)
|
||||
if !strings.Contains(out, "Operation not permitted") {
|
||||
c.Fatalf("expected unshare with seccomp profile denied to fail, got %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunSeccompProfileDenyChmod checks that 'docker run --security-opt seccomp:/tmp/profile.json busybox chmod 400 /etc/hostname' exits with operation not permitted.
|
||||
func (s *DockerSuite) TestRunSeccompProfileDenyChmod(c *check.C) {
|
||||
testRequires(c, SameHostDaemon)
|
||||
jsonData := `{
|
||||
"defaultAction": "SCMP_ACT_ALLOW",
|
||||
"syscalls": [
|
||||
{
|
||||
"name": "chmod",
|
||||
"action": "SCMP_ACT_ERRNO"
|
||||
}
|
||||
]
|
||||
}`
|
||||
tmpFile, err := ioutil.TempFile("", "profile.json")
|
||||
defer tmpFile.Close()
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := tmpFile.Write([]byte(jsonData)); err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
runCmd := exec.Command(dockerBinary, "run", "--security-opt", "seccomp:"+tmpFile.Name(), "busybox", "chmod", "400", "/etc/hostname")
|
||||
out, _, _ := runCommandWithOutput(runCmd)
|
||||
if !strings.Contains(out, "Operation not permitted") {
|
||||
c.Fatalf("expected chmod with seccomp profile denied to fail, got %s", out)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue