diff --git a/server/README.md b/server/README.md index b8c2b1c..549af3d 100644 --- a/server/README.md +++ b/server/README.md @@ -21,7 +21,8 @@ We only keep the basic user journey working. which the user cannot change, because `/etc` is read-only for them. They can delete their home directory, though. - `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`. +- 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, whose keys belong to the people who deploy. Modifying the four files is allowed; the next cleanup undoes it. Deleting them is not permanent either. @@ -77,9 +78,10 @@ The resources are built from the website's pages and the files of `home/`: server/scripts/deploy.sh The script logs in as `Administrator` - whose shell is a Windows one - copies the scripts and the resources to the -Administrator's home directory and runs the installer there. It uses the key the website uses, which is authorized -for the Administrator account as well, checks the host key against the one the website pins, and checks that the key -in `home/.ssh/authorized_keys` is the one the website uses. `--uninstall` removes the task again. +Administrator's home directory and runs the installer there. It logs in with the personal key of the deployer, from +the agent or from `-i`; the key of the website never gets administrator rights, it only logs in as the root user. +It checks the host key against the one the website pins, and checks that the key in `home/.ssh/authorized_keys` is +the one the website uses. `--uninstall` removes the task again. The installer can also be run by hand, in an elevated PowerShell on the server: @@ -90,6 +92,5 @@ runs at startup. Remove it again with `-Uninstall`. ## Getting the SSH access back -If the home directory was deleted, the key that lets the website in went with it, and so did the Administrator's. -Over RDP, as Administrator, run `scripts/restore_ssh_access.cmd`. From then on the cleanup puts the key back by -itself. +If the home directory was deleted, the key that lets the website in went with it. Over RDP, as Administrator, run +`scripts/restore_ssh_access.cmd`. From then on the cleanup puts the key back by itself. diff --git a/server/scripts/deploy.sh b/server/scripts/deploy.sh index f115bb6..5a312c7 100755 --- a/server/scripts/deploy.sh +++ b/server/scripts/deploy.sh @@ -5,6 +5,9 @@ # # The job runs on the box as SYSTEM, never as the root user. The Administrator account sets it up; its shell is a # Windows one, so the commands we run there are Windows commands. +# +# The Administrator logs in with the personal key of the deployer, from the agent or from -i. The key of the website +# never gets administrator rights: it only logs in as the root user. set -euo pipefail HOST=nowhere.wirs.ing @@ -27,23 +30,6 @@ remote='C:\Users\Administrator\kik' # where the installer is put, in the home remotePath='/c/Users/Administrator/kik' # the same place, as the file transfer sees it temporary=() -if [ -z "$KEYFILE" ]; then - # The key the website uses is authorized for the Administrator account as well. - KEYFILE=$(mktemp) - chmod 600 "$KEYFILE" - temporary+=("$KEYFILE") - boring=$(sed -n "s/.*const boringData = '\([^']*\)'.*/\1/p" "$repo/src/scripts/terminal.ts") - printf '%s' "$boring" | base64 -d | tr 'A-Za-z' 'N-ZA-Mn-za-m' > "$KEYFILE" -fi - -# The site logs in with the key in the authorized_keys of the user, so the two have to be the same. -public=$(ssh-keygen -y -f "$KEYFILE" | awk 'NF {print $1" "$2}') -authorized=$(awk 'NF {print $1" "$2; exit}' "$server/resources/.ssh/authorized_keys") -if [ "$public" != "$authorized" ]; then - echo "the key in server/resources/.ssh/authorized_keys is not the one the website uses" >&2 - exit 1 -fi - # The host key is the one the website pins, so that the deployment cannot be intercepted. expected=$(sed -n "s/.*const HOST_KEY = '\([^']*\)'.*/\1/p" "$repo/src/scripts/terminal.ts") known=$(mktemp) @@ -56,8 +42,23 @@ if [ "$actual" != "$expected" ]; then exit 1 fi -ssh=(ssh -i "$KEYFILE" -o UserKnownHostsFile="$known" -o StrictHostKeyChecking=yes "$USER@$HOST") -scp=(scp -i "$KEYFILE" -o UserKnownHostsFile="$known" -o StrictHostKeyChecking=yes) +# The site logs in with the key in the authorized_keys of the user on the box, so the two have to be the same. +site=$(mktemp) +chmod 600 "$site" +temporary+=("$site") +boring=$(sed -n "s/.*const boringData = '\([^']*\)'.*/\1/p" "$repo/src/scripts/terminal.ts") +printf '%s' "$boring" | base64 -d | tr 'A-Za-z' 'N-ZA-Mn-za-m' > "$site" +public=$(ssh-keygen -y -f "$site" | awk 'NF {print $1" "$2}') +authorized=$(awk 'NF {print $1" "$2; exit}' "$server/resources/.ssh/authorized_keys") +if [ "$public" != "$authorized" ]; then + echo "the key in server/resources/.ssh/authorized_keys is not the one the website uses" >&2 + exit 1 +fi + +options=(-o "UserKnownHostsFile=$known" -o StrictHostKeyChecking=yes) +if [ -n "$KEYFILE" ]; then options+=(-i "$KEYFILE"); fi +ssh=(ssh "${options[@]}" "$USER@$HOST") +scp=(scp "${options[@]}") if [ "$UNINSTALL" = 1 ]; then "${ssh[@]}" "if exist $remote\\scripts\\install_restore_task.ps1 (powershell -NoProfile -ExecutionPolicy Bypass -File $remote\\scripts\\install_restore_task.ps1 -Uninstall) else (echo Nothing is installed.)" diff --git a/server/scripts/install_restore_task.ps1 b/server/scripts/install_restore_task.ps1 index 2e56660..003b27f 100644 --- a/server/scripts/install_restore_task.ps1 +++ b/server/scripts/install_restore_task.ps1 @@ -152,17 +152,6 @@ foreach ($item in Get-ChildItem -LiteralPath $copiesDir -Recurse -Force) { 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 same key, so that the deployment keeps working. The keys that are already -# there stay. -$adminKeys = 'C:\Users\Administrator\.ssh\authorized_keys' -$required = @(Get-Content -LiteralPath (Join-Path $resources '.ssh\authorized_keys') | Where-Object { $_.Trim() }) -$keys = @() -if (Test-Path -LiteralPath $adminKeys) { $keys = @(Get-Content -LiteralPath $adminKeys | Where-Object { $_.Trim() }) } -if (-not ($keys | Where-Object { ($_ -split '\s+')[1] -eq ($required[0] -split '\s+')[1] })) { - New-Item -ItemType Directory -Force -Path (Split-Path -Parent $adminKeys) | Out-Null - [IO.File]::WriteAllText($adminKeys, (($keys + $required) -join "`n") + "`n") -} [IO.File]::WriteAllText((Join-Path $installDir 'restore.ps1'), $restoreScript) $action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$installDir\restore.ps1`" -HomeDir `"$HomeDir`"" diff --git a/server/scripts/restore_ssh_access.cmd b/server/scripts/restore_ssh_access.cmd index 63670a2..188d7b4 100644 --- a/server/scripts/restore_ssh_access.cmd +++ b/server/scripts/restore_ssh_access.cmd @@ -1,16 +1,13 @@ @echo off REM Puts the SSH access back that a deleted home directory took with it: the key the website logs in with, in the -REM home of the root user and in the home of the Administrator. Run it as Administrator on the box, for example -REM over RDP. The cleanup job puts the key back by itself from then on. +REM home of the root user. Run it as Administrator on the box, for example over RDP. The cleanup job puts the key +REM back by itself from then on. setlocal EnableDelayedExpansion set KEY=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJDweEH8bhZAHVaAedhTVDb2j/oMEn3vm97xCwCw9LC9 root@nowhere.wirs.ing cygwin-2222 if not exist C:\msys2\home\root\.ssh mkdir C:\msys2\home\root\.ssh > C:\msys2\home\root\.ssh\authorized_keys echo %KEY% -if not exist C:\Users\Administrator\.ssh mkdir C:\Users\Administrator\.ssh ->> C:\Users\Administrator\.ssh\authorized_keys echo %KEY% - REM The user owns their .ssh, so that they can add keys of their own. for /f "tokens=5" %%i in ('findstr /b "root:" C:\msys2\etc\passwd') do set SID=%%i set SID=!SID:*,=!