diff --git a/README.md b/README.md index ce90cde..cf55475 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,15 @@ keepalived Deploy Keepalived across the cluster ## Companion files -Large installer artifacts used by this project are stored in the separate -`TAI/files` repository. The SentinelOne package filename and displayed version -are defined together in `defaults.inc`. +Large installer artifacts used by this project are stored in the private +`TAI/files` package registry. SentinelOne installation requires a temporary +deployment code from TAPM Deployment Access; the private Gitea credential is +never stored on or returned to a Proxmox host. ## Runtime requirements - Proxmox VE and root privileges -- Bash, Git, curl, wget, and standard Debian package tools +- Bash, Git, curl, wget, Python 3, and standard Debian package tools - `/opt/idssys/defaults/default.inc` - `/opt/idssys/defaults/colors.inc` diff --git a/defaults.inc b/defaults.inc index a88bbc9..770baa2 100755 --- a/defaults.inc +++ b/defaults.inc @@ -3,7 +3,7 @@ action="${1:-}" FOLDER='/opt/idssys/ta-proxmenu' -VERS='2026.7.25-18' +VERS='2026.7.25-19' noupdate=' ' @@ -27,7 +27,8 @@ VIRTIO_FILE="${VIRTIO_DOWNLOAD_URL##*/}" S1_VERSION='26_1_1_31' S1_PACKAGE="SentinelAgent_linux_x86_64_v${S1_VERSION}.deb" -S1_DOWNLOAD_URL="https://git.scity.us/TAI/files/raw/branch/main/${S1_PACKAGE}" +S1_BROKER_URL='https://tapm.scity.us' +S1_BROKER_PACKAGE='sentinelone-linux' if [[ -d /mnt/pve/PVE-Shared-Storage/template/iso ]]; then DLDIR='/mnt/pve/PVE-Shared-Storage/template/iso' diff --git a/proxmenu-scripts.sh b/proxmenu-scripts.sh index 9525e1b..882bdea 100755 --- a/proxmenu-scripts.sh +++ b/proxmenu-scripts.sh @@ -131,15 +131,99 @@ INSTALL_RMM() { } INSTALL_S1() { + local deploycode exchange_response host_fingerprint + local -a s1_access + echo + if ! command -v python3 >/dev/null 2>&1; then + echo -e "${idsCL[LightRed]}Python 3 is required to request the protected installer.${idsCL[Default]}" + FINISH_ACTION + return + fi + echo -en "${idsCL[LightYellow]}Paste the TAPM deployment code: ${idsCL[Default]}" + read -r -s deploycode + echo + deploycode="${deploycode^^}" + if [[ ! "$deploycode" =~ ^TAPM-[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{5}$ ]]; then + unset deploycode + echo -e "${idsCL[LightRed]}The TAPM deployment code is not valid.${idsCL[Default]}" + FINISH_ACTION + return + fi + + host_fingerprint="$(sha256sum /etc/machine-id | cut -d' ' -f1)" + exchange_response="$( + TAPM_CODE="$deploycode" TAPM_FINGERPRINT="$host_fingerprint" TAPM_HOSTNAME="$(hostname)" \ + python3 -c 'import json, os, sys; json.dump({"code": os.environ["TAPM_CODE"], "host_fingerprint": os.environ["TAPM_FINGERPRINT"], "hostname": os.environ["TAPM_HOSTNAME"]}, sys.stdout)' | + curl --fail --silent --show-error \ + --header 'Content-Type: application/json' \ + --data-binary @- "${S1_BROKER_URL}/api/v1/exchange" + )" || { + unset deploycode host_fingerprint exchange_response + echo -e "${idsCL[LightRed]}The protected SentinelOne installer could not be authorized.${idsCL[Default]}" + FINISH_ACTION + return + } + unset deploycode host_fingerprint + + mapfile -t s1_access < <( + printf '%s' "$exchange_response" | + S1_SLUG="$S1_BROKER_PACKAGE" python3 -c ' +import json, os, sys +data = json.load(sys.stdin) +package = next((item for item in data.get("packages", []) if item.get("slug") == os.environ["S1_SLUG"]), None) +if not package: + raise SystemExit(1) +print(data.get("session_token", "")) +print(package.get("download_url", "")) +print(package.get("sha256", "")) +' + ) || true + unset exchange_response + if [[ ${#s1_access[@]} -ne 3 || -z "${s1_access[0]}" || + ! "${s1_access[2]}" =~ ^[0-9a-fA-F]{64}$ ]]; then + unset s1_access + echo -e "${idsCL[LightRed]}SentinelOne is not included in this deployment authorization.${idsCL[Default]}" + FINISH_ACTION + return + fi + + rm -f "/tmp/${S1_PACKAGE}" + if ! printf 'header = "Authorization: Bearer %s"\n' "${s1_access[0]}" | + curl --fail --location --show-error --config - \ + --output "/tmp/${S1_PACKAGE}" "${s1_access[1]}"; then + unset s1_access + rm -f "/tmp/${S1_PACKAGE}" + echo -e "${idsCL[LightRed]}The SentinelOne installer download failed.${idsCL[Default]}" + FINISH_ACTION + return + fi + if [[ "$(sha256sum "/tmp/${S1_PACKAGE}" | cut -d' ' -f1)" != "${s1_access[2],,}" ]]; then + unset s1_access + rm -f "/tmp/${S1_PACKAGE}" + echo -e "${idsCL[LightRed]}The SentinelOne installer checksum did not match. The file was removed.${idsCL[Default]}" + FINISH_ACTION + return + fi + unset s1_access + echo -en "${idsCL[LightYellow]}Paste the customers SentinelOne Site Token: ${idsCL[Default]}" read -r -s s1token echo - [[ -n "$s1token" ]] || { echo "No SentinelOne site token supplied."; FINISH_ACTION; return; } + [[ -n "$s1token" ]] || { + rm -f "/tmp/${S1_PACKAGE}" + echo "No SentinelOne site token supplied." + FINISH_ACTION + return + } cd /tmp || return 1 - rm -f "/tmp/${S1_PACKAGE}" - wget "$S1_DOWNLOAD_URL" -O "/tmp/${S1_PACKAGE}" - dpkg -i "/tmp/${S1_PACKAGE}" + if ! dpkg -i "/tmp/${S1_PACKAGE}"; then + unset s1token + rm -f "/tmp/${S1_PACKAGE}" + echo -e "${idsCL[LightRed]}SentinelOne installation failed.${idsCL[Default]}" + FINISH_ACTION + return + fi /opt/sentinelone/bin/sentinelctl management token set "$s1token" unset s1token /opt/sentinelone/bin/sentinelctl control start