CI Pipeline / Rust Format Check (push) Successful in 6s
CI Pipeline / Clippy Lints (push) Successful in 52s
CI Pipeline / Rust Unit Tests (push) Successful in 1m10s
CI Pipeline / Security Audit (push) Successful in 1m26s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 15s
CI Pipeline / Build .deb & Release (push) Has been skipped
* feat(pki): add CRL generation, distribution endpoint, and enrollment bundle extension
Implements manager-side CRL infrastructure for issue #7:
- Add CertAuthority::generate_crl() using rcgen 0.13
- Add GET /api/v1/pki/crl.pem public endpoint
- Extend PkiBundle with ca_chain and crl_pem fields
- Update enrollment route to include CRL in bundle
- Mount pki route as public endpoint
- Add proptest dev-dependency
* style: fix cargo fmt in enrollment.rs
---------
Co-authored-by: Draco Lunaris <331325+Draco-Lunaris@users.noreply.github.com>
CI Pipeline / Rust Format Check (push) Successful in 8s
CI Pipeline / Clippy Lints (push) Successful in 50s
CI Pipeline / Rust Unit Tests (push) Successful in 1m8s
CI Pipeline / Security Audit (push) Successful in 5s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 15s
CI Pipeline / Build .deb & Release (push) Has been skipped
Encrypt three sensitive secrets that were stored in plaintext: OIDC client_secret, SMTP smtp_password, TOTP totp_secret. AES-256-GCM via pm-core::crypto helper. New per-install key at /etc/patch-manager/keys/secret-encryption.key, separate from health-check.key for blast-radius isolation. MASKED placeholder behavior in API responses is preserved.
23 files changed, +1248 / -28. Closes#6.
Hardens the IP allowlist in require_auth against the two bypasses filed in #3.
1. Bypass via missing X-Forwarded-For (no IP to check, allowlist skipped).
2. Spoofing via attacker-controlled X-Forwarded-For (header trusted unconditionally).
Resolves both by deriving the client IP from the socket peer (ConnectInfo<SocketAddr>) and only honoring X-Forwarded-For when the immediate peer is in a new security.trusted_proxies allowlist (default empty = strict). Fails closed with 403 forbidden_ip when a non-empty allowlist is configured and the client IP cannot be determined. Empty ip_whitelist continues to mean allow all (preserved for dev installs).
27 pm-auth tests pass (12 new resolver + 8 new middleware + 7 existing). Spec: tasks/ip-allowlist-spec.md.
ClosesDraco-Lunaris/Linux-Patch-Manager#10
The browser WebSocket endpoint at GET /api/v1/ws/jobs previously
authenticated solely via a single-use, 60-second ticket passed as a query
parameter. A leaked ticket (browser history, Referer, proxy logs, support
bundles) could be redeemed from any origin, enabling Cross-Site WebSocket
Hijacking (CSWSH).
This change adds a second gate: the Origin header must match an explicit
allowlist. The check runs BEFORE ticket validation so that rejected
cross-origin probes do not consume the legitimate users ticket.
Changes:
- pm-core: new security.allowed_origins config field; default derived
from sso_callback_url; startup warning if both are unparseable
- pm-web: ws_handler extracts HeaderMap and calls check_origin first;
returns 403 on missing/malformed/disallowed origins
- config: documented allowed_origins key in config.example.toml
- docs: security-review.md section 1.4 (WebSocket Origin Allowlist)
- tests: 40 unit tests (7 pm-core, 33 pm-web)
- Replace custom DashMap<IpAddr, Instant> rate limiting in enrollment.rs
that fell back to 0.0.0.0 when X-Forwarded-For was missing, causing
ALL enrollment traffic to share a single global rate limit bucket
- Use tower_governor with SmartIpKeyExtractor for proper per-IP rate
limiting that respects X-Forwarded-For headers (critical behind HAProxy)
- Add three configurable rate limit tiers via config.toml:
* Enrollment: 5 req/min per IP, burst 3 (strict)
* Auth: 20 req/min per IP, burst 10 (moderate)
* API: 120 req/min per IP, burst 30 (normal)
- Remove enrollment_rate_limits from AppState and cleanup task
- Remove manual rate limit code from enrollment.rs (headers param, IP extraction)
- Add into_make_service_with_connect_info for ConnectInfo fallback
- Add RateLimitConfig to AppConfig with sensible defaults
Fixes: #1
CI Pipeline / Rust Format Check (push) Failing after 2s
CI Pipeline / Clippy Lints (push) Failing after 1s
CI Pipeline / Rust Unit Tests (push) Failing after 2s
CI Pipeline / Security Audit (push) Failing after 2s
CI Pipeline / Frontend Lint & Type Check (push) Failing after 3s
CI Pipeline / Build .deb & Release (push) Has been skipped
- health_poller: persist agent_version from HealthData.version
- health_poller: call /system/info to update os_family, os_name, arch
- enrollment: set os_family and arch from os_details during approval
- enrollment: build os_name from os+os_version when name field absent
- COALESCE in UPDATE preserves existing values when new data unavailable
- version bump 0.1.7 -> 0.1.8
- Refactored azure_sso.rs to sso.rs with generic OIDC provider support
- Added OIDC discovery URL lookup with 1hr TTL caching
- Added PKCE for all providers, client_secret optional for public clients
- Added /api/v1/auth/sso/login and /api/v1/auth/sso/callback routes
- Added /api/v1/auth/azure/* backward-compatible routes
- Added POST /settings/sso/discover and POST /settings/sso/test endpoints
- Frontend: Provider dropdown (Keycloak/Azure AD/Custom OIDC)
- Frontend: Auto-fill discovery URL for Keycloak
- Frontend: Discover Endpoints and Test Connection buttons
- Frontend: Dynamic SSO button based on provider display name
- Made migration 014 idempotent with DO blocks and IF NOT EXISTS
- Fixed debian/install to use /usr/local/bin/ for binaries
- Fixed frontend file path in .deb package
- Reset admin password on dev server
- Fixed database permissions for oidc_config table
1. Compliance CSV/PDF: Replace non-existent pd.total_packages with
jsonb_array_length(pd.installed_packages) and pd.pending_patches
with pd.patch_count. Fix GROUP BY to match new columns.
2. Vulnerability CSV/PDF: Replace non-existent pd.cve_data with
jsonb_array_elements on pd.available_patches JSONB, extracting
cve_ids via nested lateral join. Replace pd.updated_at with
pd.polled_at (actual column name).
3. TypeScript: Remove duplicate PollingConfig interface declaration
in frontend/src/types/index.ts.
4. ReportsPage: Replace Group ID text field with Select dropdown
populated from GET /api/v1/groups, showing group names instead
of requiring UUID input.
5. PDF charts: Increase embed_image scale from 0.18 to 0.28 for
better visibility on A4 landscape pages.
6. Vulnerability CSV: Remove invalid (no data) comment row on
query failure; return header-only CSV instead to maintain valid
CSV format.
- Add target_host_id column to host_health_checks table (nullable UUID FK)
- Allow service checks to query a different host agent
- Backend models, API routes, and poller updated
- Frontend: host selector dropdown for service checks
- Validation: target host must exist and be healthy
- FK ON DELETE SET NULL: revert to own host if target deleted
- Added health_check_poller.rs: periodic service/HTTP health checks
- Added pre-patch health gate in job_executor.rs
- Added waiting_health_check job status (migration 008)
- Added health_check_status to HostSummary and hosts API
- Added health check types and API functions to frontend
- Added health check UI section to HostDetailPage
- Added health check status indicators to HostsPage and PatchDeploymentPage
- Added serde default for health_check_poll_interval_secs
- Fixed missing AgentClient import in health_check_poller.rs
- Fixed missing ws_relay import in main.rs
- Fixed missing closing paren in retry_pending_jobs SQL
- Added ReadWritePaths for /etc/patch-manager/keys in systemd services
BUG-6: Add TLS support via axum-server + rustls
- Added axum-server with tls-rustls feature to workspace and pm-web
- pm-web now serves HTTPS when TLS certs exist, falls back to HTTP with warning
- setup.sh generates self-signed ECDSA P-256 TLS cert with SANs
- Config already had web_tls_cert_path/web_tls_key_path fields
BUG-7: Fix audit chain integrity errors
- Migration 005 now TRUNCATEs audit_log after adding prev_hash column
- Existing rows had broken hash chains (inserted before prev_hash existed)
BUG-8: Disable WatchdogSec in patch-manager-web.service
- pm-web does not implement sd_notify, causing systemd to kill the service
BUG-9: Disable WatchdogSec in patch-manager-worker.service
- Same issue as BUG-8, worker does not implement sd_notify
Previous fixes (BUG-1 through BUG-5) also included:
- setup.sh: PostgreSQL 15+ schema GRANTs
- Axum route syntax :param → {param} (19 routes)
- DbUser struct role: String → UserRole enum mapping
- UserRole/AuthProvider Display trait implementations
- Seed admin password hash (Argon2id)