Raspberry pi を RADIUS サーバーにしたい
Wi-Fi でつなぐ端末も管理したい...
前回の記事で、クローズネットワーク内に Raspberry pi を置いて、NAT ルーター兼 DHCP + DNS サーバーにしました。
ただこのままだと端末を有線でつなぐ必要があるので、Raspberry pi の下にアクセスポイントを置くことにしました。
アクセスポイントのセキュリティ方式にはいくつかありますが、Raspberry pi がネットワークを管理しているような状態なので、Wi-Fi の認証も Raspberry pi にやらせることにしました。
方式は WPA Enterprise で、Raspberry pi を RADIUS サーバーにします。
ネットワークの完成イメージ
Raspberry pi の配下にアクセスポイントを追加し、Wi-Fi での接続機能を付加します。
この Wi-Fi で接続できる端末認証を Raspberry pi に持たせるようにします。
以下の YouTube 映像が、今回の動作イメージです。
www.youtube.com
なお、この映像のとおりにやると、認証方式が freeradius デフォルトの "MD5" になります。
これは脆弱性が指摘されている方式のため、今回は "PEAP" という方式でセットアップします。
認証方式については以下のサイトが詳しいです。
必要な部品
WPA/WPA2 Enterprise に対応したアクセスポイントが必要です。
自宅では TP-Link を使っています。
(別にこのメーカー、機種でなくても良いですが、一例です)
TP-Link 無線LANルーター 11ac/n/a/g/b 866+300Mbps デュアルバンド ギガビット 3年保証 Wi-Fiルーター Archer C55
- 出版社/メーカー: TP-LINK
- 発売日: 2016/11/24
- メディア: Personal Computers
- この商品を含むブログを見る
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
# 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 ではないですが、以下を参考にしました。
これを 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 に載せられるようになりました。
Python で自身の IP アドレスを取得したい - Windows 編
[2019/07/17 追記]
本記事は Windows でのみ動作します。
Ubuntu 編を改めて記載しましたので、Ubuntu の方は以下の記事をご覧ください。
低レイヤーのネットワークプログラミングをしていると、自身の IP アドレスを取得したいことがよくあります。
ところが Python では、これを簡単に取得するということができませんでした。
Stack overflow でもこれが議論になっており、一番簡単なのは外部ライブラリ "netifaces" を使うという結論でした。
stackoverflow.com
でも、移植性を考えると、できれば外部ライブラリは使いたくありません。
なんかないかな~と探していたら、標準ライブラリでいけそうな方法を見つけたので紹介します。
Python socket モジュールで自身の IP アドレスを取得する
実行環境は Python 3.5.3 / Windows 10 です。
標準ライブラリの socket モジュールを使います。
$ python --version Python 3.5.3 :: Anaconda 4.4.0 (64-bit)
IP アドレスを取得する socket API
以下の 2 種類があります。
- socket.gethostbyname(hostname)
- ホスト名を '100.50.200.5' のようなIPv4形式のアドレスに変換します。
- socket.gethostbyname_ex(hostname)
- (hostname, aliaslist, ipaddrlist) のタプルを返し、 hostname は ip_address で指定したホストの正式名、 aliaslist は同じアドレスの別名のリスト(空の場合もある)、 ipaddrlist は同じホスト上の同一インターフェースのIPv4アドレスのリスト(ほとんどの場合は単一のアドレスのみ)を示します。
どちらも引数は hostname です。
つまり、自身の hostname を取得できれば、上記の API に渡すことで IP アドレスを取得できるということです。
上記を組み合わせて、自身の IP アドレスを取得する
まずは自身の IP アドレスを確認してみます。
$ ipconfig | grep IPv4 ipconfig | grep IPv4 IPv4 Address. . . . . . . . . . . : 192.168.1.10 IPv4 Address. . . . . . . . . . . : 192.168.56.1 IPv4 Address. . . . . . . . . . . : 10.150.2.139
3 つもありました。
1 番上は有線、2 番目は Virtual Box の仮想アダプタ、3 番目は無線です。
では、Python コンソールで socket API を使ってみましょう。
>>> import socket >>> socket.gethostname() 'edosha_notepc' >>> socket.gethostbyname(socket.gethostname()) '192.168.1.10' >>> socket.gethostbyname_ex(socket.gethostname()) ('edosha_notepc', [], ['192.168.56.1', '10.150.2.139', '192.168.1.10'])
gethostbyname で有線の IP アドレス、gethostbyname_ex で全ての IP アドレスが取得できました。
私のように 3 つもアドレスを使うのでなければ、gethostbyname でよさそうです。
どれかを選ばせたいときは gethostbyname_ex が有効かもしれません。