Initial bootstrap: cpanel-importer sanitization sandbox
Skeleton for the cpanel-importer Docker container — a one-shot sandbox the WHP panel invokes BEFORE extracting a customer cpmove tarball. See cpanel-import-container-spec.md (in /workspace/) for the full design. What this ships in v1.0: - Dockerfile: almalinux:10-minimal + PHP 8.4 (Remi) + ClamAV 1.4 + SaneSecurity Foxhole.PHP rules + tar/mariadb-client/rsync. Runs as UID 999 (whp-import) via the panel-side --user 999:999 flag. - scripts/entrypoint.sh: validates env, runs (optional) freshclam, drives extract -> scan-files -> scan-dbs -> rsync -> report.json. - scripts/extract.sh + scripts/lib/scan-symlinks.php: pre-extract symlink scan ported standalone from web-files/libs/CpanelBackupImporter.php (the existing 2026-05-29 whp02 destruction-vector fix). Aborts with exit 3 before tar runs if any DANGEROUS symlink is found. - scripts/scan-files.php: ClamAV walk + classify-and-action. v1.0 ships with an empty cleaner registry — every hit is QUARANTINE_ONLY. Cleaner hooks are stubbed for v1.1. - scripts/scan-dbs.php: regex MyISAM -> InnoDB rewrite (always applied), WordPress identification, and ONE WP content scan check (siteurl_external_domain). v1.1 will grow the check set. - scripts/lib/safety-net.php: container-narrow open_basedir allow-list, much tighter than the panel-side one. - .gitea/workflows/build-push.yaml: builds + smoke-tests + PHP-syntax-checks + bash-syntax-checks before pushing to repo.anhonesthost.net/cloud-hosting-platform/cpanel-importer. - tests/build-fixtures.sh: builds cpmove-clean.tar.gz (benign WP dump) and cpmove-alfa.tar.gz (the ALFA-shell symlink-to-/etc vector) for local end-to-end testing. - README.md / CONTRIBUTING.md: docker-run invocation, bind-mount catalog, report.json schema, how to add a cleaner pattern or a WP scan signature. Local acceptance test results: - clean fixture -> status=completed, 3 MyISAM->InnoDB, no flags, 0 - ALFA fixture -> exit 1, status=failed, failed_stage=extract, "tarball contains dangerous symlinks; aborting" on stderr - compromised-siteurl fixture -> imported_into_new_server=false, .flagged file written, summary_for_panel.show_alert=true Image size: 197 MB compressed (gzipped docker save), ~397 MB unique layers extracted. Well under the spec's 600 MB compressed / 1.2 GB extracted budget. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
98
.gitea/workflows/build-push.yaml
Normal file
98
.gitea/workflows/build-push.yaml
Normal file
@@ -0,0 +1,98 @@
|
||||
name: cpanel-importer Build and Push
|
||||
run-name: ${{ gitea.actor }} pushed a change to trunk
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- trunk
|
||||
tags:
|
||||
- '20[0-9][0-9].[0-9][0-9].[0-9]+'
|
||||
|
||||
jobs:
|
||||
Build-and-Push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: repo.anhonesthost.net
|
||||
username: ${{ secrets.CI_USER }}
|
||||
password: ${{ secrets.CI_TOKEN }}
|
||||
|
||||
# Compute the version tag. If the commit is on a `YYYY.MM.NNN` tag
|
||||
# we tag the image with that version; otherwise we only tag :latest
|
||||
# and :<sha>.
|
||||
- name: Compute tags
|
||||
id: tags
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA="${GITHUB_SHA:0:12}"
|
||||
REG="repo.anhonesthost.net/cloud-hosting-platform/cpanel-importer"
|
||||
TAGS="${REG}:latest"$'\n'"${REG}:${SHA}"
|
||||
# If this push includes a YYYY.MM.NNN tag, add it.
|
||||
VER_TAG="${GITHUB_REF_NAME:-}"
|
||||
if [[ "${GITHUB_REF:-}" == refs/tags/* && "$VER_TAG" =~ ^20[0-9][0-9]\.[0-9][0-9]\.[0-9]+$ ]]; then
|
||||
TAGS="${TAGS}"$'\n'"${REG}:${VER_TAG}"
|
||||
fi
|
||||
echo "tags<<EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "$TAGS" >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "Resolved tags:"
|
||||
echo "$TAGS"
|
||||
|
||||
# First build locally (no push) so we can run a smoke test against
|
||||
# the resolved image before pushing. The build is cached by Buildx
|
||||
# so the push step below re-uses layers and is near-instant.
|
||||
- name: Build Image (local, for smoke test)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: false
|
||||
load: true
|
||||
tags: cpanel-importer:smoke
|
||||
no-cache: true
|
||||
|
||||
- name: Smoke test — image starts and `echo ok` works
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Override the entrypoint so we don't have to provide the full
|
||||
# IMPORT_* env set just to verify the image runs.
|
||||
out="$(docker run --rm --entrypoint /bin/echo cpanel-importer:smoke ok)"
|
||||
if [[ "$out" != "ok" ]]; then
|
||||
echo "smoke test failed: expected 'ok', got '$out'"
|
||||
exit 1
|
||||
fi
|
||||
echo "smoke test passed"
|
||||
|
||||
- name: PHP syntax check
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for f in scripts/*.php scripts/lib/*.php; do
|
||||
docker run --rm -v "$PWD:/src" --entrypoint php cpanel-importer:smoke -l "/src/$f"
|
||||
done
|
||||
|
||||
- name: Bash syntax check
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for f in scripts/*.sh; do
|
||||
docker run --rm -v "$PWD:/src" --entrypoint bash cpanel-importer:smoke -n "/src/$f"
|
||||
done
|
||||
|
||||
- name: Build and Push Image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ steps.tags.outputs.tags }}
|
||||
cache-from: type=registry,ref=repo.anhonesthost.net/cloud-hosting-platform/cpanel-importer:latest
|
||||
cache-to: type=inline
|
||||
Reference in New Issue
Block a user