ラズパイでコンソールサーバアプライアンスを作る

## 事のあらまし

ネットワーク機器を触るときコンソールサーバがあるととても便利。Telnet接続でもいいがIPアドレスがいるうえ、管理ポートが落ちたりROMMONに入ったら現地に走っていくことになる。:scream::skull::ghost:
というわけでコンソールサーバが欲しいのだが、なかなか予算も貰えないので自分で作ってみることにした。

最終的にこんな感じになった。コンソール7ポートでシステム停止ボタン付き。
![IMG_20210822_142954_450.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356849/cf7cac47-34c7-082a-e4cb-d449f34f3493.jpeg)

# 1. 目標

- よくあるコンソールサーバと同じ使い勝手のものを作る
  - PC～コンソールサーバ間はSSH接続
  - コンソール操作はログを自動で残す
- コンソールサーバ上はコンソール操作専用とする
  - ユーザができるのはコンソール操作と操作ログの参照のみ
  - 踏み台サーバやスクリプト書いたりといった利用はさせない
- シリアルポートの設定は9600baud固定
  - 57600, 115200baudあたりも最近はあるので対応したいが。。。

``` shell:動作イメージ
oresama@LocalPC$ ssh con@192.168.1.100   <-- ラズパイコンソールサーバに接続
Password:

con@PiCS$ console com1   <-- コンソールセッションを開く
[Enter `^Ec?' for help]

Username: hogehoge  <-- シリアルポート経由でCisco機器が触れる
Password:

Router> enable
Router# conf t
Router(config)# 
...
```


# 2. 実施環境

### 使用機材

- HW: RaspberryPi 4 Model B 4GB
  - USBシリアルケーブル：今回はVANDESAIL USB RJ45(FT232)×2個
  - USBハブ：安定動作のためセルフパワーが望ましい
- SW: RaspberryPi OS Lite 32bit, May 7th 2021
  - bash 5.0.3(1)
  - OpenSSH server 7.9p1
  - Conserver 8.2.6

あとシリアルで操作するネットワーク機器か何か。今回はC1812-J×2台を使用。

### 接続構成

``` shell:機器接続
             eth0  ttyUSB*      con
PC ------------ PiCS ============ Cisco
   192.168.1/24
  -----SSH----->    ---Console--->
```

- PiCS: 構築するコンソールサーバ (192.168.1.100)
- PC: クライアントPC (192.168.1.xx)
- GW(図にない): ゲートウェイルータ (192.168.1.254)
- Cisco: シリアルコンソール接続で操作するネットワーク機器


# 3. OS初期設定

Raspberry Pi ImagerでSDカードにOSイメージを書き込み、``/boot/ssh``を配置して起動する。OSが起動したらSSHで接続し、pi/raspberryでログインしたところからスタートする。

### ホスト名・ネットワーク設定の設定

いつもの手順。

``` shell:ホスト初期設定
$ sudo raspi-config
- hostname: PiCS (何でもよい)
- timezone: Asia/Tokyoを選択
- locale:   en_US.UTF8、ja_JP.UTF8を追加し、en_US.UTF8デフォルトに設定

$ sudo nano /etc/dhcpcd.conf
interface eth0
static ip_address=192.168.1.100/24
static routers=192.168.1.254
static domain_name_servers=8.8.8.8 8.8.4.4

