🐮

App Runnerでセキュリティグループを操作するWeb UIを作る

2021/08/13に公開

概要

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つだけで実現することにします。

実装はこんな感じ

https://github.com/yaasita/change-sg

各設定の解説に続きます

Docker

Dockerfileはこんな感じ

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