Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
many: add support for session dbus services via snaps #2592
Conversation
mvo5
added some commits
Jan 10, 2017
| @@ -0,0 +1,3 @@ | ||
| +<busconfig> | ||
| + <servicedir>/var/lib/snapd/desktop/dbus-1/services/</servicedir> | ||
| +</busconfig> |
jdstrand
Jan 12, 2017
Contributor
IMO, we should remove 'desktop' here-- these aren't desktop files and this isn't limited to 'desktop' systems. Also, I know you probably modeled this name after the directory in /usr/share/dbus-1, but for clarity within snapd, I think this would be better named '/var/lib/snapd/dbus-1/session-services/'. Putting it in /var/lib/snapd/dbus-1 means we can add '/var/lib/snapd/dbus-1/system-services/' if we so choose down the line and keep snappy dbus-y things in one clear spot.
| @@ -116,6 +117,7 @@ func SetRootDir(rootdir string) { | ||
| SnapMetaDir = filepath.Join(rootdir, snappyDir, "meta") | ||
| SnapBlobDir = filepath.Join(rootdir, snappyDir, "snaps") | ||
| SnapDesktopFilesDir = filepath.Join(rootdir, snappyDir, "desktop", "applications") | ||
| + SnapDBusServicesFilesDir = filepath.Join(rootdir, snappyDir, "desktop", "dbus-1/services") |
jdstrand
Jan 12, 2017
Contributor
Please rename as SnapDBusSessionServicesFilesDir to make sure there is no confusion with system services.
| + in string | ||
| + good bool | ||
| + }{ | ||
| + {"Activactable=true", false}, |
| + | ||
| + baseDir := s.MountDir() | ||
| + | ||
| + servicesFiles, err := filepath.Glob(filepath.Join(baseDir, "meta", "gui", "*.service")) |
jdstrand
Jan 12, 2017
Contributor
I don't think this should be tied to 'gui'. Most session services run in the background and don't have a gui. Some session services are dbus activatable and are started by guis accessing them, but that is a different thing.
| + } | ||
| + | ||
| + installedServiceFileName := filepath.Join(dirs.SnapDBusServicesFilesDir, fmt.Sprintf("%s_%s", s.Name(), filepath.Base(df))) | ||
| + content = sanitizeDBusServiceFile(s, installedServiceFileName, content) |
jdstrand
Jan 12, 2017
Contributor
I think here, in the MkdirAll above and in the RemoveSnapDBusServiceFiles call below you should think about when we might create a dbus service file for a system service (not that you have to implement that now, but see my later comments). That might be AddSnapDBusServiceFiles and RemoveSnapDBusServiceFiles taking an argument of 'system' or 'session' then picking the directory based on that. It might be something else. A comment in these places explicitly saying this is for session services and that we need to update these to support system services would be nice.
|
As written, if snapd install finds meta/gui/foo.service, it takes that and creates a session service file in /var/lib/snapd/desktop/dbus-1/services and allows the desktop file to set DBusActivatable. This is simple and it works to start the session service in devmode, but is lacking security policy updates that would allow it to run in strict mode. Also, looking at what can be declared in service files, I think that we can have snapd generate them and expose the options via yaml. The dbus specification (https://dbus.freedesktop.org/doc/dbus-specification.html) is not very clear on service files but bus/desktop-file.h from the dbus sources is. These are the supported directives:
We want to use 'Name=' and 'Exec='. We can ignore 'User=' for now since session services don't use it (they are expected to run as the same user as the user's session) and system services are started as root via systemd (when we support per-snap users, we'll adjust the systemd service file for services, not a dbus serevice service file). We can ignore SystemdService= since it is used by dbus-daemon to tell systemd to start the service on its behalf. We will have dbus-daemon starting session services directly and systemd starting system services directly, so this isn't needed. It is important to remember that on series 16, we have systemd user sessions, but this isn't available in 14.04, so we should have dbus-daemon start session services itself rather than systemd. I propose:
With the above, we have the pieces in place to create session service files, but no way to declare they should be used or how to generate the security policy. I think we can look at the dbus interface and add a slot side attribute. We currently have:
We add:
Then the dbus interface can be adjusted such that:
With this, the |
mvo5
added some commits
Jan 16, 2017
mvo5
added some commits
Jan 16, 2017
|
Fwiw, the 14.04 failure can be ignored, the packaging branch needs an update and then it should work. |
mvo5
added some commits
Jan 17, 2017
mvo5
referenced this pull request
Jan 20, 2017
Closed
wrappers: add DBusActivatable to the allowed values for desktop files #2591
|
Would it make sense to also generate a If we're assuming systemd for system and user init, then this would reduce the number of code paths for launching confined daemons on the system. |
| @@ -0,0 +1,3 @@ | ||
| +<busconfig> | ||
| + <servicedir>/var/lib/snapd/dbus-1/services/</servicedir> |
niemeyer
Feb 13, 2017
Contributor
Can we please use "dbus/services"? That "dbus-1" convention always feels awkward to me, and we don't have many reasons to reproduce it here I think. The compatibility issues and their solution will be the same as for every other service we hold data for.
| } | ||
| + if err := b.setupBusservices(snapInfo, snippets); err != nil { |
niemeyer
Feb 13, 2017
Contributor
Our naming convention is generally:
s/Busconf/BusConf/
s/Busserv/BusServ/
Same below for remove, combineSnippets, addContent, etc.
| + securityTag := appInfo.SecurityTag() | ||
| + | ||
| + // FIXME: layer violation, we really should do that in the | ||
| + // dbus interface but without PR #2613 its really tricky |
niemeyer
Feb 13, 2017
Contributor
#2613 is now in. Seems okay to fix that in a follow up, though, assuming everything else is golden.
| + if !ok { | ||
| + continue | ||
| + } | ||
| + if bus != "session" { |
mvo5
Feb 14, 2017
Collaborator
I think we want that in the longer run, my main focus for this PR was to unblock people who need dbus activated services (I think unity and kde has some of those).
jdstrand
Feb 14, 2017
Contributor
If you aren't going to support system now, can you add a TODO. Eg:
// TODO: we can eventually support 'system' by:
// 1. creating the SnapDBusSystemServicesFilesDir directory
// 2. writing the service file to SnapDBusSystemServicesFilesDir
// when 'daemon' is set to 'dbus' (see validate.go)
// 3. add 'Type=dbus' and 'BusName=slot.Attrs["name"].(string)'
// to the systemd unit when 'slot.Attrs["service"].(bool) == True'
// and 'daemon' is set to 'dbus'
| + if !ok { | ||
| + continue | ||
| + } | ||
| + isService := slot.Attrs["service"].(bool) |
niemeyer
Feb 13, 2017
Contributor
Even without #2613, can we please at least validate that setting there, and cross-link it here (mentioning this is being used with this purpose).
mvo5
Feb 14, 2017
Collaborator
I added checks that its really bool and also added a comment why we need this. Should I do more (I'm not sure if that addresses the cross-link comment).
| + glob := fmt.Sprintf("%s.service", interfaces.SecurityTagGlob(snapName)) | ||
| + dir := dirs.SnapDBusSessionServicesFilesDir | ||
| + if err := os.MkdirAll(dir, 0755); err != nil { | ||
| + return fmt.Errorf("cannot create directory for DBus service files %q: %s", dir, err) |
niemeyer
Feb 13, 2017
Contributor
The %q/dir is probably redundant here. The error from MkdirAll will include the path name as well.
| }, "|")).MatchString | ||
| // rewriteExecLine rewrites a "Exec=" line to use the wrapper path for snap application. | ||
| -func rewriteExecLine(s *snap.Info, desktopFile, line string) (string, error) { | ||
| +func rewriteExecLine(s *snap.Info, desktopFile, line, env string) (string, error) { |
niemeyer
Feb 13, 2017
Contributor
Why the change here? Previous version seemed cleaner than new one. Now the call site needs to know what "env" means (it's not an environment at all) and the curious place in which it will be inserted.
| @@ -300,8 +300,14 @@ func (s *sanitizeDesktopFileSuite) TestLangLang(c *C) { | ||
| // bad ones | ||
| {"Name[foo=bar", false}, | ||
| {"Icon[xx]=bar", false}, | ||
| + // dbus related | ||
| + {"Activactable=true", false}, |
mvo5
Feb 14, 2017
Collaborator
ups, I fixed that typo, the test was focused on the missing "DBus" in front, not so much on the typo :)
mvo5
added some commits
Feb 14, 2017
|
@jhenstridge re 'SystemdService key', I covered this in: #2592 (comment) |
jdstrand
approved these changes
Feb 14, 2017
Thanks @mvo5 for taking my suggestions and implementing this. +1, but please add the comments I suggested for clarity and next steps.
| + return nil | ||
| +} | ||
| + | ||
| +func (b *Backend) setupBusConf(snapInfo *snap.Info, snippets map[string][][]byte) error { |
jdstrand
Feb 14, 2017
Contributor
Can you add a comment for what this function does? Eg:
// Create the DBus busconfig policy for the snap
| + return nil | ||
| +} | ||
| + | ||
| +func (b *Backend) removeBusConf(snapName string) error { |
jdstrand
Feb 14, 2017
Contributor
Can you add a comment above this function. Eg
// Remove the DBus busconfig policy for the snap
jdstrand
Feb 16, 2017
•
Contributor
@mvo5 - nitpick, this comment still didn't make it in. It looks like you added it in the wrong place (see below).
| -func (b *Backend) combineSnippets(snapInfo *snap.Info, snippets map[string][][]byte) (content map[string]*osutil.FileState, err error) { | ||
| +// combineSnippetsBusConf combines security snippets collected from | ||
| +// all the interfaces affecting a given snap into a content map | ||
| +// applicable to EnsureDirState. |
jdstrand
Feb 14, 2017
Contributor
Can you rephrase this to be:
// combineSnippetsBusConf combines security snippets for busconfig
// policy collected from all the interfaces affecting a given snap into a
// content map applicable to EnsureDirState.
| } | ||
| return content, nil | ||
| } | ||
| -func addContent(securityTag string, executableSnippets [][]byte, content map[string]*osutil.FileState) { | ||
| +func addContentBusConf(securityTag string, executableSnippets [][]byte, content map[string]*osutil.FileState) { |
jdstrand
Feb 14, 2017
Contributor
Can you add a comment above this. Eg:
// addContentBusConf creates/updates the xml busconfig policy for the snap
| + if !ok { | ||
| + continue | ||
| + } | ||
| + if bus != "session" { |
mvo5
Feb 14, 2017
Collaborator
I think we want that in the longer run, my main focus for this PR was to unblock people who need dbus activated services (I think unity and kde has some of those).
jdstrand
Feb 14, 2017
Contributor
If you aren't going to support system now, can you add a TODO. Eg:
// TODO: we can eventually support 'system' by:
// 1. creating the SnapDBusSystemServicesFilesDir directory
// 2. writing the service file to SnapDBusSystemServicesFilesDir
// when 'daemon' is set to 'dbus' (see validate.go)
// 3. add 'Type=dbus' and 'BusName=slot.Attrs["name"].(string)'
// to the systemd unit when 'slot.Attrs["service"].(bool) == True'
// and 'daemon' is set to 'dbus'
| + continue | ||
| + } | ||
| + // we check if its a service here so that we know | ||
| + // if a dbus service file needs to get generated. |
| + continue | ||
| + } | ||
| + | ||
| + var buffer bytes.Buffer |
jdstrand
Feb 14, 2017
Contributor
Can you add a comment here. Eg:
// We set only 'Name' and 'Exec' for now. We may add 'User' for 'system'
// services when we support per-snap users. Don't specify 'SystemdService'
// and just let dbus-daemon launch the service since 'SystemdService' is only
// used by dbus-daemon to tell systemd to launch the service and systemd
// user sessions aren't available everywhere yet.
| @@ -134,7 +134,7 @@ var validAppName = regexp.MustCompile("^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$") | ||
| // ValidateApp verifies the content in the app info. | ||
| func ValidateApp(app *AppInfo) error { | ||
| switch app.Daemon { | ||
| - case "", "simple", "forking", "oneshot", "dbus", "notify": | ||
| + case "", "simple", "forking", "oneshot", "notify": |
jdstrand
Feb 14, 2017
•
Contributor
Please add:
// TODO: add 'dbus' when we support 'service: true' with 'bus: system'
// in the dbus interface
jdstrand
referenced this pull request
Feb 14, 2017
Merged
interfaces: add an interface for use by thumbnailer #2783
mvo5
added some commits
Feb 15, 2017
|
Test failures are related:
|
| + return nil | ||
| +} | ||
| + | ||
| +// Remove the DBus busconfig policy for the snap |
jdstrand
Feb 16, 2017
Contributor
This comment should be:
// Remove the DBus service file for the snap
mvo5
added some commits
Feb 17, 2017
| @@ -0,0 +1,3 @@ | ||
| +<busconfig> |
mvo5
Feb 17, 2017
Collaborator
This will not work with re-exec, i.e. this config file needs to be in the regular deb package right now and if snapd just re-execs it won't be there. So we probably need to add code that writes it if its missing. Which has the downside that it will also mean that we leave files behind when snapd gets removed.
niemeyer
Feb 23, 2017
Contributor
That's already the case with pretty much all of the security backends, and we actually already have a dbus backend that is tasked precisely with writing out that sort of file, and we ought to be removing these already when the package goes away (are we?).
|
Closing for now as this needs further work |
mvo5
closed this
Mar 6, 2017
jdstrand
referenced this pull request
Mar 10, 2017
Merged
interfaces/builtin: add online-accounts-service interface #2869
mvo5
reopened this
Apr 20, 2017
|
Reopening @morphis was interessted. |
mvo5
added some commits
Apr 21, 2017
jdstrand
requested changes
May 1, 2017
This is looking really good. Thanks for picking this up! Few open questions inline.
| @@ -138,6 +140,8 @@ func SetRootDir(rootdir string) { | ||
| SnapMetaDir = filepath.Join(rootdir, snappyDir, "meta") | ||
| SnapBlobDir = filepath.Join(rootdir, snappyDir, "snaps") | ||
| SnapDesktopFilesDir = filepath.Join(rootdir, snappyDir, "desktop", "applications") | ||
| + SnapDBusSessionServicesFilesDir = filepath.Join(rootdir, snappyDir, "dbus/services") |
jdstrand
May 1, 2017
Contributor
Can you add a comment along the lines of:
# Use 'dbus/services' to mirror /usr/share/dbus-1/services for session
# services. With system services, will use 'dbus/system-services'.
| @@ -226,6 +227,15 @@ func (iface *DbusInterface) getAttribs(attribs map[string]interface{}) (string, | ||
| return bus, name, nil | ||
| } | ||
| +// isDbusService returns true if the yaml declares it is a service | ||
| +func isDbusService(attribs map[string]interface{}) bool { | ||
| + if isService, ok := attribs["service"].(bool); ok { |
jdstrand
May 1, 2017
Contributor
I don't think "service" is the right term. Anything that slots the dbus interface is a "service", it is just that currently with system services they are automatically started and with session services they are not. If we were going to go with an attribute at all, I would suggest "activatable" and verify that it is only used when "bus" is "session".
That said, we may be able to do better. What if instead of adding an attribute we look at the surrounding yaml. For example, if "bus" is "session" and "daemon" is present, create the session service activation files, otherwise, operate how we do now. This should improve the developer experience and make things "just work" the way people expect.
| @@ -316,13 +326,30 @@ func (iface *DbusInterface) DBusPermanentSlot(spec *dbus.Specification, slot *in | ||
| } | ||
| // only system services need bus policy |
jdstrand
May 1, 2017
Contributor
Perhaps change this to be:
// System services get system bus policy
| + spec.AddSnippet(strings.Replace(dbusPermanentSlotDBus, old, new, -1)) | ||
| + } | ||
| + | ||
| + // dbus activation currently only supported by the session bus |
jdstrand
May 1, 2017
Contributor
and change this to:
Session services get session activation bus policy
| + // 3. add 'Type=dbus' and 'BusName=slot.Attrs["name"].(string)' to | ||
| + // the systemd unit when 'slot.Attrs["service"].(bool) == True' and | ||
| + // 'daemon' is set to 'dbus' | ||
| + if bus == "session" && isDbusService(slot.Attrs) && len(slot.Apps) == 1 { |
| + } | ||
| + | ||
| + // FIXME: also check that the dbus name is not already taken | ||
| + // by an existing snap |
morphis
May 12, 2017
Contributor
How can we reliable implement this? Is there any way to query dbus for this as I think examining available configuration files isn't enough. org.freedesktop.DBus.ListActivatableNames looks like a good candidate for this. I am adding godbus to the snapd source tree already with #3260 so would be fine to add it with this PR too.
jdstrand
May 12, 2017
Contributor
The FIXME is for if a snap has taken it, not if anything has taken it. snapd has all the information for this: specifically all the snaps on the system with connected dbus interfaces and the attributes of those interfaces.
I don't think we want to query DBus only if we want to make this generalizable beyond activatable names because it would be useful if a non-activatable names were also checked. Right now, those snaps simply fail to start, but we could do something better if we were smarter with existing connections. snapd could additionally look at ListActivatableNames for the activated services to check for non-snap activated service collisions.
| + // by an existing snap | ||
| + if isDbusService(slot.Attrs) && len(slot.Apps) > 1 { | ||
| + return fmt.Errorf("cannot add dbus service slot to multiple apps") | ||
| + } |
jdstrand
May 1, 2017
Contributor
This is an interesting requirement because it forces that the snap.yaml must be of the form:
slots:
dbus-session:
...
apps:
foo:
command: ...
slots: [ dbus-session ]
bar:
command: ...
but thus far yaml has allowed specifying slots (and plugs) only in the top-level:
slots:
dbus-session:
...
apps:
foo:
command: ...
bar:
command: ...
where the security policy for 'dbus-session' is given to both foo and bar. What is different here is that there can be only one activation file since we must map the 'Name' in the service file/interface attribute to a specific 'Exec' in the service file. I can't think of a way to make it cleaner than what you have, except to add a comment above saying:
# Because activation service files necessarily must map one DBus well-known
# name to one command, enforce that constraint in the interface here.
I sorta feel like the error message should be improved, but not sure to what. Perhaps add the attribute "name" so it is clearer?
| + <servicedir>/var/lib/snapd/dbus/services/</servicedir> | ||
| +</busconfig> | ||
| +`) | ||
| + return ioutil.WriteFile(reexecDbusSessionConf, sessionBusConfig, 0644) |
jdstrand
May 1, 2017
Contributor
This checks for snapd.conf and exits, but snapd is not creating it. Is that because snapd distro packaging will always ship this file? If so, then with how the code is written, snapd-reexec.conf will only ever be written if snapd.conf for some reason doesn't exist. While I see that in your postrm you cleanup this file on purge (great!), for systems only upgrading, if a later version of snapd then comes that provides snapd.conf, the previously created snapd-reexec.conf will never get updated or used, potentially causing confusion. Is snapd-reexec.conf meant to be preferred over snapd.conf when re-execing? If so, this code doesn't do that.
How is this supposed to work with packaging? Perhaps a comment in the code saying as much would help....
| @@ -74,10 +118,46 @@ func (b *Backend) Setup(snapInfo *snap.Info, opts interfaces.ConfinementOptions, | ||
| return nil | ||
| } | ||
| +func (b *Backend) setupBusServ(snapInfo *snap.Info, spec interfaces.Specification) error { |
jdstrand
May 1, 2017
Contributor
setupBusServ is too generic, no? Right now this is only for session activated services, but the name of this function implies it could be for any dbus service. Perhaps setupBusActivatedSessionServ?
| @@ -86,9 +166,18 @@ func (b *Backend) Remove(snapName string) error { | ||
| return nil | ||
| } | ||
| +func (b *Backend) removeBusServ(snapName string) error { |
| + } | ||
| + return nil | ||
| +} | ||
| + | ||
| // deriveContent combines security snippets collected from all the interfaces | ||
| // affecting a given snap into a content map applicable to EnsureDirState. |
jdstrand
May 1, 2017
Contributor
Can you adjust this comment to be:
// deriveContentBusConf combines security snippets for busconfig
// policy collected from all the interfaces affecting a given snap into a
// content map applicable to EnsureDirState.
zyga
requested changes
May 10, 2017
A few things I'd like to see changed. Let me know if you need a hand, I will gladly make those changes.
| +// packaged snapd. | ||
| +func setupHostDBusSessionConf() error { | ||
| + dbusSessionConf := filepath.Join(dirs.SnapSessionBusPolicyDir, "snapd.conf") | ||
| + if osutil.FileExists(dbusSessionConf) { |
zyga
May 10, 2017
Contributor
Could we use osutil.EnsureDirState for this? This will allow us to just have a simple call here that ensures the correct content is in that file.
| +func (b *Backend) setupBusActivatedSessionServ(snapInfo *snap.Info, spec interfaces.Specification) error { | ||
| + snapName := snapInfo.Name() | ||
| + | ||
| + content := map[string]*osutil.FileState{} |
zyga
May 10, 2017
Contributor
Could you please move this to a helper similar to deriveContentBusConf?
| + | ||
| + content := map[string]*osutil.FileState{} | ||
| + for securityTag, serviceContent := range spec.(*Specification).SessionServices() { | ||
| + fname := fmt.Sprintf("%s.service", securityTag) |
morphis
May 12, 2017
Contributor
If not we can pipe the security tag through systemd-escape, which is maybe safe in any case when we generate a systemd unit name.
mvo5
May 17, 2017
Collaborator
The files written here are dbus service files. The spec https://dbus.freedesktop.org/doc/dbus-specification.html has no restrictions on the name AFAICS.
| + spec.sessionServices[appInfo.SecurityTag()] = fmt.Sprintf(`[D-BUS Service] | ||
| +Name=%s | ||
| +Exec=%s | ||
| +`, name, appInfo.LauncherCommand()) |
zyga
May 10, 2017
Contributor
Note that we have systemd.Service and the systemd backend as well. We could perhaps reuse some of those here.
mvo5
May 17, 2017
Collaborator
In this particular case we write a dbus service file which is different from a systemd unit file.
pedronis
referenced this pull request
May 16, 2017
Merged
overlord/snapstate: avoid creating command aliases for daemons #3323
|
see discussion in #3323 whether we need to tweak wrappers.IsService here |
mvo5
added some commits
May 17, 2017
|
@pedronis I think it does make sense to make wrapper.IsService() return true for dbus activated services. This way we also avoid writing the /snap/bin/svc binary and avoid polluting the namespace of PATH with those. |
|
Closing for now as this needs more work but there is no time available right now, I will reopen once I get the chance to work on it again. |
mvo5 commentedJan 10, 2017
•
Edited 1 time
-
mvo5
Jan 10, 2017
This adds support for dbus activation for snaps. This version is session bus only but it will be relatively easy to extend for the system bus. It implements the proposal from Jamie in #2592 (comment) and the snap.yaml for an application that implements io.snapcraft.SnapDbusService will look something like this (
service: trueis the key here):LP: #1648990