nginx 1.6.2 + lua-nginx-moduleでapache2 mod_access_tokenを実装

lua-nginx-moduleをインストールし、サンプルを書いてみました。
Debian 7 + nginx 1.6.2 + lua-nginx-moduleの環境構築
nginx 1.6.2 + lua-nginx-moduleで簡易ファイルアップローダー

アクセス制御のサンプルとして、apache2 mod_access_tokenと
同等の機能を実現してみようと思います。

apache2 mod_access_tokenは、こちらで触ってみました。
apache2 mod_access_tokenをPythonから使用する



アクセス制御



HttpLuaModule
lua-nginx-module を使いこなす

こちらを参考に進めていきます。

まず、アクセスされたら403 Forbiddenを返すサンプルから始めます。
こういう用途の場合は、「access_by_lua」を使うと良さそうです。
luaプログラムをファイルに記載した場合は「access_by_lua_file」となります。

nginx.confは以下のように定義しました。


worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include     mime.types;
    default_type application/octet-stream;

    sendfile        on;

    keepalive_timeout 65;
    client_max_body_size 20M;

    server {
        listen     80;
        server_name localhost;

        location / {
            access_by_lua_file /opt/nginx/lua/sample.lua;
        }
    }
}




/opt/nginx/lua/sample.luaには、403を返すコードを記載


  1. -- 403 Forbidden
  2. ngx.exit(ngx.HTTP_FORBIDDEN)




テスト用のPythonプログラムです。


  1. # -*- coding:utf-8 -*-
  2. import urllib2
  3. f = urllib2.urlopen('http://192.168.1.102/')
  4. print f.read()




実行すると狙い通り403エラーになりました。




パラメーターの取得



AccessKey、Signatureの2つのパラメーターはソースに直接書いても良いのですが、
nginx.confに記載するようにします。

ngx.var.[変数名]で取得できるようなので試してみます。

nginx.conf


worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include     mime.types;
    default_type application/octet-stream;

    sendfile        on;

    keepalive_timeout 65;
    client_max_body_size 20M;

    server {
        listen     80;
        server_name localhost;

        location / {
            # ここで変数を宣言し、luaで読み込む
            set $AccessKey 'Key';
            set $Signature 'SignKey';
            access_by_lua_file /opt/nginx/lua/sample.lua;
        }
    }
}




sample.lua


  1. ngx.say(ngx.var.AccessKey)
  2. ngx.say(ngx.var.Signature)




前述のPythonプログラムを実行してみると、ちゃんと設定値が返却されました。


$ python get.py
Key
SignKey






リクエストuri、パラメーターの取得



テスト用のPythonプログラムをちょっと修正します。


  1. # -*- coding:utf-8 -*-
  2. import urllib2
  3. f = urllib2.urlopen('http://192.168.1.102/index.html?key=value')
  4. print f.read()




アクセスされた時の「/index.html」「key=value」を取得する方法を調べてみます。
リクエストのuriは「ngx.var.uri」
パラメーターは「ngx.req.get_uri_args()」
リクエストメソッドは「ngx.req.get_method()」で取得できます。


  1. -- リクエストURIの取得
  2. ngx.say(ngx.var.uri)
  3. -- リクエストパラメーターの取得
  4. local args = ngx.req.get_uri_args()
  5. for key, val in pairs(args) do
  6.     ngx.say(key .. ':' .. val)
  7. end




実行すると狙い通りの応答が得られました。


$ python get.py
/index.html
key:value






UNIX エポックからの秒数



アクセスの許可時間を計測する上で、UNIX エポックからの秒数が必要となります。
組み込み関数「ngx.time()」であっさり取得出来ました。


  1. ngx.say(ngx.time())




Pythonで、time.time()を表示し、チェックしてみます。


  1. # -*- coding:utf-8 -*-
  2. import urllib2
  3. import time
  4. f = urllib2.urlopen('http://192.168.1.102/index.html?key=value')
  5. print f.read()
  6. print int(time.time())




ほぼ同じ数値となります。
狙い通りの値が取得できていますね。


$ python get.py
1418049404
1418049405





hmac_sha1とbase64encode



暗号化やbase64encodeも組み込み関数が用意されています。


  1. ngx.hmac_sha1(key, src)
  2. ngx.encode_base64(digest)




ただし、--with-http_ssl_moduleオプションをつけてnginxを
ビルドしている必要があります。

私はオプションをつけていなかったので、ビルドしなおしました。


# ./configure --prefix=/opt/nginx \
> --with-http_ssl_module \
> --add-module=/usr/local/src/ngx_devel_kit \
> --add-module=/usr/local/src/lua-nginx-module

# make
# make install





狙っている値が生成されるか、こちらで作成したPythonプログラムで
出力した値と比較します。

apache2 mod_access_tokenをPythonから使用する


  1. # -*- coding:utf-8 -*-
  2. import time
  3. import hmac
  4. import hashlib
  5. import base64
  6. import urllib
  7. import urllib2
  8. uri = '/index.html'
  9. accesskey = 'Key'
  10. secret = 'SignKey'
  11. expires = int(time.time()) + 10 # 10秒許可
  12. plaintext = 'GET' + uri + str(expires) + accesskey
  13. signature = base64.b64encode(hmac.new(secret, plaintext, hashlib.sha1).digest())
  14. signature = signature.replace('=', '')
  15. print expires
  16. print signature