$ sudo reboot
```

``` shell:ネットワーク動作確認
$ ifconfig eth0         <-- IPアドレスが指定したものになっていること
$ route                 <-- デフォルトルートがGWに向いていること
$ ping 192.168.1.254    <-- GWへの疎通確認
$ ping 8.8.8.8          <-- インターネット向けの疎通確認
```

### ビルド環境の導入

いつもの手順その2。後で使うのでbuild-essentialとufwを入れておく。

``` shell:ソフトウェア更新・ビルド環境の導入
$ sudo apt update
$ sudo apt upgrade -y
$ sudo apt install build-essential ufw
```

### NTPサーバの登録

どこでもいいがログに時刻情報がつくようにするのでNTPで同期しておく。

``` shell:時刻同期設定
$ sudo nano /etc/systemd/timesyncd.conf
(以下の部分にNTPサーバアドレスを記述。ntp.nict.jpなど)
[Time]
NTP=192.168.1.254
```

``` shell:systemd-timesyncdの有効化
$ sudo systemctl enable systemd-timesyncd
$ sudo systemctl start systemd-timesyncd
$
$ timedatectl
               Local time: Sat 2021-07-10 12:18:33 JST
           Universal time: Sat 2021-07-10 03:18:33 UTC
                 RTC time: n/a
                Time zone: Asia/Tokyo (JST, +0900)
System clock synchronized: yes
              NTP service: active  <-- ここがactiveになる
          RTC in local TZ: no
```

### USBシリアルケーブルの接続

USBシリアルケーブルをラズパイが認識し、デバイスファイルが作成されていることを確認する。あとでここで確認したデバイスファイル名をconserverの設定に登録する。

``` shell:シリアルポートデバイスの確認
$ dmesg | grep ttyUSB
[    6.788169] usb 1-1.2: FTDI USB Serial Device converter now attached to ttyUSB0
[    6.795067] usb 1-1.3: FTDI USB Serial Device converter now attached to ttyUSB1

$ ls -l /dev | grep ttyUSB
crw-rw----  1 root dialout 188,   0 Jul 10 10:58 ttyUSB0  <-- ここのファイル名をあとで使う
crw-rw----  1 root dialout 188,   1 Jul 10 10:59 ttyUSB1
```

- USBシリアルケーブルをラズパイが認識すると``/dev/ttyUSB*``が作成される

# 4. Conserver導入・設定

RPiOSのaptリポジトリにはいまのところconserverが登録されていないためソースからビルドする必要がある。そのためのbuild-essential。

### Conserverの導入

githubからソースを落としてきてビルドとインストールを行う。

``` shell:conserverのビルドとインストール
$ wget https://github.com/bstansell/conserver/releases/download/v8.2.6/conserver-8.2.6.tar.gz
$ sudo tar zxf conserver-8.2.6.tar.gz -C /usr/src
$ cd /usr/src/conserver
$ sudo ./configure
$ sudo make && sudo make install
```

- ソースの配置先（どこでもいい）
  - ``/usr/src/conserver``
- インストール先
  - ``/usr/local/sbin/conserver``
  - ``/usr/local/bin/console``
- 設定ファイル
  - ``/etc/hosts``：Linuxのシステムファイル。接続先情報を追記する
  - ``/etc/services``：Linuxのシステムファイル。conserverのポート番号を追記する
  - ``/usr/local/etc/conserver.cf``：サーバの動作設定。新規で作る
  - ``/usr/local/etc/conserver.passwd``：認証パスワード設定。新規で作る

### Conserverの設定

サンプル設定ファイルもあるのだが1から書いたほうが分かりやすい。

``` shell:Conserverの動作設定
default * {
	master localhost;

	# コンソールポートごとのログ設定
	logfile /var/log/console/&;
	logfilemax 4m;
	timestamp 1hab;

	# コンソールポート設定
	rw *;
	type device;
	baud 9600;
	parity none;

	# 接続時のメッセージで切断方法を表示
	motd "Type Ctrl-E, C, '.' to close this session";
}

# 認証なしでlocalhostからのみ接続可
access * { trusted 127.0.0.1; }

