Commit graph

299 commits

Author SHA1 Message Date
aa7ca96e95
harden security: file permissions, WebSocket accept validation, backoff jitter
- Self-update: write binary with 0700 permissions instead of 0755
- WebSocket: validate Sec-WebSocket-Accept per RFC 6455 to prevent MITM
- WebSocket: add computeAcceptKey helper with correct RFC 6455 GUID
- Agent: add nextBackoff with 25% jitter to prevent thundering herd
2026-02-12 10:33:17 -06:00
ab2c28c595
harden security: frame limits, HTTPS enforcement, checksum validation, download bounds
- WebSocket: reject frames exceeding 16 MB to prevent OOM
- MikroTik: reject words exceeding 10 MB to prevent OOM
- Self-update: require HTTPS for download URLs
- Self-update: require non-empty SHA256 checksum
- Self-update: use constant-time comparison for checksum verification
- Self-update: bound download size to 100 MB
2026-02-12 10:24:49 -06:00
7ee88dad81
remove legacy Rust build config and artifacts 2026-02-12 10:06:34 -06:00
4e6dd45415
use raw ICMP ping with exec fallback for unprivileged environments 2026-02-12 10:02:54 -06:00
94192f402a
propagate context to job executors for cancellation on disconnect 2026-02-12 09:59:45 -06:00
04d8349a6c
pool protobuf marshal buffers to reduce allocations
Use sync.Pool to reuse 4KB byte slices for protobuf marshaling via
MarshalAppend instead of allocating fresh buffers per result message.
Reduces GC pressure under high-throughput polling.
2026-02-12 09:55:38 -06:00
da8b644f40
batch SNMP results before sending over WebSocket
Accumulate SNMP results and flush in batches of 50 or every 100ms,
whichever comes first. Reduces per-message overhead when thousands
of devices produce results near-simultaneously. Also flushes
remaining results on disconnect/shutdown.
2026-02-12 09:55:08 -06:00
82f5698802
combine WebSocket frame header and payload into single write
Build the entire frame (header + mask key + masked payload) in one
buffer and write it with a single syscall instead of two separate
writes. Removes the failOnNthWrite test that tested the old
two-write implementation detail.
2026-02-12 09:54:31 -06:00
fbaf7a07dc
buffer WebSocket reads to reduce syscalls
Wrap the WebSocket connection in an 8KB bufio.Reader so that small
reads (2-byte frame headers, length extensions) are served from
buffer instead of making individual syscalls.
2026-02-12 09:53:16 -06:00
c57addc6c7
increase channel buffer sizes to handle thousands of devices
Raise write channel from 500 to 10000, SNMP/monitoring result
channels from 1000 to 10000, MikroTik/credential channels to 5000.
Prevents silent data loss when polling thousands of devices.
2026-02-12 09:50:46 -06:00
8b866ae946
add worker pool with bounded concurrency for job dispatch
Replace unbounded goroutine spawning with fixed-size worker pools:
100 workers for SNMP, 20 for MikroTik, 50 for ping. Prevents file
descriptor exhaustion and device overload when handling thousands
of concurrent jobs. Pool provides backpressure when saturated.
2026-02-12 09:50:17 -06:00
07e7ed2745
replace fmt.Sprintf with strconv in hot paths
Use strconv.FormatInt/FormatUint instead of fmt.Sprintf for integer
conversions in snmpValueToString. Use encoding/hex + strings.Builder
instead of fmt.Sprintf loop in formatHex. Use strconv.FormatUint for
WebSocket ref counter. Reduces allocations per OID significantly.
2026-02-12 09:47:42 -06:00
82df96cd59
batch SNMP GETs into chunks of 60 OIDs per PDU
Previously each OID was fetched individually, meaning 1000 OIDs
required 1000 round trips. Now OIDs are batched into groups of 60
(the SNMP max PDU size), reducing round trips by ~50x. Also
pre-sizes the OID value map to avoid repeated rehashing.
2026-02-12 09:41:48 -06:00
92f7c2ed85
fix SNMPv1 walks failing due to unsupported GETBULK
use GETNEXT-based WalkAll for v1 devices instead of BulkWalkAll
2026-02-11 13:04:32 -06:00
01cd109d10
cleanup 2026-02-11 12:42:49 -06:00
289bc3d4f1
fix docker tags by replacing colons in timestamp version
Docker tags cannot contain colons. Use a separate tag-safe format
(colons replaced with hyphens) for Docker tags while keeping the
RFC 3339 timestamp as the version baked into the binary.
2026-02-11 11:48:05 -06:00
297cb8fc91
use UTC timestamp for version instead of git describe 2026-02-11 11:44:19 -06:00
39641b73d4
enable ping in docker container for non-root user
add libcap and setcap cap_net_raw+p on /bin/ping so ICMP works
without container-level capabilities. add NET_RAW cap_add to
docker-compose.example.yml as fallback.
2026-02-11 10:58:30 -06:00
894dcb18e4
skip ping test when raw sockets unavailable 2026-02-11 10:54:15 -06:00
9611cfd620
fix missed errcheck in websocket test 2026-02-11 10:50:00 -06:00
5ad95fac42
fix errcheck lint violations in test files 2026-02-11 10:48:41 -06:00
a36a2b7fd7
increase test coverage and improve logger output 2026-02-11 10:42:19 -06:00
a757bd7615
Optimize CI caching for Blacksmith runners, fix SNMP bulk walk on Mikrotik
- Dockerfile: add BuildKit cache mounts for Go module and build caches,
  pin both stages to Alpine 3.23
