Caddy v2 实现访问控制

为 Caddy v2 配置访问控制,例如 IP 的黑白名单、路径黑白名单和 basicauth 等。

Nginx 配置

在使用 Nginx 的时候,可以很方便的通过 allowdeny设置黑白名单,例如:

白名单:

1
2
3
4
5
6
server{
    listen:80;
    server_name localhost;
    allow 192.168.1.0/24;
    deny all;
}

黑名单:

1
2
3
4
5
server{
    listen:80;
    server_name localhost;
    deny 192.168.1.0/24;
}

Caddy 配置

到了 Caddy,为了实现对应的目的,去查了一下文档,找到了这两个相关内容:

Request matchers (Caddyfile) — Caddy Documentation

respond (Caddyfile directive) — Caddy Documentation

指定响应的语法是这样的:

1
2
3
4
respond [<matcher>] <status>|<body> [<status>] {
    body <text>
    close
}

简单来说,就是设定一个 matcher,对符合 matcher 的请求返回 403 状态码就可以了。

对于指定的 IP 进行过滤,使用的是 remote-ip,语法相当简单:

1
remote_ip 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 123.45.67.89

IP 黑白名单

对于 IP 过滤,组合起来就是:

白名单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
test.example.com {
    @ip_whitelist {
            remote_ip 192.168.1.9 172.16.0.0/12
    }

    route @ip_whitelist {
            file_server browse
    }

    respond "Blocked" 403
}

黑名单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
test.example.com {
    @ip_blacklist {
            remote_ip 192.168.1.9 172.16.0.0/12
    }

    route @ip_blacklist {
        respond "Blocked" 403
    }

    file_server browse
}

结合多个匹配器

有时候我们只允许指定 IP 访问指定路径,可以加上 Matchers 里的 path 或者 path_regexp。

需要注意的是,在 Caddy 的一个 matcher block 中定义多个 matchers,那么他们是 AND 的关系;在同一个 matcher 中定义多个条件(比如路径或 IP),他们是 OR 的关系。例如:

1
2
3
4
@matcher {
        remote_ip 192.168.1.9
        path /test1 /test2
}

效果是:IP 地址为 192.168.1.9 的客户端访问 /test1 /test2 路径时满足该 matcher block。

其他的 matcher 语法可以查看文档 Request matchers (Caddyfile) — Caddy Documentation,只需要配置好 matcher,并为每个 matcher 指定对应的 route,即可实现各种访问控制。

多个指令的顺序问题

在加入 basicauth 后,遇到了一点小问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@ip_blacklist {
        remote_ip 192.168.1.9 172.16.0.0/12
}

route @ip_blacklist {
    respond "Blocked" 403
}

basicauth * {
    Bob JDJhJDEwJEVCNmdaNEg2Ti5iejRMYkF3MFZhZ3VtV3E1SzBWZEZ5Q3VWc0tzOEJwZE9TaFlZdEVkZDhX
}
file_server browse

配置文件像上面一样写,测试发现:当黑名单内的客户端访问时,不是直接返回 403,而是弹出了 basicauth,在 basicauth 通过后才会返回 403。查了文档之后发现,不同指令(Directive)之间是存在优先级的,basicauth的优先级高于route,因此被首先执行。

参考文档Directive order,指令的优先级是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
tracing
map
vars
root
header
request_body
redir

# incoming request manipulation
method
rewrite
uri
try_files

# middleware handlers; some wrap responses
basicauth
request_header
encode
push
templates

# special routing & dispatching directives
handle
handle_path
route

# handlers that typically respond to requests
abort
error
respond
metrics
reverse_proxy
php_fastcgi
file_server
acme_server

为了让 basicauth 在黑名单后面执行,我们可以将指令都放到 route 中,这样便不会对指令进行重排序,会按照定义的顺序执行,配置文件应当修改为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@ip_blacklist {
        remote_ip 192.168.1.9 172.16.0.0/12
}

route {
    respond @ip_blacklist "Blocked" 403
    basicauth * {
        BobJDJhJDEwJEVCNmdaNEg2Ti5iejRMYkF3MFZhZ3VtV3E1SzBWZEZ5Q3VWc0tzOEJwZE9TaFlZdEVkDhX
    }
    file_server browse
}

放到 route 中后要尤其注意,basicauth 要写在提供服务的指令之前。不在 route 中时由于 Caddy 会自动重排序,因此 basicauth 即使在最后也能正确触发验证。而在 route 中 Caddy 会严格按照书写顺序执行指令,因此一定要把 basicauth 放到前面,以免验证失效。