Lambda関数URLという便利機能が公開されましたが、
- Lamdba関数URLって使う上でのリスクないの?
- Lambda関数URLを安全に使うには?
など、使う際のリスクや安全な使い方に関する疑問が浮かんできましたが、関連する記事を見つけられなかったので、考えてみることにしました。
以下のように考えました。私の頭の中です。(私の頭の中には、おじさんとお姉さんがいます)
攻撃された時の対処法がないから不安なの?
うん!攻撃された時、何もできないけど大丈夫?
大丈夫じゃな気がするけど
じゃ、AWS WAFを使えれば安心かな?
そうだね。でもWAFは使えないようですよ〜
確かに…
じゃあ、CloudFront経由でLambda関数URLにアクセスするよう設定すれば良いよ!CloudFrontにWAF設定する感じ!
そうだね。でもLambda関数URLに直接アクセスされたらどうするの?
認証タイプ:AWS_IAMにすればいいよ!
CloudFront経由の場合は、Lambda@Edge使ってSigv4署名すれば、
CloudFront経由のみLambda関数URL実行できるじゃない?
なるほど、やってみますか
要するに、以下のようにLambda関数URLを呼び出すイメージとなります
CloudFront→Lambda@Edge(Sigv4)→Lambda関数URL
これでCloudFront経由でしかLambda関数URLにアクセスできなくなります。
CloudFrontにはWAFを設定できるため、攻撃を受けた際も通信を遮断することなどが可能!という考えです。
本題に入る前に、Lambda関数URLとは?について記載していきます。
Lambda関数URLとは
Lambda関数をHTTPS経由で実行するには、Lambdaの前段にAPI Gatewayを設置する必要がありました。
Lambda関数URLというものが公開され、この機能をONにするだけでLambda関数をHTTPS公開できるようになりました。
API Gatewayを使わずにLambda単体でAPI(HTTPS)公開できるようになったということです。
↓公式記事ですー↓
Lambda関数URLの使い道・どんな用途で使えるか
- WebAPIとしての利用
- API GateWayを使用せずに直接Lambdaを呼び出す
- Lambda関数URLの認証タイプでAWS_IAMを使い、CloudFront経由にアクセスを絞る
- CloudFrontではWAFを使えるので、Lambda関数URLを安全に使うことができる
- 本記事ではこのケースの実装方法を記載しています。
- Lambda関数URLの認証タイプでAWS_IAMを使い、CloudFront経由にアクセスを絞る
- API GateWayを使用せずに直接Lambdaを呼び出す
- 非同期処理としての利用(15分以内に終わる処理限定)
- Lambdaに非同期処理を実装しておく
- ECS/EC2などのWEBサーバで発生した非同期処理はLambdaで実行するようにしておく
- Lambda関数URLの認証タイプでAWS_IAMにしておき、WEBサーバ以外のリクエストは受ける気ないようにしておく
- ECS/EC2などのWEBサーバで発生した非同期処理はLambdaで実行するようにしておく
- Lambdaに非同期処理を実装しておく
CloudFront経由でのみLambda関数URLにアクセスできるように実装する
以下の流れで、Lambda関数URLにアクセスするよう実装していきます。
CloudFront→Lambda@Edge(Sigv4)→Lambda関数URLの実装
実装イメージは以下の通りです。
①Lambda関数URL設定画面で認証タイプをAWS_IAMに設定します。AWS_IAMに設定すると直接関数URLにアクセスできなくなります。
②CloudFrontを作成し、オリジンをLambda関数URLに設定します。
③CloudFront経由にしただけではIAMの権限がついていないので、Lambda@Edgeを使ってSigv4署名してアクセスできるようにします
これで以下の構成の実装ができるということです!
CloudFront→Lambda@Edge(Sigv4)→Lambda関数URL
まずはLambda関数URLを使用できるようにします
関数の作成ボタンから以下の設定で関数を作成します。
- 関数名:適切なもの
- ランタイム:Node.js 18.x
- 詳細設定->関数URLの有効化にチェックを入れる
- 認証タイプはAWS_IAMにチェックを入れる
ソースコードは以下のコードを設定し、デプロイします。
(40秒スリープしてからレスポンスを返すプログラムです)
const sleep = (m) => {
return new Promise((resolve) => setTimeout(resolve, m));
};
export const handler = async(event) => {
// TODO implement
await sleep(40000);
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
作成した関数のタイムアウト値は1分に変更しておきます
次にCloudFrontを作成します
ディストリビューションを作成ボタンを押下して以下の設定で作成します。
- オリジンドメイン:Lambda関数URLを貼り付ける
- 追加設定の「レスポンスタイムアウト」と「キープアライブタイムアウト」を60に設定
- 上記以外はデフォルトのままとします
次にLambda@edgeを作成します
関数の作成ボタンから以下の設定で関数を作成します。
- 関数名:適切なもの
- ランタイム:Python 3.9
- 上記以外はデフォルトのままとします
ソースコードは以下の通りです。
Sigv4を使って署名してあげます。
import boto3
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
import urllib
import base64
session = boto3.Session()
CREDENTIALS = session.get_credentials()
creds = CREDENTIALS.get_frozen_credentials()
def lambda_handler(event, context):
request = event['Records'][0]['cf']['request']
headers = request['headers']
url = f"https://{headers['host'][0]['value']}{request['uri']}"
method = request['method']
data = base64.b64decode(request['body']['data'])
headers = {v[0]['key']:v[0]['value'] for k,v in headers.items()}
headers.pop('X-Forwarded-For')
url_segments = urllib.parse.urlparse(url).netloc.split('.')
region = url_segments[2]
req = AWSRequest(method=method, url=url, params=None, headers=headers, data=data)
SigV4Auth(creds, 'lambda', region).add_auth(req)
signed_headers=dict(req.headers.items())
request['headers'] = { k.lower():[{'key':k,'value':v}] for k,v in signed_headers.items() }
return request
Sigv4(AWS 署名バージョン 4)とは
Sigv4(AWS 署名バージョン 4)は、HTTPリクエストをAWSサービスに行う際の認証情報です。HTTPのホスト名やアクセスパス、クエリ、ヘッダーなどの情報とIAMのアクセスキーを元に署名を作成し、それを認証情報としてリクエストに付加して送信します。
以下、公式ページと公式ページの引用です。
署名バージョン 4 は、AWS 署名プロトコルです。AWS は、マルチリージョン API リクエストの署名をサポートする署名バージョン 4A という拡張機能もサポートしています。詳細については、GitHub で「sigv4a-signing-example」プロジェクトを参照してください。
次にLambdaのロールの信頼関係を以下のように編集します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com",
"edgelambda.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
次に以下の設定でトリガーを追加します。
次に関数URLを有効にしたLambda関数にアクセス権限を追加します
アクセス権限を追加ボタンを押下し、以下の設定でアクセス権限を追加します。
- ステートメント ID:適切なもの
- プリンシパル:Lambda@EdgeのロールのARN
動作確認
ディストリビューションドメイン名でブラウザからアクセスして、
“Hello from Lambda!”
が表示されればOKです。
ついでに、Lambda関数URLへ直接アクセスできないことを確認しましょ!
Lambda関数URLにブラウザでアクセスし、
“Hello from Lambda!”
が表示されなければOKです。
これで、以下の実装ができました!
CloudFront→Lambda@Edge(Sigv4)→Lambda関数URL
次にWAFでIP制限をかけてみます。
WAFの設定(Lambda関数URLにIP制限をかける)
AWS WAFの作成
- AWSマネジメントコンソールからWAF & Shieldを選択します。
- 「Create web ACL」を選択します。
- 以下の設定を行い「Next」で次へ進みます。
- Web ACL details
- Resource type :「CloudFront distributions」を指定
- Name:適切に設定
- Associated AWS resources
- 作成したCloudFrontを追加
- Web ACL details
- 以下の設定でAdd rules and rule groupsの設定を行います。
- Rules:指定なし
- Default action:「Block」を選択
- 「Next」で次へ進み、ACLを作成します
WebACL でIP 制限ルールをかける
IP set を作成する
- 「Create IP set」を押します。
- 以下、IP set detailsの設定を行います
- IP set name:適切に設定
- IP addresses:許可するIPアドレスを指定(例: 10.0.0.0/32)
- 「Create IP set」を押下し作成します。
ルールを設定する
- 作成したWebACLを選択、Rulesタブを選択します。
- 「Add rules」の「Add my own rules and rule groups」を選択します。
- 以下、Ruleの設定を行います。
- Rule type:「Rule builder」を選択
- Rule Name:適切に設定
- If a request:「matches the statement」を選択
- Statement を設定する。
- Inspect:「Original from an IP address in」
- IP set:先ほど作成したIP setを選択
- IP address to use as the originating address:「Source IP address」を選択
- ActionのAction:「Allow」を選択
- 「Add Rule」でルールを追加する
動作確認
以下の2点について動作確認できればOKです。
- 許可したIPアドレスでのアクセス
- 「”Hello from Lambda!”」が表示されればOK
- 許可していないIPアドレすでのアクセス
- 403エラーが返ってくればOK
まとめ
「CloudFront経由でLambda関数URLにアクセスできるようにする」方法について紹介させていただきました。
このやり方の最大の弱点は、簡単に導入できるはずのLambda関数URL導入が、やや難しいものになってしまう点です。