App Runnerでセキュリティグループを操作するWeb UIを作る
概要
AWSに一部の人しかアクセスできないシステムがあり、セキュリティグループによりIP制限をかけています。
このIPアドレスの追加・削除を簡単にしたいというのが今回の目的
以下のようにApp RunnerでWebページを作り、クライアントのIPアドレスをセキュリティグループに追加します。
App RunnerにはGoogle認証をかけて特定の人しかアクセスできないようにします
なんでこんなややこしい事をするかというと
- システム利用者全員がAWSの操作権限を持っているわけではない
- システムはWeb以外のサービスもあるため、IP制限を利用したい(システム一律Google認証にできない)
というような背景があったりします
コンテナ内では、bash, apache, node.js, cronのプロセスを動かします
bashはPID=1として起動し、apache, node.js, cronプロセスを実行
apacheはGoogle認証部分を行いnode.jsにリクエストを投げます。
node.jsはリクエストを受け取りセキュリティグループを更新します。
cronは定期的にセキュリティグループを調べ、IPアドレス追加から30日経ったエントリを削除します
Google認証の部分はCognito等を使って実現できますが、今回はApp runner1つだけで実現することにします。
実装はこんな感じ
各設定の解説に続きます
Docker
TARGET_SGという環境変数に変更するセキュリティグループのID設定します
apache, node.js, cronをインストールしています
bash
dockerのCMD(PID=1)は/run.shというbashスクリプトを指定しています
bashスクリプトはapache, cron, node.jsを起動し5分毎にPIDの存在チェックをしています。
(kill -0は存在チェック)
存在しなかった場合は、bashがset -eの効果で落ちるのでコンテナが終了します。するとApp Runnerが自動的に再起動してくれます
プロセスごとに再起動したりちゃんと管理したい場合はsupervisord等を使うと良いかもしれません
今回は内部向けと言う事でbashで簡易的に起動しています。
env | perl -ple 's/^/export /' > env.sh
の部分は後述するcronタスクで使用する環境変数を保存しています。
ECS_CONTAINER_METADATA_URI_V4
, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
等の環境変数をcronタスクへ渡さないと、IAMロールの認証情報を使えないからです。
/run.sh
#!/bin/bash
set -euxo pipefail
cron -f &
CRON_PID=$!
/usr/sbin/apache2 -DFOREGROUND &
APACHE_PID=$!
cd /app
env | perl -ple 's/^/export /' > env.sh
node index.js &
NODE_PID=$!
while :;do
kill -0 $APACHE_PID
kill -0 $NODE_PID
kill -0 $CRON_PID
sleep 5m
done
apache
auth-openidcモジュールとproxyモジュールを使って、Google認証を通過したら後述のnodejsにリクエストを流す構成にします。
OIDCの設定はGoogle API ConsoleからOAuthクライアントIDの作成の通りです
設定画面でリダイレクトURIを設定して作成
カスタムドメインではなくApp Runner作成時に作られるドメインを使う場合はデプロイするまで確定しないので、あとでリダイレクトURIを設定します
auth_openidc.conf内のOIDCRedirectURI
, OIDCCryptoPassphrase
, OIDCClientID
, OIDCClientSecret
を設定します
apache2.confのRequireは制限したい内容に合わせて変更します
以下の場合はexample.netドメインのアカウントのみアクセス可能
Require claim hd:example.net
nodejs
express.jsを使います
セキュリティグループにクライアントのIPを追加するプログラムなので、クライアントのIPを知る必要がありますが、
App Runnerのロードバランサー => apache => node.js
という構成になっているので、直接req.ipで参照できません。
App RunnerとapacheはX-Forwarded-Forを付与してくれるのでこちらからクライアントIPを知ることができます
クライアントとnode.jsの間には2段リバースプロキシがあるので以下のように設定します
参考
app.set("trust proxy", 2);
ちなみにApp Runnerのロードバランサーはx-envoy-external-address
というヘッダにもクライアントIPを入れてくれているのでこちらを参照しても良いかと思います
セキュリティグループへの追加はdescriptionに「日付 ユーザー名」を入れます
ユーザー名はapacheから渡されるoidc_claim_email
ヘッダの@より前の部分を使用します
(今回は内部向けなので、oidc_claim_email_verifiedヘッダの検証はしてないですが万全を期すなら見た方がいいかもしれないです)
日付はISO8601形式です
あと、App RunnerはIPv6に対応してないようですが、念の為IPv6で来ても対応できるようにしておきます
(セキュリティグループはIPv6に対応していますので)
cron
crontabはIAMロールを使えるようにbashでつくった環境変数を貰うようにします
また、標準出力にログに実行ログを出すようにしています
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
*/5 * * * * root source /app/env.sh && node /app/cron.js > /proc/$(cat /var/run/crond.pid)/fd/1 2>&1
cron.js内ではセキュリティグループのルールを見て、登録から30日経過しているものを抽出し削除しています
(日付 + 名前の形式になっていないルールはスキップします)
cronを走らせる上での注意なのですがApp Runnerは自動的にスケールするため、コンテナが複数台になるとcronが同時に実行される可能性があります。
今回は同時に実行されても問題ないですし、深夜1時は忙しくないため恐らくコンテナ1台だと思われるのでcronをそのまま動かしています
通常は排他制御が必要かと思います
App Runner
今回はセキュリティグループを変更するので権限を付けたIAMロールを作る必要があります
ロールの信頼されたエンティティにはtasks.apprunner.amazonaws.com
を指定します
あとはECRにpushしてdeployするだけなので簡単です
費用については最小設定で一日あたり$0.43くらいでした
ロードバランサーも含んでいると考えると安いと思います
もっと料金を抑えたいなら一時停止もできるので、夜間は停止させておく運用も良さそうです
感想
App Runner簡単で良いですね
しかも値段が安いのでモックとかに使うのには良い気がします
Discussion