2017年8月7日月曜日

mosquitto-auth-plugin の HTTP 用の認証サーバを作成してみた

概要

前回 mosquitto-auth-plugin をビルドして mosquitto にインストールするところまでやってみました
今回は実際に認証サーバを作成して pub/sub 時の挙動を確認してみたいと思います

環境

  • mosquitto
    • Ubuntu 16.04
    • mosquitto 1.4.8
    • mosquitto-auth-plugin 6eea8229a230aff562906c7bdb8b7a959bd46418
  • 認証サーバ
    • CentOS 7.3.1611
    • Ruby 2.3.3p222
    • sinatra 2.0.0

認証サーバの作成

3 つの URI を POST で受け付けるサーバを作成するだけです
今回は Ruby + Sinatra で作成してみました

  • bundle init
  • vim Gemfile
gem "sinatra"
  • bundle install –path vender/bundle
  • vim app.rb
require 'sinatra'

set :bind, '0.0.0.0'

post '/auth' do
  puts '/auth'
  puts params
  status 200
end

post '/superuser' do
  puts '/superuser'
  puts params
  status 400
end

post '/acl' do
  puts '/acl'
  puts params
  status 200
end
  • bundle exec ruby app.rb

で起動までしましょう
ちょろっと解説すると 3 つの URI は前回 mosquitto の設定ファイルに記載した

...
auth_opt_http_getuser_uri /auth
auth_opt_http_superuser_uri /superuser
auth_opt_http_aclcheck_uri /acl
...

になります
なので設定ファイル側に記載した URI が異なる場合は認証サーバ側で受ける URI の定義も変更してください

そしてレスポンスコードですが 200 or 400 を返却してます
auth-plugin の説明を見ると 200 系が返れば成功みなし、400 系や 500 系が返ると失敗とみなすようです

今回は /auth と /acl は 200 を返却し /superuser は 400 を返すようにしました
こうすることで /auth -> /superuser -> /auth すべての URI がコールされるようになります
もし /superuser で 200 が返ってしまうとその後の /acl のチェックはスルーされるようです
(スーパーユーザだから権限チェックする必要はないということだと思います)

また、今回の実装ではパラメータの情報をデバッグしているだけですが実際には認証のチェックや権限のチェックをしなければなりません
例えば /auth では MySQL に接続して params の情報を元にユーザ認証する機能を持たせるとか、/acl では params の acc 情報を元に該当するユーザがトピックにアクセスできる権限があるかチェックする機能を実装する必要があります

一応公式でも python の参照実装が用意されているのでこれを参考に実装しても良いと思います
https://github.com/jpmens/mosquitto-auth-plug/blob/master/examples/http-auth-be.py

動作確認

auth-plugin を有効にした mosquitto に実際に publish と subscribe をしたみました
その時の認証サーバ側のログも追ってみます
今回の実装ではどんなユーザ/パスワードでも認証を通ります

まずは subscribe してみます

  • mosquitto_sub -d -t topic -u usernameA -P passwordA
/auth
{"username"=>"usernameA", "password"=>"passwordA", "topic"=>"", "acc"=>"-1", "clientid"=>""}
192.168.100.105 - - [04/Aug/2017:11:31:48 +0900] "POST /auth HTTP/1.1" 200 - 0.0004
192.168.100.105 - - [04/Aug/2017:11:31:48 JST] "POST /auth HTTP/1.1" 200 0
- -> /auth

通るのは auth だけのようです
clientid は /auth では送られてこないようです

subscribe できたら publish してみます

  • mosquitto_pub -d -t topic -u usernameA -P passwordA -m “$(date)”
