サーバが自分自身のグローバルIPアドレスを調べるとき面倒なことがよくあるので、API Gateway を使って ifconfig のようなことをする。単純に、リクエストしてきたIPアドレスを返すだけ。(こういうサービスは他にもあるけど、レスポンスが遅めだったり、信用できるか分からなかったりするので、自分で作るのが安全そう)
curl https://ip.ecp.plus
としたら自分のIPアドレスが返るというのがゴール。ブラウザで見ても同じ。ちなみに↑はもう動いているのでご自由にお使いください。
最初、API Gateway -> Lambda として、Lambda に IPアドレスを渡して、Lambda はそれを返して、API Gateway はレスポンスをそのままスルーする。というのをやったが、実は Lambda なしで出来たようだ。
はじめ Lambda を使ったバージョンをやったので、両方書く。
API Gateway の設定
Create Method
URLは短くしたいので、ドメイン直下をGETしたときに返したい。 /
に対して Create Method で GET を指定する。
Method Request は認証などしないのでそのまま。
/ - GET - Method Execution
API Gateway にアクセスしてきた IP アドレスは、下記のようにして取れる。
{
"ipaddress" : "$context.identity.sourceIp"
}
のだが、API Gateway のデフォルトで割り振られる URL は非常に長く、自分のドメインで使いたい。Custom Domain Names を使うためには、TLS の証明書が必要なのだが、お手軽さが無いのでやめた。既に動いている Caddy でリクエストを転送することにした。
ただ、そうするとリクエスト元のIPアドレスが Caddy が動いているサーバの IPアドレスになってしまうため、ヘッダーの X-Forwarded-For
を見ることにする。ここの先頭に、オリジナルのIPアドレスが入ってる。
{
"ipaddress" : "$input.params().header.get('X-Forwarded-For')"
}
Lambda
ipaddress として渡ってきたデータの中から、一番最初のIPアドレスを返すだけの処理を書く。
exports.handler = function(event, context) {
context.succeed([event.ipaddress.split(/\s*,\s*/)[0], "\n"].join(''));
};
/ - GET - Integration Response
再び API Gateway に戻ってきて、レスポンスの加工をする。一見、Output passthrough で良さそうだが、これだとダブルクオーテーションで囲まれたものが返ってしまう。
Content-Type: application/json
で Mapping Template として下記のようにする。
$input.path('$')
これでAWS側の実装は完了。
Caddy の設定
ip.ecp.plus に来たものを API Gateway に飛ばす。API Gateway 側で GET /
を叩くために、転送先の末尾にスラッシュを忘れず付ける必要がある。
ip.ecp.plus {
proxy / https://EXAMPLE.execute-api.ap-northeast-1.amazonaws.com/prod/
}
とりあえずここまでで完成。
だが、Lambda がどう見てもあまり意味がなさそうだったので、API Gateway だけで出来ないか見ていたところ、開発中に使う用途と思っていた Mock Integration を使えば出来た。
API Gateway の設定(再)
/ - GET - Method Execution
Integration type として Mock Integration を選択。Mapping template として
{
"statusCode" : 200
}
のようなものが定義されてるが、これがないと勝手にエラーにされてしまうのでそのままにしておく。
/ - GET - Integration Response
どうやら、Response 側でも、リクエストで渡ってきた値がそのまま取れるようなので、こちらにIPアドレスを返す処理を書けば良い。
#set($ipAddresses = $input.params().header.get('X-Forwarded-For').split(','))
$ipAddresses[0]
#set($dummy = "dummy")
最後の行でよくわからないことをしているが、レスポンスの末尾に改行を入れたいためこうしている。"\n" をどうにか入れたかったが、うまく行かずバッドノウハウ的な解決方法になっている…。本当は良い方法があるはずだ。
ここまでで完成。
尤も、こんな単純なものなら Caddy 動かしてるサーバで処理しろよという感じだけれど、最近 API Gateway にハマっているので作ってみた。API Gateway 単体で動かないと、Caddy のサーバが落ちたときに使えなくて激しく意味がないが、Custom Domain Names の設定までやるのは面倒であった。Route53 使ってたらシームレスに連携出来たりすれば良いのに、と思った。
(追記)CloudFront を使ってサーバレスに出来た
その後、Caddy の代わりに CloudFront を使えば、TLS証明書も無料で、好きなドメインで出来ることが分かった。
CloudFront の画面から、Create Distribution -> Web の Get Started で、作成画面へ入る。
Origin Domain Name
は、API Gateway の Invoke URL を指定する。https:// を付けても勝手に外してくれるので、URLコピペで良い。stage名は消す。つまりドメイン部分だけを記述する。
Origin Path
は、stage名を入れる。 prod なら /prod
とする。
あとは項目を読みながら設定すれば良い。CloudFront は独自ドメインのTLS証明書が無料なので、とてもありがたい。注意点としては、 CloudFront にキャッシュされると前に実行した人のIPアドレスが返ってしまうので、キャッシュさせないようにしなければいけない。
Behaviors
のタブで、初期から存在しているものを編集して、Object Caching : Customize
として、Minimum TTL : 0
, Maximum TTL : 0
, Default TTL : 0
とすれば良い。
これで、自己管理するインフラ無しで、当初のやりたかったことが出来た。