Ubuntu Weekly Recipe

第884回ser2netでリモートコンソール環境をさらに便利に使う

第879回のser2netで作るリモートシリアルコンソール環境では、ser2netを用いてリモートからtelnetコマンドでシリアルコンソールにアクセスできる環境を整えました。しかしながらこのままだと誰でもシリアルコンソールにアクセスできますし、その通信は平文で送られてしまいます。そこで今回は通信の暗号化や認証を導入してみましょう。

図1 このシリアルポートを暗号化する。ちなみに前回は記事公開当初「D-Sub 9ピンのシリアルポート」と言いつつ、何を勘違いしたのか「D-Sub 15ピンのVGAポートを掲載していた。今回こそD-Sub 9ピンのシリアルポートの画像である。今でも省スペース型でないデスクトップなら搭載されているモデルも存在する
図2 環境によってはこういうPCIeカードが活躍しているだろう。これは左奥のケーブルからさらに2ポート(1カードスロット)分のインターフェースが出ており、都合4ポートのシリアルポートを増設できる。このようなデバイスを接続すると/dev/ttySxがたくさん作られるため、udevで名前を付けたり、ser2netで管理することが有用になる

SSL/TLSで経路を暗号化する

改めて紹介すると、ser2netはYAML形式でシリアルコンソール設定を管理できる多機能なシリアルコンソールサーバーです。マシン上やリモートにある複数のシリアルコンソールポートを、telnetベースのプロトコルで公開できます。これによりシリアルコンソールケーブルをつなぎ替えることなく、複数のユーザーがそのポートを共有できます。

第879回では基本的な使い方として、任意のシリアルポートをser2netで公開する方法と複数ユーザーの共有・タイムアウト・ログの設定方法などを紹介しました。基本的にこれらさえ使えれば、通常の利用には問題ありません。しかしながらtelnetプロトコルなので、そのデータは平文でネットワークに流れます。つまりシリアルコンソール経由で何かにログインしようとすると、そのユーザー名とパスワードもそのままネットワークに流れてしまうのです。

図3 パケットキャプチャからログインプロンプトで入力し、エコーバックされたユーザー名rootを表示した例。キャプチャできる時点で秘密もあったものではないが、状況次第では対応が必要となる

まずはSSL/TLSで暗号化してみましょう。ser2netの設定ファイルである/etc/ser2net.yamlaccepterだけを変更します。最低限の設定は「ssl」を追加するだけです。

  accepter: telnet,ssl,2000

同時にサーバー証明書一式を生成します。これはopensslなどを用いて生成した任意の証明書一式を利用できますが、今回は自己署名証明書を使いましょう。実はser2netが使用しているgensioライブラリには、このあたりのツールが一通り揃っています。動作確認にもgensioのツールを使うと便利ですので、まずはそれをインストールしましょう。

$ sudo apt install gensio-bin
$ sudo gtlssh-keygen --keydir /etc/ser2net --commonname "$(hostname)-ser2net" serverkey ser2net
Key created at /etc/ser2net/ser2net.key.
Server certificate /etc/ser2net/ser2net.crt created.
$ ls -la /etc/ser2net/
total 16
drwxr-xr-x   2 root root 4096 Sep 20 14:15 .
drwxr-xr-x 109 root root 4096 Sep 20 14:15 ..
-rw-r--r--   1 root root  595 Sep 20 14:15 ser2net.crt
-rw-------   1 root root  119 Sep 20 14:15 ser2net.key
$ file /etc/ser2net/ser2net.crt
/etc/ser2net/ser2net.crt: PEM certificate
$ sudo systemctl restart ser2net.service

証明書一式は/etc/ser2net/以下に保存されています。これで準備完了です。ちなみにsslオプションを有効化した場合は、通常のtelnetコマンドでは接続できなくなります。いくつかの対応案がありますが、今回はgensio-binに含まれるgensiotコマンドを使ってみましょう。

$ gensiot telnet,ssl,localhost,2000
open error on telnet,ssl,localhost,2000: Certificate is not valid

指定するオプションてはプロトコル,ssl,接続先アドレス,接続先ポート番号です。⁠ssl」を省けば、普通のtelnetクライアントとしても使えます。ただ、今回は自己署名証明書を使ったため、証明書関連のエラーがでてしまっています。そこでCAファイルを明示的に指定しましょう。

$ gensiot 'telnet,ssl(CA=/etc/ser2net/ser2net.crt),localhost,2000'
ser2net port telnet,ssl,2000 device serialdev, /dev/ttyUSB0, 115200n81,local [115200N81,CLOCAL,HANGUP_WHEN_DONE] (Ubuntu)
(以下略)

無事に接続できたことでしょう。なお、gensiotの切断方法はCtrl-\ qです。コントロールキーを押しながらバックスラッシュを入力します。その後コントロールキーを離してからqキーを押すと、切断完了です。これで通信は暗号化できました。

もしどうしてもtelnetコマンドを使いたい場合は、telnet-sslパッケージをインストールしましょう。これはtelnetコマンドにsslオプションを追加するバイナリです。

