No description
  • Java 97.1%
  • Shell 2.7%
  • HTML 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Robert von Burg a733e8f246
[Docs] Updated backlog to reflect completion of task 011
Marked task 011 (authenticated viewer sessions) as complete and updated next steps for task 012. Revised task details, dependencies, and metrics to reflect the updated implementation status. Included references to verification evidence and clarified completion criteria.
2026-10-06 07:55:44 +02:00
docs [Docs] Updated backlog to reflect completion of task 011 2026-10-06 07:55:44 +02:00
intruvia-app [New] Added session handler and updated roles configuration 2026-10-06 07:54:10 +02:00
intruvia-core [New] Introduced IngestService for handling event ingestion 2026-10-05 21:05:53 +02:00
intruvia-rest [New] Added Viewer session management with authentication 2026-10-06 07:53:51 +02:00
intruvia-web [Project] Added runtime baseline probe with tests and verification 2026-09-28 13:10:48 +02:00
probes [Project] Added PAT revocation probe with test scenarios 2026-10-04 17:36:26 +02:00
scripts [Project] Implemented Strolch-based persistence layer with PostgreSQL integration, initial model migration, and event repository 2026-09-28 15:53:45 +02:00
.gitignore [Project] Updated .gitignore 2026-10-05 20:52:06 +02:00
AGENTS.md [Project] Added AGPL license 2026-09-28 08:28:04 +02:00
LICENSE [Project] Added AGPL license 2026-09-28 08:28:04 +02:00
next-task.sh [Docs] Updated next-task.sh with Maven artifact resolution and local recovery guidelines 2026-09-29 08:31:40 +02:00
pom.xml [New] Added GeoIP2 dependency for IP-based geolocation 2026-10-04 19:30:21 +02:00
README.md [Docs] Updated README with Viewer session details 2026-10-06 07:54:36 +02:00

Intruvia

Intruvia is a self-hosted security-event viewer under development. See the specification, backlog, and status ledger for scope and implementation progress.

Run the next implementation task

The runner needs Bash, flock, and an authenticated Codex CLI. It finds Codex on PATH or uses the desktop app's bundled /usr/lib/chatgpt/resources/codex executable. To select another installation, set CODEX_BIN=/full/path/to/codex. If authentication is needed, run the selected executable with login (for example, /usr/lib/chatgpt/resources/codex login). From the project directory, run:

./next-task.sh --dry-run
./next-task.sh

You can also invoke the script by absolute path from any directory. Each invocation starts a fresh Codex CLI session using your configured model and authentication. It instructs Codex to resume an in-progress task or choose the first eligible TODO, implement and verify it, and update the ledger. Run it again for the following task. This does not continue the desktop conversation; project files provide its context.

The run can edit project files and execute checks in the workspace-write sandbox. If permissions or prerequisites prevent completion, the agent should record the blocker. Review its final report and the status ledger: a successful CLI exit alone does not prove that the task is DONE. Avoid editing the same task concurrently in another session. The script lock prevents overlapping invocations of this script.

See runner details and the official Codex non-interactive documentation.

License

Intruvia is licensed under the GNU Affero General Public License, version 3.0 (SPDX: AGPL-3.0-only). See LICENSE for the full terms. Third-party components retain their respective licenses.

Verified runtime baseline

Task 001 pins Java 25, Strolch, Jersey and Jetty in an isolated compatibility probe. See the baseline decision for exact versions, integration conventions and verification limits. Run it with Java 25:

JAVA_HOME=/path/to/jdk-25 PATH=/path/to/jdk-25/bin:$PATH \
  mvn -B -f probes/runtime-baseline/pom.xml clean verify

The probe starts Strolch/Jetty on loopback, verifies REST and WebSocket exchange, and shuts both down. It needs Maven dependency access but no PostgreSQL server. The application skeleton is available below; the event viewer remains pending.

To retry a blocked task after fixing its environment:

./next-task.sh --retry-blocked

The runner enables outbound network access for dependency downloads and permits writes to ~/.m2/repository while retaining the workspace filesystem sandbox. If Maven uses a custom local repository, set MAVEN_REPOSITORY to that absolute path. The retry checks the blocker again and preserves previous failure evidence.

