diff --git a/docs/interfaces.md b/docs/interfaces.md index 12d7868fced..b4ef1e9a303 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -258,6 +258,24 @@ printing. * Auto-Connect: no +### docker + +Can access snaps providing the docker interface which gives privileged access +to the system. + +* Auto-Connect: no + +### docker-support + +Can access resources and syscalls necessary to run Docker application +containers. The ``privileged-containers`` attribute may be used to give the +necessary access to run privileged containers. Providing snaps specifying this +interface currently may only be established with the Docker project. + +* Auto-Connect: no +* Attributes: + * privileged-containers (plug): true|false (defaults to ``false``) + ### firewall-control Can configure network firewalling giving privileged access to networking. diff --git a/interfaces/builtin/all.go b/interfaces/builtin/all.go index 54799b4e530..d5613561e99 100644 --- a/interfaces/builtin/all.go +++ b/interfaces/builtin/all.go @@ -28,6 +28,8 @@ var allInterfaces = []interfaces.Interface{ &BluezInterface{}, &BrowserSupportInterface{}, &ContentInterface{}, + &DockerInterface{}, + &DockerSupportInterface{}, &GpioInterface{}, &HidrawInterface{}, &LocationControlInterface{}, diff --git a/interfaces/builtin/all_test.go b/interfaces/builtin/all_test.go index aeba88155d0..42c7f3b3cd8 100644 --- a/interfaces/builtin/all_test.go +++ b/interfaces/builtin/all_test.go @@ -35,6 +35,8 @@ func (s *AllSuite) TestInterfaces(c *C) { c.Check(all, Contains, &builtin.BoolFileInterface{}) c.Check(all, Contains, &builtin.BluezInterface{}) c.Check(all, Contains, &builtin.BrowserSupportInterface{}) + c.Check(all, Contains, &builtin.DockerInterface{}) + c.Check(all, Contains, &builtin.DockerSupportInterface{}) c.Check(all, Contains, &builtin.GpioInterface{}) c.Check(all, Contains, &builtin.HidrawInterface{}) c.Check(all, Contains, &builtin.LocationControlInterface{}) diff --git a/interfaces/builtin/docker.go b/interfaces/builtin/docker.go new file mode 100644 index 00000000000..7a51f006424 --- /dev/null +++ b/interfaces/builtin/docker.go @@ -0,0 +1,132 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package builtin + +import ( + "fmt" + + "github.com/snapcore/snapd/interfaces" +) + +const dockerConnectedPlugAppArmor = ` +# Description: allow access to the Docker daemon socket. This gives privileged +# access to the system via Docker's socket API. +# Usage: reserved + +# Allow talking to the docker daemon +/{,var/}run/docker.sock rw, +` + +const dockerConnectedPlugSecComp = ` +# Description: allow access to the Docker daemon socket. This gives privileged +# access to the system via Docker's socket API. +# Usage: reserved + +setsockopt +bind +` + +type DockerInterface struct{} + +func (iface *DockerInterface) Name() string { + return "docker" +} + +func (iface *DockerInterface) PermanentPlugSnippet(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor, + interfaces.SecurityDBus, + interfaces.SecurityMount, + interfaces.SecuritySecComp, + interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *DockerInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor: + snippet := []byte(dockerConnectedPlugAppArmor) + return snippet, nil + case interfaces.SecuritySecComp: + snippet := []byte(dockerConnectedPlugSecComp) + return snippet, nil + case interfaces.SecurityDBus, + interfaces.SecurityMount, + interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *DockerInterface) PermanentSlotSnippet(slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + // No permanent slot policy. Instead, use the docker-support interface + switch securitySystem { + case interfaces.SecurityAppArmor, + interfaces.SecurityDBus, + interfaces.SecurityMount, + interfaces.SecuritySecComp, + interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *DockerInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + // The docker socket is a named socket and therefore mediated by + // AppArmor file rules. As such, there is no additional ConnectedSlot + // policy to add. + switch securitySystem { + case interfaces.SecurityAppArmor, + interfaces.SecurityDBus, + interfaces.SecurityMount, + interfaces.SecuritySecComp, + interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *DockerInterface) SanitizePlug(plug *interfaces.Plug) error { + if iface.Name() != plug.Interface { + panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) + } + return nil +} + +func (iface *DockerInterface) SanitizeSlot(slot *interfaces.Slot) error { + snapName := slot.Snap.Name() + devName := slot.Snap.Developer + // The docker slot interface can only by used with the docker project + // and Canonical + if snapName != "docker" || (devName != "canonical" && devName != "docker") { + return fmt.Errorf("docker slot interface is reserved for the upstream docker project") + } + return nil +} + +func (iface *DockerInterface) AutoConnect() bool { + return false +} diff --git a/interfaces/builtin/docker_support.go b/interfaces/builtin/docker_support.go new file mode 100644 index 00000000000..e0a764d820b --- /dev/null +++ b/interfaces/builtin/docker_support.go @@ -0,0 +1,617 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package builtin + +import ( + "fmt" + + "github.com/snapcore/snapd/interfaces" +) + +const dockerSupportConnectedPlugAppArmor = ` +# Description: allow operating as the Docker daemon. This policy is +# intentionally not restrictive and is here to help guard against programming +# errors and not for security confinement. The Docker daemon by design requires +# extensive access to the system and cannot be effectively confined against +# malicious activity. +# Usage: reserved + +#include + +# Allow sockets +/{,var/}run/docker.sock rw, +/{,var/}run/docker/ rw, +/{,var/}run/docker/** mrwklix, +/{,var/}run/runc/ rw, +/{,var/}run/runc/** mrwklix, + +# Wide read access to /proc, but somewhat limited writes for now +@{PROC}/ r, +@{PROC}/** r, +@{PROC}/[0-9]*/attr/exec w, +@{PROC}/[0-9]*/oom_score_adj w, + +# Limited read access to specific bits of /sys +/sys/kernel/mm/hugepages/ r, +/sys/fs/cgroup/cpuset/cpuset.cpus r, +/sys/fs/cgroup/cpuset/cpuset.mems r, +/sys/module/apparmor/parameters/enabled r, + +# Limit cgroup writes a bit (Docker uses a "docker" sub-group) +/sys/fs/cgroup/*/docker/ rw, +/sys/fs/cgroup/*/docker/** rw, + +# Allow tracing ourself (especially the "runc" process we create) +ptrace (trace) peer=@{profile_name}, + +# Docker needs a lot of caps, but limits them in the app container +capability, + +# Docker does all kinds of mounts all over the filesystem +/dev/mapper/control rw, +/dev/mapper/docker* rw, +/dev/loop-control r, +/dev/loop[0-9]* rw, +mount, +umount, + +# After doing a pivot_root using //.pivot_rootNNNNNN, +# Docker removes the leftover /.pivot_rootNNNNNN directory (which is now +# relative to "/" instead of "/" thanks to pivot_root) +pivot_root, +/.pivot_root[0-9]*/ rw, + +# file descriptors (/proc/NNN/fd/X) +# file descriptors in the container show up here due to attach_disconnected +/[0-9]* rw, + +# Docker needs to be able to create and load the profile it applies to +# containers ("docker-default") +/sbin/apparmor_parser ixr, +/etc/apparmor.d/cache/ r, +/etc/apparmor.d/cache/.features r, +/etc/apparmor.d/cache/docker* rw, +/etc/apparmor/parser.conf r, +/etc/apparmor/subdomain.conf r, +/sys/kernel/security/apparmor/.replace rw, +/sys/kernel/security/apparmor/{,**} r, + +# use 'privileged-containers: true' to support --security-opts +change_profile -> docker-default, +signal (send) peer=docker-default, +ptrace (read, trace) peer=docker-default, + +# Graph (storage) driver bits +/dev/shm/aufs.xino rw, +/proc/fs/aufs/plink_maint w, +/sys/fs/aufs/** r, + +#cf bug 1502785 +/ r, +` + +const dockerSupportConnectedPlugSecComp = ` +# Description: allow operating as the Docker daemon. This policy is +# intentionally not restrictive and is here to help guard against programming +# errors and not for security confinement. The Docker daemon by design requires +# extensive access to the system and cannot be effectively confined against +# malicious activity. +# Usage: reserved + +# Because seccomp may only go more strict, we must allow all syscalls to Docker +# that it expects to give to containers in addition to what it needs to run and +# trust that docker daemon # only gives out reasonable syscalls to containers. + +# Docker includes these in the default container whitelist, but they're +# potentially dangerous. +#finit_module +#init_module +#query_module +#delete_module + +# These have a history of vulnerabilities, are not widely used, and +# open_by_handle_at has been used to break out of Docker containers by brute +# forcing the handle value: http://stealth.openwall.net/xSports/shocker.c +#name_to_handle_at +#open_by_handle_at + +# Calls the Docker daemon itself requires + +# /snap/docker/VERSION/bin/docker-runc +# "do not inherit the parent's session keyring" +# "make session keyring searcheable" +# runC uses this to ensure the container doesn't have access to the host +# keyring +keyctl + +# /snap/docker/VERSION/bin/docker-runc +pivot_root + +# ptrace can be abused to break out of the seccomp sandbox +# but is required by the Docker daemon. +ptrace + +# This list comes from Docker's default seccomp whitelist (which is applied to +# all containers launched unless a custom profile is specified or +# "--privileged" is used) +# https://github.com/docker/docker/blob/v1.12.0/profiles/seccomp/seccomp_default.go#L39-L1879 +# It has been further filtered to exclude certain known-troublesome syscalls. +accept +accept4 +access +acct +adjtimex +alarm +arch_prctl +bind +bpf +breakpoint +brk +cacheflush +capget +capset +chdir +chmod +chown +chown32 +chroot +clock_getres +clock_gettime +clock_nanosleep +clone +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_init +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 +ioperm +iopl +ioprio_get +ioprio_set +io_setup +io_submit +ipc +kcmp +kill +lchown +lchown32 +lgetxattr +link +linkat +listen +listxattr +llistxattr +_llseek +lookup_dcookie +lremovexattr +lseek +lsetxattr +lstat +lstat64 +madvise +memfd_create +mincore +mkdir +mkdirat +mknod +mknodat +mlock +mlock2 +mlockall +mmap +mmap2 +modify_ldt +mount +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 +perf_event_open +personality +pipe +pipe2 +poll +ppoll +prctl +pread64 +preadv +prlimit64 +process_vm_readv +process_vm_writev +pselect6 +pwrite64 +pwritev +read +readahead +readlink +readlinkat +readv +reboot +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 +s390_pci_mmio_read +s390_pci_mmio_write +s390_runtime_instr +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 +setdomainname +setfsgid +setfsgid32 +setfsuid +setfsuid32 +setgid +setgid32 +setgroups +setgroups32 +sethostname +setitimer +setns +setpgid +setpriority +setregid +setregid32 +setresgid +setresgid32 +setresuid +setresuid32 +setreuid +setreuid32 +setrlimit +set_robust_list +setsid +setsockopt +set_thread_area +set_tid_address +settimeofday +set_tls +setuid +setuid32 +setxattr +shmat +shmctl +shmdt +shmget +shutdown +sigaltstack +signalfd +signalfd4 +sigreturn +socket +socketcall +socketpair +splice +stat +stat64 +statfs +statfs64 +stime +symlink +symlinkat +sync +sync_file_range +syncfs +sysinfo +syslog +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 +umount +umount2 +uname +unlink +unlinkat +unshare +utime +utimensat +utimes +vfork +vhangup +vmsplice +wait4 +waitid +waitpid +write +writev +` + +const dockerSupportPrivilegedAppArmor = ` +# Description: allow docker daemon to run privileged containers. This gives +# full access to all resources on the system and thus gives device ownership to +# connected snaps. + +# These rules are here to allow Docker to launch unconfined containers but +# allow the docker daemon itself to go unconfined. Since it runs as root, this +# grants device ownership. +change_profile -> *, +signal (send) peer=unconfined, +ptrace (read, trace) peer=unconfined, + +# This grants raw access to device files and thus device ownership +/dev/** mrwkl, +@{PROC}/** mrwkl, +` + +const dockerSupportPrivilegedSecComp = ` +# Description: allow docker daemon to run privileged containers. This gives +# full access to all resources on the system and thus gives device ownership to +# connected snaps. + +# This grants, among other things, kernel module loading and therefore device +# ownership. +@unrestricted +` + +type DockerSupportInterface struct{} + +func (iface *DockerSupportInterface) Name() string { + return "docker-support" +} + +func (iface *DockerSupportInterface) PermanentPlugSnippet(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor, + interfaces.SecurityDBus, + interfaces.SecurityMount, + interfaces.SecuritySecComp, + interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *DockerSupportInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + privileged, _ := plug.Attrs["privileged-containers"].(bool) + switch securitySystem { + case interfaces.SecurityAppArmor: + snippet := []byte(dockerSupportConnectedPlugAppArmor) + if privileged { + snippet = append(snippet, dockerSupportPrivilegedAppArmor...) + } + return snippet, nil + case interfaces.SecuritySecComp: + snippet := []byte(dockerSupportConnectedPlugSecComp) + if privileged { + snippet = append(snippet, dockerSupportPrivilegedSecComp...) + } + return snippet, nil + case interfaces.SecurityDBus, + interfaces.SecurityMount, + interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *DockerSupportInterface) PermanentSlotSnippet(slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor, + interfaces.SecurityDBus, + interfaces.SecurityMount, + interfaces.SecuritySecComp, + interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *DockerSupportInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor, + interfaces.SecurityDBus, + interfaces.SecurityMount, + interfaces.SecuritySecComp, + interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *DockerSupportInterface) SanitizeSlot(slot *interfaces.Slot) error { + if iface.Name() != slot.Interface { + panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) + } + return nil +} + +func (iface *DockerSupportInterface) SanitizePlug(plug *interfaces.Plug) error { + snapName := plug.Snap.Name() + devName := plug.Snap.Developer + // The docker-support interface can only by used with the docker + // project and Canonical + if snapName != "docker" || (devName != "canonical" && devName != "docker") { + return fmt.Errorf("docker-support interface is reserved for the upstream docker project") + } + + if v, ok := plug.Attrs["privileged-containers"]; ok { + if _, ok = v.(bool); !ok { + return fmt.Errorf("docker-support plug requires bool with 'privileged-containers'") + } + } + return nil +} + +func (iface *DockerSupportInterface) AutoConnect() bool { + return false +} diff --git a/interfaces/builtin/docker_support_test.go b/interfaces/builtin/docker_support_test.go new file mode 100644 index 00000000000..68b54801e26 --- /dev/null +++ b/interfaces/builtin/docker_support_test.go @@ -0,0 +1,276 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package builtin_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" +) + +type DockerSupportInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&DockerSupportInterfaceSuite{ + iface: &builtin.DockerSupportInterface{}, + slot: &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "ubuntu-core", + Type: snap.TypeOS}, + Name: "docker-support", + Interface: "docker-support", + }, + }, + plug: &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "docker", + SideInfo: snap.SideInfo{Developer: "docker"}, + }, + Name: "docker-support", + Interface: "docker-support", + }, + }, +}) + +func (s *DockerSupportInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "docker-support") +} + +func (s *DockerSupportInterfaceSuite) TestUnusedSecuritySystems(c *C) { + systems := [...]interfaces.SecuritySystem{interfaces.SecurityAppArmor, + interfaces.SecuritySecComp, interfaces.SecurityDBus, + interfaces.SecurityUDev, interfaces.SecurityMount} + for _, system := range systems { + snippet, err := s.iface.PermanentPlugSnippet(s.plug, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityDBus) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityUDev) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityMount) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) +} + +func (s *DockerSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { + // connected plugs have a non-nil security snippet for apparmor + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + // connected plugs have a non-nil security snippet for seccomp + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecuritySecComp) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) +} + +func (s *DockerSupportInterfaceSuite) TestUnexpectedSecuritySystems(c *C) { + snippet, err := s.iface.PermanentPlugSnippet(s.plug, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) +} + +func (s *DockerSupportInterfaceSuite) TestAutoConnect(c *C) { + c.Check(s.iface.AutoConnect(), Equals, false) +} + +func (s *DockerSupportInterfaceSuite) TestConnectedPlugSnippet(c *C) { + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `pivot_root`) + + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecuritySecComp) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `pivot_root`) +} + +func (s *DockerSupportInterfaceSuite) TestSanitizeSlot(c *C) { + err := s.iface.SanitizeSlot(s.slot) + c.Assert(err, IsNil) +} + +func (s *DockerSupportInterfaceSuite) TestSanitizePlugDockerDev(c *C) { + err := s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "docker", + SideInfo: snap.SideInfo{Developer: "docker"}, + }, + Name: "docker", + Interface: "docker-support", + }}) + c.Assert(err, IsNil) +} + +func (s *DockerSupportInterfaceSuite) TestSanitizePlugCanonicalDev(c *C) { + err := s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "docker", + SideInfo: snap.SideInfo{Developer: "canonical"}, + }, + Name: "docker", + Interface: "docker-support", + }}) + c.Assert(err, IsNil) +} + +func (s *DockerSupportInterfaceSuite) TestSanitizePlugOtherDev(c *C) { + err := s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "docker", + SideInfo: snap.SideInfo{Developer: "notdocker"}, + }, + Name: "docker", + Interface: "docker-support", + }}) + c.Assert(err, ErrorMatches, "docker-support interface is reserved for the upstream docker project") +} + +func (s *DockerSupportInterfaceSuite) TestSanitizePlugNotDockerDockerDev(c *C) { + err := s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "notdocker", + SideInfo: snap.SideInfo{Developer: "docker"}, + }, + Name: "notdocker", + Interface: "docker-support", + }}) + c.Assert(err, ErrorMatches, "docker-support interface is reserved for the upstream docker project") +} + +func (s *DockerSupportInterfaceSuite) TestSanitizePlugNotDockerCanonicalDev(c *C) { + err := s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "notdocker", + SideInfo: snap.SideInfo{Developer: "canonical"}, + }, + Name: "notdocker", + Interface: "docker-support", + }}) + c.Assert(err, ErrorMatches, "docker-support interface is reserved for the upstream docker project") +} + +func (s *DockerSupportInterfaceSuite) TestSanitizePlugNotDockerOtherDev(c *C) { + err := s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "notdocker", + SideInfo: snap.SideInfo{Developer: "notdocker"}, + }, + Name: "notdocker", + Interface: "docker-support", + }}) + c.Assert(err, ErrorMatches, "docker-support interface is reserved for the upstream docker project") +} + +func (s *DockerSupportInterfaceSuite) TestSanitizePlugWithPrivilegedTrue(c *C) { + var mockSnapYaml = []byte(`name: docker +version: 1.0 +plugs: + privileged: + interface: docker-support + privileged-containers: true +`) + + info, err := snap.InfoFromSnapYaml(mockSnapYaml) + c.Assert(err, IsNil) + info.SideInfo = snap.SideInfo{Developer: "docker"} + + plug := &interfaces.Plug{PlugInfo: info.Plugs["privileged"]} + err = s.iface.SanitizePlug(plug) + c.Assert(err, IsNil) + + snippet, err := s.iface.ConnectedPlugSnippet(plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `change_profile -> *,`) + + snippet, err = s.iface.ConnectedPlugSnippet(plug, s.slot, interfaces.SecuritySecComp) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `@unrestricted`) +} + +func (s *DockerSupportInterfaceSuite) TestSanitizePlugWithPrivilegedFalse(c *C) { + var mockSnapYaml = []byte(`name: docker +version: 1.0 +plugs: + privileged: + interface: docker-support + privileged-containers: false +`) + + info, err := snap.InfoFromSnapYaml(mockSnapYaml) + c.Assert(err, IsNil) + info.SideInfo = snap.SideInfo{Developer: "docker"} + + plug := &interfaces.Plug{PlugInfo: info.Plugs["privileged"]} + err = s.iface.SanitizePlug(plug) + c.Assert(err, IsNil) + + snippet, err := s.iface.ConnectedPlugSnippet(plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), Not(testutil.Contains), `change_profile -> *,`) + + snippet, err = s.iface.ConnectedPlugSnippet(plug, s.slot, interfaces.SecuritySecComp) + c.Assert(err, IsNil) + c.Assert(string(snippet), Not(testutil.Contains), `@unrestricted`) +} + +func (s *DockerSupportInterfaceSuite) TestSanitizePlugWithPrivilegedBad(c *C) { + var mockSnapYaml = []byte(`name: docker +version: 1.0 +plugs: + privileged: + interface: docker-support + privileged-containers: bad +`) + + info, err := snap.InfoFromSnapYaml(mockSnapYaml) + c.Assert(err, IsNil) + info.SideInfo = snap.SideInfo{Developer: "docker"} + + plug := &interfaces.Plug{PlugInfo: info.Plugs["privileged"]} + err = s.iface.SanitizePlug(plug) + c.Assert(err, Not(IsNil)) + c.Assert(err, ErrorMatches, "docker-support plug requires bool with 'privileged-containers'") +} diff --git a/interfaces/builtin/docker_test.go b/interfaces/builtin/docker_test.go new file mode 100644 index 00000000000..0b80702a291 --- /dev/null +++ b/interfaces/builtin/docker_test.go @@ -0,0 +1,203 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package builtin_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" +) + +type DockerInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&DockerInterfaceSuite{ + iface: &builtin.DockerInterface{}, + slot: &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "docker", + SideInfo: snap.SideInfo{Developer: "docker"}, + }, + Name: "docker-daemon", + Interface: "docker", + }, + }, + plug: &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "docker"}, + Name: "docker-client", + Interface: "docker", + }, + }, +}) + +func (s *DockerInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "docker") +} + +func (s *DockerInterfaceSuite) TestUnusedSecuritySystems(c *C) { + systems := [...]interfaces.SecuritySystem{interfaces.SecurityAppArmor, + interfaces.SecuritySecComp, interfaces.SecurityDBus, + interfaces.SecurityUDev, interfaces.SecurityMount} + for _, system := range systems { + snippet, err := s.iface.PermanentPlugSnippet(s.plug, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityDBus) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityUDev) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityMount) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) +} + +func (s *DockerInterfaceSuite) TestUsedSecuritySystems(c *C) { + // connected plugs have a non-nil security snippet for apparmor + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + // connected plugs have a non-nil security snippet for seccomp + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecuritySecComp) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) +} + +func (s *DockerInterfaceSuite) TestUnexpectedSecuritySystems(c *C) { + snippet, err := s.iface.PermanentPlugSnippet(s.plug, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) +} + +func (s *DockerInterfaceSuite) TestAutoConnect(c *C) { + c.Check(s.iface.AutoConnect(), Equals, false) +} + +func (s *DockerInterfaceSuite) TestConnectedPlugSnippet(c *C) { + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `run/docker.sock`) + + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecuritySecComp) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `bind`) +} + +func (s *DockerInterfaceSuite) TestSanitizeSlotDockerDev(c *C) { + err := s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "docker", + SideInfo: snap.SideInfo{Developer: "docker"}, + }, + Name: "docker", + Interface: "docker", + }}) + c.Assert(err, IsNil) +} + +func (s *DockerInterfaceSuite) TestSanitizeSlotCanonicalDev(c *C) { + err := s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "docker", + SideInfo: snap.SideInfo{Developer: "canonical"}, + }, + Name: "docker", + Interface: "docker", + }}) + c.Assert(err, IsNil) +} + +func (s *DockerInterfaceSuite) TestSanitizeSlotOtherDev(c *C) { + err := s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "docker", + SideInfo: snap.SideInfo{Developer: "notdocker"}, + }, + Name: "docker", + Interface: "docker", + }}) + c.Assert(err, ErrorMatches, "docker slot interface is reserved for the upstream docker project") +} + +func (s *DockerInterfaceSuite) TestSanitizeSlotNotDockerDockerDev(c *C) { + err := s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "notdocker", + SideInfo: snap.SideInfo{Developer: "docker"}, + }, + Name: "notdocker", + Interface: "docker", + }}) + c.Assert(err, ErrorMatches, "docker slot interface is reserved for the upstream docker project") +} + +func (s *DockerInterfaceSuite) TestSanitizeSlotNotDockerCanonicalDev(c *C) { + err := s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "notdocker", + SideInfo: snap.SideInfo{Developer: "canonical"}, + }, + Name: "notdocker", + Interface: "docker", + }}) + c.Assert(err, ErrorMatches, "docker slot interface is reserved for the upstream docker project") +} + +func (s *DockerInterfaceSuite) TestSanitizeSlotNotDockerOtherDev(c *C) { + err := s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "notdocker", + SideInfo: snap.SideInfo{Developer: "notdocker"}, + }, + Name: "notdocker", + Interface: "docker", + }}) + c.Assert(err, ErrorMatches, "docker slot interface is reserved for the upstream docker project") +} + +func (s *DockerInterfaceSuite) TestSanitizePlug(c *C) { + err := s.iface.SanitizePlug(s.plug) + c.Assert(err, IsNil) +} diff --git a/snap/implicit.go b/snap/implicit.go index da838b29f71..4b42f1bcebc 100644 --- a/snap/implicit.go +++ b/snap/implicit.go @@ -29,6 +29,7 @@ import ( var implicitSlots = []string{ "bluetooth-control", + "docker-support", "firewall-control", "fuse-support", "home",