Skip to content

Commit 85e128a

Browse files
committed
feat: add confine base image
1 parent 17ea7cc commit 85e128a

9 files changed

Lines changed: 894 additions & 1 deletion

File tree

.github/copilot-instructions.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# GZCTF Challenge Base Images
2+
3+
This repository provides **minimal Alpine-based Docker images** for CTF challenges used with GZCTF platform. Images are published to `ghcr.io/gzctf-org/challenge-base/{name}:{tag}`.
4+
5+
## Architecture
6+
7+
### Base Images Structure
8+
- **`base/{name}/`**: Each directory contains Dockerfiles and `config.json` defining build variants
9+
- **Multi-variant pattern**: Single base can generate multiple tagged images (e.g., `python:alpine`, `python:3.11-alpine`, `python:3.12-alpine`)
10+
- **Alpine-first**: All images target minimal size using Alpine Linux as foundation
11+
12+
### Config-Driven Workflow System
13+
GitHub Actions workflows are **auto-generated** from `config.json` files using `scripts/gen_action.py`:
14+
15+
```json
16+
{
17+
"action_name": "Python 3.12", // Workflow display name
18+
"action_file": "python-3.12", // Output: .github/workflows/base.python-3.12.yml
19+
"dockerfile": "Dockerfile.3.12", // Which Dockerfile to build
20+
"tag": "3.12-alpine", // Docker tag
21+
"tier": 1, // Update tier (1 or 2)
22+
"support_multi_arch": true // Enable arm64 builds
23+
}
24+
```
25+
26+
**Critical**: Never manually edit `.github/workflows/base.*.yml` - regenerate via `python scripts/gen_action.py`
27+
28+
## Key Workflows
29+
30+
### Adding New Base Image
31+
1. Create `base/{name}/` directory with:
32+
- `Dockerfile` (or variant-specific `Dockerfile.{version}`)
33+
- `config.json` with array of build configs
34+
- Optional: `README.md` with usage example
35+
2. Run: `python scripts/gen_action.py` to generate workflows
36+
3. Commit both `base/{name}/` and generated `.github/workflows/base.{name}.yml`
37+
38+
### Triggering Rebuilds
39+
- **Tier system**: Images are organized into tier 1 and tier 2 based on dependencies
40+
- **Tier 1**: Base images with no internal dependencies (e.g., socat, readflag, language runtimes)
41+
- **Tier 2**: Images that depend on tier 1 images (e.g., confine depends on socat, php-mariadb depends on php)
42+
- Tier separation ensures builds happen in correct order: tier 1 completes before tier 2 starts
43+
- Update trigger: `python scripts/update.py -t {1|2} -c` creates timestamp in `updates/tier{n}.md`
44+
- All workflows watch their tier file - updating it triggers rebuilds of all images in that tier
45+
46+
### Multi-Architecture Builds
47+
- Set `"support_multi_arch": true` in config.json for `linux/amd64,linux/arm64`
48+
- Disable for architecture-specific binaries (see `readflag` using x86 assembly)
49+
50+
## Project Conventions
51+
52+
### CTF Challenge Pattern
53+
Images follow GZCTF conventions:
54+
- User `ctf` (UID varies by base image)
55+
- Working directory `/home/ctf`
56+
- `init.sh` script receives `$GZCTF_FLAG` env var, writes to file, unsets var
57+
- Challenge runs via socat or web server
58+
59+
Example from `base/python/src/init.sh`:
60+
```bash
61+
echo $GZCTF_FLAG > /home/ctf/flag
62+
chmod 444 /home/ctf/flag
63+
unset GZCTF_FLAG
64+
socat TCP-LISTEN:1337,reuseaddr,fork EXEC:"python3 challenge.py"
65+
```
66+
67+
### Dependency Management
68+
- Workflows auto-watch: `base/{name}/**`, `updates/tier{n}.md`, own workflow file
69+
- Dependencies added via `config.json["depends_on"]` array for cross-image deps
70+
71+
### Image Lifecycle
72+
- **Auto-cleanup**: `dataaxiom/ghcr-cleanup-action` prunes untagged/ghost/partial images after each build
73+
- **Concurrency**: Cleanup jobs use `group: cleanup-images-{name}` to prevent conflicts
74+
75+
## Common Tasks
76+
77+
**Test config changes locally**: `python scripts/gen_action.py` and inspect generated YAML
78+
**Force rebuild all tier 1 images**: `python scripts/update.py -t 1 -c && git push`
79+
**Check existing variants**: Review `base/{name}/config.json` for tag/Dockerfile mappings
80+
**Debug workflow**: Check `base/{name}/**` paths in generated workflow match actual files

.github/workflows/base.confine.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Confine
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
paths:
7+
- "socat"
8+
- "base/confine/**"
9+
- "updates/tier2.md"
10+
- ".github/workflows/base.confine.yml"
11+
workflow_dispatch:
12+
13+
env:
14+
REGISTRY: ghcr.io
15+
NAME: confine
16+
TAG: alpine
17+
18+
jobs:
19+
build-and-push-image:
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
packages: write
24+
25+
steps:
26+
- name: Checkout repository
27+
uses: actions/checkout@v4
28+
29+
- name: Set up QEMU
30+
uses: docker/setup-qemu-action@v3
31+
32+
- name: Set up Docker Buildx
33+
uses: docker/setup-buildx-action@v3
34+
35+
- name: Log in to the Container registry
36+
uses: docker/login-action@v3
37+
with:
38+
registry: ${{ env.REGISTRY }}
39+
username: ${{ github.actor }}
40+
password: ${{ secrets.GITHUB_TOKEN }}
41+
42+
- name: Extract metadata (tags, labels) for Docker
43+
id: meta
44+
uses: docker/metadata-action@v5
45+
with:
46+
images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.NAME }}
47+
tags: |
48+
${{ env.TAG }}
49+
50+
- name: Build and push Docker image
51+
uses: docker/build-push-action@v5
52+
with:
53+
context: base/${{ env.NAME }}
54+
file: base/${{ env.NAME }}/Dockerfile
55+
platforms: linux/amd64,linux/arm64
56+
tags: ${{ steps.meta.outputs.tags }}
57+
labels: ${{ steps.meta.outputs.labels }}
58+
push: true
59+
60+
cleanup-images:
61+
runs-on: ubuntu-latest
62+
needs: build-and-push-image
63+
permissions:
64+
contents: read
65+
packages: write
66+
concurrency:
67+
group: cleanup-images-confine
68+
steps:
69+
- name: Get lower case repository name
70+
id: lower_repo
71+
run: |
72+
export REPO_NAME=${{ github.event.repository.name }}
73+
echo "repo_name=${REPO_NAME@L}" >> $GITHUB_OUTPUT
74+
75+
- name: Prune old packages
76+
uses: dataaxiom/ghcr-cleanup-action@v1
77+
with:
78+
token: ${{ secrets.GITHUB_TOKEN }}
79+
package: ${{ steps.lower_repo.outputs.repo_name }}/${{ env.NAME }}
80+
dry-run: false
81+
delete-untagged: true
82+
delete-ghost-images: true
83+
delete-partial-images: true

.github/workflows/base.echo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Echo
1+
name: Echo - Example
22

33
on:
44
push:

base/confine/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM ghcr.io/gzctf/challenge-base/socat:alpine
2+
3+
RUN apk add --no-cache libcap
4+
5+
COPY --chmod=755 src/init.sh /init.sh
6+
COPY --chmod=755 src/pwn /home/ctf/pwn
7+
8+
ENTRYPOINT ["/init.sh"]

0 commit comments

Comments
 (0)