diff --git a/infrastructure/host-os/installer.sh b/infrastructure/host-os/installer.sh index 75c5efd..1c24346 100644 --- a/infrastructure/host-os/installer.sh +++ b/infrastructure/host-os/installer.sh @@ -255,8 +255,7 @@ q run "sudo swapon /$latest_trimmed_parition" echo "Update /etc/fstab for the new partition line" run "sudo sed -i '$ d' /etc/fstab" - run "sudo chmod 766 /etc/fstab" - echo "/dev/disk/by-uuid/6443e3b1-12bc-41d0-83d8-e5c25477b5a0 none swap sw 0 " >> /etc/fstab + run "echo '/dev/disk/by-uuid/6443e3b1-12bc-41d0-83d8-e5c25477b5a0 none swap sw 0 ' | sudo tee -a /etc/fstab > /dev/null" run "sudo chmod 644 /etc/fstab" fi fi @@ -455,7 +454,7 @@ function InternalConfigSetup() { run "echo 'set enable-bracketed-paste off' >> /etc/inputrc" fi run "echo 'sys_olvtelemetry ALL=(ALL) NOPASSWD: /usr/sbin/biosdecode, /usr/sbin/dmidecode, /usr/sbin/ownership, /usr/sbin/vpddecode' > /etc/sudoers.d/user-sudo" - run "echo 'user ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/user-sudo" + run "echo 'user ALL=(ALL) ALL' >> /etc/sudoers.d/user-sudo" run "chmod 440 /etc/sudoers.d/user-sudo" run "sed -i 's/.*AutomaticLoginEnable =.*/AutomaticLoginEnable = true/g' /etc/gdm3/custom.conf" run "sed -i 's/.*AutomaticLogin = user1/AutomaticLogin = user/g' /etc/gdm3/custom.conf" diff --git a/infrastructure/installation-scripts/download-resources.sh b/infrastructure/installation-scripts/download-resources.sh index 48212b9..6df8fab 100755 --- a/infrastructure/installation-scripts/download-resources.sh +++ b/infrastructure/installation-scripts/download-resources.sh @@ -60,6 +60,37 @@ check_dep() { command -v "$1" &>/dev/null || { echo "ERROR: '$1' is required but not found. Install it and retry."; exit 1; } } +verify_sha256_hex() { + # $1 = file path, $2 = expected 64-hex sha256 + local file="$1" expected="$2" got + [[ "${#expected}" -eq 64 ]] || { echo "[ERROR] verify_sha256_hex: invalid expected sha length for $(basename "$file")"; return 2; } + got="$(sha256sum "$file" | awk '{print $1}')" + if [[ "$got" != "$expected" ]]; then + echo "[ERROR] sha256 mismatch: $(basename "$file")" + echo " expected: $expected" + echo " got : $got" + return 1 + fi + echo "[OK] sha256 verified: $(basename "$file")" +} + +verify_sha256_from_sumfile() { + # $1 = file path, $2 = sha256sum-format file, $3 = optional upstream name + # (defaults to basename $1) + local file="$1" sumfile="$2" name="${3:-$(basename "$1")}" expected got + [[ -f "$sumfile" ]] || { echo "[ERROR] sumfile not found: $sumfile"; return 1; } + expected="$(awk -v n="$name" '$2 == n || $2 == "*"n {print $1; exit}' "$sumfile")" + [[ -n "$expected" ]] || { echo "[ERROR] no entry for $name in $(basename "$sumfile")"; return 1; } + got="$(sha256sum "$file" | awk '{print $1}')" + if [[ "$got" != "$expected" ]]; then + echo "[ERROR] sha256 mismatch: $name" + echo " expected: $expected" + echo " got : $got" + return 1 + fi + echo "[OK] sha256 verified: $name" +} + # ------------------------------------------------------------------------------ # Preflight # ------------------------------------------------------------------------------ @@ -87,10 +118,16 @@ echo "" # K3s # ============================================================================== K3S_BASE_URL="https://github.com/k3s-io/k3s/releases/download/${K3S_VERSION}" +info "Downloading K3s checksums..." +curl -fL "${K3S_BASE_URL}/sha256sum-${K3S_ARCH}.txt" \ + -o "${RESOURCES_DIR}/k3s/sha256sum-${K3S_ARCH}.txt" +K3S_SUMS="${RESOURCES_DIR}/k3s/sha256sum-${K3S_ARCH}.txt" +success "sha256sum-${K3S_ARCH}.txt saved" info "Downloading K3s binary..." curl -fL "${K3S_BASE_URL}/${K3S_BINARY}" \ -o "${RESOURCES_DIR}/k3s/k3s" +verify_sha256_from_sumfile "${RESOURCES_DIR}/k3s/k3s" "${K3S_SUMS}" "${K3S_BINARY}" chmod +x "${RESOURCES_DIR}/k3s/k3s" success "k3s binary saved" @@ -98,24 +135,30 @@ info "Downloading K3s airgap images (this may take several minutes)..." # Try the newer .tar.zst format first; fall back to .tar.gz if curl -fL "${K3S_BASE_URL}/k3s-airgap-images-${K3S_ARCH}.tar.zst" \ -o "${RESOURCES_DIR}/k3s/k3s-airgap-images-${K3S_ARCH}.tar.zst" 2>/dev/null; then + verify_sha256_from_sumfile \ + "${RESOURCES_DIR}/k3s/k3s-airgap-images-${K3S_ARCH}.tar.zst" "${K3S_SUMS}" success "k3s-airgap-images-${K3S_ARCH}.tar.zst saved" else warn ".tar.zst not found, falling back to .tar.gz" curl -fL "${K3S_BASE_URL}/k3s-airgap-images-${K3S_ARCH}.tar.gz" \ -o "${RESOURCES_DIR}/k3s/k3s-airgap-images-${K3S_ARCH}.tar.gz" + verify_sha256_from_sumfile \ + "${RESOURCES_DIR}/k3s/k3s-airgap-images-${K3S_ARCH}.tar.gz" "${K3S_SUMS}" success "k3s-airgap-images-${K3S_ARCH}.tar.gz saved" fi info "Downloading K3s install script..." -curl -fL "https://get.k3s.io" -o "${RESOURCES_DIR}/k3s/install.sh" +K3S_INSTALL_URL="https://raw.githubusercontent.com/k3s-io/k3s/${K3S_VERSION}/install.sh" +curl -fL "${K3S_INSTALL_URL}" -o "${RESOURCES_DIR}/k3s/install.sh" +if [[ -n "${K3S_INSTALL_SH_SHA256:-}" ]]; then + verify_sha256_hex "${RESOURCES_DIR}/k3s/install.sh" "${K3S_INSTALL_SH_SHA256}" +else + warn "K3S_INSTALL_SH_SHA256 not set — install.sh content is not checksum-verified." + warn " Pin it for reproducibility: K3S_INSTALL_SH_SHA256= ./download-resources.sh" +fi chmod +x "${RESOURCES_DIR}/k3s/install.sh" success "install.sh saved" -info "Downloading K3s checksums..." -curl -fL "${K3S_BASE_URL}/sha256sum-${K3S_ARCH}.txt" \ - -o "${RESOURCES_DIR}/k3s/sha256sum-${K3S_ARCH}.txt" -success "sha256sum-${K3S_ARCH}.txt saved" - # Store the version so install scripts can report it echo "${K3S_VERSION}" > "${RESOURCES_DIR}/k3s/VERSION" @@ -128,12 +171,26 @@ DOCKER_STATIC_URL="https://download.docker.com/linux/static/stable/${DOCKER_ARCH info "Downloading Docker static binaries..." curl -fL "${DOCKER_STATIC_URL}" \ -o "${RESOURCES_DIR}/docker/docker-${DOCKER_VERSION}.tgz" + +if [[ -n "${DOCKER_SHA256:-}" ]]; then + verify_sha256_hex "${RESOURCES_DIR}/docker/docker-${DOCKER_VERSION}.tgz" "${DOCKER_SHA256}" +else + warn "DOCKER_SHA256 not set — docker-${DOCKER_VERSION}.tgz is not checksum-verified." + warn " Pin it for reproducibility: DOCKER_SHA256= ./download-resources.sh" +fi success "docker-${DOCKER_VERSION}.tgz saved" info "Downloading Docker Compose plugin (${COMPOSE_VERSION})..." COMPOSE_URL="https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-${COMPOSE_ARCH}" curl -fL "${COMPOSE_URL}" \ -o "${RESOURCES_DIR}/docker/docker-compose" + +curl -fL "${COMPOSE_URL}.sha256" \ + -o "${RESOURCES_DIR}/docker/docker-compose.sha256" +verify_sha256_from_sumfile \ + "${RESOURCES_DIR}/docker/docker-compose" \ + "${RESOURCES_DIR}/docker/docker-compose.sha256" \ + "docker-compose-linux-${COMPOSE_ARCH}" chmod +x "${RESOURCES_DIR}/docker/docker-compose" success "docker-compose saved" @@ -158,6 +215,11 @@ curl -fL "${HELM_BASE_URL}/${HELM_TARBALL}.sha256sum" \ -o "${RESOURCES_DIR}/helm/${HELM_TARBALL}.sha256sum" success "${HELM_TARBALL}.sha256sum saved" +verify_sha256_from_sumfile \ + "${RESOURCES_DIR}/helm/${HELM_TARBALL}" \ + "${RESOURCES_DIR}/helm/${HELM_TARBALL}.sha256sum" \ + "${HELM_TARBALL}" + # Store the version echo "${HELM_VERSION}" > "${RESOURCES_DIR}/helm/VERSION" diff --git a/infrastructure/installation-scripts/install-helm.sh b/infrastructure/installation-scripts/install-helm.sh index 0a20e8c..9e7a75e 100755 --- a/infrastructure/installation-scripts/install-helm.sh +++ b/infrastructure/installation-scripts/install-helm.sh @@ -70,21 +70,18 @@ echo "======================================================================" echo "" # ============================================================================== -# 1. Verify checksum (if available) +# 1. Verify checksum (missing treated as a hard error so a tampered bundle that +# strips the .sha256sum cannot bypass verification.) # ============================================================================== CHECKSUM_FILE="${HELM_TARBALL}.sha256sum" -if [[ -f "${CHECKSUM_FILE}" ]]; then - info "Verifying checksum ..." - # sha256sum file contains an absolute path; rebuild it relative to RESOURCES_DIR - EXPECTED_SUM="$(awk '{print $1}' "${CHECKSUM_FILE}")" - ACTUAL_SUM="$(sha256sum "${HELM_TARBALL}" | awk '{print $1}')" - if [[ "${EXPECTED_SUM}" != "${ACTUAL_SUM}" ]]; then - die "Checksum mismatch for $(basename "${HELM_TARBALL}")!\n Expected: ${EXPECTED_SUM}\n Got : ${ACTUAL_SUM}\nDelete the file and re-run download-resources.sh." - fi - success "Checksum verified" -else - echo "[WARN] No checksum file found — skipping verification." +[[ -f "${CHECKSUM_FILE}" ]] || die "Checksum file not found: $(basename "${CHECKSUM_FILE}")\nRefusing to install without verification. Re-run download-resources.sh to repopulate the bundle." +info "Verifying checksum ..." +EXPECTED_SUM="$(awk '{print $1}' "${CHECKSUM_FILE}")" +ACTUAL_SUM="$(sha256sum "${HELM_TARBALL}" | awk '{print $1}')" +if [[ "${EXPECTED_SUM}" != "${ACTUAL_SUM}" ]]; then + die "Checksum mismatch for $(basename "${HELM_TARBALL}")!\n Expected: ${EXPECTED_SUM}\n Got : ${ACTUAL_SUM}\nDelete the file and re-run download-resources.sh." fi +success "Checksum verified" # ============================================================================== # 2. Extract Helm binary diff --git a/infrastructure/installation-scripts/install-k3s.sh b/infrastructure/installation-scripts/install-k3s.sh index d7a59a5..9205f96 100755 --- a/infrastructure/installation-scripts/install-k3s.sh +++ b/infrastructure/installation-scripts/install-k3s.sh @@ -109,14 +109,30 @@ if [[ "${STAGED}" -eq 0 ]]; then fi # ============================================================================== -# 3. Ensure config directory exists +# 3. Ensure config directory exists (root-only) # ============================================================================== -mkdir -p "${K3S_CONFIG_DIR}" +install -d -m 0700 -o root -g root "${K3S_CONFIG_DIR}" -# Copy a user-supplied config if provided +# Copy a user-supplied config if provided. Permissions hardened to 0600 root:root +# because the config may contain `token:` or other secrets. if [[ -n "${K3S_CONFIG_FILE:-}" && -f "${K3S_CONFIG_FILE}" ]]; then info "Copying config file ${K3S_CONFIG_FILE} → ${K3S_CONFIG_DIR}/config.yaml" - cp "${K3S_CONFIG_FILE}" "${K3S_CONFIG_DIR}/config.yaml" + install -m 0600 -o root -g root "${K3S_CONFIG_FILE}" "${K3S_CONFIG_DIR}/config.yaml" +fi + +# Persist server URL and join token to a permission-hardened drop-in. +if [[ "${K3S_MODE}" == "agent" ]]; then + install -d -m 0700 -o root -g root "${K3S_CONFIG_DIR}/config.yaml.d" + AGENT_DROPIN="${K3S_CONFIG_DIR}/config.yaml.d/00-agent.yaml" + ( umask 077 && cat > "${AGENT_DROPIN}" </dev/null || true # Export so child processes (curl, helm, kubectl, etc.) inherit them export http_proxy https_proxy HTTP_PROXY HTTPS_PROXY no_proxy NO_PROXY 2>/dev/null || true -export KUBECONFIG=/etc/rancher/k3s/k3s.yaml + +K3S_KUBECONFIG=/etc/rancher/k3s/k3s.yaml +k() { KUBECONFIG="$K3S_KUBECONFIG" kubectl "$@"; } # ── Disable docker — kubernetes node does not run docker ────────────────── systemctl disable docker 2>/dev/null || true @@ -43,8 +45,10 @@ systemctl enable --now k3s # ── Configure k3s proxy for containerd (if proxy variables are set) ─────── if [ -n "${HTTP_PROXY:-}" ] || [ -n "${HTTPS_PROXY:-}" ]; then echo "Configuring k3s proxy settings..." - mkdir -p /etc/systemd/system/k3s.service.d - cat > /etc/systemd/system/k3s.service.d/http-proxy.conf < "$DROPIN" </dev/null | grep -q ' Ready' && break + k get nodes --no-headers 2>/dev/null | grep -q ' Ready' && break sleep 5 done -if ! kubectl get nodes >/dev/null 2>&1; then +if ! k get nodes >/dev/null 2>&1; then echo "ERROR: K3s API not ready after 5 minutes — aborting plugin setup" exit 1 fi echo "K3s nodes:" -kubectl get nodes +k get nodes # ── Install Helm if not already present ─────────────────────────────────── INSTALL_SCRIPTS="/opt/edge/scripts" +HELM_VERSION="v3.17.2" if ! command -v helm >/dev/null 2>&1; then - if [ -f "${INSTALL_SCRIPTS}/install-helm.sh" ] && ls "${INSTALL_SCRIPTS}"/helm-*-linux-*.tar.gz >/dev/null 2>&1; then - echo "Installing Helm from local resources..." + if [ -f "${INSTALL_SCRIPTS}/install-helm.sh" ] && ls "${INSTALL_SCRIPTS}"/resources/helm/helm-*-linux-*.tar.gz >/dev/null 2>&1; then + echo "Installing Helm from local airgap bundle..." bash "${INSTALL_SCRIPTS}/install-helm.sh" else - # Local helm tarball not bundled (resources/helm/ not present in hook OS). - # Attempt internet install only if reachable within 10s; skip otherwise. - echo "Local Helm resources not found — testing internet connectivity..." - if curl -fsSL --connect-timeout 10 --max-time 10 \ - https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 \ - -o /tmp/get-helm-3 2>/dev/null; then - bash /tmp/get-helm-3 || true - rm -f /tmp/get-helm-3 + echo "Local Helm bundle not found" + case "$(uname -m)" in + x86_64) HELM_ARCH="amd64" ;; + aarch64) HELM_ARCH="arm64" ;; + armv7l) HELM_ARCH="arm" ;; + *) HELM_ARCH="" ;; + esac + + if [ -z "${HELM_ARCH}" ]; then + echo "ERROR: Unsupported architecture $(uname -m) — cannot install Helm" else - echo "WARNING: Helm internet install skipped (endpoint unreachable)." - echo " Install Helm manually after first boot:" - echo " curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash" + HELM_TARBALL="helm-${HELM_VERSION}-linux-${HELM_ARCH}.tar.gz" + HELM_TMP="$(mktemp -d)" + ( umask 077 && cd "${HELM_TMP}" && \ + for i in 1 2 3; do + curl -fsSL --max-time 120 --retry 3 "https://get.helm.sh/${HELM_TARBALL}" -o "${HELM_TARBALL}" && \ + curl -fsSL --max-time 30 --retry 3 "https://get.helm.sh/${HELM_TARBALL}.sha256sum" -o "${HELM_TARBALL}.sha256sum" && break + echo " helm download attempt $i failed, retrying..." + sleep 5 + done && \ + sha256sum -c "${HELM_TARBALL}.sha256sum" && \ + tar -xzf "${HELM_TARBALL}" && \ + install -m 0755 "linux-${HELM_ARCH}/helm" /usr/local/bin/helm + ) && echo "Helm ${HELM_VERSION} installed: $(helm version --short 2>/dev/null)" \ + || echo "WARNING: Helm install failed (download or checksum verification error)" + rm -rf "${HELM_TMP}" fi fi else @@ -122,15 +144,15 @@ fi # applying manifests directly (k3s pulls images at runtime). apply_manifests_directly() { # Manifests are at /opt/edge/scripts/ (flat layout from hook OS) - [ -f "${INSTALL_SCRIPTS}/nfd.yaml" ] && kubectl apply -f "${INSTALL_SCRIPTS}/nfd.yaml" && sleep 15 || true - [ -f "${INSTALL_SCRIPTS}/nfd-node-feature-rules.yaml" ] && kubectl apply -f "${INSTALL_SCRIPTS}/nfd-node-feature-rules.yaml" || true - [ -f "${INSTALL_SCRIPTS}/gpu-plugin.yaml" ] && kubectl apply -f "${INSTALL_SCRIPTS}/gpu-plugin.yaml" || true - [ -f "${INSTALL_SCRIPTS}/npu-plugin.yaml" ] && kubectl apply -f "${INSTALL_SCRIPTS}/npu-plugin.yaml" || true + [ -f "${INSTALL_SCRIPTS}/nfd.yaml" ] && k apply -f "${INSTALL_SCRIPTS}/nfd.yaml" && sleep 15 || true + [ -f "${INSTALL_SCRIPTS}/nfd-node-feature-rules.yaml" ] && k apply -f "${INSTALL_SCRIPTS}/nfd-node-feature-rules.yaml" || true + [ -f "${INSTALL_SCRIPTS}/gpu-plugin.yaml" ] && k apply -f "${INSTALL_SCRIPTS}/gpu-plugin.yaml" || true + [ -f "${INSTALL_SCRIPTS}/npu-plugin.yaml" ] && k apply -f "${INSTALL_SCRIPTS}/npu-plugin.yaml" || true } if [ -f "${INSTALL_SCRIPTS}/install-intel-device-plugins.sh" ]; then echo "Running install-intel-device-plugins.sh..." - bash "${INSTALL_SCRIPTS}/install-intel-device-plugins.sh" || { + KUBECONFIG="$K3S_KUBECONFIG" bash "${INSTALL_SCRIPTS}/install-intel-device-plugins.sh" || { echo "WARNING: install-intel-device-plugins.sh failed (likely missing pre-pulled images) — applying manifests directly" apply_manifests_directly } @@ -140,7 +162,7 @@ else fi echo "=== Pod status after plugin installation ===" -kubectl get pods -A +k get pods -A # ── SR-IOV Configuration (Optional) ─────────────────────────────────────── # Set up SR-IOV virtual functions if enabled in config-file