interfaces/builtin: add dbus interface #1613

Merged
merged 111 commits into from Dec 15, 2016

Conversation

Projects
None yet
8 participants
Contributor

jdstrand commented Aug 1, 2016

This implements dbus in support of 'Apps can't own session bus names'. Specifically, snaps may specify a slot using this interface which grants access to bind. Eg:

name: gnome-logs-udt
slots:
  org.gnome.Logs:
    interface: dbus
    bus: session
    name: org.gnome.Logs

This will allow the snap to own the well-known bus name 'org.gnome.Logs' and also integrate into the classic environment.

This also implements the plugs side and allows other snaps to connect to the interface with something like:

name: foo
plugs:
  org.gnome.Logs:
    interface: dbus
    bus: session
    name: org.gnome.Logs

Upon snap connect, this allows foo to introspect gnome-logs-udt and use its entire DBus API.

Please see #1613 (comment) for complete details.

LP: #1590679

jdstrand added some commits Jun 22, 2016

BROKEN TEST BRANCH
Test with:
 go build -tags=excludeintegration -v github.com/snapcore/snapd/... && go test
-tags=excludeintegration -v github.com/snapcore/snapd/interfaces/builtin
add dbus-app interface (LP: #1590679)
This implements dbus-app in support of 'Apps can't own session bus names'. An
exploratory PR is found in: #1446 but
that PR was more than what is needed and it was decided we should implement a
subset of that to address the bug, and then revisit once more details are
available.

In this PR, we allow dbus binding to a well-known name via 'slots':

  name: gnome-logs
  slots:
    interface: dbus-app
    session:
    - org.gnome.Logs

We use 'slots' instead of plugs because the application performing the bind on
dbus is providing a service end point for other dbus processes to communicate
with. In this implementation, on all-snaps systems we do not allow any
communications to the slot implementation (ie, it may only bind, not send or
receive) and on classic systems we allow communications from unconfined
processes to the slot implementation. In this manner, the snap using this
interface can bind to the dbus well-known name without issue (permanent slot
is unconditionally granted) and it may operate as a leaf application in
all-snaps and classic environments.

Since we know that some applications bind to multiple well-known names, an app
is allowed to bind to any number of names.

Since well-known DBus names are rather free-form and don't typically conform to
the snap name and because snaps sometimes need to bind to multiple well-known
names, the store will ensure that snaps from different publishers do not
collide. Future assertions work will allow for auto-approval in the store once
the assertion is granted for binding to the well-known name. In the short term,
we will adjust the review tools to do an easy check (if snapname is 'foo' and
bind interface is 'bar', auto-approve, else manual).

t#
interfaces/builtin/dbus_app.go
+ }
+ switch securitySystem {
+ case interfaces.SecurityAppArmor:
+ var snippets bytes.Buffer
@mvo5

mvo5 Aug 3, 2016

Collaborator

(nitpick) You could write snippets := bytes.NewBufferString("") here to save 1 line.

@jdstrand

jdstrand Aug 3, 2016

Contributor

Done

interfaces/builtin/dbus_app.go
+}
+
+// verify that name for bus is in list
+func (iface *DbusAppInterface) verifyNameInAttributes(bus string, name string, attribs map[string]interface{}) bool {
@mvo5

mvo5 Aug 3, 2016

Collaborator

Am I overlooking something or is this function not used?

@jdstrand

jdstrand Aug 3, 2016

Contributor

No, you are right. This was used in the dbus-bind exploratory PR for making sure the slot side had an entry for the specified plug side. Removed. Thanks!

interfaces/builtin/dbus_app.go
+}
+
+func (iface *DbusAppInterface) AutoConnect() bool {
+ return false
@mvo5

mvo5 Aug 3, 2016

Collaborator

Maybe this just shows my ignorance, but earlier we have +Auto-Connect: yes in the docs and false here. Or is this a different auto-connect?

@jdstrand

jdstrand Aug 3, 2016

Contributor

Since we are implementing this in the permanent slot side it is auto-generated. There is no plugs side atm.

interfaces/builtin/dbus_app_test.go
+ c.Assert(err, IsNil)
+}
+
+/*
@mvo5

mvo5 Aug 3, 2016

Collaborator

Maybe a comment here why it is commented out?

@jdstrand

jdstrand Aug 3, 2016

Contributor

Oh, this was meant to be removed-- it's a test for the plug side of the connection which we don't support in this PR (perhaps in the future as per description in this PR).

Collaborator

mvo5 commented Aug 3, 2016

Looks good, thanks for doing this work! Some questions inline but no blockers.

jdstrand added some commits Aug 3, 2016

Contributor

jdstrand commented Aug 3, 2016

@mvo5 I think I addressed all your questions. Thanks for the review! :)

interfaces/builtin/dbus_app.go
+ return err
+}
+
+// Since we only implement the permanent slot side, this is meaningless but
@mvo5

mvo5 Aug 4, 2016

Collaborator

Thanks for this comment!

Collaborator

mvo5 commented Aug 4, 2016

Nice! Thank you, 👍

@niemeyer niemeyer changed the title from add dbus-app interface (LP: #1590679) to interfaces/builtin: add dbus-object interface Aug 6, 2016

@niemeyer niemeyer changed the title from interfaces/builtin: add dbus-object interface to interfaces/builtin: add dbus-app interface Aug 6, 2016

Contributor

niemeyer commented Aug 6, 2016

@jdstrand I still feel like there's something not quite right with this interface. The whole logic seems to focus on the slot side only. What happens if someone connects to that slot? How do we even hint to the user what can be connected to what? How do we prevent an app from binding? If the answers are respectively "nothing", "we don't know", and "we can't", then something is wrong.

Taking our lesson from our conversation this week, isn't this actually more correct as dbus-support, where the settings are done on the plug side, and only when actually granted via a connection?

Contributor

niemeyer commented Aug 8, 2016

(s/dbus-setup/dbus-support)

@niemeyer niemeyer added the Decaying label Aug 26, 2016

@jdstrand jdstrand removed the Decaying label Sep 13, 2016

Contributor

niemeyer commented Nov 22, 2016

Well, even if it can, that won't quite work as intended, as we won't be able to match plug and slot.

I don't understand how this is being used.. how can it use a pid as part of the interface? What is the dbus client doing to talk to this server object?

This misunderstanding may be fundamental to the overall feature, so let's make sure to clear this up before we move on with this plan.

Contributor

jdstrand commented Nov 22, 2016

@niemeyer - ktuberling is abusing the dbus bind and only having part of the name be well-known. I'm guessing the reason why they are doing this is so that multiple instances of ktuberling can be launched within the same session and that KDE apps will register themselves with some KDE service and other KDE apps will use that service to find the various ktuberlings to access them. GNOME doesn't do this and is compliant with the freedesktop.org spec: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names

I don't think this a misunderstanding of the overall feature, but we have to think about how to support it for KDE. We can support the pid in the security policy by simply adding an alternation to the rule. Eg, slot still has 'name: org.kde.ktuberling' but we create a bind rule for it like so:

dbus (bind)
    bus=session
    name=org.kde.ktuberling{,-[1-9],-[1-9][0-9],-[1-9][0-9][0-9],-[1-9][0-9][0-9][0-9],-[1-9][0-9][0-9][0-9][0-9],-[1-9][0-9][0-9][0-9][0-9][0-9]},

While it is somewhat unfortunate the way that KDE apps are misusing the well-known name, we can deal with it. There is a problem with the above in that if KDE creates 'ktuberling' and I create 'ktuberling-12345' then due to the above rules, we have an overlap:

snap             PID   allowed
------------------------------
ktuberling       12345 org.kde.ktuberling,org.kde.ktuberling-1,...,org.kde.ktuberling-12345
ktuberling-12345 ...   org.kde.ktuberling-12345,org.kde.ktuberling-12345-1,...

This could be addressed in a few ways:

  • we ignore the overlap
  • we don't allow the name attribute to end with -[0-9]+
  • we don't worry about it in snapd and handle it in the store

UPDATE:
Other things that could be considered:

  • add an attribute to flag when to add the pid alternation to the rule
  • only add the pid alternation to the rule on classic
Contributor

jdstrand commented Nov 22, 2016

I suggest for the time being we don't allow the name attribute to end with -[0-9]+ in snapd and add the alternation unconditionally then see how real people use the interface.

Contributor

jdstrand commented Nov 22, 2016

FYI, the introspection rule needs to be adjusted for KDE, but that is less of a concern.

jdstrand added some commits Nov 22, 2016

Contributor

jdstrand commented Nov 22, 2016

In the interest of time, I've added an alternation rule that allows KDE snaps to bind to org.kde.foo-12345 when they simply declare 'name: org.kde.foo'. I also adjusted ValidateDBusBusName() in interfaces/core.go to not allow bus names ending with '-[0-9]+'. These changes can be easily reverted if needed or iterated on in the future.

jdstrand added some commits Nov 22, 2016

A few more comments, but the direction and approach LGTM. Thanks for pushing this forward.

+ deny-connection:
+ slot-attributes:
+ name: .+
+ deny-auto-connection: true
@niemeyer

niemeyer Nov 22, 2016

Contributor

Why the denials here? This will prevent people from using the interface altogether until explicitly allowed.

@jdstrand

jdstrand Nov 22, 2016

Contributor

Yes. I thought we agreed that people would need to have their well-known dbus name approved in the store. This will trigger a manual review and enforce the use of a snap declaration to claim the well-known name.

@jdstrand

jdstrand Nov 22, 2016

Contributor

Also, it doesn't prevent them from using the interface-- they can install a snap that slots this interface and get the bind rules needed to run; it is just that you can't connect other snaps to this interface until a snap declaration is present.

I think this is the right approach for this interface and others in general, but it sheds a light on a developer pain point as described in https://bugs.launchpad.net/snappy/+bug/1640874. I think this is perhaps solved by a locally signed snap declaration, but that needs your input (see my comments in the bug).

@niemeyer

niemeyer Nov 23, 2016

Contributor

We'd indeed not auto-connect it, but is there a reason to disable the manual connection altogether? This would be the user manually establishing the allowance for dbus communication between two separate snaps, over a well defined API. I can't quite see why we'd reject this yet.

@jdstrand

jdstrand Nov 23, 2016

Contributor

We reject it because the snap has not claimed the name via the store and doesn't yet have a snap declaration. On initial upload with using this interface, the reviewer will issue a snap declaration that allows the connection, and then all is well for connections and subsequent uploads. I think this is in line with everything we've discussed regarding the base declaration and snap declarations-- slot implementations (typically) need some sort of snap declaration since they (typically) allow privileged access to the system. In this case the privilege is to claim a well-known name.

On a related note, the review tools are now considering the base declaration and prompting for manual review due to this constraint. If we remove the constraint today, the tools would let this through without prompting for manual review. If we decide that the constraint should not be there, then the tools will have to be modified to prompt for manual review based on factors outside of the base declaration for an interface review (which is totally doable, but I thought one goal of the base declaration was to be the one place the review tools would consult for how interface reviews should be performed).

interfaces/builtin/dbus.go
+func (iface *DbusInterface) getAttribs(attribs map[string]interface{}) (string, string, error) {
+ bus := ""
+ name := ""
+ for attr := range attribs {
@niemeyer

niemeyer Nov 22, 2016

Contributor

This is a map.. we can access the attributes we care about directly with the following, and drop all the code below I think:

bus, ok := attribs["bus"].(string)
if !ok {
    ... error ...
}
name, ok := attribs["name"].(string)
if !ok {
    ... error ...
}
@jdstrand

jdstrand Nov 22, 2016

Contributor

Fixed

interfaces/builtin/dbus.go
+ name := ""
+ for attr := range attribs {
+ if attr != "bus" && attr != "name" {
+ return "", "", fmt.Errorf("unknown attribute '%s'", attr)
@niemeyer

niemeyer Nov 22, 2016

Contributor

This breaks future compatibility, meaning we cannot ever add anything at all in this interface without it permanently breaking every old client. In practice, this renders the interface completely immutable, so it's something we should try to avoid on this and in other interfaces I think.

@jdstrand

jdstrand Nov 22, 2016

Contributor

Fixed

interfaces/builtin/dbus.go
+ snippet = bytes.Replace(snippet, old, new, -1)
+
+ // convert name to AppArmor dbus path (eg 'org.foo' to '/org/foo{,/**}')
+ dot_re := regexp.MustCompile("\\.")
@niemeyer

niemeyer Nov 22, 2016

Contributor

Isn't that just Replace(sinppet, ".", "/", -1)?

That said, why is it doing that? Isn't DBUS_PATH supposed to be gone? I thought we had agreed not to guess?

@jdstrand

jdstrand Nov 22, 2016

Contributor

I found that I couldn't get rid of dbus path entirely due to snaps using the org.freedesktop.* interfaces and so instead have:

dbus (receive, send)
    bus=###DBUS_BUS###
    interface=###DBUS_INTERFACE###
    peer=(label=###SLOT_SECURITY_TAGS###),
                                                     
dbus (receive, send)
    bus=###DBUS_BUS###
    path=###DBUS_PATH###
    peer=(label=###SLOT_SECURITY_TAGS###),  

This allows us to match by either interface (and security peer label) or path (and security peer label) and avoids the problems of trying to specify them both at the same time. In other words, still have the rule you mention that doesn't require guessing, but then have another rule that allows using the org.freedesktop.DBus interface in predictable ways.

I discussed this with @tyhicks and we agreed on the approach.

@niemeyer

niemeyer Nov 23, 2016

Contributor

That still doesn't look right. This is an interface name, not a path. If we are translating from one to the other, we're doing guess work which can be wrong, because after all those are two different fields in the dbus system instead of a single one. If the interface matching doesn't work for some cases which require a path instead of an interface, let's please raise that and talk about it in those terms.

@jdstrand

jdstrand Dec 6, 2016

Contributor

@niemeyer - It's actually a well-known connection name, not an interface name nor a path. I am deriving the path and the interface from the well-known name because this interface in its current form is not meant to cover the entire dbus system, but rather traditional desktop applications until we better understand what people want from this interface for the entire dbus system. This should work for observed applications from GNOME and KDE and is consistent with how these toolkits setup the DBus interfaces. For example, GNOME applications use DBUS_PATH very regularly and this implementation allows them to work. The goal of this PR as implemented is to a) allow dbus binds to work and b) allow connections to work when the dbus api is well-formed.

A benefit of this approach as implemented is that the dbus interface and path is namespaced to the slot implementation (eg, bind to org.foo.bar but can't offer interfaces for org.baz.norf or paths on /org/baz/norf). This seems reasonable considering that we don't quite yet know the future of this interface.

We could simply allow the bind to the well-known name and then only mediate on the peer label for connected snaps like this:

# ConnectedPlug
dbus (receive, send)
  bus=session
  peer=(name=###DBUS_NAME###,label=###SLOT_SECURITY_TAGS###),

which is exactly what I think you are asking for on the plugs side. Unfortunately in practice dbus accesses are never quite this clean. For example, we can't use a similar rule on the slots side because the 'name' used in the peer (connecting client) is not well-known and is of the form ':[0-9]*' so the slot side needs this rule:

# ConnectedSlot
dbus (receive, send)
  bus=session
  peer=(label=###PLUG_SECURITY_TAGS###),

which means that the slot can send and receive any interface, path and method to a connected client. There is a mismatch here I'd rather avoid if we can: the plugging side can send and receive to a process with the slot's label and well-known name, but the slot can send anything to a process with the plug's label which is imprecise and makes the policy somewhat ambiguous, both of which make me little uncomfortable considering that unlike privileged slot implementations, snaps slotting this interface are not meant to be considered particularly special beyond claiming the well-known name. Maybe in the future we can add 'interface' and 'path' attributes that will be used instead of our derived paths; maybe the ambiguous rules are where we want to go instead. For the first cut I was leaning towards usable but slightly stricter since it is easier to add wider permissions, but it would be impossible to remove them later.

(I'm pretty sure both sides are going to need more than the above two rules anyway. I'll take a look at that next.)

@jdstrand

jdstrand Dec 6, 2016

Contributor

"(I'm pretty sure both sides are going to need more than the above two rules anyway. I'll take a look at that next.)"

Indeed, on the plugs side I tried just this rule:

dbus (receive, send)
    bus=session
    peer=(name=###DBUS_NAME###, label=###SLOT_SECURITY_TAGS###),

and found that the org.gtk.Actions and org.gtk.Menus interfaces were blocked to connected snaps due to the missing ###DBUS_PATH### rule because the communication is happening via a non-well-known, non-snap-specific connection name. Without ###DBUS_PATH###, this rule is required:

dbus (send)
    bus=session
    peer=(name=:[0-9].[0-9]*, label=###SLOT_SECURITY_TAGS###),

The corresponding slot side rule becomes:

dbus (receive)
    bus=session
    peer=(name=:[0-9].[0-9]*, label=###SLOT_SECURITY_TAGS###),

The two combined mean that the plugging side can connect to the slot side via any non-well-known connection name with any interface, path or method.

With these rules that omit interface and path, the snaps are allowed to communicate with each other but the mediation is imprecise due to overlapping mediation. Eg, with the following slot yaml:

slots:
  foo:
    bus: session
    name: org.foo
  bar:
    bus: session
    name: org.bar

apps:
  daemon:
    slots: [ foo, bar ]

and the following plug yaml:

plugs:
  foo:
    bus: session
    name: org.foo

and then snap connect plugging-snap:foo slotting-snap:foo results in this on the plugs side:

dbus (receive, send)
    bus=session
    peer=(name=org.foo, label=snap.slotting-snap.daemon),

dbus (send)
    bus=session
    peer=(name=:[0-9].[0-9]*, label=snap.slotting-snap.daemon),

which means that the plugging snap can talk to daemon service's non-well-known connection names corresponding to the org.bar service. This is precisely why I wanted the interface and path rules.

We have a few choices:

  1. stick with the current PR with the two extra derived interface and path rules. This allows all communications via the well-known connection name as well as non-well-known connection names when a path or interface corresponds with the well-known name
  2. only allow the plugs side to use peer=(name=###DBUS_NAME###, ###SLOT_SECURITY_TAGS###), remove the non-well-known rule from the plugging side, but leave it in the slot side
  3. use ambiguous rules as described in this comment

'1' is strict but allows both sides and works with GNOME and KDE. '2' is stricter on the plugs side which allows communications only via the well-known name. '3' works but is lenient regarding non-well-known connection name communications.

I prefer 1 or 2. If we go with '2', we will break certain DBus APIs (eg, org.gtk.Menus, org.gtk.Actions) and the future use of these slot snaps in snapped desktop environments like unity, gnome and plasma, but we can document this today and add other interfaces (or augment this one) to handle these cases when the need arises (of course, then the combined rules will be similar to what we have with '1').

(Note: the issues surrounding this is because we are applying the untrusted app trust model onto applications designed for the user session trust model).

@niemeyer

niemeyer Dec 12, 2016

Contributor

Okay, sounds fine. We can always include an additional path field if we need to be explicit.

+ new = pathBuf.Bytes()
+ snippet = bytes.Replace(snippet, old, new, -1)
+
+ // convert name to AppArmor dbus interface (eg, 'org.foo' to 'org.foo{,.*}')
@niemeyer

niemeyer Nov 22, 2016

Contributor

Comment looks bogus.

@jdstrand

jdstrand Nov 22, 2016

Contributor

No, it is accurate. It takes 'name' which might be 'org.foo' and turns it into the glob 'org.foo{,.*}'.

@niemeyer

niemeyer Nov 23, 2016

Contributor

Sorry, indeed.

interfaces/builtin/dbus.go
+ }
+
+ // ensure that we only connect to slot with matching attributes
+ if bus != busPlug || name != namePlug {
@niemeyer

niemeyer Nov 22, 2016

Contributor

This should be an error. We should not even allow that connection to be established.

@jdstrand

jdstrand Nov 22, 2016

Contributor

I initially had this as an error but throwing an error here means that situations like TestConnectionFirst() and TestConnectionSecond() in dbus_test.go throw an error, but they should not. I'm trying to discard 'the other' one so just skip it. Is there a better way?

@niemeyer

niemeyer Nov 23, 2016

Contributor

Why they should not? I feel like I'm missing something important, perhaps unrelated to this PR. If we're running the connection code for things which are incompatible and that should not be auto-connected, erroring seems sane. If it's blowing up in a place it shouldn't, definitely curious to hear about it.

@jdstrand

jdstrand Dec 5, 2016

Contributor

@niemeyer - I haven't looked at this terribly closely, but it looks like this is cause by ResolveConnect() in interfaces/repo.go:

func (r *Repository) ResolveConnect(plugSnapName, plugName, slotSnapName, slotName string) (ConnRef, error) {
...
        if slotName == "" {                                                     
                // Find the unambiguous slot that satisfies plug requirements   
                var candidates []string                                         
                for candidateSlotName, candidateSlot := range r.slots[slotSnapName] {
                        // TODO: use some smarter matching (e.g. against $attrs)
                        if candidateSlot.Interface == plug.Interface {          
                                candidates = append(candidates, candidateSlotName)
                        }                                                       
                }

Note // TODO: use some smarter matching (e.g. against $attrs) - it isn't matching against attrs so the interface code must when matching like this. This makes sense with my observations that I could not error and instead must silently ignore attribute mismatches since Connected*Snippet was getting each combination.

@niemeyer

niemeyer Dec 12, 2016

Contributor

Okay, we need to look into the auto-connection rule based on attributes soon anyway, so let's not block on this.

@pedronis

pedronis Dec 12, 2016

Contributor

yes, it looks like that code in repo.go should use the policy checks, especially once we have attribute comparision

@pedronis

pedronis Dec 13, 2016

Contributor

@jdstrand could you replace that TODO there with use interface/policy Checkers

@jdstrand

jdstrand Dec 13, 2016

Contributor

Added a TODO comment

interfaces/builtin/dbus.go
+}
+
+// Since we only implement the permanent slot side, this is meaningless but
+// we have to supply the method, so set it to something safe.
@niemeyer

niemeyer Nov 22, 2016

Contributor

"true" doesn't seem very safe. :)

@jdstrand

jdstrand Nov 22, 2016

Contributor

Oh, that comment is bogus. It is a holdover from LegacyConnect and earlier iterations of this interface. Removed.

jdstrand added some commits Nov 22, 2016

+<policy context="default">
+ <allow send_destination="###DBUS_NAME###"/>
+</policy>
+`
@jdstrand

jdstrand Nov 23, 2016

Contributor

dbusPermanentSlotDBus wasn't in the original PR but it is needed for 'bus=system'. @niemeyer and @tyhicks: the question then becomes, should non-root be allowed to talk to it? As implemented (in a separate commit for easy reverting), the answer is 'yes' because snapd should allow a non-root snap command to be able to connect to a root daemon within the same command and it is up to the snap to perform any uid checks. However, because of the dbusPermanentSlotAppArmorClassic policy for on classic (and the lack of policykit in the core snap) this means that unconfined non-root can talk to the snap-- this needs discussion but consider that this interface does alone not grant extra access to the system, b) this interface in combination with privileged ones (eg, network-control) requires a user to connect the interfaces and c) unconfined non-root can aa_change_profile() to something that would allow the access.

'b' is somewhat mediated in the initial store upload since the base declaration requires that to use this interface you need to claim the name and receive a snap declaration. However, the tools aren't considering combinations of interfaces and it would be a shame if a root-running system service slot implementation has say, network-control and exposes a DBus API but doesn't perform uid checks so non-root users could escalate privileges to change networking via the poorly implemented snap. Granted, there is nothing saying this poorly written snap couldn't expose a named unix socket in SNAP_DATA world read/write and do the same thing without this interface, so this is probably one of those things where this poorly written snap gets poor reviews and/or bug report and we exercise store policies to compel the developer to fix it (or revoke the snap).

@niemeyer and @tyhicks, put simply, should non-root be able to contact a dbus system service via this interface?

(sorry for the second email on this, I made a change and the comment was hidden so I deleted the first and added this one)

jdstrand added some commits Dec 5, 2016

@pedronis pedronis self-assigned this Dec 12, 2016

jdstrand added some commits Dec 12, 2016

Contributor

jdstrand commented Dec 12, 2016

The trusty issue is unrelated to this PR:
Exit request sent.
blame: https://github.com/snapcore/snapd.git#refs/pull/1613/head
badpkg: Test dependencies are unsatisfiable. A common reason is that your testbed is out of date with respect to the archive, and you need to use a current testbed or run apt-get update or use -U.
autopkgtest [21:00:56]: ERROR: erroneous package: Test dependencies are unsatisfiable. A common reason is that your testbed is out of date with respect to the archive, and you need to use a current testbed or run apt-get update or use -U.

@jdstrand jdstrand added this to the 2.20 milestone Dec 13, 2016

Contributor

jdstrand commented Dec 13, 2016

Added the 2.20 milestone since AIUI all this needs is one more review and this interface has been lingering. @pedronis - AIUI you will be doing that review, is that correct?

+}
+
+// Obtain yaml-specified bus well-known name
+func (iface *DbusInterface) getAttribs(attribs map[string]interface{}) (string, string, error) {
@pedronis

pedronis Dec 13, 2016

Contributor

would be better to use named results here: (bus, name string, err error)

interfaces/builtin/dbus.go
+ return "", "", fmt.Errorf("DBus bus name must not end with -NUMBER")
+ }
+
+ if bus == "" {
@pedronis

pedronis Dec 13, 2016

Contributor

I'm confused by these checks, it doesn't seem that bus == "" would pass the previous check being equal session or system, same for name == "" and ValidateDBusName ?

@jdstrand

jdstrand Dec 13, 2016

Contributor

@pedronis, you are right. These are a holdover from a refactor. Removed.

interfaces/builtin/dbus.go
+ dot_re := regexp.MustCompile("\\.")
+ var pathBuf bytes.Buffer
+ pathBuf.WriteString(`"/`)
+ pathBuf.WriteString(dot_re.ReplaceAllString(name, "/"))
@pedronis

pedronis Dec 13, 2016

Contributor

the bit stands that this could be just strings.Replace(name, ".", "/", -1)

@jdstrand

jdstrand Dec 13, 2016

Contributor

Done

interfaces/builtin/dbus.go
+ }
+
+ // ensure that we only connect to slot with matching attributes
+ if bus != busPlug || name != namePlug {
@niemeyer

niemeyer Nov 22, 2016

Contributor

This should be an error. We should not even allow that connection to be established.

@jdstrand

jdstrand Nov 22, 2016

Contributor

I initially had this as an error but throwing an error here means that situations like TestConnectionFirst() and TestConnectionSecond() in dbus_test.go throw an error, but they should not. I'm trying to discard 'the other' one so just skip it. Is there a better way?

@niemeyer

niemeyer Nov 23, 2016

Contributor

Why they should not? I feel like I'm missing something important, perhaps unrelated to this PR. If we're running the connection code for things which are incompatible and that should not be auto-connected, erroring seems sane. If it's blowing up in a place it shouldn't, definitely curious to hear about it.

@jdstrand

jdstrand Dec 5, 2016

Contributor

@niemeyer - I haven't looked at this terribly closely, but it looks like this is cause by ResolveConnect() in interfaces/repo.go:

func (r *Repository) ResolveConnect(plugSnapName, plugName, slotSnapName, slotName string) (ConnRef, error) {
...
        if slotName == "" {                                                     
                // Find the unambiguous slot that satisfies plug requirements   
                var candidates []string                                         
                for candidateSlotName, candidateSlot := range r.slots[slotSnapName] {
                        // TODO: use some smarter matching (e.g. against $attrs)
                        if candidateSlot.Interface == plug.Interface {          
                                candidates = append(candidates, candidateSlotName)
                        }                                                       
                }

Note // TODO: use some smarter matching (e.g. against $attrs) - it isn't matching against attrs so the interface code must when matching like this. This makes sense with my observations that I could not error and instead must silently ignore attribute mismatches since Connected*Snippet was getting each combination.

@niemeyer

niemeyer Dec 12, 2016

Contributor

Okay, we need to look into the auto-connection rule based on attributes soon anyway, so let's not block on this.

@pedronis

pedronis Dec 12, 2016

Contributor

yes, it looks like that code in repo.go should use the policy checks, especially once we have attribute comparision

@pedronis

pedronis Dec 13, 2016

Contributor

@jdstrand could you replace that TODO there with use interface/policy Checkers

@jdstrand

jdstrand Dec 13, 2016

Contributor

Added a TODO comment

+
+func (iface *DbusInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool {
+ // allow what declarations allowed
+ return true
@pedronis

pedronis Dec 13, 2016

Contributor

the check // ensure that we only connect to slot with matching attributes

  • if bus != busPlug || name != namePlug {

should probably be done here? instead?

that's what content is doing atm btw, until we don't have support for specifying attribute matching in the basedecl

@jdstrand

jdstrand Dec 13, 2016

Contributor

AutoConnect() isn't the right place for the check for the dbus interface because the check needs to happen at connection, not auto connection and at connection we only connect when bus and name match. The content interface is different in that while the content attribute must match for auto-connect, the content attribute is not used as part of the connected snippets, only read and write which aren't part of the content interface's plugging attributes.

Note that the dbus interface design and its attributes are approved.

jdstrand added some commits Dec 13, 2016

Contributor

jdstrand commented Dec 13, 2016

@pedronis, I believe I addressed your comments.

jdstrand added some commits Dec 13, 2016

Contributor

jdstrand commented Dec 13, 2016

@pedronis and I discussed the 'if bus != busPlug || name != namePlug' part on irc and agreed that what is implemented is the best we can do without the attribute matching. The code will ensure that the correct security policy is generated. I updated the comment to reflect this.

Contributor

jdstrand commented Dec 13, 2016

@pedronis and @tyhicks - I believe I addressed all your comments. Can you give a +1 or more feedback if needed?

Thanks for your reviews!

looks reasonable given the current infra limitations

Contributor

tyhicks commented Dec 13, 2016

The security policy looks good to me. Thanks for making the adjustments we spoke about over IRC.

Contributor

jdstrand commented Dec 13, 2016

@niemeyer - I believe this is mergeable now based on my incorporating feedback from you, @pedronis, and @tyhicks. Please consider approving the changes pending the testsuite run.

Contributor

jdstrand commented Dec 14, 2016

The travis testsuite failure is unrelated:
2016/12/14 00:37:47 Failed tasks: 1
- linode:ubuntu-core-16-64:tests/main/revert-devmode:production

Contributor

jdstrand commented Dec 14, 2016

The trusty failure is unrelated:
Exit request sent.
autopkgtest [01:12:08]: ERROR: erroneous package: Test dependencies are unsatisfiable. A common reason is that your testbed is out of date with respect to the archive, and you need to use a current testbed or run apt-get update or use -U.
blame: https://github.com/snapcore/snapd.git#refs/pull/1613/head
badpkg: Test dependencies are unsatisfiable. A common reason is that your testbed is out of date with respect to the archive, and you need to use a current testbed or run apt-get update or use -U.

Contributor

jdstrand commented Dec 14, 2016

The zesty failures are unrelated:

2016/12/14 01:22:14 Error executing autopkgtest:ubuntu-16.04-64:tests/main/server-snap:pythonServer : 
-----
++ nc -4 localhost 80
+ response=
+ statusPattern='(?s)HTTP\/1\.0 200 OK\n*'
+ grep -Pzq '(?s)HTTP\/1\.0 200 OK\n*'
+ echo ''
2016/12/14 01:11:22 Error executing autopkgtest:ubuntu-16.04-64:tests/main/interfaces-network-bind : 
-----
+ . /home/gopath/src/github.com/snapcore/snapd/tests/lib/names.sh
+++ sed -n 's/^\(pc\|pi[23]\|dragonboard\) .*/\1/p'
+++ snap list
++ gadget_name=
++ kernel_name=-kernel
+++ awk '/^(ubuntu-)?core / {print $1; exit}'
+++ snap list
++ core_name=core
++ '[' -kernel = pi3-kernel ']'
+ CONNECTED_PATTERN='(?s)Slot +Plug\n.*?\n:network-bind +network-bind-consumer'
+ DISCONNECTED_PATTERN='(?s)Slot +Plug\n.*?\n- +network-bind-consumer:network-bind'
+ echo 'Then the snap is listed as connected'
Then the snap is listed as connected
+ grep -Pzq '(?s)Slot +Plug\n.*?\n:network-bind +network-bind-consumer'
+ snap interfaces
+ echo ============================================
============================================
+ echo 'When the plug is disconnected'
When the plug is disconnected
+ snap disconnect network-bind-consumer:network-bind core:network-bind

