harden build scripts and fix correctness issues from audit
Security & correctness fixes following the audit in REPORT.md.
- setup-hypervisor.sh: fix broken error handling — use curl -fsSL,
check failures properly, return valid exit codes, and fetch
/releases/latest (arch-aware) instead of the possibly-draft .[0]
- entrypoint.sh: quote "$@" and build --net conditionally so empty
NET_INTERFACE/NET_MAC don't yield "tap=,mac="
- image-updater: replace tight 3s retry loop with capped exponential
backoff + periodic pull instead of hammering the registry
- sshd: set PermitRootLogin prohibit-password explicitly (key-only root)
- vm.Dockerfile: copy only host private keys at mode 600 instead of
the whole secret/* glob (drops .gitkeep/.pub from /etc/ssh)
- Makefile: stop generating redundant _pub key files
- build-image.sh: detect failure via alpine-make-vm-image's real exit
status rather than grepping stdout for "ERROR"
- remove orphaned etc/alloy/config.alloy (service not installed)
- README: correct data.raw path
- add REPORT.md audit notes (H1/H2 accepted as out-of-scope)
This commit is contained in:
@@ -45,7 +45,6 @@ $(SECRET_FILES):
|
|||||||
ssh-keygen -t "$(KEYTYPE_$(notdir $@))" -f "$@" \
|
ssh-keygen -t "$(KEYTYPE_$(notdir $@))" -f "$@" \
|
||||||
-C "automatically generated bearcloud ssh key" \
|
-C "automatically generated bearcloud ssh key" \
|
||||||
-N ""
|
-N ""
|
||||||
ssh-keygen -y -f "$@" > "$@_pub"
|
|
||||||
|
|
||||||
secrets: $(SECRET_FILES)
|
secrets: $(SECRET_FILES)
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ Edit corresponding files in [image/overlay](./image/overlay) to customize VM beh
|
|||||||
# vim .env
|
# vim .env
|
||||||
# VM_OPTS="--no-cache" HY_OPTS="--no-cache" make
|
# VM_OPTS="--no-cache" HY_OPTS="--no-cache" make
|
||||||
# fallocate -l 128G ./data/data.raw
|
# fallocate -l 128G ./data/data.raw
|
||||||
# sgdisk -o -n 1:0:0 -t 1:8300 ./data.raw
|
# sgdisk -o -n 1:0:0 -t 1:8300 ./data/data.raw
|
||||||
# losetup -Pf ./data.raw
|
# losetup -Pf ./data/data.raw
|
||||||
# mkfs.ext4 /dev/loop0p1
|
# mkfs.ext4 /dev/loop0p1
|
||||||
# losetup -d /dev/loop0
|
# losetup -d /dev/loop0
|
||||||
|
|
||||||
|
|||||||
+12
-6
@@ -14,14 +14,18 @@ TMP=$(mktemp)
|
|||||||
modprobe nbd max_parts=8 && [ -e /dev/nbd0 ] || mknod /dev/nbd0 b 43 0
|
modprobe nbd max_parts=8 && [ -e /dev/nbd0 ] || mknod /dev/nbd0 b 43 0
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
rm $TMP
|
rm -f "$TMP" "${TMP}.rc"
|
||||||
}
|
}
|
||||||
|
|
||||||
trap cleanup INT TERM EXIT
|
trap cleanup INT TERM EXIT
|
||||||
|
|
||||||
# We use BIOS here to skip creating partitions
|
# We use BIOS here to skip creating partitions
|
||||||
|
|
||||||
alpine-make-vm-image \
|
RC_FILE="${TMP}.rc"
|
||||||
|
|
||||||
|
# Capture the real exit status of alpine-make-vm-image (the `| tee` pipeline
|
||||||
|
# would otherwise mask it behind tee's status; busybox ash has no PIPESTATUS).
|
||||||
|
{ alpine-make-vm-image \
|
||||||
--boot-mode "BIOS" \
|
--boot-mode "BIOS" \
|
||||||
--branch "$ALPINE_BRANCH" \
|
--branch "$ALPINE_BRANCH" \
|
||||||
--image-format "$IMAGE_FORMAT" \
|
--image-format "$IMAGE_FORMAT" \
|
||||||
@@ -33,9 +37,11 @@ alpine-make-vm-image \
|
|||||||
--script-chroot \
|
--script-chroot \
|
||||||
--packages "nftables curl docker openssh" \
|
--packages "nftables curl docker openssh" \
|
||||||
"$IMAGE_FILE" \
|
"$IMAGE_FILE" \
|
||||||
"$CONFIGURE_SH" | tee $TMP
|
"$CONFIGURE_SH"; echo $? > "$RC_FILE"; } 2>&1 | tee "$TMP"
|
||||||
|
|
||||||
if grep -q "ERROR" $TMP; then
|
rc=$(cat "$RC_FILE" 2>/dev/null || echo 1)
|
||||||
echo "BUILD FAILED"
|
|
||||||
exit 114514
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
echo "BUILD FAILED (alpine-make-vm-image exited $rc)"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
discovery.docker "local" {
|
|
||||||
host = "unix:///var/run/docker.sock"
|
|
||||||
refresh_interval = "5s"
|
|
||||||
}
|
|
||||||
|
|
||||||
discovery.relabel "docker" {
|
|
||||||
targets = discovery.docker.local.targets
|
|
||||||
|
|
||||||
//
|
|
||||||
// Container Name
|
|
||||||
//
|
|
||||||
rule {
|
|
||||||
source_labels = ["__meta_docker_container_name"]
|
|
||||||
regex = "/(.*)"
|
|
||||||
replacement = "$1"
|
|
||||||
target_label = "container"
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Docker Compose
|
|
||||||
//
|
|
||||||
rule {
|
|
||||||
source_labels = ["__meta_docker_container_label_com_docker_compose_service"]
|
|
||||||
target_label = "service"
|
|
||||||
}
|
|
||||||
|
|
||||||
rule {
|
|
||||||
source_labels = ["__meta_docker_container_label_com_docker_compose_project"]
|
|
||||||
target_label = "compose_project"
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Image
|
|
||||||
//
|
|
||||||
rule {
|
|
||||||
source_labels = ["__meta_docker_container_image"]
|
|
||||||
target_label = "image"
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// stdout / stderr
|
|
||||||
//
|
|
||||||
rule {
|
|
||||||
source_labels = ["__meta_docker_container_log_stream"]
|
|
||||||
target_label = "stream"
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Query 用
|
|
||||||
//
|
|
||||||
rule {
|
|
||||||
source_labels = ["__meta_docker_container_label_com_docker_compose_service"]
|
|
||||||
target_label = "job"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loki.process "docker" {
|
|
||||||
|
|
||||||
stage.static_labels {
|
|
||||||
values = {
|
|
||||||
node = env("NODE_NAME"),
|
|
||||||
environment = env("ENVIRONMENT"),
|
|
||||||
platform = "docker",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
forward_to = [loki.write.default.receiver]
|
|
||||||
}
|
|
||||||
|
|
||||||
loki.source.docker "local" {
|
|
||||||
host = "unix:///var/run/docker.sock"
|
|
||||||
|
|
||||||
targets = discovery.relabel.docker.output
|
|
||||||
|
|
||||||
refresh_interval = "5s"
|
|
||||||
|
|
||||||
forward_to = [
|
|
||||||
loki.process.docker.receiver,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
loki.write "default" {
|
|
||||||
|
|
||||||
endpoint {
|
|
||||||
url = env("LOKI_URL")
|
|
||||||
tenant_id = env("LOKI_TENANT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
KbdInteractiveAuthentication no
|
KbdInteractiveAuthentication no
|
||||||
PasswordAuthentication no
|
PasswordAuthentication no
|
||||||
PubkeyAuthentication yes
|
PubkeyAuthentication yes
|
||||||
|
# Root is the only account with an authorized_keys; allow key-based root login
|
||||||
|
# only (never password), and make the policy explicit rather than relying on
|
||||||
|
# the compile-time default.
|
||||||
|
PermitRootLogin prohibit-password
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
# Keep the workspace image fresh. Runs long-lived under supervise-daemon:
|
||||||
|
# pulls periodically, with capped exponential backoff on failure instead of a
|
||||||
|
# tight retry loop that hammers the registry.
|
||||||
|
set -u
|
||||||
|
|
||||||
until docker pull git.sfclub.cc/cloud/workspace-image:latest >/dev/null 2>&1; do
|
IMAGE="git.sfclub.cc/cloud/workspace-image:latest"
|
||||||
sleep 3
|
INTERVAL="${IMAGE_UPDATER_INTERVAL:-3600}" # seconds between successful pulls
|
||||||
|
MIN_DELAY="${IMAGE_UPDATER_MIN_DELAY:-5}" # initial retry delay on failure
|
||||||
|
MAX_DELAY="${IMAGE_UPDATER_MAX_DELAY:-300}" # cap on retry delay
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
delay="$MIN_DELAY"
|
||||||
|
until docker pull "$IMAGE" >/dev/null 2>&1; do
|
||||||
|
echo "image-updater: pull failed, retrying in ${delay}s" >&2
|
||||||
|
sleep "$delay"
|
||||||
|
delay=$((delay * 2))
|
||||||
|
[ "$delay" -gt "$MAX_DELAY" ] && delay="$MAX_DELAY"
|
||||||
|
done
|
||||||
|
echo "image-updater: pulled $IMAGE"
|
||||||
|
sleep "$INTERVAL"
|
||||||
done
|
done
|
||||||
|
|||||||
+14
-4
@@ -1,8 +1,18 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -euo pipefail
|
set -eu
|
||||||
|
|
||||||
API_SOCKET="/hy.socks"
|
API_SOCKET="/hy.socks"
|
||||||
|
|
||||||
|
# Build the --net argument, omitting empty fields so cloud-hypervisor can pick
|
||||||
|
# sensible defaults (auto-created tap / generated MAC) instead of getting
|
||||||
|
# "tap=,mac=".
|
||||||
|
NET_INTERFACE="${NET_INTERFACE:-}"
|
||||||
|
NET_MAC="${NET_MAC:-}"
|
||||||
|
NET_ARG="tap=${NET_INTERFACE}"
|
||||||
|
if [ -n "$NET_MAC" ]; then
|
||||||
|
NET_ARG="${NET_ARG},mac=${NET_MAC}"
|
||||||
|
fi
|
||||||
|
|
||||||
/usr/bin/cloud-hypervisor \
|
/usr/bin/cloud-hypervisor \
|
||||||
--kernel /boot/vmlinuz-virt --initramfs /boot/initramfs-virt \
|
--kernel /boot/vmlinuz-virt --initramfs /boot/initramfs-virt \
|
||||||
--disk path=/image/vm.raw,image_type=raw \
|
--disk path=/image/vm.raw,image_type=raw \
|
||||||
@@ -11,8 +21,8 @@ API_SOCKET="/hy.socks"
|
|||||||
--cmdline "root=/dev/vda rootfstype=ext4 modules=ext4a rw console=hvc0" \
|
--cmdline "root=/dev/vda rootfstype=ext4 modules=ext4a rw console=hvc0" \
|
||||||
--cpus boot=${CPU_COUNT:-4} \
|
--cpus boot=${CPU_COUNT:-4} \
|
||||||
--memory size=${MEMORY:-4G},shared=on \
|
--memory size=${MEMORY:-4G},shared=on \
|
||||||
--net "tap=$NET_INTERFACE,mac=$NET_MAC" \
|
--net "$NET_ARG" \
|
||||||
$@ &
|
"$@" &
|
||||||
|
|
||||||
CH_PID=$!
|
CH_PID=$!
|
||||||
|
|
||||||
@@ -24,4 +34,4 @@ _stop() {
|
|||||||
|
|
||||||
trap _stop TERM INT
|
trap _stop TERM INT
|
||||||
|
|
||||||
wait $CH_PID
|
wait $CH_PID
|
||||||
|
|||||||
+56
-14
@@ -1,22 +1,64 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
# Download and install the latest cloud-hypervisor + ch-remote static binaries.
|
||||||
|
set -eu
|
||||||
|
|
||||||
set -u
|
API="https://api.github.com/repos/cloud-hypervisor/cloud-hypervisor/releases/latest"
|
||||||
|
|
||||||
echo "fetching latest version of cloud-hypervisor"
|
# Pick the asset matching this architecture.
|
||||||
RESPONSE=$(curl https://api.github.com/repos/cloud-hypervisor/cloud-hypervisor/releases)
|
case "$(uname -m)" in
|
||||||
HYPERVISOR_URL=$(echo $RESPONSE | jq -r '.[0].assets.[] | select( .name == "cloud-hypervisor-static") | .browser_download_url')
|
x86_64|amd64)
|
||||||
CH_REMOTE_URL=$(echo $RESPONSE | jq -r '.[0].assets.[] | select( .name == "ch-remote-static") | .browser_download_url' )
|
ch_asset="cloud-hypervisor-static"
|
||||||
|
chremote_asset="ch-remote-static"
|
||||||
|
;;
|
||||||
|
aarch64|arm64)
|
||||||
|
ch_asset="cloud-hypervisor-static-aarch64"
|
||||||
|
chremote_asset="ch-remote-static-aarch64"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unsupported architecture: $(uname -m)" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
echo "fetching latest cloud-hypervisor release metadata"
|
||||||
echo "FAILED TO FETCH DOWNLOAD LINK OF CLOUD-HYPERVISOR-STATIC"
|
if ! RESPONSE=$(curl -fsSL "$API"); then
|
||||||
exit -1
|
echo "FAILED TO QUERY CLOUD-HYPERVISOR RELEASES API" >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -sLo /usr/bin/cloud-hypervisor "$HYPERVISOR_URL" && chmod +x /usr/bin/cloud-hypervisor && cloud-hypervisor --help >/dev/null 2>&1
|
HYPERVISOR_URL=$(printf '%s' "$RESPONSE" | jq -r --arg n "$ch_asset" \
|
||||||
|
'.assets[] | select(.name == $n) | .browser_download_url')
|
||||||
|
CH_REMOTE_URL=$(printf '%s' "$RESPONSE" | jq -r --arg n "$chremote_asset" \
|
||||||
|
'.assets[] | select(.name == $n) | .browser_download_url')
|
||||||
|
|
||||||
curl -sLo /usr/bin/ch-remote "$CH_REMOTE_URL" && chmod +x /usr/bin/ch-remote && ch-remote --help >/dev/null 2>&1
|
if [ -z "$HYPERVISOR_URL" ] || [ "$HYPERVISOR_URL" = "null" ] \
|
||||||
|
|| [ -z "$CH_REMOTE_URL" ] || [ "$CH_REMOTE_URL" = "null" ]; then
|
||||||
|
echo "FAILED TO RESOLVE DOWNLOAD URLS (asset missing for $(uname -m)?)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
# install_bin <url> <dest>
|
||||||
echo "FAILED TO DOWNLOAD CLOUD-HYPERVISOR or CLOUD-HYPERVISOR IS NOT EXECUTABLE. (wrong arch?)"
|
install_bin() {
|
||||||
exit -1
|
_url="$1"; _dest="$2"
|
||||||
fi
|
echo "downloading $_url"
|
||||||
|
if ! curl -fsSL -o "$_dest" "$_url"; then
|
||||||
|
echo "FAILED TO DOWNLOAD $_url" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
chmod +x "$_dest"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_bin "$HYPERVISOR_URL" /usr/bin/cloud-hypervisor
|
||||||
|
install_bin "$CH_REMOTE_URL" /usr/bin/ch-remote
|
||||||
|
|
||||||
|
# Sanity-check the binaries actually run on this platform.
|
||||||
|
if ! /usr/bin/cloud-hypervisor --version >/dev/null 2>&1; then
|
||||||
|
echo "cloud-hypervisor is not executable (wrong arch?)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! /usr/bin/ch-remote --version >/dev/null 2>&1; then
|
||||||
|
echo "ch-remote is not executable (wrong arch?)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "cloud-hypervisor installed"
|
||||||
|
|||||||
+7
-1
@@ -17,7 +17,13 @@ COPY .env /kitchen/.env
|
|||||||
RUN sh /kitchen/substitution.sh < /kitchen/.env
|
RUN sh /kitchen/substitution.sh < /kitchen/.env
|
||||||
COPY --from=bubble-builder --chmod=755 /build/daemon /kitchen/overlay/usr/bin/bubble
|
COPY --from=bubble-builder --chmod=755 /build/daemon /kitchen/overlay/usr/bin/bubble
|
||||||
COPY --from=bubble-builder --chmod=755 /build/auth_server /kitchen/overlay/usr/bin/auth-server
|
COPY --from=bubble-builder --chmod=755 /build/auth_server /kitchen/overlay/usr/bin/auth-server
|
||||||
COPY ./secret/* /kitchen/overlay/etc/ssh/
|
# Ship only the SSH host private keys (sshd derives the public halves), with
|
||||||
|
# strict perms — not the whole secret/ dir (which also holds .pub/.gitkeep).
|
||||||
|
COPY --chmod=600 \
|
||||||
|
secret/ssh_host_ed25519_key \
|
||||||
|
secret/ssh_host_ecdsa_key \
|
||||||
|
secret/ssh_host_rsa_key \
|
||||||
|
/kitchen/overlay/etc/ssh/
|
||||||
RUN --security=insecure \
|
RUN --security=insecure \
|
||||||
--mount=type=bind,from=host-modules,source=/,target=/lib/modules \
|
--mount=type=bind,from=host-modules,source=/,target=/lib/modules \
|
||||||
cd /kitchen && rm -f vm.raw && ALPINE_BRANCH="3.24" ./build-image.sh
|
cd /kitchen && rm -f vm.raw && ALPINE_BRANCH="3.24" ./build-image.sh
|
||||||
|
|||||||
Reference in New Issue
Block a user