Skip to content

arm64 support: full dual-arch parity + native blue-team toolkit + desktop UX#11

Open
cyberblu3s wants to merge 19 commits into
mainfrom
arm64-bringup
Open

arm64 support: full dual-arch parity + native blue-team toolkit + desktop UX#11
cyberblu3s wants to merge 19 commits into
mainfrom
arm64-bringup

Conversation

@cyberblu3s

@cyberblu3s cyberblu3s commented Apr 18, 2026

Copy link
Copy Markdown
Owner

Summary

Brings CyberBlueSOC to full dual-architecture parity. The same codebase now builds and runs on amd64 (x86_64 — AWS / VMware / VirtualBox / bare metal) and arm64 (AWS Graviton / Apple Silicon via UTM / Raspberry Pi-class hardware), validated end-to-end on Ubuntu Server 24.04 LTS aarch64 in a UTM VM on Apple Silicon.

Alongside arm64, this PR lands the native blue-team toolkit (~56 CLI + GUI tools installed on the host next to the container stack), the XFCE-based desktop UX layer (branded wallpaper, welcome dashboard, categorized app menu, cyberblue CLI, Firefox policies, noVNC), and a new cyberblue_init.sh umbrella that chains the whole thing into one command.

What's in this PR

Docker container stack (arm64)

  • Wazuh: local arm64 rebuild pipeline (wazuh/build-arm64-images.sh) now builds wazuh-manager, wazuh-indexer, wazuh-dashboard, and wazuh-certs-generator from the vendored sources, with arch-aware Filebeat RPM selection. Upstream Wazuh doesn't publish arm64 images; the installer calls the rebuild helper automatically on arm64 first install (~25 min one-time cost).
  • Caldera: multi-arch Go tarball download via runtime arch detection (not buildx ARG, which didn't work reliably); conf/local.yml bind-mount switched from :ro to writable (Caldera ≥5 rewrites the file on boot — was broken on both arches).
  • Arkime: Dockerfile made architecture-aware via TARGETARCH.
  • Fleet: gated behind an amd64 Docker Compose profile (upstream Fleet images are amd64-only); arm64 installs skip Fleet and use Velociraptor for endpoint management.
  • Velociraptor, Suricata, Zeek, EveBox, MISP, TheHive/Cortex, Shuffle core, Portal, CyberChef, Wireshark-web, MITRE Navigator, Portainer: all already multi-arch; validated on arm64.
  • Known arm64 limitation: frikky/shuffle:tenzir integration image is amd64-only, so tenzir-node restarts on arm64 — documented as an integration-layer exception, not a core service.

Native blue-team toolkit (tools/native/install.sh)

~56 CLI/GUI tools installed on the host. All tools classified in tools/native/ARM64_AUDIT.md:

  • 50 green (already arm64-native through apt / pip / pipx / git / multi-arch Docker / pure-Go / pure-Python).
  • 3 fix-url (chainsaw, hayabusa, stratus) — upstream ships arm64, URLs patched via a single top-level arch case block ($CHAINSAW_ARCH, $HAYABUSA_ARCH, $STRATUS_ARCH).
  • 1 rebuild-deferred (bulk_extractor) — skipped on arm64 with a clear message.
  • 2 gap (sysmonforlinux, zui) — amd64-only upstream; skipped on arm64 with mitigation guidance.
  • Installer now uses a _latest_release_with_asset helper that pages through /releases instead of blindly trusting /releases/latest, mitigating the Nuclei-v3.8.0-style partial-publish failures.
  • NetExec's arm64 path pre-installs rustc, cargo, libpcap-dev, libssl-dev, libffi-dev so aardwolf and pcapy-ng can compile from source (arm64 wheels don't exist upstream).

Desktop UX layer (tools/native/desktop/install-desktop.sh)

  • Branded XFCE wallpaper + logo, now self-contained inside the repo (tools/native/desktop/branding/) so non-ISO installs don't depend on the workspace-level iso/branding/.
  • Welcome dashboard, 76 categorized .desktop entries, cyberblue CLI, Firefox policies + bookmarks, noVNC/websockify/TigerVNC via apt, cbsoc-status.timer. Everything is arch-agnostic and validated on arm64.
  • Fixes a typo in caldera-autostart.service and docker-networking-fix.service unit templates (CyberBlueSOCxCyberBlue).

Infrastructure

  • cyberblue_init.sh (new) — post-prereqs umbrella: chains cyberblue_install.shtools/native/install.sh (which auto-chains install-desktop.sh when a desktop session is detected). Each stage stays independently runnable and idempotent.
  • cyberblue_install.sh — preventive fix for the /run/docker.sock ownership race observed on fresh Ubuntu Server 24.04 arm64 (the socket was coming up owned by 1001:1001 instead of root:docker). Forces a docker.socket restart after usermod -aG docker.
  • cyberblue_install.sh — installer runs the Wazuh arm64 build under sudo with correct PIPESTATUS propagation (previously the pipe masked non-zero exit codes and the installer reported success on failure).
  • .env.example — cleaned up stale trailing duplicates (YETI_AUTH_SECRET_KEY x3, SURICATA_INT x2); the canonical lines live at the top with an inline comment explaining the installer's SURICATA_INT auto-detect.

