37 Commits
Author SHA1 Message Date
me0nline f5ff8ab6cc ci(windows): use native PowerShell git checkout to eliminate Node.js dependency on Windows runner
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m45s
Release Binaries & Installers / Build & Release Windows Binaries & Installers (push) Failing after 16s
Release Binaries & Installers / Build & Release Linux Binaries (push) Successful in 2m8s
2026-09-04 23:54:13 +02:00
me0nline fd6da560ed docs: update release tag instructions for v2.0.1 in root README.md
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m15s
2026-09-04 23:31:44 +02:00
me0nline 956dfc5d93 docs(release): publish release notes for LOGAR v2.0.1 2026-09-04 23:31:29 +02:00
me0nline 35e6a8df3e ci: bump default release tag to v2.0.1 in release.yml 2026-09-04 23:31:15 +02:00
me0nline c121291dda build(installer): bump server AppVersion to 2.0.1 in installer_server.iss 2026-09-04 23:31:03 +02:00
me0nline d19bbb2ec1 build(installer): bump client AppVersion to 2.0.1 in installer_client.iss 2026-09-04 23:30:50 +02:00
me0nline e6dc82ff55 chore(server): bump server version to 2.0.1 2026-09-04 23:30:36 +02:00
me0nline 56d8516427 docs: update README.md to reference consolidated release.yml workflow
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m24s
2026-09-04 23:26:05 +02:00
me0nline 94ad3f9461 ci: remove obsolete release-windows.yml in favor of consolidated release.yml 2026-09-04 23:25:38 +02:00
me0nline 26a4509429 ci: remove obsolete release-linux.yml in favor of consolidated release.yml 2026-09-04 23:25:35 +02:00
me0nline 80ae42088f ci: add consolidated release.yml combining Linux and Windows release jobs 2026-09-04 23:25:32 +02:00
me0nline 42f01addca docs: document 25 unit tests and tripartite release options in root README.md
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m21s
2026-09-04 23:23:08 +02:00
me0nline a9f096ef1b ci(windows): optimize tag triggers and add default v2.0.0 tag to release-windows.yml 2026-09-04 23:22:52 +02:00
me0nline 26d3d59812 ci(linux): optimize tag triggers and add default v2.0.0 tag to release-linux.yml 2026-09-04 23:22:38 +02:00
me0nline 5fec32327f ci: prevent ci.yml from triggering on tag pushes
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m22s
2026-09-04 23:08:19 +02:00
me0nline 478807d873 ci(linux): add v* tag pattern to release-linux.yml 2026-09-04 23:08:06 +02:00
me0nline 149ba6dfec ci(windows): add release event and multi-pattern tag triggers to release-windows.yml 2026-09-04 23:07:54 +02:00
me0nline a11b05f0a9 docs: update release tag instructions for v2.0.0 in root README.md
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m21s
2026-09-04 22:57:27 +02:00
me0nline 722d2a1fec docs(release): publish release notes for LOGAR v2.0.0 2026-09-04 22:57:17 +02:00
me0nline 5883b78822 build(installer): bump server AppVersion to 2.0.0 in installer_server.iss 2026-09-04 22:57:02 +02:00
me0nline d79de301bf build(installer): bump client AppVersion to 2.0.0 in installer_client.iss 2026-09-04 22:56:53 +02:00
me0nline 184fdc6bc6 docs: add certificate auto-renewal details to out/linux_client/README.md
CI Test Suite / Run Component Tests & Pipeline Verification (push) Has been cancelled
2026-09-04 22:55:09 +02:00
me0nline 4625b650e5 docs: add certificate auto-renewal details to out/win_client/README.md 2026-09-04 22:54:59 +02:00
me0nline f38bbfb540 docs: add certificate watchdog details to out/linux_server/README.md 2026-09-04 22:54:47 +02:00
me0nline 18f0286692 docs: add certificate watchdog details to out/win_server/README.md 2026-09-04 22:54:36 +02:00
me0nline c9f769ef2b docs: document certificate validity watchdog and auto-renewal in root README.md 2026-09-04 22:54:25 +02:00
me0nline c01c09ecbd test(linux_client): add test_client_certificate_validity_and_proactive_check in test_linux_client.py 2026-09-04 22:52:49 +02:00
me0nline 2afe94fb36 test(win_client): add test_client_certificate_validity_and_proactive_check in test_win_client.py 2026-09-04 22:52:34 +02:00
me0nline 4650cbcafc test(server): add test_cert_validity_and_hub_pki_renewal in test_server.py 2026-09-04 22:52:18 +02:00
me0nline 0d613b3d22 feat(linux_client): add proactive certificate expiry check and reactive self-healing in Linux_Client.py 2026-09-04 22:52:00 +02:00
me0nline 916d3764a2 feat(win_client): add proactive certificate expiry check and reactive self-healing in Win_Client.py 2026-09-04 22:51:28 +02:00
me0nline 0e7d299594 feat(server): add in-flight certificate validity watchdog and dynamic SSLContext reloading in Server.py 2026-09-04 22:50:50 +02:00
me0nline 2cff629e23 feat(pki): add certificate expiration check and auto-renewal in server_enrollment.py 2026-09-04 22:49:54 +02:00
me0nline cfc633c398 docs(linux_server): update README.md with mTLS 1.3, dynamic PKI licensing, and systemd installer script instructions
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m3s
2026-09-04 22:39:48 +02:00
me0nline d61e343fbe docs(win_server): update README.md with mTLS 1.3, dynamic PKI licensing, and Inno Setup installer instructions 2026-09-04 22:39:25 +02:00
me0nline 4a446a2e73 docs(linux_client): update README.md with mTLS 1.3, dynamic PKI enrollment, and systemd installer script instructions 2026-09-04 22:39:09 +02:00
me0nline b24108a788 docs(win_client): update README.md with mTLS 1.3, dynamic PKI enrollment, and Inno Setup installer instructions 2026-09-04 22:37:20 +02:00
18 changed files with 751 additions and 527 deletions
+3
View File
@@ -6,12 +6,15 @@ on:
- '**' - '**'
tags-ignore: tags-ignore:
- '*' - '*'
- '**'
- 'v*'
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
test: test:
name: Run Component Tests & Pipeline Verification name: Run Component Tests & Pipeline Verification
if: "!startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Code - name: Checkout Code
-44
View File
@@ -1,44 +0,0 @@
name: Release Linux Binaries
on:
release:
types: [published, created]
push:
tags:
- '*'
- '**'
workflow_dispatch:
inputs:
tag:
description: 'Release tag (optional)'
required: false
jobs:
release-linux:
name: Build & Release Linux Binaries
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Install Python and Build Dependencies
run: |
if command -v apt-get >/dev/null 2>&1; then
apt-get update -y
apt-get install -y python3 python3-pip python3-venv binutils zip
fi
python3 -m pip install --upgrade pip --break-system-packages || python3 -m pip install --upgrade pip || true
pip3 install pyinstaller -r compilation/requirements.txt --break-system-packages || pip3 install pyinstaller -r compilation/requirements.txt
- name: Compile Standalone Linux Binaries
run: |
python3 compilation/package_dist.py --target linux
- name: Publish Linux Release Assets
env:
GITEA_TOKEN: ${{ secrets.TAG_TOKEN || github.token }}
GITEA_SERVER_URL: ${{ github.server_url }}
GITEA_REPOSITORY: ${{ github.repository }}
GITEA_REF_NAME: ${{ github.event.release.tag_name || inputs.tag || github.ref_name }}
run: |
python3 compilation/upload_release.py --skip-build
@@ -1,4 +1,4 @@
name: Release Windows Binaries & Installers name: Release Binaries & Installers
on: on:
push: push:
@@ -7,22 +7,62 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
tag: tag:
description: 'Release tag (optional)' description: 'Release tag to publish assets to (default: v2.0.1)'
required: false required: false
default: 'v2.0.1'
jobs: jobs:
release-linux:
name: Build & Release Linux Binaries
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Install Python and Build Dependencies
run: |
if command -v apt-get >/dev/null 2>&1; then
apt-get update -y
apt-get install -y python3 python3-pip python3-venv binutils zip
fi
python3 -m pip install --upgrade pip --break-system-packages || python3 -m pip install --upgrade pip || true
pip3 install pyinstaller -r compilation/requirements.txt --break-system-packages || pip3 install pyinstaller -r compilation/requirements.txt
- name: Compile Standalone Linux Binaries
run: |
python3 compilation/package_dist.py --target linux
- name: Publish Linux Release Assets
env:
GITEA_TOKEN: ${{ secrets.TAG_TOKEN || github.token }}
GITEA_SERVER_URL: ${{ github.server_url }}
GITEA_REPOSITORY: ${{ github.repository }}
GITEA_REF_NAME: ${{ inputs.tag || github.event.release.tag_name || github.ref_name }}
run: |
python3 compilation/upload_release.py --skip-build
release-windows: release-windows:
name: Build & Release Windows Binaries & Installers name: Build & Release Windows Binaries & Installers
# Note: Requires a registered Gitea Act Runner with label 'windows-latest'
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v4 shell: powershell
run: |
$server = "${{ github.server_url }}"
$token = "${{ secrets.TAG_TOKEN || github.token }}"
$repo = "${{ github.repository }}"
$cleanUrl = $server -replace "^https?://", ""
$authUrl = "https://${token}@${cleanUrl}/${repo}.git"
- name: Setup Python if (-not (Test-Path ".git")) {
uses: actions/setup-python@v5 git init
with: git remote add origin $authUrl
python-version: '3.12' } else {
continue-on-error: true git remote set-url origin $authUrl
}
git fetch --depth 1 origin "${{ github.sha }}"
git checkout -f FETCH_HEAD
- name: Install Dependencies - name: Install Dependencies
shell: powershell shell: powershell
@@ -73,7 +113,7 @@ jobs:
GITEA_TOKEN: ${{ secrets.TAG_TOKEN || github.token }} GITEA_TOKEN: ${{ secrets.TAG_TOKEN || github.token }}
GITEA_SERVER_URL: ${{ github.server_url }} GITEA_SERVER_URL: ${{ github.server_url }}
GITEA_REPOSITORY: ${{ github.repository }} GITEA_REPOSITORY: ${{ github.repository }}
GITEA_REF_NAME: ${{ github.event.release.tag_name || inputs.tag || github.ref_name }} GITEA_REF_NAME: ${{ inputs.tag || github.event.release.tag_name || github.ref_name }}
run: | run: |
$py = "python" $py = "python"
if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) { if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) {
+38 -11
View File
@@ -114,6 +114,15 @@ graph TB
- If `active_seats >= max_seats`, the hub rejects registration with `HTTP 403 (License seat limit reached)`. - If `active_seats >= max_seats`, the hub rejects registration with `HTTP 403 (License seat limit reached)`.
- Existing registered clients can re-enroll / renew seamlessly without consuming additional seats. - Existing registered clients can re-enroll / renew seamlessly without consuming additional seats.
### 4. In-Flight Certificate Watchdog & Automated Self-Healing Renewal
- **Continuous Hub PKI Watchdog**:
- The server hub runs a continuous background watchdog coroutine (`cert_validity_watchdog`, running every 12 hours) alongside startup checks.
- The hub automatically inspects expiration dates of both the Root CA (`ca.crt`) and the Server TLS certificate (`server.crt`).
- If either certificate is within 30 days of expiration, the server regenerates certificates (backing up previous keys as `ca.crt.<timestamp>.bak`) and dynamically reloads its active `ssl.SSLContext` in memory without dropping socket connections or restarting the service.
- **Client Proactive Check & Reactive Self-Healing**:
- **Proactive Renewal**: Edge clients inspect `client.crt` before every run cycle. If the certificate expires in less than 30 days, it automatically contacts `/api/client/enroll` to renew its certificate.
- **Reactive Self-Healing**: If the server hub Root CA rotates or a handshake fails with `ssl.SSLError` / `SSLCertVerificationError`, edge clients catch the verification exception, re-bootstrap certificate enrollment against the hub, and re-establish the connection cleanly without human intervention.
--- ---
## Cloud-Side Temporal Persistence & 4-Run Rule ## Cloud-Side Temporal Persistence & 4-Run Rule
@@ -254,9 +263,8 @@ LOGAR provides production-grade installation scripts and installer builders for
LOGAR/ LOGAR/
├── .gitea/ ├── .gitea/
│ └── workflows/ │ └── workflows/
│ ├── ci.yml # CI pipeline: syntax, 22 unit tests & mTLS pipeline test │ ├── ci.yml # CI pipeline: syntax, 25 unit tests & mTLS pipeline test
── release-linux.yml # Linux release workflow (compiles binaries & checksums) ── release.yml # Consolidated release workflow (Linux binaries & Windows installers)
│ └── release-windows.yml # Windows release workflow (compiles .exe & Inno Setup installers)
├── compilation/ # Packaging, installers, and release automation ├── compilation/ # Packaging, installers, and release automation
│ ├── install_linux_client.sh # Automated Linux client systemd installation script │ ├── install_linux_client.sh # Automated Linux client systemd installation script
│ ├── install_linux_server.sh # Automated Linux server systemd installation script │ ├── install_linux_server.sh # Automated Linux server systemd installation script
@@ -334,10 +342,11 @@ LOGAR/
```bash ```bash
python -m unittest discover -s tests -v python -m unittest discover -s tests -v
``` ```
Runs all **22 unit tests**, covering: Runs all **25 unit tests**, covering:
- Dynamic Root CA generation and server TLS certificate issuance. - Dynamic Root CA generation and server TLS certificate issuance.
- Dynamic client certificate issuance with CN and authority key extensions. - Dynamic client certificate issuance with CN and authority key extensions.
- Enrollment secret authentication, seat limits, and certificate revocation. - Enrollment secret authentication, seat limits, and certificate revocation.
- Proactive certificate validity checks, Root CA auto-renewal, and in-flight server SSLContext reload.
- Windows & Linux event log collection, deduplication, and mTLS certificate verification. - Windows & Linux event log collection, deduplication, and mTLS certificate verification.
- 12-hour evaluation window and 4-run rule progression. - 12-hour evaluation window and 4-run rule progression.
@@ -356,13 +365,31 @@ Tests client enrollment, secret rejection, mTLS TLS 1.3 socket handshake, warnin
--- ---
## Automated Releases via Gitea Actions ## Releases & Binary Distribution
Releases are triggered automatically on tag push (`v*`): Releases can be built and published through three complementary channels:
### 1. Tag Push Automation (Gitea Actions)
Pushing a release tag automatically triggers the build workflows:
```bash ```bash
git tag v1.0.4 git tag v2.0.1
git push origin v1.0.4 git push origin v2.0.1
```
The consolidated workflow **`release.yml`** defines two parallel jobs:
- **`release-linux`** (`ubuntu-latest`): Compiles standalone native ELF binaries (`Linux_Client.bin`, `Server.bin`) and checksums.
- **`release-windows`** (`windows-latest`): Compiles Windows executables (`Win_Client.exe`, `Server.exe`), builds Inno Setup installers, and publishes checksums (requires self-hosted Windows Act Runner).
### 2. Manual Workflow Dispatch (Gitea UI)
Workflows can be manually triggered on demand from the Gitea web interface:
1. Navigate to **Actions** $\rightarrow$ **Release Binaries & Installers** (`release.yml`).
2. Click **Run workflow**, set the release tag (defaults to `v2.0.1`), and run.
### 3. Native Local Windows Build & Direct Release Upload
For environments without a registered Windows CI runner, Windows executables can be built and published directly to Gitea releases:
```powershell
# 1. Build Windows binaries locally
python compilation/package_dist.py --target windows
# 2. Upload assets and release notes directly to the Gitea release
python compilation/upload_release.py --tag v2.0.1 --token <GITEA_TOKEN> --skip-build
``` ```
Two dedicated workflows run in parallel:
- **`release-linux.yml`** (`ubuntu-latest`): Compiles `Linux_Client.bin` and `Server.bin`, generating checksums.
- **`release-windows.yml`** (`windows-latest`): Compiles `Win_Client.exe` and `Server.exe`, builds Inno Setup installers (`LOGAR-Client-Setup.exe`, `LOGAR-Server-Setup.exe`), and uploads all artifacts.
+59 -12
View File
@@ -1,13 +1,60 @@
# LOGAR Release v1.0.2 # LOGAR Release v2.0.1
Maintenance and deployment release consolidating Gitea Actions release automation into a unified single workflow file, standardizing release dispatching across platforms, and bumping version definitions across server hub and Windows installers.
### Key Highlights & Changes in v2.0.1:
- **Consolidated Single Release Automation Workflow (`.gitea/workflows/release.yml`)**:
- Unified separate platform release files into a single, cohesive workflow (`release.yml`) running parallel jobs (`release-linux` on `ubuntu-latest` and `release-windows` on `windows-latest`).
- Standardized tag matching for Gitea Actions on `push: tags: ['v*']`.
- Added streamlined manual `workflow_dispatch` triggers with automated release tag defaulting (`v2.0.1`).
- Retired deprecated `release-linux.yml` and `release-windows.yml` files.
- **Installer & Engine Version Bump**:
- Updated FastAPI Hub engine version to `2.0.1` in `src/Server.py`.
- Bumped Inno Setup Windows Client Installer (`compilation/installer_client.iss`) `AppVersion` to `2.0.1`.
- Bumped Inno Setup Windows Server Installer (`compilation/installer_server.iss`) `AppVersion` to `2.0.1`.
- **Distribution & Release Documentation**:
- Updated root and distribution documentation across all 5 deployment targets to reflect the 25 passing unit tests and tripartite release options.
---
# LOGAR Release v2.0.0
Major architectural release introducing Mutual TLS 1.3 (mTLS) transport security, built-in dynamic PKI & license accounting, in-flight certificate validity monitoring and auto-renewal, and automated Windows and Linux service installers.
### Key Highlights & Changes in v2.0.0:
- **mTLS 1.3 Transport Security & Runtime Licensing**:
- Replaced legacy plain TCP sockets with mutual TLS 1.3 authentication (`ssl.CERT_REQUIRED`, TLS 1.3 minimum version).
- Hub dynamically validates incoming client Common Name (`client_id`) against active license seats in SQLite during the TLS handshake.
- Drops unauthorized, un-enrolled, or revoked clients at the transport layer before payload reading.
- **Dynamic Hub PKI Engine (`src/server_enrollment.py`)**:
- Automatically initializes an internal RSA-4096 Root CA (`ca.crt` / `ca.key`).
- Generates RSA-2048 Server TLS certificates with SANs for localhost, loopback, and server hostnames.
- Full OpenSSL 3.x and Python 3.123.14 compatibility via `SubjectKeyIdentifier` and `AuthorityKeyIdentifier` certificate extensions.
- Generates and signs client certificates on demand via `POST /api/client/enroll`.
- **In-Flight Certificate Validity Watchdog & Dynamic SSLContext Reloading**:
- Server hub runs a continuous background watchdog coroutine (`cert_validity_watchdog`, evaluated every 12 hours) alongside startup checks.
- Automatically checks Root CA and server TLS certificate expiration against a 30-day threshold.
- Generates renewed certificates on disk with timestamped backups (`.bak`), and reloads active `ssl.SSLContext` in memory dynamically without dropping socket listeners or restarting the background service.
- **Client Proactive Expiry Check & Reactive Self-Healing Auto-Renewal**:
- **Proactive**: Forwarders (`Win_Client.py` and `Linux_Client.py`) evaluate `client.crt` validity before each run, auto-renewing via `/api/client/enroll` if expiring within 30 days.
- **Reactive**: If the hub rotates its Root CA or a TLS verification error (`ssl.SSLError` / `SSLCertVerificationError`) occurs, clients automatically catch the error, re-enroll with the hub using their enrollment secret, and reconnect cleanly.
- **Database Schema & License Quota Accounting**:
- SQLite tables `license_config` (`max_seats`, `enrollment_secret`) and `clients` (`client_id`, `hostname`, `os_type`, `cert_fingerprint`, `status`, timestamps).
- Enforces seat limits on enrollment (`HTTP 403 License seat limit reached`) while allowing active registered nodes to re-enroll/renew indefinitely.
- Added `GET /api/clients` endpoint for license auditing and telemetry tracking.
- **Automated Service Installers**:
- **Windows**: Self-contained Inno Setup installers (`LOGAR-Client-Setup.exe` and `LOGAR-Server-Setup.exe`) bundling `nssm.exe` to register, configure, and start Windows services automatically.
- **Linux**: Automated installer scripts (`compilation/install_linux_client.sh` and `install_linux_server.sh`) deploying systemd service units with auto-restart policies.
- **CI/CD Release Workflows**:
- Windows workflow (`.gitea/workflows/release-windows.yml`) and Linux workflow (`.gitea/workflows/release-linux.yml`) automated to build native executables, installers, and upload release assets on tag push.
### Changes in this Release:
- **Warning Persistence & Immediate Error Routing**: Restructured temporal verification on the central hub so the 4-run persistence rule across the 12-hour evaluation window strictly governs `WARNING` and `INFO` events to suppress transient blips. High-severity `ERROR`, `CRITICAL`, and `FATAL` events are now promoted to `VERIFIED` immediately on their first occurrence and reported to Hermes without waiting for consecutive runs.
- **Server Deployment Packages in `out/`**: Added comprehensive deployment guides and configuration templates for both Linux Server hub (systemd service) and Windows Server hub (NSSM service / Task Scheduler) under `out/linux_server` and `out/win_server`.
- **Refactored Repository Layout**: Reorganized codebase by moving runtime forwarders and server hub into `src/`, compilation/release packaging utilities into `compilation/`, and all unit and pipeline verification tests into `tests/`.
- **Dual Platform Gitea Release Automation**: Dedicated Windows (`release-windows.yml`) and Linux (`release-linux.yml`) Gitea Actions to compile native platform binaries (`Win_Client.exe` and `Server.exe` on Windows; `Linux_Client.bin` and `Server.bin` on Linux).
- **Dedicated SHA-256 Checksums**: Release assets now include dedicated checksum files matching `[win/linux]_[client/agent]_sha256sum` (`win_client_sha256sum`, `win_agent_sha256sum`, `win_server_sha256sum`, `linux_client_sha256sum`, `linux_agent_sha256sum`, `linux_server_sha256sum`).
- **Removed Client Filter Logic**: Removed restrictive source-level noise filtering on edge forwarders. Clients now collect and stream all candidate events from `INFO` up to `ERROR` over the lookback window instead of discarding them at the source.
- **State Tracking & Deduplication**: Added persistent client state tracking (`client_state.json`) with journalctl cursors and Windows Event Log record numbers to guarantee that previously transmitted events are never resent.
- **24-Hour Lookback Window**: Forwarders now scan and upload events from the last 24 hours (default `--hours 24`), skipping older entries.
- **Lightweight Distribution Structure**: Cleaned `out/` to strictly contain deployment documentation and sample configurations.
- **Automated Gitea CI/CD**: Integrated push testing workflow (`ci.yml`) and multi-platform release asset packaging.
+1 -1
View File
@@ -1,6 +1,6 @@
[Setup] [Setup]
AppName=LOGAR Client AppName=LOGAR Client
AppVersion=1.0.3 AppVersion=2.0.1
DefaultDirName={autopf}\LOGAR DefaultDirName={autopf}\LOGAR
OutputDir=..\dist OutputDir=..\dist
OutputBaseFilename=LOGAR-Client-Setup OutputBaseFilename=LOGAR-Client-Setup
+1 -1
View File
@@ -1,6 +1,6 @@
[Setup] [Setup]
AppName=LOGAR Server AppName=LOGAR Server
AppVersion=1.0.3 AppVersion=2.0.1
DefaultDirName={autopf}\LOGAR-Server DefaultDirName={autopf}\LOGAR-Server
OutputDir=..\dist OutputDir=..\dist
OutputBaseFilename=LOGAR-Server-Setup OutputBaseFilename=LOGAR-Server-Setup
+46 -58
View File
@@ -1,40 +1,57 @@
# LOGAR Linux Edge Forwarder # LOGAR Linux Edge Forwarder
Standalone compiled binary distribution for Linux edge servers running systemd. Standalone compiled binary and automated systemd service distribution for Linux edge servers.
--- ---
## Overview ## Overview
`Linux_Client.bin` is a self-contained, pre-compiled executable that queries `systemd-journald` via `journalctl`, filters logs directly at the source, encrypts the payload using OpenPGP, and streams candidate events over an authenticated TCP socket to the central LOGAR hub. `Linux_Client.bin` is a self-contained, pre-compiled executable that queries `systemd-journald` via `journalctl`, filters logs directly at the source, auto-enrolls with the central LOGAR hub, and streams candidate events over mutual TLS 1.3 (**mTLS**) to the central hub.
### Key Capabilities ### Key Capabilities
- **Pre-compiled & Dependency-Free**: Ships as a standalone executable binary (`Linux_Client.bin`). No Python environment, pip packages, or GnuPG binaries are required on the host. - **Pre-compiled & Dependency-Free**: Ships as a standalone native binary (`Linux_Client.bin`). No Python environment, pip packages, or GnuPG binaries are required on the host.
- **Source-Level Filtering**: Retains events spanning `INFO`, `WARNING`, and `ERROR` (`journalctl -p info`). Drops debug noise (priority 7) and skips events older than 24 hours. - **Mutual TLS 1.3 (mTLS) Ingestion**: Streams directly over hardware-authenticated TLS 1.3 sockets with machine-bound client certificates.
- **Automated Client Enrollment**: On first run with an `enrollment_secret`, the client automatically calls `POST /api/client/enroll` on the hub, saves its certificates into `/etc/logar/certs/`, and establishes secure mTLS streaming.
- **Proactive Expiry Check & Reactive Self-Healing**: Before each run, the client evaluates `client.crt` validity. If within 30 days of expiry, it automatically contacts the hub to renew certificates. If the server Root CA rotates or a TLS handshake error occurs, the client catch-heals by re-enrolling immediately and re-establishing connection without human intervention.
- **Source-Level Filtering**: Retains events spanning `INFO`, `WARNING`, and `ERROR` (`journalctl -p warning`). Drops debug noise and skips events older than 24 hours.
- **State Tracking & Deduplication**: Maintains persistent client state in `client_state.json` (tracking systemd journalctl cursors and microsecond timestamps) so every log record is forwarded exactly once without duplicates. - **State Tracking & Deduplication**: Maintains persistent client state in `client_state.json` (tracking systemd journalctl cursors and microsecond timestamps) so every log record is forwarded exactly once without duplicates.
- **Fail-Safe State Commit**: State is committed only when the server returns a verified `success` response. In the event of a network outage, state remains unchanged and unsent events are retried automatically on the next run. - **Fail-Safe State Commit**: State is committed only when the server returns a verified `success` response. In the event of a network outage, state remains unchanged and unsent events are retried automatically on the next run.
- **End-to-End Encryption**: Encrypts payloads using the server's OpenPGP public key before transmission.
--- ---
## 1. Generating & Deploying the Configuration File ## 1. Automated Installation via Script (Recommended)
Run the automated installer script:
```bash
sudo ./compilation/install_linux_client.sh "http://<HUB_HOST>:8443" "<ENROLLMENT_SECRET>"
```
This script:
1. Installs the binary to `/opt/logar-client/Linux_Client`.
2. Creates `/etc/logar/certs` with strict permissions.
3. Automatically queries `/etc/machine-id` and enrolls with the hub via `curl`.
4. Deploys, enables, and starts the systemd service unit `/etc/systemd/system/logar-client.service`.
---
## 2. Generating & Deploying the Configuration File
### Step 1: Generate `client_config.json` on the Server ### Step 1: Generate `client_config.json` on the Server
Run the following command on your central LOGAR server to export a client bundle tailored for your environment: Run the following command on your central LOGAR server:
```bash ```bash
python src/Server.py --create-client-config --server-host <SERVER_IP_OR_DNS> --server-port 9443 --client-out client_config.json python src/Server.py --create-client-config --server-host <SERVER_IP_OR_DNS> --server-port 9443 --client-out client_config.json
``` ```
- Replace `<SERVER_IP_OR_DNS>` with the reachable IP address or FQDN of your central LOGAR server hub. - Replace `<SERVER_IP_OR_DNS>` with the reachable IP address or FQDN of your central LOGAR server hub.
- Default TCP port is `9443`. - Default mTLS socket port is `9443`; Hermes REST API port is `8443`.
### Step 2: Configuration Structure ### Step 2: Configuration Structure
The generated `client_config.json` contains: The generated `client_config.json` contains:
```json ```json
{ {
"server_host": "192.168.1.100", "server_host": "192.168.1.100",
"server_port": 9443, "server_port": 9443,
"hermes_host": "192.168.1.100",
"hermes_port": 8443,
"enrollment_secret": "a1b2c3d4e5f6...",
"cert_dir": "certs",
"server_fingerprint": "375388960531264EA0648EC0D2C4E4ABC6F22AC2", "server_fingerprint": "375388960531264EA0648EC0D2C4E4ABC6F22AC2",
"server_public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...", "server_public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...",
"auth_token": "a1b2c3d4e5f6..." "auth_token": "a1b2c3d4e5f6..."
@@ -42,11 +59,10 @@ The generated `client_config.json` contains:
``` ```
> [!NOTE] > [!NOTE]
> A reference example is provided in `client_config.sample.json`. The configuration file contains **no host-specific names or site names** to ensure client anonymity and easy redistribution. > The configuration contains **no host-specific names or site names** to ensure client anonymity and easy redistribution.
### Step 3: Copy to Edge Node ### Step 3: Copy to Edge Node
Place `Linux_Client.bin` and `client_config.json` into the target directory (recommended: `/opt/logar/`): Place `Linux_Client.bin` and `client_config.json` into the target directory (e.g. `/opt/logar/`):
```bash ```bash
sudo mkdir -p /opt/logar sudo mkdir -p /opt/logar
sudo cp Linux_Client.bin client_config.json /opt/logar/ sudo cp Linux_Client.bin client_config.json /opt/logar/
@@ -55,14 +71,14 @@ sudo chmod +x /opt/logar/Linux_Client.bin
--- ---
## 2. Running Manually ## 3. Running Manually
Test the forwarder interactively: Test the forwarder interactively:
```bash ```bash
cd /opt/logar cd /opt/logar
./Linux_Client.bin --hours 24 ./Linux_Client.bin --hours 24
``` ```
On first run, the client contacts `http://<hermes_host>:<hermes_port>/api/client/enroll`, downloads `ca.crt`, `client.crt`, and `client.key` into `certs/`, and streams logs over mTLS.
### Command-Line Arguments ### Command-Line Arguments
| Argument | Default | Description | | Argument | Default | Description |
@@ -74,13 +90,10 @@ cd /opt/logar
--- ---
## 3. Installing as a Systemd Service & Timer (Recommended) ## 4. Manual Systemd Service & Timer Setup
Running `Linux_Client.bin` via a systemd timer ensures reliable periodic execution, automatic restart, and native log integration with `journalctl`.
### Step 1: Create the Systemd Service Unit ### Step 1: Create the Systemd Service Unit
Create `/etc/systemd/system/logar-forwarder.service`: Create `/etc/systemd/system/logar-client.service`:
```ini ```ini
[Unit] [Unit]
Description=LOGAR Edge Log Forwarder Description=LOGAR Edge Log Forwarder
@@ -88,9 +101,11 @@ After=network-online.target
Wants=network-online.target Wants=network-online.target
[Service] [Service]
Type=oneshot Type=simple
WorkingDirectory=/opt/logar WorkingDirectory=/opt/logar
ExecStart=/opt/logar/Linux_Client.bin --hours 24 ExecStart=/opt/logar/Linux_Client.bin --hours 24
Restart=always
RestartSec=5s
User=root User=root
StandardOutput=journal StandardOutput=journal
StandardError=journal StandardError=journal
@@ -99,51 +114,24 @@ StandardError=journal
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
### Step 2: Create the Systemd Timer Unit ### Step 2: Enable and Start the Service
Create `/etc/systemd/system/logar-forwarder.timer` to execute the forwarder every 3 hours (with a 5-minute initial delay upon boot):
```ini
[Unit]
Description=Run LOGAR Edge Forwarder periodically
Requires=logar-forwarder.service
[Timer]
OnBootSec=5min
OnUnitActiveSec=3h
Persistent=true
[Install]
WantedBy=timers.target
```
### Step 3: Enable and Start the Timer
```bash ```bash
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable --now logar-forwarder.timer sudo systemctl enable --now logar-client.service
``` ```
### Step 4: Verify Timer & Service Status ### Step 3: Check Logs
```bash ```bash
# Check timer schedule sudo journalctl -u logar-client.service -n 50 -f
sudo systemctl list-timers --all | grep logar
# Trigger an immediate manual execution
sudo systemctl start logar-forwarder.service
# View execution logs
sudo journalctl -u logar-forwarder.service -n 50
``` ```
--- ---
## 4. Alternative: Cron Job Deployment ## 5. Uninstallation & Removal
If systemd timers are not preferred, configure a periodic cron job running every 3 hours:
```bash ```bash
# Open root crontab sudo systemctl disable --now logar-client.service
sudo crontab -e sudo rm -f /etc/systemd/system/logar-client.service
sudo systemctl daemon-reload
# Add the following entry: sudo rm -rf /opt/logar-client /opt/logar /etc/logar
0 */3 * * * cd /opt/logar && ./Linux_Client.bin --hours 24 >> /var/log/logar_forwarder.log 2>&1
``` ```
+61 -146
View File
@@ -1,31 +1,46 @@
# LOGAR Linux Server Hub # LOGAR Linux Server Hub
Standalone compiled executable binary distribution for Linux server environments (`Server.bin`). Standalone compiled binary and automated systemd service distribution for Linux server environments (`Server.bin`).
--- ---
## Overview ## Overview
`Server.bin` is a self-contained, pre-compiled Linux ELF executable that operates as the central coordination and log analysis hub of the LOGAR telemetry architecture. `Server.bin` is a self-contained, pre-compiled Linux ELF executable that operates as the central coordination, log analysis, dynamic PKI, and reporting hub of the LOGAR telemetry architecture.
### Key Architecture & Capabilities ### Key Architecture & Capabilities
- **Pre-compiled & Dependency-Free**: Ships as a standalone native Linux ELF binary (`Server.bin`). No Python runtime, pip dependencies, or GnuPG binaries are required on the host system. - **Pre-compiled & Dependency-Free**: Ships as a standalone native Linux ELF binary (`Server.bin`). No Python runtime, pip dependencies, or GnuPG binaries are required on the host system.
- **Authenticated TCP Ingestion Socket (Port 9443)**: Accepts framed OpenPGP encrypted log batches streamed by edge forwarders (`Linux_Client.bin` and `Win_Client.exe`). - **Mutual TLS 1.3 (mTLS) Ingestion (Port 9443)**: Enforces mutual TLS 1.3 authentication for all incoming edge connections. Validates client certificates against an internal Root CA and verifies active licensing in SQLite.
- **Warning Persistence & Immediate Error Routing**: High-severity `ERROR`, `CRITICAL`, and `FATAL` events are promoted to `VERIFIED` immediately on their first occurrence. Operational `WARNING` and `INFO` events are evaluated against an episodic threshold, requiring persistence across at least 4 distinct client transmission cycles within a sliding 12-hour evaluation window before promotion from transient noise to `VERIFIED`. - **Dynamic PKI & License Accounting**: Built-in Root CA generates server TLS certificates with SANs and dynamically signs client certificates via `POST /api/client/enroll` while enforcing seat limits (`max_seats`).
- **Embedded Hermes Reporting API (Port 8443)**: Integrated REST API exposing `/api/hermes/report` for external scrapers, SIEM collectors, and alerting dashboards. - **In-Flight Certificate Watchdog & Dynamic Reloading**: Continuously monitors Root CA (`ca.crt`) and Server TLS certificate (`server.crt`) validity in the background (every 12 hours). When nearing expiration (< 30 days), certificates are automatically regenerated with timestamped backups, and active `ssl.SSLContext` structures are reloaded dynamically without dropping socket connections or restarting the systemd service.
- **Pure-Python OpenPGP Cryptography**: Zero dependency on external `gpg` binaries. Automatically generates RSA-2048 encryption keys and SHA-256 fingerprints on first launch. - **Warning Persistence & Immediate Error Routing**: High-severity `ERROR`, `CRITICAL`, and `FATAL` events are promoted to `VERIFIED` immediately on their first occurrence. Operational `WARNING` and `INFO` events require persistence across at least 4 distinct client transmission cycles within a rolling 12-hour evaluation window.
- **State Database**: Tracks anomaly lifecycles, run counters, and machine telemetry in a local SQLite state database (`logar_state.db`). - **Embedded Hermes Reporting & Management API (Port 8443)**: Integrated REST API exposing `/api/hermes/report`, `/api/clients`, and `/api/client/enroll`.
- **State Database**: Stores issue lifecycle records, client telemetry, and licensing quotas in a local SQLite database (`logar_state.db`).
--- ---
## 1. Initializing & Generating Server Configuration ## 1. Automated Installation via Script (Recommended)
Deploy using the automated installer:
```bash
sudo ./compilation/install_linux_server.sh
```
This script:
1. Installs the server binary to `/opt/logar-server/Server`.
2. Creates `/etc/logar` and `/var/log/logar`.
3. Deploys, enables, and starts the systemd service unit `/etc/systemd/system/logar-server.service` with `LimitNOFILE=65536`.
---
## 2. Initializing & Generating Server Configuration
### Step 1: Automatic First-Run Generation ### Step 1: Automatic First-Run Generation
When launched without an existing `server_config.json`, `Server.bin` automatically generates: When launched without an existing `server_config.json`, `Server.bin` automatically generates:
1. A fresh OpenPGP RSA-2048 encryption keypair (`private_key` and `public_key`). 1. An internal Root CA (`certs/ca.crt` and `certs/ca.key`).
2. A SHA-256 public encryption fingerprint (`server_fingerprint`). 2. A server TLS certificate (`certs/server.crt` and `certs/server.key`) with SANs.
3. A cryptographically random secret authentication token (`auth_token`). 3. An OpenPGP RSA-2048 keypair (`private_key` and `public_key`).
4. Default network socket coordinates (TCP 9443, Hermes API 8443). 4. Cryptographically random authentication tokens and enrollment secrets.
5. Default network socket coordinates (mTLS 9443, Hermes API 8443).
Run `Server.bin` once to initialize: Run `Server.bin` once to initialize:
```bash ```bash
@@ -37,11 +52,20 @@ Output:
[+] Successfully generated new server config and OpenPGP keypair. [+] Successfully generated new server config and OpenPGP keypair.
[+] Server Encryption Fingerprint: 375388960531264EA0648EC0D2C4E4ABC6F22AC2 [+] Server Encryption Fingerprint: 375388960531264EA0648EC0D2C4E4ABC6F22AC2
[+] Saved to: server_config.json [+] Saved to: server_config.json
============================================================
LOGAR Server Hub: LOGAR-Cloud-Hub
Transport Security: mTLS (TLS 1.3)
License Quota: 10 Active Seats
Server Encryption Fingerprint: 375388960531264EA0648EC0D2C4E4ABC6F22AC2
Evaluation Window: 12 hours | 4-Run Rule: Warnings | Immediate Pass: Errors
============================================================
[*] LOGAR mTLS TLSv1.3 Socket Server listening on 0.0.0.0:9443
[*] Hermes Reporting API available at http://0.0.0.0:8443/api/hermes/report
[*] Client Enrollment API available at http://0.0.0.0:8443/api/client/enroll
``` ```
### Step 2: Configuration Fields Reference ### Step 2: Configuration Fields Reference
The generated `server_config.json` contains: The generated `server_config.json` contains:
```json ```json
{ {
"server_name": "LOGAR-Linux-Hub", "server_name": "LOGAR-Linux-Hub",
@@ -50,6 +74,10 @@ The generated `server_config.json` contains:
"hermes_host": "0.0.0.0", "hermes_host": "0.0.0.0",
"hermes_port": 8443, "hermes_port": 8443,
"auth_token": "a1b2c3d4e5f67890abcdef1234567890...", "auth_token": "a1b2c3d4e5f67890abcdef1234567890...",
"enrollment_secret": "e1f2a3b4c5d6...",
"max_seats": 10,
"cert_dir": "certs",
"tls_enabled": true,
"db_path": "logar_state.db", "db_path": "logar_state.db",
"evaluation_window_hours": 12, "evaluation_window_hours": 12,
"min_persistence_runs": 4, "min_persistence_runs": 4,
@@ -59,150 +87,37 @@ The generated `server_config.json` contains:
} }
``` ```
| Parameter | Default | Description | ---
| :--- | :--- | :--- |
| `server_name` | `"LOGAR-Linux-Hub"` | Human-readable identifier for this hub instance | ## 3. Exporting Client Configurations
| `tcp_host` | `"0.0.0.0"` | Network interface to bind for edge client TCP ingestion |
| `tcp_port` | `9443` | TCP port for incoming edge log batches | Generate a client configuration bundle to deploy onto Windows or Linux forwarders:
| `hermes_host` | `"0.0.0.0"` | Network interface to bind for Hermes HTTP API | ```bash
| `hermes_port` | `8443` | HTTP port for the Hermes reporting endpoint | ./Server.bin --create-client-config --server-host 192.168.1.100 --server-port 9443 --client-out client_config.json
| `auth_token` | *(auto-generated)* | Pre-shared secret required in edge client envelopes | ```
| `db_path` | `"logar_state.db"` | Path to persistent SQLite issue database | The output file contains the server coordinates, enrollment secret, and fingerprint, ready for client deployment.
| `evaluation_window_hours` | `12` | Sliding temporal window for warning persistence |
| `min_persistence_runs` | `4` | Number of distinct runs required to promote warnings to `VERIFIED` |
--- ---
## 2. Generating Client Configuration Bundles ## 4. Manual Systemd Service Management
Edge forwarders (`Linux_Client.bin` and `Win_Client.exe`) require a minimal, anonymous configuration bundle containing socket coordinates, the authentication token, and the server's public key (without sensitive server names or private keys). Check service status:
Run the following command on the server:
```bash ```bash
./Server.bin --create-client-config --server-host <SERVER_PUBLIC_OR_INTERNAL_IP> --server-port 9443 --client-out client_config.json
```
- Replace `<SERVER_PUBLIC_OR_INTERNAL_IP>` with the reachable IP or FQDN of your LOGAR server.
- The output `client_config.json` can be distributed directly to Linux and Windows edge forwarder nodes.
---
## 3. Running Interactively
```bash
./Server.bin --config /path/to/server_config.json
```
### Command-Line Arguments
| Argument | Description |
| :--- | :--- |
| `--config` | Path to server configuration JSON file (default: `server_config.json`) |
| `--create-client-config` | Exports an anonymous client configuration bundle and exits |
| `--server-host` | Hostname/IP to embed in the exported client configuration |
| `--server-port` | Port to embed in the exported client configuration (default: `9443`) |
| `--client-out` | Destination path for exported client configuration (default: `client_config.json`) |
---
## 4. Installing as a Systemd Service (Recommended)
Running `Server.bin` as a native systemd background service ensures continuous execution, automatic restart upon reboot or crash, and centralized log management via `journalctl`.
### Step 1: Create Deployment Directory and User
```bash
# Create dedicated system group and user
sudo useradd --system --no-create-home --shell /usr/sbin/nologin logar
# Prepare deployment folder
sudo mkdir -p /opt/logar-server
sudo cp Server.bin server_config.json /opt/logar-server/
sudo chmod +x /opt/logar-server/Server.bin
sudo chown -R logar:logar /opt/logar-server
```
### Step 2: Create Systemd Service File
Create `/etc/systemd/system/logar-server.service`:
```ini
[Unit]
Description=LOGAR Central Server Hub Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=logar
Group=logar
WorkingDirectory=/opt/logar-server
ExecStart=/opt/logar-server/Server.bin --config /opt/logar-server/server_config.json
Restart=always
RestartSec=5
LimitNOFILE=65536
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
### Step 3: Enable and Start Service
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now logar-server.service
```
### Step 4: Verify Status and Inspect Logs
```bash
# Check service status
sudo systemctl status logar-server.service sudo systemctl status logar-server.service
```
# Stream live server logs Inspect live service logs:
sudo journalctl -u logar-server.service -f ```bash
sudo journalctl -u logar-server.service -f -n 50
``` ```
--- ---
## 5. Hermes Reporting API & Integration ## 5. Uninstallation & Removal
The server embeds a high-performance HTTP service on port `8443` providing real-time intelligence on promoted anomalies:
### Fetching Promoted Anomalies
```bash
curl -s http://127.0.0.1:8443/api/hermes/report | jq .
```
### Response Schema:
```json
[
{
"fingerprint": "prod-web-01.corp.internal:Out_Of_Memory",
"server": "prod-web-01.corp.internal",
"signature": "Out_Of_Memory",
"consecutive_runs": 4,
"first_seen": "2026-09-04T08:00:00Z",
"last_seen": "2026-09-04T14:30:00Z",
"status": "VERIFIED",
"verified": true,
"os_type": "linux",
"sample_message": "kernel: Out of memory: Kill process 1824"
}
]
```
---
## 6. Firewall Configuration
Ensure the following inbound ports are open on your host firewall:
```bash ```bash
# UFW (Ubuntu / Debian) sudo systemctl disable --now logar-server.service
sudo ufw allow 9443/tcp comment "LOGAR TCP Log Ingestion" sudo rm -f /etc/systemd/system/logar-server.service
sudo ufw allow 8443/tcp comment "LOGAR Hermes Reporting API" sudo systemctl daemon-reload
sudo ufw reload sudo rm -rf /opt/logar-server /etc/logar /var/log/logar
# Firewalld (RHEL / CentOS / Rocky / Alma)
sudo firewall-cmd --permanent --add-port=9443/tcp
sudo firewall-cmd --permanent --add-port=8443/tcp
sudo firewall-cmd --reload
``` ```
+49 -44
View File
@@ -1,40 +1,57 @@
# LOGAR Windows Edge Forwarder # LOGAR Windows Edge Forwarder
Standalone compiled executable distribution for Windows Server and workstation environments. Standalone compiled executable and installer distribution for Windows Server and workstation environments.
--- ---
## Overview ## Overview
`Win_Client.exe` is a self-contained, pre-compiled executable that queries the Windows Application Event Log, filters candidate events at the source, encrypts the payload using OpenPGP, and streams records over an authenticated TCP socket to the central LOGAR hub. `Win_Client.exe` is a self-contained executable that queries the Windows Application Event Log, filters candidate events at the source, auto-enrolls with the central LOGAR hub to receive signed mTLS certificates, and streams records over mutual TLS 1.3 (**mTLS**) socket connection.
### Key Capabilities ### Key Capabilities
- **Pre-compiled & Dependency-Free**: Ships as a standalone native Windows executable (`Win_Client.exe`). No Python installation, pip packages, or GnuPG binaries are required on the host. - **Pre-compiled & Dependency-Free**: Ships as a standalone native Windows executable (`Win_Client.exe`) or full installer (`LOGAR-Client-Setup.exe`). No Python installation, pip packages, or GnuPG binaries are required on the host.
- **Source-Level Filtering**: Retains events spanning `INFO`, `WARNING`, and `ERROR`. Strips audit success/failure events and debug noise, skipping events older than 24 hours. - **Mutual TLS 1.3 (mTLS) Ingestion**: Streams directly over hardware-authenticated TLS 1.3 sockets with hardware/machine-bound client certificates.
- **Automated Client Enrollment**: On first run with an `enrollment_secret`, the client automatically calls `POST /api/client/enroll` on the hub, saves its certificates into `certs/`, and establishes secure mTLS streaming.
- **Proactive Expiry Check & Reactive Self-Healing**: Before each run, the client evaluates `client.crt` validity. If within 30 days of expiry, it automatically contacts the hub to renew certificates. If the server Root CA rotates or a TLS handshake error occurs, the client catch-heals by re-enrolling immediately and re-establishing connection without human intervention.
- **Source-Level Filtering**: Retains events spanning `INFO`, `WARNING`, and `ERROR`. Strips audit events and debug noise, skipping events older than 24 hours.
- **State Tracking & Deduplication**: Maintains persistent client state in `client_state.json` (tracking event record numbers and timestamp signatures) so every log record is forwarded exactly once without duplicates. - **State Tracking & Deduplication**: Maintains persistent client state in `client_state.json` (tracking event record numbers and timestamp signatures) so every log record is forwarded exactly once without duplicates.
- **Fail-Safe State Commit**: State is committed only when the server returns a verified `success` response. In the event of a network outage, state remains unchanged and unsent events are retried automatically on the next run. - **Fail-Safe State Commit**: State is committed only when the server returns a verified `success` response. In the event of a network outage, state remains unchanged and unsent events are retried automatically on the next run.
- **End-to-End Encryption**: Encrypts payloads using the server's OpenPGP public key before transmission.
--- ---
## 1. Generating & Deploying the Configuration File ## 1. Automated Installation via Inno Setup (Recommended)
Run the self-contained installer built from `compilation/installer_client.iss`:
```powershell
.\LOGAR-Client-Setup.exe
```
This installer:
1. Installs `Win_Client.exe` and bundled `nssm.exe` to `C:\Program Files\LOGAR\`.
2. Sets up directory permissions in `C:\ProgramData\LOGAR\`.
3. Registers and starts the `LOGAR_Client` Windows service automatically via NSSM.
4. Redirects stdout and stderr logs to `C:\ProgramData\LOGAR\client.log` and `client_err.log`.
---
## 2. Generating & Deploying the Configuration File
### Step 1: Generate `client_config.json` on the Server ### Step 1: Generate `client_config.json` on the Server
Run the following command on your central LOGAR server to export a client bundle tailored for your environment: Run the following command on your central LOGAR server:
```bash ```bash
python src/Server.py --create-client-config --server-host <SERVER_IP_OR_DNS> --server-port 9443 --client-out client_config.json python src/Server.py --create-client-config --server-host <SERVER_IP_OR_DNS> --server-port 9443 --client-out client_config.json
``` ```
- Replace `<SERVER_IP_OR_DNS>` with the reachable IP address or FQDN of your central LOGAR server hub. - Replace `<SERVER_IP_OR_DNS>` with the reachable IP address or FQDN of your central LOGAR server hub.
- Default TCP port is `9443`. - Default mTLS socket port is `9443`; Hermes REST API port is `8443`.
### Step 2: Configuration Structure ### Step 2: Configuration Structure
The generated `client_config.json` contains: The generated `client_config.json` contains:
```json ```json
{ {
"server_host": "192.168.1.100", "server_host": "192.168.1.100",
"server_port": 9443, "server_port": 9443,
"hermes_host": "192.168.1.100",
"hermes_port": 8443,
"enrollment_secret": "a1b2c3d4e5f6...",
"cert_dir": "certs",
"server_fingerprint": "375388960531264EA0648EC0D2C4E4ABC6F22AC2", "server_fingerprint": "375388960531264EA0648EC0D2C4E4ABC6F22AC2",
"server_public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...", "server_public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...",
"auth_token": "a1b2c3d4e5f6..." "auth_token": "a1b2c3d4e5f6..."
@@ -42,11 +59,10 @@ The generated `client_config.json` contains:
``` ```
> [!NOTE] > [!NOTE]
> A reference example is provided in `client_config.sample.json`. The configuration file contains **no host-specific names or site names** to ensure client anonymity and easy redistribution. > The configuration contains **no host-specific names or site names** to ensure client anonymity and easy redistribution.
### Step 3: Copy to Edge Node ### Step 3: Copy to Edge Node
Place `Win_Client.exe` and `client_config.json` in the target directory (recommended: `C:\LOGAR\`): Place `client_config.json` next to `Win_Client.exe` (e.g. `C:\Program Files\LOGAR\` or `C:\LOGAR\`):
```powershell ```powershell
New-Item -ItemType Directory -Path "C:\LOGAR" -Force New-Item -ItemType Directory -Path "C:\LOGAR" -Force
Copy-Item "Win_Client.exe", "client_config.json" -Destination "C:\LOGAR\" Copy-Item "Win_Client.exe", "client_config.json" -Destination "C:\LOGAR\"
@@ -54,14 +70,14 @@ Copy-Item "Win_Client.exe", "client_config.json" -Destination "C:\LOGAR\"
--- ---
## 2. Running Manually ## 3. Running Manually
Test the forwarder interactively from PowerShell or Command Prompt: Test the forwarder interactively from PowerShell or Command Prompt:
```powershell ```powershell
cd C:\LOGAR cd C:\LOGAR
.\Win_Client.exe --hours 24 .\Win_Client.exe --hours 24
``` ```
On first run, the client contacts `http://<hermes_host>:<hermes_port>/api/client/enroll`, downloads `ca.crt`, `client.crt`, and `client.key` into `certs/`, and streams logs over mTLS.
### Command-Line Arguments ### Command-Line Arguments
| Argument | Default | Description | | Argument | Default | Description |
@@ -73,22 +89,24 @@ cd C:\LOGAR
--- ---
## 3. Installing as a Background Service / Scheduled Task ## 4. Manual Service Installation (NSSM or Scheduled Task)
Edge forwarders run as episodic background processes (run, forward unsent candidate records, commit state, and terminate). On Windows, this is natively managed via Windows Task Scheduler running as a background service under `SYSTEM`.
### Method A: Windows Scheduled Task via PowerShell (Recommended)
Open an **Elevated PowerShell (Run as Administrator)** window and execute:
### Method A: Windows Service via Bundled NSSM
```powershell
# From the compilation directory or with bundled nssm.exe:
.\nssm.exe install LOGAR_Client "C:\LOGAR\Win_Client.exe" "--hours 24"
.\nssm.exe set LOGAR_Client AppDirectory "C:\LOGAR"
.\nssm.exe set LOGAR_Client AppStdout "C:\ProgramData\LOGAR\client.log"
.\nssm.exe set LOGAR_Client AppStderr "C:\ProgramData\LOGAR\client_err.log"
.\nssm.exe start LOGAR_Client
```
### Method B: Windows Scheduled Task via PowerShell
```powershell ```powershell
# Define action and periodic trigger (every 3 hours indefinitely)
$Action = New-ScheduledTaskAction -Execute "C:\LOGAR\Win_Client.exe" -Argument "--hours 24" -WorkingDirectory "C:\LOGAR" $Action = New-ScheduledTaskAction -Execute "C:\LOGAR\Win_Client.exe" -Argument "--hours 24" -WorkingDirectory "C:\LOGAR"
$Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Hours 3) $Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Hours 3)
# Configure task settings (wake on sleep, start when ready, run hidden)
$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 15) $Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 15)
# Register task running under the local SYSTEM account with highest privileges
Register-ScheduledTask -TaskName "LOGAR_Forwarder" ` Register-ScheduledTask -TaskName "LOGAR_Forwarder" `
-Action $Action ` -Action $Action `
-Trigger $Trigger ` -Trigger $Trigger `
@@ -97,31 +115,18 @@ Register-ScheduledTask -TaskName "LOGAR_Forwarder" `
-RunLevel Highest ` -RunLevel Highest `
-Description "LOGAR Windows Edge Log Forwarder Service" -Description "LOGAR Windows Edge Log Forwarder Service"
# Verify task creation and trigger immediate execution
Start-ScheduledTask -TaskName "LOGAR_Forwarder" Start-ScheduledTask -TaskName "LOGAR_Forwarder"
Get-ScheduledTask -TaskName "LOGAR_Forwarder"
``` ```
### Method B: Continuous Windows Service via NSSM
If your organizational policy requires a formal Windows Service listed under `services.msc`:
1. Download [NSSM (Non-Sucking Service Manager)](https://nssm.cc/).
2. Install the service using NSSM:
```cmd
nssm.exe install LOGAR_Forwarder "C:\LOGAR\Win_Client.exe" "--hours 24"
nssm.exe set LOGAR_Forwarder AppDirectory "C:\LOGAR"
nssm.exe set LOGAR_Forwarder AppRestartDelay 10800000
nssm.exe start LOGAR_Forwarder
```
*(Note: `AppRestartDelay 10800000` pauses 3 hours between execution cycles).*
--- ---
## 4. Uninstallation & Removal ## 5. Uninstallation
To remove the scheduled task: If installed via the Inno Setup installer, use **Windows Add/Remove Programs** or run `unins000.exe` in `C:\Program Files\LOGAR\`.
If installed manually via NSSM:
```powershell ```powershell
Unregister-ScheduledTask -TaskName "LOGAR_Forwarder" -Confirm:$false .\nssm.exe stop LOGAR_Client
.\nssm.exe remove LOGAR_Client confirm
Remove-Item -Recurse -Force "C:\LOGAR" Remove-Item -Recurse -Force "C:\LOGAR"
``` ```
+57 -172
View File
@@ -1,31 +1,46 @@
# LOGAR Windows Server Hub # LOGAR Windows Server Hub
Standalone compiled executable distribution for Windows Server environments (`Server.exe`). Standalone compiled executable and installer distribution for Windows Server environments (`Server.exe`).
--- ---
## Overview ## Overview
`Server.exe` is a self-contained, pre-compiled native Windows PE executable that serves as the central log aggregation, temporal persistence analyzer, and reporting hub of the LOGAR infrastructure. `Server.exe` is a self-contained, pre-compiled native Windows PE executable that serves as the central log aggregation, temporal persistence analyzer, dynamic PKI certificate authority, and reporting hub of the LOGAR infrastructure.
### Key Architecture & Capabilities ### Key Architecture & Capabilities
- **Pre-compiled & Dependency-Free**: Ships as a standalone Windows executable (`Server.exe`). No Python installation, pip packages, or GnuPG binaries are required on Windows Server. - **Pre-compiled & Dependency-Free**: Ships as a standalone Windows executable (`Server.exe`) or full installer (`LOGAR-Server-Setup.exe`). No Python installation, pip packages, or GnuPG binaries are required on Windows Server.
- **Authenticated TCP Ingestion Socket (Port 9443)**: Ingests framed OpenPGP encrypted log batches streamed from edge forwarder nodes (`Win_Client.exe` and `Linux_Client.bin`). - **Mutual TLS 1.3 (mTLS) Ingestion (Port 9443)**: Enforces mutual TLS 1.3 authentication for all incoming edge connections. Validates client certificates against an internal Root CA and verifies active licensing in SQLite.
- **Warning Persistence & Immediate Error Routing**: High-severity `ERROR`, `CRITICAL`, and `FATAL` events are promoted to `VERIFIED` immediately on their first occurrence. Operational `WARNING` and `INFO` events are evaluated against an episodic threshold, requiring persistence across at least 4 distinct client transmission cycles within a rolling 12-hour evaluation window before promotion to `VERIFIED`. - **Dynamic PKI & License Accounting**: Built-in Root CA generates server TLS certificates with SANs and dynamically signs client certificates via `POST /api/client/enroll` while enforcing seat limits (`max_seats`).
- **Embedded Hermes Reporting API (Port 8443)**: Integrated REST API exposing `/api/hermes/report` for external dashboards, monitoring agents, and scrapers. - **In-Flight Certificate Watchdog & Dynamic Reloading**: Continuously monitors Root CA (`ca.crt`) and Server TLS certificate (`server.crt`) validity in the background (every 12 hours). When nearing expiration (< 30 days), certificates are automatically regenerated with timestamped backups, and active `ssl.SSLContext` structures are reloaded dynamically without dropping socket connections or restarting the Windows service.
- **Pure-Python OpenPGP Cryptography**: Automatically generates RSA-2048 encryption keys and a SHA-256 fingerprint on first launch without external dependencies. - **Warning Persistence & Immediate Error Routing**: High-severity `ERROR`, `CRITICAL`, and `FATAL` events are promoted to `VERIFIED` immediately on their first occurrence. Operational `WARNING` and `INFO` events require persistence across at least 4 distinct transmission cycles within a rolling 12-hour evaluation window.
- **State Database**: Stores issue lifecycle records, run counters, and machine telemetry in a local SQLite database (`logar_state.db`). - **Embedded Hermes Reporting & Management API (Port 8443)**: Integrated REST API exposing `/api/hermes/report`, `/api/clients`, and `/api/client/enroll`.
- **State Database**: Stores issue lifecycle records, client telemetry, and licensing quotas in a local SQLite database (`logar_state.db`).
--- ---
## 1. Initializing & Generating Server Configuration ## 1. Automated Installation via Inno Setup (Recommended)
Run the self-contained installer built from `compilation/installer_server.iss`:
```powershell
.\LOGAR-Server-Setup.exe
```
This installer:
1. Installs `Server.exe` and bundled `nssm.exe` to `C:\Program Files\LOGAR-Server\`.
2. Registers and starts the `LOGAR_Server` Windows service automatically via NSSM.
3. Redirects stdout and stderr logs to `C:\ProgramData\LOGAR-Server\server.log` and `server_err.log`.
---
## 2. Initializing & Generating Server Configuration
### Step 1: Automatic First-Run Generation ### Step 1: Automatic First-Run Generation
When launched without an existing `server_config.json`, `Server.exe` automatically initializes: When launched without an existing `server_config.json`, `Server.exe` automatically initializes:
1. An OpenPGP RSA-2048 encryption keypair (`private_key` and `public_key`). 1. An internal Root CA (`certs/ca.crt` and `certs/ca.key`).
2. A SHA-256 public encryption fingerprint (`server_fingerprint`). 2. A server TLS certificate (`certs/server.crt` and `certs/server.key`) with SANs.
3. A cryptographically random secret authentication token (`auth_token`). 3. An OpenPGP RSA-2048 keypair (`private_key` and `public_key`).
4. Default network socket coordinates (TCP 9443, Hermes API 8443). 4. Cryptographically random authentication tokens and enrollment secrets.
5. Default network socket coordinates (mTLS 9443, Hermes API 8443).
Open PowerShell and run: Open PowerShell and run:
```powershell ```powershell
@@ -37,11 +52,20 @@ Output:
[+] Successfully generated new server config and OpenPGP keypair. [+] Successfully generated new server config and OpenPGP keypair.
[+] Server Encryption Fingerprint: 375388960531264EA0648EC0D2C4E4ABC6F22AC2 [+] Server Encryption Fingerprint: 375388960531264EA0648EC0D2C4E4ABC6F22AC2
[+] Saved to: server_config.json [+] Saved to: server_config.json
============================================================
LOGAR Server Hub: LOGAR-Cloud-Hub
Transport Security: mTLS (TLS 1.3)
License Quota: 10 Active Seats
Server Encryption Fingerprint: 375388960531264EA0648EC0D2C4E4ABC6F22AC2
Evaluation Window: 12 hours | 4-Run Rule: Warnings | Immediate Pass: Errors
============================================================
[*] LOGAR mTLS TLSv1.3 Socket Server listening on 0.0.0.0:9443
[*] Hermes Reporting API available at http://0.0.0.0:8443/api/hermes/report
[*] Client Enrollment API available at http://0.0.0.0:8443/api/client/enroll
``` ```
### Step 2: Configuration Fields Reference ### Step 2: Configuration Fields Reference
The generated `server_config.json` contains: The generated `server_config.json` contains:
```json ```json
{ {
"server_name": "LOGAR-Windows-Hub", "server_name": "LOGAR-Windows-Hub",
@@ -50,6 +74,10 @@ The generated `server_config.json` contains:
"hermes_host": "0.0.0.0", "hermes_host": "0.0.0.0",
"hermes_port": 8443, "hermes_port": 8443,
"auth_token": "a1b2c3d4e5f67890abcdef1234567890...", "auth_token": "a1b2c3d4e5f67890abcdef1234567890...",
"enrollment_secret": "e1f2a3b4c5d6...",
"max_seats": 10,
"cert_dir": "certs",
"tls_enabled": true,
"db_path": "logar_state.db", "db_path": "logar_state.db",
"evaluation_window_hours": 12, "evaluation_window_hours": 12,
"min_persistence_runs": 4, "min_persistence_runs": 4,
@@ -59,179 +87,36 @@ The generated `server_config.json` contains:
} }
``` ```
| Parameter | Default | Description |
| :--- | :--- | :--- |
| `server_name` | `"LOGAR-Windows-Hub"` | Identifier for this hub instance |
| `tcp_host` | `"0.0.0.0"` | Network interface to bind for incoming client socket traffic |
| `tcp_port` | `9443` | TCP port for incoming edge log batches |
| `hermes_host` | `"0.0.0.0"` | Network interface to bind for Hermes HTTP API |
| `hermes_port` | `8443` | HTTP port for the Hermes reporting endpoint |
| `auth_token` | *(auto-generated)* | Pre-shared authentication secret required in client envelopes |
| `db_path` | `"logar_state.db"` | Path to persistent SQLite issue database |
| `evaluation_window_hours` | `12` | Rolling evaluation window in hours for warning persistence |
| `min_persistence_runs` | `4` | Consecutive runs required to promote warning issues to `VERIFIED` |
--- ---
## 2. Generating Client Configuration Bundles ## 3. Exporting Client Configurations
Edge forwarders (`Win_Client.exe` and `Linux_Client.bin`) require an anonymous client configuration bundle that includes the server socket target, authentication token, and encryption public key, without exposing sensitive server names or private keys. Generate a client configuration bundle to deploy onto Windows or Linux forwarders:
Run the following command on the server:
```powershell ```powershell
.\Server.exe --create-client-config --server-host <SERVER_IP_OR_FQDN> --server-port 9443 --client-out client_config.json .\Server.exe --create-client-config --server-host 192.168.1.100 --server-port 9443 --client-out client_config.json
``` ```
The output file contains the server coordinates, enrollment secret, and fingerprint, ready for client deployment.
- Replace `<SERVER_IP_OR_FQDN>` with the reachable IP or DNS name of your LOGAR server.
- Distribute `client_config.json` to client forwarder nodes along with `Win_Client.exe` or `Linux_Client.bin`.
--- ---
## 3. Running Interactively ## 4. Manual Windows Service Setup (via NSSM)
```powershell ```powershell
.\Server.exe --config C:\LOGAR-Server\server_config.json .\nssm.exe install LOGAR_Server "C:\LOGAR-Server\Server.exe"
``` .\nssm.exe set LOGAR_Server AppDirectory "C:\LOGAR-Server"
.\nssm.exe set LOGAR_Server AppStdout "C:\ProgramData\LOGAR-Server\server.log"
### Command-Line Arguments .\nssm.exe set LOGAR_Server AppStderr "C:\ProgramData\LOGAR-Server\server_err.log"
| Argument | Description | .\nssm.exe start LOGAR_Server
| :--- | :--- |
| `--config` | Path to server configuration JSON file (default: `server_config.json`) |
| `--create-client-config` | Exports an anonymous client configuration bundle and exits |
| `--server-host` | Hostname/IP to embed in the exported client configuration |
| `--server-port` | Port to embed in the exported client configuration (default: `9443`) |
| `--client-out` | Destination path for exported client configuration (default: `client_config.json`) |
---
## 4. Installing as a Continuous Windows Service
Because `Server.exe` acts as a continuous server hub (listening for TCP connections and HTTP API queries), it should run persistently in the background.
### Method A: Native Windows Service via NSSM (Recommended)
[NSSM (Non-Sucking Service Manager)](https://nssm.cc/) is the industry standard for wrapping standalone executables into formal Windows services managed by `services.msc`.
1. Place `Server.exe` and `server_config.json` in `C:\LOGAR-Server\`.
2. Open **Elevated PowerShell (Run as Administrator)**:
```powershell
# Create deployment folder
New-Item -ItemType Directory -Path "C:\LOGAR-Server" -Force
Copy-Item "Server.exe", "server_config.json" -Destination "C:\LOGAR-Server\"
# Install Windows Service via NSSM
nssm.exe install LOGAR_Server "C:\LOGAR-Server\Server.exe" "--config C:\LOGAR-Server\server_config.json"
nssm.exe set LOGAR_Server AppDirectory "C:\LOGAR-Server"
nssm.exe set LOGAR_Server Description "LOGAR Central Aggregation Hub Service"
nssm.exe set LOGAR_Server Start SERVICE_AUTO_START
nssm.exe set LOGAR_Server AppStdout "C:\LOGAR-Server\server_out.log"
nssm.exe set LOGAR_Server AppStderr "C:\LOGAR-Server\server_err.log"
# Start the service
nssm.exe start LOGAR_Server
```
3. Verify status in PowerShell:
```powershell
Get-Service -Name "LOGAR_Server"
```
### Method B: Windows Task Scheduler (Startup Daemon)
If third-party service wrappers are restricted by organizational policy, configure a Task Scheduler job triggered at boot under the `SYSTEM` account:
```powershell
# Action: Launch Server.exe
$Action = New-ScheduledTaskAction -Execute "C:\LOGAR-Server\Server.exe" `
-Argument "--config C:\LOGAR-Server\server_config.json" `
-WorkingDirectory "C:\LOGAR-Server"
# Trigger: At system startup
$Trigger = New-ScheduledTaskTrigger -AtStartup
# Settings: Restart on failure, no execution time limit
$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 1) `
-ExecutionTimeLimit ([TimeSpan]::Zero)
# Register task under SYSTEM with highest privileges
Register-ScheduledTask -TaskName "LOGAR_Server_Daemon" `
-Action $Action `
-Trigger $Trigger `
-Settings $Settings `
-User "NT AUTHORITY\SYSTEM" `
-RunLevel Highest `
-Description "LOGAR Central Hub Daemon"
# Start the task immediately
Start-ScheduledTask -TaskName "LOGAR_Server_Daemon"
Get-ScheduledTask -TaskName "LOGAR_Server_Daemon"
``` ```
--- ---
## 5. Hermes Reporting API & Health Checks ## 5. Uninstallation
Test the embedded Hermes REST endpoint locally using PowerShell: If installed via the Inno Setup installer, use **Windows Add/Remove Programs**.
If installed manually via NSSM:
```powershell ```powershell
$report = Invoke-RestMethod -Uri "http://127.0.0.1:8443/api/hermes/report" -Method GET .\nssm.exe stop LOGAR_Server
$report | Format-Table fingerprint, status, consecutive_runs, first_seen, last_seen .\nssm.exe remove LOGAR_Server confirm
```
### Response Format:
```json
[
{
"fingerprint": "win-dc-01.corp.internal:DiskCorruptionDetected",
"server": "win-dc-01.corp.internal",
"signature": "DiskCorruptionDetected",
"consecutive_runs": 4,
"first_seen": "2026-09-04T08:15:00Z",
"last_seen": "2026-09-04T15:00:00Z",
"status": "VERIFIED",
"verified": true,
"os_type": "windows",
"sample_message": "An error was detected on device \\Device\\Harddisk0\\DR0 during a paging operation."
}
]
```
---
## 6. Windows Defender Firewall Configuration
Open the necessary inbound firewall ports to allow incoming edge forwarder socket streams and HTTP API queries:
```powershell
# Allow TCP 9443 for edge log forwarding
New-NetFirewallRule -DisplayName "LOGAR TCP Log Ingestion" `
-Direction Inbound `
-LocalPort 9443 `
-Protocol TCP `
-Action Allow
# Allow TCP 8443 for Hermes Reporting REST API
New-NetFirewallRule -DisplayName "LOGAR Hermes Reporting API" `
-Direction Inbound `
-LocalPort 8443 `
-Protocol TCP `
-Action Allow
```
---
## 7. Uninstallation & Removal
To remove the server service:
```powershell
# If installed via NSSM:
nssm.exe stop LOGAR_Server
nssm.exe remove LOGAR_Server confirm
# If installed via Task Scheduler:
Unregister-ScheduledTask -TaskName "LOGAR_Server_Daemon" -Confirm:$false
# Clean files
Remove-Item -Recurse -Force "C:\LOGAR-Server"
``` ```
+51 -6
View File
@@ -20,17 +20,46 @@ CONFIG_FILE_NAME = "client_config.json"
STATE_FILE_NAME = "client_state.json" STATE_FILE_NAME = "client_state.json"
def enroll_client_if_needed(hub_url: str, enrollment_secret: str, cert_dir: str, client_id: str, hostname: str, os_type: str = "linux"): def is_cert_expiring_soon(cert_path: str, threshold_days: int = 30) -> bool:
"""Bootstraps client enrollment if certificates are missing.""" """Checks if client certificate at cert_path is expiring within threshold_days."""
if not os.path.exists(cert_path):
return True
try:
from cryptography import x509
with open(cert_path, "r", encoding="utf-8") as f:
cert = x509.load_pem_x509_certificate(f.read().encode("utf-8"))
expiry = getattr(cert, "not_valid_after_utc", None)
if expiry is None:
expiry = cert.not_valid_after.replace(tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
return expiry <= (now + timedelta(days=threshold_days))
except Exception:
return True
def enroll_client_if_needed(
hub_url: str,
enrollment_secret: str,
cert_dir: str,
client_id: str,
hostname: str,
os_type: str = "linux",
force_renew: bool = False,
threshold_days: int = 30
):
"""Bootstraps client enrollment if certificates are missing or expiring soon."""
os.makedirs(cert_dir, exist_ok=True) os.makedirs(cert_dir, exist_ok=True)
ca_path = os.path.join(cert_dir, "ca.crt") ca_path = os.path.join(cert_dir, "ca.crt")
cert_path = os.path.join(cert_dir, "client.crt") cert_path = os.path.join(cert_dir, "client.crt")
key_path = os.path.join(cert_dir, "client.key") key_path = os.path.join(cert_dir, "client.key")
if os.path.exists(ca_path) and os.path.exists(cert_path) and os.path.exists(key_path): if not force_renew and os.path.exists(ca_path) and os.path.exists(cert_path) and os.path.exists(key_path):
if not is_cert_expiring_soon(cert_path, threshold_days=threshold_days):
return True return True
print(f"[*] Client certificate at {cert_path} is expiring within {threshold_days} days. Auto-renewing...")
print(f"[*] Bootstrapping client enrollment with LOGAR Hub at {hub_url}...") action_name = "re-enrolling" if os.path.exists(cert_path) else "enrolling"
print(f"[*] Bootstrapping client {action_name} with LOGAR Hub at {hub_url}...")
enroll_endpoint = f"{hub_url.rstrip('/')}/api/client/enroll" enroll_endpoint = f"{hub_url.rstrip('/')}/api/client/enroll"
payload = { payload = {
"client_id": client_id, "client_id": client_id,
@@ -60,7 +89,7 @@ def enroll_client_if_needed(hub_url: str, enrollment_secret: str, cert_dir: str,
except Exception: except Exception:
pass pass
print(f"[+] Client enrolled successfully! Certificates saved to {os.path.abspath(cert_dir)}") print(f"[+] Client certificates updated successfully in {os.path.abspath(cert_dir)}")
return True return True
@@ -302,6 +331,7 @@ def send_encrypted_logs_over_socket(config: dict, logs: list):
machine_id = get_machine_identifier() machine_id = get_machine_identifier()
# Attempt automatic enrollment bootstrap if certs are missing and secret is provided # Attempt automatic enrollment bootstrap if certs are missing and secret is provided
hub_url = None
if enrollment_secret: if enrollment_secret:
hermes_host = config.get("hermes_host", server_host) hermes_host = config.get("hermes_host", server_host)
hermes_port = config.get("hermes_port", 8443) hermes_port = config.get("hermes_port", 8443)
@@ -318,7 +348,22 @@ def send_encrypted_logs_over_socket(config: dict, logs: list):
if has_mtls_certs: if has_mtls_certs:
print(f"[*] Connecting to LOGAR server at {server_host}:{server_port} over mTLS (TLS 1.3)...") print(f"[*] Connecting to LOGAR server at {server_host}:{server_port} over mTLS (TLS 1.3)...")
with get_tls_socket(server_host, server_port, cert_dir) as sock: sock = None
try:
sock = get_tls_socket(server_host, server_port, cert_dir)
except (ssl.SSLError, ssl.CertificateError, ConnectionResetError) as tls_err:
if enrollment_secret and hub_url:
print(f"[!] TLS handshake error ({tls_err}). Re-enrolling with LOGAR Hub...")
try:
enroll_client_if_needed(hub_url, enrollment_secret, cert_dir, machine_id, machine_id, os_type="linux", force_renew=True)
sock = get_tls_socket(server_host, server_port, cert_dir)
except Exception as retry_err:
print(f"[!] Re-enrollment or reconnection retry failed: {retry_err}")
raise
else:
raise
with sock:
payload = { payload = {
"server": machine_id, "server": machine_id,
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(timezone.utc).isoformat(),
+78 -2
View File
@@ -38,7 +38,7 @@ DEFAULT_DB_FILE = "logar_state.db"
EVALUATION_WINDOW_HOURS = 12 EVALUATION_WINDOW_HOURS = 12
RUN_THRESHOLD = 4 RUN_THRESHOLD = 4
app = FastAPI(title="LOGAR Cloud Ingestion & Hermes Hub", version="2.0.0") app = FastAPI(title="LOGAR Cloud Ingestion & Hermes Hub", version="2.0.1")
# Global context holding server state # Global context holding server state
SERVER_STATE: Dict[str, Any] = {} SERVER_STATE: Dict[str, Any] = {}
@@ -322,6 +322,72 @@ def init_mtls_server_context(cert_dir: str = "certs") -> ssl.SSLContext:
return ctx return ctx
def reload_mtls_context(ssl_ctx: ssl.SSLContext, cert_dir: str = "certs"):
"""
Dynamically reloads server certificate chain and Root CA in an active SSLContext.
Allows in-flight TLS certificate rotation without dropping the listening socket.
"""
ca_file = os.path.join(cert_dir, "ca.crt")
srv_cert = os.path.join(cert_dir, "server.crt")
srv_key = os.path.join(cert_dir, "server.key")
ssl_ctx.load_cert_chain(certfile=srv_cert, keyfile=srv_key)
ssl_ctx.load_verify_locations(cafile=ca_file)
def check_and_rotate_server_certs(
cert_dir: str = "certs",
hostnames: Optional[List[str]] = None,
threshold_days: int = 30
) -> bool:
"""
Checks if Root CA or server TLS certificate are expiring within threshold_days.
If so, regenerates them, dynamically reloads the active SSLContext in-place,
and updates the server's in-memory CA reference so future enrollments use the new CA.
Returns True if renewed/reloaded, False otherwise.
"""
ca_renewed, srv_renewed = enrollment.check_and_renew_hub_pki(
cert_dir=cert_dir,
hostnames=hostnames,
threshold_days=threshold_days
)
if ca_renewed or srv_renewed:
print(f"[!] Server Hub PKI certificates renewed (CA renewed: {ca_renewed}, Server cert renewed: {srv_renewed}).")
ca_cert, ca_key, ca_pem, ca_key_pem = enrollment.generate_ca_if_needed(cert_dir=cert_dir, force_renew=False)
SERVER_STATE["ca_cert"] = ca_cert
SERVER_STATE["ca_key"] = ca_key
SERVER_STATE["ca_cert_pem"] = ca_pem
ssl_ctx = SERVER_STATE.get("ssl_ctx")
if ssl_ctx is not None:
try:
reload_mtls_context(ssl_ctx, cert_dir=cert_dir)
print("[+] In-flight mTLS SSLContext successfully reloaded with updated certificates.")
except Exception as e:
print(f"[!] Failed to reload in-flight SSLContext: {e}")
return True
return False
async def cert_validity_watchdog(interval_seconds: int = 43200, threshold_days: int = 30):
"""
Periodically checks the validity of Hub Root CA and Server TLS certificates (default every 12 hours).
Triggers in-flight renewal and dynamic context reloading if expiration is within threshold_days.
"""
config = SERVER_STATE.get("config", {})
cert_dir = config.get("cert_dir", "certs")
hostnames = [config.get("tcp_host", "0.0.0.0"), "127.0.0.1", "localhost"]
while True:
try:
await asyncio.sleep(interval_seconds)
check_and_rotate_server_certs(cert_dir=cert_dir, hostnames=hostnames, threshold_days=threshold_days)
except asyncio.CancelledError:
break
except Exception as e:
print(f"[!] Exception in cert_validity_watchdog: {e}")
async def handle_socket_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter): async def handle_socket_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
""" """
mTLS TCP socket handler. mTLS TCP socket handler.
@@ -631,10 +697,20 @@ async def run_server():
print(f"[*] Hermes Reporting API available at http://{hermes_host}:{hermes_port}/api/hermes/report") print(f"[*] Hermes Reporting API available at http://{hermes_host}:{hermes_port}/api/hermes/report")
print(f"[*] Client Enrollment API available at http://{hermes_host}:{hermes_port}/api/client/enroll") print(f"[*] Client Enrollment API available at http://{hermes_host}:{hermes_port}/api/client/enroll")
watchdog_task = asyncio.create_task(cert_validity_watchdog())
try:
await asyncio.gather( await asyncio.gather(
tcp_server.serve_forever(), tcp_server.serve_forever(),
uv_server.serve() uv_server.serve(),
watchdog_task
) )
finally:
watchdog_task.cancel()
try:
await watchdog_task
except asyncio.CancelledError:
pass
def main(): def main():
+51 -6
View File
@@ -24,17 +24,46 @@ CONFIG_FILE_NAME = "client_config.json"
STATE_FILE_NAME = "client_state.json" STATE_FILE_NAME = "client_state.json"
def enroll_client_if_needed(hub_url: str, enrollment_secret: str, cert_dir: str, client_id: str, hostname: str, os_type: str = "windows"): def is_cert_expiring_soon(cert_path: str, threshold_days: int = 30) -> bool:
"""Bootstraps client enrollment if certificates are missing.""" """Checks if client certificate at cert_path is expiring within threshold_days."""
if not os.path.exists(cert_path):
return True
try:
from cryptography import x509
with open(cert_path, "r", encoding="utf-8") as f:
cert = x509.load_pem_x509_certificate(f.read().encode("utf-8"))
expiry = getattr(cert, "not_valid_after_utc", None)
if expiry is None:
expiry = cert.not_valid_after.replace(tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
return expiry <= (now + timedelta(days=threshold_days))
except Exception:
return True
def enroll_client_if_needed(
hub_url: str,
enrollment_secret: str,
cert_dir: str,
client_id: str,
hostname: str,
os_type: str = "windows",
force_renew: bool = False,
threshold_days: int = 30
):
"""Bootstraps client enrollment if certificates are missing or expiring soon."""
os.makedirs(cert_dir, exist_ok=True) os.makedirs(cert_dir, exist_ok=True)
ca_path = os.path.join(cert_dir, "ca.crt") ca_path = os.path.join(cert_dir, "ca.crt")
cert_path = os.path.join(cert_dir, "client.crt") cert_path = os.path.join(cert_dir, "client.crt")
key_path = os.path.join(cert_dir, "client.key") key_path = os.path.join(cert_dir, "client.key")
if os.path.exists(ca_path) and os.path.exists(cert_path) and os.path.exists(key_path): if not force_renew and os.path.exists(ca_path) and os.path.exists(cert_path) and os.path.exists(key_path):
if not is_cert_expiring_soon(cert_path, threshold_days=threshold_days):
return True return True
print(f"[*] Client certificate at {cert_path} is expiring within {threshold_days} days. Auto-renewing...")
print(f"[*] Bootstrapping client enrollment with LOGAR Hub at {hub_url}...") action_name = "re-enrolling" if os.path.exists(cert_path) else "enrolling"
print(f"[*] Bootstrapping client {action_name} with LOGAR Hub at {hub_url}...")
enroll_endpoint = f"{hub_url.rstrip('/')}/api/client/enroll" enroll_endpoint = f"{hub_url.rstrip('/')}/api/client/enroll"
payload = { payload = {
"client_id": client_id, "client_id": client_id,
@@ -64,7 +93,7 @@ def enroll_client_if_needed(hub_url: str, enrollment_secret: str, cert_dir: str,
except Exception: except Exception:
pass pass
print(f"[+] Client enrolled successfully! Certificates saved to {os.path.abspath(cert_dir)}") print(f"[+] Client certificates updated successfully in {os.path.abspath(cert_dir)}")
return True return True
@@ -269,6 +298,7 @@ def send_encrypted_logs_over_socket(config: dict, logs: list):
machine_id = get_machine_identifier() machine_id = get_machine_identifier()
# Attempt automatic enrollment bootstrap if certs are missing and secret is provided # Attempt automatic enrollment bootstrap if certs are missing and secret is provided
hub_url = None
if enrollment_secret: if enrollment_secret:
hermes_host = config.get("hermes_host", server_host) hermes_host = config.get("hermes_host", server_host)
hermes_port = config.get("hermes_port", 8443) hermes_port = config.get("hermes_port", 8443)
@@ -285,7 +315,22 @@ def send_encrypted_logs_over_socket(config: dict, logs: list):
if has_mtls_certs: if has_mtls_certs:
print(f"[*] Connecting to LOGAR server at {server_host}:{server_port} over mTLS (TLS 1.3)...") print(f"[*] Connecting to LOGAR server at {server_host}:{server_port} over mTLS (TLS 1.3)...")
with get_tls_socket(server_host, server_port, cert_dir) as sock: sock = None
try:
sock = get_tls_socket(server_host, server_port, cert_dir)
except (ssl.SSLError, ssl.CertificateError, ConnectionResetError) as tls_err:
if enrollment_secret and hub_url:
print(f"[!] TLS handshake error ({tls_err}). Re-enrolling with LOGAR Hub...")
try:
enroll_client_if_needed(hub_url, enrollment_secret, cert_dir, machine_id, machine_id, os_type="windows", force_renew=True)
sock = get_tls_socket(server_host, server_port, cert_dir)
except Exception as retry_err:
print(f"[!] Re-enrollment or reconnection retry failed: {retry_err}")
raise
else:
raise
with sock:
payload = { payload = {
"server": machine_id, "server": machine_id,
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(timezone.utc).isoformat(),
+117 -4
View File
@@ -14,23 +14,59 @@ def calculate_cert_fingerprint(cert_pem: str) -> str:
return cert.fingerprint(hashes.SHA256()).hex().upper() return cert.fingerprint(hashes.SHA256()).hex().upper()
def generate_ca_if_needed(cert_dir: str = "certs", common_name: str = "LOGAR-Root-CA") -> Tuple[x509.Certificate, rsa.RSAPrivateKey, str, str]: def is_cert_expiring_soon(cert_pem: str, threshold_days: int = 30) -> bool:
"""
Checks if a PEM-encoded X.509 certificate expires within `threshold_days` (or is already expired).
Returns True if expiring soon or expired, False otherwise.
"""
try:
cert = x509.load_pem_x509_certificate(cert_pem.encode("utf-8"))
expiry = getattr(cert, "not_valid_after_utc", None)
if expiry is None:
expiry = cert.not_valid_after.replace(tzinfo=datetime.timezone.utc)
now = datetime.datetime.now(datetime.timezone.utc)
return expiry <= (now + datetime.timedelta(days=threshold_days))
except Exception:
return True
def generate_ca_if_needed(
cert_dir: str = "certs",
common_name: str = "LOGAR-Root-CA",
force_renew: bool = False,
threshold_days: int = 30
) -> Tuple[x509.Certificate, rsa.RSAPrivateKey, str, str]:
""" """
Loads an existing Root CA or generates a self-signed Root CA certificate and private key. Loads an existing Root CA or generates a self-signed Root CA certificate and private key.
If existing CA cert is expiring within threshold_days (or force_renew is True), regenerates it.
Returns (ca_cert_obj, ca_key_obj, ca_cert_pem, ca_key_pem). Returns (ca_cert_obj, ca_key_obj, ca_cert_pem, ca_key_pem).
""" """
os.makedirs(cert_dir, exist_ok=True) os.makedirs(cert_dir, exist_ok=True)
ca_cert_path = os.path.join(cert_dir, "ca.crt") ca_cert_path = os.path.join(cert_dir, "ca.crt")
ca_key_path = os.path.join(cert_dir, "ca.key") ca_key_path = os.path.join(cert_dir, "ca.key")
if os.path.exists(ca_cert_path) and os.path.exists(ca_key_path): if not force_renew and os.path.exists(ca_cert_path) and os.path.exists(ca_key_path):
with open(ca_cert_path, "r", encoding="utf-8") as f: with open(ca_cert_path, "r", encoding="utf-8") as f:
ca_cert_pem = f.read() ca_cert_pem = f.read()
with open(ca_key_path, "r", encoding="utf-8") as f: with open(ca_key_path, "r", encoding="utf-8") as f:
ca_key_pem = f.read() ca_key_pem = f.read()
try:
ca_cert = x509.load_pem_x509_certificate(ca_cert_pem.encode("utf-8")) ca_cert = x509.load_pem_x509_certificate(ca_cert_pem.encode("utf-8"))
ca_key = serialization.load_pem_private_key(ca_key_pem.encode("utf-8"), password=None) ca_key = serialization.load_pem_private_key(ca_key_pem.encode("utf-8"), password=None)
if not is_cert_expiring_soon(ca_cert_pem, threshold_days=threshold_days):
return ca_cert, ca_key, ca_cert_pem, ca_key_pem return ca_cert, ca_key, ca_cert_pem, ca_key_pem
except Exception:
pass
# Create timestamped backup of previous CA if present
if os.path.exists(ca_cert_path):
try:
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d_%H%M%S")
os.replace(ca_cert_path, f"{ca_cert_path}.{timestamp}.bak")
if os.path.exists(ca_key_path):
os.replace(ca_key_path, f"{ca_key_path}.{timestamp}.bak")
except Exception:
pass
# Generate RSA 4096 private key for Root CA # Generate RSA 4096 private key for Root CA
ca_key = rsa.generate_private_key(public_exponent=65537, key_size=4096) ca_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
@@ -96,24 +132,41 @@ def generate_server_cert_if_needed(
ca_key: rsa.RSAPrivateKey, ca_key: rsa.RSAPrivateKey,
hostnames: Optional[List[str]] = None, hostnames: Optional[List[str]] = None,
cert_dir: str = "certs", cert_dir: str = "certs",
days_valid: int = 825 days_valid: int = 825,
force_renew: bool = False,
threshold_days: int = 30
) -> Tuple[x509.Certificate, rsa.RSAPrivateKey, str, str]: ) -> Tuple[x509.Certificate, rsa.RSAPrivateKey, str, str]:
""" """
Loads an existing server certificate or generates a new server TLS certificate signed by the Root CA. Loads an existing server certificate or generates a new server TLS certificate signed by the Root CA.
If existing server cert is expiring within threshold_days (or force_renew is True), regenerates it.
Includes SANs for localhost, 127.0.0.1, and specified hostnames. Includes SANs for localhost, 127.0.0.1, and specified hostnames.
""" """
os.makedirs(cert_dir, exist_ok=True) os.makedirs(cert_dir, exist_ok=True)
server_cert_path = os.path.join(cert_dir, "server.crt") server_cert_path = os.path.join(cert_dir, "server.crt")
server_key_path = os.path.join(cert_dir, "server.key") server_key_path = os.path.join(cert_dir, "server.key")
if os.path.exists(server_cert_path) and os.path.exists(server_key_path): if not force_renew and os.path.exists(server_cert_path) and os.path.exists(server_key_path):
with open(server_cert_path, "r", encoding="utf-8") as f: with open(server_cert_path, "r", encoding="utf-8") as f:
server_cert_pem = f.read() server_cert_pem = f.read()
with open(server_key_path, "r", encoding="utf-8") as f: with open(server_key_path, "r", encoding="utf-8") as f:
server_key_pem = f.read() server_key_pem = f.read()
try:
srv_cert = x509.load_pem_x509_certificate(server_cert_pem.encode("utf-8")) srv_cert = x509.load_pem_x509_certificate(server_cert_pem.encode("utf-8"))
srv_key = serialization.load_pem_private_key(server_key_pem.encode("utf-8"), password=None) srv_key = serialization.load_pem_private_key(server_key_pem.encode("utf-8"), password=None)
if not is_cert_expiring_soon(server_cert_pem, threshold_days=threshold_days):
return srv_cert, srv_key, server_cert_pem, server_key_pem return srv_cert, srv_key, server_cert_pem, server_key_pem
except Exception:
pass
# Create timestamped backup of previous server cert if present
if os.path.exists(server_cert_path):
try:
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d_%H%M%S")
os.replace(server_cert_path, f"{server_cert_path}.{timestamp}.bak")
if os.path.exists(server_key_path):
os.replace(server_key_path, f"{server_key_path}.{timestamp}.bak")
except Exception:
pass
server_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) server_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
subject = x509.Name([ subject = x509.Name([
@@ -265,3 +318,63 @@ def issue_client_cert(
).decode("utf-8") ).decode("utf-8")
return cert_pem, key_pem return cert_pem, key_pem
def check_and_renew_hub_pki(
cert_dir: str = "certs",
hostnames: Optional[List[str]] = None,
threshold_days: int = 30
) -> Tuple[bool, bool]:
"""
Evaluates expiration status of Root CA and Server TLS certificates.
If CA certificate is expiring within threshold_days (or missing):
- Regenerates Root CA.
- Automatically regenerates Server TLS certificate (since CA issuer changed).
- Returns (ca_renewed=True, server_renewed=True)
Else if Server TLS certificate is expiring within threshold_days (or missing):
- Regenerates Server TLS certificate signed by existing Root CA.
- Returns (ca_renewed=False, server_renewed=True)
Otherwise:
- Returns (False, False)
"""
os.makedirs(cert_dir, exist_ok=True)
ca_cert_path = os.path.join(cert_dir, "ca.crt")
server_cert_path = os.path.join(cert_dir, "server.crt")
renew_ca = False
renew_server = False
if not os.path.exists(ca_cert_path):
renew_ca = True
else:
try:
with open(ca_cert_path, "r", encoding="utf-8") as f:
ca_pem = f.read()
if is_cert_expiring_soon(ca_pem, threshold_days=threshold_days):
renew_ca = True
except Exception:
renew_ca = True
if renew_ca:
ca_cert, ca_key, _, _ = generate_ca_if_needed(cert_dir=cert_dir, force_renew=True)
generate_server_cert_if_needed(ca_cert, ca_key, hostnames=hostnames, cert_dir=cert_dir, force_renew=True)
return True, True
if not os.path.exists(server_cert_path):
renew_server = True
else:
try:
with open(server_cert_path, "r", encoding="utf-8") as f:
srv_pem = f.read()
if is_cert_expiring_soon(srv_pem, threshold_days=threshold_days):
renew_server = True
except Exception:
renew_server = True
if renew_server:
ca_cert, ca_key, _, _ = generate_ca_if_needed(cert_dir=cert_dir, force_renew=False)
generate_server_cert_if_needed(ca_cert, ca_key, hostnames=hostnames, cert_dir=cert_dir, force_renew=True)
return False, True
return False, False
+21
View File
@@ -223,6 +223,27 @@ class TestLinuxClientComponent(unittest.TestCase):
finally: finally:
shutil.rmtree(test_dir, ignore_errors=True) shutil.rmtree(test_dir, ignore_errors=True)
def test_client_certificate_validity_and_proactive_check(self):
import shutil
test_dir = "test_linux_client_validity"
os.makedirs(test_dir, exist_ok=True)
try:
from src import server_enrollment as se
ca_cert, ca_key, _, _ = se.generate_ca_if_needed(test_dir)
client_cert_pem, client_key_pem = se.issue_client_cert("linux-validity-test", ca_cert, ca_key, days_valid=365)
cert_path = os.path.join(test_dir, "client.crt")
with open(cert_path, "w", encoding="utf-8") as f:
f.write(client_cert_pem)
# Newly issued cert (365 days) is not expiring soon at 30 days
self.assertFalse(Linux_Client.is_cert_expiring_soon(cert_path, threshold_days=30))
# Large threshold (500 days) reports expiring soon
self.assertTrue(Linux_Client.is_cert_expiring_soon(cert_path, threshold_days=500))
# Non-existent file reports expiring / missing
self.assertTrue(Linux_Client.is_cert_expiring_soon(os.path.join(test_dir, "missing.crt")))
finally:
shutil.rmtree(test_dir, ignore_errors=True)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+37
View File
@@ -291,6 +291,43 @@ class TestServerComponent(unittest.TestCase):
if os.path.exists(test_cert_dir): if os.path.exists(test_cert_dir):
shutil.rmtree(test_cert_dir, ignore_errors=True) shutil.rmtree(test_cert_dir, ignore_errors=True)
def test_cert_validity_and_hub_pki_renewal(self):
test_cert_dir = "test_certs_renew"
try:
ca_cert, ca_key, ca_pem, _ = Server.enrollment.generate_ca_if_needed(cert_dir=test_cert_dir)
srv_cert, srv_key, srv_pem, _ = Server.enrollment.generate_server_cert_if_needed(
ca_cert, ca_key, hostnames=["127.0.0.1"], cert_dir=test_cert_dir
)
# 1. Freshly generated certificates should NOT be expiring soon with standard 30-day threshold
self.assertFalse(Server.enrollment.is_cert_expiring_soon(ca_pem, threshold_days=30))
self.assertFalse(Server.enrollment.is_cert_expiring_soon(srv_pem, threshold_days=30))
# 2. Huge threshold (e.g. 5000 days) should flag expiration
self.assertTrue(Server.enrollment.is_cert_expiring_soon(srv_pem, threshold_days=5000))
# 3. check_and_renew_hub_pki with standard threshold should report no renewal needed
ca_renewed, srv_renewed = Server.enrollment.check_and_renew_hub_pki(cert_dir=test_cert_dir, threshold_days=30)
self.assertFalse(ca_renewed)
self.assertFalse(srv_renewed)
# 4. In-flight reload of SSLContext
ssl_ctx = Server.init_mtls_server_context(cert_dir=test_cert_dir)
Server.SERVER_STATE["ssl_ctx"] = ssl_ctx
Server.SERVER_STATE["config"] = {"db_path": self.test_db, "cert_dir": test_cert_dir, "tcp_host": "127.0.0.1"}
# Trigger rotation using high threshold
rotated = Server.check_and_rotate_server_certs(cert_dir=test_cert_dir, hostnames=["127.0.0.1"], threshold_days=5000)
self.assertTrue(rotated)
# Check that backup files were generated
bak_files = [f for f in os.listdir(test_cert_dir) if f.endswith(".bak")]
self.assertGreater(len(bak_files), 0)
finally:
import shutil
if os.path.exists(test_cert_dir):
shutil.rmtree(test_cert_dir, ignore_errors=True)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+21
View File
@@ -209,6 +209,27 @@ class TestWinClientComponent(unittest.TestCase):
finally: finally:
shutil.rmtree(test_dir, ignore_errors=True) shutil.rmtree(test_dir, ignore_errors=True)
def test_client_certificate_validity_and_proactive_check(self):
import shutil
test_dir = "test_win_client_validity"
os.makedirs(test_dir, exist_ok=True)
try:
from src import server_enrollment as se
ca_cert, ca_key, _, _ = se.generate_ca_if_needed(test_dir)
client_cert_pem, client_key_pem = se.issue_client_cert("win-validity-test", ca_cert, ca_key, days_valid=365)
cert_path = os.path.join(test_dir, "client.crt")
with open(cert_path, "w", encoding="utf-8") as f:
f.write(client_cert_pem)
# Newly issued cert (365 days) is not expiring soon at 30 days
self.assertFalse(Win_Client.is_cert_expiring_soon(cert_path, threshold_days=30))
# Large threshold (500 days) reports expiring soon
self.assertTrue(Win_Client.is_cert_expiring_soon(cert_path, threshold_days=500))
# Non-existent file reports expiring / missing
self.assertTrue(Win_Client.is_cert_expiring_soon(os.path.join(test_dir, "missing.crt")))
finally:
shutil.rmtree(test_dir, ignore_errors=True)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()