Skip to content

Commit

Permalink
iface builtin: Add new systemd-user-control interface
Browse files Browse the repository at this point in the history
This is meant to support desktop sessions boots fully driven by systemd
and D-Bus activation on the user session.
  • Loading branch information
er-vin committed Aug 14, 2024
1 parent 502f79f commit d6d6435
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 13 deletions.
107 changes: 107 additions & 0 deletions interfaces/builtin/systemd_user_control.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.
*
*/

package builtin

const systemdUserControlSummary = `allows to control the user session service manager`

const systemdUserControlBaseDeclarationPlugs = `
systemd-user-control:
allow-installation: false
deny-auto-connection: true
`

const systemdUserControlBaseDeclarationSlots = `
systemd-user-control:
allow-installation:
slot-snap-type:
- core
deny-auto-connection: true
`

const systemdUserControlConnectedPlugAppArmor = `
# Description: Can control the user session service manager
#include <abstractions/dbus-session-strict>
#include <abstractions/dbus-strict>
# Supporting session boot fully driven by user session systemd
# and D-Bus activation
# Please note that UpdateActivationEnvironment can alter D-Bus activated services behavior
# (e.g. by setting LD_PRELOAD)
# It is thus intended to be restricted only to snaps acting as a desktop session on Ubuntu Core systems
#
# For such snaps, it allows the session to pass important variables to other processes in the session
# (e.g. DISPLAY, WAYLAND_DISPLAY)
dbus (send)
bus=session
path={/,/org/freedesktop/DBus}
interface=org.freedesktop.DBus
member=UpdateActivationEnvironment
peer=(label=unconfined),
dbus (send)
bus=session
path=/org/freedesktop/systemd1
interface=org.freedesktop.DBus.Properties
member={Set,Get,GetAll}
peer=(label=unconfined),
# Please note that SetEnvironment can alter existing units behavior (e.g. by setting LD_PRELOAD)
# It is thus intended to be restricted only to snaps acting as a desktop session on Ubuntu Core systems
#
# For such snaps, it allows the session to pass important variables to other processes in the session
# (e.g. DISPLAY, WAYLAND_DISPLAY)
dbus (send)
bus=session
path=/org/freedesktop/systemd1
interface=org.freedesktop.systemd1.Manager
member={SetEnvironment,UnsetEnvironment,UnsetAndSetEnvironment}
peer=(label=unconfined),
# Allow to introspect the units available in the session
dbus (send)
bus=session
path=/org/freedesktop/systemd1
interface=org.freedesktop.systemd1.Manager
member={Reload,ListUnitFiles,ListUnitFilesByPatterns}
peer=(label=unconfined),
# Allow to manage the units available in the session
# (e.g. to start the target describing the full session, phase parts of the startup)
dbus (send)
bus=session
path=/org/freedesktop/systemd1
interface=org.freedesktop.systemd1.Manager
member={ResetFailed,Reload,StartUnit,StopUnit,RestartUnit}
peer=(label=unconfined),
`

func init() {
registerIface(&commonInterface{
name: "systemd-user-control",
summary: systemdUserControlSummary,
implicitOnCore: true,
implicitOnClassic: false, // This is meant for use by session snaps on core, no use for apps in classic mode
baseDeclarationPlugs: systemdUserControlBaseDeclarationPlugs,
baseDeclarationSlots: systemdUserControlBaseDeclarationSlots,
connectedPlugAppArmor: systemdUserControlConnectedPlugAppArmor,
})
}
89 changes: 89 additions & 0 deletions interfaces/builtin/systemd_user_control_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.
*
*/

package builtin_test

import (
. "gopkg.in/check.v1"

"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/builtin"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
)

type SystemdUserControlInterfaceSuite struct {
iface interfaces.Interface
slotInfo *snap.SlotInfo
slot *interfaces.ConnectedSlot
plugInfo *snap.PlugInfo
plug *interfaces.ConnectedPlug
}

var _ = Suite(&SystemdUserControlInterfaceSuite{
iface: builtin.MustInterface("systemd-user-control"),
})

