299 lines
10 KiB
PowerShell
299 lines
10 KiB
PowerShell
#Requires -RunAsAdministrator
|
|
|
|
param(
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$DnsServer,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$Domain
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# --- Configuration ---
|
|
|
|
$NebulaVersion = "1.10.3"
|
|
$NebulaUrl = "https://github.com/slackhq/nebula/releases/download/v$NebulaVersion/nebula-windows-amd64.zip"
|
|
|
|
# Verify running as Administrator
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$principal = [Security.Principal.WindowsPrincipal]$identity
|
|
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
|
Write-Error "This script must be run as a machine Administrator."
|
|
exit 1
|
|
}
|
|
|
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
|
$InstallDir = "C:\Program Files\Nebula"
|
|
$ConfigPath = "$InstallDir\config.yml"
|
|
$StartupScript = "$InstallDir\set-dns-on-start.ps1"
|
|
$ExpectedBinPath = "`"$InstallDir\nebula.exe`" -service run -config `"$ConfigPath`""
|
|
$TempZip = Join-Path $env:TEMP "nebula-windows-amd64.zip"
|
|
$TempExtract = Join-Path $env:TEMP "nebula-extract"
|
|
|
|
# Derive the NetBIOS domain name from the FQDN (first label, uppercased)
|
|
$NetBIOSDomain = ($Domain -split '\.')[0].ToUpper()
|
|
|
|
# --- Validate per-host files before making any changes ---
|
|
|
|
$missing = @()
|
|
|
|
$HostFiles = @("config.yml", "host.crt", "host.key")
|
|
foreach ($file in $HostFiles) {
|
|
if (-not (Test-Path (Join-Path $PWD $file))) {
|
|
$missing += "$file (expected in working directory: $PWD)"
|
|
}
|
|
}
|
|
|
|
# ca.crt must be provided alongside the script
|
|
if (-not (Test-Path (Join-Path $ScriptDir "ca.crt"))) {
|
|
$missing += "ca.crt (expected in script directory: $ScriptDir)"
|
|
}
|
|
|
|
if ($missing.Count -gt 0) {
|
|
Write-Error "Missing required files:`n - $($missing -join "`n - ")"
|
|
exit 1
|
|
}
|
|
|
|
# --- Check if already installed and running correctly ---
|
|
|
|
$existing = Get-Service -Name "nebula" -ErrorAction SilentlyContinue
|
|
if ($existing) {
|
|
$svcConfig = sc.exe qc nebula 2>&1 | Out-String
|
|
$correctBinary = $svcConfig -match [regex]::Escape($ExpectedBinPath)
|
|
|
|
if ($existing.Status -eq "Running" -and $correctBinary) {
|
|
# Check installed version
|
|
$installedVersion = & "$InstallDir\nebula.exe" -version 2>&1 | Select-String -Pattern "Version: (.+)" | ForEach-Object { $_.Matches[0].Groups[1].Value }
|
|
if ($installedVersion -eq $NebulaVersion) {
|
|
# Compare host files against installed files by hash
|
|
$allMatch = $true
|
|
foreach ($file in @("ca.crt")) {
|
|
$src = (Get-FileHash (Join-Path $ScriptDir $file)).Hash
|
|
$dst = (Get-FileHash (Join-Path $InstallDir $file) -ErrorAction SilentlyContinue).Hash
|
|
if ($src -ne $dst) { $allMatch = $false; break }
|
|
}
|
|
if ($allMatch) {
|
|
foreach ($file in $HostFiles) {
|
|
$src = (Get-FileHash (Join-Path $PWD $file)).Hash
|
|
$dst = (Get-FileHash (Join-Path $InstallDir $file) -ErrorAction SilentlyContinue).Hash
|
|
if ($src -ne $dst) { $allMatch = $false; break }
|
|
}
|
|
}
|
|
|
|
# Also verify the scheduled task and startup script exist
|
|
$taskExists = Get-ScheduledTask -TaskName "NebulaDNS" -ErrorAction SilentlyContinue
|
|
$scriptExists = Test-Path $StartupScript
|
|
|
|
if ($allMatch -and $taskExists -and $scriptExists) {
|
|
Write-Host "Nebula $NebulaVersion is already installed and running with matching files. No changes needed."
|
|
exit 0
|
|
}
|
|
}
|
|
|
|
Write-Host "Nebula is running but files or version have changed. Reinstalling..."
|
|
}
|
|
|
|
Write-Host "Stopping existing Nebula service..."
|
|
if ($existing.Status -eq "Running") {
|
|
sc.exe stop nebula | Out-Null
|
|
$timeout = 10
|
|
while ($timeout -gt 0) {
|
|
$svc = Get-Service -Name "nebula" -ErrorAction SilentlyContinue
|
|
if ($svc.Status -eq "Stopped") { break }
|
|
Start-Sleep -Seconds 1
|
|
$timeout--
|
|
}
|
|
if ($timeout -eq 0) {
|
|
Write-Error "Timed out waiting for Nebula service to stop."
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
sc.exe delete nebula | Out-Null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "Failed to remove existing Nebula service. A reboot may be required if it is marked for deletion."
|
|
exit 1
|
|
}
|
|
Start-Sleep -Seconds 1
|
|
}
|
|
|
|
# --- Download Nebula ---
|
|
|
|
Write-Host "Downloading Nebula v$NebulaVersion..."
|
|
try {
|
|
Invoke-WebRequest -Uri $NebulaUrl -OutFile $TempZip -UseBasicParsing
|
|
} catch {
|
|
Write-Error "Failed to download Nebula: $_"
|
|
exit 1
|
|
}
|
|
|
|
# --- Extract and clean up ---
|
|
|
|
if (Test-Path $TempExtract) { Remove-Item $TempExtract -Recurse -Force }
|
|
try {
|
|
Expand-Archive -Path $TempZip -DestinationPath $TempExtract -Force
|
|
} catch {
|
|
Write-Error "Failed to extract Nebula archive: $_"
|
|
exit 1
|
|
}
|
|
|
|
# Verify nebula.exe was extracted
|
|
if (-not (Test-Path (Join-Path $TempExtract "nebula.exe"))) {
|
|
Write-Error "nebula.exe not found in downloaded archive."
|
|
exit 1
|
|
}
|
|
|
|
# Remove nebula-cert.exe — not needed for this module
|
|
$certExe = Join-Path $TempExtract "nebula-cert.exe"
|
|
if (Test-Path $certExe) { Remove-Item $certExe -Force }
|
|
|
|
# --- Create install directory ---
|
|
|
|
if (-not (Test-Path $InstallDir)) {
|
|
try {
|
|
New-Item -ItemType Directory -Path $InstallDir | Out-Null
|
|
} catch {
|
|
Write-Error "Failed to create install directory: $_"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Lock down the directory — only SYSTEM and Administrators need access.
|
|
# Program Files subdirectories inherit broad ACLs (Users: Read & Execute).
|
|
# Take ownership, disable inheritance, and strip everything except SYSTEM and Admins.
|
|
takeown /F $InstallDir /R /A /D Y | Out-Null
|
|
icacls $InstallDir /inheritance:d /T /Q
|
|
icacls $InstallDir /remove:g "BUILTIN\Users" /T /Q
|
|
icacls $InstallDir /remove:g "CREATOR OWNER" /T /Q
|
|
icacls $InstallDir /remove:g "NT SERVICE\TrustedInstaller" /T /Q
|
|
icacls $InstallDir /remove:g "APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES" /T /Q
|
|
icacls $InstallDir /remove:g "APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES" /T /Q
|
|
|
|
# --- Copy files ---
|
|
|
|
try {
|
|
Write-Host "Copying Nebula files..."
|
|
Copy-Item (Join-Path $TempExtract "nebula.exe") $InstallDir -Force
|
|
Copy-Item (Join-Path $TempExtract "dist") $InstallDir -Recurse -Force
|
|
Copy-Item (Join-Path $ScriptDir "ca.crt") $InstallDir -Force
|
|
|
|
Write-Host "Copying host files..."
|
|
foreach ($file in $HostFiles) {
|
|
Copy-Item (Join-Path $PWD $file) $InstallDir -Force
|
|
}
|
|
} catch {
|
|
Write-Error "Failed to copy files: $_"
|
|
exit 1
|
|
}
|
|
|
|
# --- Clean up temp files ---
|
|
|
|
Remove-Item $TempZip -Force -ErrorAction SilentlyContinue
|
|
Remove-Item $TempExtract -Recurse -Force -ErrorAction SilentlyContinue
|
|
|
|
# --- Verify files landed in install directory ---
|
|
|
|
foreach ($file in @("nebula.exe", "ca.crt") + $HostFiles) {
|
|
if (-not (Test-Path (Join-Path $InstallDir $file))) {
|
|
Write-Error "File copy verification failed: $file not found in $InstallDir"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path (Join-Path $InstallDir "dist"))) {
|
|
Write-Error "File copy verification failed: dist\ not found in $InstallDir"
|
|
exit 1
|
|
}
|
|
|
|
# --- Install and configure service ---
|
|
|
|
Write-Host "Installing Nebula service..."
|
|
& "$InstallDir\nebula.exe" -service install -config "$ConfigPath"
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "Failed to install Nebula service."
|
|
exit 1
|
|
}
|
|
|
|
# Wait for real network connectivity before starting, not just the TCP/IP stack
|
|
sc.exe config nebula depend= Tcpip/NlaSvc | Out-Null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "Failed to set service dependencies."
|
|
exit 1
|
|
}
|
|
|
|
# Auto start — the NlaSvc dependency ensures the physical network is up first
|
|
sc.exe config nebula start= auto | Out-Null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "Failed to set auto start."
|
|
exit 1
|
|
}
|
|
|
|
# --- Start and verify ---
|
|
|
|
Write-Host "Starting Nebula service..."
|
|
sc.exe start nebula | Out-Null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "Failed to start Nebula service. Check Event Viewer (Application log, source 'nebula')."
|
|
exit 1
|
|
}
|
|
|
|
Start-Sleep -Seconds 3
|
|
|
|
$svc = Get-Service -Name "nebula"
|
|
if ($svc.Status -eq "Running") {
|
|
Write-Host "Nebula v$NebulaVersion service is running."
|
|
} else {
|
|
Write-Error "Nebula service failed to start. Check Event Viewer (Application log, source 'nebula')."
|
|
exit 1
|
|
}
|
|
|
|
# --- Configure DNS persistence across reboots ---
|
|
#
|
|
# Nebula recreates the nebula1 TUN adapter on every start, which wipes any
|
|
# DNS settings. This scheduled task runs at startup to re-apply DNS, wait
|
|
# for the domain controller to be reachable, then force Netlogon to
|
|
# rediscover the DC — ensuring domain authentication works at the login screen.
|
|
|
|
Write-Host "Creating startup DNS script..."
|
|
$startupScriptContent = @"
|
|
# Wait up to 120 seconds for the nebula1 adapter to appear
|
|
`$timeout = 120
|
|
while (`$timeout -gt 0) {
|
|
`$adapter = Get-NetAdapter -Name "nebula1" -ErrorAction SilentlyContinue
|
|
if (`$adapter -and `$adapter.Status -eq "Up") { break }
|
|
Start-Sleep -Seconds 2
|
|
`$timeout -= 2
|
|
}
|
|
if (`$timeout -le 0) { exit 1 }
|
|
Set-DnsClientServerAddress -InterfaceAlias "nebula1" -ServerAddresses "$DnsServer"
|
|
|
|
# Wait for DC to become reachable, then force Netlogon to rediscover
|
|
`$timeout = 180
|
|
while (`$timeout -gt 0) {
|
|
`$result = Resolve-DnsName "$Domain" -Server "$DnsServer" -ErrorAction SilentlyContinue
|
|
if (`$result) { break }
|
|
Start-Sleep -Seconds 5
|
|
`$timeout -= 5
|
|
}
|
|
if (`$timeout -gt 0) {
|
|
Clear-DnsClientCache
|
|
nltest /dsgetdc:$NetBIOSDomain /force 2>&1 | Out-Null
|
|
}
|
|
"@
|
|
Set-Content -Path $StartupScript -Value $startupScriptContent -Force
|
|
|
|
Write-Host "Registering NebulaDNS scheduled task..."
|
|
$existingTask = Get-ScheduledTask -TaskName "NebulaDNS" -ErrorAction SilentlyContinue
|
|
if ($existingTask) {
|
|
Unregister-ScheduledTask -TaskName "NebulaDNS" -Confirm:$false
|
|
}
|
|
|
|
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -File `"$StartupScript`""
|
|
$trigger = New-ScheduledTaskTrigger -AtStartup
|
|
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
|
|
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
|
Register-ScheduledTask -TaskName "NebulaDNS" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Sets DNS on the Nebula adapter and forces Netlogon DC rediscovery" | Out-Null
|
|
|
|
Write-Host "NebulaDNS scheduled task registered."
|