ルーターからクラウドを操る
いかにも釣りっぽいタイトルで申し訳ないとしか言いようがないのですが、YAMAHA RTXシリーズからAWSのAPIを叩く実験をしてみたので、そのメモです。
YAMAHAルーターのLuaスクリプティング機能
最近のYAMAHAのファームにはLuaスクリプティング機能が付いていて、定期的にconfigをダウンロードして適用させたり、時間帯に応じてルーティングを変えたりといった柔軟な運用ができます。詳しくは本家のサイトに解説されています。この機能を使ってAWSのAPIを叩けたらいろいろなことができるんじゃないかと思って、POC的に実験してみました。
YAMAHAファームが準拠しているのはLua 5.1.4ということになっていますが、フル仕様ではなくサブセットのみが実装されているので、やや工夫が必要でしたが、とりあえずDescribeSecurityGroupsでセキュリティ・グループのリストを取得するスクリプトを書いてみました。
AWS REST APIのシグネチャ計算
HMAC-SHA1の計算とBASE64エンコーディングが必要になるので、以下の2カ所から先達のスクリプトを探してきました。
SHA-1 and HMAC-SHA1 Routines in Pure Lua (by Jeffrey Friedl. Public Domain)
Base Sixty Four (by Alex Kloss. LGPL2)
AWSのドキュメント(Making Query Requests)に従ってクエリを組み立てて、シグネチャを計算します。
access_key = "YOUR_ACCESS_KEY_ID" secret_key = "YOUR_SECRET_ACCESS_KEY" api = "DescribeSecurityGroups" api_version = "2011-07-15" host = "ec2.amazonaws.com" http_method = "GET" --ISO8601でタイムスタンプを取得 time_stamp_raw = os.date("!%Y-%m-%dT%H:%M:%S.000Z") time_stamp = string.gsub(time_stamp_raw, ":", "%%3A") --アクセスキー以外の引数を昇順に並べたクエリを組み立てる s2s = http_method .. "\n" .. host .. "\n/\nAWSAccessKeyId=" .. access_key .. "&Action=" .. api .. "&SignatureMethod=HmacSHA1&SignatureVersion=2&Timestamp=" .. time_stamp .. "&Version=" .. api_version --HMAC-SHA1の計算結果をBASE64エンコーディングしてシグネチャを生成 signature = base64(hex_to_binary(hmac_sha1(secret_key, s2s)))
なお、オリジナルのHMAC-SHA1の計算ロジックをYAMAHA版Luaの制約に合わせて修正する必要がありました。引っかかった制約は、
の2点でした。
今回はクエリの文字数が一定であれば計算結果が変わらない部分だったので、計算リソースの節約の意味も兼ねて、結果をハードコーディングすることで回避しました。その他のAPIも同じスクリプトで扱うようにする場合は、APIコールと計算結果をtableで関連づけるような実装になるかと思われます。(Luaにおけるtableは、RubyのHashのようなものです。)
--浮動小数点数とmath.modfはYAMAHA Luaで実装されていない --local B1, R1 = math.modf(msg_len_in_bits / 0x01000000) --local B2, R2 = math.modf( 0x01000000 * R1 / 0x00010000) --local B3, R3 = math.modf( 0x00010000 * R2 / 0x00000100) --local B4 = 0x00000100 * R3 --EC2 DescribeSecurityGroupsコールのためのdirty hack if msg_len_in_bits == 2040 then B1, B2, B3, B4 = 0, 0, 7, 248 elseif msg_len_in_bits == 672 then B1, B2, B3, B4 = 0, 0, 2, 160 end
ちなみに、浮動小数点数さえ使えれば、math.modfを実装するのは簡単で、以下のようになるはずです。
--math.modf function modf_(n) if n > 0 then f = n % 1 else f = n % -1 end i = n - f return i, f end
HTTPリクエストの発行と結果の取得
YAMAHAルーターのLuaでは、configの操作など、標準Lua以外のライブラリが実装されています。そのうちの一つであるrt.httprequestを使ってEC2エンドポイントにAPIコールを投げます。rt.httprequestでは結果のボディをファイルに書き込むことができるので、以下の例ではルーターの内蔵メモリに書き込んでいます。そのほか、機種によって差がありますが、ルーターに挿入したmicroSDやUSB接続されたストレージにファイルを書き込むことができます。
--シグネチャをつかってクエリを組み立てる query = "Action=" .. api .. "&AWSAccessKeyId=" .. access_key .. "&Version=" .. api_version .. "&Timestamp=" .. time_stamp_raw .. "&Signature=" .. signature .. "&SignatureVersion=2&SignatureMethod=HmacSHA1" --rt.httprequestではSSLは未実装 req_url = "http://" .. host .. "/?" .. query --リクエスト・パラーメータを格納したテーブルを作成してGETを発行 --rsp_t.codeでHTTPステータスコード、rsp_t.errでエラーメッセージを確認可能 file_path = "/sg_out.txt" req_t = {url=req_url, method="GET", save_file=file_path} rsp_t = rt.httprequest(req_t)
実行結果の確認
という手順で動作確認を行います。
Macですが以下のようにtftpでファイルをアップロードしました。直後にgetを行うため、個人的にはインタラクティブ・モード(?)が使いやすいと感じました。ROUTER_IP_ADDRESSとROUTER_ADMIN_PASSORDの部分は、それぞれ適切な値に置き換えてください。
$ tftp tftp> connect ROUTER_IP_ADDRESS tftp> put desc_sg.lua /desc_sg.lua/ROUTER_ADMIN_PASSWORD Sent 34235 bytes in 0.6 seconds
ルーターでLuaスクリプトを実行します。administratorで実行している点に注意。
# lua desc_sg.lua
結果をローカルPCにダウンロードして内容を確認します。
$ tftp tftp> connect ROUTER_IP_ADDRESS tftp> get /sg_out.txt/ROUTER_ADMIN_PASSWORD sg_out.txt Received 2130 bytes in 0.1 seconds tftp> quit $ cat sg_out.txt 846 <?xml version="1.0" encoding="UTF-8"?> <DescribeSecurityGroupsResponse xmlns="http://ec2.amazonaws.com/doc/2011-07-15/"> <requestId>########-####-####-####-############</requestId> <securityGroupInfo> <item> <ownerId> (以下省略)
制約とハマリどころ
繰り返しになる内容も多いですが、YAMAHA Lua上でAWS APIを叩く上で制約となりそうな点を列挙します。MacやLinux用のものとは言語仕様やライブラリの実装状況に差があるので、こまめに実機でテストするのが無難だと感じました。自分はRuby (Sinatra) でスタブを書いて、デバッグを行いました。
まとめ
YAMAHAルーター上のLuaスクリプティング機能を使ってEC2 APIの1つであるDescribeSecurityGroupを発行することができました。色々と制約はありますが、スクリプトからVPCの作成とconfigまでできたりしてしまうとおもしろいかもしれないと感じました。
なお、Credentials (Access Key IDなど) の扱いには注意する必要があるかと思われます。rt.httprequestの制限によってSSLが使えないこともありますが、実際にはIAMを使って権限に制約をつけた鍵を利用するのがいいかと思われます。
(言わずもがなですが、私の勤め先などは本記事の内容に一切関知していません。)
gistに貼っておきました → https://gist.github.com/1174442