$ sudo apt install telnet-ssl
$ telnet -z ssl,secure,cacert=ser2net.crt localhost 2000
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

ser2net port telnet,ssl,2000 device serialdev, /dev/ttyUSB0, 115200n81,local [115200N81,CLOCAL,HANGUP_WHEN_DONE] (Ubuntu)

(以下略)

ポイントは-z ssl,secure,cacert=ser2net.crtを指定していることです。これにより通常のtelnetプロトコルではなく、暗号化セッションを張ってからその中でtelnetプロトコルを用いてやりとりすることになります。なお、リモートから接続する場合は、/etc/ser2net/ser2net.crtをサーバーからコピーしておくことを忘れないようにしてください。

クライアント証明書を使ってユーザー認証する

ser2netのSSL/TLS機能は通信を暗号化するだけです。これにより平文でパスワード等が流れることはなくなりますが、CA証明書ser2net.crtファイルがあれば誰でも接続できることには変わりありません。ser2netはさらにクライアント証明書を用いたユーザー認証にも対応しているため、こちらも使ってみましょう。ユーザー認証には次の2種類が存在します。

  • accepterにssl(clientauth)を指定する
  • accepterにcertauthを指定する

前者はTLS/SSLのクライアント認証の仕組みをそのまま使っています。一応、動作自体はするのですが、設定が大変だったり、うまく動かないポイントが多かったりと手間がかかるためおすすめはしません。

それに対して後者はgensioが提供する「Shell over TLS」の仕組みを使います。telnetコマンドは使えなくなりますが、その代わりSSHのような接続インターフェースを持つgtlsshコマンドでアクセスすることになります。今回はこのcertauth機能を設定してみましょう。

まず、例のごとく/etc/ser2net.yamlを次のように修正します。

  accepter: telnet,mux,certauth,ssl,2000

「mux」「certauth」が追加されました。⁠mux」は1セッションを複数のストリームで多重化するオプションです。gtlsshコマンドは何も指定しないとストリームを多重化しようとするため、muxオプションを追加しています。

次にクライアント側で認証用の鍵ペアを生成しましょう。

クライアント$ gtlssh-keygen --keydir . --commonname shibata keygen shibata
Key created at ./shibata.key.
Certificate created.  Put ./shibata.crt into:
  .gtlssh/allowed_certs
on remote systems you want to log into without a password.

SSHで言うところの公開鍵に該当する「shibata.crt」と秘密鍵に該当する「shibata.key」が作成されます。このファイルのうち秘密鍵(と公開鍵)をクライアント側に、公開鍵をサーバー側に保存します。

クライアント側の保存方法は次の2種類のいずれかです。

  • ~/.gtlssh/keycerts/ホスト名.{key,crt}として保存する
  • ~/.gtlssh/default.{key,crt}として保存する

複数の鍵を運用したい場合は、ホスト名ごとに別のファイル名を作ります。合致するホスト名がない場合は~/.gtlssh/default.{key,crt}が使われます。今回はdefault.{key,crt}として保存することにしましょう。

クライアント$ mkdir -p ~/.gtlssh/
クライアント$ chmod 700 ~/.gtlssh/
クライアント$ mv shibata.key ~/.gtlssh/default.key
クライアント$ cp shibata.crt ~/.gtlssh/default.crt
クライアント$ openssl pkey -in ~/.gtlssh/default.key -text -noout | head -n 2
Private-Key: (2048 bit, 2 primes)
modulus:

さらにクライアント側には前節で作成したサーバーの証明書も保存しておきます。

クライアント$ mkdir -p ~/.gtlssh/server_certs/
クライアント$ cp ser2net.crt ~/.gtlssh/server_certs/ホスト名,ポート番号.crt
クライアント$ gtlssh-keygen rehash ~/.gtlssh/server_certs/
クライアント$ ls -l ~/.gtlssh/server_certs/
total 4
lrwxrwxrwx 1 shibata member  11 Oct 19 14:13 12f7e1ca.0 -> ser2net,2000.crt
-rw-r--r-- 1 shibata member 595 Oct 19 14:13 ser2net,2000.crt

クライアント側に保存する場合は、そのファイル名をホスト名,ポート番号.crtもしくはIPアドレス,2000.crtとしなくてはなりません。これは後述するgtlsshコマンドで指定するアドレスに依存します。IPv4/IPv6アドレスを個別に指定したい場合は、ipv4,IPアドレス,ポート番号.crtとしてください。上記ではホスト名を「ser2net⁠⁠、ポート番号を「2000」にした場合を例示しています。新規にファイルを作成したらgtlssh-keygen rehashコマンドの実施を忘れないでください。

なお、サーバーの証明書は最初の接続時に見つからなかったら、フィンガープリントを表示して保存してくれます。SSHにおける~/.ssh/known_hostsのような形になると考えれば良いでしょう。よって証明書のコピー自体は実施しなくてもいいのですが、明確にこのサーバー証明書を使うと意識するためにもコピーする癖を付けましょう。

