diff --git a/server/README.md b/server/README.md index 178b8dc..48ccfe4 100644 --- a/server/README.md +++ b/server/README.md @@ -17,17 +17,18 @@ We only keep the basic user journey working. ## What we keep working - The prompt: `root@khaos-im-kiez:~#`, which `~/.zshrc` sets. -- The home directory the user ends up in: `/home/root`. They can delete it, but not change where it is. +- The home directory the user ends up in: `/home/root`. They cannot change where it is, and they cannot delete it. - `cat README`, `cat README.en`, `cat CODE_OF_CONDUCT` and `cat CODE_OF_CONDUCT.en` always work. The four files are always there and always regular text files. - The website can log in: the key it uses is in `~/.ssh/authorized_keys`. That key gets access to the `root` user only, never to the Administrator account. -Modifying the four files is allowed; the next cleanup undoes it. Deleting them is not permanent either. +Modifying the four files is allowed; the next cleanup undoes it. Deleting them is not possible: the home directory +and the files that have to stay in it belong to the administrators, so the user cannot remove them. The files the +user creates are their own; they may change and delete them. The user may add keys of their own to `authorized_keys`; they stay. The key of the website is put back, so that the -site keeps working. The user owns their home directory, so they can always delete the file - nothing on Windows can -stop them from that - but the next cleanup brings it back. Until then, the site cannot log in. +site keeps working. ## What we do not keep working @@ -46,7 +47,8 @@ administrators. Neither the setup nor the cleanup ever runs as the `root` user. It creates `/home/root` if it is gone, and puts the copies from `C:\ProgramData\kik-restore\files` back into the home directory: the shell configuration, the keys and the four text files. Anything that is not a regular file is -replaced, so that the files cannot become anything but text. +replaced, so that the files cannot become anything but text. It also sets the permissions of the home directory and +of those files, so that the user cannot delete them. ## Installing the cleanup on the server diff --git a/server/scripts/install_restore_task.ps1 b/server/scripts/install_restore_task.ps1 index 5d8b122..b2aafed 100644 --- a/server/scripts/install_restore_task.ps1 +++ b/server/scripts/install_restore_task.ps1 @@ -59,15 +59,44 @@ function Restore-Keys($target, $source) { [IO.File]::WriteAllText($target, (($keys + $required) -join "`n") + "`n") } +# The permissions of the home directory and of the files in it that have to stay: the user may read and write them +# 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) { + 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) + foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) } + $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 + $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 $user, $some, $none, $noPropagate, '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')) + } + Set-Acl -LiteralPath $path -AclObject $acl + } catch { + Write-Log "the permissions of $path are not set: $_" + } +} + # The home directory is the one from /etc/passwd, which the user cannot change, but which they can delete. A new one # 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 - # The parent directory gives the users read access only, so the owner gets write access on their own home. - if ($sid) { - icacls $path /grant "*${sid}:(OI)(CI)F" | Out-Null - icacls $path /setowner "*$sid" | Out-Null - } + Set-ProtectedAcl $path $true Get-ChildItem -LiteralPath 'C:\msys2\etc\skel' -Force | Copy-Item -Destination $path -Recurse -Force } @@ -78,6 +107,8 @@ if (-not (Test-Path -LiteralPath $HomeDir)) { Remove-Anything $HomeDir New-Home $HomeDir Write-Log "replaced the link $HomeDir" +} else { + Set-ProtectedAcl $HomeDir $true } foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) { @@ -88,10 +119,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 } - if ($sid) { - icacls $dir /grant "*${sid}:(OI)(CI)F" | Out-Null - icacls $dir /setowner "*$sid" | Out-Null - } + Set-ProtectedAcl $dir $true if (Test-Path -LiteralPath $target) { $item = Get-Item -LiteralPath $target -Force if ($item.PSIsContainer -or $item.Attributes -band [IO.FileAttributes]::ReparsePoint) { @@ -100,7 +128,6 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) { } else { # the user may have locked the file; taking it back lets us write it takeown /f $target /a | Out-Null - icacls $target /reset | Out-Null } } if ($copy.Name -eq 'authorized_keys') { @@ -108,7 +135,7 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) { } else { Copy-Item -LiteralPath $copy.FullName -Destination $target -Force } - if ($sid) { icacls $target /setowner "*$sid" | Out-Null } + Set-ProtectedAcl $target $false Write-Log "restored $($copy.Name)" } catch { Write-Log "failed $($copy.Name) : $_"