Build and run the application

Use Java 25 and Maven (verified with Maven 3.9.16). From the repository root:

JAVA_HOME=/path/to/jdk-25 PATH=/path/to/jdk-25/bin:$PATH scripts/verify-postgresql.sh
# Configure an external runtime and PostgreSQL as described below, then:
/path/to/jdk-25/bin/java -jar intruvia-app/target/intruvia/intruvia-app-0.0.1.jar /path/to/runtime 8080

Open http://127.0.0.1:8080/ for the packaged local landing page. http://127.0.0.1:8080/health/live returns {"status":"UP"}. Stop with Ctrl+C or SIGTERM; Jetty stops before Strolch is stopped and destroyed. The port is optional (default 8080); 0 selects an ephemeral port printed in the log. The runtime path is required and can be absolute. Keep the application JAR, lib/ and runtime/ together when copying the assembled directory.

Locally installed Strolch artifacts

The historical baseline uses a timestamped Strolch snapshot; the current POM selects 2.8.0-SNAPSHOT for the required PAT lifecycle fix. Maven cannot satisfy an exact timestamped coordinate with an installed 2.8.0-SNAPSHOT, even when its JARs are present. For development with the local framework referenced by AGENTS.md, use the existing version property override (offline mode uses the installed artifacts without a mirror):

scripts/verify-postgresql.sh -o -Dstrolch.version=2.8.0-SNAPSHOT

Use Java 25 as above. If the local artifacts need rebuilding, install the required framework modules and their reactor dependencies first:

mvn -f /home/eitch/src/git/atx-dev/strolch/pom.xml \
    -pl strolch-service,strolch-persistence-postgresql -am install -DskipTests

The bootstrap command skips framework tests; Intruvia's full verification still runs. Omit -o when other dependencies need downloading. Record the override and artifact checksums with verification evidence: local snapshot results do not verify the original timestamped binaries. Do not rename local JARs to impersonate timestamped artifacts.

The application binds only to loopback and now requires durable PostgreSQL storage. The ingestion HTTP endpoint and viewer sessions are available. The viewer UI and readiness endpoint remain pending. The packaged production environment rejects transient stores; there is no memory-only fallback.

mvn verify requires a disposable PostgreSQL service. scripts/verify-postgresql.sh starts a loopback Docker PostgreSQL container, runs mvn -B clean verify, and removes the container on exit. Each test provisions and drops a unique database. Docker and Java 25 must already be installed. The default image is postgres:18-bookworm; set INTRUVIA_TEST_POSTGRES_IMAGE to a local image or immutable digest when needed. Alternatively, supply INTRUVIA_TEST_DB_URL, INTRUVIA_TEST_DB_USERNAME and INTRUVIA_TEST_DB_PASSWORD for a dedicated test PostgreSQL role with CREATEDB and run mvn -B clean verify. Never point tests at production. No browser/Node build is required.

For a persistent runtime, copy the packaged runtime/ outside target/, copy its config/Privilege*.xml.example files to the corresponding .xml names, and replace both CHANGE-ME values in PrivilegeConfig.xml with independently generated secrets. Keep runtime configuration and the process environment protected; example roles contain a system agent and a Viewer role; provision viewer users as described below. Set DB_URL (a PostgreSQL JDBC URL), DB_USERNAME and DB_PASSWORD through protected service configuration, not command-line arguments. Use a dedicated empty database owned by the application role.

On the first start only, set allowSchemaCreation to true in config/StrolchConfiguration.xml; after successful startup, stop the application and restore it to false. Strolch installs its pinned schema and the application runs model migration 0.0.1. Keep allowSchemaDrop and allowDataInitOnSchemaCreate false. Schema upgrades require a backup and an explicit maintenance change to allowSchemaMigration; it defaults to false. Startup fails on an absent/incompatible schema when creation/migration is disabled. Model migration versions, events, receipts and stream state survive restart. See persistence design and checks.

Event contracts (v1)

