Let the shell user install packages with pacman
The root user now owns the whole MSYS2 installation and can run pacman. The login path the journey depends on - the sshd, the login shell zsh and cat - is kept as a protected copy in C:\msys2\opt\kik that only SYSTEM and the administrators may write, and the sshd runs as SYSTEM from there (the new KiK sshd task; the msys2-sshd service is disabled). Replacing the user's own /usr/bin/sshd therefore cannot touch the running server. opt/kik is two levels below C:\msys2, so its runtime resolves the same installation root and the login shell and the user's tools share one cygwin runtime. A separate second installation does not work: two runtimes in one process tree corrupt each other's file descriptors and pacman cannot lock its database. /etc/passwd and /etc/ssh are read-only to the user, /opt/kik is first on PATH so cat README survives even if the user deletes their own /usr/bin/cat, and the restore task rebuilds the protected copy and re-applies the permissions every 30 minutes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ea0d6a85fd
commit
b228fec07e
3 changed files with 244 additions and 52 deletions
|
|
@ -32,14 +32,32 @@ Anything thats not specified above (like the content of README for example) is N
|
|||
|
||||
As long as the user story above does not break, we do not guarantee the functionality of anything else on the server. However the cleanup task should still try to best-effort restore everything to a working condition.
|
||||
|
||||
## The cleanup task
|
||||
## Packages
|
||||
|
||||
The scheduled task `KiK restore shell files` runs every 30 minutes and at every startup, as SYSTEM. The user cannot stop it. Cleanup has two jobs:
|
||||
The root user owns the whole MSYS2 installation and can install packages with pacman. That means they can also
|
||||
replace or delete the very programs the login needs. So the sshd, the login shell `zsh` and `cat` are kept as a
|
||||
second, protected copy in `C:\msys2\opt\kik`, which only SYSTEM and the administrators may write. The sshd runs as
|
||||
SYSTEM from that copy and the login shell is that copy's `zsh`, with `/opt/kik` first on the path, so the journey
|
||||
keeps working no matter what the user does to their own `/usr/bin`. Replacing `/usr/bin/sshd` cannot touch the
|
||||
running server, so the key on the website still never grants administrator access.
|
||||
|
||||
1. Act as a secondary safeguard to restore the essential files for the user journey above.
|
||||
2. Revert any other mockery the user did that might affect the system.
|
||||
The copy has to be the *same* MSYS2 installation, not a second one: `C:\msys2\opt\kik` is two levels below
|
||||
`C:\msys2`, so its runtime resolves the same root and the login shell and the user's own tools share one cygwin
|
||||
runtime. A separate installation does not work - two runtimes in one process tree corrupt each other's file
|
||||
descriptors and pacman cannot even lock its database.
|
||||
|
||||
The second job should also be able to restore a new server to the expected state.
|
||||
`/etc/passwd` (the login shell and home directory) and `/etc/ssh` (the host keys and config) are read-only to the
|
||||
user as well; everything else in the installation is theirs.
|
||||
|
||||
## The cleanup tasks
|
||||
|
||||
Two scheduled tasks run as SYSTEM, both at startup, and the user cannot stop either:
|
||||
|
||||
- `KiK sshd` runs the sshd from `C:\msys2\opt\kik`. It replaces the installation's own `msys2-sshd` service, which
|
||||
is disabled.
|
||||
- `KiK restore shell files` runs every 30 minutes and rebuilds the protected copy from its trusted originals,
|
||||
re-applies the permissions, and puts the home directory, the shell configuration, the website's key and the four
|
||||
texts back. Its second job is to best-effort revert other mockery and bring a fresh server to the expected state.
|
||||
|
||||
The files that have to stay belong to the administrators, so the user cannot delete them, and they cannot change the
|
||||
shell configuration or the key the website logs in with either. The shell reads its configuration from `~/.zsh`, so
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# The protected copies of zsh and cat (and the server itself) live here; keeping them first means the basic
|
||||
# journey - the prompt and reading the four texts - survives even if the user breaks their own /usr/bin copies.
|
||||
path=(/opt/kik $path)
|
||||
|
||||
# History shared between sessions.
|
||||
HISTFILE=~/.zsh_history HISTSIZE=10000 SAVEHIST=10000
|
||||
setopt share_history hist_ignore_dups hist_ignore_space interactive_comments auto_cd
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
# Installs the scheduled task that puts the shell server's home directory back into shape every 30 minutes: the
|
||||
# prompt, the home directory and the four text files. The task runs as SYSTEM, so the shell server's root user can
|
||||
# neither stop it nor change the copies it restores from.
|
||||
# Sets up the shell server so the website's root user can install packages with pacman, while the basic user
|
||||
# journey - logging in with the key, the prompt, the home directory and the four text files - cannot be broken.
|
||||
#
|
||||
# The whole MSYS2 installation belongs to the root user: they may write everywhere and run pacman. The login path
|
||||
# the journey depends on - the sshd, the login shell zsh and cat - is kept as a protected copy in C:\msys2\opt\kik
|
||||
# that only SYSTEM and the administrators may write. The sshd runs as SYSTEM from that copy (the scheduled task
|
||||
# "KiK sshd"), so replacing /usr/bin/sshd does not touch the running server. A second scheduled task,
|
||||
# "KiK restore shell files", puts the home directory, the four texts and the protected copy back every 30 minutes.
|
||||
#
|
||||
# opt/kik works because its MSYS2 runtime resolves the same installation root (C:\msys2) as /usr/bin, so the login
|
||||
# shell and the user's own tools share one cygwin runtime. A separate second installation cannot: two runtimes in
|
||||
# one process tree break each other's file descriptors, and pacman then cannot even lock its database.
|
||||
#
|
||||
# Run this in an elevated PowerShell on the server, with this script and the ../resources directory next to it:
|
||||
# powershell -ExecutionPolicy Bypass -File .\install_restore_task.ps1
|
||||
# Remove the task and the copies again with:
|
||||
# Remove the tasks and the protected copy again (the installation goes back to a normal cygrunsrv sshd) with:
|
||||
# powershell -ExecutionPolicy Bypass -File .\install_restore_task.ps1 -Uninstall
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
|
|
@ -13,24 +22,40 @@ param(
|
|||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$msys = 'C:\msys2'
|
||||
$kik = "$msys\opt\kik"
|
||||
$installDir = 'C:\ProgramData\kik-restore'
|
||||
$copiesDir = Join-Path $installDir 'files'
|
||||
$taskName = 'KiK restore shell files'
|
||||
$binDir = Join-Path $installDir 'bin'
|
||||
$sshdBat = Join-Path $installDir 'run-sshd.bat'
|
||||
$restoreTask = 'KiK restore shell files'
|
||||
$sshdTask = 'KiK sshd'
|
||||
$service = 'msys2-sshd'
|
||||
|
||||
# The programs the journey needs, kept out of the user's reach. sshd-session and sshd-auth are the per-connection
|
||||
# helpers OpenSSH 10 re-executes; zsh is the login shell; cat reads the four texts.
|
||||
$loginBinaries = "$msys\usr\bin\sshd.exe", "$msys\usr\lib\ssh\sshd-session.exe", "$msys\usr\lib\ssh\sshd-auth.exe",
|
||||
"$msys\usr\bin\zsh.exe", "$msys\usr\bin\cat.exe"
|
||||
|
||||
# The copies the task restores from and the script it runs both live in $installDir, which only SYSTEM and the
|
||||
# administrators may read or write.
|
||||
$restoreScript = @'
|
||||
# Puts the shell server's home directory back into shape: the directory itself, the shell configuration that gives
|
||||
# the prompt, the keys that let the website in, and the four text files. Runs as SYSTEM every 30 minutes; the shell
|
||||
# server's root user can neither stop it nor change the copies it restores from.
|
||||
# Puts the shell server back into shape and keeps the protected login path in place. Runs as SYSTEM every 30 minutes
|
||||
# and at startup; the root user can neither stop it nor change the copies it restores from.
|
||||
param([string]$HomeDir = 'C:\msys2\home\root')
|
||||
$msys = 'C:\msys2'
|
||||
$kik = "$msys\opt\kik"
|
||||
$log = Join-Path $PSScriptRoot 'restore.log'
|
||||
$copies = Join-Path $PSScriptRoot 'files\home'
|
||||
$binDir = Join-Path $PSScriptRoot 'bin'
|
||||
|
||||
# The account of the shell server's root user, whose id the files in its home directory belong to.
|
||||
$passwd = Get-Content 'C:\msys2\etc\passwd' -ErrorAction SilentlyContinue | Where-Object { $_ -like 'root:*' }
|
||||
$passwd = Get-Content "$msys\etc\passwd" -ErrorAction SilentlyContinue | Where-Object { $_ -like 'root:*' }
|
||||
$sid = if ($passwd) { ($passwd -split ':')[4].Split(',')[-1] }
|
||||
|
||||
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
|
||||
$admins = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
|
||||
|
||||
function Write-Log($message) {
|
||||
"$(Get-Date -Format s) $message" | Out-File -FilePath $log -Append
|
||||
}
|
||||
|
|
@ -47,6 +72,23 @@ function Remove-Anything($path) {
|
|||
}
|
||||
}
|
||||
|
||||
# A path that only SYSTEM and the administrators own. $userRights is what the root user may do: 'None' (the ssh keys
|
||||
# and config), 'Read' (the account database) or 'ReadAndExecute' (the login programs it has to run).
|
||||
function Set-LockedAcl($path, $forDirectory, $userRights) {
|
||||
$acl = Get-Acl -LiteralPath $path
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) }
|
||||
$acl.SetOwner($admins)
|
||||
$inherit = if ($forDirectory) { 'ContainerInherit,ObjectInherit' } else { 'None' }
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($system, 'FullControl', $inherit, 'None', 'Allow')))
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($admins, 'FullControl', $inherit, 'None', 'Allow')))
|
||||
if ($userRights -ne 'None' -and $sid) {
|
||||
$user = New-Object System.Security.Principal.SecurityIdentifier($sid)
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($user, $userRights, $inherit, 'None', 'Allow')))
|
||||
}
|
||||
Set-Acl -LiteralPath $path -AclObject $acl
|
||||
}
|
||||
|
||||
# The website logs in with a key in the authorized_keys of the user. The user may keep keys of their own, so only
|
||||
# the key of the website is put back.
|
||||
function Restore-Keys($target, $source) {
|
||||
|
|
@ -65,11 +107,9 @@ $readOnly = '.zshrc', '.zshenv', 'authorized_keys'
|
|||
# The permissions of the home directory and of the files in it that have to stay: the user may read them and write
|
||||
# the ones that may change, and delete their own files, but not the home directory itself and not the files the
|
||||
# website needs. Only SYSTEM and the administrators own them, so the user cannot take the permissions away again.
|
||||
function Set-ProtectedAcl($path, $forDirectory, $writable) {
|
||||
function Set-HomeAcl($path, $forDirectory, $writable) {
|
||||
if (-not $sid) { return }
|
||||
try {
|
||||
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
|
||||
$admins = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
|
||||
$user = New-Object System.Security.Principal.SecurityIdentifier($sid)
|
||||
$acl = Get-Acl -LiteralPath $path
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
|
|
@ -77,16 +117,15 @@ function Set-ProtectedAcl($path, $forDirectory, $writable) {
|
|||
$acl.SetOwner($admins)
|
||||
$none = [System.Security.AccessControl.InheritanceFlags]::None
|
||||
$children = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
|
||||
$noPropagate = [System.Security.AccessControl.PropagationFlags]::None
|
||||
$inheritOnly = [System.Security.AccessControl.PropagationFlags]::InheritOnly
|
||||
$full = [System.Security.AccessControl.FileSystemRights]::FullControl
|
||||
$some = [System.Security.AccessControl.FileSystemRights]::ReadAndExecute -bor [System.Security.AccessControl.FileSystemRights]::Write
|
||||
$read = [System.Security.AccessControl.FileSystemRights]::ReadAndExecute
|
||||
$inherit = if ($forDirectory) { $children } else { $none }
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $system, $full, $inherit, $noPropagate, 'Allow'))
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $admins, $full, $inherit, $noPropagate, 'Allow'))
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $system, $full, $inherit, 'None', 'Allow'))
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $admins, $full, $inherit, 'None', 'Allow'))
|
||||
$userRights = if ($writable) { $some } else { $read }
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $userRights, $none, $noPropagate, 'Allow'))
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $userRights, $none, 'None', 'Allow'))
|
||||
if ($forDirectory) {
|
||||
# the files in it are the user's own, they may delete them
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $full, $children, $inheritOnly, 'Allow'))
|
||||
|
|
@ -101,8 +140,46 @@ function Set-ProtectedAcl($path, $forDirectory, $writable) {
|
|||
# gets MSYS2's default files from /etc/skel, which MSYS2 itself only copies into a home that does not exist yet.
|
||||
function New-Home($path) {
|
||||
New-Item -ItemType Directory -Path $path | Out-Null
|
||||
Set-ProtectedAcl $path $true $true
|
||||
Get-ChildItem -LiteralPath 'C:\msys2\etc\skel' -Force | Copy-Item -Destination $path -Recurse -Force
|
||||
Set-HomeAcl $path $true $true
|
||||
Get-ChildItem -LiteralPath "$msys\etc\skel" -Force | Copy-Item -Destination $path -Recurse -Force
|
||||
}
|
||||
|
||||
try {
|
||||
# The protected login path: the copy the sshd and the login shell run from. The user cannot write it, so even
|
||||
# after they replace or remove their own copies in /usr/bin the server and the journey keep working. The trusted
|
||||
# originals are in bin\; anything missing from opt/kik is put back.
|
||||
if (Test-Path -LiteralPath $binDir) {
|
||||
if (-not (Test-Path -LiteralPath $kik)) { New-Item -ItemType Directory -Path $kik | Out-Null }
|
||||
foreach ($file in Get-ChildItem -LiteralPath $binDir -File -Force) {
|
||||
$target = Join-Path $kik $file.Name
|
||||
if (-not (Test-Path -LiteralPath $target) -or (Get-Item -LiteralPath $target -Force).Length -ne $file.Length) {
|
||||
Copy-Item -LiteralPath $file.FullName -Destination $target -Force
|
||||
Write-Log "restored opt/kik/$($file.Name)"
|
||||
}
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path "$kik\dev\shm", "$kik\dev\mqueue" | Out-Null
|
||||
Set-LockedAcl $kik $true 'ReadAndExecute'
|
||||
# /dev is scratch: the user may write it so POSIX shared memory works, the rest of opt/kik stays read-only
|
||||
if ($sid) { icacls "$kik\dev" /grant "*${sid}:(OI)(CI)M" | Out-Null }
|
||||
}
|
||||
|
||||
# The account database and the ssh keys and config the SYSTEM server reads: the user may read passwd (programs
|
||||
# map ids through it) but change none of it, and has no business in /etc/ssh at all.
|
||||
$rootShell = '/opt/kik/zsh.exe'
|
||||
$pw = "$msys\etc\passwd"
|
||||
$lines = Get-Content $pw
|
||||
if ($lines | Where-Object { $_ -match '^root:' -and $_ -notmatch ':/opt/kik/zsh\.exe$' }) {
|
||||
($lines | ForEach-Object { if ($_ -match '^root:') { $_ -replace ':[^:]*$', ":$rootShell" } else { $_ } }) | Set-Content $pw -Encoding ascii
|
||||
Write-Log 'set the root login shell to the protected zsh'
|
||||
}
|
||||
Set-LockedAcl $pw $false 'Read'
|
||||
if (Test-Path "$msys\etc\ssh") { Set-LockedAcl "$msys\etc\ssh" $true 'None' }
|
||||
|
||||
# The whole installation otherwise belongs to the user, so pacman can write it. The home directory keeps its own
|
||||
# protected permissions because they are set without inheritance.
|
||||
if ($sid) { icacls $msys /grant "*${sid}:(OI)(CI)F" | Out-Null }
|
||||
} catch {
|
||||
Write-Log "the protected login path is not fully in place: $_"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $HomeDir)) {
|
||||
|
|
@ -113,7 +190,7 @@ if (-not (Test-Path -LiteralPath $HomeDir)) {
|
|||
New-Home $HomeDir
|
||||
Write-Log "replaced the link $HomeDir"
|
||||
} else {
|
||||
Set-ProtectedAcl $HomeDir $true $true
|
||||
Set-HomeAcl $HomeDir $true $true
|
||||
}
|
||||
|
||||
foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
|
||||
|
|
@ -124,7 +201,7 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
|
|||
Remove-Anything $dir
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
|
||||
Set-ProtectedAcl $dir $true $true
|
||||
Set-HomeAcl $dir $true $true
|
||||
if (Test-Path -LiteralPath $target) {
|
||||
$item = Get-Item -LiteralPath $target -Force
|
||||
if ($item.PSIsContainer -or $item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
||||
|
|
@ -140,7 +217,7 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
|
|||
} else {
|
||||
Copy-Item -LiteralPath $copy.FullName -Destination $target -Force
|
||||
}
|
||||
Set-ProtectedAcl $target $false ($readOnly -notcontains $copy.Name)
|
||||
Set-HomeAcl $target $false ($readOnly -notcontains $copy.Name)
|
||||
# the user may have made the file read-only for themselves, the four texts are theirs to change
|
||||
(Get-Item -LiteralPath $target -Force).IsReadOnly = $false
|
||||
Write-Log "restored $($copy.Name)"
|
||||
|
|
@ -154,39 +231,90 @@ if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdent
|
|||
throw 'Run this in an elevated PowerShell.'
|
||||
}
|
||||
|
||||
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
|
||||
$admins = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
|
||||
|
||||
$passwd = Get-Content "$msys\etc\passwd" -ErrorAction SilentlyContinue | Where-Object { $_ -like 'root:*' }
|
||||
if (-not $passwd) { throw "No root user in $msys\etc\passwd." }
|
||||
$sid = ($passwd -split ':')[4].Split(',')[-1]
|
||||
|
||||
# A previous install may have locked opt/kik, the account database and the ssh keys. The administrators keep
|
||||
# full control over them, so reset the permissions to inherited now and let this run write freely; the restore
|
||||
# task locks everything again at the end.
|
||||
$prevEA = $ErrorActionPreference; $ErrorActionPreference = 'SilentlyContinue'
|
||||
foreach ($p in "$msys\opt\kik", "$msys\etc\ssh", "$msys\etc\passwd") {
|
||||
if (Test-Path -LiteralPath $p) { icacls $p /reset /t /q | Out-Null }
|
||||
}
|
||||
Get-ChildItem "$msys\etc\ssh", "$msys\etc\passwd" -Force | ForEach-Object { try { $_.IsReadOnly = $false } catch {} }
|
||||
$ErrorActionPreference = $prevEA
|
||||
|
||||
function Set-LockedAcl($path, $forDirectory, $userRights) {
|
||||
$acl = Get-Acl -LiteralPath $path
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) }
|
||||
$acl.SetOwner($admins)
|
||||
$inherit = if ($forDirectory) { 'ContainerInherit,ObjectInherit' } else { 'None' }
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($system, 'FullControl', $inherit, 'None', 'Allow')))
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($admins, 'FullControl', $inherit, 'None', 'Allow')))
|
||||
if ($userRights -ne 'None') {
|
||||
$user = New-Object System.Security.Principal.SecurityIdentifier($sid)
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($user, $userRights, $inherit, 'None', 'Allow')))
|
||||
}
|
||||
Set-Acl -LiteralPath $path -AclObject $acl
|
||||
}
|
||||
|
||||
# The DLLs an MSYS2 program loads, as Windows paths under C:\msys2.
|
||||
function Get-Libraries($program) {
|
||||
$paths = @()
|
||||
$posix = ($program -replace '^C:\\msys2', '') -replace '\\', '/'
|
||||
foreach ($line in & "$msys\usr\bin\ldd.exe" $posix 2>$null) {
|
||||
foreach ($m in [regex]::Matches($line, '/usr/[^\s]+\.dll')) { $paths += $m.Value }
|
||||
}
|
||||
$paths | Select-Object -Unique | ForEach-Object { $msys + ($_ -replace '/', '\') }
|
||||
}
|
||||
|
||||
if ($Uninstall) {
|
||||
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||
foreach ($t in $restoreTask, $sshdTask) { Unregister-ScheduledTask -TaskName $t -Confirm:$false -ErrorAction SilentlyContinue }
|
||||
if (Test-Path -LiteralPath $installDir) { Remove-Item -LiteralPath $installDir -Recurse -Force }
|
||||
Write-Host "Removed the task and $installDir."
|
||||
if (Test-Path -LiteralPath $kik) {
|
||||
icacls $kik /reset /t /q 2>$null | Out-Null
|
||||
Remove-Item -LiteralPath $kik -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
# the installation's own cygrunsrv sshd takes over again
|
||||
$pw = "$msys\etc\passwd"
|
||||
(Get-Content $pw | ForEach-Object { if ($_ -match '^root:') { $_ -replace ':[^:]*$', ':/usr/bin/zsh' } else { $_ } }) | Set-Content $pw -Encoding ascii
|
||||
icacls $pw /reset | Out-Null
|
||||
if (Test-Path "$msys\etc\ssh") { icacls "$msys\etc\ssh" /reset /t /q 2>$null | Out-Null }
|
||||
Set-Service $service -StartupType Automatic -ErrorAction SilentlyContinue
|
||||
Start-Service $service -ErrorAction SilentlyContinue
|
||||
Write-Host "Removed the tasks and $installDir; the $service service serves ssh again."
|
||||
return
|
||||
}
|
||||
|
||||
$resources = Join-Path $PSScriptRoot '..\resources'
|
||||
if (-not (Get-ChildItem -LiteralPath $resources -Recurse -File -Force)) { throw "No files in $resources." }
|
||||
|
||||
# The root user may have prepared the directory or the file names, so nothing that looks like a link is used.
|
||||
$prepared = Get-Item -LiteralPath $installDir -Force -ErrorAction SilentlyContinue
|
||||
if ($prepared -and $prepared.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw "$installDir is a link, refusing to install." }
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $copiesDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $copiesDir, $binDir | Out-Null
|
||||
|
||||
# Only SYSTEM and the administrators may read or write the copies and the script.
|
||||
# Only SYSTEM and the administrators may read or write the copies, the trusted login programs and the scripts.
|
||||
$acl = New-Object System.Security.AccessControl.DirectorySecurity
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
foreach ($sid in 'S-1-5-18', 'S-1-5-32-544') {
|
||||
$identity = New-Object System.Security.Principal.SecurityIdentifier $sid
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $identity, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow'))
|
||||
foreach ($owner in $system, $admins) {
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($owner, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')))
|
||||
}
|
||||
$acl.SetOwner((New-Object System.Security.Principal.SecurityIdentifier 'S-1-5-32-544'))
|
||||
$acl.SetOwner($admins)
|
||||
Set-Acl -LiteralPath $installDir -AclObject $acl
|
||||
|
||||
# Files the root user prepared are removed, the copies are written fresh and inherit the restricted permissions.
|
||||
foreach ($item in Get-ChildItem -LiteralPath $copiesDir -Recurse -Force) {
|
||||
foreach ($item in Get-ChildItem -LiteralPath $installDir -Recurse -Force) {
|
||||
if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw "$($item.FullName) is a link, refusing to install." }
|
||||
}
|
||||
Remove-Item -LiteralPath $copiesDir -Recurse -Force
|
||||
New-Item -ItemType Directory -Force -Path $copiesDir | Out-Null
|
||||
Copy-Item -Path (Join-Path $resources '*') -Destination $copiesDir -Recurse -Force
|
||||
|
||||
# The Administrator logs in with the key of the people who deploy, which is in the resources. The keys that are
|
||||
# already there stay.
|
||||
$adminKeys = 'C:\Users\Administrator\.ssh\authorized_keys'
|
||||
|
|
@ -199,23 +327,65 @@ if (-not ($keys | Where-Object { ($_ -split '\s+')[1] -eq ($required[0] -split '
|
|||
}
|
||||
[IO.File]::WriteAllText((Join-Path $installDir 'restore.ps1'), $restoreScript)
|
||||
|
||||
# The shell configuration used to be in the home directory, the shell reads it from ~/.zsh now.
|
||||
if (Test-Path -LiteralPath (Join-Path $HomeDir '.zsh\.zshrc')) {
|
||||
Remove-Item -LiteralPath (Join-Path $HomeDir '.zshrc') -Force -ErrorAction SilentlyContinue
|
||||
# The trusted login programs: the sshd, its helpers, the login shell and cat, with every DLL they load. The restore
|
||||
# task puts these into opt/kik, which the user cannot write.
|
||||
Remove-Item "$binDir\*" -Force -ErrorAction SilentlyContinue
|
||||
$files = @{}
|
||||
foreach ($b in $loginBinaries) {
|
||||
if (-not (Test-Path -LiteralPath $b)) { throw "$b does not exist." }
|
||||
$files[$b] = $true
|
||||
foreach ($lib in Get-Libraries $b) { $files[$lib] = $true }
|
||||
}
|
||||
foreach ($f in $files.Keys) { Copy-Item -LiteralPath $f -Destination $binDir -Force }
|
||||
|
||||
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$installDir\restore.ps1`" -HomeDir `"$HomeDir`""
|
||||
$triggers = @(
|
||||
# The sshd reads its configuration and finds its helpers in opt/kik; nothing here depends on the user's /usr.
|
||||
$sshdConfig = "$msys\etc\ssh\sshd_config"
|
||||
Set-Content -LiteralPath $sshdConfig -Encoding ascii -Value @(
|
||||
'# The website logs in as root, the deployers as Administrator. Both come in over the websocket bridge.',
|
||||
'Port 22',
|
||||
'AllowUsers root Administrator',
|
||||
'# Files are protected by Windows ACLs; under MSYS2''s noacl mounts the POSIX modes sshd checks are fake.',
|
||||
'StrictModes no',
|
||||
'Subsystem sftp internal-sftp',
|
||||
'# The server and its per-connection helpers are the protected copies, not the ones the user can replace.',
|
||||
'SshdSessionPath /opt/kik/sshd-session.exe',
|
||||
'SshdAuthPath /opt/kik/sshd-auth.exe'
|
||||
)
|
||||
|
||||
# The sshd runs as SYSTEM from the protected copy, with opt/kik first on the path so its own DLLs and the session's
|
||||
# programs (zsh, cat) are the protected ones. The log stays in the install directory, out of the user's reach.
|
||||
Set-Content -LiteralPath $sshdBat -Encoding ascii -Value @(
|
||||
'@echo off',
|
||||
'set PATH=C:\msys2\opt\kik;C:\msys2\usr\bin;%PATH%',
|
||||
'C:\msys2\opt\kik\sshd.exe -D -e > C:\ProgramData\kik-restore\sshd.log 2>&1'
|
||||
)
|
||||
|
||||
# The installation's own cygrunsrv sshd service would fight for port 22; the scheduled task owns the sshd instead.
|
||||
Set-Service $service -StartupType Disabled -ErrorAction SilentlyContinue
|
||||
Stop-Service $service -Force -ErrorAction SilentlyContinue
|
||||
|
||||
$atSystem = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
|
||||
|
||||
$sshdAction = New-ScheduledTaskAction -Execute $sshdBat
|
||||
$sshdSettings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Seconds 0) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||
Register-ScheduledTask -TaskName $sshdTask -Action $sshdAction -Trigger (New-ScheduledTaskTrigger -AtStartup) -Principal $atSystem -Settings $sshdSettings -Force | Out-Null
|
||||
|
||||
$restoreAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$installDir\restore.ps1`" -HomeDir `"$HomeDir`""
|
||||
$restoreTriggers = @(
|
||||
New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 30) -RepetitionDuration (New-TimeSpan -Days 3650)
|
||||
New-ScheduledTaskTrigger -AtStartup
|
||||
)
|
||||
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Minutes 10) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 5) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
|
||||
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $triggers -Principal $principal -Settings $settings -Force | Out-Null
|
||||
$restoreSettings = New-ScheduledTaskSettingsSet -StartWhenAvailable -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Minutes 10) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 5) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||
Register-ScheduledTask -TaskName $restoreTask -Action $restoreAction -Trigger $restoreTriggers -Principal $atSystem -Settings $restoreSettings -Force | Out-Null
|
||||
|
||||
Start-ScheduledTask -TaskName $taskName
|
||||
Start-Sleep -Seconds 5
|
||||
Write-Host "Task '$taskName' runs as SYSTEM every 30 minutes and at startup."
|
||||
Get-ScheduledTaskInfo -TaskName $taskName | Format-List LastRunTime, LastTaskResult, NextRunTime
|
||||
Get-Content (Join-Path $installDir 'restore.log') -Tail 8 -ErrorAction SilentlyContinue
|
||||
icacls $installDir
|
||||
# The restore task builds opt/kik and sets every permission; run it before starting the sshd that depends on it.
|
||||
Start-ScheduledTask -TaskName $restoreTask
|
||||
Start-Sleep -Seconds 6
|
||||
$waited = 0
|
||||
while ((Get-ScheduledTask -TaskName $restoreTask).State -eq 'Running' -and $waited -lt 60) { Start-Sleep 2; $waited += 2 }
|
||||
Start-ScheduledTask -TaskName $sshdTask
|
||||
Start-Sleep -Seconds 4
|
||||
|
||||
Write-Host "The sshd runs as SYSTEM from C:\msys2\opt\kik (task '$sshdTask'); '$restoreTask' keeps it and the home directory in shape."
|
||||
Get-Content (Join-Path $installDir 'restore.log') -Tail 10 -ErrorAction SilentlyContinue
|
||||
Get-CimInstance Win32_Process -Filter "Name='sshd.exe'" | ForEach-Object { $_.CommandLine }
|
||||
|
|
|
|||
Loading…
Reference in a new issue