Skip to content

Commit 2181396

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

File tree

1 file changed

+230
-10
lines changed

1 file changed

+230
-10
lines changed

content/doc/administrate/cache.md

Lines changed: 230 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,232 @@ 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, invert the IP check rule:
96+
97+
```bash {filename="clevercloud/varnish.vcl"}
98+
99+
```
100+
101+
To be able to configure multiple IPs to block, CIDR, exceptions, with an environment variable, use ACL as in the following example:
102+
103+
- [Varnish multiple IP blocking with environment variable](https://github.com/CleverCloud/varnish-examples/blob/main/varnish-ip-blocking/varnish.vcl)
104+
105+
### Basic authentication
106+
107+
To ask for a login/password, you can use [Basic authentication](https://www.rfc-editor.org/rfc/rfc7617.txt) and the `Authorization` header:
108+
109+
```bash {filename="clevercloud/varnish.vcl"}
110+
sub vcl_recv {
111+
# Local health check
112+
if (client.ip == "127.0.0.1") {
113+
return (synth(200, "OK"));
114+
}
115+
116+
if (!req.http.Authorization) {
117+
return (synth(401, "Authentication Required"));
118+
}
119+
120+
if (req.http.Authorization !~ "^Basic ") {
121+
return (synth(401, "Basic Authentication Required"));
122+
}
123+
124+
set req.http.X-Auth-Credentials = regsub(req.http.Authorization, "^Basic ", "");
125+
126+
# Replace CREDENTIALS with your base64 encoded login:password
127+
# You can generate it with this command: echo -n "username:password" | base64
128+
if (req.http.X-Auth-Credentials != "CREDENTIALS" &&
129+
req.http.X-Auth-Credentials != "ANOTHER_CREDENTIALS") {
130+
return (synth(401, "Valid Basic Authentication Required"));
131+
}
132+
133+
# Remove the Authorization header to avoid sending it to the backend
134+
unset req.http.X-Auth-Credentials;
135+
unset req.http.Authorization;
136+
137+
# Use return (hash); to use the cache
138+
return (pass);
139+
}
140+
141+
sub vcl_synth {
142+
if (resp.status == 200) {
143+
set resp.http.Content-Type = "text/plain";
144+
synthetic("OK");
145+
return (deliver);
146+
}
147+
148+
if (resp.status == 401) {
149+
set resp.http.Content-Type = "text/html; charset=utf-8";
150+
set resp.http.WWW-Authenticate = "Basic realm='Restricted Area'";
151+
synthetic("<html><body><h1>Authentication Required</h1></body></html>");
152+
153+
return (deliver);
154+
}
155+
}
156+
```
52157
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.
158+
To avoid writing your credentials in the `varnish.vcl` file, you can use an environment variable to store them:
54159
55-
## Example files
160+
- [Varnish Basic authentication with environment variable](https://github.com/CleverCloud/varnish-examples/blob/main/varnish-ip-blocking/varnish.vcl)
56161
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.
162+
### Bearer token authentication
163+
164+
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:
165+
166+
```bash {filename="clevercloud/varnish.vcl"}
167+
sub vcl_recv {
168+
# Local health check
169+
if (client.ip == "127.0.0.1") {
170+
return (synth(200, "OK"));
171+
}
172+
173+
if (!req.http.Authorization) {
174+
return (synth(401, "Authentication Required"));
175+
}
176+
177+
if (req.http.Authorization !~ "^Bearer ") {
178+
return (synth(401, "Bearer token Required"));
179+
}
180+
181+
set req.http.X-Token = regsub(req.http.Authorization, "^Bearer ", "");
182+
183+
# Replace YOUR_BEARER_TOKEN and ANOTHER_BEARER_TOKEN with your actual tokens
184+
if (req.http.X-Token != "YOUR_BEARER_TOKEN" &&
185+
req.http.X-Token != "ANOTHER_BEARER_TOKEN") {
186+
return (synth(401, "Valid Bearer token Required"));
187+
}
188+
189+
# Remove the Authorization header to avoid sending it to the backend
190+
unset req.http.X-Token;
191+
unset req.http.Authorization;
192+
193+
# Use return (hash); to use the cache
194+
return (pass);
195+
}
196+
197+
sub vcl_synth {
198+
if (resp.status == 200) {
199+
set resp.http.Content-Type = "text/plain";
200+
synthetic("OK");
201+
return (deliver);
202+
}
203+
204+
if (resp.status == 401) {
205+
set resp.http.Content-Type = "text/html; charset=utf-8";
206+
synthetic("<html><body><h1>Authentication Required</h1></body></html>");
207+
208+
return (deliver);
209+
}
210+
}
211+
```
212+
213+
To avoid writing your credentials in the `varnish.vcl` file, you can use an environment variable to store them:
214+
215+
- [Varnish Bearer token with environment variable](https://github.com/CleverCloud/varnish-examples/blob/main/varnish-ip-blocking/varnish.vcl)
216+
217+
## Varnish to throttle your application's traffic
218+
219+
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.
220+
221+
```bash {filename="clevercloud/varnish.vcl"}
222+
import vsthrottle;
223+
224+
sub vcl_recv {
225+
226+
# Local health check
227+
if (client.ip == "127.0.0.1") {
228+
return (synth(200, "OK"));
229+
}
230+
231+
# For other requests, we check the Forwarded header to extract the client IP and refuse access if it's malformed or missing
232+
if (!req.http.Forwarded) {
233+
return (synth(403, "Access Denied"));
234+
}
235+
236+
# Expected format is proto=https;for=IP:PORT;by=IP
237+
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}$") {
238+
return (synth(403, "Access Denied"));
239+
}
240+
241+
# We get and check the client IP format
242+
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");
243+
if (req.http.X-Client-IP !~ "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$") {
244+
return (synth(403, "Access Denied"));
245+
}
246+
247+
# Limit to 10 requests per 1 minute
248+
# It's good values for tests, use higher values in production
249+
if (vsthrottle.is_denied(req.http.X-Client-IP, 10, 1m)) {
250+
return (synth(429, "Rate limit exceeded"));
251+
}
252+
253+
# Use return (hash); to use the cache
254+
return (pass);
255+
}
256+
257+
sub vcl_synth {
258+
if (resp.status == 403) {
259+
set resp.http.Content-Type = "text/plain";
260+
synthetic("Access Denied");
261+
return (deliver);
262+
}
263+
264+
if (resp.status == 429) {
265+
set resp.http.Retry-After = "60";
266+
set resp.http.Content-Type = "text/plain";
267+
synthetic("Rate limit exceeded. Please try again later.");
268+
return (deliver);
269+
}
270+
}
271+
272+
# Send informations about rate limit and remaining requests in response headers
273+
sub vcl_deliver {
274+
set resp.http.X-RateLimit-Limit = "10";
275+
set resp.http.X-RateLimit-Remaining = "" + vsthrottle.remaining(req.http.X-Client-IP, 10, 1m);
276+
277+
unset resp.http.X-Client-IP;
278+
}
279+
```
60280
61281
## Varnish with a monorepo
62282

0 commit comments

Comments
 (0)