mirror of
https://github.com/fabriziosalmi/patterns.git
synced 2026-06-11 06:54:15 -04:00
Redesign docs with Apple-native theme; verify content; route CI to self-hosted runner-02
- VitePress: custom theme (SF system fonts, glass nav, soft surfaces, pill buttons, light/dark code blocks, refined feature cards, platform showcase + stat strip). - Replace every emoji across docs and README with inline SVG icons. - Verify and fix doc accuracy against actual scripts: JSON schema (category+pattern only), env-var configuration for json2*/import_* scripts, owasp2json CLI surface. - Add public assets (logo.svg, favicon.svg, hero-shield.svg) and Shiki haproxy alias. - Workflows default to self-hosted runner-02 with a configurable fallback to GitHub runners via the RUNS_ON repo variable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
4
.github/workflows/docs.yml
vendored
4
.github/workflows/docs.yml
vendored
@@ -20,7 +20,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ fromJSON(vars.RUNS_ON || '["self-hosted","runner-02"]') }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ fromJSON(vars.RUNS_ON || '["self-hosted","runner-02"]') }}
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
|
||||
2
.github/workflows/test_apache_docker.yml
vendored
2
.github/workflows/test_apache_docker.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
|
||||
jobs:
|
||||
validate-waf-patterns:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ fromJSON(vars.RUNS_ON || '["self-hosted","runner-02"]') }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
2
.github/workflows/test_nginx.yml
vendored
2
.github/workflows/test_nginx.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
|
||||
jobs:
|
||||
validate-nginx-configuration:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ fromJSON(vars.RUNS_ON || '["self-hosted","runner-02"]') }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
2
.github/workflows/update_patterns.yml
vendored
2
.github/workflows/update_patterns.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
|
||||
jobs:
|
||||
update-owasp-waf:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ fromJSON(vars.RUNS_ON || '["self-hosted","runner-02"]') }}
|
||||
|
||||
steps:
|
||||
- name: 🚚 Checkout Repository
|
||||
|
||||
373
README.md
373
README.md
@@ -1,212 +1,233 @@
|
||||
# 🔒 Patterns: OWASP CRS and Bad Bot Detection for Web Servers
|
||||
|
||||
Automate the scraping of **OWASP Core Rule Set (CRS)** patterns and convert them into **Apache, Nginx, Traefik, and HAProxy** WAF configurations.
|
||||
Additionally, **Bad Bot/User-Agent detection** is integrated to block malicious web crawlers and scrapers.
|
||||
|
||||
> 🚀 **Protect your servers against SQL Injection (SQLi), XSS, RCE, LFI, and malicious bots – with automated daily updates.**
|
||||
<div align="center">
|
||||
<img src="docs/public/logo.svg" alt="Patterns" width="64" height="64" />
|
||||
<h1>Patterns</h1>
|
||||
<p><strong>Production-grade WAF rules, on autopilot.</strong></p>
|
||||
<p>
|
||||
Automated <a href="https://github.com/coreruleset/coreruleset">OWASP Core Rule Set</a> and
|
||||
bad-bot patterns, converted into native configurations for
|
||||
<strong>Nginx</strong>, <strong>Apache</strong>, <strong>Traefik</strong>, and <strong>HAProxy</strong>
|
||||
— refreshed every day.
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://github.com/fabriziosalmi/patterns/releases/latest"><img alt="Latest Release" src="https://img.shields.io/github/v/release/fabriziosalmi/patterns?label=release&color=0071e3"></a>
|
||||
<a href="https://github.com/fabriziosalmi/patterns/actions/workflows/update_patterns.yml"><img alt="Update workflow" src="https://github.com/fabriziosalmi/patterns/actions/workflows/update_patterns.yml/badge.svg"></a>
|
||||
<a href="https://github.com/fabriziosalmi/patterns/actions/workflows/test_nginx.yml"><img alt="Nginx tests" src="https://github.com/fabriziosalmi/patterns/actions/workflows/test_nginx.yml/badge.svg"></a>
|
||||
<a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-1d1d1f"></a>
|
||||
<a href="https://fabriziosalmi.github.io/patterns/"><img alt="Documentation" src="https://img.shields.io/badge/docs-online-0071e3"></a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://fabriziosalmi.github.io/patterns/">Documentation</a>
|
||||
·
|
||||
<a href="https://fabriziosalmi.github.io/patterns/getting-started">Get started</a>
|
||||
·
|
||||
<a href="https://github.com/fabriziosalmi/patterns/releases/latest">Latest release</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 📌 Project Highlights
|
||||
- **🛡️ OWASP CRS Protection** – Leverages OWASP Core Rule Set for web application firewall (WAF) defense.
|
||||
- **🤖 Bad Bot Blocking** – Blocks known malicious bots using public bot lists.
|
||||
- **⚙️ Multi-Web Server Support** – Generates WAF configs for **Apache, Nginx, Traefik, and HAProxy**.
|
||||
- **🔄 Automatic Updates** – GitHub Actions fetch new rules **daily** and push updated configs.
|
||||
- **📦 Pre-Generated Configurations** – Download ready-to-use WAF configurations from [GitHub Releases](https://github.com/fabriziosalmi/patterns/releases).
|
||||
- **🧩 Scalable and Modular** – Easily extendable to support other web servers or load balancers.
|
||||
## Why Patterns
|
||||
|
||||
---
|
||||
The OWASP Core Rule Set (CRS) is the de-facto open-source rule base behind ModSecurity, but plugging it into anything other than Apache is non-trivial. Patterns automates the whole pipeline:
|
||||
|
||||
## 🌐 Supported Web Servers
|
||||
- **🔵 Nginx**
|
||||
- **🟠 Apache (ModSecurity)**
|
||||
- **🟣 Traefik**
|
||||
- **🔴 HAProxy**
|
||||
1. Pull the latest CRS rules straight from upstream.
|
||||
2. Convert them into the **native** syntax of each web server — not a generic shim.
|
||||
3. Package the output as ready-to-deploy archives, refreshed every day by GitHub Actions.
|
||||
|
||||
> [!NOTE]
|
||||
> If you are using Caddy, check the [caddy-waf](https://github.com/fabriziosalmi/caddy-waf) project.
|
||||
You get equivalent protection across SQL injection, XSS, RCE, LFI, and bad-bot traffic, regardless of which proxy you run.
|
||||
|
||||
---
|
||||
## Highlights
|
||||
|
||||
## 📂 Project Structure
|
||||
```
|
||||
patterns/
|
||||
├── waf_patterns/ # 🔧 Generated WAF config files
|
||||
│ ├── nginx/ # Nginx WAF configs
|
||||
│ ├── apache/ # Apache WAF configs (ModSecurity)
|
||||
│ ├── traefik/ # Traefik WAF configs
|
||||
│ └── haproxy/ # HAProxy WAF configs
|
||||
├── import_apache_waf.py # 📥 Import Apache WAF configurations
|
||||
├── import_haproxy_waf.py # 📥 Import HAProxy WAF configurations
|
||||
├── import_nginx_waf.py # 📥 Import Nginx WAF configurations
|
||||
├── import_traefik_waf.py # 📥 Import Traefik WAF configurations
|
||||
├── owasp2json.py # 🕵️ OWASP scraper (fetch CRS rules)
|
||||
├── json2nginx.py # 🔄 Convert OWASP JSON to Nginx WAF configs
|
||||
├── json2apache.py # 🔄 Convert OWASP JSON to Apache ModSecurity configs
|
||||
├── json2traefik.py # 🔄 Convert OWASP JSON to Traefik WAF configs
|
||||
├── json2haproxy.py # 🔄 Convert OWASP JSON to HAProxy WAF configs
|
||||
├── badbots.py # 🤖 Generate WAF configs to block bad bots
|
||||
├── requirements.txt # 📄 Required dependencies
|
||||
└── .github/workflows/ # 🤖 GitHub Actions for automation
|
||||
└── update_patterns.yml
|
||||
```
|
||||
| | |
|
||||
|---|---|
|
||||
| **OWASP CRS coverage** | SQLi, XSS, RCE, LFI, RFI, plus generic anomaly and protocol-violation rules. |
|
||||
| **Native output** | Nginx `map`/`if`, Apache `SecRule`, Traefik middleware TOML, HAProxy ACL files. |
|
||||
| **Bad-bot blocking** | Curated User-Agent lists from public sources, with safe defaults that do **not** block major search engines. |
|
||||
| **Daily refresh** | A scheduled GitHub Actions workflow rebuilds every backend and publishes a fresh release. |
|
||||
| **Pre-built archives** | Skip the toolchain — download `nginx_waf.zip`, `apache_waf.zip`, `traefik_waf.zip`, or `haproxy_waf.zip`. |
|
||||
| **Composable** | Each backend is a small Python converter on top of one JSON intermediate. Adding a new platform is a few hundred lines. |
|
||||
|
||||
---
|
||||
> Using **Caddy**? See the dedicated [`caddy-waf`](https://github.com/fabriziosalmi/caddy-waf) project.
|
||||
|
||||
## 🛠️ How It Works
|
||||
### 🔹 1. Scraping OWASP Rules
|
||||
- **`owasp2json.py`** scrapes the latest OWASP CRS patterns from GitHub.
|
||||
- Extracts **SQLi, XSS, RCE, LFI** patterns from OWASP CRS `.conf` files.
|
||||
## Quick start
|
||||
|
||||
### 🔹 2. Generating WAF Configs for Each Platform
|
||||
- **`json2nginx.py`** – Generates **Nginx WAF** configurations.
|
||||
- **`json2apache.py`** – Outputs **Apache ModSecurity** rules.
|
||||
- **`json2traefik.py`** – Creates **Traefik WAF** rules.
|
||||
- **`json2haproxy.py`** – Builds **HAProxy ACL** files.
|
||||
### Option 1 — download a pre-built release
|
||||
|
||||
### 🔹 3. Bad Bot/User-Agent Detection
|
||||
- **`badbots.py`** fetches public bot lists and generates bot-blocking configs.
|
||||
- Supports fallback lists to ensure reliable detection.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before installing, ensure you have the following:
|
||||
- **Python 3.11 or higher** (the project uses Python 3.11 as specified in the GitHub Actions workflow)
|
||||
- **pip** (Python package installer)
|
||||
- **git** (for cloning the repository)
|
||||
|
||||
### Option 1: Download Pre-Generated Configurations
|
||||
You can download the latest pre-generated WAF configurations directly from the [GitHub Releases](https://github.com/fabriziosalmi/patterns/releases) page.
|
||||
|
||||
1. Go to the [Releases](https://github.com/fabriziosalmi/patterns/releases) section.
|
||||
2. Download the zip file for your web server (e.g., `nginx_waf.zip`, `apache_waf.zip`).
|
||||
3. Extract the files and follow the integration instructions below.
|
||||
|
||||
### Option 2: Build from Source
|
||||
If you prefer to generate the configurations yourself:
|
||||
|
||||
**1. Clone the Repository:**
|
||||
```bash
|
||||
git clone https://github.com/fabriziosalmi/patterns.git
|
||||
# Pick the archive that matches your stack
|
||||
curl -LO https://github.com/fabriziosalmi/patterns/releases/latest/download/nginx_waf.zip
|
||||
unzip nginx_waf.zip -d /etc/nginx/waf_patterns
|
||||
```
|
||||
|
||||
Then follow the [Nginx](https://fabriziosalmi.github.io/patterns/nginx),
|
||||
[Apache](https://fabriziosalmi.github.io/patterns/apache),
|
||||
[Traefik](https://fabriziosalmi.github.io/patterns/traefik), or
|
||||
[HAProxy](https://fabriziosalmi.github.io/patterns/haproxy) integration guide.
|
||||
|
||||
### Option 2 — build from source
|
||||
|
||||
Requires **Python 3.11+**, `pip`, and `git`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/fabriziosalmi/patterns.git
|
||||
cd patterns
|
||||
```
|
||||
|
||||
**2. Install Dependencies:**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
|
||||
python owasp2json.py # 1. Fetch the latest OWASP CRS into owasp_rules.json
|
||||
python json2nginx.py # 2. Convert into Nginx WAF config
|
||||
python json2apache.py # …or Apache (ModSecurity)
|
||||
python json2traefik.py # …or Traefik middleware
|
||||
python json2haproxy.py # …or HAProxy ACL files
|
||||
python badbots.py # 3. Generate bad-bot blocklists
|
||||
```
|
||||
|
||||
**3. Run Manually (Optional):**
|
||||
```bash
|
||||
python owasp2json.py
|
||||
python json2nginx.py
|
||||
python json2apache.py
|
||||
python json2haproxy.py
|
||||
python json2traefik.py
|
||||
python badbots.py
|
||||
Generated files land in `waf_patterns/<platform>/`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
┌─────────────────────┐ daily cron ┌──────────────────────┐
|
||||
│ coreruleset/ │ ───────────────▶ │ owasp2json.py │
|
||||
│ coreruleset (GH) │ │ → owasp_rules.json │
|
||||
└─────────────────────┘ └──────────┬───────────┘
|
||||
│
|
||||
┌─────────────────┬──────────────────┬──────┴──────────┐
|
||||
▼ ▼ ▼ ▼
|
||||
json2nginx.py json2apache.py json2traefik.py json2haproxy.py
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
nginx_waf.zip apache_waf.zip traefik_waf.zip haproxy_waf.zip
|
||||
(published as a GitHub Release)
|
||||
```
|
||||
|
||||
---
|
||||
Each converter is independent, idempotent, and configured exclusively through environment variables (`INPUT_FILE`, `OUTPUT_DIR`). Full reference at [docs/api](https://fabriziosalmi.github.io/patterns/api).
|
||||
|
||||
## 🚀 Usage (Web Server Integration)
|
||||
## Repository layout
|
||||
|
||||
### 🔹 1. Nginx WAF Integration
|
||||
1. Download the `nginx_waf.zip` file from the [Releases](https://github.com/fabriziosalmi/patterns/releases) page.
|
||||
2. Extract the files to your Nginx configuration directory.
|
||||
3. Include the generated `.conf` files in your Nginx configuration:
|
||||
```nginx
|
||||
include /path/to/waf_patterns/nginx/*.conf;
|
||||
```
|
||||
```text
|
||||
patterns/
|
||||
├── owasp2json.py # Pull and parse OWASP CRS into a JSON intermediate
|
||||
├── json2nginx.py # JSON → Nginx (map + if directives)
|
||||
├── json2apache.py # JSON → Apache (ModSecurity SecRule)
|
||||
├── json2traefik.py # JSON → Traefik (middleware TOML)
|
||||
├── json2haproxy.py # JSON → HAProxy (ACL files)
|
||||
├── badbots.py # Public bot lists → per-platform blocklists
|
||||
├── import_*_waf.py # Optional installers for each platform
|
||||
├── waf_patterns/ # Generated outputs
|
||||
│ ├── nginx/
|
||||
│ ├── apache/
|
||||
│ ├── traefik/
|
||||
│ └── haproxy/
|
||||
├── docs/ # VitePress documentation site
|
||||
├── tests/ # Validation tests for each backend
|
||||
└── .github/workflows/ # Daily build + release automation
|
||||
```
|
||||
|
||||
### 🔹 2. Apache WAF Integration
|
||||
1. Download the `apache_waf.zip` file from the [Releases](https://github.com/fabriziosalmi/patterns/releases) page.
|
||||
2. Extract the files to your Apache configuration directory.
|
||||
3. Include the generated `.conf` files in your Apache configuration:
|
||||
```apache
|
||||
Include /path/to/waf_patterns/apache/*.conf
|
||||
```
|
||||
## Integration in 60 seconds
|
||||
|
||||
### 🔹 3. Traefik WAF Integration
|
||||
1. Download the `traefik_waf.zip` file from the [Releases](https://github.com/fabriziosalmi/patterns/releases) page.
|
||||
2. Extract the files and use the `middleware.toml` file in your Traefik configuration.
|
||||
### Nginx
|
||||
|
||||
### 🔹 4. HAProxy WAF Integration
|
||||
1. Download the `haproxy_waf.zip` file from the [Releases](https://github.com/fabriziosalmi/patterns/releases) page.
|
||||
2. Extract the files and include the `waf.acl` file in your HAProxy configuration.
|
||||
```nginx
|
||||
http {
|
||||
include /etc/nginx/waf_patterns/nginx/waf_maps.conf;
|
||||
include /etc/nginx/waf_patterns/nginx/bots.conf;
|
||||
}
|
||||
server {
|
||||
include /etc/nginx/waf_patterns/nginx/waf_rules.conf;
|
||||
if ($bad_bot) { return 403; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
### Apache (ModSecurity)
|
||||
|
||||
```apache
|
||||
<IfModule security2_module>
|
||||
SecRuleEngine On
|
||||
Include /etc/apache2/waf_patterns/apache/*.conf
|
||||
</IfModule>
|
||||
```
|
||||
|
||||
### Traefik
|
||||
|
||||
```yaml
|
||||
http:
|
||||
routers:
|
||||
app:
|
||||
rule: "Host(`example.com`)"
|
||||
service: app
|
||||
middlewares: [waf-protection@file, bot-blocker@file]
|
||||
```
|
||||
|
||||
### HAProxy
|
||||
|
||||
```haproxy
|
||||
frontend http-in
|
||||
bind *:80
|
||||
acl waf_match path,url_dec -m reg -i -f /etc/haproxy/waf.acl
|
||||
acl bad_bot hdr(User-Agent) -m reg -i -f /etc/haproxy/bots.acl
|
||||
http-request deny deny_status 403 if waf_match || bad_bot
|
||||
```
|
||||
|
||||
Full guides — with logging, whitelists, and tuning — live in the [docs](https://fabriziosalmi.github.io/patterns/).
|
||||
|
||||
## Bad-bot example output (Nginx)
|
||||
|
||||
## 🔧 Example Output (Bot Blocker – Nginx)
|
||||
```nginx
|
||||
map $http_user_agent $bad_bot {
|
||||
"~*AhrefsBot" 1;
|
||||
"~*SemrushBot" 1;
|
||||
"~*MJ12bot" 1;
|
||||
default 0;
|
||||
"~*AhrefsBot" 1;
|
||||
"~*SemrushBot" 1;
|
||||
"~*MJ12bot" 1;
|
||||
"~*GPTBot" 1;
|
||||
}
|
||||
if ($bad_bot) {
|
||||
return 403;
|
||||
}
|
||||
|
||||
if ($bad_bot) { return 403; }
|
||||
```
|
||||
|
||||
---
|
||||
The default list blocks SEO crawlers, AI training bots, and known scanners while explicitly **allowing** major search engines (Google, Bing, DuckDuckGo, Yandex, Baidu).
|
||||
|
||||
## 🤖 Automation (GitHub Workflow)
|
||||
- **🕛 Daily Updates** – GitHub Actions fetch the latest OWASP CRS rules every day.
|
||||
- **🔄 Auto Deployment** – Pushes new `.conf` files directly to `waf_patterns/`.
|
||||
- **📦 Release Automation** – Automatically creates a new release with pre-generated configurations.
|
||||
- **🎯 Manual Trigger** – Updates can also be triggered manually.
|
||||
## Automation
|
||||
|
||||
| Workflow | Schedule | Purpose |
|
||||
|----------|----------|---------|
|
||||
| [`update_patterns.yml`](.github/workflows/update_patterns.yml) | Daily + manual | Re-fetch CRS, regenerate every backend, publish a release |
|
||||
| [`test_nginx.yml`](.github/workflows/test_nginx.yml) | On PR | Validate generated Nginx rules against a live container |
|
||||
| [`test_apache_docker.yml`](.github/workflows/test_apache_docker.yml) | On PR | Validate generated Apache rules against ModSecurity in Docker |
|
||||
| [`docs.yml`](.github/workflows/docs.yml) | On `docs/` change | Build and deploy the VitePress docs to GitHub Pages |
|
||||
|
||||
All workflows target the **`runner-02`** self-hosted runner by default, with an automatic fallback to GitHub-hosted runners by setting the repository variable `RUNS_ON` to `'["ubuntu-latest"]'`.
|
||||
|
||||
## Documentation
|
||||
|
||||
The full documentation lives at **[fabriziosalmi.github.io/patterns](https://fabriziosalmi.github.io/patterns/)** — built with [VitePress](https://vitepress.dev/) and deployed automatically.
|
||||
|
||||
- [Getting Started](https://fabriziosalmi.github.io/patterns/getting-started)
|
||||
- [Nginx](https://fabriziosalmi.github.io/patterns/nginx) · [Apache](https://fabriziosalmi.github.io/patterns/apache) · [Traefik](https://fabriziosalmi.github.io/patterns/traefik) · [HAProxy](https://fabriziosalmi.github.io/patterns/haproxy)
|
||||
- [Bad Bot Detection](https://fabriziosalmi.github.io/patterns/badbots)
|
||||
- [API & Scripts Reference](https://fabriziosalmi.github.io/patterns/api)
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository.
|
||||
2. Create a feature branch: `git checkout -b feature/your-change`.
|
||||
3. Commit and push.
|
||||
4. Open a pull request — the test workflows will run automatically.
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for details and [SECURITY.md](SECURITY.md) for the disclosure policy.
|
||||
|
||||
## License
|
||||
|
||||
Released under the [MIT License](LICENSE).
|
||||
|
||||
## Resources
|
||||
|
||||
- [OWASP Core Rule Set](https://github.com/coreruleset/coreruleset)
|
||||
- [ModSecurity](https://modsecurity.org/)
|
||||
- [Nginx](https://nginx.org/) · [Apache HTTPD](https://httpd.apache.org/) · [Traefik](https://traefik.io/) · [HAProxy](https://www.haproxy.org/)
|
||||
- [ai.robots.txt](https://github.com/ai-robots-txt/ai.robots.txt) — upstream AI-bot list
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
1. **Fork** the repository.
|
||||
2. Create a **feature branch** (`feature/new-patterns`).
|
||||
3. **Commit** and push changes.
|
||||
4. Open a **Pull Request**.
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
This project is licensed under the **MIT License**.
|
||||
See the [LICENSE](LICENSE) file for details.
|
||||
|
||||
---
|
||||
|
||||
## Other Projects
|
||||
|
||||
If you like this project, you may also like these:
|
||||
|
||||
- [caddy-waf](https://github.com/fabriziosalmi/caddy-waf) Caddy WAF (Regex Rules, IP and DNS filtering, Rate Limiting, GeoIP, Tor, Anomaly Detection)
|
||||
- [blacklists](https://github.com/fabriziosalmi/blacklists) Hourly updated domains blacklist 🚫
|
||||
- [proxmox-vm-autoscale](https://github.com/fabriziosalmi/proxmox-vm-autoscale) Automatically scale virtual machines resources on Proxmox hosts
|
||||
- [UglyFeed](https://github.com/fabriziosalmi/UglyFeed) Retrieve, aggregate, filter, evaluate, rewrite and serve RSS feeds using Large Language Models for fun, research and learning purposes
|
||||
- [proxmox-lxc-autoscale](https://github.com/fabriziosalmi/proxmox-lxc-autoscale) Automatically scale LXC containers resources on Proxmox hosts
|
||||
- [DevGPT](https://github.com/fabriziosalmi/DevGPT) Code togheter, right now! GPT powered code assistant to build project in minutes
|
||||
- [websites-monitor](https://github.com/fabriziosalmi/websites-monitor) Websites monitoring via GitHub Actions (expiration, security, performances, privacy, SEO)
|
||||
- [caddy-mib](https://github.com/fabriziosalmi/caddy-mib) Track and ban client IPs generating repetitive errors on Caddy
|
||||
- [zonecontrol](https://github.com/fabriziosalmi/zonecontrol) Cloudflare Zones Settings Automation using GitHub Actions
|
||||
- [lws](https://github.com/fabriziosalmi/lws) linux (containers) web services
|
||||
- [cf-box](https://github.com/fabriziosalmi/cf-box) cf-box is a set of Python tools to play with API and multiple Cloudflare accounts.
|
||||
- [limits](https://github.com/fabriziosalmi/limits) Automated rate limits implementation for web servers
|
||||
- [dnscontrol-actions](https://github.com/fabriziosalmi/dnscontrol-actions) Automate DNS updates and rollbacks across multiple providers using DNSControl and GitHub Actions
|
||||
- [proxmox-lxc-autoscale-ml](https://github.com/fabriziosalmi/proxmox-lxc-autoscale-ml) Automatically scale the LXC containers resources on Proxmox hosts with AI
|
||||
- [csv-anonymizer](https://github.com/fabriziosalmi/csv-anonymizer) CSV fuzzer/anonymizer
|
||||
- [iamnotacoder](https://github.com/fabriziosalmi/iamnotacoder) AI code generation and improvement
|
||||
|
||||
---
|
||||
## 📞 Need Help?
|
||||
- **Issues?** Open a ticket in the [Issues Tab](https://github.com/fabriziosalmi/patterns/issues).
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Resources
|
||||
- [OWASP CRS](https://github.com/coreruleset/coreruleset)
|
||||
- [Apache ModSecurity](https://modsecurity.org/)
|
||||
- [Nginx](https://nginx.org/)
|
||||
- [Traefik](https://github.com/traefik/traefik)
|
||||
- [HaProxy](https://www.haproxy.org/)
|
||||
<div align="center">
|
||||
<sub>Built and maintained by <a href="https://github.com/fabriziosalmi">Fabrizio Salmi</a>.</sub>
|
||||
</div>
|
||||
|
||||
@@ -1,37 +1,77 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
|
||||
// Inline SVG icon factory for the features grid (renders inside .VPFeature .icon)
|
||||
const svg = (paths: string) =>
|
||||
`<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`
|
||||
|
||||
const icons = {
|
||||
shield: svg('<path d="M12 3 4.5 5.8v6.1c0 4.7 3.3 9.1 7.5 10.4 4.2-1.3 7.5-5.7 7.5-10.4V5.8L12 3Z"/><path d="m8.5 12 2.5 2.5L15.5 9.5"/>'),
|
||||
bot: svg('<rect x="4" y="7.5" width="16" height="11" rx="3"/><path d="M12 4v3.5"/><circle cx="12" cy="3.5" r="0.9" fill="currentColor" stroke="none"/><circle cx="9" cy="13" r="1.1" fill="currentColor" stroke="none"/><circle cx="15" cy="13" r="1.1" fill="currentColor" stroke="none"/><path d="M2 13.5v2M22 13.5v2"/>'),
|
||||
servers: svg('<rect x="3.5" y="4" width="17" height="6" rx="1.5"/><rect x="3.5" y="14" width="17" height="6" rx="1.5"/><path d="M7 7h.01M7 17h.01"/><path d="M11 7h6M11 17h6"/>'),
|
||||
refresh: svg('<path d="M3.5 12a8.5 8.5 0 0 1 14.5-6L20 8"/><path d="M20 3v5h-5"/><path d="M20.5 12a8.5 8.5 0 0 1-14.5 6L4 16"/><path d="M4 21v-5h5"/>'),
|
||||
package: svg('<path d="M21 8 12 3 3 8v8l9 5 9-5V8Z"/><path d="m3.3 8 8.7 5 8.7-5"/><path d="M12 13v8"/><path d="m7.5 5.5 9 5"/>'),
|
||||
puzzle: svg('<path d="M14 4.5a2 2 0 1 0-4 0V6H6a1.5 1.5 0 0 0-1.5 1.5V11h1.5a2 2 0 1 1 0 4H4.5v3.5A1.5 1.5 0 0 0 6 20h3.5v-1.5a2 2 0 1 1 4 0V20H17a1.5 1.5 0 0 0 1.5-1.5V15H20a2 2 0 1 0 0-4h-1.5V7.5A1.5 1.5 0 0 0 17 6h-3V4.5Z"/>')
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
title: 'Patterns',
|
||||
description: 'OWASP CRS and Bad Bot Detection for Web Servers',
|
||||
titleTemplate: ':title — Patterns',
|
||||
description: 'Automated OWASP CRS and bad-bot rules for Nginx, Apache, Traefik, and HAProxy.',
|
||||
base: '/patterns/',
|
||||
cleanUrls: true,
|
||||
lastUpdated: true,
|
||||
|
||||
markdown: {
|
||||
languageAlias: {
|
||||
haproxy: 'apache'
|
||||
},
|
||||
theme: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark-dimmed'
|
||||
}
|
||||
},
|
||||
|
||||
head: [
|
||||
['link', { rel: 'icon', href: '/patterns/favicon.ico' }]
|
||||
['link', { rel: 'icon', type: 'image/svg+xml', href: '/patterns/favicon.svg' }],
|
||||
['meta', { name: 'theme-color', content: '#0071e3' }],
|
||||
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
|
||||
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'default' }],
|
||||
['meta', { property: 'og:type', content: 'website' }],
|
||||
['meta', { property: 'og:title', content: 'Patterns — OWASP CRS WAF rules' }],
|
||||
['meta', { property: 'og:description', content: 'Automated OWASP CRS and bad-bot rules for Nginx, Apache, Traefik, and HAProxy.' }],
|
||||
['meta', { name: 'twitter:card', content: 'summary_large_image' }]
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
logo: '/logo.svg',
|
||||
logo: { src: '/logo.svg', width: 24, height: 24 },
|
||||
siteTitle: 'Patterns',
|
||||
|
||||
// Expose icons to markdown via Vite config? Instead we expose them through home features below.
|
||||
// (Used by index.md frontmatter via `icon: { svg: ... }`)
|
||||
// @ts-expect-error — custom field for home page
|
||||
icons,
|
||||
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Getting Started', link: '/getting-started' },
|
||||
{ text: 'Documentation', link: '/getting-started', activeMatch: '^/(getting-started|nginx|apache|traefik|haproxy|badbots|api)' },
|
||||
{
|
||||
text: 'Web Servers',
|
||||
items: [
|
||||
{ text: 'Nginx', link: '/nginx' },
|
||||
{ text: 'Apache', link: '/apache' },
|
||||
{ text: 'Apache (ModSecurity)', link: '/apache' },
|
||||
{ text: 'Traefik', link: '/traefik' },
|
||||
{ text: 'HAProxy', link: '/haproxy' }
|
||||
]
|
||||
},
|
||||
{ text: 'Bad Bots', link: '/badbots' },
|
||||
{ text: 'API', link: '/api' }
|
||||
{ text: 'API', link: '/api' },
|
||||
{ text: 'Releases', link: 'https://github.com/fabriziosalmi/patterns/releases' }
|
||||
],
|
||||
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Introduction',
|
||||
items: [
|
||||
{ text: 'Overview', link: '/' },
|
||||
{ text: 'Getting Started', link: '/getting-started' }
|
||||
]
|
||||
},
|
||||
@@ -45,10 +85,10 @@ export default defineConfig({
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Features',
|
||||
text: 'Reference',
|
||||
items: [
|
||||
{ text: 'Bad Bot Detection', link: '/badbots' },
|
||||
{ text: 'API Reference', link: '/api' }
|
||||
{ text: 'API & Scripts', link: '/api' }
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -59,16 +99,21 @@ export default defineConfig({
|
||||
|
||||
footer: {
|
||||
message: 'Released under the MIT License.',
|
||||
copyright: 'Copyright © 2024-present Fabrizio Salmi'
|
||||
copyright: `Copyright © 2024–${new Date().getFullYear()} Fabrizio Salmi`
|
||||
},
|
||||
|
||||
search: {
|
||||
provider: 'local'
|
||||
},
|
||||
search: { provider: 'local' },
|
||||
|
||||
editLink: {
|
||||
pattern: 'https://github.com/fabriziosalmi/patterns/edit/main/docs/:path',
|
||||
text: 'Edit this page on GitHub'
|
||||
}
|
||||
},
|
||||
|
||||
outline: { level: [2, 3], label: 'On this page' },
|
||||
|
||||
docFooter: { prev: 'Previous', next: 'Next' }
|
||||
}
|
||||
})
|
||||
|
||||
// Re-export icons so the home page (index.md) can pull them via a JS hash, if needed.
|
||||
export { icons }
|
||||
|
||||
63
docs/.vitepress/theme/components/HomeShowcase.vue
Normal file
63
docs/.vitepress/theme/components/HomeShowcase.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script setup>
|
||||
import IconNginx from './icons/IconNginx.vue'
|
||||
import IconApache from './icons/IconApache.vue'
|
||||
import IconTraefik from './icons/IconTraefik.vue'
|
||||
import IconHaproxy from './icons/IconHaproxy.vue'
|
||||
import IconArrow from './icons/IconArrow.vue'
|
||||
import { withBase } from 'vitepress'
|
||||
|
||||
const platforms = [
|
||||
{ name: 'Nginx', meta: 'map directives + if rules', href: '/nginx', icon: IconNginx },
|
||||
{ name: 'Apache', meta: 'ModSecurity SecRule directives', href: '/apache', icon: IconApache },
|
||||
{ name: 'Traefik', meta: 'Middleware TOML configuration', href: '/traefik', icon: IconTraefik },
|
||||
{ name: 'HAProxy', meta: 'ACL pattern files', href: '/haproxy', icon: IconHaproxy }
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="home-section">
|
||||
<div class="home-eyebrow">Integrations</div>
|
||||
<h2 class="home-title">Four web servers, one source of truth</h2>
|
||||
<p class="home-lede">
|
||||
The same OWASP CRS rule set is converted into the native syntax of each platform —
|
||||
so you get equivalent protection whether you run Nginx, Apache, Traefik, or HAProxy.
|
||||
</p>
|
||||
|
||||
<div class="platform-grid">
|
||||
<a
|
||||
v-for="p in platforms"
|
||||
:key="p.name"
|
||||
:href="withBase(p.href)"
|
||||
class="platform-card"
|
||||
>
|
||||
<span class="platform-icon">
|
||||
<component :is="p.icon" />
|
||||
</span>
|
||||
<div class="platform-name">{{ p.name }}</div>
|
||||
<div class="platform-meta">{{ p.meta }}</div>
|
||||
<span class="platform-arrow"><IconArrow /></span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="home-section">
|
||||
<div class="stats-strip">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">600+</div>
|
||||
<div class="stat-label">OWASP CRS patterns extracted</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">Daily</div>
|
||||
<div class="stat-label">Automated rule refresh</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">4</div>
|
||||
<div class="stat-label">Web server backends</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">MIT</div>
|
||||
<div class="stat-label">Open-source license</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
8
docs/.vitepress/theme/components/icons/IconApache.vue
Normal file
8
docs/.vitepress/theme/components/icons/IconApache.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M5.5 20c1-4 3-9 6.5-13.5 1.5 1.5 2.5 3.5 3 5.5" />
|
||||
<path d="M9 14c2-3 5-5 9-5" />
|
||||
<path d="M11 17c2-2 5-3 8-3" />
|
||||
<path d="M5.5 20h2" />
|
||||
</svg>
|
||||
</template>
|
||||
6
docs/.vitepress/theme/components/icons/IconArrow.vue
Normal file
6
docs/.vitepress/theme/components/icons/IconArrow.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M5 12h14" />
|
||||
<path d="m13 6 6 6-6 6" />
|
||||
</svg>
|
||||
</template>
|
||||
10
docs/.vitepress/theme/components/icons/IconBot.vue
Normal file
10
docs/.vitepress/theme/components/icons/IconBot.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<rect x="4" y="7.5" width="16" height="11" rx="3" />
|
||||
<path d="M12 4v3.5" />
|
||||
<circle cx="12" cy="3.5" r="0.9" fill="currentColor" stroke="none" />
|
||||
<circle cx="9" cy="13" r="1.1" fill="currentColor" stroke="none" />
|
||||
<circle cx="15" cy="13" r="1.1" fill="currentColor" stroke="none" />
|
||||
<path d="M2 13.5v2M22 13.5v2" />
|
||||
</svg>
|
||||
</template>
|
||||
5
docs/.vitepress/theme/components/icons/IconGitHub.vue
Normal file
5
docs/.vitepress/theme/components/icons/IconGitHub.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 2C6.48 2 2 6.58 2 12.25c0 4.53 2.87 8.37 6.84 9.73.5.09.68-.22.68-.49 0-.24-.01-.88-.01-1.73-2.78.62-3.37-1.36-3.37-1.36-.45-1.18-1.11-1.49-1.11-1.49-.91-.63.07-.62.07-.62 1 .07 1.53 1.05 1.53 1.05.89 1.56 2.34 1.11 2.91.85.09-.66.35-1.11.63-1.36-2.22-.26-4.55-1.13-4.55-5.04 0-1.11.39-2.02 1.03-2.74-.1-.26-.45-1.3.1-2.71 0 0 .84-.27 2.75 1.05A9.34 9.34 0 0 1 12 6.84c.85.004 1.71.118 2.51.34 1.91-1.32 2.75-1.05 2.75-1.05.55 1.41.2 2.45.1 2.71.64.72 1.03 1.63 1.03 2.74 0 3.92-2.34 4.78-4.57 5.03.36.32.68.94.68 1.9 0 1.37-.01 2.48-.01 2.81 0 .27.18.59.69.49C19.13 20.61 22 16.78 22 12.25 22 6.58 17.52 2 12 2Z"/>
|
||||
</svg>
|
||||
</template>
|
||||
9
docs/.vitepress/theme/components/icons/IconHaproxy.vue
Normal file
9
docs/.vitepress/theme/components/icons/IconHaproxy.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="5" cy="12" r="2" />
|
||||
<circle cx="19" cy="6" r="2" />
|
||||
<circle cx="19" cy="18" r="2" />
|
||||
<path d="M7 11 17 7" />
|
||||
<path d="M7 13l10 4" />
|
||||
</svg>
|
||||
</template>
|
||||
6
docs/.vitepress/theme/components/icons/IconNginx.vue
Normal file
6
docs/.vitepress/theme/components/icons/IconNginx.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M12 2.5 3.5 7v10L12 21.5 20.5 17V7L12 2.5Z" />
|
||||
<path d="M9 16V8l6 8V8" />
|
||||
</svg>
|
||||
</template>
|
||||
8
docs/.vitepress/theme/components/icons/IconPackage.vue
Normal file
8
docs/.vitepress/theme/components/icons/IconPackage.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" />
|
||||
<path d="m3.3 8 8.7 5 8.7-5" />
|
||||
<path d="M12 13v8" />
|
||||
<path d="m7.5 5.5 9 5" />
|
||||
</svg>
|
||||
</template>
|
||||
5
docs/.vitepress/theme/components/icons/IconPuzzle.vue
Normal file
5
docs/.vitepress/theme/components/icons/IconPuzzle.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M14 4.5a2 2 0 1 0-4 0V6H6a1.5 1.5 0 0 0-1.5 1.5V11h1.5a2 2 0 1 1 0 4H4.5v3.5A1.5 1.5 0 0 0 6 20h3.5v-1.5a2 2 0 1 1 4 0V20H17a1.5 1.5 0 0 0 1.5-1.5V15H20a2 2 0 1 0 0-4h-1.5V7.5A1.5 1.5 0 0 0 17 6h-3V4.5Z" />
|
||||
</svg>
|
||||
</template>
|
||||
8
docs/.vitepress/theme/components/icons/IconRefresh.vue
Normal file
8
docs/.vitepress/theme/components/icons/IconRefresh.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M3.5 12a8.5 8.5 0 0 1 14.5-6L20 8" />
|
||||
<path d="M20 3v5h-5" />
|
||||
<path d="M20.5 12a8.5 8.5 0 0 1-14.5 6L4 16" />
|
||||
<path d="M4 21v-5h5" />
|
||||
</svg>
|
||||
</template>
|
||||
8
docs/.vitepress/theme/components/icons/IconServers.vue
Normal file
8
docs/.vitepress/theme/components/icons/IconServers.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<rect x="3.5" y="4" width="17" height="6" rx="1.5" />
|
||||
<rect x="3.5" y="14" width="17" height="6" rx="1.5" />
|
||||
<path d="M7 7h.01M7 17h.01" />
|
||||
<path d="M11 7h6M11 17h6" />
|
||||
</svg>
|
||||
</template>
|
||||
6
docs/.vitepress/theme/components/icons/IconShield.vue
Normal file
6
docs/.vitepress/theme/components/icons/IconShield.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M12 3 4.5 5.8v6.1c0 4.7 3.3 9.1 7.5 10.4 4.2-1.3 7.5-5.7 7.5-10.4V5.8L12 3Z" />
|
||||
<path d="m8.5 12 2.5 2.5L15.5 9.5" />
|
||||
</svg>
|
||||
</template>
|
||||
5
docs/.vitepress/theme/components/icons/IconTraefik.vue
Normal file
5
docs/.vitepress/theme/components/icons/IconTraefik.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M3 12h4l2-3 3 6 3-9 2 6h4" />
|
||||
</svg>
|
||||
</template>
|
||||
14
docs/.vitepress/theme/index.ts
Normal file
14
docs/.vitepress/theme/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { h } from 'vue'
|
||||
import type { Theme } from 'vitepress'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import HomeShowcase from './components/HomeShowcase.vue'
|
||||
import './style.css'
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout: () => {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
'home-features-after': () => h(HomeShowcase)
|
||||
})
|
||||
}
|
||||
} satisfies Theme
|
||||
621
docs/.vitepress/theme/style.css
Normal file
621
docs/.vitepress/theme/style.css
Normal file
@@ -0,0 +1,621 @@
|
||||
/* =============================================================
|
||||
Patterns Docs — Apple-native theme
|
||||
System fonts, refined typography, soft surfaces, glass nav.
|
||||
============================================================= */
|
||||
|
||||
:root {
|
||||
/* Typography — system stack, identical to Apple's web properties */
|
||||
--vp-font-family-base:
|
||||
-apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
|
||||
"Helvetica Neue", "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
|
||||
--vp-font-family-mono:
|
||||
ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono",
|
||||
Menlo, Consolas, "Liberation Mono", monospace;
|
||||
|
||||
/* Brand — Apple-system blue, refined */
|
||||
--apple-blue: #0071e3;
|
||||
--apple-blue-hover: #0077ed;
|
||||
--apple-blue-active: #006edb;
|
||||
--apple-cyan: #5ac8fa;
|
||||
|
||||
/* Light surfaces */
|
||||
--vp-c-bg: #ffffff;
|
||||
--vp-c-bg-alt: #fbfbfd;
|
||||
--vp-c-bg-soft: #f5f5f7;
|
||||
--vp-c-bg-elv: #ffffff;
|
||||
|
||||
/* Borders — almost invisible, like Apple */
|
||||
--vp-c-divider: rgba(0, 0, 0, 0.07);
|
||||
--vp-c-border: rgba(0, 0, 0, 0.09);
|
||||
--vp-c-gutter: rgba(0, 0, 0, 0.06);
|
||||
|
||||
/* Text — Apple uses near-black, high contrast */
|
||||
--vp-c-text-1: #1d1d1f;
|
||||
--vp-c-text-2: #515154;
|
||||
--vp-c-text-3: #86868b;
|
||||
|
||||
/* Brand color slots used by VitePress */
|
||||
--vp-c-brand-1: var(--apple-blue);
|
||||
--vp-c-brand-2: var(--apple-blue-hover);
|
||||
--vp-c-brand-3: var(--apple-blue-active);
|
||||
--vp-c-brand-soft: rgba(0, 113, 227, 0.10);
|
||||
|
||||
/* Tip / warning / etc — calmer than defaults */
|
||||
--vp-c-tip-1: #0071e3;
|
||||
--vp-c-tip-soft: rgba(0, 113, 227, 0.08);
|
||||
--vp-c-warning-1: #b25000;
|
||||
--vp-c-warning-soft: rgba(255, 159, 10, 0.10);
|
||||
--vp-c-danger-1: #c53030;
|
||||
--vp-c-danger-soft: rgba(255, 59, 48, 0.08);
|
||||
|
||||
/* Layout rhythm */
|
||||
--vp-layout-max-width: 1440px;
|
||||
--vp-nav-height: 60px;
|
||||
|
||||
/* Radii — Apple loves soft, generous corners */
|
||||
--radius-xs: 6px;
|
||||
--radius-sm: 10px;
|
||||
--radius-md: 14px;
|
||||
--radius-lg: 20px;
|
||||
--radius-xl: 28px;
|
||||
|
||||
/* Elevation — soft, low-spread shadows */
|
||||
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
--shadow-2: 0 4px 16px -4px rgba(0, 0, 0, 0.08), 0 2px 6px -2px rgba(0, 0, 0, 0.04);
|
||||
--shadow-3: 0 16px 40px -12px rgba(0, 0, 0, 0.16), 0 4px 12px -4px rgba(0, 0, 0, 0.06);
|
||||
|
||||
/* Buttons / brand surface */
|
||||
--vp-button-brand-border: transparent;
|
||||
--vp-button-brand-text: #fff;
|
||||
--vp-button-brand-bg: var(--apple-blue);
|
||||
--vp-button-brand-hover-border: transparent;
|
||||
--vp-button-brand-hover-text: #fff;
|
||||
--vp-button-brand-hover-bg: var(--apple-blue-hover);
|
||||
--vp-button-brand-active-border: transparent;
|
||||
--vp-button-brand-active-text: #fff;
|
||||
--vp-button-brand-active-bg: var(--apple-blue-active);
|
||||
|
||||
--vp-button-alt-border: transparent;
|
||||
--vp-button-alt-text: #1d1d1f;
|
||||
--vp-button-alt-bg: rgba(0, 0, 0, 0.06);
|
||||
--vp-button-alt-hover-bg: rgba(0, 0, 0, 0.09);
|
||||
--vp-button-alt-active-bg: rgba(0, 0, 0, 0.12);
|
||||
|
||||
/* Code blocks — soft surface in light, deep in dark */
|
||||
--vp-code-block-bg: #f5f5f7;
|
||||
--vp-code-block-color: #1d1d1f;
|
||||
--vp-code-line-highlight-color: rgba(0, 0, 0, 0.05);
|
||||
--vp-code-tab-bg: #f5f5f7;
|
||||
--vp-code-tab-divider: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-bg: #000000;
|
||||
--vp-c-bg-alt: #0a0a0c;
|
||||
--vp-c-bg-soft: #161618;
|
||||
--vp-c-bg-elv: #1c1c1e;
|
||||
|
||||
--vp-c-divider: rgba(255, 255, 255, 0.08);
|
||||
--vp-c-border: rgba(255, 255, 255, 0.10);
|
||||
--vp-c-gutter: rgba(255, 255, 255, 0.06);
|
||||
|
||||
--vp-c-text-1: #f5f5f7;
|
||||
--vp-c-text-2: #a1a1a6;
|
||||
--vp-c-text-3: #6e6e73;
|
||||
|
||||
--apple-blue: #0a84ff;
|
||||
--apple-blue-hover: #409cff;
|
||||
--apple-blue-active: #0a84ff;
|
||||
|
||||
--vp-c-brand-1: var(--apple-blue);
|
||||
--vp-c-brand-2: var(--apple-blue-hover);
|
||||
--vp-c-brand-3: var(--apple-blue-active);
|
||||
--vp-c-brand-soft: rgba(10, 132, 255, 0.16);
|
||||
|
||||
--vp-c-tip-1: #0a84ff;
|
||||
--vp-c-tip-soft: rgba(10, 132, 255, 0.12);
|
||||
--vp-c-warning-1: #ffb340;
|
||||
--vp-c-warning-soft: rgba(255, 159, 10, 0.14);
|
||||
--vp-c-danger-1: #ff6961;
|
||||
--vp-c-danger-soft: rgba(255, 69, 58, 0.14);
|
||||
|
||||
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
--shadow-2: 0 4px 16px -4px rgba(0, 0, 0, 0.6), 0 2px 6px -2px rgba(0, 0, 0, 0.4);
|
||||
--shadow-3: 0 16px 40px -12px rgba(0, 0, 0, 0.7), 0 4px 12px -4px rgba(0, 0, 0, 0.5);
|
||||
|
||||
--vp-button-alt-text: #f5f5f7;
|
||||
--vp-button-alt-bg: rgba(255, 255, 255, 0.10);
|
||||
--vp-button-alt-hover-bg: rgba(255, 255, 255, 0.14);
|
||||
--vp-button-alt-active-bg: rgba(255, 255, 255, 0.18);
|
||||
|
||||
--vp-code-block-bg: #161618;
|
||||
--vp-code-block-color: #f5f5f7;
|
||||
--vp-code-line-highlight-color: rgba(255, 255, 255, 0.06);
|
||||
--vp-code-tab-bg: #161618;
|
||||
--vp-code-tab-divider: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
/* ---------- Global typography ---------- */
|
||||
|
||||
html {
|
||||
font-feature-settings: "kern", "liga", "calt", "ss01";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--vp-font-family-base);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.VPDoc h1 {
|
||||
letter-spacing: -0.025em;
|
||||
font-weight: 700;
|
||||
font-size: 2.4rem;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.VPDoc h2 {
|
||||
letter-spacing: -0.02em;
|
||||
font-weight: 650;
|
||||
margin-top: 3rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.VPDoc h3 {
|
||||
letter-spacing: -0.015em;
|
||||
font-weight: 600;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.VPDoc p,
|
||||
.VPDoc li {
|
||||
font-size: 1rem;
|
||||
line-height: 1.7;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.VPDoc a:not(.header-anchor) {
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand-1);
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
.VPDoc a:not(.header-anchor):hover {
|
||||
color: var(--vp-c-brand-2);
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
/* ---------- Glass navigation ---------- */
|
||||
|
||||
.VPNav {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.VPNavBar {
|
||||
background: rgba(255, 255, 255, 0.72) !important;
|
||||
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
||||
backdrop-filter: saturate(180%) blur(20px);
|
||||
border-bottom: 1px solid var(--vp-c-divider) !important;
|
||||
}
|
||||
.dark .VPNavBar {
|
||||
background: rgba(0, 0, 0, 0.72) !important;
|
||||
}
|
||||
.VPNavBar.has-sidebar .content-body {
|
||||
background: transparent !important;
|
||||
}
|
||||
.VPNavBar .divider {
|
||||
display: none;
|
||||
}
|
||||
.VPNavBarTitle .title {
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
.VPNavBarMenuLink,
|
||||
.VPNavBarMenuGroup .text {
|
||||
font-size: 14px !important;
|
||||
font-weight: 450 !important;
|
||||
color: var(--vp-c-text-1) !important;
|
||||
opacity: 0.92;
|
||||
}
|
||||
.VPNavBarMenuLink:hover,
|
||||
.VPNavBarMenuGroup:hover .text {
|
||||
opacity: 1;
|
||||
color: var(--vp-c-text-1) !important;
|
||||
}
|
||||
|
||||
/* ---------- Sidebar ---------- */
|
||||
|
||||
.VPSidebar {
|
||||
background: var(--vp-c-bg-alt) !important;
|
||||
border-right: 1px solid var(--vp-c-divider) !important;
|
||||
}
|
||||
.VPSidebarItem.level-0 > .item > .text {
|
||||
font-weight: 600 !important;
|
||||
letter-spacing: -0.005em;
|
||||
color: var(--vp-c-text-1) !important;
|
||||
}
|
||||
.VPSidebarItem .link .link-text {
|
||||
font-weight: 450;
|
||||
font-size: 14px;
|
||||
}
|
||||
.VPSidebarItem.is-active > .item .link-text {
|
||||
font-weight: 600 !important;
|
||||
color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
/* ---------- Buttons ---------- */
|
||||
|
||||
.VPButton {
|
||||
border-radius: 980px !important;
|
||||
padding: 0 22px !important;
|
||||
height: 44px !important;
|
||||
line-height: 44px !important;
|
||||
font-weight: 500 !important;
|
||||
font-size: 15px !important;
|
||||
letter-spacing: -0.005em !important;
|
||||
transition: background-color 0.18s ease, transform 0.12s ease, box-shadow 0.18s ease !important;
|
||||
}
|
||||
.VPButton.medium {
|
||||
height: 40px !important;
|
||||
line-height: 40px !important;
|
||||
font-size: 14px !important;
|
||||
padding: 0 18px !important;
|
||||
}
|
||||
.VPButton.brand {
|
||||
box-shadow: 0 1px 2px rgba(0, 113, 227, 0.18);
|
||||
}
|
||||
.VPButton.brand:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 14px rgba(0, 113, 227, 0.30);
|
||||
}
|
||||
.VPButton.brand:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* ---------- Hero ---------- */
|
||||
|
||||
.VPHome .VPHero {
|
||||
padding: 96px 24px 72px !important;
|
||||
}
|
||||
.VPHero .container {
|
||||
max-width: 1120px !important;
|
||||
}
|
||||
.VPHero .name,
|
||||
.VPHero .text {
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -0.04em !important;
|
||||
line-height: 1.05 !important;
|
||||
font-size: clamp(2.6rem, 5.4vw, 4.4rem) !important;
|
||||
}
|
||||
.VPHero .name .clip {
|
||||
background: linear-gradient(120deg, #0a84ff 0%, #5ac8fa 50%, #0071e3 100%) !important;
|
||||
-webkit-background-clip: text !important;
|
||||
background-clip: text !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
}
|
||||
.VPHero .tagline {
|
||||
margin-top: 22px !important;
|
||||
font-size: 1.25rem !important;
|
||||
font-weight: 400 !important;
|
||||
line-height: 1.5 !important;
|
||||
color: var(--vp-c-text-2) !important;
|
||||
letter-spacing: -0.005em !important;
|
||||
max-width: 720px;
|
||||
}
|
||||
.VPHero .actions {
|
||||
margin-top: 36px !important;
|
||||
gap: 12px !important;
|
||||
}
|
||||
.VPHero .image-container {
|
||||
transform: none !important;
|
||||
}
|
||||
.VPHero .image-bg {
|
||||
display: none !important;
|
||||
}
|
||||
.VPHero .image-src {
|
||||
max-width: 100% !important;
|
||||
filter: drop-shadow(0 30px 60px rgba(10, 132, 255, 0.18));
|
||||
}
|
||||
|
||||
/* ---------- Features grid (default VitePress homepage) ---------- */
|
||||
|
||||
.VPFeatures {
|
||||
padding: 8px 24px 80px !important;
|
||||
}
|
||||
.VPFeatures .container {
|
||||
max-width: 1120px !important;
|
||||
}
|
||||
.VPFeature {
|
||||
border: 1px solid var(--vp-c-divider) !important;
|
||||
background: var(--vp-c-bg-elv) !important;
|
||||
border-radius: var(--radius-lg) !important;
|
||||
padding: 28px !important;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease !important;
|
||||
height: 100% !important;
|
||||
box-shadow: var(--shadow-1);
|
||||
}
|
||||
.VPFeature:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-2);
|
||||
border-color: var(--vp-c-border) !important;
|
||||
}
|
||||
.VPFeature .icon {
|
||||
width: 52px !important;
|
||||
height: 52px !important;
|
||||
margin-bottom: 20px !important;
|
||||
background: var(--vp-c-brand-soft) !important;
|
||||
border-radius: 14px !important;
|
||||
font-size: 24px !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
.VPFeature .title {
|
||||
font-size: 1.05rem !important;
|
||||
font-weight: 600 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
margin-bottom: 8px !important;
|
||||
color: var(--vp-c-text-1) !important;
|
||||
}
|
||||
.VPFeature .details {
|
||||
font-size: 0.94rem !important;
|
||||
line-height: 1.55 !important;
|
||||
color: var(--vp-c-text-2) !important;
|
||||
}
|
||||
|
||||
/* ---------- Custom blocks (tip / warning) ---------- */
|
||||
|
||||
.custom-block {
|
||||
border-radius: var(--radius-md) !important;
|
||||
border: 1px solid var(--vp-c-divider) !important;
|
||||
padding: 16px 18px !important;
|
||||
}
|
||||
.custom-block.tip {
|
||||
border-color: var(--vp-c-brand-soft) !important;
|
||||
background: var(--vp-c-tip-soft) !important;
|
||||
}
|
||||
.custom-block.warning {
|
||||
background: var(--vp-c-warning-soft) !important;
|
||||
border-color: rgba(255, 159, 10, 0.18) !important;
|
||||
}
|
||||
.custom-block .custom-block-title {
|
||||
font-weight: 600 !important;
|
||||
letter-spacing: -0.005em !important;
|
||||
}
|
||||
|
||||
/* ---------- Code blocks ---------- */
|
||||
|
||||
.vp-code-group .tabs {
|
||||
border-radius: var(--radius-md) var(--radius-md) 0 0 !important;
|
||||
}
|
||||
div[class*="language-"] {
|
||||
border-radius: var(--radius-md) !important;
|
||||
box-shadow: var(--shadow-1);
|
||||
}
|
||||
div[class*="language-"] code {
|
||||
font-feature-settings: "calt" 1, "liga" 0 !important;
|
||||
font-size: 13.5px !important;
|
||||
letter-spacing: -0.005em;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code {
|
||||
background: var(--vp-c-bg-soft) !important;
|
||||
border: 1px solid var(--vp-c-divider) !important;
|
||||
border-radius: 6px !important;
|
||||
padding: 2px 6px !important;
|
||||
font-size: 0.88em !important;
|
||||
color: var(--vp-c-text-1) !important;
|
||||
}
|
||||
|
||||
/* ---------- Tables ---------- */
|
||||
|
||||
.vp-doc table {
|
||||
border-collapse: separate !important;
|
||||
border-spacing: 0 !important;
|
||||
border: 1px solid var(--vp-c-divider) !important;
|
||||
border-radius: var(--radius-md) !important;
|
||||
overflow: hidden !important;
|
||||
display: table !important;
|
||||
width: 100% !important;
|
||||
margin: 24px 0 !important;
|
||||
}
|
||||
.vp-doc th {
|
||||
background: var(--vp-c-bg-soft) !important;
|
||||
font-weight: 600 !important;
|
||||
letter-spacing: -0.005em !important;
|
||||
font-size: 0.9rem !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
.vp-doc th,
|
||||
.vp-doc td {
|
||||
border: none !important;
|
||||
border-bottom: 1px solid var(--vp-c-divider) !important;
|
||||
padding: 12px 16px !important;
|
||||
}
|
||||
.vp-doc tr:last-child td {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
.vp-doc tr {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* ---------- Footer ---------- */
|
||||
|
||||
.VPFooter {
|
||||
border-top: 1px solid var(--vp-c-divider) !important;
|
||||
padding: 28px 24px !important;
|
||||
background: var(--vp-c-bg-alt) !important;
|
||||
}
|
||||
.VPFooter .message,
|
||||
.VPFooter .copyright {
|
||||
font-size: 13px !important;
|
||||
color: var(--vp-c-text-3) !important;
|
||||
}
|
||||
|
||||
/* ---------- Search input ---------- */
|
||||
|
||||
.DocSearch-Button {
|
||||
border-radius: 980px !important;
|
||||
background: var(--vp-c-bg-soft) !important;
|
||||
border: 1px solid var(--vp-c-divider) !important;
|
||||
box-shadow: none !important;
|
||||
height: 36px !important;
|
||||
}
|
||||
|
||||
/* ---------- Subtle ambient gradient on home ---------- */
|
||||
|
||||
.VPHome::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 0 auto 0;
|
||||
height: 720px;
|
||||
background:
|
||||
radial-gradient(60% 60% at 50% 0%, rgba(10, 132, 255, 0.10) 0%, transparent 70%),
|
||||
radial-gradient(40% 40% at 80% 10%, rgba(90, 200, 250, 0.08) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* ---------- Custom home sections (used by HomeShowcase.vue) ---------- */
|
||||
|
||||
.home-section {
|
||||
max-width: 1120px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px 96px;
|
||||
}
|
||||
.home-section + .home-section {
|
||||
padding-top: 0;
|
||||
}
|
||||
.home-eyebrow {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--vp-c-brand-1);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.home-title {
|
||||
font-size: clamp(1.75rem, 3.2vw, 2.4rem);
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1.15;
|
||||
margin: 0 0 12px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
.home-lede {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.55;
|
||||
color: var(--vp-c-text-2);
|
||||
max-width: 720px;
|
||||
margin: 0 0 40px;
|
||||
}
|
||||
|
||||
.platform-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.platform-card {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 24px;
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-elv);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
.platform-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-2);
|
||||
border-color: var(--vp-c-border);
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.platform-card .platform-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.platform-card .platform-icon svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.platform-card .platform-name {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.platform-card .platform-meta {
|
||||
font-size: 0.88rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.platform-card .platform-arrow {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
color: var(--vp-c-text-3);
|
||||
opacity: 0;
|
||||
transform: translateX(-4px);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
.platform-card:hover .platform-arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
.platform-card .platform-arrow svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* ---------- Stat strip ---------- */
|
||||
|
||||
.stats-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 24px;
|
||||
padding: 28px 32px;
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
.stat-item {
|
||||
text-align: left;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--vp-c-text-1);
|
||||
line-height: 1.1;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 0.88rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ---------- Reduce motion respect ---------- */
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
transition-duration: 0s !important;
|
||||
animation-duration: 0s !important;
|
||||
}
|
||||
}
|
||||
147
docs/apache.md
147
docs/apache.md
@@ -1,93 +1,100 @@
|
||||
# Apache Integration
|
||||
|
||||
This guide explains how to integrate the WAF patterns with Apache using ModSecurity.
|
||||
This guide explains how to deploy the generated rules in Apache HTTPD using the **ModSecurity** engine.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Apache 2.4+
|
||||
- ModSecurity module installed
|
||||
|
||||
### Install ModSecurity
|
||||
- Apache HTTPD **2.4+**
|
||||
- The **ModSecurity** module installed and enabled
|
||||
|
||||
::: code-group
|
||||
|
||||
```bash [Debian/Ubuntu]
|
||||
```bash [Debian / Ubuntu]
|
||||
sudo apt install libapache2-mod-security2
|
||||
sudo a2enmod security2
|
||||
```
|
||||
|
||||
```bash [RHEL/CentOS]
|
||||
sudo yum install mod_security
|
||||
```bash [RHEL / CentOS / Rocky]
|
||||
sudo dnf install mod_security
|
||||
```
|
||||
|
||||
```bash [Alpine]
|
||||
sudo apk add mod_security
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Quick Start
|
||||
## Quick start
|
||||
|
||||
1. Download `apache_waf.zip` from [Releases](https://github.com/fabriziosalmi/patterns/releases)
|
||||
2. Extract to your Apache configuration directory
|
||||
3. Include the files in your Apache configuration
|
||||
1. Download `apache_waf.zip` from the [latest release](https://github.com/fabriziosalmi/patterns/releases/latest).
|
||||
2. Extract under your Apache config tree (e.g. `/etc/apache2/waf_patterns/apache/`).
|
||||
3. Include the `.conf` files from the relevant virtual host or globally.
|
||||
|
||||
## Configuration Files
|
||||
## Files in the archive
|
||||
|
||||
The Apache WAF package includes ModSecurity rules organized by attack type:
|
||||
The Apache output is split by attack family, each containing standard ModSecurity `SecRule` directives.
|
||||
|
||||
| File | Protection Type |
|
||||
|------|-----------------|
|
||||
| `sqli.conf` | SQL Injection |
|
||||
| `xss.conf` | Cross-Site Scripting |
|
||||
| `rce.conf` | Remote Code Execution |
|
||||
| `lfi.conf` | Local File Inclusion |
|
||||
| `rfi.conf` | Remote File Inclusion |
|
||||
| `bots.conf` | Bad Bot Detection |
|
||||
| File | Protection |
|
||||
|------|------------|
|
||||
| `sqli.conf` | SQL injection |
|
||||
| `xss.conf` | Cross-site scripting |
|
||||
| `rce.conf` | Remote code execution |
|
||||
| `lfi.conf` | Local file inclusion |
|
||||
| `rfi.conf` | Remote file inclusion |
|
||||
| `php.conf`, `java.conf`, `iis.conf`, `shells.conf` | Stack-specific exploits |
|
||||
| `attack.conf`, `generic.conf`, `correlation.conf`, `evaluation.conf` | Generic anomaly detection |
|
||||
| `bots.conf` | Bad-bot User-Agent rules |
|
||||
|
||||
## Integration
|
||||
## Step 1 — Enable the engine
|
||||
|
||||
### Step 1: Enable ModSecurity
|
||||
|
||||
Create or edit `/etc/apache2/mods-enabled/security2.conf`:
|
||||
In `/etc/apache2/mods-enabled/security2.conf` (or equivalent):
|
||||
|
||||
```apache
|
||||
<IfModule security2_module>
|
||||
SecRuleEngine On
|
||||
SecRequestBodyAccess On
|
||||
SecResponseBodyAccess Off
|
||||
SecDebugLogLevel 0
|
||||
SecAuditEngine RelevantOnly
|
||||
SecAuditLog /var/log/apache2/modsec_audit.log
|
||||
SecAuditLogParts ABCDEFHZ
|
||||
</IfModule>
|
||||
```
|
||||
|
||||
### Step 2: Include WAF Rules
|
||||
::: tip Run in detection mode first
|
||||
Set `SecRuleEngine DetectionOnly` for the first deployment. Watch the audit log, tune false positives, then flip to `On`.
|
||||
:::
|
||||
|
||||
Add to your Apache configuration or virtual host:
|
||||
## Step 2 — Include the rules
|
||||
|
||||
Either include all files in one go:
|
||||
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
<VirtualHost *:443>
|
||||
ServerName example.com
|
||||
|
||||
# Include all WAF patterns
|
||||
Include /path/to/waf_patterns/apache/*.conf
|
||||
|
||||
# ... other configurations ...
|
||||
|
||||
Include /etc/apache2/waf_patterns/apache/*.conf
|
||||
# …other directives
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
Or include specific rule sets:
|
||||
…or pick the categories you want:
|
||||
|
||||
```apache
|
||||
Include /path/to/waf_patterns/apache/sqli.conf
|
||||
Include /path/to/waf_patterns/apache/xss.conf
|
||||
Include /path/to/waf_patterns/apache/bots.conf
|
||||
Include /etc/apache2/waf_patterns/apache/sqli.conf
|
||||
Include /etc/apache2/waf_patterns/apache/xss.conf
|
||||
Include /etc/apache2/waf_patterns/apache/rce.conf
|
||||
Include /etc/apache2/waf_patterns/apache/bots.conf
|
||||
```
|
||||
|
||||
### Step 3: Restart Apache
|
||||
## Step 3 — Validate and restart
|
||||
|
||||
```bash
|
||||
sudo apachectl configtest && sudo systemctl restart apache2
|
||||
```
|
||||
|
||||
## Rule Format
|
||||
## Rule format
|
||||
|
||||
The rules follow ModSecurity syntax:
|
||||
Generated rules follow the standard ModSecurity DSL:
|
||||
|
||||
```apache
|
||||
SecRule REQUEST_URI "@rx union.*select" \
|
||||
@@ -95,68 +102,50 @@ SecRule REQUEST_URI "@rx union.*select" \
|
||||
phase:2,\
|
||||
deny,\
|
||||
status:403,\
|
||||
log,\
|
||||
msg:'SQL Injection Attempt',\
|
||||
severity:CRITICAL"
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### Adjust Severity Levels
|
||||
### Detection-only mode
|
||||
|
||||
Modify the action from `deny` to `log` for monitoring mode:
|
||||
Switch a noisy rule from blocking to logging without removing it:
|
||||
|
||||
```apache
|
||||
SecRule REQUEST_URI "@rx pattern" \
|
||||
"id:100001,\
|
||||
phase:2,\
|
||||
log,\
|
||||
pass,\
|
||||
msg:'Potential attack detected'"
|
||||
SecRuleUpdateActionById 100001 "pass,log,msg:'SQLi candidate (audit only)'"
|
||||
```
|
||||
|
||||
### Whitelist Paths
|
||||
|
||||
Add exceptions for specific paths:
|
||||
### Whitelist a path
|
||||
|
||||
```apache
|
||||
SecRule REQUEST_URI "@beginsWith /api/webhook" \
|
||||
"id:1,\
|
||||
phase:1,\
|
||||
allow,\
|
||||
nolog"
|
||||
"id:1,phase:1,nolog,allow"
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
ModSecurity logs are typically found at:
|
||||
- `/var/log/apache2/modsec_audit.log`
|
||||
- `/var/log/httpd/modsec_audit.log`
|
||||
|
||||
Enable detailed logging:
|
||||
### Disable a single rule
|
||||
|
||||
```apache
|
||||
SecAuditEngine RelevantOnly
|
||||
SecAuditLog /var/log/apache2/modsec_audit.log
|
||||
SecAuditLogParts ABCDEFHZ
|
||||
SecRuleRemoveById 100001
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
ModSecurity logs land in:
|
||||
|
||||
- `/var/log/apache2/modsec_audit.log` — full audit trail
|
||||
- `/var/log/apache2/error.log` — rule matches and engine messages
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Test SQL injection detection
|
||||
curl -I "http://example.com/?id=1' UNION SELECT * FROM users--"
|
||||
|
||||
# Check Apache error log
|
||||
curl -I "https://example.com/?id=1' UNION SELECT * FROM users--"
|
||||
sudo tail -f /var/log/apache2/error.log
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### ModSecurity not loading
|
||||
Ensure the module is enabled: `sudo a2enmod security2`
|
||||
|
||||
### Rules not triggering
|
||||
Check that `SecRuleEngine` is set to `On` and rules are being included.
|
||||
|
||||
### Performance issues
|
||||
Consider using `SecRuleRemoveById` to disable noisy rules that cause false positives.
|
||||
- **Module not loading** — confirm with `apachectl -M | grep security2`. Re-enable with `sudo a2enmod security2`.
|
||||
- **No rules triggering** — double-check `SecRuleEngine On` and that the include path resolves; `apachectl -S` lists the parsed config.
|
||||
- **Performance regressions** — identify hot rules in the audit log and disable or scope them with `SecRuleRemoveById` / `SecRule … chain`.
|
||||
|
||||
259
docs/api.md
259
docs/api.md
@@ -1,223 +1,236 @@
|
||||
# API Reference
|
||||
# API & Scripts Reference
|
||||
|
||||
This page documents the Python scripts that power the Patterns project.
|
||||
Patterns is a small Python toolchain. Every script does one job and communicates with the rest through plain JSON or files on disk — no shared state, no daemon, no database.
|
||||
|
||||
## Core Scripts
|
||||
|
||||
### owasp2json.py
|
||||
|
||||
Fetches and parses OWASP Core Rule Set patterns from GitHub.
|
||||
|
||||
```bash
|
||||
python owasp2json.py
|
||||
```text
|
||||
owasp2json.py ──▶ owasp_rules.json ──▶ json2{nginx,apache,traefik,haproxy}.py
|
||||
└▶ badbots.py (independent)
|
||||
```
|
||||
|
||||
**Output**: `owasp_rules.json`
|
||||
All scripts are configured through **environment variables** (not CLI flags) except `owasp2json.py`, which has a small `argparse` interface.
|
||||
|
||||
**Configuration**:
|
||||
- Uses environment variable `OWASP_REPO` to specify source repository
|
||||
- Default: `coreruleset/coreruleset`
|
||||
## Pipeline scripts
|
||||
|
||||
**Features**:
|
||||
- Fetches latest CRS rules from GitHub
|
||||
- Parses `.conf` files for regex patterns
|
||||
- Extracts rule metadata (ID, severity, category)
|
||||
- Outputs structured JSON for conversion scripts
|
||||
### `owasp2json.py`
|
||||
|
||||
Fetches the OWASP Core Rule Set from GitHub and emits a flat JSON rule list.
|
||||
|
||||
```bash
|
||||
python owasp2json.py --ref v4.0 --output owasp_rules.json
|
||||
```
|
||||
|
||||
| Argument / env | Default | Purpose |
|
||||
|----------------|---------|---------|
|
||||
| `--output` | `owasp_rules.json` | Output JSON path |
|
||||
| `--ref` | `v4.0` | Tag prefix to resolve (e.g. `v4.0`, `v3.3`, `dev`) |
|
||||
| `--dry-run` | off | Fetch and parse without writing |
|
||||
| `GITHUB_TOKEN` (env) | unset | Raises the GitHub API rate limit while iterating |
|
||||
|
||||
The script verifies each blob's SHA against the GitHub-reported value before parsing it.
|
||||
|
||||
---
|
||||
|
||||
### json2nginx.py
|
||||
### `json2nginx.py`
|
||||
|
||||
Converts OWASP JSON rules to Nginx WAF configuration.
|
||||
Converts `owasp_rules.json` into Nginx `map`-based rules.
|
||||
|
||||
```bash
|
||||
python json2nginx.py
|
||||
INPUT_FILE=custom.json OUTPUT_DIR=/tmp/out python json2nginx.py
|
||||
```
|
||||
|
||||
**Input**: `owasp_rules.json`
|
||||
**Output**: `waf_patterns/nginx/`
|
||||
**Generated files** (in `OUTPUT_DIR`):
|
||||
|
||||
**Generated Files**:
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `waf_maps.conf` | Map directives (http block) |
|
||||
| `waf_rules.conf` | If statements (server block) |
|
||||
| `README.md` | Integration instructions |
|
||||
| `waf_maps.conf` | `map` directives — include in the `http` block |
|
||||
| `waf_rules.conf` | `if` rules — include in the `server` block |
|
||||
| `<category>.conf` | One file per OWASP category, **for inspection only** |
|
||||
| `README.md` | In-tree usage notes |
|
||||
|
||||
**Environment Variables**:
|
||||
- `INPUT_FILE` - Path to OWASP JSON (default: `owasp_rules.json`)
|
||||
- `OUTPUT_DIR` - Output directory (default: `waf_patterns/nginx`)
|
||||
| Env var | Default |
|
||||
|---------|---------|
|
||||
| `INPUT_FILE` | `owasp_rules.json` |
|
||||
| `OUTPUT_DIR` | `waf_patterns/nginx` |
|
||||
|
||||
---
|
||||
|
||||
### json2apache.py
|
||||
### `json2apache.py`
|
||||
|
||||
Converts OWASP JSON rules to Apache ModSecurity format.
|
||||
Converts `owasp_rules.json` into ModSecurity `SecRule` directives, partitioned by attack category.
|
||||
|
||||
```bash
|
||||
python json2apache.py
|
||||
```
|
||||
|
||||
**Input**: `owasp_rules.json`
|
||||
**Output**: `waf_patterns/apache/`
|
||||
**Generated files**: one `<category>.conf` per OWASP category (`sqli.conf`, `xss.conf`, `rce.conf`, `lfi.conf`, …) — each contains pure ModSecurity rules ready to `Include`.
|
||||
|
||||
**Generated Files**:
|
||||
- Category-specific `.conf` files (sqli.conf, xss.conf, etc.)
|
||||
- Each file contains ModSecurity `SecRule` directives
|
||||
| Env var | Default |
|
||||
|---------|---------|
|
||||
| `INPUT_FILE` | `owasp_rules.json` |
|
||||
| `OUTPUT_DIR` | `waf_patterns/apache` |
|
||||
|
||||
---
|
||||
|
||||
### json2traefik.py
|
||||
### `json2traefik.py`
|
||||
|
||||
Converts OWASP JSON rules to Traefik middleware configuration.
|
||||
Converts `owasp_rules.json` into a Traefik file-provider middleware.
|
||||
|
||||
```bash
|
||||
python json2traefik.py
|
||||
```
|
||||
|
||||
**Input**: `owasp_rules.json`
|
||||
**Output**: `waf_patterns/traefik/`
|
||||
**Generated files**:
|
||||
|
||||
**Generated Files**:
|
||||
- `middleware.toml` - Traefik middleware configuration
|
||||
- `README.md` - Integration instructions
|
||||
- `middleware.toml` — complete WAF middleware definition
|
||||
- `README.md` — in-tree integration notes
|
||||
|
||||
| Env var | Default |
|
||||
|---------|---------|
|
||||
| `INPUT_FILE` | `owasp_rules.json` |
|
||||
| `OUTPUT_DIR` | `waf_patterns/traefik` |
|
||||
|
||||
---
|
||||
|
||||
### json2haproxy.py
|
||||
### `json2haproxy.py`
|
||||
|
||||
Converts OWASP JSON rules to HAProxy ACL format.
|
||||
Converts `owasp_rules.json` into HAProxy ACL files.
|
||||
|
||||
```bash
|
||||
python json2haproxy.py
|
||||
```
|
||||
|
||||
**Input**: `owasp_rules.json`
|
||||
**Output**: `waf_patterns/haproxy/`
|
||||
**Generated files**:
|
||||
|
||||
**Generated Files**:
|
||||
- `waf.acl` - Main WAF ACL rules
|
||||
- `README.md` - Integration instructions
|
||||
- `waf.acl` — one regex per line, designed for `-f /etc/haproxy/waf.acl`
|
||||
- `README.md` — in-tree integration notes
|
||||
|
||||
| Env var | Default |
|
||||
|---------|---------|
|
||||
| `INPUT_FILE` | `owasp_rules.json` |
|
||||
| `OUTPUT_DIR` | `waf_patterns/haproxy/` |
|
||||
|
||||
---
|
||||
|
||||
### badbots.py
|
||||
### `badbots.py`
|
||||
|
||||
Generates bad bot blocking configurations from public bot lists.
|
||||
Independently fetches public bad-bot User-Agent lists and emits a `bots.*` file in each platform output directory.
|
||||
|
||||
```bash
|
||||
python badbots.py
|
||||
```
|
||||
|
||||
**Output**: Bot configurations in each `waf_patterns/*/` directory
|
||||
**Generated files** (per platform):
|
||||
|
||||
**Features**:
|
||||
- Fetches from multiple public bot lists
|
||||
- Includes fallback sources for reliability
|
||||
- Generates platform-specific configs
|
||||
| Platform | File |
|
||||
|----------|------|
|
||||
| Nginx | `waf_patterns/nginx/bots.conf` |
|
||||
| Apache | `waf_patterns/apache/bots.conf` |
|
||||
| Traefik | `waf_patterns/traefik/bots.toml` |
|
||||
| HAProxy | `waf_patterns/haproxy/bots.acl` |
|
||||
|
||||
---
|
||||
| Env var | Purpose |
|
||||
|---------|---------|
|
||||
| `GITHUB_TOKEN` | Raises the GitHub API rate limit when fetching upstream lists |
|
||||
|
||||
## Import Scripts
|
||||
If a remote source is unreachable, the script falls back to a bundled list.
|
||||
|
||||
These scripts help import existing WAF configurations.
|
||||
## Import / install scripts
|
||||
|
||||
### import_nginx_waf.py
|
||||
The `import_*.py` scripts copy generated files into a server's runtime configuration directory and (optionally) splice an `Include` line into the main config. They are configured **entirely** through environment variables.
|
||||
|
||||
Import Nginx WAF patterns from external sources.
|
||||
### `import_nginx_waf.py`
|
||||
|
||||
```bash
|
||||
python import_nginx_waf.py --source /path/to/external/rules
|
||||
```
|
||||
| Env var | Default |
|
||||
|---------|---------|
|
||||
| `WAF_DIR` | `waf_patterns/nginx` |
|
||||
| `NGINX_WAF_DIR` | `/etc/nginx/waf/` |
|
||||
| `NGINX_CONF` | `/etc/nginx/nginx.conf` |
|
||||
| `BACKUP_DIR` | `/etc/nginx/waf_backup/` |
|
||||
|
||||
### import_apache_waf.py
|
||||
### `import_apache_waf.py`
|
||||
|
||||
Import Apache ModSecurity rules.
|
||||
| Env var | Default |
|
||||
|---------|---------|
|
||||
| `WAF_DIR` | `waf_patterns/apache` |
|
||||
| `APACHE_WAF_DIR` | `/etc/modsecurity.d/` |
|
||||
| `APACHE_CONF` | `/etc/apache2/apache2.conf` |
|
||||
| `BACKUP_DIR` | `/etc/modsecurity.d/backup` |
|
||||
|
||||
```bash
|
||||
python import_apache_waf.py --source /path/to/modsec/rules
|
||||
```
|
||||
### `import_traefik_waf.py`
|
||||
|
||||
### import_traefik_waf.py
|
||||
| Env var | Default |
|
||||
|---------|---------|
|
||||
| `WAF_DIR` | `waf_patterns/traefik` |
|
||||
| `TRAEFIK_WAF_DIR` | `/etc/traefik/waf/` |
|
||||
| `TRAEFIK_DYNAMIC_CONF` | `/etc/traefik/dynamic.toml` |
|
||||
| `BACKUP_DIR` | `/etc/traefik/waf_backup/` |
|
||||
|
||||
Import Traefik middleware configurations.
|
||||
### `import_haproxy_waf.py`
|
||||
|
||||
```bash
|
||||
python import_traefik_waf.py --source /path/to/traefik/config
|
||||
```
|
||||
| Env var | Default |
|
||||
|---------|---------|
|
||||
| `WAF_DIR` | `waf_patterns/haproxy` |
|
||||
| `HAPROXY_WAF_DIR` | `/etc/haproxy/waf/` |
|
||||
| `HAPROXY_CONF` | `/etc/haproxy/haproxy.cfg` |
|
||||
| `BACKUP_DIR` | `/etc/haproxy/waf_backup/` |
|
||||
|
||||
### import_haproxy_waf.py
|
||||
::: warning Privileged paths
|
||||
The defaults point at system directories (`/etc/...`). Run the import scripts as root, or override every env var to point at a sandbox before running them.
|
||||
:::
|
||||
|
||||
Import HAProxy ACL rules.
|
||||
## Data format
|
||||
|
||||
```bash
|
||||
python import_haproxy_waf.py --source /path/to/haproxy/acl
|
||||
```
|
||||
### `owasp_rules.json`
|
||||
|
||||
---
|
||||
|
||||
## Data Structures
|
||||
|
||||
### owasp_rules.json Format
|
||||
A flat JSON array. Each item is a single rule with two required fields:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "942100",
|
||||
"pattern": "(?i:union.*select)",
|
||||
"category": "sqli",
|
||||
"severity": "critical",
|
||||
"location": "request-uri",
|
||||
"description": "SQL Injection Attack Detected"
|
||||
"category": "SQLI",
|
||||
"pattern": "(?i:union[\\s\\S]+select)"
|
||||
},
|
||||
{
|
||||
"category": "XSS",
|
||||
"pattern": "(?i:<script[^>]*>)"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Fields**:
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | string | OWASP CRS rule ID |
|
||||
| `pattern` | string | Regex pattern |
|
||||
| `category` | string | Attack category (sqli, xss, rce, etc.) |
|
||||
| `severity` | string | critical, high, medium, low |
|
||||
| `location` | string | Where to match (request-uri, headers, etc.) |
|
||||
| `description` | string | Human-readable description |
|
||||
| `category` | string | OWASP CRS category derived from the source filename (e.g. `SQLI`, `XSS`, `RCE`, `LFI`, `RFI`, `BOTS`) |
|
||||
| `pattern` | string | Regex extracted from the matching `SecRule` directive |
|
||||
|
||||
---
|
||||
The converters validate each pattern with Python's `re.compile` before emitting platform-specific output, so malformed regexes are dropped rather than propagated.
|
||||
|
||||
## Extending the Project
|
||||
## Extending the toolchain
|
||||
|
||||
### Adding a New Platform
|
||||
### Adding a new platform
|
||||
|
||||
1. Create `json2<platform>.py` based on existing converters
|
||||
2. Add output directory in `waf_patterns/<platform>/`
|
||||
3. Update GitHub Actions workflow
|
||||
4. Add documentation in `docs/`
|
||||
1. Copy one of the existing `json2<platform>.py` converters as a starting point.
|
||||
2. Implement `_sanitize_pattern()` for the target syntax (escape rules differ between Nginx, Apache, HAProxy, …).
|
||||
3. Emit your output under `waf_patterns/<platform>/`.
|
||||
4. Add a workflow step in `.github/workflows/update_patterns.yml` to package the result.
|
||||
5. Add a documentation page under `docs/`.
|
||||
|
||||
### Custom Pattern Sources
|
||||
### Pinning a different OWASP CRS version
|
||||
|
||||
Modify `owasp2json.py` to add new pattern sources:
|
||||
|
||||
```python
|
||||
SOURCES = [
|
||||
"coreruleset/coreruleset",
|
||||
"your-org/your-rules",
|
||||
]
|
||||
```bash
|
||||
python owasp2json.py --ref v3.3
|
||||
```
|
||||
|
||||
---
|
||||
### Pulling rules from a fork
|
||||
|
||||
`owasp2json.py` hardcodes the upstream repository constant `coreruleset/coreruleset`. To target a fork, edit `GITHUB_REPO_URL` near the top of the script.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Listed in `requirements.txt`:
|
||||
|
||||
```
|
||||
requests>=2.28.0
|
||||
beautifulsoup4>=4.11.0
|
||||
```
|
||||
|
||||
Install with:
|
||||
Listed in [`requirements.txt`](https://github.com/fabriziosalmi/patterns/blob/main/requirements.txt). Install with:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
The pipeline targets Python **3.11+**.
|
||||
|
||||
211
docs/badbots.md
211
docs/badbots.md
@@ -1,191 +1,172 @@
|
||||
# Bad Bot Detection
|
||||
|
||||
This guide explains how to use the bad bot detection feature to block malicious crawlers and scrapers.
|
||||
`badbots.py` generates per-platform User-Agent blocklists alongside the OWASP rules, so you can drop noisy crawlers, AI scrapers, and known abusive scanners in a single include.
|
||||
|
||||
## Overview
|
||||
## How it works
|
||||
|
||||
The `badbots.py` script generates configuration files to block known malicious bots based on their User-Agent strings. It fetches bot lists from multiple public sources and generates blocking rules for each supported web server.
|
||||
1. The script fetches public bot lists — including [`ai.robots.txt`](https://github.com/ai-robots-txt/ai.robots.txt) and other community-curated sources.
|
||||
2. It deduplicates and normalizes the User-Agent patterns.
|
||||
3. It emits one file per platform under `waf_patterns/<platform>/`.
|
||||
4. The daily GitHub Actions workflow regenerates and republishes these files alongside the OWASP-derived rules.
|
||||
|
||||
## How It Works
|
||||
If a primary source is unreachable, the script falls back to a bundled list so the build still succeeds.
|
||||
|
||||
1. Fetches bot lists from public sources:
|
||||
- [ai.robots.txt](https://github.com/ai-robots-txt/ai.robots.txt)
|
||||
- Various community-maintained bot lists
|
||||
2. Generates blocking configurations for each platform
|
||||
3. Updates configurations daily via GitHub Actions
|
||||
|
||||
## Generated Files
|
||||
## Generated files
|
||||
|
||||
| Platform | File | Format |
|
||||
|----------|------|--------|
|
||||
| Nginx | `bots.conf` | Map directive |
|
||||
| Apache | `bots.conf` | ModSecurity rules |
|
||||
| Traefik | `bots.toml` | Middleware config |
|
||||
| HAProxy | `bots.acl` | ACL patterns |
|
||||
| Nginx | `bots.conf` | `map $http_user_agent $bad_bot` |
|
||||
| Apache | `bots.conf` | ModSecurity `SecRule` directives |
|
||||
| Traefik | `bots.toml` | Middleware regex replacements |
|
||||
| HAProxy | `bots.acl` | One regex per line, loadable with `-f` |
|
||||
|
||||
## Nginx Bot Blocker
|
||||
|
||||
The Nginx configuration uses a map directive:
|
||||
## Nginx
|
||||
|
||||
```nginx
|
||||
# In the http block:
|
||||
include /etc/nginx/waf_patterns/nginx/bots.conf;
|
||||
|
||||
# In any server block you want to protect:
|
||||
server {
|
||||
if ($bad_bot) { return 403; }
|
||||
}
|
||||
```
|
||||
|
||||
The map looks like:
|
||||
|
||||
```nginx
|
||||
# In http block
|
||||
map $http_user_agent $bad_bot {
|
||||
default 0;
|
||||
"~*AhrefsBot" 1;
|
||||
"~*SemrushBot" 1;
|
||||
"~*MJ12bot" 1;
|
||||
"~*DotBot" 1;
|
||||
# ... more bots
|
||||
}
|
||||
|
||||
# In server block
|
||||
if ($bad_bot) {
|
||||
return 403;
|
||||
"~*AhrefsBot" 1;
|
||||
"~*SemrushBot" 1;
|
||||
"~*MJ12bot" 1;
|
||||
"~*GPTBot" 1;
|
||||
# …
|
||||
}
|
||||
```
|
||||
|
||||
### Integration
|
||||
|
||||
```nginx
|
||||
http {
|
||||
include /path/to/waf_patterns/nginx/bots.conf;
|
||||
|
||||
server {
|
||||
if ($bad_bot) {
|
||||
return 403;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Apache Bot Blocker
|
||||
|
||||
Uses ModSecurity rules:
|
||||
## Apache
|
||||
|
||||
```apache
|
||||
SecRule REQUEST_HEADERS:User-Agent "@rx AhrefsBot" \
|
||||
"id:200001,phase:1,deny,status:403,msg:'Bad Bot Blocked'"
|
||||
```
|
||||
|
||||
## HAProxy Bot Blocker
|
||||
Include the file globally or per VirtualHost:
|
||||
|
||||
Uses ACL rules:
|
||||
|
||||
```haproxy
|
||||
acl bad_bot hdr_reg(User-Agent) -i -f /etc/haproxy/bots.acl
|
||||
http-request deny if bad_bot
|
||||
```apache
|
||||
Include /etc/apache2/waf_patterns/apache/bots.conf
|
||||
```
|
||||
|
||||
## Blocked Bot Categories
|
||||
## HAProxy
|
||||
|
||||
The following categories of bots are blocked by default:
|
||||
```haproxy
|
||||
acl bad_bot hdr(User-Agent) -m reg -i -f /etc/haproxy/bots.acl
|
||||
http-request deny deny_status 403 if bad_bot
|
||||
```
|
||||
|
||||
## Traefik
|
||||
|
||||
```toml
|
||||
[http.middlewares.bot-blocker]
|
||||
# populated automatically by bots.toml
|
||||
```
|
||||
|
||||
Reference `bot-blocker@file` from the routers you want to protect.
|
||||
|
||||
## What gets blocked
|
||||
|
||||
The default list groups User-Agent patterns into four broad categories.
|
||||
|
||||
### SEO and marketing crawlers
|
||||
|
||||
Aggressive site indexers that are usually unwelcome on production traffic:
|
||||
|
||||
### SEO/Marketing Crawlers
|
||||
- AhrefsBot
|
||||
- SemrushBot
|
||||
- MJ12bot
|
||||
- DotBot
|
||||
- BLEXBot
|
||||
|
||||
### AI/ML Crawlers
|
||||
- GPTBot
|
||||
- ChatGPT-User
|
||||
- CCBot
|
||||
- Google-Extended
|
||||
- Anthropic-AI
|
||||
### AI training crawlers
|
||||
|
||||
Most are documented at [ai.robots.txt](https://github.com/ai-robots-txt/ai.robots.txt):
|
||||
|
||||
- GPTBot, ChatGPT-User
|
||||
- ClaudeBot, Anthropic-AI
|
||||
- Google-Extended
|
||||
- CCBot, Bytespider, PerplexityBot
|
||||
|
||||
### General scrapers
|
||||
|
||||
### Scrapers
|
||||
- DataForSeoBot
|
||||
- PetalBot
|
||||
- Bytespider
|
||||
- ClaudeBot
|
||||
|
||||
### Malicious Bots
|
||||
- Known vulnerability scanners
|
||||
- Spam bots
|
||||
- Content scrapers
|
||||
### Malicious scanners
|
||||
|
||||
Public vulnerability scanners and spam bots that have no legitimate reason to crawl your origin.
|
||||
|
||||
::: tip Search engines are not blocked
|
||||
Major search engines (Googlebot, Bingbot, DuckDuckBot, Baiduspider, YandexBot) are **not** included in the default block list — blocking them harms SEO.
|
||||
:::
|
||||
|
||||
## Customization
|
||||
|
||||
### Add Custom Bots
|
||||
|
||||
Edit the generated file or add your own patterns:
|
||||
### Add your own pattern
|
||||
|
||||
```nginx
|
||||
# Nginx: Add to bots.conf
|
||||
# Append in bots.conf
|
||||
"~*MyCustomBot" 1;
|
||||
```
|
||||
|
||||
```apache
|
||||
# Apache: Add rule
|
||||
SecRule REQUEST_HEADERS:User-Agent "@rx MyCustomBot" \
|
||||
"id:200999,deny"
|
||||
"id:200999,phase:1,deny,status:403"
|
||||
```
|
||||
|
||||
### Whitelist Bots
|
||||
### Whitelist a bot
|
||||
|
||||
For Nginx, allow specific bots:
|
||||
For Nginx, override the match before the catch-all:
|
||||
|
||||
```nginx
|
||||
map $http_user_agent $bad_bot {
|
||||
default 0;
|
||||
"~*Googlebot" 0; # Allow Google
|
||||
"~*AhrefsBot" 1; # Block Ahrefs
|
||||
"~*Googlebot" 0; # explicit allow
|
||||
"~*AhrefsBot" 1;
|
||||
}
|
||||
```
|
||||
|
||||
### Allow All Bots for Specific Paths
|
||||
### Allow bots inside a path
|
||||
|
||||
```nginx
|
||||
location /public-api {
|
||||
# Override bot blocking
|
||||
if ($bad_bot) {
|
||||
# Don't block here
|
||||
}
|
||||
location /public-api/ {
|
||||
# bypass the bot rule for this path
|
||||
proxy_pass http://upstream;
|
||||
}
|
||||
|
||||
location / {
|
||||
if ($bad_bot) { return 403; }
|
||||
proxy_pass http://upstream;
|
||||
}
|
||||
```
|
||||
|
||||
## Generate Manually
|
||||
|
||||
Run the script to regenerate bot lists:
|
||||
## Regenerating manually
|
||||
|
||||
```bash
|
||||
python badbots.py
|
||||
```
|
||||
|
||||
The script supports fallback lists if primary sources are unavailable.
|
||||
The generated files end up in `waf_patterns/<platform>/`.
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Log Blocked Bots
|
||||
|
||||
Enable logging to track blocked requests:
|
||||
|
||||
```nginx
|
||||
if ($bad_bot) {
|
||||
access_log /var/log/nginx/blocked_bots.log;
|
||||
return 403;
|
||||
}
|
||||
```
|
||||
|
||||
### Analyze Bot Traffic
|
||||
Track which patterns actually fire in your traffic:
|
||||
|
||||
```bash
|
||||
# Count blocked bot requests
|
||||
grep "403" /var/log/nginx/access.log | \
|
||||
awk '{print $12}' | sort | uniq -c | sort -rn | head -20
|
||||
# Top 20 user agents that hit a 403
|
||||
awk '$9 == 403 {print $12}' /var/log/nginx/access.log \
|
||||
| sort | uniq -c | sort -rn | head -20
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Regular Updates**: The bot lists are updated daily. Pull the latest changes or download from releases.
|
||||
|
||||
2. **Monitor False Positives**: Some legitimate services may use blocked User-Agents. Monitor your logs.
|
||||
|
||||
3. **Combine with Rate Limiting**: Use bot blocking with rate limiting for comprehensive protection.
|
||||
|
||||
4. **Test Before Deploying**: Verify that legitimate traffic (search engines, monitoring) is not blocked.
|
||||
|
||||
::: warning
|
||||
Blocking search engine bots (Googlebot, Bingbot) can negatively impact SEO. The default lists do **not** block major search engines.
|
||||
:::
|
||||
If you see legitimate traffic in the list, add it to a whitelist and re-include `bots.conf` after your override.
|
||||
|
||||
@@ -1,78 +1,94 @@
|
||||
# Getting Started
|
||||
|
||||
This guide will help you get up and running with Patterns WAF configurations for your web server.
|
||||
This guide walks you through installing **Patterns** and integrating the generated WAF rules into your web server.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Python 3.11+** (if building from source)
|
||||
- **pip** (Python package installer)
|
||||
- **git** (for cloning the repository)
|
||||
| Requirement | Notes |
|
||||
|-------------|-------|
|
||||
| **Python 3.11+** | Only required when building from source. The CI workflow targets 3.11. |
|
||||
| **pip** | To install the packages listed in `requirements.txt`. |
|
||||
| **git** | Optional — only needed if cloning the repository. |
|
||||
|
||||
## Installation Options
|
||||
## Two installation paths
|
||||
|
||||
### Option 1: Download Pre-Generated Configurations
|
||||
### Option 1 — Download a pre-built release
|
||||
|
||||
The easiest way to get started is to download pre-built configurations:
|
||||
The fastest path. A scheduled GitHub Actions workflow rebuilds every archive daily and publishes them on the [Releases page](https://github.com/fabriziosalmi/patterns/releases/latest).
|
||||
|
||||
1. Go to the [Releases](https://github.com/fabriziosalmi/patterns/releases) page
|
||||
2. Download the ZIP file for your web server:
|
||||
- `nginx_waf.zip` - Nginx configurations
|
||||
- `apache_waf.zip` - Apache ModSecurity rules
|
||||
- `traefik_waf.zip` - Traefik middleware
|
||||
- `haproxy_waf.zip` - HAProxy ACL files
|
||||
3. Extract and integrate into your server configuration
|
||||
| Archive | Contains | Target |
|
||||
|---------|----------|--------|
|
||||
| `nginx_waf.zip` | `waf_maps.conf`, `waf_rules.conf`, `bots.conf`, category files | Nginx |
|
||||
| `apache_waf.zip` | Per-category ModSecurity `.conf` files, `bots.conf` | Apache + mod_security2 |
|
||||
| `traefik_waf.zip` | `middleware.toml`, `bots.toml` | Traefik (file provider) |
|
||||
| `haproxy_waf.zip` | `waf.acl`, `bots.acl` | HAProxy |
|
||||
|
||||
### Option 2: Build from Source
|
||||
Pick one, extract, then jump to the matching integration guide.
|
||||
|
||||
If you prefer to generate the configurations yourself:
|
||||
### Option 2 — Build from source
|
||||
|
||||
Choose this path if you want to pin a specific OWASP CRS tag, customize the converter, or run the toolchain in your own CI:
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/fabriziosalmi/patterns.git
|
||||
cd patterns
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Fetch latest OWASP rules
|
||||
# 1. Fetch the latest OWASP Core Rule Set into a JSON intermediate
|
||||
python owasp2json.py
|
||||
|
||||
# Generate configurations for your platform
|
||||
python json2nginx.py # For Nginx
|
||||
python json2apache.py # For Apache
|
||||
python json2traefik.py # For Traefik
|
||||
python json2haproxy.py # For HAProxy
|
||||
# 2. Convert the JSON into native rules for your platform
|
||||
python json2nginx.py
|
||||
python json2apache.py
|
||||
python json2traefik.py
|
||||
python json2haproxy.py
|
||||
|
||||
# Generate bad bot blockers
|
||||
# 3. Generate bad-bot blocklists alongside
|
||||
python badbots.py
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
::: tip GitHub API rate limits
|
||||
`owasp2json.py` reads from the GitHub API. Set `GITHUB_TOKEN` in your environment to raise the rate limit when iterating locally.
|
||||
:::
|
||||
|
||||
After running the scripts, you'll find the generated files in the `waf_patterns/` directory:
|
||||
## Output layout
|
||||
|
||||
```
|
||||
After running the converters, generated files live under `waf_patterns/`:
|
||||
|
||||
```text
|
||||
waf_patterns/
|
||||
├── nginx/ # Nginx WAF configs
|
||||
├── apache/ # Apache ModSecurity rules
|
||||
├── traefik/ # Traefik middleware configs
|
||||
└── haproxy/ # HAProxy ACL files
|
||||
├── nginx/ # waf_maps.conf, waf_rules.conf, bots.conf, per-category files
|
||||
├── apache/ # sqli.conf, xss.conf, rce.conf, lfi.conf, … bots.conf
|
||||
├── traefik/ # middleware.toml, bots.toml
|
||||
└── haproxy/ # waf.acl, bots.acl
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
## Next steps
|
||||
|
||||
Choose your web server to learn how to integrate the WAF configurations:
|
||||
Choose your platform to wire the rules into a running server:
|
||||
|
||||
- [Nginx Integration](/nginx)
|
||||
- [Apache Integration](/apache)
|
||||
- [Traefik Integration](/traefik)
|
||||
- [HAProxy Integration](/haproxy)
|
||||
- [Nginx integration](/nginx)
|
||||
- [Apache (ModSecurity) integration](/apache)
|
||||
- [Traefik integration](/traefik)
|
||||
- [HAProxy integration](/haproxy)
|
||||
|
||||
## Automatic Updates
|
||||
For details on the bot blocklist itself, see [Bad Bot Detection](/badbots). For a reference of every script and the JSON schema that ties them together, see the [API reference](/api).
|
||||
|
||||
The repository includes a GitHub Actions workflow that:
|
||||
- Fetches the latest OWASP CRS rules **daily**
|
||||
- Regenerates all WAF configurations
|
||||
- Creates a new release with updated files
|
||||
## How updates flow
|
||||
|
||||
To get the latest rules, simply download from the [Releases](https://github.com/fabriziosalmi/patterns/releases) page or pull the latest changes if you cloned the repository.
|
||||
```text
|
||||
┌─────────────────────┐ daily cron ┌──────────────────────┐
|
||||
│ coreruleset/ │ ───────────────▶ │ owasp2json.py │
|
||||
│ coreruleset (GH) │ │ → owasp_rules.json │
|
||||
└─────────────────────┘ └──────────┬───────────┘
|
||||
│
|
||||
┌─────────────────┬──────────────────┬──────┴──────────┐
|
||||
▼ ▼ ▼ ▼
|
||||
json2nginx.py json2apache.py json2traefik.py json2haproxy.py
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
nginx_waf.zip apache_waf.zip traefik_waf.zip haproxy_waf.zip
|
||||
(published as a GitHub Release)
|
||||
```
|
||||
|
||||
To stay current, either download the latest archive or `git pull` and re-run the converters.
|
||||
|
||||
176
docs/haproxy.md
176
docs/haproxy.md
@@ -1,85 +1,64 @@
|
||||
# HAProxy Integration
|
||||
|
||||
This guide explains how to integrate the WAF patterns with HAProxy using ACL rules.
|
||||
This guide explains how to plug the generated rules into HAProxy using **ACL** files.
|
||||
|
||||
## Quick Start
|
||||
## Quick start
|
||||
|
||||
1. Download `haproxy_waf.zip` from [Releases](https://github.com/fabriziosalmi/patterns/releases)
|
||||
2. Extract the files
|
||||
3. Include the ACL files in your HAProxy configuration
|
||||
1. Download `haproxy_waf.zip` from the [latest release](https://github.com/fabriziosalmi/patterns/releases/latest).
|
||||
2. Drop the ACL files into `/etc/haproxy/` (or any path you prefer).
|
||||
3. Reference them from a `frontend` block.
|
||||
4. Reload HAProxy.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
The HAProxy WAF package includes:
|
||||
## Files in the archive
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `waf.acl` | Main WAF ACL rules |
|
||||
| `bots.acl` | Bad bot detection ACLs |
|
||||
| `waf.acl` | Pre-compiled regex patterns covering every OWASP CRS category |
|
||||
| `bots.acl` | Bad-bot User-Agent patterns |
|
||||
|
||||
## Integration
|
||||
## Step 1 — Reference the ACL files
|
||||
|
||||
### Step 1: Include ACL Files
|
||||
|
||||
In your `haproxy.cfg`, include the WAF ACL files:
|
||||
The cleanest approach is to load the patterns from disk with `-f`:
|
||||
|
||||
```haproxy
|
||||
frontend http-in
|
||||
bind *:80
|
||||
|
||||
# Include WAF ACL rules
|
||||
acl waf_block_sqli path_reg -i union.*select
|
||||
acl waf_block_sqli path_reg -i insert.*into
|
||||
acl waf_block_xss path_reg -i <script>
|
||||
|
||||
# Or include from external file
|
||||
# acl waf_patterns path_reg -i -f /etc/haproxy/waf.acl
|
||||
|
||||
# Block matching requests
|
||||
http-request deny if waf_block_sqli
|
||||
http-request deny if waf_block_xss
|
||||
|
||||
bind *:443 ssl crt /etc/haproxy/certs/
|
||||
|
||||
acl waf_match path,url_dec -m reg -i -f /etc/haproxy/waf.acl
|
||||
acl waf_match_q query -m reg -i -f /etc/haproxy/waf.acl
|
||||
acl bad_bot hdr(User-Agent) -m reg -i -f /etc/haproxy/bots.acl
|
||||
|
||||
http-request deny deny_status 403 if waf_match || waf_match_q || bad_bot
|
||||
|
||||
default_backend servers
|
||||
```
|
||||
|
||||
### Step 2: Include Bot Blockers
|
||||
|
||||
```haproxy
|
||||
frontend http-in
|
||||
bind *:80
|
||||
|
||||
# Bad bot detection
|
||||
acl bad_bot hdr_reg(User-Agent) -i -f /etc/haproxy/bots.acl
|
||||
http-request deny if bad_bot
|
||||
|
||||
default_backend servers
|
||||
```
|
||||
|
||||
### Step 3: Reload HAProxy
|
||||
## Step 2 — Validate and reload
|
||||
|
||||
```bash
|
||||
haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy
|
||||
sudo haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy
|
||||
```
|
||||
|
||||
## ACL Rule Format
|
||||
## ACL primer
|
||||
|
||||
HAProxy ACLs use pattern matching on various request attributes:
|
||||
HAProxy ACLs match against fetch samples (path, query, headers, …) using converters and matchers:
|
||||
|
||||
```haproxy
|
||||
# Match path
|
||||
acl sqli_path path_reg -i union.*select
|
||||
# Match path against a regex
|
||||
acl sqli_path path -m reg -i union.*select
|
||||
|
||||
# Match query string
|
||||
acl sqli_query url_param(id) -m reg -i union.*select
|
||||
# Match a specific query parameter
|
||||
acl sqli_qid url_param(id) -m reg -i union.*select
|
||||
|
||||
# Match headers
|
||||
acl bad_referer hdr_reg(Referer) -i malicious-site\.com
|
||||
# Match a request header
|
||||
acl bad_ref hdr(Referer) -m reg -i malicious-site\.com
|
||||
|
||||
# Combined conditions
|
||||
http-request deny if sqli_path OR sqli_query
|
||||
# Combine with boolean operators
|
||||
http-request deny if sqli_path || sqli_qid
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
## A complete example
|
||||
|
||||
```haproxy
|
||||
global
|
||||
@@ -91,102 +70,75 @@ defaults
|
||||
log global
|
||||
option httplog
|
||||
timeout connect 5s
|
||||
timeout client 50s
|
||||
timeout server 50s
|
||||
timeout client 50s
|
||||
timeout server 50s
|
||||
|
||||
frontend http-in
|
||||
bind *:80
|
||||
|
||||
# WAF Rules
|
||||
acl waf_sqli path_reg -i (union.*select|insert.*into|delete.*from)
|
||||
acl waf_xss path_reg -i (<script|javascript:|on\w+\s*=)
|
||||
acl waf_lfi path_reg -i (\.\.\/|\.\.\\)
|
||||
acl waf_rce path_reg -i (;|\||`|\$\()
|
||||
|
||||
# Bot blocking
|
||||
acl bad_bot hdr_reg(User-Agent) -i (AhrefsBot|SemrushBot|MJ12bot)
|
||||
|
||||
# Deny malicious requests
|
||||
http-request deny deny_status 403 if waf_sqli
|
||||
http-request deny deny_status 403 if waf_xss
|
||||
http-request deny deny_status 403 if waf_lfi
|
||||
http-request deny deny_status 403 if waf_rce
|
||||
http-request deny deny_status 403 if bad_bot
|
||||
|
||||
|
||||
# WAF
|
||||
acl waf_match path,url_dec -m reg -i -f /etc/haproxy/waf.acl
|
||||
acl waf_match_q query -m reg -i -f /etc/haproxy/waf.acl
|
||||
acl bad_bot hdr(User-Agent) -m reg -i -f /etc/haproxy/bots.acl
|
||||
|
||||
# Block matching requests
|
||||
http-request deny deny_status 403 if waf_match || waf_match_q || bad_bot
|
||||
|
||||
default_backend servers
|
||||
|
||||
backend servers
|
||||
balance roundrobin
|
||||
server server1 127.0.0.1:8080 check
|
||||
server srv1 127.0.0.1:8080 check
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### Custom Error Pages
|
||||
### Custom error response
|
||||
|
||||
Return a custom error page for blocked requests:
|
||||
Return a styled error body instead of the default empty 403:
|
||||
|
||||
```haproxy
|
||||
http-request deny deny_status 403 content-type text/html \
|
||||
string "Access Denied" if waf_sqli
|
||||
http-request deny deny_status 403 \
|
||||
content-type "text/html; charset=utf-8" \
|
||||
string "<h1>Blocked by WAF</h1>" if waf_match
|
||||
```
|
||||
|
||||
### Logging Blocked Requests
|
||||
|
||||
Create a dedicated log for WAF blocks:
|
||||
### Logging blocked requests
|
||||
|
||||
```haproxy
|
||||
frontend http-in
|
||||
# Log blocked requests
|
||||
http-request set-var(txn.blocked) str(1) if waf_sqli
|
||||
http-request capture var(txn.blocked) len 1
|
||||
|
||||
# Custom log format
|
||||
log-format "%ci:%cp [%t] %ft %b/%s %Tq/%Tw/%Tc/%Tr/%Tt %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq blocked=%[var(txn.blocked)]"
|
||||
http-request set-var(txn.blocked) str(1) if waf_match
|
||||
http-request capture var(txn.blocked) len 1
|
||||
|
||||
log-format "%ci:%cp [%t] %ft %b/%s %ST %B blocked=%[var(txn.blocked)] ua=%[capture.req.hdr(0)]"
|
||||
```
|
||||
|
||||
### Whitelist Paths
|
||||
|
||||
Skip WAF for specific paths:
|
||||
### Per-path whitelist
|
||||
|
||||
```haproxy
|
||||
acl is_api path_beg /api/webhook
|
||||
http-request deny if waf_sqli !is_api
|
||||
acl is_webhook path_beg /api/webhook
|
||||
http-request deny deny_status 403 if waf_match !is_webhook
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Combine WAF with rate limiting:
|
||||
### Combine with rate limiting
|
||||
|
||||
```haproxy
|
||||
# Stick table for rate limiting
|
||||
stick-table type ip size 100k expire 30s store http_req_rate(10s)
|
||||
http-request track-sc0 src
|
||||
acl too_many_requests sc_http_req_rate(0) gt 100
|
||||
|
||||
http-request deny if too_many_requests
|
||||
acl too_many sc_http_req_rate(0) gt 100
|
||||
http-request deny deny_status 429 if too_many
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Test SQL injection detection
|
||||
curl -I "http://example.com/?id=1' UNION SELECT * FROM users--"
|
||||
|
||||
# Test bot blocking
|
||||
curl -A "AhrefsBot" -I "http://example.com/"
|
||||
|
||||
# Check HAProxy stats
|
||||
echo "show stat" | socat stdio /var/run/haproxy.sock
|
||||
echo "show stat" | sudo socat stdio /var/run/haproxy.sock
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### ACLs not matching
|
||||
Use `haproxy -c -f haproxy.cfg` to validate syntax. Enable debug logging to see ACL evaluation.
|
||||
|
||||
### Performance impact
|
||||
ACL evaluation is fast, but complex regex patterns can add latency. Test with realistic traffic.
|
||||
|
||||
### Configuration too large
|
||||
HAProxy has limits on configuration size. Consider splitting large ACL lists into multiple files.
|
||||
- **ACL never matches** — run `haproxy -c -f haproxy.cfg` to validate the syntax. Use `-d` for debug output to watch ACL evaluation in real time.
|
||||
- **Performance impact** — complex regex over `path,url_dec` adds per-request cost. Benchmark with realistic traffic before enabling globally.
|
||||
- **Configuration too large** — the converter keeps `waf.acl` flat and grep-friendly. Split it across multiple files if you need to apply different rule subsets to different frontends.
|
||||
|
||||
@@ -3,10 +3,10 @@ layout: home
|
||||
|
||||
hero:
|
||||
name: Patterns
|
||||
text: OWASP WAF Rules for Web Servers
|
||||
tagline: Automated OWASP CRS patterns and Bad Bot detection for Nginx, Apache, Traefik, and HAProxy
|
||||
text: Production-grade WAF rules, on autopilot.
|
||||
tagline: Automated OWASP Core Rule Set and bad-bot patterns, converted into native configurations for Nginx, Apache, Traefik, and HAProxy — refreshed every day.
|
||||
image:
|
||||
src: /shield.svg
|
||||
src: /hero-shield.svg
|
||||
alt: Patterns
|
||||
actions:
|
||||
- theme: brand
|
||||
@@ -17,47 +17,51 @@ hero:
|
||||
link: https://github.com/fabriziosalmi/patterns
|
||||
|
||||
features:
|
||||
- icon: 🛡️
|
||||
- icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 4.5 5.8v6.1c0 4.7 3.3 9.1 7.5 10.4 4.2-1.3 7.5-5.7 7.5-10.4V5.8L12 3Z"/><path d="m8.5 12 2.5 2.5L15.5 9.5"/></svg>'
|
||||
title: OWASP CRS Protection
|
||||
details: Leverages OWASP Core Rule Set for web application firewall defense against SQLi, XSS, RCE, and LFI attacks.
|
||||
- icon: 🤖
|
||||
details: Defends against SQL injection, XSS, RCE, LFI, and RFI by deriving rules from the OWASP Core Rule Set — the same engine that powers ModSecurity worldwide.
|
||||
- icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="7.5" width="16" height="11" rx="3"/><path d="M12 4v3.5"/><circle cx="12" cy="3.5" r="0.9" fill="currentColor" stroke="none"/><circle cx="9" cy="13" r="1.1" fill="currentColor" stroke="none"/><circle cx="15" cy="13" r="1.1" fill="currentColor" stroke="none"/><path d="M2 13.5v2M22 13.5v2"/></svg>'
|
||||
title: Bad Bot Blocking
|
||||
details: Blocks known malicious bots and scrapers using regularly updated public bot lists.
|
||||
- icon: ⚙️
|
||||
title: Multi-Server Support
|
||||
details: Generates WAF configs for Nginx, Apache, Traefik, and HAProxy with consistent protection across platforms.
|
||||
- icon: 🔄
|
||||
title: Daily Updates
|
||||
details: GitHub Actions automatically fetch new OWASP rules daily and push updated configurations.
|
||||
- icon: 📦
|
||||
title: Pre-Generated Configs
|
||||
details: Download ready-to-use WAF configurations from GitHub Releases without building from source.
|
||||
- icon: 🧩
|
||||
title: Extensible Design
|
||||
details: Modular architecture makes it easy to extend support to other web servers or load balancers.
|
||||
details: Curated User-Agent lists from public sources block scrapers, AI crawlers, vulnerability scanners, and SEO spam — with configurable allow-lists for legitimate bots.
|
||||
- icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="4" width="17" height="6" rx="1.5"/><rect x="3.5" y="14" width="17" height="6" rx="1.5"/><path d="M7 7h.01M7 17h.01"/><path d="M11 7h6M11 17h6"/></svg>'
|
||||
title: Native Multi-Server Output
|
||||
details: One source rule set, four idiomatic outputs — Nginx <code>map</code>+<code>if</code>, Apache <code>SecRule</code>, Traefik middleware TOML, and HAProxy ACL files.
|
||||
- icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 12a8.5 8.5 0 0 1 14.5-6L20 8"/><path d="M20 3v5h-5"/><path d="M20.5 12a8.5 8.5 0 0 1-14.5 6L4 16"/><path d="M4 21v-5h5"/></svg>'
|
||||
title: Daily Automated Updates
|
||||
details: A GitHub Actions workflow re-fetches the latest CRS release, rebuilds every backend, and publishes a fresh release archive — without manual intervention.
|
||||
- icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z"/><path d="m3.3 8 8.7 5 8.7-5"/><path d="M12 13v8"/><path d="m7.5 5.5 9 5"/></svg>'
|
||||
title: Pre-Built Releases
|
||||
details: Drop-in archives are published on every run. Skip the toolchain — download <code>nginx_waf.zip</code>, <code>apache_waf.zip</code>, <code>traefik_waf.zip</code>, or <code>haproxy_waf.zip</code>.
|
||||
- icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4.5a2 2 0 1 0-4 0V6H6a1.5 1.5 0 0 0-1.5 1.5V11h1.5a2 2 0 1 1 0 4H4.5v3.5A1.5 1.5 0 0 0 6 20h3.5v-1.5a2 2 0 1 1 4 0V20H17a1.5 1.5 0 0 0 1.5-1.5V15H20a2 2 0 1 0 0-4h-1.5V7.5A1.5 1.5 0 0 0 17 6h-3V4.5Z"/></svg>'
|
||||
title: Composable & Extensible
|
||||
details: Each backend is a small Python converter that consumes a single JSON intermediate. Adding a new platform is a few hundred lines — not a fork.
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
<div class="home-section">
|
||||
|
||||
Download the latest configurations from [GitHub Releases](https://github.com/fabriziosalmi/patterns/releases) or build from source:
|
||||
## Quick start
|
||||
|
||||
Pull the latest release archive and include it in your existing server configuration — no toolchain required.
|
||||
|
||||
```bash
|
||||
# Pick the archive that matches your stack
|
||||
curl -LO https://github.com/fabriziosalmi/patterns/releases/latest/download/nginx_waf.zip
|
||||
unzip nginx_waf.zip -d /etc/nginx/waf_patterns
|
||||
```
|
||||
|
||||
Or build from source to customize before deploying:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/fabriziosalmi/patterns.git
|
||||
cd patterns
|
||||
pip install -r requirements.txt
|
||||
python owasp2json.py
|
||||
python json2nginx.py # or json2apache.py, json2traefik.py, json2haproxy.py
|
||||
python owasp2json.py # Fetch the latest OWASP CRS
|
||||
python json2nginx.py # Or json2apache.py / json2traefik.py / json2haproxy.py
|
||||
python badbots.py # Generate bad-bot blocklists
|
||||
```
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
| Platform | Config Format | Documentation |
|
||||
|----------|---------------|---------------|
|
||||
| **Nginx** | `.conf` files | [Read more →](/nginx) |
|
||||
| **Apache** | ModSecurity rules | [Read more →](/apache) |
|
||||
| **Traefik** | Middleware TOML | [Read more →](/traefik) |
|
||||
| **HAProxy** | ACL files | [Read more →](/haproxy) |
|
||||
|
||||
::: tip Using Caddy?
|
||||
Check out the [caddy-waf](https://github.com/fabriziosalmi/caddy-waf) project for Caddy-specific WAF support.
|
||||
See the dedicated [caddy-waf](https://github.com/fabriziosalmi/caddy-waf) project for Caddy-specific WAF support.
|
||||
:::
|
||||
|
||||
</div>
|
||||
|
||||
132
docs/nginx.md
132
docs/nginx.md
@@ -1,131 +1,123 @@
|
||||
# Nginx Integration
|
||||
|
||||
This guide explains how to integrate the WAF patterns into your Nginx configuration.
|
||||
This guide explains how to wire the generated rules into an Nginx configuration.
|
||||
|
||||
## Quick Start
|
||||
## Quick start
|
||||
|
||||
1. Download `nginx_waf.zip` from [Releases](https://github.com/fabriziosalmi/patterns/releases)
|
||||
2. Extract to your Nginx configuration directory
|
||||
3. Include the configuration files as shown below
|
||||
1. Download `nginx_waf.zip` from the [latest release](https://github.com/fabriziosalmi/patterns/releases/latest) and extract it (e.g. into `/etc/nginx/waf_patterns/nginx/`).
|
||||
2. Include `waf_maps.conf` from the `http` block.
|
||||
3. Include `waf_rules.conf` from each `server` (or `location`) you want to protect.
|
||||
4. Reload Nginx.
|
||||
|
||||
## Configuration Files
|
||||
## Files in the archive
|
||||
|
||||
The Nginx WAF package includes:
|
||||
|
||||
| File | Purpose | Include Location |
|
||||
| File | Purpose | Where to include |
|
||||
|------|---------|------------------|
|
||||
| `waf_maps.conf` | Map directives for pattern matching | `http` block |
|
||||
| `waf_rules.conf` | If statements for blocking | `server` block |
|
||||
| `bots.conf` | Bad bot detection maps | `http` block |
|
||||
| `waf_maps.conf` | Defines `map` variables for every attack category | `http` block |
|
||||
| `waf_rules.conf` | `if (...) { return 403; }` rules that consume those maps | `server` or `location` block |
|
||||
| `bots.conf` | `map $http_user_agent $bad_bot` for User-Agent filtering | `http` block |
|
||||
| `sqli.conf`, `xss.conf`, `rce.conf`, `lfi.conf`, … | Per-category files for inspection only | **Do not include directly** |
|
||||
|
||||
## Integration
|
||||
::: warning Use only the two main files
|
||||
The per-category `.conf` files (`attack.conf`, `xss.conf`, `sqli.conf`, …) are emitted for inspection. They contain both `map` and `if` directives, which Nginx does not allow in the same context. Always include `waf_maps.conf` + `waf_rules.conf` instead.
|
||||
:::
|
||||
|
||||
### Step 1: Include Maps in HTTP Block
|
||||
## Step 1 — Include the maps
|
||||
|
||||
The map directives **must** be included in the `http` context:
|
||||
The `map` directives must live in the `http` context:
|
||||
|
||||
```nginx
|
||||
http {
|
||||
# Include WAF maps (pattern definitions)
|
||||
include /path/to/waf_patterns/nginx/waf_maps.conf;
|
||||
|
||||
# Include bot detection maps
|
||||
include /path/to/waf_patterns/nginx/bots.conf;
|
||||
|
||||
# ... other http configurations ...
|
||||
include /etc/nginx/waf_patterns/nginx/waf_maps.conf;
|
||||
include /etc/nginx/waf_patterns/nginx/bots.conf;
|
||||
|
||||
# …rest of your http config
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Include Rules in Server Block
|
||||
## Step 2 — Include the rules
|
||||
|
||||
The blocking rules go inside your `server` or `location` block:
|
||||
Place the blocking rules inside any `server` block you want to protect:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
listen 443 ssl http2;
|
||||
server_name example.com;
|
||||
|
||||
# Include WAF rules
|
||||
include /path/to/waf_patterns/nginx/waf_rules.conf;
|
||||
|
||||
# ... other server configurations ...
|
||||
|
||||
include /etc/nginx/waf_patterns/nginx/waf_rules.conf;
|
||||
|
||||
if ($bad_bot) { return 403; }
|
||||
|
||||
# …your locations
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Reload Nginx
|
||||
|
||||
Test and reload the configuration:
|
||||
## Step 3 — Validate and reload
|
||||
|
||||
```bash
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
## How It Works
|
||||
## How it works
|
||||
|
||||
The WAF uses Nginx's `map` directive for efficient pattern matching:
|
||||
The converter rewrites every OWASP regex into a `map` lookup, which Nginx evaluates with O(1) overhead per request:
|
||||
|
||||
```nginx
|
||||
map $request_uri $waf_block_sqli {
|
||||
default 0;
|
||||
"~*union.*select" 1;
|
||||
"~*insert.*into" 1;
|
||||
"~*union[\s\S]+select" 1;
|
||||
"~*insert[\s\S]+into" 1;
|
||||
}
|
||||
|
||||
if ($waf_block_sqli) {
|
||||
return 403;
|
||||
}
|
||||
# …elsewhere, in a server block:
|
||||
if ($waf_block_sqli) { return 403; }
|
||||
```
|
||||
|
||||
The full set of map variables is `$waf_block_<category>` — one per attack family the OWASP CRS defines.
|
||||
|
||||
## Customization
|
||||
|
||||
### Enable Logging
|
||||
### Log blocked requests
|
||||
|
||||
To log blocked requests, edit `waf_rules.conf` and uncomment the logging lines:
|
||||
Add a dedicated access log next to the deny:
|
||||
|
||||
```nginx
|
||||
if ($waf_block_sqli) {
|
||||
return 403;
|
||||
access_log /var/log/nginx/waf_blocked.log;
|
||||
log_format waf_block '$remote_addr - [$time_local] "$request" '
|
||||
'blocked=$waf_block_sqli ua="$http_user_agent"';
|
||||
|
||||
server {
|
||||
access_log /var/log/nginx/waf_blocked.log waf_block if=$waf_block_sqli;
|
||||
include /etc/nginx/waf_patterns/nginx/waf_rules.conf;
|
||||
}
|
||||
```
|
||||
|
||||
### Whitelist Specific Paths
|
||||
### Whitelist a path
|
||||
|
||||
Add exceptions before the WAF rules:
|
||||
Skip the WAF inside specific routes by branching before the include:
|
||||
|
||||
```nginx
|
||||
location /api/webhook {
|
||||
# Skip WAF for this path
|
||||
# ... your configuration ...
|
||||
location = /api/webhook {
|
||||
proxy_pass http://upstream;
|
||||
# waf_rules.conf intentionally not included here
|
||||
}
|
||||
|
||||
# WAF rules for other paths
|
||||
include /path/to/waf_patterns/nginx/waf_rules.conf;
|
||||
location / {
|
||||
include /etc/nginx/waf_patterns/nginx/waf_rules.conf;
|
||||
proxy_pass http://upstream;
|
||||
}
|
||||
```
|
||||
|
||||
::: warning Important
|
||||
Individual category files like `attack.conf` or `xss.conf` should **not** be included directly. They contain both `map` and `if` directives which cannot be used in the same context. Always use `waf_maps.conf` + `waf_rules.conf`.
|
||||
:::
|
||||
|
||||
## Testing
|
||||
|
||||
Test your WAF configuration with common attack patterns:
|
||||
Probe the deployment with known-bad payloads — both should return `403`:
|
||||
|
||||
```bash
|
||||
# Should be blocked (SQL injection)
|
||||
curl -I "http://example.com/?id=1' OR '1'='1"
|
||||
|
||||
# Should be blocked (XSS)
|
||||
curl -I "http://example.com/?q=<script>alert(1)</script>"
|
||||
curl -I "https://example.com/?id=1' OR '1'='1"
|
||||
curl -I "https://example.com/?q=<script>alert(1)</script>"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Configuration errors
|
||||
Always run `nginx -t` before reloading to catch syntax errors.
|
||||
|
||||
### False positives
|
||||
If legitimate requests are being blocked, check `/var/log/nginx/error.log` and consider adding path-specific exceptions.
|
||||
|
||||
### Performance
|
||||
The map-based approach is highly efficient. For high-traffic sites, consider enabling caching for the map variables.
|
||||
- **`nginx: configuration file test failed`** — you likely included a per-category file. Switch to `waf_maps.conf` + `waf_rules.conf`.
|
||||
- **False positives** — check `/var/log/nginx/error.log`, identify the matching `$waf_block_*` variable, then add a `location`-scoped exemption.
|
||||
- **High traffic** — the `map`-based design is already the fastest option Nginx offers; no further tuning is normally needed.
|
||||
|
||||
10
docs/public/favicon.svg
Normal file
10
docs/public/favicon.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="f" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#0a84ff"/>
|
||||
<stop offset="1" stop-color="#0040dd"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M16 2.5 4.5 6.8v8.4c0 6.5 4.6 12.5 11.5 14.3 6.9-1.8 11.5-7.8 11.5-14.3V6.8L16 2.5Z" fill="url(#f)"/>
|
||||
<path d="M11 16.4l3.4 3.4 6.6-6.8" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 534 B |
21
docs/public/hero-shield.svg
Normal file
21
docs/public/hero-shield.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="shieldGrad" x1="40" y1="20" x2="280" y2="300" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#5ac8fa"/>
|
||||
<stop offset="0.5" stop-color="#0a84ff"/>
|
||||
<stop offset="1" stop-color="#0040dd"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="shieldGlow" x1="160" y1="40" x2="160" y2="280" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity="0.18"/>
|
||||
<stop offset="1" stop-color="#fff" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="12" stdDeviation="18" flood-color="#0a84ff" flood-opacity="0.25"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<g filter="url(#shadow)">
|
||||
<path d="M160 24 56 60v82c0 64.5 45 124 104 142 59-18 104-77.5 104-142V60L160 24Z" fill="url(#shieldGrad)"/>
|
||||
<path d="M160 24 56 60v82c0 64.5 45 124 104 142 59-18 104-77.5 104-142V60L160 24Z" fill="url(#shieldGlow)"/>
|
||||
</g>
|
||||
<path d="M108 162 145 199 218 124" stroke="#fff" stroke-width="18" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
10
docs/public/logo.svg
Normal file
10
docs/public/logo.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#0a84ff"/>
|
||||
<stop offset="1" stop-color="#0040dd"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M16 2.5 4.5 6.8v8.4c0 6.5 4.6 12.5 11.5 14.3 6.9-1.8 11.5-7.8 11.5-14.3V6.8L16 2.5Z" fill="url(#g)"/>
|
||||
<path d="M11 16.4l3.4 3.4 6.6-6.8" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 534 B |
102
docs/traefik.md
102
docs/traefik.md
@@ -1,27 +1,21 @@
|
||||
# Traefik Integration
|
||||
|
||||
This guide explains how to integrate the WAF patterns with Traefik using middleware plugins.
|
||||
This guide explains how to consume the generated WAF middleware in **Traefik v2 / v3**.
|
||||
|
||||
## Quick Start
|
||||
## Quick start
|
||||
|
||||
1. Download `traefik_waf.zip` from [Releases](https://github.com/fabriziosalmi/patterns/releases)
|
||||
2. Extract the files
|
||||
3. Configure the middleware in your Traefik configuration
|
||||
1. Download `traefik_waf.zip` from the [latest release](https://github.com/fabriziosalmi/patterns/releases/latest).
|
||||
2. Drop the TOML files into your dynamic configuration directory.
|
||||
3. Reference the middleware from each router that should be protected.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
The Traefik WAF package includes:
|
||||
## Files in the archive
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `middleware.toml` | WAF middleware configuration |
|
||||
| `bots.toml` | Bad bot detection rules |
|
||||
| `middleware.toml` | WAF middleware definition (regex patterns per category) |
|
||||
| `bots.toml` | Bad-bot User-Agent middleware |
|
||||
|
||||
## Integration with File Provider
|
||||
|
||||
### Step 1: Enable File Provider
|
||||
|
||||
In your `traefik.toml` or `traefik.yml`:
|
||||
## Step 1 — Enable the file provider
|
||||
|
||||
::: code-group
|
||||
|
||||
@@ -41,39 +35,31 @@ providers:
|
||||
|
||||
:::
|
||||
|
||||
### Step 2: Copy Middleware Files
|
||||
|
||||
Copy the WAF configuration files to your dynamic configuration directory:
|
||||
## Step 2 — Drop the TOML files in
|
||||
|
||||
```bash
|
||||
cp waf_patterns/traefik/*.toml /etc/traefik/dynamic/
|
||||
sudo cp waf_patterns/traefik/*.toml /etc/traefik/dynamic/
|
||||
```
|
||||
|
||||
### Step 3: Apply Middleware to Routes
|
||||
Traefik picks them up automatically because `watch = true`.
|
||||
|
||||
Reference the middleware in your router configuration:
|
||||
## Step 3 — Reference the middleware
|
||||
|
||||
::: code-group
|
||||
|
||||
```toml [dynamic/routes.toml]
|
||||
[http.routers.my-router]
|
||||
[http.routers.app]
|
||||
rule = "Host(`example.com`)"
|
||||
service = "my-service"
|
||||
service = "app"
|
||||
middlewares = ["waf-protection", "bot-blocker"]
|
||||
|
||||
[http.middlewares.waf-protection.plugin.waf]
|
||||
# WAF configuration loaded from middleware.toml
|
||||
|
||||
[http.middlewares.bot-blocker.plugin.botblocker]
|
||||
# Bot blocking loaded from bots.toml
|
||||
```
|
||||
|
||||
```yaml [dynamic/routes.yml]
|
||||
http:
|
||||
routers:
|
||||
my-router:
|
||||
app:
|
||||
rule: "Host(`example.com`)"
|
||||
service: my-service
|
||||
service: app
|
||||
middlewares:
|
||||
- waf-protection
|
||||
- bot-blocker
|
||||
@@ -81,35 +67,27 @@ http:
|
||||
|
||||
:::
|
||||
|
||||
## Integration with Docker Labels
|
||||
The middleware names (`waf-protection`, `bot-blocker`) are the keys defined inside `middleware.toml` and `bots.toml`.
|
||||
|
||||
For Docker-based deployments:
|
||||
## Docker labels
|
||||
|
||||
For Docker / Compose deployments, attach the middleware via labels:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
my-app:
|
||||
app:
|
||||
image: my-app:latest
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.my-app.rule=Host(`example.com`)"
|
||||
- "traefik.http.routers.my-app.middlewares=waf@file"
|
||||
- "traefik.http.routers.app.rule=Host(`example.com`)"
|
||||
- "traefik.http.routers.app.middlewares=waf-protection@file,bot-blocker@file"
|
||||
```
|
||||
|
||||
## Middleware Configuration
|
||||
The `@file` suffix tells Traefik to resolve the middleware from the file provider.
|
||||
|
||||
The `middleware.toml` contains regex-based blocking rules:
|
||||
## Plugin compatibility
|
||||
|
||||
```toml
|
||||
[http.middlewares.waf.plugin.rewriteHeaders]
|
||||
# SQL Injection patterns
|
||||
[[http.middlewares.waf.plugin.rewriteHeaders.replacements]]
|
||||
regex = "(?i)union.*select"
|
||||
replacement = "BLOCKED"
|
||||
```
|
||||
|
||||
## Using with Traefik Plugins
|
||||
|
||||
For enhanced WAF capabilities, consider using community plugins:
|
||||
`middleware.toml` is generated against Traefik's built-in middleware primitives. If you prefer a dedicated WAF plugin (e.g. one of the community plugins on [Traefik Plugins](https://plugins.traefik.io/)), you can declare it side-by-side and chain both:
|
||||
|
||||
```yaml
|
||||
experimental:
|
||||
@@ -121,25 +99,24 @@ experimental:
|
||||
|
||||
## Customization
|
||||
|
||||
### Add Custom Patterns
|
||||
### Add custom patterns
|
||||
|
||||
Edit `middleware.toml` to add your own patterns:
|
||||
Edit `middleware.toml` to extend the regex set:
|
||||
|
||||
```toml
|
||||
[[http.middlewares.waf.plugin.rewriteHeaders.replacements]]
|
||||
[[http.middlewares.waf-protection.plugin.rewriteHeaders.replacements]]
|
||||
regex = "your-custom-pattern"
|
||||
replacement = "BLOCKED"
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
Enable access logs to monitor blocked requests:
|
||||
Enable structured access logs to track middleware decisions:
|
||||
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/var/log/traefik/access.log"
|
||||
format = "json"
|
||||
|
||||
[accessLog.fields]
|
||||
[accessLog.fields.headers]
|
||||
defaultMode = "keep"
|
||||
@@ -148,21 +125,12 @@ Enable access logs to monitor blocked requests:
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Test WAF detection
|
||||
curl -H "Host: example.com" \
|
||||
"http://localhost/?id=1' OR '1'='1"
|
||||
|
||||
# Check Traefik logs
|
||||
curl -H "Host: example.com" "http://localhost/?id=1' OR '1'='1"
|
||||
docker logs traefik 2>&1 | grep -i blocked
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Middleware not loading
|
||||
Check that the file provider is correctly configured and watching the right directory.
|
||||
|
||||
### Routes not applying middleware
|
||||
Ensure the middleware name matches exactly between router and middleware definition.
|
||||
|
||||
### Performance considerations
|
||||
Traefik's regex-based middleware can impact performance at high traffic. Monitor latency after enabling WAF rules.
|
||||
- **Middleware never loads** — check that the file provider directory matches and that `watch = true`. `traefik logs -f` shows hot-reload events.
|
||||
- **Router does not apply the middleware** — the middleware name must match exactly (case-sensitive) between router declaration and middleware definition.
|
||||
- **Latency** — regex middleware adds per-request overhead. Profile with `traefik` access logs and consider scoping the middleware to specific routers rather than applying globally.
|
||||
|
||||
Reference in New Issue
Block a user