Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow official dojos to override challenge files for imported challenges #623

Merged
merged 3 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dojo_plugin/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ def solves(self, *, user=None, dojo=None, module=None, ignore_visibility=False,
@property
def path(self):
return (self.module.path / self.id
if not self.path_override else
if not self.path_override or (self.module.dojo.official and os.path.exists(self.module.path / self.id)) else
pathlib.Path(self.path_override))

@property
Expand Down
22 changes: 16 additions & 6 deletions dojo_plugin/utils/dojo.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,18 @@
)],
}],
Optional("pages", default=[]): [str],
Optional("files", default=[]): [
Optional("files", default=[]): [Or(
{
"type": "download",
"path": FILE_PATH_REGEX,
"url": FILE_URL_REGEX,
},
{
"type": "text",
"path": FILE_PATH_REGEX,
"content": str,
}
],
)],
})

def setdefault_name(entry):
Expand Down Expand Up @@ -182,16 +187,21 @@ def load_dojo_subyamls(data, dojo_dir):

def dojo_initialize_files(data, dojo_dir):
for dojo_file in data.get("files", []):
assert is_admin(), "yml-specified files support requires admin privileges"
rel_path = dojo_dir / dojo_file["path"]

abs_path = dojo_dir / rel_path
assert not abs_path.is_symlink(), f"{rel_path} is a symbolic link!"
if abs_path.exists():
continue
abs_path.parent.mkdir(parents=True, exist_ok=True)

if dojo_file["type"] == "download":
if abs_path.exists():
continue
assert is_admin(), f"LFS download support requires admin privileges"
abs_path.parent.mkdir(parents=True, exist_ok=True)
urllib.request.urlretrieve(dojo_file["url"], str(abs_path))
assert abs_path.stat().st_size >= 50*1024*1024, f"{rel_path} is small enough to fit into git ({abs_path.stat().st_size} bytes) --- put it in the repository!"
if dojo_file["type"] == "text":
with open(abs_path, "w") as o:
o.write(dojo_file["content"])

def dojo_from_dir(dojo_dir, *, dojo=None):
dojo_yml_path = dojo_dir / "dojo.yml"
Expand Down
6 changes: 6 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ def simple_award_dojo(admin_session):
def no_practice_challenge_dojo(admin_session):
return create_dojo_yml(open(TEST_DOJOS_LOCATION / "no_practice_challenge.yml").read(), session=admin_session)

@pytest.fixture(scope="session")
def import_override_dojo(admin_session):
rid = create_dojo_yml(open(TEST_DOJOS_LOCATION / "import_override.yml").read(), session=admin_session)
make_dojo_official(rid, admin_session)
return rid

@pytest.fixture(scope="session")
def no_import_challenge_dojo(admin_session):
rid = create_dojo_yml(open(TEST_DOJOS_LOCATION / "no_import_challenge.yml").read(), session=admin_session)
Expand Down
16 changes: 16 additions & 0 deletions test/dojos/import_override.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
id: intro-to-cybersecurity
type: topic
modules:
- id: test
challenges:
- id: test
import:
dojo: example
module: hello
challenge: apple
files:
- type: text
path: test/test/boom
content: |
#!/opt/pwn.college/bash
cat /flag
34 changes: 17 additions & 17 deletions test/local-tester.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,30 @@ cd $(dirname "${BASH_SOURCE[0]}")/..

function usage {
set +x
echo "Usage: $0 [-r DB_BACKUP ] [ -c DOJO_CONTAINER ] [ -T ]"
echo "Usage: $0 [-r DB_BACKUP ] [ -c DOJO_CONTAINER ] [ -D DOCKER_DIR ] [ -T ]"
echo ""
echo " -r db backup to restore (relative to dojo/data/backups)"
echo " -c the name of the dojo container (default: dojo-test)"
echo " -D use a blank data volume (builds everything from scratch)"
echo " -D specify a directory for /data/docker (to avoid rebuilds)"
echo " -T don't run tests"
exit
}