Documentation

  • README.md — widened System Requirements to explicitly list amd64 and arm64; added a "Full Platform" install section pointing at cyberblue_init.sh; linked docs/ARM64_SUPPORT.md and the native toolkit README.
  • docs/ARM64_SUPPORT.md (new) — operator-facing dual-arch reference: host-environment matrix, per-service arm64 status, native toolkit bucket summary, the four locked amd64-only exceptions (Fleet, tenzir, sysmonforlinux, zui), quick verification commands, known arm64 build-time costs.
  • docs/TROUBLESHOOTING.md — new sections for the docker.sock ownership race and the stale caldera-autostart.service migration one-liner.
  • tools/native/ARM64_AUDIT.md (new) — per-tool arm64 audit, 56 entries across 11 sections.
  • tools/native/desktop/ARM64_AUDIT.md (new) — audit of the desktop layer scripts.
  • QUICK_REFERENCE.md — FleetDM row flagged as amd64-only.

Bugs fixed along the way

19 distinct arm64 issues captured across two iterative smoke-test loops on the UTM VM; full forensic notes live in ENHANCEMENTS.md entries #50, #52, #53 in the workspace. Highlights:

  • Docker silently creates empty directories at per-file bind-mount sources → "phantom bind-mount directories" that survive docker compose down -v. Hit this with Wazuh certs when wazuh-certs-generator:0.0.2 crashed with exec format error on arm64. Documented in the multi-arch policy rule.
  • Installer pipelines that masked exit codes (docker compose up ... | while read line). Fixed via a run_with_output helper that captures ${PIPESTATUS[0]}.
  • ARG TARGETARCH in Dockerfiles fails silently when compose isn't driving buildx; switched to runtime dpkg --print-architecture / rpm --eval detection.
  • Filebeat RPM name is arch-specific (filebeat-*-x86_64.rpm vs filebeat-*-aarch64.rpm); filebeat_module.sh sourced inside the Wazuh Dockerfile now detects and swaps.
  • Caldera's Go tarball URL hardcoded linux-amd64; moved to runtime arch detection.
  • Velociraptor binary name differs between amd64 and arm64 releases; installer now picks the correct asset.

Test plan

  • Phase 0: Docs + cursor rules capture the full AWS + UTM context and the 18-bug smoke-test lessons.
  • Phase 1: Clean docker compose down -v, wipe phantom cert dirs, re-run cyberblue_install.sh one-shot on the UTM arm64 VM. Acceptance: installer exit 0, 25+ containers Up, portal HTTP 200, wazuh-indexer HTTP 401/200.
  • Phase 2: Static arm64 audit of tools/native/install.shtools/native/ARM64_AUDIT.md inventory of ~56 tools.
  • Phase 3: Static audit of tools/native/desktop/install-desktop.sh and iso/scripts/buildbox-bootstrap.sh; wallpaper + logo assets committed to the repo; caldera-autostart + docker-networking-fix unit typos fixed.
  • Phase 4: On the same UTM VM that passed Phase 1, install XFCE + VNC + noVNC overlay, run tools/native/install.sh (+ auto-chained install-desktop.sh), debug arm64 issues until acceptance gate passes. Actual result on arm64: 27 containers Up (with expected tenzir-node restart), 49/49 native tools resolving and verified as ARM aarch64 ELFs, noVNC accessible, branded wallpaper applied (SHA256 matches repo), 76 app-menu entries, Firefox policies seeded, cbsoc-status.timer active.
  • Phase 5: cyberblue_init.sh umbrella decision + docker.sock race preventive fix + SURICATA_INT auto-detect validation. Confirmed auto-detect heals ens5enp0s1 end-to-end on the UTM VM via update-network-interface.sh.
  • Phase 6 (this PR): final docs pass, open PR.
  • Pre-merge human review: smoke-test a fresh git clone + ./cyberblue_init.sh on a clean amd64 host to confirm no regressions against the existing amd64 reference build.
  • Post-merge: preserve the UTM cbsoc-arm64-gold snapshot as the ISO baseline.

Out of scope

  • ISO generation (Cubic vs live-build, dual-arch build matrix, signing). Tracked in a subsequent scope-only plan document.

Made with Cursor

CyberBlueSOC Build and others added 18 commits April 18, 2026 01:40
…tials)

This commit captures the in-progress work from the AWS build box as the
baseline for the arm64-bringup branch. No ARM fixes yet - those follow in
subsequent commits.
Upstream Arkime publishes both amd64 and arm64 .deb packages for Ubuntu 22.04
(verified against github.com/arkime/arkime/releases/tag/v5.7.0). BuildKit
automatically provides TARGETARCH during `docker buildx build` and `docker
compose up --build` when DOCKER_BUILDKIT=1 is set (which cyberblue_install.sh
already exports at step 1.7), so no compose changes are required.

