Keep the shell configuration and the key out of the user's reach
The user could empty the authorized_keys and break the login of the website, change the shell configuration to break the prompt, and write startup files that zsh reads on every login. The shell configuration and the key are now read-only for the user, and the shell reads its configuration from ~/.zsh, so the files in the home directory are ignored. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e4050cbf05
commit
49c61b6b91
4 changed files with 28 additions and 10 deletions
|
|
@ -19,7 +19,7 @@ We only keep the basic user journey working.
|
|||
Anything that breaks the following user journey is not acceptable:
|
||||
|
||||
- The user can connect with the SSH key as `root`.
|
||||
- The prompt: `root@khaos-im-kiez:~#`, which `~/.zshrc` sets.
|
||||
- The prompt: `root@khaos-im-kiez:~#`, which `~/.zsh/.zshrc` sets.
|
||||
- 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.
|
||||
|
|
@ -41,6 +41,10 @@ The scheduled task `KiK restore shell files` runs every 30 minutes and at every
|
|||
|
||||
The second job should also be able to restore a new 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
|
||||
the startup files the user could write in the home directory are ignored.
|
||||
|
||||
## Installing the cleanup on the server
|
||||
|
||||
To deploy the cleanup task an the server, run:
|
||||
|
|
|
|||
36
server/resources/home/.zsh/.zshrc
Normal file
36
server/resources/home/.zsh/.zshrc
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# History shared between sessions.
|
||||
HISTFILE=~/.zsh_history HISTSIZE=10000 SAVEHIST=10000
|
||||
setopt share_history hist_ignore_dups hist_ignore_space interactive_comments auto_cd
|
||||
|
||||
# Completion: menu, case-insensitive, colored. -C skips compaudit, which is slow on
|
||||
# Windows and meaningless under MSYS2's noacl mounts (rm ~/.zcompdump after adding completions).
|
||||
autoload -Uz compinit && compinit -C
|
||||
zstyle ':completion:*' menu select
|
||||
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
|
||||
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
|
||||
|
||||
# Emacs keys; Up/Down search history for what is already typed.
|
||||
bindkey -e
|
||||
autoload -Uz up-line-or-beginning-search down-line-or-beginning-search
|
||||
zle -N up-line-or-beginning-search && zle -N down-line-or-beginning-search
|
||||
bindkey '^[[A' up-line-or-beginning-search '^[OA' up-line-or-beginning-search
|
||||
bindkey '^[[B' down-line-or-beginning-search '^[OB' down-line-or-beginning-search
|
||||
|
||||
# Prompt: user@host:directory, git branch, exit status of the last command if it failed.
|
||||
# A literal '#' as on Linux: zsh's %# would show '%', since this root is not uid 0.
|
||||
# The branch is read from .git/HEAD directly: starting git on every prompt is slow on Windows.
|
||||
precmd() {
|
||||
local d=$PWD head=
|
||||
until [[ -e $d/.git || $d == / ]]; do d=${d:h}; done
|
||||
[[ -f $d/.git/HEAD ]] && read -r head < $d/.git/HEAD
|
||||
[[ $head == 'ref: '* ]] && git_branch=${head#ref: refs/heads/} || git_branch=${head:0:7}
|
||||
print -Pn '\e]0;%n@%m: %~\a'
|
||||
}
|
||||
setopt prompt_subst
|
||||
PROMPT='%F{green}%n@%m%f:%F{blue}%~%f${git_branch:+ %5F($git_branch)%f}%(?.. %F{red}[%?]%f)# '
|
||||
|
||||
alias ls='ls --color=auto' ll='ls -lah' grep='grep --color=auto'
|
||||
|
||||
# Process monitor: htop does not exist for MSYS2 (its /proc only shows MSYS2 processes).
|
||||
# bottom is a native Windows program; winpty lets it draw in the pty, as Server 2016 has no ConPTY.
|
||||
alias btm='winpty /ucrt64/bin/btm'
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
# Preset what MSYS2's /etc/profile would otherwise start a helper process for (~90 ms each here).
|
||||
# TZ has to match the Windows timezone (W. Europe Standard Time).
|
||||
export LANG=en_US.UTF-8 TZ=Europe/Berlin
|
||||
|
||||
# The shell reads its configuration from here, not from the home directory: the user may not change it, and the
|
||||
# files in the home directory that zsh would read otherwise (.zprofile, .zlogin) are ignored.
|
||||
export ZDOTDIR=~/.zsh
|
||||
|
|
|
|||
|
|
@ -59,10 +59,13 @@ 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) {
|
||||
# The shell configuration and the key the website logs in with must not change, the four texts may be edited.
|
||||
$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) {
|
||||
if (-not $sid) { return }
|
||||
try {
|
||||
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
|
||||
|
|
@ -78,10 +81,12 @@ function Set-ProtectedAcl($path, $forDirectory) {
|
|||
$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 $user, $some, $none, $noPropagate, 'Allow'))
|
||||
$userRights = if ($writable) { $some } else { $read }
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $userRights, $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'))
|
||||
|
|
@ -96,7 +101,7 @@ function Set-ProtectedAcl($path, $forDirectory) {
|
|||
# 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
|
||||
Set-ProtectedAcl $path $true $true
|
||||
Get-ChildItem -LiteralPath 'C:\msys2\etc\skel' -Force | Copy-Item -Destination $path -Recurse -Force
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +113,7 @@ if (-not (Test-Path -LiteralPath $HomeDir)) {
|
|||
New-Home $HomeDir
|
||||
Write-Log "replaced the link $HomeDir"
|
||||
} else {
|
||||
Set-ProtectedAcl $HomeDir $true
|
||||
Set-ProtectedAcl $HomeDir $true $true
|
||||
}
|
||||
|
||||
foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
|
||||
|
|
@ -119,7 +124,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
|
||||
Set-ProtectedAcl $dir $true $true
|
||||
if (Test-Path -LiteralPath $target) {
|
||||
$item = Get-Item -LiteralPath $target -Force
|
||||
if ($item.PSIsContainer -or $item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
||||
|
|
@ -135,7 +140,7 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
|
|||
} else {
|
||||
Copy-Item -LiteralPath $copy.FullName -Destination $target -Force
|
||||
}
|
||||
Set-ProtectedAcl $target $false
|
||||
Set-ProtectedAcl $target $false ($readOnly -notcontains $copy.Name)
|
||||
Write-Log "restored $($copy.Name)"
|
||||
} catch {
|
||||
Write-Log "failed $($copy.Name) : $_"
|
||||
|
|
@ -192,6 +197,11 @@ 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
|
||||
}
|
||||
|
||||
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$installDir\restore.ps1`" -HomeDir `"$HomeDir`""
|
||||
$triggers = @(
|
||||
New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 30) -RepetitionDuration (New-TimeSpan -Days 3650)
|
||||
|
|
|
|||
Loading…
Reference in a new issue