walnut
An opinionated, constrained, secure application framework with a hard shell - like iOS, but for a home server.
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
- JSON-only expressjs APIs
 - Capability-based permissions system for (oauth3-discoverable) packages such as
- large file access (files@daplie.com)
 - database access (data@daplie.com)
 - scheduling (for background tasks, alerts, alarms, calendars, reminders, etc) (events@daplie.com)
 - payments (credit card) (payments@daplie.com)
 - email (email@daplie.com)
 - SMS (texting) (tel@daplie.com)
 - voice (calls and answering machine) (tel@daplie.com)
 - lamba-style functions (functions@daplie.com)
 
 - Per-app, per-site, and per-user configurations
 - Multi-Tentated Application Management
 - Built-in OAuth2 & OAuth3 support
 
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 http://localhost.daplie.me:3000 -H 'X-Forwarded-Proto: https'
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
corecontains all walnut codenode_modulesis a flat installation of all dependenciescertsis a directory for Let's Encrypt (or custom) certificatesvaris a directory for database files and suchpackagescontains 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
<domain.tld>
www.<domain.tld>
api.<domain.tld>
assets.<domain.tld>
cloud.<domain.tld>
api.cloud.<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/com.daplie.walnut.config.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