Fixes ARM64 smoke-test blocker #1 from the ARM readiness audit.
FleetDM does not publish a linux/arm64 image or binary as of 2026-04 - neither
the `fleetdm/fleet:latest` Docker image nor the GitHub-release tarballs have
arm64 variants. Running on arm64 hosts therefore requires skipping the Fleet
stack entirely rather than failing or silently pulling amd64 under emulation.

Changes:

- docker-compose.yml: add `profiles: ["amd64"]` to fleet-redis, fleet-mysql,
  and fleet-server so they only start when COMPOSE_PROFILES contains amd64.
- cyberblue_install.sh:
  * Detect host arch via `dpkg --print-architecture` early in the script,
    export as CYBERBLUE_ARCH, persist to .env, and set COMPOSE_PROFILES=amd64
    only on amd64 hosts.
  * Gate Step 2.12 (`fleet prepare db`) and Step 2.12a (enrollment secret)
    behind `if [ "$CYBERBLUE_ARCH" = "amd64" ]`, with a clear skip message
    on arm64.

Fixes ARM64 smoke-test blocker #2 from the ARM readiness audit.
The three agent-download scripts previously fetched only amd64 Linux binaries,
so endpoints running on arm64 (Apple Silicon Docker hosts, Raspberry Pi,
Graviton, etc.) could not be enrolled from the CyberBlueSOC portal. Upstream
publishes arm64 variants for all three, verified live:

- velociraptor-v0.75.2-linux-arm64 (GitHub release v0.75)
- wazuh-agent_4.12.0-1_arm64.deb + .aarch64.rpm (packages.wazuh.com/4.x)
- osquery_5.13.1-1.linux_arm64.deb + linux.aarch64.rpm (GitHub release 5.13.1)

The default amd64 Linux filenames (velociraptor-linux, wazuh-agent-ubuntu.deb,
osquery-ubuntu.deb) are kept unchanged so the existing portal code at
portal/app.py lines 1983, 2034, 2148 continues to work. The arm64 variants are
staged alongside under clearly-named files (-arm64.deb / -linux-arm64) and
will be surfaced by a follow-up portal change.

On download failure for an arm64 asset the script now warns and continues
(rather than hard-exit), so the CyberBlueSOC install itself doesn't fail just
because upstream temporarily moved an asset.

Fixes ARM64 smoke-test blocker #3 from the ARM readiness audit.
Wazuh Docker Hub images (wazuh-manager, wazuh-indexer, wazuh-dashboard) are
amd64-only as of 2026-04, but packages.wazuh.com publishes aarch64 RPMs for
all three server components and the vendored Dockerfiles under
wazuh/build-docker-images/ are architecture-agnostic except for one
hardcoded s6-overlay asset name in the manager Dockerfile.

Changes:

- wazuh/build-docker-images/wazuh-manager/Dockerfile: add ARG TARGETARCH and
  map Docker's (amd64, arm64) naming onto s6-overlay's (amd64, aarch64)
  naming. Wazuh packages resolve through the yum repo at runtime so the
  amazonlinux:2023 base picks the arch matching the --platform flag.
- wazuh/build-arm64-images.sh: new helper that uses `docker buildx` with
  --platform linux/arm64 to rebuild all three images under their canonical
  tags (e.g. wazuh/wazuh-manager:4.12.0) so docker-compose.yml references
  unchanged. On amd64 hosts the script is a no-op.
- cyberblue_install.sh: new Step 2.9a invokes the builder on arm64 hosts
  before Step 2.10 (`docker compose up --build -d`). On amd64 it prints a
  one-liner explaining the skip and moves on.

Fixes ARM64 smoke-test blocker #4 from the ARM readiness audit, completing
the Phase 1 set of four ARM fixes.
On a fresh install, Step 1.2 adds the ubuntu user to the docker group,
but the new group membership isn't active in the installer's own shell
until re-login. That caused Step 2.9a's bare 'bash wazuh/build-arm64-images.sh'
invocation to fail with 'permission denied /var/run/docker.sock' on arm64
hosts - but the old '| while read line' pipe masked the exit code, so the
installer printed '✅ Wazuh arm64 images built' despite the build failing,
leading to wazuh-manager/indexer/dashboard failing to start later because
the local arm64 images never existed.

Fix:
- invoke via sudo so docker-group membership isn't required
- route through run_with_output which uses ${PIPESTATUS[0]} to expose the
  real exit code
- on failure print a RED error (not YELLOW warn) and point at the manual
  rerun command, because this failure WILL break three wazuh services

Caught during the first arm64 smoke test on UTM (192.168.64.2).

Made-with: Cursor
… exit code

Three issues caught during the first arm64 smoke run on UTM (aarch64/Apple VZ):

