A lightweight IOT application server with a hard shell written for node.js
Go to file
aj a52d6c4c2e Merge branch 'master' of https://git.daplie.com/Daplie/walnut.js into v1 2017-07-31 22:50:21 +00:00
bin removed unused modules from package.json 2017-06-22 10:18:02 -06:00
boot create the config dir in the install process 2017-06-26 13:22:57 -06:00
dist add example config file 2017-06-08 13:16:27 -06:00
etc include special and auto cert dirs 2016-03-28 21:12:35 -04:00
lib com.daplie.walnut -> walnut@daplie.com 2017-07-31 22:46:03 +00:00
snippets add undo instructions 2015-02-23 19:30:34 +00:00
tests made leading comma style more consistent 2017-07-11 15:07:16 -06:00
.gitignore com.daplie.walnut -> walnut@daplie.com 2017-07-31 22:46:03 +00:00
.jshintrc add .jshintrc 2017-05-04 23:10:24 -06:00
LICENSE Update LICENSE 2017-07-06 11:43:32 -06:00
README.md Merge branch 'master' of https://git.daplie.com/Daplie/walnut.js into v1 2017-07-31 22:50:21 +00:00
add-subtree.sh add subtree script 2017-05-19 00:57:34 -05:00
install-helper.sh move install.sh to install-helper.sh 2017-07-28 17:03:41 -06:00
install.sh Add new file 2017-07-28 17:03:55 -06:00
package.json Merge branch 'master' into v1 2017-07-11 09:07:14 +00:00
setup-dev-deps.sh refactoring to use fs config 2016-04-09 19:14:00 -04:00
uninstall.sh update installer 2017-05-09 10:27:47 -06:00
walnut.js removed letsencrypt and other 2017-05-04 23:09:56 -06:00

README.md

walnut

An opinionated, constrained, secure application framework with a hard shell - kinda like iOS, but for a server.

Applications are written in express, but instead of using require for generic packages, they use req.getSiteCapability(pkg) and are restricted to packages that have been allowed by app, device, site, or user permission. Any configuration for the capability (external passwords, api keys, etc) will be set up beforehand so that they are not exposed to the application.

Security Features

  • JSON-only APIs
  • JWT (not cookie*) authentication
  • no server-rendered html
  • disallows urlencoded forms, except for secured webhooks
  • disallows cookies, except for protected static assets
  • api.* subdomain for apis
  • assets.* subdomain for protected assets
  • must sit behind a trusted https proxy (such as Goldilocks
  • HTTPS-only (checks for X-Forwarded-For)
  • AES, RSA, and ECDSA encryption and signing
  • Safe against CSRF, XSS, and SQL injection
  • Safe against Compression attacks

*Cookies are used only for GETs and only where using a token would be less secure - such as images which would otherwise require the token to be passed into the img src. They are also scoped such that CSRF attacks are not possible.

Application Features

Install

curl https://daplie.me/install-scripts | bash

daplie-install-walnut

You could also, of course, try installing from the repository directly (especially if you have goldilocks or some similar already installed)

mkdir -p /srv/walnut/
git clone git@git.daplie.com:Daplie/walnut.js.git /srv/walnut/core
pushd /srv/walnut/core
  git checkout v1
popd
bash /srv/walnut/core/install-helper.sh

Initial Configuration

Once installed and started you can visit https://localhost.daplie.me:3000.

curl -X POST http://api.localhost.daplie.me:3000/api/walnut@daplie.com/init \
  -H 'X-Forwarded-Proto: https' \
  -H 'Content-Type: application/json' \
  -d '{
    "domain": "example.com"
  }'

API

API docs are here https://git.daplie.com/Daplie/com.example.hello

Structure

Currently being tested with Ubuntu, Raspbian, and Debian on Digital Ocean, Raspberry Pi, and Heroku.

