perimeterx-fastly-enforcer 2.2.2

PerimeterX Fastly Compute@Edge Rust Enforcer
Documentation

image

PerimeterX Fastly Compute Rust Enforcer

Crates.io Documentation License Badge

Table of Contents

Project layout

  • src folder contains Fastly Compute Rust Module
  • example folder contains a sample Fastly Compute@ 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 Package, Fastly CLI must be installed and configured: Compute@ services

Configuration

PXConfig holds the full enforcer configuration. HUMAN product docs describe each option’s behavior; the following matches src/pxconfig.rs in this repository.

Loading and updates

  • ConfigStore (default): PXConfig::new(config_store_name, secret_store_name) opens the Fastly ConfigStore and reads each present PXConfigKey string. Keys that are missing keep the Rust Default for PXConfig.
  • Secret Store: when secret_store_name is non-empty, the enforcer opens the Fastly Secret Store and overlays these keys (same px_* names): px_app_id, px_cookie_secret, px_auth_token, px_logger_auth_token. Secret Store values override ConfigStore/KVStore for those fields. Missing secrets are non-fatal and leave the ConfigStore/KVStore value (or default) in place. If secret_store_name is an empty string (""), Secret Store is ignored and no secrets are loaded.
  • KVStore (kv_store Cargo feature): the same keys are read from (and optional updates persisted to) a Fastly KVStore.

Secret store keys

secret name type notes
px_app_id string HUMAN application ID; overrides ConfigStore/KVStore when present
px_cookie_secret string Secret used to validate risk cookies; overrides ConfigStore/KVStore when present
px_auth_token string Bearer token for Risk API, activities, telemetry; overrides ConfigStore/KVStore when present
px_logger_auth_token string Secret for header-based enforcer log collection; overrides ConfigStore/KVStore when present

Default Secret Store name: perimeterx_fastly_enforcer::DEFAULT_SECRETSTORE_NAME ("PXSecrets").

Config store keys (aligned with PXConfigKey)

configuration key type default if absent from store notes
px_app_id string "" Required, HUMAN application ID; may also be set in Secret Store
px_cookie_secret string "" Required, secret used to validate risk cookies; may also be set in Secret Store
px_auth_token string "" Required, Bearer token for Risk API, activities, telemetry; may also be set in Secret Store
px_debug bool false Enables verbose px_debug! logging
px_blocking_score number (u8) 100 Cookie v3 / Risk score blocking threshold
px_module_enabled bool false Master switch
px_module_mode string "monitor" e.g. monitor, active_blocking (PXModuleMode)
px_sensitive_headers list ["Cookie","Cookies"] Header names stripped from outbound risk/activity payloads
px_sensitive_routes list [] Path prefixes; always Risk API after valid low-risk cookie
px_sensitive_routes_regex list (regex strings) [] Same semantics; invalid patterns dropped at load
px_filter_by_route list [] Path prefixes skipped before verification
px_filter_by_extension list pxconstants::WHITELIST_EXT File extensions skipped before verification
px_filter_by_user_agent list [] User-agent values (exact match) skipped before verification
px_filter_by_ip list [] IPv4s (exact) or CIDR blocks (/0, /8, /16, /24, /32) skipped before verification
px_filter_by_http_method list [] Methods skipped before verification
px_custom_cookie_header string "x-px-cookies" Alternate cookie header; empty uses Cookie only
px_enforced_routes list [] Blocking on these paths even in monitor mode
px_monitored_routes list [] Monitor on these paths even in active blocking
px_bypass_monitor_header string "x-px-block" Header name; value 1 forces real block in monitor mode
px_first_party_enabled bool true First-party sensor / captcha / XHR proxy
px_custom_logo string "" Block page logo URL
px_js_ref string "" Extra block-page script URL
px_css_ref string "" Extra block-page stylesheet URL
px_ip_headers list [] Trusted client-IP headers, first match wins
px_log_endpoint string "" Fastly logging endpoint for activity delivery
px_data_enrichment_header_name string "" Request header for verified PXDE JSON; empty disables
px_extracted_cookies list [] Cookie names on Risk API extras (_pxac handled in code)
px_cors_support_enabled bool false CORS preflight / block response handling
px_cors_preflight_request_filter_enabled bool false Pass OPTIONS preflight without full verification when CORS enabled
px_graphql_enabled bool false POST GraphQL extraction on matching routes
px_graphql_routes list (regex strings) one pattern ^/graphql$ Invalid patterns dropped at load
px_sensitive_graphql_operation_names list [] Forces sensitive GraphQL handling
px_sensitive_graphql_operation_types list [] Operation types forcing sensitive handling
px_graphql_body_max_length number (usize) 1048576 Max bytes of body read for GraphQL JSON
px_s2s_timeout number (u32) 2000 Risk API timeout in ms (fail-open)
px_token_version string "3" Expected risk / mobile token version (TokenVersion)
px_custom_first_party_captcha_endpoint string "" Optional first-party captcha path override
px_custom_first_party_sensor_endpoint string "" Optional first-party sensor path override
px_custom_first_party_xhr_endpoint string "" Optional first-party XHR path override
px_user_agent_max_length number (usize) 8528 UA length cap for cookie validation
px_jwt_cookie_name string "" Cookie name that carries the customer JWT
px_jwt_cookie_user_id_field_name string "" Dot path in the JWT payload for the app user ID
px_jwt_cookie_additional_field_names list [] Extra JWT payload dot paths extracted from the cookie JWT
px_jwt_header_name string "" Header name that carries the customer JWT
px_jwt_header_user_id_field_name string "" Dot path in the JWT payload for the app user ID
px_jwt_header_additional_field_names list [] Extra JWT payload dot paths extracted from the header JWT
px_risk_cookie_max_length number (usize) 2048 Max raw risk cookie length
px_risk_cookie_min_iterations number (usize) 500 Cookie v3 PBKDF2 iteration lower bound
px_risk_cookie_max_iterations number (usize) 5000 Cookie v3 PBKDF2 iteration upper bound
px_logger_auth_token string "" Secret for header-based enforcer log collection; may also be set in Secret Store

