endpointjs
Version:
Endpoint.js enables modules within a web application to discover and use each other, whether that be on the same web page, other browser windows and tabs, iframes, servers and web workers in a reactive way by providing robust discovery, execution and stre
257 lines (208 loc) • 9.98 kB
Markdown
# Security
## Table of Contents
- [Anatomy of an Endpoint.js Network](#anatomy-of-an-endpoint-js-network)
- [Bridging Links](#bridging-links)
- [Limiting Discovery](#limiting-discovery)
- [Limiting Bus Events](#limiting-bus-events)
- [Client and Server Communication](#client-and-server-communication)
- [Cross-Domain in the Browser](#cross-domain-in-the-browser)
- [Stream Transformation](#stream-transformation)
- [Encryption and Digital Signatures](#encryption-and-digital-signatures)
- [PIN Authorization](#pin-authorization)
## Anatomy of an Endpoint.js Network
Endpoint.js uses [Zone Routing Protocol](https://en.wikipedia.org/wiki/Zone_Routing_Protocol) to transfer messages
throughout an ad-hoc [overlay network](https://en.wikipedia.org/wiki/Overlay_network) between windows, web workers,
clients, and servers. Intra-zone routing (or IARP) uses a modified, proactive
[Destination-Sequenced Distance Vector](https://en.wikipedia.org/wiki/Destination-Sequenced_Distance_Vector_routing), or DSDV,
routing protocol to discover and establish a routing table between hosts in the 'internal' network. Internal addressing is done using a unique
UUID value generated every time an Endpoint.js node starts up. Inter-zone routing (or IERP) uses a reactive path-vector based
algorithm to establish routes between hosts in the 'external' network. External addressing is done using a random
UUID value generated by the 'server' host (in the client/server relationship). Discovering and building routes can only
be done through a message passed via the Endpoint.js Bus.
Standard communication between windows, iframes, and web workers on the same origin is considered an 'internal' network.
Client to server connections are considered 'external' connections, as well as connections between windows using cross-origin
[PostMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) communication.
Effort has gone into securing Endpoint.js from malicious code execution, spoofing, and poisoning attacks, as well as
memory flooding attacks. However, as Endpoint.js Facade/Adapter connections are stateful, it still suffers from general
Denial of Service attack vectors. It is recommended that access be restricted to authenticated, authorized users to help
prevent these attacks.
## Limiting Discovery
Definitions:
- Local: Within the single Endpoint.js instance
- Group: Within all Endpoint.js instances considered 'internal', or within the same IARP routing network.
- Global: To all internal nodes, and immediate 'external' or border networks.
- Universal: To all nodes and networks, including those reachable by my neighbors
Adapters can specify whether they want to respond to local, group (default) or global/universal messages. This prevents
unauthorized clients from accessing protected functionality. By default, Adapters respond to Local or Group requests.
If a message originated from an external node, then it is considered a global/universal message. There is no way of
discriminating between a global or universal message because there is no trust relationship between external nodes.
Setting the neighborhood on an adapter. It will only respond to local facades:
```javascript
var settings = {
neighborhood: 'local'
};
var adapter = endpoint.createAdapter('chat-api', '1.0', chatApi, settings);
```
Facades can specify whether they want to broadcast on local(default), group, global or universal. If global is
specified, then the message will be sent to an external node with the request that it not be redistributed. If the
external node is malicious, it could send the message to an external node (thus making it universal). If universal
is specified, then the message will be sent universally to every internal and external node in the network. It is
recommended that this is used sparingly due to performance considerations.
See below on how to set the neighborhood on a query or facade. They will search for any adapter within the group:
```javascript
var settings = {
neighborhood: 'group'
};
var query = endpoint.createQuery('chat-api', '1.0', settings);
var facade = endpoint.createFacade('chat-api', '1.0', settings);
```
Valid values are 'local', 'group', 'global' and 'universal'.
## Bridging Links
By default, Endpoint.js will not operate as a relay for external traffic. This functionality must be explicitly
enabled by creating a bridge.
To create a bridge, use the configuration API:
```javascript
var bridge = endpoint.getConfiguration().createBridge(['external-interface-1', 'internal-interface-1']);
```
Additional links can be added or removed from the bridge after it has been created, or it can be closed:
```javascript
bridge.addLinkId('trusted-server');
bridge.removeLinkId('trusted-server');
bridge.close();
```
The 'createBridge' function supports a second argument allowing Endpoint.js to relay traffic between connections
on the link itself:
```javascript
var bridge = endpoint.getConfiguration().createBridge(['external-interface'], true);
```
Internal links will always relay to each other.
## Limiting Bus Events
Bridges can be used to limit which links receive bus events. This can be useful if you want to ensure that
requests for certain external APIs are only fulfilled by a certain interface.
The following example shows using the bridge to query and create a facade on a specific
interface only:
```javascript
var bridge = endpoint.getConfiguration().createBridge(['external-interface']);
var settings = {
bridgeId: bridge.getId()
};
// Create a query
var query = endpoint.createQuery('chat-api', '1.0', settings);
// Create a facade
var facade = endpoint.createFacade('chat-api', '1.0', settings);
```
## Client and Server Communication
By default, 'server' connections are considered external to protect internal networks. However, you may wish to
setup an internal network of nodes to take advantage of a cloud architecture or load balance. To create an internal
network, you can specify the 'external' flag when creating your link:
```javascript
var internalLink = {
linkId: 'internal-server',
type: 'server',
settings {
external: false
}
};
```
You must remember to host this link off of a different socket.io instance as one hosting external links. See
the app.js file in the examples/ folder for an example of this.
## Cross-Origin in the Browser
By default, additional 'window' links (beyond the built in one) are considered external to protect internal
networks.
To add a cross-domain window link, you must specify the origin:
```javascript
var crossDomainLink = {
linkId: 'cross-domain-xyz.com',
type: 'window',
settings {
external: true,
origin: 'xyz.com'
}
};
```
## Stream Transformation
All underlying connections in Endpoint.js are stream based. This means that transformations can be applied, traffic
can be interdicted, logged, encrypted, or digitally signed.
To add a stream transformer, specify the 'transformerFactory' option when creating/adding the link. This function should
take one parameter, an instance of LinkTransform, which allows the user to add new transformations.
Creating the link:
```javascript
var transformFactory = function(transform) {
var readFunc = function(chunk, enc, cb) {
console.log('reading message %j', chunk);
this.push(chunk);
cb();
};
var writeFunc = function(chunk, enc, cb) {
console.log('writing message %j', chunk);
this.push(chunk);
cb();
};
transform.addTransform(readFunc, writeFunc);
};
var link = {
linkId: 'id',
type: 'server',
settings: {
transformFactory: transformFactory
}
};
```
You can also use Node.js streams in the arguments to addTransform(). The 'chunk' values will be JSON objects, so
if encrypting or decrypting, you may have to apply a transform.
### Encryption and Digital Signatures
You can directly use the [Node.js crypto](https://nodejs.org/api/crypto.html) package to digitally sign messages
before they are sent to another node. Transform streams are considered Object streams, so you should perform
an operation to 'stringify' (See [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify))
the data before signing it.
### PIN Authorization
To authorize cross-domain communication, you may wish to create a 'pin' based authentication mechanism. In this type of
authorization model, a window pops up with a pin value, requesting that the value be copied and pasted into another window
on the opposite window. Once that value is specified, it is passed through the link and verified, letting traffic
pass.
A basic conceptual example (untested) follows:
Pin generation function:
```javascript
var pin;
var pinGenerated = false;
var queuedMessage = null;
var verified = false;
var pinRead = function(chunk, enc, cb) {
if (verified) {
this.push(chunk);
cb();
}
else {
if (!pinGenerated) {
pin = '12345';
pinGenerated = true;
createPinDialog(pin);
}
if (chunk.pin) {
if (chunk.pin == pin) {
verified = true;
if (queuedMessage) {
this.push(queuedMessage);
}
cb();
}
}
else {
queuedMessage = chunk;
}
}
};
```
Pin verification function:
```javascript
var pinWrite = function(chunk, enc, cb) {
var _this = this;
uiEvent.on('pin-ready', function(pin)) {
_this.push({
pin: pin
});
});
this.push(chunk);
cb();
};
```