- Technická specifikace požadavků na úlohu spusitelnou v platformě Haxagon
Systém Haxagon zprostředkovává soutěžícím přístup k soutěžním úlohám. Automatizovaně tyto úlohy spouští a vytváří vzdálené připojení pro soutěžící. Aby úloha byla nasaditelná v systému, je nutné aby splňovala určité technícké specifikace.
V systému Haxagon mohou být úlohy spouštěny pomocí dvou druhů virtualizace:
Vagrant je nástroj pro správu virtuálních strojů, který umožňuje vytvořit, spravovat a automatizovat virtuální prostředí. Je nutné použít libvirt provider pro spouštění úloh v systému Haxagon.
Docker-compose je nástroj pro spouštění a správu aplikací pomocí Dockeru. Umožňuje spouštět více kontejnerů jako jeden celek a řídit je pomocí jednoho konfiguračního souboru.
Formát úlohy je velice jednoduchý - stačí systému poskytnout následující set souborů v gitovém repozitáři:
challenge.yaml
- Soubor ve formátu YAML, který definuje základní parametry úlohy- potřebné zadání pro virtualizačního providera, který vytvoří infrastrukturu pro každou instanci úlohy (podle druhu virtualizace):
- Vagrantfile
- docker-compose.yaml
docker-compose.yaml
/docker-compose.yml
-DESCRIPTION.md
-Markdown soubor obsahující zadání pro plniteleHANDBOOK.md
- Markdown soubor obsahující obsah, který slouží jako příručka pro učitele
Tyto soubory jsou dále detailněji rozebírány v následujících sekcích
název parametru | popis parametru | příklad |
---|---|---|
title | pojmenování úlohy | Uživatelské oprávnění v linuxu |
description | relativní cesta k Markdown souboru, který obsahuje zadání úlohy | DESCRIPTION.md |
handbook | relativní cesta k Markdown souboru, který obsahuje příručku pro učitele | HANDBOOK.md |
flags | pole objektů definující vlajky, které budou součástí úlohy, platforma rozeznává 5 druhů vlajek, definujíse v systému následovně |
společné parametry objektů v poli flags:
název parametru | popis parametru | příklad |
---|---|---|
name | pojmenování vlajky | oprávnění souboru file-1 |
description | bližší info o úkolu | Změň oprávnění souboru ~/file-1 na 742 |
points | bodové ohodnocení vlajky | 20 |
identifier | unikátní (v rámci souboru challenge.yaml) identifikátor pro vlajku | file-perms-check1 |
type | číslo označující druh vlajky | "4" |
Každá instance má unikatně vygenerované vlajky, tak aby se zamezilo podvádění. Jejich unikátnost je zaručena vygenerováním náhodného řetězce, kterým jsou nahrazeny všechny výskyty placeholderu v repozitáři scénáře. Tak aby nedošlo k nechtěné záměně, jsou všechny místa určená k nahrazení ohraničena znaky #@{{
a }}@#
. Právě placeholder slouží autorovi úlohy k označení a odlišení jednotlivých míst. Při spuštění se tedy z #@{{vlajka1}}@#
stane např. haxagon{897316929176464ebc9ad085f31e7284}
a v jiné instanci s úplně stejným scénářem zase haxagon{99bd2e29f6b569bb880f601815cd77ef}
.
Pozor! K nahrazení řetězců docházi až v runtime instance. Tzn. Nahrazení probíhá těsně předtím, než systém zavolá
docker compose up
. Je potřeba rozdělitbuild
fázi kontejnerů a jejichruntime
fázi. Například tento kod v souboru Dockerfile některého z kontejnerů ulohy:RUN echo #@{{vlajka1}}@# > /tmp/test
, nesplní tížené očekávání, protože příkaz RUN v Dockerfile je spouštěn při buildění obrazů kontejnerů. Pro zpřístupnění dynamické vlajky vruntime
fázi kontejneru, je třeba vytvořit složku, v ní vytvořit soubor s placeholderem a tu složku namountovat pomocí docker compose definicevolumes
specifika pro objekt vlajky tohoto typu
název parametru | popis parametru | příklad |
---|---|---|
type | 1 | |
placeholder | zástupný řetězec znaků sloužící pro označení místa, do kterého se vloží unikátní vlajka pro instanci (viz. detailní popis níže) | flag2 |
maximumTries | maximální možný počet pokusů o odpověď | 3 |
Tento druh vlajek má ve všech instancích a pro všechny uživatele stejnou hodnotu
specifika pro objekt vlajky tohoto typu | název parametru | popis parametru | příklad
název parametru | popis parametru | příklad |
---|---|---|
type | 2 | |
answer | odpověď na úkol | flag{1234} |
maximumTries | maximální možný počet pokusů o odpověď | 3 |
specifika pro objekt vlajky tohoto typu | název parametru | popis parametru | příklad |
název parametru | popis parametru | příklad |
---|---|---|
type | 3 | |
maximumTries | maximální možný počet pokusů o odpověď | 3 |
options | pole objektů možných odpovědí | |
objekt odpovědi má následující strukturu: |
- value: "chybná odpověď"
correct: false
Pomocí docker compose exec
se v definovaném intervalu spouští command a podle exitCodu procesu se určí, zda-li byla vlajka splněna. container určuje, ve kterým z možných kontejnerů se proces spustí.
specifika pro objekt vlajky tohoto typu | název parametru | popis parametru | příklad
název parametru | popis parametru | příklad |
---|---|---|
type | 4 | |
command | příkaz, který se spustí pro ověření splnění úkolu | bash -c '[ "$(cat /tmp/test.txt)" == "ahoj" ]' |
container | cílový kontejner, ve kterém se příkaz bude spouštět | server |
shell | shell, ve kterém je příkaz spouštěn | sh |
user | uživatel, pod kterým je příkaz spoštěn | root |
internval | interval, ve kterém bude docházet ke spuštění příkazu | 2000 |
exitCode | v případě, že příkaz bude ukončet s touto hodnotou exit kodu, bude vlajka splněna | 0 |
ukázkový soubor challenge.yaml
title: Ukázková úloha
# relativni cesta k souboru obsahujici zadani ulohy
description: ./DESCRIPTION.md
# relativni cesta k souboru obsahujici ucitelskou prirucku k uloze
handbook: ./HANDBOOK.md
# vlajky, ktere jsou s ulohou spojeny
flags:
- name: Obsah souboru /tmp/file1
description:
points: 10
type: "1"
identifier: "file-content-1"
placeholder: flag1
maximumTries: 3
- name: Heslo uživatele adam
description: Najdi způsob jak získat heslo uživatele adam v plaintextu
points: 20
type: "2"
identifier: "password-dump1"
answer: flag{adamisbest}
maximumTries: 2
- name: Vyber správnou odpověď
description:
points: 30
type: "3"
identifier: "choice-flag1"
maximumTries: 2
options:
- value: správná odpověd
correct: true
- value: chybná odpověd
correct: false
- name: /tmp/test.txt
description: Do souboru /tmp/test.txt zapiš text "ahoj"
points: 30
type: "4"
identifier: "file-content-check1"
command: "`bash -c '[ "$(cat /tmp/test.txt)" == "ahoj" ]'`"
interval: 1000
container: "server"
exitCode: 0
Pomocí tohoto souboru jsme schopni definovat, jaká infrastruktura se pro úlohu spustí. Dokumentace formátu je dostupná zde: https://docs.docker.com/compose/
V souboru docker-compose není možné:
- eskalovat práva kontejneru:
- vytvářet privilegované kontejnery
- přidávat kontejnerům systémové schopnosti (SYStem capabilities)
- mountovat adresáře a soubory (vše potřebné by do kontejneru mělo být předáno v build fázi)
Je nutné systém informovat o tom, že scénář je spuštěný a vše je připraveno. Tuto informaci je možné systému sdělit tím, že do stdout entrypointu/commandu libovolného commandu definovaného v docker-compose souboru vypíšete řetězec "SCENARIO_IS_READY". Ukázkový docker-compose.yml:
version: "3"
services:
webserver:
image: nonbusybox
container_name: webserver
command: sh -c '/setup.sh && echo SCENARIO_IS_READY && sleep infinity'
ports:
- "80:80"
Vagrantfile
je konfigurační soubor pro Vagrant, který se používá k nastavení virtuálního prostředí pro vaši úlohu. Tento soubor by měl být umístěn ve stejné složce jako challenge.yaml
.
V Vagrantfile
může být nastaveno např.:
- image operačního systému, který se má použít pro virtuální stroj (např. Ubuntu nebo CentOS)
- konfigurace sítě pro virtuální stroj
- provisioning
- omezení prostředků (RAM, CPU, různé IO atp.)
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
# Nastavení libvirt provider
config.vm.provider :libvirt do |libvirt|
libvirt.driver = "kvm"
end
# Nastavení obrayu operačního systému pro virtuální stroj
config.vm.box = "ubuntu/focal64"
# Konfigurace privátní sítě pro virtuální stroj
config.vm.network "private_network", ip: "192.168.33.10"
# Nastavení RAM a CPU pro virtuální stroj
config.vm.provider "libvirt" do |vb|
vb.memory = "512"
vb.cpus = 1
end
# Konfigurace shell provisioningu
config.vm.provision "shell" do |s|
s.inline = <<-SHELL
apt-get update
apt-get install -y apache2
echo SCENARIO_IS_READY
SHELL
s.env = {
"VARIABLE_NAME" => "value"
}
end
end
Zadání úlohy by mělo splňovat následující konvence:
Obsah zadání se dělí na teoretickou část, kde jsou řešiteli předávány teoretické znalosti bez vazby na obsah úlohy a zadání. Tato konzistentní struktura napříč úlohami, kde v první řadě v nejdříve v souboru uvedena sekce # Teorie
a poté až sekce # Zadání
zlepšuje orientaci řešitelů a zadavatelů v úlohách.
příklad
## Teorie
### Enumerace neznámé sitě
informace o tom, jak funguje průzkum sítě, může obsahovat odkaz na asciinema.org, který bude vyrenderovat
## Zadání
Úvodní text zadání
### Přístup do úlohy
Potřebné informace k připojení se do úlohy. Např. port k SSH službě a přístupové údaje uživatele v systému. Může obsahovat placeholder %%INSTANCE_IP%%, ktery bude nahrazen IP adresou instance ulohy
### Vlajka č. 1: Domovský adresář
info k vlajce č. 1
### Vlajka č. 2
info k vlajce č. 2
- části textu obsahující nějakou technickou informaci (např. definici subnet -
192.168.40.0/24
, příkazyfind --name file
, parametr ---service-scan
atp.) zaobalíme do code highlight bloku pomocí znaku ` - konzistence termínů napříč zadáním a příručkou k úloze