Nebula recreates the nebula1 TUN adapter on every start, wiping DNS settings. This caused domain authentication to fail at the Windows login screen because Netlogon could not reach the DC. Changes: - install-nebula.ps1 now takes -DnsServer and -Domain parameters - Changed service start type from delayed-auto to auto - Creates set-dns-on-start.ps1 startup script and NebulaDNS scheduled task - Sets ExpectedDialupDelay=60 in Netlogon registry - Idempotency check verifies scheduled task and startup script exist
298 lines
10 KiB
PowerShell
298 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
|
|
}
|
|
}
|
|
|
|
# --- 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."
|
|
|
|
# --- Configure Netlogon to wait for slow network links ---
|
|
#
|
|
# ExpectedDialupDelay tells Netlogon to keep retrying DC discovery for the
|
|
# specified number of seconds. This covers the window between Windows boot
|
|
# and the Nebula tunnel becoming fully operational.
|
|
|
|
$netlogonKey = "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters"
|
|
Set-ItemProperty -Path $netlogonKey -Name "ExpectedDialupDelay" -Value 60 -Type DWord
|
|
Write-Host "Netlogon ExpectedDialupDelay set to 60 seconds."
|