pxconfig.sh script could be used to populate Config store with the required values. Usage:

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):

  • human_sapi: sapi-${APP_ID}.perimeterx.net
  • human_collector: collector-${APP_ID}.perimeterx.net
  • human_client: client.perimeterx.net
  • human_captcha: captcha.px-cdn.net

All HUMAN backends should use SSL/TLS. The SAPI and collector backend hosts can be overridden with the matching px_human_*_host and px_human_*_backend config keys. It's recommended to set "First byte (ms)" and "Between bytes (ms)" to 1000 ms.

Installation

Include perimeterx-fastly-enforcer dependency to Cargo.toml:

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):

    let mut px: PXEnforcer = PXEnforcer::new(
        perimeterx_fastly_enforcer::DEFAULT_CONFIGSTORE_NAME,
        perimeterx_fastly_enforcer::DEFAULT_SECRETSTORE_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(&mut response);

Enforcer API

Initialize PXEnforcer structure. It takes the Fastly ConfigStore name and Secret Store name (defaults: perimeterx_fastly_enforcer::DEFAULT_CONFIGSTORE_NAME and perimeterx_fastly_enforcer::DEFAULT_SECRETSTORE_NAME). secret_store_name can be an empty string (""); in that case, Secret Store is ignored and no secrets are loaded.

pub fn new(config_store_name: &str, secret_store_name: &str) -> Self

This function takes Request and returns a Result which optionally contains "Response" (for "blocked" or "first party" requests):

pub fn enforce(&mut self, req: &mut Request) -> Result<Option<Response>, Error>

At the end of request processing, the following function must be called to finalize PerimeterX enforcer code:

pub fn post_enforce(&mut self, res: &mut Response)

It is possible to access PXContext structure with various Enforcer variables via px.ctx():

    // send "score" value to the Origin
    req.set_header("x-px-score", px.ctx().get_score().unwrap_or(0).to_string());

To set "custom_parameters" variables, the following callback function could be used

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:

pub fn set_enrich_custom_params_fn(&mut self, f: PXEnrichCustomParamsFn)

To set "sensitive request" identification function, the following callback function could be used

pub type PXIsSensitiveRequestFn = fn (req: &Request, conf: &PXConfig) -> bool;

where:

  • req: fastly::Request
  • conf: PXConfig

To set sensitive request identification function, use the following setter:

pub fn set_is_sensitive_request_fn(&mut self, f: PXIsSensitiveRequestFn)

To set "additional activity" handler function, the following callback function could be used

pub type PXAdditionalActivityHandlerFn = fn (req: &Request, conf: &PXConfig, ctx: &PXContext);

where:

  • req: fastly::Request
  • conf: PXConfig
  • ctx: PXContext

To set additional activity handler function, use the following setter:

pub fn set_additional_activity_handler_fn(&mut self, f: PXAdditionalActivityHandlerFn)

To set a CORS preflight handler, the following callback function could be used

pub type PXCorsCustomPreflightHandlerFn = fn(req: &Request, conf: &PXConfig) -> Option<Response>;

where:

  • req: fastly::Request
  • conf: PXConfig

To set a custom preflight handler, use the following setter:

pub fn set_cors_custom_preflight_handler_fn(&mut self, f: PXCorsCustomPreflightHandlerFn)

To set custom CORS headers for block responses, the following callback function could be used

pub type PXCorsCustomBlockResponseHeadersFn =
    fn(req: &Request, conf: &PXConfig) -> Vec<(String, String)>;

where:

  • req: fastly::Request
  • conf: PXConfig

To set custom block-response CORS headers, use the following setter:

pub fn set_cors_create_custom_block_response_headers_fn(
    &mut self,
    f: PXCorsCustomBlockResponseHeadersFn,
)

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:

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<Response, Error> {
    match req.send(ORIGIN_BACKEND) {
        Ok(r) => return Ok(r),
        Err(e) => return Err(e.into()),
    }
}

#[fastly::main]
fn main(mut req: Request) -> Result<Response, Error> {

    // initialize PX Enforcer
    let mut px: PXEnforcer = PXEnforcer::new(
        perimeterx_fastly_enforcer::DEFAULT_CONFIGSTORE_NAME,
        perimeterx_fastly_enforcer::DEFAULT_SECRETSTORE_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 ...

    // it's possible to access "PXContext" structure.
    // Usage Example: send "score" value to the Origin, if "score" is available
    if let Some(score) = px.ctx().get_score() {
        req.set_header("x-px-score", score.to_string());
    } else {
        log::info!("Score is not available");
    }

    // a client function to communicate with the Origin
    let mut response = send_to_origin(req)?;

    // ... process Origin response ...

    // must be called at the end
    px.post_enforce(&mut response);

    // we are ok to send response back to client
    return Ok(response);
}

For a complete example: example folder contains a sample project.

Documentation

To see the crate documentation, run:

cargo doc --open

Additional Tools

Testing

cargo test --lib --tests