Prerequisite

To proceed with this guide, ensure you have:


Return an Unauthorized Error

Referencing the earlier post 02-caddy-hardening, update the configuration to permit only the local network, resulting in an unauthorized error (401) response instead of an abort.

(safe) {
    # 192.168.0.0/24: local ip range
    @allowed remote_ip 192.168.0.0/24

    handle {
        # return unauthorized error if
        # the remote IP isn't in the specified range
        respond 401
    }
}


Format Caddy Logs

Add a global configuration on top of your Caddyfile that add a log directive:

{
    log access-log {
        include http.log.access.access-log
        output file /data/access.log
    }
}

Configured in this manner, all access-log logs will be written to the /data/access.log file, serving as the reference for Fail2Ban to monitor and identify potentially suspicious access.

The Caddy logfile is rich in information. For improved readability and parsing, leverage the transform logging encoder available in the access-formatted plugin.

{
    log access-log {
        include http.log.access.access-log
        output file /data/access.log
        format transform `{ts} {request>headers>X-Forwarded-For>[0]:request>remote_ip} {request>host} {request>method} {request>uri} {status}` {
            time_format "02/Jan/2006:15:04:05"
        }
    }
}

This will standardize the format of each log entry to look like:

23/Dec/2023:17:53:24 192.168.0.24 subdomain.domain.name GET /index 200

Create Fail2Ban Filter

On the data/filter.d/ folder, create a file caddy-custom.conf with the content:

# data/filter.d/caddy-custom.conf
 
[Definition]
# catch all lines that terminate with an unauthorized error
failregex = <HOST> \S+ \S+ \S+ (401)$
ignoreregex =

Create Fail2Ban Jail

On the data/jail.d/ folder, create a file caddy.conf containing:

# data/jail.d/caddy.conf
 
[caddy]
backend = auto
enabled = true
chain = FORWARD
protocol = tcp
port = http,https
filter = caddy-custom
maxretry = 3
bantime = 86400
findtime = 43200
# replace with the correct path
logpath = /opt/caddy/data/access.log

Configure Fail2Ban Access Log

version: "3.9"
 
services:
 fail2ban:
   image: crazymax/fail2ban:latest
   container_name: fail2ban
   network_mode: "host"
   cap_add:
     - NET_ADMIN
     - NET_RAW
   volumes:
     - "./data:/data"
     - "/var/log/auth.log:/var/log/auth.log:ro"
      # replace with the correct path
     - "/opt/caddy/data/access.log:/opt/caddy/data/access.log:ro"
   restart: unless-stopped

You can now restart Fail2Ban using the command:

docker compose restart

Conclusion

Fail2Ban is now set to automatically ban clients attempting to connect to a subdomain configured exclusively for the local network. After three unsuccessful attempts, the client will be restricted from making further requests to the Caddy server.


Ressources