Centinel AnalyticaCentinel Analytica
PlatformsCDN / Edge

Fastly Compute (Rust)

Add Centinel Analytica bot protection to your Fastly Compute service using the Rust SDK.

Overview

The Centinel Rust crate integrates with Fastly Compute to validate requests at the edge before they reach your origin. Suspicious requests are blocked or redirected; legitimate traffic and browser script injection are handled automatically. If the API is unavailable, requests pass through to keep your service online.

Prerequisites

  • Fastly account with Compute enabled
  • Centinel secret key (server-side validation)
  • Centinel site key (browser script)
  • Rust 1.78.0 or later
  • Fastly CLI installed

Install

Add the Centinel crate to your Fastly Compute project:

[dependencies]
centinel-analytica-fastly = "0.1"
fastly = "0.11"

Configure

Update your main function

Replace src/main.rs:

use centinel_analytica_fastly::Centinel;
use fastly::{Error, Request, Response};

#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
    let centinel = Centinel::new()?;

    if let Some(block_response) = centinel.check_request(&req)? {
        return Ok(block_response);
    }

    Ok(req.send("origin")?)
}
Configure backends

Add to fastly.toml:

[setup.backends]
[setup.backends.origin]
address = "your-origin.com"
port = 443

[setup.backends.centinel]
address = "validator.centinelanalytica.com"
port = 443
override_host = "validator.centinelanalytica.com"

[setup.config_stores]
[setup.config_stores.centinel]
Create config store
# Create the config store
fastly config-store create --name=centinel

# Add your secret key
fastly config-store-entry create \
  --store-id=YOUR_STORE_ID \
  --key=centinel_secret_key \
  --value=YOUR_SECRET_KEY

# Add your site key (for browser script)
fastly config-store-entry create \
  --store-id=YOUR_STORE_ID \
  --key=centinel_site_key \
  --value=YOUR_SITE_KEY
Link config store to service
fastly resource-link create \
  --version=latest \
  --autoclone \
  --service-id=YOUR_SERVICE_ID \
  --resource-id=YOUR_STORE_ID

Configuration options

All configuration lives in the Fastly Config Store named centinel:

Prop

Type

Advanced configuration

Custom routing

Route different paths to different backends:

use centinel_analytica_fastly::Centinel;
use fastly::{Error, Request, Response};

#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
    let centinel = Centinel::new()?;

    if let Some(block_response) = centinel.check_request(&req)? {
        return Ok(block_response);
    }

    let backend = if req.get_path().starts_with("/api/") {
        "api_backend"
    } else {
        "origin"
    };

    Ok(req.send(backend)?)
}

Health check bypass

Skip Centinel for health checks:

use centinel_analytica_fastly::Centinel;
use fastly::{Error, Request, Response};

#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
    if req.get_path() == "/health" {
        return Ok(Response::from_status(200)
            .with_body_text_plain("OK\n"));
    }

    let centinel = Centinel::new()?;

    if let Some(block_response) = centinel.check_request(&req)? {
        return Ok(block_response);
    }

    Ok(req.send("origin")?)
}

URL exclusions

Exclude static assets from bot protection:

fastly config-store-entry create \
  --store-id=YOUR_STORE_ID \
  --key=centinel_url_exclusion \
  --value='\.(css|js|png|jpg|svg|woff2?)$'

Protect specific paths

Only protect API routes:

fastly config-store-entry create \
  --store-id=YOUR_STORE_ID \
  --key=centinel_url_inclusion \
  --value='^/api/'

Custom timeout

Adjust the API timeout (default 300ms):

fastly config-store-entry create \
  --store-id=YOUR_STORE_ID \
  --key=centinel_timeout_ms \
  --value=2000

Verify

Deploy
# Build the WASM package
fastly compute build

# Deploy to Fastly
fastly compute deploy

Usage patterns

Simple validation

Server-side validation only:

use centinel_analytica_fastly::Centinel;
use fastly::{Error, Request, Response};

#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
    let centinel = Centinel::new()?;

    if let Some(block_response) = centinel.check_request(&req)? {
        return Ok(block_response);
    }

    Ok(req.send("origin")?)
}

Validation + script injection

Server-side validation with browser script:

use centinel_analytica_fastly::Centinel;
use fastly::{Error, Request, Response};

#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
    let centinel = Centinel::new()?;

    if let Some(block_response) = centinel.check_request(&req)? {
        return Ok(block_response);
    }

    let mut response = req.send("origin")?;

    if centinel.has_site_key() {
        response = centinel.inject_script(response);
    }

    Ok(response)
}

Automatic protection

Callback pattern with automatic script injection:

use centinel_analytica_fastly::Centinel;
use fastly::{Error, Request, Response};

#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
    Centinel::protect(req, |allowed_req| {
        Ok(allowed_req.send("origin")?)
    })
}

Local testing

Configure local testing in fastly.toml:

[local_server.config_stores]
[local_server.config_stores.centinel]
format = "inline-toml"
[local_server.config_stores.centinel.contents]
centinel_secret_key = "test-secret-key"
centinel_site_key = "test-site-key"

Run locally:

# Start local development server
fastly compute serve

# Service available at http://127.0.0.1:7676

Install testing tools:

cargo install viceroy cargo-nextest
rustup target add wasm32-wasip1

Create .cargo/config.toml:

[build]
target = "wasm32-wasip1"

[target.wasm32-wasip1]
runner = "viceroy run -C fastly.toml -- "

Run tests:

cargo nextest run --release

Troubleshooting

Config Store not found

Link the config store to your service:

fastly resource-link create \
  --version=latest \
  --autoclone \
  --service-id=YOUR_SERVICE_ID \
  --resource-id=YOUR_STORE_ID

All requests being allowed

Check your logs for errors:

fastly log-tail --service-id=YOUR_SERVICE_ID | grep -i centinel

Verify your secret key is correct and increase timeout if needed.

Script not injecting

Make sure centinel_site_key is set in your config store and responses have Content-Type: text/html.

Resources

Changelog

  • v0.1.11 — Hardened validator client
  • v0.1.6 — Response header passthrough

On this page