1) portal/Dockerfile - add gcc + python3-dev + libffi-dev
   psutil==5.9.5 ships no matching prebuilt wheel for linux/arm64 on the
   python:3.9-slim base, so pip falls back to a source build. Without a C
   toolchain, that fails with 'command gcc: No such file or directory',
   which in turn aborts `docker compose up --build` for the whole stack
   (portal is the first build-from-source service compose reaches).
   Adding the build tools is a no-op on amd64 (wheel is used) and unblocks
   arm64.

2) wazuh/build-arm64-images.sh - pass dotted WAZUH_VERSION
   The vendored Wazuh Dockerfiles install the RPM via
   `yum install wazuh-indexer-${WAZUH_VERSION}-${WAZUH_TAG_REVISION}`.
   packages.wazuh.com publishes 'wazuh-indexer-4.12.0-1', but an earlier
   revision of this script stripped the dots (WAZUH_VERSION_NUM=4120),
   producing a package spec yum couldn't resolve:
   'Error: Unable to find a match: wazuh-indexer-4120-1'.
   Fix: pass WAZUH_IMAGE_VERSION untouched.

3) cyberblue_install.sh - preserve compose up exit code
   The previous `sudo docker compose up --build -d | while read` pipe
   exposed only the `while` loop's exit code, so a compose build failure
   (e.g. fix #1 above) was reported as '✅ deployed' and the installer
   marched on to Step 2.11 with zero containers running. Route through
   run_with_output, which uses ${PIPESTATUS[0]} to expose the real code,
   and print a RED error + diagnostic hints on failure.

Made-with: Cursor
Caldera's upstream Dockerfile hardcodes:
  curl -L https://go.dev/dl/go1.25.0.linux-amd64.tar.gz

On arm64 hosts, this pulls an amd64 Go binary which then fails
`go version` with exit 126 (cannot execute binary file), aborting
the compose-up build target for caldera and cascading to every
service that depends on a successful full compose-up pass.

Fix: after Step 2.6 clones caldera (which happens at install time,
not at repo-clone time, so we can't vendor the patch), rewrite
the Dockerfile in-place to use linux-${TARGETARCH}. docker buildx
sets TARGETARCH automatically per --platform, so:
  amd64 hosts → go1.25.0.linux-amd64.tar.gz
  arm64 hosts → go1.25.0.linux-arm64.tar.gz

The sed is idempotent (guarded by grep -q of the upstream literal
URL) and also injects the ARG TARGETARCH line after the runtime
FROM so the shell-level ${TARGETARCH} expands correctly.

Verified during arm64 smoke run #2 on UTM (aarch64): caldera was
the only compose build failure after the portal + wazuh + arkime
fixes - every other image either has a multi-arch manifest upstream
or gets built locally by our helpers.

Made-with: Cursor
Previous attempt used 'ARG TARGETARCH=amd64' + ${TARGETARCH} substitution,
expecting BuildKit to override the default when the host was arm64.
Turns out 'docker compose up --build' does not reliably populate the
BuildKit TARGETARCH built-in on every host - the outcome depends on
whether compose was invoked with DOCKER_BUILDKIT=1 and whether buildx
is the active builder for that daemon. On the arm64 smoke-test VM,
the ARG stayed at its default (amd64), so curl still downloaded the
amd64 tarball and 'go version' kept failing with exit 126.

Replace the ARG-based substitution with direct shell substitution
inside the RUN step:

  RUN curl ... https://go.dev/dl/go1.25.0.linux-$(dpkg --print-architecture).tar.gz ...

This runs at container-build time on whatever the debian:bookworm-slim
base actually is (which matches the build platform), so it picks the
correct tarball without any buildx or ARG plumbing. Works identically
on amd64 and arm64 hosts.

Made-with: Cursor
Three runtime-phase arm64 bugs caught when 26/28 containers started
successfully after pass #3 but 5 crashed on startup with exec-format errors:

1) wazuh-manager/Dockerfile - detect arch via rpm, not ARG TARGETARCH
   The previous ARG TARGETARCH=amd64 default was being baked in because
   'docker compose up --build' on this host's Docker CE 29.4 does not
   populate BuildKit's TARGETARCH built-in (same root cause as the earlier
   caldera go-tarball bug). Result: amd64 s6-overlay tarball installed
   into arm64 image layer → 'exec /init: exec format error' on startup.
   Fix: derive S6_ARCH from rpm --eval '%{_arch}' at RUN time.

2) velociraptor/Dockerfile - grep correct arch from release JSON
   The RUN step hardcoded 'grep "linux-amd64"' when selecting the
   Velociraptor binary from the GitHub release API. Image layer was
   arm64, but the embedded binary was x86_64 → 'cannot execute binary
   file: Exec format error' on every container start.
   Fix: use dpkg --print-architecture at RUN time to match 'linux-amd64'
   on amd64 builds and 'linux-arm64' on arm64 builds. 'file' verifies.

