mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-19 05:07:56 +00:00
a8e394d299
Adds a CLI and RESTful API operation for "keyring remove", with simple test cases. Added the corresponding Java API operation. Updated the API documentation. API change: for consistency with RESTful API design, the GET /restful/keyring/add operation now returns "201 Created" not "200 OK" if successful.
534 lines
22 KiB
Markdown
534 lines
22 KiB
Markdown
REST API
|
|
========
|
|
[Serval Project][], February 2016
|
|
|
|
Introduction
|
|
------------
|
|
|
|
The [Serval DNA][] daemon that runs on every node in a [Serval Mesh network][]
|
|
gives applications access to the network through two main classes of [API][]:
|
|
|
|
* the [MDP API][MDP] and [MSP API][MSP] provide "traditional" packet and
|
|
stream transport, allowing applications to send and receive Serval network
|
|
packets to and from nearby nodes with latencies of up to several seconds;
|
|
|
|
* the various [HTTP REST][] APIs provide applications with access to Serval
|
|
services:
|
|
|
|
- [Keyring REST API][] -- local identity management by querying and
|
|
modifying the [Keyring][]
|
|
|
|
- [Rhizome REST API][] -- store-and-forward (high latency) content
|
|
distribution by extracting and inserting content in the local [Rhizome][]
|
|
store
|
|
|
|
- [MeshMS REST API][] -- secure one-to-one messaging by reading and writing
|
|
the local cache of [MeshMS][] messages
|
|
|
|
This document describes the features in common to all the [HTTP REST][] APIs.
|
|
|
|
### Protocol and port
|
|
|
|
The Serval DNA [HTTP REST][] API is an [HTTP 1.0][] server that only accepts
|
|
requests on the loopback interface (IPv4 address 127.0.0.1), TCP port 4110. It
|
|
rejects requests that do not originate on the local host, by replying
|
|
[403 Forbidden](#403-forbidden).
|
|
|
|
### Security
|
|
|
|
The REST API uses plain HTTP *without* encryption. REST requests and responses
|
|
are not carried over any physical network link, only local (“logical”) links
|
|
between processes, so there is no risk of remote eavesdropping. The only
|
|
potential threat comes from hostile local processes.
|
|
|
|
Operating system kernels such as Linux (Android, Ubuntu) and Darwin (Apple)
|
|
prevent normal processes from accessing the traffic on local sockets between
|
|
other processes. To attack Serval DNA and its clients, a local process on the
|
|
local host would have to gain super-user privilege (eg, through a privilege
|
|
escalation vulnerability). A super-user process would have many ways to attack
|
|
Serval DNA and its clients, much more effective than intercepting their
|
|
communications, so encrypting client-server communications would offer no
|
|
protection whatsoever.
|
|
|
|
### Authentication
|
|
|
|
Clients of the HTTP REST API must authenticate themselves using [Basic
|
|
Authentication][]. This narrows the window for opportunistic attacks on the
|
|
server's HTTP port by malicious applications that scan for open local ports to
|
|
exploit. Any process wishing to use the REST API must supply valid
|
|
authentication credentials (name/password), or will receive a [401
|
|
Unauthorized](#401-unauthorized) response.
|
|
|
|
Client applications obtain their REST API credentials via a back channel
|
|
specific to their particular platform. This delegates the exercise of handing
|
|
out credentials to the application layer, where users can (usually) exercise
|
|
their own discretion. For example, on Android, a client app sends an
|
|
[Intent][] to the [Serval Mesh][] app requesting a Serval REST credential, and
|
|
will receive a reply only if it possesses the right Android [Permission][].
|
|
When users install or run the client app, Android informs them that the app
|
|
requests the "Serval Network" permission, and users may allow or deny it.
|
|
|
|
As a fall-back mechanism, created primarily to facilitate testing, HTTP REST
|
|
API credentials can be [configured][] using configuration options of the form:
|
|
|
|
api.restful.users.USERNAME.password=PASSWORD
|
|
|
|
PASSWORD is a cleartext secret, so the Serval DNA configuration file must be
|
|
protected from unauthorised access or modification by other apps. That makes
|
|
this mechanism unsuitable for general use.
|
|
|
|
### Request
|
|
|
|
An HTTP REST request is a normal [HTTP 1.0][] [GET](#get) or [POST](#post):
|
|
|
|
#### GET
|
|
|
|
A **GET** request consists of an initial "GET" line containing the *path* and
|
|
*HTTP version*, followed by zero or more header lines, followed by a blank
|
|
line. As usual for HTTP, all lines are terminated by an ASCII CR-LF sequence.
|
|
|
|
For example:
|
|
|
|
GET /restful/keyring/identities.json?pin=1234 HTTP/1.0
|
|
Authorization: Basic aGFycnk6cG90dGVy
|
|
Accept: */*
|
|
|
|
|
|
GET requests only accept parameters as [query parameters][] in the *path*.
|
|
|
|
[query parameters]: http://tools.ietf.org/html/rfc3986#section-3.4
|
|
|
|
#### POST
|
|
|
|
A **POST** request is the same as a GET request except that the first word
|
|
of the first line is "POST", the blank line is followed by a request *body*,
|
|
and the following request headers are mandatory:
|
|
* [Content-Length](#request-content-length)
|
|
* [Content-Type](#request-content-type)
|
|
|
|
POST requests accept parameters as [query parameters][] in the *path* and also
|
|
as a request body with a [Content-Type](#request-content-type) of
|
|
[multipart/form-data][]. These two kinds of parameters are not exclusive; a
|
|
request may contain a mixture of both.
|
|
|
|
#### Request Content-Length
|
|
|
|
In a request, the **Content-Length** header gives the exact number of bytes
|
|
(octets) in the request's body, which must be correct. Serval DNA will not
|
|
process a request until it receives Content-Length bytes, so if Content-Length
|
|
is too large, the request will suspend and eventually time out. Serval DNA
|
|
will ignore any bytes received after it has read Content-Length bytes, so if
|
|
Content-Length is too small, the request body will be malformed.
|
|
|
|
#### Request Content-Type
|
|
|
|
In a request, the **Content-Type** header gives the [Internet Media Type][] of
|
|
the body. Serval DNA currently supports the following media types in requests:
|
|
|
|
* **[multipart/form-data][]** is used to send parameters in [POST](#post)
|
|
requests. The **boundary** parameter must specify a string that does not
|
|
occur anywhere within the content of any form part.
|
|
|
|
* **text/plain; charset=utf-8** is used for [MeshMS][] message form parts.
|
|
The only supported charset is utf-8; a missing or different charset will
|
|
cause a [415 Unsupported Media Type](#415-unsupported-media-type) response.
|
|
|
|
* **rhizome/manifest; format=text+binarysig** is used for [Rhizome
|
|
manifest][]s in [text+binarysig format][].
|
|
|
|
A missing Content-Type header in a `POST` request will cause a [400 Bad
|
|
Request](#400-bad-request) response. An unsupported content type will cause a
|
|
[415 Unsupported Media Type](#415-unsupported-media-type) response.
|
|
|
|
The following media types are *not supported*:
|
|
|
|
* [application/x-www-form-urlencoded][] is commonly used to send parameters in
|
|
[POST](#post) requests, and is the predecessor web standard to
|
|
[multipart/form-data][]. It has the benefit of being simpler than
|
|
[multipart/form-data][] for requests that take short, mainly textual
|
|
parameters, but is very inefficient for encoding large binary values and
|
|
does not provide any means to associate metadata such as content-type and
|
|
encoding with individual parameters. In future, some REST API requests may
|
|
support [application/x-www-form-urlencoded][].
|
|
|
|
[multipart/form-data]: https://www.ietf.org/rfc/rfc2388.txt
|
|
[application/x-www-form-urlencoded]: https://tools.ietf.org/html/rfc1866#section-8.2.1
|
|
|
|
#### Request Range
|
|
|
|
[HTTP 1.1 Range][] retrieval is partially supported. In a request, the
|
|
**Range** header gives the start and end, in byte offsets, of the resource to
|
|
be returned. The server may respond with exactly the range requested, in which
|
|
case the response status code will be [206 Partial Content](#206-partial-content),
|
|
or it may ignore the Range header and respond with the entire requested
|
|
resource.
|
|
|
|
For example, the following header asks that the server omit the first 64 bytes
|
|
and send only the next 64 bytes (note that ranges are inclusive of their end
|
|
byte number):
|
|
|
|
Range: bytes=64-127
|
|
|
|
The [specification][HTTP 1.1 Range] allows for more than one start-end range to
|
|
be supplied, separated by commas, however not all REST API operations support
|
|
multi ranges. If a multi-range header is used in such a request, then the
|
|
response may be the entire content or [501 Not Implemented](#501-not-implemented).
|
|
|
|
[HTTP 1.1 Range]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
|
|
|
|
### Responses
|
|
|
|
An HTTP REST response is a normal [HTTP 1.0][] response consisting of a header
|
|
block, a blank line, and an optional body, for example: As usual, all lines are
|
|
terminated by an ASCII CR-LF sequence. For example:
|
|
|
|
HTTP/1.0 200 OK
|
|
Content-Type: application/json
|
|
Content-Length: 78
|
|
|
|
{
|
|
"http_status_code": 200,
|
|
"http_status_message": "OK"
|
|
}
|
|
|
|
The lingua franca of the HTTP REST API is [JSON][] in [UTF-8][] encoding. All
|
|
Serval DNA HTTP REST responses have a Content-Type of **[application/json][]**
|
|
unless otherwise documented.
|
|
|
|
Some responses contain non-standard HTTP headers as part of the result they
|
|
return to the client; for example, [Rhizome response headers](#rhizome-response-headers).
|
|
|
|
[application/json]: https://tools.ietf.org/html/rfc4627
|
|
|
|
### Response status code
|
|
|
|
The HTTP REST API response uses the [HTTP status code][] to indicate the
|
|
outcome of the request as follows:
|
|
|
|
[HTTP status code]: http://www.w3.org/Protocols/HTTP/1.0/spec.html#Status-Codes
|
|
|
|
#### 200 OK
|
|
|
|
The operation was successful and no new entity was created. Most requests
|
|
return this code to indicate success. Requests that create a new entity only
|
|
return this code if the entity already existed, meaning that the creation was
|
|
not performed but the request can be considered a success since the desired
|
|
outcome was achieved: namely, the existence of the entity. (If the entity was
|
|
created, then these requests return [201 Created](#201-created) instead.)
|
|
|
|
(Serval APIs are all [idempotent][] with respect to creation: creating the same
|
|
entity twice yields the same state as creating it once. This is an important
|
|
property for a purely distributed network that has no central arbiter to
|
|
enforce sequencing of operations.)
|
|
|
|
#### 201 Created
|
|
|
|
The operation was successful and the entity was created. This code is only
|
|
returned by requests that create new entities, in the case that the entity did
|
|
not exist beforehand and has been created successfully.
|
|
|
|
#### 202 Accepted
|
|
|
|
The operation was successful but the entity was not created. This code is only
|
|
returned by requests that create new entities, in the case that the request was
|
|
valid but the entity was not created because other existing entities take
|
|
precedence. For example, the [Rhizome REST API](#rhizome-rest-api) returns
|
|
this code when inserting a bundle to a full Rhizome store if the new bundle's
|
|
rank falls below all other bundles, so the new bundle itself would be evicted
|
|
to make room.
|
|
|
|
#### 206 Partial Content
|
|
|
|
The operation was successful and the response contains part of the requested
|
|
content. This code is only returned by requests that fetch an entity (the
|
|
fetched entity forms the body of the response) if the request supplied a
|
|
[Range](#request-range) header that specified less than the entire entity.
|
|
|
|
#### 400 Bad Request
|
|
|
|
The HTTP request was malformed, and should not be repeated without
|
|
modifications. This could be for several reasons:
|
|
- invalid syntax in the request header block
|
|
- a `POST` request MIME part is missing, duplicated or out of order
|
|
- a `POST` request was given an unsupported MIME part
|
|
- a `POST` request MIME part has missing or malformed content
|
|
|
|
#### 401 Unauthorized
|
|
|
|
The request did not supply an "Authorization" header with a recognised
|
|
credential. This response contains a "WWW-Authenticate" header that describes
|
|
the missing credential:
|
|
|
|
HTTP/1.0 401 Unauthorized
|
|
Content-Type: application/json
|
|
Content-Length: 88
|
|
WWW-Authenticate: Basic "Serval RESTful API"
|
|
|
|
{
|
|
"http_status_code": 401
|
|
"http_status_message": "Unauthorized"
|
|
}
|
|
|
|
#### 403 Forbidden
|
|
|
|
The request failed because the server does not accept requests from the
|
|
originating host.
|
|
|
|
#### 404 Not Found
|
|
|
|
The request failed because the [HTTP request URI][] does not exist. This could
|
|
be for several reasons:
|
|
- the request specified an incorrect path (typographic mistake)
|
|
- the path is unavailable because the API in question is unavailable (eg, the
|
|
[Rhizome REST API](#rhizome-rest-api)) is currently [configured][] as
|
|
disabled
|
|
- the path contains a reference to an entity (eg, [SID][], [Bundle ID][]) that
|
|
does not exist
|
|
|
|
[HTTP request URI]: http://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-URI
|
|
|
|
#### 405 Method Not Allowed
|
|
|
|
The request failed because the [HTTP request method][] is not supported for the
|
|
given path. Usually this means that a [GET](#get) request was attempted on a
|
|
path that only supports [POST](#post), or vice versa.
|
|
|
|
[HTTP request method]: http://www.w3.org/Protocols/HTTP/1.0/spec.html#Method
|
|
|
|
#### 411 Length Required
|
|
|
|
A `POST` request did not supply a [Content-Length](#request-content-length)
|
|
header.
|
|
|
|
#### 414 Request-URI Too Long
|
|
|
|
The request failed because the [HTTP request URI][] was too long. The server
|
|
persists the path and a few other pieces of the request in a fixed size request
|
|
buffer, and this response is triggered if the collective size of these does not
|
|
leave enough buffer for receiving the remainder of the request.
|
|
|
|
#### 415 Unsupported Media Type
|
|
|
|
A `POST` request failed because of an unsupported content type, which could be
|
|
for several reasons:
|
|
- the request's [Content-Type](#request-content-type) header specified an
|
|
unsupported media type
|
|
- a part of a [multipart/form-data][] request body has:
|
|
- a missing `Content-Disposition` header, or
|
|
- a `Content-Disposition` header that is not of type `form-data`, or
|
|
- a missing or unsupported `Content-Type` header (including a missing or
|
|
unsupported `charset` parameter)
|
|
|
|
#### 416 Requested Range Not Satisfiable
|
|
|
|
The [Range](#request-range) header specified a range whose start position falls
|
|
outside the size of the requested entity.
|
|
|
|
#### 419 Authentication Timeout
|
|
|
|
The request failed because the server does not possess and cannot derive the
|
|
necessary cryptographic secret or credential. For example, updating a [Rhizome
|
|
bundle][] without providing the [bundle secret][]. This code is not part of
|
|
the HTTP standard.
|
|
|
|
#### 422 Unprocessable Entity
|
|
|
|
A `POST` request supplied data that was inconsistent or violates semantic
|
|
constraints, so cannot be processed. For example, the [Rhizome
|
|
insert](./REST-API-Rhizome.md#post-restfulrhizomeinsert) operation responds
|
|
with 422 if the manifest *filesize* and *filehash* fields do not match the
|
|
supplied payload.
|
|
|
|
#### 423 Locked
|
|
|
|
The request cannot be performed because a necessary resource is busy for
|
|
reasons outside the control of the requester and server.
|
|
|
|
This code is returned by Rhizome requests if the Rhizome store database is
|
|
currently locked by another process. The architecture of [Serval DNA][] is
|
|
being improved to prevent any process other than the Serval DNA daemon itself
|
|
from directly accessing the Rhizome database. Once these improvements are
|
|
done, this code should no longer occur except during unusual testing and
|
|
development situations.
|
|
|
|
#### 429 Too Many Requests
|
|
|
|
The request cannot be performed because a necessary resource is temporarily
|
|
unavailable due to a high volume of concurrent requests.
|
|
|
|
The original use of this code was for Rhizome operations if the server's
|
|
manifest table ran out of free manifests, which would only happen if there were
|
|
many concurrent Rhizome requests holding manifest structures open in server
|
|
memory.
|
|
|
|
This code may also be used to indicate temporary exhaustion of other finite
|
|
resources. For example, if [Serval DNA][] is ever limited to service only a
|
|
few HTTP requests at a time, then this code will be returned to new requests
|
|
that would exceed the limit.
|
|
|
|
#### 431 Request Header Fields Too Large
|
|
|
|
The request header block was too long.
|
|
|
|
Initial implementations of [Serval DNA][] allocated approximately 8 KiB of
|
|
buffer memory for each [request](#request), and the HTTP server read each
|
|
header line entirely into that buffer before parsing it. If a single header
|
|
exceeded the size of this buffer, then the 431 response was returned.
|
|
|
|
#### 500 Internal Server Error
|
|
|
|
The request failed because of an internal error in [Serval DNA][], not an error
|
|
in the request itself. This could be for several reasons:
|
|
- software defect (bug)
|
|
- unavailable system resource (eg, memory, disk space)
|
|
- corrupted environment (eg, bad configuration, database inconsistency)
|
|
|
|
Internal errors of this kind may persist or may resolve if the request is
|
|
re-tried, but in general they will persist because the cause is not transient.
|
|
Temporary failures that can be resolved by re-trying the request are generally
|
|
indicated by other status codes, such as [423 Locked](#423-locked).
|
|
|
|
#### 501 Not Implemented
|
|
|
|
The requested operation is valid but not yet implemented. This is used for the
|
|
following cases:
|
|
|
|
- a request [Range](#request-range) header specifies a multi range
|
|
|
|
#### Cross-Origin Resource Sharing (CORS)
|
|
|
|
To support client-side JavaScript applications, Serval DNA has a limited
|
|
implementation of [Cross-Origin Resource Sharing][CORS]. If a request contains
|
|
an **Origin** header with either “null” or a single URI with scheme “http” or
|
|
“https” or “file”, hostname “localhost” or “127.0.0.1” (or empty in the case of
|
|
a “file” scheme), and optionally any port number, then the response will
|
|
contain three **Access-Control** headers granting permission for other pages on
|
|
the same site to access resources in the returned response.
|
|
|
|
For example, given the request:
|
|
|
|
GET /restful/keyring/identities.json HTTP/1.0
|
|
Origin: http://localhost:8080/
|
|
...
|
|
|
|
Serval DNA will respond:
|
|
|
|
HTTP/1.0 200 OK
|
|
Access-Control-Allow-Origin: http://localhost:8080
|
|
Access-Control-Allow-Methods: GET, POST, OPTIONS
|
|
Access-Control-Allow-Headers: Authorization
|
|
...
|
|
|
|
[CORS]: http://www.w3.org/TR/cors/
|
|
|
|
#### JSON result
|
|
|
|
All responses that convey no special content return the following *JSON result*
|
|
object:
|
|
|
|
{
|
|
"http_status_code": ...,
|
|
"http_status_message": "..."
|
|
}
|
|
|
|
The `http_status_code` field is an integer equal to the [status
|
|
code](#response-status-code) that follows the `HTTP/1.0` token in the first
|
|
line of the response.
|
|
|
|
The `http_status_message` field is usually the same as the *reason phrase* text
|
|
that follows the code in the first line of the HTTP response. This reason
|
|
phrase may be a [standard phrase][status code], or it may be more explanatory;
|
|
for example, some *404* responses from Rhizome have phrases like, “Bundle not
|
|
found”, “Payload not found”, etc.
|
|
|
|
Some responses augment the *JSON result* object with extra fields; for example,
|
|
[Rhizome JSON result][] and [Keyring JSON result][].
|
|
|
|
### JSON table
|
|
|
|
Many HTTP REST responses that return a list of regular objects (eg, [GET
|
|
/restful/rhizome/bundlelist.json](./REST-API-Rhizome.md#get-restfulrhizomebundlelistjson))
|
|
use the following *JSON table* format:
|
|
|
|
{
|
|
"header":["fieldname1","fieldname2","fieldname3", ... ],
|
|
"rows":[
|
|
[field1, field2, field3, ... ],
|
|
[field1, field2, field3, ... ],
|
|
...
|
|
]
|
|
}
|
|
|
|
The JSON table format is more compact than the most straightforward JSON
|
|
representation, an array of JSON objects, which has the overhead of redundantly
|
|
repeating all field labels in every single object:
|
|
|
|
[
|
|
{
|
|
"fieldname1: field1,
|
|
"fieldname2: field2,
|
|
"fieldname3: field3,
|
|
...
|
|
},
|
|
{
|
|
"fieldname1: field1,
|
|
"fieldname2: field2,
|
|
"fieldname3: field3,
|
|
...
|
|
},
|
|
...
|
|
]
|
|
|
|
A JSON table can easily be transformed into its equivalent array of JSON
|
|
objects. The [test scripts](../testdefs_json.sh) use the following [jq(1)][]
|
|
expression to perform the transformation:
|
|
|
|
[
|
|
.header as $header |
|
|
.rows as $rows |
|
|
$rows | keys | .[] as $index |
|
|
[ $rows[$index] as $d | $d | keys | .[] as $i | {key:$header[$i], value:$d[$i]} ] |
|
|
from_entries |
|
|
.["__index"] = $index
|
|
]
|
|
|
|
-----
|
|
**Copyright 2015 Serval Project Inc.**
|
|
![CC-BY-4.0](./cc-by-4.0.png)
|
|
Available under the [Creative Commons Attribution 4.0 International licence][CC BY 4.0].
|
|
|
|
|
|
[Serval Project]: http://www.servalproject.org/
|
|
[CC BY 4.0]: ../LICENSE-DOCUMENTATION.md
|
|
[API]: https://en.wikipedia.org/wiki/Application_programming_interface
|
|
[Serval DNA]: ../README.md
|
|
[Serval Mesh network]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:mesh_network
|
|
[HTTP REST]: https://en.wikipedia.org/wiki/Representational_state_transfer
|
|
[HTTP 1.0]: http://www.w3.org/Protocols/HTTP/1.0/spec.html
|
|
[MDP]: ./Mesh-Datagram-Protocol.md
|
|
[MSP]: ./Mesh-Stream-Protocol.md
|
|
[Keyring REST API]: ./REST-API-Keyring.md
|
|
[Keyring]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:keyring
|
|
[Rhizome REST API]: ./REST-API-Rhizome.md
|
|
[Rhizome]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:rhizome
|
|
[MeshMS REST API]: ./REST-API-MeshMS.md
|
|
[MeshMS]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:meshms
|
|
[Basic Authentication]: https://en.wikipedia.org/wiki/Basic_access_authentication
|
|
[Serval Mesh]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:servalmesh:development
|
|
[Intent]: http://developer.android.com/reference/android/content/Intent.html
|
|
[Permission]: https://developer.android.com/preview/features/runtime-permissions.html
|
|
[configured]: ./Servald-Configuration.md
|
|
[Internet Media Type]: https://www.iana.org/assignments/media-types/media-types.xhtml
|
|
[Rhizome bundle]: ./REST-API-Rhizome.md#bundle
|
|
[Rhizome manifest]: ./REST-API-Rhizome.md#manifest
|
|
[Rhizome JSON result]: ./REST-API-Rhizome.md#rhizome-json-result
|
|
[Keyring JSON result]: ./REST-API-Keyring.md#keyring-json-result
|
|
[bundle secret]: ./REST-API-Rhizome.md#bundle-secret
|
|
[text+binarysig format]: ./REST-API-Rhizome.md#textbinarysig-manifest-format
|
|
[JSON]: https://en.wikipedia.org/wiki/JSON
|
|
[UTF-8]: https://en.wikipedia.org/wiki/UTF-8
|
|
[jq(1)]: https://stedolan.github.io/jq/
|
|
[idempotent]: https://en.wikipedia.org/wiki/Idempotence
|
|
[SID]: ./REST-API-Keyring.md#serval-id
|
|
[Bundle ID]: ./REST-API-Rhizome.md#bundle-id
|