# comXの名前でシリアルポートを登録(速度設定はdefault *から引継ぎ)
console com1 { type device; device /dev/ttyUSB0; }  <-- 確認したデバイス名
console com2 { type device; device /dev/ttyUSB1; }
# (必要なだけ書く)
```

- ``access *``：接続時（セッション開始時）の認証設定
  - ``trusted 127.0.0.1``: localhostからのアクセスを認証なしで許可
  - trustedの対象をallowedにも書く必要はない。先にマッチしたほう優先
- ``default *``：全セッションのデフォルト設定
  - ``master localhost``：よく分からないがとりあえずlocalhostでいいらしい
  - ``logfile``, ``logfilemax``, ``timestamp``：コンソールセッションのログ設定。4MBまで、1時間ごとにタイムスタンプを残す
  - ``rw *``：コンソールセッションに対して読み書きを全ユーザ許可
  - ``baud``, ``parity``：シリアルポートのデフォルト設定。Ciscoなどのデフォルトに合わせる
  - ``motd``：セッション開始時に表示するコメント。セッションの切断方法を表示しておく
- ``console <console-name>``：コンソールセッション設定
  - ``type device``：接続先がデバイスファイル
  - ``device <device-file>``: シリアルポートのデバイスファイルを指定
  - シリアルポート設定やログ設定はdefault *から自動的に引き継がれる

``` shell:パスワードを追加
$ sudo nano /usr/local/etc/conserver.passwd
*any*:*
```

- 今回はtrustしたlocalhostからの接続のみ受け付け、認証もしないので実際には使われない（はず）。

``` shell:ログ出力ディレクトリの作成
$ sudo mkdir /var/log/console
```

- ``conserver.cf``の``logfile``で指定したディレクトリを作成
- あとでユーザから参照しやすいようシンボリックリンクを張る

``` shell:ホスト名の登録
$ sudo nano /etc/hosts
(末尾に以下を追加)
127.0.0.1    console
```

- ホスト名設定
  - console: consoleコマンドの接続先アドレスを設定 (conserverを動かすホスト。今回はlocalhost)
  - consoleコマンドは内部で接続先サーバをホスト名指定している模様。hostsかDNSで名前解決ができないと動かない。

``` shell:サービス名の登録
$ sudo nano /etc/services
(末尾にサービスのエントリを追加)
# Local services
console		782/tcp		conserver
```

- サービス設定
  - console: consoleコマンドの接続先ポート
  - conserver (alies): conserverコマンドの待ち受けポート
  - 782/tcpはビルド時に--with-portオプションを変えていない場合のデフォルトポート

### 動作確認1

ターミナル2面を開いてconserverコマンド（サーバ）、consoleコマンド（クライアント）を実行してみる。クライアントからコンソール経由でシリアルケーブルの接続先機器が操作できればOK。

``` shell:conserverの起動（ターミナル#1）
pi@PiCS:~ $ sudo conserver  <-- 操作(1) conserverのテスト起動
[Sat Jul 10 10:57:49 2021] conserver (1163): conserver.com version 8.2.6
[Sat Jul 10 10:57:49 2021] conserver (1163): started as `root' by `pi'
[Sat Jul 10 10:58:20 2021] conserver (1164): [com1] login pi@localhost   <-- 操作(2)の出力ログ
[Sat Jul 10 10:59:21 2021] conserver (1164): [com2] login pi@localhost   <-- 操作(3)の出力ログ
[Sat Jul 10 10:59:21 2021] conserver (1164): [com1] logout pi@localhost
[Sat Jul 10 10:59:30 2021] conserver (1164): [com2] logout pi@localhost  <-- 操作(4)の出力ログ
^C[Sat Jul 10 11:02:59 2021] conserver (1163): terminated  <-- 操作(5 ) Ctrl-C
``` 

``` shell:console接続（ターミナル#2）
pi@PiCS:~ $ ps -ef | grep conserver -v grep  <-- conserverプロセスの動作確認
root      1164     1  0 10:58 ?        00:00:00 /usr/local/sbin/conserver  <-- これ
root      1165  1164  0 10:59 ?        00:00:00 /usr/local/sbin/conserver  <-- これも
pi        1204  1174  0 11:01 pts/1    00:00:00 grep --color=auto conserver

