From 7297ba3aeba2cef546c9245b2b6ae1139568cf40 Mon Sep 17 00:00:00 2001 From: Bruce Ashfield Date: Mon, 9 Feb 2026 03:17:37 +0000 Subject: vcontainer: add secure registry support with virtio-9p CA transport Enable vdkr/vcontainer to pull from TLS-secured registries by transporting the CA certificate via virtio-9p shared folder. vcontainer-common.sh: Add --secure-registry, --ca-cert, --registry-user, --registry-password CLI options. Auto-detect bundled CA cert at registry/ca.crt in the tarball and enable secure mode automatically. vrunner.sh: Copy CA cert to the virtio-9p shared folder for both daemon and non-daemon modes. Fix daemon mode missing _9p=1 kernel cmdline parameter which prevented the init script from mounting the shared folder. vdkr-init.sh: Read CA cert from /mnt/share/ca.crt (virtio-9p) instead of base64-decoding from kernel cmdline (which caused truncation for large certificates). Install cert to /etc/docker/certs.d/{host}/ca.crt for Docker TLS verification. Support optional credential passing for authenticated registries. vcontainer-tarball.bb: Add script files to SRC_URI for proper file tracking and rebuild triggers. Signed-off-by: Bruce Ashfield --- .../vcontainer/files/vcontainer-common.sh | 34 ++++++ recipes-containers/vcontainer/files/vdkr-init.sh | 127 ++++++++++++++++++++- recipes-containers/vcontainer/files/vrunner.sh | 63 ++++++++++ .../vcontainer/vcontainer-tarball.bb | 28 +++++ 4 files changed, 246 insertions(+), 6 deletions(-) (limited to 'recipes-containers') diff --git a/recipes-containers/vcontainer/files/vcontainer-common.sh b/recipes-containers/vcontainer/files/vcontainer-common.sh index 792bd7a2..cb48e1bb 100755 --- a/recipes-containers/vcontainer/files/vcontainer-common.sh +++ b/recipes-containers/vcontainer/files/vcontainer-common.sh @@ -752,6 +752,12 @@ build_runner_args() { args+=("--insecure-registry" "$reg") done + # Add secure registry options + [ "$SECURE_REGISTRY" = "true" ] && args+=("--secure-registry") + [ -n "$CA_CERT" ] && args+=("--ca-cert" "$CA_CERT") + [ -n "$REGISTRY_USER" ] && args+=("--registry-user" "$REGISTRY_USER") + [ -n "$REGISTRY_PASS" ] && args+=("--registry-pass" "$REGISTRY_PASS") + echo "${args[@]}" } @@ -767,9 +773,21 @@ DISABLE_KVM="false" NO_DAEMON="false" REGISTRY="" INSECURE_REGISTRIES=() +SECURE_REGISTRY="false" +CA_CERT="" +REGISTRY_USER="" +REGISTRY_PASS="" COMMAND="" COMMAND_ARGS=() +# Auto-detect bundled CA certificate for secure registry +# If CA cert is bundled in the tarball, automatically enable secure mode +BUNDLED_CA_CERT="$SCRIPT_DIR/registry/ca.crt" +if [ -f "$BUNDLED_CA_CERT" ]; then + SECURE_REGISTRY="true" + CA_CERT="$BUNDLED_CA_CERT" +fi + while [ $# -gt 0 ]; do case $1 in --arch|-a) @@ -840,6 +858,22 @@ while [ $# -gt 0 ]; do INSECURE_REGISTRIES+=("$2") shift 2 ;; + --secure-registry) + SECURE_REGISTRY="true" + shift + ;; + --ca-cert) + CA_CERT="$2" + shift 2 + ;; + --registry-user) + REGISTRY_USER="$2" + shift 2 + ;; + --registry-password|--registry-pass) + REGISTRY_PASS="$2" + shift 2 + ;; -it|--interactive) INTERACTIVE="true" shift diff --git a/recipes-containers/vcontainer/files/vdkr-init.sh b/recipes-containers/vcontainer/files/vdkr-init.sh index 084a8791..a993aca4 100755 --- a/recipes-containers/vcontainer/files/vdkr-init.sh +++ b/recipes-containers/vcontainer/files/vdkr-init.sh @@ -22,8 +22,12 @@ # docker_network=1 Enable networking (configure eth0, DNS) # docker_registry= Default registry for unqualified images (e.g., 10.0.2.2:5000/yocto) # docker_insecure_registry= Mark registry as insecure (HTTP). Can repeat. +# docker_registry_secure=1 Enable TLS verification for registry +# docker_registry_ca=1 CA certificate available in /mnt/share/ca.crt +# docker_registry_user= Registry username for authentication +# docker_registry_pass= Base64-encoded registry password # -# Version: 2.4.0 +# Version: 2.5.0 # Set runtime-specific parameters before sourcing common code VCONTAINER_RUNTIME_NAME="vdkr" @@ -31,13 +35,21 @@ VCONTAINER_RUNTIME_CMD="docker" VCONTAINER_RUNTIME_PREFIX="docker" VCONTAINER_STATE_DIR="/var/lib/docker" VCONTAINER_SHARE_NAME="vdkr_share" -VCONTAINER_VERSION="2.4.0" +VCONTAINER_VERSION="2.5.0" # Docker-specific: default registry for unqualified image names # Set via kernel param: docker_registry=10.0.2.2:5000/yocto # Or baked into rootfs: /etc/vdkr/registry.conf DOCKER_DEFAULT_REGISTRY="" +# Secure registry mode (TLS verification) +# Set via kernel param: docker_registry_secure=1 +# CA cert passed via: virtio-9p share at /mnt/share/ca.crt +DOCKER_REGISTRY_SECURE="" +DOCKER_REGISTRY_CA="" +DOCKER_REGISTRY_USER="" +DOCKER_REGISTRY_PASS="" + # Source common init functions # When installed as /init, common file is at /vcontainer-init-common.sh . /vcontainer-init-common.sh @@ -56,6 +68,97 @@ load_registry_config() { fi } +# Parse secure registry settings from kernel cmdline +parse_secure_registry_config() { + # Check for secure mode flag + GREP_RESULT=$(grep -o 'docker_registry_secure=[^ ]*' /proc/cmdline 2>/dev/null || true) + if [ -n "$GREP_RESULT" ]; then + DOCKER_REGISTRY_SECURE=$(echo "$GREP_RESULT" | sed 's/docker_registry_secure=//') + log "Secure registry mode: $DOCKER_REGISTRY_SECURE" + fi + + # Check for CA certificate in shared folder (passed via virtio-9p) + if [ -f "/mnt/share/ca.crt" ]; then + DOCKER_REGISTRY_CA="/mnt/share/ca.crt" + log "Found CA certificate in shared folder" + fi + + # Check for registry user + GREP_RESULT=$(grep -o 'docker_registry_user=[^ ]*' /proc/cmdline 2>/dev/null || true) + if [ -n "$GREP_RESULT" ]; then + DOCKER_REGISTRY_USER=$(echo "$GREP_RESULT" | sed 's/docker_registry_user=//') + log "Registry user: $DOCKER_REGISTRY_USER" + fi + + # Check for registry password (base64 encoded) + GREP_RESULT=$(grep -o 'docker_registry_pass=[^ ]*' /proc/cmdline 2>/dev/null || true) + if [ -n "$GREP_RESULT" ]; then + DOCKER_REGISTRY_PASS=$(echo "$GREP_RESULT" | sed 's/docker_registry_pass=//') + log "Received registry password from cmdline" + fi +} + +# Install CA certificate for secure registry +# Creates /etc/docker/certs.d/{registry}/ca.crt +install_registry_ca() { + if [ "$DOCKER_REGISTRY_SECURE" != "1" ]; then + return 0 + fi + + if [ -z "$DOCKER_DEFAULT_REGISTRY" ]; then + log "WARNING: Secure mode enabled but no registry configured" + return 0 + fi + + # Extract registry host (strip path/namespace) + local registry_host=$(echo "$DOCKER_DEFAULT_REGISTRY" | cut -d'/' -f1) + + # Install CA cert if provided via shared folder + if [ -n "$DOCKER_REGISTRY_CA" ] && [ -f "$DOCKER_REGISTRY_CA" ]; then + local cert_dir="/etc/docker/certs.d/$registry_host" + mkdir -p "$cert_dir" + + # Copy CA cert from shared folder + if cp "$DOCKER_REGISTRY_CA" "$cert_dir/ca.crt" 2>/dev/null && [ -s "$cert_dir/ca.crt" ]; then + log "Installed CA certificate: $cert_dir/ca.crt" + else + log "WARNING: Failed to copy CA certificate from $DOCKER_REGISTRY_CA" + rm -f "$cert_dir/ca.crt" + fi + else + # Check if CA cert exists from baked rootfs + local cert_dir="/etc/docker/certs.d/$registry_host" + if [ -f "$cert_dir/ca.crt" ]; then + log "Using baked CA certificate: $cert_dir/ca.crt" + else + log "WARNING: Secure mode enabled but no CA certificate available" + fi + fi + + # Setup Docker auth if credentials provided + if [ -n "$DOCKER_REGISTRY_USER" ] && [ -n "$DOCKER_REGISTRY_PASS" ]; then + local password=$(echo "$DOCKER_REGISTRY_PASS" | base64 -d 2>/dev/null) + if [ -n "$password" ]; then + mkdir -p /root/.docker + # Create auth config + local auth=$(echo -n "$DOCKER_REGISTRY_USER:$password" | base64 | tr -d '\n') + cat > /root/.docker/config.json << EOF +{ + "auths": { + "$registry_host": { + "auth": "$auth" + } + } +} +EOF + chmod 600 /root/.docker/config.json + log "Configured Docker auth for: $registry_host" + else + log "WARNING: Failed to decode registry password" + fi + fi +} + # ============================================================================ # Docker-Specific Functions # ============================================================================ @@ -141,10 +244,16 @@ start_dockerd() { if [ -n "$DOCKER_DEFAULT_REGISTRY" ]; then # Extract host:port for insecure registry config (strip path/namespace) REGISTRY_HOST=$(echo "$DOCKER_DEFAULT_REGISTRY" | cut -d'/' -f1) - # Auto-add to insecure registries if it looks like a local/private registry - if echo "$REGISTRY_HOST" | grep -qE '^(localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)'; then - DOCKER_OPTS="$DOCKER_OPTS --insecure-registry=$REGISTRY_HOST" - log "Auto-added insecure registry: $REGISTRY_HOST" + + # In secure mode, DO NOT add to insecure-registries (use TLS verification) + if [ "$DOCKER_REGISTRY_SECURE" = "1" ]; then + log "Secure mode: using TLS verification for $REGISTRY_HOST" + else + # Auto-add to insecure registries if it looks like a local/private registry + if echo "$REGISTRY_HOST" | grep -qE '^(localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)'; then + DOCKER_OPTS="$DOCKER_OPTS --insecure-registry=$REGISTRY_HOST" + log "Auto-added insecure registry: $REGISTRY_HOST" + fi fi fi @@ -569,6 +678,12 @@ configure_networking # Load baked registry config (can be overridden by kernel cmdline) load_registry_config +# Parse secure registry settings from kernel cmdline +parse_secure_registry_config + +# Install CA certificate for secure registry +install_registry_ca + # Start containerd and dockerd (Docker-specific) start_containerd start_dockerd diff --git a/recipes-containers/vcontainer/files/vrunner.sh b/recipes-containers/vcontainer/files/vrunner.sh index 4e99cba7..22e9229a 100755 --- a/recipes-containers/vcontainer/files/vrunner.sh +++ b/recipes-containers/vcontainer/files/vrunner.sh @@ -311,6 +311,10 @@ PORT_FORWARDS=() # Registry configuration DOCKER_REGISTRY="" INSECURE_REGISTRIES=() +SECURE_REGISTRY="false" +CA_CERT="" +REGISTRY_USER="" +REGISTRY_PASS="" # Batch import mode BATCH_IMPORT="false" @@ -381,6 +385,26 @@ while [ $# -gt 0 ]; do INSECURE_REGISTRIES+=("$2") shift 2 ;; + --secure-registry) + # Enable TLS verification for registry + SECURE_REGISTRY="true" + shift + ;; + --ca-cert) + # Path to CA certificate for TLS verification + CA_CERT="$2" + shift 2 + ;; + --registry-user) + # Registry username + REGISTRY_USER="$2" + shift 2 + ;; + --registry-pass) + # Registry password + REGISTRY_PASS="$2" + shift 2 + ;; --interactive|-it) INTERACTIVE="true" shift @@ -1153,6 +1177,22 @@ for reg in "${INSECURE_REGISTRIES[@]}"; do KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_insecure_registry=$reg" done +# Secure registry mode (TLS verification) +# CA certificate is passed via virtio-9p share, not kernel cmdline (too large) +if [ "$SECURE_REGISTRY" = "true" ]; then + KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_registry_secure=1" +fi + +# Registry credentials +if [ -n "$REGISTRY_USER" ]; then + KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_registry_user=$REGISTRY_USER" +fi +if [ -n "$REGISTRY_PASS" ]; then + # Base64 encode the password to handle special characters + REGISTRY_PASS_B64=$(echo -n "$REGISTRY_PASS" | base64 -w0) + KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_registry_pass=$REGISTRY_PASS_B64" +fi + # Tell init script if interactive mode if [ "$INTERACTIVE" = "true" ]; then KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_interactive=1" @@ -1246,6 +1286,8 @@ if [ "$DAEMON_MODE" = "start" ]; then # Use security_model=none for simplest file sharing (no permission mapping) # This allows writes from container (running as root) to propagate to host QEMU_OPTS="$QEMU_OPTS -virtfs local,path=$DAEMON_SHARE_DIR,mount_tag=$SHARE_TAG,security_model=none,id=$SHARE_TAG" + # Tell init script to mount the share + KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" # Add virtio-serial device for command channel # Using virtserialport creates /dev/vport0p1 in guest, host sees unix socket @@ -1298,6 +1340,12 @@ if [ "$DAEMON_MODE" = "start" ]; then fi fi + # Copy CA certificate to shared folder (too large for kernel cmdline) + if [ -n "$CA_CERT" ] && [ -f "$CA_CERT" ]; then + cp "$CA_CERT" "$DAEMON_SHARE_DIR/ca.crt" + log "DEBUG" "CA certificate copied to shared folder" + fi + log "INFO" "Starting daemon..." log "DEBUG" "PID file: $DAEMON_PID_FILE" log "DEBUG" "Socket: $DAEMON_SOCKET" @@ -1406,6 +1454,21 @@ if [ "$DAEMON_MODE" = "start" ]; then fi fi +# For non-daemon mode with CA cert, we need virtio-9p to pass the cert +# (kernel cmdline is too small for base64-encoded certs) +if [ -n "$CA_CERT" ] && [ -f "$CA_CERT" ]; then + # Create temp share dir for CA cert + CA_SHARE_DIR="$TEMP_DIR/ca_share" + mkdir -p "$CA_SHARE_DIR" + cp "$CA_CERT" "$CA_SHARE_DIR/ca.crt" + + # Add virtio-9p mount for CA cert + SHARE_TAG="${TOOL_NAME}_share" + QEMU_OPTS="$QEMU_OPTS -virtfs local,path=$CA_SHARE_DIR,mount_tag=$SHARE_TAG,security_model=none,readonly=on,id=cashare" + KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" + log "DEBUG" "CA certificate available via virtio-9p" +fi + log "INFO" "Starting QEMU..." log "DEBUG" "Command: $QEMU_CMD $QEMU_OPTS -append \"$KERNEL_APPEND\"" diff --git a/recipes-containers/vcontainer/vcontainer-tarball.bb b/recipes-containers/vcontainer/vcontainer-tarball.bb index a4e9a9a5..8d7cecf9 100644 --- a/recipes-containers/vcontainer/vcontainer-tarball.bb +++ b/recipes-containers/vcontainer/vcontainer-tarball.bb @@ -31,6 +31,15 @@ LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda FILESEXTRAPATHS:prepend := "${THISDIR}/files:" TOOLCHAIN_SHAR_EXT_TMPL = "${THISDIR}/files/toolchain-shar-extract.sh" +# Declare script sources so BitBake tracks changes and rebuilds when they change +SRC_URI = "\ + file://vrunner.sh \ + file://vcontainer-common.sh \ + file://vdkr.sh \ + file://vpdmn.sh \ + file://toolchain-shar-extract.sh \ +" + # No target sysroot - host tools only (like buildtools-tarball) TOOLCHAIN_TARGET_TASK = "" TARGET_ARCH = "none" @@ -62,6 +71,7 @@ EXCLUDE_FROM_WORLD = "1" inherit populate_sdk inherit toolchain-scripts-base inherit nopackages +inherit container-registry # Must be set AFTER inherit populate_sdk (class sets it to target arch) REAL_MULTIMACH_TARGET_SYS = "none" @@ -169,6 +179,8 @@ create_sdk_files:append () { TOPDIR="${TOPDIR}" THISDIR="${THISDIR}" ARCHITECTURES="${VCONTAINER_ARCHITECTURES}" + CONTAINER_REGISTRY_SECURE="${CONTAINER_REGISTRY_SECURE}" + CONTAINER_REGISTRY_CA_CERT="${CONTAINER_REGISTRY_CA_CERT}" # SDK output directory SDK_OUT="${SDK_OUTPUT}/${SDKPATH}" @@ -290,6 +302,18 @@ create_sdk_files:append () { bbnote "Installed vpdmn" fi + # Copy CA certificate for secure registry mode (if available) + SECURE_MODE="${CONTAINER_REGISTRY_SECURE}" + CA_CERT="${CONTAINER_REGISTRY_CA_CERT}" + if [ "${SECURE_MODE}" = "1" ] && [ -f "${CA_CERT}" ]; then + mkdir -p "${SDK_OUT}/registry" + cp "${CA_CERT}" "${SDK_OUT}/registry/ca.crt" + bbnote "Included secure registry CA certificate" + elif [ "${SECURE_MODE}" = "1" ]; then + bbwarn "Secure registry mode enabled but CA cert not found at ${CA_CERT}" + bbwarn "Run: bitbake container-registry-index -c generate_registry_script" + fi + # Create README cat > "${SDK_OUT}/README.txt" <