Skip to content

Commit 2aac2f0

Browse files
committed
administrate(varnish): add auth/restrict examples
1 parent 2f88118 commit 2aac2f0

1 file changed

Lines changed: 251 additions & 10 deletions

File tree

content/doc/administrate/cache.md

Lines changed: 251 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ type: docs
1515

1616
## Overview
1717

18-
[Varnish](https://www.varnish-cache.org/) is a HTTP proxy-cache, which works as a reverse proxy between your application
19-
and the client. Following rules defined by the user, Varnish will cache the data of an application to reduce the load on its server. We use **Varnish 7.7.1 and varnish-modules 0.26.0**.
18+
[Varnish](https://www.varnish-cache.org/) is a HTTP proxy-cache, which works as a reverse proxy between your application and the client. Following rules defined by the user, Varnish will cache the data of an application to reduce the load on its server. We use **Varnish 7.7.1 and varnish-modules 0.26.0**.
2019

2120
## Limitations
2221

23-
Varnish is available on **FrakenPHP**, **Go**, **Node.js** and **PHP with Apache** applications. Support for other applications is in discussion.
22+
Varnish is available on **FrankenPHP**, **Go**, **Node.js** and **PHP with Apache** applications. Support for other applications is in discussion.
2423

2524
For more information about it, contact [Clever Cloud Support](https://console.clever-cloud.com/ticket-center-choice).
2625

@@ -34,7 +33,11 @@ The `vcl 4.1;` and backend section of the `varnish.vcl` configuration file are n
3433
If you have a PHP FTP application or if your `varnish.vcl` file is on an FS Bucket, make sure you redeploy the application for the changes to take effect.
3534
{{< /callout >}}
3635

37-
To know how to write your `varnish.vcl` file, have a look at the [Varnish 6 book](https://info.varnish-software.com/resources/varnish-6-by-example-book).
36+
To learn how to write your `varnish.vcl` file, read:
37+
- [Varnish documentation](https://varnish-cache.org/docs/)
38+
- [Varnish 6 book](https://info.varnish-software.com/resources/varnish-6-by-example-book)
39+
40+
If you already have a configuration for an older version of Varnish, read [the upgrading to 7.0 guide](https://varnish-cache.org/docs/7.0/whats-new/upgrading-7.0.html).
3841

3942
## Listen on the right port
4043

@@ -48,15 +51,253 @@ You can change the storage size specified in the varnish.params file with the `C
4851
CC_VARNISH_STORAGE_SIZE=2G
4952
```
5053

51-
## Varnish 6 migration
54+
## Varnish to cache your application's content
55+
56+
We provide some [examples of Varnish configuration files](https://GitHub.com/CleverCloud/varnish-examples) that you can use for your application.
57+
58+
## Varnish to restrict access to your application
59+
60+
As Varnish acts as a reverse proxy, you can also use it to chose whether requests should be forwarded to your application, based on the Authorization header or the IP address of the client for example.
61+
62+
>[!NOTE] Varnish on Clever Cloud and client IP
63+
>Clever Cloud's applications are behind a load balancer, which means that the `client.ip` variable in Varnish will always be the IP address of the load balancer. To get the real client IP address, you should use the `X-Forwarded-For` or `Forwarded` headers.
64+
65+
### Block IP addresses
66+
67+
```bash {filename="clevercloud/varnish.vcl"}
68+
sub vcl_recv {
69+
# Local health check
70+
if (client.ip == "127.0.0.1") {
71+
return (synth(200, "OK"));
72+
}
73+
74+
# We don't rely on client.ip which send the load balancer IP address
75+
# We check if the IP to block is included in the Forwarded header instead
76+
# Replace X.X.X.X and Y.Y.Y.Y with the IP addresses you want to block
77+
if (req.http.Forwarded ~ "X.X.X.X" ||
78+
req.http.Forwarded ~ "Y.Y.Y.Y" ) {
79+
return (synth(403, "Blocked"));
80+
}
81+
82+
# Use return (hash); to use the cache
83+
return (pass);
84+
}
85+
86+
sub vcl_synth {
87+
if (resp.status == 403) {
88+
set resp.http.Content-Type = "text/plain";
89+
synthetic("Access denied");
90+
return (deliver);
91+
}
92+
}
93+
```
94+
95+
If you need to only authorize some IP addresses, you can't just block if the IP is mentioned in the `Forwarded` header, as a user can add content to it. Instead, block all malformed requests and extract the IP address to check it:
96+
97+
```bash {filename="clevercloud/varnish.vcl"}
98+
# For other requests, we check the Forwarded header to extract the client IP and refuse access if it's malformed or missing
99+
if (!req.http.Forwarded) {
100+
return (synth(403, "Access Denied"));
101+
}
102+
103+
# Expected format is proto=https;for=IP:PORT;by=IP
104+
if (req.http.Forwarded !~ "^proto=https;for=[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]+;by=[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$") {
105+
return (synth(403, "Access Denied"));
106+
}
107+
108+
# We get and check the client IP format
109+
set req.http.X-Client-IP = regsub(req.http.Forwarded, "^proto=https;for=([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}):[0-9]+;by=.*$", "\1");
110+
if (req.http.X-Client-IP !~ "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$") {
111+
return (synth(403, "Access Denied"));
112+
}
113+
114+
if (req.http.X-Client-IP != "X.X.X.X" &&
115+
req.http.X-Client-IP != "Y.Y.Y.Y" ) {
116+
return (synth(403, "Blocked"));
117+
}
118+
119+
unset req.http.X-Client-IP;
120+
```
121+
122+
To be able to configure multiple IPs to authorize/block, CIDR, exceptions, with an environment variable, use ACL as in the following example:
123+
124+
- [Varnish multiple IP blocking with environment variable](https://github.com/CleverCloud/varnish-examples/blob/main/varnish-ip-blocking/varnish.vcl)
125+
126+
### Basic authentication
127+
128+
To ask for a login/password, you can use [Basic authentication](https://www.rfc-editor.org/rfc/rfc7617.txt) and the `Authorization` header:
129+
130+
```bash {filename="clevercloud/varnish.vcl"}
131+
sub vcl_recv {
132+
# Local health check
133+
if (client.ip == "127.0.0.1") {
134+
return (synth(200, "OK"));
135+
}
136+
137+
if (!req.http.Authorization) {
138+
return (synth(401, "Authentication Required"));
139+
}
140+
141+
if (req.http.Authorization !~ "^Basic ") {
142+
return (synth(401, "Basic Authentication Required"));
143+
}
144+
145+
set req.http.X-Auth-Credentials = regsub(req.http.Authorization, "^Basic ", "");
146+
147+
# Replace CREDENTIALS with your base64 encoded login:password
148+
# You can generate it with this command: echo -n "username:password" | base64
149+
if (req.http.X-Auth-Credentials != "CREDENTIALS" &&
150+
req.http.X-Auth-Credentials != "ANOTHER_CREDENTIALS") {
151+
return (synth(401, "Valid Basic Authentication Required"));
152+
}
153+
154+
# Remove the Authorization header to avoid sending it to the backend
155+
unset req.http.X-Auth-Credentials;
156+
unset req.http.Authorization;
157+
158+
# Use return (hash); to use the cache
159+
return (pass);
160+
}
161+
162+
sub vcl_synth {
163+
if (resp.status == 200) {
164+
set resp.http.Content-Type = "text/plain";
165+
synthetic("OK");
166+
return (deliver);
167+
}
168+
169+
if (resp.status == 401) {
170+
set resp.http.Content-Type = "text/html; charset=utf-8";
171+
set resp.http.WWW-Authenticate = "Basic realm='Restricted Area'";
172+
synthetic("<html><body><h1>Authentication Required</h1></body></html>");
173+
174+
return (deliver);
175+
}
176+
}
177+
```
178+
179+
To avoid writing your credentials in the `varnish.vcl` file, you can use an environment variable to store them:
180+
181+
- [Varnish Basic authentication with environment variable](https://github.com/CleverCloud/varnish-examples/blob/main/varnish-ip-blocking/varnish.vcl)
182+
183+
### Bearer token authentication
184+
185+
To protect access to an API, using a Bearer token is a common practice. You can use Varnish to check the `Authorization` header and validate the Bearer token before forwarding the request to your application:
186+
187+
```bash {filename="clevercloud/varnish.vcl"}
188+
sub vcl_recv {
189+
# Local health check
190+
if (client.ip == "127.0.0.1") {
191+
return (synth(200, "OK"));
192+
}
193+
194+
if (!req.http.Authorization) {
195+
return (synth(401, "Authentication Required"));
196+
}
52197
53-
If you already have a configuration for an older version of varnish, you can read this [guide](https://varnish-cache.org/docs/6.0/whats-new/upgrading-6.0.html) to upgrade to version 6.
198+
if (req.http.Authorization !~ "^Bearer ") {
199+
return (synth(401, "Bearer token Required"));
200+
}
201+
202+
set req.http.X-Token = regsub(req.http.Authorization, "^Bearer ", "");
203+
204+
# Replace YOUR_BEARER_TOKEN and ANOTHER_BEARER_TOKEN with your actual tokens
205+
if (req.http.X-Token != "YOUR_BEARER_TOKEN" &&
206+
req.http.X-Token != "ANOTHER_BEARER_TOKEN") {
207+
return (synth(401, "Valid Bearer token Required"));
208+
}
209+
210+
# Remove the Authorization header to avoid sending it to the backend
211+
unset req.http.X-Token;
212+
unset req.http.Authorization;
213+
214+
# Use return (hash); to use the cache
215+
return (pass);
216+
}
217+
218+
sub vcl_synth {
219+
if (resp.status == 200) {
220+
set resp.http.Content-Type = "text/plain";
221+
synthetic("OK");
222+
return (deliver);
223+
}
224+
225+
if (resp.status == 401) {
226+
set resp.http.Content-Type = "text/html; charset=utf-8";
227+
synthetic("<html><body><h1>Authentication Required</h1></body></html>");
228+
229+
return (deliver);
230+
}
231+
}
232+
```
54233
55-
## Example files
234+
To avoid writing your credentials in the `varnish.vcl` file, you can use an environment variable to store them:
56235
57-
We provide some [examples of Varnish configuration files](https://GitHub.com/CleverCloud/varnish-examples) that you can
58-
use for your application. Create a `/clevercloud` folder at the root of your application if it does not exist,
59-
rename the file to `varnish.vcl` and move it in the `/clevercloud` folder.
236+
- [Varnish Bearer token with environment variable](https://github.com/CleverCloud/varnish-examples/blob/main/varnish-ip-blocking/varnish.vcl)
237+
238+
## Varnish to throttle your application's traffic
239+
240+
You can use Varnish to limit the number of requests per second to your application, which is useful to prevent abuse or to protect your application from DDoS attacks. This can be combined with other Varnish rules and features.
241+
242+
```bash {filename="clevercloud/varnish.vcl"}
243+
import vsthrottle;
244+
245+
sub vcl_recv {
246+
247+
# Local health check
248+
if (client.ip == "127.0.0.1") {
249+
return (synth(200, "OK"));
250+
}
251+
252+
# For other requests, we check the Forwarded header to extract the client IP and refuse access if it's malformed or missing
253+
if (!req.http.Forwarded) {
254+
return (synth(403, "Access Denied"));
255+
}
256+
257+
# Expected format is proto=https;for=IP:PORT;by=IP
258+
if (req.http.Forwarded !~ "^proto=https;for=[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]+;by=[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$") {
259+
return (synth(403, "Access Denied"));
260+
}
261+
262+
# We get and check the client IP format
263+
set req.http.X-Client-IP = regsub(req.http.Forwarded, "^proto=https;for=([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}):[0-9]+;by=.*$", "\1");
264+
if (req.http.X-Client-IP !~ "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$") {
265+
return (synth(403, "Access Denied"));
266+
}
267+
268+
# Limit to 10 requests per 1 minute
269+
# It's good values for tests, use higher values in production
270+
if (vsthrottle.is_denied(req.http.X-Client-IP, 10, 1m)) {
271+
return (synth(429, "Rate limit exceeded"));
272+
}
273+
274+
# Use return (hash); to use the cache
275+
return (pass);
276+
}
277+
278+
sub vcl_synth {
279+
if (resp.status == 403) {
280+
set resp.http.Content-Type = "text/plain";
281+
synthetic("Access Denied");
282+
return (deliver);
283+
}
284+
285+
if (resp.status == 429) {
286+
set resp.http.Retry-After = "60";
287+
set resp.http.Content-Type = "text/plain";
288+
synthetic("Rate limit exceeded. Please try again later.");
289+
return (deliver);
290+
}
291+
}
292+
293+
# Send informations about rate limit and remaining requests in response headers
294+
sub vcl_deliver {
295+
set resp.http.X-RateLimit-Limit = "10";
296+
set resp.http.X-RateLimit-Remaining = "" + vsthrottle.remaining(req.http.X-Client-IP, 10, 1m);
297+
298+
unset resp.http.X-Client-IP;
299+
}
300+
```
60301
61302
## Varnish with a monorepo
62303

0 commit comments

Comments
 (0)