diff --git a/overlord/assertstate/testkeys.go b/asserts/sysdb/testkeys.go similarity index 94% rename from overlord/assertstate/testkeys.go rename to asserts/sysdb/testkeys.go index 25d4c13afe9..7b615645970 100644 --- a/overlord/assertstate/testkeys.go +++ b/asserts/sysdb/testkeys.go @@ -18,7 +18,7 @@ * */ -package assertstate +package sysdb import ( "github.com/snapcore/snapd/asserts/systestkeys" @@ -26,5 +26,5 @@ import ( // init will inject the test trusted assertions when this module build tag "withtestkeys" is defined. func init() { - systestkeys.Inject() + InjectTrusted(systestkeys.Trusted) } diff --git a/asserts/systestkeys/trusted.go b/asserts/systestkeys/trusted.go index 90834f6823c..eeb2f2a6563 100644 --- a/asserts/systestkeys/trusted.go +++ b/asserts/systestkeys/trusted.go @@ -24,7 +24,6 @@ import ( "fmt" "github.com/snapcore/snapd/asserts" - "github.com/snapcore/snapd/asserts/sysdb" ) const ( @@ -239,6 +238,8 @@ var ( TestRootAccountKey asserts.Assertion // here for convenience, does not need to be in the trusted set TestStoreAccountKey asserts.Assertion + // Testing-only trusted assertions for injecting in the the system trusted set. + Trusted []asserts.Assertion ) func init() { @@ -258,9 +259,5 @@ func init() { TestRootAccount = acct TestRootAccountKey = accKey TestStoreAccountKey = storeAccKey -} - -// Inject includes the test trusted assertions in the system trusted set. -func Inject() { - sysdb.InjectTrusted([]asserts.Assertion{TestRootAccount, TestRootAccountKey}) + Trusted = []asserts.Assertion{TestRootAccount, TestRootAccountKey} } diff --git a/boot/kernel_os_test.go b/boot/kernel_os_test.go index 22c79c12e2f..d37ca865c56 100644 --- a/boot/kernel_os_test.go +++ b/boot/kernel_os_test.go @@ -152,7 +152,7 @@ func (s *kernelOSSuite) TestSetNextBootOnClassic(c *C) { defer restore() // Create a fake OS snap that we try to update - snapInfo := snaptest.MockSnap(c, "name: os\ntype: os", &snap.SideInfo{Revision: snap.R(42)}) + snapInfo := snaptest.MockSnap(c, "name: os\ntype: os", "SNAP", &snap.SideInfo{Revision: snap.R(42)}) err := boot.SetNextBoot(snapInfo) c.Assert(err, IsNil) diff --git a/cmd/snap-exec/main_test.go b/cmd/snap-exec/main_test.go index 5b370eca135..1e59ce5dfbf 100644 --- a/cmd/snap-exec/main_test.go +++ b/cmd/snap-exec/main_test.go @@ -73,6 +73,8 @@ hooks: configure: `) +var mockContents = "" + var binaryTemplate = `#!/bin/sh echo "$(basename $0)" >> %[1]q for arg in "$@"; do @@ -128,7 +130,7 @@ func (s *snapExecSuite) TestFindCommandNoCommand(c *C) { func (s *snapExecSuite) TestSnapExecAppIntegration(c *C) { dirs.SetRootDir(c.MkDir()) - snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("42"), }) @@ -152,7 +154,7 @@ func (s *snapExecSuite) TestSnapExecAppIntegration(c *C) { func (s *snapExecSuite) TestSnapExecHookIntegration(c *C) { dirs.SetRootDir(c.MkDir()) - snaptest.MockSnap(c, string(mockHookYaml), &snap.SideInfo{ + snaptest.MockSnap(c, string(mockHookYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("42"), }) @@ -173,7 +175,7 @@ func (s *snapExecSuite) TestSnapExecHookIntegration(c *C) { func (s *snapExecSuite) TestSnapExecHookMissingHookIntegration(c *C) { dirs.SetRootDir(c.MkDir()) - snaptest.MockSnap(c, string(mockHookYaml), &snap.SideInfo{ + snaptest.MockSnap(c, string(mockHookYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("42"), }) @@ -210,7 +212,7 @@ func (s *snapExecSuite) TestSnapExecAppRealIntegration(c *C) { os.Setenv("SNAP_REVISION", "42") defer os.Unsetenv("SNAP_REVISION") - snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("42"), }) @@ -251,7 +253,7 @@ func (s *snapExecSuite) TestSnapExecHookRealIntegration(c *C) { canaryFile := filepath.Join(c.MkDir(), "canary.txt") - testSnap := snaptest.MockSnap(c, string(mockHookYaml), &snap.SideInfo{ + testSnap := snaptest.MockSnap(c, string(mockHookYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("42"), }) hookPath := filepath.Join("meta", "hooks", "configure") @@ -286,7 +288,7 @@ func actuallyExec(argv0 string, argv []string, env []string) error { func (s *snapExecSuite) TestSnapExecShellIntegration(c *C) { dirs.SetRootDir(c.MkDir()) - snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("42"), }) diff --git a/cmd/snap/cmd_run_test.go b/cmd/snap/cmd_run_test.go index 38e015f37b2..f111a79cb12 100644 --- a/cmd/snap/cmd_run_test.go +++ b/cmd/snap/cmd_run_test.go @@ -42,6 +42,7 @@ apps: hooks: configure: `) +var mockContents = "SNAP" func (s *SnapSuite) TestInvalidParameters(c *check.C) { invalidParameters := []string{"run", "--hook=configure", "--command=command-name", "snap-name"} @@ -66,7 +67,7 @@ func (s *SnapSuite) TestSnapRunAppIntegration(c *check.C) { dirs.SetRootDir(c.MkDir()) defer func() { dirs.SetRootDir("/") }() - si := snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("x2"), }) err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current")) @@ -102,7 +103,7 @@ func (s *SnapSuite) TestSnapRunAppWithCommandIntegration(c *check.C) { dirs.SetRootDir(c.MkDir()) defer func() { dirs.SetRootDir("/") }() - si := snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current")) @@ -154,7 +155,7 @@ func (s *SnapSuite) TestSnapRunHookIntegration(c *check.C) { dirs.SetRootDir(c.MkDir()) defer func() { dirs.SetRootDir("/") }() - si := snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current")) @@ -189,7 +190,7 @@ func (s *SnapSuite) TestSnapRunHookUnsetRevisionIntegration(c *check.C) { dirs.SetRootDir(c.MkDir()) defer func() { dirs.SetRootDir("/") }() - si := snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current")) @@ -225,10 +226,10 @@ func (s *SnapSuite) TestSnapRunHookSpecificRevisionIntegration(c *check.C) { defer func() { dirs.SetRootDir("/") }() // Create both revisions 41 and 42 - snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(41), }) - snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) @@ -262,7 +263,7 @@ func (s *SnapSuite) TestSnapRunHookMissingRevisionIntegration(c *check.C) { defer func() { dirs.SetRootDir("/") }() // Only create revision 42 - si := snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current")) @@ -292,7 +293,7 @@ func (s *SnapSuite) TestSnapRunHookMissingHookIntegration(c *check.C) { defer func() { dirs.SetRootDir("/") }() // Only create revision 42 - si := snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current")) @@ -331,7 +332,7 @@ func (s *SnapSuite) TestSnapRunSaneEnvironmentHandling(c *check.C) { dirs.SetRootDir(c.MkDir()) defer func() { dirs.SetRootDir("/") }() - si := snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current")) @@ -350,11 +351,11 @@ func (s *SnapSuite) TestSnapRunSaneEnvironmentHandling(c *check.C) { os.Setenv("SNAP_ARCH", "PDP-7") defer os.Unsetenv("SNAP_NAME") defer os.Unsetenv("SNAP_ARCH") - // but unreleated stuff is ok + // but unrelated stuff is ok os.Setenv("SNAP_THE_WORLD", "YES") defer os.Unsetenv("SNAP_THE_WORLD") - // and ensure those SNAP_ vars get overriden + // and ensure those SNAP_ vars get overridden rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) diff --git a/cmd/snap/cmd_set_test.go b/cmd/snap/cmd_set_test.go index cd51f302bfe..676c1b42477 100644 --- a/cmd/snap/cmd_set_test.go +++ b/cmd/snap/cmd_set_test.go @@ -36,6 +36,7 @@ version: 1.0 hooks: configure: `) +var validApplyContents = "" func (s *SnapSuite) TestInvalidSetParameters(c *check.C) { invalidParameters := []string{"set", "snap-name", "key", "value"} @@ -48,7 +49,7 @@ func (s *SnapSuite) TestSnapSetIntegrationString(c *check.C) { dirs.SetRootDir(c.MkDir()) defer func() { dirs.SetRootDir("/") }() - snaptest.MockSnap(c, string(validApplyYaml), &snap.SideInfo{ + snaptest.MockSnap(c, string(validApplyYaml), string(validApplyContents), &snap.SideInfo{ Revision: snap.R(42), }) @@ -65,7 +66,7 @@ func (s *SnapSuite) TestSnapSetIntegrationNumber(c *check.C) { dirs.SetRootDir(c.MkDir()) defer func() { dirs.SetRootDir("/") }() - snaptest.MockSnap(c, string(validApplyYaml), &snap.SideInfo{ + snaptest.MockSnap(c, string(validApplyYaml), string(validApplyContents), &snap.SideInfo{ Revision: snap.R(42), }) @@ -82,7 +83,7 @@ func (s *SnapSuite) TestSnapSetIntegrationJson(c *check.C) { dirs.SetRootDir(c.MkDir()) defer func() { dirs.SetRootDir("/") }() - snaptest.MockSnap(c, string(validApplyYaml), &snap.SideInfo{ + snaptest.MockSnap(c, string(validApplyYaml), string(validApplyContents), &snap.SideInfo{ Revision: snap.R(42), }) diff --git a/daemon/api_mock_test.go b/daemon/api_mock_test.go index 7efb0c285eb..07841d964c2 100644 --- a/daemon/api_mock_test.go +++ b/daemon/api_mock_test.go @@ -33,7 +33,7 @@ func (s *apiSuite) mockSnap(c *C, yamlText string) *snap.Info { panic("call s.daemon(c) in your test first") } - snapInfo := snaptest.MockSnap(c, yamlText, &snap.SideInfo{Revision: snap.R(1)}) + snapInfo := snaptest.MockSnap(c, yamlText, "", &snap.SideInfo{Revision: snap.R(1)}) snap.AddImplicitSlots(snapInfo) st := s.d.overlord.State() diff --git a/daemon/api_test.go b/daemon/api_test.go index 6ca13422de9..26d26e6bf8c 100644 --- a/daemon/api_test.go +++ b/daemon/api_test.go @@ -212,9 +212,10 @@ func (s *apiBaseSuite) mkInstalledInState(c *check.C, daemon *Daemon, name, deve name: %s version: %s %s`, name, version, extraYaml) + contents := "" // Mock the snap on disk - snapInfo := snaptest.MockSnap(c, yamlText, sideInfo) + snapInfo := snaptest.MockSnap(c, yamlText, contents, sideInfo) c.Assert(os.MkdirAll(snapInfo.DataDir(), 0755), check.IsNil) metadir := filepath.Join(snapInfo.MountDir(), "meta") @@ -245,7 +246,8 @@ version: 1 type: gadget gadget: {store: {id: %q}} `, store) - snaptest.MockSnap(c, yamlText, &snap.SideInfo{Revision: snap.R(1)}) + contents := "" + snaptest.MockSnap(c, yamlText, contents, &snap.SideInfo{Revision: snap.R(1)}) c.Assert(os.Symlink("1", filepath.Join(dirs.SnapMountDir, "test", "current")), check.IsNil) } diff --git a/debian/changelog b/debian/changelog index 50445b24ba3..e34fda8ced7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -192,7 +192,7 @@ snapd (2.17) xenial; urgency=medium declarations (#2027) - store: apply deltas if explicitly enabled (#2031) - tests: fix create-key/snap-sign test isolation (#2032) - - snap/implicit: don't restrict the camera iface to clasic (#2025) + - snap/implicit: don't restrict the camera iface to classic (#2025) - client, cmd: change buy command to match UX document (#2011) - coreconfig: nuke it. Also, ignore po/snappy.pot. (#2030) - store: download deltas if explicitly enabled (#2017) @@ -310,7 +310,7 @@ snapd (2.15.2ubuntu1) xenial; urgency=medium (cherry pick PR: #1926) - interfaces: allow xdg-open in unity7, unity7 cleanups (cherry pick PR: #1946) - - tests: ensure http{,s}_proxy is defined inside the fake-store + - tests: ensure http{,s}_proxy is defined inside the fake-store (cherry pick PR: #1949) -- Michael Vogt Wed, 21 Sep 2016 17:21:12 +0200 @@ -579,7 +579,7 @@ snapd (2.13) xenial; urgency=medium - overlord/snapstate: check changes to SnapState for conflicts also. - docs/interfaces: change snappy command to snap - tests: test `snap run --hook` using in-tree snap-exec. - - partition: ensure that snap_{kernel,core} is not overriden with an + - partition: ensure that snap_{kernel,core} is not overridden with an empty value - asserts,overlord/assertstate: introduce an assertstate task handler to fetch snap assertions @@ -1139,7 +1139,7 @@ snapd (2.0.4) xenial; urgency=medium - debian/tests: add reboot capability to autopkgtest and execute snapPersistsSuite - daemon,snappy,progress: drop license agreement broken logic - - daemon,client,cmd/snap: nice access denied message + - daemon,client,cmd/snap: nice access denied message (LP: #1574829) - daemon: add user parameter to all commands - snap, store: rework purchase methods into decorators @@ -1373,7 +1373,7 @@ snapd (1.9.3) xenial; urgency=medium - daemon: auto install ubuntu-core if missing - oauth,store: remove OAuth authentication logic - overlord/ifacestate: simplify some tests with implicit manager - initalization + initialization - store, snappy: move away from hitting details directly - overlord/ifacestate: reload connections when restarting the manager @@ -1531,7 +1531,7 @@ ubuntu-snappy (1.7.3+20160303ubuntu1) xenial; urgency=medium - snappy,daemon,snap/lightweight,cmd/snappy,docs/rest.md: expose explicit channel selection to rest api - interfaces,daemon: rename package holding built-in interfaces - - integration-tests: add the first classic dimention tests + - integration-tests: add the first classic dimension tests - client,deaemon,docs: rename skills to interfaces on the wire - asserts: add identity assertion type - integration-tests: add the no_proxy env var @@ -1683,11 +1683,11 @@ ubuntu-snappy (1.7.2+20160204ubuntu1) xenial; urgency=medium - snappy: move SnapFile.Install() into Overlord.Install() - integration-tests: re-enable some failover tests - client: remove snaps - - asserts: uniform searching accross trusted (account keys) and main + - asserts: uniform searching across trusted (account keys) and main backstore - asserts: introduce Decoder to parse streams of assertions and Encoder to build them - - client: filter snaps with a seach query + - client: filter snaps with a search query - client: pass query as well as path in client internals - skills: provide different security snippets for skill and slot side @@ -1832,7 +1832,7 @@ ubuntu-snappy (1.2-0ubuntu1) wily; urgency=medium - Consider the root directory when installing and removing policies - In the uboot TestHandleAssetsNoHardwareYaml, patch the cache dir before creating the partition type - - In the PartitionTestSuite, remove the unnecesary patches for + - In the PartitionTestSuite, remove the unnecessary patches for defaultCacheDir - Fix the help output of "snappy install -h" @@ -1895,7 +1895,7 @@ ubuntu-snappy (0.1.1-0ubuntu1) vivid; urgency=low -- Michael Vogt Thu, 12 Feb 2015 13:51:22 +0100 -ubuntu-snappy (0.1-0ubuntu1) vivid; urgency=medium +ubuntu-snappy (0.1-0ubuntu1) vivid; urgency=medium * Initial packaging diff --git a/dirs/dirs.go b/dirs/dirs.go index 24ce0a73c39..b3a8efa5cdd 100644 --- a/dirs/dirs.go +++ b/dirs/dirs.go @@ -66,6 +66,8 @@ var ( ClassicDir string LibExecDir string + + XdgRuntimeDirGlob string ) var ( @@ -142,4 +144,6 @@ func SetRootDir(rootdir string) { ClassicDir = filepath.Join(rootdir, "/writable/classic") LibExecDir = filepath.Join(rootdir, "/usr/lib/snapd") + + XdgRuntimeDirGlob = filepath.Join(rootdir, "/run/user/*/") } diff --git a/etc/X11/Xsession.d/65snappy b/etc/X11/Xsession.d/65snappy index b19b6465369..17e92118ff6 100644 --- a/etc/X11/Xsession.d/65snappy +++ b/etc/X11/Xsession.d/65snappy @@ -1,5 +1,5 @@ # This file is sourced by Xsession(5), not executed. -# Add additionnal the additonal snappy desktop path +# Add the additional snappy desktop path if [ -z "$XDG_DATA_DIRS" ]; then # 60x11-common_xdg_path does not always set XDG_DATA_DIRS diff --git a/image/image.go b/image/image.go index 61ac831df3e..56c98814d01 100644 --- a/image/image.go +++ b/image/image.go @@ -38,9 +38,6 @@ import ( "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/squashfs" "github.com/snapcore/snapd/store" - - // important so that the testkeys get imported - _ "github.com/snapcore/snapd/overlord/assertstate" ) var ( @@ -407,6 +404,7 @@ func setBootvars(downloadedSnapsInfo map[string]*snap.Info) error { } m := map[string]string{ + "snap_mode": "", "snap_try_core": "", "snap_try_kernel": "", } diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go index ab827e2d8d1..6c8870fa842 100644 --- a/interfaces/apparmor/template.go +++ b/interfaces/apparmor/template.go @@ -237,6 +237,7 @@ var defaultTemplate = []byte(` owner @{PROC}/@{pid}/cmdline r, # Miscellaneous accesses + /dev/{,u}random w, /etc/machine-id r, /etc/mime.types r, @{PROC}/ r, @@ -319,6 +320,10 @@ var defaultTemplate = []byte(` # access in /dev/shm for shm_open() and files in subdirectories for open() /{dev,run}/shm/snap.@{SNAP_NAME}.** mrwlkix, + # Snap-specific XDG_RUNTIME_DIR that is based on the UID of the user + owner /{dev,run}/user/[0-9]*/snap.@{SNAP_NAME}/ rw, + owner /{dev,run}/user/[0-9]*/snap.@{SNAP_NAME}/** mrwklix, + # Allow apps from the same package to communicate with each other via an # abstract or anonymous socket unix peer=(label=snap.@{SNAP_NAME}.*), diff --git a/interfaces/builtin/all.go b/interfaces/builtin/all.go index 0dcb79b00c4..c7a370bf5a1 100644 --- a/interfaces/builtin/all.go +++ b/interfaces/builtin/all.go @@ -24,14 +24,16 @@ import ( ) var allInterfaces = []interfaces.Interface{ - &BoolFileInterface{}, &BluezInterface{}, + &BoolFileInterface{}, &BrowserSupportInterface{}, &ContentInterface{}, &DockerInterface{}, &DockerSupportInterface{}, + &FwupdInterface{}, &GpioInterface{}, &HidrawInterface{}, + &I2cInterface{}, &LocationControlInterface{}, &LocationObserveInterface{}, &LxdInterface{}, @@ -40,46 +42,49 @@ var allInterfaces = []interfaces.Interface{ &ModemManagerInterface{}, &MprisInterface{}, &NetworkManagerInterface{}, + &OfonoInterface{}, &PppInterface{}, - &SerialPortInterface{}, &PulseAudioInterface{}, + &SerialPortInterface{}, &UDisks2Interface{}, - &FwupdInterface{}, + NewAlsaInterface(), + NewAvahiObserveInterface(), + NewBluetoothControlInterface(), + NewCameraInterface(), + NewCupsControlInterface(), NewDcdbasControlInterface(), NewFirewallControlInterface(), + NewFuseSupportInterface(), NewGsettingsInterface(), NewHardwareObserveInterface(), NewHomeInterface(), + NewKernelModuleControlInterface(), + NewLibvirtInterface(), NewLocaleControlInterface(), NewLogObserveInterface(), NewMountObserveInterface(), - NewNetworkInterface(), NewNetworkBindInterface(), NewNetworkControlInterface(), + NewNetworkInterface(), NewNetworkObserveInterface(), NewNetworkSetupObserveInterface(), + NewOpenglInterface(), + NewOpticalDriveInterface(), NewProcessControlInterface(), NewRemovableMediaInterface(), NewScreenInhibitControlInterface(), + NewShutdownInterface(), NewSnapdControlInterface(), NewSystemObserveInterface(), NewSystemTraceInterface(), + NewTimeControlInterface(), NewTimeserverControlInterface(), NewTimezoneControlInterface(), NewTpmInterface(), - NewUnity7Interface(), NewUPowerObserveInterface(), + NewUnity7Interface(), + NewUsbRawInterface(), NewX11Interface(), - NewOpenglInterface(), - NewCupsControlInterface(), - NewOpticalDriveInterface(), - NewCameraInterface(), - NewBluetoothControlInterface(), - NewKernelModuleControlInterface(), - NewFuseSupportInterface(), - NewShutdownInterface(), - NewTimeControlInterface(), - NewLibvirtInterface(), } // Interfaces returns all of the built-in interfaces. diff --git a/interfaces/builtin/all_test.go b/interfaces/builtin/all_test.go index 052f486808c..381ef405eae 100644 --- a/interfaces/builtin/all_test.go +++ b/interfaces/builtin/all_test.go @@ -32,49 +32,55 @@ var _ = Suite(&AllSuite{}) func (s *AllSuite) TestInterfaces(c *C) { all := builtin.Interfaces() - c.Check(all, Contains, &builtin.BoolFileInterface{}) c.Check(all, Contains, &builtin.BluezInterface{}) + c.Check(all, Contains, &builtin.BoolFileInterface{}) c.Check(all, Contains, &builtin.BrowserSupportInterface{}) c.Check(all, Contains, &builtin.DockerInterface{}) c.Check(all, Contains, &builtin.DockerSupportInterface{}) + c.Check(all, Contains, &builtin.FwupdInterface{}) + c.Check(all, Contains, &builtin.FwupdInterface{}) c.Check(all, Contains, &builtin.GpioInterface{}) c.Check(all, Contains, &builtin.HidrawInterface{}) + c.Check(all, Contains, &builtin.I2cInterface{}) c.Check(all, Contains, &builtin.LocationControlInterface{}) c.Check(all, Contains, &builtin.LocationObserveInterface{}) c.Check(all, Contains, &builtin.LxdSupportInterface{}) c.Check(all, Contains, &builtin.MirInterface{}) c.Check(all, Contains, &builtin.MprisInterface{}) - c.Check(all, Contains, &builtin.SerialPortInterface{}) c.Check(all, Contains, &builtin.PulseAudioInterface{}) + c.Check(all, Contains, &builtin.SerialPortInterface{}) c.Check(all, Contains, &builtin.UDisks2Interface{}) - c.Check(all, Contains, &builtin.FwupdInterface{}) + c.Check(all, DeepContains, builtin.NewAlsaInterface()) + c.Check(all, DeepContains, builtin.NewAvahiObserveInterface()) + c.Check(all, DeepContains, builtin.NewBluetoothControlInterface()) + c.Check(all, DeepContains, builtin.NewCameraInterface()) + c.Check(all, DeepContains, builtin.NewCupsControlInterface()) c.Check(all, DeepContains, builtin.NewFirewallControlInterface()) + c.Check(all, DeepContains, builtin.NewFuseSupportInterface()) c.Check(all, DeepContains, builtin.NewGsettingsInterface()) c.Check(all, DeepContains, builtin.NewHomeInterface()) + c.Check(all, DeepContains, builtin.NewKernelModuleControlInterface()) c.Check(all, DeepContains, builtin.NewLocaleControlInterface()) c.Check(all, DeepContains, builtin.NewLogObserveInterface()) c.Check(all, DeepContains, builtin.NewMountObserveInterface()) - c.Check(all, DeepContains, builtin.NewNetworkInterface()) c.Check(all, DeepContains, builtin.NewNetworkBindInterface()) c.Check(all, DeepContains, builtin.NewNetworkControlInterface()) + c.Check(all, DeepContains, builtin.NewNetworkInterface()) c.Check(all, DeepContains, builtin.NewNetworkObserveInterface()) + c.Check(all, DeepContains, builtin.NewOpenglInterface()) + c.Check(all, DeepContains, builtin.NewOpticalDriveInterface()) c.Check(all, DeepContains, builtin.NewProcessControlInterface()) c.Check(all, DeepContains, builtin.NewRemovableMediaInterface()) c.Check(all, DeepContains, builtin.NewScreenInhibitControlInterface()) c.Check(all, DeepContains, builtin.NewSnapdControlInterface()) c.Check(all, DeepContains, builtin.NewSystemObserveInterface()) c.Check(all, DeepContains, builtin.NewSystemTraceInterface()) + c.Check(all, DeepContains, builtin.NewTimeControlInterface()) c.Check(all, DeepContains, builtin.NewTimeserverControlInterface()) c.Check(all, DeepContains, builtin.NewTimezoneControlInterface()) c.Check(all, DeepContains, builtin.NewTpmInterface()) - c.Check(all, DeepContains, builtin.NewUnity7Interface()) c.Check(all, DeepContains, builtin.NewUPowerObserveInterface()) + c.Check(all, DeepContains, builtin.NewUnity7Interface()) + c.Check(all, DeepContains, builtin.NewUsbRawInterface()) c.Check(all, DeepContains, builtin.NewX11Interface()) - c.Check(all, DeepContains, builtin.NewOpenglInterface()) - c.Check(all, DeepContains, builtin.NewCupsControlInterface()) - c.Check(all, DeepContains, builtin.NewOpticalDriveInterface()) - c.Check(all, DeepContains, builtin.NewCameraInterface()) - c.Check(all, DeepContains, builtin.NewKernelModuleControlInterface()) - c.Check(all, DeepContains, builtin.NewFuseSupportInterface()) - c.Check(all, DeepContains, builtin.NewTimeControlInterface()) } diff --git a/interfaces/builtin/alsa.go b/interfaces/builtin/alsa.go new file mode 100644 index 00000000000..886fc6e5ded --- /dev/null +++ b/interfaces/builtin/alsa.go @@ -0,0 +1,39 @@ +// -*- 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 "github.com/snapcore/snapd/interfaces" + +const alsaConnectedPlugAppArmor = ` +# Description: Allow access to raw ALSA devices. + +/dev/snd/ r, +/dev/snd/* rw, + +/run/udev/data/c116:[0-9]* r, # alsa +` + +func NewAlsaInterface() interfaces.Interface { + return &commonInterface{ + name: "alsa", + connectedPlugAppArmor: alsaConnectedPlugAppArmor, + reservedForOS: true, + } +} diff --git a/interfaces/builtin/alsa_test.go b/interfaces/builtin/alsa_test.go new file mode 100644 index 00000000000..f27b91f1368 --- /dev/null +++ b/interfaces/builtin/alsa_test.go @@ -0,0 +1,88 @@ +// -*- 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 AlsaInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&AlsaInterfaceSuite{ + iface: builtin.NewAlsaInterface(), + slot: &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "alsa", + Interface: "alsa", + }, + }, + plug: &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "other"}, + Name: "alsa", + Interface: "alsa", + }, + }, +}) + +func (s *AlsaInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "alsa") +} + +func (s *AlsaInterfaceSuite) TestSanitizeSlot(c *C) { + err := s.iface.SanitizeSlot(s.slot) + c.Assert(err, IsNil) + err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "alsa", + Interface: "alsa", + }}) + c.Assert(err, ErrorMatches, "alsa slots are reserved for the operating system snap") +} + +func (s *AlsaInterfaceSuite) TestSanitizePlug(c *C) { + err := s.iface.SanitizePlug(s.plug) + c.Assert(err, IsNil) +} + +func (s *AlsaInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { + c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, + PanicMatches, `slot is not of interface "alsa"`) + c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, + PanicMatches, `plug is not of interface "alsa"`) +} + +func (s *AlsaInterfaceSuite) 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)) + c.Check(string(snippet), testutil.Contains, "/dev/snd/* rw,") +} diff --git a/interfaces/builtin/avahi_observe.go b/interfaces/builtin/avahi_observe.go new file mode 100644 index 00000000000..b73acecd582 --- /dev/null +++ b/interfaces/builtin/avahi_observe.go @@ -0,0 +1,129 @@ +// -*- 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 "github.com/snapcore/snapd/interfaces" + +const avahiObserveConnectedPlugAppArmor = ` +# Description: allows domain browsing, service browsing and service resolving + +#include +dbus (send) + bus=system + path=/ + interface=org.freedesktop.DBus.Peer + member=Ping + peer=(name=org.freedesktop.Avahi,label=unconfined), + +dbus (send) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member=Get* + peer=(name=org.freedesktop.Avahi,label=unconfined), + + +# These allows tampering with other snap's browsers, so don't autoconnect for +# now. + +# service browsing +dbus (send) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member=ServiceBrowserNew + peer=(name=org.freedesktop.Avahi,label=unconfined), + +dbus (send) + bus=system + path=/Client*/ServiceBrowser* + interface=org.freedesktop.Avahi.ServiceBrowser + member=Free + peer=(name=org.freedesktop.Avahi,label=unconfined), + +dbus (receive) + bus=system + interface=org.freedesktop.Avahi.ServiceBrowser + peer=(label=unconfined), + +# service resolving +dbus (send) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member=ServiceResolverNew + peer=(name=org.freedesktop.Avahi,label=unconfined), + +dbus (send) + bus=system + path=/Client*/ServiceResolver* + interface=org.freedesktop.Avahi.ServiceResolver + member=Free + peer=(name=org.freedesktop.Avahi,label=unconfined), + +dbus (receive) + bus=system + interface=org.freedesktop.Avahi.ServiceResolver + peer=(label=unconfined), + +# domain browsing +dbus (send) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member=DomainBrowserNew + peer=(name=org.freedesktop.Avahi,label=unconfined), + +dbus (send) + bus=system + path=/Client*/DomainBrowser* + interface=org.freedesktop.Avahi.DomainBrowser + member=Free + peer=(name=org.freedesktop.Avahi,label=unconfined), + +dbus (receive) + bus=system + path=/Client*/DomainBrowser* + interface=org.freedesktop.Avahi.DomainBrowser + peer=(label=unconfined), +` + +const avahiObserveConnectedPlugSecComp = ` +# Description: allows domain browsing, service browsing and service resolving + +# dbus +connect +getsockname +recvfrom +recvmsg +send +sendto +sendmsg +socket +` + +func NewAvahiObserveInterface() interfaces.Interface { + return &commonInterface{ + name: "avahi-observe", + connectedPlugAppArmor: avahiObserveConnectedPlugAppArmor, + connectedPlugSecComp: avahiObserveConnectedPlugSecComp, + reservedForOS: true, + } +} diff --git a/interfaces/builtin/avahi_observe_test.go b/interfaces/builtin/avahi_observe_test.go new file mode 100644 index 00000000000..d73ab86cd13 --- /dev/null +++ b/interfaces/builtin/avahi_observe_test.go @@ -0,0 +1,93 @@ +// -*- 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 AvahiObserveInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&AvahiObserveInterfaceSuite{ + iface: builtin.NewAvahiObserveInterface(), + slot: &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "avahi-observe", + Interface: "avahi-observe", + }, + }, + plug: &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "other"}, + Name: "avahi-observe", + Interface: "avahi-observe", + }, + }, +}) + +func (s *AvahiObserveInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "avahi-observe") +} + +func (s *AvahiObserveInterfaceSuite) TestSanitizeSlot(c *C) { + err := s.iface.SanitizeSlot(s.slot) + c.Assert(err, IsNil) + err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "avahi-observe", + Interface: "avahi-observe", + }}) + c.Assert(err, ErrorMatches, "avahi-observe slots are reserved for the operating system snap") +} + +func (s *AvahiObserveInterfaceSuite) TestSanitizePlug(c *C) { + err := s.iface.SanitizePlug(s.plug) + c.Assert(err, IsNil) +} + +func (s *AvahiObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { + c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, + PanicMatches, `slot is not of interface "avahi-observe"`) + c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, + PanicMatches, `plug is not of interface "avahi-observe"`) +} + +func (s *AvahiObserveInterfaceSuite) 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)) + c.Check(string(snippet), testutil.Contains, "name=org.freedesktop.Avahi") + + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecuritySecComp) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + c.Check(string(snippet), testutil.Contains, "sendto") +} diff --git a/interfaces/builtin/basedeclaration.go b/interfaces/builtin/basedeclaration.go index 9f10cfb8f66..183b353ca6b 100644 --- a/interfaces/builtin/basedeclaration.go +++ b/interfaces/builtin/basedeclaration.go @@ -153,6 +153,16 @@ plugs: allow-installation: false deny-auto-connection: true slots: + alsa: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true + avahi-observe: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true bluetooth-control: allow-installation: slot-snap-type: @@ -246,6 +256,12 @@ slots: - core deny-auto-connection: on-classic: false + i2c: + allow-installation: + slot-snap-type: + - gadget + - core + deny-auto-connection: true kernel-module-control: allow-installation: slot-snap-type: @@ -331,6 +347,14 @@ slots: slot-snap-type: - core deny-auto-connection: true + ofono: + allow-installation: + slot-snap-type: + - app + - core + deny-auto-connection: true + deny-connection: + on-classic: false opengl: allow-installation: slot-snap-type: @@ -420,6 +444,11 @@ slots: allow-installation: slot-snap-type: - core + usb-raw: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true x11: allow-installation: slot-snap-type: diff --git a/interfaces/builtin/basedeclaration_test.go b/interfaces/builtin/basedeclaration_test.go index 5630a2ba2c9..a1163c63ee3 100644 --- a/interfaces/builtin/basedeclaration_test.go +++ b/interfaces/builtin/basedeclaration_test.go @@ -157,9 +157,6 @@ func (s *baseDeclSuite) TestAutoConnection(c *C) { expected := autoconnect[iface.Name()] comm := Commentf(iface.Name()) - // cross-check with past behavior - c.Check(expected, Equals, iface.LegacyAutoConnect(), comm) - // check base declaration cand := s.connectCand(c, iface.Name(), "", "") err := cand.CheckAutoConnect() @@ -361,10 +358,12 @@ var ( "docker-support": []string{"core"}, "gpio": []string{"core", "gadget"}, "hidraw": []string{"core", "gadget"}, + "i2c": []string{"core", "gadget"}, "lxd-support": []string{"core"}, "mir": []string{"app"}, "mpris": []string{"app"}, "network-manager": []string{"app", "core"}, + "ofono": []string{"app", "core"}, "ppp": []string{"core"}, "pulseaudio": []string{"app", "core"}, "serial-port": []string{"core", "gadget"}, diff --git a/interfaces/builtin/bluetooth_control_test.go b/interfaces/builtin/bluetooth_control_test.go index b6068f2772b..51e740b67cf 100644 --- a/interfaces/builtin/bluetooth_control_test.go +++ b/interfaces/builtin/bluetooth_control_test.go @@ -88,7 +88,3 @@ func (s *BluetoothControlInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *BluetoothControlInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/bluez.go b/interfaces/builtin/bluez.go index 4eb905c50d7..294df76537b 100644 --- a/interfaces/builtin/bluez.go +++ b/interfaces/builtin/bluez.go @@ -240,10 +240,6 @@ func (iface *BluezInterface) SanitizeSlot(slot *interfaces.Slot) error { return nil } -func (iface *BluezInterface) LegacyAutoConnect() bool { - return false -} - func (iface *BluezInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/bool_file.go b/interfaces/builtin/bool_file.go index 691211dcd57..ab29ff1539d 100644 --- a/interfaces/builtin/bool_file.go +++ b/interfaces/builtin/bool_file.go @@ -147,10 +147,6 @@ func (iface *BoolFileInterface) isGPIO(slot *interfaces.Slot) bool { panic("slot is not sanitized") } -func (iface *BoolFileInterface) LegacyAutoConnect() bool { - return false -} - // AutoConnect returns whether plug and slot should be implicitly // auto-connected assuming they will be an unambiguous connection // candidate and declaration-based checks allow. diff --git a/interfaces/builtin/bool_file_test.go b/interfaces/builtin/bool_file_test.go index e08224987ba..1dc9d54c55c 100644 --- a/interfaces/builtin/bool_file_test.go +++ b/interfaces/builtin/bool_file_test.go @@ -237,7 +237,3 @@ func (s *BoolFileInterfaceSuite) TestPermanentSlotSnippetPanicksOnUnsanitizedSlo s.iface.PermanentSlotSnippet(s.missingPathSlot, interfaces.SecurityAppArmor) }, PanicMatches, "slot is not sanitized") } - -func (s *BoolFileInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/browser_support.go b/interfaces/builtin/browser_support.go index 427fae9438e..55a01294f11 100644 --- a/interfaces/builtin/browser_support.go +++ b/interfaces/builtin/browser_support.go @@ -137,6 +137,7 @@ unix (bind) addr="@[0-9A-F]*._service_*", # Policy needed only when using the chrome/chromium setuid sandbox +capability sys_ptrace, ptrace (trace) peer=snap.@{SNAP_NAME}.**, unix (receive, send) peer=(label=snap.@{SNAP_NAME}.**), @@ -260,10 +261,6 @@ func (iface *BrowserSupportInterface) PermanentPlugSnippet(plug *interfaces.Plug return nil, nil } -func (iface *BrowserSupportInterface) LegacyAutoConnect() bool { - return true -} - func (iface *BrowserSupportInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { return true } diff --git a/interfaces/builtin/browser_support_test.go b/interfaces/builtin/browser_support_test.go index 1af4a484fd0..2a5a4268781 100644 --- a/interfaces/builtin/browser_support_test.go +++ b/interfaces/builtin/browser_support_test.go @@ -173,7 +173,3 @@ func (s *BrowserSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *BrowserSupportInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} diff --git a/interfaces/builtin/common.go b/interfaces/builtin/common.go index d91927b319d..94cf479e5f6 100644 --- a/interfaces/builtin/common.go +++ b/interfaces/builtin/common.go @@ -39,7 +39,6 @@ type commonInterface struct { connectedPlugSecComp string connectedPlugKMod string reservedForOS bool - autoConnect bool // OBSOLETE, only cross-check info atm rejectAutoConnectPairs bool } @@ -116,10 +115,6 @@ func (iface *commonInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot * return nil, nil } -func (iface *commonInterface) LegacyAutoConnect() bool { - return iface.autoConnect -} - // AutoConnect returns whether plug and slot should be implicitly // auto-connected assuming they will be an unambiguous connection // candidate and declaration-based checks allow. diff --git a/interfaces/builtin/content.go b/interfaces/builtin/content.go index b8357460894..cc9fa3eb010 100644 --- a/interfaces/builtin/content.go +++ b/interfaces/builtin/content.go @@ -153,10 +153,6 @@ func (iface *ContentInterface) PermanentPlugSnippet(plug *interfaces.Plug, secur return nil, nil } -func (iface *ContentInterface) LegacyAutoConnect() bool { - return true -} - func (iface *ContentInterface) AutoConnect(plug *interfaces.Plug, slot *interfaces.Slot) bool { return plug.Attrs["content"] == slot.Attrs["content"] } diff --git a/interfaces/builtin/content_test.go b/interfaces/builtin/content_test.go index 961a1a4840b..c899d8a7e1e 100644 --- a/interfaces/builtin/content_test.go +++ b/interfaces/builtin/content_test.go @@ -245,39 +245,3 @@ slots: expected := "/var/snap/producer/common/export /var/snap/consumer/common/import none bind 0 0\n" c.Assert(string(content), Equals, expected) } - -func (s *ContentSuite) TestLegacyAutoConnect(c *C) { - const plugSnapYaml = `name: content-slot-snap -version: 1.0 -plugs: - content-plug: - interface: content - content: cont1 -` - info := snaptest.MockInfo(c, plugSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["content-plug"]} - - const slotSnapYaml = `name: content-slot-snap -version: 1.0 -slots: - content-slot: - interface: content - content: cont1 -` - info = snaptest.MockInfo(c, slotSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - - c.Check(s.iface.AutoConnect(plug, slot), Equals, true) - - const otherSnapYaml = `name: content-other-snap -version: 1.0 -slots: - content-slot: - interface: content - content: cont2 -` - info = snaptest.MockInfo(c, otherSnapYaml, nil) - otherslot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - - c.Check(s.iface.AutoConnect(plug, otherslot), Equals, false) -} diff --git a/interfaces/builtin/cups_control.go b/interfaces/builtin/cups_control.go index 16449384f9f..93a4793f478 100644 --- a/interfaces/builtin/cups_control.go +++ b/interfaces/builtin/cups_control.go @@ -29,6 +29,8 @@ const cupsControlConnectedPlugAppArmor = ` ` const cupsControlConnectedPlugSecComp = ` +recvfrom +sendto setsockopt ` @@ -39,6 +41,5 @@ func NewCupsControlInterface() interfaces.Interface { connectedPlugAppArmor: cupsControlConnectedPlugAppArmor, connectedPlugSecComp: cupsControlConnectedPlugSecComp, reservedForOS: true, - autoConnect: false, } } diff --git a/interfaces/builtin/dcdbas_control_test.go b/interfaces/builtin/dcdbas_control_test.go index a6f8bed54c6..f9ef816a5d9 100644 --- a/interfaces/builtin/dcdbas_control_test.go +++ b/interfaces/builtin/dcdbas_control_test.go @@ -86,7 +86,3 @@ func (s *DcdbasControlInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(snippet, Not(IsNil)) c.Assert(string(snippet), testutil.Contains, `/dcdbas/smi_data`) } - -func (s *DcdbasControlInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/docker.go b/interfaces/builtin/docker.go index bcd9f1c1334..a6f2335ef69 100644 --- a/interfaces/builtin/docker.go +++ b/interfaces/builtin/docker.go @@ -90,10 +90,6 @@ func (iface *DockerInterface) SanitizeSlot(slot *interfaces.Slot) error { return nil } -func (iface *DockerInterface) LegacyAutoConnect() bool { - return false -} - func (iface *DockerInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/docker_support.go b/interfaces/builtin/docker_support.go index 1219c2e870f..056a1dab230 100644 --- a/interfaces/builtin/docker_support.go +++ b/interfaces/builtin/docker_support.go @@ -69,6 +69,7 @@ capability, /dev/mapper/docker* rw, /dev/loop-control r, /dev/loop[0-9]* rw, +/sys/devices/virtual/block/dm-[0-9]*/** r, mount, umount, @@ -575,10 +576,6 @@ func (iface *DockerSupportInterface) SanitizePlug(plug *interfaces.Plug) error { return nil } -func (iface *DockerSupportInterface) LegacyAutoConnect() bool { - return false -} - func (iface *DockerSupportInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/docker_support_test.go b/interfaces/builtin/docker_support_test.go index 1b109b01d5e..9ab57596a24 100644 --- a/interfaces/builtin/docker_support_test.go +++ b/interfaces/builtin/docker_support_test.go @@ -72,10 +72,6 @@ func (s *DockerSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(snippet, Not(IsNil)) } -func (s *DockerSupportInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} - func (s *DockerSupportInterfaceSuite) TestConnectedPlugSnippet(c *C) { snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) c.Assert(err, IsNil) diff --git a/interfaces/builtin/docker_test.go b/interfaces/builtin/docker_test.go index d502b2abf77..3100f40ca98 100644 --- a/interfaces/builtin/docker_test.go +++ b/interfaces/builtin/docker_test.go @@ -70,10 +70,6 @@ func (s *DockerInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(snippet, Not(IsNil)) } -func (s *DockerInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} - func (s *DockerInterfaceSuite) TestConnectedPlugSnippet(c *C) { snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) c.Assert(err, IsNil) diff --git a/interfaces/builtin/firewall_control_test.go b/interfaces/builtin/firewall_control_test.go index 64e9ff0c279..cf6cb7a1127 100644 --- a/interfaces/builtin/firewall_control_test.go +++ b/interfaces/builtin/firewall_control_test.go @@ -92,7 +92,3 @@ func (s *FirewallControlInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *FirewallControlInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/fuse_support.go b/interfaces/builtin/fuse_support.go index 49762cb1bbf..a8c93dbd450 100644 --- a/interfaces/builtin/fuse_support.go +++ b/interfaces/builtin/fuse_support.go @@ -80,6 +80,5 @@ func NewFuseSupportInterface() interfaces.Interface { connectedPlugAppArmor: fuseSupportConnectedPlugAppArmor, connectedPlugSecComp: fuseSupportConnectedPlugSecComp, reservedForOS: true, - autoConnect: false, } } diff --git a/interfaces/builtin/fuse_support_test.go b/interfaces/builtin/fuse_support_test.go index c7dacd0dad3..5e835150a49 100644 --- a/interfaces/builtin/fuse_support_test.go +++ b/interfaces/builtin/fuse_support_test.go @@ -88,7 +88,3 @@ func (s *FuseSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *FuseSupportInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/fwupd.go b/interfaces/builtin/fwupd.go index 26f6f758b1f..8286183e72d 100644 --- a/interfaces/builtin/fwupd.go +++ b/interfaces/builtin/fwupd.go @@ -248,10 +248,6 @@ func (iface *FwupdInterface) SanitizeSlot(slot *interfaces.Slot) error { return nil } -func (iface *FwupdInterface) LegacyAutoConnect() bool { - return false -} - func (iface *FwupdInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/gpio.go b/interfaces/builtin/gpio.go index fda77d9b3a0..fae814393ae 100644 --- a/interfaces/builtin/gpio.go +++ b/interfaces/builtin/gpio.go @@ -147,10 +147,6 @@ func (iface *GpioInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *in return rawSnippet, nil } -func (iface *GpioInterface) LegacyAutoConnect() bool { - return false -} - func (iface *GpioInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/gsettings.go b/interfaces/builtin/gsettings.go index 1402ec6bb83..5c2e147db4e 100644 --- a/interfaces/builtin/gsettings.go +++ b/interfaces/builtin/gsettings.go @@ -62,6 +62,5 @@ func NewGsettingsInterface() interfaces.Interface { connectedPlugAppArmor: gsettingsConnectedPlugAppArmor, connectedPlugSecComp: gsettingsConnectedPlugSecComp, reservedForOS: true, - autoConnect: true, } } diff --git a/interfaces/builtin/gsettings_test.go b/interfaces/builtin/gsettings_test.go index 93af2df4482..f30d247f34e 100644 --- a/interfaces/builtin/gsettings_test.go +++ b/interfaces/builtin/gsettings_test.go @@ -88,7 +88,3 @@ func (s *GsettingsInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *GsettingsInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} diff --git a/interfaces/builtin/hardware_observe.go b/interfaces/builtin/hardware_observe.go index 4b582d0796d..4239e94707c 100644 --- a/interfaces/builtin/hardware_observe.go +++ b/interfaces/builtin/hardware_observe.go @@ -45,6 +45,14 @@ capability sys_rawio, # util-linux /{,usr/}bin/lscpu ixr, @{PROC}/bus/pci/devices r, + +# lsusb +# Note: lsusb and its database have to be shipped in the snap if not on classic +/{,usr/}bin/lsusb ixr, +/var/lib/usbutils/usb.ids r, +/dev/ r, +/dev/bus/usb/{,**/} r, +/etc/udev/udev.conf r, ` // NewHardwareObserveInterface returns a new "hardware-observe" interface. diff --git a/interfaces/builtin/hardware_observe_test.go b/interfaces/builtin/hardware_observe_test.go index 549f698b2f4..27e8cc031ee 100644 --- a/interfaces/builtin/hardware_observe_test.go +++ b/interfaces/builtin/hardware_observe_test.go @@ -84,7 +84,3 @@ func (s *HardwareObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *HardwareObserveInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/hidraw.go b/interfaces/builtin/hidraw.go index 42548e649eb..7f5f03f7ced 100644 --- a/interfaces/builtin/hidraw.go +++ b/interfaces/builtin/hidraw.go @@ -171,7 +171,7 @@ func (iface *HidrawInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot * } var udevSnippet bytes.Buffer for appName := range plug.Apps { - tag := fmt.Sprintf("snap_%s_%s", plug.Snap.Name(), appName) + tag := udevSnapSecurityName(plug.Snap.Name(), appName) udevSnippet.Write(udevUsbDeviceSnippet("hidraw", usbVendor, usbProduct, "TAG", tag)) } return udevSnippet.Bytes(), nil @@ -179,10 +179,6 @@ func (iface *HidrawInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot * return nil, nil } -func (iface *HidrawInterface) LegacyAutoConnect() bool { - return false -} - func (iface *HidrawInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/home.go b/interfaces/builtin/home.go index ca063ef83ec..1ef383751d0 100644 --- a/interfaces/builtin/home.go +++ b/interfaces/builtin/home.go @@ -21,7 +21,6 @@ package builtin import ( "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/release" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/home @@ -56,6 +55,5 @@ func NewHomeInterface() interfaces.Interface { name: "home", connectedPlugAppArmor: homeConnectedPlugAppArmor, reservedForOS: true, - autoConnect: release.OnClassic, } } diff --git a/interfaces/builtin/home_test.go b/interfaces/builtin/home_test.go index 5b9c2bc29dd..3cb6e3f6949 100644 --- a/interfaces/builtin/home_test.go +++ b/interfaces/builtin/home_test.go @@ -84,9 +84,3 @@ func (s *HomeInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *HomeInterfaceSuite) TestAutoConnect(c *C) { - iface := builtin.NewHomeInterface() - // allow what declarations allowed - c.Check(iface.AutoConnect(nil, nil), Equals, true) -} diff --git a/interfaces/builtin/i2c.go b/interfaces/builtin/i2c.go new file mode 100644 index 00000000000..33806f45540 --- /dev/null +++ b/interfaces/builtin/i2c.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 ( + "bytes" + "fmt" + "github.com/snapcore/snapd/interfaces" + "path/filepath" + "regexp" + "strings" +) + +// The type for i2c interface +type I2cInterface struct{} + +// Getter for the name of the i2c interface +func (iface *I2cInterface) Name() string { + return "i2c" +} + +func (iface *I2cInterface) String() string { + return iface.Name() +} + +// Pattern to match allowed i2c device nodes. It is gonna be used to check the +// validity of the path attributes in case the udev is not used for +// identification +var i2cControlDeviceNodePattern = regexp.MustCompile("^/dev/i2c-[0-9]+$") + +// Check validity of the defined slot +func (iface *I2cInterface) SanitizeSlot(slot *interfaces.Slot) error { + // Does it have right type? + if iface.Name() != slot.Interface { + panic(fmt.Sprintf("slot is not of interface %q", iface)) + } + + // Creation of the slot of this type + // is allowed only by a gadget snap + if !(slot.Snap.Type == "gadget" || slot.Snap.Type == "os") { + return fmt.Errorf("%s slots only allowed on gadget or core snaps", iface.Name()) + } + + // Validate the path + path, ok := slot.Attrs["path"].(string) + if !ok || path == "" { + return fmt.Errorf("%s slot must have a path attribute", iface.Name()) + } + + path = filepath.Clean(path) + + if !i2cControlDeviceNodePattern.MatchString(path) { + return fmt.Errorf("%s path attribute must be a valid device node", iface.Name()) + } + + return nil +} + +// Checks and possibly modifies a plug +func (iface *I2cInterface) SanitizePlug(plug *interfaces.Plug) error { + if iface.Name() != plug.Interface { + panic(fmt.Sprintf("plug is not of interface %q", iface)) + } + // Currently nothing is checked on the plug side + return nil +} + +// Returns snippet granted on install +func (iface *I2cInterface) PermanentSlotSnippet(slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + return nil, nil +} + +// Getter for the security snippet specific to the plug +func (iface *I2cInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + path, pathOk := slot.Attrs["path"].(string) + if !pathOk { + return nil, nil + } + switch securitySystem { + case interfaces.SecurityAppArmor: + cleanedPath := filepath.Clean(path) + return []byte(fmt.Sprintf("%s rw,\n", cleanedPath)), nil + + case interfaces.SecurityUDev: + var tagSnippet bytes.Buffer + const pathPrefix = "/dev/" + const udevRule string = `KERNEL="%s", TAG+="%s"` + for appName := range plug.Apps { + tag := udevSnapSecurityName(plug.Snap.Name(), appName) + tagSnippet.WriteString(fmt.Sprintf(udevRule, strings.TrimPrefix(path, pathPrefix), tag)) + tagSnippet.WriteString("\n") + } + return tagSnippet.Bytes(), nil + } + return nil, nil +} + +// No extra permissions granted on connection +func (iface *I2cInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + return nil, nil +} + +// No permissions granted to plug permanently +func (iface *I2cInterface) PermanentPlugSnippet(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) { + return nil, nil +} + +func (iface *I2cInterface) LegacyAutoConnect() bool { + return false +} + +func (iface *I2cInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { + // Allow what is allowed in the declarations + return true +} diff --git a/interfaces/builtin/i2c_test.go b/interfaces/builtin/i2c_test.go new file mode 100644 index 00000000000..c324ead1c99 --- /dev/null +++ b/interfaces/builtin/i2c_test.go @@ -0,0 +1,201 @@ +// -*- 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/snaptest" + "github.com/snapcore/snapd/testutil" +) + +type I2cInterfaceSuite struct { + testutil.BaseTest + iface interfaces.Interface + + // OS Snap + testSlot1 *interfaces.Slot + + // Gadget Snap + testUdev1 *interfaces.Slot + testUdev2 *interfaces.Slot + testUdev3 *interfaces.Slot + testUdevBadValue1 *interfaces.Slot + testUdevBadValue2 *interfaces.Slot + testUdevBadValue3 *interfaces.Slot + testUdevBadValue4 *interfaces.Slot + testUdevBadValue5 *interfaces.Slot + testUdevBadValue6 *interfaces.Slot + testUdevBadValue7 *interfaces.Slot + testUdevBadInterface1 *interfaces.Slot + + // Consuming Snap + testPlugPort1 *interfaces.Plug +} + +var _ = Suite(&I2cInterfaceSuite{ + iface: &builtin.I2cInterface{}, +}) + +func (s *I2cInterfaceSuite) SetUpTest(c *C) { + // Mock for OS Snap + osSnapInfo := snaptest.MockInfo(c, ` +name: ubuntu-core +type: os +slots: + test-port-1: + interface: i2c + path: /dev/i2c-0 +`, nil) + s.testSlot1 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-1"]} + + // Mock for Gadget Snap + gadgetSnapInfo := snaptest.MockInfo(c, ` +name: some-device +type: gadget +slots: + test-udev-1: + interface: i2c + path: /dev/i2c-1 + test-udev-2: + interface: i2c + path: /dev/i2c-11 + test-udev-3: + interface: i2c + path: /dev/i2c-0 + test-udev-bad-value-1: + interface: i2c + path: /dev/i2c + test-udev-bad-value-2: + interface: i2c + path: /dev/i2c-a + test-udev-bad-value-3: + interface: i2c + path: /dev/i2c-2a + test-udev-bad-value-4: + interface: i2c + path: /dev/foo-0 + test-udev-bad-value-5: + interface: i2c + path: /dev/i2c-foo + test-udev-bad-value-6: + interface: i2c + path: "" + test-udev-bad-value-7: + interface: i2c + test-udev-bad-interface-1: + interface: other-interface +`, nil) + s.testUdev1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-1"]} + s.testUdev2 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-2"]} + s.testUdev3 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-3"]} + s.testUdevBadValue1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-1"]} + s.testUdevBadValue2 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-2"]} + s.testUdevBadValue3 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-3"]} + s.testUdevBadValue4 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-4"]} + s.testUdevBadValue5 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-5"]} + s.testUdevBadValue6 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-6"]} + s.testUdevBadValue7 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-7"]} + s.testUdevBadInterface1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-interface-1"]} + + // Snap Consumers + consumingSnapInfo := snaptest.MockInfo(c, ` +name: client-snap +plugs: + plug-for-port-1: + interface: i2c +apps: + app-accessing-1-port: + command: foo + plugs: [i2c] +`, nil) + s.testPlugPort1 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-port-1"]} +} + +func (s *I2cInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "i2c") +} + +func (s *I2cInterfaceSuite) TestSanitizeCoreSnapSlot(c *C) { + err := s.iface.SanitizeSlot(s.testSlot1) + c.Assert(err, IsNil) +} + +func (s *I2cInterfaceSuite) TestSanitizeGadgetSnapSlot(c *C) { + + err := s.iface.SanitizeSlot(s.testUdev1) + c.Assert(err, IsNil) + + err = s.iface.SanitizeSlot(s.testUdev2) + c.Assert(err, IsNil) + + err = s.iface.SanitizeSlot(s.testUdev3) + c.Assert(err, IsNil) +} + +func (s *I2cInterfaceSuite) TestSanitizeBadGadgetSnapSlot(c *C) { + + err := s.iface.SanitizeSlot(s.testUdevBadValue1) + c.Assert(err, ErrorMatches, "i2c path attribute must be a valid device node") + + err = s.iface.SanitizeSlot(s.testUdevBadValue2) + c.Assert(err, ErrorMatches, "i2c path attribute must be a valid device node") + + err = s.iface.SanitizeSlot(s.testUdevBadValue3) + c.Assert(err, ErrorMatches, "i2c path attribute must be a valid device node") + + err = s.iface.SanitizeSlot(s.testUdevBadValue4) + c.Assert(err, ErrorMatches, "i2c path attribute must be a valid device node") + + err = s.iface.SanitizeSlot(s.testUdevBadValue5) + c.Assert(err, ErrorMatches, "i2c path attribute must be a valid device node") + + err = s.iface.SanitizeSlot(s.testUdevBadValue6) + c.Assert(err, ErrorMatches, "i2c slot must have a path attribute") + + err = s.iface.SanitizeSlot(s.testUdevBadValue7) + c.Assert(err, ErrorMatches, "i2c slot must have a path attribute") + + c.Assert(func() { s.iface.SanitizeSlot(s.testUdevBadInterface1) }, PanicMatches, `slot is not of interface "i2c"`) +} + +func (s *I2cInterfaceSuite) TestConnectedPlugUdevSnippets(c *C) { + expectedSnippet1 := []byte(`KERNEL="i2c-1", TAG+="snap_client-snap_app-accessing-1-port" +`) + + snippet, err := s.iface.ConnectedPlugSnippet(s.testPlugPort1, s.testUdev1, interfaces.SecurityUDev) + c.Assert(err, IsNil) + c.Assert(snippet, DeepEquals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, snippet)) +} + +func (s *I2cInterfaceSuite) TestConnectedPlugAppArmorSnippets(c *C) { + expectedSnippet1 := []byte(`/dev/i2c-1 rw, +`) + snippet, err := s.iface.ConnectedPlugSnippet(s.testPlugPort1, s.testUdev1, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(snippet, DeepEquals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, snippet)) + +} + +func (s *I2cInterfaceSuite) TestAutoConnect(c *C) { + c.Check(s.iface.AutoConnect(nil, nil), Equals, true) +} diff --git a/interfaces/builtin/kernel_module_control.go b/interfaces/builtin/kernel_module_control.go index 1472789e5c5..157183f22c3 100644 --- a/interfaces/builtin/kernel_module_control.go +++ b/interfaces/builtin/kernel_module_control.go @@ -49,6 +49,5 @@ func NewKernelModuleControlInterface() interfaces.Interface { connectedPlugAppArmor: kernelModuleControlConnectedPlugAppArmor, connectedPlugSecComp: kernelModuleControlConnectedPlugSecComp, reservedForOS: true, - autoConnect: false, } } diff --git a/interfaces/builtin/libvirt.go b/interfaces/builtin/libvirt.go index 4218bd8cb9e..93ae32d7bb4 100644 --- a/interfaces/builtin/libvirt.go +++ b/interfaces/builtin/libvirt.go @@ -43,7 +43,5 @@ func NewLibvirtInterface() interfaces.Interface { connectedPlugAppArmor: libvirtConnectedPlugAppArmor, connectedPlugSecComp: libvirtConnectedPlugSecComp, reservedForOS: true, - // cannot auto-connect, it grants too much power - autoConnect: false, } } diff --git a/interfaces/builtin/libvirt_test.go b/interfaces/builtin/libvirt_test.go index 09caf762c97..b331c415415 100644 --- a/interfaces/builtin/libvirt_test.go +++ b/interfaces/builtin/libvirt_test.go @@ -64,7 +64,3 @@ func (s *LibvirtInterfaceSuite) TestSanitizePlug(c *C) { err := s.iface.SanitizePlug(s.plug) c.Assert(err, IsNil) } - -func (s *LibvirtInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/locale_control_test.go b/interfaces/builtin/locale_control_test.go index 5aa2fba6d46..174828cb0f2 100644 --- a/interfaces/builtin/locale_control_test.go +++ b/interfaces/builtin/locale_control_test.go @@ -84,7 +84,3 @@ func (s *LocaleControlInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *LocaleControlInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/location_control.go b/interfaces/builtin/location_control.go index 983678ca479..9b611a305f7 100644 --- a/interfaces/builtin/location_control.go +++ b/interfaces/builtin/location_control.go @@ -208,10 +208,6 @@ func (iface *LocationControlInterface) SanitizeSlot(slot *interfaces.Slot) error return nil } -func (iface *LocationControlInterface) LegacyAutoConnect() bool { - return false -} - func (iface *LocationControlInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/location_observe.go b/interfaces/builtin/location_observe.go index f2f9865a7ea..8c8f84972f4 100644 --- a/interfaces/builtin/location_observe.go +++ b/interfaces/builtin/location_observe.go @@ -281,10 +281,6 @@ func (iface *LocationObserveInterface) SanitizeSlot(slot *interfaces.Slot) error return nil } -func (iface *LocationObserveInterface) LegacyAutoConnect() bool { - return false -} - func (iface *LocationObserveInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/log_observe_test.go b/interfaces/builtin/log_observe_test.go index eec1943f8b1..c7529c5e048 100644 --- a/interfaces/builtin/log_observe_test.go +++ b/interfaces/builtin/log_observe_test.go @@ -84,7 +84,3 @@ func (s *LogObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *LogObserveInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/lxd.go b/interfaces/builtin/lxd.go index 0b6ab4f6d91..b1f700eb41e 100644 --- a/interfaces/builtin/lxd.go +++ b/interfaces/builtin/lxd.go @@ -81,10 +81,6 @@ func (iface *LxdInterface) SanitizeSlot(slot *interfaces.Slot) error { return nil } -func (iface *LxdInterface) LegacyAutoConnect() bool { - return false -} - func (iface *LxdInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/lxd_support.go b/interfaces/builtin/lxd_support.go index c96e1a8825a..613e3ebf1db 100644 --- a/interfaces/builtin/lxd_support.go +++ b/interfaces/builtin/lxd_support.go @@ -73,11 +73,6 @@ func (iface *LxdSupportInterface) SanitizeSlot(slot *interfaces.Slot) error { return nil } -func (iface *LxdSupportInterface) LegacyAutoConnect() bool { - // since limited to lxd.canonical, we can auto-connect - return true -} - func (iface *LxdSupportInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/lxd_support_test.go b/interfaces/builtin/lxd_support_test.go index 88358a8fb8f..8eca63fbc0f 100644 --- a/interfaces/builtin/lxd_support_test.go +++ b/interfaces/builtin/lxd_support_test.go @@ -93,10 +93,6 @@ func (s *LxdSupportInterfaceSuite) TestPermanentSlotPolicySecComp(c *C) { c.Check(string(snippet), testutil.Contains, "@unrestricted\n") } -func (s *LxdSupportInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} - func (s *LxdSupportInterfaceSuite) TestAutoConnect(c *C) { c.Check(s.iface.AutoConnect(nil, nil), Equals, true) } diff --git a/interfaces/builtin/lxd_test.go b/interfaces/builtin/lxd_test.go index 0a020fbd020..34612168cd3 100644 --- a/interfaces/builtin/lxd_test.go +++ b/interfaces/builtin/lxd_test.go @@ -92,10 +92,6 @@ func (s *LxdInterfaceSuite) TestConnectedPlugSnippetSecComp(c *C) { c.Check(string(snippet), testutil.Contains, "shutdown\n") } -func (s *LxdInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} - func (s *LxdInterfaceSuite) TestAutoConnect(c *C) { // allow what declarations allowed c.Check(s.iface.AutoConnect(nil, nil), Equals, true) diff --git a/interfaces/builtin/mir.go b/interfaces/builtin/mir.go index b276f6b989c..bdcbd732dc5 100644 --- a/interfaces/builtin/mir.go +++ b/interfaces/builtin/mir.go @@ -137,10 +137,6 @@ func (iface *MirInterface) SanitizeSlot(slot *interfaces.Slot) error { return nil } -func (iface *MirInterface) LegacyAutoConnect() bool { - return true -} - func (iface *MirInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { return true } diff --git a/interfaces/builtin/mir_test.go b/interfaces/builtin/mir_test.go index e338f4d8133..f65bfb02081 100644 --- a/interfaces/builtin/mir_test.go +++ b/interfaces/builtin/mir_test.go @@ -72,7 +72,3 @@ func (s *MirInterfaceSuite) TestUsedSecuritySystems(c *C) { } } } - -func (s MirInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} diff --git a/interfaces/builtin/modem_manager.go b/interfaces/builtin/modem_manager.go index a372aba80aa..2883395645d 100644 --- a/interfaces/builtin/modem_manager.go +++ b/interfaces/builtin/modem_manager.go @@ -1209,10 +1209,6 @@ func (iface *ModemManagerInterface) SanitizeSlot(slot *interfaces.Slot) error { return nil } -func (iface *ModemManagerInterface) LegacyAutoConnect() bool { - return false -} - func (iface *ModemManagerInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/mount_observe.go b/interfaces/builtin/mount_observe.go index aed48fcfc5b..18cb9c96192 100644 --- a/interfaces/builtin/mount_observe.go +++ b/interfaces/builtin/mount_observe.go @@ -29,6 +29,9 @@ const mountObserveConnectedPlugAppArmor = ` # it gives privileged read access to mount arguments and should only be used # with trusted apps. # Usage: reserved + +/{,usr/}bin/df ixr, + # Needed by 'df'. This is an information leak @{PROC}/mounts r, owner @{PROC}/@{pid}/mounts r, diff --git a/interfaces/builtin/mount_observe_test.go b/interfaces/builtin/mount_observe_test.go index d7773f6ea10..4dd1c3fbb7b 100644 --- a/interfaces/builtin/mount_observe_test.go +++ b/interfaces/builtin/mount_observe_test.go @@ -84,7 +84,3 @@ func (s *MountObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *MountObserveInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/mpris.go b/interfaces/builtin/mpris.go index d7e09292453..0ff54fc4e2a 100644 --- a/interfaces/builtin/mpris.go +++ b/interfaces/builtin/mpris.go @@ -248,10 +248,6 @@ func (iface *MprisInterface) SanitizeSlot(slot *interfaces.Slot) error { return err } -func (iface *MprisInterface) LegacyAutoConnect() bool { - return false -} - func (iface *MprisInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/mpris_test.go b/interfaces/builtin/mpris_test.go index 983c3475dc8..c0aeb8d196f 100644 --- a/interfaces/builtin/mpris_test.go +++ b/interfaces/builtin/mpris_test.go @@ -343,8 +343,3 @@ func (s *MprisInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *MprisInterfaceSuite) TestLegacyAutoConnect(c *C) { - iface := &builtin.MprisInterface{} - c.Check(iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/network.go b/interfaces/builtin/network.go index d0c8702df63..952eeadd335 100644 --- a/interfaces/builtin/network.go +++ b/interfaces/builtin/network.go @@ -69,6 +69,5 @@ func NewNetworkInterface() interfaces.Interface { connectedPlugAppArmor: networkConnectedPlugAppArmor, connectedPlugSecComp: networkConnectedPlugSecComp, reservedForOS: true, - autoConnect: true, } } diff --git a/interfaces/builtin/network_bind.go b/interfaces/builtin/network_bind.go index 95143d39487..43202244b2a 100644 --- a/interfaces/builtin/network_bind.go +++ b/interfaces/builtin/network_bind.go @@ -99,6 +99,5 @@ func NewNetworkBindInterface() interfaces.Interface { connectedPlugAppArmor: networkBindConnectedPlugAppArmor, connectedPlugSecComp: networkBindConnectedPlugSecComp, reservedForOS: true, - autoConnect: true, } } diff --git a/interfaces/builtin/network_bind_test.go b/interfaces/builtin/network_bind_test.go index 58ea36bcb8d..9c5cbaf14e3 100644 --- a/interfaces/builtin/network_bind_test.go +++ b/interfaces/builtin/network_bind_test.go @@ -88,7 +88,3 @@ func (s *NetworkBindInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *NetworkBindInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} diff --git a/interfaces/builtin/network_manager.go b/interfaces/builtin/network_manager.go index b5f44c0d8c2..b02cb7b5d66 100644 --- a/interfaces/builtin/network_manager.go +++ b/interfaces/builtin/network_manager.go @@ -450,10 +450,6 @@ func (iface *NetworkManagerInterface) SanitizeSlot(slot *interfaces.Slot) error return nil } -func (iface *NetworkManagerInterface) LegacyAutoConnect() bool { - return false -} - func (iface *NetworkManagerInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/network_observe_test.go b/interfaces/builtin/network_observe_test.go index 2ce2638d530..14eabd34e0e 100644 --- a/interfaces/builtin/network_observe_test.go +++ b/interfaces/builtin/network_observe_test.go @@ -88,7 +88,3 @@ func (s *NetworkObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *NetworkObserveInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/network_setup_observe_test.go b/interfaces/builtin/network_setup_observe_test.go index 0741c670c61..11008080803 100644 --- a/interfaces/builtin/network_setup_observe_test.go +++ b/interfaces/builtin/network_setup_observe_test.go @@ -84,7 +84,3 @@ func (s *NetworkSetupObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *NetworkSetupObserveInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/network_test.go b/interfaces/builtin/network_test.go index 1a4611a7281..a0db5a3687b 100644 --- a/interfaces/builtin/network_test.go +++ b/interfaces/builtin/network_test.go @@ -88,7 +88,3 @@ func (s *NetworkInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *NetworkInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} diff --git a/interfaces/builtin/ofono.go b/interfaces/builtin/ofono.go new file mode 100644 index 00000000000..12ea28c84db --- /dev/null +++ b/interfaces/builtin/ofono.go @@ -0,0 +1,324 @@ +// -*- 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 ( + "bytes" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/release" +) + +const ofonoPermanentSlotAppArmor = ` +# Description: Allow operating as the ofono service. Reserved because this +# gives privileged access to the system. + +# to create ppp network interfaces +capability net_admin, + +# To check present devices +/run/udev/data/+usb:* r, +/run/udev/data/+usb-serial:* r, +/run/udev/data/+pci:* r, +/run/udev/data/+platform:* r, +/run/udev/data/+pnp:* r, +/run/udev/data/c* r, +/run/udev/data/n* r, +/sys/bus/usb/devices/ r, +# FIXME snapd should be querying udev and adding the /sys and /run/udev accesses +# that are assigned to the snap, but we are not there yet. +/sys/bus/usb/devices/** r, + +# To get current seat, used to know user preferences like default SIM in +# multi-SIM devices. +/run/systemd/seats/{,*} r, + +# Access to modem ports +# FIXME snapd should be more dynamic to avoid conflicts between snaps trying to +# access same ports. +/dev/tty[^0-9]* rw, +/dev/cdc-* rw, +/dev/modem* rw, +/dev/dsp rw, +/dev/chnlat11 rw, +/dev/socket/rild* rw, +# ofono puts ppp on top of the tun device +/dev/net/tun rw, + +network netlink raw, +network netlink dgram, +network bridge, +network inet, +network inet6, +network packet, +network bluetooth, + +include + +# DBus accesses +include + +dbus (send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member={Request,Release}Name + peer=(name=org.freedesktop.DBus, label=unconfined), + +# Allow binding the service to the requested connection name +dbus (bind) + bus=system + name="org.ofono", + +# Allow traffic to/from our path and interface with any method for unconfined +# clients to talk to our ofono services. +dbus (receive, send) + bus=system + path=/{,**} + interface=org.ofono.* + peer=(label=unconfined), +` + +const ofonoConnectedSlotAppArmor = ` +# Allow service to interact with connected clients + +# Allow traffic to/from our interfaces. The path depends on the modem plugin, +# and is arbitrary. +dbus (receive, send) + bus=system + path=/{,**} + interface=org.ofono.* + peer=(label=###PLUG_SECURITY_TAGS###), +` + +const ofonoConnectedPlugAppArmor = ` +# Description: Allow using Ofono service. Reserved because this gives +# privileged access to the Ofono service. + +#include + +# Allow all access to ofono services +dbus (receive, send) + bus=system + path=/{,**} + interface=org.ofono.* + peer=(label=###SLOT_SECURITY_TAGS###), +` + +const ofonoConnectedPlugAppArmorClassic = ` +# Allow access to the unconfined ofono services on classic. +dbus (receive, send) + bus=system + path=/{,**} + interface=org.ofono.* + peer=(label=unconfined), +` + +const ofonoPermanentSlotSecComp = ` +# Description: Allow operating as the ofono service. Reserved because this +# gives privileged access to the system. + +# Communicate with DBus, netlink, rild +accept +accept4 +bind +getsockopt +listen +recv +recvfrom +recvmmsg +recvmsg +send +sendmmsg +sendmsg +sendto +shutdown +` + +const ofonoConnectedPlugSecComp = ` +# Description: Allow using ofono service. Reserved because this gives +# privileged access to the ofono service. + +# Can communicate with DBus system service +recv +recvmsg +recvfrom +send +sendto +sendmsg +` + +const ofonoPermanentSlotDBus = ` + + + + + + + + + + + + + + + + + +` + +const ofonoPermanentSlotUdev = ` +## Concatenation of all ofono udev rules (plugins/*.rules in ofono sources) +## Note that ofono uses this for very few modems and that in most cases it finds +## modems by checking directly in code udev events, so changes here will be rare + +## plugins/ofono.rules +# do not edit this file, it will be overwritten on update + +ACTION!="add|change", GOTO="ofono_end" + +# ISI/Phonet drivers +SUBSYSTEM!="net", GOTO="ofono_isi_end" +ATTRS{type}!="820", GOTO="ofono_isi_end" +KERNELS=="gadget", GOTO="ofono_isi_end" + +# Nokia N900 modem +SUBSYSTEMS=="hsi", ENV{OFONO_DRIVER}="n900", ENV{OFONO_ISI_ADDRESS}="108" +KERNEL=="phonet*", ENV{OFONO_DRIVER}="n900", ENV{OFONO_ISI_ADDRESS}="108" + +# STE u8500 +KERNEL=="shrm0", ENV{OFONO_DRIVER}="u8500" + +LABEL="ofono_isi_end" + +SUBSYSTEM!="usb", GOTO="ofono_end" +ENV{DEVTYPE}!="usb_device", GOTO="ofono_end" + +# Ignore fake serial number +ATTRS{serial}=="1234567890ABCDEF", ENV{ID_SERIAL_SHORT}="" + +# Nokia CDMA Device +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="023e", ENV{OFONO_DRIVER}="nokiacdma" +ATTRS{idVendor}=="0421", ATTRS{idProduct}=="00b6", ENV{OFONO_DRIVER}="nokiacdma" + +# Lenovo H5321gw 0bdb:1926 +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1926", ENV{OFONO_DRIVER}="mbm" + +LABEL="ofono_end" + +## plugins/ofono-speedup.rules +# do not edit this file, it will be overwritten on update + +ACTION!="add|change", GOTO="ofono_speedup_end" + +SUBSYSTEM!="tty", GOTO="ofono_speedup_end" +KERNEL!="ttyUSB[0-9]*", GOTO="ofono_speedup_end" + +# SpeedUp 7300 +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9e00", ENV{ID_USB_INTERFACE_NUM}=="00", ENV{OFONO_LABEL}="modem" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9e00", ENV{ID_USB_INTERFACE_NUM}=="03", ENV{OFONO_LABEL}="aux" + +# SpeedUp +ATTRS{idVendor}=="2020", ATTRS{idProduct}=="1005", ENV{ID_USB_INTERFACE_NUM}=="03", ENV{OFONO_LABEL}="modem" +ATTRS{idVendor}=="2020", ATTRS{idProduct}=="1005", ENV{ID_USB_INTERFACE_NUM}=="01", ENV{OFONO_LABEL}="aux" + +ATTRS{idVendor}=="2020", ATTRS{idProduct}=="1008", ENV{ID_USB_INTERFACE_NUM}=="03", ENV{OFONO_LABEL}="modem" +ATTRS{idVendor}=="2020", ATTRS{idProduct}=="1008", ENV{ID_USB_INTERFACE_NUM}=="01", ENV{OFONO_LABEL}="aux" + +# SpeedUp 9800 +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9800", ENV{ID_USB_INTERFACE_NUM}=="01", ENV{OFONO_LABEL}="modem" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9800", ENV{ID_USB_INTERFACE_NUM}=="02", ENV{OFONO_LABEL}="aux" + +# SpeedUp U3501 +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{ID_USB_INTERFACE_NUM}=="03", ENV{OFONO_LABEL}="modem" +ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{ID_USB_INTERFACE_NUM}=="01", ENV{OFONO_LABEL}="aux" + +LABEL="ofono_speedup_end" +` + +type OfonoInterface struct{} + +func (iface *OfonoInterface) Name() string { + return "ofono" +} + +func (iface *OfonoInterface) PermanentPlugSnippet(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) { + return nil, nil +} + +func (iface *OfonoInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor: + old := []byte("###SLOT_SECURITY_TAGS###") + new := slotAppLabelExpr(slot) + snippet := bytes.Replace([]byte(ofonoConnectedPlugAppArmor), old, new, -1) + if release.OnClassic { + // Let confined apps access unconfined ofono on classic + snippet = append(snippet, ofonoConnectedPlugAppArmorClassic...) + } + return snippet, nil + case interfaces.SecuritySecComp: + return []byte(ofonoConnectedPlugSecComp), nil + } + return nil, nil +} + +func (iface *OfonoInterface) PermanentSlotSnippet(slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor: + return []byte(ofonoPermanentSlotAppArmor), nil + case interfaces.SecuritySecComp: + return []byte(ofonoPermanentSlotSecComp), nil + case interfaces.SecurityUDev: + return []byte(ofonoPermanentSlotUdev), nil + case interfaces.SecurityDBus: + return []byte(ofonoPermanentSlotDBus), nil + } + return nil, nil +} + +func (iface *OfonoInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor: + old := []byte("###PLUG_SECURITY_TAGS###") + new := plugAppLabelExpr(plug) + snippet := bytes.Replace([]byte(ofonoConnectedSlotAppArmor), old, new, -1) + return snippet, nil + } + return nil, nil +} + +func (iface *OfonoInterface) SanitizePlug(plug *interfaces.Plug) error { + return nil +} + +func (iface *OfonoInterface) SanitizeSlot(slot *interfaces.Slot) error { + return nil +} + +func (iface *OfonoInterface) LegacyAutoConnect() bool { + return false +} + +func (iface *OfonoInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { + // allow what declarations allowed + return true +} diff --git a/interfaces/builtin/ofono_test.go b/interfaces/builtin/ofono_test.go new file mode 100644 index 00000000000..ac5428de3fc --- /dev/null +++ b/interfaces/builtin/ofono_test.go @@ -0,0 +1,173 @@ +// -*- 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/release" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" +) + +type OfonoInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&OfonoInterfaceSuite{ + iface: &builtin.OfonoInterface{}, + slot: &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "ofono"}, + Name: "ofono", + Interface: "ofono", + }, + }, + plug: &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "ofono"}, + Name: "dbus-send", + Interface: "ofono", + }, + }, +}) + +func (s *OfonoInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "ofono") +} + +// The label glob when all apps are bound to the ofono slot +func (s *OfonoInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { + app1 := &snap.AppInfo{Name: "app1"} + app2 := &snap.AppInfo{Name: "app2"} + slot := &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "ofono", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "ofono", + Interface: "ofono", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + } + release.OnClassic = false + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.ofono.*"),`) +} + +// The label uses alternation when some, but not all, apps is bound to the ofono slot +func (s *OfonoInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelSome(c *C) { + app1 := &snap.AppInfo{Name: "app1"} + app2 := &snap.AppInfo{Name: "app2"} + app3 := &snap.AppInfo{Name: "app3"} + slot := &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "ofono", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "ofono", + Interface: "ofono", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + } + release.OnClassic = false + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.ofono.{app1,app2}"),`) +} + +// The label uses short form when exactly one app is bound to the ofono slot +func (s *OfonoInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { + app := &snap.AppInfo{Name: "app"} + slot := &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "ofono", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "ofono", + Interface: "ofono", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + } + release.OnClassic = false + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.ofono.app"),`) +} + +func (s *OfonoInterfaceSuite) TestConnectedPlugSnippetUsesUnconfinedLabelOnClassic(c *C) { + release.OnClassic = true + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + // verify apparmor connected + c.Assert(string(snippet), testutil.Contains, "#include ") + // verify classic connected + c.Assert(string(snippet), testutil.Contains, "peer=(label=unconfined),") +} + +func (s *OfonoInterfaceSuite) TestConnectedPlugSnippetAppArmor(c *C) { + release.OnClassic = false + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + // verify apparmor connected + c.Assert(string(snippet), testutil.Contains, "#include ") + // verify classic didn't connect + c.Assert(string(snippet), Not(testutil.Contains), "peer=(label=unconfined),") +} + +func (s *OfonoInterfaceSuite) TestConnectedPlugSnippetSecComp(c *C) { + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecuritySecComp) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + + c.Check(string(snippet), testutil.Contains, "send\n") +} + +func (s *OfonoInterfaceSuite) TestConnectedSlotSnippetAppArmor(c *C) { + snippet, err := s.iface.ConnectedSlotSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + + c.Check(string(snippet), testutil.Contains, "peer=(label=\"snap.ofono.*\")") +} + +func (s *OfonoInterfaceSuite) TestPermanentSlotSnippetAppArmor(c *C) { + snippet, err := s.iface.PermanentSlotSnippet(s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + + c.Check(string(snippet), testutil.Contains, "/dev/net/tun rw,") +} + +func (s *OfonoInterfaceSuite) TestPermanentSlotSnippetSecComp(c *C) { + snippet, err := s.iface.PermanentSlotSnippet(s.slot, interfaces.SecuritySecComp) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + + c.Check(string(snippet), testutil.Contains, "listen\n") +} diff --git a/interfaces/builtin/opengl.go b/interfaces/builtin/opengl.go index 83a81f2e8ab..c69ba89e541 100644 --- a/interfaces/builtin/opengl.go +++ b/interfaces/builtin/opengl.go @@ -70,6 +70,5 @@ func NewOpenglInterface() interfaces.Interface { connectedPlugAppArmor: openglConnectedPlugAppArmor, connectedPlugSecComp: openglConnectedPlugSecComp, reservedForOS: true, - autoConnect: true, } } diff --git a/interfaces/builtin/optical_drive.go b/interfaces/builtin/optical_drive.go index 3c63e7969c7..f720b8a5226 100644 --- a/interfaces/builtin/optical_drive.go +++ b/interfaces/builtin/optical_drive.go @@ -34,6 +34,5 @@ func NewOpticalDriveInterface() interfaces.Interface { name: "optical-drive", connectedPlugAppArmor: opticalDriveConnectedPlugAppArmor, reservedForOS: true, - autoConnect: true, } } diff --git a/interfaces/builtin/ppp.go b/interfaces/builtin/ppp.go index efb8b27104b..1d6a2123db8 100644 --- a/interfaces/builtin/ppp.go +++ b/interfaces/builtin/ppp.go @@ -86,10 +86,6 @@ func (iface *PppInterface) SanitizeSlot(slot *interfaces.Slot) error { return nil } -func (iface *PppInterface) LegacyAutoConnect() bool { - return false -} - func (iface *PppInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/process_control_test.go b/interfaces/builtin/process_control_test.go index 7b24ae8216c..b444d1e8d72 100644 --- a/interfaces/builtin/process_control_test.go +++ b/interfaces/builtin/process_control_test.go @@ -87,7 +87,3 @@ func (s *ProcessControlInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *ProcessControlInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/pulseaudio.go b/interfaces/builtin/pulseaudio.go index 478100c6e54..7cc60b29308 100644 --- a/interfaces/builtin/pulseaudio.go +++ b/interfaces/builtin/pulseaudio.go @@ -72,9 +72,12 @@ owner @{PROC}/@{pid}/exe r, # Audio related @{PROC}/asound/devices r, @{PROC}/asound/card** r, + +# Should use the alsa interface instead /dev/snd/pcm* rw, /dev/snd/control* rw, /dev/snd/timer r, + /sys/**/sound/** r, # For udev @@ -160,10 +163,6 @@ func (iface *PulseAudioInterface) SanitizeSlot(slot *interfaces.Slot) error { return nil } -func (iface *PulseAudioInterface) LegacyAutoConnect() bool { - return true -} - func (iface *PulseAudioInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { return true } diff --git a/interfaces/builtin/removable_media_test.go b/interfaces/builtin/removable_media_test.go index 6c99645a872..445b2d10cdd 100644 --- a/interfaces/builtin/removable_media_test.go +++ b/interfaces/builtin/removable_media_test.go @@ -84,7 +84,3 @@ func (s *RemovableMediaInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *RemovableMediaInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/screen_inhibit_control.go b/interfaces/builtin/screen_inhibit_control.go index 643a76c1104..86e64f8b09c 100644 --- a/interfaces/builtin/screen_inhibit_control.go +++ b/interfaces/builtin/screen_inhibit_control.go @@ -79,6 +79,5 @@ func NewScreenInhibitControlInterface() interfaces.Interface { connectedPlugAppArmor: screenInhibitControlConnectedPlugAppArmor, connectedPlugSecComp: screenInhibitControlConnectedPlugSecComp, reservedForOS: true, - autoConnect: true, } } diff --git a/interfaces/builtin/screen_inhibit_control_test.go b/interfaces/builtin/screen_inhibit_control_test.go index e34664761b6..dc451b31b4a 100644 --- a/interfaces/builtin/screen_inhibit_control_test.go +++ b/interfaces/builtin/screen_inhibit_control_test.go @@ -88,7 +88,3 @@ func (s *ScreenInhibitControlInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *ScreenInhibitControlInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} diff --git a/interfaces/builtin/serial_port.go b/interfaces/builtin/serial_port.go index 8bd8dd5528a..d69149ecfc8 100644 --- a/interfaces/builtin/serial_port.go +++ b/interfaces/builtin/serial_port.go @@ -171,7 +171,7 @@ func (iface *SerialPortInterface) ConnectedPlugSnippet(plug *interfaces.Plug, sl } var udevSnippet bytes.Buffer for appName := range plug.Apps { - tag := fmt.Sprintf("snap_%s_%s", plug.Snap.Name(), appName) + tag := udevSnapSecurityName(plug.Snap.Name(), appName) udevSnippet.Write(udevUsbDeviceSnippet("tty", usbVendor, usbProduct, "TAG", tag)) } return udevSnippet.Bytes(), nil @@ -179,10 +179,6 @@ func (iface *SerialPortInterface) ConnectedPlugSnippet(plug *interfaces.Plug, sl return nil, nil } -func (iface *SerialPortInterface) LegacyAutoConnect() bool { - return false -} - func (iface *SerialPortInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/shutdown.go b/interfaces/builtin/shutdown.go index 34fbe2516fa..00738e3fbd6 100644 --- a/interfaces/builtin/shutdown.go +++ b/interfaces/builtin/shutdown.go @@ -54,6 +54,5 @@ func NewShutdownInterface() interfaces.Interface { connectedPlugAppArmor: shutdownConnectedPlugAppArmor, connectedPlugSecComp: shutdownConnectedPlugSecComp, reservedForOS: true, - autoConnect: false, } } diff --git a/interfaces/builtin/shutdown_test.go b/interfaces/builtin/shutdown_test.go index 6104ccbfa76..dfcab1d9af2 100644 --- a/interfaces/builtin/shutdown_test.go +++ b/interfaces/builtin/shutdown_test.go @@ -99,7 +99,3 @@ func (s *ShutdownInterfaceSuite) TestConnectedPlugSnippet(c *C) { c.Assert(err, IsNil) c.Assert(string(snippet), testutil.Contains, `recvfrom`) } - -func (s *ShutdownInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/snapd_control.go b/interfaces/builtin/snapd_control.go index 02cd40d15b7..6033e551b49 100644 --- a/interfaces/builtin/snapd_control.go +++ b/interfaces/builtin/snapd_control.go @@ -55,6 +55,5 @@ func NewSnapdControlInterface() interfaces.Interface { connectedPlugAppArmor: snapdControlConnectedPlugAppArmor, connectedPlugSecComp: snapdControlConnectedPlugSecComp, reservedForOS: true, - autoConnect: true, } } diff --git a/interfaces/builtin/snapd_control_test.go b/interfaces/builtin/snapd_control_test.go index f5b6fc1550e..8da52bbacb5 100644 --- a/interfaces/builtin/snapd_control_test.go +++ b/interfaces/builtin/snapd_control_test.go @@ -88,7 +88,3 @@ func (s *SnapdControlInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *SnapdControlInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} diff --git a/interfaces/builtin/system_observe.go b/interfaces/builtin/system_observe.go index 4c1abfbd063..84b3b3b1219 100644 --- a/interfaces/builtin/system_observe.go +++ b/interfaces/builtin/system_observe.go @@ -59,6 +59,13 @@ deny ptrace (trace), @{PROC}/*/{,task/*/}stat r, @{PROC}/*/{,task/*/}statm r, @{PROC}/*/{,task/*/}status r, + +dbus (send) + bus=system + path=/org/freedesktop/hostname1 + interface=org.freedesktop.DBus.Properties + member=Get{,All} + peer=(label=unconfined), ` // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/seccomp/policygroups/ubuntu-core/16.04/system-observe @@ -74,6 +81,16 @@ const systemObserveConnectedPlugSecComp = ` # Note: may uncomment once ubuntu-core-launcher understands @deny rules and # if/when we conditionally deny this in the future. #@deny ptrace + +# for connecting to /org/freedesktop/hostname1 over DBus +connect +getsockname +recvfrom +recvmsg +send +sendto +sendmsg +socket ` // NewSystemObserveInterface returns a new "system-observe" interface. diff --git a/interfaces/builtin/system_observe_test.go b/interfaces/builtin/system_observe_test.go index 3a4954c0eb2..5cf501ae765 100644 --- a/interfaces/builtin/system_observe_test.go +++ b/interfaces/builtin/system_observe_test.go @@ -88,7 +88,3 @@ func (s *SystemObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *SystemObserveInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/system_trace_test.go b/interfaces/builtin/system_trace_test.go index cd431111f3d..79e1bcd6605 100644 --- a/interfaces/builtin/system_trace_test.go +++ b/interfaces/builtin/system_trace_test.go @@ -84,7 +84,3 @@ func (s *SystemTraceInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *SystemTraceInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/time_control.go b/interfaces/builtin/time_control.go index 4374b080105..44d6ea90086 100644 --- a/interfaces/builtin/time_control.go +++ b/interfaces/builtin/time_control.go @@ -79,6 +79,5 @@ func NewTimeControlInterface() interfaces.Interface { connectedPlugAppArmor: timeControlConnectedPlugAppArmor, connectedPlugSecComp: timeControlConnectedPlugSecComp, reservedForOS: true, - autoConnect: false, } } diff --git a/interfaces/builtin/time_control_test.go b/interfaces/builtin/time_control_test.go index c84bec74bb5..33bc96d5688 100644 --- a/interfaces/builtin/time_control_test.go +++ b/interfaces/builtin/time_control_test.go @@ -88,7 +88,3 @@ func (s *TimeControlTestInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *TimeControlTestInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/timeserver_control_test.go b/interfaces/builtin/timeserver_control_test.go index a89b4e5082a..7374a420aff 100644 --- a/interfaces/builtin/timeserver_control_test.go +++ b/interfaces/builtin/timeserver_control_test.go @@ -89,7 +89,3 @@ func (s *TimeserverControlInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *TimeserverControlInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/timezone_control_test.go b/interfaces/builtin/timezone_control_test.go index 2bc4ac99928..d37cde4301f 100644 --- a/interfaces/builtin/timezone_control_test.go +++ b/interfaces/builtin/timezone_control_test.go @@ -89,7 +89,3 @@ func (s *TimezoneControlInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *TimezoneControlInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/tpm.go b/interfaces/builtin/tpm.go index 23cecad02b4..f45514a51f6 100644 --- a/interfaces/builtin/tpm.go +++ b/interfaces/builtin/tpm.go @@ -33,6 +33,5 @@ func NewTpmInterface() interfaces.Interface { name: "tpm", connectedPlugAppArmor: tpmConnectedPlugAppArmor, reservedForOS: true, - autoConnect: false, } } diff --git a/interfaces/builtin/tpm_test.go b/interfaces/builtin/tpm_test.go index a41a56009b6..25321fabdc9 100644 --- a/interfaces/builtin/tpm_test.go +++ b/interfaces/builtin/tpm_test.go @@ -84,7 +84,3 @@ func (s *TpmInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *TpmInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/udisks2.go b/interfaces/builtin/udisks2.go index 3ad5390d713..7f0a6bddb0f 100644 --- a/interfaces/builtin/udisks2.go +++ b/interfaces/builtin/udisks2.go @@ -414,10 +414,6 @@ func (iface *UDisks2Interface) SanitizeSlot(slot *interfaces.Slot) error { return nil } -func (iface *UDisks2Interface) LegacyAutoConnect() bool { - return false -} - func (iface *UDisks2Interface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff --git a/interfaces/builtin/udisks2_test.go b/interfaces/builtin/udisks2_test.go index ba24c996582..c5f6616d615 100644 --- a/interfaces/builtin/udisks2_test.go +++ b/interfaces/builtin/udisks2_test.go @@ -249,7 +249,3 @@ func (s *UDisks2InterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *UDisks2InterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, false) -} diff --git a/interfaces/builtin/unity7.go b/interfaces/builtin/unity7.go index 0539ff88f03..5a79cc5e5d3 100644 --- a/interfaces/builtin/unity7.go +++ b/interfaces/builtin/unity7.go @@ -26,7 +26,7 @@ import ( // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/unity7 const unity7ConnectedPlugAppArmor = ` # Description: Can access Unity7. Restricted because Unity 7 runs on X and -# requires access to various DBus services and this enviroment does not prevent +# requires access to various DBus services and this environment does not prevent # eavesdropping or apps interfering with one another. # Usage: reserved @@ -305,28 +305,28 @@ dbus (send) dbus (send) bus=session - path=/StatusNotifierWatcher + path=/{StatusNotifierWatcher,org/ayatana/NotificationItem/*} interface=org.kde.StatusNotifierWatcher member=RegisterStatusNotifierItem - peer=(name=org.kde.StatusNotifierWatcher, label=unconfined), + peer=(label=unconfined), dbus (send) bus=session - path=/StatusNotifierItem + path=/{StatusNotifierItem,org/ayatana/NotificationItem/*} interface=org.kde.StatusNotifierItem member="New{AttentionIcon,Icon,OverlayIcon,Status,Title,ToolTip}" peer=(name=org.freedesktop.DBus, label=unconfined), dbus (send) bus=session - path=/StatusNotifierItem/menu + path=/{StatusNotifierItem/menu,org/ayatana/NotificationItem/*/Menu} interface=com.canonical.dbusmenu member="{LayoutUpdated,ItemsPropertiesUpdated}" peer=(name=org.freedesktop.DBus, label=unconfined), dbus (receive) bus=session - path=/StatusNotifierItem{,/menu} + path=/{StatusNotifierItem,StatusNotifierItem/menu,org/ayatana/NotificationItem/**} interface={org.freedesktop.DBus.Properties,com.canonical.dbusmenu} member={Get*,AboutTo*,Event*} peer=(label=unconfined), @@ -345,6 +345,13 @@ dbus (receive) member=NotificationClosed peer=(label=unconfined), +dbus (send) + bus=session + path=/org/ayatana/NotificationItem/* + interface=org.kde.StatusNotifierItem + member=XAyatanaNew* + peer=(name=org.freedesktop.DBus, label=unconfined), + # unity launcher dbus (send) bus=session @@ -409,13 +416,13 @@ dbus (receive) # Lttng tracing is very noisy and should not be allowed by confined apps. Can # safely deny. LP: #1260491 -deny /{,var/}run/shm/lttng-ust-* r, +deny /{,var/}{dev,run}/shm/lttng-ust-* r, ` // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/seccomp/policygroups/ubuntu-core/16.04/unity7 const unity7ConnectedPlugSecComp = ` # Description: Can access Unity7. Restricted because Unity 7 runs on X and -# requires access to various DBus services and this enviroment does not prevent +# requires access to various DBus services and this environment does not prevent # eavesdropping or apps interfering with one another. # X @@ -442,6 +449,5 @@ func NewUnity7Interface() interfaces.Interface { connectedPlugAppArmor: unity7ConnectedPlugAppArmor, connectedPlugSecComp: unity7ConnectedPlugSecComp, reservedForOS: true, - autoConnect: true, } } diff --git a/interfaces/builtin/unity7_test.go b/interfaces/builtin/unity7_test.go index 0107b52bc9e..7dd1afee7b1 100644 --- a/interfaces/builtin/unity7_test.go +++ b/interfaces/builtin/unity7_test.go @@ -88,7 +88,3 @@ func (s *Unity7InterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *Unity7InterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} diff --git a/interfaces/builtin/upower_observe.go b/interfaces/builtin/upower_observe.go index c78d2c7d14d..98bfe7323c3 100644 --- a/interfaces/builtin/upower_observe.go +++ b/interfaces/builtin/upower_observe.go @@ -88,6 +88,5 @@ func NewUPowerObserveInterface() interfaces.Interface { connectedPlugAppArmor: upowerObserveConnectedPlugAppArmor, connectedPlugSecComp: upowerObserveConnectedPlugSecComp, reservedForOS: true, - autoConnect: true, } } diff --git a/interfaces/builtin/upower_observe_test.go b/interfaces/builtin/upower_observe_test.go index 7c17ccaac2b..abc03c9e045 100644 --- a/interfaces/builtin/upower_observe_test.go +++ b/interfaces/builtin/upower_observe_test.go @@ -88,7 +88,3 @@ func (s *UPowerObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(err, IsNil) c.Assert(snippet, Not(IsNil)) } - -func (s *UPowerObserveInterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} diff --git a/interfaces/builtin/usb_raw.go b/interfaces/builtin/usb_raw.go new file mode 100644 index 00000000000..1e10bff44b4 --- /dev/null +++ b/interfaces/builtin/usb_raw.go @@ -0,0 +1,49 @@ +// -*- 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 ( + "github.com/snapcore/snapd/interfaces" +) + +const usbrawConnectedPlugAppArmor = ` +# Description: Allow raw access to all connected USB devices. +# Reserved because this gives privileged access to the system. +# Usage: reserved +/dev/bus/usb/[0-9][0-9][0-9]/[0-9][0-9][0-9] rw, + +# Allow detection of usb devices. Leaks plugged in USB device info +/sys/bus/usb/devices/ r, +/sys/devices/pci**/usb[0-9]** r, + +/run/udev/data/c16[67]:[0-9] r, # ACM USB modems +/run/udev/data/b180:* r, # various USB block devices +/run/udev/data/c18[089]:* r, # various USB character devices: USB serial converters, etc. +/run/udev/data/+usb:* r, +` + +// Transitional interface which allows access to all usb devices. +func NewUsbRawInterface() interfaces.Interface { + return &commonInterface{ + name: "usb-raw", + connectedPlugAppArmor: usbrawConnectedPlugAppArmor, + reservedForOS: true, + } +} diff --git a/interfaces/builtin/usb_raw_test.go b/interfaces/builtin/usb_raw_test.go new file mode 100644 index 00000000000..03dbbb12c57 --- /dev/null +++ b/interfaces/builtin/usb_raw_test.go @@ -0,0 +1,88 @@ +// -*- 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 UsbRawSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&UsbRawSuite{ + iface: builtin.NewUsbRawInterface(), + slot: &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "usb-raw", + Interface: "usb-raw", + }, + }, + plug: &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "other"}, + Name: "usb-raw", + Interface: "usb-raw", + }, + }, +}) + +func (s *UsbRawSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "usb-raw") +} + +func (s *UsbRawSuite) TestSanitizeSlot(c *C) { + err := s.iface.SanitizeSlot(s.slot) + c.Assert(err, IsNil) + err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "usb-raw", + Interface: "usb-raw", + }}) + c.Assert(err, ErrorMatches, "usb-raw slots are reserved for the operating system snap") +} + +func (s *UsbRawSuite) TestSanitizePlug(c *C) { + err := s.iface.SanitizePlug(s.plug) + c.Assert(err, IsNil) +} + +func (s *UsbRawSuite) TestSanitizeIncorrectInterface(c *C) { + c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, + PanicMatches, `slot is not of interface "usb-raw"`) + c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, + PanicMatches, `plug is not of interface "usb-raw"`) +} + +func (s *UsbRawSuite) 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)) + c.Assert(string(snippet), testutil.Contains, `/sys/bus/usb/devices/`) +} diff --git a/interfaces/builtin/utils.go b/interfaces/builtin/utils.go index b6f4551c304..540ba2681a8 100644 --- a/interfaces/builtin/utils.go +++ b/interfaces/builtin/utils.go @@ -83,3 +83,12 @@ func udevUsbDeviceSnippet(subsystem string, usbVendor int64, usbProduct int64, k udevSnippet.WriteString("\n") return udevSnippet.Bytes() } + +// Function to create an udev TAG, essentially the cgroup name for +// the snap application. +// @param snapName is the name of the snap +// @param appName is the name of the application +// @return string "snap__" +func udevSnapSecurityName(snapName string, appName string) string { + return fmt.Sprintf(`snap_%s_%s`, snapName, appName) +} diff --git a/interfaces/builtin/x11.go b/interfaces/builtin/x11.go index 15ba51680ca..80acb4102ff 100644 --- a/interfaces/builtin/x11.go +++ b/interfaces/builtin/x11.go @@ -58,6 +58,5 @@ func NewX11Interface() interfaces.Interface { connectedPlugAppArmor: x11ConnectedPlugAppArmor, connectedPlugSecComp: x11ConnectedPlugSecComp, reservedForOS: true, - autoConnect: true, } } diff --git a/interfaces/builtin/x11_test.go b/interfaces/builtin/x11_test.go index 992ce35a29f..b47bde2e110 100644 --- a/interfaces/builtin/x11_test.go +++ b/interfaces/builtin/x11_test.go @@ -90,10 +90,6 @@ func (s *X11InterfaceSuite) TestUsedSecuritySystems(c *C) { c.Assert(snippet, Not(IsNil)) } -func (s *X11InterfaceSuite) TestLegacyAutoConnect(c *C) { - c.Check(s.iface.LegacyAutoConnect(), Equals, true) -} - // The getsockname system call is allowed func (s *X11InterfaceSuite) TestLP1574526(c *C) { snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecuritySecComp) diff --git a/interfaces/core.go b/interfaces/core.go index 74c1659fe4d..8717c970c7e 100644 --- a/interfaces/core.go +++ b/interfaces/core.go @@ -150,12 +150,6 @@ type Interface interface { // doesn't recognize the security system. ConnectedSlotSnippet(plug *Plug, slot *Slot, securitySystem SecuritySystem) ([]byte, error) - // LegacyAutoConnect is OBSOLETE, only used temporarily in tests - // to cross check with past behavior. - // It returned whether plugs and slots should be implicitly - // auto-connected when an unambiguous connection candidate is available. - LegacyAutoConnect() bool - // AutoConnect returns whether plug and slot should be // implicitly auto-connected assuming they will be an // unambiguous connection candidate and declaration-based checks diff --git a/interfaces/seccomp/template.go b/interfaces/seccomp/template.go index fefef2908c2..a6819d944a7 100644 --- a/interfaces/seccomp/template.go +++ b/interfaces/seccomp/template.go @@ -264,6 +264,7 @@ open openat pause +personality pipe pipe2 poll diff --git a/interfaces/testtype.go b/interfaces/testtype.go index 95ae10b2ae5..077e6e24a8e 100644 --- a/interfaces/testtype.go +++ b/interfaces/testtype.go @@ -112,10 +112,6 @@ func (t *TestInterface) PermanentSlotSnippet(slot *Slot, securitySystem Security return nil, nil } -func (t *TestInterface) LegacyAutoConnect() bool { - panic("no test should depend on this anymore") -} - // AutoConnect returns whether plug and slot should be implicitly // auto-connected assuming they will be an unambiguous connection // candidate. diff --git a/osutil/chattr.go b/osutil/chattr.go new file mode 100644 index 00000000000..b6a010335a6 --- /dev/null +++ b/osutil/chattr.go @@ -0,0 +1,76 @@ +// -*- 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 osutil + +import ( + "os" + "syscall" + "unsafe" +) + +const ( + // from /usr/include/linux/fs.h + FS_SECRM_FL = 0x00000001 /* Secure deletion */ + FS_UNRM_FL = 0x00000002 /* Undelete */ + FS_COMPR_FL = 0x00000004 /* Compress file */ + FS_SYNC_FL = 0x00000008 /* Synchronous updates */ + FS_IMMUTABLE_FL = 0x00000010 /* Immutable file */ + FS_APPEND_FL = 0x00000020 /* writes to file may only append */ + FS_NODUMP_FL = 0x00000040 /* do not dump file */ + FS_NOATIME_FL = 0x00000080 /* do not update atime */ + FS_DIRTY_FL = 0x00000100 + FS_COMPRBLK_FL = 0x00000200 /* One or more compressed clusters */ + FS_NOCOMP_FL = 0x00000400 /* Don't compress */ + FS_ECOMPR_FL = 0x00000800 /* Compression error */ + FS_BTREE_FL = 0x00001000 /* btree format dir */ + FS_INDEX_FL = 0x00001000 /* hash-indexed directory */ + FS_IMAGIC_FL = 0x00002000 /* AFS directory */ + FS_JOURNAL_DATA_FL = 0x00004000 /* Reserved for ext3 */ + FS_NOTAIL_FL = 0x00008000 /* file tail should not be merged */ + FS_DIRSYNC_FL = 0x00010000 /* dirsync behaviour (directories only) */ + FS_TOPDIR_FL = 0x00020000 /* Top of directory hierarchies*/ + FS_EXTENT_FL = 0x00080000 /* Extents */ + FS_DIRECTIO_FL = 0x00100000 /* Use direct i/o */ + FS_NOCOW_FL = 0x00800000 /* Do not cow file */ + FS_PROJINHERIT_FL = 0x20000000 /* Create with parents projid */ + FS_RESERVED_FL = 0x80000000 /* reserved for ext2 lib */ +) + +func ioctl(f *os.File, request uintptr, attrp *int32) error { + argp := uintptr(unsafe.Pointer(attrp)) + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), request, argp) + if errno != 0 { + return os.NewSyscallError("ioctl", errno) + } + + return nil +} + +// GetAttr retrieves the attributes of a file on a linux filesystem +func GetAttr(f *os.File) (int32, error) { + attr := int32(-1) + err := ioctl(f, _FS_IOC_GETFLAGS, &attr) + return attr, err +} + +// SetAttr sets the attributes of a file on a linux filesystem to the given value +func SetAttr(f *os.File, attr int32) error { + return ioctl(f, _FS_IOC_SETFLAGS, &attr) +} diff --git a/osutil/chattr_32.go b/osutil/chattr_32.go new file mode 100644 index 00000000000..09ca79d61c7 --- /dev/null +++ b/osutil/chattr_32.go @@ -0,0 +1,27 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build armhf 386 + +/* + * 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 osutil + +const ( + // these are actually _FS_IOC32 (i'm cheating) + _FS_IOC_GETFLAGS = uintptr(0x80046601) + _FS_IOC_SETFLAGS = uintptr(0x40046602) +) diff --git a/osutil/chattr_64.go b/osutil/chattr_64.go new file mode 100644 index 00000000000..599746b28ce --- /dev/null +++ b/osutil/chattr_64.go @@ -0,0 +1,28 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build arm64 amd64 ppc64le s390x + +/* + * 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 osutil + +const ( + // There is a logic to these but I don't care to implement it all. + // If you do, chase them from linux/fs.h + _FS_IOC_GETFLAGS = uintptr(0x80086601) + _FS_IOC_SETFLAGS = uintptr(0x40086602) +) diff --git a/osutil/export_test.go b/osutil/export_test.go index 0b124a562bc..413926717d8 100644 --- a/osutil/export_test.go +++ b/osutil/export_test.go @@ -30,6 +30,13 @@ func MockUserLookup(mock func(name string) (*user.User, error)) func() { return func() { userLookup = realUserLookup } } +func MockUserCurrent(mock func() (*user.User, error)) func() { + realUserCurrent := userCurrent + userCurrent = mock + + return func() { userCurrent = realUserCurrent } +} + func MockSudoersDotD(mockDir string) func() { realSudoersD := sudoersDotD sudoersDotD = mockDir diff --git a/osutil/user.go b/osutil/user.go index eb8d0714c64..087b35d93e8 100644 --- a/osutil/user.go +++ b/osutil/user.go @@ -126,13 +126,24 @@ func AddUser(name string, opts *AddUserOptions) error { return nil } -// RealUser finds the user behind a sudo invocation, if applicable and possible. +var userCurrent = user.Current + +// RealUser finds the user behind a sudo invocation when root, if applicable +// and possible. +// +// Don't check SUDO_USER when not root and simply return the current uid +// to properly support sudo'ing from root to a non-root user func RealUser() (*user.User, error) { - cur, err := user.Current() + cur, err := userCurrent() if err != nil { return nil, err } + // not root, so no sudo invocation we care about + if cur.Uid != "0" { + return cur, nil + } + realName := os.Getenv("SUDO_USER") if realName == "" { // not sudo; current is correct diff --git a/osutil/user_test.go b/osutil/user_test.go index 9a1283a3fb3..928f36fb810 100644 --- a/osutil/user_test.go +++ b/osutil/user_test.go @@ -21,8 +21,10 @@ package osutil_test import ( "io/ioutil" + "os" "os/user" "path/filepath" + "strconv" "gopkg.in/check.v1" @@ -152,3 +154,35 @@ func (s *createUserSuite) TestAddUserWithPassword(c *check.C) { }) } + +func (s *createUserSuite) TestRealUser(c *check.C) { + oldUser := os.Getenv("SUDO_USER") + defer func() { os.Setenv("SUDO_USER", oldUser) }() + + for _, t := range []struct { + SudoUsername string + CurrentUsername string + CurrentUid int + }{ + // simulate regular "root", no SUDO_USER set + {"", os.Getenv("USER"), 0}, + // simulate a normal sudo invocation + {"guy", "guy", 0}, + // simulate running "sudo -u some-user -i" as root + // (LP: #1638656) + {"root", os.Getenv("USER"), 1000}, + } { + restore := osutil.MockUserCurrent(func() (*user.User, error) { + return &user.User{ + Username: t.CurrentUsername, + Uid: strconv.Itoa(t.CurrentUid), + }, nil + }) + defer restore() + + os.Setenv("SUDO_USER", t.SudoUsername) + cur, err := osutil.RealUser() + c.Assert(err, check.IsNil) + c.Check(cur.Username, check.Equals, t.CurrentUsername) + } +} diff --git a/overlord/devicestate/devicemgr_test.go b/overlord/devicestate/devicemgr_test.go index e53927accdc..ea0d2123429 100644 --- a/overlord/devicestate/devicemgr_test.go +++ b/overlord/devicestate/devicemgr_test.go @@ -226,12 +226,12 @@ func (s *deviceMgrSuite) mockServer(c *C, reqID string) *httptest.Server { })) } -func (s *deviceMgrSuite) setupGadget(c *C, snapYaml string) { +func (s *deviceMgrSuite) setupGadget(c *C, snapYaml string, snapContents string) { sideInfoGadget := &snap.SideInfo{ RealName: "gadget", Revision: snap.R(2), } - snaptest.MockSnap(c, snapYaml, sideInfoGadget) + snaptest.MockSnap(c, snapYaml, snapContents, sideInfoGadget) snapstate.Set(s.state, "gadget", &snapstate.SnapState{ SnapType: "gadget", Active: true, @@ -240,12 +240,12 @@ func (s *deviceMgrSuite) setupGadget(c *C, snapYaml string) { }) } -func (s *deviceMgrSuite) setupCore(c *C, name, snapYaml string) { +func (s *deviceMgrSuite) setupCore(c *C, name, snapYaml string, snapContents string) { sideInfoCore := &snap.SideInfo{ RealName: name, Revision: snap.R(3), } - snaptest.MockSnap(c, snapYaml, sideInfoCore) + snaptest.MockSnap(c, snapYaml, snapContents, sideInfoCore) snapstate.Set(s.state, name, &snapstate.SnapState{ SnapType: "os", Active: true, @@ -277,7 +277,7 @@ func (s *deviceMgrSuite) TestFullDeviceRegistrationHappy(c *C) { name: gadget type: gadget version: gadget -`) +`, "") auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", @@ -347,7 +347,7 @@ func (s *deviceMgrSuite) TestDoRequestSerialIdempotentAfterAddSerial(c *C) { name: gadget type: gadget version: gadget -`) +`, "") auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", @@ -412,7 +412,7 @@ func (s *deviceMgrSuite) TestDoRequestSerialIdempotentAfterGotSerial(c *C) { name: gadget type: gadget version: gadget -`) +`, "") auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", @@ -479,7 +479,7 @@ func (s *deviceMgrSuite) TestFullDeviceRegistrationPollHappy(c *C) { name: gadget type: gadget version: gadget -`) +`, "") auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", @@ -570,7 +570,7 @@ type: gadget version: gadget hooks: prepare-device: -`) +`, "") auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", @@ -857,7 +857,7 @@ func (s *deviceMgrSuite) TestDeviceManagerEnsureSeedYamlRecover(c *C) { name: ubuntu-core type: os version: ubuntu-core -`) +`, "") // have a model assertion model, err := s.storeSigning.Sign(asserts.ModelType, map[string]interface{}{ diff --git a/overlord/hookstate/hookmgr_test.go b/overlord/hookstate/hookmgr_test.go index afdb83c9fde..0106f1a4037 100644 --- a/overlord/hookstate/hookmgr_test.go +++ b/overlord/hookstate/hookmgr_test.go @@ -57,6 +57,7 @@ hooks: configure: prepare-device: ` +var snapContents = "" func (s *hookManagerSuite) SetUpTest(c *C) { dirs.SetRootDir(c.MkDir()) @@ -83,7 +84,7 @@ func (s *hookManagerSuite) SetUpTest(c *C) { s.change.AddTask(s.task) sideInfo := &snap.SideInfo{RealName: "test-snap", SnapID: "some-snap-id", Revision: snap.R(1)} - snaptest.MockSnap(c, snapYaml, sideInfo) + snaptest.MockSnap(c, snapYaml, snapContents, sideInfo) snapstate.Set(s.state, "test-snap", &snapstate.SnapState{ Active: true, Sequence: []*snap.SideInfo{sideInfo}, diff --git a/overlord/ifacestate/ifacemgr_test.go b/overlord/ifacestate/ifacemgr_test.go index b46c126ab5a..2cff3b7327d 100644 --- a/overlord/ifacestate/ifacemgr_test.go +++ b/overlord/ifacestate/ifacemgr_test.go @@ -491,7 +491,7 @@ func (s *interfaceManagerSuite) mockSnap(c *C, yamlText string) *snap.Info { sideInfo := &snap.SideInfo{ Revision: snap.R(1), } - snapInfo := snaptest.MockSnap(c, yamlText, sideInfo) + snapInfo := snaptest.MockSnap(c, yamlText, "", sideInfo) sideInfo.RealName = snapInfo.Name() a, err := s.db.FindMany(asserts.SnapDeclarationType, map[string]string{ @@ -520,7 +520,7 @@ func (s *interfaceManagerSuite) mockSnap(c *C, yamlText string) *snap.Info { func (s *interfaceManagerSuite) mockUpdatedSnap(c *C, yamlText string, revision int) *snap.Info { sideInfo := &snap.SideInfo{Revision: snap.R(revision)} - snapInfo := snaptest.MockSnap(c, yamlText, sideInfo) + snapInfo := snaptest.MockSnap(c, yamlText, "", sideInfo) sideInfo.RealName = snapInfo.Name() s.state.Lock() diff --git a/overlord/managers_test.go b/overlord/managers_test.go index bc94ba4c273..79eaeba7292 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -493,7 +493,7 @@ apps: c.Check(snapRev50.(*asserts.SnapRevision).SnapID(), Equals, fooSnapID) c.Check(snapRev50.(*asserts.SnapRevision).SnapRevision(), Equals, 50) - // check udpated wrapper + // check updated wrapper symlinkTarget, err := os.Readlink(info.Apps["bar"].WrapperPath()) c.Assert(err, IsNil) c.Assert(symlinkTarget, Equals, "/usr/bin/snap") diff --git a/overlord/patch/patch.go b/overlord/patch/patch.go index a119637dc87..687521af61c 100644 --- a/overlord/patch/patch.go +++ b/overlord/patch/patch.go @@ -69,7 +69,7 @@ func Apply(s *state.State) error { } err := applyOne(patch, s, level) if err != nil { - logger.Noticef("Cannnot patch: %v", err) + logger.Noticef("Cannot patch: %v", err) return fmt.Errorf("cannot patch system state from level %d to %d: %v", level, level+1, err) } level++ diff --git a/overlord/snapstate/backend/copydata_test.go b/overlord/snapstate/backend/copydata_test.go index 50f9bf4cbdd..a45c89b3186 100644 --- a/overlord/snapstate/backend/copydata_test.go +++ b/overlord/snapstate/backend/copydata_test.go @@ -62,6 +62,7 @@ version: 1.0 helloYaml2 = `name: hello version: 2.0 ` + helloContents = "" ) func (s *copydataSuite) TestCopyData(c *C) { @@ -75,7 +76,7 @@ func (s *copydataSuite) TestCopyData(c *C) { canaryData := []byte("ni ni ni") - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) // just creates data dirs in this case err = s.be.CopySnapData(v1, nil, &s.nullProgress) c.Assert(err, IsNil) @@ -91,7 +92,7 @@ func (s *copydataSuite) TestCopyData(c *C) { err = ioutil.WriteFile(filepath.Join(homeCommonData, "canary.common_home"), canaryData, 0644) c.Assert(err, IsNil) - v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) err = s.be.CopySnapData(v2, v1, &s.nullProgress) c.Assert(err, IsNil) @@ -122,11 +123,11 @@ func (s *copydataSuite) TestCopyDataBails(c *C) { oldSnapDataHomeGlob := dirs.SnapDataHomeGlob defer func() { dirs.SnapDataHomeGlob = oldSnapDataHomeGlob }() - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) c.Assert(s.be.CopySnapData(v1, nil, &s.nullProgress), IsNil) c.Assert(os.Chmod(v1.DataDir(), 0), IsNil) - v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) err := s.be.CopySnapData(v2, v1, &s.nullProgress) c.Check(err, ErrorMatches, "cannot copy .*") } @@ -139,7 +140,7 @@ func (s *copydataSuite) TestCopyDataNoUserHomes(c *C) { defer func() { dirs.SnapDataHomeGlob = oldSnapDataHomeGlob }() dirs.SnapDataHomeGlob = filepath.Join(s.tempdir, "no-such-home", "*", "snap") - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) err := s.be.CopySnapData(v1, nil, &s.nullProgress) c.Assert(err, IsNil) @@ -150,7 +151,7 @@ func (s *copydataSuite) TestCopyDataNoUserHomes(c *C) { err = ioutil.WriteFile(canaryDataFile, []byte(""), 0644) c.Assert(err, IsNil) - v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) err = s.be.CopySnapData(v2, v1, &s.nullProgress) c.Assert(err, IsNil) @@ -195,12 +196,12 @@ func (s copydataSuite) populateHomeData(c *C, user string, revision snap.Revisio } func (s *copydataSuite) TestCopyDataDoUndo(c *C) { - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) s.populateData(c, snap.R(10)) homedir := s.populateHomeData(c, "user1", snap.R(10)) // pretend we install a new version - v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) // copy data err := s.be.CopySnapData(v2, v1, &s.nullProgress) @@ -230,11 +231,11 @@ func (s *copydataSuite) TestCopyDataDoUndoNoUserHomes(c *C) { defer func() { dirs.SnapDataHomeGlob = oldSnapDataHomeGlob }() dirs.SnapDataHomeGlob = filepath.Join(s.tempdir, "no-such-home", "*", "snap") - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) s.populateData(c, snap.R(10)) // pretend we install a new version - v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) // copy data err := s.be.CopySnapData(v2, v1, &s.nullProgress) @@ -253,7 +254,7 @@ func (s *copydataSuite) TestCopyDataDoUndoNoUserHomes(c *C) { } func (s *copydataSuite) TestCopyDataDoUndoFirstInstall(c *C) { - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) // first install err := s.be.CopySnapData(v1, nil, &s.nullProgress) @@ -272,12 +273,12 @@ func (s *copydataSuite) TestCopyDataDoUndoFirstInstall(c *C) { } func (s *copydataSuite) TestCopyDataDoABA(c *C) { - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) s.populateData(c, snap.R(10)) c.Check(s.populatedData("10"), Equals, "10\n") // pretend we install a new version - v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) // and write our own data to it s.populateData(c, snap.R(20)) c.Check(s.populatedData("20"), Equals, "20\n") @@ -297,12 +298,12 @@ func (s *copydataSuite) TestCopyDataDoABA(c *C) { } func (s *copydataSuite) TestCopyDataDoUndoABA(c *C) { - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) s.populateData(c, snap.R(10)) c.Check(s.populatedData("10"), Equals, "10\n") // pretend we install a new version - v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) // and write our own data to it s.populateData(c, snap.R(20)) c.Check(s.populatedData("20"), Equals, "20\n") @@ -326,13 +327,13 @@ func (s *copydataSuite) TestCopyDataDoUndoABA(c *C) { func (s *copydataSuite) TestCopyDataDoIdempotent(c *C) { // make sure that a retry wouldn't stumble on partial work - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) s.populateData(c, snap.R(10)) homedir := s.populateHomeData(c, "user1", snap.R(10)) // pretend we install a new version - v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) // copy data err := s.be.CopySnapData(v2, v1, &s.nullProgress) @@ -354,12 +355,12 @@ func (s *copydataSuite) TestCopyDataDoIdempotent(c *C) { func (s *copydataSuite) TestCopyDataUndoIdempotent(c *C) { // make sure that a retry wouldn't stumble on partial work - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) s.populateData(c, snap.R(10)) homedir := s.populateHomeData(c, "user1", snap.R(10)) // pretend we install a new version - v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) // copy data err := s.be.CopySnapData(v2, v1, &s.nullProgress) @@ -382,7 +383,7 @@ func (s *copydataSuite) TestCopyDataUndoIdempotent(c *C) { } func (s *copydataSuite) TestCopyDataDoFirstInstallIdempotent(c *C) { - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) // first install err := s.be.CopySnapData(v1, nil, &s.nullProgress) @@ -405,7 +406,7 @@ func (s *copydataSuite) TestCopyDataDoFirstInstallIdempotent(c *C) { } func (s *copydataSuite) TestCopyDataUndoFirstInstallIdempotent(c *C) { - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) // first install err := s.be.CopySnapData(v1, nil, &s.nullProgress) @@ -428,11 +429,11 @@ func (s *copydataSuite) TestCopyDataUndoFirstInstallIdempotent(c *C) { } func (s *copydataSuite) TestCopyDataCopyFailure(c *C) { - v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) s.populateData(c, snap.R(10)) // pretend we install a new version - v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) defer testutil.MockCommand(c, "cp", "echo cp: boom; exit 3").Restore() diff --git a/overlord/snapstate/backend/link_test.go b/overlord/snapstate/backend/link_test.go index aad61f8eff8..85d369283e1 100644 --- a/overlord/snapstate/backend/link_test.go +++ b/overlord/snapstate/backend/link_test.go @@ -69,8 +69,9 @@ apps: command: svc daemon: simple ` + const contents = "" - info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) + info := snaptest.MockSnap(c, yaml, contents, &snap.SideInfo{Revision: snap.R(11)}) err := s.be.LinkSnap(info) c.Assert(err, IsNil) @@ -98,8 +99,9 @@ func (s *linkSuite) TestLinkDoUndoCurrentSymlink(c *C) { const yaml = `name: hello version: 1.0 ` + const contents = "" - info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) + info := snaptest.MockSnap(c, yaml, contents, &snap.SideInfo{Revision: snap.R(11)}) err := s.be.LinkSnap(info) c.Assert(err, IsNil) @@ -139,8 +141,9 @@ apps: command: svc daemon: simple ` + const contents = "" - info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) + info := snaptest.MockSnap(c, yaml, contents, &snap.SideInfo{Revision: snap.R(11)}) err := s.be.LinkSnap(info) c.Assert(err, IsNil) @@ -180,8 +183,9 @@ apps: command: svc daemon: simple ` + const contents = "" - info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) + info := snaptest.MockSnap(c, yaml, contents, &snap.SideInfo{Revision: snap.R(11)}) err := s.be.LinkSnap(info) c.Assert(err, IsNil) diff --git a/overlord/snapstate/backend/snapdata.go b/overlord/snapstate/backend/snapdata.go index d50d2a3d7d9..28af31c7416 100644 --- a/overlord/snapstate/backend/snapdata.go +++ b/overlord/snapstate/backend/snapdata.go @@ -97,6 +97,14 @@ func snapCommonDataDirs(snap *snap.Info) ([]string, error) { if err != nil { return nil, err } + + // then XDG_RUNTIME_DIRs for the users + foundXdg, err := filepath.Glob(snap.XdgRuntimeDirs()) + if err != nil { + return nil, err + } + found = append(found, foundXdg...) + // then system data found = append(found, snap.CommonDataDir()) diff --git a/overlord/snapstate/booted_test.go b/overlord/snapstate/booted_test.go index 6efea45ab27..b00cb2e1d5a 100644 --- a/overlord/snapstate/booted_test.go +++ b/overlord/snapstate/booted_test.go @@ -88,8 +88,8 @@ func (bs *bootedSuite) settle() { } func (bs *bootedSuite) makeInstalledKernelOS(c *C, st *state.State) { - snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", osSI1) - snaptest.MockSnap(c, "name: core\ntype: os\nversion: 2", osSI2) + snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", "", osSI1) + snaptest.MockSnap(c, "name: core\ntype: os\nversion: 2", "", osSI2) snapstate.Set(st, "core", &snapstate.SnapState{ SnapType: "os", Active: true, @@ -97,8 +97,8 @@ func (bs *bootedSuite) makeInstalledKernelOS(c *C, st *state.State) { Current: snap.R(2), }) - snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 1", kernelSI1) - snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 2", kernelSI2) + snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 1", "", kernelSI1) + snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 2", "", kernelSI2) snapstate.Set(st, "canonical-pc-linux", &snapstate.SnapState{ SnapType: "kernel", Active: true, diff --git a/overlord/snapstate/check_snap_test.go b/overlord/snapstate/check_snap_test.go index cb8fa116e68..6b39fdca16e 100644 --- a/overlord/snapstate/check_snap_test.go +++ b/overlord/snapstate/check_snap_test.go @@ -237,7 +237,7 @@ func (s *checkSnapSuite) TestCheckSnapGadgetUpdate(c *C) { name: gadget type: gadget version: 1 -`, si) +`, "", si) snapstate.Set(st, "gadget", &snapstate.SnapState{ SnapType: "gadget", Active: true, @@ -279,7 +279,7 @@ func (s *checkSnapSuite) TestCheckSnapGadgetUpdateLocal(c *C) { name: gadget type: gadget version: 1 -`, si) +`, "", si) snapstate.Set(st, "gadget", &snapstate.SnapState{ SnapType: "gadget", Active: true, @@ -321,7 +321,7 @@ func (s *checkSnapSuite) TestCheckSnapGadgetUpdateToUnassertedProhibited(c *C) { name: gadget type: gadget version: 1 -`, si) +`, "", si) snapstate.Set(st, "gadget", &snapstate.SnapState{ SnapType: "gadget", Active: true, @@ -362,7 +362,7 @@ func (s *checkSnapSuite) TestCheckSnapGadgetAdditionProhibited(c *C) { name: gadget type: gadget version: 1 -`, si) +`, "", si) snapstate.Set(st, "gadget", &snapstate.SnapState{ SnapType: "gadget", Active: true, @@ -403,7 +403,7 @@ func (s *checkSnapSuite) TestCheckSnapGadgetAdditionProhibitedBySnapID(c *C) { name: gadget type: gadget version: 1 -`, si) +`, "", si) snapstate.Set(st, "gadget", &snapstate.SnapState{ SnapType: "gadget", Active: true, @@ -522,7 +522,7 @@ func (s *checkSnapSuite) TestCheckSnapKernelUpdate(c *C) { name: kernel type: kernel version: 1 -`, si) +`, "", si) snapstate.Set(st, "kernel", &snapstate.SnapState{ SnapType: "kernel", Active: true, @@ -564,7 +564,7 @@ func (s *checkSnapSuite) TestCheckSnapKernelAdditionProhibitedBySnapID(c *C) { name: kernel type: kernel version: 1 -`, si) +`, "", si) snapstate.Set(st, "kernel", &snapstate.SnapState{ SnapType: "kernel", Active: true, diff --git a/overlord/snapstate/snapmgr_test.go b/overlord/snapstate/snapmgr_test.go index ed514a8ab24..e9ce8df5a31 100644 --- a/overlord/snapstate/snapmgr_test.go +++ b/overlord/snapstate/snapmgr_test.go @@ -3158,12 +3158,12 @@ func (s *snapmgrQuerySuite) SetUpTest(c *C) { name: name0 version: 1.1 description: | - Lots of text`, sideInfo11) + Lots of text`, "", sideInfo11) snaptest.MockSnap(c, ` name: name0 version: 1.2 description: | - Lots of text`, sideInfo12) + Lots of text`, "", sideInfo12) snapstate.Set(st, "name1", &snapstate.SnapState{ Active: true, Sequence: []*snap.SideInfo{sideInfo11, sideInfo12}, @@ -3295,7 +3295,7 @@ func (s *snapmgrQuerySuite) TestTypeInfo(c *C) { RealName: x.snapName, Revision: snap.R(2), } - snaptest.MockSnap(c, fmt.Sprintf("name: %q\ntype: %q\nversion: %q\n", x.snapName, x.snapType, x.snapName), sideInfo) + snaptest.MockSnap(c, fmt.Sprintf("name: %q\ntype: %q\nversion: %q\n", x.snapName, x.snapType, x.snapName), "", sideInfo) snapstate.Set(st, x.snapName, &snapstate.SnapState{ SnapType: string(x.snapType), Active: true, @@ -3814,7 +3814,7 @@ func (s *snapmgrTestSuite) TestSeqRevertPostRevertFailure(c *C) { s.testRevertFailureSequence(c, &opSeqOpts{before: []int{1, 2, 3}, current: 2, via: 1}) } -// 4c. taht fails after link +// 4c. that fails after link func (s *snapmgrTestSuite) TestSeqTotalRevertPostRevertFailure(c *C) { s.testTotalRevertFailureSequence(c, &opSeqOpts{before: []int{1, 2, 3}, current: 2, via: 1}) } @@ -4088,7 +4088,7 @@ volumes: func (s *snapmgrTestSuite) prepareGadget(c *C) { gadgetSideInfo := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)} - gadgetInfo := snaptest.MockSnap(c, "name: the-gadget\nversion: 1.0", gadgetSideInfo) + gadgetInfo := snaptest.MockSnap(c, "name: the-gadget\nversion: 1.0", "", gadgetSideInfo) err := ioutil.WriteFile(filepath.Join(gadgetInfo.MountDir(), "meta/gadget.yaml"), []byte(gadgetYaml), 0600) c.Assert(err, IsNil) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 9ab517814be..e2a6c724eab 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -755,7 +755,7 @@ func Remove(s *state.State, name string, revision snap.Revision) (*state.TaskSet return full, nil } -// Revert returns a set of tasks for reverting to the pervious version of the snap. +// Revert returns a set of tasks for reverting to the previous version of the snap. // Note that the state must be locked by the caller. func Revert(s *state.State, name string, flags Flags) (*state.TaskSet, error) { var snapst SnapState diff --git a/overlord/state/task.go b/overlord/state/task.go index b8b587c3c13..995157c190a 100644 --- a/overlord/state/task.go +++ b/overlord/state/task.go @@ -403,7 +403,7 @@ func (t *Task) JoinLane(lane int) { t.lanes = append(t.lanes, lane) } -// At schedules the task, if it's not ready, to happen no earlier than when, if when is the zero time any previous special scheduling is supressed. +// At schedules the task, if it's not ready, to happen no earlier than when, if when is the zero time any previous special scheduling is suppressed. func (t *Task) At(when time.Time) { t.state.writing() iszero := when.IsZero() diff --git a/overlord/state/taskrunner.go b/overlord/state/taskrunner.go index 30b21174be6..b618e8df22d 100644 --- a/overlord/state/taskrunner.go +++ b/overlord/state/taskrunner.go @@ -381,7 +381,7 @@ func mustWait(t *Task) bool { return false } -// wait expectes to be called with th r.mu lock held +// wait expects to be called with th r.mu lock held func (r *TaskRunner) wait() { for len(r.tombs) > 0 { for _, t := range r.tombs { diff --git a/partition/bootloader.go b/partition/bootloader.go index 8e97a0199d6..7eb8fb0ad8f 100644 --- a/partition/bootloader.go +++ b/partition/bootloader.go @@ -112,7 +112,7 @@ func ForceBootloader(booloader Bootloader) { forcedBootloader = booloader } -// MarkBootSuccessful marks the current boot as sucessful. This means +// MarkBootSuccessful marks the current boot as successful. This means // that snappy will consider this combination of kernel/os a valid // target for rollback func MarkBootSuccessful(bootloader Bootloader) error { diff --git a/progress/progress.go b/progress/progress.go index 602999a9234..e734f027d5d 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -48,7 +48,7 @@ type Meter interface { // interface for writer Write(p []byte) (n int, err error) - // notify the user of miscelaneous events + // notify the user of miscellaneous events Notify(string) } @@ -172,7 +172,7 @@ func (t *TextProgress) Agreed(intro, license string) bool { return unicode.ToLower(r) == 'y' } -// Notify the user of miscelaneous events +// Notify the user of miscellaneous events func (*TextProgress) Notify(msg string) { fmt.Printf("\r%s%s\n", msg, clearUntilEOL) } diff --git a/run-checks b/run-checks index 454effc31da..4bff156ab04 100755 --- a/run-checks +++ b/run-checks @@ -101,6 +101,11 @@ if [ "$STATIC" = 1 ]; then go vet $pkg done + echo Checking spelling errors + go get -u github.com/client9/misspell/cmd/misspell + for file in $(ls . | grep -v 'vendor\|po'); do + ${GOBIN:-$GOPATH/bin}/misspell -error -i auther $file + done fi if [ "$UNIT" = 1 ]; then @@ -116,7 +121,7 @@ if [ "$UNIT" = 1 ]; then # tests echo Running tests from $(pwd) - for pkg in $(go list ./... | grep -v '/vendor/' ); do + for pkg in $(go list ./... | grep -v '/vendor/' ); do $goctest -v -coverprofile=.coverage/profile.out $pkg append_coverage .coverage/profile.out done diff --git a/snap/container.go b/snap/container.go index 91fca607a9d..765e99e74f0 100644 --- a/snap/container.go +++ b/snap/container.go @@ -32,6 +32,9 @@ import ( // Container is the interface to interact with the low-level snap files type Container interface { + // Size returns the size of the snap in bytes. + Size() (int64, error) + // ReadFile returns the content of a single file from the snap. ReadFile(relative string) ([]byte, error) diff --git a/snap/gadget_test.go b/snap/gadget_test.go index 02d8c380bf8..a979b1ba3f3 100644 --- a/snap/gadget_test.go +++ b/snap/gadget_test.go @@ -69,6 +69,8 @@ volumes: unpack: false `) +var mockGadgetSnapContents = "SNAP" + func (s *gadgetYamlTestSuite) SetUpTest(c *C) { dirs.SetRootDir(c.MkDir()) } @@ -78,13 +80,13 @@ func (s *gadgetYamlTestSuite) TearDownTest(c *C) { } func (s *gadgetYamlTestSuite) TestReadGadgetYamlMissing(c *C) { - info := snaptest.MockSnap(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(42)}) + info := snaptest.MockSnap(c, mockGadgetSnapYaml, mockGadgetSnapContents, &snap.SideInfo{Revision: snap.R(42)}) _, err := snap.ReadGadgetInfo(info) c.Assert(err, ErrorMatches, ".*meta/gadget.yaml: no such file or directory") } func (s *gadgetYamlTestSuite) TestReadGadgetYamlValid(c *C) { - info := snaptest.MockSnap(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(42)}) + info := snaptest.MockSnap(c, mockGadgetSnapYaml, mockGadgetSnapContents, &snap.SideInfo{Revision: snap.R(42)}) err := ioutil.WriteFile(filepath.Join(info.MountDir(), "meta", "gadget.yaml"), mockGadgetYaml, 0644) c.Assert(err, IsNil) @@ -130,7 +132,7 @@ func (s *gadgetYamlTestSuite) TestReadGadgetYamlValid(c *C) { } func (s *gadgetYamlTestSuite) TestReadGadgetYamlEmptydBootloader(c *C) { - info := snaptest.MockSnap(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(42)}) + info := snaptest.MockSnap(c, mockGadgetSnapYaml, mockGadgetSnapContents, &snap.SideInfo{Revision: snap.R(42)}) mockGadgetYamlBroken := []byte(` volumes: name: @@ -145,7 +147,7 @@ volumes: } func (s *gadgetYamlTestSuite) TestReadGadgetYamlInvalidBootloader(c *C) { - info := snaptest.MockSnap(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(42)}) + info := snaptest.MockSnap(c, mockGadgetSnapYaml, mockGadgetSnapContents, &snap.SideInfo{Revision: snap.R(42)}) mockGadgetYamlBroken := []byte(` volumes: name: @@ -160,7 +162,7 @@ volumes: } func (s *gadgetYamlTestSuite) TestReadGadgetYamlMissingBootloader(c *C) { - info := snaptest.MockSnap(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(42)}) + info := snaptest.MockSnap(c, mockGadgetSnapYaml, mockGadgetSnapContents, &snap.SideInfo{Revision: snap.R(42)}) err := ioutil.WriteFile(filepath.Join(info.MountDir(), "meta", "gadget.yaml"), nil, 0644) c.Assert(err, IsNil) diff --git a/snap/implicit.go b/snap/implicit.go index c8025068ab0..6bb08b85649 100644 --- a/snap/implicit.go +++ b/snap/implicit.go @@ -28,13 +28,16 @@ import ( ) var implicitSlots = []string{ + "alsa", "bluetooth-control", + "camera", "dcdbas-control", "docker-support", "firewall-control", "fuse-support", - "home", "hardware-observe", + "home", + "kernel-module-control", "locale-control", "log-observe", "lxd-support", @@ -56,17 +59,18 @@ var implicitSlots = []string{ "timeserver-control", "timezone-control", "tpm", - "kernel-module-control", - "camera", + "usb-raw", } var implicitClassicSlots = []string{ + "avahi-observe", "browser-support", "cups-control", "gsettings", "libvirt", "modem-manager", "network-manager", + "ofono", "optical-drive", "pulseaudio", "screen-inhibit-control", diff --git a/snap/info.go b/snap/info.go index 27d10c1ab94..7b00a0f16ca 100644 --- a/snap/info.go +++ b/snap/info.go @@ -56,6 +56,9 @@ type PlaceInfo interface { // CommonDataHomeDir returns the per user data directory common across revisions of the snap. CommonDataHomeDir() string + + // XdgRuntimeDirs returns the XDG_RUNTIME_DIR directories for all users of the snap. + XdgRuntimeDirs() string } // MinimalPlaceInfo returns a PlaceInfo with just the location information for a snap of the given name and revision. @@ -230,6 +233,16 @@ func (s *Info) CommonDataHomeDir() string { return filepath.Join(dirs.SnapDataHomeGlob, s.Name(), "common") } +// UserXdgRuntimeDir returns the XDG_RUNTIME_DIR directory of the snap for a particular user. +func (s *Info) UserXdgRuntimeDir(euid int) string { + return filepath.Join("/run/user", fmt.Sprintf("%d/snap.%s", euid, s.Name())) +} + +// XdgRuntimeDirs returns the XDG_RUNTIME_DIR directories for all users of the snap. +func (s *Info) XdgRuntimeDirs() string { + return filepath.Join(dirs.XdgRuntimeDirGlob, fmt.Sprintf("snap.%s", s.Name())) +} + // NeedsDevMode retursn whether the snap needs devmode. func (s *Info) NeedsDevMode() bool { return s.Confinement == DevmodeConfinement @@ -465,6 +478,12 @@ func ReadInfo(name string, si *SideInfo) (*Info, error) { return nil, err } + st, err := os.Stat(MountFile(name, si.Revision)) + if err != nil { + return nil, err + } + info.Size = st.Size() + err = addImplicitHooks(info) if err != nil { return nil, err @@ -486,6 +505,11 @@ func ReadInfoFromSnapFile(snapf Container, si *SideInfo) (*Info, error) { return nil, err } + info.Size, err = snapf.Size() + if err != nil { + return nil, err + } + err = addImplicitHooksFromContainer(info, snapf) if err != nil { return nil, err diff --git a/snap/info_snap_yaml_test.go b/snap/info_snap_yaml_test.go index 24436cd6a67..df8997973cf 100644 --- a/snap/info_snap_yaml_test.go +++ b/snap/info_snap_yaml_test.go @@ -1388,3 +1388,14 @@ apps: "k2": "v2", }) } + +// classic confinement +func (s *YamlSuite) TestClassicConfinement(c *C) { + y := []byte(` +name: foo +confinement: classic +`) + info, err := snap.InfoFromSnapYaml(y) + c.Assert(err, IsNil) + c.Assert(info.Confinement, Equals, snap.ClassicConfinement) +} diff --git a/snap/info_test.go b/snap/info_test.go index 38e1f163abd..9817f827bfc 100644 --- a/snap/info_test.go +++ b/snap/info_test.go @@ -117,10 +117,12 @@ apps: command: foo ` +const sampleContents = "SNAP" + func (s *infoSuite) TestReadInfo(c *C) { si := &snap.SideInfo{Revision: snap.R(42), EditedSummary: "esummary"} - snapInfo1 := snaptest.MockSnap(c, sampleYaml, si) + snapInfo1 := snaptest.MockSnap(c, sampleYaml, sampleContents, si) snapInfo2, err := snap.ReadInfo("sample", si) c.Assert(err, IsNil) @@ -358,10 +360,10 @@ version: 1.0` c.Assert(err, ErrorMatches, ".*invalid hook name.*") } -func (s *infoSuite) checkInstalledSnapAndSnapFile(c *C, yaml string, hooks []string, checker func(c *C, info *snap.Info)) { +func (s *infoSuite) checkInstalledSnapAndSnapFile(c *C, yaml string, contents string, hooks []string, checker func(c *C, info *snap.Info)) { // First check installed snap sideInfo := &snap.SideInfo{Revision: snap.R(42)} - info0 := snaptest.MockSnap(c, yaml, sideInfo) + info0 := snaptest.MockSnap(c, yaml, contents, sideInfo) snaptest.PopulateDir(info0.MountDir(), emptyHooks(hooks...)) info, err := snap.ReadInfo(info0.Name(), sideInfo) c.Check(err, IsNil) @@ -379,7 +381,7 @@ func (s *infoSuite) checkInstalledSnapAndSnapFile(c *C, yaml string, hooks []str func (s *infoSuite) TestReadInfoNoHooks(c *C) { yaml := `name: foo version: 1.0` - s.checkInstalledSnapAndSnapFile(c, yaml, nil, func(c *C, info *snap.Info) { + s.checkInstalledSnapAndSnapFile(c, yaml, "SNAP", nil, func(c *C, info *snap.Info) { // Verify that no hooks were loaded for this snap c.Check(info.Hooks, HasLen, 0) }) @@ -388,7 +390,7 @@ version: 1.0` func (s *infoSuite) TestReadInfoSingleImplicitHook(c *C) { yaml := `name: foo version: 1.0` - s.checkInstalledSnapAndSnapFile(c, yaml, []string{"test-hook"}, func(c *C, info *snap.Info) { + s.checkInstalledSnapAndSnapFile(c, yaml, "SNAP", []string{"test-hook"}, func(c *C, info *snap.Info) { // Verify that the `test-hook` hook has now been loaded, and that it has // no associated plugs. c.Check(info.Hooks, HasLen, 1) @@ -399,7 +401,7 @@ version: 1.0` func (s *infoSuite) TestReadInfoMultipleImplicitHooks(c *C) { yaml := `name: foo version: 1.0` - s.checkInstalledSnapAndSnapFile(c, yaml, []string{"foo", "bar"}, func(c *C, info *snap.Info) { + s.checkInstalledSnapAndSnapFile(c, yaml, "SNAP", []string{"foo", "bar"}, func(c *C, info *snap.Info) { // Verify that both hooks have now been loaded, and that neither have any // associated plugs. c.Check(info.Hooks, HasLen, 2) @@ -414,7 +416,7 @@ func (s *infoSuite) TestReadInfoInvalidImplicitHook(c *C) { yaml := `name: foo version: 1.0` - s.checkInstalledSnapAndSnapFile(c, yaml, []string{"foo", "bar"}, func(c *C, info *snap.Info) { + s.checkInstalledSnapAndSnapFile(c, yaml, "SNAP", []string{"foo", "bar"}, func(c *C, info *snap.Info) { // Verify that only foo has been loaded, not bar c.Check(info.Hooks, HasLen, 1) verifyImplicitHook(c, info, "foo") @@ -427,7 +429,7 @@ version: 1.0 hooks: explicit: plugs: [test-plug]` - s.checkInstalledSnapAndSnapFile(c, yaml, []string{"explicit", "implicit"}, func(c *C, info *snap.Info) { + s.checkInstalledSnapAndSnapFile(c, yaml, "SNAP", []string{"explicit", "implicit"}, func(c *C, info *snap.Info) { // Verify that the `implicit` hook has now been loaded, and that it has // no associated plugs. Also verify that the `explicit` hook is still // valid. @@ -475,7 +477,9 @@ func (s *infoSuite) TestDirAndFileMethods(c *C) { c.Check(info.UserDataDir("/home/bob"), Equals, "/home/bob/snap/name/1") c.Check(info.UserCommonDataDir("/home/bob"), Equals, "/home/bob/snap/name/common") c.Check(info.CommonDataDir(), Equals, "/var/snap/name/common") + c.Check(info.UserXdgRuntimeDir(12345), Equals, "/run/user/12345/snap.name") // XXX: Those are actually a globs, not directories c.Check(info.DataHomeDir(), Equals, "/home/*/snap/name/1") c.Check(info.CommonDataHomeDir(), Equals, "/home/*/snap/name/common") + c.Check(info.XdgRuntimeDirs(), Equals, "/run/user/*/snap.name") } diff --git a/snap/snapdir/snapdir.go b/snap/snapdir/snapdir.go index a8edc12a103..866dd1102d7 100644 --- a/snap/snapdir/snapdir.go +++ b/snap/snapdir/snapdir.go @@ -41,6 +41,17 @@ func New(path string) *SnapDir { return &SnapDir{path: path} } +func (s *SnapDir) Size() (size int64, err error) { + totalSize := int64(0) + f := func(_ string, info os.FileInfo, err error) error { + totalSize += info.Size() + return err + } + filepath.Walk(s.path, f) + + return totalSize, nil +} + func (s *SnapDir) Install(targetPath, mountDir string) error { return os.Symlink(s.path, targetPath) } diff --git a/snap/snapenv/snapenv.go b/snap/snapenv/snapenv.go index 6a4e7d9f89f..dfa6af67bcb 100644 --- a/snap/snapenv/snapenv.go +++ b/snap/snapenv/snapenv.go @@ -91,6 +91,7 @@ func userEnv(info *snap.Info, home string) map[string]string { "HOME": info.UserDataDir(home), "SNAP_USER_COMMON": info.UserCommonDataDir(home), "SNAP_USER_DATA": info.UserDataDir(home), + "XDG_RUNTIME_DIR": info.UserXdgRuntimeDir(os.Geteuid()), } } diff --git a/snap/snapenv/snapenv_test.go b/snap/snapenv/snapenv_test.go index 05247717303..0928862cb6e 100644 --- a/snap/snapenv/snapenv_test.go +++ b/snap/snapenv/snapenv_test.go @@ -79,6 +79,7 @@ func (ts *HTestSuite) TestUser(c *C) { "HOME": "/root/snap/foo/17", "SNAP_USER_COMMON": "/root/snap/foo/common", "SNAP_USER_DATA": "/root/snap/foo/17", + "XDG_RUNTIME_DIR": fmt.Sprintf("/run/user/%d/snap.foo", os.Geteuid()), }) } @@ -112,6 +113,7 @@ func (s *HTestSuite) TestSnapRunSnapExecEnv(c *C) { "SNAP_USER_COMMON": fmt.Sprintf("%s/snap/snapname/common", usr.HomeDir), "SNAP_USER_DATA": fmt.Sprintf("%s/snap/snapname/42", usr.HomeDir), "SNAP_VERSION": "1.0", + "XDG_RUNTIME_DIR": fmt.Sprintf("/run/user/%d/snap.snapname", os.Geteuid()), }) } } diff --git a/snap/snaptest/build.go b/snap/snaptest/build.go index 05aafa19a56..6702c7e58cc 100644 --- a/snap/snaptest/build.go +++ b/snap/snaptest/build.go @@ -176,7 +176,7 @@ func copyToBuildDir(sourceDir, buildDir string) error { if err := os.Mkdir(dest, info.Mode()); err != nil { return err } - // ensure that premissions are preserved + // ensure that permissions are preserved uid := int(info.Sys().(*syscall.Stat_t).Uid) gid := int(info.Sys().(*syscall.Stat_t).Gid) return os.Chown(dest, uid, gid) diff --git a/snap/snaptest/snaptest.go b/snap/snaptest/snaptest.go index 7ebe9649991..1def2ddeedd 100644 --- a/snap/snaptest/snaptest.go +++ b/snap/snaptest/snaptest.go @@ -35,7 +35,7 @@ import ( // // The caller is responsible for mocking root directory with dirs.SetRootDir() // and for altering the overlord state if required. -func MockSnap(c *check.C, yamlText string, sideInfo *snap.SideInfo) *snap.Info { +func MockSnap(c *check.C, yamlText string, snapContents string, sideInfo *snap.SideInfo) *snap.Info { c.Assert(sideInfo, check.Not(check.IsNil)) // Parse the yaml (we need the Name). @@ -52,6 +52,13 @@ func MockSnap(c *check.C, yamlText string, sideInfo *snap.SideInfo) *snap.Info { err = ioutil.WriteFile(filepath.Join(metaDir, "snap.yaml"), []byte(yamlText), 0644) c.Assert(err, check.IsNil) + // Write the .snap to disk + err = os.MkdirAll(filepath.Dir(snapInfo.MountFile()), 0755) + c.Assert(err, check.IsNil) + err = ioutil.WriteFile(snapInfo.MountFile(), []byte(snapContents), 0644) + c.Assert(err, check.IsNil) + snapInfo.Size = int64(len(snapContents)) + return snapInfo } diff --git a/snap/snaptest/snaptest_test.go b/snap/snaptest/snaptest_test.go index 264a07b3066..3765d2b784b 100644 --- a/snap/snaptest/snaptest_test.go +++ b/snap/snaptest/snaptest_test.go @@ -44,6 +44,7 @@ plugs: network: interface: network ` +const sampleContents = "" type snapTestSuite struct{} @@ -58,7 +59,7 @@ func (s *snapTestSuite) TearDownTest(c *C) { } func (s *snapTestSuite) TestMockSnap(c *C) { - snapInfo := snaptest.MockSnap(c, sampleYaml, &snap.SideInfo{Revision: snap.R(42)}) + snapInfo := snaptest.MockSnap(c, sampleYaml, sampleContents, &snap.SideInfo{Revision: snap.R(42)}) // Data from YAML is used c.Check(snapInfo.Name(), Equals, "sample") // Data from SideInfo is used diff --git a/snap/squashfs/squashfs.go b/snap/squashfs/squashfs.go index d8ed2d5957e..8787e9760c9 100644 --- a/snap/squashfs/squashfs.go +++ b/snap/squashfs/squashfs.go @@ -107,6 +107,16 @@ func (s *Snap) Unpack(src, dstDir string) error { return runCommand("unsquashfs", "-f", "-i", "-d", dstDir, s.path, src) } +// Size returns the size of a squashfs snap. +func (s *Snap) Size() (size int64, err error) { + st, err := os.Stat(s.path) + if err != nil { + return 0, err + } + + return st.Size(), nil +} + // ReadFile returns the content of a single file inside a squashfs snap. func (s *Snap) ReadFile(filePath string) (content []byte, err error) { tmpdir, err := ioutil.TempDir("", "read-file") diff --git a/snap/types.go b/snap/types.go index 3f81cb5883b..b32fef8d7f8 100644 --- a/snap/types.go +++ b/snap/types.go @@ -81,6 +81,7 @@ type ConfinementType string // The various confinement types we support const ( DevmodeConfinement ConfinementType = "devmode" + ClassicConfinement ConfinementType = "classic" StrictConfinement ConfinementType = "strict" ) @@ -106,7 +107,7 @@ func (confinementType *ConfinementType) UnmarshalYAML(unmarshal func(interface{} func (confinementType *ConfinementType) fromString(str string) error { c := ConfinementType(str) - if c != DevmodeConfinement && c != StrictConfinement { + if c != DevmodeConfinement && c != ClassicConfinement && c != StrictConfinement { return fmt.Errorf("invalid confinement type: %q", str) } diff --git a/spread.yaml b/spread.yaml index 143eda9f911..429da3e2786 100644 --- a/spread.yaml +++ b/spread.yaml @@ -146,11 +146,6 @@ prepare: | # utilities apt-get install -y devscripts expect gdebi-core jq rng-tools software-properties-common git - # needed so that we have golang-gopkg-macaroon.v1 which is not (yet) - # in trusty - add-apt-repository --update ppa:snappy-dev/image - # this should not be needed but apparently it is :/ - apt-get update # in 16.04: apt build-dep -y ./ apt-get install -y $(gdebi --quiet --apt-line ./debian/control) @@ -158,13 +153,6 @@ prepare: | go get -u github.com/kardianos/govendor govendor sync - # FIXME: this can be removed once snap-confine 1.0.38-0ubuntu0.16.04.8 - # hits xenial-updates - apt-get install -y snap-confine - - # and remove the image PPA again - add-apt-repository --remove ppa:snappy-dev/image - # increment version so upgrade can work dch -i "testing build" diff --git a/store/logger.go b/store/logger.go index f5b45a5c055..7a55b409888 100644 --- a/store/logger.go +++ b/store/logger.go @@ -20,6 +20,7 @@ package store import ( + "errors" "net/http" "net/http/httputil" "os" @@ -55,6 +56,7 @@ func (f debugflag) debugBody() bool { type LoggedTransport struct { Transport http.RoundTripper Key string + body bool } // RoundTrip is from the http.RoundTripper interface. @@ -62,14 +64,14 @@ func (tr *LoggedTransport) RoundTrip(req *http.Request) (*http.Response, error) flags := tr.getFlags() if flags.debugRequest() { - buf, _ := httputil.DumpRequestOut(req, flags.debugBody()) + buf, _ := httputil.DumpRequestOut(req, tr.body && flags.debugBody()) logger.Debugf("> %q", buf) } rsp, err := tr.Transport.RoundTrip(req) if err == nil && flags.debugResponse() { - buf, _ := httputil.DumpResponse(rsp, flags.debugBody()) + buf, _ := httputil.DumpResponse(rsp, tr.body && flags.debugBody()) logger.Debugf("< %q", buf) } @@ -85,13 +87,34 @@ func (tr *LoggedTransport) getFlags() debugflag { return debugflag(flags) } -// returns a new http.Client with a LoggedTransport and a Timeout -func newHTTPClient() *http.Client { +type httpClientOpts struct { + Timeout time.Duration + MayLogBody bool +} + +// returns a new http.Client with a LoggedTransport, a Timeout and preservation +// of range requests across redirects +func newHTTPClient(opts *httpClientOpts) *http.Client { + if opts == nil { + opts = &httpClientOpts{} + } + return &http.Client{ Transport: &LoggedTransport{ Transport: http.DefaultTransport, Key: "SNAPD_DEBUG_HTTP", + body: opts.MayLogBody, + }, + Timeout: opts.Timeout, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + if len(via) > 10 { + return errors.New("stopped after 10 redirects") + } + // preserve the range header across redirects + // to the CDN + v := via[0].Header.Get("Range") + req.Header.Set("Range", v) + return nil }, - Timeout: 10 * time.Second, } } diff --git a/store/logger_test.go b/store/logger_test.go index 2806f4ef03f..6ef2708f00c 100644 --- a/store/logger_test.go +++ b/store/logger_test.go @@ -22,13 +22,16 @@ package store_test import ( "bytes" "fmt" + "io/ioutil" "net/http" "os" + "strings" "gopkg.in/check.v1" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/testutil" ) type loggerSuite struct { @@ -97,3 +100,31 @@ func (s loggerSuite) TestLogging(c *check.C) { c.Check(s.logbuf.String(), check.Matches, `(?ms).*> "WAT / HTTP/\S+.*`) c.Check(s.logbuf.String(), check.Matches, `(?ms).*< "HTTP/\S+ 999 WAT.*`) } + +func (s loggerSuite) TestNotLoggingOctetStream(c *check.C) { + req, err := http.NewRequest("GET", "http://example.com/data", nil) + c.Assert(err, check.IsNil) + needle := "lots of binary data" + rsp := &http.Response{ + Status: "200 OK", + StatusCode: 200, + Header: http.Header{ + "Content-Type": []string{"application/octet-stream"}, + }, + Body: ioutil.NopCloser(strings.NewReader(needle)), + } + tr := &store.LoggedTransport{ + Transport: &fakeTransport{ + rsp: rsp, + }, + Key: "TEST_FOO", + } + + os.Setenv("TEST_FOO", "7") + + aRsp, err := tr.RoundTrip(req) + c.Assert(err, check.IsNil) + c.Check(aRsp, check.Equals, rsp) + c.Check(s.logbuf.String(), check.Matches, `(?ms).*> "GET /data HTTP/\S+.*`) + c.Check(s.logbuf.String(), check.Not(testutil.Contains), needle) +} diff --git a/store/store.go b/store/store.go index c6668ec14ef..2b7635e9851 100644 --- a/store/store.go +++ b/store/store.go @@ -27,6 +27,7 @@ import ( "errors" "fmt" "io" + "net" "net/http" "net/url" "os" @@ -42,12 +43,15 @@ import ( "github.com/snapcore/snapd/arch" "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" + + "gopkg.in/retry.v1" ) // TODO: better/shorter names are probably in order once fewer legacy places are using this @@ -174,6 +178,27 @@ type Store struct { suggestedCurrency string } +func shouldRetryHttpResponse(attempt *retry.Attempt, resp *http.Response) bool { + return (resp.StatusCode == 500 || resp.StatusCode == 503) && attempt.More() +} + +func shouldRetryError(attempt *retry.Attempt, err error) bool { + if !attempt.More() { + return false + } + if netErr, ok := err.(net.Error); ok { + return netErr.Timeout() + } + return err == io.ErrUnexpectedEOF +} + +var defaultRetryStrategy = retry.LimitCount(6, retry.LimitTime(10*time.Second, + retry.Exponential{ + Initial: 10 * time.Millisecond, + Factor: 1.67, + }, +)) + func respToError(resp *http.Response, msg string) error { tpl := "cannot %s: got unexpected HTTP status code %d via %s to %q" if oops := resp.Header.Get("X-Oops-Id"); oops != "" { @@ -377,9 +402,13 @@ func New(cfg *Config, authContext auth.AuthContext) *Store { architecture: architecture, fallbackStoreID: cfg.StoreID, detailFields: fields, - client: newHTTPClient(), authContext: authContext, deltaFormat: deltaFormat, + + client: newHTTPClient(&httpClientOpts{ + Timeout: 10 * time.Second, + MayLogBody: true, + }), } } @@ -1068,11 +1097,26 @@ func findRev(needle snap.Revision, haystack []snap.Revision) bool { // filename. // The file is saved in temporary storage, and should be removed // after use to prevent the disk from running out of space. -func (s *Store) Download(name string, targetFn string, downloadInfo *snap.DownloadInfo, pbar progress.Meter, user *auth.UserState) error { - if err := os.MkdirAll(filepath.Dir(targetFn), 0755); err != nil { +func (s *Store) Download(name string, targetPath string, downloadInfo *snap.DownloadInfo, pbar progress.Meter, user *auth.UserState) error { + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + return err + } + if useDeltas() { + logger.Debugf("Available deltas returned by store: %v", downloadInfo.Deltas) + } + if useDeltas() && len(downloadInfo.Deltas) == 1 { + err := s.downloadAndApplyDelta(name, targetPath, downloadInfo, pbar, user) + if err == nil { + return nil + } + // We revert to normal downloads if there is any error. + logger.Noticef("Cannot download or apply deltas for %s: %v", name, err) + } + w, err := os.OpenFile(targetPath+".partial", os.O_RDWR|os.O_CREATE, 0644) + if err != nil { return err } - w, err := os.Create(targetFn + ".partial") + resume, err := w.Seek(0, os.SEEK_END) if err != nil { return err } @@ -1090,12 +1134,12 @@ func (s *Store) Download(name string, targetFn string, downloadInfo *snap.Downlo url = downloadInfo.DownloadURL } - err = download(name, downloadInfo.Sha3_384, url, user, s, w, pbar) + err = download(name, downloadInfo.Sha3_384, url, user, s, w, resume, pbar) if err != nil { return err } - if err := os.Rename(w.Name(), targetFn); err != nil { + if err := os.Rename(w.Name(), targetPath); err != nil { return err } @@ -1106,7 +1150,7 @@ func (s *Store) Download(name string, targetFn string, downloadInfo *snap.Downlo var downloadBackoffs = []int{113, 191, 331, 557, 929, 0} // download writes an http.Request showing a progress.Meter -var download = func(name, sha3_384, downloadURL string, user *auth.UserState, s *Store, w io.Writer, pbar progress.Meter) error { +var download = func(name, sha3_384, downloadURL string, user *auth.UserState, s *Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter) error { storeURL, err := url.Parse(downloadURL) if err != nil { return err @@ -1116,15 +1160,29 @@ var download = func(name, sha3_384, downloadURL string, user *auth.UserState, s Method: "GET", URL: storeURL, } + h := crypto.SHA3_384.New() + + if resume > 0 { + reqOptions.ExtraHeaders = map[string]string{ + "Range": fmt.Sprintf("bytes=%d-", resume), + } + // seed the sha3 with the already local file + seekStart := 0 + if _, err := w.Seek(0, seekStart); err != nil { + return err + } + n, err := io.Copy(h, w) + if err != nil { + return err + } + if n != resume { + return fmt.Errorf("resume offset wrong: %d != %d", resume, n) + } + } var resp *http.Response for _, n := range downloadBackoffs { - // we do *not* want to reuse the client between iterations in - // this case as it will have internal state (e.g. cached - // connections) that led us to an error (the default client is - // documented as not reusing the transport unless the body is - // read to EOF and closed, so this is a belt-and-braces thing). - r, err := s.doRequest(&http.Client{}, reqOptions, user) + r, err := s.doRequest(newHTTPClient(nil), reqOptions, user) if err != nil { return err } @@ -1137,7 +1195,12 @@ var download = func(name, sha3_384, downloadURL string, user *auth.UserState, s } time.Sleep(time.Duration(n) * time.Millisecond) } - if resp.StatusCode != 200 { + switch resp.StatusCode { + case http.StatusOK, http.StatusPartialContent: + break + case http.StatusUnauthorized: + return fmt.Errorf(i18n.G("cannot download non-free snap without purchase")) + default: return &ErrDownload{Code: resp.StatusCode, URL: resp.Request.URL} } @@ -1145,87 +1208,113 @@ var download = func(name, sha3_384, downloadURL string, user *auth.UserState, s pbar = &progress.NullProgress{} } pbar.Start(name, float64(resp.ContentLength)) - h := crypto.SHA3_384.New() mw := io.MultiWriter(w, h, pbar) _, err = io.Copy(mw, resp.Body) pbar.Finished() actualSha3 := fmt.Sprintf("%x", h.Sum(nil)) if sha3_384 != "" && sha3_384 != actualSha3 { - return fmt.Errorf("sha3-384 mismatch downloading %s: got %s but expected %s", name, sha3_384, actualSha3) + return fmt.Errorf("sha3-384 mismatch downloading %s: got %s but expected %s", name, actualSha3, sha3_384) } return err } // downloadDelta downloads the delta for the preferred format, returning the path. -func (s *Store) downloadDelta(name string, downloadDir string, downloadInfo *snap.DownloadInfo, pbar progress.Meter, user *auth.UserState) (string, error) { +func (s *Store) downloadDelta(deltaName string, downloadInfo *snap.DownloadInfo, w io.ReadWriteSeeker, pbar progress.Meter, user *auth.UserState) error { if len(downloadInfo.Deltas) != 1 { - return "", errors.New("store returned more than one download delta") + return errors.New("store returned more than one download delta") } deltaInfo := downloadInfo.Deltas[0] if deltaInfo.Format != s.deltaFormat { - return "", fmt.Errorf("store returned a download delta with the wrong format (%q instead of the configured %s format)", deltaInfo.Format, s.deltaFormat) - } - - deltaName := fmt.Sprintf("%s_%d_%d_delta.%s", name, deltaInfo.FromRevision, deltaInfo.ToRevision, deltaInfo.Format) - - w, err := os.Create(path.Join(downloadDir, deltaName)) - if err != nil { - return "", err + return fmt.Errorf("store returned unsupported delta format %q (only xdelta currently)", deltaInfo.Format) } - deltaPath := w.Name() - defer func() { - if cerr := w.Close(); cerr != nil && err == nil { - err = cerr - } - if err != nil { - os.Remove(w.Name()) - deltaPath = "" - } - }() url := deltaInfo.AnonDownloadURL if url == "" || hasStoreAuth(user) { url = deltaInfo.DownloadURL } - err = download(deltaName, deltaInfo.Sha3_384, url, user, s, w, pbar) - if err != nil { - return "", err - } - - return deltaPath, nil + return download(deltaName, deltaInfo.Sha3_384, url, user, s, w, 0, pbar) } // applyDelta generates a target snap from a previously downloaded snap and a downloaded delta. -var applyDelta = func(name string, deltaPath string, deltaInfo *snap.DeltaInfo) (string, error) { +var applyDelta = func(name string, deltaPath string, deltaInfo *snap.DeltaInfo, targetPath string, targetSha3_384 string) error { snapBase := fmt.Sprintf("%s_%d.snap", name, deltaInfo.FromRevision) snapPath := filepath.Join(dirs.SnapBlobDir, snapBase) if !osutil.FileExists(snapPath) { - return "", fmt.Errorf("snap %q revision %d not found at %s", name, deltaInfo.FromRevision, snapPath) + return fmt.Errorf("snap %q revision %d not found at %s", name, deltaInfo.FromRevision, snapPath) } if deltaInfo.Format != "xdelta" { - return "", fmt.Errorf("unsupported delta format %q. Currently only \"xdelta\" format is supported", deltaInfo.Format) + return fmt.Errorf("cannot apply unsupported delta format %q (only xdelta currently)", deltaInfo.Format) } - targetSnapName := fmt.Sprintf("%s_%d_patched_from_%d.snap", name, deltaInfo.ToRevision, deltaInfo.FromRevision) - targetSnapPath := targetSnapName + ".partial" + partialTargetPath := targetPath + ".partial" - xdeltaArgs := []string{"patch", deltaPath, snapPath, targetSnapPath} + xdeltaArgs := []string{"patch", deltaPath, snapPath, partialTargetPath} cmd := exec.Command("xdelta", xdeltaArgs...) if err := cmd.Run(); err != nil { - os.Remove(targetSnapPath) - return "", err + if err := os.Remove(partialTargetPath); err != nil { + logger.Noticef("failed to remove partial delta target %q: %s", partialTargetPath, err) + } + return err + } + + bsha3_384, _, err := osutil.FileDigest(partialTargetPath, crypto.SHA3_384) + if err != nil { + return err + } + sha3_384 := fmt.Sprintf("%x", bsha3_384) + if targetSha3_384 != "" && sha3_384 != targetSha3_384 { + if err := os.Remove(partialTargetPath); err != nil { + logger.Noticef("failed to remove partial delta target %q: %s", partialTargetPath, err) + } + return fmt.Errorf("sha3-384 mismatch after patching %q: got %s but expected %s", name, sha3_384, targetSha3_384) } - return targetSnapPath, nil + if err := os.Rename(partialTargetPath, targetPath); err != nil { + return osutil.CopyFile(partialTargetPath, targetPath, 0) + } + + return nil +} + +// downloadAndApplyDelta downloads and then applies the delta to the current snap. +func (s *Store) downloadAndApplyDelta(name, targetPath string, downloadInfo *snap.DownloadInfo, pbar progress.Meter, user *auth.UserState) error { + deltaInfo := &downloadInfo.Deltas[0] + + deltaPath := fmt.Sprintf("%s.%s-%d-to-%d.partial", targetPath, deltaInfo.Format, deltaInfo.FromRevision, deltaInfo.ToRevision) + deltaName := filepath.Base(deltaPath) + + w, err := os.Create(deltaPath) + if err != nil { + return err + } + defer func() { + if cerr := w.Close(); cerr != nil && err == nil { + err = cerr + } + os.Remove(deltaPath) + }() + + err = s.downloadDelta(deltaName, downloadInfo, w, pbar, user) + if err != nil { + return err + } + + logger.Debugf("Successfully downloaded delta for %q at %s", name, deltaPath) + if err := applyDelta(name, deltaPath, deltaInfo, targetPath, downloadInfo.Sha3_384); err != nil { + return err + } + + logger.Debugf("Successfully applied delta for %q at %s, saving %d bytes.", name, deltaPath, downloadInfo.Size-deltaInfo.Size) + return nil } type assertionSvcError struct { @@ -1438,40 +1527,52 @@ func (s *Store) ReadyToBuy(user *auth.UserState) error { URL: s.customersMeURI, Accept: jsonContentType, } - resp, err := s.doRequest(s.client, reqOptions, user) - if err != nil { - return err - } - defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK: - var customer storeCustomer - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&customer); err != nil { + for attempt := retry.Start(defaultRetryStrategy, nil); attempt.Next(); { + resp, err := s.doRequest(s.client, reqOptions, user) + if err != nil { + if shouldRetryError(attempt, err) { + continue + } return err } - if !customer.LatestTOSAccepted { - return ErrTOSNotAccepted - } - if !customer.HasPaymentMethod { - return ErrNoPaymentMethods - } - return nil - case http.StatusNotFound: - // Likely because user has no account registered on the pay server - return fmt.Errorf("cannot get customer details: server says no account exists") - case http.StatusUnauthorized: - return ErrInvalidCredentials - default: - var errors storeErrors - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&errors); err != nil { - return err + + if shouldRetryHttpResponse(attempt, resp) { + resp.Body.Close() + continue } - if len(errors.Errors) == 0 { - return fmt.Errorf("cannot get customer details: unexpected HTTP code %d", resp.StatusCode) + + switch resp.StatusCode { + case http.StatusOK: + var customer storeCustomer + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&customer); err != nil { + return err + } + if !customer.LatestTOSAccepted { + return ErrTOSNotAccepted + } + if !customer.HasPaymentMethod { + return ErrNoPaymentMethods + } + return nil + case http.StatusNotFound: + // Likely because user has no account registered on the pay server + return fmt.Errorf("cannot get customer details: server says no account exists") + case http.StatusUnauthorized: + return ErrInvalidCredentials + default: + var errors storeErrors + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&errors); err != nil { + return err + } + if len(errors.Errors) == 0 { + return fmt.Errorf("cannot get customer details: unexpected HTTP code %d", resp.StatusCode) + } + return &errors } - return &errors } + + return nil } diff --git a/store/store_test.go b/store/store_test.go index 3157cb9d0c3..d2577b070be 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -21,7 +21,9 @@ package store import ( "bytes" + "crypto" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -55,7 +57,7 @@ type remoteRepoTestSuite struct { localUser *auth.UserState device *auth.DeviceState - origDownloadFunc func(string, string, string, *auth.UserState, *Store, io.Writer, progress.Meter) error + origDownloadFunc func(string, string, string, *auth.UserState, *Store, io.ReadWriteSeeker, int64, progress.Meter) error origBackoffs []int mockXDelta *testutil.MockCmd } @@ -264,7 +266,7 @@ func (t *remoteRepoTestSuite) expectedAuthorization(c *C, user *auth.UserState) func (t *remoteRepoTestSuite) TestDownloadOK(c *C) { - download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.Writer, pbar progress.Meter) error { + download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter) error { c.Check(url, Equals, "anon-url") w.Write([]byte("I was downloaded")) return nil @@ -285,8 +287,36 @@ func (t *remoteRepoTestSuite) TestDownloadOK(c *C) { c.Assert(string(content), Equals, "I was downloaded") } +func (t *remoteRepoTestSuite) TestDownloadRangeRequest(c *C) { + partialContentStr := "partial content " + + download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter) error { + c.Check(resume, Equals, int64(len(partialContentStr))) + c.Check(url, Equals, "anon-url") + w.Write([]byte("was downloaded")) + return nil + } + + snap := &snap.Info{} + snap.RealName = "foo" + snap.AnonDownloadURL = "anon-url" + snap.DownloadURL = "AUTH-URL" + snap.Sha3_384 = "abcdabcd" + + targetFn := filepath.Join(c.MkDir(), "foo_1.0_all.snap") + err := ioutil.WriteFile(targetFn+".partial", []byte(partialContentStr), 0644) + c.Assert(err, IsNil) + + err = t.store.Download("foo", targetFn, &snap.DownloadInfo, nil, nil) + c.Assert(err, IsNil) + + content, err := ioutil.ReadFile(targetFn) + c.Assert(err, IsNil) + c.Assert(string(content), Equals, partialContentStr+"was downloaded") +} + func (t *remoteRepoTestSuite) TestAuthenticatedDownloadDoesNotUseAnonURL(c *C) { - download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.Writer, pbar progress.Meter) error { + download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter) error { // check user is pass and auth url is used c.Check(user, Equals, t.user) c.Check(url, Equals, "AUTH-URL") @@ -311,7 +341,7 @@ func (t *remoteRepoTestSuite) TestAuthenticatedDownloadDoesNotUseAnonURL(c *C) { } func (t *remoteRepoTestSuite) TestLocalUserDownloadUsesAnonURL(c *C) { - download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.Writer, pbar progress.Meter) error { + download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter) error { c.Check(url, Equals, "anon-url") w.Write([]byte("I was downloaded")) @@ -335,7 +365,7 @@ func (t *remoteRepoTestSuite) TestLocalUserDownloadUsesAnonURL(c *C) { func (t *remoteRepoTestSuite) TestDownloadFails(c *C) { var tmpfile *os.File - download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.Writer, pbar progress.Meter) error { + download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter) error { tmpfile = w.(*os.File) return fmt.Errorf("uh, it failed") } @@ -354,7 +384,7 @@ func (t *remoteRepoTestSuite) TestDownloadFails(c *C) { func (t *remoteRepoTestSuite) TestDownloadSyncFails(c *C) { var tmpfile *os.File - download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.Writer, pbar progress.Meter) error { + download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter) error { tmpfile = w.(*os.File) w.Write([]byte("sync will fail")) err := tmpfile.Close() @@ -385,15 +415,38 @@ func (t *remoteRepoTestSuite) TestActualDownload(c *C) { defer mockServer.Close() theStore := New(&Config{}, nil) - var buf bytes.Buffer + var buf SillyBuffer // keep tests happy sha3 := "" - err := download("foo", sha3, mockServer.URL, nil, theStore, &buf, nil) + err := download("foo", sha3, mockServer.URL, nil, theStore, &buf, 0, nil) c.Assert(err, IsNil) c.Check(buf.String(), Equals, "response-data") c.Check(n, Equals, 1) } +type nopeSeeker struct{ io.ReadWriter } + +func (nopeSeeker) Seek(int64, int) (int64, error) { + return -1, errors.New("what is this, quidditch?") +} + +func (t *remoteRepoTestSuite) TestActualDownloadNonPurchased401(c *C) { + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + w.WriteHeader(http.StatusUnauthorized) + })) + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + theStore := New(&Config{}, nil) + var buf bytes.Buffer + err := download("foo", "sha3", mockServer.URL, nil, theStore, nopeSeeker{&buf}, -1, nil) + c.Assert(err, NotNil) + c.Check(err.Error(), Equals, "cannot download non-free snap without purchase") + c.Check(n, Equals, 1) +} + func (t *remoteRepoTestSuite) TestActualDownload404(c *C) { n := 0 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -404,8 +457,8 @@ func (t *remoteRepoTestSuite) TestActualDownload404(c *C) { defer mockServer.Close() theStore := New(&Config{}, nil) - var buf bytes.Buffer - err := download("foo", "sha3", mockServer.URL, nil, theStore, &buf, nil) + var buf SillyBuffer + err := download("foo", "sha3", mockServer.URL, nil, theStore, &buf, 0, nil) c.Assert(err, NotNil) c.Assert(err, FitsTypeOf, &ErrDownload{}) c.Check(err.(*ErrDownload).Code, Equals, http.StatusNotFound) @@ -422,19 +475,351 @@ func (t *remoteRepoTestSuite) TestActualDownload500(c *C) { defer mockServer.Close() theStore := New(&Config{}, nil) - var buf bytes.Buffer - err := download("foo", "sha3", mockServer.URL, nil, theStore, &buf, nil) + var buf SillyBuffer + err := download("foo", "sha3", mockServer.URL, nil, theStore, &buf, 0, nil) c.Assert(err, NotNil) c.Assert(err, FitsTypeOf, &ErrDownload{}) c.Check(err.(*ErrDownload).Code, Equals, http.StatusInternalServerError) c.Check(n, Equals, len(downloadBackoffs)) // woo!! } +// SillyBuffer is a ReadWriteSeeker buffer with a limited size for the tests +// (bytes does not implement an ReadWriteSeeker) +type SillyBuffer struct { + buf [1024]byte + pos int64 + end int64 +} + +func NewSillyBufferString(s string) *SillyBuffer { + sb := &SillyBuffer{ + pos: int64(len(s)), + end: int64(len(s)), + } + copy(sb.buf[0:], []byte(s)) + return sb +} +func (sb *SillyBuffer) Read(b []byte) (n int, err error) { + if sb.pos >= int64(sb.end) { + return 0, io.EOF + } + n = copy(b, sb.buf[sb.pos:sb.end]) + sb.pos += int64(n) + return n, nil +} +func (sb *SillyBuffer) Seek(offset int64, whence int) (int64, error) { + if whence != 0 { + panic("only io.SeekStart implemented in SillyBuffer") + } + if offset < 0 || offset > int64(sb.end) { + return 0, fmt.Errorf("seek out of bounds: %d", offset) + } + sb.pos = offset + return sb.pos, nil +} +func (sb *SillyBuffer) Write(p []byte) (n int, err error) { + n = copy(sb.buf[sb.pos:], p) + sb.pos += int64(n) + if sb.pos > sb.end { + sb.end = sb.pos + } + return n, nil +} +func (sb *SillyBuffer) String() string { + return string(sb.buf[0:sb.pos]) +} + +func (t *remoteRepoTestSuite) TestActualDownloadResume(c *C) { + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + io.WriteString(w, "data") + })) + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + theStore := New(&Config{}, nil) + buf := NewSillyBufferString("some ") + // calc the expected hash + h := crypto.SHA3_384.New() + h.Write([]byte("some data")) + sha3 := fmt.Sprintf("%x", h.Sum(nil)) + err := download("foo", sha3, mockServer.URL, nil, theStore, buf, int64(len("some ")), nil) + c.Check(err, IsNil) + c.Check(buf.String(), Equals, "some data") + c.Check(n, Equals, 1) +} + type downloadBehaviour []struct { url string error bool } +var deltaTests = []struct { + downloads downloadBehaviour + info snap.DownloadInfo + expectedContent string +}{{ + // The full snap is not downloaded, but rather the delta + // is downloaded and applied. + downloads: downloadBehaviour{ + {url: "delta-url"}, + }, + info: snap.DownloadInfo{ + AnonDownloadURL: "full-snap-url", + Deltas: []snap.DeltaInfo{ + {AnonDownloadURL: "delta-url", Format: "xdelta"}, + }, + }, + expectedContent: "snap-content-via-delta", +}, { + // If there is an error during the delta download, the + // full snap is downloaded as per normal. + downloads: downloadBehaviour{ + {error: true}, + {url: "full-snap-url"}, + }, + info: snap.DownloadInfo{ + AnonDownloadURL: "full-snap-url", + Deltas: []snap.DeltaInfo{ + {AnonDownloadURL: "delta-url", Format: "xdelta"}, + }, + }, + expectedContent: "full-snap-url-content", +}, { + // If more than one matching delta is returned by the store + // we ignore deltas and do the full download. + downloads: downloadBehaviour{ + {url: "full-snap-url"}, + }, + info: snap.DownloadInfo{ + AnonDownloadURL: "full-snap-url", + Deltas: []snap.DeltaInfo{ + {AnonDownloadURL: "delta-url", Format: "xdelta"}, + {AnonDownloadURL: "delta-url-2", Format: "xdelta"}, + }, + }, + expectedContent: "full-snap-url-content", +}} + +func (t *remoteRepoTestSuite) TestDownloadWithDelta(c *C) { + origUseDeltas := os.Getenv("SNAPD_USE_DELTAS_EXPERIMENTAL") + defer os.Setenv("SNAPD_USE_DELTAS_EXPERIMENTAL", origUseDeltas) + c.Assert(os.Setenv("SNAPD_USE_DELTAS_EXPERIMENTAL", "1"), IsNil) + + for _, testCase := range deltaTests { + downloadIndex := 0 + download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter) error { + if testCase.downloads[downloadIndex].error { + downloadIndex++ + return errors.New("Bang") + } + c.Check(url, Equals, testCase.downloads[downloadIndex].url) + w.Write([]byte(testCase.downloads[downloadIndex].url + "-content")) + downloadIndex++ + return nil + } + applyDelta = func(name string, deltaPath string, deltaInfo *snap.DeltaInfo, targetPath string, targetSha3_384 string) error { + c.Check(deltaInfo, Equals, &testCase.info.Deltas[0]) + err := ioutil.WriteFile(targetPath, []byte("snap-content-via-delta"), 0644) + c.Assert(err, IsNil) + return nil + } + + path := filepath.Join(c.MkDir(), "subdir", "downloaded-file") + err := t.store.Download("foo", path, &testCase.info, nil, nil) + + c.Assert(err, IsNil) + defer os.Remove(path) + content, err := ioutil.ReadFile(path) + c.Assert(err, IsNil) + c.Assert(string(content), Equals, testCase.expectedContent) + } +} + +var downloadDeltaTests = []struct { + info snap.DownloadInfo + authenticated bool + useLocalUser bool + format string + expectedURL string + expectError bool +}{{ + // An unauthenticated request downloads the anonymous delta url. + info: snap.DownloadInfo{ + Sha3_384: "sha3", + Deltas: []snap.DeltaInfo{ + {AnonDownloadURL: "anon-delta-url", Format: "xdelta", FromRevision: 24, ToRevision: 26}, + }, + }, + authenticated: false, + format: "xdelta", + expectedURL: "anon-delta-url", + expectError: false, +}, { + // An authenticated request downloads the authenticated delta url. + info: snap.DownloadInfo{ + Sha3_384: "sha3", + Deltas: []snap.DeltaInfo{ + {DownloadURL: "auth-delta-url", Format: "xdelta", FromRevision: 24, ToRevision: 26}, + }, + }, + authenticated: true, + useLocalUser: false, + format: "xdelta", + expectedURL: "auth-delta-url", + expectError: false, +}, { + // A local authenticated request downloads the anonymous delta url. + info: snap.DownloadInfo{ + Sha3_384: "sha3", + Deltas: []snap.DeltaInfo{ + {AnonDownloadURL: "anon-delta-url", Format: "xdelta", FromRevision: 24, ToRevision: 26}, + }, + }, + authenticated: true, + useLocalUser: true, + format: "xdelta", + expectedURL: "anon-delta-url", + expectError: false, +}, { + // An error is returned if more than one matching delta is returned by the store, + // though this may be handled in the future. + info: snap.DownloadInfo{ + Sha3_384: "sha3", + Deltas: []snap.DeltaInfo{ + {DownloadURL: "xdelta-delta-url", Format: "xdelta", FromRevision: 24, ToRevision: 25}, + {DownloadURL: "bsdiff-delta-url", Format: "xdelta", FromRevision: 25, ToRevision: 26}, + }, + }, + authenticated: false, + format: "xdelta", + expectedURL: "", + expectError: true, +}, { + // If the supported format isn't available, an error is returned. + info: snap.DownloadInfo{ + Sha3_384: "sha3", + Deltas: []snap.DeltaInfo{ + {DownloadURL: "xdelta-delta-url", Format: "xdelta", FromRevision: 24, ToRevision: 26}, + {DownloadURL: "ydelta-delta-url", Format: "ydelta", FromRevision: 24, ToRevision: 26}, + }, + }, + authenticated: false, + format: "bsdiff", + expectedURL: "", + expectError: true, +}} + +func (t *remoteRepoTestSuite) TestDownloadDelta(c *C) { + origUseDeltas := os.Getenv("SNAPD_USE_DELTAS_EXPERIMENTAL") + defer os.Setenv("SNAPD_USE_DELTAS_EXPERIMENTAL", origUseDeltas) + c.Assert(os.Setenv("SNAPD_USE_DELTAS_EXPERIMENTAL", "1"), IsNil) + + for _, testCase := range downloadDeltaTests { + t.store.deltaFormat = testCase.format + download = func(name, sha3, url string, user *auth.UserState, s *Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter) error { + expectedUser := t.user + if testCase.useLocalUser { + expectedUser = t.localUser + } + if !testCase.authenticated { + expectedUser = nil + } + c.Check(user, Equals, expectedUser) + c.Check(url, Equals, testCase.expectedURL) + w.Write([]byte("I was downloaded")) + return nil + } + + w, err := ioutil.TempFile("", "") + c.Assert(err, IsNil) + defer os.Remove(w.Name()) + + authedUser := t.user + if testCase.useLocalUser { + authedUser = t.localUser + } + if !testCase.authenticated { + authedUser = nil + } + + err = t.store.downloadDelta("snapname", &testCase.info, w, nil, authedUser) + + if testCase.expectError { + c.Assert(err, NotNil) + } else { + c.Assert(err, IsNil) + content, err := ioutil.ReadFile(w.Name()) + c.Assert(err, IsNil) + c.Assert(string(content), Equals, "I was downloaded") + } + } +} + +var applyDeltaTests = []struct { + deltaInfo snap.DeltaInfo + currentRevision uint + error string +}{{ + // A supported delta format can be applied. + deltaInfo: snap.DeltaInfo{Format: "xdelta", FromRevision: 24, ToRevision: 26}, + currentRevision: 24, + error: "", +}, { + // An error is returned if the expected current snap does not exist on disk. + deltaInfo: snap.DeltaInfo{Format: "xdelta", FromRevision: 24, ToRevision: 26}, + currentRevision: 23, + error: "snap \"foo\" revision 24 not found", +}, { + // An error is returned if the format is not supported. + deltaInfo: snap.DeltaInfo{Format: "nodelta", FromRevision: 24, ToRevision: 26}, + currentRevision: 24, + error: "cannot apply unsupported delta format \"nodelta\" (only xdelta currently)", +}} + +func (t *remoteRepoTestSuite) TestApplyDelta(c *C) { + for _, testCase := range applyDeltaTests { + name := "foo" + currentSnapName := fmt.Sprintf("%s_%d.snap", name, testCase.currentRevision) + currentSnapPath := filepath.Join(dirs.SnapBlobDir, currentSnapName) + targetSnapName := fmt.Sprintf("%s_%d.snap", name, testCase.deltaInfo.ToRevision) + targetSnapPath := filepath.Join(dirs.SnapBlobDir, targetSnapName) + err := os.MkdirAll(filepath.Dir(currentSnapPath), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(currentSnapPath, nil, 0644) + c.Assert(err, IsNil) + deltaPath := filepath.Join(dirs.SnapBlobDir, "the.delta") + err = ioutil.WriteFile(deltaPath, nil, 0644) + c.Assert(err, IsNil) + // When testing a case where the call to the external xdelta is successful, + // simulate the resulting .partial. + if testCase.error == "" { + err = ioutil.WriteFile(targetSnapPath+".partial", nil, 0644) + c.Assert(err, IsNil) + } + + err = applyDelta(name, deltaPath, &testCase.deltaInfo, targetSnapPath, "") + + if testCase.error == "" { + c.Assert(err, IsNil) + c.Assert(t.mockXDelta.Calls(), DeepEquals, [][]string{ + {"xdelta", "patch", deltaPath, currentSnapPath, targetSnapPath + ".partial"}, + }) + c.Assert(osutil.FileExists(targetSnapPath+".partial"), Equals, false) + c.Assert(osutil.FileExists(targetSnapPath), Equals, true) + c.Assert(os.Remove(targetSnapPath), IsNil) + } else { + c.Assert(err, NotNil) + c.Assert(err.Error()[0:len(testCase.error)], Equals, testCase.error) + c.Assert(osutil.FileExists(targetSnapPath+".partial"), Equals, false) + c.Assert(osutil.FileExists(targetSnapPath), Equals, false) + } + c.Assert(os.Remove(currentSnapPath), IsNil) + c.Assert(os.Remove(deltaPath), IsNil) + } +} + func (t *remoteRepoTestSuite) TestDoRequestSetsAuth(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.UserAgent(), Equals, userAgent) @@ -2611,8 +2996,9 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreBuyFailArgumentChecking(c *C) { } var readyToBuyTests = []struct { - Input func(w http.ResponseWriter) - Test func(c *C, err error) + Input func(w http.ResponseWriter) + Test func(c *C, err error) + NumOfCalls int }{ { // A user account the is ready for buying @@ -2629,6 +3015,7 @@ var readyToBuyTests = []struct { Test: func(c *C, err error) { c.Check(err, IsNil) }, + NumOfCalls: 1, }, { // A user account that hasn't accepted the TOS @@ -2646,6 +3033,7 @@ var readyToBuyTests = []struct { c.Assert(err, NotNil) c.Check(err.Error(), Equals, "terms of service not accepted") }, + NumOfCalls: 1, }, { // A user account that has no payment method @@ -2663,6 +3051,7 @@ var readyToBuyTests = []struct { c.Assert(err, NotNil) c.Check(err.Error(), Equals, "no payment methods") }, + NumOfCalls: 1, }, { // No user account exists @@ -2674,6 +3063,7 @@ var readyToBuyTests = []struct { c.Assert(err, NotNil) c.Check(err.Error(), Equals, "cannot get customer details: server says no account exists") }, + NumOfCalls: 1, }, { // An unknown set of errors occurs @@ -2697,6 +3087,7 @@ var readyToBuyTests = []struct { c.Assert(err, NotNil) c.Check(err.Error(), Equals, `store reported an error: message 1`) }, + NumOfCalls: 6, }, } @@ -2733,6 +3124,40 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreReadyToBuy(c *C) { err = repo.ReadyToBuy(t.user) test.Test(c, err) - c.Check(purchaseServerGetCalled, Equals, 1) + c.Check(purchaseServerGetCalled, Equals, test.NumOfCalls) } } + +func (t *remoteRepoTestSuite) TestDoRequestSetRangeHeaderOnRedirect(c *C) { + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch n { + case 0: + http.Redirect(w, r, r.URL.Path+"-else", 302) + n++ + case 1: + c.Check(r.URL.Path, Equals, "/somewhere-else") + rg := r.Header.Get("Range") + c.Check(rg, Equals, "bytes=5-") + default: + panic("got more than 2 requests in this test") + } + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + url, err := url.Parse(mockServer.URL + "/somewhere") + c.Assert(err, IsNil) + reqOptions := &requestOptions{ + Method: "GET", + URL: url, + ExtraHeaders: map[string]string{ + "Range": "bytes=5-", + }, + } + + sto := New(&Config{}, nil) + _, err = sto.doRequest(sto.client, reqOptions, t.user) + c.Assert(err, IsNil) +} diff --git a/store/userinfo.go b/store/userinfo.go index c46aeb18886..d92ccd201b7 100644 --- a/store/userinfo.go +++ b/store/userinfo.go @@ -23,10 +23,14 @@ import ( "encoding/json" "fmt" "net/url" + "time" ) var ( - httpClient = newHTTPClient() + httpClient = newHTTPClient(&httpClientOpts{ + Timeout: 10 * time.Second, + MayLogBody: true, + }) ) type keysReply struct { diff --git a/strutil/strutil.go b/strutil/strutil.go index d7a95708e2c..ce1b5bfe6c9 100644 --- a/strutil/strutil.go +++ b/strutil/strutil.go @@ -33,7 +33,7 @@ const letters = "BCDFGHJKLMNPQRSTVWXYbcdfghjklmnpqrstvwxy0123456789" // MakeRandomString returns a random string of length length // -// The vowels are omited to avoid that words are created by pure +// The vowels are omitted to avoid that words are created by pure // chance. Numbers are included. func MakeRandomString(length int) string { diff --git a/systemd/systemd.go b/systemd/systemd.go index a79f4683d38..309ccc5bb2b 100644 --- a/systemd/systemd.go +++ b/systemd/systemd.go @@ -455,9 +455,8 @@ func (s *systemd) WriteMountUnitFile(name, what, where, fstype string) (string, if osutil.IsDirectory(what) { extra = "Options=bind\n" fstype = "none" - } - - if fstype == "squashfs" && useFuse() { + } else if fstype == "squashfs" && useFuse() { + extra = "Options=ro,allow_other\n" fstype = "fuse.squashfuse" } diff --git a/systemd/systemd_test.go b/systemd/systemd_test.go index fcff1919835..88713cdc274 100644 --- a/systemd/systemd_test.go +++ b/systemd/systemd_test.go @@ -392,6 +392,7 @@ Description=Mount unit for foo What=%s Where=/apps/foo/1.0 Type=fuse.squashfuse +Options=ro,allow_other [Install] WantedBy=multi-user.target diff --git a/tests/lib/snaps/test-snapd-content-plug/meta/snap.yaml b/tests/lib/snaps/test-snapd-content-plug/meta/snap.yaml index 86ed021e653..2a530668f55 100644 --- a/tests/lib/snaps/test-snapd-content-plug/meta/snap.yaml +++ b/tests/lib/snaps/test-snapd-content-plug/meta/snap.yaml @@ -9,4 +9,4 @@ plugs: interface: content target: import content: mylib - default-provider: hello-world + default-provider: test-snapd-tools diff --git a/tests/lib/snaps/test-snapd-fuse-consumer/snapcraft.yaml b/tests/lib/snaps/test-snapd-fuse-consumer/snapcraft.yaml index 352e7f79eb9..bb590d04023 100644 --- a/tests/lib/snaps/test-snapd-fuse-consumer/snapcraft.yaml +++ b/tests/lib/snaps/test-snapd-fuse-consumer/snapcraft.yaml @@ -11,6 +11,7 @@ apps: parts: create: plugin: python2 + build-packages: [python-fuse] stage-packages: [python-fuse] make: plugin: make diff --git a/tests/main/chattr/task.yaml b/tests/main/chattr/task.yaml new file mode 100644 index 00000000000..8ee2b02182b --- /dev/null +++ b/tests/main/chattr/task.yaml @@ -0,0 +1,15 @@ +summary: test chattr +# ubuntu-core doesn't have go :-) +systems: [-ubuntu-core-16-64, -ubuntu-core-16-arm-64, -ubuntu-core-16-arm-32] +prepare: | + go build -o toggle ./toggle.go +execute: | + touch foo + # no immutable flag: + lsattr foo | grep -qv i + test "$(./toggle foo)" = "mutable -> immutable" + # and now an immutable flag!: + lsattr foo | grep -q i + test "$(./toggle foo)" = "immutable -> mutable" + # no immutable flag again: + lsattr foo | grep -qv i diff --git a/tests/main/chattr/toggle.go b/tests/main/chattr/toggle.go new file mode 100644 index 00000000000..79f36ac6aac --- /dev/null +++ b/tests/main/chattr/toggle.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "os" + + "github.com/snapcore/snapd/osutil" +) + +func die(err error) { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} + +func main() { + if len(os.Args) < 2 { + die(fmt.Errorf("usage: %s file", os.Args[0])) + } + + f, err := os.Open(os.Args[1]) + if err != nil { + die(err) + } + + before, err := osutil.GetAttr(f) + if err != nil { + die(err) + } + + err = osutil.SetAttr(f, before^osutil.FS_IMMUTABLE_FL) + if err != nil { + die(err) + } + + after, err := osutil.GetAttr(f) + if err != nil { + die(err) + } + + if before&osutil.FS_IMMUTABLE_FL != 0 { + fmt.Print("immutable") + } else { + fmt.Print("mutable") + } + fmt.Print(" -> ") + if after&osutil.FS_IMMUTABLE_FL != 0 { + fmt.Println("immutable") + } else { + fmt.Println("mutable") + } +} diff --git a/tests/main/install-store-laaaarge/task.yaml b/tests/main/install-store-laaaarge/task.yaml index aaed53001fd..a47f61061ad 100644 --- a/tests/main/install-store-laaaarge/task.yaml +++ b/tests/main/install-store-laaaarge/task.yaml @@ -11,6 +11,6 @@ restore: | systemctl start snapd.{socket,service} execute: | - # hello-world is about 20k, tmpfs is 4k :-) - snap install hello-world - snap remove hello-world + # test-snapd-tools is about 8k, tmpfs is 4k :-) + snap install test-snapd-tools + snap remove test-snapd-tools diff --git a/tests/main/login/task.yaml b/tests/main/login/task.yaml index 3d671ca7cc1..75e18247c27 100644 --- a/tests/main/login/task.yaml +++ b/tests/main/login/task.yaml @@ -7,10 +7,10 @@ restore: | execute: | echo "Checking missing email error" - expect -f missing_email_error.exp + expect -d -f missing_email_error.exp echo "Checking wrong password error" - expect -f unsuccessful_login.exp + expect -d -f unsuccessful_login.exp output=$(snap managed) if [ "$output" != "false" ]; then @@ -20,7 +20,7 @@ execute: | if [[ $SPREAD_STORE_USER && $SPREAD_STORE_PASSWORD ]]; then echo "Checking successful login" - expect -f successful_login.exp + expect -d -f successful_login.exp output=$(snap managed) if [ "$output" != "system is managed" ]; then diff --git a/tests/main/login/unsuccessful_login.exp b/tests/main/login/unsuccessful_login.exp index 5804b63ee8b..94ceff8a89b 100644 --- a/tests/main/login/unsuccessful_login.exp +++ b/tests/main/login/unsuccessful_login.exp @@ -1,3 +1,5 @@ +set timeout 60 + spawn snap login someemail@testing.com expect "Password: " diff --git a/tests/main/postrm-purge/task.yaml b/tests/main/postrm-purge/task.yaml index 11632bfe4ac..f769c65164b 100644 --- a/tests/main/postrm-purge/task.yaml +++ b/tests/main/postrm-purge/task.yaml @@ -6,7 +6,7 @@ execute: | echo "When some snaps are installed" . $TESTSLIB/snaps.sh install_local test-snapd-tools - snap install hello-world + snap install test-snapd-control-consumer echo "And snapd is purged" sh -x ${SPREAD_PATH}/debian/snapd.postrm purge diff --git a/tests/main/security-devpts/pts.exp b/tests/main/security-devpts/pts.exp index f0194352270..86f26184d72 100644 --- a/tests/main/security-devpts/pts.exp +++ b/tests/main/security-devpts/pts.exp @@ -1,6 +1,6 @@ #!/usr/bin/expect -f -set timeout 2 +set timeout 20 spawn bash send "ls /dev/pts\n" diff --git a/tests/main/security-devpts/task.yaml b/tests/main/security-devpts/task.yaml index 0f7654a64ca..790b2d5eefc 100644 --- a/tests/main/security-devpts/task.yaml +++ b/tests/main/security-devpts/task.yaml @@ -9,4 +9,4 @@ prepare: | execute: | echo "Then the pts device follows confinement rules" - expect -f pts.exp + expect -d -f pts.exp diff --git a/tests/main/security-private-tmp/task.yaml b/tests/main/security-private-tmp/task.yaml index ca3416b9fe0..f6d0a61225f 100644 --- a/tests/main/security-private-tmp/task.yaml +++ b/tests/main/security-private-tmp/task.yaml @@ -23,7 +23,7 @@ restore: | execute: | echo "When a temporary file is created by one snap" - expect -f tmp-create.exp + expect -d -f tmp-create.exp if [ -e /usr/lib/snapd/snap-discard-ns ]; then echo "Then that file is accessible from other calls of commands from the same snap" diff --git a/tests/main/security-private-tmp/tmp-create.exp b/tests/main/security-private-tmp/tmp-create.exp index d7eba45ce3c..18ec89ec6da 100644 --- a/tests/main/security-private-tmp/tmp-create.exp +++ b/tests/main/security-private-tmp/tmp-create.exp @@ -1,6 +1,6 @@ #!/usr/bin/expect -f -set timeout 2 +set timeout 20 spawn bash diff --git a/tests/main/snap-download/task.yaml b/tests/main/snap-download/task.yaml index 7ed9f824108..9c0f094f616 100644 --- a/tests/main/snap-download/task.yaml +++ b/tests/main/snap-download/task.yaml @@ -9,9 +9,9 @@ execute: | grep "type: snap-revision" "$fn" } echo "Snap download can download snaps" - snap download hello-world - ls hello-world_*.snap - verify_asserts hello-world_*.assert + snap download test-snapd-control-consumer + ls test-snapd-control-consumer_*.snap + verify_asserts test-snapd-control-consumer_*.assert echo "Snap download understand --edge" snap download --edge test-snapd-tools diff --git a/tests/main/snap-env/task.yaml b/tests/main/snap-env/task.yaml new file mode 100644 index 00000000000..cd9aa1bd614 --- /dev/null +++ b/tests/main/snap-env/task.yaml @@ -0,0 +1,30 @@ +summary: inspect all the set environment variables prefixed with SNAP_ and XDG_ +prepare: | + snapbuild $TESTSLIB/snaps/test-snapd-tools . + snap install --dangerous test-snapd-tools_1.0_all.snap +restore: | + rm -f *.snap +execute: | + echo "Collect SNAP and XDG environment variables" + test-snapd-tools.env | egrep '^SNAP_' | sort > snap-vars.txt + test-snapd-tools.env | egrep '^XDG_' | sort > xdg-vars.txt + + echo "Ensure that SNAP environment variables are what we expect" + egrep -q '^SNAP_ARCH=(amd64|i386|arm64|armhf)$' snap-vars.txt + egrep -q '^SNAP_COMMON=/var/snap/test-snapd-tools/common$' snap-vars.txt + egrep -q '^SNAP_DATA=/var/snap/test-snapd-tools/x1$' snap-vars.txt + egrep -q '^SNAP_LIBRARY_PATH=/var/lib/snapd/lib/gl:$' snap-vars.txt + egrep -q '^SNAP_NAME=test-snapd-tools$' snap-vars.txt + # XXX: probably not something we ought to test + # egrep -q '^SNAP_REEXEC=0$' snap-vars.txt + egrep -q '^SNAP_REVISION=x1$' snap-vars.txt + egrep -q '^SNAP_USER_COMMON=/root/snap/test-snapd-tools/common$' snap-vars.txt + egrep -q '^SNAP_USER_DATA=/root/snap/test-snapd-tools/x1$' snap-vars.txt + egrep -q '^SNAP_VERSION=1.0$' snap-vars.txt + test $(wc -l < snap-vars.txt) -eq 10 + + echo "Enure that XDG environment variables are what we expect" + egrep -q '^XDG_RUNTIME_DIR=/run/user/0/snap.test-snapd-tools$' xdg-vars.txt + test $(wc -l < xdg-vars.txt) -eq 2 +debug: | + cat *-vars.txt diff --git a/testutil/checkers_test.go b/testutil/checkers_test.go index 0387ff14ab1..e7f3183f4e4 100644 --- a/testutil/checkers_test.go +++ b/testutil/checkers_test.go @@ -1,8 +1,8 @@ // -*- Mode: Go; indent-tabs-mode: t -*- // -// 20160229: The tests with gccgo on powerpc fails for this file -// and it will loop endlessly. This is not reproducable -// with gccgo on amd64. Given that its a relatively little +// 20160229: The tests with gccgo on powerpc fail for this file +// and it will loop endlessly. This is not reproducible +// with gccgo on amd64. Given that it's a relatively little // used arch we disable the tests in here to workaround this // gccgo bug. // +build !ppc diff --git a/vendor/vendor.json b/vendor/vendor.json index b7c7b0a59ac..7bd6baf22bc 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -99,6 +99,12 @@ "revision": "ab3940c6c16510a850e1c2dd628b919f0f3f1464", "revisionTime": "2015-01-21T11:42:31Z" }, + { + "checksumSHA1": "lBMMakT63h9ywP4d5wlkhOYMCAs=", + "path": "gopkg.in/retry.v1", + "revision": "c09f6b86ba4d5d2cf5bdf0665364aec9fd4815db", + "revisionTime": "2016-10-25T18:07:18Z" + }, { "checksumSHA1": "WiyCOMvfzRdymImAJ3ME6aoYUdM=", "path": "gopkg.in/tomb.v2", diff --git a/wrappers/binaries_test.go b/wrappers/binaries_test.go index 80663f1d02f..8b841f1ab06 100644 --- a/wrappers/binaries_test.go +++ b/wrappers/binaries_test.go @@ -63,9 +63,10 @@ apps: post-stop-command: bin/missya daemon: forking ` +const contentsHello = "HELLO" func (s *binariesTestSuite) TestAddSnapBinariesAndRemove(c *C) { - info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(11)}) + info := snaptest.MockSnap(c, packageHello, contentsHello, &snap.SideInfo{Revision: snap.R(11)}) err := wrappers.AddSnapBinaries(info) c.Assert(err, IsNil) diff --git a/wrappers/desktop_test.go b/wrappers/desktop_test.go index eb3d0cf20ec..c4f19821853 100644 --- a/wrappers/desktop_test.go +++ b/wrappers/desktop_test.go @@ -63,12 +63,13 @@ var mockDesktopFile = []byte(` [Desktop Entry] Name=foo Icon=${SNAP}/foo.png`) +var desktopContents = "" func (s *desktopSuite) TestAddPackageDesktopFiles(c *C) { expectedDesktopFilePath := filepath.Join(dirs.SnapDesktopFilesDir, "foo_foobar.desktop") c.Assert(osutil.FileExists(expectedDesktopFilePath), Equals, false) - info := snaptest.MockSnap(c, desktopAppYaml, &snap.SideInfo{Revision: snap.R(11)}) + info := snaptest.MockSnap(c, desktopAppYaml, desktopContents, &snap.SideInfo{Revision: snap.R(11)}) // generate .desktop file in the package baseDir baseDir := info.MountDir() diff --git a/wrappers/services_test.go b/wrappers/services_test.go index 3f9a2ea475a..452abc6dd9f 100644 --- a/wrappers/services_test.go +++ b/wrappers/services_test.go @@ -66,7 +66,7 @@ func (s *servicesTestSuite) TestAddSnapServicesAndRemove(c *C) { return []byte("ActiveState=inactive\n"), nil } - info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) + info := snaptest.MockSnap(c, packageHello, contentsHello, &snap.SideInfo{Revision: snap.R(12)}) err := wrappers.AddSnapServices(info, nil) c.Assert(err, IsNil) @@ -121,7 +121,7 @@ apps: command: wat stop-timeout: 250ms daemon: forking -`, &snap.SideInfo{Revision: snap.R(11)}) +`, "", &snap.SideInfo{Revision: snap.R(11)}) err := wrappers.AddSnapServices(info, nil) c.Assert(err, IsNil) @@ -148,7 +148,7 @@ func (s *servicesTestSuite) TestStartSnapServices(c *C) { return []byte("ActiveState=inactive\n"), nil } - info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) + info := snaptest.MockSnap(c, packageHello, contentsHello, &snap.SideInfo{Revision: snap.R(12)}) svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") err := wrappers.StartSnapServices(info, nil)