Symfoware

Symfowareについての考察blog

nginx 拡張モジュールでapache2 mod_access_tokenを実装

ずっとこれがやりたくて準備していました。

nginx 拡張モジュールの作り方
nginx モジュール作成 メソッド、URL、クエリパラメーターの取得方法
nginx 拡張モジュール サーバー時間を取得
nginx 拡張モジュール 設定ファイルの値を取得する
nginx 拡張モジュール SHA1ハッシュの計算とbase64エンコード
nginx 拡張モジュール URLデコードの方法(ngx_unescape_uri)


lua拡張でやったこと
nginx 1.6.2 + lua-nginx-moduleでapache2 mod_access_tokenを実装
これを拡張モジュールでやってみます。



仕様



nginx.confファイルに暗号化キー、アクセスキーを定義します。


access_token on; # モジュールの有効化
access_key 'Key'; # アクセスキー
access_signature 'SignKey'; # シークレットキー




全体像はこんな感じ。


worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include     mime.types;
    default_type application/octet-stream;
    error_log /opt/nginx/logs/error.log debug;
    sendfile        on;

    server {
        listen     80;
        server_name localhost;

        location / {
            # アクセストークンの有効化
            access_token on;
            access_key 'Key';
            access_signature 'SignKey';
            root html;
            index index.html index.htm;
        }
    }
}





