Commit 303e37d3 authored by Geoff Simmons's avatar Geoff Simmons

Add docs and examples for ACLs.

parent 04e83f7f
......@@ -264,3 +264,181 @@ spec:
See the [``examples/`` folder](/examples/authentication) for working
examples of authentication configurations.
### ``spec.acl``
The ``acl`` element is optional, and if present contains a non-empty
array of specifications of access control lists for whitelisting or
blacklisting requests by IP address.
For each element of ``acl``, the required fields are:
* ``name`` (string): unique among the names for acl specifications
* ``addrs``: non-empty array of IP specifications (detailed below)
Optional fields for ``acl`` are:
* ``type``: ``whitelist`` or ``blacklist``, default ``whitelist``
* ``fail-status`` (integer): HTTP status for a synthetic failure
response, default 403 (for "403 Forbidden")
* ``comparand``: specification of the IP value against which the ACL
is matched, as detailed below; default ``client.ip``
* ``conditions``: array of conditions under which the ACL match is
executed, as detailed below. By default, ``conditions`` is empty,
in which case the match is executed for every client request.
Each element of the ``addrs`` array may have these fields, of which
``addr`` is required:
* ``addr`` (string, required): an IPv4 address, IPv6 address, or host
name that is resolved by Varnish to an IP address at VCL load time
* ``mask-bits`` (integer, >= 0 and <= 128): bitmask as expressed by
[CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing),
so that an address range is specified; that is, the number of
leading 1-bits in a subnet mask. By default (when ``mask-bits``
is left out), there is no bitmask, and ``addr`` defines an exact
IP address.
* ``negate`` (boolean): when true, the ACL does not match an IP
if it matches the address or range specified by ``addr`` and
``mask-bits``; default false.
So the range 192.0.2.0/24 is expressed as:
```
- addr: 192.0.2.0
mask-bits: 24
```
``negate`` can be used to define IPs as "exceptions" if they fall in
ranges specified by other elements of the ACL:
```
# Match all IPs in 192.0.2.0/24, but not 192.0.2.23.
- addr: 192.0.2.0
mask-bits: 24
- addr: 192.0.2.23
negate: true
```
When ``type`` is ``whitelist``, the failure response is sent when an
IP does not match the ACL. For ``blacklist``, the failure response is
invoked for an IP that does match the ACL.
``comparand`` specifies the IP value against which the ACL is matched,
and can have one of these values:
* ``client.ip``: interpreted as in
[VCL](https://varnish-cache.org/docs/6.1/reference/vcl.html#local-server-remote-and-client)
* ``server.ip``: as in VCL
* ``remote.ip``: as in VCL
* ``local.ip``: as in VCL
* ``req.http.$HEADER``, where ``$HEADER`` is the name of a client
request header
* ``xff-first``: match the first comma-separated field in the
``X-Forwarded-For`` request header
* ``xff-2ndlast``: match the next-to-last comma-separated field in
``X-Forwarded-For``, *after* Varnish has appended the client IP
To briefly summarize the ``*.ip`` values:
* ``remote.ip`` is always the address of the peer connection (the
component that sent the request to Varnish)
* ``local.ip`` is the "Varnish side" of the connection (the IP at
which the listener received the request)
* If the [PROXY protocol](/docs/varnish-pod-template.md) is in use:
* ``client.ip`` and ``server.ip`` are the addresses sent in the
PROXY header.
* Otherwise:
* ``client.ip`` and ``server.ip`` are equal to ``remote.ip`` and
``local.ip``, respectively.
If ``req.http.$HEADER`` is specified for ``comparand``, the ACL is
matched against the IP in the value of the header. If the value of the
header is not an IP address, or if the header is not present in the
request, then the match fails. This can be used for a setup in which a
forwarding component sends a client IP in a header such as
``X-Real-IP``.
If ``xff-first`` is specified for ``comparand``, then the ACL is
matched against the IP in the first comma-separated field of the
``X-Forwarded-For`` request header. If the value of that field is not
an IP address, the match fails. Varnish always appends the client IP
to ``X-Forwarded-For``, and creates the header with that value if it
is not present in the request as received. So if there is no
``X-Forwarded-For`` when Varnish receives the request, then the first
field is the client IP (and ``xff-first`` is the same as matching
against ``client.ip``).
If ``xff-2ndlast`` is specified for ``comparand``, then the ACL is
matched against the next-to-last field in ``X-Forwarded-For`` *after*
Varnish appends the client IP. In other words, it is matched against
the last field in ``X-Forwarded-For`` as received by Varnish. If
Varnish receives a request without ``X-Forwarded-For``, then there is
no next-to-last field, and the match fails.
``X-Forwarded-For`` may appear more than once in a request (as if they
are separate headers). If either of ``xff-first`` or ``xff-2ndlast``
is specified as the comparand for any ACL in the VarnishConfig, the
values of ``X-Forwarded-For`` are collected into a single
comma-separated header, in the order in which they appeared in the
request.
Thus if Varnish receives a request with this value of ``X-Forwarded-For``:
```
X-Forwarded-For: 192.0.2.47, 203.0.113.11
```
... then ``xff-first`` specifies a match against 192.0.2.47, and
``xff-2ndlast`` specifies a match against 203.0.113.11.
If ``conditions`` are specified for an ACL, they define restrictions
for executing the match. Each element of ``conditions`` must specify
these three fields (all required):
* ``comparand`` (string): either ``req.url`` or ``req.http.$HEADER``,
where ``$HEADER`` is the name of a client request header.
* ``regex``: a regular expression
* ``match`` (boolean): whether the condition term succeeds if the
``comparand`` does or does not match ``regex`` -- ``true`` for
match, ``false`` for no-match.
The ACL match is executed only if all of the ``conditions`` succeed;
in other words, the ``conditions`` are the boolean AND of all of the
match terms.
For example, these ``conditions`` specify that the match is executed
when the URL begins with "/tea", and the Host header is exactly
"cafe.example.com":
```
conditions:
- comparand: req.url
match: true
regex: ^/tea(/|$)
- comparand: req.http.Host
match: yes
regex: ^cafe\.example\.com$
```
See the [``examples/`` folder``](/examples/cal) for working examples
of ACL configurations.
......@@ -17,3 +17,6 @@ requirements.
([docs](/docs/self-sharding.md))
* [Basic and Proxy Authentication](/examples/authentication)
* [Access control lists](/examples/acl) -- whitelisting or
blacklisting requests by IP address
# Access Control Lists
The sample manifest in this folder sets values in the ``acl`` section
of a VarnishConfig to specify the whitelisting or blacklisting of IP
addresses againt access control lists. See the
[docs](/docs/ref-varnish-cfg.md) for the specifcation of ``acl`` in
VarnishConfig.
The example applies to the Ingress and Services defined in the
["cafe" example](/examples/hello).
Note that the order of elements in the ``acl`` array is significant.
If you define more than one ACL, then matches against the ACLs will be
executed in the order given in ``acl``, until one of them invokes the
failure status, or all of them pass.
## Whitelist for all client requests
The first example is a whitelist against the address ranges in private
IPv4 networks:
```
- name: private-ip4
addrs:
- addr: 10.0.0.0
mask-bits: 24
- addr: 172.16.0.0
mask-bits: 12
- addr: 192.168.0.0
mask-bits: 16
```
This sets the address ranges to:
* 10.0.0.0/24
* 172.16.0.0/12
* 192.168.0.0/16
The ``fail-status`` field is left out and has the default value 403,
meaning that a synthetic "403 Forbidden" response is generated for ACL
failures. The ``type`` field has the default value ``whitelist``,
which means that the failure status is invoked for IP addresses that
do not match the ACL.
The ``comparand`` has the default value ``client.ip``. This means that
the ACL is matched against the VCL
[``client.ip`` object](https://varnish-cache.org/docs/6.1/reference/vcl.html#local-server-remote-and-client),
which is the client address sent in the PROXY header if the PROXY
protocol is in use, or the peer address of the connection if not.
(See the [docs](/docs/varnish-pod-template.md) about how to use the
PROXY protocol.) If ``comparand`` is set to any of ``server.ip``,
``remote.ip`` or ``local.ip``, then the IP address to be matched is
also evaluated as in VCL -- see the
[docs](https://varnish-cache.org/docs/6.1/reference/vcl.html#local-server-remote-and-client)
for details.
The ``conditions`` field is not set in this example, so the ACL match
is executed for every client request.
## ACL restricted to requests for a Service
The next example re-creates the ACL shown as an example in
[vcl(7)](https://varnish-cache.org/docs/6.1/reference/vcl.html#access-control-list-acl):
```
- name: man-vcl-example
addrs:
- addr: localhost
- addr: 192.0.2.0
mask-bits: 24
- addr: 192.0.2.23
negate: true
comparand: req.http.X-Real-IP
type: whitelist
conditions:
- comparand: req.url
match: true
regex: ^/tea(/|$)
- comparand: req.http.Host
match: yes
regex: ^cafe\.example\.com$
```
Addresses that match the ACL are:
* the IP resolved for ``localhost`` at VCL load time
* the range 192.0.2.0/24
But the ACL does not match the IP 192.0.2.23, since ``negate`` is
``true`` for that value.
Note that if a host name is given (``localhost`` in the example), then
Varnish resolves it once at VCL load time, and uses the first IP that
it finds. The IP is never changed after that, so this is not useful
for dynamic DNS entries. ACL matches are always matches against fixed
sets of IP addresses.
The ``type`` is ``whitelist`` as in the first example, this time set
explicitly. The ``fail-status`` is again default 403.
The ``comparand`` is ``req.http.X-Real-IP``, meaning that the ACL is
matched against the IP address in the client request header
``X-Real-IP``. The header must be present in the request and must
contain an IP address, or the ACL match will fail. This can be used in
a setup where Varnish receives requests from a component that sets the
client IP in a header like ``X-Real-IP``.
The ``conditions`` specify that the ACL match is executed when:
* the URL path begins with "/tea"
* the Host header is exactly "cafe.example.com"
According to the Ingress in the ["cafe" example](/examples/hello),
requests are routed to the Service ``tea-svc`` under these
conditions. So ``conditions`` serves to restrict the ACL match to
requests for that Service.
To verify this configuration with curl, we use the ``-x`` option (or
``--proxy``) set to ``$ADDR:$PORT``, where ``$ADDR`` is the external
address of the cluster, and ``$PORT`` is the port at which requests
are routed to the Ingress.
```
# Requests without an X-Real-IP header fail the ACL match, and get
# the 403 Forbidden response
$ curl -v -x $ADDR:$PORT http://cafe.example.com/tea
[...]
> GET http://cafe.example.com/tea HTTP/1.1
> Host: cafe.example.com
[...]
< HTTP/1.1 403 Forbidden
[...]
# Request with the X-Real-IP header, but set to an IP that does not
# match the ACL:
$ curl -H 'X-Real-IP: 198.51.100.47' -v -x $ADDR:$PORT http://cafe.example.com/tea
[...]
> GET http://cafe.example.com/tea HTTP/1.1
> Host: cafe.example.com
[...]
> X-Real-IP: 198.51.100.47
[...]
< HTTP/1.1 403 Forbidden
[...]
# Request with an X-Real-IP header that matches the ACL:
$ curl -H 'X-Real-IP: 192.0.2.120' -v -x $ADDR:$PORT http://cafe.example.com/tea
[...]
> GET http://cafe.example.com/tea HTTP/1.1
> Host: cafe.example.com
[...]
> X-Real-IP: 192.0.2.120
[...]
< HTTP/1.1 200 OK
[...]
```
## Blacklist and use of X-Forwarded-For
The next example defines a blacklist for the ranges 192.0.20/24 and
198.51.100.0/24, to match against the ``X-Forwarded-For`` header:
```
- name: xff-first-example
addrs:
- addr: 192.0.2.0
mask-bits: 24
- addr: 198.51.100.0
mask-bits: 24
comparand: xff-first
type: blacklist
fail-status: 404
conditions:
- comparand: req.url
match: true
regex: ^/coffee/black(/|$)
- comparand: req.http.Host
match: yes
regex: ^cafe\.example\.com$
```
Type ``blacklist`` means that the failure status is returned for IPs
that match the ACL.
``fail-status`` in this case is set to 404 for the "404 Not Found"
response, so clients who do not match the ACL get responses that
appear as if they used an invalid URL.
The ``comparand`` is set to ``xff-first``, which means that the ACL is
matched against the IP in the first comma-separated field of the
``X-Forwarded-For`` request header. If that field is not an IP
address, then the match fails. Note that if there is no
``X-Forwarded-For`` header in the request received by Varnish, Varnish
adds it with the value of ``client.ip``, before the ACL match is
evaluated; so ``xff-first`` is the same as matching against
``client.ip`` in that case.
Since ``X-Forwarded-For`` may appear more than once in a request
header, all instances of the header are consolidated into one header
before the match is performed, comma-separated in the order in which
they appeared.
The ``conditions`` specify that the ACL match is executed when:
* the URL path begins with "/coffee/black"
* the Host header is exactly "cafe.example.com"
Note that this ACL specification, which is restricted to
"/coffee/black" URLs, appears in the VarnishConfig before the next
one, which restricts ACL matches to URLs beginning with
"/coffee". This is important to the logic of ACL matching -- the match
for the "more specific" URL range is executed first.
Verifying the ACL with curl:
```
# A request without any X-Forwarded-For header does not match the ACL,
# and hence is not blocked by the blacklist. Varnish adds
# X-Forwarded-For with the client IP before the match is evaluated,
# but that IP does not match the ACL.
$ curl -v -x $ADDR:$ADDR http://cafe.example.com/coffee/black
[...]
> GET http://cafe.example.com/coffee/black HTTP/1.1
> Host: cafe.example.com
[...]
< HTTP/1.1 200 OK
[...]
# A request in which the first field of X-Forwarded-For does not match
# the blacklist is not blocked:
$ curl -H 'X-Forwarded-For: 203.0.113.47, 192.0.2.11' -v -x $ADDR:$PORT http://cafe.example.com/coffee/black
[...]
> GET http://cafe.example.com/coffee/black HTTP/1.1
> Host: cafe.example.com
[...]
> X-Forwarded-For: 203.0.113.47, 192.0.2.11
[...]
< HTTP/1.1 200 OK
[...]
# A request in which the first field of X-Forwarded-For matches the
# blacklist is not blocked, and the client receives the 404 response
# as specified by fail-status:
$ curl -H 'X-Forwarded-For: 192.0.2.11' -v -x $ADDR:$PORT http://cafe.example.com/coffee/black
[...]
> GET http://cafe.example.com/coffee/black HTTP/1.1
> Host: cafe.example.com
[...]
> X-Forwarded-For: 192.0.2.11
[...]
< HTTP/1.1 404 Not Found
[...]
```
## Another use of X-Forwarded-For
The final example defines a blacklist for the range 203.0.113.0/24:
```
- name: xff-2ndlast-example
addrs:
- addr: 203.0.113.0
mask-bits: 24
comparand: xff-2ndlast
type: blacklist
conditions:
- comparand: req.url
match: true
regex: ^/coffee(/|$)
- comparand: req.http.Host
match: yes
regex: ^cafe\.example\.com$
```
The ``comparand`` is ``xff-2ndlast``, which means that the ACL is
matched against the IP in the next-to-last comma-separated field of
the ``X-Forwarded-For`` request header, *after* Varnish appends the
client IP to ``X-Forwarded-For``. In other words, it specifies the IP
in the last field of ``X-Forwarded-For`` as received by Varnish.
For example, Varnish may receive a request in which the header looks
like this:
```
X-Forwarded-For: 192.0.2.47, 203.0.113.11
```
Varnish always appends the client IP, which may change the header to:
```
X-Forwarded-For: 192.0.2.47, 203.0.113.11, 172.17.0.1
```
``xff-2ndlast`` in this case specifies 203.0.113.11, which matches the
blacklist. If there is no ``X-Forwarded-For`` in a request, Varnish
adds the header with the client IP value; but then there is no
next-to-last field, so matches for ``xff-2ndlast`` fail.
The ``conditions`` specify that the ACL match is executed when:
* the URL path begins with "/coffee"
* the Host header is exactly "cafe.example.com"
In the ["cafe" example](/examples/hello), this applies to requests
that are routed to the Service ``coffee-svc``.
Verifying the blacklist:
```
# Request sent to Varnish in which the last field of X-Forwarded-For
# (which becomes next-to-last after Varnish modifies the header) does
# not match the blacklist, so it is not blocked:
$ curl -H 'X-Forwarded-For: 203.0.113.47, 192.0.2.11' -v -x $ADDR:$ADDR http://cafe.example.com/coffee/black
[...]
> GET http://cafe.example.com/coffee HTTP/1.1
> Host: cafe.example.com
[...]
> X-Forwarded-For: 203.0.113.47, 192.0.2.11
[...]
< HTTP/1.1 200 OK
[...]
# A request sent to Varnish in which the last (and only) field in
# X-Forwarded-For matches the blacklist is blocked:
$ curl -H 'X-Forwarded-For: 203.0.113.47' -v -x $ADDR:$PORT http://cafe.example.com/coffee
[...]
> GET http://cafe.example.com/coffee HTTP/1.1
> Host: cafe.example.com
[...]
> X-Forwarded-For: 203.0.113.47
[...]
< HTTP/1.1 403 Forbidden
[...]
# A request sent with no X-Forwarded-For header never matches the
# blecklist, and hence is not blocked:
$ curl -v -x 192.168.0.100:30376 http://cafe.example.com/coffee
[...]
> GET http://cafe.example.com/coffee HTTP/1.1
> Host: cafe.example.com
[...]
< HTTP/1.1 200 OK
[...]
```
# Sample configurations for Access Control Lists.
apiVersion: "ingress.varnish-cache.org/v1alpha1"
kind: VarnishConfig
metadata:
name: acl-example-cfg
spec:
# The services array is required and must have at least one element.
# Lists the Service names of Varnish services in the same namespace
# to which this config is to be applied.
services:
- varnish-ingress
# This config defines two whitelists and two blacklists.
# The order of the elements in acl is significant -- ACL matches are
# run in order, until the first set of conditions that leads to a
# failure status is matched, or all of them run without failing.
acl:
# This ACL defines the address ranges for private IPv4 networks:
# 10.0.0.0/24, 172.16.0.0/12 and 192.168.0.0/16
#
# comparand is the default: client.ip, as interpreted in VCL
# (either the client IP forwarded by the PROXY protocol, or
# the peer address of the connection if PROXY is not used).
#
# fail-status is the default (403 Forbidden).
#
# type is default (whitelist) -- the failure status is returned
# if client.ip does not match the ACL.
#
# The conditions field is not defined -- this ACL is matched for
# all client requests.
- name: private-ip4
addrs:
- addr: 10.0.0.0
mask-bits: 24
- addr: 172.16.0.0
mask-bits: 12
- addr: 192.168.0.0
mask-bits: 16
# This is the ACL shown as an example in vcl(7):
# https://varnish-cache.org/docs/6.1/reference/vcl.html#access-control-list-acl
# The ACL matches:
# - the IP resolved for "localhost" at VCL load time
# - 192.0.2.0/24
# - but it does *not* match the IP 192.0.2.23 (since the negate
# field for that address is set to true).
#
# comparand is the client request header X-Real-IP. The header
# must contain an IP address, otherwise it will not match the ACL.
#
# fail-status is default 403.
#
# type is whitelist.
#
# The conditions specify that the ACL match is executed when:
# - the URL path begins with "/tea", and
# - the Host header is exactly equal to "cafe.example.com"
# (According to the Ingress rules of the cafe example, requests
# are routed to the Service tea-svc under these conditions).
- name: man-vcl-example
addrs:
- addr: localhost
- addr: 192.0.2.0
mask-bits: 24
- addr: 192.0.2.23
negate: true
comparand: req.http.X-Real-IP
type: whitelist
conditions:
- comparand: req.url
match: true
regex: ^/tea(/|$)
- comparand: req.http.Host
match: yes
regex: ^cafe\.example\.com$
# This ACL defines a blacklist:
# 192.0.20/24 and 198.51.100.0/24
#
# comparand xff-first means that the IP address to be matched
# is in the first comma-separated field in the X-Forwarded-For
# header. This field must contain an IP address, otherwise it
# will not match the ACL.
#
# fail-status is 404 Not Found (making it appear to non-matching
# clients that they used an invalid URL).
#
# type is blacklist -- the failure status is returned if the
# IP address (from X-Forwarded-For) matches the ACL.
#
# The conditions specify that the ACL match is executed when:
# - the URL path begins with "/coffee/black", and
# - the Host header is exactly equal to "cafe.example.com"
- name: xff-first-example
addrs:
- addr: 192.0.2.0
mask-bits: 24
- addr: 198.51.100.0
mask-bits: 24
comparand: xff-first
type: blacklist
fail-status: 404
conditions:
- comparand: req.url
match: true
regex: ^/coffee/black(/|$)
- comparand: req.http.Host
match: yes
regex: ^cafe\.example\.com$
# This ACL defines a blacklist for 203.0.113.0/24.
#
# comparand xff-2ndlast means that the IP address to be matched
# is in the next-to-last comma-separated field in the X-Forwarded-For
# header, *after* Varnish appends the client IP to X-Forwarded-For.
# In other words, it is the last field in X-Forwarded-For when
# Varnish receives the request. As with xff-first, the field must
# contain an IP address.
#
# fail-status is default 403.
#
# type is blacklist.
#
# The conditions specify that the ACL match is executed when:
# - the URL path begins with "/coffee", and
# - the Host header is exactly equal to "cafe.example.com"
# (According to the Ingress rules of the cafe example, requests
# are routed to the Service coffee-svc under these conditions).
- name: xff-2ndlast-example
addrs:
- addr: 203.0.113.0
mask-bits: 24
comparand: xff-2ndlast
type: blacklist
conditions:
- comparand: req.url
match: true
regex: ^/coffee(/|$)
- comparand: req.http.Host
match: yes
regex: ^cafe\.example\.com$
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment