Skip to content

Commit

Permalink
add ansible ctfd
Browse files Browse the repository at this point in the history
  • Loading branch information
xanhacks committed Sep 30, 2024
1 parent 5ae4068 commit f44edaf
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 4 deletions.
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
.env
.env

*.key
*.pem

venv/
4 changes: 4 additions & 0 deletions ansible/.ansible-lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---

skip_list:
- name[template]
5 changes: 4 additions & 1 deletion ansible/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
github.key
github.key
hacker_theme.zip
# todo: remove this line
challenge.py
110 changes: 110 additions & 0 deletions ansible/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Deployment with Ansible

## Getting started

### Installation

```bash
python3 -m pip install ansible
```

### Inventory

Update the [inventories/](inventories/) folder with your SSH information.

Configuration your ssh client configuration at `~/.ssh/config`.

```bash
Host heroctf-ctfd
User root
Hostname XXX
Port 22
IdentityFile /home/xanhacks/.ssh/heroctf/linode

Host heroctf-challenge-1
User root
Hostname XXX
Port 22
IdentityFile /home/xanhacks/.ssh/heroctf/linode

Host heroctf-challenge-2
User root
Hostname XXX
Port 22
IdentityFile /home/xanhacks/.ssh/heroctf/linode

# [...]
```

Check the SSH connection is working and accept all SSH fingerprints.

```bash
ansible -i inventories/dev -m ping all -v
ansible -i inventories/prod -m ping all -v
```

## Roles

### Prerequisites

#### Features

1. Update APT cache & install usefull packages (`git`, `curl`, `vim`...).
2. Configure the VM timezone, hostname and create a low privileged account.
3. Install and start `docker` & `docker-compose`.

### CTFd

#### Features

1. Clone CTFd at a specific version.
2. Overwrite the default `docker-compose.yml` and `http.conf` with enhanced configuration (HTTPs and better performance).
3. Upload HTTPs certificates.
4. (optional) Extract custom CTFd theme.
5. (optional) Enable first blood Discord webhook.

> Generate your HTTPs certificates using certbot: `certbot certonly --manual --preferred-challenges dns -d 'ctf.heroctf.fr' --work-dir $(pwd) --logs-dir $(pwd) --config-dir $(pwd)`
#### Setup & Run

1. Configure the `ctfd` role at [./group_vars/ctfd.yml](./group_vars/ctfd.yml).
2. Add `fullchain.pem` and `privkey.pem` to [./roles/ctfd/files/certs](./roles/ctfd/files/certs).
3. Configure the [./roles/ctfd/files/.env.sample](./roles/ctfd/files/.env.sample) file to `.env`.
4. (optional) Add CTFd Theme `hacker_theme.zip` at [./roles/ctfd/files/](./roles/ctfd/files/).

```bash
ansible-playbook ctfd.yml -i inventories/dev
ansible-playbook ctfd.yml -i inventories/prod
```

### Challenges

#### Features

1. Upload and configure the Github SSH private key to see private repositories.
2. Clone the challenge repository.

> You can create a Github SSH key for a repository under the `Settings > Deploy keys` tab, then click on `Add deploy key`.
#### Setup & Run

1. Configure `challenges` role at [./group_vars/challenges.yml](./group_vars/challenges.yml).
2. Add your Github private SSH key to [./roles/challenges/files/](./roles/challenges/files/) under the name `github.key`.

```bash
ansible-playbook challenges.yml -i inventories/dev
ansible-playbook challenges.yml -i inventories/prod
```

## Linter

