建置一個只能執行 ssh 指令的跳板伺服器

因為某些原因,我溫暖的家的 IP 被當成連線到某台伺服器的跳板。

但為了避免我在假日被其他人透過電話當成阿凡達操控的情況發生,所以我決定在我家放一台機器,讓需要的人們可以自行先 SSH 到該機器,再藉由我家的 IP 連到目的地。

有了動機以後,大致列出我要達到的目標

  • 找一個可以放在家裡,存在感低,能設置 SSH 服務的便宜機器
  • 讓這台機器盡量與我家內網隔離
  • 使用者連進來以後只能執行 ssh 指令,而且只能連到特定的遠端

開始一步一步處理這些事項


找一個可以放在家裡,存在感低,能設置 SSH 服務的便宜機器

定義所謂存在感低

  • 不會發出聲響,所以排除有散熱風扇的電腦
  • 不要吃太多電,Fanless PC 也排除
  • 體積小

↑↑↑其實上面的條件都是多餘的↑↑↑

光是便宜這個條件大概可以去掉 87% 的選項,留下 Raspberry Pi 這種 ARM 開發板作為候選。不過家裡的樹莓派現在都很忙,所以我只好再上淘寶找便宜的 ARM 開發板 (絕對不是我要找理由買新玩具)

一番尋找之後,就決定是 NanoPi NEO 了!NanoPi NEO 由中國的友善之臂出品,是一塊長 x 寬只有 40mm x 40mm,且具有 RJ-45 插孔的 ARM 開發板。友善之臂官方針對 NanoPi NEO 訂製並維護了一套 Ubuntu Core 16.04,不喜歡的話還有 ArmbianDietPi 等民間發行版可以選用,重點是板子本身便宜。然而這板子據說容易過熱,所以再加購官方散熱片,因為決定讓它裸機所以連外殼也省了,Micro SD 卡、USB 線以及電源用現成的。

NanoPi NEO w/512MB RAM. 如果家裡有多的散熱片就別買官方的, 這訂價也太貴了吧?

OS 基礎設定

分享一下我用過的 distro 感想:

  • Armbian (民間高手維護): 「似乎」可以跑 Docker、3.x kernel 可以用 I²C; 4.x kernel 實測結果不能用 I²C,MAC address 重開機就會變一次,當我試著固定它的時候就連不進去了,促使我改為安裝 Ubuntu Core 16.04
  • Ubuntu Core 16.04 (友善之臂維護): 官方有在更新、I²C 可用、kernel 版本新、網卡 MAC address 預設固定不變;不能跑 lxc (所以也不能用 Docker)

本文之後的設置都是以 Ubuntu Core 16.04 作為示範。插入灌好 OS 的記憶卡至 NanoPi NEO,開機等它抓到 IP,用 SSH 進去,它會顯示系統的基本資訊。可以看到我用的記憶卡容量只有 4GB,RAM 有 512MB,而且還顯示了本機的 IP。

截圖是為了寫本文才截的,uptime 就別在意了

系統起來了,緊接著開始進行設定


若不是使用 root 登入,請改用 root 登入,預設的密碼為 fa

先刪掉不需要的內建帳號 (fa) 並改掉其他內建帳號 (root, pi) 的密碼

# deluser fa
# passwd
# passwd pi

更新系統,並安裝我常用的 package

# apt-get update && apt-get dist-upgrade -y && apt-get install git curl wget htop byobu zsh vim tmux screen iotop bwm-ng man cron ufw python-software-properties software-properties-common apt-transport-https -y

改掉 hostname

# hostnamectl set-hostname "new-hostname"
## 可能還需要手動修改 /etc/hosts

改掉 timezone

# timedatectl set-timezone Asia/Taipei

產生用來登入的 ssh key

(略)

修改 SSH server 的 listening port 為 2222、不允許 X11Forwarding、不允許 tunneling、每次登入僅能進行一次登入嘗試、僅能使用 public key authentication 登入

## change port from 22 to 65536
# sed -i 's/^Port 22/Port 65536/g' /etc/ssh/sshd_config
## limit auth try per session to 1
# printf "\n\n# Allow only one auth try per session to limit\nMaxAuthTries 1" >> /etc/ssh/sshd_config
## 如果打算同時使用密碼認證以及 pub key 認證的話,不要執行下面這一行
# sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config
## 不讓 root 登入 SSH,我習慣用 sudo 帳號登入
## optional but strongly recommended
# sed -i 's/PermitRootLogin yes/PermitRootLogin no/g' /etc/ssh/sshd_config
## 重啟 ssh
# systemctl restart ssh.service
## 重新啟動之後,不要斷掉目前的連線,開一個新的視窗試試看能不能登入,登不進去的話表示 sshd 設定出問題了,快用活著的連線去排除,不然你以後就連不進來了

disable 我目前用不到的 IPv6 (optional)

# echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf

啟用 RTC (optional),會需要做這項設定是因為我手邊有剩下的 DS3231 RTC module,所以順手插上去。前面特別提到 I²C 支援的原因也是因為它

# sed -i 's/^blacklist rtc_ds1307/#blacklist rtc_ds1307/g' /etc/modprobe.d/matrix-blacklist.conf
## Use DS3231 instead of the built-in RTC in H3 chip
# echo HCTOSYS_DEVICE=rtc1 >> /etc/default/hwclock

為了避免連進來的人玩弄我家區網,所以設定簡單的防火牆規則。為了方便我使用 ufw 進行設定

