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",