Centinel AnalyticaCentinel Analytica
Platforms

Fastly VCL

Deploy Centinel Analytica on your Fastly VCL service via Terraform or the Fastly Web UI.

Overview

Seven VCL snippets and a Terraform module add an edge-side validation step against the Centinel /validate API. Requests are checked, blocked, redirected, or forwarded to your origin based on the validator's decision. Fail-open by default. Takes 2-3 minutes to roll out across Fastly's edge.

API key storage

The integration stores the API key in a Fastly Edge Dictionary named centinel_config. By default this is a standard (non-private) dictionary, so the value is visible in the Fastly dashboard and via the API to anyone who can read the service.

To hide the value from list/read endpoints, mark the dictionary as private (write_only=true). Private dictionaries can only be created via the Fastly API, not the Web UI. Even when private, Fastly stores the value in plaintext on its infrastructure.

Pure VCL services cannot use Fastly's Secret Store (Compute@Edge-only). If you need encryption-at-rest for the API key, use the Fastly Compute (Rust) integration instead.

Prerequisites

  • Centinel API key (for validator authentication)
  • Fastly account with API token (Account → Personal API Tokens, full-service access)
  • A configured origin backend
  • Terraform 1.0+ (recommended) or access to the Fastly Web UI

Download

Download centinel-fastly.zip — contains the seven VCL snippet files (init, recv, pass, miss, fetch, deliver, error), the Terraform module (main.tf), and a README explaining the snippet contract.

How it works

On every request, the snippets:

  1. Skip static assets and any path matching the configured exclusion regex in recv.vcl.
  2. POST request metadata to validator.centinelanalytica.com/validate with User-Agent: centinel-fastly-vcl/2.0.0. Because pure Fastly VCL cannot set a custom POST body, the payload is sent as X-Centinel-* request headers (URL, method, IP, cookie, referrer, and original UA/Accept headers).
  3. Read the validator's decision from x-centinel-* response headers. No JSON parsing happens in VCL.
  4. Apply the decision:
    • allow / not_matched → forward to origin
    • block → 403 + custom HTML body (decoded from base64)
    • redirect → 200 + verification page
  5. Forward Set-Cookie from the validator and a whitelisted set of six response headers (Content-Type, Cache-Control, X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, Referrer-Policy).

If the validator returns a non-2xx response or is unreachable, requests fail-open to origin.

Method 1: Terraform deployment

Step 1 — Install prerequisites

# macOS
brew install terraform

terraform --version    # >= 1.0

Step 2 — Set up authentication

export FASTLY_API_KEY="your-fastly-api-token"

Create an API token in your Fastly dashboard under Account → Personal API Tokens. The token needs full-service access.

Step 3 — Configure variables

In the snippets/ directory from the downloaded zip, create terraform.tfvars:

centinel_api_key = "your-centinel-api-key"
domain_name      = "www.example.com"
origin_address   = "origin.example.com"
service_name     = "Centinel Production"

# Optional:
# origin_port              = 443
# origin_use_ssl           = true
# debug                    = false
# validator_host           = "validator.centinelanalytica.com"

Step 4 — Initialize and deploy

terraform init
terraform plan
terraform apply

