Skip to main content

Install

Requirements​

Otoroshi18.0.0 or later
Java17 or above

Download Otoroshi​

Otoroshi release

curl -L -o otoroshi.jar \
'https://github.com/MAIF/otoroshi/releases/download/v18.0.0-preview6/otoroshi.jar'

Download the Threat Protection Suite extension​

Threat Protection Suite release

Grab the latest jar from the releases page. The asset is named otoroshi-waf-extension_3-<version>.jar:

curl -L -o otoroshi-waf-extension.jar \
'https://github.com/cloud-apim/otoroshi-waf-extension/releases/download/<version>/otoroshi-waf-extension_3-<version>.jar'

Every release also ships a checksum. Worth checking, for a security component:

curl -L -o otoroshi-waf-extension.jar.sha256 \
'https://github.com/cloud-apim/otoroshi-waf-extension/releases/download/<version>/otoroshi-waf-extension_3-<version>.jar.sha256'

shasum -a 256 otoroshi-waf-extension.jar | cut -d ' ' -f 1
cat otoroshi-waf-extension.jar.sha256
Releases up to 0.0.10 are for Otoroshi 17

Those assets are named otoroshi-waf-extension_2.12-*.jar, are built for Scala 2.12 and Otoroshi 17.x, and do not include the ip reputation module. Everything in this documentation that concerns threat feeds and CrowdSec needs a _3 build on Otoroshi 18.

Run Otoroshi with the extension​

java -cp "./otoroshi-waf-extension.jar:./otoroshi.jar" \
-Dotoroshi.adminLogin=admin \
-Dotoroshi.adminPassword=password \
-Dotoroshi.storage=file \
-Dotoroshi.admin-extensions.configurations.cloud-apim_extensions_waf.enabled=true \
play.core.server.ProdServerStart

The admin UI is then at http://otoroshi.oto.tools:8080/.

For anything beyond a local trial, see the Otoroshi setup documentation.

Use Docker​

Alternatively, mount the jar into the plugins directory Otoroshi already scans:

docker run \
-p 8080:8080 \
-e APP_STORAGE=file \
-e OTOROSHI_PLUGINS_DIR_PATH=/usr/app/plugins \
-e CLOUD_APIM_EXTENSIONS_WAF_ENABLED=true \
-v "$(pwd)/otoroshi-waf-extension.jar:/usr/app/plugins/waf.jar" \
-v "$(pwd)/otoroshi.db:/usr/app/filedb/state.ndjson" \
maif/otoroshi:latest

Enable the extension​

Extensions are off by default. The commands above already switch it on — with the system property in the java -cp form, or CLOUD_APIM_EXTENSIONS_WAF_ENABLED in the Docker form. In a configuration file:

otoroshi.admin-extensions.configurations.cloud-apim_extensions_waf {
enabled = true
}

You should see this on startup:

the 'Cloud APIM - Threat Protection Suite' extension is enabled !

and a Threat Protection entry in the admin sidebar.

Configuration reference​

Everything is optional — the defaults are the ones below.

