diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8cceffb..89f3796 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -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 diff --git a/.github/workflows/test_apache_docker.yml b/.github/workflows/test_apache_docker.yml index c46c605..654d2e9 100644 --- a/.github/workflows/test_apache_docker.yml +++ b/.github/workflows/test_apache_docker.yml @@ -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 diff --git a/.github/workflows/test_nginx.yml b/.github/workflows/test_nginx.yml index 55eb905..673fed1 100644 --- a/.github/workflows/test_nginx.yml +++ b/.github/workflows/test_nginx.yml @@ -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 diff --git a/.github/workflows/update_patterns.yml b/.github/workflows/update_patterns.yml index 3f4f811..6758275 100644 --- a/.github/workflows/update_patterns.yml +++ b/.github/workflows/update_patterns.yml @@ -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 diff --git a/README.md b/README.md index c3bb320..54eab68 100644 --- a/README.md +++ b/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.** +
+ Patterns +

Patterns

+

Production-grade WAF rules, on autopilot.

+

+ Automated OWASP Core Rule Set and + bad-bot patterns, converted into native configurations for + Nginx, Apache, Traefik, and HAProxy + — refreshed every day. +

+

+ Latest Release + Update workflow + Nginx tests + License + Documentation +

+

+ Documentation + · + Get started + · + Latest release +

+
--- -## πŸ“Œ 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//`. + +## 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 + + SecRuleEngine On + Include /etc/apache2/waf_patterns/apache/*.conf + +``` + +### 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/) +
+ Built and maintained by Fabrizio Salmi. +
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index c910f4d..0a87182 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,37 +1,77 @@ import { defineConfig } from 'vitepress' +// Inline SVG icon factory for the features grid (renders inside .VPFeature .icon) +const svg = (paths: string) => + `` + +const icons = { + shield: svg(''), + bot: svg(''), + servers: svg(''), + refresh: svg(''), + package: svg(''), + puzzle: svg('') +} + 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 } diff --git a/docs/.vitepress/theme/components/HomeShowcase.vue b/docs/.vitepress/theme/components/HomeShowcase.vue new file mode 100644 index 0000000..fa0d8e4 --- /dev/null +++ b/docs/.vitepress/theme/components/HomeShowcase.vue @@ -0,0 +1,63 @@ + + + diff --git a/docs/.vitepress/theme/components/icons/IconApache.vue b/docs/.vitepress/theme/components/icons/IconApache.vue new file mode 100644 index 0000000..0deda2a --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconApache.vue @@ -0,0 +1,8 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconArrow.vue b/docs/.vitepress/theme/components/icons/IconArrow.vue new file mode 100644 index 0000000..93735ad --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconArrow.vue @@ -0,0 +1,6 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconBot.vue b/docs/.vitepress/theme/components/icons/IconBot.vue new file mode 100644 index 0000000..5c6e1b1 --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconBot.vue @@ -0,0 +1,10 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconGitHub.vue b/docs/.vitepress/theme/components/icons/IconGitHub.vue new file mode 100644 index 0000000..529d45e --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconGitHub.vue @@ -0,0 +1,5 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconHaproxy.vue b/docs/.vitepress/theme/components/icons/IconHaproxy.vue new file mode 100644 index 0000000..e6769ed --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconHaproxy.vue @@ -0,0 +1,9 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconNginx.vue b/docs/.vitepress/theme/components/icons/IconNginx.vue new file mode 100644 index 0000000..4f9c320 --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconNginx.vue @@ -0,0 +1,6 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconPackage.vue b/docs/.vitepress/theme/components/icons/IconPackage.vue new file mode 100644 index 0000000..a94e55e --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconPackage.vue @@ -0,0 +1,8 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconPuzzle.vue b/docs/.vitepress/theme/components/icons/IconPuzzle.vue new file mode 100644 index 0000000..bd8b796 --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconPuzzle.vue @@ -0,0 +1,5 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconRefresh.vue b/docs/.vitepress/theme/components/icons/IconRefresh.vue new file mode 100644 index 0000000..8d730b7 --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconRefresh.vue @@ -0,0 +1,8 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconServers.vue b/docs/.vitepress/theme/components/icons/IconServers.vue new file mode 100644 index 0000000..51964db --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconServers.vue @@ -0,0 +1,8 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconShield.vue b/docs/.vitepress/theme/components/icons/IconShield.vue new file mode 100644 index 0000000..e6966bf --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconShield.vue @@ -0,0 +1,6 @@ + diff --git a/docs/.vitepress/theme/components/icons/IconTraefik.vue b/docs/.vitepress/theme/components/icons/IconTraefik.vue new file mode 100644 index 0000000..ab0e5fc --- /dev/null +++ b/docs/.vitepress/theme/components/icons/IconTraefik.vue @@ -0,0 +1,5 @@ + diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 0000000..ef1009f --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -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 diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 0000000..3c8424e --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -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; + } +} diff --git a/docs/apache.md b/docs/apache.md index 54ef177..e9bf2c5 100644 --- a/docs/apache.md +++ b/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 SecRuleEngine On SecRequestBodyAccess On SecResponseBodyAccess Off - SecDebugLogLevel 0 + SecAuditEngine RelevantOnly + SecAuditLog /var/log/apache2/modsec_audit.log + SecAuditLogParts ABCDEFHZ ``` -### 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 - + 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 ``` -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`. diff --git a/docs/api.md b/docs/api.md index 544ec58..df327ca 100644 --- a/docs/api.md +++ b/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 | +| `.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 `.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:]*>)" } ] ``` -**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.py` based on existing converters -2. Add output directory in `waf_patterns//` -3. Update GitHub Actions workflow -4. Add documentation in `docs/` +1. Copy one of the existing `json2.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//`. +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+**. diff --git a/docs/badbots.md b/docs/badbots.md index 563c319..a15aafa 100644 --- a/docs/badbots.md +++ b/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//`. +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//`. ## 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. diff --git a/docs/getting-started.md b/docs/getting-started.md index b2324b4..5a0971a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -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. diff --git a/docs/haproxy.md b/docs/haproxy.md index 0ad55c1..c04fcb5 100644 --- a/docs/haproxy.md +++ b/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 " +curl -I "https://example.com/?id=1' OR '1'='1" +curl -I "https://example.com/?q=" ``` ## 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. diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg new file mode 100644 index 0000000..54f10f6 --- /dev/null +++ b/docs/public/favicon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/public/hero-shield.svg b/docs/public/hero-shield.svg new file mode 100644 index 0000000..4db1cd7 --- /dev/null +++ b/docs/public/hero-shield.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/logo.svg b/docs/public/logo.svg new file mode 100644 index 0000000..a5ee07e --- /dev/null +++ b/docs/public/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/traefik.md b/docs/traefik.md index e92b69d..02c2ff5 100644 --- a/docs/traefik.md +++ b/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.