/auth
{"username"=>"usernameA", "password"=>"passwordA", "topic"=>"", "acc"=>"-1", "clientid"=>""}
192.168.100.105 - - [04/Aug/2017:11:32:36 +0900] "POST /auth HTTP/1.1" 200 - 0.0004
192.168.100.105 - - [04/Aug/2017:11:32:36 JST] "POST /auth HTTP/1.1" 200 0
- -> /auth
/superuser
{"username"=>"usernameA", "password"=>"", "topic"=>"", "acc"=>"-1", "clientid"=>""}
192.168.100.105 - - [04/Aug/2017:11:32:36 +0900] "POST /superuser HTTP/1.1" 400 - 0.0003
192.168.100.105 - - [04/Aug/2017:11:32:36 JST] "POST /superuser HTTP/1.1" 400 0
- -> /superuser
/acl
{"username"=>"usernameA", "password"=>"", "topic"=>"topic", "acc"=>"2", "clientid"=>"publisher"}
192.168.100.105 - - [04/Aug/2017:11:32:36 +0900] "POST /acl HTTP/1.1" 200 - 0.0003
192.168.100.105 - - [04/Aug/2017:11:32:36 JST] "POST /acl HTTP/1.1" 200 0
- -> /acl
/superuser
{"username"=>"usernameA", "password"=>"", "topic"=>"", "acc"=>"-1", "clientid"=>""}
192.168.100.105 - - [04/Aug/2017:11:32:36 +0900] "POST /superuser HTTP/1.1" 400 - 0.0003
192.168.100.105 - - [04/Aug/2017:11:32:36 JST] "POST /superuser HTTP/1.1" 400 0
- -> /superuser
/acl
{"username"=>"usernameA", "password"=>"", "topic"=>"topic", "acc"=>"1", "clientid"=>"subscriber"}
192.168.100.105 - - [04/Aug/2017:11:32:36 +0900] "POST /acl HTTP/1.1" 200 - 0.0003
192.168.100.105 - - [04/Aug/2017:11:32:36 JST] "POST /acl HTTP/1.1" 200 0
- -> /acl

5 つほどログが流れました
はじめの /auth, /superuser はおそらく publisher のものだと思います
/auth, /superuser の場合も clientid がないようです
そして、その後の /acl で初めて clientid が付与されます
acc=2 が publish のようです

publisher のあと /superuser と /acl がコールされています
これはおそらく subscriber 側がコールしたものになります
/auth と通っていないのはすでに認証に成功しているためです
subscriber でも clientid が付与されるのは /acl のときだけのようです
subscribe の場合 acc は 1 のようです
/superuser が 400 の場合 /acl のチェックに移行しここで 200 が返ってくれば無事 subscribe することができます

1 つ publish したあとで続けざまに pub してみましょう
すると /auth が 1 度だけコールされます

/auth
{"username"=>"usernameA", "password"=>"passwordA", "topic"=>"", "acc"=>"-1", "clientid"=>""}
192.168.100.105 - - [04/Aug/2017:11:42:50 +0900] "POST /auth HTTP/1.1" 200 - 0.0004
192.168.100.105 - - [04/Aug/2017:11:42:50 JST] "POST /auth HTTP/1.1" 200 0
- -> /auth

これは publisher の /auth になります
/superuser と /acl がチェックされていないのは auth-plugin の機能で /acl チェックをキャッシュする機能があるためです
acl_cacheseconds というパラメータがありデフォルトでは 300 秒に設定されているようです
なので、この時間を過ぎるまでは同一ユーザでのアクセスは /superuser, /acl のチェックがスルーされます
0 に設定することで disable にすることもできます

また、/auth にもキャッシュするがあるようで一度認証が通った場合は認証情報をスルーできます
公式を見るとデフォルトが「0:disable」で設定されているのでコマンドでアクセスする度に毎回 /auth は通ります

おそらくキャッシュ機能は認証サーバ側の負荷を下げるために用意された機能なのでアクセスが多い場合などはキャッシュ有効にすると良いと思います

最後に

auth-plugin の HTTP 機能を使って認証サーバを作成してみました
I/F の部分に関しては実装する部分がそこまで多くないので簡単に作れると思います

ただ、実際に DB などを使って認証、権限チェックをさせる場合いろいろと実装が必要になるのでそこは環境に合わせて変更してください
逆に言うと auth-plugin の HTTP 認証機能の場合、認証するための仕組みを自分で自由に変更できるのは嬉しい点かなと思います

auth-plugin には他にも MySQL や ldap と連携する機能がありますが個人的には HTTP 機能を使ったほうが認証/認可の実装の自由度が高いのでおすすめです
ただ、認証サーバの管理をしなければいけなくなるのでその辺りはトレードオフかなと思います

0 件のコメント:

コメントを投稿