/srv/walnut/
├── setup.sh (in-progress)
├── core
│   ├── bin
│   ├── boot
│   ├── holepunch
│   └── lib
├── etc
│   └── client-api-grants
├── node_modules
├── packages
│   ├── apis
│   ├── pages
│   └── services
└── var
    └── sites
  • core contains all walnut code
  • node_modules is a flat installation of all dependencies
  • certs is a directory for Let's Encrypt (or custom) certificates
  • var is a directory for database files and such
  • packages contains 3 types of packages

Will install to

/srv/walnut/core/
/etc/walnut
/opt/walnut
/var/log/walnut
/etc/systemd/system/walnut.service
/etc/tmpfiles.d/walnut.conf

Implementation details

Initialization

needs to know its primary domain

POST https://api.<domain.tld>/api/walnut@daplie.com/init

{ "domain": "<domain.tld>" }

The following domains are required to point to WALNUT server

cloud.<domain.tld>
api.cloud.<domain.tld>

and

<domain.tld>
www.<domain.tld>

api.<domain.tld>
assets.<domain.tld>

Example /etc/goldilocks/goldilocks.yml:

tls:
  email: domains@example.com
  servernames:
    - example.com
    - www.example.com
    - api.example.com
    - assets.example.com
    - cloud.example.com
    - api.cloud.example.com

http:
  trust_proxy: true
  modules:
    - name: proxy
      domains:
        - '*'
      address: '127.0.0.1:3000'

Resetting the Initialization

Once you run the app the initialization files will appear in these locations

/srv/walnut/var/walnut+config@daplie.com.sqlite3
/srv/walnut/config/<domain.tld>/config.json

Deleting those files will rese

Accessing static apps

Static apps are stored in packages/pages

# App ID as files with a list of packages they should load
# note that '#' is used in place of '/' because files and folders may not contain '/' in their names
/srv/walnut/packages/sites/<domain.tld#path>          # https://domain.tld/path
/srv/walnut/packages/sites/<domain.tld>               # https://domain.tld and https://domain.tld/foo match

# packages are directories with reverse dns name      # For the sake of debugging these packages can be accessed directly, without a site by
/srv/walnut/packages/pages/<tld.domain.package>       # matches apps.<domain.tld>/<package-name> and <domain.tld>/apps/<package-name>

Accessing REST APIs

# Apps are granted access to use a package by listing it in the grants file by the name of the app url (domain.tld)
/srv/walnut/packages/client-api-grants/<domain.tld>   # matches api.<domain.tld>/api/ and contains a list of allowed REST APIs
                                                      # the REST apis themselves are submatched as api.<domain.tld>/api/<tld.domain.package>

# packages are directories with reverse dns name, a package.json, and an index.js
/srv/walnut/packages/rest/<tld.domain.package>

Example tree with contents:

Here com.example.hello is a package with a REST API and a static page and foobar.me is a WALNUT-configured domain (smithfam.net, etc).

The packages:

/srv/walnut/packages/
├── api
├── pages
│   └── com.example.hello
│       └── index.html
│             '''
│             <html>
│               <head><title>com.example.hello</title></head>
│               <body>
│                 <h1>com.example.hello</h1>
│               </body>
│             </html>
│             '''
│
├── rest
│   └── com.example.hello
│      ├── package.json
│      └── index.js
│           '''
│           'use strict';
│
│           module.exports.create = function (conf, deps, app) {
│
│             app.use('/', function (req, res) {
│               console.log('[com.example.hello] req.url', req.url);
│               res.send({ message: 'hello' });
│             });
│
│             return deps.Promise.resolve();
│           };
│
│           '''
│
└── services

The permissions:

/srv/walnut/packages/
├── client-api-grants
│   └── cloud.foobar.me
│         '''
│         com.example.hello     # refers to /srv/walnut/packages/rest/com.example.hello
│         '''
│
└── sites
    └── daplie.me
          '''
          com.example.hello     # refers to /srv/walnut/packages/pages/com.example.hello
          '''

API

req.apiUrlPrefix => https://api.example.com/api/tld.domain.pkg

TODO

  • HTTPS Key Pinning
  • Heroku (pending completion of PostgreSQL support)
  • GunDB Support
  • OpenID support