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. Blocking traffic from known-bad user agents.

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


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 library to fix this.

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

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

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.


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:

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.


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
  1. Check the Paraxial lines in config/dev.exs are similar to:
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"])
  1. 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:

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'sendpoint.exfile 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? 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. Q: Why this order? Why can't AllowedPlug be after RecordPlug? "A: If you do that order, then ban an IP, you will get a (Plug.Conn.AlreadySentError). This is because the RecordPlug is NOT read-only." - old answer, RecordPlug is read only now. Maybe you can swap. ## 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 withparaxial. | 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 If [available in Hex](https://hex.pm/docs/publish), the package can be installed by addingparaxialto your list of dependencies inmix.exs`: elixir def deps do [ {:paraxial, "~> 0.0.2"} ] end Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/paraxial. Mix deps versions, https://hexdocs.pm/elixir/Version.html