From 5b2e2aa417f821d4fda1b922457eb859268e0bae Mon Sep 17 00:00:00 2001 From: Marius Tarau Date: Sun, 31 May 2026 22:19:16 +0300 Subject: [PATCH] Add location-aware DNS suffix auto-switch for LBOOK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LBOOK is domain-joined to intern.beletage.ro, so the AD suffix was searched first for every single-label name. Off the office LAN, home names like home-ws were tried as home-ws.intern.beletage.ro, routed by NRPT to the AD DNS over VPN, stalling ~11s on timeout before falling back to .lan — slow RDP to home hosts. scripts/dns-location-suffix.ps1 sets the global suffix search order from the physical NIC subnet (Sophos TAP excluded): intern-first on the office LAN (10.10.10.x / 10.10.40.x), lan-first everywhere else. install-dns-location-task.ps1 registers it as a SYSTEM scheduled task triggered on network-connect (NetworkProfile 10000) and logon; verify-dns-location-task.ps1 reads it back (the task is not queryable unelevated). Also adds .claude/settings.json allowlisting read-only network diagnostics. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/settings.json | 18 +++++++ scripts/dns-location-suffix.ps1 | 72 +++++++++++++++++++++++++++ scripts/install-dns-location-task.ps1 | 64 ++++++++++++++++++++++++ scripts/verify-dns-location-task.ps1 | 34 +++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 .claude/settings.json create mode 100644 scripts/dns-location-suffix.ps1 create mode 100644 scripts/install-dns-location-task.ps1 create mode 100644 scripts/verify-dns-location-task.ps1 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..1688e9f --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,18 @@ +{ + "permissions": { + "allow": [ + "PowerShell(Get-NetAdapter)", + "PowerShell(Get-NetAdapter *)", + "PowerShell(Get-NetRoute *)", + "PowerShell(Get-VpnConnection)", + "PowerShell(Get-VpnConnection *)", + "PowerShell(Get-NetIPAddress *)", + "PowerShell(ipconfig /all)", + "PowerShell(klist)", + "PowerShell(klist *)", + "PowerShell(Clear-DnsClientCache)", + "PowerShell(ipconfig /flushdns)", + "Bash(pdftotext *)" + ] + } +} diff --git a/scripts/dns-location-suffix.ps1 b/scripts/dns-location-suffix.ps1 new file mode 100644 index 0000000..a19ab5f --- /dev/null +++ b/scripts/dns-location-suffix.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS + Auto-switch the global DNS suffix search order based on network location. + +.DESCRIPTION + Office LAN (physical NIC in 10.10.10.x / 10.10.40.x) -> intern.beletage.ro first. + Home / remote / VPN-from-anywhere -> lan first. + + Rationale: LBOOK is domain-joined to intern.beletage.ro. When the AD suffix is + searched first, every single-label home name (e.g. "home-ws") is first tried as + .intern.beletage.ro, which the NRPT rule routes to the AD DNS 10.10.10.2. + Off the office LAN that server is only reachable over VPN (and not at all when the + VPN is down), so the query stalls on a timeout before falling back to .lan. + Putting "lan" first makes home names resolve locally (~3 ms); work names still + resolve via the NRPT rule after a fast local .lan NXDOMAIN. + + The Sophos TAP adapter is excluded from detection so that starting the VPN from + home (which may assign a 10.10.x address on the tunnel) does not flip us to the + "office" ordering. + + Installed as a SYSTEM scheduled task (see install-dns-location-task.ps1), triggered + on network-connect events and at logon. Idempotent: only writes when the order + actually needs to change. +#> + +$ErrorActionPreference = 'Stop' +$log = 'C:\ProgramData\Beletage\dns-location.log' + +$dir = Split-Path $log +if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null } + +function Write-Log($msg) { + $line = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg + Add-Content -Path $log -Value $line -Encoding UTF8 +} + +try { + $officePrefixes = @('10.10.10.', '10.10.40.') + + # Indices of VPN/tunnel adapters to ignore (Sophos SSL VPN TAP). + $vpnIdx = (Get-NetAdapter -ErrorAction SilentlyContinue | + Where-Object { $_.InterfaceDescription -like '*Sophos*' }).ifIndex + + $ips = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | + Where-Object { + $_.IPAddress -notlike '169.254.*' -and + $_.IPAddress -ne '127.0.0.1' -and + ($vpnIdx -notcontains $_.InterfaceIndex) + }).IPAddress + + $atOffice = $false + foreach ($ip in $ips) { + foreach ($p in $officePrefixes) { + if ($ip.StartsWith($p)) { $atOffice = $true } + } + } + + $desired = if ($atOffice) { @('intern.beletage.ro', 'lan') } else { @('lan', 'intern.beletage.ro') } + $current = (Get-DnsClientGlobalSetting).SuffixSearchList + + if (($current -join ',') -ne ($desired -join ',')) { + Set-DnsClientGlobalSetting -SuffixSearchList $desired + Clear-DnsClientCache + Write-Log ('CHANGED ips=[{0}] office={1} [{2}] -> [{3}]' -f ($ips -join ' '), $atOffice, ($current -join ','), ($desired -join ',')) + } + else { + Write-Log ('OK ips=[{0}] office={1} already [{2}]' -f ($ips -join ' '), $atOffice, ($desired -join ',')) + } +} +catch { + Write-Log ('ERROR {0}' -f $_.Exception.Message) +} diff --git a/scripts/install-dns-location-task.ps1 b/scripts/install-dns-location-task.ps1 new file mode 100644 index 0000000..82201ba --- /dev/null +++ b/scripts/install-dns-location-task.ps1 @@ -0,0 +1,64 @@ +<# +.SYNOPSIS + One-time elevated installer for the location-aware DNS suffix task. + +.DESCRIPTION + Copies dns-location-suffix.ps1 to C:\ProgramData\Beletage and registers a SYSTEM + scheduled task that runs it on every network-connect event and at logon. Re-runnable + (idempotent) and device-independent (locates the payload via $PSScriptRoot), so the + same script installs the automation on any machine where this repo is cloned. + + Run elevated: + Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','\install-dns-location-task.ps1' +#> + +$ErrorActionPreference = 'Stop' + +$taskName = 'Beletage-DNS-Location-Suffix' +$dst = 'C:\ProgramData\Beletage' +$payload = Join-Path $dst 'dns-location-suffix.ps1' +$log = Join-Path $dst 'install.log' + +New-Item -ItemType Directory -Force -Path $dst | Out-Null +Start-Transcript -Path $log -Append | Out-Null + +try { + # 1. Deploy the detection script next to where SYSTEM will run it. + Copy-Item (Join-Path $PSScriptRoot 'dns-location-suffix.ps1') $payload -Force + Write-Host "Copied payload -> $payload" + + # 2. Action: run the detection script hidden, policy-bypassed. + $action = New-ScheduledTaskAction -Execute 'powershell.exe' ` + -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$payload`"" + + # 3a. Trigger: NetworkProfile "network connected" event (ID 10000). + $evtTrigger = New-CimInstance -ClientOnly ` + -CimClass (Get-CimClass -ClassName MSFT_TaskEventTrigger -Namespace Root/Microsoft/Windows/TaskScheduler) + $evtTrigger.Enabled = $true + $evtTrigger.Subscription = '' + + # 3b. Trigger: at any user logon (covers boot / resume). + $logonTrigger = New-ScheduledTaskTrigger -AtLogOn + + # 4. Run as SYSTEM, highest privileges. + $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest + + $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` + -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 5) + + Register-ScheduledTask -TaskName $taskName -Force ` + -Action $action -Trigger $evtTrigger, $logonTrigger -Principal $principal -Settings $settings | Out-Null + Write-Host "Registered task: $taskName" + + # 5. Run once now to apply the correct order immediately. + Start-ScheduledTask -TaskName $taskName + Start-Sleep -Seconds 2 + Write-Host "Triggered initial run. Current suffix order: [$((Get-DnsClientGlobalSetting).SuffixSearchList -join ', ')]" +} +catch { + Write-Host "INSTALL FAILED: $($_.Exception.Message)" + throw +} +finally { + Stop-Transcript | Out-Null +} diff --git a/scripts/verify-dns-location-task.ps1 b/scripts/verify-dns-location-task.ps1 new file mode 100644 index 0000000..aa520e2 --- /dev/null +++ b/scripts/verify-dns-location-task.ps1 @@ -0,0 +1,34 @@ +<# +.SYNOPSIS + Elevated read-out of the Beletage-DNS-Location-Suffix task (triggers, principal, + last run) to C:\ProgramData\Beletage\verify.txt. The task runs as SYSTEM with a + restrictive security descriptor, so it is not queryable from a non-elevated shell. + + Run elevated: + Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','\verify-dns-location-task.ps1' +#> + +$ErrorActionPreference = 'Stop' +$out = 'C:\ProgramData\Beletage\verify.txt' +$name = 'Beletage-DNS-Location-Suffix' +$lines = @() + +try { + $t = Get-ScheduledTask -TaskName $name + $lines += "FOUND $($t.TaskName) State=$($t.State)" + $lines += "Principal $($t.Principal.UserId) RunLevel=$($t.Principal.RunLevel) LogonType=$($t.Principal.LogonType)" + $lines += "Triggers $($t.Triggers.Count)" + foreach ($tr in $t.Triggers) { + $cn = $tr.CimClass.CimClassName + $tag = if ($tr.Subscription) { ' (event: NetworkProfile connected / EventID 10000)' } else { '' } + $lines += " - $cn$tag" + } + $info = Get-ScheduledTaskInfo -TaskName $name + $lines += "LastRun $($info.LastRunTime) LastResult=0x$('{0:X}' -f $info.LastTaskResult)" + $lines += "NextRun $($info.NextRunTime)" +} +catch { + $lines += "ERROR $($_.Exception.Message)" +} + +$lines | Set-Content -Path $out -Encoding UTF8