実行結果


$ python sample.py
1418049656
dgLFpfjx+BV0Bjz/l3331B3C3Vpo




uri = '/index.html'
accesskey = 'Key'
secret = 'SignKey'
expires = 1418049656
このパラメーターで「dgLFpfjx+V0Bjz/l3331B3C3Vpo」が作れれば成功ということになります。


こんなluaプログラムでOKでした。


  1. local uri = '/index.html'
  2. local accesskey = 'Key'
  3. local secret = 'SignKey'
  4. local expires = 1418049656
  5. local plaintext = string.format("GET%s%d%s", uri, expires, accesskey)
  6. local digest = ngx.hmac_sha1(secret, plaintext)
  7. local b64 = ngx.encode_base64(digest)
  8. local ret = string.gsub(b64 , '=', '')
  9. ngx.say(ret)







総括



道具は全て揃いました。

オジリナルのソース
https://code.google.com/p/modaccesstoken/source/browse/trunk/mod_access_token.c

こちらを見ながらざっくり実装してみます。

nginx.conf


worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include     mime.types;
    default_type application/octet-stream;

    sendfile        on;

    keepalive_timeout 65;
    client_max_body_size 20M;

    server {
        listen     80;
        server_name localhost;

        location / {
            # ここで変数を宣言し、luaで読み込む
            set $AccessKey 'Key';
            set $Signature 'SignKey';
            access_by_lua_file /opt/nginx/lua/sample.lua;
        }
    }
}




sample.lua


  1. -- リクエストパラメーター取得
  2. local args = ngx.req.get_uri_args()
  3. local expires = args.Expires
  4. local access_key = args.AccessKey
  5. local sig = args.Signature
  6. -- 各パラメーターのチェック
  7. if not expires then
  8.     ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  9. end
  10. if not access_key then
  11.     ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  12. end
  13. if not sig then
  14.     ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  15. end
  16. -- アクセスキーのチェック
  17. if ngx.var.AccessKey ~= access_key then
  18.     ngx.exit(ngx.HTTP_FORBIDDEN)
  19. end
  20. -- 時間経過のチェック
  21. if tonumber(expires) < ngx.time() then
  22.     ngx.exit(ngx.HTTP_FORBIDDEN)
  23. end
  24. -- シグニチャー計算
  25. local uri = ngx.var.uri
  26. local method = ngx.req.get_method()
  27. local plaintext = string.format("%s%s%d%s", method, uri, expires, ngx.var.AccessKey)
  28. local digest = ngx.hmac_sha1(ngx.var.Signature, plaintext)
  29. local b64 = ngx.encode_base64(digest)
  30. local calc_sig = string.gsub(b64 , '=', '')
  31. -- リクエストと自分の計算が異なればエラー
  32. if sig ~= calc_sig then
  33.     ngx.exit(ngx.HTTP_FORBIDDEN)
  34. end



ポイントはtonumberで数値に変換してから比較すること。
luaは文字列、数値をいい感じで変換して比較してくれるとのことなのですが、
多分luajitの仕様でちゃんと数値にして比較しないとエラーになります。


早速テストです。
ドキュメントルートにtest.htmlを作成。
中に


ok



とだけ書いておいておきました。



テスト用のPythonプログラムを作成し、実行してみます。


  1. # -*- coding:utf-8 -*-
  2. import time
  3. import hmac
  4. import hashlib
  5. import base64
  6. import urllib
  7. import urllib2
  8. uri = '/test.html'
  9. accesskey = 'Key'
  10. secret = 'SignKey'
  11. expires = int(time.time()) + 10 # 10秒許可
  12. plaintext = 'GET' + uri + str(expires) + accesskey
  13. signature = base64.b64encode(hmac.new(secret, plaintext, hashlib.sha1).digest())
  14. signature = signature.replace('=', '')
  15. signature = urllib.quote(signature)
  16. token_param = 'AccessKey=%s&Expires=%s&Signature=%s' % (accesskey, expires, signature)
  17. while True:
  18.     f = urllib2.urlopen('http://192.168.1.102' + uri + '?' + token_param)
  19.     print f.read(),
  20.     time.sleep(1)




ちゃんと一定期間経過すると403エラーになります。


$ python sample.py
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
Traceback (most recent call last):
File "sample.py", line 23, in <module>
    f = urllib2.urlopen('http://192.168.1.102' + uri + '?' + token_param)
File "/usr/lib/python2.7/urllib2.py", line 127, in urlopen
    return _opener.open(url, data, timeout)
File "/usr/lib/python2.7/urllib2.py", line 410, in open
    response = meth(req, response)
File "/usr/lib/python2.7/urllib2.py", line 523, in http_response
    'http', request, response, code, msg, hdrs)
File "/usr/lib/python2.7/urllib2.py", line 448, in error
    return self._call_chain(*args)
File "/usr/lib/python2.7/urllib2.py", line 382, in _call_chain
    result = func(*args)
File "/usr/lib/python2.7/urllib2.py", line 531, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 403: Forbidden
baranche@atropos:~/dev/python$




もちろん、キーが違う時もエラーになってくれます。
関連記事

コメント

プロフィール

Author:symfo
blog形式だと探しにくいので、まとめサイト作成中です。
https://symfo.web.fc2.com/

PR

検索フォーム

月別アーカイブ