ソースファイルはこうなりました。


  1. #include <ngx_config.h>
  2. #include <ngx_core.h>
  3. #include <ngx_http.h>
  4. #include "ngx_sha1.h"
  5. #include <openssl/evp.h>
  6. #include <openssl/hmac.h>
  7. #define ACCESS_KEY_NAME "AccessKey"
  8. #define EXPIRES_NAME "Expires"
  9. #define SIGNATURE_NAME "Signature"
  10. // nginx.confの設定値取得用
  11. typedef struct {
  12.     ngx_flag_t enable; // on,offフラグ
  13.     ngx_str_t key; // access_key
  14.     ngx_str_t signature; // access_signature
  15. } ngx_http_access_token_loc_conf_t;
  16. static ngx_int_t ngx_http_access_token_handler(ngx_http_request_t *r);
  17. static ngx_int_t ngx_http_access_token_string_cmp(ngx_str_t *dst, ngx_str_t *src);
  18. static void *ngx_http_access_token_create_loc_conf(ngx_conf_t *cf);
  19. static char *ngx_http_access_token_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
  20. static ngx_int_t ngx_http_access_token_init(ngx_conf_t *cf);
  21. // confファイルの記載内容読み取り指定
  22. static ngx_command_t ngx_http_access_token_commands[] = {
  23.     // on,off制御
  24.     { ngx_string("access_token"), // キーの名称
  25.      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, // どのconfを読むか
  26.      ngx_conf_set_flag_slot, // on,off 0,1の読み取り
  27.      NGX_HTTP_LOC_CONF_OFFSET,
  28.      offsetof(ngx_http_access_token_loc_conf_t, enable), // ngx_http_access_token_loc_conf_t->enableに設定
  29.      NULL },
  30.     
  31.     // 文字列パラメーター「access_key」
  32.     { ngx_string("access_key"), // キーの名称
  33.      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, // NGX_CONF_TAKE1で1つの値
  34.      ngx_conf_set_str_slot, // 文字列の読み取り
  35.      NGX_HTTP_LOC_CONF_OFFSET,
  36.      offsetof(ngx_http_access_token_loc_conf_t, key), // ngx_http_access_token_loc_conf_t->keyに設定
  37.      NULL },
  38.     
  39.     // 文字列パラメーター「access_signature」
  40.     { ngx_string("access_signature"), // キーの名称
  41.      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, // NGX_CONF_TAKE1で1つの値
  42.      ngx_conf_set_str_slot, // 文字列の読み取り
  43.      NGX_HTTP_LOC_CONF_OFFSET,
  44.      offsetof(ngx_http_access_token_loc_conf_t, signature), // ngx_http_access_token_loc_conf_t->signatureに設定
  45.      NULL },
  46.     
  47.      ngx_null_command
  48. };
  49. static ngx_http_module_t ngx_http_access_token_module_ctx = {
  50.     NULL, /* preconfiguration */
  51.     ngx_http_access_token_init, /* postconfiguration */
  52.     NULL, /* create main configuration */
  53.     NULL, /* init main configuration */
  54.     NULL, /* create server configuration */
  55.     NULL, /* merge server configuration */
  56.     ngx_http_access_token_create_loc_conf, /* create location configuration */
  57.     ngx_http_access_token_merge_loc_conf /* merge location configuration */
  58. };
  59. ngx_module_t ngx_http_access_token_module = {
  60.     NGX_MODULE_V1,
  61.     &ngx_http_access_token_module_ctx, /* module context */
  62.     ngx_http_access_token_commands, /* module directives */
  63.     NGX_HTTP_MODULE,             /* module type */
  64.     NULL,                         /* init master */
  65.     NULL,                         /* init module */
  66.     NULL,                         /* init process */
  67.     NULL,                         /* init thread */
  68.     NULL,                         /* exit thread */
  69.     NULL,                         /* exit process */
  70.     NULL,                         /* exit master */
  71.     NGX_MODULE_V1_PADDING
  72. };
  73. // conf読み取り初期化
  74. static void *
  75. ngx_http_access_token_create_loc_conf(ngx_conf_t *cf)
  76. {
  77.     ngx_http_access_token_loc_conf_t *conf;
  78.     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_access_token_loc_conf_t));
  79.     if (conf == NULL) {
  80.         return NGX_CONF_ERROR;
  81.     }
  82.     
  83.     // 初期値はoff
  84.     conf->enable = NGX_CONF_UNSET;
  85.     
  86.     return conf;
  87. }
  88. // 読み取ったconfの値をマージする
  89. static char *
  90. ngx_http_access_token_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
  91. {
  92.     ngx_http_access_token_loc_conf_t *prev = parent;
  93.     ngx_http_access_token_loc_conf_t *conf = child;
  94.     
  95.     ngx_conf_merge_value(conf->enable, prev->enable, 0);
  96.     ngx_conf_merge_str_value(conf->key, prev->key, "DummyKey");
  97.     ngx_conf_merge_str_value(conf->signature, prev->signature, "DummySignature");
  98.     
  99.     return NGX_CONF_OK;
  100. }
  101. // 初期化処理
  102. static ngx_int_t
  103. ngx_http_access_token_init(ngx_conf_t *cf)
  104. {
  105.     ngx_http_handler_pt        *h;
  106.     ngx_http_core_main_conf_t *cmcf;
  107.     cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
  108.     // アクセス制御のイベントにハンドラーを設定
  109.     h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
  110.     if (h == NULL) {
  111.         return NGX_ERROR;
  112.     }
  113.     *h = ngx_http_access_token_handler;
  114.     
  115.     return NGX_OK;
  116. }
  117. static ngx_int_t
  118. ngx_http_access_token_handler(ngx_http_request_t *r)
  119. {
  120.     // 設定値
  121.     ngx_http_access_token_loc_conf_t *alcf;
  122.     
  123.     // アクセス解析
  124.     ngx_str_t access_key, expires, signature; //, signature_unescape;
  125.     ngx_int_t expires_int;
  126.     u_char *src, *dst;
  127.     
  128.     // 暗号化
  129.     const EVP_MD            *evp_md;
  130.     unsigned int             md_len;
  131.     unsigned char            md[EVP_MAX_MD_SIZE];
  132.     ngx_str_t plaintext;
  133.     
  134.     // base64
  135.     ngx_str_t base64src, base64dst;
  136.     
  137.     // conf読み込み
  138.     alcf = ngx_http_get_module_loc_conf(r, ngx_http_access_token_module);
  139.     
  140.     // onになっていなければ終了
  141.     if (!alcf->enable) {
  142.         return NGX_OK;
  143.     }
  144.     
  145.     
  146.     // クエリパラメーターチェック
  147.     if (!r->args.len) {
  148.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  149.             "no argument error.needs AccessKey,Expires,Signature");
  150.         return NGX_HTTP_INTERNAL_SERVER_ERROR;
  151.     }
  152.     
  153.     if (ngx_http_arg(r, (u_char *) ACCESS_KEY_NAME, strlen(ACCESS_KEY_NAME), &access_key) != NGX_OK) {
  154.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no argument error.needs AccessKey");
  155.         return NGX_HTTP_INTERNAL_SERVER_ERROR;
  156.     }
  157.     
  158.     if (ngx_http_arg(r, (u_char *) EXPIRES_NAME, strlen(EXPIRES_NAME), &expires) != NGX_OK) {
  159.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no argument error.needs Expires");
  160.         return NGX_HTTP_INTERNAL_SERVER_ERROR;
  161.     }
  162.     
  163.     if (ngx_http_arg(r, (u_char *) SIGNATURE_NAME, strlen(SIGNATURE_NAME), &signature) != NGX_OK) {
  164.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no argument error.needs Signature");
  165.         return NGX_HTTP_INTERNAL_SERVER_ERROR;
  166.     }
  167.     
  168.     // 設定値チェック
  169.     if ((alcf->key.len == 0) || (alcf->signature.len == 0)) {
  170.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  171.             "Configuration error. You MUST specify AccessTokenAccessKey and AccessTokenSecret");
  172.         return NGX_HTTP_INTERNAL_SERVER_ERROR;
  173.     }
  174.     
  175.     // キーチェック
  176.     if (ngx_http_access_token_string_cmp(&alcf->key, &access_key) != 0) {
  177.         ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "does not match key");
  178.         return NGX_HTTP_FORBIDDEN;
  179.     }
  180.     
  181.     
  182.     // 引数の時間をチェック
  183.     expires_int = ngx_atoi(expires.data, expires.len);
  184.     if (expires_int == NGX_LOG_ERR) {
  185.         ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Invalid request arguments Expires");
  186.         return NGX_HTTP_INTERNAL_SERVER_ERROR;
  187.     }
  188.     
  189.     // 期限切れなら一律403
  190.     if (expires_int < ngx_time()) {
  191.         ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Request has expired");
  192.         return NGX_HTTP_FORBIDDEN;
  193.     }
  194.     
  195.     // method uri expires access_keyで連結した文字列を作成
  196.     plaintext.len = r->method_name.len + r->uri.len + expires.len + access_key.len;
  197.     plaintext.data = ngx_pnalloc(r->connection->pool, plaintext.len);
  198.     if (plaintext.data == NULL) {
  199.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "plaintext pnalloc error.");
  200.         return NGX_ERROR;
  201.     }
  202.     
  203.     ngx_sprintf(plaintext.data, "%V%V%V%V", &r->method_name, &r->uri, &expires, &access_key);
  204.     
  205.     evp_md = EVP_sha1();
  206.     // HMAC計算 結果はmdに代入される
  207.     HMAC(evp_md, (u_char*)alcf->signature.data, alcf->signature.len, (u_char*)plaintext.data, plaintext.len, md, &md_len);
  208.     
  209.     // base64 変換元バイナリデータ
  210.     base64src.data = md;
  211.     base64src.len = md_len;
  212.     
  213.     // base64エンコード用にデータをコピー
  214.     base64dst.data = ngx_pnalloc(r->connection->pool, md_len);
  215.     if (base64dst.data == NULL) {
  216.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "base64dst pnalloc error.");
  217.         return NGX_ERROR;
  218.     }
  219.     
  220.     base64dst.data = ngx_copy(base64dst.data, md, md_len);
  221.     base64dst.len = ngx_base64_encoded_length(md_len);
  222.     
  223.     // base64エンコード実行
  224.     ngx_encode_base64(&base64dst, &base64src);
  225.     
  226.     // 最後に「=」がつくので位置文字削る
  227.     base64dst.len -= 1;
  228.     
  229.     
  230.     // ここで引数signatureのURLデコード
  231.     src = signature.data;
  232.     dst = ngx_pnalloc(r->pool, signature.len);
  233.     if (dst == NULL) {
  234.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "signature pnalloc error.");
  235.         return NGX_ERROR;
  236.     }
  237.     
  238.     signature.data = dst;
  239.     ngx_unescape_uri(&dst, &src, signature.len, 0);
  240.     signature.len = dst - signature.data;
  241.     
  242.     
  243.     // 引数と比較
  244.     if (ngx_http_access_token_string_cmp(&base64dst, &signature) != 0) {
  245.         ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "dose not match Signature.");
  246.         return NGX_HTTP_FORBIDDEN;
  247.     }
  248.     
  249.     ngx_pfree(r->pool, &plaintext);
  250.     ngx_pfree(r->pool, &base64dst);
  251.     ngx_pfree(r->pool, &dst);
  252.     
  253.     return NGX_OK;
  254. }
  255. static ngx_int_t
  256. ngx_http_access_token_string_cmp(ngx_str_t *dst, ngx_str_t *src) {
  257.     
  258.     size_t i;
  259.     
  260.     if (dst->len != src->len) {
  261.         return 1;
  262.     }
  263.     
  264.     for (i = 0; i < dst->len; i++) {
  265.         if (dst->data[i] != src->data[i]) {
  266.             return 1;
  267.         }
  268.     }
  269.     return 0;
  270. }




--with-http_ssl_moduleオプションをつけてコンパイルします。


./configure --prefix=/opt/nginx \
--with-http_ssl_module \
--add-module=/usr/local/src/access_token_module






動作テストに使用した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. print token_param
  18. f = urllib2.urlopen('http://192.168.1.102' + uri + '?' + token_param)
  19. print f.read(),






ソースはこちらにコミットしておきました。

https://bitbucket.org/symfo/access_token_module/
関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2014/12/21(日) 16:09:52|
  2. 備忘録
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
<<nginx拡張モジュール リクエストヘッダー情報を取得する | ホーム | nginx 拡張モジュール URLデコードの方法(ngx_unescape_uri)>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://symfoware.blog68.fc2.com/tb.php/1570-c457aaf7
この記事にトラックバックする(FC2ブログユーザー)