# Communications Backbone

Communications Backbone by C2 Team (NOC)

### Infrastructure 

The backbone has 3 runtime components: 

- RabbitMQ - run as a docker container 
- The bus which handles the delivery of messages between queues 
- The API which provides an interface to read from and write to the queues

### Data flow 

- Client A sends to `client-a-outbox` (by POSTing to API /send)
- Messages are forwarded from `client-a-outbox` to `soar-publish` 
- Messages are published from `soar-publish` with the given topic read from the message 
- Subscribers listen to for messages 
- Subscription is delivered to `client-b-inbox` (if the client subscription matches the message topic) 
- Client B reads from `client-b-inbox` (by GETting from API /receive)

There is a parallel flow when a client sends to `client-a-broadcast` (by POSTing to /notify). 
In this case the messages are delivered through the broadcast exchange to all clients `client-x-inbox`.

### Prerequisites 

- Python >= 3.8
- A virtual environment manager - virtualenv, venv or pipenv 
- Docker and docker-compose 

### Running via docker-compose

```
run-compose.sh
```

Using `docker-compose` will mean that everything is setup automatically, this includes the `rabbitmq` container, the backbone API, and the backbone bus. The `run-compose.sh` script has been provided to simplify this even further - all you have to do is set whatever env vars you need in the `.env` file and then run `./run-compose.sh` (the defaults in `.env` are fine for local dev work, but ones not labelled `optional` will need setting in a production setting). The env vars are:

- `DATA_DIR` - Where to mount the volume of the API container on your local system. This defaults to the result of `pwd`, which should be within the `communications-backbone` repo
- `SOAR_TOKEN_LIFETIME` (Optional) - The number of hours until a newly created token expires
- `SOAR_TOKEN_SECRET` (Optional) - A secret key used to encrypt/decrypt token data. If specified the value should be set using TokenModel.getKey()

### Running the bus and API natively (without docker)

#### Setup 

We recommend using some form of python virtual environment to maintain a consistent 
python version and ring-fence the package management.

In your virtual environment: 

```
pip install -r requirements-dev.txt  
```

This installs both the development and runtime dependencies for the project.

### Testing 

Current coverage: 

- API: yes 
- Pika RabbitMQ implementation: no

In your virtual environment: 

```
pytest
```

#### RabbitMQ 

`docker run --rm -p 5672:5672 -d --hostname rmq --name rmq rabbitmq:management`

#### API 

In your virtual environment:

```
python api.py 
```

#### Event bus 

In your virtual environment: 

``` 
python soar_bus.py
```

At present the soar bus creates the clients defined in the API when it starts 
but does not monitor for new clients so if you create a new client you will 
need to restart the bus. This will be fixed in a later release.

### Client Adapters 

The intended use of the backbone is that multiple clients connect to the backbone 
using adapters. An adapter handles: 

- Authentication 
- Sending and receiving over the backbone API 
- Transforming messages between local formats and the backbone message formats

We have implemented the following template client adapters: 

- [Javascript](https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-javascript)
- [Python](https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-python)

If you need to port the adapter to another language please contact us. 

The adapters can be installed as packages and sub-classed as required for your 
client application.

For install and usage instructions for the adapters see the READMEs. 

### Usage

- Run the backbone 
- Create some clients
- Restart the soar_bus service 
  - `cd docker && docker-compose restart soar_bus` 
- Save your client credentials 
- Test reading and writing directly to the queues
- Create an adapter 
- Test sending and receiving via the adapter    

#### Create some clients

##### With the script 

```bash 
python client_create.py
# will create a default 'admin' client subscribed to all messages (#)
# OR 
python client_create.py --id=[client_id] --name="Your Client Name" --sub="something.something.#"
# will create a client with your preferred name, id and subscription
```

##### Through the API 

`POST` to `http://localhost:8087/client`

The POST body should be JSON. 

```json
{ 
  "client_id": "noc-c2",
  "client_name": "NOC C2", 
  "subscription": "soar.*.noc.#"
}
```

- `client_id` - a project unique human readable name 
- `client_name` - how to refer to that client on screen 
- `subscription` - the topic pattern identifying the messages you want to receive 

(`*` = single word wildcard, `#` = multi-word wildcard)

The response from the post contains your client `secret`. This is only displayed once. 

Subsequent GETS to `/client` or `/client/[client-id]` will not return the secret. 

You should save the response as `soar-config.json` adding the additional `api` field 
to specify the root URL of the API you're connecting to 
(eg http://localhost:8087 for local development)

#### Send / Receive directly 

```
# Send a message 
python client_send.py noc-c2-outbox 'soar.noc.slocum.something' from noc-c2
```

```
# Receive messages  
python client_read.py noc-sfmc-inbox
```

These scripts bypass authentication reading and writing directly to rabbitmq.

#### Authentication 

Authentication is handled by a client credentials grant which is a GET 
request to `/token?client_id=[client-id]&secret=[secret]`

The response includes a token and this token should be included as an 
authorization header in requests to the API: 

```json
{
  "Authorization": "Bearer [token]"
}
```

#### Send and Receive via API

Once you have a bearer token you can make requests to send and receive 
messages via the API: 

- GET `/receive?max_messages=[X]` - gets all messages in the queue 
  - the default value of `max_messages` if not specified is 10
- POST `/send` - publish a message to a given topic 
- POST `/notify` - broadcast a message to all clients 

Supplying the token as an Authorization header as described above.

#### Send and Receive using adapters 

If you have implemented one of the adapter templates then authentication 
is handled for you.

You will need to create a set of client credentials as above using the 
POST `/client` endpoint and save the response. 

Then you pass your credentials when you create your adapter instance.