Skip to content

Commit 4fc5db8

Browse files
authored
Merge pull request #19 from Res260/manychanges
Many improvements
2 parents 20f46ec + 5c239d0 commit 4fc5db8

File tree

11 files changed

+1888
-9
lines changed

11 files changed

+1888
-9
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ challenges/*/terraform/versions.tf
1818
*.egg-info
1919

2020
.vscode/
21-
21+
.idea

README.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,48 @@
11
# CTF Script
22

3-
## Usage
3+
Opinionated command line interface (CLI) tool to manage Capture The Flag (CTF) challenges.
4+
It uses:
5+
- YAML files to describe a challenge and forum posts
6+
- OpenTofu (terraform fork) to describe the infrastructure
7+
- Incus (LXD fork) to run the challenges in containers
8+
- Ansible to configure the challenges
49

5-
Setup a `CTF_ROOT_DIR` environment variable to make the script execute in the right folder or execute the script from that folder.
10+
This tool is used by the NorthSec CTF team to manage their challenges since 2025.
11+
[NorthSec](https://nsec.io/) is one of the largest on-site cybersecurity CTF in the world, held annually in Montreal, Canada,
12+
where 700+ participants compete in a 48-hour long CTF competition.
13+
14+
## Features and Usage
15+
16+
- `ctf init` to initialize a new ctf repository
17+
- `ctf new` to create a new challenge. Supports templates for common challenge types.
18+
- `ctf deploy` deploys the challenges to a local (or remote) Incus server
19+
- `ctf validate` runs lots of static checks (including JSON Schemas) on the challenges to ensure quality
20+
- `ctf stats` provide lots of helpful statistics about the CTF
21+
- and many more. See `ctf --help` for the full list of commands.
22+
23+
To run `ctf` from any directory, set up the `CTF_ROOT_DIR` environment variable to make the script
24+
execute in the right directory or execute the script from that directory. If not set, `ctf` will go up the directory
25+
tree until it finds `challenges/` and `.deploy` directories, which is the root of the CTF repository.
26+
27+
## Structure of a CTF repository
28+
29+
```
30+
my-ctf/
31+
├── challenges/ # Directory containing all the tracks
32+
│ ├── track1/ # Directory for a specific track that contains N flags.
33+
│ │ ├── track.yaml # Main file that describes the track
34+
│ │ ├── files/ # Directory that contains all the files available for download in the track
35+
│ │ │ ├── somefile.zip
36+
│ │ ├── ansible/ # Directory containing Ansible playbooks to configure the track
37+
│ │ │ ├── deploy.yaml # Main playbook to deploy the track
38+
│ │ │ └── inventory # Inventory file for Ansible
39+
│ │ ├── terraform/ # Directory containing OpenTofu (terraform fork) files to describe the infrastructure
40+
│ │ │ └── main.tf # Main OpenTofu file to deploy the track
41+
│ │ ├── posts/ # Directory containing forum posts related to the track
42+
│ │ │ ├── track1.yaml # Initial post for the track
43+
│ │ │ └── track1_flag1.yaml # Inventory file for Ansible
44+
45+
```
646

747
## Installation
848

@@ -18,6 +58,12 @@ Install with pipx:
1858
pipx install git+https://github.com/nsec/ctf-script.git
1959
```
2060

61+
Install with pip:
62+
63+
```bash
64+
pip install git+https://github.com/nsec/ctf-script.git
65+
```
66+
2167
### Add Bash/Zsh autocompletion to .bashrc
2268

2369
```bash

ctf/__main__.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,14 @@ def terraform_binary() -> str:
9393

9494

9595
def init(args: argparse.Namespace) -> None:
96-
if os.path.isdir(os.path.join(args.path, "challenges")) or os.path.isdir(
97-
os.path.join(args.path, ".deploy")
98-
):
99-
LOG.error(f"Directory {args.path} is already initialized.")
96+
if (
97+
os.path.isdir(os.path.join(args.path, "challenges"))
98+
or os.path.isdir(os.path.join(args.path, ".deploy"))
99+
) and not args.force:
100+
LOG.error(
101+
f"Directory {args.path} is already initialized. Use --force to overwrite."
102+
)
103+
LOG.error(args.force)
100104
exit(code=1)
101105

102106
created_assets: list[str] = []
@@ -105,7 +109,7 @@ def init(args: argparse.Namespace) -> None:
105109
for asset in os.listdir(p := os.path.join(TEMPLATES_ROOT_DIRECTORY, "init")):
106110
dst_asset = os.path.join(args.path, asset)
107111
if os.path.isdir(src_asset := os.path.join(p, asset)):
108-
shutil.copytree(src_asset, dst_asset)
112+
shutil.copytree(src_asset, dst_asset, dirs_exist_ok=True)
109113
LOG.info(f"Created {dst_asset} folder")
110114
else:
111115
shutil.copy(src_asset, dst_asset)
@@ -1356,6 +1360,13 @@ def main():
13561360
default=CTF_ROOT_DIRECTORY,
13571361
help="Initialize the folder at the given path.",
13581362
)
1363+
parser_init.add_argument(
1364+
"--force",
1365+
"-f",
1366+
action="store_true",
1367+
default=False,
1368+
help="Overwrite the directory if it's already initialized.",
1369+
)
13591370

13601371
parser_flags = subparsers.add_parser(
13611372
"flags",
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
3+
"features": {
4+
"ghcr.io/robbert229/devcontainer-features/opentofu:1": {
5+
"version": "1.9.0"
6+
},
7+
"ghcr.io/devcontainers-extra/features/ansible:2": {}
8+
},
9+
"runArgs": [
10+
"--privileged",
11+
"--cap-add=SYS_PTRACE",
12+
"--security-opt", "seccomp=unconfined",
13+
"--cgroupns=host",
14+
"--pid=host",
15+
"--volume", "/dev:/dev",
16+
"--volume", "/lib/modules:/lib/modules:ro"
17+
]
18+
}

ctf/templates/init/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ challenges/*/terraform/versions.tf
1818
*.egg-info
1919

2020
.vscode/
21+
!.vscode/settings.json
22+
!.vscode/extensions.json
2123

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"recommendations": [
3+
"redhat.vscode-yaml",
4+
"hashicorp.terraform",
5+
"github.codespaces",
6+
"redhat.ansible"
7+
]
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"[yaml]": {
3+
"editor.tabSize": 2
4+
},
5+
"yaml.schemas": {
6+
"scripts/schemas/track.yaml.json": "challenges/**/track.yaml",
7+
"scripts/schemas/post.json": "challenges/*/posts/*.yaml"
8+
}
9+
}

0 commit comments

Comments
 (0)