geenforce: harden allowlist + fix share-less kiosk client and display scope
- allowlist auth uses remote_addr, not the spoofable first X-Forwarded-For hop (adds _trusted_client_ip + a regression test); rate-limit path unchanged - client psm1: fix Set-StrictMode crashes reading absent keys in Get-ShopdbConfig (token-less mode) and Resolve-ShopdbPayloads (no-payload entries); validate the manifest response is JSON before overwriting the last-known-good cache - runner: pass the engine its required -InstallerRoot/-LogFile; create the log directory so enforce logging is not silently lost on a fresh kiosk - display scope: dispatcher writes an all-users Startup shortcut instead of Start-Process (SYSTEM cannot show a window in session 0), resolves the base URL from HKLM, and adds an always-on power/no-lock entry; tests updated for the 6-entry scope
This commit is contained in:
@@ -80,15 +80,20 @@ def build_dispatcher_script():
|
||||
|
||||
return f"""# Invoke-DisplayKioskDispatch.ps1 -- gea-shopfloor-display dispatcher.
|
||||
#
|
||||
# Reads C:\\Enrollment\\display-type.txt and launches the kiosk browser at the
|
||||
# route mapped for that display subtype. The kiosk browser is baked into the
|
||||
# display image; this script only points it at the right target.
|
||||
# Reads C:\\Enrollment\\display-type.txt and ensures an all-users Startup
|
||||
# shortcut that launches Edge in kiosk fullscreen at the route mapped for that
|
||||
# subtype. The kiosk browser is baked into the display image; this only points
|
||||
# the launch at the right target and heals the shortcut on a URL/type change.
|
||||
#
|
||||
# WHY a Startup shortcut, not Start-Process: this runs as SYSTEM under the
|
||||
# enforce task. SYSTEM has no interactive desktop, so Start-Process msedge.exe
|
||||
# opens INVISIBLY in session 0. A shortcut in the all-users Startup folder is
|
||||
# instead launched by the auto-login user at logon, in their own visible
|
||||
# session. Idempotent: prior ShopDB kiosk shortcuts are cleared first, then the
|
||||
# one for the resolved target is written, so a display-type/URL change self-heals.
|
||||
#
|
||||
# The DisplayTypeTargets table below is the single source of truth for the
|
||||
# subtype -> route map. Edit a row to retarget a subtype.
|
||||
#
|
||||
# TODO-confirm: 3DPrintRoom uses the printedparts /parts-kiosk route as a
|
||||
# PLACEHOLDER. Confirm the real 3D-print-room target before production use.
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
@@ -97,19 +102,24 @@ $DisplayTypeTargets = @{{
|
||||
{table_body}
|
||||
}}
|
||||
|
||||
# Base URL the kiosk browser opens; the route from the table is appended. Edit
|
||||
# to point at this site's shopdb host. Kept here so the map above stays pure.
|
||||
$KioskBaseUrl = 'https://localhost'
|
||||
# Base URL the kiosk browser opens; the route from the table is appended. Read
|
||||
# from HKLM (the value the GE-Enforce client is configured with) so it stays
|
||||
# site-agnostic; falls back to the West Jefferson shopdb host.
|
||||
$KioskBaseUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb'
|
||||
try {{
|
||||
$shopdbConfig = Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\GE\\ShopDB' -Name BaseUrl -ErrorAction Stop
|
||||
if ($shopdbConfig.BaseUrl) {{ $KioskBaseUrl = ([string]$shopdbConfig.BaseUrl).TrimEnd('/') }}
|
||||
}} catch {{}}
|
||||
|
||||
$displayTypeFile = 'C:\\Enrollment\\display-type.txt'
|
||||
if (-not (Test-Path -LiteralPath $displayTypeFile)) {{
|
||||
Write-Host "display-type.txt not found at $displayTypeFile; nothing to launch."
|
||||
Write-Host "display-type.txt not found at $displayTypeFile; nothing to configure."
|
||||
return
|
||||
}}
|
||||
|
||||
$displayType = (Get-Content -LiteralPath $displayTypeFile -Raw).Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($displayType)) {{
|
||||
Write-Host 'display-type.txt is empty; nothing to launch.'
|
||||
Write-Host 'display-type.txt is empty; nothing to configure.'
|
||||
return
|
||||
}}
|
||||
|
||||
@@ -122,25 +132,91 @@ if (-not $matchedKey) {{
|
||||
return
|
||||
}}
|
||||
|
||||
$targetRoute = $DisplayTypeTargets[$matchedKey]
|
||||
$kioskUrl = "$KioskBaseUrl$targetRoute"
|
||||
Write-Host "display-type '$displayType' -> kiosk target $kioskUrl"
|
||||
$kioskUrl = "$KioskBaseUrl$($DisplayTypeTargets[$matchedKey])"
|
||||
|
||||
# Idempotent: if an Edge kiosk process is already serving this URL, leave it be
|
||||
# so an enforce cycle does not relaunch the kiosk every run.
|
||||
$alreadyRunning = Get-CimInstance Win32_Process -Filter "Name='msedge.exe'" -ErrorAction SilentlyContinue |
|
||||
Where-Object {{ $_.CommandLine -and $_.CommandLine.Contains($kioskUrl) }}
|
||||
if ($alreadyRunning) {{
|
||||
Write-Host 'Kiosk already running for this target; leaving it in place.'
|
||||
return
|
||||
# Resolve msedge.exe (baked into the display image).
|
||||
$edge = $null
|
||||
foreach ($p in @("${{env:ProgramFiles(x86)}}\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"$env:ProgramFiles\\Microsoft\\Edge\\Application\\msedge.exe")) {{
|
||||
if (Test-Path -LiteralPath $p) {{ $edge = $p; break }}
|
||||
}}
|
||||
if (-not $edge) {{ Write-Host 'msedge.exe not found; cannot write kiosk startup shortcut.'; return }}
|
||||
|
||||
$edgeArguments = @("--kiosk", $kioskUrl, "--edge-kiosk-type=fullscreen", "--no-first-run")
|
||||
Start-Process -FilePath 'msedge.exe' -ArgumentList $edgeArguments
|
||||
Write-Host 'Launched kiosk browser.'
|
||||
$startup = Join-Path $env:ProgramData 'Microsoft\\Windows\\Start Menu\\Programs\\Startup'
|
||||
if (-not (Test-Path $startup)) {{ New-Item -ItemType Directory -Path $startup -Force | Out-Null }}
|
||||
# Clear any prior ShopDB kiosk shortcut so a URL/type change self-heals.
|
||||
Get-ChildItem -LiteralPath $startup -Filter 'ShopDB Kiosk*.lnk' -ErrorAction SilentlyContinue |
|
||||
ForEach-Object {{ Remove-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue }}
|
||||
|
||||
$edgeArgs = "--kiosk `"$kioskUrl`" --edge-kiosk-type=fullscreen --no-first-run --no-default-browser-check"
|
||||
$lnkPath = Join-Path $startup 'ShopDB Kiosk.lnk'
|
||||
$shell = New-Object -ComObject WScript.Shell
|
||||
$lnk = $shell.CreateShortcut($lnkPath)
|
||||
$lnk.TargetPath = $edge
|
||||
$lnk.Arguments = $edgeArgs
|
||||
$lnk.WorkingDirectory = Split-Path -Parent $edge
|
||||
$lnk.WindowStyle = 3
|
||||
$lnk.Description = "ShopDB kiosk display: $kioskUrl"
|
||||
$lnk.Save()
|
||||
Write-Host "display-type '$displayType' -> kiosk Startup shortcut for $kioskUrl"
|
||||
"""
|
||||
|
||||
|
||||
ALWAYSON_FILENAME = 'Set-DisplayAlwaysOn.ps1'
|
||||
|
||||
|
||||
def build_alwayson_script():
|
||||
"""Return the inline always-on PowerShell as text.
|
||||
|
||||
Keeps a display awake 24/7 (kiosk page always visible): powercfg never-off on
|
||||
AC+DC, screensaver + auto-lock disabled. Plain raw string - no table
|
||||
interpolation, so no brace escaping.
|
||||
"""
|
||||
return r'''# Set-DisplayAlwaysOn.ps1 -- gea-shopfloor-display always-on.
|
||||
# Keeps the display awake 24/7 so the kiosk page stays visible: powercfg
|
||||
# never-off (AC+DC) + screensaver/auto-lock disabled. SYSTEM, every cycle.
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
foreach ($c in @(
|
||||
'monitor-timeout-ac 0','monitor-timeout-dc 0',
|
||||
'standby-timeout-ac 0','standby-timeout-dc 0',
|
||||
'hibernate-timeout-ac 0','hibernate-timeout-dc 0',
|
||||
'disk-timeout-ac 0','disk-timeout-dc 0')) {
|
||||
try { Start-Process -FilePath powercfg -ArgumentList "/change $c" -NoNewWindow -Wait -ErrorAction Stop } catch {}
|
||||
}
|
||||
|
||||
# disable screensaver + secure lock for future profiles (.DEFAULT) + loaded hives.
|
||||
if (-not (Get-PSDrive -Name HKU -ErrorAction SilentlyContinue)) {
|
||||
New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
$hives = @('HKU:\.DEFAULT')
|
||||
try {
|
||||
Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction Stop |
|
||||
Where-Object { $_.PSChildName -match '^S-1-5-21' -and $_.PSChildName -notmatch '_Classes$' } |
|
||||
ForEach-Object { $hives += "HKU:\$($_.PSChildName)" }
|
||||
} catch {}
|
||||
foreach ($h in $hives) {
|
||||
$cp = "$h\Control Panel\Desktop"
|
||||
try {
|
||||
if (-not (Test-Path $cp)) { New-Item -Path $cp -Force -ErrorAction Stop | Out-Null }
|
||||
Set-ItemProperty -Path $cp -Name ScreenSaveActive -Value '0' -ErrorAction Stop
|
||||
Set-ItemProperty -Path $cp -Name ScreenSaverIsSecure -Value '0' -ErrorAction SilentlyContinue
|
||||
Set-ItemProperty -Path $cp -Name ScreenSaveTimeOut -Value '0' -ErrorAction SilentlyContinue
|
||||
} catch {}
|
||||
}
|
||||
|
||||
# never auto-lock the kiosk console.
|
||||
try {
|
||||
$pol = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Personalization'
|
||||
if (-not (Test-Path $pol)) { New-Item -Path $pol -Force | Out-Null }
|
||||
Set-ItemProperty -Path $pol -Name NoLockScreen -Value 1 -Type DWord -ErrorAction SilentlyContinue
|
||||
} catch {}
|
||||
|
||||
Write-Host 'kiosk always-on enforced (power never-off + screensaver/lock disabled).'
|
||||
'''
|
||||
|
||||
|
||||
def _registry_drift_heal_entry(name, regname, regvalue, regtype, comment):
|
||||
"""One Type=Registry entry that writes a value and detects drift via
|
||||
ValueMatches against that same path/name.
|
||||
@@ -198,13 +274,16 @@ def build_display_manifest():
|
||||
{
|
||||
'_comment': (
|
||||
'Kiosk dispatcher. Reads C:\\Enrollment\\display-type.txt and '
|
||||
'launches the kiosk target for the subtype. Data-driven map: '
|
||||
'writes an all-users Startup shortcut that launches Edge kiosk '
|
||||
'fullscreen at the subtype target. Data-driven map: '
|
||||
+ _relaunch_window_targets_comment()
|
||||
+ '. 3DPrintRoom target is a PLACEHOLDER (/parts-kiosk); '
|
||||
'TODO-confirm the real target. Delivered inline over HTTPS '
|
||||
'(share-less displays); DetectionMethod Always so it re-asserts '
|
||||
'each cycle, but the script is idempotent (skips if the kiosk is '
|
||||
'already serving the target URL).'),
|
||||
+ '. Base URL from HKLM BaseUrl, else the WJ host. Runs as SYSTEM '
|
||||
'(no interactive desktop), so it does NOT Start-Process Edge '
|
||||
'(that opens invisibly in session 0) - the auto-login user runs '
|
||||
'the shortcut at logon in a visible session. Delivered inline over '
|
||||
'HTTPS (share-less displays); Always re-asserts, idempotent '
|
||||
'(rewrites only the resolved-target shortcut, self-heals a '
|
||||
'type/URL change).'),
|
||||
'Name': 'Display kiosk dispatcher (display-type.txt)',
|
||||
'Type': 'PS1',
|
||||
'Script': DISPATCHER_FILENAME,
|
||||
@@ -212,6 +291,19 @@ def build_display_manifest():
|
||||
'PayloadRef': DISPATCHER_FILENAME,
|
||||
'DetectionMethod': 'Always',
|
||||
},
|
||||
{
|
||||
'_comment': (
|
||||
'Keep the display awake 24/7 so the kiosk page stays visible: '
|
||||
'powercfg never-off (AC+DC) + screensaver/auto-lock disabled. '
|
||||
'Delivered inline over HTTPS; Always re-asserts + self-heals a '
|
||||
'power-plan drift each cycle.'),
|
||||
'Name': 'Display always-on (no sleep/blank/lock)',
|
||||
'Type': 'PS1',
|
||||
'Script': ALWAYSON_FILENAME,
|
||||
'PayloadSource': 'inline',
|
||||
'PayloadRef': ALWAYSON_FILENAME,
|
||||
'DetectionMethod': 'Always',
|
||||
},
|
||||
]
|
||||
return {
|
||||
'Version': SCOPE_VERSION,
|
||||
@@ -248,6 +340,13 @@ def seed_display_scope(publish=False, notes='seed gea-shopfloor-display'):
|
||||
dispatcher, DISPATCHER_FILENAME,
|
||||
'text/plain; charset=utf-8', scriptbytes)
|
||||
|
||||
alwayson = next(entry for entry in scope.entries
|
||||
if entry.name == 'Display always-on (no sleep/blank/lock)')
|
||||
alwaysonbytes = build_alwayson_script().encode('utf-8')
|
||||
alwaysonpayload = service.store_inline_payload(
|
||||
alwayson, ALWAYSON_FILENAME,
|
||||
'text/plain; charset=utf-8', alwaysonbytes)
|
||||
|
||||
publishedversion = None
|
||||
if publish:
|
||||
publishedversion = service.publish_scope(
|
||||
@@ -260,5 +359,6 @@ def seed_display_scope(publish=False, notes='seed gea-shopfloor-display'):
|
||||
'entrycount': len(scope.entries),
|
||||
'entrytypes': [entry.entrytype for entry in scope.entries],
|
||||
'dispatchersha256': payload.payloadsha256,
|
||||
'alwaysonsha256': alwaysonpayload.payloadsha256,
|
||||
'publishedversion': publishedversion,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user