otoroshi.admin-extensions.configurations.cloud-apim_extensions_waf {
enabled = true

// the seclang engine
integration {
// how many compiled rule programs are kept in memory. one entry per distinct
// ruleset, not per route, so the default is generous for most deployments
max-cache-items = 1000
// engine-level debug logging. noisy — for troubleshooting only
log = false
}

waf {
// what a route does when the WAF it asks for cannot run: the extension is not enabled, the waf
// config it names does not exist, or its rules do not compile. off: the request is refused with
// a 503. on: it goes through uninspected. either way the logs say so
fail-open = false
}

// the decision fabric: shared score, policies, bans, incidents
security {
enabled = true
// how often the ban list is refreshed from shared state, and incidents are evicted
tick-interval-seconds = 10
// how long events from one caller keep collapsing into the same incident
incident-window-seconds = 1800
// set this to guarantee bans are shared between nodes whatever the otoroshi storage backend is
// redis-uri = "redis://localhost:6379"

tarpit {
// how many requests one node holds at once, in a tarpit or before a slow refusal. past
// this, refusals are sent at once and tarpits let the request through
max-held-connections = 1000
}

alerts {
// off: no alert rule is evaluated on this node, whatever the rules say
enabled = true
}

traffic {
// how many route, source, api key and network baselines one node learns at once
max-keys = 200000
}

objects {
// how many consumers and kinds of object one node watches at once, a few kilobytes each
max-keys = 20000
}

api.inventory {
// how many operations, paths outside the contracts and drifts one node counts
max-records = 20000
}

events {
// also emit every decision and every alert as an OCSF Detection Finding, as its own event
// type (CloudApimSecurityOcsf), for Security Lake, Sentinel and the other OCSF readers
ocsf = false
}

ledger {
enabled = true
window-seconds = 3600
ban-threshold = 100
ban-duration-seconds = 3600
// off by default: the threat response plugin already charges the ledger for what it enforced
feed-waf-blocks = false
feed-waf-monitored = false
waf-weight = 25
}

relay {
correlate-waf-events = true
}
}

// the ip reputation module
reputation {
// the module idles when no feed and no bouncer exists, so leaving this on costs nothing
enabled = true
// how often due refreshes and crowdsec syncs are checked
tick-interval-seconds = 5

geo {
// where geolocation databases are unpacked. a temporary directory of the process by default
// work-dir = "/var/lib/otoroshi/geo"
}

// dns blocklists, for @rbl in waf rules
rbl {
enabled = true
// how long one dns query may take before it counts as unknown (not listed)
timeout-millis = 2000
// how long a request waits for an answer nobody has asked yet. 0 never waits:
// the first request from an address is then judged without it
wait-millis = 100
listed-ttl-seconds = 900
unlisted-ttl-seconds = 300
error-ttl-seconds = 30
max-entries = 100000
// the name servers to ask. the system's by default, but most blocklists refuse public
// resolvers: see the dns blocklists page
// nameservers = ["10.0.0.53", "10.0.0.54:5353"]
// project honey pot's http:BL access key, for @rbl dnsbl.httpbl.org
// httpbl-key = "abcdefghijkl"
}
}
}
KeyEnv varDefault
enabledCLOUD_APIM_EXTENSIONS_WAF_ENABLEDfalse (true in dev mode)
integration.max-cache-itemsCLOUD_APIM_EXTENSIONS_WAF_INTEGRATION_MAX_CACHE_ITEMS1000
integration.logCLOUD_APIM_EXTENSIONS_WAF_INTEGRATION_LOGfalse
waf.fail-openCLOUD_APIM_EXTENSIONS_WAF_FAIL_OPENfalse
reputation.enabled—true
reputation.tick-interval-seconds—5
reputation.geo.work-dir—a temporary directory
reputation.rbl.enabled—true
reputation.rbl.timeout-millis—2000
reputation.rbl.wait-millis—100
reputation.rbl.listed-ttl-seconds, unlisted-ttl-seconds, error-ttl-seconds—900, 300, 30
reputation.rbl.max-entries—100000
reputation.rbl.nameservers—the system's
reputation.rbl.httpbl-key——
Clustered deployments

Every node keeps its own in-memory feed index and its own CrowdSec stream cursor — the same thing every official CrowdSec bouncer does, so no coordination is needed. Nodes stagger their first refresh by a random 5–25 seconds so a rolling restart does not hit every feed provider at once.

Upgrading​

The extension id is cloud-apim.extensions.Waf and has been stable since the first release, so datastore keys, the API group and route paths are unchanged across upgrades. Entities added by later versions read their new fields with defaults, so an older WafConfig stays valid.

Upgrading to 1.0.0: more Core Rule Set rules fire

Before 1.0.0, the embedded Core Rule Set never found its data files, so none of the rules that read one could match: scanner user agents (913100), LFI paths (930120), shell commands (932xxx), PHP functions (933xxx), and the SQL and PHP error leaks of the response rules. They all run now, which means real detections that were missed and, on some APIs, new false positives. Upgrade a blocking config through a period in monitoring (block: false) and read the trail before arming it again. @ipMatch with a CIDR (10.0.0.0/8) never matched either, and does now.

Building from source​

Only needed if you want an unreleased change.

git clone https://github.com/cloud-apim/otoroshi-waf-extension.git
cd otoroshi-waf-extension
sbt assembly

The artifact lands in target/scala-3.8.4/otoroshi-waf-extension-assembly_3-dev.jar and is used exactly like a released jar.