README.md

# Paraxial.io Elixir Agent

## Introduction 

The Paraxial agent provides developers with several Plugs for use in the defense of their application from malicious bots. These Plugs enable the following key features:

1. Classification and blocking of IP addresses from cloud providers, a strong signal the traffic is from a bot and not a real user.

2. Blocking clients that violate some user defined rule, for example, `If an IP sends > 3 login requests in a 1 second period, create an alert and ban the IP`.

3. Custom block and allow lists, as defined in your site's Paraxial.io account.

<br>

## Requirements and Installation 

### (Optional) Install remote_ip in your application 

If your application is showing a different `conn.remote_ip` than expected, it is probably behind a proxy. Install the [remote_ip](https://github.com/ajvondrak/remote_ip) library to fix this.

### Install :paraxial in your application's `mix.exs` file:

```elixir
def deps do
  [
    {:paraxial, "~> 0.1.0"}
  ]
end
```

<br>

### Use plug Plug.RequestId in your application's endpoint.ex file:

The majority of Phoenix applications do this by default. Check your `endpoint.ex` file for the line:

```
  plug Plug.RequestId
```

This plug sets `x-request-id`, which is required for the Paraxial agent to work correctly. 

<br>

### Application Configuration Example:

In your Paraxial.io account, we recommend creating two different sites for your application. One site for development/testing, and one site for production. For your local environment, edit your application's `config/dev.exs`: 

```elixir
config :paraxial,
  paraxial_api_key: System.get_env("PARAXIAL_API_KEY"),
  paraxial_url: "https://app.paraxial.io",
  fetch_cloud_ips: true,
  bulk: %{email: %{trusted: 100, untrusted: 3}},
  trusted_domains: MapSet.new(["paraxial.io", "blackcatprojects.xyz"])
```

Configuration keys and values:

1. `paraxial_api_key` - Found in your site's settings page. Required for secure communication between the agent and Paraxial.io backend service.

2. `paraxial_url` - This is most likely `https://app.paraxial.io` for your configuration.

3. `fetch_cloud_ips` - By default, Paraxial.io will sent HTTP requests to retrieve the public IP ranges of several cloud providers. If you wish to disable this, set `fetch_cloud_ips` to `false`. When disabled, matching incoming requests against cloud IP addresses will not work. 

<br>

## Check Configuration Settings

It is highly recommend you stop here and check that your application is configured correctly for your development environment before preceding.

1. Set your application's local logging level to debug. This will allow you to see debug messages from the Paraxial agent. Example `config/dev.exs`:

```
config :logger, level: :debug
```

2. Check the Paraxial lines in `config/dev.exs` are similar to:

```elixir
config :paraxial,
  paraxial_api_key: System.get_env("PARAXIAL_API_KEY"),
  paraxial_url: "https://app.paraxial.io",
  fetch_cloud_ips: true,
  bulk: %{email: %{trusted: 100, untrusted: 3}},
  trusted_domains: MapSet.new(["paraxial.io", "blackcatprojects.xyz"])
```

3. Start your application locally, read the debug lines from Paraxial. 

Bad start:

```
@ house % mix phx.server
[warning] Paraxial API key not found.
```

This warning means your application is not configured correctly. Check your `config` files. 

Bad start:

```
[error] Task #PID<0.764.0> started from Paraxial.Crow terminating
** (Protocol.UndefinedError) protocol Enumerable not implemented for nil of type Atom. Thi
```

This warning means you have an api_key and url defined, but at least one of them is incorrect. Check both.

Good start:
```
@ house % mix phx.server
[debug] [Paraxial] :fetch_cloud_ips set to true, fetching...
[debug] [Paraxial] Prefixes downloaded for aws: 7981
[debug] [Paraxial] Prefixes downloaded for azure: 59042
[debug] [Paraxial] Prefixes downloaded for digital_ocean: 1640
[debug] [Paraxial] Prefixes downloaded for gcp: 531
[debug] [Paraxial] Prefixes downloaded for oracle: 490
[debug] [Paraxial] Prefixes length with duplicates: 69684
[debug] [Paraxial] Iptrie count - 41834
[debug] [Paraxial] Iptrie size in MB: 1.299092
[debug] [Paraxial] Agent start success
[info] Running HouseWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4002 (http)
[info] Access HouseWeb.Endpoint at http://localhost:4002
[watch] build finished, watching for changes...
```

Now that the Paraxial agent is running in your application, it is time to configure the Paraxial Plugs. 

## Paraxial API Functions

`Paraxial.bulk_allowed?(email, bulk_action, count)`

Input: An application user's email address, the name of a bulk action, and the count for the action. 

Return value: True if the user is allowed to perform the action, false otherwise.

Example config:

```elixir
config :paraxial,
  # ...
  bulk: %{email: %{trusted: 100, untrusted: 3}},
  trusted_domains: MapSet.new(["paraxial.io", "blackcatprojects.xyz"])
```

```
> Paraxial.bulk_allowed?("mike@blackcatprojects.xyz", :email, 3)
> true

> Paraxial.bulk_allowed?("mike@blackcatprojects.xyz", :email, 100)
> true

> Paraxial.bulk_allowed?("mike@test.xyz", :email, 4)
> false
```

## Paraxial Plug Configuration

The Paraxial.io Agent provides several Plugs to be used in your application code:

1. `Paraxial.AllowedPlug` - Required, this Plug determines if an incoming requests matches your allow/block lists. If a request is halted by this Plug, internally Paraxial will still record it. 

2. `Paraxial.RecordPlug` - Required, records incoming HTTP requests into a local buffer, then sends them to the Paraxial.io backend.

3. `Paraxial.AssignCloudIP` - Optional, if the `remote_ip` of an incoming request matching a cloud provider IP address, this plug will add metadata to the conn via an assigns. For example, if a conn's remote_ip matches aws, this plug will do `assigns(conn, :paraxial_cloud_ip, :aws)`.

4. `Paraxial.BlockCloudIP` - Optional, similar to AssignCloudIP. When a conn matches a cloud provider IP, the assign is updated and the conn is halted, with a 404 response sent to the client. 


## Plug Installation 

To install Paraxial's plugs, open your application's `endpoint.ex` file and add the following before and after your Router:

```
plug Paraxial.AllowedPlug
plug Paraxial.RecordPlug
plug ExampleApp.Router
plug Paraxial.RecordPlug
```

When a request comes in, it first goes through the AllowedPlug. If it matches an IP on the ban list, it is blocked and the conn is halted. Requests that are halted in the AllowedPlug are still recorded, even though they never reach RecordPlug.

If a request passes AllowedPlug, un-halted, it has not been recorded yet, so it goes into the RecordPlug. The reason for putting a RecordPlug before the Router is to record requests that fail to match a path in the Router.

In your application you have the ability to define certain assigns, such as :paraxial_login_user_name, to map IP addresses to login attempts. After the Router, the request conn passes through RecordPlug again, with updated information (such as new assigns). If a request passes through both RecordPlugs, only the conn from the second one is recorded. 

### FAQ

Q: Phoenix applications send a response in the Router, why do you have a plug after the router?

A: It is still possible to read values off a conn that has been sent back to the user. RecordPlug is read-only, it never sends a response based on the conn, so it is okay to have one after the Router. 

## Paraxial Conn Assigns Index

This is a table of every assigns value Paraxial may set on an incoming conn. To avoid conflict with assigns in your application code, each assigns is prefixed with `paraxial`. 

| Key                       | Set By           | Type |
| :---                      | :---             | :--- |
| :paraxial_login_success   | User Application | Boolean    |
| :paraxial_login_user_name | User Application | String     |
| :paraxial_current_user    | User Application | String     |
| :paraxial_cloud_ip        | Paraxial Agent   | String (aws, azure, etc.) |


## How to Use Assigns in your Application 

To monitor login attempts, use:

```
assign(conn, :paraxial_login_success, true/false)
```

in your Phoenix application's authentication system. 

If you want to keep track of user names, use:

```
assign(conn, :paraxial_login_user_name, "userNameHere")
```

## Installation

```elixir
def deps do
  [
    {:paraxial, "~> 0.0.2"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/paraxial>.

Mix deps versions, https://hexdocs.pm/elixir/Version.html