From b5e06b885aa13fb153f89afc0013b74db125ed9d Mon Sep 17 00:00:00 2001 From: OfficeCom Codex Date: Fri, 17 Jul 2026 00:55:16 +0200 Subject: [PATCH] Remove legacy AttackTracer repo content --- README.md | 6 +- build/build-client-package.ps1 | 6 +- build/build-release-manifest.ps1 | 2 +- .../attacktracer-server-settings.example.json | 15 - ...le.json => ocsentinel-client.example.json} | 0 ....json => ocsentinel-settings.example.json} | 0 docs/attacktracer-analysis.md | 74 --- docs/attacktracer-ninja-cli.md | 102 ---- docs/attacktracer-ninja-server.md | 82 --- docs/attacktracer-ninja-v2-architecture.md | 469 ---------------- docs/attacktracer-ninja-v2-n8n-contract.md | 131 ----- docs/attacktracer-ninja-v2-repo-plan.md | 523 ------------------ docs/ninjaone-monitoring-playbook.md | 136 ----- docs/ninjaone-org-report-playbook.md | 107 ---- docs/ocsentinel-architecture.md | 28 + ...deployment.md => ocsentinel-deployment.md} | 62 +-- docs/ocsentinel-n8n-contract.md | 27 + .../AttackTracerNinjaBootstrapper.csproj | 16 - .../AttackTracerNinjaBootstrapper/Program.cs | 71 --- ...AttackTracerNinjaServerBootstrapper.csproj | 18 - .../Program.cs | 70 --- installer/install-attacktracer-ninja.ps1 | 15 - installer/install-ocsentinel.ps1 | 4 - .../runtime-build-attacktracer-org-report.ps1 | 307 ---------- ...runtime-run-attacktracer-ninja-monitor.ps1 | 15 - .../runtime-run-attacktracer-ninja-server.ps1 | 308 ----------- installer/runtime-run-attacktracer-ninja.ps1 | 15 - ...rver-install-attacktracer-ninja-server.ps1 | 113 ---- ...er-uninstall-attacktracer-ninja-server.ps1 | 16 - installer/uninstall-attacktracer-ninja.ps1 | 15 - installer/update-attacktracer-ninja.ps1 | 15 - scripts/build-attacktracer-installer.ps1 | 99 ---- scripts/build-attacktracer-org-report.ps1 | 306 ---------- .../build-attacktracer-server-installer.ps1 | 55 -- scripts/extract-attacktracer.ps1 | 72 --- scripts/protect-attacktracer-secret.ps1 | 15 - scripts/run-attacktracer-ninja-monitor.ps1 | 15 - scripts/run-attacktracer-ninja-server.ps1 | 307 ---------- scripts/run-attacktracer-ninja.ps1 | 15 - scripts/run-ocsentinel.ps1 | 4 +- .../AttackScanner.cs | 2 +- .../BuildMetadata.cs | 2 +- .../Commands/ScanAndUploadCommand.cs | 2 +- .../Commands/ScanCommand.cs | 2 +- .../Commands/UploadCommand.cs | 8 +- .../Commands/VersionCommand.cs | 2 +- .../Configuration.cs | 2 +- .../Configuration/ClientConfiguration.cs | 2 +- .../JsonOptions.cs | 2 +- .../Models.cs | 2 +- .../Models/UploadResult.cs | 2 +- .../OCSentinelCli.csproj} | 2 +- .../Program.cs | 4 +- .../ScanOptions.cs | 2 +- .../Security/ProtectedSecretStore.cs | 2 +- .../Transport/N8nUploadClient.cs | 4 +- .../VulnerabilityCorrelation.cs | 2 +- 57 files changed, 98 insertions(+), 3602 deletions(-) delete mode 100644 config/attacktracer-server-settings.example.json rename config/{attacktracer-client.example.json => ocsentinel-client.example.json} (100%) rename config/{attacktracer-settings.example.json => ocsentinel-settings.example.json} (100%) delete mode 100644 docs/attacktracer-analysis.md delete mode 100644 docs/attacktracer-ninja-cli.md delete mode 100644 docs/attacktracer-ninja-server.md delete mode 100644 docs/attacktracer-ninja-v2-architecture.md delete mode 100644 docs/attacktracer-ninja-v2-n8n-contract.md delete mode 100644 docs/attacktracer-ninja-v2-repo-plan.md delete mode 100644 docs/ninjaone-monitoring-playbook.md delete mode 100644 docs/ninjaone-org-report-playbook.md create mode 100644 docs/ocsentinel-architecture.md rename docs/{attacktracer-ninja-v2-deployment.md => ocsentinel-deployment.md} (55%) create mode 100644 docs/ocsentinel-n8n-contract.md delete mode 100644 installer/AttackTracerNinjaBootstrapper/AttackTracerNinjaBootstrapper.csproj delete mode 100644 installer/AttackTracerNinjaBootstrapper/Program.cs delete mode 100644 installer/AttackTracerNinjaServerBootstrapper/AttackTracerNinjaServerBootstrapper.csproj delete mode 100644 installer/AttackTracerNinjaServerBootstrapper/Program.cs delete mode 100644 installer/install-attacktracer-ninja.ps1 delete mode 100644 installer/runtime-build-attacktracer-org-report.ps1 delete mode 100644 installer/runtime-run-attacktracer-ninja-monitor.ps1 delete mode 100644 installer/runtime-run-attacktracer-ninja-server.ps1 delete mode 100644 installer/runtime-run-attacktracer-ninja.ps1 delete mode 100644 installer/server-install-attacktracer-ninja-server.ps1 delete mode 100644 installer/server-uninstall-attacktracer-ninja-server.ps1 delete mode 100644 installer/uninstall-attacktracer-ninja.ps1 delete mode 100644 installer/update-attacktracer-ninja.ps1 delete mode 100644 scripts/build-attacktracer-installer.ps1 delete mode 100644 scripts/build-attacktracer-org-report.ps1 delete mode 100644 scripts/build-attacktracer-server-installer.ps1 delete mode 100644 scripts/extract-attacktracer.ps1 delete mode 100644 scripts/protect-attacktracer-secret.ps1 delete mode 100644 scripts/run-attacktracer-ninja-monitor.ps1 delete mode 100644 scripts/run-attacktracer-ninja-server.ps1 delete mode 100644 scripts/run-attacktracer-ninja.ps1 rename src/{AttackTracerNinjaCli => OCSentinelCli}/AttackScanner.cs (99%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/BuildMetadata.cs (90%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Commands/ScanAndUploadCommand.cs (96%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Commands/ScanCommand.cs (99%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Commands/UploadCommand.cs (95%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Commands/VersionCommand.cs (78%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Configuration.cs (96%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Configuration/ClientConfiguration.cs (95%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/JsonOptions.cs (89%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Models.cs (99%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Models/UploadResult.cs (88%) rename src/{AttackTracerNinjaCli/AttackTracerNinjaCli.csproj => OCSentinelCli/OCSentinelCli.csproj} (95%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Program.cs (93%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/ScanOptions.cs (99%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Security/ProtectedSecretStore.cs (95%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/Transport/N8nUploadClient.cs (97%) rename src/{AttackTracerNinjaCli => OCSentinelCli}/VulnerabilityCorrelation.cs (99%) diff --git a/README.md b/README.md index 2d65c77..61188b7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ OfficeCom Sentinel is the hardened endpoint client for Windows event correlation ## Main Paths -- CLI source: `src/AttackTracerNinjaCli` +- CLI source: `src/OCSentinelCli` - local runner: `scripts/run-ocsentinel.ps1` - Ninja monitor wrapper: `scripts/run-ocsentinel-monitor.ps1` - packaged installer runtime: `installer/runtime-run-ocsentinel.ps1` @@ -33,6 +33,4 @@ This creates: - `artifacts/version.json` -## Compatibility - -Legacy `attacktracer` script names are still present as wrappers so existing NinjaOne jobs do not break immediately. New work should use the `ocsentinel` script names. +The repository contains only the current `ocsentinel` client path and release flow. diff --git a/build/build-client-package.ps1 b/build/build-client-package.ps1 index e6c9d96..2199784 100644 --- a/build/build-client-package.ps1 +++ b/build/build-client-package.ps1 @@ -5,7 +5,7 @@ param( $ErrorActionPreference = "Stop" $repoRoot = Split-Path -Parent $PSScriptRoot -$projectPath = Join-Path $repoRoot "src\AttackTracerNinjaCli\AttackTracerNinjaCli.csproj" +$projectPath = Join-Path $repoRoot "src\OCSentinelCli\OCSentinelCli.csproj" $installerRoot = Join-Path $repoRoot "installer" $artifactsRoot = Join-Path $repoRoot "artifacts" $publishRoot = Join-Path $artifactsRoot "publish\win-x64" @@ -56,8 +56,8 @@ Copy-Item -Path (Join-Path $installerRoot "runtime-run-ocsentinel.ps1") -Destina Copy-Item -Path (Join-Path $installerRoot "runtime-run-ocsentinel-monitor.ps1") -Destination (Join-Path $packageRoot "scripts\run-ocsentinel-monitor.ps1") -Force Copy-Item -Path (Join-Path $repoRoot "scripts\protect-ocsentinel-secret.ps1") -Destination (Join-Path $packageRoot "scripts\protect-ocsentinel-secret.ps1") -Force -Copy-Item -Path (Join-Path $repoRoot "config\attacktracer-settings.example.json") -Destination (Join-Path $packageRoot "config\ocsentinel-settings.example.json") -Force -Copy-Item -Path (Join-Path $repoRoot "config\attacktracer-client.example.json") -Destination (Join-Path $packageRoot "config\ocsentinel-client.example.json") -Force +Copy-Item -Path (Join-Path $repoRoot "config\ocsentinel-settings.example.json") -Destination (Join-Path $packageRoot "config\ocsentinel-settings.example.json") -Force +Copy-Item -Path (Join-Path $repoRoot "config\ocsentinel-client.example.json") -Destination (Join-Path $packageRoot "config\ocsentinel-client.example.json") -Force Copy-Item -Path (Join-Path $repoRoot "config\update-channel.example.json") -Destination (Join-Path $packageRoot "config\update-channel.example.json") -Force Copy-Item -Path (Join-Path $repoRoot "samples\ninja-vulnerability-export.example.csv") -Destination (Join-Path $packageRoot "samples\ninja-vulnerability-export.example.csv") -Force Copy-Item -Path (Join-Path $repoRoot "samples\webhook-payload.example.json") -Destination (Join-Path $packageRoot "samples\webhook-payload.example.json") -Force diff --git a/build/build-release-manifest.ps1 b/build/build-release-manifest.ps1 index 207624a..6ffb915 100644 --- a/build/build-release-manifest.ps1 +++ b/build/build-release-manifest.ps1 @@ -10,7 +10,7 @@ param( $ErrorActionPreference = "Stop" $repoRoot = Split-Path -Parent $PSScriptRoot -$projectPath = Join-Path $repoRoot "src\AttackTracerNinjaCli\AttackTracerNinjaCli.csproj" +$projectPath = Join-Path $repoRoot "src\OCSentinelCli\OCSentinelCli.csproj" $packageFullPath = [System.IO.Path]::GetFullPath((Join-Path $repoRoot $PackagePath)) $outputFullPath = [System.IO.Path]::GetFullPath((Join-Path $repoRoot $OutputPath)) diff --git a/config/attacktracer-server-settings.example.json b/config/attacktracer-server-settings.example.json deleted file mode 100644 index e2060a2..0000000 --- a/config/attacktracer-server-settings.example.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "ninjaBaseUrl": "https://app.ninjarmm.com", - "organizationId": 0, - "clientId": "", - "clientSecretEncrypted": "", - "oauthScope": "monitoring management", - "reportsRoot": "\\\\wsus2\\ATNShare$", - "htmlOutputPath": "\\\\wsus2\\ATNShare$\\attacktracer-org-report.html", - "statusFieldName": "attacktracerorgstatus", - "summaryFieldName": "attacktracerorgsummary", - "lastUpdateFieldName": "attacktracerorglastupdate", - "htmlFieldName": "attacktracerorgreport", - "updateHtmlField": false, - "maxAlertRows": 25 -} diff --git a/config/attacktracer-client.example.json b/config/ocsentinel-client.example.json similarity index 100% rename from config/attacktracer-client.example.json rename to config/ocsentinel-client.example.json diff --git a/config/attacktracer-settings.example.json b/config/ocsentinel-settings.example.json similarity index 100% rename from config/attacktracer-settings.example.json rename to config/ocsentinel-settings.example.json diff --git a/docs/attacktracer-analysis.md b/docs/attacktracer-analysis.md deleted file mode 100644 index c9e8d88..0000000 --- a/docs/attacktracer-analysis.md +++ /dev/null @@ -1,74 +0,0 @@ -# AttackTracer Reverse Engineering Notes - -## What the legacy app does - -`AttackTracer` is a .NET Framework 4.5 WinForms application installed under `Servolutions\BotFence`. - -Its main behavior is: - -- scan Windows Event Logs for failed logins - - Security `4625` for Windows logons - - Application `18456` for SQL Server login failures - - Application `1035` for Exchange-related login failures -- scan IIS/FTP logs under `C:\inetpub\logs\LogFiles` and `D:\inetpub\logs\LogFiles` -- scan FileZilla Server logs under both `C:` and `D:` -- aggregate attacks by source IP -- geolocate IPs through `http://www.autotimezone.net/atzonline/atz.svc` -- render a top-10 result list and an HTML report -- optionally email the report through `http://pulse.serverpulse.com/PulseCounter/notifications.svc` - -## Important weaknesses in the old design - -- UI-first architecture - - the app is a desktop WinForms tool instead of a background service or scheduled automation -- hard-coded paths - - it only checks a few fixed IIS and FileZilla folders -- legacy external dependencies - - geolocation and email both depend on old unauthenticated HTTP WCF endpoints -- weak integration model - - results are shown locally or emailed, but not pushed into a central operations system -- mixed responsibilities - - scanning, aggregation, rendering, and notification all live inside the same form workflow - -## Meaningful direction for a NinjaOne-connected replacement - -Instead of reviving the WinForms app, the better replacement is a small service or scheduled CLI with this split: - -1. collectors - - Windows Security/Event Log collector - - IIS/FTP log collector - - FileZilla collector -2. normalization - - normalize all findings into one event schema like: - - `timestamp` - - `sourceIp` - - `targetType` - - `username` - - `source` -3. correlation - - aggregate by IP and time window - - compute severity, velocity, and repeated targets -4. outputs - - JSON report on disk - - HTML report if needed - - NinjaOne-facing output channel - -## Suggested NinjaOne integration patterns - -Because there is no NinjaOne code in this repository yet, the safest first integration targets are: - -- write a machine-readable JSON summary that a NinjaOne script can collect -- emit a plain-text summary to stdout for script-result capture -- optionally write a CSV or HTML artifact for technicians -- keep notification transport separate so we can later swap in a NinjaOne API client, webhook, or custom-field updater - -## Practical next step - -Build a new `AttackTracer` replacement around a headless scanner first, then add the NinjaOne transport as a separate adapter. - -That lets us preserve the useful detection logic while dropping: - -- WinForms -- WCF -- external HTTP dependencies -- vendor-specific mail plumbing diff --git a/docs/attacktracer-ninja-cli.md b/docs/attacktracer-ninja-cli.md deleted file mode 100644 index 93ac4cb..0000000 --- a/docs/attacktracer-ninja-cli.md +++ /dev/null @@ -1,102 +0,0 @@ -# AttackTracer Ninja CLI - -## Purpose - -`AttackTracerNinjaCli` is the first headless replacement for the legacy `AttackTracer` WinForms tool. - -It keeps the useful detection behavior, but drops the old UI and external WCF dependencies. - -## Current inputs - -- Windows Security Event Log `4625` -- Windows Application Event Log `18456` -- Windows Application Event Log `1035` -- IIS FTP logs under: - - `C:\inetpub\logs\LogFiles` - - `D:\inetpub\logs\LogFiles` -- FileZilla Server logs under: - - `C:\Program Files (x86)\FileZilla Server\Logs` - - `D:\Program Files (x86)\FileZilla Server\Logs` - -## Current outputs - -- human-readable console summary -- JSON report for later collection by `ninja1` -- optional `ATTACKTRACER_*` key/value lines for RMM parsing -- optional correlation with exported NinjaOne CVE data for the local device - -## Usage - -```powershell -dotnet run --project .\src\AttackTracerNinjaCli -- --output .\reports\attacktracer-summary.json -``` - -Or through the wrapper script: - -```powershell -.\scripts\run-attacktracer-ninja.ps1 -LookbackDays 7 -TopCount 10 -FailOnThreshold -``` - -With vulnerability correlation: - -```powershell -.\scripts\run-attacktracer-ninja.ps1 -LookbackDays 7 -TopCount 10 -VulnerabilityCsvPath .\samples\ninja-vulnerability-export.example.csv -``` - -Useful flags: - -- `--json-only` -- `--lookback-days 7` -- `--top 20` -- `--config .\config\attacktracer-settings.example.json` -- `--ninja-output` -- `--fail-on-attacks` -- `--fail-on-threshold` -- `--vulnerability-csv .\path\to\ninja-export.csv` - -## Configuration - -A sample config is available at [config/attacktracer-settings.example.json](C:\Users\Besitzer\Documents\AttackTracerNinjaVersion\config\attacktracer-settings.example.json). - -It currently supports: - -- warning and critical thresholds -- correlation thresholds for local CVE findings -- FTP root overrides -- FileZilla root overrides -- source IP exclusions - -## CVE correlation - -You can correlate AttackTracer findings with exported vulnerability data for the current host. - -Expected minimum CSV columns: - -- a device-name column such as `device`, `hostname`, or `computername` -- a CVE column such as `cve` or `cve_id` - -Optional columns: - -- `severity` -- `cvss` -- `remediation` - -A sample file is available at [samples/ninja-vulnerability-export.example.csv](C:\Users\Besitzer\Documents\AttackTracerNinjaVersion\samples\ninja-vulnerability-export.example.csv). - -## Next integration step - -The intended `ninja1` path is: - -1. run the installed monitor wrapper from a NinjaOne script or scheduled task -2. let the monitor wrapper write endpoint-level NinjaOne custom fields when `Ninja-Property-Set` or `ninjarmm-cli` is available -3. collect the JSON artifact if you want deeper troubleshooting data -4. use the custom fields and/or `ATTACKTRACER_*` console lines for alerting - -For organization-level reporting, you can mirror each device JSON report to a shared folder and render a local HTML summary using [docs/ninjaone-org-report-playbook.md](C:\Users\Besitzer\Documents\AttackTracerNinjaVersion\docs\ninjaone-org-report-playbook.md). - -## Deliberate omissions for v1 - -- no geolocation -- no email sending -- no WinForms UI -- no dependency on legacy HTTP services diff --git a/docs/attacktracer-ninja-server.md b/docs/attacktracer-ninja-server.md deleted file mode 100644 index edefe96..0000000 --- a/docs/attacktracer-ninja-server.md +++ /dev/null @@ -1,82 +0,0 @@ -# AttackTracer Ninja Server - -> Legacy: This document describes the older share/server architecture. The recommended direction is now the V2 client + n8n + Postgres model documented in `attacktracer-ninja-v2-architecture.md`. - -## Purpose - -`AttackTracerNinjaServer` is the central companion package for one designated server. - -It is separate from the normal endpoint client and is responsible for: - -- reading mirrored JSON reports from the network share -- building the consolidated HTML report -- updating NinjaOne organization custom fields through the NinjaOne API - -## Separation of roles - -### Endpoint client - -Install the standard `AttackTracerNinja` package on all monitored systems. - -Use it to: - -- scan local logs -- write device custom fields -- mirror JSON reports to the central share - -### Server package - -Install `AttackTracerNinjaServer` on exactly one central server. - -Use it to: - -- read `\\share\*.json` -- generate `attacktracer-org-report.html` -- update: - - `attacktracerorgstatus` - - `attacktracerorgsummary` - - `attacktracerorglastupdate` -- optionally update `attacktracerorgreport` through the API - -## Installer - -Build output: - -- `artifacts\AttackTracerNinjaServerSetup.exe` - -During installation, the server installer prompts for: - -- Ninja base URL -- organization ID -- OAuth client ID -- OAuth client secret -- OAuth scope -- reports share path -- HTML output path -- target org field names - -Suggested OAuth scope default: - -- `monitoring management` - -## Runtime command - -Installed command: - -```powershell -& "C:\Program Files\AttackTracerNinjaServer\scripts\run-attacktracer-ninja-server.ps1" -``` - -## OAuth requirements - -Use a NinjaOne API client with at least: - -- `Monitoring` -- `Management` -- `Client Credentials` - -Relevant official references: - -- [Client Credentials Flow](https://app.ninjarmm.com/apidocs-beta/authorization/flows/client-credentials-flow) -- [API OAuth Token Configuration](https://www.ninjaone.com/docs/application-programming-interface-api/oauth-token-configuration/) -- [Organization custom fields](https://app.ninjarmm.com/apidocs-beta/core-resources/operations/getNodeCustomFields_1) diff --git a/docs/attacktracer-ninja-v2-architecture.md b/docs/attacktracer-ninja-v2-architecture.md deleted file mode 100644 index ad6bdd7..0000000 --- a/docs/attacktracer-ninja-v2-architecture.md +++ /dev/null @@ -1,469 +0,0 @@ -# AttackTracer Ninja V2 Architecture - -## Goal - -Build a lightweight but robust endpoint agent that: - -- runs on every monitored Windows device -- reads local attack telemetry and optional local vulnerability exports -- writes device-level NinjaOne custom fields locally -- uploads signed JSON reports to a central n8n ingestion workflow -- receives updates through NinjaOne tasks from GitLab-hosted releases - -This design replaces: - -- the shared-folder aggregation model -- the dedicated `AttackTracerNinjaServer` -- organization-level field processing on an endpoint - -Organization-wide correlation and NinjaOne organization API updates move to n8n. - -## Core principles - -1. Endpoint collection is local, central correlation is remote. -2. Device custom field writes stay local through NinjaOne-supported endpoint mechanisms. -3. Organization logic never depends on one central Windows server. -4. Update delivery is controlled by NinjaOne, with release artifacts hosted in GitLab. -5. Endpoint uploads are authenticated and tamper-evident. -6. The central pipeline treats endpoint data as useful but never fully trusted. - -## Recommended component split - -### 1. Endpoint agent - -Use a compiled Windows agent written in C#. - -Responsibilities: - -- scan Windows Security/Application logs -- parse supported local logs such as FTP/FileZilla -- optionally ingest local exported vulnerability data -- build normalized JSON report -- print Ninja-style key/value status lines -- expose a command for device-local execution from NinjaOne -- upload signed report to n8n - -Why compiled instead of PowerShell-only: - -- harder to casually tamper with than plain scripts -- easier to sign -- easier to version and hash-verify -- simpler to protect internal protocol logic such as signing and replay prevention - -### 2. Thin PowerShell deployment wrapper - -Use PowerShell only for: - -- install -- uninstall -- update -- scheduled execution wrapper -- NinjaOne field publishing wrapper when needed - -This keeps operational flexibility while protecting core collection logic inside a signed binary. - -### 3. GitLab release channel - -GitLab hosts: - -- release ZIP or installer -- version manifest -- SHA-256 checksum file -- optional detached signature - -Recommended files per release: - -- `AttackTracerNinjaClient-win-x64.zip` -- `version.json` -- `AttackTracerNinjaClient-win-x64.zip.sha256` -- `release-notes.md` - -### 4. n8n ingestion pipeline - -n8n receives endpoint JSON through an authenticated webhook and performs: - -- signature validation -- timestamp and replay validation -- schema validation -- persistence to Postgres -- organization aggregation -- NinjaOne organization field updates -- optional notifications - -### 5. Postgres as primary store - -Postgres should be the system of record for incoming reports. - -Recommended tables: - -- `devices` -- `device_reports` -- `device_findings` -- `device_vulnerability_findings` -- `organization_rollups` -- `ingestion_events` - -### 6. Optional Nextcloud archive - -Nextcloud can be used for: - -- archived JSON bundles -- generated HTML reports -- long-term human-readable reports - -Do not use it as the primary operational datastore. - -## Endpoint security model - -## Threat assumptions - -Assume an attacker may: - -- modify files under the install directory -- stop scheduled tasks or Ninja jobs -- alter local logs -- replay older JSON uploads -- inspect locally stored configuration - -Assume an attacker with full local admin or SYSTEM access can eventually subvert the endpoint. The architecture therefore aims to: - -- raise the effort of tampering -- make tampering detectable -- reduce blast radius of stolen secrets -- preserve central evidence of missing or suspicious reporting - -## Required protections - -### Signed binaries - -- Sign the compiled agent executable. -- Optionally sign the deployment PowerShell scripts. -- The updater must verify Authenticode signature and expected hash before replacing files. - -### Protected local secrets - -Do not embed one global master secret in all clients. - -Use one of these approaches: - -- per-tenant ingest token wrapped with DPAPI on each machine -- per-device secret provisioned during install and stored encrypted with DPAPI -- short-lived signed enrollment flow if you later want stronger provisioning - -Minimum recommendation: - -- store an n8n upload secret encrypted via DPAPI in a local config file readable only by SYSTEM/Administrators - -### Signed report uploads - -Each report upload should include: - -- device identifier -- report timestamp in UTC -- monotonic nonce or GUID -- client version -- payload hash -- HMAC signature over canonicalized request fields - -Suggested headers: - -- `X-ATN-Device` -- `X-ATN-Timestamp` -- `X-ATN-Nonce` -- `X-ATN-Version` -- `X-ATN-Signature` - -n8n must reject: - -- stale timestamps outside tolerance -- duplicate nonce values -- invalid HMAC signatures - -### Tamper evidence - -The endpoint should include in its report: - -- installed client version -- scanner execution start/end UTC -- whether upload succeeded -- hash of produced JSON payload -- optional configuration version - -n8n should track: - -- expected reporting cadence per device -- missing devices -- repeated version lag -- repeated upload failures -- sudden disappearance of formerly noisy devices - -### Least-privilege local behavior - -- install under `C:\Program Files\AttackTracerNinja` -- write mutable state under `C:\ProgramData\AttackTracerNinja` -- restrict config/log/state ACLs to `SYSTEM` and Administrators -- avoid storing writable binaries under user-controlled locations - -## Update architecture - -## Distribution model - -NinjaOne remains the deployment engine. - -Recommended flow: - -1. Build signed release in CI. -2. Publish release artifact to GitLab. -3. Publish `version.json` with latest version metadata. -4. NinjaOne scheduled update task runs on endpoints. -5. Update task checks local version against GitLab manifest. -6. If newer, download artifact, verify hash/signature, install, and record result. - -## Version manifest - -Example `version.json`: - -```json -{ - "channel": "stable", - "version": "2.0.0", - "publishedAtUtc": "2026-07-16T18:00:00Z", - "artifactUrl": "https://gitlab.example.com/group/project/-/releases/v2.0.0/downloads/AttackTracerNinjaClient-win-x64.zip", - "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - "minUpdaterVersion": "1.0.0" -} -``` - -## Endpoint update task - -The NinjaOne update task should: - -- run as SYSTEM -- read installed version -- fetch `version.json` -- compare versions -- download ZIP to a temporary directory -- verify SHA-256 -- verify Authenticode signature on executable -- stop running agent if needed -- replace files atomically -- write update result to a local log - -The update task should never install unsigned or hash-mismatched artifacts. - -## Runtime execution model - -Recommended commands: - -- `AttackTracerNinjaCli.exe scan` -- `AttackTracerNinjaCli.exe scan --ninja-output` -- `AttackTracerNinjaCli.exe upload --report ` -- `AttackTracerNinjaCli.exe scan-and-upload` - -The PowerShell wrapper invoked by NinjaOne should typically run: - -```powershell -& "C:\Program Files\AttackTracerNinja\app\AttackTracerNinjaCli.exe" scan-and-upload --ninja-output -``` - -Wrapper responsibilities: - -- ensure paths exist -- capture stdout/stderr to log -- set NinjaOne device custom fields from the produced status -- return a useful exit code for monitoring - -## Suggested data flow - -1. Endpoint agent scans local data. -2. Agent writes a JSON report locally. -3. Wrapper publishes device custom fields to NinjaOne. -4. Agent signs and uploads the JSON report to n8n. -5. n8n validates authenticity and freshness. -6. n8n stores raw and normalized data in Postgres. -7. n8n computes organization-level summaries. -8. n8n updates NinjaOne organization fields through the API. -9. Optional HTML and long-form artifacts are generated centrally. - -## n8n workflow design - -## Workflow A: ingest-device-report - -Trigger: - -- Webhook - -Steps: - -1. Validate required headers. -2. Validate timestamp tolerance. -3. Check nonce replay in Postgres. -4. Recompute HMAC and compare. -5. Validate JSON schema. -6. Upsert device metadata. -7. Insert raw report row. -8. Insert findings rows. -9. Mark ingestion success. - -Failure handling: - -- log validation error -- store rejected attempt metadata -- optionally alert on repeated bad signatures - -## Workflow B: organization-rollup - -Trigger: - -- Cron every 5 or 15 minutes - -Steps: - -1. Query latest accepted report per device. -2. Compute org status and summary. -3. Detect stale devices and missing submissions. -4. Update NinjaOne organization fields: - - `attacktracerorgstatus` - - `attacktracerorgsummary` - - `attacktracerorglastupdate` -5. Optionally generate HTML and archive it - -## Workflow C: stale-device-alerting - -Trigger: - -- Cron - -Logic: - -- find devices with no valid upload within expected interval -- raise notification or ticket - -## Recommended report contract - -Each JSON report should include: - -```json -{ - "schemaVersion": "2.0", - "machineName": "WSUS", - "deviceIdHint": "", - "organizationHint": "", - "generatedAtUtc": "2026-07-16T18:42:11Z", - "clientVersion": "2.0.0", - "lookbackDays": 7, - "baseStatus": "ok", - "alertState": "ok", - "totalEvents": 0, - "uniqueIpCount": 0, - "errorCount": 0, - "attackFindings": [], - "vulnerabilityCorrelation": { - "totalCount": 0, - "criticalCount": 0, - "highCvssCount": 0 - }, - "runtime": { - "startedAtUtc": "2026-07-16T18:42:09Z", - "finishedAtUtc": "2026-07-16T18:42:11Z", - "uploadAttempted": true - } -} -``` - -Authentication metadata should travel in headers, not inside the JSON body. - -## Packaging recommendation - -Use a ZIP-based package for GitLab delivery and NinjaOne installation. - -Recommended layout: - -```text -AttackTracerNinjaClient-win-x64.zip - app/ - AttackTracerNinjaCli.exe - AttackTracerNinjaCli.dll - scripts/ - install-attacktracer-ninja.ps1 - uninstall-attacktracer-ninja.ps1 - update-attacktracer-ninja.ps1 - run-attacktracer-ninja.ps1 - config/ - attacktracer-settings.example.json - VERSION.txt -``` - -This avoids the operational overhead of a heavy GUI installer while staying easy to deploy from NinjaOne. - -## Hardening recommendations - -- enable script and binary code signing where possible -- set strict ACLs on `Program Files` and `ProgramData` content -- log every update attempt locally -- include a watchdog check for missing executions -- keep secrets out of command-line parameters where possible -- prefer HTTPS with certificate validation for all uploads -- optionally pin the server certificate thumbprint if your environment allows it - -## Migration plan - -### Phase 1: define v2 contract - -- freeze current JSON model and derive `schemaVersion 2.0` -- define n8n webhook contract -- define version manifest format - -### Phase 2: build central pipeline - -- create Postgres schema -- create n8n ingest workflow -- create n8n rollup workflow -- test NinjaOne org field updates from n8n - -### Phase 3: refactor endpoint client - -- remove share mirroring logic -- remove organization/server logic from endpoint package -- add signed upload path -- add DPAPI-backed local secret storage - -### Phase 4: implement update channel - -- publish GitLab release artifacts -- implement version manifest check -- implement hash/signature validation -- implement NinjaOne update task - -### Phase 5: controlled rollout - -- pilot on a small device group -- validate upload cadence and rollups -- compare with old system -- then roll out to all devices - -### Phase 6: retire old architecture - -- stop `AttackTracerNinjaServer` -- remove share-based reporting dependency -- deprecate old org-report scripts - -## Recommendation summary - -Recommended final direction: - -- keep a compiled endpoint agent -- use PowerShell only as thin install/update/run wrapper -- distribute and update through NinjaOne -- host signed release artifacts in GitLab -- upload signed JSON reports to n8n -- store operational data in Postgres -- perform organization-level NinjaOne API writes only from n8n - -This gives the best balance of: - -- endpoint robustness -- easier rollout -- central visibility -- reduced single-point-of-failure risk -- maintainable future growth diff --git a/docs/attacktracer-ninja-v2-n8n-contract.md b/docs/attacktracer-ninja-v2-n8n-contract.md deleted file mode 100644 index 6ca1f60..0000000 --- a/docs/attacktracer-ninja-v2-n8n-contract.md +++ /dev/null @@ -1,131 +0,0 @@ -# AttackTracer Ninja V2 n8n Contract - -## Purpose - -This document defines the concrete webhook contract between the endpoint client and n8n. - -## Request - -Method: - -- `POST` - -Content type: - -- `application/json` - -Webhook URL: - -- example: `https://n8n.example.com/webhook/attacktracer-ingest` - -## Required headers - -- `X-ATN-Device` -- `X-ATN-Timestamp` -- `X-ATN-Nonce` -- `X-ATN-Version` -- `X-ATN-Payload-SHA256` -- `X-ATN-Signature` - -## Header semantics - -- `X-ATN-Device` - - endpoint machine name used for attribution -- `X-ATN-Timestamp` - - UTC timestamp in ISO 8601 format -- `X-ATN-Nonce` - - unique per request, used for replay protection -- `X-ATN-Version` - - client version string -- `X-ATN-Payload-SHA256` - - SHA-256 over the JSON body, lowercase hex -- `X-ATN-Signature` - - HMAC-SHA256 over the canonical signing string, lowercase hex - -## Canonical signing string - -The client signs this exact string: - -```text -device + "\n" + timestamp + "\n" + nonce + "\n" + version + "\n" + payloadSha256 -``` - -## Signature algorithm - -- `HMAC-SHA256` - -## Recommended n8n validation - -1. Reject when a required header is missing. -2. Reject when timestamp skew exceeds allowed tolerance. -3. Reject when nonce was already seen. -4. Recompute payload hash and compare to `X-ATN-Payload-SHA256`. -5. Recompute HMAC and compare to `X-ATN-Signature`. -6. Parse JSON only after headers and signature validation pass. - -## Suggested timestamp tolerance - -- 5 minutes - -## Suggested replay protection persistence - -Persist nonce records in Postgres with: - -- nonce -- device -- timestamp -- first_seen_utc - -## JSON body schema - -The body is a `ScanResult` JSON document with schema version `2.0`. - -Key fields: - -- `schemaVersion` -- `machineName` -- `generatedAtUtc` -- `clientVersion` -- `lookbackDays` -- `alertState` -- `baseAlertState` -- `totalEvents` -- `uniqueIpCount` -- `errors` -- `vulnerabilityCorrelation` -- `runtime` - -## Example body - -See: - -- [webhook-payload.example.json](C:/Users/Besitzer/Documents/AttackTracerNinjaVersion/samples/webhook-payload.example.json) - -## n8n response recommendation - -On success: - -- HTTP `200` or `202` -- JSON body with minimal confirmation - -Example: - -```json -{ - "accepted": true, - "device": "WSUS", - "nonce": "f53a3f0b0c2c466ea1717d88d55fd393" -} -``` - -On failure: - -- HTTP `4xx` for validation/signature problems -- HTTP `5xx` for processing/storage problems - -## Device identity guidance - -The webhook should not trust `machineName` alone for authorization. - -For initial rollout, use the shared secret as the trust anchor. -For later hardening, add a per-device secret or enrollment identity. diff --git a/docs/attacktracer-ninja-v2-repo-plan.md b/docs/attacktracer-ninja-v2-repo-plan.md deleted file mode 100644 index 564d578..0000000 --- a/docs/attacktracer-ninja-v2-repo-plan.md +++ /dev/null @@ -1,523 +0,0 @@ -# AttackTracer Ninja V2 Repo Plan - -## Purpose - -This document turns the V2 architecture into a concrete repository refactor plan. - -It answers: - -- which existing components should be removed or deprecated -- which new components should be created -- how the client package should be structured -- what the n8n webhook contract should look like -- in which order the migration should happen - -## Target result - -The repository should end up centered around one endpoint client package that: - -- runs on every managed Windows device -- writes NinjaOne device fields locally -- uploads signed JSON reports to n8n -- can be installed and updated from NinjaOne using GitLab-hosted release artifacts - -The repository should no longer rely on: - -- share mirroring -- a central `AttackTracerNinjaServer` -- organization aggregation on a Windows endpoint - -## Existing components to remove or deprecate - -## Remove from active architecture - -These should be retired from the primary product path: - -- `scripts/build-attacktracer-org-report.ps1` -- `installer/runtime-build-attacktracer-org-report.ps1` -- `scripts/run-attacktracer-ninja-server.ps1` -- `installer/runtime-run-attacktracer-ninja-server.ps1` -- `scripts/build-attacktracer-server-installer.ps1` -- `installer/server-install-attacktracer-ninja-server.ps1` -- `installer/server-uninstall-attacktracer-ninja-server.ps1` -- `installer/AttackTracerNinjaServerBootstrapper/*` -- `docs/attacktracer-ninja-server.md` -- share-based org-report playbook content in `docs/ninjaone-org-report-playbook.md` - -## Keep only as legacy reference - -These can remain temporarily during migration but should be clearly marked legacy: - -- `scripts/run-attacktracer-ninja-monitor.ps1` -- `scripts/run-attacktracer-ninja.ps1` -- `docs/ninjaone-monitoring-playbook.md` -- `config/attacktracer-settings.example.json` - -## Existing components to preserve and refactor - -These are still valuable and should become the base for V2: - -- `src/AttackTracerNinjaCli/AttackScanner.cs` -- `src/AttackTracerNinjaCli/VulnerabilityCorrelation.cs` -- `src/AttackTracerNinjaCli/Models.cs` -- `src/AttackTracerNinjaCli/JsonOptions.cs` -- `src/AttackTracerNinjaCli/Program.cs` -- `src/AttackTracerNinjaCli/ScanOptions.cs` - -Core scanning logic should remain in C#. - -## New repository structure - -Recommended target layout: - -```text -src/ - AttackTracerNinjaCli/ - AttackTracerNinjaCli.csproj - Commands/ - ScanCommand.cs - UploadCommand.cs - ScanAndUploadCommand.cs - VersionCommand.cs - Security/ - HmacSigner.cs - NonceStore.cs - ProtectedSecretStore.cs - Transport/ - N8nUploadClient.cs - UploadEnvelopeBuilder.cs - Models/ - ReportEnvelope.cs - ScanReport.cs - UploadResult.cs - Configuration/ - ClientConfiguration.cs - ConfigurationLoader.cs - -scripts/ - install-attacktracer-ninja.ps1 - uninstall-attacktracer-ninja.ps1 - update-attacktracer-ninja.ps1 - run-attacktracer-ninja.ps1 - publish-ninja-fields.ps1 - -config/ - attacktracer-client.example.json - update-channel.example.json - -docs/ - attacktracer-ninja-v2-architecture.md - attacktracer-ninja-v2-repo-plan.md - attacktracer-ninja-v2-n8n-contract.md - attacktracer-ninja-v2-deployment.md - -build/ - build-client-package.ps1 - build-release-manifest.ps1 - -samples/ - webhook-payload.example.json - version.example.json -``` - -## New endpoint client responsibilities - -## Binary responsibilities - -The compiled client should do these jobs: - -- local attack scanning -- vulnerability correlation -- JSON report generation -- report signing -- authenticated upload to n8n -- stable exit codes -- machine-readable output for NinjaOne wrappers - -## Wrapper responsibilities - -PowerShell wrappers should only do: - -- install/uninstall -- update -- local config bootstrap -- invoking the binary -- publishing NinjaOne device custom fields -- logging wrapper-level failures - -## New client command model - -Recommended commands: - -### `scan` - -Performs local scan and writes a report file. - -Example: - -```powershell -AttackTracerNinjaCli.exe scan --output "C:\ProgramData\AttackTracerNinja\reports\latest.json" --ninja-output -``` - -### `upload` - -Uploads an existing report to n8n. - -Example: - -```powershell -AttackTracerNinjaCli.exe upload --report "C:\ProgramData\AttackTracerNinja\reports\latest.json" -``` - -### `scan-and-upload` - -Performs a scan and directly uploads the result. - -Example: - -```powershell -AttackTracerNinjaCli.exe scan-and-upload --output "C:\ProgramData\AttackTracerNinja\reports\latest.json" --ninja-output -``` - -### `version` - -Prints installed version and build metadata. - -## Config model - -Recommended local config file: - -- `C:\ProgramData\AttackTracerNinja\config\attacktracer-client.json` - -Recommended fields: - -```json -{ - "schemaVersion": "2.0", - "environment": "production", - "lookbackDays": 7, - "topFindings": 10, - "n8nWebhookUrl": "https://n8n.example.com/webhook/attacktracer-ingest", - "deviceIdentifierMode": "machineName", - "uploadTimeoutSeconds": 30, - "enableVulnerabilityCorrelation": true, - "vulnerabilityCsvPath": "", - "secretReference": "device-default" -} -``` - -Secrets should not be stored here in clear text. - -## Local secret handling plan - -## Secret storage - -Create a protected local secret file under: - -- `C:\ProgramData\AttackTracerNinja\secrets\upload-secret.dat` - -Use DPAPI machine protection to encrypt the secret. - -## Secret bootstrap - -Initial rollout options: - -1. NinjaOne install task writes a tenant token once and immediately protects it with DPAPI. -2. Later evolution: unique per-device secret issued centrally. - -Recommended first cut: - -- tenant-level ingest secret wrapped with DPAPI -- HMAC signature over request metadata + payload hash - -## n8n webhook contract - -## Endpoint request - -Method: - -- `POST` - -URL: - -- provided via config, for example: - - `https://n8n.example.com/webhook/attacktracer-ingest` - -Headers: - -- `Content-Type: application/json` -- `X-ATN-Device` -- `X-ATN-Timestamp` -- `X-ATN-Nonce` -- `X-ATN-Version` -- `X-ATN-Payload-SHA256` -- `X-ATN-Signature` - -Body: - -- JSON scan report only - -Signature input recommendation: - -```text -device + "\n" + timestamp + "\n" + nonce + "\n" + version + "\n" + payloadSha256 -``` - -Algorithm: - -- `HMAC-SHA256` - -## n8n validation rules - -n8n should reject when: - -- any required header is missing -- timestamp is outside tolerance -- nonce already exists -- payload hash mismatches body -- signature mismatches -- schema is invalid - -## Suggested report payload schema - -```json -{ - "schemaVersion": "2.0", - "machineName": "WSUS", - "generatedAtUtc": "2026-07-16T19:12:00Z", - "clientVersion": "2.0.0", - "baseStatus": "ok", - "alertState": "ok", - "totalEvents": 0, - "uniqueIpCount": 0, - "errorCount": 0, - "attackFindings": [], - "vulnerabilityCorrelation": { - "totalCount": 0, - "criticalCount": 0, - "highCvssCount": 0 - }, - "runtime": { - "startedAtUtc": "2026-07-16T19:11:57Z", - "finishedAtUtc": "2026-07-16T19:12:00Z", - "uploadAttempted": true - } -} -``` - -## GitLab release plan - -## Release artifacts - -Each release should publish: - -- `AttackTracerNinjaClient-win-x64.zip` -- `AttackTracerNinjaClient-win-x64.zip.sha256` -- `version.json` - -## ZIP layout - -```text -AttackTracerNinjaClient-win-x64.zip - app/ - AttackTracerNinjaCli.exe - AttackTracerNinjaCli.dll - scripts/ - install-attacktracer-ninja.ps1 - uninstall-attacktracer-ninja.ps1 - update-attacktracer-ninja.ps1 - run-attacktracer-ninja.ps1 - publish-ninja-fields.ps1 - config/ - attacktracer-client.example.json - VERSION.txt -``` - -## Manifest format - -```json -{ - "channel": "stable", - "version": "2.0.0", - "artifactUrl": "https://gitlab.example.com/group/project/-/releases/v2.0.0/downloads/AttackTracerNinjaClient-win-x64.zip", - "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - "publishedAtUtc": "2026-07-16T19:15:00Z" -} -``` - -## NinjaOne task model - -## Task A: install/update client - -Runs as SYSTEM. - -Responsibilities: - -- fetch `version.json` -- compare with local version -- download artifact when newer -- verify SHA-256 -- verify Authenticode signature on executable -- install or update package - -## Task B: run scan - -Runs as SYSTEM on each endpoint. - -Responsibilities: - -- invoke `run-attacktracer-ninja.ps1` -- publish device custom fields -- trigger upload to n8n - -## Device custom field path - -Keep the current device-level field strategy. - -Wrappers should continue using: - -- `Ninja-Property-Set` when available -- Ninja CLI fallback if needed - -Fields to preserve: - -- `attacktracerstatus` -- `attacktracerreason` -- `attacktracerbasestatus` -- `attacktracerevents` -- `attacktraceruniqueips` -- `attacktracercvecritical` -- `attacktracercvetotal` -- `attacktracermode` -- `attacktracertriggered` -- `attacktracerlastscanutc` - -## Concrete code refactor plan - -## Step 1: isolate scan model - -Refactor current CLI so scan output is represented by one stable report class. - -Create: - -- `Models/ScanReport.cs` - -Move report shape ownership there. - -## Step 2: add transport layer - -Create: - -- `Transport/N8nUploadClient.cs` -- `Transport/UploadEnvelopeBuilder.cs` - -Responsibilities: - -- prepare headers -- hash payload -- sign request -- upload with timeout and retry policy - -## Step 3: add secret protection - -Create: - -- `Security/ProtectedSecretStore.cs` - -Responsibilities: - -- store secret using DPAPI -- load secret at runtime - -## Step 4: add nonce/replay support - -Create: - -- `Security/NonceStore.cs` - -Local nonce history is optional, but useful for diagnostics. - -## Step 5: split commands - -Refactor `Program.cs` into command-oriented classes: - -- `ScanCommand.cs` -- `UploadCommand.cs` -- `ScanAndUploadCommand.cs` -- `VersionCommand.cs` - -## Step 6: simplify wrappers - -Replace older server/share assumptions in wrappers. - -`run-attacktracer-ninja.ps1` should become: - -- invoke binary -- capture output -- write Ninja device fields -- exit with monitoring-friendly code - -## Step 7: replace build pipeline - -Create: - -- `build/build-client-package.ps1` -- `build/build-release-manifest.ps1` - -Deprecate: - -- heavy installer-first flow if ZIP distribution is enough - -## Step 8: deprecate server package - -Mark these as legacy and remove from active release build: - -- `AttackTracerNinjaServer*` -- org HTML writer logic -- share mirroring flow - -## Implementation order - -## Phase A: repo cleanup and design freeze - -1. Mark server/share docs as legacy -2. Add new config and payload docs -3. Freeze V2 JSON schema - -## Phase B: client refactor - -1. Add command split -2. Add signed upload path -3. Add DPAPI secret handling -4. Keep existing scan logic intact - -## Phase C: packaging - -1. Build ZIP package -2. Build version manifest -3. Add install/update scripts - -## Phase D: central integration - -1. Build n8n ingest workflow -2. Build Postgres schema -3. Build NinjaOne org-field update workflow - -## Phase E: rollout - -1. Pilot group -2. Validate cadence and missing-report detection -3. Roll out widely -4. Retire server/share path - -## Final recommendation - -Implement V2 as: - -- compiled endpoint collector -- PowerShell operational wrapper -- GitLab-hosted signed releases -- NinjaOne-based deployment and update -- n8n-based central ingestion and org aggregation - -This gives a cleaner codebase and a safer operational model than extending the old share/server architecture further. diff --git a/docs/ninjaone-monitoring-playbook.md b/docs/ninjaone-monitoring-playbook.md deleted file mode 100644 index 41b8b69..0000000 --- a/docs/ninjaone-monitoring-playbook.md +++ /dev/null @@ -1,136 +0,0 @@ -# NinjaOne Monitoring Playbook - -## Goal - -Use `AttackTracerNinja` in NinjaOne with simple, predictable monitor behaviors and endpoint-visible custom-field data. - -## Recommended scripts - -Primary data collection: - -- [scripts/run-attacktracer-ninja.ps1](C:\Users\Besitzer\Documents\AttackTracerNinjaVersion\scripts\run-attacktracer-ninja.ps1) - -Monitor-oriented wrapper: - -- [scripts/run-attacktracer-ninja-monitor.ps1](C:\Users\Besitzer\Documents\AttackTracerNinjaVersion\scripts\run-attacktracer-ninja-monitor.ps1) - -## Recommended monitor layout - -## Recommended custom fields - -Create these device custom fields in NinjaOne and allow script write access: - -- `attacktracerstatus` -- `attacktracerreason` -- `attacktracerbasestatus` -- `attacktracerevents` -- `attacktraceruniqueips` -- `attacktracercvecritical` -- `attacktracercvetotal` -- `attacktracermode` -- `attacktracertriggered` -- `attacktracerlastscanutc` - -Suggested field types: - -- text: `attacktracerstatus`, `attacktracerreason`, `attacktracerbasestatus`, `attacktracermode`, `attacktracerlastscanutc` -- number/integer: `attacktracerevents`, `attacktraceruniqueips`, `attacktracercvecritical`, `attacktracercvetotal` -- checkbox or text: `attacktracertriggered` - -### 1. Attack status monitor - -Purpose: -- trigger when the final correlated status is not `ok` - -Suggested command: - -```powershell -& "C:\Program Files\AttackTracerNinja\scripts\run-attacktracer-ninja-monitor.ps1" -Mode status -LookbackDays 7 -``` - -Meaning: -- alerts on attack-only findings -- alerts on attack-plus-CVE escalation - -### 2. Attack-only monitor - -Purpose: -- trigger only on attack activity, ignoring pure CVE context - -Suggested command: - -```powershell -& "C:\Program Files\AttackTracerNinja\scripts\run-attacktracer-ninja-monitor.ps1" -Mode attack-only -LookbackDays 7 -``` - -Meaning: -- good for brute-force / failed-login monitoring -- independent of vulnerability imports - -### 3. Critical CVE monitor - -Purpose: -- trigger when the imported NinjaOne vulnerability export shows critical/high CVEs for this device - -Suggested command: - -```powershell -& "C:\Program Files\AttackTracerNinja\scripts\run-attacktracer-ninja-monitor.ps1" -Mode cve-critical -LookbackDays 7 -VulnerabilityCsvPath "C:\Program Files\AttackTracerNinja\samples\ninja-vulnerability-export.example.csv" -``` - -Meaning: -- CVE-focused monitor -- no attack activity required - -### 4. Attack plus CVE correlation monitor - -Purpose: -- trigger only when this endpoint has attack activity and critical/high CVEs at the same time - -Suggested command: - -```powershell -& "C:\Program Files\AttackTracerNinja\scripts\run-attacktracer-ninja-monitor.ps1" -Mode attack-plus-cve -LookbackDays 7 -VulnerabilityCsvPath "C:\Program Files\AttackTracerNinja\samples\ninja-vulnerability-export.example.csv" -``` - -Meaning: -- highest-signal monitor -- ideal for priority triage - -## Suggested alert interpretation - -- `status` - - use for general security operations visibility -- `attack-only` - - use for incident-style login abuse detection -- `cve-critical` - - use for vulnerability backlog / patch pressure -- `attack-plus-cve` - - use for urgent escalation - -## Useful output fields from the main runner - -The main runner emits: - -- `ATTACKTRACER_STATUS` -- `ATTACKTRACER_REASON` -- `ATTACKTRACER_BASE_STATUS` -- `ATTACKTRACER_EVENTS` -- `ATTACKTRACER_UNIQUE_IPS` -- `ATTACKTRACER_ERRORS` -- `ATTACKTRACER_CVE_TOTAL` -- `ATTACKTRACER_CVE_CRITICAL` -- `ATTACKTRACER_CVE_HIGH_CVSS` - -These are useful if you prefer a condition-based script monitor rather than exit-code-only behavior. - -The installed monitor wrapper also attempts to populate NinjaOne custom fields automatically using `Ninja-Property-Set` first and `C:\ProgramData\NinjaRMMAgent\ninjarmm-cli.exe` as a fallback. - -## Practical recommendation - -If you want the smallest workable setup, start with: - -1. a scheduled script that runs `status` and updates the custom fields -2. a condition or device health check on `attacktracertriggered = true` - -That gives you one broad monitor, keeps the latest values visible on the device, and avoids relying only on transient script output. diff --git a/docs/ninjaone-org-report-playbook.md b/docs/ninjaone-org-report-playbook.md deleted file mode 100644 index 7f9d8a1..0000000 --- a/docs/ninjaone-org-report-playbook.md +++ /dev/null @@ -1,107 +0,0 @@ -# NinjaOne Organization Report Playbook - -> Legacy: This playbook documents the older shared-folder organization reporting model. For the recommended replacement, use the V2 n8n-based design in `attacktracer-ninja-v2-architecture.md`. - -## Goal - -Build a central AttackTracer overview as a local HTML report generated from mirrored device reports. - -## Recommended design - -### Device layer - -Each endpoint continues to write these device custom fields: - -- `attacktracerstatus` -- `attacktracerreason` -- `attacktracerbasestatus` -- `attacktracerevents` -- `attacktraceruniqueips` -- `attacktracercvecritical` -- `attacktracercvetotal` -- `attacktracermode` -- `attacktracertriggered` -- `attacktracerlastscanutc` - -Each endpoint can also mirror its JSON report to a shared folder: - -```powershell -& "C:\Program Files\AttackTracerNinja\scripts\run-attacktracer-ninja-monitor.ps1" -Mode status -MirrorRoot "\\fileserver\AttackTracer\OrgA" -``` - -That will create one JSON file per machine, such as: - -- `\\fileserver\AttackTracer\OrgA\WSUS.json` -- `\\fileserver\AttackTracer\OrgA\MILSRV222.json` - -## Central report host - -Use one always-on Windows server as the central report host: - -- stable network path access to the mirrored report share -- enough permissions to read every mirrored JSON file -- optional browser/file access for opening the rendered HTML report - -This host only builds the local HTML report. It does not write organization-level custom fields back into NinjaOne. - -If your NinjaOne environment exposes `Set-NinjaOrganizationProperty` or `Ninja-Organization-Property-Set`, the same script can also update these text-based organization fields: - -- `attacktracerorgstatus` -- `attacktracerorgsummary` -- `attacktracerorglastupdate` - -## Org report generator - -Use the organization generator script on the delegate machine: - -- [scripts/build-attacktracer-org-report.ps1](C:\Users\Besitzer\Documents\AttackTracerNinjaVersion\scripts\build-attacktracer-org-report.ps1) - -Installed path after setup: - -- `C:\Program Files\AttackTracerNinja\scripts\build-attacktracer-org-report.ps1` - -### Generate HTML only - -```powershell -& "C:\Program Files\AttackTracerNinja\scripts\build-attacktracer-org-report.ps1" ` - -ReportsRoot "\\fileserver\AttackTracer\OrgA" ` - -OutputPath "..\reports\attacktracer-org-report.html" -``` - -### Recommended production command - -```powershell -& "C:\Program Files\AttackTracerNinja\scripts\build-attacktracer-org-report.ps1" ` - -ReportsRoot "\\fileserver\AttackTracer\OrgA" ` - -OutputPath "\\fileserver\AttackTracer\OrgA\attacktracer-org-report.html" -``` - -### Optional: also update organization summary fields - -```powershell -& "C:\Program Files\AttackTracerNinja\scripts\build-attacktracer-org-report.ps1" ` - -ReportsRoot "\\fileserver\AttackTracer\OrgA" ` - -OutputPath "\\fileserver\AttackTracer\OrgA\attacktracer-org-report.html" ` - -WriteNinjaOrgSummary -``` - -## What the HTML contains - -The generated HTML report includes: - -- scanned device count -- alerting device count -- critical device count -- total event count -- unique source IP count -- an alert table with the most relevant rows -- a compact `Log sauber` device list - -## Operational recommendation - -Schedule it in two stages: - -1. run the endpoint monitor script on all managed servers and mirror JSON output to the organization share -2. run the organization report generator on the central report host a few minutes later - -This keeps the device logic simple and gives you one stable HTML organization summary to open locally or from the share. diff --git a/docs/ocsentinel-architecture.md b/docs/ocsentinel-architecture.md new file mode 100644 index 0000000..40a1c56 --- /dev/null +++ b/docs/ocsentinel-architecture.md @@ -0,0 +1,28 @@ +# OfficeCom Sentinel Architecture + +## Goal + +OfficeCom Sentinel is a Windows endpoint client that: + +- runs on every monitored device +- reads local attack telemetry and optional local vulnerability exports +- writes NinjaOne device custom fields locally +- uploads signed JSON reports to n8n +- receives updates through NinjaOne tasks from Gitea-hosted releases + +## Current Model + +The repository now keeps only the client-side architecture: + +- endpoint scan and correlation +- local NinjaOne field publishing +- optional n8n upload +- packaged ZIP release flow for NinjaOne deployment + +## Removed Model + +The following older pieces are intentionally no longer part of the repo: + +- share-based organization aggregation +- dedicated server-side collector package +- server-side NinjaOne organization field updater diff --git a/docs/attacktracer-ninja-v2-deployment.md b/docs/ocsentinel-deployment.md similarity index 55% rename from docs/attacktracer-ninja-v2-deployment.md rename to docs/ocsentinel-deployment.md index 13991a0..283e7d2 100644 --- a/docs/attacktracer-ninja-v2-deployment.md +++ b/docs/ocsentinel-deployment.md @@ -4,7 +4,7 @@ Deploy and update the endpoint client through NinjaOne while hosting release artifacts in Gitea. -## Release assets +## Release Assets Each Gitea release should publish: @@ -20,13 +20,7 @@ powershell -ExecutionPolicy Bypass -File .\build\build-release-manifest.ps1 ` -ArtifactUrl "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v2.0.0/OCSentinelClient-win-x64.zip" ``` -See example manifest: - -- [update-channel.example.json](C:/Users/Besitzer/Documents/AttackTracerNinjaVersion/config/update-channel.example.json) - -## Endpoint package contents - -The installed package should include: +## Installed Layout - `app\OCSentinelCli.exe` - `scripts\run-ocsentinel.ps1` @@ -36,9 +30,9 @@ The installed package should include: - `config\ocsentinel-settings.json` - `config\ocsentinel-client.json` -## Initial install through NinjaOne +## NinjaOne Tasks -Recommended NinjaOne task: +Initial install/update: ```powershell & "C:\Program Files\OCSentinel\scripts\update-ocsentinel.ps1" ` @@ -46,44 +40,14 @@ Recommended NinjaOne task: -Force ``` -If the client is not installed yet, you can also first distribute a bootstrap ZIP or setup package, then switch to the updater-only model. - -## Routine update task - -Recommended scheduled task command: +Routine update: ```powershell & "C:\Program Files\OCSentinel\scripts\update-ocsentinel.ps1" ` -ManifestUrl "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v2.0.0/version.json" ``` -## Behavior - -The updater: - -1. downloads the manifest -2. compares installed and available version -3. downloads the ZIP only when newer -4. validates SHA-256 -5. validates Authenticode signature when present -6. runs the package installer - -## Secret bootstrap - -After installation, provision the upload secret once: - -```powershell -& "C:\Program Files\OCSentinel\scripts\protect-ocsentinel-secret.ps1" ` - -SecretValue "" -``` - -This writes a DPAPI-protected file under: - -- `C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat` - -## Runtime task - -Recommended runtime task: +Runtime: ```powershell & "C:\Program Files\OCSentinel\scripts\run-ocsentinel-monitor.ps1" ` @@ -91,13 +55,13 @@ Recommended runtime task: -OutputPath "..\reports\ocsentinel-summary.json" ``` -Upload is enabled automatically when: +## Secret Bootstrap -- `config\ocsentinel-client.json` exists -- the protected secret file exists +```powershell +& "C:\Program Files\OCSentinel\scripts\protect-ocsentinel-secret.ps1" ` + -SecretValue "" +``` -Otherwise the client falls back to local scan behavior. +This writes: -## Migration guidance - -During migration you can keep old share-based logic disabled by default and only enable the new n8n upload path as secrets and webhook config become available. +- `C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat` diff --git a/docs/ocsentinel-n8n-contract.md b/docs/ocsentinel-n8n-contract.md new file mode 100644 index 0000000..5888514 --- /dev/null +++ b/docs/ocsentinel-n8n-contract.md @@ -0,0 +1,27 @@ +# OfficeCom Sentinel n8n Contract + +## Request + +- method: `POST` +- content type: `application/json` +- example webhook: `https://n8n.example.com/webhook/ocsentinel-ingest` + +## Required Payload Intent + +The endpoint should send one signed JSON document per scan run containing: + +- device identity +- client version +- scan metadata +- attack findings +- CVE correlation summary +- raw status fields needed for downstream NinjaOne updates + +## Expected Downstream Work + +n8n is responsible for: + +- ingest validation +- storage in the central backend +- organization-wide aggregation +- NinjaOne organization API updates diff --git a/installer/AttackTracerNinjaBootstrapper/AttackTracerNinjaBootstrapper.csproj b/installer/AttackTracerNinjaBootstrapper/AttackTracerNinjaBootstrapper.csproj deleted file mode 100644 index fdb3593..0000000 --- a/installer/AttackTracerNinjaBootstrapper/AttackTracerNinjaBootstrapper.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Exe - net10.0-windows - enable - enable - false - 1.2.2 - - - - - - - diff --git a/installer/AttackTracerNinjaBootstrapper/Program.cs b/installer/AttackTracerNinjaBootstrapper/Program.cs deleted file mode 100644 index f53c0a8..0000000 --- a/installer/AttackTracerNinjaBootstrapper/Program.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Diagnostics; -using System.IO.Compression; -using System.Reflection; - -namespace AttackTracerNinjaBootstrapper; - -internal static class Program -{ - private static int Main() - { - string tempRoot = Path.Combine(Path.GetTempPath(), "AttackTracerNinjaSetup", Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(tempRoot); - - try - { - Assembly assembly = Assembly.GetExecutingAssembly(); - string resourceName = assembly.GetManifestResourceNames() - .First(name => name.EndsWith("payload.zip", StringComparison.OrdinalIgnoreCase)); - - string zipPath = Path.Combine(tempRoot, "payload.zip"); - using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName) - ?? throw new InvalidOperationException("Embedded payload.zip was not found.")) - using (FileStream output = File.Create(zipPath)) - { - resourceStream.CopyTo(output); - } - - string extractRoot = Path.Combine(tempRoot, "payload"); - ZipFile.ExtractToDirectory(zipPath, extractRoot); - - string installScript = Path.Combine(extractRoot, "install-attacktracer-ninja.ps1"); - if (!File.Exists(installScript)) - { - throw new FileNotFoundException("Installer script missing from payload.", installScript); - } - - var startInfo = new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-ExecutionPolicy Bypass -File \"{installScript}\"", - WorkingDirectory = extractRoot, - UseShellExecute = true - }; - - using Process process = Process.Start(startInfo) - ?? throw new InvalidOperationException("Failed to launch installer process."); - - process.WaitForExit(); - return process.ExitCode; - } - catch (Exception ex) - { - Console.Error.WriteLine($"AttackTracerNinja installer failed: {ex}"); - return 1; - } - finally - { - try - { - if (Directory.Exists(tempRoot)) - { - Directory.Delete(tempRoot, recursive: true); - } - } - catch - { - // Best-effort cleanup; a locked temp folder should not hide the installer result. - } - } - } -} diff --git a/installer/AttackTracerNinjaServerBootstrapper/AttackTracerNinjaServerBootstrapper.csproj b/installer/AttackTracerNinjaServerBootstrapper/AttackTracerNinjaServerBootstrapper.csproj deleted file mode 100644 index 23b1997..0000000 --- a/installer/AttackTracerNinjaServerBootstrapper/AttackTracerNinjaServerBootstrapper.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - Exe - net10.0-windows - enable - enable - false - AttackTracerNinjaServerBootstrapper - AttackTracerNinjaServerBootstrapper - 1.0.9 - - - - - - - diff --git a/installer/AttackTracerNinjaServerBootstrapper/Program.cs b/installer/AttackTracerNinjaServerBootstrapper/Program.cs deleted file mode 100644 index c6653d0..0000000 --- a/installer/AttackTracerNinjaServerBootstrapper/Program.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Diagnostics; -using System.IO.Compression; -using System.Reflection; - -namespace AttackTracerNinjaServerBootstrapper; - -internal static class Program -{ - private static int Main() - { - string tempRoot = Path.Combine(Path.GetTempPath(), "AttackTracerNinjaServerSetup", Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(tempRoot); - - try - { - Assembly assembly = Assembly.GetExecutingAssembly(); - string resourceName = assembly.GetManifestResourceNames() - .First(name => name.EndsWith("payload.zip", StringComparison.OrdinalIgnoreCase)); - - string zipPath = Path.Combine(tempRoot, "payload.zip"); - using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName) - ?? throw new InvalidOperationException("Embedded payload.zip was not found.")) - using (FileStream output = File.Create(zipPath)) - { - resourceStream.CopyTo(output); - } - - string extractRoot = Path.Combine(tempRoot, "payload"); - ZipFile.ExtractToDirectory(zipPath, extractRoot); - - string installScript = Path.Combine(extractRoot, "install-attacktracer-ninja-server.ps1"); - if (!File.Exists(installScript)) - { - throw new FileNotFoundException("Installer script missing from payload.", installScript); - } - - var startInfo = new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = $"-ExecutionPolicy Bypass -File \"{installScript}\"", - WorkingDirectory = extractRoot, - UseShellExecute = true - }; - - using Process process = Process.Start(startInfo) - ?? throw new InvalidOperationException("Failed to launch installer process."); - - process.WaitForExit(); - return process.ExitCode; - } - catch (Exception ex) - { - Console.Error.WriteLine($"AttackTracerNinjaServer installer failed: {ex}"); - return 1; - } - finally - { - try - { - if (Directory.Exists(tempRoot)) - { - Directory.Delete(tempRoot, recursive: true); - } - } - catch - { - } - } - } -} diff --git a/installer/install-attacktracer-ninja.ps1 b/installer/install-attacktracer-ninja.ps1 deleted file mode 100644 index 7fb1785..0000000 --- a/installer/install-attacktracer-ninja.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$RemainingArgs -) - -$ErrorActionPreference = "Stop" - -$newScript = Join-Path $PSScriptRoot "install-ocsentinel.ps1" -if (-not (Test-Path $newScript)) { - throw "Replacement script not found: $newScript" -} - -Write-Host "Compatibility wrapper: install-attacktracer-ninja.ps1 -> install-ocsentinel.ps1" -& powershell.exe -ExecutionPolicy Bypass -File $newScript @RemainingArgs -exit $LASTEXITCODE diff --git a/installer/install-ocsentinel.ps1 b/installer/install-ocsentinel.ps1 index 2df014a..50ef214 100644 --- a/installer/install-ocsentinel.ps1 +++ b/installer/install-ocsentinel.ps1 @@ -35,9 +35,6 @@ if (Test-Path (Join-Path $packageRoot "scripts\protect-ocsentinel-secret.ps1")) if (Test-Path (Join-Path $packageRoot "scripts\update-ocsentinel.ps1")) { Copy-Item -Path (Join-Path $packageRoot "scripts\update-ocsentinel.ps1") -Destination $scriptRoot -Force } -if (Test-Path (Join-Path $packageRoot "scripts\build-attacktracer-org-report.ps1")) { - Copy-Item -Path (Join-Path $packageRoot "scripts\build-attacktracer-org-report.ps1") -Destination $scriptRoot -Force -} Copy-Item -Path (Join-Path $packageRoot "scripts\uninstall-ocsentinel.ps1") -Destination $scriptRoot -Force $mainConfig = Join-Path $configRoot "ocsentinel-settings.json" @@ -74,4 +71,3 @@ Write-Host "Main path: $installRoot" Write-Host "Runner: $(Join-Path $scriptRoot 'run-ocsentinel.ps1')" Write-Host "Monitor: $(Join-Path $scriptRoot 'run-ocsentinel-monitor.ps1')" Write-Host "Updater: $(Join-Path $scriptRoot 'update-ocsentinel.ps1')" -Write-Host "Org report:$(Join-Path $scriptRoot 'build-attacktracer-org-report.ps1')" diff --git a/installer/runtime-build-attacktracer-org-report.ps1 b/installer/runtime-build-attacktracer-org-report.ps1 deleted file mode 100644 index 94073d2..0000000 --- a/installer/runtime-build-attacktracer-org-report.ps1 +++ /dev/null @@ -1,307 +0,0 @@ -param( - [string]$ReportsRoot = "..\reports", - [string]$OutputPath = "..\reports\attacktracer-org-report.html", - [int]$MaxAlertRows = 25, - [switch]$WriteNinjaOrgSummary, - [switch]$EmitHtml -) - -$ErrorActionPreference = "Stop" - -function Escape-Html { - param([AllowNull()][string]$Value) - - if ($null -eq $Value) { - return "" - } - - return [System.Net.WebUtility]::HtmlEncode($Value) -} - -function Resolve-PathLike { - param( - [Parameter(Mandatory)] - [string]$PathValue, - [Parameter(Mandatory)] - [string]$BasePath - ) - - if ([string]::IsNullOrWhiteSpace($PathValue)) { - return $PathValue - } - - if ([System.IO.Path]::IsPathRooted($PathValue) -or $PathValue.StartsWith("\\")) { - return [System.IO.Path]::GetFullPath($PathValue) - } - - return [System.IO.Path]::GetFullPath((Join-Path $BasePath $PathValue)) -} - -function Get-IncidentLabel { - param([pscustomobject]$Source) - - $target = @($Source.Targets)[0] - $origin = @($Source.Sources)[0] - - if ($target -match "Windows login") { return "Win Login-Fail" } - if ($target -match "SQL Server") { return "SQL Login-Fail" } - if ($target -match "Exchange") { return "Exchange Login-Fail" } - if ($target -match "FTP") { return "FTP Login-Fail" } - if ($origin) { return [string]$origin } - if ($target) { return [string]$target } - return "Auffaelligkeit" -} - -function Get-AlertRows { - param([pscustomobject]$Report) - - $rows = @() - - foreach ($source in @($Report.TopSources)) { - $rows += [pscustomobject]@{ - MachineName = [string]$Report.MachineName - Incident = Get-IncidentLabel -Source $source - Account = if (@($source.Usernames).Count -gt 0) { [string](@($source.Usernames)[0]) } else { "-" } - Timestamp = [string]$source.LastSeenLocal - Count = [int]$source.Count - Ip = [string]$source.SourceIp - Status = [string]$Report.AlertState - } - } - - if ($rows.Count -eq 0 -and ([string]$Report.AlertState -ne "ok" -or [int]$Report.TotalEvents -gt 0 -or [int]$Report.VulnerabilityCorrelation.CriticalCount -gt 0)) { - $rows += [pscustomobject]@{ - MachineName = [string]$Report.MachineName - Incident = if ([int]$Report.VulnerabilityCorrelation.CriticalCount -gt 0) { "CVE Korrelation" } else { "Auffaelligkeit" } - Account = "-" - Timestamp = [string]$Report.GeneratedAtLocal - Count = [Math]::Max([int]$Report.TotalEvents, [int]$Report.VulnerabilityCorrelation.CriticalCount) - Ip = "-" - Status = [string]$Report.AlertState - } - } - - return $rows -} - -function Build-OrgReportHtml { - param( - [pscustomobject[]]$Reports, - [int]$MaxRows - ) - - $sortedReports = @($Reports | Sort-Object MachineName) - $alertingReports = @($sortedReports | Where-Object { $_.AlertState -ne "ok" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 }) - $criticalReports = @($sortedReports | Where-Object { $_.AlertState -eq "critical" }) - $totalEvents = (@($sortedReports | Measure-Object -Property TotalEvents -Sum).Sum) - $totalEvents = if ($null -eq $totalEvents) { 0 } else { [int]$totalEvents } - - $allIps = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) - foreach ($report in $sortedReports) { - foreach ($source in @($report.TopSources)) { - if (-not [string]::IsNullOrWhiteSpace([string]$source.SourceIp)) { - $null = $allIps.Add([string]$source.SourceIp) - } - } - } - - $alertRows = foreach ($report in $alertingReports) { - Get-AlertRows -Report $report - } - - $alertRows = @($alertRows | Sort-Object @{ Expression = { $_.Status -eq "critical" }; Descending = $true }, @{ Expression = { [DateTimeOffset]::Parse($_.Timestamp) }; Descending = $true }) - if ($alertRows.Count -gt $MaxRows) { - $alertRows = @($alertRows | Select-Object -First $MaxRows) - } - - $cleanDevices = @($sortedReports | Where-Object { $_.AlertState -eq "ok" -and $_.TotalEvents -eq 0 -and $_.VulnerabilityCorrelation.CriticalCount -eq 0 } | Select-Object -ExpandProperty MachineName -Unique) - $generatedAt = (Get-Date).ToString("dd.MM.yyyy HH:mm:ss") - - $sb = [System.Text.StringBuilder]::new() - [void]$sb.AppendLine('
') - [void]$sb.AppendLine("

AttackTracer Report - $(Escape-Html ((Get-Date).ToString("dd.MM.yyyy")))

") - [void]$sb.AppendLine("
$(Escape-Html ([string]$sortedReports.Count)) Geraete gescannt | $(Escape-Html ([string]$alertingReports.Count)) auffaellig | $(Escape-Html ([string]$criticalReports.Count)) kritisch | $(Escape-Html ([string]$totalEvents)) Events | $(Escape-Html ([string]$allIps.Count)) eindeutige IPs
") - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - - if ($alertRows.Count -eq 0) { - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - } - else { - foreach ($row in $alertRows) { - $rowStyle = if ($row.Status -eq "critical") { "background-color: #fee2e2; color: #991b1b;" } else { "background-color: #fff7ed; color: #c2410c;" } - $timestampText = $row.Timestamp - try { - $timestampText = ([DateTimeOffset]::Parse($row.Timestamp)).ToString("dd.MM.yy HH:mm") - } - catch { - } - - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(' ') - } - } - - [void]$sb.AppendLine('
ServerVorfallKontoZeitpunktAnzahlIP
Keine Angriffe oder Korrelationen im ausgewerteten Bestand gefunden.
$(Escape-Html $row.MachineName)$(Escape-Html $row.Incident)$(Escape-Html $row.Account)$(Escape-Html $timestampText)$(Escape-Html ([string]$row.Count))$(Escape-Html $row.Ip)
') - - if ($cleanDevices.Count -gt 0) { - [void]$sb.AppendLine('
') - [void]$sb.AppendLine(" Log sauber / Keine Angriffe: $(Escape-Html ($cleanDevices -join ', '))") - [void]$sb.AppendLine('
') - } - - [void]$sb.AppendLine("
Automatisch generiert am $(Escape-Html $generatedAt)
") - [void]$sb.AppendLine('
') - - return $sb.ToString() -} - -function Get-OrganizationStatus { - param([pscustomobject[]]$Reports) - - if (@($Reports | Where-Object { $_.AlertState -eq "critical" }).Count -gt 0) { - return "critical" - } - - if (@($Reports | Where-Object { $_.AlertState -eq "warning" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 }).Count -gt 0) { - return "warning" - } - - return "ok" -} - -function Build-OrganizationSummaryText { - param([pscustomobject[]]$Reports) - - $deviceCount = @($Reports).Count - $alertingCount = @($Reports | Where-Object { $_.AlertState -ne "ok" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 }).Count - $criticalCount = @($Reports | Where-Object { $_.AlertState -eq "critical" }).Count - $totalEvents = (@($Reports | Measure-Object -Property TotalEvents -Sum).Sum) - $totalEvents = if ($null -eq $totalEvents) { 0 } else { [int]$totalEvents } - - $topSystems = @( - $Reports | - Where-Object { $_.AlertState -ne "ok" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 } | - Sort-Object @{ Expression = { $_.AlertState -eq "critical" }; Descending = $true }, @{ Expression = { [int]$_.TotalEvents }; Descending = $true } | - Select-Object -ExpandProperty MachineName -First 5 - ) - - $summary = "$deviceCount Geraete gescannt, $alertingCount auffaellig, $criticalCount kritisch, $totalEvents Events." - if ($topSystems.Count -gt 0) { - $summary += " Top-Systeme: $($topSystems -join ', ')." - } - - return $summary -} - -function Set-NinjaOrganizationFieldValue { - param( - [Parameter(Mandatory)] - [string]$Name, - [AllowEmptyString()] - [string]$Value - ) - - if (Get-Command -Name "Set-NinjaOrganizationProperty" -ErrorAction SilentlyContinue) { - Set-NinjaOrganizationProperty -Name $Name -Value $Value | Out-Null - return "Set-NinjaOrganizationProperty" - } - - if (Get-Command -Name "Ninja-Organization-Property-Set" -ErrorAction SilentlyContinue) { - Ninja-Organization-Property-Set $Name $Value | Out-Null - return "Ninja-Organization-Property-Set" - } - - throw "No supported NinjaOne organization custom field writer was available." -} - -$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -$installRoot = Split-Path -Parent $scriptDir -$reportsRootPath = Resolve-PathLike -PathValue $ReportsRoot -BasePath $installRoot -$outputPathFull = Resolve-PathLike -PathValue $OutputPath -BasePath $installRoot - -$reportFiles = @() -if (Test-Path $reportsRootPath -PathType Leaf) { - $reportFiles = @($reportsRootPath) -} -elseif (Test-Path $reportsRootPath -PathType Container) { - $reportFiles = @(Get-ChildItem -Path $reportsRootPath -Recurse -Filter *.json | Select-Object -ExpandProperty FullName) -} - -if ($reportFiles.Count -eq 0) { - throw "No report JSON files found under $reportsRootPath" -} - -$reports = foreach ($file in $reportFiles) { - try { - $report = Get-Content $file -Raw | ConvertFrom-Json - if ($report.MachineName) { - $report - } - } - catch { - Write-Warning "Skipping invalid report file ${file}: $($_.Exception.Message)" - } -} - -if (@($reports).Count -eq 0) { - throw "No valid AttackTracer report files were parsed." -} - -$latestReports = @( - $reports | - Group-Object MachineName | - ForEach-Object { - $_.Group | - Sort-Object { - try { - [DateTimeOffset]::Parse([string]$_.GeneratedAtLocal) - } - catch { - [DateTimeOffset]::MinValue - } - } -Descending | - Select-Object -First 1 - } -) - -$html = Build-OrgReportHtml -Reports $latestReports -MaxRows $MaxAlertRows -$orgStatus = Get-OrganizationStatus -Reports $latestReports -$orgSummary = Build-OrganizationSummaryText -Reports $latestReports -$orgLastUpdate = (Get-Date).ToString("o") - -$outputDirectory = Split-Path -Parent $outputPathFull -if (-not [string]::IsNullOrWhiteSpace($outputDirectory)) { - if (-not (Test-Path $outputDirectory)) { - New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null - } -} - -Set-Content -Path $outputPathFull -Value $html -Encoding UTF8 -Write-Host "Organization HTML report written to $outputPathFull" - -if ($WriteNinjaOrgSummary) { - $writer = Set-NinjaOrganizationFieldValue -Name "attacktracerorgstatus" -Value $orgStatus - Set-NinjaOrganizationFieldValue -Name "attacktracerorgsummary" -Value $orgSummary | Out-Null - Set-NinjaOrganizationFieldValue -Name "attacktracerorglastupdate" -Value $orgLastUpdate | Out-Null - Write-Host "Organization summary fields updated via $writer" -} - -if ($EmitHtml) { - Write-Output $html -} diff --git a/installer/runtime-run-attacktracer-ninja-monitor.ps1 b/installer/runtime-run-attacktracer-ninja-monitor.ps1 deleted file mode 100644 index 9b01727..0000000 --- a/installer/runtime-run-attacktracer-ninja-monitor.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$RemainingArgs -) - -$ErrorActionPreference = "Stop" - -$newScript = Join-Path $PSScriptRoot "runtime-run-ocsentinel-monitor.ps1" -if (-not (Test-Path $newScript)) { - throw "Replacement script not found: $newScript" -} - -Write-Host "Compatibility wrapper: runtime-run-attacktracer-ninja-monitor.ps1 -> runtime-run-ocsentinel-monitor.ps1" -& powershell.exe -ExecutionPolicy Bypass -File $newScript @RemainingArgs -exit $LASTEXITCODE diff --git a/installer/runtime-run-attacktracer-ninja-server.ps1 b/installer/runtime-run-attacktracer-ninja-server.ps1 deleted file mode 100644 index a0a9735..0000000 --- a/installer/runtime-run-attacktracer-ninja-server.ps1 +++ /dev/null @@ -1,308 +0,0 @@ -param( - [string]$ConfigPath = "..\config\attacktracer-server-settings.json" -) - -$ErrorActionPreference = "Stop" - -function Resolve-PathLike { - param( - [Parameter(Mandatory)] - [string]$PathValue, - [Parameter(Mandatory)] - [string]$BasePath - ) - - if ([string]::IsNullOrWhiteSpace($PathValue)) { - return $PathValue - } - - if ([System.IO.Path]::IsPathRooted($PathValue) -or $PathValue.StartsWith("\\")) { - return [System.IO.Path]::GetFullPath($PathValue) - } - - return [System.IO.Path]::GetFullPath((Join-Path $BasePath $PathValue)) -} - -function Join-Url { - param( - [Parameter(Mandatory)][string]$BaseUrl, - [Parameter(Mandatory)][string]$RelativePath - ) - - $base = $BaseUrl.TrimEnd('/') - $relative = $RelativePath.TrimStart('/') - return "$base/$relative" -} - -function ConvertTo-PlainText { - param([Parameter(Mandatory)][string]$EncryptedValue) - - $secure = ConvertTo-SecureString $EncryptedValue - $credential = New-Object System.Management.Automation.PSCredential("ignored", $secure) - return $credential.GetNetworkCredential().Password -} - -function Normalize-NinjaScope { - param([AllowEmptyString()][string]$Scope) - - if ([string]::IsNullOrWhiteSpace($Scope)) { - return "" - } - - $tokens = @( - $Scope -split '[,\s;]+' | - Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | - ForEach-Object { - switch ($_.Trim().ToLowerInvariant()) { - "monitoring" { "monitoring"; break } - "uberwachen" { "monitoring"; break } - "ueberwachen" { "monitoring"; break } - "management" { "management"; break } - "verwalten" { "management"; break } - "control" { "control"; break } - "steuerung" { "control"; break } - "offline_access" { "offline_access"; break } - default { $_.Trim().ToLowerInvariant() } - } - } - ) - - return ($tokens | Select-Object -Unique) -join " " -} - -function Get-OrganizationStatus { - param([pscustomobject[]]$Reports) - - if (@($Reports | Where-Object { $_.AlertState -eq "critical" }).Count -gt 0) { - return "critical" - } - - if (@($Reports | Where-Object { $_.AlertState -eq "warning" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 }).Count -gt 0) { - return "warning" - } - - return "ok" -} - -function Build-OrganizationSummaryText { - param([pscustomobject[]]$Reports) - - $deviceCount = @($Reports).Count - $alertingCount = @($Reports | Where-Object { $_.AlertState -ne "ok" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 }).Count - $criticalCount = @($Reports | Where-Object { $_.AlertState -eq "critical" }).Count - $totalEvents = (@($Reports | Measure-Object -Property TotalEvents -Sum).Sum) - $totalEvents = if ($null -eq $totalEvents) { 0 } else { [int]$totalEvents } - - $topSystems = @( - $Reports | - Where-Object { $_.AlertState -ne "ok" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 } | - Sort-Object @{ Expression = { $_.AlertState -eq "critical" }; Descending = $true }, @{ Expression = { [int]$_.TotalEvents }; Descending = $true } | - Select-Object -ExpandProperty MachineName -First 5 - ) - - $summary = "$deviceCount Geraete gescannt, $alertingCount auffaellig, $criticalCount kritisch, $totalEvents Events." - if ($topSystems.Count -gt 0) { - $summary += " Top-Systeme: $($topSystems -join ', ')." - } - - return $summary -} - -function Get-LatestReports { - param([Parameter(Mandatory)][string]$ReportsRootPath) - - $reportFiles = @() - if (Test-Path $ReportsRootPath -PathType Leaf) { - $reportFiles = @($ReportsRootPath) - } - elseif (Test-Path $ReportsRootPath -PathType Container) { - $reportFiles = @(Get-ChildItem -Path $ReportsRootPath -Recurse -Filter *.json | Select-Object -ExpandProperty FullName) - } - - if ($reportFiles.Count -eq 0) { - throw "No report JSON files found under $ReportsRootPath" - } - - $reports = foreach ($file in $reportFiles) { - try { - $report = Get-Content $file -Raw | ConvertFrom-Json - if ($report.MachineName) { - $report - } - } - catch { - Write-Warning "Skipping invalid report file ${file}: $($_.Exception.Message)" - } - } - - if (@($reports).Count -eq 0) { - throw "No valid AttackTracer report files were parsed." - } - - return @( - $reports | - Group-Object MachineName | - ForEach-Object { - $_.Group | - Sort-Object { - try { - [DateTimeOffset]::Parse([string]$_.GeneratedAtLocal) - } - catch { - [DateTimeOffset]::MinValue - } - } -Descending | - Select-Object -First 1 - } - ) -} - -function Get-NinjaAccessToken { - param( - [Parameter(Mandatory)][string]$BaseUrl, - [Parameter(Mandatory)][string]$ClientId, - [Parameter(Mandatory)][string]$ClientSecret, - [string]$Scope = "" - ) - - $body = @{ - grant_type = "client_credentials" - client_id = $ClientId - client_secret = $ClientSecret - } - - $normalizedScope = Normalize-NinjaScope -Scope $Scope - if (-not [string]::IsNullOrWhiteSpace($normalizedScope)) { - $body.scope = $normalizedScope - } - - $tokenEndpoints = @( - (Join-Url -BaseUrl $BaseUrl -RelativePath "ws/oauth/token"), - (Join-Url -BaseUrl $BaseUrl -RelativePath "oauth/token") - ) - - $failures = New-Object System.Collections.Generic.List[string] - - foreach ($tokenEndpoint in $tokenEndpoints) { - try { - Write-Host "Requesting NinjaOne OAuth token from $tokenEndpoint" - $response = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Body $body -ContentType "application/x-www-form-urlencoded" -TimeoutSec 60 - if (-not $response.access_token) { - throw "OAuth token response did not contain an access_token." - } - - Write-Host "NinjaOne OAuth token acquired successfully" - return [string]$response.access_token - } - catch { - $message = "Token endpoint $tokenEndpoint failed: " + $_.Exception.Message - if ($_.ErrorDetails.Message) { - $message += " | " + $_.ErrorDetails.Message - } - - $failures.Add($message) - } - } - - throw "Failed to obtain NinjaOne OAuth token. " + ($failures -join " || ") -} - -function Invoke-NinjaOrganizationFieldPatch { - param( - [Parameter(Mandatory)][string]$BaseUrl, - [Parameter(Mandatory)][int]$OrganizationId, - [Parameter(Mandatory)][string]$AccessToken, - [Parameter(Mandatory)][hashtable]$FieldValues - ) - - $endpoint = (Join-Url -BaseUrl $BaseUrl -RelativePath "v2/organization/$OrganizationId/custom-fields") - $headers = @{ - Authorization = "Bearer $AccessToken" - Accept = "application/json" - } - - $payloadCandidates = @( - $FieldValues, - @{ customFields = $FieldValues }, - @{ fields = @($FieldValues.GetEnumerator() | ForEach-Object { @{ name = $_.Key; value = $_.Value } }) } - ) - - $failures = New-Object System.Collections.Generic.List[string] - - foreach ($payload in $payloadCandidates) { - try { - $json = $payload | ConvertTo-Json -Depth 8 - $fieldNames = @($FieldValues.Keys) -join ", " - Write-Host "Organization custom fields endpoint: $endpoint" - Write-Host "Updating NinjaOne organization custom fields: $fieldNames" - Write-Host "PATCH payload size: $($json.Length) characters" - Invoke-RestMethod -Method Patch -Uri $endpoint -Headers $headers -ContentType "application/json" -Body $json -TimeoutSec 60 | Out-Null - Write-Host "NinjaOne organization custom fields updated successfully" - return - } - catch { - $message = $_.Exception.Message - if ($_.ErrorDetails.Message) { - $message += " | " + $_.ErrorDetails.Message - } - - $failures.Add($message) - } - } - - throw "Failed to update NinjaOne organization custom fields. " + ($failures -join " || ") -} - -$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -$installRoot = Split-Path -Parent $scriptDir -$configBasePath = $scriptDir -$configFullPath = Resolve-PathLike -PathValue $ConfigPath -BasePath $configBasePath -if (-not (Test-Path $configFullPath)) { - throw "Server config file not found: $configFullPath" -} - -$config = Get-Content $configFullPath -Raw | ConvertFrom-Json -$reportsRootPath = Resolve-PathLike -PathValue $config.reportsRoot -BasePath $installRoot -$htmlOutputPath = Resolve-PathLike -PathValue $config.htmlOutputPath -BasePath $installRoot -$orgReportsScript = Join-Path $scriptDir "build-attacktracer-org-report.ps1" - -& powershell -ExecutionPolicy Bypass -File $orgReportsScript -ReportsRoot $reportsRootPath -OutputPath $htmlOutputPath -MaxAlertRows $config.maxAlertRows -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} - -$latestReports = Get-LatestReports -ReportsRootPath $reportsRootPath -$orgStatus = Get-OrganizationStatus -Reports $latestReports -$orgSummary = Build-OrganizationSummaryText -Reports $latestReports -$orgLastUpdate = (Get-Date).ToString("o") -$htmlContent = Get-Content $htmlOutputPath -Raw -Write-Host "Organization summary prepared" -Write-Host "HTML report size: $($htmlContent.Length) characters" -$clientSecret = ConvertTo-PlainText -EncryptedValue ([string]$config.clientSecretEncrypted) -$accessToken = Get-NinjaAccessToken -BaseUrl ([string]$config.ninjaBaseUrl) -ClientId ([string]$config.clientId) -ClientSecret $clientSecret -Scope ([string]$config.oauthScope) - -$fieldValues = @{ - ([string]$config.statusFieldName) = $orgStatus - ([string]$config.summaryFieldName) = $orgSummary - ([string]$config.lastUpdateFieldName) = $orgLastUpdate -} - -Invoke-NinjaOrganizationFieldPatch -BaseUrl ([string]$config.ninjaBaseUrl) -OrganizationId ([int]$config.organizationId) -AccessToken $accessToken -FieldValues $fieldValues - -if ($config.updateHtmlField -and -not [string]::IsNullOrWhiteSpace([string]$config.htmlFieldName)) { - $htmlFieldName = [string]$config.htmlFieldName - Write-Host "Attempting separate HTML organization field update for '$htmlFieldName'" - - try { - Invoke-NinjaOrganizationFieldPatch -BaseUrl ([string]$config.ninjaBaseUrl) -OrganizationId ([int]$config.organizationId) -AccessToken $accessToken -FieldValues @{ - $htmlFieldName = $htmlContent - } - } - catch { - Write-Warning "HTML organization field update failed for '$htmlFieldName'. Keeping local HTML report only. $($_.Exception.Message)" - } -} - -Write-Host "Organization HTML report written to $htmlOutputPath" -Write-Host "Organization custom fields updated via NinjaOne API" -Write-Host "Status field: $($config.statusFieldName)=$orgStatus" diff --git a/installer/runtime-run-attacktracer-ninja.ps1 b/installer/runtime-run-attacktracer-ninja.ps1 deleted file mode 100644 index 7889dfa..0000000 --- a/installer/runtime-run-attacktracer-ninja.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$RemainingArgs -) - -$ErrorActionPreference = "Stop" - -$newScript = Join-Path $PSScriptRoot "runtime-run-ocsentinel.ps1" -if (-not (Test-Path $newScript)) { - throw "Replacement script not found: $newScript" -} - -Write-Host "Compatibility wrapper: runtime-run-attacktracer-ninja.ps1 -> runtime-run-ocsentinel.ps1" -& powershell.exe -ExecutionPolicy Bypass -File $newScript @RemainingArgs -exit $LASTEXITCODE diff --git a/installer/server-install-attacktracer-ninja-server.ps1 b/installer/server-install-attacktracer-ninja-server.ps1 deleted file mode 100644 index 259264d..0000000 --- a/installer/server-install-attacktracer-ninja-server.ps1 +++ /dev/null @@ -1,113 +0,0 @@ -param() - -$ErrorActionPreference = "Stop" - -function Read-DefaultValue { - param( - [Parameter(Mandatory)][string]$Prompt, - [string]$DefaultValue = "" - ) - - $suffix = if ([string]::IsNullOrWhiteSpace($DefaultValue)) { "" } else { " [$DefaultValue]" } - $value = Read-Host "$Prompt$suffix" - if ([string]::IsNullOrWhiteSpace($value)) { - return $DefaultValue - } - - return $value -} - -function Read-YesNo { - param( - [Parameter(Mandatory)][string]$Prompt, - [bool]$DefaultValue = $false - ) - - $defaultText = if ($DefaultValue) { "Y/n" } else { "y/N" } - $value = Read-Host "$Prompt [$defaultText]" - if ([string]::IsNullOrWhiteSpace($value)) { - return $DefaultValue - } - - return $value.Trim().StartsWith("y", [System.StringComparison]::OrdinalIgnoreCase) -} - -$packageRoot = Split-Path -Parent $MyInvocation.MyCommand.Path -$installRoot = Join-Path ${env:ProgramFiles} "AttackTracerNinjaServer" -$configRoot = Join-Path $installRoot "config" -$scriptRoot = Join-Path $installRoot "scripts" -$versionFile = Join-Path $packageRoot "VERSION.txt" -$version = if (Test-Path $versionFile) { (Get-Content $versionFile -Raw).Trim() } else { "1.0.0" } - -Write-Host "Installing AttackTracerNinjaServer $version to $installRoot" - -New-Item -ItemType Directory -Force -Path $configRoot, $scriptRoot | Out-Null - -Copy-Item -Path (Join-Path $packageRoot "build-attacktracer-org-report.ps1") -Destination $scriptRoot -Force -Copy-Item -Path (Join-Path $packageRoot "run-attacktracer-ninja-server.ps1") -Destination $scriptRoot -Force -Copy-Item -Path (Join-Path $packageRoot "uninstall-attacktracer-ninja-server.ps1") -Destination $scriptRoot -Force -Copy-Item -Path (Join-Path $packageRoot "attacktracer-server-settings.example.json") -Destination (Join-Path $configRoot "attacktracer-server-settings.example.json") -Force - -$configPath = Join-Path $configRoot "attacktracer-server-settings.json" -$examplePath = Join-Path $configRoot "attacktracer-server-settings.example.json" -$existing = if (Test-Path $configPath) { Get-Content $configPath -Raw | ConvertFrom-Json } else { Get-Content $examplePath -Raw | ConvertFrom-Json } - -$reportsRoot = Read-DefaultValue -Prompt "ReportsRoot share path" -DefaultValue ([string]$existing.reportsRoot) -$htmlOutputPath = Read-DefaultValue -Prompt "HTML output path" -DefaultValue ([string]$existing.htmlOutputPath) -$ninjaBaseUrl = Read-DefaultValue -Prompt "Ninja base URL" -DefaultValue ([string]$existing.ninjaBaseUrl) -$organizationId = Read-DefaultValue -Prompt "Ninja organization ID" -DefaultValue ([string]$existing.organizationId) -$clientId = Read-DefaultValue -Prompt "Ninja OAuth Client ID" -DefaultValue ([string]$existing.clientId) -$oauthScope = Read-DefaultValue -Prompt "Ninja OAuth scope" -DefaultValue ([string]$existing.oauthScope) -$secretPrompt = Read-Host "Ninja OAuth Client Secret (leave empty to keep existing)" -AsSecureString - -$clientSecretEncrypted = [string]$existing.clientSecretEncrypted -if ($secretPrompt.Length -gt 0) { - $clientSecretEncrypted = ConvertFrom-SecureString $secretPrompt -} - -$updateHtmlField = Read-YesNo -Prompt "Also update attacktracerorgreport via API" -DefaultValue ([bool]$existing.updateHtmlField) -$htmlFieldName = Read-DefaultValue -Prompt "HTML field name" -DefaultValue ([string]$existing.htmlFieldName) -$statusFieldName = Read-DefaultValue -Prompt "Status field name" -DefaultValue ([string]$existing.statusFieldName) -$summaryFieldName = Read-DefaultValue -Prompt "Summary field name" -DefaultValue ([string]$existing.summaryFieldName) -$lastUpdateFieldName = Read-DefaultValue -Prompt "Last update field name" -DefaultValue ([string]$existing.lastUpdateFieldName) -$maxAlertRows = [int](Read-DefaultValue -Prompt "Max alert rows in HTML" -DefaultValue ([string]$existing.maxAlertRows)) - -$config = [ordered]@{ - ninjaBaseUrl = $ninjaBaseUrl - organizationId = [int]$organizationId - clientId = $clientId - clientSecretEncrypted = $clientSecretEncrypted - oauthScope = $oauthScope - reportsRoot = $reportsRoot - htmlOutputPath = $htmlOutputPath - statusFieldName = $statusFieldName - summaryFieldName = $summaryFieldName - lastUpdateFieldName = $lastUpdateFieldName - htmlFieldName = $htmlFieldName - updateHtmlField = $updateHtmlField - maxAlertRows = $maxAlertRows -} - -$config | ConvertTo-Json -Depth 6 | Set-Content -Path $configPath -Encoding UTF8 - -$uninstallScript = Join-Path $scriptRoot "uninstall-attacktracer-ninja-server.ps1" -$uninstallCommand = "powershell.exe -ExecutionPolicy Bypass -File `"$uninstallScript`"" -$uninstallKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\AttackTracerNinjaServer" - -if (-not (Test-Path $uninstallKey)) { - New-Item -Path $uninstallKey -Force | Out-Null -} - -Set-ItemProperty -Path $uninstallKey -Name "DisplayName" -Value "AttackTracerNinjaServer" -Set-ItemProperty -Path $uninstallKey -Name "DisplayVersion" -Value $version -Set-ItemProperty -Path $uninstallKey -Name "Publisher" -Value "AttackTracerNinja" -Set-ItemProperty -Path $uninstallKey -Name "InstallLocation" -Value $installRoot -Set-ItemProperty -Path $uninstallKey -Name "UninstallString" -Value $uninstallCommand -Set-ItemProperty -Path $uninstallKey -Name "QuietUninstallString" -Value $uninstallCommand -Set-ItemProperty -Path $uninstallKey -Name "NoModify" -Value 1 -Type DWord -Set-ItemProperty -Path $uninstallKey -Name "NoRepair" -Value 1 -Type DWord - -Write-Host "Installation complete." -Write-Host "Main path: $installRoot" -Write-Host "Server config:$configPath" -Write-Host "Server runner:$(Join-Path $scriptRoot 'run-attacktracer-ninja-server.ps1')" diff --git a/installer/server-uninstall-attacktracer-ninja-server.ps1 b/installer/server-uninstall-attacktracer-ninja-server.ps1 deleted file mode 100644 index b7ab22b..0000000 --- a/installer/server-uninstall-attacktracer-ninja-server.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -param() - -$ErrorActionPreference = "Stop" - -$installRoot = Join-Path ${env:ProgramFiles} "AttackTracerNinjaServer" -$uninstallKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\AttackTracerNinjaServer" - -if (Test-Path $uninstallKey) { - Remove-Item -Path $uninstallKey -Force -Recurse -} - -if (Test-Path $installRoot) { - Remove-Item -LiteralPath $installRoot -Force -Recurse -} - -Write-Host "AttackTracerNinjaServer removed from $installRoot" diff --git a/installer/uninstall-attacktracer-ninja.ps1 b/installer/uninstall-attacktracer-ninja.ps1 deleted file mode 100644 index 97b34c5..0000000 --- a/installer/uninstall-attacktracer-ninja.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$RemainingArgs -) - -$ErrorActionPreference = "Stop" - -$newScript = Join-Path $PSScriptRoot "uninstall-ocsentinel.ps1" -if (-not (Test-Path $newScript)) { - throw "Replacement script not found: $newScript" -} - -Write-Host "Compatibility wrapper: uninstall-attacktracer-ninja.ps1 -> uninstall-ocsentinel.ps1" -& powershell.exe -ExecutionPolicy Bypass -File $newScript @RemainingArgs -exit $LASTEXITCODE diff --git a/installer/update-attacktracer-ninja.ps1 b/installer/update-attacktracer-ninja.ps1 deleted file mode 100644 index 4c3fd89..0000000 --- a/installer/update-attacktracer-ninja.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$RemainingArgs -) - -$ErrorActionPreference = "Stop" - -$newScript = Join-Path $PSScriptRoot "update-ocsentinel.ps1" -if (-not (Test-Path $newScript)) { - throw "Replacement script not found: $newScript" -} - -Write-Host "Compatibility wrapper: update-attacktracer-ninja.ps1 -> update-ocsentinel.ps1" -& powershell.exe -ExecutionPolicy Bypass -File $newScript @RemainingArgs -exit $LASTEXITCODE diff --git a/scripts/build-attacktracer-installer.ps1 b/scripts/build-attacktracer-installer.ps1 deleted file mode 100644 index 0ea0610..0000000 --- a/scripts/build-attacktracer-installer.ps1 +++ /dev/null @@ -1,99 +0,0 @@ -param( - [string]$Configuration = "Release" -) - -$ErrorActionPreference = "Stop" - -$repoRoot = Split-Path -Parent $PSScriptRoot -$projectPath = Join-Path $repoRoot "src\AttackTracerNinjaCli\AttackTracerNinjaCli.csproj" -$installerRoot = Join-Path $repoRoot "installer" -$artifactsRoot = Join-Path $repoRoot "artifacts" -$publishRoot = Join-Path $artifactsRoot "publish\win-x64" -$packageRoot = Join-Path $artifactsRoot "installer-payload" -$bootstrapperProject = Join-Path $repoRoot "installer\AttackTracerNinjaBootstrapper\AttackTracerNinjaBootstrapper.csproj" -$bootstrapperPayload = Join-Path $repoRoot "installer\AttackTracerNinjaBootstrapper\payload.zip" -$bootstrapperPublish = Join-Path $artifactsRoot "bootstrapper\win-x64" -$outputExe = Join-Path $artifactsRoot "AttackTracerNinjaSetup.exe" - -function Get-ShortPath([string]$path) { - $resolved = [System.IO.Path]::GetFullPath($path) - $cmdPath = $resolved.Replace('"', '""') - $shortPath = cmd /c "for %I in (""$cmdPath"") do @echo %~sI" - return ($shortPath | Select-Object -Last 1).Trim() -} - -[xml]$projectXml = Get-Content $projectPath -$version = $projectXml.Project.PropertyGroup.Version | Select-Object -First 1 -if ([string]::IsNullOrWhiteSpace($version)) { - $version = "1.2.2" -} - -Write-Host "Publishing AttackTracerNinja version $version" - -if (Test-Path $publishRoot) { Remove-Item -LiteralPath $publishRoot -Recurse -Force } -if (Test-Path $packageRoot) { Remove-Item -LiteralPath $packageRoot -Recurse -Force } -if (Test-Path $bootstrapperPublish) { Remove-Item -LiteralPath $bootstrapperPublish -Recurse -Force } -if (Test-Path $bootstrapperPayload) { Remove-Item -LiteralPath $bootstrapperPayload -Force } -if (Test-Path $outputExe) { Remove-Item -LiteralPath $outputExe -Force } -New-Item -ItemType Directory -Force -Path $publishRoot, $packageRoot, $bootstrapperPublish | Out-Null - -& dotnet restore $projectPath -r win-x64 -if ($LASTEXITCODE -ne 0) { - throw "dotnet restore failed" -} - -& dotnet publish $projectPath ` - -c $Configuration ` - -r win-x64 ` - --self-contained true ` - -p:PublishSingleFile=true ` - -p:IncludeNativeLibrariesForSelfExtract=true ` - -o $publishRoot - -if ($LASTEXITCODE -ne 0) { - throw "dotnet publish failed" -} - -Copy-Item -Path (Join-Path $publishRoot "AttackTracerNinjaCli.exe") -Destination (Join-Path $packageRoot "AttackTracerNinjaCli.exe") -Force -if (Test-Path (Join-Path $publishRoot "AttackTracerNinjaCli.pdb")) { - Copy-Item -Path (Join-Path $publishRoot "AttackTracerNinjaCli.pdb") -Destination (Join-Path $packageRoot "AttackTracerNinjaCli.pdb") -Force -} -Copy-Item -Path (Join-Path $repoRoot "config\attacktracer-settings.example.json") -Destination (Join-Path $packageRoot "attacktracer-settings.example.json") -Force -Copy-Item -Path (Join-Path $repoRoot "config\attacktracer-client.example.json") -Destination (Join-Path $packageRoot "attacktracer-client.example.json") -Force -Copy-Item -Path (Join-Path $repoRoot "config\update-channel.example.json") -Destination (Join-Path $packageRoot "update-channel.example.json") -Force -Copy-Item -Path (Join-Path $repoRoot "samples\ninja-vulnerability-export.example.csv") -Destination (Join-Path $packageRoot "ninja-vulnerability-export.example.csv") -Force -Copy-Item -Path (Join-Path $installerRoot "install-attacktracer-ninja.ps1") -Destination (Join-Path $packageRoot "install-attacktracer-ninja.ps1") -Force -Copy-Item -Path (Join-Path $installerRoot "launch-install.cmd") -Destination (Join-Path $packageRoot "launch-install.cmd") -Force -Copy-Item -Path (Join-Path $installerRoot "uninstall-attacktracer-ninja.ps1") -Destination (Join-Path $packageRoot "uninstall-attacktracer-ninja.ps1") -Force -Copy-Item -Path (Join-Path $installerRoot "runtime-run-attacktracer-ninja.ps1") -Destination (Join-Path $packageRoot "run-attacktracer-ninja.ps1") -Force -Copy-Item -Path (Join-Path $installerRoot "runtime-run-attacktracer-ninja-monitor.ps1") -Destination (Join-Path $packageRoot "run-attacktracer-ninja-monitor.ps1") -Force -Copy-Item -Path (Join-Path $repoRoot "scripts\protect-attacktracer-secret.ps1") -Destination (Join-Path $packageRoot "protect-attacktracer-secret.ps1") -Force -Copy-Item -Path (Join-Path $installerRoot "update-attacktracer-ninja.ps1") -Destination (Join-Path $packageRoot "update-attacktracer-ninja.ps1") -Force -Copy-Item -Path (Join-Path $installerRoot "runtime-build-attacktracer-org-report.ps1") -Destination (Join-Path $packageRoot "build-attacktracer-org-report.ps1") -Force -Copy-Item -Path (Join-Path $installerRoot "README.txt") -Destination (Join-Path $packageRoot "README.txt") -Force -Set-Content -Path (Join-Path $packageRoot "VERSION.txt") -Value $version -NoNewline - -Write-Host "Creating embedded payload zip" -Compress-Archive -Path (Join-Path $packageRoot "*") -DestinationPath $bootstrapperPayload -CompressionLevel Optimal -Force - -Write-Host "Publishing bootstrapper installer" -& dotnet restore $bootstrapperProject -r win-x64 -if ($LASTEXITCODE -ne 0) { - throw "Bootstrapper restore failed" -} - -& dotnet publish $bootstrapperProject ` - -c $Configuration ` - -r win-x64 ` - --self-contained true ` - -p:PublishSingleFile=true ` - -p:IncludeNativeLibrariesForSelfExtract=true ` - -o $bootstrapperPublish - -if ($LASTEXITCODE -ne 0) { - throw "Bootstrapper publish failed" -} - -Copy-Item -Path (Join-Path $bootstrapperPublish "AttackTracerNinjaBootstrapper.exe") -Destination $outputExe -Force - -Write-Host "Installer created at $outputExe" diff --git a/scripts/build-attacktracer-org-report.ps1 b/scripts/build-attacktracer-org-report.ps1 deleted file mode 100644 index 0590cba..0000000 --- a/scripts/build-attacktracer-org-report.ps1 +++ /dev/null @@ -1,306 +0,0 @@ -param( - [string]$ReportsRoot = ".\reports", - [string]$OutputPath = ".\reports\attacktracer-org-report.html", - [int]$MaxAlertRows = 25, - [switch]$WriteNinjaOrgSummary, - [switch]$EmitHtml -) - -$ErrorActionPreference = "Stop" - -function Escape-Html { - param([AllowNull()][string]$Value) - - if ($null -eq $Value) { - return "" - } - - return [System.Net.WebUtility]::HtmlEncode($Value) -} - -function Resolve-PathLike { - param( - [Parameter(Mandatory)] - [string]$PathValue, - [Parameter(Mandatory)] - [string]$BasePath - ) - - if ([string]::IsNullOrWhiteSpace($PathValue)) { - return $PathValue - } - - if ([System.IO.Path]::IsPathRooted($PathValue) -or $PathValue.StartsWith("\\")) { - return [System.IO.Path]::GetFullPath($PathValue) - } - - return [System.IO.Path]::GetFullPath((Join-Path $BasePath $PathValue)) -} - -function Get-IncidentLabel { - param([pscustomobject]$Source) - - $target = @($Source.Targets)[0] - $origin = @($Source.Sources)[0] - - if ($target -match "Windows login") { return "Win Login-Fail" } - if ($target -match "SQL Server") { return "SQL Login-Fail" } - if ($target -match "Exchange") { return "Exchange Login-Fail" } - if ($target -match "FTP") { return "FTP Login-Fail" } - if ($origin) { return [string]$origin } - if ($target) { return [string]$target } - return "Auffaelligkeit" -} - -function Get-AlertRows { - param([pscustomobject]$Report) - - $rows = @() - - foreach ($source in @($Report.TopSources)) { - $rows += [pscustomobject]@{ - MachineName = [string]$Report.MachineName - Incident = Get-IncidentLabel -Source $source - Account = if (@($source.Usernames).Count -gt 0) { [string](@($source.Usernames)[0]) } else { "-" } - Timestamp = [string]$source.LastSeenLocal - Count = [int]$source.Count - Ip = [string]$source.SourceIp - Status = [string]$Report.AlertState - } - } - - if ($rows.Count -eq 0 -and ([string]$Report.AlertState -ne "ok" -or [int]$Report.TotalEvents -gt 0 -or [int]$Report.VulnerabilityCorrelation.CriticalCount -gt 0)) { - $rows += [pscustomobject]@{ - MachineName = [string]$Report.MachineName - Incident = if ([int]$Report.VulnerabilityCorrelation.CriticalCount -gt 0) { "CVE Korrelation" } else { "Auffaelligkeit" } - Account = "-" - Timestamp = [string]$Report.GeneratedAtLocal - Count = [Math]::Max([int]$Report.TotalEvents, [int]$Report.VulnerabilityCorrelation.CriticalCount) - Ip = "-" - Status = [string]$Report.AlertState - } - } - - return $rows -} - -function Build-OrgReportHtml { - param( - [pscustomobject[]]$Reports, - [int]$MaxRows - ) - - $sortedReports = @($Reports | Sort-Object MachineName) - $alertingReports = @($sortedReports | Where-Object { $_.AlertState -ne "ok" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 }) - $criticalReports = @($sortedReports | Where-Object { $_.AlertState -eq "critical" }) - $totalEvents = (@($sortedReports | Measure-Object -Property TotalEvents -Sum).Sum) - $totalEvents = if ($null -eq $totalEvents) { 0 } else { [int]$totalEvents } - - $allIps = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) - foreach ($report in $sortedReports) { - foreach ($source in @($report.TopSources)) { - if (-not [string]::IsNullOrWhiteSpace([string]$source.SourceIp)) { - $null = $allIps.Add([string]$source.SourceIp) - } - } - } - - $alertRows = foreach ($report in $alertingReports) { - Get-AlertRows -Report $report - } - - $alertRows = @($alertRows | Sort-Object @{ Expression = { $_.Status -eq "critical" }; Descending = $true }, @{ Expression = { [DateTimeOffset]::Parse($_.Timestamp) }; Descending = $true }) - if ($alertRows.Count -gt $MaxRows) { - $alertRows = @($alertRows | Select-Object -First $MaxRows) - } - - $cleanDevices = @($sortedReports | Where-Object { $_.AlertState -eq "ok" -and $_.TotalEvents -eq 0 -and $_.VulnerabilityCorrelation.CriticalCount -eq 0 } | Select-Object -ExpandProperty MachineName -Unique) - $generatedAt = (Get-Date).ToString("dd.MM.yyyy HH:mm:ss") - - $sb = [System.Text.StringBuilder]::new() - [void]$sb.AppendLine('
') - [void]$sb.AppendLine("

AttackTracer Report - $(Escape-Html ((Get-Date).ToString("dd.MM.yyyy")))

") - [void]$sb.AppendLine("
$(Escape-Html ([string]$sortedReports.Count)) Geraete gescannt | $(Escape-Html ([string]$alertingReports.Count)) auffaellig | $(Escape-Html ([string]$criticalReports.Count)) kritisch | $(Escape-Html ([string]$totalEvents)) Events | $(Escape-Html ([string]$allIps.Count)) eindeutige IPs
") - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - - if ($alertRows.Count -eq 0) { - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - [void]$sb.AppendLine(' ') - } - else { - foreach ($row in $alertRows) { - $rowStyle = if ($row.Status -eq "critical") { "background-color: #fee2e2; color: #991b1b;" } else { "background-color: #fff7ed; color: #c2410c;" } - $timestampText = $row.Timestamp - try { - $timestampText = ([DateTimeOffset]::Parse($row.Timestamp)).ToString("dd.MM.yy HH:mm") - } - catch { - } - - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(" ") - [void]$sb.AppendLine(' ') - } - } - - [void]$sb.AppendLine('
ServerVorfallKontoZeitpunktAnzahlIP
Keine Angriffe oder Korrelationen im ausgewerteten Bestand gefunden.
$(Escape-Html $row.MachineName)$(Escape-Html $row.Incident)$(Escape-Html $row.Account)$(Escape-Html $timestampText)$(Escape-Html ([string]$row.Count))$(Escape-Html $row.Ip)
') - - if ($cleanDevices.Count -gt 0) { - [void]$sb.AppendLine('
') - [void]$sb.AppendLine(" Log sauber / Keine Angriffe: $(Escape-Html ($cleanDevices -join ', '))") - [void]$sb.AppendLine('
') - } - - [void]$sb.AppendLine("
Automatisch generiert am $(Escape-Html $generatedAt)
") - [void]$sb.AppendLine('
') - - return $sb.ToString() -} - -function Get-OrganizationStatus { - param([pscustomobject[]]$Reports) - - if (@($Reports | Where-Object { $_.AlertState -eq "critical" }).Count -gt 0) { - return "critical" - } - - if (@($Reports | Where-Object { $_.AlertState -eq "warning" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 }).Count -gt 0) { - return "warning" - } - - return "ok" -} - -function Build-OrganizationSummaryText { - param([pscustomobject[]]$Reports) - - $deviceCount = @($Reports).Count - $alertingCount = @($Reports | Where-Object { $_.AlertState -ne "ok" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 }).Count - $criticalCount = @($Reports | Where-Object { $_.AlertState -eq "critical" }).Count - $totalEvents = (@($Reports | Measure-Object -Property TotalEvents -Sum).Sum) - $totalEvents = if ($null -eq $totalEvents) { 0 } else { [int]$totalEvents } - - $topSystems = @( - $Reports | - Where-Object { $_.AlertState -ne "ok" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 } | - Sort-Object @{ Expression = { $_.AlertState -eq "critical" }; Descending = $true }, @{ Expression = { [int]$_.TotalEvents }; Descending = $true } | - Select-Object -ExpandProperty MachineName -First 5 - ) - - $summary = "$deviceCount Geraete gescannt, $alertingCount auffaellig, $criticalCount kritisch, $totalEvents Events." - if ($topSystems.Count -gt 0) { - $summary += " Top-Systeme: $($topSystems -join ', ')." - } - - return $summary -} - -function Set-NinjaOrganizationFieldValue { - param( - [Parameter(Mandatory)] - [string]$Name, - [AllowEmptyString()] - [string]$Value - ) - - if (Get-Command -Name "Set-NinjaOrganizationProperty" -ErrorAction SilentlyContinue) { - Set-NinjaOrganizationProperty -Name $Name -Value $Value | Out-Null - return "Set-NinjaOrganizationProperty" - } - - if (Get-Command -Name "Ninja-Organization-Property-Set" -ErrorAction SilentlyContinue) { - Ninja-Organization-Property-Set $Name $Value | Out-Null - return "Ninja-Organization-Property-Set" - } - - throw "No supported NinjaOne organization custom field writer was available." -} - -$repoRoot = Split-Path -Parent $PSScriptRoot -$reportsRootPath = Resolve-PathLike -PathValue $ReportsRoot -BasePath $repoRoot -$outputPathFull = Resolve-PathLike -PathValue $OutputPath -BasePath $repoRoot - -$reportFiles = @() -if (Test-Path $reportsRootPath -PathType Leaf) { - $reportFiles = @($reportsRootPath) -} -elseif (Test-Path $reportsRootPath -PathType Container) { - $reportFiles = @(Get-ChildItem -Path $reportsRootPath -Recurse -Filter *.json | Select-Object -ExpandProperty FullName) -} - -if ($reportFiles.Count -eq 0) { - throw "No report JSON files found under $reportsRootPath" -} - -$reports = foreach ($file in $reportFiles) { - try { - $report = Get-Content $file -Raw | ConvertFrom-Json - if ($report.MachineName) { - $report - } - } - catch { - Write-Warning "Skipping invalid report file ${file}: $($_.Exception.Message)" - } -} - -if (@($reports).Count -eq 0) { - throw "No valid AttackTracer report files were parsed." -} - -$latestReports = @( - $reports | - Group-Object MachineName | - ForEach-Object { - $_.Group | - Sort-Object { - try { - [DateTimeOffset]::Parse([string]$_.GeneratedAtLocal) - } - catch { - [DateTimeOffset]::MinValue - } - } -Descending | - Select-Object -First 1 - } -) - -$html = Build-OrgReportHtml -Reports $latestReports -MaxRows $MaxAlertRows -$orgStatus = Get-OrganizationStatus -Reports $latestReports -$orgSummary = Build-OrganizationSummaryText -Reports $latestReports -$orgLastUpdate = (Get-Date).ToString("o") - -$outputDirectory = Split-Path -Parent $outputPathFull -if (-not [string]::IsNullOrWhiteSpace($outputDirectory)) { - if (-not (Test-Path $outputDirectory)) { - New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null - } -} - -Set-Content -Path $outputPathFull -Value $html -Encoding UTF8 -Write-Host "Organization HTML report written to $outputPathFull" - -if ($WriteNinjaOrgSummary) { - $writer = Set-NinjaOrganizationFieldValue -Name "attacktracerorgstatus" -Value $orgStatus - Set-NinjaOrganizationFieldValue -Name "attacktracerorgsummary" -Value $orgSummary | Out-Null - Set-NinjaOrganizationFieldValue -Name "attacktracerorglastupdate" -Value $orgLastUpdate | Out-Null - Write-Host "Organization summary fields updated via $writer" -} - -if ($EmitHtml) { - Write-Output $html -} diff --git a/scripts/build-attacktracer-server-installer.ps1 b/scripts/build-attacktracer-server-installer.ps1 deleted file mode 100644 index 1457318..0000000 --- a/scripts/build-attacktracer-server-installer.ps1 +++ /dev/null @@ -1,55 +0,0 @@ -param( - [string]$Configuration = "Release" -) - -$ErrorActionPreference = "Stop" - -$repoRoot = Split-Path -Parent $PSScriptRoot -$installerRoot = Join-Path $repoRoot "installer" -$artifactsRoot = Join-Path $repoRoot "artifacts" -$packageRoot = Join-Path $artifactsRoot "server-installer-payload" -$bootstrapperProject = Join-Path $repoRoot "installer\AttackTracerNinjaServerBootstrapper\AttackTracerNinjaServerBootstrapper.csproj" -$bootstrapperPayload = Join-Path $repoRoot "installer\AttackTracerNinjaServerBootstrapper\payload.zip" -$bootstrapperPublish = Join-Path $artifactsRoot "server-bootstrapper\win-x64" -$outputExe = Join-Path $artifactsRoot "AttackTracerNinjaServerSetup.exe" -$version = "1.0.9" - -Write-Host "Publishing AttackTracerNinjaServer version $version" - -if (Test-Path $packageRoot) { Remove-Item -LiteralPath $packageRoot -Recurse -Force } -if (Test-Path $bootstrapperPublish) { Remove-Item -LiteralPath $bootstrapperPublish -Recurse -Force } -if (Test-Path $bootstrapperPayload) { Remove-Item -LiteralPath $bootstrapperPayload -Force } -if (Test-Path $outputExe) { Remove-Item -LiteralPath $outputExe -Force } -New-Item -ItemType Directory -Force -Path $packageRoot, $bootstrapperPublish | Out-Null - -Copy-Item -Path (Join-Path $repoRoot "config\attacktracer-server-settings.example.json") -Destination (Join-Path $packageRoot "attacktracer-server-settings.example.json") -Force -Copy-Item -Path (Join-Path $installerRoot "server-install-attacktracer-ninja-server.ps1") -Destination (Join-Path $packageRoot "install-attacktracer-ninja-server.ps1") -Force -Copy-Item -Path (Join-Path $installerRoot "server-uninstall-attacktracer-ninja-server.ps1") -Destination (Join-Path $packageRoot "uninstall-attacktracer-ninja-server.ps1") -Force -Copy-Item -Path (Join-Path $installerRoot "runtime-build-attacktracer-org-report.ps1") -Destination (Join-Path $packageRoot "build-attacktracer-org-report.ps1") -Force -Copy-Item -Path (Join-Path $installerRoot "runtime-run-attacktracer-ninja-server.ps1") -Destination (Join-Path $packageRoot "run-attacktracer-ninja-server.ps1") -Force -Set-Content -Path (Join-Path $packageRoot "VERSION.txt") -Value $version -NoNewline - -Write-Host "Creating embedded payload zip" -Compress-Archive -Path (Join-Path $packageRoot "*") -DestinationPath $bootstrapperPayload -CompressionLevel Optimal -Force - -Write-Host "Publishing server bootstrapper installer" -& dotnet restore $bootstrapperProject -r win-x64 -if ($LASTEXITCODE -ne 0) { - throw "Bootstrapper restore failed" -} - -& dotnet publish $bootstrapperProject ` - -c $Configuration ` - -r win-x64 ` - --self-contained true ` - -p:PublishSingleFile=true ` - -p:IncludeNativeLibrariesForSelfExtract=true ` - -o $bootstrapperPublish - -if ($LASTEXITCODE -ne 0) { - throw "Bootstrapper publish failed" -} - -Copy-Item -Path (Join-Path $bootstrapperPublish "AttackTracerNinjaServerBootstrapper.exe") -Destination $outputExe -Force - -Write-Host "Server installer created at $outputExe" diff --git a/scripts/extract-attacktracer.ps1 b/scripts/extract-attacktracer.ps1 deleted file mode 100644 index 45b422c..0000000 --- a/scripts/extract-attacktracer.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -$ErrorActionPreference = "Stop" - -$repoRoot = Split-Path -Parent $PSScriptRoot -$setupExe = Join-Path $repoRoot "SetupAttackTracer.exe" -$payloadDir = Join-Path $repoRoot "payload" -$msiAdminDir = Join-Path $repoRoot "msi-admin" -$decompiledDir = Join-Path $repoRoot "decompiled\AttackTracer" -$ilspy = Join-Path $env:USERPROFILE ".dotnet\tools\ilspycmd.exe" - -if (-not (Test-Path $setupExe)) { - throw "SetupAttackTracer.exe not found at $setupExe" -} - -if (-not (Test-Path $ilspy)) { - throw "ilspycmd.exe not found at $ilspy" -} - -New-Item -ItemType Directory -Force -Path $payloadDir | Out-Null -New-Item -ItemType Directory -Force -Path $msiAdminDir | Out-Null -New-Item -ItemType Directory -Force -Path $decompiledDir | Out-Null - -$tempBefore = @(Get-ChildItem $env:TEMP -Directory | Select-Object -ExpandProperty FullName) -$proc = Start-Process -FilePath $setupExe -PassThru -Start-Sleep -Seconds 4 - -$tempAfter = @(Get-ChildItem $env:TEMP -Directory | Select-Object -ExpandProperty FullName) -$newTempDirs = Compare-Object $tempBefore $tempAfter | - Where-Object SideIndicator -eq "=>" | - Select-Object -ExpandProperty InputObject - -try { - if (-not $newTempDirs) { - throw "No new temp directory detected while launching SetupAttackTracer.exe" - } - - $payloadSource = $null - foreach ($dir in $newTempDirs) { - if (Test-Path (Join-Path $dir "AttackTracer.msi")) { - $payloadSource = $dir - break - } - } - - if (-not $payloadSource) { - throw "Could not locate AttackTracer.msi in the installer temp directories" - } - - Copy-Item -Path (Join-Path $payloadSource "*") -Destination $payloadDir -Recurse -Force - - $msiPath = Join-Path $payloadDir "AttackTracer.msi" - if (-not (Test-Path $msiPath)) { - throw "AttackTracer.msi was not copied into $payloadDir" - } - - & msiexec /a $msiPath /qn TARGETDIR=$msiAdminDir - - $appExe = Join-Path $msiAdminDir "program files\Servolutions\BotFence\AttackTracer.exe" - if (-not (Test-Path $appExe)) { - throw "Deployed application executable not found at $appExe" - } - - & $ilspy -p -o $decompiledDir $appExe - - Write-Host "Payload extracted to: $payloadDir" - Write-Host "MSI admin image: $msiAdminDir" - Write-Host "Decompiled sources: $decompiledDir" -} -finally { - if ($proc -and -not $proc.HasExited) { - Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue - } -} diff --git a/scripts/protect-attacktracer-secret.ps1 b/scripts/protect-attacktracer-secret.ps1 deleted file mode 100644 index 5a1756d..0000000 --- a/scripts/protect-attacktracer-secret.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$RemainingArgs -) - -$ErrorActionPreference = "Stop" - -$newScript = Join-Path $PSScriptRoot "protect-ocsentinel-secret.ps1" -if (-not (Test-Path $newScript)) { - throw "Replacement script not found: $newScript" -} - -Write-Host "Compatibility wrapper: protect-attacktracer-secret.ps1 -> protect-ocsentinel-secret.ps1" -& powershell.exe -ExecutionPolicy Bypass -File $newScript @RemainingArgs -exit $LASTEXITCODE diff --git a/scripts/run-attacktracer-ninja-monitor.ps1 b/scripts/run-attacktracer-ninja-monitor.ps1 deleted file mode 100644 index ef468c7..0000000 --- a/scripts/run-attacktracer-ninja-monitor.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$RemainingArgs -) - -$ErrorActionPreference = "Stop" - -$newScript = Join-Path $PSScriptRoot "run-ocsentinel-monitor.ps1" -if (-not (Test-Path $newScript)) { - throw "Replacement script not found: $newScript" -} - -Write-Host "Compatibility wrapper: run-attacktracer-ninja-monitor.ps1 -> run-ocsentinel-monitor.ps1" -& powershell.exe -ExecutionPolicy Bypass -File $newScript @RemainingArgs -exit $LASTEXITCODE diff --git a/scripts/run-attacktracer-ninja-server.ps1 b/scripts/run-attacktracer-ninja-server.ps1 deleted file mode 100644 index 80f04d2..0000000 --- a/scripts/run-attacktracer-ninja-server.ps1 +++ /dev/null @@ -1,307 +0,0 @@ -param( - [string]$ConfigPath = ".\config\attacktracer-server-settings.json" -) - -$ErrorActionPreference = "Stop" - -function Resolve-PathLike { - param( - [Parameter(Mandatory)] - [string]$PathValue, - [Parameter(Mandatory)] - [string]$BasePath - ) - - if ([string]::IsNullOrWhiteSpace($PathValue)) { - return $PathValue - } - - if ([System.IO.Path]::IsPathRooted($PathValue) -or $PathValue.StartsWith("\\")) { - return [System.IO.Path]::GetFullPath($PathValue) - } - - return [System.IO.Path]::GetFullPath((Join-Path $BasePath $PathValue)) -} - -function Join-Url { - param( - [Parameter(Mandatory)][string]$BaseUrl, - [Parameter(Mandatory)][string]$RelativePath - ) - - $base = $BaseUrl.TrimEnd('/') - $relative = $RelativePath.TrimStart('/') - return "$base/$relative" -} - -function ConvertTo-PlainText { - param([Parameter(Mandatory)][string]$EncryptedValue) - - $secure = ConvertTo-SecureString $EncryptedValue - $credential = New-Object System.Management.Automation.PSCredential("ignored", $secure) - return $credential.GetNetworkCredential().Password -} - -function Normalize-NinjaScope { - param([AllowEmptyString()][string]$Scope) - - if ([string]::IsNullOrWhiteSpace($Scope)) { - return "" - } - - $tokens = @( - $Scope -split '[,\s;]+' | - Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | - ForEach-Object { - switch ($_.Trim().ToLowerInvariant()) { - "monitoring" { "monitoring"; break } - "uberwachen" { "monitoring"; break } - "ueberwachen" { "monitoring"; break } - "management" { "management"; break } - "verwalten" { "management"; break } - "control" { "control"; break } - "steuerung" { "control"; break } - "offline_access" { "offline_access"; break } - default { $_.Trim().ToLowerInvariant() } - } - } - ) - - return ($tokens | Select-Object -Unique) -join " " -} - -function Get-OrganizationStatus { - param([pscustomobject[]]$Reports) - - if (@($Reports | Where-Object { $_.AlertState -eq "critical" }).Count -gt 0) { - return "critical" - } - - if (@($Reports | Where-Object { $_.AlertState -eq "warning" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 }).Count -gt 0) { - return "warning" - } - - return "ok" -} - -function Build-OrganizationSummaryText { - param([pscustomobject[]]$Reports) - - $deviceCount = @($Reports).Count - $alertingCount = @($Reports | Where-Object { $_.AlertState -ne "ok" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 }).Count - $criticalCount = @($Reports | Where-Object { $_.AlertState -eq "critical" }).Count - $totalEvents = (@($Reports | Measure-Object -Property TotalEvents -Sum).Sum) - $totalEvents = if ($null -eq $totalEvents) { 0 } else { [int]$totalEvents } - - $topSystems = @( - $Reports | - Where-Object { $_.AlertState -ne "ok" -or $_.TotalEvents -gt 0 -or $_.VulnerabilityCorrelation.CriticalCount -gt 0 } | - Sort-Object @{ Expression = { $_.AlertState -eq "critical" }; Descending = $true }, @{ Expression = { [int]$_.TotalEvents }; Descending = $true } | - Select-Object -ExpandProperty MachineName -First 5 - ) - - $summary = "$deviceCount Geraete gescannt, $alertingCount auffaellig, $criticalCount kritisch, $totalEvents Events." - if ($topSystems.Count -gt 0) { - $summary += " Top-Systeme: $($topSystems -join ', ')." - } - - return $summary -} - -function Get-LatestReports { - param([Parameter(Mandatory)][string]$ReportsRootPath) - - $reportFiles = @() - if (Test-Path $ReportsRootPath -PathType Leaf) { - $reportFiles = @($ReportsRootPath) - } - elseif (Test-Path $ReportsRootPath -PathType Container) { - $reportFiles = @(Get-ChildItem -Path $ReportsRootPath -Recurse -Filter *.json | Select-Object -ExpandProperty FullName) - } - - if ($reportFiles.Count -eq 0) { - throw "No report JSON files found under $ReportsRootPath" - } - - $reports = foreach ($file in $reportFiles) { - try { - $report = Get-Content $file -Raw | ConvertFrom-Json - if ($report.MachineName) { - $report - } - } - catch { - Write-Warning "Skipping invalid report file ${file}: $($_.Exception.Message)" - } - } - - if (@($reports).Count -eq 0) { - throw "No valid AttackTracer report files were parsed." - } - - return @( - $reports | - Group-Object MachineName | - ForEach-Object { - $_.Group | - Sort-Object { - try { - [DateTimeOffset]::Parse([string]$_.GeneratedAtLocal) - } - catch { - [DateTimeOffset]::MinValue - } - } -Descending | - Select-Object -First 1 - } - ) -} - -function Get-NinjaAccessToken { - param( - [Parameter(Mandatory)][string]$BaseUrl, - [Parameter(Mandatory)][string]$ClientId, - [Parameter(Mandatory)][string]$ClientSecret, - [string]$Scope = "" - ) - - $body = @{ - grant_type = "client_credentials" - client_id = $ClientId - client_secret = $ClientSecret - } - - $normalizedScope = Normalize-NinjaScope -Scope $Scope - if (-not [string]::IsNullOrWhiteSpace($normalizedScope)) { - $body.scope = $normalizedScope - } - - $tokenEndpoints = @( - (Join-Url -BaseUrl $BaseUrl -RelativePath "ws/oauth/token"), - (Join-Url -BaseUrl $BaseUrl -RelativePath "oauth/token") - ) - - $failures = New-Object System.Collections.Generic.List[string] - - foreach ($tokenEndpoint in $tokenEndpoints) { - try { - Write-Host "Requesting NinjaOne OAuth token from $tokenEndpoint" - $response = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Body $body -ContentType "application/x-www-form-urlencoded" -TimeoutSec 60 - if (-not $response.access_token) { - throw "OAuth token response did not contain an access_token." - } - - Write-Host "NinjaOne OAuth token acquired successfully" - return [string]$response.access_token - } - catch { - $message = "Token endpoint $tokenEndpoint failed: " + $_.Exception.Message - if ($_.ErrorDetails.Message) { - $message += " | " + $_.ErrorDetails.Message - } - - $failures.Add($message) - } - } - - throw "Failed to obtain NinjaOne OAuth token. " + ($failures -join " || ") -} - -function Invoke-NinjaOrganizationFieldPatch { - param( - [Parameter(Mandatory)][string]$BaseUrl, - [Parameter(Mandatory)][int]$OrganizationId, - [Parameter(Mandatory)][string]$AccessToken, - [Parameter(Mandatory)][hashtable]$FieldValues - ) - - $endpoint = (Join-Url -BaseUrl $BaseUrl -RelativePath "v2/organization/$OrganizationId/custom-fields") - $headers = @{ - Authorization = "Bearer $AccessToken" - Accept = "application/json" - } - - $payloadCandidates = @( - $FieldValues, - @{ customFields = $FieldValues }, - @{ fields = @($FieldValues.GetEnumerator() | ForEach-Object { @{ name = $_.Key; value = $_.Value } }) } - ) - - $failures = New-Object System.Collections.Generic.List[string] - - foreach ($payload in $payloadCandidates) { - try { - $json = $payload | ConvertTo-Json -Depth 8 - $fieldNames = @($FieldValues.Keys) -join ", " - Write-Host "Organization custom fields endpoint: $endpoint" - Write-Host "Updating NinjaOne organization custom fields: $fieldNames" - Write-Host "PATCH payload size: $($json.Length) characters" - Invoke-RestMethod -Method Patch -Uri $endpoint -Headers $headers -ContentType "application/json" -Body $json -TimeoutSec 60 | Out-Null - Write-Host "NinjaOne organization custom fields updated successfully" - return - } - catch { - $message = $_.Exception.Message - if ($_.ErrorDetails.Message) { - $message += " | " + $_.ErrorDetails.Message - } - - $failures.Add($message) - } - } - - throw "Failed to update NinjaOne organization custom fields. " + ($failures -join " || ") -} - -$repoRoot = Split-Path -Parent $PSScriptRoot -$configBasePath = $PSScriptRoot -$configFullPath = Resolve-PathLike -PathValue $ConfigPath -BasePath $configBasePath -if (-not (Test-Path $configFullPath)) { - throw "Server config file not found: $configFullPath" -} - -$config = Get-Content $configFullPath -Raw | ConvertFrom-Json -$reportsRootPath = Resolve-PathLike -PathValue $config.reportsRoot -BasePath $repoRoot -$htmlOutputPath = Resolve-PathLike -PathValue $config.htmlOutputPath -BasePath $repoRoot -$orgReportsScript = Join-Path $repoRoot "scripts\build-attacktracer-org-report.ps1" - -& powershell -ExecutionPolicy Bypass -File $orgReportsScript -ReportsRoot $reportsRootPath -OutputPath $htmlOutputPath -MaxAlertRows $config.maxAlertRows -if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE -} - -$latestReports = Get-LatestReports -ReportsRootPath $reportsRootPath -$orgStatus = Get-OrganizationStatus -Reports $latestReports -$orgSummary = Build-OrganizationSummaryText -Reports $latestReports -$orgLastUpdate = (Get-Date).ToString("o") -$htmlContent = Get-Content $htmlOutputPath -Raw -Write-Host "Organization summary prepared" -Write-Host "HTML report size: $($htmlContent.Length) characters" -$clientSecret = ConvertTo-PlainText -EncryptedValue ([string]$config.clientSecretEncrypted) -$accessToken = Get-NinjaAccessToken -BaseUrl ([string]$config.ninjaBaseUrl) -ClientId ([string]$config.clientId) -ClientSecret $clientSecret -Scope ([string]$config.oauthScope) - -$fieldValues = @{ - ([string]$config.statusFieldName) = $orgStatus - ([string]$config.summaryFieldName) = $orgSummary - ([string]$config.lastUpdateFieldName) = $orgLastUpdate -} - -Invoke-NinjaOrganizationFieldPatch -BaseUrl ([string]$config.ninjaBaseUrl) -OrganizationId ([int]$config.organizationId) -AccessToken $accessToken -FieldValues $fieldValues - -if ($config.updateHtmlField -and -not [string]::IsNullOrWhiteSpace([string]$config.htmlFieldName)) { - $htmlFieldName = [string]$config.htmlFieldName - Write-Host "Attempting separate HTML organization field update for '$htmlFieldName'" - - try { - Invoke-NinjaOrganizationFieldPatch -BaseUrl ([string]$config.ninjaBaseUrl) -OrganizationId ([int]$config.organizationId) -AccessToken $accessToken -FieldValues @{ - $htmlFieldName = $htmlContent - } - } - catch { - Write-Warning "HTML organization field update failed for '$htmlFieldName'. Keeping local HTML report only. $($_.Exception.Message)" - } -} - -Write-Host "Organization HTML report written to $htmlOutputPath" -Write-Host "Organization custom fields updated via NinjaOne API" -Write-Host "Status field: $($config.statusFieldName)=$orgStatus" diff --git a/scripts/run-attacktracer-ninja.ps1 b/scripts/run-attacktracer-ninja.ps1 deleted file mode 100644 index a7f04db..0000000 --- a/scripts/run-attacktracer-ninja.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$RemainingArgs -) - -$ErrorActionPreference = "Stop" - -$newScript = Join-Path $PSScriptRoot "run-ocsentinel.ps1" -if (-not (Test-Path $newScript)) { - throw "Replacement script not found: $newScript" -} - -Write-Host "Compatibility wrapper: run-attacktracer-ninja.ps1 -> run-ocsentinel.ps1" -& powershell.exe -ExecutionPolicy Bypass -File $newScript @RemainingArgs -exit $LASTEXITCODE diff --git a/scripts/run-ocsentinel.ps1 b/scripts/run-ocsentinel.ps1 index 4aa1e4f..4409cf0 100644 --- a/scripts/run-ocsentinel.ps1 +++ b/scripts/run-ocsentinel.ps1 @@ -16,9 +16,9 @@ param( $ErrorActionPreference = "Stop" $repoRoot = Split-Path -Parent $PSScriptRoot -$projectPath = Join-Path $repoRoot "src\AttackTracerNinjaCli\AttackTracerNinjaCli.csproj" +$projectPath = Join-Path $repoRoot "src\OCSentinelCli\OCSentinelCli.csproj" $outputFullPath = [System.IO.Path]::GetFullPath((Join-Path $repoRoot $OutputPath)) -$buildOutputDir = Join-Path $repoRoot "src\AttackTracerNinjaCli\bin\Debug\net10.0" +$buildOutputDir = Join-Path $repoRoot "src\OCSentinelCli\bin\Debug\net10.0" $dllPath = Join-Path $buildOutputDir "OCSentinelCli.dll" function Resolve-PathLike { diff --git a/src/AttackTracerNinjaCli/AttackScanner.cs b/src/OCSentinelCli/AttackScanner.cs similarity index 99% rename from src/AttackTracerNinjaCli/AttackScanner.cs rename to src/OCSentinelCli/AttackScanner.cs index 2f72dca..89ad08c 100644 --- a/src/AttackTracerNinjaCli/AttackScanner.cs +++ b/src/OCSentinelCli/AttackScanner.cs @@ -4,7 +4,7 @@ using System.Net; using System.Runtime.Versioning; using System.Text.RegularExpressions; -namespace AttackTracerNinjaCli; +namespace OCSentinelCli; [SupportedOSPlatform("windows")] internal sealed class AttackScanner diff --git a/src/AttackTracerNinjaCli/BuildMetadata.cs b/src/OCSentinelCli/BuildMetadata.cs similarity index 90% rename from src/AttackTracerNinjaCli/BuildMetadata.cs rename to src/OCSentinelCli/BuildMetadata.cs index 613e7f2..ad38abf 100644 --- a/src/AttackTracerNinjaCli/BuildMetadata.cs +++ b/src/OCSentinelCli/BuildMetadata.cs @@ -1,6 +1,6 @@ using System.Reflection; -namespace AttackTracerNinjaCli; +namespace OCSentinelCli; internal static class BuildMetadata { diff --git a/src/AttackTracerNinjaCli/Commands/ScanAndUploadCommand.cs b/src/OCSentinelCli/Commands/ScanAndUploadCommand.cs similarity index 96% rename from src/AttackTracerNinjaCli/Commands/ScanAndUploadCommand.cs rename to src/OCSentinelCli/Commands/ScanAndUploadCommand.cs index 9ff7d5f..ac0d5b0 100644 --- a/src/AttackTracerNinjaCli/Commands/ScanAndUploadCommand.cs +++ b/src/OCSentinelCli/Commands/ScanAndUploadCommand.cs @@ -1,4 +1,4 @@ -namespace AttackTracerNinjaCli.Commands; +namespace OCSentinelCli.Commands; internal static class ScanAndUploadCommand { diff --git a/src/AttackTracerNinjaCli/Commands/ScanCommand.cs b/src/OCSentinelCli/Commands/ScanCommand.cs similarity index 99% rename from src/AttackTracerNinjaCli/Commands/ScanCommand.cs rename to src/OCSentinelCli/Commands/ScanCommand.cs index 61afac1..2047b89 100644 --- a/src/AttackTracerNinjaCli/Commands/ScanCommand.cs +++ b/src/OCSentinelCli/Commands/ScanCommand.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Runtime.Versioning; -namespace AttackTracerNinjaCli.Commands; +namespace OCSentinelCli.Commands; [SupportedOSPlatform("windows")] internal static class ScanCommand diff --git a/src/AttackTracerNinjaCli/Commands/UploadCommand.cs b/src/OCSentinelCli/Commands/UploadCommand.cs similarity index 95% rename from src/AttackTracerNinjaCli/Commands/UploadCommand.cs rename to src/OCSentinelCli/Commands/UploadCommand.cs index 696e3ed..0408321 100644 --- a/src/AttackTracerNinjaCli/Commands/UploadCommand.cs +++ b/src/OCSentinelCli/Commands/UploadCommand.cs @@ -1,9 +1,9 @@ -using AttackTracerNinjaCli.Configuration; +using OCSentinelCli.Configuration; using System.Text.Json; -using AttackTracerNinjaCli.Security; -using AttackTracerNinjaCli.Transport; +using OCSentinelCli.Security; +using OCSentinelCli.Transport; -namespace AttackTracerNinjaCli.Commands; +namespace OCSentinelCli.Commands; internal static class UploadCommand { diff --git a/src/AttackTracerNinjaCli/Commands/VersionCommand.cs b/src/OCSentinelCli/Commands/VersionCommand.cs similarity index 78% rename from src/AttackTracerNinjaCli/Commands/VersionCommand.cs rename to src/OCSentinelCli/Commands/VersionCommand.cs index 488deb9..c8d0e58 100644 --- a/src/AttackTracerNinjaCli/Commands/VersionCommand.cs +++ b/src/OCSentinelCli/Commands/VersionCommand.cs @@ -1,4 +1,4 @@ -namespace AttackTracerNinjaCli.Commands; +namespace OCSentinelCli.Commands; internal static class VersionCommand { diff --git a/src/AttackTracerNinjaCli/Configuration.cs b/src/OCSentinelCli/Configuration.cs similarity index 96% rename from src/AttackTracerNinjaCli/Configuration.cs rename to src/OCSentinelCli/Configuration.cs index 532684f..bbaf36f 100644 --- a/src/AttackTracerNinjaCli/Configuration.cs +++ b/src/OCSentinelCli/Configuration.cs @@ -1,6 +1,6 @@ using System.Text.Json; -namespace AttackTracerNinjaCli; +namespace OCSentinelCli; internal sealed record ScannerConfiguration { diff --git a/src/AttackTracerNinjaCli/Configuration/ClientConfiguration.cs b/src/OCSentinelCli/Configuration/ClientConfiguration.cs similarity index 95% rename from src/AttackTracerNinjaCli/Configuration/ClientConfiguration.cs rename to src/OCSentinelCli/Configuration/ClientConfiguration.cs index 2796e31..5ce8700 100644 --- a/src/AttackTracerNinjaCli/Configuration/ClientConfiguration.cs +++ b/src/OCSentinelCli/Configuration/ClientConfiguration.cs @@ -1,6 +1,6 @@ using System.Text.Json; -namespace AttackTracerNinjaCli.Configuration; +namespace OCSentinelCli.Configuration; internal sealed record ClientConfiguration { diff --git a/src/AttackTracerNinjaCli/JsonOptions.cs b/src/OCSentinelCli/JsonOptions.cs similarity index 89% rename from src/AttackTracerNinjaCli/JsonOptions.cs rename to src/OCSentinelCli/JsonOptions.cs index 86c0455..393f901 100644 --- a/src/AttackTracerNinjaCli/JsonOptions.cs +++ b/src/OCSentinelCli/JsonOptions.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace AttackTracerNinjaCli; +namespace OCSentinelCli; internal static class JsonOptions { diff --git a/src/AttackTracerNinjaCli/Models.cs b/src/OCSentinelCli/Models.cs similarity index 99% rename from src/AttackTracerNinjaCli/Models.cs rename to src/OCSentinelCli/Models.cs index 024ace2..2195ff0 100644 --- a/src/AttackTracerNinjaCli/Models.cs +++ b/src/OCSentinelCli/Models.cs @@ -1,4 +1,4 @@ -namespace AttackTracerNinjaCli; +namespace OCSentinelCli; internal sealed record AttackEvent { diff --git a/src/AttackTracerNinjaCli/Models/UploadResult.cs b/src/OCSentinelCli/Models/UploadResult.cs similarity index 88% rename from src/AttackTracerNinjaCli/Models/UploadResult.cs rename to src/OCSentinelCli/Models/UploadResult.cs index 0b795fd..61fa950 100644 --- a/src/AttackTracerNinjaCli/Models/UploadResult.cs +++ b/src/OCSentinelCli/Models/UploadResult.cs @@ -1,4 +1,4 @@ -namespace AttackTracerNinjaCli.Models; +namespace OCSentinelCli.Models; internal sealed record UploadResult { diff --git a/src/AttackTracerNinjaCli/AttackTracerNinjaCli.csproj b/src/OCSentinelCli/OCSentinelCli.csproj similarity index 95% rename from src/AttackTracerNinjaCli/AttackTracerNinjaCli.csproj rename to src/OCSentinelCli/OCSentinelCli.csproj index ce4ee80..dd94463 100644 --- a/src/AttackTracerNinjaCli/AttackTracerNinjaCli.csproj +++ b/src/OCSentinelCli/OCSentinelCli.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/src/AttackTracerNinjaCli/Program.cs b/src/OCSentinelCli/Program.cs similarity index 93% rename from src/AttackTracerNinjaCli/Program.cs rename to src/OCSentinelCli/Program.cs index 2eec4d9..b7e6390 100644 --- a/src/AttackTracerNinjaCli/Program.cs +++ b/src/OCSentinelCli/Program.cs @@ -1,7 +1,7 @@ using System.Runtime.Versioning; -using AttackTracerNinjaCli.Commands; +using OCSentinelCli.Commands; -namespace AttackTracerNinjaCli; +namespace OCSentinelCli; [SupportedOSPlatform("windows")] internal static class Program diff --git a/src/AttackTracerNinjaCli/ScanOptions.cs b/src/OCSentinelCli/ScanOptions.cs similarity index 99% rename from src/AttackTracerNinjaCli/ScanOptions.cs rename to src/OCSentinelCli/ScanOptions.cs index 60e88b5..5cbc518 100644 --- a/src/AttackTracerNinjaCli/ScanOptions.cs +++ b/src/OCSentinelCli/ScanOptions.cs @@ -1,4 +1,4 @@ -namespace AttackTracerNinjaCli; +namespace OCSentinelCli; internal sealed record ScanOptions { diff --git a/src/AttackTracerNinjaCli/Security/ProtectedSecretStore.cs b/src/OCSentinelCli/Security/ProtectedSecretStore.cs similarity index 95% rename from src/AttackTracerNinjaCli/Security/ProtectedSecretStore.cs rename to src/OCSentinelCli/Security/ProtectedSecretStore.cs index be06327..b6d7677 100644 --- a/src/AttackTracerNinjaCli/Security/ProtectedSecretStore.cs +++ b/src/OCSentinelCli/Security/ProtectedSecretStore.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; using System.Text; -namespace AttackTracerNinjaCli.Security; +namespace OCSentinelCli.Security; internal static class ProtectedSecretStore { diff --git a/src/AttackTracerNinjaCli/Transport/N8nUploadClient.cs b/src/OCSentinelCli/Transport/N8nUploadClient.cs similarity index 97% rename from src/AttackTracerNinjaCli/Transport/N8nUploadClient.cs rename to src/OCSentinelCli/Transport/N8nUploadClient.cs index ad56f10..6e2b9e1 100644 --- a/src/AttackTracerNinjaCli/Transport/N8nUploadClient.cs +++ b/src/OCSentinelCli/Transport/N8nUploadClient.cs @@ -1,9 +1,9 @@ using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text; -using AttackTracerNinjaCli.Models; +using OCSentinelCli.Models; -namespace AttackTracerNinjaCli.Transport; +namespace OCSentinelCli.Transport; internal sealed class N8nUploadClient { diff --git a/src/AttackTracerNinjaCli/VulnerabilityCorrelation.cs b/src/OCSentinelCli/VulnerabilityCorrelation.cs similarity index 99% rename from src/AttackTracerNinjaCli/VulnerabilityCorrelation.cs rename to src/OCSentinelCli/VulnerabilityCorrelation.cs index 83064bf..a0691be 100644 --- a/src/AttackTracerNinjaCli/VulnerabilityCorrelation.cs +++ b/src/OCSentinelCli/VulnerabilityCorrelation.cs @@ -1,6 +1,6 @@ using System.Globalization; -namespace AttackTracerNinjaCli; +namespace OCSentinelCli; internal static class VulnerabilityCorrelation {