3) docker-compose.yml - swap stamus/suricata for jasonish/suricata
   stamus/suricata:latest publishes only linux/amd64 on Docker Hub.
   On arm64 hosts, compose silently pulls the amd64 image and the
   container crashes with 'exec /usr/bin/suricata: exec format error'.
   jasonish/suricata:latest is the de-facto community multi-arch image
   (same maintainer as evebox, which we already use) and ships both
   linux/amd64 and linux/arm64, with the same CLI and config layout -
   drop-in replacement, mounts and command: unchanged.

Made-with: Cursor
Sourced by the wazuh-manager Dockerfile during image build - previously
hardcoded filebeat-oss-7.10.2-x86_64.rpm. On arm64 builds (amazonlinux
aarch64 base), this triggered an arch-mismatch failure silently buried
under a longer multi-line RUN, and the whole image build aborted after
wazuh-manager RPM install succeeded.

Fix: pick the arch at runtime via rpm --eval '%{_arch}' (x86_64 or aarch64)
and substitute into both the filename and the yum install target. Elastic
publishes both variants at the same URL pattern, so no version pin change.

Made-with: Cursor
Two more arm64 smoke-test issues resolved:

1. wazuh/wazuh-certs-generator:0.0.2 is amd64-only on Docker Hub. On arm64
   hosts compose pulls it anyway and the container crashes with
   'exec /entrypoint.sh: exec format error'. Docker then creates empty
   *directories* at every per-file bind-mount target (root-ca.pem,
   wazuh.indexer.pem, etc.), which persist even after later runs, causing
   wazuh-indexer to crash with 'root-ca.pem - is a directory' forever.

   Fix: build-arm64-images.sh now also builds the certs-generator locally
   from the vendored wazuh/indexer-certs-creator/ Dockerfile (ubuntu:focal,
   multi-arch base), tags it as wazuh/wazuh-certs-generator:0.0.2 so
   existing compose references resolve to the arm64 image transparently.

2. caldera:/usr/src/app/conf/local.yml was bind-mounted :ro. Caldera >= 5.x
   calls BaseWorld.apply_config(apply_hash=True) at startup which REWRITES
   that file with an integrity hash, so :ro causes an immediate
   'OSError: Read-only file system' crash. Dropped :ro. Arch-agnostic bug
   (affects amd64 too), surfaced during arm64 smoke.

Made-with: Cursor
Static arm64 audit of tools/native/install.sh, categorizing ~56
tools into green (50) / fix-url (3) / rebuild (1) / gap (2)
buckets. Full inventory in tools/native/ARM64_AUDIT.md.

URL fixes applied for the three fix-url tools via new variables in
the top-level `case "$ARCH"` block (single source of truth, no
more hardcoded amd64/x86_64/x64 in the body):

- Chainsaw  -> $CHAINSAW_ARCH  (aarch64-unknown-linux-gnu on arm64)
- Hayabusa  -> $HAYABUSA_ARCH  (lin-aarch64-gnu on arm64)
- Stratus   -> $STRATUS_ARCH   (Linux_arm64 on arm64)

Explicit arm64 short-circuits (with informative skip messages) for
the three locked amd64-only exceptions:

- bulk_extractor  (prebuilt amd64-only; source build deferred)
- sysmonforlinux  (Microsoft apt arm64-HTTP-404; Falco+auditd mit.)
- zui (Brim)      (upstream publishes amd64 .deb only)

Tested: bash -n passes; runtime verification happens in Phase 4 of
the arm64 full-parity plan (sudo bash install.sh on the UTM VM).

Made-with: Cursor
Static arm64 audit of the desktop UX layer found zero arm64 blockers
in either install-desktop.sh or iso/scripts/buildbox-bootstrap.sh
(full inventory in tools/native/desktop/ARM64_AUDIT.md). Both are
built from apt packages, static text assets, pure-Python helpers,
and POSIX shell — nothing arch-specific.

Independent fixes bundled with the audit:

1. Branded wallpaper committed to the repo
   - Before: PNG only lived in workspace at iso/branding/, outside the
     CyberBlue/ git tree. Standalone `git clone && sudo bash
     install-desktop.sh` had no wallpaper to install.
   - After: committed tools/native/desktop/branding/{cyberbluesoc-
     wallpaper.png,cyberbluesoc-logo.png}. Added a new "1b. Branded
     wallpaper" block to install-desktop.sh that copies the PNG to
     /usr/share/backgrounds/cyberbluesoc/ and seeds xfce4-desktop.xml
     ONLY if no desktop config exists yet — so the AWS/ISO path (where
     buildbox-bootstrap.sh already writes the xml) stays untouched.