## 允許 SSH 連入
# ufw limit 65536/tcp comment 'SSH'
## 阻擋連出至 router 特定服務、允許連到 router,阻擋連出到內網設備
# ufw deny out from any to x.x.x.1 port 65536 comment 'Router web'
# ufw allow out from any to x.x.x.1 comment 'Router'
# ufw deny out from any to x.x.x.0/24
## 允許連線到 VIP
# ufw allow out from any to x.x.x.x port 65536 proto tcp comment 'VIP SSH'
## 如果規則沒設好,可能在下列的指令打完之後就連不進來了...
# ufw default deny outgoing
# ufw enable

其他規則、router 的防火牆以及 NAT 設定在此省略。

做到這裡,建議先重開機,確認改了這些東西後 NanoPi NEO 還能動。

# reboot

如果你在設定 SSH server 的時候有設定 PermitRootLogin no,記得改用擁有 sudo 權限的帳號 (如預設的 pi) 登入,並使用下列指令切換成 root 後再接著操作後面的部分

$ sudo su -

讓使用者連入 Nano Pi NEO 後只能 SSH 到某伺服器

OS 基礎大概處理好之後,再來是:怎麼讓使用者專心工作,不亂玩這片小板子。

防火牆的部分前面已經設定完成,現在要讓他們只能在 shell 執行 ssh 指令,關於這項,我考慮過幾種方案:

  • rssh: 只能連進來執行 scp/sftp 的 shell,跟我的要求不一樣
  • rbash: 有功能限制的 bash,有著不能 cd …… 等限制,但不能限制使用者可以執行的指令,只能達到部分要求
  • chroot: 將登入使用者的根目錄換成某個資料夾,達成某種程度的隔離效果,但是檔案是跟沒有 chroot 的使用者放在一起的,我覺得隔離不夠乾淨
  • container (Docker 或是只使用 lxc): 使用 cgroups 等技術,建立一個與 host 隔離的容器,可以設定各種資源的使用限制,而且如果覺得不乾淨把整個 container 刪掉就解決,再加上 chroot 還有 rbash 根本完美

我原本想用 Docker+chroot+rbash 進行設定,但是因為前面的設定都用好了之後我才發現 Ubuntu Core 的 kernel 不支援 lxc,最後只能捨棄 Docker,僅用 chroot+rbash


以下開始設定 chroot 環境 (部分參考 Restrict an SSH user session to a specific directory by setting chrooted jail)

建立 chroot資料夾結構、複製必要的檔案

我們需要一個簡化版的 linux 根目錄結構,讓使用者的帳號在登入後被 chroot 到該資料夾下

# JAIL=/home/jails
# mkdir -p $JAIL
# mkdir -p $JAIL/dev/
# mknod -m 666 $JAIL/dev/null c 1 3
# mknod -m 666 $JAIL/dev/tty c 5 0
# mknod -m 666 $JAIL/dev/zero c 1 5
# mknod -m 666 $JAIL/dev/random c 1 8cd $JAIL
# chmod 0755 $JAIL
# cd $JAIL
# mkdir homes

複製 rbash, ssh 及其依賴的 library

就是只給使用者連線到伺服器所需要的工具,其他的一概不提供,這樣他們就會專心工作了吧 (?)

網路上有人分享了一個 script (Export a linux binary with its lib dependencies to a chroot or initramfs, …) 除了幫忙複製 binary 本身,也一併將相依的 library 複製到 chroot 資料夾內

## 複製上述文章內的 script 並存檔為 exportbin.sh
# chmod +x exportbin.sh
# ./exportbin.sh /bin/rbash $JAIL
# ./exportbin.sh /usr/bin/ssh $JAIL

建立帳號、密碼、chroot 時的家目錄

這次我新增 aaa, bbb 兩個 user

## 建立帳號
# adduser aaa
# adduser bbb
## 設定密碼
# passwd aaa
# passwd bbb
## 設定家目錄
# mkdir $JAIL/home/aaa
# mkdir $JAIL/home/bbb
## enable color prompt
# COLOR_PROMPT="PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '"
# echo $COLOR_PROMPT > $JAIL/home/aaa/.profile
# echo $COLOR_PROMPT > $JAIL/home/bbb/.profile
## disable motd on user login
# touch $JAIL/home/aaa/.hushlogin
# touch $JAIL/home/bbb/.hushlogin
## 每次新增了要被 chroot 的帳號後,都需同步 passwd & groups
## 也可以選擇不複製這些檔案,但這將導致使用者登入後
## shell 原本應該顯示 username 的地方顯示 "I have no name!"
# cp /etc/passwd $JAIL/etc/passwd
# cp /etc/groups $JAIL/etc/groups

到這裡大概就可以了,趕快從公司網路試試看能不能連,然後玩一下:

除了 ssh 什麼都不能做,這樣他們會高興嘛?

好,可以連線了,把 NanoPi NEO 電源拔掉。

為什麼?

畢竟只有在假日需要上 server 處理問題的時候才會用到這塊跳板,所以平常都是維持斷電狀態的,只在被召喚的時候才會把它插上電源。


其他要設定 TOTP 還是 YubiPAM 或者其他認證方式這裡沒提到了,各位可以自行實作想要的措施。

有點麻煩的點在於,若使用者要改密碼的話,我就必須先登入沒有被 chroot 的 sudo 帳號,然後把鍵盤借給他們輸入新的密碼…很麻煩


本文若有幫到你的話,請考慮贊助我一杯咖啡!

Bitcoin: 1Lrboa558FTmuecz8TwCB6eaidzjd1wGTT
Monero: 49WrA7vjaAahGh3jE7ss6eNFDuwJ2oMvPjLEmhCSFeTEHuYrrnvjmjbeoLkiFH4nwYMfxuBoRz2DKggNMTAPLJRt2X51h8W
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.