143 lines
6.1 KiB
PowerShell
143 lines
6.1 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$ManifestUrl = "https://gitea.officecom.cloud/officecom/oc-sentinel/raw/main/release/stable/version.json",
|
|
[string]$WebhookUrl = "",
|
|
[string]$SecretValue = "",
|
|
[switch]$RunInitialStatusScan
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$ProgressPreference = "SilentlyContinue"
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
|
|
$installRoot = Join-Path $env:ProgramFiles "OCSentinel"
|
|
$updaterPath = Join-Path $installRoot "scripts\update-ocsentinel.ps1"
|
|
$monitorPath = Join-Path $installRoot "scripts\run-ocsentinel-monitor.ps1"
|
|
$appPath = Join-Path $installRoot "app\OCSentinelCli.exe"
|
|
$clientConfigPath = Join-Path $installRoot "config\ocsentinel-client.json"
|
|
$secretScriptPath = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1"
|
|
$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat"
|
|
|
|
# NinjaOne script variables are exposed as process environment variables.
|
|
if ([string]::IsNullOrWhiteSpace($WebhookUrl)) {
|
|
$WebhookUrl = $env:WebhookUrl
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($SecretValue)) {
|
|
$SecretValue = $env:SecretValue
|
|
}
|
|
|
|
$runInitialScan = $RunInitialStatusScan.IsPresent
|
|
if (-not $runInitialScan -and -not [string]::IsNullOrWhiteSpace($env:RunInitialStatusScan)) {
|
|
$runInitialScan = $env:RunInitialStatusScan -match '^(1|true|yes|on)$'
|
|
}
|
|
|
|
function Assert-ArtifactSignature {
|
|
param([Parameter(Mandatory)][string]$ExecutablePath)
|
|
|
|
$signature = Get-AuthenticodeSignature -FilePath $ExecutablePath
|
|
if ($signature.Status -notin @("Valid", "NotSigned")) {
|
|
throw "Executable signature validation failed with status: $($signature.Status)"
|
|
}
|
|
|
|
if ($signature.Status -eq "NotSigned") {
|
|
Write-Warning "The package hash was verified, but OCSentinelCli.exe is not code-signed yet."
|
|
}
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $updaterPath) {
|
|
Write-Host "Existing OCSentinel installation found. Checking for updates."
|
|
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $updaterPath -ManifestUrl $ManifestUrl
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "OCSentinel updater exited with code $LASTEXITCODE"
|
|
}
|
|
}
|
|
else {
|
|
Write-Host "Reading OCSentinel release manifest: $ManifestUrl"
|
|
$manifest = Invoke-RestMethod -Method Get -Uri $ManifestUrl -TimeoutSec 60
|
|
if ([string]::IsNullOrWhiteSpace($manifest.version) -or [string]::IsNullOrWhiteSpace($manifest.artifactUrl) -or [string]::IsNullOrWhiteSpace($manifest.sha256)) {
|
|
throw "Release manifest is missing version, artifactUrl, or sha256."
|
|
}
|
|
|
|
$downloadRoot = Join-Path $env:ProgramData ("OCSentinel\\bootstrap\\" + [Guid]::NewGuid().ToString("N"))
|
|
$zipPath = Join-Path $downloadRoot "OCSentinelClient.zip"
|
|
$extractRoot = Join-Path $downloadRoot "payload"
|
|
|
|
try {
|
|
New-Item -ItemType Directory -Force -Path $extractRoot | Out-Null
|
|
Write-Host "Downloading OCSentinel $($manifest.version)"
|
|
Invoke-WebRequest -Uri ([string]$manifest.artifactUrl) -OutFile $zipPath -TimeoutSec 300
|
|
|
|
$actualHash = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
$expectedHash = ([string]$manifest.sha256).ToLowerInvariant()
|
|
if ($actualHash -ne $expectedHash) {
|
|
throw "SHA-256 mismatch for the downloaded OCSentinel package."
|
|
}
|
|
|
|
Write-Host "Package hash verified. Extracting release payload."
|
|
Expand-Archive -LiteralPath $zipPath -DestinationPath $extractRoot -Force
|
|
$payloadApp = Get-ChildItem -Path $extractRoot -Recurse -Filter "OCSentinelCli.exe" | Select-Object -First 1
|
|
$installer = Get-ChildItem -Path $extractRoot -Recurse -Filter "install-ocsentinel.ps1" | Select-Object -First 1
|
|
if ($null -eq $payloadApp -or $null -eq $installer) {
|
|
throw "The downloaded package is incomplete."
|
|
}
|
|
|
|
Assert-ArtifactSignature -ExecutablePath $payloadApp.FullName
|
|
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $installer.FullName
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "OCSentinel installer exited with code $LASTEXITCODE"
|
|
}
|
|
}
|
|
finally {
|
|
if (Test-Path -LiteralPath $downloadRoot) {
|
|
Remove-Item -LiteralPath $downloadRoot -Recurse -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $appPath)) {
|
|
throw "OCSentinel installation completed, but the client executable was not found."
|
|
}
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($WebhookUrl)) {
|
|
if (-not (Test-Path -LiteralPath $clientConfigPath)) {
|
|
throw "OCSentinel client configuration was not found: $clientConfigPath"
|
|
}
|
|
|
|
$clientConfig = Get-Content -LiteralPath $clientConfigPath -Raw | ConvertFrom-Json
|
|
$clientConfig.n8nWebhookUrl = $WebhookUrl
|
|
$clientConfig.environment = "production"
|
|
$clientConfig | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $clientConfigPath -Encoding UTF8
|
|
Write-Host "Configured OCSentinel upload endpoint."
|
|
}
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($SecretValue)) {
|
|
if (-not (Test-Path -LiteralPath $secretScriptPath)) {
|
|
throw "OCSentinel secret bootstrap script was not found: $secretScriptPath"
|
|
}
|
|
|
|
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $secretScriptPath -SecretValue $SecretValue
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "OCSentinel secret bootstrap failed with code $LASTEXITCODE"
|
|
}
|
|
}
|
|
|
|
if ($runInitialScan) {
|
|
if (-not (Test-Path -LiteralPath $monitorPath)) {
|
|
throw "OCSentinel was installed, but the monitor script is missing."
|
|
}
|
|
|
|
Write-Host "Running initial OCSentinel status scan."
|
|
$scanArguments = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $monitorPath, "-Mode", "status", "-OutputPath", "..\\reports\\ocsentinel-summary.json")
|
|
if ((Test-Path -LiteralPath $clientConfigPath) -and (Test-Path -LiteralPath $secretPath)) {
|
|
$scanArguments += @("-ClientConfigPath", $clientConfigPath, "-SecretPath", $secretPath, "-UploadMode", "required")
|
|
}
|
|
|
|
& powershell.exe @scanArguments
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Initial OCSentinel status scan exited with code $LASTEXITCODE"
|
|
}
|
|
}
|
|
|
|
Write-Host "OCSentinel bootstrap completed successfully."
|