diff --git a/.github/workflows/zoonk.yml b/.github/workflows/zoonk.yml new file mode 100644 index 0000000..004a32c --- /dev/null +++ b/.github/workflows/zoonk.yml @@ -0,0 +1,108 @@ +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 + + 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"