Release builds the tag's commissioning image, without SSH keys
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 42s

build-image.sh copied the build user's authorized_keys into the image
unless told otherwise, so the images published with v0.2.0 and v0.2.1
let three lab keys in as root on commissioning machines. Keys are now
opt-in (AUTHKEYS=); the lab deploy passes the deploy user's keys.

The release workflow gets an image job: it builds the tag's agent/ on the
PXE server in a directory of its own (the lab image stays), refuses an
image with authorized_keys, and uploads it as commissioning-* (token
over stdin). Tried by hand on pxe-server: built, no keys, new agents.

Closes #29

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ilker ManapandClaude Opus 5.5 committed 2026-10-30 23:47:28 +01:00
1 parent 008fa3f66f
commit 554b7bffd8
4 files changed
+66 -3

No files matched your search

+2 -1
View File
@@ -80,7 +80,8 @@ jobs:
env: env:
HOST: ${{ vars.PXE_HOST }} HOST: ${{ vars.PXE_HOST }}
USER: ${{ vars.PXE_USER }} USER: ${{ vars.PXE_USER }}
run: ssh "$USER@$HOST" 'sudo /opt/rollcall/agent/build-image.sh 2>&1 | tail -20' # the lab image lets the deploy user's keys in as root (debugging); release images do not
run: ssh "$USER@$HOST" 'sudo env AUTHKEYS=$HOME/.ssh/authorized_keys /opt/rollcall/agent/build-image.sh 2>&1 | tail -20'
site: site:
needs: server needs: server
+53
View File
@@ -69,3 +69,56 @@ jobs:
-F "attachment=@$f" "$API/releases/$id/assets?name=$f" >/dev/null -F "attachment=@$f" "$API/releases/$id/assets?name=$f" >/dev/null
echo "uploaded $f" echo "uploaded $f"
done done
# The commissioning image of this tag: built with live-build on the PXE server
# (the runner cannot), in a directory of its own (the lab's live image and
# build stay as they are), without anyone's SSH keys, and uploaded to the
# release as commissioning-* for bootstrap.sh --image-url.
image:
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: SSH setup
env:
KEY: ${{ secrets.PXE_SSH_KEY }}
run: |
mkdir -p ~/.ssh && chmod 700 ~/.ssh
printf '%s\n' "$KEY" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
printf 'Host *\n StrictHostKeyChecking accept-new\n ServerAliveInterval 30\n' > ~/.ssh/config
- name: Build the image on the PXE server
env:
HOST: ${{ vars.PXE_HOST }}
USER: ${{ vars.PXE_USER }}
TAG: ${{ github.ref_name }}
run: |
set -eu
W=/var/tmp/rollcall-release-$TAG
ssh "$USER@$HOST" "sudo rm -rf $W && mkdir -p $W/src"
scp -q -r agent deploy/publish-image.sh "$USER@$HOST:$W/src/"
ssh "$USER@$HOST" "sudo env AUTHKEYS= BUILD=$W/build OUT=$W/image bash $W/src/agent/build-image.sh > $W/build.log 2>&1 || { tail -40 $W/build.log; exit 1; }; tail -5 $W/build.log"
# refuse an image that lets someone in (a listing that fails refuses too)
ssh "$USER@$HOST" "sudo unsquashfs -l $W/image/filesystem.squashfs > $W/files" ||
{ echo "::error::cannot list the image" >&2; exit 1; }
if ssh "$USER@$HOST" "grep -E '/\.ssh/authorized_keys' $W/files"; then
echo "::error::the image carries SSH authorized_keys" >&2; exit 1
fi
- name: Upload to the release
env:
HOST: ${{ vars.PXE_HOST }}
USER: ${{ vars.PXE_USER }}
TAG: ${{ github.ref_name }}
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eu
W=/var/tmp/rollcall-release-$TAG
# the token over stdin, not on a command line
printf '%s\n' "$TOKEN" | ssh "$USER@$HOST" "read -r GITEA_TOKEN && export GITEA_TOKEN && IMG=$W/image sh $W/src/publish-image.sh $TAG $API"
- name: Clean up
if: always()
env:
HOST: ${{ vars.PXE_HOST }}
USER: ${{ vars.PXE_USER }}
TAG: ${{ github.ref_name }}
run: ssh "$USER@$HOST" "sudo rm -rf /var/tmp/rollcall-release-$TAG" || true
+7
View File
@@ -22,6 +22,8 @@ publish a tag that has no section here.
address (DNS-published in `dns_view`, or IPAM-only), ownership by the `rollcall_machine` extensible address (DNS-published in `dns_view`, or IPAM-only), ownership by the `rollcall_machine` extensible
attribute (kept with `extattrs+`), leases, fixed addresses and ranges count as taken, delegated attribute (kept with `extattrs+`), leases, fixed addresses and ranges count as taken, delegated
networks by the `rollcall` attribute. Not tested against a real grid yet. networks by the `rollcall` attribute. Not tested against a real grid yet.
- The release workflow builds the tag's commissioning image on the PXE server and publishes it as
`commissioning-*` assets.
- Lab NetBox (`lab/netbox/install.sh`): a container on the lab segment for the NetBox drivers. - Lab NetBox (`lab/netbox/install.sh`): a container on the lab segment for the NetBox drivers.
- Fixed leases on the site's DHCP server for proxy-mode VLANs (`dhcp_reservations`), for Kea with - Fixed leases on the site's DHCP server for proxy-mode VLANs (`dhcp_reservations`), for Kea with
`host_cmds` (Kea 3.0+) and OpenWrt (rpcd). Rollcall's own leases are reconciled; foreign ones are `host_cmds` (Kea 3.0+) and OpenWrt (rpcd). Rollcall's own leases are reconciled; foreign ones are
@@ -49,6 +51,11 @@ publish a tag that has no section here.
### Security ### Security
A review of the whole code base; the fixes below. Upgrading: rerun `deploy/install.sh` on the A review of the whole code base; the fixes below. Upgrading: rerun `deploy/install.sh` on the
region and `deploy/install-rack.sh` on rack hosts (new dnsmasq layout, signed rack updates). region and `deploy/install-rack.sh` on rack hosts (new dnsmasq layout, signed rack updates).
- **Root SSH keys in published images.** `agent/build-image.sh` took
the build user's `authorized_keys` unless told otherwise, so the images published with v0.2.0 and
v0.2.1 let three lab keys (the maintainer's laptop, the CI deploy key, the lab hypervisor) log in as
root to machines while they commission. Keys are now opt-in (`AUTHKEYS=`, the lab deploy sets it),
and the release workflow builds the image of the tag itself and refuses one with `authorized_keys`.
- **Region file read (critical).** A rack controller could read any file on the region (CA key, - **Region file read (critical).** A rack controller could read any file on the region (CA key,
config) through `/api/v1/rack/binary?arch=../..`. `arch` is now a known architecture. config) through `/api/v1/rack/binary?arch=../..`. `arch` is now a known architecture.
- **Path traversal.** In `/api/v1/render/...` (admin) as well. - **Path traversal.** In `/api/v1/render/...` (admin) as well.
+4 -2
View File
@@ -6,7 +6,9 @@ SRC=$(cd "$(dirname "$0")" && pwd)
BUILD=${BUILD:-/srv/rollcall/build} BUILD=${BUILD:-/srv/rollcall/build}
OUT=${OUT:-/srv/rollcall/image} OUT=${OUT:-/srv/rollcall/image}
MIRROR=${MIRROR:-http://deb.debian.org/debian} MIRROR=${MIRROR:-http://deb.debian.org/debian}
AUTHKEYS=${AUTHKEYS:-/home/ilker/.ssh/authorized_keys} # root's SSH keys in the commissioning environment: none unless asked for (a lab
# debugging aid; a published image must not carry anyone's keys)
AUTHKEYS=${AUTHKEYS:-}
mkdir -p "$BUILD" "$OUT" mkdir -p "$BUILD" "$OUT"
cd "$BUILD" cd "$BUILD"
@@ -98,7 +100,7 @@ install -m 0755 "$SRC/rollcall-agent.py" config/includes.chroot/usr/local/bin/ro
install -m 0755 "$SRC/rollcall-deploy.py" config/includes.chroot/usr/local/bin/rollcall-deploy.py install -m 0755 "$SRC/rollcall-deploy.py" config/includes.chroot/usr/local/bin/rollcall-deploy.py
install -m 0644 "$SRC/rollcall-agent.service" config/includes.chroot/etc/systemd/system/rollcall-agent.service install -m 0644 "$SRC/rollcall-agent.service" config/includes.chroot/etc/systemd/system/rollcall-agent.service
printf 'ipmi_msghandler\nipmi_devintf\nipmi_si\nipmi_ssif\n' > config/includes.chroot/etc/modules-load.d/ipmi.conf printf 'ipmi_msghandler\nipmi_devintf\nipmi_si\nipmi_ssif\n' > config/includes.chroot/etc/modules-load.d/ipmi.conf
if [ -r "$AUTHKEYS" ]; then if [ -n "$AUTHKEYS" ] && [ -r "$AUTHKEYS" ]; then
install -m 0600 "$AUTHKEYS" config/includes.chroot/root/.ssh/authorized_keys install -m 0600 "$AUTHKEYS" config/includes.chroot/root/.ssh/authorized_keys
chmod 0700 config/includes.chroot/root/.ssh chmod 0700 config/includes.chroot/root/.ssh
fi fi