Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@nothingmuch
Last active January 6, 2019 23:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nothingmuch/6177e9e9716ebb7ed866eb7e79506b43 to your computer and use it in GitHub Desktop.
Save nothingmuch/6177e9e9716ebb7ed866eb7e79506b43 to your computer and use it in GitHub Desktop.

Motivation

Locking down REST and/or JSON-RPC APIs in a generic way, with an object capability security model.

Proposed Design

Given some sort of priviliged access to a backend, this proxy would expose the service with no changes to the API apart from additional requirement for macaroon authorization.

A second mode would also be able to expose such a service to an unrestricted port, by statically configuring a macaroon to be submitted to an upstream endpoint, allowing hardened APIs to be exposed with no macaroon required.

These two modes should normally be used in separate processes, but could also be deployed in a single processes space without actual macaroons validation (i.e. just a static list of 1st party caveats to apply to some proxied connections).

Caveats Types

General Purpose

  • time expiry (stateless)
  • rate limiting (in memory state)
  • use limits (persistent state, e.g. up to n requests)
  • socket metadata (client address, etc)

RPC

Note that HTTP and JSON-RPC are orthogonal, either or both might be appropriate for a given service. JSON-RPC should support REST as well as stream (UNIX/TCP) based JSON-RPC, including bitcoind's nonstandard implementation ("in band" errors reporting).

  • HTTP
    • method & path validation
    • header requirements
    • request body constraints
      • formal validity (JSON, multipart form, etc)
    • response status types (e.g. to avoid leaking internal errors)
  • JSON-RPC
    • constraining method sets
    • JSON schema (for params or REST JSON bodies) - care must be taken to not load external references unless explicitly allowed in server configuration, and to parse caveats containing schemas conservatively or to provide a server whitelist of schema based caveats. a reasonable option is to trust upstream schema definitions (e.g. swagger APIs) but allow caveats to constrain them
    • what about more semantic validation later on? perhaps simple JSON parsing preprocessors, e.g. to split strings, or extract numerical values, to make schema based validation more useful?
    • response binding - this would be too complicated for the initial version, but in principle responses can be used to mint macaroons with caveats (e.g. issuing a successful request grants you access to call some other method, but only with e.g. a parameter from the response)

What about GRPC? generic Protobuf? cap'n'proto? these could be added later using some custom build tool to compile a server binary with the schema definitions if the internal APIs are sufficiently generic, but this is substantially more complex than dynamic validation

Configuration

Server configuration requires a graph of 3 layers for defining service routing:

  • list of named upstream services of various types, with optional access macaroons
  • named services can be encumbered by associating with a MAC secret for macaroon validation (static or generated on each startup), or with a static list of 1st party caveats, with composition
  • internally encumbered services can then be exposed to listeners

the configuration itself should conceptually be a list of operations to be replayed at startup, constructing the capability graph, binding sockets, etc, so that an administrative RPC api can also be added later (though I'm dubious about use cases, apart from having an administrative API that can mint new macaroons, but it might be a good dogfooding exercise)

Creating Macaroons

Priviliged macaroons would be exported as files on startup, and a separate utility can restrict macaroons by adding caveats known to be understood by the server.

For easy hardening of well known APIs, a reviewed library of stock caveats could be maintained outside of the main server code base.

Transport Model

Low level model for the high level (3 layer) graph above, which must be decomposed into actual protocol stacks.

  • sockets - tcp, unix domain - listener & dialer
    • what about wrapping/multiplexing protocols, e.g. websockets or SOCKS?
    • TLS termination & upstream authentication?
  • transport layer (only considering JSONRPC & REST for now)
    • stream
      • single request per socket
      • NDJSON - seems to be most common
      • netstrings? i've never encountered in the wild
      • streaming decoder, IIRC trivially possible with standard Go JSON decoder,
    • HTTP
      • macaroon extraction & injection
        • headers
        • request param (query string & request body)
      • arbitrary header injection (e.g. for upstream basic auth)
  • abstract request/response
    • REST
      • method
      • path & query params
      • headers
      • body by content type
        • application/x-www-form-urlencoded
        • multipart/form-data ?
        • application/json - formal validation, composes with JSON-RPC abstract request type
      • response status
    • JSON-RPC
      • method
      • params
      • response (for error scrubbing)

Language Bikeshedding

Both Go and Rust seem to be good choices for this daemon. Possible library choices:

Go

Rust

  • Macaroons - panicbit/rust-macaroons - no 3rd party caveats & discharge macaroon support, but otherwise seems solid
  • HTTP - hyper
  • JSONRPC - apoelstra/rust-jsonrpc - JSONRPC 2.0, transport agnostic req/res types, method params as [serde_json::Value], with client over http transport
  • JSON Schema - rustless/valico - practically full JSON schema support, with additional validator DSL

c-lightning

seems to be jsonrpc 2.0 over UNIX domain stream transport, seems to rely on streaming decoding, tolerating NDJSON?

clients (cli, python & kotlin proxy) seem to only make 1 request per connection (notably python client seem to implicitly make jsonrpc 1.0 requests by omitting jsonrpc member, but daemon assumes jsonrpc 2.0. kotlin proxy enforces 2.0)

bitcoind

JSON-RPC 1.0 (ish) over HTTP with basic authentication, no TLS

btcd

TBD

lnd

a REST/JSON api is provided (wraps GRPC), has native support for macaroons supplied via Grpc-Metadata-macaroon header, TLS mandatory

exposing with a client should be possible if only for compatibility/usability testing (e.g. essentially providing --no-macaroons for a subset of the API if using a macaroon with caveats).

lightningnetwork/lnd#20 lightningnetwork/lnd#1147

electrum personal server

electrum protocol uses JSON-RPC, preferably 2.0, over stream sockets, using NDJSON, optional

zerotier controller API

REST/JSON api using a static secret via X-ZT1-Auth

https://github.com/zerotier/ZeroTierOne/tree/master/controller

perkeep

REST API, not fully JSON, /share prefix provides optionally transitive read capabilities, otherwise auth is based on HTTP basic auth, and vivifysecret allows upload only permission (e.g. for mobile client), optional https (w/ acme support or self signed)

given auth model, usefulness seems dubious

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment