Osgood and CouchDB
Tags: javascript, security, microservices
Published: 2019-07-22 18:53:38

We recently announced a new open source project, [Osgood](https://github.com/IntrinsicLabs/osgood), which aims to be a secure platform for running JavaScript on the server. This platform applies the _Principle of Least Privilege_ to application code. One of the ways we enforce this is by limiting the types of operations an application can perform. For example, arbitrary network connections cannot be made and child processes cannot be executed.

Outbound HTTP requests are a first class citizen thanks to the `fetch()` API. This means that **CouchDB**, a NoSQL database with an HTTP API, is a perfect match for performing application persistence with Osgood.

One of the biggest strengths of Osgood is the ability to specify policies on a per-route basis. This allows for very-fine security enforcement, allowing each Osgood Worker to only perform pre-approved operations.


## Sample CRUD Application

Consider a simple CRUD application. This app represents a microservice within a larger organization. The service is essentially a facade in front of other services. It performs validation of the provided data, like enforcing username length. It limits database interactions, such as preventing arbitrary destructive queries from running. This app also decouples application code from the database implementation by transforming data into an ideal format. It also handles the database authentication, keeping the credentials on a trusted internal service and out of the client.

This microservice will have five endpoints:

- List Users (`GET /users`)
- Create User (`POST /users`)
- Get User (`GET /users/{user_id}`)
- Delete User (`DELETE /users/{user_id}`)
- Update User (`PUT /users/{user_id}`)

> Note: If you'd like to follow along with this example, the code for the project is open source and available in our repository: [IntrinsicLabs/osgood/examples/couchdb-rest](https://github.com/IntrinsicLabs/osgood/tree/master/examples/couchdb-rest)


## Application Configuration: `app.js`

Osgood Applications are configured using JavaScript. There is a global object called `app` available for setting properties. The first one is `interface` and is the name of the interface we want our application to bind to. The second one is `port` and is the port we want to listen on.

There are also some methods available on the `app` object for performing routing of incoming HTTP requests based on HTTP method and path patterns. For example, to route an incoming `GET` request to the `/users` endpoint, one can call `app.get('/users', ...)`. The second argument to the routing functions is a path to the Osgood Worker file. The third argument is a function for configuring the route's policy.

Within the policy configuration functions we specify which URLs can be requested. These can be configured by calling methods like this: `policy.outboundHttp.allowMETHOD(urlPattern)`. The `urlPattern` uses the `glob` syntax.

> Note: This API is described in much more detail in our [API Documentation](https://github.com/IntrinsicLabs/osgood/wiki/Osgood-Application-File#route-pattern).

This is what an Osgood Application file might look like for our CouchDB application:

```javascript
app.interface = '0.0.0.0';
app.port = 8000;

app.get('/users', 'list.js', policy => {
  policy.outboundHttp.allowGet('http://localhost:5984/users/_all_docs');
});

app.get('/users/:user_id', 'view.js', policy => {
  policy.outboundHttp.allowGet('http://localhost:5984/users/*');
});

app.delete('/users/:user_id', 'delete.js', policy => {
  policy.outboundHttp.allowGet('http://localhost:5984/users/*');
  policy.outboundHttp.allowDelete('http://localhost:5984/users/*');
});

app.post('/users', 'create.js', policy => {
  policy.outboundHttp.allowPost('http://localhost:5984/users');
});

app.put('/users/:user_id', 'update.js', policy => {
  policy.outboundHttp.allowPut('http://localhost:5984/users/*');
});
```

We've now described all of the capabilities and have fully configured our application within a single file. With this configuration our application would _not_ be able to, say, send an HTTP request to `http://evil.co`, nor would the `GET /users` route be able to perform a `DELETE` operation against the `users` collection in CouchDB.

Describing the capabilities up front is beneficial for two reasons. The straightforward reason is that it is secure. A side-effect is that application code is now _much_ easier to audit. Imagine how quick those tedious GDPR audits could be if you had this list of I/O available for all your other apps.


## Create User Worker: `create.js`

Our application has five operations that it can perform. In this post we'll only look at one of them: the creation of users (if you'd like to see the other examples checkout the [sample application](https://github.com/IntrinsicLabs/osgood/tree/master/examples/couchdb-rest) on GitHub).

This route will accept an incoming POST request, convert the body into JSON, perform some minimal validation, then pass the data to CouchDB (along with auth credentials). It'll then relay information to the client based on whether or not the operation succeeds.

```javascript
const AUTH = `Basic ${btoa('osgood_admin:hunter12')}`;

export default async (request) => {
  try {
    var user = await request.json();
  } catch (e) {
    return json({"error": "CANNOT_PARSE_REQUEST"}, 400);
  }

  if (user.id || user._id) {
    return json({"error": "CANNOT_OVERRIDE_ID"}, 400);
  }

  if (!user.username || typeof user.username !== 'string'
      || user.username.length < 3 || user.username.length > 20) {
    return json({"error": "USERNAME_INVALID"}, 400);
  }

  const payload = await fetch(`http://localhost:5984/users`, {
    method: 'POST',
    headers: {
      Authorization: AUTH,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(user)
  });

  const obj = await payload.json();

  if (obj.error) {
    return json({"error": "UNABLE_TO_INSERT"}, 500);
  }

  return json({ok: true});
}

function json(obj, status = 200) {
  const headers = new Headers({
    'Content-Type': 'application/json'
  });

  const body = JSON.stringify(obj);

  const response = new Response(body, { headers, status });

  return response;
}
```

If you've ever worked with Service Workers, Lambda Functions, or Express.js controllers then this code might look familiar. The file exports a single default function which accepts `request` and `context` arguments. The `request` argument is an instance of the [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object available in modern browsers. The `context` argument has some additional niceties that we don't need for this particular example. The function itself can be an `async` function or otherwise return a promise. If the promise rejects Osgood will respond to the client with a `500` error. If it resolves a `string` or a simple object then Osgood will reply with a `200` and an appropriate Content Type. But, for fine grained control, a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object can be returned which allows for manually setting the HTTP status code and other headers.


## Running Osgood

To run Osgood, first [download a release](https://github.com/IntrinsicLabs/osgood/releases) for your platform. Once that's done extract the `osgood` binary somewhere, ideally in your `$PATH`.

Then, download the six files for this project (`app.js`, `list.js`, `create.js`, `delete.js`, `update.js`, `view.js`). Finally, run this command:

```sh
$ osgood app.js
```

This will start the Osgood Application and route requests to the five Osgood Workers. Of course, the service won't be too useful without a CouchDB instance to talk to. The following commands will run CouchDB in a Docker container:

```sh
$ docker run \
  -e COUCHDB_USER=osgood_admin \
  -e COUCHDB_PASSWORD=hunter12 \
  -p 5984:5984 \
  --name osgood-couch \
  -d couchdb
$ curl \
  -X PUT \
  http://localhost:5984/users
```

After that we're ready to interact with the application. The next command will send a POST request to the Osgood Application and create our first user:

```sh
$ curl \
  -X POST \
  http://localhost:8000/users \
  -d '{"username": "osgood"}' \
  -H "Content-Type: application/json"
```


## More Information

Osgood is open source. It's written in Rust and runs JavaScript using the speedy V8 engine.

The sourcecode is hosted on GitHub and is available at [IntrinsicLabs/osgood](https://github.com/IntrinsicLabs/osgood). Pull Requests welcome!