Fastly Compute (Rust)
Add Centinel Analytica bot protection to your Fastly Compute service using the Rust SDK.
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
Installation
Add the Centinel crate to your Fastly Compute project:
[dependencies]
centinel-analytica-fastly = "0.1.2"
fastly = "0.11"Setup
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")?)
}Add to fastly.toml:
[setup.backends]
[setup.backends.origin]
address = "your-origin.com"
port = 443
[setup.backends.centinel]
address = "validate.centinelanalytica.com"
port = 443
override_host = "validate.centinelanalytica.com"
[setup.config_stores]
[setup.config_stores.centinel]# 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_KEYfastly resource-link create \
--version=latest \
--autoclone \
--service-id=YOUR_SERVICE_ID \
--resource-id=YOUR_STORE_ID# Build the WASM package
fastly compute build
# Deploy to Fastly
fastly compute deployUsage 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")?)
})
}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=2000Local 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:7676Install testing tools:
cargo install viceroy cargo-nextest
rustup target add wasm32-wasip1Create .cargo/config.toml:
[build]
target = "wasm32-wasip1"
[target.wasm32-wasip1]
runner = "viceroy run -C fastly.toml -- "Run tests:
cargo nextest run --releaseConfiguration options
All configuration lives in the Fastly Config Store named centinel:
Prop
Type
How it works
Centinel validates requests at the edge before they reach your origin. Suspicious requests are blocked or redirected. Browser scripts can be injected into HTML responses for client-side tracking. If the API is unavailable, requests pass through to keep your service online.
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_IDAll requests being allowed
Check your logs for errors:
fastly log-tail --service-id=YOUR_SERVICE_ID | grep -i centinelVerify 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.