- Add custom RateLimitMiddleware using governor crate for per-IP rate limiting
- Two-tier rate limiting: destructive (20 req/min, burst 10) and read (120 req/min, burst 30)
- Health endpoints (/health, /api/v1/system/info) exempt from rate limiting
- Add max_queue_depth to JobManager (default: 100, configurable via config.yaml)
- Return 429 Too Many Requests with Retry-After header when queue is full
- Add RateLimitConfig to config.yaml with all rate limit settings
- Add 10 tests covering rate limiting, queue depth, and configuration defaults
CI/CD Pipeline / Verify Enrollment CLI Flag (push) Successful in 57s
CI/CD Pipeline / Build Debian Package (push) Failing after 4s
CI/CD Pipeline / Build RPM Package (push) Successful in 2m12s
CI/CD Pipeline / Build Arch Package (push) Successful in 2m18s
CI/CD Pipeline / Build Alpine Package (push) Failing after 3m7s
- Remove all private key files from git tracking (git rm --cached)
- configs/certs/ca.key.pem, server.key.pem, client001.key.pem
- tests/e2e/certs/client.key
- Also remove public certs from configs/certs/ (generated at runtime)
- Add .gitignore patterns for *.key, *.key.pem, configs/certs/*.pem, *.srl
- Add scripts/generate-dev-certs.sh for runtime test cert generation
- Update Python e2e test to generate certs on demand (ensure_certs())
- Update test_wrong_cert_connection to generate wrong-CA certs at runtime
- Add gitleaks secret scanning job to CI workflow
- Update SECURITY_FINDINGS_REPORT.md with critical finding for Issue #12
- Update SECURITY_CONTROLS_MATRIX.md evidence references
- Add README.md to configs/certs/ and tests/e2e/certs/
Private keys were dev/test only - no production key rotation needed.
Git history purge with filter-repo will follow after PR merge.
Co-authored-by: git-echo <git-echo@moon-dragon.us>
CI/CD Pipeline / Build Debian Package (push) Failing after 4s
CI/CD Pipeline / Build Arch Package (push) Successful in 2m24s
CI/CD Pipeline / Build RPM Package (push) Successful in 2m15s
CI/CD Pipeline / Build Alpine Package (push) Successful in 3m16s
* test: add CRL unit tests and CrlAwareVerifier construction tests (PR 6 of 6)
* fix(ci): rename fmt job to match required status check context
---------
Co-authored-by: git-echo <git-echo@moon-dragon.us>
Implements agent-side CRL consumption for mTLS certificate revocation
checking, as specified in issue #20.
Changes:
- NEW: src/auth/crl.rs - CRL loading, parsing, signature verification,
in-memory revoked serial index (HashSet), 24h background refresh task
- MODIFY: src/auth/mtls.rs - CrlAwareVerifier wrapping WebPkiClientVerifier
with post-chain CRL serial lookup; fails closed on invalid signature,
degrades gracefully when CRL is missing
- MODIFY: src/auth/mod.rs - Register crl module, re-export CrlState/CrlStatus
- MODIFY: src/config/loader.rs - Add crl_path field to TlsConfig
- MODIFY: src/main.rs - Load CRL on startup, spawn refresh task, wire
SharedCrlState into server and health endpoint
- MODIFY: src/api/handlers/system.rs - Add crl_status and crl_age_seconds
to health check response
- MODIFY: Cargo.toml - Add arc-swap, base64 deps; enable x509-parser
verify feature for CRL signature verification
Design decisions:
- ArcSwap for lock-free atomic CRL state swaps on the hot path
- O(1) serial lookup via HashSet<String> of hex-encoded serials
- Stale CRL = continue serving + warn + health reports degraded
- Invalid CRL signature = refuse to start (fail-closed)
- Missing CRL = fall back to WebPKI-only (backward compatible)
Companion to PR #26 in linux-patch-manager (manager-side CRL generation)
Refs: #20
Previous build runs leave root-owned artifacts in releases/ directory
which causes actions/checkout@v4 to fail with EACCES on subsequent runs.
- Added sudo rm -rf releases/ before checkout in all 6 self-hosted jobs
- Alpine build unaffected (runs in Docker container, clean each run)
Co-authored-by: git-echo <git-echo@moon-dragon.us>
- Force HOME=/root in build-alpine.sh for consistent key location
- Use find instead of ls for key discovery (handles dash-prefixed filenames)
- Search multiple paths for generated keys
- Copy keys from KEY_DIR to builduser home directory
- Set env.HOME=/root in Alpine container spec
- Remove separate abuild-keygen step (handled by build-alpine.sh)
- Add error exit if no signing key found
Co-authored-by: git-echo <git-echo@moon-dragon.us>
CI/CD Pipeline / Verify Enrollment CLI Flag (push) Successful in 1m5s
CI/CD Pipeline / Build Debian Package (push) Failing after 4s
CI/CD Pipeline / Build Arch Package (push) Successful in 2m22s
CI/CD Pipeline / Build RPM Package (push) Successful in 2m17s
CI/CD Pipeline / Build Alpine Package (push) Successful in 3m7s
* fix: switch to build-package.sh for .deb builds
Replace dpkg-buildpackage with scripts/build-package.sh using
dpkg-deb --build approach. This bypasses the dpkg-buildpackage
subprocess chain (dh → make → debian/rules → cargo) which
does not inherit the rustup environment (RUSTUP_HOME, CARGO_HOME,
default toolchain) from GitHub Actions.
Same approach as Linux-Patch-Manager which passes CI.
- Add scripts/build-package.sh (modeled after Manager)
- Add Version and Installed-Size to debian/control
- Update CI workflow to use build-package.sh
- Fix release files path (project root, not ../)
* fix: extract only binary package paragraph from debian/control
dpkg-deb --build expects a single control paragraph starting
with Package: field. The debian/control file has two paragraphs
(source + binary). The awk command extracts only the binary
package paragraph to avoid dpkg-deb parse errors.
* fix: generate DEBIAN/control from scratch in build-package.sh
dpkg-deb --build is fundamentally incompatible with debian/control
which uses dpkg-buildpackage substitution variables like
${shlibs:Depends} and ${misc:Depends}. Generate a clean control
file from scratch in the script to eliminate all incompatibilities.
- No substitution variables
- No source paragraph
- No Build-Depends
- Homepage points to GitHub
- Installed-Size calculated before control file generation
---------
Co-authored-by: git-echo <git-echo@moon-dragon.us>
CI/CD Pipeline / Verify Enrollment CLI Flag (push) Successful in 1m6s
CI/CD Pipeline / Build Debian Package (push) Failing after 4s
CI/CD Pipeline / Build Arch Package (push) Successful in 2m20s
CI/CD Pipeline / Build RPM Package (push) Successful in 2m13s
CI/CD Pipeline / Build Alpine Package (push) Successful in 3m5s
The debian/rules override_dh_auto_build was sourcing $HOME/.cargo/env
which fails under sudo where HOME=/root while cargo is at
/home/runner/.cargo/. The CI workflow already passes PATH via
"sudo env PATH=$PATH", so cargo is in PATH without sourcing.
Co-authored-by: git-echo <git-echo@moon-dragon.us>
CI/CD Pipeline / Build RPM Package (push) Successful in 2m35s
CI/CD Pipeline / Build Arch Package (push) Successful in 2m37s
CI/CD Pipeline / Build Alpine Package (push) Successful in 3m32s
CI/CD Pipeline / Build Debian Package (push) Successful in 2m2s
- Add debhelper to system dependencies for dpkg-buildpackage
- Add permissions: contents: write for GitHub Release creation
- Fixes Build & Release job failures on tag push
Co-authored-by: git-echo <git-echo@moon-dragon.us>
- Auto-enrollment on startup when certs are missing/invalid and enrollment.manager_url configured
- Certificate validation (existence, parse, expiry, key match, CA trust)
- --enroll exits after completion (no port conflict with systemd service)
- --renew-certs flag for manual cert renewal
- SO_REUSEADDR on TcpListener::bind (prevents Address already in use)
- Polling token persistence for enrollment resume after restart
- Exit code strategy (0=clean, 1=error, 2=enrollment in progress)
- HTTP 409 (host already exists) handling during enrollment
- Move 'Listening on' log after actual bind
- Increase RestartSec to 10s and add StartLimitBurst=5
- Postinst checks for certs and enrollment URL, prints guidance
- EnrollmentConfig.manager_url changed to Option<String>
- cert_renewal_threshold_days and polling_token config fields
- Updated SPEC.md and DEPLOYMENT_GUIDE.md with new workflow
- RCA document for crash loop root cause analysis
- Version bumped to 1.2.0
Automates version bumps across all version source files:
- Cargo.toml (PRIMARY)
- debian/changelog (prepend new entry)
- install.sh (update VERSION variable)
- Stale references check after bump
Usage: ./scripts/bump-version.sh <new_version> <old_version>
Root causes of ALL Alpine build failures:
1. ci.yml: Verify Alpine package step was TRUNCATED at line 339 -
missing closing quote, then clause, fi, and entire upload step.
This caused YAML parse failure every run.
2. build-alpine.sh: Copy path was /home/builduser/packages/home/x86_64/
but abuild outputs to /home/builduser/packages/builduser/x86_64/.
The find fallback caught stale packages from previous builds.
Fixes:
- Complete the Verify Alpine package step with proper if/fi
- Add Upload to Gitea Release step for Alpine (was completely missing)
- Fix abuild output path in build-alpine.sh
- Revert build-alpine.sh to original (no cleanup lines)
- Remove CI Alpine cleanup step entirely
- Keep version verification and exact version upload in CI
- The original build worked fine without cleanup; stale packages
are caught by version verification
- Remove mkdir -p /home/builduser/packages/home/x86_64/ that was creating
root-owned directories that abuild (running as builduser) couldnt write to
- Keep targeted rm -f of stale .apk files only
- abuild creates its own output directories with correct ownership
- Alpine: clean entire /home/builduser/packages/ before abuild (not just releases/)
- Alpine: add version verification step to CI (like RPM already has)
- Alpine: upload uses exact version match instead of head -1
- Debian: add u2404 suffix to build-deb output filename
- Remove duplicate 1.1.12 entry from debian/changelog
- Add /releases/ to .gitignore to prevent tracking build artifacts
- Remove old 1.0.0 .deb files from git tracking
- Add stale .apk cleanup to build-alpine.sh (matching build-arch.sh)
- Add cleanup step to CI Alpine workflow to remove stale packages
Fixes Alpine package version mismatch caused by old artifacts in releases/
- New src/packages/cache.rs module with PackageCacheState, stale detection,
state persistence, 404 retry logic
- Add refresh_package_cache() and last_cache_update() to PackageManagerBackend
trait, implemented on all 5 backends (APT, DNF, YUM, APK, Pacman)
- Health check now reports last_cache_update and cache_status fields,
triggers cache refresh if stale (>4h), returns degraded on failure
- Patch apply jobs now force cache refresh before applying patches,
with 404/fetch error retry (1 retry after cache refresh)
- Cache state persists to /var/lib/linux_patch_api/state/cache.json
- Version bump to 1.1.17
- Update ARCHITECTURE.md and REQUIREMENTS.md (FR-007)
Closes: #2