Skip to content

Commit

Permalink
Improves README, disallow more than one 'prefix_dir'. (#33)
Browse files Browse the repository at this point in the history
I noticed that the `strict` directive wasn't documented at all. And
there wasn't really a good description of the syntax and other
directives.

This also modifies the code to disallow more than one `prefix_dir`
directive per block since more than one doesn't make sense.

This also modifies the code to allow more than one `country` directive
to match the behavior of `ip`. Thus you don't have to specify all the
country codes on a single line.
  • Loading branch information
krader1961 authored and pyed committed Nov 27, 2018
1 parent 397e68d commit 22aff73
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 44 deletions.
148 changes: 110 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,114 @@
# ipfilter
[![Go Report Card](https://goreportcard.com/badge/pyed/ipfilter)](https://goreportcard.com/report/pyed/ipfilter)

ipfilter is a middleware for [Caddy](http://caddyserver.com)
This is middleware for the [Caddy](http://caddyserver.com)
web server that implements black and whitelisting based on
IP addresses (or CIDR ranges) or country of origin using a
[MaxMind](https://dev.maxmind.com/geoip/geoip2/geolite2/) database.

# Caddyfile examples
## Syntax

```
ipfilter <basepath> {
rule <block | allow>
ip <addresses or CIDR ranges to block>
prefix_dir <IP addr directory prefix>
database </path/to/GeoLite2-Country.mmdb>
country <ISO two letter country codes>
blockpage <blockpage.html>
strict
}
```

You can specify zero or more `ipfilter` blocks. Each `ipfilter` block has
to specify at least one `ip`, `prefix_dir` or `country` directive. If no
`ipfilter` blocks are defined this middleware will allow every request.

* **basepath**: A sequence of URI path prefixes to match for the filter
to be active. You have to specify at least one path prefix. Use `/` to
match every request. If the request doesn't match one of these prefixes
the filter is ignored for purposes of determining if the request is
blocked or allowed.

* **rule**: Should the filter `block` (blacklist) or `allow` (whitelist)
the addresses. This directive is mandatory. It is an error to use it more
than once per ipfilter block. The **rule** in effect for the last `ipfilter`
block to match a request determines if it is blocked or allowed.

Note that if you only have `ipfilter` blocks that specify `rule allow`
then any request which doesn't match those filters will be implicitly
blocked.

* **ip**: A sequence of IP adddresses or CIDR ranges to match. For example,
`ip 1.2.3.4 192.168.0.0/24` This is optional. It can be used more than
once in each `ipfilter` block rather than enumerating all IPs after a single
`ip` directive.

* **prefix_dir**: Specifies a directory in which to search for file names
matching the IP address of the request. This is optional. It is an error
to use this more than once per `ipfilter` block.

You can specify a relative pathname to place it relative to the Caddy
server CWD (which should be the content root dir). When putting the
blacklisted directory in the web server document tree you should also add
an `internal` directive to ensure those files are not visible via HTTP
GET requests. For example, `internal /blacklist/`. You can also specify
an absolute pathname to locate the blacklist directory outside the
document tree. And the path can include environment vars. For example,
`prefix_dir {$HOME}/etc/www/blacklist`.

You can create the file in the root of the blacklist directory. This is
known as using a "flat" namespace. For example, *blacklist/127.0.0.1*
or *blacklist/2601:647:4601:fa93:1865:4b6c:d055:3f3*. However,
putting thousands of files in a single directory may cause
poor performance of the lookup function. So you can also,
and should, use a "sharded" namespace. This involves creating
the file in a subdirectory based on the first two components
of the address. For example, *blacklist/127/0/127.0.0.1* or
*blacklist/2601/647/2601:647:4601:fa93:1865:4b6c:d055:3f3*.

Note that you can also whitelist IP addresses using this mechanism
by specifying `rule allow`. This may be useful when it follows a more
general blocking rule (e.g., by country) and you want to selectively
allow some addresses through but don't want to hardcode the addresses
in the Caddy config file.

This mechanism is most useful when coupled with automated monitoring of
your web server activity to detect signals that your server is under
attack from malware. All your monitoring software has to do is create
a file in the blacklist directory.

At this time the content of the file is ignored. In the future the
contents will probably be read and exposed as a placeholder variable
for use in conjuction with a template to be filled in via the `markdown`
directive. So you should consider putting some explanatory text in the
file explaining why the address was blocked.

* **database**: Specifies the path to a
[MaxMind](https://dev.maxmind.com/geoip/geoip2/geolite2/) database. This
is required if using the **country** directive; otherwise it should
be omitted.

* **country**: A whitespace separated sequence of ISO two letter country
codes to filter. This is optional but if used also requires a **database**
directive. Note that if a country could not be found for the address it
will be the empty string. This can be specified more than once per block
rather than enumerating all countries on a single line.

* **blockpage**: Names the file to be returned if the ipfilter
matches. Note that a `http.StatusOK` (200) status is returned if the
page is successfully returned to the client. This is optional. If not
specified then a `http.StatusForbidden` (403) status is returned.

* **strict**: Use this to disallow use of the address in the
`X-Forwarded-For` request header if any. This is optional and defaults
to false. If true or there is no `X-Forwarded-For` header use the address
from the request remote address.

## Caddyfile examples

#### Filter clients based on a given IP or range of IPs

```
ipfilter / {
rule block
Expand All @@ -30,39 +133,7 @@ ipfilter / {
}
```
`caddy` will block any client IP that appears as a file name in the
`blacklisted` directory. A relative pathname is relative to the CWD when
`caddy` is started. When putting the blacklisted directory in the web
server document tree you should also add an `internal` directive to
ensure those files are not visible via HTTP GET requests. For example,
`internal /blacklisted/`. You can also specify an absolute pathname to
locate the blacklist directory outside the document tree.

You can create the file in the root of the blacklist directory. This is
known as using a "flat" namespace. For example, `blacklisted/127.0.0.1`
or `blacklisted/2601:647:4601:fa93:1865:4b6c:d055:3f3`. However,
putting thousands of files in a single directory may cause
poor performance of the lookup function. So you can also,
and should, use a "sharded" namespace. This involves creating
the file in a subdirectory based on the first two components
of the address. For example, `blacklisted/127/0/127.0.0.1` or
`blacklisted/2601/647/2601:647:4601:fa93:1865:4b6c:d055:3f3`.

Note that you can also whitelist IP addresses using this mechanism by
specifying `rule allow`. This may be useful when it follows a more general
blocking rule (e.g., by country) and you want to selectively allow some
addresses through but don't want to hardcode the addresses in the Caddy
config file.

This mechanism is most useful when coupled with automated monitoring of your
web server activity to detect signals that your server is under attack from
malware. All your monitoring software has to do is create a file in the
blacklist directory.

At this time the content of the file is ignored. In the future the contents
will probably be read and exposed as a placeholder variable for use in
conjuction with a template to be filled in via the `markdown` directive. So
you should consider putting some explanatory text in the file explaining why
the address was blocked.
*blacklisted* directory.

#### Filter clients based on their [Country ISO Code](https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes)

Expand Down Expand Up @@ -105,7 +176,8 @@ ipfilter /webhook {
ip 192.168.1.0/24
}
```
You can use as many `ipfilter` blocks as you please, the above says: block everyone but `32.55.3.10`, Unless it falls in `192.168.1.0/24` and requesting a path in `/webhook`.
You can use as many `ipfilter` blocks as you please, the above says: block everyone but `32.55.3.10`, Unless it falls in `192.168.1.0/24` and requesting a path in `/webhook`. Note that this is slightly subtle. Any request doesn't match any of those filters is implicitly blocked. In other words, there is no need to explicitly block every address followed by "allow" filters like those above.

## Backward compatibility

#### Backward compatibility
`ipfilter` Now support [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing), and it is the recommended way of specifiying ranges, The old formats of ranging over IPs will get converted to CIDR via [range2CIDRs](https://github.com/pyed/ipfilter/blob/master/range2CIDRs.go) only for the purpose of backward compatibility.
`ipfilter` supports [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing). This is the recommended way of specifiying ranges. The old formats of ranging over IPs will get converted to CIDR via [range2CIDRs](https://github.com/pyed/ipfilter/blob/master/range2CIDRs.go) for the purpose of backward compatibility.
14 changes: 8 additions & 6 deletions ipfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,11 @@ func ipfilterParseSingle(config *IPFConfig, c *caddy.Controller) (IPPath, error)
}
cPath.BlockPage = blockpage
case "country":
cPath.CountryCodes = c.RemainingArgs()
if len(cPath.CountryCodes) == 0 {
countryCodes := c.RemainingArgs()
if len(countryCodes) == 0 {
return cPath, c.ArgErr()
}
cPath.CountryCodes = append(cPath.CountryCodes, countryCodes...)
case "ip":
ips := c.RemainingArgs()
if len(ips) == 0 {
Expand All @@ -402,18 +403,19 @@ func ipfilterParseSingle(config *IPFConfig, c *caddy.Controller) (IPPath, error)
cPath.Nets = append(cPath.Nets, ipRange...)
}
case "strict":
if c.NextArg() {
return cPath, c.ArgErr()
}
cPath.Strict = true
case "prefix_dir":
if !c.NextArg() {
if !c.NextArg() || cPath.PrefixDir != "" {
return cPath, c.ArgErr()
}

// Verify the blacklist path prefix exists and is a directory.
// Verify the IP address path prefix exists and is a directory.
prefixDir := c.Val()
if statb, err := os.Stat(prefixDir); os.IsNotExist(err) || !statb.IsDir() {
return cPath, c.Err("ipfilter: No such blacklist prefix dir: " + prefixDir)
}

cPath.PrefixDir = prefixDir
}
}
Expand Down

0 comments on commit 22aff73

Please sign in to comment.