# ![image](https://storage.googleapis.com/perimeterx-logos/primary_logo_red_cropped.png) # [PerimeterX](http://www.perimeterx.com) Fastly Compute@Edge Rust Enforcer [![Crates.io][crates-badge]][crates-url] [![License Badge]](./LICENSE) [crates-badge]: https://img.shields.io/crates/v/perimeterx-fastly-enforcer.svg?style=for-the-badge&logo=rust [crates-url]: https://crates.io/crates/perimeterx-fastly-enforcer [License Badge]: https://img.shields.io/crates/l/perimeterx-fastly-enforcer?style=for-the-badge ## Table of Contents - [Project layout](#projectlayout) - [Prerequisites](#prerequisites) - [Configuration](#configuration) - [PX Backend](#backend) - [Module installation](#installation) - [Module integration](#integration) - [Enforcer API](#api) - [Logging](#logging) - [Sample code](#sample) ## Project layout - `src` folder contains Fastly Compute@Edge Rust Module - `example` folder contains a sample Fastly Compute@Edge Rust application - `contrib/pxconfig.sh` script to create Fastly Config store and populate with default values - `contrib/pxbackend.sh` script to create and configure PX Backend host ## Prerequisites In order to compile and deploy Fastly Compute@Edge Package, Fastly CLI must be installed and configured: Compute@Edge services ## Configuration Module configuration is done using Compute@Edge Config store. Please refer PerimeterX documentation for individual configuration option description. This is the list of Config store (PXConfig) items and datatypes: | configuration name | type | | default | |--------------------|------|--|---------| | px_app_id | string | required | - | | px_cookie_secret | string | required | - | | px_auth_token | string | required | - | | px_module_enabled | bool | optional | false | | px_module_mode | "active_blocking", "monitor" | optional | "monitor" | | px_debug | bool | optional | false | | px_blocking_score | number | optional | 100 | | px_sensitive_headers | list | optional | [] | | px_sensitive_routes | list | optional | [] | | px_filter_by_route | list | optional | [] | | px_filter_by_user_agent | list | optional | [] | | px_filter_by_ip | list | optional | [] | | px_filter_by_http_method | list | optional | [] | | px_custom_cookie_header | string | optional | "" | | px_enforced_routes | list | optional | [] | | px_monitored_routes | list | optional | [] | | px_bypass_monitor_header | string | optional | "" | | px_first_party_enabled | bool | optional | true | | px_custom_logo | string | optional | "" | | px_js_ref | string | optional | "" | | px_css_ref | string | optional | "" | | px_ip_headers | list | optional | [] | | log_endpoint | string | optional | "" | | px_graphql_enabled | bool | optional | false | | px_graphql_routes | list | optional | ["^/graphql$"] | | px_sensitive_graphql_operation_names | list | optional | [] | | px_sensitive_graphql_operation_types | list | optional | [] | `pxconfig.sh` script could be used to populate Config store with the required values. Usage: ```bash Required options: -s, --service-id=SERVICE_ID specify a service to deploy the Config store -v, --version=VER specify a service version -a, --appid=APPID specify an appid -c, --cookie_secret=SECRET specify a cookie_secret -t, --auth_token=TOKEN specify an auth_token ``` ## PerimeterX Backend In order for PerimeterX Enforcer to communicate with PerimeterX Collector server, a special "backend" server must be added and configured in Fastly UI (or using contrib/pxbackend.sh script). Backend parameters (replace `${APP_ID}` with your PerimeterX Application ID): - Name: `PX_BACKEND_${APP_ID}` - Hostname: `sapi-${APP_ID}.perimeterx.net` - Override host: (empty value) - Use SSL/TLS: Yes ## Installation Include `perimeterx-fastly-enforcer` dependency to `Cargo.toml`: ```bash cargo add perimeterx-fastly-enforcer ``` ## Module integration To integrate PerimeterX Rust module into existing Rust code, the following base snippet could be used (for a more advanced example see the project in "example" folder): ```rust let mut px: PXEnforcer = PXEnforcer::new(perimeterx_fastly_enforcer::DEFAULT_CONFIGSTORE_NAME); let px_result = px.enforce(&mut req)?; if let Some(r) = px_result { return Ok(r); }; //... communicate with Origin server / process request and response px.post_enforce(&response); ``` ## Enforcer API Initialize PXEnforcer structure, it takes a name of Fastly "ConfigStore" (it's possible to use the default name: `perimeterx_fastly_enforcer::DEFAULT_CONFIGSTORE_NAME`) ```rust pub fn new(config_store_name: &str) -> Self ``` This function takes Request and returns a Result which optionally contains "Response" (for "blocked" or "first party" requests): ```rust pub fn enforce(&mut self, req: &mut Request) -> Result, Error> ``` At the end of request processing, the following function must be called to finalize PerimeterX enforcer code: ```rust pub fn post_enforce(&self, res: &Response) ``` It is possible to access PXContext structure with various Enforcer variables via `px.ctx` member: ```rust // send "score" value to the Origin req.set_header("x-px-score", px.ctx.score.to_string()); ``` To set "custom_parameters" variables, the following callback function could be used: ```rust pub type PXEnrichCustomParamsFn = fn (req: &Request, conf: &PXConfig, params: &mut PXCustomParams); ``` where: `req`: fastly::Request `conf`: PXConfig `params`: modifiable structure with custom_param1 .. custom_param10 fields To set custom parameters callback function, use the following setter: ```rust pub fn set_enrich_custom_params_fn(&mut self, f: PXEnrichCustomParamsFn) ``` ## Logging PerimeterX Rust Enforcer logs the most messages (both "info" and "debug") using `Level::Info` level. In order to enable Enforcer to produce "debug" messages, set `px_debug` configuration value to `true`. ## Sample code This is the simplest example how to use PerimeterX Rust module: ```rust use fastly::{Error, Request, Response}; use perimeterx_fastly_enforcer::pxenforce::PXEnforcer; const ORIGIN_BACKEND: &str = "origin_backend"; // send a request to the Origin server fn send_to_origin(req: Request) -> Result { match req.send(ORIGIN_BACKEND) { Ok(r) => return Ok(r), Err(e) => return Err(e.into()), } } #[fastly::main] fn main(mut req: Request) -> Result { // initialize PX Enforcer let mut px: PXEnforcer = PXEnforcer::new(perimeterx_fastly_enforcer::DEFAULT_CONFIGSTORE_NAME); // execute PX Enforcer for Request let px_result = px.enforce(&mut req)?; // return, if it's a "blocked" or "first party" response if let Some(r) = px_result { return Ok(r); }; // ... process Client request ... // we can access "PXContext" structure. // as an example: send "score" value to the Origin req.set_header("x-px-score", px.ctx.score.to_string()); // a client function to communicate with the Origin let response = send_to_origin(req)?; // ... process Origin response ... // must be called at the end px.post_enforce(&response); // we are ok to send response back to client return Ok(response); } ``` For a complete example: `example` folder contains a sample project.