interfaces: add a serial-port interface #1301

Merged
merged 4 commits into from Jun 20, 2016
View
@@ -136,6 +136,14 @@ with trusted apps.
Usage: reserved
Auto-Connect: no
+### serial-port
+
+Can access serial ports. This is restricted because it provides privileged
+access to configure serial port hardware.
+
+Usage: reserved
+Auto-Connect: no
+
### snapd-control
Can manage snaps via snapd.
@@ -150,3 +150,30 @@
be checked in the ubuntu-control-center under the printers applet. Right
click on the default printer and look at the queue. Ensure it contains the
new item.
+
+# Test serial-port interface using miniterm app
+
+1. Using Ubuntu classic build and install a simple snap containing the Python
+ pySerial module. Define a app that runs the module and starts miniterm.
+
+```yaml
+ name: miniterm
+ version: 1
+ summary: pySerial miniterm in a snap
+ description: |
+ Simple snap that contains the modules necessary to run
+ pySerial. Useful for testing serial ports.
+ confinement: strict
+ apps:
+ open:
+ command: python3 -m serial.tools.miniterm
+ plugs: [serial-port]
+ parts:
+ my-part:
+ plugin: nil
+ stage-packages:
+ - python3-serial
+```
+
+2. Ensure the 'serial-port' interface is connected to miniterm
+3. Use sudo miniterm.open /dev/tty<DEV> to open a serial port
@@ -31,6 +31,7 @@ var allInterfaces = []interfaces.Interface{
&ModemManagerInterface{},
&NetworkManagerInterface{},
&PppInterface{},
+ &SerialPortInterface{},
NewFirewallControlInterface(),
NewGsettingsInterface(),
NewHomeInterface(),
@@ -36,6 +36,7 @@ func (s *AllSuite) TestInterfaces(c *C) {
c.Check(all, Contains, &builtin.BluezInterface{})
c.Check(all, Contains, &builtin.LocationControlInterface{})
c.Check(all, Contains, &builtin.LocationObserveInterface{})
+ c.Check(all, Contains, &builtin.SerialPortInterface{})
c.Check(all, DeepContains, builtin.NewFirewallControlInterface())
c.Check(all, DeepContains, builtin.NewGsettingsInterface())
c.Check(all, DeepContains, builtin.NewHomeInterface())
@@ -0,0 +1,132 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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
+
+import (
+ "fmt"
+ "path/filepath"
+ "regexp"
+
+ "github.com/snapcore/snapd/interfaces"
+)
+
+// SerialPortInterface is the type for serial port interfaces.
+type SerialPortInterface struct{}
+
+// Name of the serial-port interface.
+func (iface *SerialPortInterface) Name() string {
+ return "serial-port"
+}
+
+func (iface *SerialPortInterface) String() string {
+ return iface.Name()
+}
+
+// Pattern to match allowed serial device nodes, path attributes will be
+// compared to this for validity
+var serialAllowedPathPattern = regexp.MustCompile("^/dev/tty[A-Z]{1,3}[0-9]{1,3}$")
+
+// SanitizeSlot checks validity of the defined slot
+func (iface *SerialPortInterface) SanitizeSlot(slot *interfaces.Slot) error {
+ // Check slot is of right type
+ if iface.Name() != slot.Interface {
+ panic(fmt.Sprintf("slot is not of interface %q", iface))
+ }
+
+ // Check slot has a path attribute identify serial device
+ path, ok := slot.Attrs["path"].(string)
+ if !ok || path == "" {
+ return fmt.Errorf("serial-port slot must have a path attribute")
+ }
+
@zyga

zyga Jun 9, 2016

Contributor

We should perhaps run filepath.Clean on the path before checking it (https://golang.org/pkg/path/filepath/#Clean)

+ // Clean the path before checking it matches the pattern
+ path = filepath.Clean(path)
+
+ // Check the path attribute is in the allowable pattern
+ if serialAllowedPathPattern.MatchString(path) {
+ return nil
+ }
+ return fmt.Errorf("serial-port path attribute must be a valid device node")
+}
+
+// SanitizePlug checks and possibly modifies a plug.
+func (iface *SerialPortInterface) SanitizePlug(slot *interfaces.Plug) error {
+ if iface.Name() != slot.Interface {
+ panic(fmt.Sprintf("plug is not of interface %q", iface))
+ }
+ // NOTE: currently we don't check anything on the plug side.
+ return nil
+}
+
+// PermanentSlotSnippet returns snippets granted on install
+func (iface *SerialPortInterface) PermanentSlotSnippet(slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor:
+ return []byte(fmt.Sprintf("\n%s rwk,\n", iface.path(slot))), nil
+ case interfaces.SecuritySecComp, interfaces.SecurityDBus, interfaces.SecurityUDev:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+// ConnectedSlotSnippet no extra permissions granted on connection
+func (iface *SerialPortInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityDBus, interfaces.SecurityUDev:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+// PermanentPlugSnippet no permissions provided to plug permanently
+func (iface *SerialPortInterface) PermanentPlugSnippet(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityDBus, interfaces.SecurityUDev:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+// ConnectedPlugSnippet returns security snippet specific to the plug
+func (iface *SerialPortInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor:
+ return []byte(fmt.Sprintf("%s rwk,\n", iface.path(slot))), nil
+ case interfaces.SecuritySecComp, interfaces.SecurityDBus, interfaces.SecurityUDev:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+func (iface *SerialPortInterface) path(slot *interfaces.Slot) string {
+ if path, ok := slot.Attrs["path"].(string); ok {
+ return filepath.Clean(path)
+ }
+ panic("slot is not sanitized")
+}
+
+// AutoConnect indicates whether this type of interface should allow autoconnect
+func (iface *SerialPortInterface) AutoConnect() bool {
+ return false
+}
Oops, something went wrong.