pi@PiCS:~ $ netstat -a | grep console  <-- consoleサービスの待ち受け確認
tcp        0      0 0.0.0.0:console         0.0.0.0:*               LISTEN

pi@PiCS:~ $ console com1    <-- 操作(2) シェルでconsole com1を実行
[Enter `^Ec?' for help]
[-- MOTD -- Type Ctrl-E, C, '.' to close this session]


User Access Verification

Username:
[console: com2]             <-- 操作(3) Ctrl-E, C, ';' -> com2
[Enter `^Ec?' for help]
[-- MOTD -- Type Ctrl-E, C, '.' to close this session]

com2-Router#
com2-Router#[disconnect]    <-- 操作(4) Ctrl-E, C, '.'
pi@PiCS:~ $
```

# 5. Systemd設定

### Conserverのサービス登録

``` shell:Systemdサービスファイルの作成
$ sudo nano /etc/systemd/system/conserver.service
[Unit]
Description=conserver daemon
After=network.target

[Service]
Type=simple
RemainAfterExit=no
ExecStart=/usr/local/sbin/conserver
ExecStop=kill `cat /var/run/conserver.pid`
ExecRestart=kill `cat /var/run/conserver.pid` ; /usr/local/sbin/conserver
PIDFile=/var/run/conserver.pid
Restart=always

[Install]
WantedBy=multi-user.target
```

- ``ExecStart``：単に起動しているだけ。停止したらrestartさせたいのでデーモン化しない
- ``ExecStop``：PIDファイルに記述されたPIDをkill
- ``ExecRestart''：ExecStop・ExecStartと同じことを順に実行
- ``RemainAdterExit``：ExecStart/ExecRestart実行後に制御が戻った場合にプロセスが動作しているか、終了しているかの情報
- ``Restart``：プロセス終了を検知したときにExecStartを再実行するか。
- ``After``：他のサービスの起動完了後に実行

### Conserverサービスの起動

``` shell:サービスの有効化・開始
$ sudo systemctl daemon-reload
$ sudo systemctl enable conserver
$ sudo systemctl start  conserver
```

# 6. ファイアウォール設定

``` shell:ufwの設定・有効化
$ sudo ufw reset
$ sudo ufw default deny
$ sudo ufw allow ssh
$ sudo ufw enable
```

- リモート接続用のSSHポートのみ許可して後はすべて閉じる
- conserverの782/tcpはローカルホストのみにサービスするため開ける必要なし

# 7. コンソール操作専用ユーザの設定

rbashを使ってコンソールポート操作だけ行える共通ユーザアカウントを作る。
共用サーバとして自由に使えてもいいのだが環境汚れるし、ラズパイのSDカード上であれこれやられたくないので操作を制限したユーザを置くことにする。

``` shell:コンソール操作専用ユーザの追加
$ sudo adduser con
[sudo] password for pi:
Adding user `con' ...
Adding new group `con' (1002) ...
Adding new user `con' (1002) with group `con' ...
Creating home directory `/home/con' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for con
Enter the new value, or press ENTER for the default
        Full Name []:
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n] y

$ sudo usermod -s /usr/bin/rbash con
```

- SSHでログインするコンソール操作用のユーザを追加。ここでは"con"とした
  - 作成したユーザのログインシェルを``/usr/bin/rbash``に変更
  - dialoutグループなどに追加でグループに所属させる必要はない

``` shell:デフォルトプロファイルの削除
$ sudo rm /home/con/.profile
$ sudo rm /home/con/.bashrc
$ sudo rm /home/con/.bash_profile
``` 

``` shell:カスタムプロファイルの作成(rootの持ち物として！)
$ sudo nano /home/con/.profile
export PS1='PiCS$ '
export PATH=/opt/rbash

shopt -s histappend
shopt -s checkwinsize
shopt -u progcomp
shopt -u promptvars

HISTCONTROL=ignoreboth
HISTSIZE=100
HISTFILESIZE=100

$ sudo ln -s /home/con/.profile /home/con/.bashrc
$ sudo ln -s /home/con/.profile /home/con/.bash_profile
```

- **root:rootのファイルとして**プロファイルを再作成
  - PATHを``/opt/rbash``のみに上書き（重要）。このディレクトリに配置した実行ファイルのみ叩けるようになる
  - その他は任意。入力補完とかは切っておいた方がよいはず。
  - ユーザ自身のファイルとしてプロファイルを作ると書き換えが可能になってしまうのでrootの持ち物にしておく

``` shell:rbashで実行可能なファイルを登録
$ sudo mkdir /opt/rbash
$ sudo ln -s /usr/local/bin/console /opt/rbash/
$ sudo ln -s /usr/local/bin/cat     /opt/rbash/
$ sudo ln -s /usr/local/bin/more    /opt/rbash/
$ sudo ln -s /usr/local/bin/less    /opt/rbash/
$ sudo ln -s /usr/local/bin/tail    /opt/rbash/
$ sudo ln -s /usr/local/bin/grep    /opt/rbash/
$ sudo ln -s /usr/local/bin/ls      /opt/rbash/

$ ls -l /opt/rbash
total 4
lrwxrwxrwx 1 root root   12 Jul  8 09:37 cat -> /usr/bin/cat
lrwxrwxrwx 1 root root   22 Jul  5 17:55 console -> /usr/local/bin/console
lrwxrwxrwx 1 root root   13 Jul  8 09:38 grep -> /usr/bin/grep
lrwxrwxrwx 1 root root   13 Jul  8 09:41 less -> /usr/bin/less
lrwxrwxrwx 1 root root   11 Jul  8 09:37 ls -> /usr/bin/ls
lrwxrwxrwx 1 root root   13 Jul  8 09:38 more -> /usr/bin/more
lrwxrwxrwx 1 root root   13 Jul  8 09:50 tail -> /usr/bin/tail
```

* rbash向けに通したパスに実行を許可するコマンドのシンボリックリンクを貼る
  * ``console``がconserverに接続するための必須コマンド
  * ``cat``, ``more``, ``less``, ``tail``, ``grep``はログ確認用
  * ``ls``はログディレクトリのファイル一覧表示用

``` shell:ログディレクトリへのシンボリックリンク
$ sudo ln -s /var/log/console /home/con/log
```

### 動作確認2

``` shell:動作確認(コンソール操作とログ確認)
PiCS$ console com1
[Enter `^Ec?' for help]
[-- MOTD -- Type Ctrl-E, C, '.' to close this session]


User Access Verification

Username: [disconnect]
PiCS$ tail -10 log/com1




[-- con@localhost attached -- Mon Jul 12 12:19:07 2021]


User Access Verification

Username: [-- con@localhost detached -- Mon Jul 12 12:19:11 2021]
PiCS$お
```

``` shell:動作確認(禁止操作が実行できないこと)
PiCS$ cd /
-rbash: cd: restricted

PiCS$ rm log/com1
-rbash: rm: command not found

PiCS$ telnet 192.168.1.254
-rbash: telnet: command not found
```

* SSH接続のあとコンソール接続・操作ができ、``~/log/com*``でログの閲覧ができた
* 許可されていないコマンドはnot foundになり、カレントディレクトリの移動なども失敗する

お疲れさまでした！　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　

# 99. 本文でやっていないこと

### デフォルトユーザpiの無効化

ちゃんとした管理ユーザを設定し、piユーザを削除またロック。忘れずにやりましょう。

### SDカードのRead-only化

高耐久のSDカードを使用すれば当面持つようだが、ログを書き込んでいるのでそのうちファイルシステムが飛んでしまうかも。
```/```をROでマウント、tmpfsを```/var/log```に割り当ててそこにログを記録する方がよいはず。

### USBシリアルケーブルのデバイスファイル名の固定

USBシリアルケーブルは認識順で対応するデバイスファイルが変わることがあり、コンソールポートの並びが変わることがあります。
コンソールサーバだとそれは困るので、あるUSBシリアルケーブルには必ず同じデバイスファイルが割り当てられるようにudevに登録するか、```/dev/serial/by-path/```配下のデバイスファイルからアクセスするようにするべき。

**やり方**
- ``udevadm``でベンダーID・モデルIDとシリアル番号を調べる
- 調べた内容をudevルールファイルに記入し、指定のシンボリックリンクが作成されるようにする (あるシリアル番号のケーブル＝常に``/dev/ttyUSB-com1``など)
- udevルールをリロードし、指定シリアルに対して設定したデバイスファイル（シンボリックリンク）が作成されることを確認
- conserverの参照先デバイスファイル名をシンボリックリンク側に変更

``` shell:
$ udevadm info -q property -n /dev/ttyUSB0 | grep -e _ID -e _SERIAL
ID_VENDOR_ID=0403
ID_MODEL_ID=6001
ID_SERIAL=FTDI_FT232R_USB_UART_A9XPPS**
ID_SERIAL_SHORT=A9XPPS**

$ udevadm info -q property -n /dev/ttyUSB1 | grep -e _ID -e _SERIAL
ID_VENDOR_ID=0403
ID_MODEL_ID=6001
ID_SERIAL=FTDI_FT232R_USB_UART_A9BF1J**
ID_SERIAL_SHORT=A9BF1J**

...
``` 

``` shell:udevルールの追加
$ sudo nano /etc/udev/rules.d/50-usb-serial.conf

SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="A9XPPS**", SYMLINK+="ttyUSB-com1", GROUP="dialout"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="A9BF1J**", SYMLINK+="ttyUSB-com2", GROUP="dialout"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="A9L4YM**", SYMLINK+="ttyUSB-com3", GROUP="dialout"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="A9A9KU**", SYMLINK+="ttyUSB-com4", GROUP="dialout"

$ sudo udevadm control --reload-rules
$ sudo udevadm trigger

$ ls -l /dev | grep ttyUSB
crw-rw---- 1 root dialout 188,   0 Jul 10 19:08 ttyUSB0
crw-rw---- 1 root dialout 188,   1 Jul 10 19:08 ttyUSB1
crw-rw---- 1 root dialout 188,   2 Jul 10 19:08 ttyUSB2
crw-rw---- 1 root dialout 188,   3 Jul 10 19:07 ttyUSB3
lrwxrwxrwx 1 root root           7 Jul 10 19:07 ttyUSB-com1 -> ttyUSB0  <--ここから4つ。別名がついている
lrwxrwxrwx 1 root root           7 Jul 10 19:07 ttyUSB-com2 -> ttyUSB1
lrwxrwxrwx 1 root root           7 Jul 10 19:07 ttyUSB-com3 -> ttyUSB2
lrwxrwxrwx 1 root root           7 Jul 10 19:07 ttyUSB-com4 -> ttyUSB3
```

``` shell:conserver.cfの書き換え
$ sudo nano /usr/local/etc/conserver.cf
(コンソールのデバイスファイル名を変更)
console com1 { type device; device /dev/ttyUSB-com1; }
console com2 { type device; device /dev/ttyUSB-com2; }
console com3 { type device; device /dev/ttyUSB-com3; }
console com4 { type device; device /dev/ttyUSB-com4; }

$ sudo systemctl restart conserver
```

## 参考URL

* Conserver本家

https://www.conserver.com/

* Conserverの先人ユーザ

https://netwiz.jp/linux-console-server/

* rbashの設定方法

https://qiita.com/utisam/items/a20e176cabe21e74e19e

* udevルールの書き方

https://smdn.jp/electronics/udev_create_persistent_usb_device_symlink/

* ラズパイをGPIOのボタンでシャットダウンさせる話

https://jellyware.jp/kurage/raspi/shutdown_button.html