298 lines
7.8 KiB
Markdown
298 lines
7.8 KiB
Markdown
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://git.coolaj86.com/coolaj86/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@oauth3.org)
|
|
* database access (data@oauth3.org)
|
|
* scheduling (for background tasks, alerts, alarms, calendars, reminders, etc) (events@oauth3.org)
|
|
* payments (credit card) (payments@oauth3.org)
|
|
* email (email@oauth3.org)
|
|
* SMS (texting) (tel@oauth3.org)
|
|
* voice (calls and answering machine) (tel@oauth3.org)
|
|
* lamba-style functions (functions@oauth3.org)
|
|
* Per-app, per-site, and per-user configurations
|
|
* Multi-Tentated Application Management
|
|
* Built-in OAuth2 & OAuth3 support
|
|
|
|
Currently being tested with Ubuntu, Raspbian, and Debian on Digital Ocean, Raspberry Pi, and Heroku.
|
|
|
|
Installation
|
|
------------
|
|
|
|
We're still in a stage where the installation generally requires many manual steps.
|
|
|
|
```bash
|
|
curl https://git.coolaj86.com/coolaj86/walnut.js/raw/v1.2/installer/get.sh | bash
|
|
```
|
|
|
|
See [INSTALL.md](/INSTALL.md)
|
|
|
|
### Uninstall
|
|
|
|
```bash
|
|
rm -rf /srv/walnut/ /var/walnut/ /etc/walnut/ /opt/walnut/ /var/log/walnut/ /etc/systemd/system/walnut.service /etc/tmpfiles.d/walnut.conf
|
|
```
|
|
|
|
Usage
|
|
-----
|
|
|
|
Here's how you run the thing, once installed:
|
|
|
|
```
|
|
/opt/walnut/bin/node /srv/walnut/core/bin/walnut.js
|
|
```
|
|
|
|
It listens on all addresses, port 3000.
|
|
|
|
TODO: Add config to restrict listening to localhost.
|
|
|
|
API
|
|
---
|
|
|
|
The API is still in flux, but you can take a peek anyway.
|
|
|
|
See [API.md](/API.md)
|
|
|
|
Understanding Walnut
|
|
====================
|
|
|
|
```
|
|
/srv/walnut/
|
|
├── setup.sh (in-progress)
|
|
├── core
|
|
│ ├── bin
|
|
│ ├── boot
|
|
│ └── lib
|
|
├── etc
|
|
│ └── client-api-grants
|
|
├── node_modules
|
|
├── packages
|
|
│ ├── apis
|
|
│ ├── pages
|
|
│ ├── rest
|
|
│ └── 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
|
|
```
|
|
|
|
Initialization
|
|
--------------
|
|
|
|
needs to know its primary domain
|
|
|
|
```
|
|
POST https://api.<domain.tld>/api/walnut@oauth3.org/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>
|
|
```
|
|
|
|
The domains can be setup through the OAuth3 Desktop App or with `oauth3-tools`
|
|
|
|
```bash
|
|
# set device address and attach primary domain
|
|
oauth3 devices:attach -d foodevice -n example.com -a 127.0.0.1
|
|
|
|
# attach all other domains with same device/address
|
|
oauth3 devices:attach -d foodevice -n www.example.com
|
|
oauth3 devices:attach -d foodevice -n api.example.com
|
|
oauth3 devices:attach -d foodevice -n assets.example.com
|
|
oauth3 devices:attach -d foodevice -n cloud.example.com
|
|
oauth3 devices:attach -d foodevice -n api.cloud.example.com
|
|
```
|
|
|
|
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/walnut+config@oauth3.org.sqlite3
|
|
/srv/walnut/config/<domain.tld>/config.json
|
|
```
|
|
|
|
Deleting those files and restarting walnut will reset it to its bootstrap state.
|
|
|
|
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/pages/<domain.tld#path> # https://domain.tld/path
|
|
/srv/walnut/packages/pages/<domain.tld> # https://domain.tld and https://domain.tld/foo match
|
|
|
|
# packages are directories with email-style name # For the sake of debugging these packages can be accessed directly, without a site by
|
|
/srv/walnut/packages/pages/<package@domain.tld> # 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
|
|
├── 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
|
|
```
|
|
|
|
```
|
|
/srv/walnut/packages/
|
|
└── pages
|
|
└── demo@example.com
|
|
└── index.html
|
|
'''
|
|
<html>
|
|
<head><title>demo@example.com</title></head>
|
|
<body>
|
|
<h1>demo@example.com</h1>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
```
|
|
|
|
The permissions:
|
|
|
|
```
|
|
/srv/walnut/packages/
|
|
└── client-api-grants
|
|
└── cloud.foobar.me
|
|
'''
|
|
hello@example.com # refers to /srv/walnut/packages/rest/hello@example.com
|
|
'''
|
|
```
|
|
|
|
```
|
|
/srv/walnut/var/
|
|
└── sites
|
|
└── example.com
|
|
'''
|
|
seed@example.com # refers to /srv/walnut/packages/pages/seed@example.com
|
|
'''
|
|
```
|