子育てしながらエンジニアしたい

現在 2 歳女の子の子育て中エンジニアによる、技術系 + 日常系ブログ。

Raspberry pi で SSH ログアウト後もプロセスを残したい

作業の途中で SSH ログアウトしたい...

前回の記事で、Raspberry piRadius サーバーとして活用する方法をご紹介しました。

edosha.hatenablog.jp

こんなふうに大量の作業をするときは、一時中断するために SSH からログアウトすることもあります。
コンフィグファイルなど、ファイルに書き込むような作業はログアウトしても問題ありませんが、たとえば Python スクリプトを走らせっぱなしにしてログアウトすると、Python プロセスが終了してしまいます。

それを避けるために 2 つの方法を試しました。

SSH ログアウト後もプロセスを残す方法

1. screen コマンドを使う

Linux では仮想端末を作成する screen というコマンドがあります。
SSH をログアウトしても仮想端末を残す」ことで、仮想端末上にプロセスを残すことができます。

askubuntu によれば、tmux というコマンドのほうが良いぜ!ということらしいのですが、Raspberry pi 上に入れるのに一苦労しそうだったので、まぁ良いかということで screen にしました。

askubuntu.com

インストール

apt-get でインストールするだけです。

$ sudo apt-get install screen
プロセスの残し方

基本的には以下の流れがすべてです。

1. screen を起動する

$ screen

2. 所望のプロセスを起動する
3. Ctrl + A、Ctrl + D をタイプする

これで、所望のプロセスが起動したまま、仮想端末から抜ける (デタッチ) ことができます。
抜けた後で SSH ログアウトしても、仮想端末が残っているのでプロセスも残る、というわけです。

ちなみに、またこの仮想端末に入るときは

$ screen -r

とすれば OK です。

その他の screen コマンド

いくつか便利なコマンドを紹介します。

  • screen を起動するときに出るメッセージを消す
    • screen -q
  • 仮想端末のリストを出す
    • screen -list
  • 該当する仮想端末にアタッチする
    • screen -r [プロセス番号]
  • 該当する仮想端末を削除する
    • screen -X -S [プロセス番号] quit
  • コマンドのヘルプを見る
    • screen -h

その他、以下のサイトに詳しく紹介してくれています。

qiita.com


2. シリアルを使う

Raspberry pi はシリアル接続でコマンドを投げることもできます。
SSH ログアウト後」という趣旨とはちょっと違いますが、そもそも SSH/ネットワークを使わなければプロセスはそのまま残る、ということで、この方法も紹介しておきます。

なお PC とシリアル接続をするには、USB シリアル変換とジャンパーケーブルが必要です。
自分は以下の製品を使っています。

ジャンパワイヤ(オス?メス) 10本セット

ジャンパワイヤ(オス?メス) 10本セット

Raspberry pi のシリアルを有効にする

まず最初に、Raspberry pi のシリアルを使えるようにしてあげる必要があります。
なお Raspberry pi のモデルや OS バージョンによって、微妙にやり方が異なるようです。
自分が使っているのは Raspberry pi 3 model B で、OS は Raspbian Jessie 8.0 (2017/07/05) です。

ここは SSH にログインしてやりましょう。

$ sudo raspi-config

5. Interfacing Options を選択します。
P6 Serial を選択し、Yes にします。
そのあとリブートすれば、シリアルが有効になります。

Raspberry pi との接続

以下のように Raspberry pi と USB シリアル変換を接続します。
Raspberry pi 3 model B でやっていますが、他のモデルだと配置が違うと思うのでご注意ください。

f:id:edosha:20171110141446j:plain:w400

Raspberry pi --- USB Serial
6 (GND)       -  GND
8 (UART TX)   -  RX
10 (UART RX)  -  TX

TX と RX を接続することに注意が必要です。
TX 同士、RX 同士をつないでも送受信できません。
また、スイッチサイエンスの USB シリアル変換モジュールは 5V/3.3V の両対応になっており、ジャンパで切り替えられます。
Raspberry pi は 3.3V なので、あらかじめ 3.3V 側にジャンパしておきましょう。

最後に PC と USB シリアル変換モジュールを USB でつなげば完了です。
ドライバは勝手にインストールされる、と思います。

シリアル接続の良いところは、余計なコマンドを叩かなくても良いことですね。
忘れっぽい自分に合ってます。
デメリットはもちろん外部モジュールが必要なことですが...