The core module now provides immutable event, GeoIP, receipt and stream-state contracts and a typed Strolch Resource mapper. See the field mapping, v1 API schema and complete synthetic fixture. Sequences are decimal strings on the wire; absent coordinates remain null. The fixture uses a documentation IPv6 address with invented geography solely for serialization tests; real ingestion classifies that address as NON_PUBLIC. The ingestion HTTP endpoint is available below. Run scripts/verify-postgresql.sh with Java 25 to verify JSON and Strolch XML round trips, including IPv6, partial locations and 64-bit sequence precision.

Machine ingestion credentials

Machine authentication uses Strolch Personal Access Tokens (PATs). The ingestion endpoint is POST /api/v1/fail2ban/events. Send Authorization: Bearer <tokenId>:<tokenValue> over HTTPS. Tokens must have exactly the event:ingest privilege; viewer/session credentials and broad PATs are rejected.

Provision one ENABLED Strolch technical user per server, with an ingestion-only role. Use supported Strolch management APIs to issue a scoped PAT and persist it. Store the returned secret directly in a protected producer file; never put it in arguments, logs or source control. See the verified APIs, role configuration and rotation procedure.

Create the optional external runtime/config/machine-identities.conf with mode 0600 (or 0400), containing one username-to-stable-instance mapping per line:

producer-a=server-a
producer-b=server-b

It contains identities only, no credentials. Both names must match [A-Za-z0-9][A-Za-z0-9._-]{0,127}; duplicate users or instance IDs are rejected. Blank lines and # comments are allowed. Protect the runtime and parent directories. Missing mapping disables machine access; malformed/insecure files fail startup. Restart to change mappings.

Rotate by issuing a replacement PAT for the same technical user, persisting it, switching the producer file, then revoking and persisting removal of the old PAT. The stable server ID is unchanged. Revocation, expiry and disabled owners are checked even for cached PATs, without an Intruvia restart. The obsolete custom TSV verifier is no longer loaded. No public token-administration endpoint is exposed.

Task 005 was verified using local Strolch 2.8.0-SNAPSHOT with the upstream revocation fix; see source/binary identities and full regression results.

Fail2ban input validation

Task 006 adds a strict, bounded JSON parser and canonical Fail2ban adapter. The public ingestion endpoint uses this parser. Inputs are limited to 16 KiB by default; unknown/duplicate fields, invalid literals and malformed values are rejected. IPv4-mapped IPv6 becomes IPv4, timestamps become UTC, and equivalent payloads share a stable digest. Jail text remains plain data, with a 128-character Unicode limit. Accepted retries can be checked before the separate seven-day age / five-minute future policy. See the validation table and integration contract for limits, constructor configuration, digest format and trusted envelope mapping.

Run focused validation and contract tests without PostgreSQL using Java 25:

mvn -B -o -Dstrolch.version=2.8.0-SNAPSHOT -pl intruvia-rest -am clean test

Local GeoIP enrichment

Task 007 provides a lifecycle-managed local MaxMind City reader. In your external runtime/config/StrolchConfiguration.xml, set the GeoIp component property:

<Properties><databasePath>/srv/intruvia/geoip/GeoLite2-City.mmdb</databasePath></Properties>

Supply your own licensed GeoLite2 City or GeoIP2 City database and keep it readable by the service account. No database, license key, download or per-event network lookup is bundled. The default empty path, missing file or failed load leaves GeoIP degraded (UNAVAILABLE) while the application starts. Private/reserved addresses yield NON_PUBLIC; missing records and lookup failures have distinct statuses. Partial locations remain unmapped, and legitimate zero coordinates are preserved. GeoIP is approximate network location, not a person's physical location.

The reader keeps a reusable in-memory snapshot; budget heap for the database size. For live replacement, publish a complete file by atomic rename to <databasePath>.staged. Intruvia checks it every 60 seconds, validates every record, and atomically replaces the working database and reader. Failed updates retain the working reader; lookups continue during validation. Build/load time, age and the default 14-day stale flag are available through the component diagnostics API. Budget roughly three database sizes of heap during validation. See the update procedure and external geoipupdate example for directory permissions, configuration, recovery and shutdown behavior. Enrichment is used by the ingestion service and public ingestion endpoint. See policy, configuration, provenance and synthetic test fixtures.

