BentoPDF for bau-pm, without third-party hosts (bau-pm BAU-119)
BentoPDF v2.8.8 built from source at its pinned commit with BentoPDF's own air-gapped build options, every module it would load from a CDN vendored and checked against vendor.lock, a Content-Security-Policy without a single host, digital-sign-pdf and timestamp-pdf disabled because both need a third party, and the hand-off glue on every page: the AGPL source link, the hand-in of a file from bau-pm through a one-minute cookie into sessionStorage, confirmed by the tool's reaction, the hand-back of a result from every window, and the signing tool's flatten switch locked on. The zero-host gate drives all 117 enabled tool pages in Chromium and fails on any request to another origin, any CSP violation, a CSP that names a host, and a disabled id that names no page. Green on this image (3 min 52 s on a private network), red on the stock upstream image (115 findings) and on a misspelled id. CI runs it on every push, the release workflow before it pushes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
47138ddfbe
19 files changed
+1733
No files matched your search
@@ -0,0 +1,49 @@
|
||||
# CI - every push to dev and every pull request: the scripts linted, the image
|
||||
# built from BentoPDF's pinned commit, and the gate this repository exists for
|
||||
# (README, "The gate"): a page may talk to its own origin and to nobody else.
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
|
||||
# Over stdin: the runner passes the host's Docker socket through, so a
|
||||
# mounted workspace would resolve on the host and be empty.
|
||||
- name: shellcheck
|
||||
run: |
|
||||
for f in scripts/*.sh; do
|
||||
echo "-- $f"
|
||||
docker run --rm -i koalaman/shellcheck:v0.11.0 - < "$f"
|
||||
done
|
||||
|
||||
# npm ci and the Vite build are the slow part, several minutes on the
|
||||
# shared runner; the vendor stage downloads about 60 MB from npm.
|
||||
- name: build the image
|
||||
timeout-minutes: 60
|
||||
run: docker build --build-arg VERSION="ci-${GITHUB_SHA::8}" -t bau-pm-pdf-tools:ci .
|
||||
|
||||
- name: build the gate
|
||||
timeout-minutes: 15
|
||||
run: docker build -f test/Dockerfile -t bau-pm-pdf-tools-gate:ci .
|
||||
|
||||
- name: gate - nothing leaves the origin
|
||||
timeout-minutes: 40
|
||||
run: |
|
||||
net="bau-pm-pdf-tools-ci-${GITHUB_RUN_ID}"
|
||||
docker network create "$net" >/dev/null
|
||||
trap 'docker rm -f -v "$net-app" >/dev/null 2>&1; docker network rm "$net" >/dev/null 2>&1' EXIT
|
||||
docker run -d --name "$net-app" --network "$net" --network-alias pdf-tools bau-pm-pdf-tools:ci >/dev/null
|
||||
for i in $(seq 1 30); do
|
||||
docker run --rm --network "$net" bau-pm-pdf-tools-gate:ci wget -q --spider http://pdf-tools:8080/ && break
|
||||
[ "$i" -eq 30 ] && { echo "the image never answered" >&2; exit 1; }
|
||||
sleep 2
|
||||
done
|
||||
docker run --rm --network "$net" bau-pm-pdf-tools-gate:ci node test/zero-hosts.mjs http://pdf-tools:8080
|
||||
@@ -0,0 +1,83 @@
|
||||
# Release - runs on semver tags (vMAJOR.MINOR.BUGFIX, cut on main after a
|
||||
# fast-forward from dev): the gates, the image built and checked like in CI,
|
||||
# pushed to the registry, and a Gitea release with the CHANGELOG section.
|
||||
# Required: secrets REGISTRY_USERNAME, REGISTRY_PASSWORD (instance-wide).
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v[0-9]+.[0-9]+.[0-9]+']
|
||||
|
||||
env:
|
||||
REGISTRY: ${{ vars.REGISTRY || 'hub.bundespruefstelle.ch' }}
|
||||
IMAGE: bau-pm/pdf-tools
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 150
|
||||
steps:
|
||||
# Full history, or the ancestry gate cannot answer and would pass.
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: derive version from tag
|
||||
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: gate - CHANGELOG has a section for this version
|
||||
run: |
|
||||
if ! grep -q "^## \[$VERSION\]" CHANGELOG.md; then
|
||||
echo "no '## [$VERSION]' section in CHANGELOG.md - cut it from [Unreleased] first" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: gate - the tag is on main
|
||||
run: |
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
|
||||
echo "$GITHUB_SHA is not an ancestor of main - fast-forward main before tagging" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: build the image
|
||||
timeout-minutes: 60
|
||||
run: |
|
||||
docker build --build-arg VERSION="v$VERSION" \
|
||||
-t "$REGISTRY/$IMAGE:$VERSION" -t "$REGISTRY/$IMAGE:latest" .
|
||||
|
||||
- name: gate - nothing leaves the origin
|
||||
timeout-minutes: 50
|
||||
run: |
|
||||
docker build -f test/Dockerfile -t bau-pm-pdf-tools-gate:release .
|
||||
net="bau-pm-pdf-tools-release-${GITHUB_RUN_ID}"
|
||||
docker network create "$net" >/dev/null
|
||||
trap 'docker rm -f -v "$net-app" >/dev/null 2>&1; docker network rm "$net" >/dev/null 2>&1' EXIT
|
||||
docker run -d --name "$net-app" --network "$net" --network-alias pdf-tools "$REGISTRY/$IMAGE:$VERSION" >/dev/null
|
||||
for i in $(seq 1 30); do
|
||||
docker run --rm --network "$net" bau-pm-pdf-tools-gate:release wget -q --spider http://pdf-tools:8080/ && break
|
||||
[ "$i" -eq 30 ] && { echo "the image never answered" >&2; exit 1; }
|
||||
sleep 2
|
||||
done
|
||||
docker run --rm --network "$net" bau-pm-pdf-tools-gate:release node test/zero-hosts.mjs http://pdf-tools:8080
|
||||
|
||||
- name: push
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" \
|
||||
| docker login "$REGISTRY" -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
|
||||
docker push "$REGISTRY/$IMAGE:$VERSION"
|
||||
docker push "$REGISTRY/$IMAGE:latest"
|
||||
echo "digest: $(docker inspect --format '{{index .RepoDigests 0}}' "$REGISTRY/$IMAGE:$VERSION")"
|
||||
|
||||
# Below 1.0.0 a release is a prerelease, derived from the tag.
|
||||
- name: create Gitea release with changelog notes
|
||||
run: |
|
||||
notes="$(awk -v ver="$VERSION" '$0 ~ "^## \\[" ver "\\]" {f=1; next} /^## \[/ {f=0} f' CHANGELOG.md)"
|
||||
payload="$(printf '%s' "$notes" | python3 -c 'import json,os,sys; v=os.environ["VERSION"]; print(json.dumps({"tag_name": "v" + v, "name": "bau-pm-pdf-tools v" + v, "body": sys.stdin.read().strip(), "prerelease": v.split(".")[0] == "0"}))')"
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" >/dev/null \
|
||||
&& echo "Gitea release v$VERSION created" \
|
||||
|| echo "release creation skipped (may already exist)"
|
||||
Reference in new issue
Block a user