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を返すコードを記載
- -- 403 Forbidden
- ngx.exit(ngx.HTTP_FORBIDDEN)
テスト用のPythonプログラムです。
- # -*- coding:utf-8 -*-
- import urllib2
- f = urllib2.urlopen('http://192.168.1.102/')
- 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
- ngx.say(ngx.var.AccessKey)
- ngx.say(ngx.var.Signature)
前述のPythonプログラムを実行してみると、ちゃんと設定値が返却されました。
$ python get.py
Key
SignKey
リクエストuri、パラメーターの取得
テスト用のPythonプログラムをちょっと修正します。
- # -*- coding:utf-8 -*-
- import urllib2
- f = urllib2.urlopen('http://192.168.1.102/index.html?key=value')
- print f.read()
アクセスされた時の「/index.html」「key=value」を取得する方法を調べてみます。
リクエストのuriは「ngx.var.uri」
パラメーターは「ngx.req.get_uri_args()」
リクエストメソッドは「ngx.req.get_method()」で取得できます。
- -- リクエストURIの取得
- ngx.say(ngx.var.uri)
- -- リクエストパラメーターの取得
- local args = ngx.req.get_uri_args()
- for key, val in pairs(args) do
- ngx.say(key .. ':' .. val)
- end
実行すると狙い通りの応答が得られました。
$ python get.py
/index.html
key:value
UNIX エポックからの秒数
アクセスの許可時間を計測する上で、UNIX エポックからの秒数が必要となります。
組み込み関数「ngx.time()」であっさり取得出来ました。
- ngx.say(ngx.time())
Pythonで、time.time()を表示し、チェックしてみます。
- # -*- coding:utf-8 -*-
- import urllib2
- import time
- f = urllib2.urlopen('http://192.168.1.102/index.html?key=value')
- print f.read()
- print int(time.time())
ほぼ同じ数値となります。
狙い通りの値が取得できていますね。
$ python get.py
1418049404
1418049405
hmac_sha1とbase64encode
暗号化やbase64encodeも組み込み関数が用意されています。
- ngx.hmac_sha1(key, src)
- 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から使用する
- # -*- coding:utf-8 -*-
- import time
- import hmac
- import hashlib
- import base64
- import urllib
- import urllib2
- uri = '/index.html'
- accesskey = 'Key'
- secret = 'SignKey'
- expires = int(time.time()) + 10 # 10秒許可
- plaintext = 'GET' + uri + str(expires) + accesskey
- signature = base64.b64encode(hmac.new(secret, plaintext, hashlib.sha1).digest())
- signature = signature.replace('=', '')
- print expires
- print signature
実行結果
$ python sample.py
1418049656
dgLFpfjx+BV0Bjz/l3331B3C3Vpo
uri = '/index.html'
accesskey = 'Key'
secret = 'SignKey'
expires = 1418049656
このパラメーターで「dgLFpfjx+V0Bjz/l3331B3C3Vpo」が作れれば成功ということになります。
こんなluaプログラムでOKでした。
- local uri = '/index.html'
- local accesskey = 'Key'
- local secret = 'SignKey'
- local expires = 1418049656
- local plaintext = string.format("GET%s%d%s", uri, expires, accesskey)
- local digest = ngx.hmac_sha1(secret, plaintext)
- local b64 = ngx.encode_base64(digest)
- local ret = string.gsub(b64 , '=', '')
- 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
- -- リクエストパラメーター取得
- local args = ngx.req.get_uri_args()
- local expires = args.Expires
- local access_key = args.AccessKey
- local sig = args.Signature
- -- 各パラメーターのチェック
- if not expires then
- ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
- end
- if not access_key then
- ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
- end
- if not sig then
- ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
- end
- -- アクセスキーのチェック
- if ngx.var.AccessKey ~= access_key then
- ngx.exit(ngx.HTTP_FORBIDDEN)
- end
- -- 時間経過のチェック
- if tonumber(expires) < ngx.time() then
- ngx.exit(ngx.HTTP_FORBIDDEN)
- end
- -- シグニチャー計算
- local uri = ngx.var.uri
- local method = ngx.req.get_method()
- local plaintext = string.format("%s%s%d%s", method, uri, expires, ngx.var.AccessKey)
- local digest = ngx.hmac_sha1(ngx.var.Signature, plaintext)
- local b64 = ngx.encode_base64(digest)
- local calc_sig = string.gsub(b64 , '=', '')
- -- リクエストと自分の計算が異なればエラー
- if sig ~= calc_sig then
- ngx.exit(ngx.HTTP_FORBIDDEN)
- end
ポイントはtonumberで数値に変換してから比較すること。
luaは文字列、数値をいい感じで変換して比較してくれるとのことなのですが、
多分luajitの仕様でちゃんと数値にして比較しないとエラーになります。
早速テストです。
ドキュメントルートにtest.htmlを作成。
中に
ok
とだけ書いておいておきました。
テスト用のPythonプログラムを作成し、実行してみます。
- # -*- coding:utf-8 -*-
- import time
- import hmac
- import hashlib
- import base64
- import urllib
- import urllib2
- uri = '/test.html'
- accesskey = 'Key'
- secret = 'SignKey'
- expires = int(time.time()) + 10 # 10秒許可
- plaintext = 'GET' + uri + str(expires) + accesskey
- signature = base64.b64encode(hmac.new(secret, plaintext, hashlib.sha1).digest())
- signature = signature.replace('=', '')
- signature = urllib.quote(signature)
- token_param = 'AccessKey=%s&Expires=%s&Signature=%s' % (accesskey, expires, signature)
- while True:
- f = urllib2.urlopen('http://192.168.1.102' + uri + '?' + token_param)
- print f.read(),
- 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$
もちろん、キーが違う時もエラーになってくれます。
- 関連記事
-
- luvi 実行ファイルに同梱されているファイルの取得
- luviでLuaプログラムをバイナリの実行形式に変換する
- nginx 1.6.2 + lua-nginx-moduleでapache2 mod_access_tokenを実装
- nginx 1.6.2 + lua-nginx-moduleで簡易ファイルアップローダー
- Debian 7 + nginx 1.6.2 + lua-nginx-moduleの環境構築
コメント