- CI: replace BUILDKIT_INLINE_CACHE with registry-based layer caching
  (mode=max) for branch and release builds
- .dockerignore: exclude test files, Rust legacy, nix, and other
  non-build artifacts from Docker context
- SNMP: lower MaxRepetitions from default 50 to 10 to prevent Mikrotik
  routers from timing out during bulk walks
2026-02-11 10:31:10 -06:00
Graham McIntire
86da744e68
Merge pull request #21 from towerops-app/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 4 to 6
2026-02-11 10:20:14 -06:00
dependabot[bot]
51210a5986
Bump actions/checkout from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 16:20:06 +00:00
Graham McIntire
720ccb40e2
Merge pull request #20 from towerops-app/dependabot/github_actions/actions/upload-artifact-6
Bump actions/upload-artifact from 4 to 6
2026-02-11 10:19:59 -06:00
Graham McIntire
62e31e3d05
Merge pull request #18 from towerops-app/dependabot/github_actions/golangci/golangci-lint-action-9
Bump golangci/golangci-lint-action from 7 to 9
2026-02-11 10:19:40 -06:00
Graham McIntire
91820976ae
Merge pull request #19 from towerops-app/dependabot/github_actions/actions/setup-go-6
Bump actions/setup-go from 5 to 6
2026-02-11 10:19:26 -06:00
Graham McIntire
b03edf8f9b
Merge pull request #17 from towerops-app/dependabot/github_actions/actions/download-artifact-7
Bump actions/download-artifact from 4 to 7
2026-02-11 10:19:14 -06:00
dependabot[bot]
c360705fd4
Bump actions/upload-artifact from 4 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 16:18:57 +00:00
dependabot[bot]
e8ae0769a5
Bump actions/setup-go from 5 to 6
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 16:18:53 +00:00
dependabot[bot]
a72ad8a130
Bump golangci/golangci-lint-action from 7 to 9
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 7 to 9.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v7...v9)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 16:18:49 +00:00
dependabot[bot]
5757bd4a13
Bump actions/download-artifact from 4 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 16:18:45 +00:00
Graham McIntire
ee29613047
Merge pull request #16 from towerops-app/golang-rewrite
Full rewrite in golang
2026-02-11 10:18:12 -06:00
58d1af4f84
refactor some code 2026-02-11 10:17:01 -06:00
37f411ec55
cleanup 2026-02-11 10:07:27 -06:00
769ac1f623
Full rewrite in golang 2026-02-11 10:04:30 -06:00
99997e0c1f
Add OID count to SNMP job completion logs
Shows how many OID values were collected and sent back, making it
visible at info level whether SNMP walks returned data or not.
2026-02-11 09:14:36 -06:00
477b5f06cb
Promote job lifecycle logging from debug to info level
Job receipt, start, and completion messages were all at debug level,
making it impossible to tell if the agent was processing jobs without
setting LOG_LEVEL=debug.
2026-02-11 09:09:10 -06:00
81a5d26655
Cleanup from dokku testing 2026-02-11 09:01:18 -06:00
a9bf0b0f8b
Switch from Alpine (musl) to Debian slim (glibc) to fix SNMP SIGSEGV
musl libc has weak fork-safety guarantees in multi-threaded processes.
When Tokio's runtime is running and we fork() for SNMP operations, musl's
internal state can be inconsistent in the child, causing SIGSEGV on every
SNMP walk to Ubiquiti devices. glibc handles this via pthread_atfork.

