Traffic Policies
Overview
This feature allows you to describe rules and actions for processing traffic for incoming requests and more flexibly configure interaction with your application.
Available only with a subscription on the Team plan.
To understand why policies are needed and how they work, let's look at an example of enabling basic authorization only for the /admin prefix:
---
rules:
- name: Authorization for admin panel
expressions:
- req.URL.Path contains "/admin"
actions:
- name: Enable basic auth
type: basic_auth
config:
userList:
admin: password
Here we have described 1 rule (rules), it has an expression (expressions), it contains a condition in which we check that the request is intended for the /admin prefix, if the condition is true, an action occurs (actions), in the example we apply basic authorization, set a list of username, password pairs. If the user passes authorization, then traffic is directed to your service. This rule will allow you to open access to the site under development for everyone, for example for customers or colleagues, but at the same time you can restrict access to private parts of the site. This is just one example, below you will find a complete description and more live examples.
To make it easier for you to write the necessary rule, we added a debugger right in the dashboard, where you can check if the rule works correctly, and several examples are also provided:
Rules
Rules consist of actions - actions (required), which must take effect and expressions - expressions (optional), filtering traffic to which they are applied. Rules are evaluated and executed sequentially in the order they are described, without interrupting their execution. That is, if a request falls under several rules at once, all actions will also be applied to this request. You can also specify a name name. The description of rules is stored in the .tuna.yml file in the current directory, in YAML format. The path to the file can be overridden using the --policy-file flag or the TUNA_POLICY_FILE environment variable.
For example, there are 2 rules. In the first one we enable basic authorization for all requests to the /admin prefix.
In the second one we check if there is an Accept-Language header and if it contains Russian language ru-RU. The client must first enter username and password, and then the ban by Accept-Language header will work if the header value contains ru-RU.
---
rules:
- expressions:
- req.URL.Path contains "/admin"
actions:
- type: basic_auth
config:
userList:
admin: admin
- expressions:
- any(req.Headers["Accept-Language"], {# matches "ru-RU"})
actions:
- type: deny
Expressions
Website with syntax description expr-lang
Expressions are based on expr-lang syntax. This is a rich language with many functions, comparison operations, inclusion, string operations and much more. Listing expressions in a list is perceived as a logical AND (and).
Example of blocking POST requests from bots:
---
rules:
- expressions:
- lower(req.UserAgent) matches "(chatgpt-user|gptbot)"
- req.Method in ["POST"]
actions:
- type: deny
Variables with request data
To work with an incoming request, there are several variables for getting information or metadata:
| Name | Description |
|---|---|
| req.Proto | Contains the HTTP protocol version that was used when sending the request (for example HTTP/1.1 or HTTP/2.0) |
| req.Host | Contains the URL host, including the port if specified (alias for req.URL.Host) |
| req.Method | Contains the request method GET / POST and others |
| req.URL | Full request URL (has its own methods, which are described in the table below) |
| req.UserAgent | Content of the User-Agent header |
| req.RemoteAddr | Client IP address from which the request came |
| req.ContentLength | Returns the length of the request body in bytes, if known, taken from the Content-Length header. |
| req.Headers | List of all headers |
Variables inside req.URL allow you to get individual parts of the URL:
| Name | Description |
|---|---|
| req.URL.Scheme | Contains the URL scheme, which defines the protocol (for example, http, https, ftp, etc.). |
| req.URL.Opaque | Contains opaque (encoded) URL data that cannot be directly parsed into parts such as scheme, host and path. |
| req.URL.User | Contains user information, containing username and possibly password in the URL. |
| req.URL.Host | Contains the URL host, including the port if specified (for example, example.com:8080). |
| req.URL.Path | Contains the URL path, which points to a specific resource (for example, /articles/2024/). |
| req.URL.RawPath | Contains the encoded version of the path (if it differs), may be useful for cases where the path contains special characters. |
| req.URL.RawQuery | Contains encoded query parameters without the ? character (for example, name=John&age=30). |
| req.URL.Fragment | Contains the URL fragment, which points to a specific part of the document, without the # character (for example, section2). |
| req.URL.RawFragment | Contains the encoded version of the fragment if it contains special characters (for example, spaces or Unicode characters). |
Request metadata:
| Name | Description |
|---|---|
| meta.Time | Exact time when the request arrived, +- current time |
| meta.RequestID | Unique request identifier |
Built-in functions
In addition to the fairly rich expr-lang syntax, built-in functions are also available for quick work with data.
| Name | Description | Example |
|---|---|---|
| IpInRange | Checks if an IP is in a subnet | IpInRange(req.RemoteAddr, "127.0.0.0/8") |
| IsIPV6 | Checks if a string is an IPv6 address | IsIPV6(req.RemoteAddr) |
| IsIPV4 | Checks if a string is an IPv4 address | IsIPV4(req.RemoteAddr) |
| IsIP | Checks if a string is an IP address | IsIP(req.RemoteAddr) |
| RandInt | Generate a random integer | RandInt(0, 10) |
| RandDouble | Generate a random decimal number in the range from 0 to 1 | RandDouble() |
Expression examples
Check that the request came via https:
- expressions:
- req.URL.Scheme == "https"
Request contains the /admin prefix:
- expressions:
- req.URL.Path contains "/admin"
Request is not from subnet 10.0.0.0/8:
- expressions:
- not IpInRange(req.RemoteAddr, "10.0.0.0/8")
Actions
Execution of actions depends on whether the condition in expressions is true, if the expression is missing, then the action is always executed.
The action must contain a type type and configuration config (not always required). You can also specify a name name.
Action types
Several types of actions are available, some modify the request, others are responsible for independent processing. You can specify multiple types, they will be processed in order.
add_headers
Add headers.
You can add headers both when passing them to the upstream application context: request, and when responding to the client context: response.
The keyword headers defines a key=value map, where the key is the header name, and the value is the header content.
---
rules:
- actions:
- type: add_headers
config:
context: request
headers:
foo: bar
qwe: rty
- actions:
- type: add_headers
config:
context: response
headers:
foo: bar
qwe: rty
remove_headers
Remove headers.
You can remove headers both when passing them to the upstream application context: request, and when responding to the client context: response.
The keyword headers defines a list of headers.
---
rules:
- actions:
- type: remove_headers
config:
context: request
headers:
- foo
- qwe
- actions:
- type: remove_headers
config:
context: response
headers: ["foo", "qwe"]
basic_auth
Enable basic authorization.
The keyword userList defines a key=value map, where the key is username, and the value is password.
---
rules:
- actions:
- type: basic_auth
config:
userList:
admin: admin
foma: kiniaev
ащьф: лштшфум
url_rewrite
Rewrite URI.
The keyword from defines the path from which the request to the upstream server should be rewritten, and to defines the path that will be passed to the upstream server. Regular expressions also work.
---
rules:
- actions:
- type: url_rewrite
config:
from: /foo
to: /bar
redirect
URL redirect.
The keyword to defines the Location to redirect to, you can also override statusCode (default 307).
The keyword headers defines a key=value map, where the key is the header name, and the value is the header content.
---
rules:
- actions:
- type: redirect
config:
to: /foobar
statusCode: 304
headers:
foo: bar
deny
Prohibit access to the upstream application.
You can override statusCode (default 403).
---
rules:
- actions:
- type: deny
config:
statusCode: 451
restrict_ips
Restrict access by subnets.
The keyword allow defines a list of subnets from which access is allowed.
The keyword deny defines a list of subnets from which access is prohibited.
---
rules:
- actions:
- type: restrict_ips
config:
allow:
- 127.0.0.1/32
deny:
- 10.0.0.10/32
rate_limit
Limit access by number of requests.
The keyword rate defines the time interval, and capacity the number of requests that can be made. When this threshold is exceeded, the client will start receiving 429 Too Many Requests.
---
rules:
- actions:
- type: rate_limit
config:
rate: "60s"
capacity: 2
custom_response
Custom response.
The required parameter statusCode defines the HTTP status. You can also pass a body, the content is set in the content parameter.
And headers, headers defines a key=value map, where the key is the header name, and the value is the header content.
---
rules:
- actions:
- type: custom_response
config:
statusCode: 200
content: |
User-agent: *
Disallow: /
headers:
content-type: "text/plain"
reverse_proxy
Reverse proxy to another service.
With this action, you can direct individual requests to a third-party service - this can be useful, for example, when developing microservices.
The required parameter url specifies an alternative point for the reverse proxy, the optional parameter onError defines the behavior when the request could not be passed to the third-party service:
halt(default) - request processing will be interrupted on error;continue- the request will be processed further according to downstream rules.
Standard behavior:
---
rules:
- actions:
- type: reverse_proxy
config:
url: "http://127.0.0.1:8000"
With the onError parameter set to continue:
---
rules:
- actions:
- type: reverse_proxy
config:
url: "http://127.0.0.1:8000"
onError: continue
- type: custom_response
config:
statusCode: 500
content: Unavailable
In this case, if the resource http://127.0.0.1:8000 is unavailable, HTTP 500 and the text Unavailable will be returned in response to the request.
Examples
Block requests by headers
Block requests if there is no X-Api-Version header, and respond with HTTP 400 if below 3:
---
rules:
- name: Block API v0
expressions:
- len(req.Headers["X-Api-Version"]) == 0
actions:
- name: Deny
type: deny
- name: 400 on API v1 and v2
expressions:
- req.Headers["X-Api-Version"][0] in ["1", "2"]
actions:
- name: Respond with 400 JSON
type: custom_response
config:
statusCode: 400
headers:
content-type: "application/json"
content: |
{"msg":"You are using an old API version, minimum version is 3"}
Reverse proxy to third-party services by prefix and headers
Redirect requests to /v1 to a third-party service on port 8000, and requests with User-Agent containing mobile to the service on port 3000.
At the same time, the command tuna http 8080 by default proxies all requests to 8080.
---
rules:
- expressions:
- hasPrefix(req.URL.Path, "/v1")
- lower(req.UserAgent) contains "mobile"
actions:
- type: reverse_proxy
config:
url: "http://127.0.0.1:3000"
- expressions:
- hasPrefix(req.URL.Path, "/v1")
actions:
- type: reverse_proxy
config:
url: "http://127.0.0.1:8000"
Custom response on weekends
---
rules:
- expressions:
- now().Weekday().String() in ["Saturday", "Sunday"]
actions:
- type: custom_response
config:
statusCode: 200
content: Take a rest!
Basic authentication
---
rules:
- expressions:
- req.URL.Path contains "/admin"
actions:
- type: basic_auth
config:
userList:
admin: password
Multiple actions without expressions
Restrict requests from 127.0.0.1 and 10.0.0.10, and for all others limit the number of requests per second to 2.
---
rules:
- actions:
- type: restrict_ips
config:
allow:
- 127.0.0.1/32
deny:
- 10.0.0.10/32
- actions:
- type: rate_limit
config:
rate: "1s"
capacity: 2
Rewrite with regular expression
---
rules:
- expressions:
- hasPrefix(req.URL.Path, "/blog")
actions:
- type: url_rewrite
config:
from: "/blog/([0-9]+)/([a-zA-Z]+)/"
to: /index.php?p=$1&title=$2"