This creates:

  • A Fastly service with your domain
  • An origin backend pointing at origin_address
  • A centinel backend pointing at validator.centinelanalytica.com
  • An Edge Dictionary named centinel_config holding the API key and debug flag. Non-private by default. To make it private, set dictionary_write_only = true in tfvars and populate items via the Fastly API after apply (the Terraform provider can't read write_only dictionaries).
  • 7 VCL snippets at priority 50 (init, recv, pass, miss, fetch, deliver, error)

Step 5 — Verify

terraform output service_id
terraform output service_domain

# Hit your domain
curl -i https://www.example.com/

# Expect: 200 OK, Set-Cookie: _centinel=...; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=86400
# and: Server-Timing: validator;dur=<ms>

Method 2: Manual installation via Fastly Web UI

Step 1 — Prepare your service

  1. Log into your Fastly dashboard.
  2. Select your service or create a new one.
  3. Click Clone version to create an editable draft (you can't edit active versions).

Step 2 — Create the origin backend

In Origins → Hosts, add your origin backend (name it origin).

Step 3 — Create the centinel backend

In Origins → Hosts, add a second backend:

  • Name: centinel
  • Address: validator.centinelanalytica.com
  • Port: 443
  • Use SSL: yes
  • SSL hostname: validator.centinelanalytica.com
  • SSL SNI hostname: validator.centinelanalytica.com
  • Override host: validator.centinelanalytica.com
  • Connect timeout: 3000 ms
  • First byte timeout: 5000 ms
  • Between bytes timeout: 2000 ms

Step 4 — Create the Edge Dictionary

In Edge Dictionaries → Create:

  • Name: centinel_config
  • Add items:
    • secret_key<your-centinel-api-key>
    • debugfalse
  • Save.

Want a private dictionary?

Private (write_only) dictionaries can only be created via the Fastly API, not the Web UI. After creating the regular dictionary above, you can recreate it as private via:

fastly dictionary create --service-id=<SID> --version=<active>+1 \
  --name=centinel_config --write-only

then re-add items via fastly dictionary-item create. Private dictionaries hide values from list endpoints but Fastly still stores them in plaintext.

Step 5 — Upload the seven VCL snippets

In VCL Snippets → Create snippet, repeat for each file in the snippets/ directory from the downloaded zip:

FileSnippet typeNamePriority
init.vclinitcentinel_init50
recv.vclrecvcentinel_recv50
pass.vclpasscentinel_pass50
miss.vclmisscentinel_miss50
fetch.vclfetchcentinel_fetch50
deliver.vcldelivercentinel_deliver50
error.vclerrorcentinel_error50

Paste each file's contents verbatim. You do not edit a placeholder API key. The snippets read the key from the dictionary at runtime via table.lookup(centinel_config, "secret_key", "").

Step 6 — Activate the service

  1. Review all snippets and backends.
  2. Click Activate on the new version.
  3. Wait 2-3 minutes for the new VCL to propagate globally.

Step 7 — Verify

curl -i https://www.example.com/

# Expect:
#   HTTP/1.1 200 OK
#   Set-Cookie: _centinel=...; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=86400
#   Server-Timing: validator;dur=<ms>

Operating the integration

Customizing the path filter

The static-asset exclusion regex is hardcoded in recv.vcl because Fastly VCL does not allow regex patterns to be sourced from variables. To customize, either:

  1. Edit snippets/recv.vcl directly before upload, OR
  2. Use Terraform's replace() / templatefile() to substitute the regex at deploy time.

The default excludes common static-asset extensions: avi, avif, bmp, css, eot, flac, flv, gif, gz, ico, jpeg, jpg, js, json, less, map, mka, mkv, mov, mp3, mp4, mpeg, mpg, ogg, ogm, opus, otf, png, svg, svgz, swf, ttf, wav, webm, webp, woff, woff2, xml, zip.

Self-hosted validator

If you run a self-hosted Centinel validator, set the Terraform variable validator_host = "<your-host>". For UI installs, edit the centinel backend you created in Step 3.

Debug mode

Set debug = "true" on the dictionary item (or debug = true in Terraform). The response then includes x-centinel-decision, x-centinel-request-id, x-centinel-crawler-name, and x-centinel-crawler-category headers. Useful for support and curl debugging. Turn off in production.

Disabling temporarily

Delete the secret_key dictionary item. The validator returns 401, fetch.vcl fails open, and all requests flow to origin until you re-add the key. Alternatively, delete the centinel_recv snippet to bypass validation entirely.

Rotating the API key

# Terraform path
terraform apply -replace=fastly_service_dictionary_items.centinel_config

# UI path: edit the `secret_key` item in the centinel_config dictionary.

Validator outage handling

The integration is fail-open: if the validator returns a non-2xx response, times out, or is unreachable, the request goes through to origin. You'll see x-centinel-failed: 1 in responses when debug=true, and the response still includes Server-Timing: validator;dur=<ms>.


Configuration reference

Terraform variables

VariableTypeRequiredDefaultDescription
centinel_api_keystringyesCentinel validator API key. Stored in the dictionary; required for validation.
domain_namestringyesYour service domain (e.g. www.example.com).
origin_addressstringyesOrigin backend address.
service_namestringnocentinel_protected_serviceFastly service name.
origin_portnumberno443Origin port.
origin_use_sslboolnotrueUse TLS to origin.
validator_hoststringnovalidator.centinelanalytica.comOverride the validator hostname (self-hosted setups).
validator_ssl_check_certboolnotrueStrict cert validation on the validator backend. Set false only for test setups behind shared-cert proxies.
debugboolnofalseEcho x-centinel-* debug headers on the client response.
dictionary_write_onlyboolnofalseMark centinel_config as private (write_only). When true, populate items via the Fastly API after terraform apply; the Terraform provider cannot read write_only dictionaries.

Edge Dictionary items (centinel_config)

KeyRequiredDescription
secret_keyyesCentinel validator API key.
debugno"true" or "false" (string). When "true", debug headers leak to client responses.

Snippet contract

Upload all snippets at priority 50. Names must match the centinel_<type> convention so future updates can find them.


Limitations

The integration is constrained by what pure Fastly VCL supports. Known caps for v2.0.0:

  • Cookies cap = 1. VCL extracts only the first cookie from the validator's x-centinel-cookies-json response header (Fastly VCL has no JSON parser; regex extraction handles only one). Today the validator returns at most one cookie (_centinel), so this is a non-issue in practice.
  • Out-header whitelist is fixed. VCL forwards a hardcoded set of 6 response headers (Content-Type, Cache-Control, X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, Referrer-Policy). Adding more requires editing deliver.vcl.
  • No collector script injection. Body modification is fragile in pure VCL. Add the Centinel collector <script> tag directly in your HTML, or use the Fastly Compute (Rust) integration which supports injection.
  • API key is plaintext on Fastly infra. The centinel_config Edge Dictionary stores values in plaintext by default; this is visible in the dashboard and via the Fastly API. Marking the dictionary as private (write_only=true, API-only) hides the value from list/read endpoints, but Fastly still stores it in plaintext on its infrastructure. For encryption-at-rest, switch to the Fastly Compute (Rust) integration which uses Fastly Secret Store.
  • Backend health-pre-check is not available. Pure VCL cannot inspect a named backend's .healthy from outside its currently-assigned scope. Outages incur one validator timeout per request before fail-open kicks in.

Troubleshooting

The validator was unreachable. Look for:

  • Server-Timing: validator;dur=<ms> in the response confirms the validator was attempted.
  • x-centinel-failed: 1 (when debug=true) confirms fail-open fired.
  • Check network connectivity from Fastly to validator.centinelanalytica.com.
  • Verify the secret_key dictionary item is set (a missing key causes 401 from validator → fail-open).

Requests blocked unexpectedly

  • Set debug=true and inspect x-centinel-decision and x-centinel-request-id.
  • Check your Centinel dashboard for the corresponding request ID.

terraform apply fails with "Not allowed to read contents of write_only dictionary"

You set dictionary_write_only = true. The Fastly Terraform provider cannot read items from write_only dictionaries. Either:

  • Set dictionary_write_only = false (default) and accept that dictionary items appear in plan output, OR
  • Set dictionary_write_only = true AND populate items via the Fastly API after first apply, then remove the fastly_service_dictionary_items resource from the Terraform module.

Static asset paths still get validated

Your file extension isn't in the default regex in recv.vcl. Edit the regex literal to add it, then redeploy.


Changelog

  • 2.0.0 — Rewrite. Header-mode response from validator. Edge Dictionary for the API key (optionally private via API). Backoff via Fastly's backend health probe + vcl_fetch fail-open. End-to-end verified on real Fastly.
  • 1.0.0 — Initial release. Superseded by v2.

On this page