2. caldera-autostart.service path typo
   - cyberblue_install.sh line 964: WorkingDirectory=/home/ubuntu/
     CyberBlueSOCx -> /home/ubuntu/CyberBlue
   - fix-docker-external-access.sh line 276: ExecStart=/home/ubuntu/
     CyberBlueSOCx/... -> /home/ubuntu/CyberBlue/...
   Previously the stray "x" suffix baked a broken path into systemd,
   silently breaking Caldera auto-start on every reboot.

Tested: bash -n passes on all three shell scripts; runtime verification
in Phase 4 (full desktop-stack install on UTM arm64 VM).

Made-with: Cursor
Two fixes uncovered during the Phase 4 arm64 runtime smoke on the UTM
VM (2026-04-18, tools/native/ARM64_AUDIT.md).

1. Robust latest-release lookup (/releases?per_page=20 + jq filter).

   Nuclei v3.8.0 was published with only nuclei_3.8.0_checksums.txt —
   all per-arch binary zips were missing at the exact time our
   installer hit it (upstream publish race / CI failure on amd64 and
   arm64 alike). The old pattern of:

     curl /releases/latest | jq -r .tag_name

   happily returned v3.8.0 and the subsequent download 404'd with no
   fallback, breaking step 2 of install.sh.

   New helper _latest_release_with_asset(repo, asset-name-regex)
   walks the 20 most recent non-draft / non-prerelease releases and
   returns the newest one that ACTUALLY publishes an asset matching
   our per-arch filename pattern. Applied to nuclei, chainsaw,
   hayabusa, stratus, and zui — every GitHub release pull in the
   script now survives a broken latest release on either arch.

2. Rust toolchain + libpcap-dev for NetExec's native wheels.

   On a clean arm64 VM (Ubuntu Server 24.04), the NetExec
   install failed with:

     error: can't find Rust compiler
     Failed building wheel for aardwolf
     Failed building wheel for pcapy-ng

   aardwolf (RDP lib) uses PyO3 bindings, pcapy-ng needs libpcap
   headers. Both land via apt on both archs. Added:

     apt-get install -y pipx rustc cargo \
       libpcap-dev libssl-dev libffi-dev

   before the `pipx install git+...NetExec` call. Works equally on
   amd64 (idempotent if already installed).

Tested: on UTM arm64 VM with these fixes, `sudo bash install.sh`
completes end-to-end and 49/49 summary tools resolve via command -v
(100% — see tools/native/ARM64_AUDIT.md verification gate).

Made-with: Cursor
…ooting

- Add cyberblue_init.sh as the post-prereqs umbrella that chains
  cyberblue_install.sh then tools/native/install.sh (which in turn
  auto-chains install-desktop.sh when a desktop session is present).
  Each downstream script stays independently runnable and idempotent.

- cyberblue_install.sh: preventive fix for the /run/docker.sock
  permission-denied race seen on fresh Ubuntu Server 24.04 arm64
  (UTM). After adding the user to the docker group we now force a
  docker.socket restart and re-apply root:docker 0660, so the socket
  no longer inherits a stray numeric uid from an earlier daemon
  start.

- .env.example: drop the stale duplicate YETI_AUTH_SECRET_KEY and
  SURICATA_INT lines that had accumulated at EOF across installs,
  and add an inline comment explaining the installer's SURICATA_INT
  auto-detect behavior plus the common per-host interface names.

- docs/TROUBLESHOOTING.md: document the docker.sock ownership race
  (diagnosis + safe recovery) and the stale caldera-autostart typo
  healing one-liner for hosts upgrading from pre-Phase-3 installs.

Captured in ENHANCEMENTS.md #53 (Phase 4 hard-won lessons).

Made-with: Cursor
- README: widen System Requirements to explicitly list amd64 and arm64
  host environments (AWS Intel/AMD, AWS Graviton, UTM on Apple Silicon,
  VMware, VirtualBox, Raspberry Pi-class, bare metal). Add a
  "Full Platform" install section pointing at cyberblue_init.sh for
  operators who want the Docker stack + native blue-team toolkit +
  XFCE desktop UX in one command. Link the new ARM64_SUPPORT guide
  and the native toolkit README under Documentation.

- docs/ARM64_SUPPORT.md (new): operator-facing dual-arch reference.
  Documents the supported host-environment matrix, per-service arm64
  status across the Docker stack (with Wazuh local-rebuild, tenzir
  amd64-only, Fleet amd64-only), native toolkit bucket summary
  (green / fix-url / rebuild / gap), desktop UX layer, the four
  locked amd64-only exceptions (Fleet, tenzir, sysmonforlinux, zui),
  quick verification commands, and known arm64 build-time costs
  (Wazuh rebuild ~25min, NetExec compile ~3min).

- QUICK_REFERENCE.md: mark FleetDM row as amd64-only so arm64
  operators know upfront why port 7007 may be absent.

Made-with: Cursor
grafana, portal healthcheck)

All four issues were reported after Phase 4 sign-off; all are real bugs
that existed pre-arm64, not arm64 regressions.