- Dockerfile: Alpine → rust:1.93-bookworm build + debian:12-slim runtime
- CI: Remove ensure-netsnmp-base/manifest jobs, simplify build deps
- Delete Dockerfile.netsnmp and netsnmp-base.yml workflow
- Add NULL pointer guards in snmp_helper.c for fork-safety
- Add SNMP exception type handling (NoSuchObject/Instance/EndOfMibView)
- Add crash reproduction tests
2026-02-11 08:41:06 -06:00
fc14fa7798
Disable embedded perl in net-snmp build 2026-02-11 08:06:37 -06:00
2542909690
Add missing file dependency for net-snmp configure 2026-02-11 08:03:44 -06:00
0b0d326892
Add net-snmp base image as CI prerequisite
The ensure-netsnmp-base jobs check if the pre-built image exists in
GHCR before any Docker builds start. First run compiles from source
on native runners (~5 min), subsequent runs skip with a ~5s check.

build-branch and build-release now depend on ensure-netsnmp-manifest.
2026-02-11 08:01:23 -06:00
2386f0d5a5
Upgrade net-snmp to 5.9.5.2 to fix SIGSEGV in SNMP walks
Alpine 3.23 ships net-snmp 5.9.4 which has segfault bugs in varbind
construction and buffer overflows in octet string handling. Version
5.9.5.2 fixes these issues.

Adds a pre-built base image (Dockerfile.netsnmp) compiled from source
so the net-snmp build doesn't run on every agent deploy. The base
image is built separately via the netsnmp-base workflow and referenced
in the main Dockerfile.
2026-02-10 17:58:43 -06:00
06c45cd404
Fix SIGSEGV in forked SNMP child: remove double init_snmp() call
In Fork mode the parent never calls snmp_init_library(), so
pthread_once remains untriggered. The child called init_snmp()
directly, then snmp_open_session() triggered pthread_once calling
init_snmp() a second time. This double-initialization of libnetsnmp
caused signal 11 in every child process.

Let snmp_open_session() handle initialization via pthread_once,
which correctly initializes exactly once and sets numeric OID format.
2026-02-10 17:35:49 -06:00
0db587338e
Use Blacksmith runner for amd64 release build
Switches from ubuntu-latest (2 vCPU) to blacksmith-4vcpu-ubuntu-2404
to match the arm64 build runner and speed up Rust compilation.
2026-02-10 16:52:59 -06:00
4573869607
Reduce log noise by changing INFO to DEBUG for routine operations
Changed the following from INFO to DEBUG level:
- Channel join messages
- Job execution start/completion messages
- SNMP operation messages (GET/WALK)
- Device health check results
- MikroTik job execution
- Device poller thread lifecycle
- Credential test messages
- OID collection results

These messages were generating excessive noise during normal operation.
Kept INFO level for:
- Initial connection establishment
- Shutdown/restart messages
- Update operations
- Errors (already at ERROR/WARN level)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 16:49:58 -06:00
73b75c8aa4
Disable MIB loading in forked SNMP child processes
Net-SNMP was crashing (signal 11) on every operation despite fork isolation.
The issue was that init_snmp() tries to load MIB files by default, which can
cause crashes if MIB files are missing, corrupted, or incompatible.

Since we only use numeric OIDs (e.g., "1.3.6.1.2.1.1.1.0"), we don't need
MIB name resolution at all. This change explicitly disables MIB loading in
the child process by:

1. Setting MIBS and MIBDIRS environment variables to empty
2. Calling netsnmp_set_mib_directory("") to disable MIB directory search
3. These must happen BEFORE calling init_snmp()

This should fix the persistent SIGSEGV crashes in SNMP operations.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 16:39:32 -06:00
7b793b315a
speed up tests: reduce SNMP timeout from 1s to 200ms 2026-02-10 16:19:54 -06:00