Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interfaces: Add new systemd-user-control interface #13920

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions interfaces/builtin/systemd_user_control.go
er-vin marked this conversation as resolved.
Show resolved Hide resolved
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
er-vin marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1342,6 +1363,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 @@ -293,6 +293,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
Loading