CTFd-Whale

CTFd-Whale是CTFd的一个插件,用来为CTFd提供控制动态容器的能力,通过CTFd-Whale,可以实现选手间及队伍间题目容器的隔离、独立flag等

原理

原理图

CTFd-Whale在CTFd中工作,CTFd-Whale通过Docker API连接Docker进行容器的控制,通过frpc admin api进行内网穿透的控制,容器运行的Docker环境需要开启了Swarm,若有多台容器服务器则通过Swarm进行集群,frpc与容器通过Docker network进行互通,frpc通过与frps连接进行内网穿透,选手通过内网穿透连接到容器

必要条件:

  • CTFd-Whale与Docker通过API连接
  • CTFd-Whale与frpc通过API连接
  • frpc与容器通过Docker network连接
  • frpc与frps连接

非必要条件:

  • CTFd的运行环境不一定是Docker,也可以通过非Docker方式运行
  • frps与CTFd、Docker可以在同一台或不同服务器运行

部署方式

单机部署

单机部署时推荐将CTFd、frpc、frps均部署到Docker中,通过映射docker.sock连接Docker API,在Docker中建立管理网络用于连接CTFd与frpc,建立容器网络用于连接frpc与容器

优势:成本低,易于部署,仅需要一个出口IP

缺点:存在一定的安全风险

适用于小型比赛

多机部署

多机部署时推荐将CTFd与题目容器分离,在容器服务器中开启Docker API的远程连接,映射frpc的管理端口,CTFd-Whale通过IP连接到Docker API,通过IP连接到frpc的API,frpc与frps部署在同一台服务器

优势:平台与容器隔离,使用不同出口IP,互不影响

缺点:成本中等,配置较麻烦

适用于中型比赛

集群部署

集群部署推荐将CTFd、题目容器、frps分离部署,在容器服务器中开启Docker API的远程连接,映射frpc的管理端口,CTFd-Whale通过IP连接到Docker API,通过IP连接到frpc的API,frpc与frps通过IP连接

优势:平台与容器隔离,可动态分配容器运行的服务器,可根据服务器网络、内存等选择最优部署方案

缺点:成本高,配置项多,前期运维麻烦

适用于大型比赛

部署方法

下载CTFd-Whale

使用Git下载

进入CTFd/CTFd/plugins目录,使用git clone https://github.com/frankli0324/ctfd-whale下载插件,下载完成后使用docker compose up -d --build构建并启动CTFd

在管理面板选择Whale

Whale

此时提示无法连接到Docker API和frpc admin api,插件安装成功

设置Docker API

单机部署

对于CTFd未使用Docker的情况,安装Docker后即可使用unix:///var/run/docker.sock连接到Docker API

对于CTFd在Docker中的情况,在docker-compose.ymlservices.ctfd.volumes节添加- /var/run/docker.sock:/var/run/docker.sock映射关系即可

多机部署或集群部署

在Swarm管理节点中,编辑Docker启动参数,开启远程访问

在CTFd-Whale配置面板中将连接地址填入API URL中,例如tcp://127.0.0.1:2375

设置frpc admin api

单机部署

docker-compose.yml中修改version3或更高

1
version: '3'

docker-compose.yml中加入networks.frp_adminnetworks.frpc_container网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
networks:
...
frp_admin: # frp控制网络
internal: true # 内网
ipam: # 可选
config: # 可选
- subnet: 172.1.0.0/16 # 内网地址
frpc_containers: # frpc容器网络
driver: overlay # swarm中网络驱动
internal: true # 容器不出网,可选
attachable: true # 网络可附加
ipam: # 可选
config: # 可选
- subnet: 172.2.0.0/16 # 内网地址

docker-compose.yml中加入services.frpc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
services:
...
frpc:
image: snowdreamtech/frpc
restart: always
volumes:
- ./conf/frp/frpc.ini:/etc/frp/frpc.ini # 配置文件放在CTFd/conf/frp/frpc.ini
entrypoint:
- /usr/bin/frpc
- '-c'
- /etc/frp/frpc.ini
depends_on:
- frps # 可选,frps与frpc在同一个文件中时使用
networks:
frp_admin:
ipv4_address: 172.1.0.2 # 可选
frpc_containers:

CTFd/conf/frp创建frpc.ini

1
2
3
4
5
6
7
8
[common]
server_addr = frps # frps或具体IP
server_port = 7000
token = TOKEN
admin_addr = frpc # frpc或frp_admin指定IP
admin_port = 7400
admin_user = user
admin_pwd = pwd

对于frps不单独部署的情况,在docker-compose.yml中加入services.frps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
services:
...
frps:
image: snowdreamtech/frps
restart: always
volumes:
- ./conf/frp/frps.ini:/etc/frp/frps.ini # 配置文件放在CTFd/conf/frp/frps.ini
entrypoint:
- /usr/bin/frps
- '-c'
- /etc/frp/frps.ini
ports:
- '10000-11000:10000-11000' # 题目端口范围
networks:
frp_admin:
ipv4_address: 172.1.0.3 # 可选
default:

CTFd/conf/frp创建frps.ini

1
2
3
4
[common]
bind_addr = frps # frps或frp_admin指定IP
bind_port = 7000
token = TOKEN

对于frps单独部署的情况,在docker-compose.yml中加入services.frpc.extra_hosts

1
2
3
4
5
6
services:
...
frpc:
...
extra_hosts:
- 'host.docker.internal:host-gateway'

docker-compose.ymlservices.ctfd

1
2
3
4
5
6
7
8
9
10
services:
...
ctfd:
...
depends_on:
...
- frpc
networks:
...
frp_admin:

并去掉frp_admin中的internal,frpc使用host.docker.internal连接frps

在CTFd-Whale管理面板中Router节中API URL中填写http://user:pwd@frpc:7400

多机部署或集群部署

配置与单机部署类似,删除docker-compose.yml中原services内容,并添加services.frpc.ports

1
2
3
4
5
6
services:
...
frpc:
...
ports:
- 7400:7400

对于不使用Docker Compose启动的方式

1
2
docker network create --driver overlay --attachable frpc_containers
docker run -v ./conf/frp/frpc.ini:/etc/frp/frpc.ini --restart=always --network frpc_containers --add-host=host.docker.internal:host-gateway -p 7400:7400 --entrypoint /usr/bin/frpc snowdreamtech/frpc -c /etc/frp/frpc.ini

在CTFd-Whale管理面板中Router节中API URL中填写http://user:pwd@frpc IP:7400

启用Docker Swarm

1
2
docker swarm init
docker node update --label-add "name=linux-1" $(docker node ls -q)