VOLUME_ARGS=("-v" "$PWD:/opt/pwn.college" "-v" "$PWD/data:/data:shared")
WORKDIR=$(mktemp -d /tmp/dojo-test-XXXXXX)

VOLUME_ARGS=("-v" "$PWD:/opt/pwn.college" "-v" "$WORKDIR:/data:shared")
ENV_ARGS=( )
DB_RESTORE=""
DOJO_CONTAINER=dojo-test
TEST=yes
while getopts "r:c:he:TD" OPT
DOCKER_DIR=""
while getopts "r:c:he:TD:" OPT
do
case $OPT in
r) DB_RESTORE="$OPTARG" ;;
c) DOJO_CONTAINER="$OPTARG" ;;
T) TEST=no ;;
D)
DATA_DIR=$(mktemp -d)
VOLUME_ARGS[3]="$DATA_DIR:/data:shared"
;;
D) DOCKER_DIR="$OPTARG" ;;
e) ENV_ARGS+=("-e" "$OPTARG") ;;
h) usage ;;
?)
Expand All @@ -38,23 +38,24 @@ do
done
shift $((OPTIND-1))

[ "${#VOLUME_ARGS[@]}" -eq 2 ] && VOLUME_ARGS+=(
"-v" "/data/dojos"
"-v" "/data/mysql"
)

export DOJO_CONTAINER
docker kill "$DOJO_CONTAINER" 2>/dev/null || echo "No $DOJO_CONTAINER container to kill."
docker rm "$DOJO_CONTAINER" 2>/dev/null || echo "No $DOJO_CONTAINER container to remove."
while docker ps -a | grep "$DOJO_CONTAINER"; do sleep 1; done

# freaking bad unmount
sleep 1
mount | grep $PWD | sed -e "s/.* on //" | sed -e "s/ .*//" | tac | while read ENTRY
mount | grep /tmp/dojo-test- | sed -e "s/.* on //" | sed -e "s/ .*//" | tac | while read ENTRY
do
sudo umount "$ENTRY"
done

if [ -n "$DOCKER_DIR" ]
then
VOLUME_ARGS+=( "-v" "$DOCKER_DIR:/data/docker" )
sudo rm -rf $DOCKER_DIR/{containers,volumes}
fi

docker run --rm --privileged -d "${VOLUME_ARGS[@]}" "${ENV_ARGS[@]}" -p 2222:22 -p 80:80 -p 443:443 --name "$DOJO_CONTAINER" dojo || exit 1

# fix the insane routing thing
Expand All @@ -73,8 +74,7 @@ then
fi

until curl -Ls localhost.pwn.college | grep -q pwn; do sleep 1; done
# fix up the data permissions and git
sudo chown "$USER:$USER" "$PWD/data"
git checkout "$PWD/data/.gitkeep" || true

docker exec "$DOJO_CONTAINER" docker tag pwncollege-challenge pwncollege/challenge-legacy:latest

[ "$TEST" == "yes" ] && MOZ_HEADLESS=1 pytest -v test/test_running.py test/test_welcome.py
11 changes: 11 additions & 0 deletions test/test_running.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,17 @@ def test_lfs(lfs_dojo, random_user):
except subprocess.CalledProcessError:
assert False, "LFS didn't create dojo.txt"

@pytest.mark.dependency(depends=["test_join_dojo"])
def test_import_override(import_override_dojo, random_user):
uid, session = random_user
assert session.get(f"{DOJO_URL}/dojo/{import_override_dojo}/join/").status_code == 200
start_challenge(import_override_dojo, "test", "test", session=session)
try:
workspace_run("[ -f '/challenge/boom' ]", user=uid)
workspace_run("[ ! -f '/challenge/apple' ]", user=uid)
except subprocess.CalledProcessError:
assert False, "dojo_initialize_files didn't create /challenge/boom"

@pytest.mark.dependency(depends=["test_join_dojo"])
def test_no_import(no_import_challenge_dojo, admin_session):
try:
Expand Down
Loading