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://git.daplie.com/Daplie/goldilocks.js) * 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 ------- ```bash 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) ```bash 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 . ``` 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 ``` * `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./api/com.daplie.walnut.init { "domain": "" } ``` The following domains are required to point to WALNUT server ``` www. api. assets. cloud. api.cloud. ``` Example `/etc/goldilocks/goldilocks.yml`: ```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//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/ # https://domain.tld/path /srv/walnut/packages/sites/ # 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/ # matches apps./ and /apps/ ``` 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/ # matches api./api/ and contains a list of allowed REST APIs # the REST apis themselves are submatched as api./api/ # packages are directories with reverse dns name, a package.json, and an index.js /srv/walnut/packages/rest/ ``` 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 │ ''' │ │ com.example.hello │ │

com.example.hello

│ │ │ ''' │ ├── 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](https://gundb.io) Support * OpenID support