Skip to main content

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.

info

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:

policy_debug

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

note

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:

NameDescription
req.ProtoContains the HTTP protocol version that was used when sending the request (for example HTTP/1.1 or HTTP/2.0)
req.HostContains the URL host, including the port if specified (alias for req.URL.Host)
req.MethodContains the request method GET / POST and others
req.URLFull request URL (has its own methods, which are described in the table below)
req.UserAgentContent of the User-Agent header
req.RemoteAddrClient IP address from which the request came
req.ContentLengthReturns the length of the request body in bytes, if known, taken from the Content-Length header.
req.HeadersList of all headers

Variables inside req.URL allow you to get individual parts of the URL:

NameDescription
req.URL.SchemeContains the URL scheme, which defines the protocol (for example, http, https, ftp, etc.).
req.URL.OpaqueContains opaque (encoded) URL data that cannot be directly parsed into parts such as scheme, host and path.
req.URL.UserContains user information, containing username and possibly password in the URL.
req.URL.HostContains the URL host, including the port if specified (for example, example.com:8080).
req.URL.PathContains the URL path, which points to a specific resource (for example, /articles/2024/).
req.URL.RawPathContains the encoded version of the path (if it differs), may be useful for cases where the path contains special characters.
req.URL.RawQueryContains encoded query parameters without the ? character (for example, name=John&age=30).
req.URL.FragmentContains the URL fragment, which points to a specific part of the document, without the # character (for example, section2).
req.URL.RawFragmentContains the encoded version of the fragment if it contains special characters (for example, spaces or Unicode characters).

Request metadata:

NameDescription
meta.TimeExact time when the request arrived, +- current time
meta.RequestIDUnique 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.

NameDescriptionExample
IpInRangeChecks if an IP is in a subnetIpInRange(req.RemoteAddr, "127.0.0.0/8")
IsIPV6Checks if a string is an IPv6 addressIsIPV6(req.RemoteAddr)
IsIPV4Checks if a string is an IPv4 addressIsIPV4(req.RemoteAddr)
IsIPChecks if a string is an IP addressIsIP(req.RemoteAddr)
RandIntGenerate a random integerRandInt(0, 10)
RandDoubleGenerate a random decimal number in the range from 0 to 1RandDouble()

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"