サーバー側には公開鍵をコピーして保存します。こちらはまず、ユーザー名(ここでは「shibata⁠⁠)に合わせたディレクトリを作成してください。

サーバー$ sudo mkdir -p /usr/share/ser2net/auth/shibata/allowed_certs/
サーバー$ sudo mv shibata.crt /usr/share/ser2net/auth/shibata/allowed_certs/
サーバー$ sudo gtlssh-keygen rehash /usr/share/ser2net/auth/shibata/allowed_certs/
サーバー$ ls -l /usr/share/ser2net/auth/shibata/allowed_certs/
total 4
lrwxrwxrwx 1 root    root      11 Oct 19 04:49 964d1b2a.0 -> shibata.crt
-rw-r--r-- 1 shibata shibata 1200 Oct 19 04:48 shibata.crt

ファイル名もユーザー名.crtとなります。なおサーバー側もgtlssh-keygen rehashが必要です。

これで準備が完了しました。クライアント側から次のようにアクセスしてみましょう。

クライアント$ gtlssh -p 2000 ホスト名
gensio err log: Error verifying certificate: self-signed certificate
Certificate for ホスト名 ホスト名,2000
 is not present, fingerprint is:
8D:61:41:B0:B1:8C:BB:B5:43:E5:C2:A8:53:35:7D:9D:DD:BF:DD:8E
Please validate the fingerprint and verify if you want it
added to the set of valid servers.
Add this certificate? (y/n): y

ser2net port telnet,mux,certauth,ssl,2000 device serialdev, /dev/ttyUSB0, 115200n81,local [115200N81,CLOCAL,HANGUP_WHEN_DONE] (Ubuntu)
(以下略)

上記の例ではサーバー証明書をクライアント側にコピーしなかった場合の結果を表示しています。もしクライアント側に存在しない場合は、フィンガープリントと「Add this certificate」の問い合わせが表示されます。

いずれにせよ無事にコンソールが表示されたら成功です。切断はgensiotと同じくCtrl-\ qです。コントロールキーを押しながらバックスラッシュを入力します。その後コントロールキーを離してからqキーを押すと、切断完了です。

特定のコンソールを特定のユーザーだけ利用できるようにする

ser2netでユーザー用のポートと、管理用ポートを公開していたとします。その時、管理用ポートは特定のユーザーにのみアクセスを許可したいこともあるでしょう。これはallowed-usersオプションで実現できます。具体的には次のように設定してください。

  options:
    banner: *banner
    kickolduser: false
    max-connections: 3
    allowed-users: shibata

allowed-usersは空白区切りで複数のユーザー名を指定できます。allowed-users自体を指定しなければ誰でも接続可能です。また空の場合は「誰もアクセスできない」状態となります。

mDNSでシリアルポート情報を公開する

ser2netはmDNSもサポートしています。ただし「gtlsshやgensiot、telnetにmDNSで公開された名前を使える」わけではありません。ser2netのmDNS機能は「mDNSでser2netの設定情報を公開する」ためのものです。これにより、⁠このポート番号はどの接続方式だったっけ」という疑問を解消できます。

実際に使ってみましょう。まずmDNS機能を使うためには、サーバー側にlibavahi-daemonをインストールしておく必要があります。

$ sudo apt install libavahi-daemon

次にオプションでmdns機能を有効化しましょう。

  options:
    mdns: true

あとは適当なマシンからavahi-utilsパッケージに含まれる、avahi-browseコマンドを実行します。

$ avahi-browse -rt _iostream._tcp
+ ens160 IPv6 con1                                          _iostream._tcp       local
+ ens160 IPv4 con1                                          _iostream._tcp       local
= ens160 IPv6 con1                                          _iostream._tcp       local
   hostname = [ホスト名.local]
   address = [IPv6アドレス]
   port = [2000]
   txt = ["gensiostack=telnet,mux,certauth,ssl,tcp" "provider=ser2net"]
= ens160 IPv4 con1                                          _iostream._tcp       local
   hostname = [ホスト名.local]
   address = [IPv4アドレス]
   port = [2000]
   txt = ["gensiostack=telnet,mux,certauth,ssl,tcp" "provider=ser2net"]

con1はホスト名.localのポート「2000」でアクセスできることがわかります。さらにcertauthとsslから認証と暗号化が有効になっており、gtlsshでのアクセスを行う必要性を確認できます。

ser2netに登録したシリアルポートが増えるほど、ここのリストも増えます。mDNSの情報をもとに「con1はポート2000だな」と判断するわけです。言い方を変えると「con1」は、⁠どのデバイスに繋がっているシリアルポートなのか」を把握しやすい名前にしておきましょう。

mDNSに表示する項目は多少調整が可能です。詳細はser2net.yamlのmanページを参照してください。特にmdns-type: _名前._tcpを設定すると、上記の_iostream._tcpを置き換えられます。アンダースコアは必要なこと、プロトコルに応じて後ろの._tcpを変えなくてはいけない旨は注意してください。

おすすめ記事

記事・ニュース一覧