1. MITRE Navigator (both arches, broken): compose mapped "7013:4200"
   but the runtime stage is nginx:alpine serving the compiled Angular
   bundle on port 80 — nothing listens on 4200 in the prod image
   (4200 is the Angular dev-server port, only used by `npm run serve`).
   Result: connection refused on http://host:7013. Changed mapping to
   "7013:80" to match attack-navigator/Dockerfile EXPOSE 80.

2. EveBox (both arches, broken): evebox self-signs a cert on first
   boot and serves HTTPS on 5636, so http://host:7015 was rejected
   with "Received HTTP/0.9 when not allowed" and the portal tile
   silently failed to load. Added --no-tls to the evebox command to
   mirror the existing --no-auth pattern — this is a lab-convenience
   platform, every other core service is plain HTTP on its 70xx
   port, and the self-signed cert provides no real security while
   breaking every http:// link pointing at 7015.

3. Grafana (both arches, missing entirely): tools/grafana/docker-
   compose.yml carries `profiles: [standard]` and is aggregated via
   docker-compose.extras.yml, but cyberblue_install.sh only invoked
   `docker compose up --build -d` — no -f extras flag, no --profile,
   so grafana (and zeek, honeypots, crowdsec) never got a container.
   Added a new Step 2.10a that brings up the extras stack as a
   follow-on invocation, non-fatal if one image is arm64-missing
   so the core stack stays green. Also updated the post-Docker-
   restart re-bring-up in Step 2.11 so extras come back with core.

4. Portal (cosmetic: always "unhealthy"): portal/Dockerfile set
   HEALTHCHECK curl http://localhost:5500/health, but when
   ENABLE_HTTPS=true (our default) Flask only binds HTTPS 5443 and
   5500 isn't served — so the healthcheck was failing on every
   probe for every install, marking the container unhealthy even
   though https://host:5443 was perfectly reachable. Several users
   (including in the arm64 smoke) saw the red badge and assumed
   the portal was broken. Updated the HEALTHCHECK to probe HTTPS
   first and fall back to HTTP, so it passes in either ENABLE_HTTPS
   mode. Also widened start-period from 5s to 15s to cover the
   Flask import time.

Impact: fixes four pre-existing bugs that were hiding behind docker
port mappings / "unhealthy" badges / missing-container confusion
and visible on both amd64 (AWS reference) and arm64 (UTM) stacks.

Made-with: Cursor
@cyberblu3s

Copy link
Copy Markdown
Owner Author

Follow-up fix pushed (3035927): four web apps that were broken on BOTH arches pre-existing.

# Service Arch Root cause Fix
1 MITRE Navigator both Compose mapped 7013:4200 but nginx serves the built bundle on port 80 (4200 is only the Angular dev-server port) — connection refused on localhost:7013 Compose mapping → 7013:80
2 EveBox both evebox self-signs a cert and serves HTTPS on 5636, so http://host:7015 was rejected with "HTTP/0.9 not allowed". Every portal tile / README link pointed at http:// Added --no-tls to the evebox command (mirrors the existing --no-auth lab convention)
3 Grafana both tools/grafana/docker-compose.yml has profiles: [standard] aggregated via docker-compose.extras.yml, but installer only ran plain docker compose up -d — grafana/zeek/honeypots/crowdsec never got a container Added Step 2.10a that invokes the extras stack with --profile standard, non-fatal on missing arm64 images
4 Portal (unhealthy) both Dockerfile HEALTHCHECK curled http://localhost:5500/health but with ENABLE_HTTPS=true Flask binds 5443 only, so healthcheck always failed → red "unhealthy" badge although the service was perfectly reachable at https://host:5443 HEALTHCHECK now probes HTTPS 5443 first then falls back to HTTP 5500

Verified on the UTM arm64 VM:

port 5443  https=200 http=000   (portal — HTTPS served, "healthy" now)
port 7013  https=000 http=200   (mitre-navigator — was refused)
port 7015  https=000 http=200   (evebox — was TLS-rejected)
port 3000  https=000 http=302   (grafana — new container, login redirect)

cyber-blue-portal  healthy (failingstreak=0)
grafana            healthy
crowdsec           healthy
opencanary         healthy

Will retest on the AWS amd64 reference next — but since none of these fixes are arch-specific, the amd64 reference should behave identically after cyberblue_install.sh re-runs (or a targeted recreate of the four containers).

@cyberblu3s

Copy link
Copy Markdown
Owner Author