Raspberry pi を RADIUS サーバーにしたい

Wi-Fi でつなぐ端末も管理したい...

前回の記事で、クローズネットワーク内に Raspberry pi を置いて、NAT ルーターDHCP + DNS サーバーにしました。

edosha.hatenablog.jp

ただこのままだと端末を有線でつなぐ必要があるので、Raspberry pi の下にアクセスポイントを置くことにしました。
アクセスポイントのセキュリティ方式にはいくつかありますが、Raspberry pi がネットワークを管理しているような状態なので、Wi-Fi の認証も Raspberry pi にやらせることにしました。
方式は WPA Enterprise で、Raspberry piRADIUS サーバーにします。

ネットワークの完成イメージ

f:id:edosha:20170913144158p:plain

Raspberry pi の配下にアクセスポイントを追加し、Wi-Fi での接続機能を付加します。
この Wi-Fi で接続できる端末認証を Raspberry pi に持たせるようにします。

以下の YouTube 映像が、今回の動作イメージです。
www.youtube.com
なお、この映像のとおりにやると、認証方式が freeradius デフォルトの "MD5" になります。
これは脆弱性が指摘されている方式のため、今回は "PEAP" という方式でセットアップします。

認証方式については以下のサイトが詳しいです。

必要な部品

WPA/WPA2 Enterprise に対応したアクセスポイントが必要です。
自宅では TP-Link を使っています。
(別にこのメーカー、機種でなくても良いですが、一例です)

Raspberry pi の設定

Raspberry pi のモデル、OS

使用しているのは、前回の記事と同じく Raspberry pi 3 model B です。
OS は Raspbian Jessie Lite です。

freeradius のセットアップ

Linux 系で有名な RADIUS サーバーは freeradius のようなので、このライブラリを使うことにします。

インストール

まずは、必要なパッケージをインストールします。

$ sudo apt-get install freeradius freeradius-mysql apache2 php5 libapache2-mod-php5 mysql-server mysql-client php5-mysql php-pear php5-gd php-db
MySQL を使用したユーザー管理

freeradius のユーザーデータベースは、デフォルトではファイルに平文で管理されています。
これを MySQL を用いた管理に変更したいと思います。

まずは /etc/freeradius/radiusd.conf を編集します。

$ sudo emacs /etc/freeradius/radiusd.conf

以下の 2 行がそれぞれコメントアウトされているので、"#" を削除します。

# $INCLUDE sql.conf
# $INCLUDE sql/mysql/counter.conf

次に、/etc/freeradius/sql.conf を編集します。

$ sudo emacs /etc/freeradius/sql.conf

以下の部分を、所望の形になるように編集します。
これらは、あとで設定する MySQL のデータベースと同じになるようにします。

server = "localhost"
#port = 3306
login = "radiususer"
password = "radius_password"
# Database table configuration for everything except Oracle
radius_db = "radiusdb"

最後に、/etc/freeradius/sites-enabled/default を編集します。

$ sudo emacs /etc/freeradius/sites-enabled/default

以下の "sql" がコメントアウトされているので、"#" を削除します。

# See "Authorization Queries" in sql.conf
sql
# See "Accounting queries" in sql.conf
sql
# See "Simultaneous Use Checking Queries" in sql.conf
sql
# See "Authentication Logging Queries" in sql.conf
sql
MySQL データベースの作成

上で sql.conf に設定したものと同じように、MySQL のデータベースを作成します。

$ mysql -u root -p
mysql>create database radiusdb;
mysql>exit;

データベースの設定の雛形は、freeradius のディレクトリ配下にあります。
これを radiusdb に流し込みます。
読み取り権限がついていないファイルだったので、最初に権限を付加しています。

$ sudo chmod +r /etc/freeradius/sql/mysql/schema.sql
$ mysql -u root -p radiusdb < /etc/freeradius/sql/mysql/schema.sql

freeradius ライブラリが使用するユーザーを追加します。
こちらも sql.conf に記載したものと同じようにします。

$ mysql -u root -p
mysql>CREATE USER 'radiususer';
mysql>SET PASSWORD FOR 'radiususer' = PASSWORD('radius_password');
mysql>GRANT ALL ON radiusdb.* to 'radiususer';
mysql>exit;
ユーザーの作成

MySQL データベースに、ユーザーを追加します。
ユーザーごとに動作を変えることもできるようなのですが、今回はとりあえず追加だけにしました。
詳しくは公式ページの How Toをご覧ください。

まずは作ったデータベースに接続し、テーブルを確認します。

$ mysql -u root -p
mysql> connect radiusdb;
mysql> show tables;
+--------------------+
| Tables_in_radiusdb |
+--------------------+
| radacct            |
| radcheck           |
| radgroupcheck      |
| radgroupreply      |
| radpostauth        |
| radreply           |
| radusergroup       |
+--------------------+
7 rows in set (0.00 sec)

このうち、ユーザーは radcheck で管理されます。
テーブルの書式を describe で確認します。

mysql> describe radcheck;
+-----------+------------------+------+-----+---------+----------------+
| Field     | Type             | Null | Key | Default | Extra          |
+-----------+------------------+------+-----+---------+----------------+
| id        | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
| username  | varchar(64)      | NO   | MUL |         |                |
| attribute | varchar(64)      | NO   |     |         |                |
| op        | char(2)          | NO   |     | ==      |                |
| value     | varchar(253)     | NO   |     |         |                |
+-----------+------------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

たとえば、

  • User ID: test_user
  • Password: test_pass

と設定したければ、それぞれ以下のように設定するようです。

  • id: 任意のユニーク値
  • username: test_user
  • attribute: Cleartext-Password
  • op: :=
  • value: test_pass

したがって以下のようにユーザーを追加します。

mysql> insert into radcheck values(1, 'test_user', 'Cleartext-Password', ':=', 'test_pass');
Query OK, 1 row affected (0.02 sec)

mysql> select * from radcheck;
+----+-----------+--------------------+----+-----------+
| id | username  | attribute          | op | value     |
+----+-----------+--------------------+----+-----------+
|  1 | test_user | Cleartext-Password | := | test_pass |
+----+-----------+--------------------+----+-----------+
1 rows in set (0.00 sec)
アクセスポイントとの接続設定

アクセスポイントの IP や、認証の際に使用する secret (パスワード) を設定します。
まず最初に /etc/freeradius/clients.conf を編集します。

$ sudo emacs /etc/freeradius/clients.conf

このファイルの適当なところに、サーバー情報を記載します。
いろいろな書き方があるようで、clients.conf にはいくつか例が記載されています。
ここでは、アクセスポイントは 192.168.0.0/24 配下にあり、secret は somesecret としました。
この secret は、アクセスポイントにも同じキーワードを設定する必要があります。

client 192.168.0.0/24 {
 secret = somesecret
 shortname = radius_wlan_guest
}
PEAP 認証の設定

デフォルトでは MD5 形式になっている認証を、PEAP に変更します。
まず /etc/freeradius/eap.conf を編集します。

$ sudo emacs /etc/freeradius/eap.conf

このファイル内に default_eap_type というところがあります。
そこを md5 から peap に変更します。

default_eap_type = peap

次に /etc/freeradius/modules/mschap を編集します。

$ sudo emacs /etc/freeradius/modules/mschap

以下の部分を編集します。

use_mppe = yes
require_encryption = yes
require_strong = yes
with_ntdomain_hack = yes

最後に /etc/freeradius/sites-enabled/inner-tunnel を編集します。

$ sudo emacs /etc/freeradius/sites-enabled/inner-tunnel

以下の sql 部分のコメントアウトを外します。

# See "Authorization Queries" in sql.conf
sql
# See "Simultaneous Use Checking Queries" in sql.conf
sql
# See "Authentication Logging Queries" in sql.conf
sql
サーバー証明書の作成

PEAP 認証ではサーバー証明書が必要になります。
本当は、信頼できる証明書が取得できれば一番良いのですが、個人レベルでの取得は難しそうなので、自作証明書を作ります。
freeradius では証明書を簡単に作れるサンプルがありますので、それを使います。

必要なファイルは /usr/share/doc/freeradius/examples/certs 以下にあるので、それを freeradius 以下にコピーします。

cd /usr/share/doc/freeradius/examples/certs
sudo cp Makefile ca.cnf server.cnf xpextensions /etc/freeradius/certs
cd /etc/freeradius/certs

ca.cnf と server.cnf を同じように編集します。
パスワードや国、地域は任意で設定してください。

[ CA_default ]
default_days = 1826 # 5 years
[ req ]
input_password = your_inputoutput_password
output_password = your_inputoutput_password
[certificate_authority]
countryName = JP
stateOrProvinceName = somestate
localityName = yourtown
organizationName = yourorg
emailAddress = mail@yourdomain.com
commonName = "some cool short desription"

ca.cnf と server.cnf が編集できたら、make します。

sudo make all

最後に、/etc/freeradius/eap.conf を編集します。

$ sudo emacs /etc/freeradius/eap.conf

さきほど証明書のところで設定したパスワードを、eap.conf の private_key_password に設定します。

private_key_password = your_inputoutput_password
freeradius の起動

編集が終わったので、freeradius を再起動します。
普通に再起動するときは

$ sudo service freeradius restart

デバッグモードで起動するときは、

$ sudo service freeradius stop
$ sudo freeradius -X

となります。

アクセスポイントの設定

アクセスポイント側では以下の項目を設定します。
機種によって文言は違うかもしれませんが、だいたいこんな感じかと思います。

  • セキュリティ: WPA-Enterprise (もしくは WPA2-Enterprise)
  • RADIUS サーバー IP: Raspberry pi の IP
  • RADIUS サーバーポート: 1812
  • RADIUS シークレット (パスワード): /etc/freeradius/clients.conf の secret に設定した値

これで、アクセスポイントとの接続設定ができているはずです。
freeradius をデバッグモードで起動し、Wi-Fi で端末を接続してみると、メッセージが出ていると思います。

Python: JSON でバイナリを扱う

バイナリデータをネットワーク送受信したい

バイナリデータをネットワーク越しに送受信したいことがあります。
普通にバイナリだけ送れば良いのですが、そのバイナリの属性値などを一緒に送りたいという要望がありました。
いろんな属性値を一度に送るのは JSON が便利なので、バイナリも JSON 形式に載せて送りたいと思ったのですが、ちょっと手こずったので記録しておきます。

テキストを JSON で送るには...

テキスト形式の dict を JSON として送信するのは、json モジュールを使って簡単に実現できます。

import json

# 送りたい dict データ
dictdata = {
  "str": "hoge",
  "num": 2,
}

# json.dumps を使って文字列に変換
strdata = json.dumps(dictdata)

# encode を使ってネットワークで送れるバイナリ形式に変換
bindata = strdata.encode()

# ネットワークに送る

しかし、このやり方でバイナリを送ろうとすると、json.dumps でエラーになります。

import json

# 送りたい dict データ
dictdata = {
  "str": "hoge",
  "bin": b"aa",
}

# json.dumps を使って文字列に変換
strdata = json.dumps(dictdata)

# ここで下記のエラーになる
# TypeError: b'aa' is not JSON serializable

こういうときは、バイナリを文字列に変換してから送ると良いようです。

バイナリを base64 エンコードする

Python ではないですが、以下を参考にしました。

qiita.com

これを Python で実現するには、そのままずばりの base64 モジュールを使います。

import json
import base64

# 送りたい dict データ
dictdata = {
  "str": "hoge",
  "bin": base64.b64encode(b"aa").decode('utf-8'), # base64 エンコード
}

# json.dumps を使って文字列に変換 (エラーにはならない)
strdata = json.dumps(dictdata)

# encode を使ってネットワークで送れるバイナリ形式に変換
bindata = strdata.encode()

# ネットワークに送る

注意しなければならないのは、base64.b64encode の出力はバイト列になるということです。
つまり b64encode の出力は String ではないので、json.dumps で変換できません。
これを解消するために、b64encode したものをさらに .decode('utf-8') することで文字列に変換し、json.dumps で変換できるようにしています。

バイナリと文字列を何度行き来しているんだという感じですが...

受信するときは

上記のようにエンコードされたデータを受信するときは以下のような感じになります。

import json
import base64

# ネットワークから受信したデータ: binrx
# decode を使って、バイナリから文字列に変換
strrx = binrx.decode()

# json.loads を使って dict に変換
dictrx = json.loads(strrx)

# base64 を使って元のバイナリに変換
dictrx['bin'] = base64.b64decode(dictrx['bin'].encode())

ちょっと複雑ですが、これでバイナリを JSON に載せられるようになりました。