Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c2170090e | ||
|
|
91c5794502 | ||
|
|
f392057535 |
@@ -54,6 +54,7 @@ Copy-Item -Path (Join-Path $installerRoot "uninstall-ocsentinel.ps1") -Destinati
|
||||
Copy-Item -Path (Join-Path $installerRoot "update-ocsentinel.ps1") -Destination (Join-Path $packageRoot "scripts\update-ocsentinel.ps1") -Force
|
||||
Copy-Item -Path (Join-Path $installerRoot "runtime-run-ocsentinel.ps1") -Destination (Join-Path $packageRoot "scripts\run-ocsentinel.ps1") -Force
|
||||
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 $installerRoot "runtime-run-ocsentinel-scheduled.ps1") -Destination (Join-Path $packageRoot "scripts\run-ocsentinel-scheduled.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\ocsentinel-settings.example.json") -Destination (Join-Path $packageRoot "config\ocsentinel-settings.example.json") -Force
|
||||
|
||||
@@ -30,6 +30,22 @@ powershell -ExecutionPolicy Bypass -File .\build\build-release-manifest.ps1 `
|
||||
- `config\ocsentinel-settings.json`
|
||||
- `config\ocsentinel-client.json`
|
||||
|
||||
## Local Schedule And Burst Mode
|
||||
|
||||
The installer creates two Windows Scheduled Tasks running as `SYSTEM`:
|
||||
|
||||
- `OCSentinel Daily Scan`: runs once per day and uploads one signed report.
|
||||
The installer deterministically assigns each device a stable slot between
|
||||
`04:00` and `06:59`, derived from its Windows `MachineGuid`. This distributes
|
||||
a fleet rollout instead of sending all reports at the same time.
|
||||
- `OCSentinel Burst Check`: runs every five minutes. It performs no scan unless
|
||||
the NinjaOne device custom field `ocsentinelburst` is enabled.
|
||||
|
||||
Create `ocsentinelburst` as a device-level `Checkbox` custom field and allow
|
||||
automation read access. Set it to `true` for a device to begin the five-minute
|
||||
burst scans; clear it to stop them. The normal daily scan continues regardless
|
||||
of the checkbox.
|
||||
|
||||
## NinjaOne Tasks
|
||||
|
||||
Create a PowerShell script in NinjaOne named `OCSentinel - Installieren oder aktualisieren`.
|
||||
|
||||
@@ -36,7 +36,7 @@ For the isolated development environment only, HTTP is permitted at
|
||||
`http://172.16.41.197:5678/webhook/ocsentinel-ingest`. Do not reuse this URL,
|
||||
the development shared secret, or a disabled-TLS configuration in production.
|
||||
|
||||
1. `Webhook`: accept `POST` on the configured private URL.
|
||||
1. `Webhook`: accept `POST` on the configured private URL and enable **Raw Body**.
|
||||
2. `Code`: reject a request if `X-ATN-Device`, `X-ATN-Timestamp`,
|
||||
`X-ATN-Nonce`, `X-ATN-Version`, `X-ATN-Payload-SHA256`, or
|
||||
`X-ATN-Signature` is missing; reject timestamps outside five minutes.
|
||||
@@ -49,6 +49,11 @@ the development shared secret, or a disabled-TLS configuration in production.
|
||||
<device>\n<timestamp>\n<nonce>\n<version>\n<payload-sha256>
|
||||
```
|
||||
|
||||
In the current n8n Webhook node, the raw bytes are exposed as Base64 at
|
||||
`$binary.data.data`. Decode this value before calculating the payload hash.
|
||||
Do not hash `JSON.stringify($json.body)`: parsing and reserializing JSON
|
||||
changes whitespace and can change the signed byte sequence.
|
||||
|
||||
4. `Postgres`: insert the nonce into `ocsentinel.ingest_nonce` with a short
|
||||
expiry. If it already exists, return `409` and do not process the report.
|
||||
5. `Postgres`: upsert the device, insert a row in `ocsentinel.scan_report`,
|
||||
|
||||
@@ -32,6 +32,7 @@ if (Test-Path (Join-Path $packageRoot "config\ocsentinel-client.dev.example.json
|
||||
Copy-Item -Path (Join-Path $packageRoot "samples\ninja-vulnerability-export.example.csv") -Destination (Join-Path $samplesRoot "ninja-vulnerability-export.example.csv") -Force
|
||||
Copy-Item -Path (Join-Path $packageRoot "scripts\run-ocsentinel.ps1") -Destination $scriptRoot -Force
|
||||
Copy-Item -Path (Join-Path $packageRoot "scripts\run-ocsentinel-monitor.ps1") -Destination $scriptRoot -Force
|
||||
Copy-Item -Path (Join-Path $packageRoot "scripts\run-ocsentinel-scheduled.ps1") -Destination $scriptRoot -Force
|
||||
if (Test-Path (Join-Path $packageRoot "scripts\protect-ocsentinel-secret.ps1")) {
|
||||
Copy-Item -Path (Join-Path $packageRoot "scripts\protect-ocsentinel-secret.ps1") -Destination $scriptRoot -Force
|
||||
}
|
||||
@@ -69,8 +70,34 @@ Set-ItemProperty -Path $uninstallKey -Name "QuietUninstallString" -Value $uninst
|
||||
Set-ItemProperty -Path $uninstallKey -Name "NoModify" -Value 1 -Type DWord
|
||||
Set-ItemProperty -Path $uninstallKey -Name "NoRepair" -Value 1 -Type DWord
|
||||
|
||||
$scheduledScript = Join-Path $scriptRoot "run-ocsentinel-scheduled.ps1"
|
||||
$taskPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
|
||||
$taskSettings = New-ScheduledTaskSettingsSet -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 30) -MultipleInstances IgnoreNew
|
||||
|
||||
# Spread fleet uploads across the early-morning window while keeping each device's slot stable.
|
||||
$machineGuid = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Cryptography" -Name "MachineGuid").MachineGuid
|
||||
$guidBytes = [Text.Encoding]::UTF8.GetBytes([string]$machineGuid)
|
||||
$sha256 = [Security.Cryptography.SHA256]::Create()
|
||||
try {
|
||||
$slotHash = $sha256.ComputeHash($guidBytes)
|
||||
}
|
||||
finally {
|
||||
$sha256.Dispose()
|
||||
}
|
||||
$dailySlotMinutes = [BitConverter]::ToUInt32($slotHash, 0) % 180
|
||||
$dailyRunAt = (Get-Date -Hour 4 -Minute 0 -Second 0).AddMinutes($dailySlotMinutes)
|
||||
|
||||
$dailyAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scheduledScript`" -Kind daily" -WorkingDirectory $scriptRoot
|
||||
$dailyTrigger = New-ScheduledTaskTrigger -Daily -At $dailyRunAt
|
||||
Register-ScheduledTask -TaskName "OCSentinel Daily Scan" -Action $dailyAction -Trigger $dailyTrigger -Principal $taskPrincipal -Settings $taskSettings -Description "OfficeCom Sentinel daily signed scan and upload." -Force | Out-Null
|
||||
|
||||
$burstAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scheduledScript`" -Kind burst" -WorkingDirectory $scriptRoot
|
||||
$burstTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(2) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 3650)
|
||||
Register-ScheduledTask -TaskName "OCSentinel Burst Check" -Action $burstAction -Trigger $burstTrigger -Principal $taskPrincipal -Settings $taskSettings -Description "OfficeCom Sentinel burst check; scans only when Ninja field ocsentinelburst is enabled." -Force | Out-Null
|
||||
|
||||
Write-Host "Installation complete."
|
||||
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 "Schedule: Daily scan at $($dailyRunAt.ToString('HH:mm')) (deterministic 04:00-06:59 slot); burst check every 5 minutes."
|
||||
|
||||
@@ -9,6 +9,7 @@ param(
|
||||
[string]$UploadMode = "auto",
|
||||
[string]$VulnerabilityCsvPath = "",
|
||||
[string]$MirrorRoot = "",
|
||||
[switch]$SuppressTriggerExit,
|
||||
[ValidateSet("status", "attack-only", "cve-critical", "attack-plus-cve")]
|
||||
[string]$Mode = "status"
|
||||
)
|
||||
@@ -227,8 +228,8 @@ Write-Host "Total CVEs: $totalCves"
|
||||
Write-Host "Report: $outputFullPath"
|
||||
Write-Host "Runner exit code: $runnerExitCode"
|
||||
|
||||
if ($monitorTriggered) {
|
||||
if ($monitorTriggered -and -not $SuppressTriggerExit) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
exit $runnerExitCode
|
||||
|
||||
50
installer/runtime-run-ocsentinel-scheduled.ps1
Normal file
50
installer/runtime-run-ocsentinel-scheduled.ps1
Normal file
@@ -0,0 +1,50 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet("daily", "burst")]
|
||||
[string]$Kind = "daily"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$monitorScript = Join-Path $scriptDir "run-ocsentinel-monitor.ps1"
|
||||
$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat"
|
||||
$mutexName = "Global\OfficeComSentinelScan"
|
||||
|
||||
function Get-NinjaBurstEnabled {
|
||||
if (Get-Command -Name "Get-NinjaProperty" -ErrorAction SilentlyContinue) {
|
||||
return [bool](Get-NinjaProperty -Name "ocsentinelburst" -Type "Checkbox")
|
||||
}
|
||||
|
||||
if (Get-Command -Name "Ninja-Property-Get" -ErrorAction SilentlyContinue) {
|
||||
$value = Ninja-Property-Get -Name "ocsentinelburst"
|
||||
return [string]$value -match "^(1|true|yes)$"
|
||||
}
|
||||
|
||||
Write-Warning "Ninja custom-field reader is unavailable; burst scan skipped."
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($Kind -eq "burst" -and -not (Get-NinjaBurstEnabled)) {
|
||||
Write-Host "OfficeCom Sentinel burst check: disabled."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$createdNew = $false
|
||||
$mutex = [Threading.Mutex]::new($false, $mutexName, [ref]$createdNew)
|
||||
try {
|
||||
if (-not $mutex.WaitOne(0)) {
|
||||
Write-Host "OfficeCom Sentinel scan skipped: another scan is already running."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "OfficeCom Sentinel scheduled $Kind scan started."
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $monitorScript -Mode status -UploadMode required -SecretPath $secretPath -SuppressTriggerExit
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $mutex) {
|
||||
try { $mutex.ReleaseMutex() } catch { }
|
||||
$mutex.Dispose()
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,12 @@ $ErrorActionPreference = "Stop"
|
||||
$installRoot = Join-Path ${env:ProgramFiles} "OCSentinel"
|
||||
$uninstallKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\OCSentinel"
|
||||
|
||||
foreach ($taskName in @("OCSentinel Daily Scan", "OCSentinel Burst Check")) {
|
||||
if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {
|
||||
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path $uninstallKey) {
|
||||
Remove-Item -Path $uninstallKey -Force -Recurse
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"channel": "stable",
|
||||
"version": "1.2.11",
|
||||
"publishedAtUtc": "2026-07-25T19:17:43.7567344Z",
|
||||
"artifactUrl": "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v1.2.11/OCSentinelClient-win-x64.zip",
|
||||
"sha256": "4a5415ce281f444a987a3fd72980c159afaa37787bbaa396f42d2c496736e5e1",
|
||||
"version": "1.3.2",
|
||||
"publishedAtUtc": "2026-07-26T00:23:31.3024295Z",
|
||||
"artifactUrl": "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v1.3.2/OCSentinelClient-win-x64.zip",
|
||||
"sha256": "dbfedf0890176873cef8bc91b9f7a6d6c3c8382ffadc972324b9900bad1153cc",
|
||||
"minUpdaterVersion": "1.0.0"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ param(
|
||||
[string]$UploadMode = "auto",
|
||||
[string]$VulnerabilityCsvPath = "",
|
||||
[string]$MirrorRoot = "",
|
||||
[switch]$SuppressTriggerExit,
|
||||
[ValidateSet("status", "attack-only", "cve-critical", "attack-plus-cve")]
|
||||
[string]$Mode = "status"
|
||||
)
|
||||
@@ -227,8 +228,8 @@ Write-Host "Total CVEs: $totalCves"
|
||||
Write-Host "Report: $outputFullPath"
|
||||
Write-Host "Runner exit code: $runnerExitCode"
|
||||
|
||||
if ($monitorTriggered) {
|
||||
if ($monitorTriggered -and -not $SuppressTriggerExit) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
exit $runnerExitCode
|
||||
|
||||
50
scripts/run-ocsentinel-scheduled.ps1
Normal file
50
scripts/run-ocsentinel-scheduled.ps1
Normal file
@@ -0,0 +1,50 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet("daily", "burst")]
|
||||
[string]$Kind = "daily"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$monitorScript = Join-Path $scriptDir "run-ocsentinel-monitor.ps1"
|
||||
$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat"
|
||||
$mutexName = "Global\OfficeComSentinelScan"
|
||||
|
||||
function Get-NinjaBurstEnabled {
|
||||
if (Get-Command -Name "Get-NinjaProperty" -ErrorAction SilentlyContinue) {
|
||||
return [bool](Get-NinjaProperty -Name "ocsentinelburst" -Type "Checkbox")
|
||||
}
|
||||
|
||||
if (Get-Command -Name "Ninja-Property-Get" -ErrorAction SilentlyContinue) {
|
||||
$value = Ninja-Property-Get -Name "ocsentinelburst"
|
||||
return [string]$value -match "^(1|true|yes)$"
|
||||
}
|
||||
|
||||
Write-Warning "Ninja custom-field reader is unavailable; burst scan skipped."
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($Kind -eq "burst" -and -not (Get-NinjaBurstEnabled)) {
|
||||
Write-Host "OfficeCom Sentinel burst check: disabled."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$createdNew = $false
|
||||
$mutex = [Threading.Mutex]::new($false, $mutexName, [ref]$createdNew)
|
||||
try {
|
||||
if (-not $mutex.WaitOne(0)) {
|
||||
Write-Host "OfficeCom Sentinel scan skipped: another scan is already running."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "OfficeCom Sentinel scheduled $Kind scan started."
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $monitorScript -Mode status -UploadMode required -SecretPath $secretPath -SuppressTriggerExit
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $mutex) {
|
||||
try { $mutex.ReleaseMutex() } catch { }
|
||||
$mutex.Dispose()
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,10 @@
|
||||
<RootNamespace>OCSentinelCli</RootNamespace>
|
||||
<Product>OfficeCom Sentinel</Product>
|
||||
<Company>OfficeCom</Company>
|
||||
<Version>1.2.11</Version>
|
||||
<AssemblyVersion>1.2.11.0</AssemblyVersion>
|
||||
<FileVersion>1.2.11.0</FileVersion>
|
||||
<InformationalVersion>1.2.11</InformationalVersion>
|
||||
<Version>1.3.2</Version>
|
||||
<AssemblyVersion>1.3.2.0</AssemblyVersion>
|
||||
<FileVersion>1.3.2.0</FileVersion>
|
||||
<InformationalVersion>1.3.2</InformationalVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user