NodeJS Strict Confinement RPI GPIO?


#1

Hey Everyone,

Using Ubuntu Core 18

Just wondering if it’s possible to have strict confinement with a nodejs snap that has control over a raspberry pi gpio. My example is below. I can control GPIO4 in this case, but the command requires sudo, and I have been installing it in classic mode. Eventually, I want to setup a service that listens to a remote database to control the gpio, but I’m concerned I do not have confinement / permissions setup correctly, since these commands error without sudo. (see commands at bottom).

Is there anything I am doing that inherently requires sudo? Is there something I’m missing?

After installation, I make a connection to the gpio interface via:

snap connect raspberry-pi-node-gpio:gpio pi:bcm-gpio-4

snapcraft.yaml

name: raspberry-pi-node-gpio
base: core18
version: '0.0.2'
summary: Control Raspberry Pi GPIO Pins With Node On Ubuntu Core 18
description: |
  This boilerplate snap shows how to control the GPIO pins of a raspberry pi
  running Ubuntu Core 18 with a snap that follows strict confinement.

grade: devel
confinement: strict

architectures:
  - build-on: arm64
    run-on: arm64

apps:
  main:
    command: main
    plugs:
      - network-bind
      - gpio
parts:
  raspberry-pi-node-gpio:
    source: .
    plugin: nodejs
  scripts:
    source: ./scripts
    plugin: dump

Main.js - allows the gpio4 to be “on” or “off” based on the argument sent.

#!/usr/bin/env node
var fs = require("fs");

console.log("Reading / writing state of pin gpio4...");

if(typeof process.argv[2] === "undefined" || (process.argv[2] !== 'on' && process.argv[2] !== 'off')) {
    console.log("Indicate 'on' or 'off' as an argument.");
}

// Perform reads
fs.readFile('/sys/class/gpio/gpio4/value', 'utf8', (err, contents) => {
    if(err) {
        console.log("Error: ", err);
    }
    console.log("Initial Value: ", contents);
});

fs.readFile('/sys/class/gpio/gpio4/direction', 'utf8', (err, contents) => {
    if(err) {
        console.log("Error: ", err);
    }
    console.log("Initial Direction: ", contents);
});

if(process.argv[2] === "on") {
    // Perform Writes
    fs.writeFile('/sys/class/gpio/gpio4/direction', 'out', (err) => { 
        if(err) {
            console.log("Error: ", err);
        }
        console.log("Wrote direction out.");
    });
    fs.writeFile('/sys/class/gpio/gpio4/value', '0', (err) => { 
        if(err) {
            console.log("Error: ", err);
        }
        console.log("Wrote value 0.");
    });
}

if(process.argv[2] === "off") {
    // Perform Writes
    fs.writeFile('/sys/class/gpio/gpio4/direction', 'out', (err) => { 
        if(err) {
            console.log("Error: ", err);
        }
        console.log("Wrote direction out.");
    });
    fs.writeFile('/sys/class/gpio/gpio4/value', '1', (err) => {
        if(err) {
            console.log("Error: ", err);
        }
        console.log("Wrote value 1.");
    });
}

console.log("Reading / writing complete.");

Command Examples

raspberry-pi-node-gpio.main off
raspberry-pi-node-gpio.main on

#2

This is a long-standing dicussion elsewhere on the forum. At this time you do need sudo. We have plans that would allow non-root users to get access but this is highly complex and has not been scheduled yet.


#3

Is this a nodejs specific problem? Should I consider another option like python? I went with nodejs only because it’s what I’m most experienced in.


#4

This is unrelated to node. It’s the classic UNIX permission model at play.

The app runs as a regular user, the device is not writable to that user.


#5

The other way you could handle this, complicated as it may be, is by having the code that talks to the gpio pin (currently your main app) instead run as a daemon in the snap, and then your app (say something like gpio-client, etc.) just talks to the daemon some way to tell the daemon what to do. Then the app doesn’t need to be run as root, as the process that actually performs the action is run as root via the daemon and so has permission.

This is a common solution to the permission problem, but it is quite a bit of work to actually implement in some cases, and it does mean you need to do authentication and authorization of the requests manually rather than rely on the traditional unix permissions system as your app currently is.

I also don’t know how hard this is to implement in nodejs, but I’ve seen it implemented many times in go, for example the snap command talks to snapd this way in some cases, docker talks to dockerd this way, etc.


#6

the assumption used to be that ubuntu core devices are actually appliances … i.e. without users (unless you do development on the device) so anything you run on it would be a service … which by default runs as root anyway …

there are various tricks you can pull … like having a oneshot service with the necessary interfaces that then changes permissions of the device node on boot … or having a dbus listener or a small daemon (as ian said) that uses a socket that you then talk to, so the actual device access works under elevated permissions while the user interface side runs with the users permissions…


#7

Thank you both. I do not actually need or want to deal with having linux user accounts or permissions on-device, so ijohnson’s solution sounds perfect.

I am actually using Firebase Authentication with an app, and the node snap will be running the firebase nodejs SDK to listen to a Firebase database, changing GPIO values based on database values it’s listening for changes to - so theoretically, only firebase authenticated users changing database values should be able to communicate with the device (from a security standpoint).

So I’ll be experimenting with getting this working and I’ll come back and update with a boilerplate public github repo / snapcraft repository link once it’s working.