Implement lxd-client interface exposing the lxd snap (LP: #1634880) #2225

Merged
merged 7 commits into from Nov 15, 2016

Conversation

Projects
None yet
5 participants
Contributor

kalikiana commented Oct 27, 2016

interfaces/builtin/basedeclaration.go
+ allow-installation:
+ slot-snap-type:
+ - core
+ deny-auto-connection: false
@stgraber

stgraber Oct 31, 2016

Contributor

Not sure what that flag means, but if it means that other snaps will be allowed to auto-connect without special casing in the store, then this is a terrible idea.

Accessing the LXD socket is equivalent to having root access on the system. It lets you access everything on the filesystem and effectively bypass all seccomp and apparmor policies.

It's certainly fine for us to hardcode a few exceptions for well behaved, trusted snaps, but we should never allow a random snap to the store to connect to LXD without an explicit "snap connect" from the user.

interfaces/builtin/lxd_client.go
+}
+
+func (iface *LxdClientInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool {
+ return true
@stgraber

stgraber Oct 31, 2016

Contributor

Right, so again, if my understanding of how snapd works nowadays is correct, this should absolutely not be True.

Contributor

stgraber commented Oct 31, 2016

Left a few in-line comments. tl&dr is that we should NEVER allow an untrusted snap to connect to the LXD daemon.

Only snaps that we'd trust with full unconfined root privileges on the machine should be allowed to talk to connect to the lxd interface without requiring user interaction.

Access to the lxd socket allows you to alter the host network setup, access any character or block devices in /dev, bind-mount any path on the filesystem, load any apparmor policy and bypass any existing apparmor or seccomp policies.

I'd say that this interface should be changed to never auto-connect for now. Then the downstream snaps that want to use it can start using it by asking users to manually connect, then once those snaps are deemed stable enough and have been reviewed for safety, an exception can be added so that the store version of those snaps will auto-connect to the lxd interface.

Contributor

stgraber commented Oct 31, 2016

interfaces/builtin/lxd_client.go
+const lxdClientConnectedPlugSecComp = `
+# Description: Can manage socket to use the 'lxd' API.
+setsockopt
+bind
@stgraber

stgraber Oct 31, 2016

Contributor

Hmm, so those don't cover what you actually need to connect to a unix socket, which would be "socket()" and "connect()", rather than "setsockopt()" and "bind()".

Those two are the usual suspects for Go clients which will always call them when establishing a network connection. That's certainly fine, but also not really relevant for this interface.

Especially when you consider that all the needed bits are already in the auto-connectable "network" interface.

interfaces/builtin/basedeclaration.go
+ lxd-client:
+ allow-installation:
+ slot-snap-type:
+ - core
@jdstrand

jdstrand Nov 1, 2016

Contributor

After changing to a slot implementation, change this to:

lxd:
  allow-installation: false
  deny-connection: true
  deny-auto-connection: true  

This will require the slot side implementation to need a snap declaration to even be installed and requires a plugging snap to also need a snap declaration to connect to lxd. I fully agree with @stgraber's comments regarding how privileged this interface is, and adjusting the base declaration to do this addresses those concerns. You'll of course need to adjust the testsuite changes accordingly.

interfaces/builtin/lxd_client.go
+type LxdClientInterface struct{}
+
+func (iface *LxdClientInterface) Name() string {
+ return "lxd-client"
@jdstrand

jdstrand Nov 1, 2016

Contributor

This should be renamed as 'lxd' with the lxd snap adding 'slots: [ lxd ]' and clients using 'plugs: [ lxd ]'. See interfaces/builtin/docker.go for an example of what this might look like. We do this because as implemented, the 'lxd-client' interface is assuming there is something to connect to at /var/snap/lxd/common/lxd/unix.socket, but that is only there when the lxd snap is installed. Doing it as I suggest will allow the lxd snap to be auto-installed when installing a client that needs it (when that feature is implemented).

@stgraber

stgraber Nov 1, 2016

Contributor

Sounds good, though I think you meant "lxd" and "lxd-client" above rather than "lxc" and "lxc-client" :)

@jdstrand

jdstrand Nov 1, 2016

Contributor

Erf, yes. 'lxd'. I'll adjust the comment.

interfaces/builtin/lxd_client.go
+ return nil, nil
+ }
+ return nil, nil
+}
@jdstrand

jdstrand Nov 1, 2016

Contributor

I think you do want whatever seccomp rules are needed to talk to and use the socket without any other 'plugs'.

@stgraber

stgraber Nov 1, 2016

Contributor

From a basic netcat, we see:

socket(AF_LOCAL, SOCK_STREAM, 0)        = 3
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0
connect(3, {sa_family=AF_LOCAL, sun_path="/var/lib/lxd/unix.socket"}, 26) = 0
poll([{fd=3, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1
) = 1 ([{fd=0, revents=POLLIN}])
read(0, "\n", 2048)                     = 1
write(3, "\n", 1)                       = 1
poll([{fd=3, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=3, revents=POLLIN|POLLHUP}])
read(3, "HTTP/1.1 400 Bad Request\r\nConten"..., 2048) = 88
write(1, "HTTP/1.1 400 Bad Request\r\nConten"..., 88HTTP/1.1 400 Bad Request
Content-Type: text/plain
Connection: close

400 Bad Request) = 88
poll([{fd=3, events=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1 ([{fd=3, revents=POLLIN|POLLHUP}])
read(3, "", 2048)                       = 0
shutdown(3, SHUT_RD)                    = 0
close(3)                                = 0

So that's:

  • socket (included in base)
  • fcntl (included in base)
  • connect (included in base)
  • poll (included in base)
  • read (included in base)
  • write (included in base)
  • shutdown
  • close (included in base)

So looks like all that would be allowed for a basic unix socket connection would be "shutdown". Everything else being allowed already by default.

That obviously won't actually let you interact with the socket from something that's written in Go and does extra fancy socket setup, but that's specific to the client rather than the interface.

interfaces/builtin/lxd_client.go
+
+func (iface *LxdClientInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool {
+ return false
+}
@jdstrand

jdstrand Nov 1, 2016

Contributor

This should be:

// allow what declarations allowed
return true

which simply says that the interface won't override whatever the base and snap declarations allow, and with the suggested changes for the base declaration, this is the correct approach.

snap/implicit.go
@@ -36,6 +36,7 @@ var implicitSlots = []string{
"hardware-observe",
"locale-control",
"log-observe",
+ "lxd-client",
@jdstrand

jdstrand Nov 1, 2016

Contributor

Please remove this since it won't be an implicit plug any more.

Contributor

kalikiana commented Nov 4, 2016

So the current state is, by changing the rules locally I can install an lxd snap with slots: [lxd], and a test snap using the Go API via the socket works. From what I understand it's expected that local testing of the correct rules as in the current code is impossible.

Contributor

stgraber commented Nov 5, 2016

Looks like your branch needs rebasing. You don't usually want Merge commits in a pull request.

This is close; thanks for your changes! Just a few more minor requests.

@@ -179,6 +179,7 @@ func (s *baseDeclSuite) TestAutoConnectPlugSlot(c *C) {
snowflakes := map[string]bool{
"content": true,
"home": true,
+ "lxd": true,
@jdstrand

jdstrand Nov 8, 2016

Contributor

I'm not sure why lxd is a snowflake in TestAutoConnectPlugSlot. Can you comment?

@@ -466,6 +468,7 @@ func (s *baseDeclSuite) TestConnection(c *C) {
"fwupd": true,
"location-control": true,
"location-observe": true,
+ "lxd": true,
"mir": true,
"modem-manager": true,
"udisks2": true,
@jdstrand

jdstrand Nov 8, 2016

Contributor

Please add another test to TestSlotInstallation() for lxd, underneath the docker one.

interfaces/builtin/lxd.go
+)
+
+const lxdConnectedPlugAppArmor = `
+# Description: Can access commands and socket from the 'lxd' snap.
@jdstrand

jdstrand Nov 8, 2016

Contributor

Can you update this description to be like the one you have in lxdConnectedPlugSecComp?

interfaces/builtin/lxd.go
+const lxdConnectedPlugSecComp = `
+# Description: allow access to the LXD daemon socket. This gives privileged
+# access to the system via LXD's socket API.
+# Usage: reserved
@jdstrand

jdstrand Nov 8, 2016

Contributor

Can you remove 'Usage' from here?

+ // 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))
@jdstrand

jdstrand Nov 8, 2016

Contributor

Can you add a test here for interfaces.SecuritySecComp?

interfaces/builtin/lxd_test.go
+ c.Assert(snippet, Not(IsNil))
+}
+
+func (s *LxdInterfaceSuite) TestPermanentSlotPolicyAppArmor(c *C) {
@jdstrand

jdstrand Nov 8, 2016

Contributor

This function is named incorrectly. You are testing ConnectedPlugSnippet.

interfaces/builtin/lxd_test.go
+ snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor)
+ c.Assert(err, IsNil)
+ c.Check(string(snippet), testutil.Contains, "/var/snap/lxd/common/lxd/unix.socket rw,\n")
+}
@jdstrand

jdstrand Nov 8, 2016

Contributor

Can you add another test for TestConnectedPlugPolicySecComp and look for shutdown in the same manner that you are searching for an apparmor rule here?

+ c.Check(s.iface.LegacyAutoConnect(), Equals, false)
+}
+
+func (s *LxdInterfaceSuite) TestAutoConnect(c *C) {
@jdstrand

jdstrand Nov 8, 2016

Contributor

Please add the following comment: // allow what declarations allowed

Contributor

jdstrand commented Nov 10, 2016

Thanks for the updates!

Collaborator

mvo5 commented Nov 15, 2016

LGTM as well, if @jdstrand is happy please merge.

@kalikiana kalikiana changed the title from Implement lxd-client interface exposing the lxd snap to Implement lxd-client interface exposing the lxd snap (LP: #1634880) Nov 15, 2016

@jdstrand jdstrand merged commit 935a9cd into snapcore:master Nov 15, 2016

1 of 3 checks passed

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

vicamo commented Nov 18, 2016

@jdstrand any example how may one downstream snap use this interface?

Contributor

jdstrand commented Nov 18, 2016

@vicamo - The interface only gives access to the lxd socket via plugs: [ lxd ] so you can use whatever client tools and libraries to access lxd. Note this is an extremely privileged interface and it is restricted by the base declaration and it requires a snap declaration to install and use. Please see this bug when developing a snap that uses this interface:

https://bugs.launchpad.net/snappy/+bug/1640874

Contributor

vicamo commented Nov 18, 2016

@jdstrand, yes, I read that notice from your comments, and it's what we need. Thank you.

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