Verified the same four fixes on the AWS amd64 reference box (18.118.5.223) — applied surgically (not via git, because the AWS working tree has AWS-specific runtime edits to .env / portal/app.py that shouldn't leak into the PR). Same three edits, same rebuild dance:

Service Before After
Portal unhealthy (failingstreak=2901) — healthcheck had been failing for hours healthy (failingstreak=0)
MITRE Navigator http://localhost:7013 -> 000 (connection refused, nginx on 80 but mapped to 4200) http://localhost:7013 -> 200
EveBox http://localhost:7015 -> 000 (HTTPS-only + http request rejected) http://localhost:7015 -> 200
Grafana already running (someone had brought up extras manually — confirms the installer was the gap) 302 login redirect, healthy

Container state on AWS after the fix:

cyber-blue-portal  Up 45 seconds (healthy)
evebox             Up 47 seconds
mitre-navigator    Up 57 seconds
grafana            Up 25 hours (healthy)

External reachability from a macOS client → AWS public IP confirms the fixes hold through the real network path (Portal 5443 and Grafana 3000 are open in the Security Group; 7013/7015 aren't in the public SG on that box, but that's orthogonal — they're reachable from the Portal and from the VNC browser inside the instance).

Bottom line: these were four latent bugs on the main branch that the arm64 bring-up just happened to surface while probing the service matrix. Both architectures now green on the four endpoints.

and opensearch-init to avoid the cyber-blue vs cyberblue_default split

Symptom: on the arm64 UTM VM, wazuh-dashboard showed
"Wazuh dashboard server is not ready yet" on port 7001. Logs showed
the dashboard trying to resolve wazuh.indexer and getting ENOTFOUND
in an infinite retry loop.

Root cause: the three wazuh services (wazuh.manager, wazuh.indexer,
wazuh.dashboard) and opensearch-init were the only four services in
docker-compose.yml without an explicit `networks:` key. They silently
fell back to the compose project's "default" network.

With only docker-compose.yml, that default is `cyberblue_default` (the
auto-created one). But docker-compose.extras.yml declares at the bottom:

    networks:
      default:
        name: cyber-blue
        external: true

which renames the default to the external `cyber-blue` network when
both files are loaded together. So whichever compose invocation last
force-recreated each container decided its network:

- Extras-aware invocations  → `cyber-blue`
- Plain compose invocations → `cyberblue_default`

On the arm64 VM the extras-aware up-pass recreated wazuh-indexer and
wazuh-manager (they landed on cyber-blue), but a subsequent plain
invocation recreated wazuh-dashboard onto cyberblue_default — splitting
the stack across two non-routed networks and breaking the dashboard's
`opensearch.hosts: https://wazuh.indexer:9200` DNS lookup.

On AWS the sequence happened to land all three on `cyberblue_default`
consistently, so the bug was latent but not visible — still a real
bug because any operator running `cyberblue_init.sh` on a clean box
or running extras/core compose invocations out of order could trip it.

Fix: add `networks: [cyber-blue]` to all four previously-implicit
services so their network selection is deterministic regardless of
which compose files are passed. Matches every other service in the
file, 31 of which already declare it explicitly.

Audited docker-compose.yml after the fix — every service either has
`networks: [...]` or uses `network_mode: host` (suricata only).

Made-with: Cursor
@cyberblu3s

Copy link
Copy Markdown
Owner Author

Fifth web-app fix: Wazuh dashboard on port 7001.

Symptom (arm64 UTM only, AWS was lucky)

Dashboard stuck on "Wazuh dashboard server is not ready yet". Logs showed [ConnectionError]: getaddrinfo ENOTFOUND wazuh.indexer in an endless retry loop, even though wazuh-indexer was running and healthy.

Root cause

The three wazuh services (wazuh.manager, wazuh.indexer, wazuh.dashboard) plus opensearch-init were the only four services in docker-compose.yml without an explicit networks: key. They fell back to the compose project's "default" network — whose name flips between:

  • cyberblue_default — when only the main compose file is loaded
  • cyber-blue — when docker-compose.extras.yml is also loaded (because extras declares networks.default.name: cyber-blue external: true)

So whichever compose invocation last force-recreated each container decided its network. The bring-up sequence on the arm64 VM ended up with wazuh-indexer and wazuh-manager on cyber-blue and wazuh-dashboard on cyberblue_default — DNS split, dashboard can't reach the indexer. On AWS the sequence happened to land all three on the same default network, so the bug was latent but every bit as real.

Fix

Add networks: [cyber-blue] to all four affected services. Audited the entire compose file afterwards — every service now either declares its network explicitly or uses network_mode: host (only suricata).

Verified on both arches

# ARM64 UTM VM
wazuh-indexer : net=cyber-blue
wazuh-dashboard : net=cyber-blue
wazuh-manager : net=cyber-blue
https://localhost:7001 -> 302  (login redirect, healthy)

# AWS amd64 reference box
wazuh-indexer : net=cyber-blue
wazuh-dashboard : net=cyber-blue
wazuh-manager : net=cyber-blue
https://localhost:7001 -> 302

Full state of every endpoint touched in this PR thread (AWS)

portal             https://localhost:5443/ -> 200
mitre-navigator    http://localhost:7013/  -> 200
evebox             http://localhost:7015/  -> 200
grafana            http://localhost:3000/  -> 302
wazuh-dashboard    https://localhost:7001/ -> 302

Commit 0a0b14c on arm64-bringup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant