Files
worker/docs/changelog.md
Gleb Tv bd6070ee1f
Все проверки выполнены успешно
CI / test (push) Successful in 3m13s
Docker / Build and publish worker image (push) Successful in 10m35s
feat(installer): build worker source over SSH
2026-08-13 00:07:43 +03:00

12 KiB

Changelog

2026-08-12

Source-install hardening review

  • Fail-closed remote scripts: checkout, branch resolution, and build steps now run under set -eu (and package/record steps chain with &&), so a failed checkout or fetch can never be masked by a stale rev-parse or subsequent command. The checkout step additionally refuses (git diff --quiet / --cached --quiet) before the destructive checkout -B, because -B silently discards local changes and would otherwise never fail on a dirty tree. A dirty-tree checkout failure surfaces as a check out branch error before the build runs; the new TestSourceInstallSSHCheckoutFailureNotMasked unit test and TestSourceInstallDirtyCheckoutPreservesStaging Docker test prove the previous staging binary and commit record are preserved byte-for-byte.
  • Atomic toolchain replacement: the Go toolchain is downloaded, SHA-256 verified, extracted into a same-filesystem staging dir, verified to report the target version, and only then swapped into ToolchainDir with the prior toolchain moved to a sibling .go-backup that is restored on swap failure. A failed download/verify/extract/swap never destroys the prior Go.
  • Record-after-build pairing: rsmon-worker.commit is written only after a successful build, so the record and the staged binary always correspond to the same commit. The build verifies <stage>.new --version before an atomic mv -f over the previous staging binary; GOMODCACHE is now set alongside GOCACHE inside the build dir so reruns reuse both caches.
  • Origin verification: an existing checkout's remote.origin.url must exactly match the configured repository before anything is fetched or built.
  • Repository hardening: only https:// clone URLs without userinfo are accepted (ValidateRepoURL, enforced before dialing and again when planning).
  • Explicit charset validation for Go version and architecture overrides (sshinstall.ValidGoVersion / ValidGoArch) before any remote mutation.
  • Bounded remote execution: each remote command is capped by --session-timeout (default 30m) and captured stdout is size-bounded alongside the existing stderr bound; deploy's streaming runRemote keeps its historical no-timeout behavior.
  • The source installer now defaults to the remote's default branch (the public repo publishes master) instead of the plan's stale main default, while --branch still pins an explicit branch that must exist remotely. The README quickstart no longer shows the incorrect --branch main.
  • CLI secret flags keep their compatibility, but docs now explicitly state that file options (-password-file, etc.) keep secrets out of argv and shell history while direct flags expose them through the process list.
  • The harness accepts RSMON_TEST_DOCKER_DNS (comma-separated) to pin docker run --dns for fixture containers, so internet-facing installs are not at the mercy of a flaky local resolver.

Remote source-install execution (work package 3)

  • Added installer.SourceInstall (internal/installer/sourceinstall.go): executes the source-install flow through the existing SSH transport, reusing the deploy command's SSHOptions (keys, passphrases, passwords, sudo passwords, known-hosts, pinned fingerprints) and its privilege path. Extracted the shared SSHOptions struct and a sudoWrap helper so deploy and source install cannot diverge.
  • Steps implemented: minimal package-prerequisite install per distro (apk/apt/pacman/dnf, never a compiler), SHA-256-verified Go 1.26 toolchain download/extraction with an idempotent version-skip and temp-dir cleanup, clone-or-update of the public repository (with a bounded 3-attempt retry for transient DNS/TLS/proxy failures), resolution of the remote default branch (a pinned branch must exist remotely), a resolved branch and commit record at <BuildDir>/rsmon-worker.commit, and a staging build (CGO_ENABLED=0, -trimpath, repository -ldflags) verified via --version. The running service, config, and data directory are untouched (work package 4 boundary).
  • Security: every interpolated remote value is single-quoted; branch and commit values are strictly validated; no worker token or control-plane credential is sent; sudo passwords travel only over session stdin; remote errors are bounded (stderr truncated in runRemoteOutput).
  • Added unit tests for the remote scripts, option validation, branch/commit parsing, sudo wrapping, the secrets-absent contract, and an in-process real-SSH orchestration flow (with missing-pinned-branch, build-failure, and detection-failure paths).
  • Added TestSourceInstallFixtures to the Docker/OpenSSH harness: each of Alpine, Ubuntu, and Arch installs from a clean state through the real harness transport (prerequisite install, verified Go 1.26, clone, resolved commit, staging build), then a rerun proves idempotency (same branch, toolchain reuse, no temp leaks). All three resolved the public repo's master at 4651deb2... in the recorded run. make test-ssh timeout raised to 60m.
  • Documented the branch-resolution reality: the public repository currently publishes master, and the installer records whatever the remote default branch resolves to.

Source-install harness and planning foundations (work packages 1-2)

  • Added internal/installer/harness: a reusable Docker/OpenSSH test harness that builds real OpenSSH containers for Alpine, Ubuntu, and Arch, waits for real network SSH readiness, captures the server host key into a temp known_hosts file, and tears the container, network, per-instance fixture image tag, and temp dir down reliably. It uses the golang.org/x/crypto/ssh library and known_hosts verification semantics the installer's deploy path relies on (the harness owns its connection code rather than reusing the installer functions) and never mocks SSH. A fresh known_hosts file trusts the first key (TOFU); the host-key mismatch test proves a different key is rejected before any command runs.
  • Added distro fixtures under internal/installer/harness/testdata/fixtures. Alpine defaults to the reg.rsxx.ru/library/alpine:3 mirror; Ubuntu and Arch fall back to Docker Hub refs overridable via RSMON_TEST_IMAGE_<NAME>. Each fixture starts clean (no Go, no worker source) and authenticates with a bundled test key; password auth is disabled. The test key is strictly test-only - it grants root only to the disposable fixture containers - and must never be used outside the harness.
  • Added opt-in integration controls: the Docker tests run only with RSMON_TEST_DOCKER=1 (make test-ssh); default make test and go test ./... skip them and never pull or start containers. make test also pins RSMON_TEST_DOCKER=0 so an exported opt-in flag cannot leak into the unit run.
  • Integration tests assert real SSH round trips, clean target state, distro / package-manager / init detection per fixture, a full source plan including the pinned Go toolchain, host-key mismatch rejection, host-key stability, failed-start cleanup, and complete teardown (container, network, fixture image tag, and temp dir gone).
  • Added internal/sshinstall: pure, unit-tested detection and planning for the source installer - os-release parsing, distro/package-manager/init resolution, uname -m to Go archive mapping, pinned Go 1.26 toolchain with published SHA-256, and a reviewable ordered plan. No remote execution yet.
  • make test now includes the new packages; make test-ssh runs the live fixture matrix.

Public endpoint configuration (milestone 1 of public-endpoint-and-identity)

  • PUBLIC_URL is now the canonical advertised public origin; the legacy WORKER_URL is accepted only for the bounded migration and logs a startup deprecation warning. PUBLIC_URL wins whenever both are set, and the installer drops WORKER_URL from freshly written env files when PUBLIC_URL is present.
  • Startup and install validate the origin shape: absolute http/https URL with scheme and authority only; userinfo, query, fragment, and any path other than / are rejected.
  • Plain-HTTP PUBLIC_URL on a non-loopback host is rejected in an explicitly production environment (DEPLOY_ENV, RSMON_ENV, or GO_ENV = production); other environments keep the historical warning.
  • internal/wire adds public_url to WorkerInit (control plane to worker), keeping the legacy url field for old control planes; the worker prefers public_url and rejects unusable values, keeping the previous accepted URL. RegisterRequest.public_url is the registration contract for the pending RSMon counterpart (the worker does not currently transmit the URL during registration; it consumes the accepted endpoint from WorkerInit).
  • The legacy WORKER_URL is held only to the tolerant absolute-URL check (no newly rejected legacy shapes); PUBLIC_URL is held to the strict scheme-and-authority origin shape. Both reject a missing hostname, e.g. https://:27401.

2026-07-19

Standalone installation and deployment

  • Added rsmon-worker install for installing the current binary, a mode-0600 environment file, and a root-owned systemd service.
  • Added rsmon-worker deploy for installing workers over SSH with key or password authentication, optional secret files, and host-key verification through known_hosts or a pinned fingerprint.
  • Added --token-file, --url, --api-key, and --no-start deployment options.
  • Added optional --docker deployment using a prebuilt image. Docker install and deploy now require an immutable repository@sha256:... reference before any Docker or remote-host mutation; the former mutable latest default is no longer accepted.
  • Simplified the default systemd service to Type=simple, User=root, and Restart=on-failure.
  • Reworked the legacy scripts/install-systemd.sh script as a compatibility wrapper around the built-in installer.

Standalone repository cleanup

  • Removed the remaining certificate-bundle fallback under /data/rsmon and updated its documentation.
  • Changed Docker build and runtime bases to public Go and Debian images.
  • Documented binary, systemd, Docker, and SSH deployment workflows.
  • Added installer and SSH host-verification tests.

Verification

  • Passed make test, make build, go vet, and go mod verify.
  • Validated systemd units, Compose configuration, Docker image pull and execution, and production worker job reporting.
  • Published the changes as commit 3256dcd (feat: add worker install and deploy) on master, triggering the Docker image workflow.

Task protocol and local audit hardening

  • Required one task-envelope branch, matching outer/inner job IDs, and a non-empty lease token before local execution.
  • Added structured terminal failures for unsupported check kinds and safely attributable malformed envelopes.
  • Recorded delegated notification outcomes in the bounded /notifications view using only job ID, method, status, duration, and time.
  • Added static permanent handling for invalid deadlines and recovered notification executor panics without retaining secret-bearing text.
  • Removed the critical-cluster test-config endpoint, CLI flag, environment switch, and production helper; hardcoded config application is test-only.
  • Made runner token rotation connection-scoped and in-memory: it reconnects without stopping web, inventory, metrics, or cluster subsystems. Durable token storage, bootstrap exchange, rotation acknowledgement, and revocation remain unimplemented.
  • Added bounded resend of dequeued check and notification result envelopes after websocket reconnect; control-plane application remains at-least-once and must deduplicate by leased job and lease token. Failed metric snapshots are dropped and replaced by the next periodic tick, not replayed.
  • SIGTERM stops new dispatch and waits for active work, but stale-lease acknowledgement, bounded graceful final-result drain, and duplicate-frame coverage remain open.