手抜きIAM Policyをセキュアに使ってC#でAWS Lambdaを開発しよう!
TL;DR
AssumeRoleを利用することでアクセスキー流出時のリスクを抑えながら強力な権限を持つIAM Userを払い出せます。
1. Opening
1-1. この記事の目的
AWSにはShellや汎用言語、IDEからAWSリソースを触るためのツールが用意されています。
これらのツールを利用する際はWeb Consoleでのログインとは異なりアクセスキーでの認証を利用できます。しかし、アクセスキーはプログラム等で利用することもあり、知識の無いユーザーが利用すると意図せず認証情報を流出させてしまうリスクがあります。
[AWS利用者必読] アクセスキー漏洩による不正利用について
リスク低減のプラクティスとして「IAM Policyは必要最小限の権限に絞っておく」ことが挙げられますが、サービス利用前に必要最小限の権限を洗い出すのが難しいこと、AWS CLIやToolkit等を利用する際はEC2インスタンスの作成などの強力な権限を付与しておきたいことなどから、実際にはリソースを指定しないFullAccessやReadOnlyAccess権限がアタッチされているケースが存在していると思います。
単に私がものぐさなだけかもしれませんが…
本記事では適切なIAM Policyを書くのが面倒な方に向けて、C#でのLambda開発を例にAssumeRoleを利用したリスク低減方法を共有します。
1-2. 想定する読者
- 適切なIAM Policyを書くのが面倒な方
- AWS ToolkitやNoSQL Workbentch等のクライアントツール向けに強力なIAM Policyを欲している方
- .NETでAWS Lambda開発に取り組む予定の方
1-3. 書くこと
- AssumeRoleを利用したリスク低減方法
- C#の単体テスト方法
- AWS Toolkit for Visual Studioを利用したAWSリソースの操作方法
1-4. 書かないこと
- C#のコーディング方法
1-5. 環境/前提条件
例として利用するAWS Toolkit for Visual StudioはWindows OSでのみ動作します。Visual Studio for Macでは動作しませんので、基本的にWindows OSでの利用方法を記載します。しかし、AWS ToolkitはVS Code用やJetBrains IDEs用が存在するため、macOSでも同等の操作が可能です。
2-1. C#でAWS Lambdaを書こう!
※C#でのLambda開発に興味の無い方はAssumeRoleで権限の利用に制限をかけるまで読み飛ばしてください。
.NET統合後初のLTSである.NET 6とVisual Studio 2022が正式リリースされて.NET界隈が盛り上がっていますね。AWS Toolkit for Visual Studioも既に2022に対応しています。AWS LambdaではRuntimeとしてまだ.NET 6を選択できませんが、今回は.NET Core 3.1を使ったAWS Lambda開発を例にAssumeRoleの利用方法を解説したいと思います。AWS Lambdaでは.NET以外にも様々な言語のRuntimeを選択できますが、.NETを選択することで下記のメリット(とデメリット)を享受できます。
👍 静的型付け言語のため、早い段階でミスに気付ける
👍 (比較的)実行するOSの違いでハマることが少ない
👍 強力な開発支援ツールであるAWS Toolkit for Visual Studioが使える
👎 AWS LambdaのWeb Consoleからソースの編集ができない
👎 ある程度メモリを積まないとColdStart時に遅くなる
👎 エンタープライズ用途ではVisual Studioが有料
2-1-1. AWS Toolkit for Visual Studioのインストール
AWS Toolkit for Visual StudioはVisual Studioの拡張機能からインストールができます。Visual Studioを立ち上げて 【拡張機能】 => 【拡張機能の管理】と進みます。【オンライン】から"AWS"と検索してダウンロードしましょう。
AWS Toolkit for Visual Studioのセットアップ
2-1-2. Lambda用プロジェクトの作成
AWS Toolkit for Visual StudioがインストールできたらAWS Lambda用の新規プロジェクトを作成します。Toolkitが正しくインストールされていると、AWS Lambda Functionのテンプレートが表示されます。下記テンプレートを選択してプロジェクトを作成しましょう。Lambdaにデプロイするためのプロジェクトの他に、xUnitのプロジェクトが自動で作成されます。
- AWS Lambda Project with Tests(.NET Core - C#)
2-1-3. Lambda用プロジェクトを動かしてみる
作成されたLambda用プロジェクトを動かしてみましょう。Lambda用プロジェクトを簡単に動かす方法はざっくり分けて3つあります。
- AWS LambdaにデプロイしてWebコンソールやトリガーとなるサービスから実行する
- xUnit等の単体テストツールでFunction単位に実行する
- AWS Toolkit for Visual Studioに含まれるAWS .NET Core 3.1 Mock Lambda Test Toolから実行する
各手順については後述しますが、それぞれ下記のメリット/デメリットが存在します。
-
1
👍 実際のLambda上で動作するのでMockやFakeツールのバグを踏まない
👎 動作確認の度にデプロイが必要で面倒くさい -
2, 3
👍 ローカルで実行するのでデバッグツールが利用できる
👎 ローカルにも権限を持たせないとAWSリソースに接続する処理が動作しない
2-1-3-1. AWS LambdaにデプロイしてWebコンソールやトリガーとなるサービスから実行する
ローカルからAWS Lambdaにデプロイするには下記二通りの手法が存在します。 // CI/CDを設計する場合はこの限りではありません。個人の検証環境以外ではマニュアルデプロイは避け、ソース管理ルールにあわせて自動でデプロイされるように設定しましょう。
2を利用することでVisual Studioの画面から直接AWS Lambdaにデプロイすることが可能です。また、デプロイ後はVisual Studioの画面から直接AWS LambdaをAWS上で実行することも可能なため、実際にAWS上で動作確認をしたい場合はこちらを利用するのが最も簡単であると思われます。
- マニュアルでビルドしたファイルをzip化してWebコンソールでアップロードする
- AWS Toolkit for Visual StudioでPublishする
2-1-3-2. xUnit等の単体テストツールでFunction単位に実行する
AWS Toolkit for Visual Studioに含まれるTests付きのLambdaテンプレートを使用している場合、xUnitのプロジェクトが自動で作成されています。サンプルとなるテストコードも自動で生成されているため、Visual Studioのテストエクスプローラーから実行することで動作を確認することが可能です。また、AWS SDKを使用してS3やDynamoリソースを操作する場合、ローカルに保存されているAWS認証情報を利用することが可能です。特にプロファイル等の設定を記述していない場合、自動的に[default]プロファイルの認証情報が利用され、S3やDynamoDBに接続することが可能です。
Using the shared AWS credentials file
2-1-3-3. AWS Toolkit for Visual Studioに含まれるAWS .NET Core 3.1 Mock Lambda Test Toolから実行する
AWS Toolkit for Visual Studioのテンプレートから作成したプロジェクトには'aws-lambda-tools-defaults.json'というファイルが含まれています。こちらのファイルにはMock Lambda Test Toolで実行する際のメモリサイズ等の設定が記録されています。あわせて実行構成が自動で作成されているため、Visual Studioからデバッグ実行することで自動的にMock Lambda Test Toolがブラウザ上で開きます。こちらもローカルにあるAWS認証情報を自動で読み取ってくれるため、ブラウザ上から適切なプロファイルを選択することで実際にAWS上にあるリソースに接続することが可能です。
Debugging .NET Core AWS Lambda functions using the AWS .NET Mock Lambda Test Tool
2-2. AssumeRoleで権限の利用に制限をかける
ここまでC#でAWS Lambdaを開発する方法をご紹介しましたが、いずれも適切なAWS認証と権限を必要とします。AWS認証情報はIAMのWebコンソールからCLI用のユーザーを作成するだけで払い出せますが、冒頭で記述した通りアクセスキーが流出すると攻撃者にAWSアカウントを不正利用される恐れがあります。本来であれば必要最小限の権限に絞ってIAM Policyを用意すべきですが、開発中は必要な権限を洗い出すことが難しいことや適切な権限を書くのが面倒くさいことから「とりあえずリソース指定せずにReadOnlyAccess権限をアタッチしておくか~」という運用が発生しがちです。そんな手抜きIAM Policyでも、流出したときに悪用するハードルを高めておくことで安心して利用することができます。今回はAssumeRoleの機能を応用することでローカルからIAM Roleを利用する場合はMFA必須&IP Filteringを施します。一方で、同じRoleをLambdaから利用する場合は制限をかけないようにしてみます。
▼今回構築するAssumeRoleのイメージ
2-2-1. AssumeRoleとは?
AssumeRoleとはIAM Roleの権限を委譲するための仕組みです。AssumeRoleを利用することでIAM Userとしてログインしたアカウントから別のアカウントにスイッチロールをして別アカウントの操作をすることが可能です。また、AWS LambdaがIAM Roleの権限を取得するのにもAssumeRoleが利用されていると思われます。その証拠にLambda作成時に自動生成されるIAM Roleには信頼関係の中でLambdaリソースに対してAssumeRoleを許可しています。逆にいうと、信頼関係でLambdaに対するAssumeRoleを許可していない場合、Lambdaの実行ロールとしてそのロールを選択することができません。スイッチロールでは別アカウントから利用するのが一般的ですが、同一アカウント内のIAM UserからもAssumeRoleは利用できます。今回はそちらを応用してAssumeRole時に制限をかけていきます。
▼AWS Lambda作成時に自動生成されるIAM Roleの信頼関係(trust relationship)
{
"Version": "2008-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
IAMロール徹底理解 〜 AssumeRoleの正体
IAMのスイッチロールを理解したい
2-2-2. AssumeRole用のIAM User / IAM Roleを作成する
AssumeRoleをするためにはAssumeRoleを実行するIAM Userと権限を委譲するIAM Roleが必要になります。どちらから作成しても構いませんが、今回はIAM Roleから作成してみます。 // 上記構成図のLambdaExecutionRoleを作成します
- IAMコンソールを開きます。
- メニューから「ロール」を選択します。
- 「ロールの作成」ボタンを押下します。
- 「信頼されたエンティティの選択」ではLambdaを選択します。
- てきとーなIAM Policyをアタッチします。 // AmazonS3ReadOnlyAccessなど
- 必要があればタグを設定します。
- 名前を決めて「ロールの作成」を選択します。
ここまでが通常のIAM Role作成の手順です。次に作成するIAM UserからのAssumeRoleを許可するために、作成したIAM Roleを選択し、信頼関係を書き換えます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com",
"AWS": "arn:aws:iam::[your account id]:root"
},
"Action": "sts:AssumeRole"
}
]
}
これでIAM Roleの準備は完了です。次にIAM Userを作成します。 // 上記構成図のDelegateAccessUserを作成します。
- IAMコンソールを開きます。
- メニューから「ユーザー」を選択します。
- 「ユーザーを追加」ボタンを押下します。
- ユーザー名を入力して「アクセスキー・プログラムによるアクセス」にチェックを付けます
- アクセス許可の設定はスキップします。
- 必要があればタグを設定します。
- 「ユーザーの作成」を選択します。
- アクセスキー・シークレットキーが表示されるので控えておきます。
ここまでが通常のIAM User作成の手順です。LambdaExecutionRoleにAssumeRoleするため、作成したIAM Userを選択しインラインポリシーを追加します。ここでポイントになるのが"Condition"です。下記設定ではAssumeRoleをする際にMFA必須かつ、指定されたIPアドレスからのみリクエストを受け付けます。例ではMFAを必須にしたので、「認証情報」からMFAデバイスを登録しておきます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::[your account id]:role/LambdaExecutionRole",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
},
"IpAddress": {
"aws:SourceIp": [
"your ip address"
]
}
}
}
]
}
2-2-3. AssumeRoleしてみよう!
IAM UserとIAM Roleの準備ができたので実際にAssumeRoleしてみます。AWS認証情報ファイルに下記内容を追記します。 // AWS認証情報ファイルが存在しない場合、マニュアルで作成するかAWS CLIのaws configureで作成しましょう。
~/.aws/credentials
[DelegateAccessUser]
aws_access_key_id=[your access key]
aws_secret_access_key=[your secret key]
[LambdaExecutionRole]
source_profile = DelegateAccessUser
role_arn = arn:aws:iam::[your account id]:role/LambdaExecutionRole
mfa_serial = arn:aws:iam::[your account id]:mfa/DelegateAccessUser
AWS認証情報の設定が完了したら後は通常のアクセスキーの利用方法と変わりありません。profileを指定して利用する場合、LambdaExecutionRoleの方を指定します。また、MFAを必須とするのが煩わしい場合はmta_serialを削除しインラインポリシーも書き換えましょう。
最後にVisual Studioの「表示」から「AWS Explorer」を表示してみたところ…S3バケットの一覧が見れました!
3. Closing
3-1. まとめ
IAM User側でAssumeRoleの条件を指定することで特定のIPアドレスでのみ利用可能な権限を払い出すことができました。自宅の回線が固定IPにできない場合でもAWSを利用すれば簡単に固定IPが取得できます。検証用に強力な権限が必要な場合は、こちらの方法で身を守りましょう。
Discussion