Use [ansible-lint](https://github.com/ansible/ansible-lint) to checks playbooks for practices and behavior that could potentially be improved.

```bash
python3 -m pip install ansible-lint
ansible-lint
```

## About

Tested on debian 12.
4 changes: 4 additions & 0 deletions ansible/group_vars/ctfd.yml
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
ctfd_version: "3.7.3"
ctfd_install_theme: true
ctfd_theme_name: "hacker_theme.zip"
ctfd_domain: "ctf.heroctf.fr"
ctfd_install_discord_webhook: false
1 change: 1 addition & 0 deletions ansible/roles/challenges/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- name: "Clone HeroCTF challenges' repository to '/home/{{ ctf_user }}/challenges'"
ansible.builtin.git:
repo: "{{ challenges_git_url }}"
version: "main"
dest: "/home/{{ ctf_user }}/challenges"
accept_hostkey: true
force: true
Expand Down
2 changes: 2 additions & 0 deletions ansible/roles/ctfd/files/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SECRET_KEY=xxxxxxxxxxxxxxxxx
DISCORD_WEBHOOK_URL=https://xxxxxxxxxxxx
Empty file.
103 changes: 103 additions & 0 deletions ansible/roles/ctfd/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---

- name: "Clone CTFd at version '{{ ctfd_version }}' to '/home/{{ ctf_user }}/CTFd'"
ansible.builtin.git:
repo: "https://github.com/CTFd/CTFd.git"
dest: "/home/{{ ctf_user }}/CTFd"
version: "{{ ctfd_version }}"
force: true
become: true
become_user: "{{ ctf_user }}"

- name: "Copy '.env' to '/home/{{ ctf_user }}/CTFd/.env'"
ansible.builtin.copy:
src: "files/.env"
dest: "/home/{{ ctf_user }}/CTFd/.env"
owner: "{{ ctf_user }}"
group: "{{ ctf_user }}"
mode: "0644"
become: true

- name: "Copy 'docker-compose.yml.j2' to '/home/{{ ctf_user }}/CTFd/docker-compose.yml'"
ansible.builtin.template:
src: "docker-compose.yml.j2"
dest: "/home/{{ ctf_user }}/CTFd/docker-compose.yml"
owner: "{{ ctf_user }}"
group: "{{ ctf_user }}"
mode: "0644"
become: true
become_user: "{{ ctf_user }}"

- name: "Copy 'http.conf.j2' to '/home/{{ ctf_user }}/CTFd/conf/nginx/http.conf'"
ansible.builtin.template:
src: "http.conf.j2"
dest: "/home/{{ ctf_user }}/CTFd/conf/nginx/http.conf"
owner: "{{ ctf_user }}"
group: "{{ ctf_user }}"
mode: "0644"
become: true

- name: "Create a 'certs' directory for HTTPs certificates"
ansible.builtin.file:
path: "/home/{{ ctf_user }}/CTFd/conf/nginx/certs/"
state: directory
owner: "{{ ctf_user }}"
group: "{{ ctf_user }}"
mode: "0775"
become: true

- name: "Copy 'fullchain.pem' & 'privkey.pem' to '/home/{{ ctf_user }}/CTFd/conf/nginx/certs/'"
ansible.builtin.copy:
src: "files/certs/{{ item }}"
dest: "/home/{{ ctf_user }}/CTFd/conf/nginx/certs/{{ item }}"
owner: "{{ ctf_user }}"
group: "{{ ctf_user }}"
mode: "0644"
loop:
- fullchain.pem
- privkey.pem
become: true

- name: Copy challenge.py # with first blood bot
ansible.builtin.copy:
src: "files/challenge.py"
dest: "/home/{{ ctf_user }}/CTFd/CTFd/api/v1/challenges.py"
owner: "{{ ctf_user }}"
group: "{{ ctf_user }}"
mode: "0644"
when: ctfd_install_discord_webhook
become: true

- name: Extract the custom theme
ansible.builtin.unarchive:
src: "files/{{ ctfd_theme_name }}"
dest: "/home/{{ ctf_user }}/CTFd/CTFd/themes/"
become: true
become_user: "{{ ctf_user }}"
when: ctfd_install_theme

- name: "Create a 'data' directory for docker-compose volumes"
ansible.builtin.file:
path: "/home/{{ ctf_user }}/CTFd/data/"
state: directory
owner: "{{ ctf_user }}"
group: "{{ ctf_user }}"
mode: "0777"
recurse: true
become: true

- name: "Add 'psycopg2-binary' to requirements.txt for PostgreSQL connection"
ansible.builtin.shell: "echo 'psycopg2-binary' >> requirements.txt"
args:
chdir: "/home/{{ ctf_user }}/CTFd"
become: true
become_user: "{{ ctf_user }}"

- name: Start CTFd docker-compose
ansible.builtin.shell: "docker compose up -d --build"
args:
chdir: "/home/{{ ctf_user }}/CTFd"
register: ctfd_docker_compose_output
changed_when: "'recreated' in ctfd_docker_compose_output.stdout or 'Pulling' in ctfd_docker_compose_output.stdout"
become: true
become_user: "{{ ctf_user }}"
65 changes: 65 additions & 0 deletions ansible/roles/ctfd/templates/docker-compose.yml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---

services:
ctfd:
build: .
user: root
restart: always
environment:
- UPLOAD_FOLDER=/var/uploads
- DATABASE_URL=postgresql://ctfd:ctfd@db/ctfd
- REDIS_URL=redis://redis:6379
- WORKERS={{ ansible_processor_vcpus }}
- LOG_FOLDER=/var/log/CTFd
- ACCESS_LOG=-
- ERROR_LOG=-
- REVERSE_PROXY=true
- SECRET_KEY=${SECRET_KEY}
- DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL} # Discord Webhook for first blood bot
volumes:
- ./data/CTFd/logs:/var/log/CTFd
- ./data/CTFd/uploads:/var/uploads
- .:/opt/CTFd:ro
depends_on:
- db
networks:
default:
internal:

nginx:
image: nginx:1.27-alpine
restart: always
volumes:
- ./conf/nginx/http.conf:/etc/nginx/nginx.conf
- ./conf/nginx/certs:/etc/nginx/certs
ports:
- 443:443
depends_on:
- ctfd
networks:
default:

db:
image: postgres:17-alpine
restart: always
environment:
- POSTGRES_USER=ctfd
- POSTGRES_PASSWORD=ctfd
- POSTGRES_DB=ctfd
volumes:
- ./data/postgres:/var/lib/postgresql/data
networks:
internal:

redis:
image: redis:7-alpine
restart: always
volumes:
- ./data/redis:/data
networks:
internal:

networks:
default:
internal:
internal: true
47 changes: 47 additions & 0 deletions ansible/roles/ctfd/templates/http.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
worker_processes auto;

events {
worker_connections 2048;
}

http {
upstream app_servers {
server ctfd:8000;
}

server {
listen 443 ssl;

server_name {{ ctfd_domain}};
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;

gzip on;
client_max_body_size 512m;

# Handle Server Sent Events for Notifications
location /events {
proxy_pass http://app_servers;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}

# Proxy connections to the application servers
location / {
proxy_pass http://app_servers;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
}
4 changes: 2 additions & 2 deletions terraform/firewall.tf
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ resource "linode_firewall" "ctfd_firewall" {
}

inbound {
label = "allow-http-https"
label = "allow-https"
action = "ACCEPT"
protocol = "TCP"
ports = "80,443"
ports = "443"
ipv4 = ["0.0.0.0/0"]
ipv6 = ["::/0"]
}
Expand Down

0 comments on commit f44edaf

Please sign in to comment.