Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 304 additions & 0 deletions content/guides/otoroshi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
---
type: docs
linkTitle: Otoroshi Advanced Features
title: Otoroshi Advanced Features Guide
description: Configure and use Otoroshi advanced features including WAF, Reverse Proxy, Rate Limiting, and Canary Deployments with detailed tutorials and best practices
keywords:
- otoroshi
- api gateway
- waf
- reverse proxy
- canary deployment
- rate limiting
- load balancing
---

Otoroshi is a modern API Gateway that provides powerful features to secure, manage, and optimize your services.

This guide covers the main features tested and validated for deployment on Clever Cloud.

---

## WAF (Web Application Firewall)

Otoroshi integrates **Coraza**, an open-source WAF compatible with ModSecurity rules and compliant with OWASP recommendations. It filters and blocks malicious requests before they reach your applications.

Check warning on line 24 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L24

[Google.WordList] Use 'open source' instead of 'open-source'.
Raw output
{"message": "[Google.WordList] Use 'open source' instead of 'open-source'.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 24, "column": 36}}}, "severity": "WARNING"}

Check notice on line 24 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L24

[Google.Acronyms] Spell out 'OWASP', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'OWASP', if it's unfamiliar to the audience.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 24, "column": 105}}}, "severity": "INFO"}

### Configuration

#### Step 1: Create a WAF Item

1. Navigate to **Categories → WAF → WAF Config**
2. Click **Add item**
3. Add your security directives

#### Example Directives

```
SecRuleEngine On
SecRule REQUEST_HEADERS:X-Forwarded-For "@rx <IPV4>" "id:1001,deny,status:403,log,msg:'IP bloquee'"
```

**Directive Explanations:**

- **`SecRuleEngine On`** : Enables the Coraza rules engine. Without this directive, no rules are evaluated

Check notice on line 43 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L43

[Google.Passive] In general, use active voice instead of passive voice ('are evaluated').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('are evaluated').", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 43, "column": 94}}}, "severity": "INFO"}
- **`SecRule REQUEST_HEADERS:X-Forwarded-For`** : Rule applied in phase 1 (header analysis) that reads the real client IP from the `X-Forwarded-For` header, required because requests go through Clever Cloud load balancers
- **`@rx <IPV4>`** : Regex operator that matches the header value against the provided IPv4 address
- **`id:1001`** : Mandatory unique identifier for the rule
- **`phase:1`** : Evaluation during the header analysis phase, before request body processing
- **`deny`** : Disruptive action that rejects the request
- **`status:403`** : HTTP status code returned to the blocked client
- **`log`** : Records the event in the logs
- **`msg:'IP blocked'`** : Message associated with the event in the logs

**Further Reading:**
- 📖 [Coraza SecLang Documentation](https://coraza.io/docs/seclang/) — Full reference for all directives, operators, variables and actions

Check failure on line 54 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L54

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 54, "column": 68}}}, "severity": "ERROR"}
- 🛡️ [OWASP Core Rule Set with Coraza](https://coraza.io/docs/tutorials/coreruleset/) — How to enable and configure the OWASP CRS for broader WAF protection

Check notice on line 55 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L55

[Google.Acronyms] Spell out 'OWASP', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'OWASP', if it's unfamiliar to the audience.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 55, "column": 7}}}, "severity": "INFO"}

Check failure on line 55 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L55

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 55, "column": 86}}}, "severity": "ERROR"}

Check notice on line 55 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L55

[Google.Acronyms] Spell out 'OWASP', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'OWASP', if it's unfamiliar to the audience.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 55, "column": 121}}}, "severity": "INFO"}

Check notice on line 55 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L55

[Google.Acronyms] Spell out 'CRS', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'CRS', if it's unfamiliar to the audience.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 55, "column": 127}}}, "severity": "INFO"}

{{< callout type="warning" >}}
**Important** : Each directive must be on a **separate line** in the directives field. Mixing multiple directives in a single entry will cause a WASM runtime crash and may lead to Out of Memory errors on your Otoroshi instance.

Check warning on line 58 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L58

[Google.Colons] ': E' should be in lowercase.
Raw output
{"message": "[Google.Colons] ': E' should be in lowercase.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 58, "column": 15}}}, "severity": "WARNING"}

Check warning on line 58 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L58

[Google.Will] Avoid using 'will'.
Raw output
{"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 58, "column": 133}}}, "severity": "WARNING"}

Check notice on line 58 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L58

[Google.Acronyms] Spell out 'WASM', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'WASM', if it's unfamiliar to the audience.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 58, "column": 146}}}, "severity": "INFO"}
{{< /callout >}}

{{< callout type="info" >}}
**Note** : Using `REQUEST_HEADERS:X-Forwarded-For` instead of `REMOTE_ADDR` is mandatory behind a load balancer. `REMOTE_ADDR` would contain the load balancer's IP address instead of the real client IP.

Check warning on line 62 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L62

[Google.Colons] ': U' should be in lowercase.
Raw output
{"message": "[Google.Colons] ': U' should be in lowercase.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 62, "column": 10}}}, "severity": "WARNING"}

Check failure on line 62 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L62

[Vale.Spelling] Did you really mean 'balancer's'?
Raw output
{"message": "[Vale.Spelling] Did you really mean 'balancer's'?", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 62, "column": 151}}}, "severity": "ERROR"}

Additionally, **IPv4 addresses are required**. IPv6 addresses are not reliably supported by the Coraza WASM module embedded in Otoroshi. Use `curl -4 ifconfig.io` to retrieve your IPv4 address.

Check notice on line 64 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L64

[Google.Passive] In general, use active voice instead of passive voice ('are required').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('are required').", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 64, "column": 32}}}, "severity": "INFO"}

Check notice on line 64 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L64

[Google.Contractions] Use 'aren't' instead of 'are not'.
Raw output
{"message": "[Google.Contractions] Use 'aren't' instead of 'are not'.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 64, "column": 63}}}, "severity": "INFO"}

Check notice on line 64 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L64

[Google.Acronyms] Spell out 'WASM', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'WASM', if it's unfamiliar to the audience.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 64, "column": 104}}}, "severity": "INFO"}
{{< /callout >}}

#### Step 2: Create and Configure a Route

1. Navigate to **Shortcuts → Routes → Create new route**
2. Configure the **Frontend** (public entry point):
- Public URL used by your clients (set on Otoroshi addon)

Check failure on line 71 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L71

[Vale.Spelling] Did you really mean 'addon'?
Raw output
{"message": "[Vale.Spelling] Did you really mean 'addon'?", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 71, "column": 55}}}, "severity": "ERROR"}

Check failure on line 71 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L71

[Vale.Avoid] Avoid using 'addon'.
Raw output
{"message": "[Vale.Avoid] Avoid using 'addon'.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 71, "column": 55}}}, "severity": "ERROR"}
3. Configure the **Backend** (actual service):
- Internal URL of your application that will process requests (set on your application)

Check warning on line 73 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L73

[Google.Will] Avoid using 'will'.
Raw output
{"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 73, "column": 44}}}, "severity": "WARNING"}
4. Add the **WAF** in the "Plugins" section

{{< callout type="info" >}}
The frontend DNS must point to Otoroshi, not directly to your application. The backend URL is used by Otoroshi to proxy requests after applying WAF rules.

Check notice on line 77 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L77

[Google.Passive] In general, use active voice instead of passive voice ('is used').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is used').", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 77, "column": 92}}}, "severity": "INFO"}
{{< /callout >}}

---

## Reverse Proxy

### Overview

Otoroshi natively supports multiple protocols:

- **HTTP/HTTPS**: Standard web requests
- **TCP**: Raw TCP connections (databases, custom services)
- **gRPC**: Modern RPC protocol based on HTTP/2

Check notice on line 90 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L90

[Google.Acronyms] Spell out 'RPC', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'RPC', if it's unfamiliar to the audience.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 90, "column": 20}}}, "severity": "INFO"}

### Configuring a Simple Reverse Proxy

Otoroshi's versatile protocol support allows it to act as a reverse proxy for various types of services:

1. **Create a route** in Otoroshi
2. **Configure the Frontend**:
- Public hostname, port, and path (set on Otoroshi addon)

Check failure on line 98 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L98

[Vale.Spelling] Did you really mean 'addon'?
Raw output
{"message": "[Vale.Spelling] Did you really mean 'addon'?", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 98, "column": 55}}}, "severity": "ERROR"}

Check failure on line 98 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L98

[Vale.Avoid] Avoid using 'addon'.
Raw output
{"message": "[Vale.Avoid] Avoid using 'addon'.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 98, "column": 55}}}, "severity": "ERROR"}
3. **Configure the Backend**:
- IP + port or URL of your actual service (set on your application)

Otoroshi intercepts incoming requests, applies your rules (security, rate limiting, etc.), and forwards them to the backend.

**Use Cases:**
- Proxy HTTP/HTTPS requests to web applications
- Forward TCP connections to databases or custom services
- Route gRPC calls to microservices

### Frontend vs Backend

**Frontend**: The public entry point that clients use to access your service. This is the URL your users will call.

Check warning on line 111 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L111

[Google.Colons] ': T' should be in lowercase.
Raw output
{"message": "[Google.Colons] ': T' should be in lowercase.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 111, "column": 13}}}, "severity": "WARNING"}

Check warning on line 111 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L111

[Google.Will] Avoid using 'will'.
Raw output
{"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 111, "column": 106}}}, "severity": "WARNING"}

**Backend**: The actual internal address of your application/service that processes requests. Otoroshi proxies requests to this address after filtering through WAF and other plugins.

Check warning on line 113 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L113

[Google.Colons] ': T' should be in lowercase.
Raw output
{"message": "[Google.Colons] ': T' should be in lowercase.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 113, "column": 12}}}, "severity": "WARNING"}

{{< callout type="info" >}}
Your DNS must point the frontend domain to Otoroshi, not to your backend application. The backend URL is used internally by Otoroshi to forward filtered traffic.

Check notice on line 116 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L116

[Google.Passive] In general, use active voice instead of passive voice ('is used').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is used').", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 116, "column": 103}}}, "severity": "INFO"}
{{< /callout >}}

---

## Rate Limiting & Custom Quotas

### Overview

Rate limiting protects your services from abuse by controlling the number of requests allowed per time period. Otoroshi provides flexible quota management based on various criteria.

### Implementation

To limit the number of requests per IP or per user:

1. Navigate to your route's configuration
2. Add the **"Custom quotas"** plugin in the "Plugins" section
3. Configure the following parameters:
- **Quota**: Number of authorized requests
- **Period**: Time window (per second, minute, hour, day)
- **Criteria**: IP address, API key, user, etc.

**Common Use Cases:**
- Protect your APIs against abuse and brute force attacks
- Manage differentiated quotas for commercial tiers
- Mitigate DDoS attempts
- Implement fair usage policies (free vs paid tiers)

### Configuration Example

```json
{
"throttling_quota": "1000",
"throttling_period": "3600",
"throttling_by": "ip"
}
```

This configuration allows **1000 requests per hour per IP address**.

### Additional Options

You can customize rate limiting based on:
- **IP address**: Limit per visitor
- **API key**: Different quotas per client
- **User**: Authenticated user limits
- **Custom header**: Advanced filtering

---

## Canary Deployments

Canary mode allows you to test a new version of your application on a percentage of traffic before a complete deployment. This technique reduces risk by gradually exposing new code to production users.

### Method 1: Route-Level Configuration with Plugin (Recommended)

This method provides more granular control and is easier to configure for single routes.

#### Configuration Steps

1. **Create a new route**
2. **Select the "Canary Mode" plugin**
3. **Configure the following parameters**:
- **Frontend**: Public URL (set on Otoroshi addon)

Check failure on line 179 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L179

[Vale.Spelling] Did you really mean 'addon'?
Raw output
{"message": "[Vale.Spelling] Did you really mean 'addon'?", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 179, "column": 49}}}, "severity": "ERROR"}

Check failure on line 179 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L179

[Vale.Avoid] Avoid using 'addon'.
Raw output
{"message": "[Vale.Avoid] Avoid using 'addon'.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 179, "column": 49}}}, "severity": "ERROR"}
- **Canary mode**:
- **Traffic**: 0.2 for 20% redirection to canary (or 0.5 for 50%)
- **Targets**:
- Hostname: `app-canary.cleverapps.io`
- Port: `443`
- Weight: `1`
- **Backend**: Stable application URL (e.g., `app-stable.cleverapps.io`)

Check failure on line 186 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L186

[Google.Latin] Use 'for example' instead of 'e.g.'.
Raw output
{"message": "[Google.Latin] Use 'for example' instead of 'e.g.'.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 186, "column": 44}}}, "severity": "ERROR"}

#### Complete Test Script

```sh
#!/bin/bash

echo "=== Otoroshi Canary Mode Test ==="
echo "Date: $(date)"
echo ""

STABLE=0
CANARY=0
UNKNOWN=0

# Replace with your actual Otoroshi frontend URL
FRONTEND_URL="http://myapp.mydomain.com"

for i in {1..100}; do
RESPONSE=$(curl -L -s "$FRONTEND_URL")

if echo "$RESPONSE" | grep -q "STABLE"; then
((STABLE++))
elif echo "$RESPONSE" | grep -q "CANARY"; then
((CANARY++))
else
((UNKNOWN++))
fi

echo -n "."
done

echo ""
echo ""
echo "Results over 100 requests:"
echo " → Stable: $STABLE"
echo " → Canary: $CANARY"
echo " → Unknown: $UNKNOWN"
echo ""

PERCENT_CANARY=$((CANARY))
echo "Canary rate: ${PERCENT_CANARY}%"
```

#### Expected Results

- Traffic at **0.2** (20%): ~20% to CANARY, ~80% to STABLE
- Traffic at **0.5** (50%): ~50% to CANARY, ~50% to STABLE

{{< callout type="warning" >}}
Always monitor error rates and latency when testing canary deployments. Start with a low percentage (5-10%) and gradually increase if metrics remain healthy.
{{< /callout >}}

---

### Method 2: Service-Level Configuration

This method is useful for managing multiple routes with the same canary configuration.

#### Configuration Steps

1. **Create a new Service**:
```
Name: "My Service"
Description: "Service with Canary"
```

2. **Service Exposition Settings**:
```
Exposed domain: myapp.mydomain.com
Legacy domain: true
Strip path: true
```

3. **Service Targets**:
```
Load balancing: WeightBestResponseTime
Weight ratio: 0.2

Target 1: app-stable.cleverapps.io (STABLE)
Target 2: app-canary.cleverapps.io (CANARY)
```

4. **URL Patterns**:
```
Public patterns: "/.*"
```

#### Testing

```sh
# Replace with your actual frontend URL
out=$(for i in {1..200}; do curl -s https://myapp.mydomain.com | grep Version; done);
echo "CANARY: $(echo "$out" | grep -c CANARY)"
echo "STABLE: $(echo "$out" | grep -c STABLE)"
```

Expected Result: ~40 requests to CANARY, ~160 to STABLE (20/80 ratio)
{{< callout type=“info” >}}
Traffic Flow: Client → your-domain.com → Otoroshi → { stable / canary }

Check warning on line 285 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L285

[Google.Colons] ': C' should be in lowercase.
Raw output
{"message": "[Google.Colons] ': C' should be in lowercase.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 285, "column": 13}}}, "severity": "WARNING"}
{{< /callout >}}

### Load Balancing Strategies

When using canary deployments, you can choose from several load balancing algorithms:
- WeightBestResponseTime: Routes traffic based on response time and weights
- RoundRobin: Distributes requests evenly across targets
- Random: Randomly selects a target for each request
- IpAddressHash: Routes based on client IP (sticky sessions)

---
## Resources

- Official Otoroshi Documentation : https://maif.github.io/otoroshi/
- Clever Cloud Documentation : https://www.clever.cloud/developers/doc/
- OWASP ModSecurity Core Rule Set : https://owasp.org/www-project-modsecurity-core-rule-set/

Check notice on line 301 in content/guides/otoroshi.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/guides/otoroshi.md#L301

[Google.Acronyms] Spell out 'OWASP', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'OWASP', if it's unfamiliar to the audience.", "location": {"path": "content/guides/otoroshi.md", "range": {"start": {"line": 301, "column": 3}}}, "severity": "INFO"}
- Coraza WAF Documentation : https://coraza.io/
- API Gateway Patterns : https://microservices.io/patterns/apigateway.html

Loading