Atomic ingestion service

Task 009 provides a Strolch service for validated Fail2ban events. Application code invokes a fresh IngestService through the registered ServiceHandler, using the producer's framework certificate and the protected identity mapping. It requires event:ingest; identity comes from the certificate username. GeoIP runs before the write transaction, and event, receipt and sequence commit atomically. Equal retries return the original ID/sequence, including after event pruning; changed payloads conflict. An accepted retry bypasses timestamp-age rejection and new enrichment.

The public POST endpoint delegates to this service. See the service contract and locking protocol for result handling and later retention integration. Run scripts/verify-postgresql.sh -o -Dstrolch.version=2.8.0-SNAPSHOT with Java 25 for the real-database concurrency, rollback and retry checks.

Fail2ban ingestion HTTP endpoint

Send the v1 JSON payload from the specification to POST /api/v1/fail2ban/events, with Content-Type: application/json and the Strolch PAT Authorization header described above. Use HTTPS at the reverse proxy; the application remains bound to loopback. The request body must contain only the producer fields, never a server identity or geography. The certificate and protected mapping select the server identity, and the same certificate authorizes the service.

A committed event returns 201 with id, decimal-string sequence, duplicate:false and geoStatus. An identical retry returns 200 with the original id, sequence and duplicate:true; it omits geoStatus because a retained receipt may outlive its event. Changed retries return 409. Validation, authentication, privilege, size and media failures return 400, 401, 403, 413 and 415; database failures return 503 without acknowledging success. Errors have {error:{code,message,requestId}}. Responses use Cache-Control: no-store and do not echo payloads or credentials.

Optionally copy runtime/config/ingestion.properties.example to ingestion.properties in the external runtime. Defaults are a 16 KiB UTF-8 body, seven-day maximum event age, five-minute future allowance, and 10 requests/second with burst 100 per configured instance. All authenticated attempts, including retries and rejected payloads, consume that instance's bucket. PAT rotation shares the bucket. A separate global bucket allows 100 API requests/second with burst 1000 before authentication; Jetty caps connections at 256 with a 30-second idle timeout. Request-rate rejection returns 429 with integer Retry-After seconds; connection saturation pauses accepts. Reverse-proxy connection and request limits should also cover internet-facing traffic. No request queues or rate state are persisted; restart resets buckets. Compressed request bodies are unsupported.

See HTTP design, setting bounds and checks. Verify with Java 25 and scripts/verify-postgresql.sh -o -Dstrolch.version=2.8.0-SNAPSHOT. Receipt-capacity admission is task 013; WebSocket delivery remains pending.

Viewer sessions

Copy runtime/config/viewer.properties.example to viewer.properties in your external runtime and set origin=https://your-intruvia-host (no trailing slash). Missing configuration disables viewer sessions. HTTP is allowed only for explicit loopback development origins. HTTPS origins receive Secure, HttpOnly, SameSite=Strict cookies restricted to /api/v1; no token belongs in browser storage.

Provision an ENABLED Strolch user with the supplied Viewer role (event:read) and a Strolch password hash in protected external privilege configuration. Existing runtimes must also add the SessionHandler component and agent GetCertificates permission from the updated examples. See the provisioning steps and HTTP contract.

The same-origin client logs in with POST /api/v1/session, JSON {username,password}, Content-Type: application/json and X-Intruvia-CSRF: 1; the browser sends Origin. GET /api/v1/session checks the cookie session. DELETE /api/v1/session logs out with the same CSRF header and Origin. Both login and session checks return only {username}. Sessions expire after at most 30 minutes by default, and logout invalidates the shared REST/future WebSocket validator immediately. Authorization headers and machine PATs cannot authenticate viewer routes.

The login/map UI is task 015; read APIs and the WebSocket endpoint are still pending. Verify sessions and the full regression suite with Java 25 and scripts/verify-postgresql.sh -o -Dstrolch.version=2.8.0-SNAPSHOT.