[|] Disconnect network-bind-consumer:network-bind from core:network-bind�[K
�[K+ grep -Pzq '(?s)Slot +Plug\n.*?\n- +network-bind-consumer:network-bind'
+ snap interfaces
+ echo 'Then the plug can be connected again'
Then the plug can be connected again
+ snap connect network-bind-consumer:network-bind core:network-bind

[|] Connect network-bind-consumer:network-bind to core:network-bind�[K
�[K+ grep -Pzq '(?s)Slot +Plug\n.*?\n:network-bind +network-bind-consumer'
+ snap interfaces
+ echo ============================================
============================================
+ echo 'When the plug is connected'
When the plug is connected
+ snap connect network-bind-consumer:network-bind core:network-bind

[|] Connect network-bind-consumer:network-bind to core:network-bind�[K
�[K+ grep -Pzq '(?s)Slot +Plug\n.*?\n:network-bind +network-bind-consumer'
+ snap interfaces
+ echo 'Then the service is accessible by a client'
Then the service is accessible by a client
+ grep -Pqz 'ok\n'
+ nc -w 2 localhost 8081
2016/12/14 01:22:18 Error executing autopkgtest:ubuntu-16.04-64:tests/main/server-snap:goServer : 
-----
++ nc -6 ip6-localhost 8081
+ response=
+ statusPattern='(?s)HTTP\/1\.0 200 OK\n*'
+ grep -Pzq '(?s)HTTP\/1\.0 200 OK\n*'
+ echo ''
Contributor

jdstrand commented Dec 14, 2016

Retrying the travis tests since there seem to be intermittent failures.

+ bus=###DBUS_BUS###
+ path=/org/freedesktop/DBus
+ interface=org.freedesktop.DBus
+ member="GetConnectionUnix{ProcessID,User}"
@jhenstridge

jhenstridge Dec 14, 2016

Contributor

Would it make sense to also include org.freedesktop.DBus.GetConnectionCredentials here? That seems to be the replacement for the above two if you need both.

@jdstrand

jdstrand Dec 14, 2016

Contributor

That is a good point. Done

+ var ifaceBuf bytes.Buffer
+ ifaceBuf.WriteString(`"`)
+ ifaceBuf.WriteString(name)
+ ifaceBuf.WriteString(`{,.*}"`)
@jhenstridge

jhenstridge Dec 14, 2016

Contributor

This might make sense as a default value, but interface names are not the same as bus names in the general case. For my work on storage-framework, we have a number of daemons that all implement the same D-Bus interface, but will register on the bus with different well known names.

I believe MPRIS works similarly.

Also, what if the service exposes method calls or signals on more than one interface? I imagine it'd be quite common for an app to want to respond to method calls on the org.freedesktop.DBus.Properties interface, but I think it'd be a mistake to simply special case it.

@jdstrand

jdstrand Dec 14, 2016

Contributor

As mentioned on the mailing list: https://lists.ubuntu.com/archives/snapcraft/2016-December/001998.html this initial implementation of the interface intentionally does not address complex dbus services. It is meant to address leaf applications (eg, GNOME and KDE) that bind to a well-known name.

I'd prefer not to delay this PR any longer by adding more functionality to it. @niemeyer and I agreed in this PR that we would implement what we have and then see how people are using it and potentially add DBus paths, interfaces, activation, complex bus policy, etc down the line. Do keep in mind that generic interfaces like this one are a new concept in snappy that is in some ways counter to the initial goals of interfaces, and we want to make sure we design and implement them in a controlled fashion.

As for storage framework in particular, if you haven't already, I suggest reading the aforementioned thread. At this time, it will need to be its own interface (and that's ok-- it's easy getting interfaces into snappy :). Perhaps at a future date the dbus interface will get to the point to support all DBus services generically, but not just yet.

@jhenstridge

jhenstridge Dec 14, 2016

Contributor

If you don't want to make it configurable, then I'd strongly consider allowing at least org.freedesktop.DBus.Properties as an extra one unconditionally. Without it, it is impossible to access APIs using the standard D-Bus property system.

To get property "A" on interface "I", you call org.freedesktop.DBus.Properties.Get("I", "A") -- not a method call on the interface declaring the property.

@jdstrand

jdstrand Dec 14, 2016

Contributor

To be clear, we do want to make it configurable-- at some point. There is an urgency to this PR as it is blocking GNOME and KDE devs from targeting stable. This PR specifically addresses that use case. Other use cases can come in other PRs.

As for Properties, this is already handled by the path rule without an interface:

# allow connected snaps to all interfaces via ###DBUS_PATH### (eg,
# org.freedesktop.*, org.gtk.Application, etc) to allow full integration with
# connected snaps.
dbus (receive, send)
    bus=###DBUS_BUS###
    path=###DBUS_PATH###
    peer=(label=###SLOT_SECURITY_TAGS###),

This was a tested condition for the leaf applications we are trying to address.

+ var pathBuf bytes.Buffer
+ pathBuf.WriteString(`"/`)
+ pathBuf.WriteString(strings.Replace(name, ".", "/", -1))
+ pathBuf.WriteString(`{,/**}"`)
@jhenstridge

jhenstridge Dec 14, 2016

Contributor

Similar to the comment about interface names, you can't necessarily derive the paths a D-Bus service uses from the bus name it will try to acquire. If you're going to restrict object paths (which seems a bit dubious), shouldn't it be configurable on the slot?

@jdstrand

jdstrand Dec 14, 2016

Contributor

See above comment. We intentionally left out path configuration in this initial implementation.

jdstrand added some commits Dec 14, 2016

Contributor

jdstrand commented Dec 14, 2016

Travis failure is unrelated: linode:ubuntu-core-16-64-fixme:tests/main/ubuntu-core-upgrade-no-gc

Trusty autopkgtest failure is unrelated

Zesty autopkgtest failures are unrelated (and same as before):
- autopkgtest:ubuntu-16.04-64:tests/main/interfaces-network-bind
- autopkgtest:ubuntu-16.04-64:tests/main/server-snap:goServer
- autopkgtest:ubuntu-16.04-64:tests/main/server-snap:pythonServer

Contributor

jdstrand commented Dec 14, 2016

From IRC:

12:58 < niemeyer> So do we support any interface on the well known name?
13:01 < jdstrand> we allow a connected snap to introspect our dbus api
13:01 < jdstrand> we allow a connected snap to use any dbus interface and any dbus 
                  path if they are connecting to the well-known name
13:02 < jdstrand> if they are using a non-well-known name, we allow connecting to any 
                  dbus path if the dbus interface is regular according to our 
                  well-known name
13:02 < niemeyer> Okay, that sounds reasonable
13:02 < niemeyer> Thanks for the long explanation
13:02 < jdstrand> if they are using a non-well-known name, we allow connecting with 
                  any dbus interface if the dbus path is regular accoriding to our 
                  well-known name
13:03 < niemeyer> Happy to go forward with it
13:03 < jdstrand> \o/
13:03 < niemeyer> Is the PR blocked on anything else at this point?
13:04 < jdstrand> niemeyer: no. tyhicks reviewed the security policy. pedronis 
                  reviewed the code. I incorporated all applicable feedback from 
                  everywhere
13:04 < niemeyer> So let's get it in
13:04 < jdstrand> thanks!
13:04 < niemeyer> Thank you!
13:06 < jdstrand> niemeyer: so with that, I plan to work with the personal folks on 
                  designing their interfaces (eg, storage framework) and will be 
                  reviewing tvoss' work for session services and will be thinking 
                  about how all of this relates to this interface, improvements we 
                  can make to this interface, etc and when I have coherent thoughts 
                  on it, will present phase ii, phase iii, etc, etc iterations to you

@niemeyer niemeyer changed the title from interfaces/builtin: add dbus interface (LP: #1590679) to interfaces/builtin: add dbus interface Dec 15, 2016

@mvo5 mvo5 merged commit c1ff793 into snapcore:master Dec 15, 2016

4 of 6 checks passed

trusty-amd64 autopkgtest finished (failure)
Details
zesty-amd64 autopkgtest finished (failure)
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
xenial-amd64 autopkgtest finished (success)
Details
xenial-i386 autopkgtest finished (success)
Details
yakkety-amd64 autopkgtest finished (success)
Details
Contributor

jdstrand commented Dec 15, 2016

Thanks especially @niemeyer for your time on this and thank you to all reviewers and commenters for your input and reviews! :)

@jdstrand jdstrand deleted the jdstrand:dbus-app branch Jan 9, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment