はじめに
「Wiki IoT/Bot Computing」 というのを開発中です。これは一種の並列計算機構で、「Wiki Bot」と名付けた、IoTのエッジ端末がこの計算機構の演算装置群であり、インターネット上に分散配置されたWiki (PukiWiki) のページをこの計算機構の記憶装置として使おうとするものです。Wiki Bot のソースコードの一部は、PukiWiki のAPIとして利用することができます。今回、Wiki Bot にユーザ認証とアクセス制御の機能を加えました。
経緯
2011年頃、スマホOSのAndroid でADKが利用できるようになりました。ArduinoからはArduino Mega ADKが発売されました。これにより、Android とArduino Mega をUSBケーブルで接続して、Android で、Arduino Megaに接続された機器を操作したり、Arduino Megaに接続されたセンサの値を収集してAndroidに送ることができるようになりました。Android端末は加速度センサやGPSなど、様々なセンサをもともと備えているのですが、Arduino Megaと接続することで、接続できるセンサの種類が多くなり、Android側からArduino Megaに接続された様々なアクチュエータを動かすことができるようになりました。
2012年頃、ADKを使って、Arduino MEGAと Android を組み合わせて作ったセンサ端末を作成し、そのセンサ端末で収集したデータをWiki (PukiWiki)に保存することができるようにしました。ArduinoとAndroidとWikiで、一種のIoTシステムを作ったわけです。これについて、論文"A M2M system using Arduino, Android and Wiki Software"で発表したところ、世界中の研究論文でこの論文を引用していただきました。
その後、Androidの代わりにRaspberry Pi を使い、Arduino Mega を使わずに、直接 Raspberry Pi のGPIOにセンサを接続して、サーバ室の監視を行うIoTシステムを開発し、以下の論文で発表しました。この時、Wikiに書かれたスクリプトにより、Raspberry Piで作ったIoT端末を制御できるようになっていました。この時の端末側ハードとソフトを Wiki Bot と呼んでいます。
その後、Raspberry Pi にArduinoと赤外線LEDを組み合わせて作った赤外線リモコンを接続し、Wiki に書かれたスクリプトによってプロジェクタのON/OFFを制御するシステムを開発し、以下の論文で発表しました。
従来、Wiki Bot はJavaを中心にして作っていましたが、そのWiki Botの機能を、以下の記事の、ページ変更機能とページ挿入機能の追加により、ほぼ、Python に移し替えました。
これにより、毎時ごとにその時間のデータを、その時間のページ(合計24ページ)に書き込むとともに、毎日、その日のデータを、その日のページ(合計31ページ)に書き込むシステムを実現することができます。また、このとき、毎時ごとに行う処理のスクリプトを1つのページにまとめて書いておき、それを毎時ごとのページに挿入することができます。同じように、毎日行う処理のスクリプトを1つのページにまとめて書いておき、それを日々のページに挿入することができます。
これに加えて、以下の記事で、Java版にはなかった、画像などのファイルをuploadする機能を加えました。
ユーザ認証とアクセス制御
今回、Wiki IoT/Bot Computing でユーザ認証とアクセス制御ができるようになりました。従来のJava版のWiki Botでも、Basic 認証には対応していたのですが、この時は、閲覧と編集の両方を禁止するか、両方を許可するかのどちらかになっていました。PukiWiki は、その設定ファイル、pukiwiki.ini.php の設定により、IDによって、PukiWikiサイトの読みこみ可、読みこみ不可、編集可、編集不可を制御できるのですが、WikiBotにIDとパスワードを設定して、そのIDに許可されたWikiサイトへのアクセスが可能になりました。
利用環境
以下を用意します。
- Wiki BotのハードウェアであるRaspberry Pi。Raspbian をOSとして利用します。
- Wiki Botを動かすスクリプトのページと、Wiki Botを動かした結果、得られるデータを格納するための PukiWikiのサイト。
- Raspberry Pi からPukiWikiのサイトにアクセスするためのネットワーク。
Wiki Bot を Raspberry Pi で動かすための準備
Raspberry Pi 側の準備
ホームディレクトリの直下に、pythonという名前のディレクトリを作成し、そのディレクトリに移動します。
cd ~/
mkdir python
cd python
Wiki Botのプログラム(pythonプログラム)は requests_toolbelt パッケージの、MultipartEncoder クラスやpyserialパッケージを利用しているのですが、最近のRaspberry Pi OSでは、pip3コマンドが使えないので、
$ sudo apt install virtualenv python3-virtualenv -y
$ virtualenv -p /usr/bin/python3 testpip
を実行して、python3の仮想環境が使えるようにします。
$ source testpip/bin/activate
を実行して、仮想環境に入ります。
pip3 install requests_toolbelt
pip3 install pyserial
を実行して、 requests_toolbelt と pyserial を install しておきます。
参考:
以下のリンク先のpython のプログラム wiki_bot_10.py が、ユーザ認証とアクセス制御の機能を加えた Wiki Bot のプログラムです。これを、Raspberry Pi で動かします。
上のプログラムをダウンロードして、 Raspberry Pi の /home/pi/pythonの直下に置きます。
PukiWikiの準備
WikiBot を動かすためのスクリプトを格納したり、WikiBotの実行結果を格納したりするための、PukiWikiのサイトとページを用意します。PukiWikiは以下のサイトからダウンロードできます。
Wiki IoT/Bot Computing では、PukiWikiのサイトが格納されたサーバに障害が発生したときのため、PukiWikiのサイトを2つ(2つ以上)のサーバに格納しておき、High Availability (HA)構成をとることができます。
とりあえず、一つのサイトだけ使うこともできます。
Wiki Botは、PukiWikiのサイトに格納された、object_page を読み込み、そこに書かれた(又はinclude されたページに書かれた)スクリプトを定期的に実行します。PukiWiki のサイトや object_pageについては以下などを参照してください。
HA構成のとき、片方のURIを、object_page 1、もう片方のURIを object_page 2と呼ぶことにします。
以下のForm認証を使って、PukiWikiのユーザ認証とアクセス制御の設定を行います。
PukiWikiをサーバにインストール(解凍)したとき、インストールしたディレクトリに、PukiWiki の設定ファイルである pukiwiki.ini.php が格納されています。
このファイルで、admin password, userとpassword, readの可否, edit の可否を設定します。
admin password
pukiwiki.ini.phpの188行付近から以下のような記述があります。
/////////////////////////////////////////////////
// Admin password for this Wikisite
// Default: always fail
$adminpass = '{x-php-md5}!';
// Sample:
//$adminpass = 'pass'; // Cleartext
//$adminpass = '{x-php-md5}1a1dc91c907325c69271ddf0c944bc72'; // PHP md5() 'pa\
ss'
...
この中の、
$adminpass = '{x-php-md5}!';
を
//$adminpass = '{x-php-md5}!';
のようにコメントアウトして、
//$adminpass = 'pass'; // Cleartext
を
$adminpass = 'new_pass'; // Cleartext
のように書き換えることによって、adminpass を設定します。(この場合、adminpassは、new_pass になります)
生パスワードを設定ファイルに直接記述することはセキュリティの脆弱性につながりますので、この付近に書いてある他の方法でパスワードを設定する方法をお勧めします。
userとpassword
256行付近にある
/////////////////////////////////////////////////
// User definition
$auth_users = array(
// Username => password
'foo' => 'foo_passwd', // Cleartext
'bar' => '{x-php-md5}f53ae779077e987718cc285b14dfbe86', // PHP md5() \
'bar_passwd'
'hoge' => '{SMD5}OzJo/boHwM4q5R+g7LCOx2xGMkFKRVEx', // LDAP SMD5 \
'hoge_passwd'
);
を
/////////////////////////////////////////////////
// User definition
$auth_users = array(
// Username => password
'user_01' => 'pass_01', // Cleartext
'bar' => '{x-php-md5}f53ae779077e987718cc285b14dfbe86', // PHP md5() \
'bar_passwd'
'hoge' => '{SMD5}OzJo/boHwM4q5R+g7LCOx2xGMkFKRVEx', // LDAP SMD5 \
'hoge_passwd'
);
のように書き換えることによって、ID (ユーザ名)とそのパスワードを設定します。
この場合は、user_01というユーザIDに、パスワード pass_01 を設定しています。
readの可否
278行付近の
/////////////////////////////////////////////////
// Read auth (0:Disable, 1:Enable)
$read_auth = 0;
$read_auth_pages = array(
// Regex Groupname or Username
'#PageForAllValidUsers#' => 'valid-user',
'#HogeHoge#' => 'hoge',
'#(NETABARE|NetaBare)#' => 'foo,bar,hoge',
);
の部分を、
/////////////////////////////////////////////////
// Read auth (0:Disable, 1:Enable)
$read_auth = 1;
$read_auth_pages = array(
// Regex Groupname or Username
'##' => 'user_01',
'#PageForAllValidUsers#' => 'valid-user',
'#HogeHoge#' => 'hoge',
'#(NETABARE|NetaBare)#' => 'foo,bar,hoge',
);
のように書き換えることにより、ID user_01 のユーザが、
IDとパスワードによる認証を経て、このサイトのすべてのページ
を読むことができるようになり、ユーザIDが登録されていないユーザ
は、このサイトを読むことができなくなります。
ここで、
'##' => 'user_01',
の## は、このサイトの、任意のページ(の名前)を表します。
editの可否
readの可否の設定のちょっと後ろに、以下のような部分があります。
/////////////////////////////////////////////////
// Edit auth (0:Disable, 1:Enable)
$edit_auth = 0;
$edit_auth_pages = array(
// Regex Username
'#BarDiary#' => 'bar',
'#HogeHoge#' => 'hoge',
'#(NETABARE|NetaBare)#' => 'foo,bar,hoge',
);
これを
/////////////////////////////////////////////////
// Edit auth (0:Disable, 1:Enable)
$edit_auth = 1;
$edit_auth_pages = array(
// Regex Username
'##' => 'user_01',
'#BarDiary#' => 'bar',
'#HogeHoge#' => 'hoge',
'#(NETABARE|NetaBare)#' => 'foo,bar,hoge',
);
のように書き換えることで、user_01が、IDとパスワードによる認証を
経て、すべてのページの編集を行うことができるようになります。現時点では、Wiki Botがページを読む時(read)のユーザと編集(edit)するときのユーザは同じユーザである必要があります。
Wiki Botの初期設定
以下を実行して、Wiki BotのGUIを表示します。
python3 wiki_bot_10.py
起動すると、以下のGUIの左側のmain tabが表示されます。
main tab で、この図に書いてあるように、object_pageの初期URI (PukiWikiのページのURI)(バックアップのため、object_page 1と object_page 2 があります)と、画像等のuploadのための adminpassを設定します。
画面の上の方にあるauthをクリックすることで、右の、auth_tab が表示されます。
ここに、object_page 1 と object_page 2 のそれぞれの、URL、readが可能であるユーザのIDとパスワード、editが可能であるユーザのIDとパスワードを
指定します。
Wiki Bot はObject Page 以外の、アクセス可能な任意のURLのページを読むことができます。そのページがPukiWikiのページで、なおかつ、ユーザ認証によるアクセス制御がかかっているとき、object_page の場合と同様に、そのときのURL、readが可能であるユーザのIDとパスワード、editが可能であるユーザのIDとパスワードを指定します。
設定が終わったら、main_tab の上の方の「save property」 ボタンをクリックして、設定値を保存します。
Wiki Bot の実行
GUIの main_tabの「start」ボタンをクリックすることでWiki ページのスクリプトが繰り返し実行されます。前の設定を行って、実行できることを確認した後、実行を終了し、
仮想環境を動かしているターミナルで、
python3 wiki_bot_10.py -s
のように、-sオプションをつけて起動することにより、
それ以前に設定して保存した実行URIやadmin pass や読み書きのIDを使って、自動的にstartボタンがクリックされて実行が開始されます。
以下に起動シェルスクリプトの例を示します。
#!/usr/bin/bash
sleep 120
xhost +
export DISPLAY=:0.0
cd /home/pi/python
source testpip/bin/activate
python3 wiki_bot_10.py -s
定時の自動再起動や、電源投入時の自動起動については、以下に記載しています。
PukiWikiのPython APIとしての利用
今回のWiki Bot のプログラム(以下のリンク先のプログラム)の一部は、PukiWiki のPython APIとして利用することができます。
このプログラムの中の、pico_wiki_driver class がAPIに相当し、この中で以下のような機能を定義しています。
ページの読み込み(認証がある場合の処理も含む)
関数
def get_and_post_wiki(self,uri):
...
は、uri で示されたURIのページを読んで、返すことを表しています。このとき、もし、アクセス制御がかかっていた場合、認証を行います。得られるページはHTML形式のページの記述です。
認証で必要な、その時のURL(PukiWikiのサイトのURL)、ID(読む時)、パスワード(読む時)は、このクラスの関数(メソッド)の
def set_properties(self,prop):
...
によって与えます。この時の引数 prop は初期値としてPropertiesクラスの中で以下のように与えられている properties です。
プログラムとしての初期値は以下のように与えていますが、ファイル bot.propertiesがこのプログラムと同じディレクトリに存在していれば、このプログラムの起動時に、そのファイルから読み込まれるようになっています。このファイルは、GUIのmain_tabの「save properties」ボタンをクリックすることにより、GUIに設定されている値を使って作成、保存されます。
class Properties:
def __init__(self):
for i in range(10):
si=str(i)
key_aurl='auth_url_'+si
val_aurl='http://192.168.10.'+si+'/'
key_aurl_r_id='auth_url_'+si+'_read_id'
val_aurl_r_id='read_id_'+si
key_aurl_r_pass='auth_url_'+si+'_read_pass'
val_aurl_r_pass='read_pass_'+si
key_aurl_e_id='auth_url_'+si+'_edit_id'
val_aurl_e_id='edit_id_'+si
key_aurl_e_pass='auth_url_'+si+'_edit_pass'
val_aurl_e_pass='edit_pass_'+si
initials[key_aurl]=val_aurl
initials[key_aurl_r_id]=val_aurl_r_id
initials[key_aurl_r_pass]=val_aurl_r_pass
initials[key_aurl_e_id]=val_aurl_e_id
initials[key_aurl_e_pass]=val_aurl_e_pass
self.properties=initials
...
Wikiページの置き換え(update)
関数
def replace_wiki_page(self,new_body):
...
は、self.url とself.page に設定されたuri のページ内容を、PukiWikiのマークアップ言語で書かれたテキストである、new_bodyで置き換えることを行います。
PukiWikiでは、ページの書き換えは、「編集」ボタンをclickし、出てきた編集画面でPukiWikiのマークアップ言語によりページを記述/編集することにより行います。認証が必要になる場合は、「編集」ボタンをclickした直後に、認証画面が現れ、そこにIDとパスワードを入力します。「更新」ボタンをclickして、編集結果をページに反映させるのですが、このとき、認証が必要になる場合も同様に行います。
この関数は、以上の、人間がPukiWikiで行う編集の手順を、自動的に行います。
この関数(method)は、中で関数 get_and_post_wiki とprocess_after_post を呼び出しており、ユーザ認証とアクセス制御はそこで行われます。
Wikiページのソーステキスト(マークアップ言語で書かれたテキスト)の獲得
関数
def get_wiki_source(self,uri):
...
は、uriで示されたPukiWikiのページのマークアップ言語で書かれたソーステキストを獲得します。この関数(method)は、中で関数 get_and_post_wiki を呼び出しており、ユーザ認証とアクセス制御はそこで行われます。
その他
その他、ファイルのupload、添付ファイルの削除、添付ファイルのリスト獲得などのAPIが利用できます。詳しくはソースをご覧ください。
謝辞
本作品を制作するにあたり、その一部は、科研費 21K11858 の支援を受けています。感謝します。
