きっかけ
Slack botの開発を始めたところ非常にやりにくかった。
手元のPCにSlackアプリがあり、自作のSlack botも起動している状態で、それがお互いに通信するにはSlackのサーバーを経由する必要があるからだ。
そうすると、Slackサーバーから手元のSlack botにアクセスできるようにしなければならない。
ということで、色々調べてみるとリバースプロキシサービスを使って開発している人が多いようだった。
例えば、ngrok, servo, localhost.runなどを使っている人がいた。
ただ、外部のサービスに社内のチャット内容とかトークンとか流したくない・・・と思ったので自分の管理下のAWS VPC内にリバースプロキシを立てることにした。
最終的にこんな感じで起動できるようになった。
$ yarn -s generate 2> /dev/null | aws ec2 run-instances --launch-template LaunchTemplateName=reverse-proxy --user-data file:///dev/stdin
出来ること
リバースプロキシで出来るようにしたこと。
実際はCaddyがやってくれてるので、自分では大したことしてない。
-
HTTPSのリバースプロキシになる
HTTPSでアクセスでき上流にプロキシする。
ここで上流とはSlack bot = お手元のPCのこと。 -
自動でDNSにドメインを登録する
起動時にDNSにドメインを自動登録する。
AWS VPC上に起動する前提なのでDNSはRoute53を使う。 -
自動でTLS証明書を取得する
登録したドメインでLet's Encryptから証明書を取得する。
ここがCaddyの機能。 -
自動で停止する
SSHのセッションが切れたら指定時間後に自動でターミネートする。
開発を終えてリモートポートフォワーディングを切断したら放っておけば停止する。
停止前に再接続するとカウンターがリセットされる。
遣り残したこと
-
NPM
手元で作って使ってただけなのでNPMモジュールとして動かそうとしてなかった。 -
Let's Encryptからのお叱り
何度も起動していると都度TLS証明書を取得してしまうので行儀が悪い。
EIPを割り当てればよさそうだが、今のところその前にDNS登録してしまいそう・・・ -
Slackサーバーからのリクエストの検証
これはSlack限定の話だが、SlackサーバーからSlack botへのリクエストはSigningSecret
で検証ができる。
Caddyのプラグインとしてリクエストの検証機能があれば、botを作らずともリバースプロキシだけ立てておいてエンドポイントのVerifyを通すことが出来るようになると思う。
あと、お手元のPCでリクエストの検証を行う危険も回避できる。
実際はただのuser-data
ここの bin/generate.js
を見てもらうと分かる通り、実際はサーバーを実装したのではなくほぼCaddyにお任せのEC2インスタンス用user-dataを動的に生成するスクリプトである。
cloud-init の per-instance
でCaddyのインストールや起動設定を行い per-boot
でRoute53にドメインをUPSERTしている。
使用方法
起動
NPMモジュールになっていないので clone
しないといけない。
$ git clone https://github.com/ryo0301/instant-reverse-proxy
$ cd instant-reverse-proxy
.env
に設定を書く。
HOSTED_ZONE_DOMAIN
に設定するドメインはあらかじめRoute53に用意しておく。
Hosted Zone IDはこのドメイン名から逆引きしている。
残りは適宜設定。
ちなみに、Slack botのエンドポイントとしてSlack側に設定するURLはこんな感じ。
https://
HOST_NAME
.HOSTED_ZONE_DOMAIN
/slack/events/
$ cat <<EOF > .env
HOST_NAME=$USER
HOSTED_ZONE_DOMAIN=proxy.example.com
CADDY_DL_URL=https://github.com/caddyserver/caddy/releases/download/v2.0.0/caddy_2.0.0_linux_amd64.tar.gz
SHUTDOWN_TIMER_HOUR=1
UPSTREAM_PORT=3000
LOCALE=ja_JP.UTF-8
TIMEZONE=Asia/Tokyo
EOF
これでuser-dataが出力されるはず。
これを手動で貼り付けてもいい。
$ yarn -s generate
また、起動するEC2インスタンスに付けるIAMロールも必要。
必要なアクションはこの2つ。
- route53:ChangeResourceRecordSets
- route53:ListHostedZonesByName
EC2のローンチテンプレートを使うと起動が楽になる。
テンプレートにはさきほどのIAMロールの設定など諸々設定しておく。
これで1行で起動できるようになった。
$ yarn -s generate 2> /dev/null | aws ec2 run-instances --launch-template LaunchTemplateName=reverse-proxy --user-data file:///dev/stdin
接続
リモートポートフォワーディング
リバースプロキシが起動したらリモート→お手元のPCにポートフォワードする。
接続できなかったらEC2インスタンスの状態を見たりSSHでログインしてみて欲しい。
$ ssh -i ~/.ssh/id_rsa -l ec2-user i-XXXXXX -N -R 3000:localhost:3000
SessionManagerを使う場合
普段はEC2 Instance ConnectとSessionManagerを使って接続している。
事前にSessionManager Pluginのインストールや ~/.ssh/config
に設定を追加する必要がある。
$ cat <<EOF >> ~/.ssh/config
Host i-* mi-*
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
EOF
こちらはEC2 Instance Connectを使う場合。
公開鍵をEC2メタデータサービスに送ったあと、対象のEC2インスタンスに60秒間だけログイン可能になる。
送信に成功したら対応する秘密鍵でポートフォワードする。
$ ssh-keygen -t rsa -b 4096
$ aws ec2-instance-connect send-ssh-public-key --instance-id i-XXXXXX --instance-os-user ec2-user --availability-zone ap-northeast-1a --ssh-public-key file:///$HOME/.ssh/id_rsa.pub
最終的な開発環境
Slack botの開発環境は、AWSの構成も含めて最終的にこんな感じになった。
AWSの構成についてはこちらにまとめた。
[AWS]踏み台不要、キーペア登録不要、ポート開放不要のSSH接続環境を作る | Qiita