diff --git a/config/ocsentinel-client.dev.example.json b/config/ocsentinel-client.dev.example.json index 3cbc234..8ac85b5 100644 --- a/config/ocsentinel-client.dev.example.json +++ b/config/ocsentinel-client.dev.example.json @@ -4,6 +4,12 @@ "lookbackDays": 7, "topFindings": 10, "n8nWebhookUrl": "http://172.16.41.197:5678/webhook/ocsentinel-ingest", + "ninjaOrganizationId": "", + "ninjaOrganizationName": "", + "ninjaMachineId": "", + "ninjaNodeId": "", + "ninjaLocationId": "", + "ninjaLocationName": "", "deviceIdentifierMode": "machineName", "uploadTimeoutSeconds": 30, "uploadQueueMaxReports": 100, diff --git a/config/ocsentinel-client.example.json b/config/ocsentinel-client.example.json index 04335c2..36836a6 100644 --- a/config/ocsentinel-client.example.json +++ b/config/ocsentinel-client.example.json @@ -4,6 +4,12 @@ "lookbackDays": 7, "topFindings": 10, "n8nWebhookUrl": "https://n8n.example.com/webhook/ocsentinel-ingest", + "ninjaOrganizationId": "", + "ninjaOrganizationName": "", + "ninjaMachineId": "", + "ninjaNodeId": "", + "ninjaLocationId": "", + "ninjaLocationName": "", "deviceIdentifierMode": "machineName", "uploadTimeoutSeconds": 30, "uploadQueueMaxReports": 100, diff --git a/docs/ocsentinel-deployment.md b/docs/ocsentinel-deployment.md index 92c6ca2..2410d5c 100644 --- a/docs/ocsentinel-deployment.md +++ b/docs/ocsentinel-deployment.md @@ -32,6 +32,11 @@ powershell -ExecutionPolicy Bypass -File .\build\build-release-manifest.ps1 ` ## Local Schedule And Burst Mode +During a NinjaOne installation or update, OCSentinel stores the device's +NinjaOne organization, location, and device identifiers in its local client +configuration. Scheduled `SYSTEM` scans restore that context before creating a +report, so their uploads remain assigned to the correct organization. + The installer creates two Windows Scheduled Tasks running as `SYSTEM`: - `OCSentinel Daily Scan`: runs once per day and uploads one signed report. diff --git a/installer/runtime-run-ocsentinel.ps1 b/installer/runtime-run-ocsentinel.ps1 index 07d837f..8a820d2 100644 --- a/installer/runtime-run-ocsentinel.ps1 +++ b/installer/runtime-run-ocsentinel.ps1 @@ -39,6 +39,40 @@ function Resolve-PathLike { return [System.IO.Path]::GetFullPath((Join-Path $BasePath $PathValue)) } +function Restore-NinjaContextFromClientConfiguration { + param([Parameter(Mandatory)][string]$Path) + + if (-not (Test-Path -LiteralPath $Path)) { + return + } + + try { + $clientConfiguration = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json + $mappings = @( + @{ EnvironmentName = "NINJA_ORGANIZATION_ID"; PropertyName = "ninjaOrganizationId" }, + @{ EnvironmentName = "NINJA_ORGANIZATION_NAME"; PropertyName = "ninjaOrganizationName" }, + @{ EnvironmentName = "NINJA_AGENT_MACHINE_ID"; PropertyName = "ninjaMachineId" }, + @{ EnvironmentName = "NINJA_AGENT_NODE_ID"; PropertyName = "ninjaNodeId" }, + @{ EnvironmentName = "NINJA_LOCATION_ID"; PropertyName = "ninjaLocationId" }, + @{ EnvironmentName = "NINJA_LOCATION_NAME"; PropertyName = "ninjaLocationName" } + ) + + foreach ($mapping in $mappings) { + if (-not [string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($mapping.EnvironmentName, "Process"))) { + continue + } + + $value = [string]$clientConfiguration.($mapping.PropertyName) + if (-not [string]::IsNullOrWhiteSpace($value)) { + [Environment]::SetEnvironmentVariable($mapping.EnvironmentName, $value, "Process") + } + } + } + catch { + Write-Warning "Could not restore stored NinjaOne context: $($_.Exception.Message)" + } +} + if (-not (Test-Path $appExe)) { throw "Application executable not found: $appExe" } @@ -54,6 +88,8 @@ else { $secretFullPath = if ([string]::IsNullOrWhiteSpace($SecretPath)) { "" } else { Resolve-PathLike -PathValue $SecretPath -BasePath $scriptDir } $canUpload = (Test-Path $clientConfigFullPath) -and (-not [string]::IsNullOrWhiteSpace($secretFullPath)) -and (Test-Path $secretFullPath) + Restore-NinjaContextFromClientConfiguration -Path $clientConfigFullPath + if ($UploadMode -eq "required" -and -not $canUpload) { throw "UploadMode 'required' was set, but client config or protected secret is missing." } diff --git a/scripts/bootstrap-ocsentinel-ninja.ps1 b/scripts/bootstrap-ocsentinel-ninja.ps1 index cdd3160..5372e4e 100644 --- a/scripts/bootstrap-ocsentinel-ninja.ps1 +++ b/scripts/bootstrap-ocsentinel-ninja.ps1 @@ -164,8 +164,22 @@ if (-not [string]::IsNullOrWhiteSpace($WebhookUrl)) { $clientConfig = Get-Content -LiteralPath $clientConfigPath -Raw | ConvertFrom-Json $clientConfig.n8nWebhookUrl = $WebhookUrl $clientConfig.environment = "production" + $ninjaContext = @( + @{ EnvironmentName = "NINJA_ORGANIZATION_ID"; PropertyName = "ninjaOrganizationId" }, + @{ EnvironmentName = "NINJA_ORGANIZATION_NAME"; PropertyName = "ninjaOrganizationName" }, + @{ EnvironmentName = "NINJA_AGENT_MACHINE_ID"; PropertyName = "ninjaMachineId" }, + @{ EnvironmentName = "NINJA_AGENT_NODE_ID"; PropertyName = "ninjaNodeId" }, + @{ EnvironmentName = "NINJA_LOCATION_ID"; PropertyName = "ninjaLocationId" }, + @{ EnvironmentName = "NINJA_LOCATION_NAME"; PropertyName = "ninjaLocationName" } + ) + foreach ($entry in $ninjaContext) { + $value = [Environment]::GetEnvironmentVariable($entry.EnvironmentName, "Process") + if (-not [string]::IsNullOrWhiteSpace($value)) { + $clientConfig | Add-Member -NotePropertyName $entry.PropertyName -NotePropertyValue $value.Trim() -Force + } + } $clientConfig | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $clientConfigPath -Encoding UTF8 - Write-Host "Configured OCSentinel upload endpoint." + Write-Host "Configured OCSentinel upload endpoint and NinjaOne context." } if (-not [string]::IsNullOrWhiteSpace($SecretValue)) { diff --git a/scripts/run-ocsentinel.ps1 b/scripts/run-ocsentinel.ps1 index 4409cf0..cbbcf9b 100644 --- a/scripts/run-ocsentinel.ps1 +++ b/scripts/run-ocsentinel.ps1 @@ -40,6 +40,40 @@ function Resolve-PathLike { return [System.IO.Path]::GetFullPath((Join-Path $BasePath $PathValue)) } +function Restore-NinjaContextFromClientConfiguration { + param([Parameter(Mandatory)][string]$Path) + + if (-not (Test-Path -LiteralPath $Path)) { + return + } + + try { + $clientConfiguration = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json + $mappings = @( + @{ EnvironmentName = "NINJA_ORGANIZATION_ID"; PropertyName = "ninjaOrganizationId" }, + @{ EnvironmentName = "NINJA_ORGANIZATION_NAME"; PropertyName = "ninjaOrganizationName" }, + @{ EnvironmentName = "NINJA_AGENT_MACHINE_ID"; PropertyName = "ninjaMachineId" }, + @{ EnvironmentName = "NINJA_AGENT_NODE_ID"; PropertyName = "ninjaNodeId" }, + @{ EnvironmentName = "NINJA_LOCATION_ID"; PropertyName = "ninjaLocationId" }, + @{ EnvironmentName = "NINJA_LOCATION_NAME"; PropertyName = "ninjaLocationName" } + ) + + foreach ($mapping in $mappings) { + if (-not [string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($mapping.EnvironmentName, "Process"))) { + continue + } + + $value = [string]$clientConfiguration.($mapping.PropertyName) + if (-not [string]::IsNullOrWhiteSpace($value)) { + [Environment]::SetEnvironmentVariable($mapping.EnvironmentName, $value, "Process") + } + } + } + catch { + Write-Warning "Could not restore stored NinjaOne context: $($_.Exception.Message)" + } +} + $arguments = @( $dllPath ) @@ -53,6 +87,8 @@ else { $secretFullPath = if ([string]::IsNullOrWhiteSpace($SecretPath)) { "" } else { Resolve-PathLike -PathValue $SecretPath -BasePath $repoRoot } $canUpload = (Test-Path $clientConfigFullPath) -and (-not [string]::IsNullOrWhiteSpace($secretFullPath)) -and (Test-Path $secretFullPath) + Restore-NinjaContextFromClientConfiguration -Path $clientConfigFullPath + if ($UploadMode -eq "required" -and -not $canUpload) { throw "UploadMode 'required' was set, but client config or protected secret is missing." } diff --git a/src/OCSentinelCli/OCSentinelCli.csproj b/src/OCSentinelCli/OCSentinelCli.csproj index 441d0b9..e63f5a6 100644 --- a/src/OCSentinelCli/OCSentinelCli.csproj +++ b/src/OCSentinelCli/OCSentinelCli.csproj @@ -9,10 +9,10 @@ OCSentinelCli OfficeCom Sentinel OfficeCom - 1.3.5 - 1.3.5.0 - 1.3.5.0 - 1.3.5 + 1.3.6 + 1.3.6.0 + 1.3.6.0 + 1.3.6