func (s *SystemdUserControlInterfaceSuite) SetUpTest(c *C) {
var mockPlugSnapInfoYaml = `name: other
version: 1.0
apps:
app:
command: foo
plugs: [systemd-user-control]
`
var mockSlotSnapInfoYaml = `name: core
version: 0
type: os
slots:
systemd-user-control:
`
s.plug, s.plugInfo = MockConnectedPlug(c, mockPlugSnapInfoYaml, nil, "systemd-user-control")
s.slot, s.slotInfo = MockConnectedSlot(c, mockSlotSnapInfoYaml, nil, "systemd-user-control")
}

func (s *SystemdUserControlInterfaceSuite) TestName(c *C) {
c.Assert(s.iface.Name(), Equals, "systemd-user-control")
}

func (s *SystemdUserControlInterfaceSuite) TestSanitizeSlot(c *C) {
c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil)
}

func (s *SystemdUserControlInterfaceSuite) TestSanitizePlug(c *C) {
c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
}

func (s *SystemdUserControlInterfaceSuite) TestUsedSecuritySystems(c *C) {
// connected plugs have a non-nil security snippet for apparmor
appSet, err := interfaces.NewSnapAppSet(s.plug.Snap(), nil)
c.Assert(err, IsNil)
apparmorSpec := apparmor.NewSpecification(appSet)
err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
c.Assert(err, IsNil)
c.Assert(apparmorSpec.Snippets(), HasLen, 1)
c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "bus=session")
c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), Not(testutil.Contains), "bus=system")
c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "UpdateActivationEnvironment")
}

func (s *SystemdUserControlInterfaceSuite) TestInterfaces(c *C) {
c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
}
48 changes: 35 additions & 13 deletions interfaces/policy/basedeclaration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,20 @@ func (s *baseDeclSuite) TestAutoConnection(c *C) {
// these have more complex or in flux policies and have their
// own separate tests
snowflakes := map[string]bool{
"content": true,
"core-support": true,
"desktop": true,
"home": true,
"lxd-support": true,
"microstack-support": true,
"multipass-support": true,
"packagekit-control": true,
"pkcs11": true,
"remoteproc": true,
"snapd-control": true,
"upower-observe": true,
"empty": true,
"content": true,
"core-support": true,
"desktop": true,
"home": true,
"lxd-support": true,
"microstack-support": true,
"multipass-support": true,
"packagekit-control": true,
"pkcs11": true,
"remoteproc": true,
"snapd-control": true,
"systemd-user-control": true,
"upower-observe": true,
"empty": true,
}

// these simply auto-connect, anything else doesn't
Expand Down Expand Up @@ -333,6 +334,25 @@ plugs:
c.Check(arity.SlotsPerPlugAny(), Equals, false)
}

func (s *baseDeclSuite) TestAutoConnectionSystemdUserControl(c *C) {
cand := s.connectCand(c, "systemd-user-control", "", "")
_, err := cand.CheckAutoConnect()
c.Check(err, NotNil)
c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"systemd-user-control\"")

plugsSlots := `
plugs:
systemd-user-control:
allow-auto-connection: true
`

lxdDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
cand.PlugSnapDeclaration = lxdDecl
arity, err := cand.CheckAutoConnect()
c.Check(err, IsNil)
c.Check(arity.SlotsPerPlugAny(), Equals, false)
}

func (s *baseDeclSuite) TestAutoConnectionContent(c *C) {
// random snaps cannot connect with content
// (Sanitize* will now also block this)
Expand Down Expand Up @@ -1040,6 +1060,7 @@ func (s *baseDeclSuite) TestPlugInstallation(c *C) {
"snapd-control": true,
"steam-support": true,
"system-files": true,
"systemd-user-control": true,
"tee": true,
"uinput": true,
"unity8": true,
Expand Down Expand Up @@ -1341,6 +1362,7 @@ func (s *baseDeclSuite) TestValidity(c *C) {
"snapd-control": true,
"steam-support": true,
"system-files": true,
"systemd-user-control": true,
"tee": true,
"udisks2": true,
"uinput": true,
Expand Down
3 changes: 3 additions & 0 deletions tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ apps:
xilinx-dma:
command: bin/run
plugs: [ xilinx-dma ]
systemd-user-control:
command: bin/run
plugs: [ systemd-user-control ]

plugs:
custom-device:
Expand Down

0 comments on commit d6d6435

Please sign in to comment.