サーバーレスセキュリティのベストプラクティス10選
2019年5月31日
0 分で読めますチートシートシリーズの今回は、サーバーレスのデプロイメントを保護するためのベストプラクティスを紹介します。
それでは、サーバーレスセキュリティのベストプラクティス10選を見ていきましょう。
まだダウンロードしていない場合は、今すぐこのチートシートをダウンロードして、目につく場所に貼っておきましょう。次の意思決定を安全なものにするために役立ちます。
多くの例やユースケースではAWS Lambdaを取り上げていますが、ほかのクラウドやサーバーレスのベンダーにも広く当てはまります。参考として、CNCF Landscapeの一覧もご覧ください。
それでは、サーバーレスセキュリティのベストプラクティス10選を見ていきましょう。
1. 関数の依存関係にパッチを適用する
Function as a Service(FaaS)プラットフォームは、OSの依存関係へのパッチ適用を担いますが、npm、PyPI、Mavenなどから取得するアプリケーションの依存関係は保護しません。こうしたライブラリはOSの依存関係と同様に広く使われ、脆弱性も存在します。アプリケーションの所有者である皆さんには、脆弱性が公表された際にライブラリをアップグレードまたは更新する責任があります。
Snykのようなソリューションを使って、サーバーレスプロジェクトのオープンソース依存関係に既知の脆弱性がないかスキャンしましょう。Snykは脆弱性レポートの作成にとどまらず、修正方法のアドバイスを提供し、バージョンアップやセキュリティパッチを通じて自動的に修正を適用します。
Snykのツールを使えば、開発ライフサイクル全体を通じて関数を保護できます。VSCodeやIntelliJのプラグインを使って統合開発環境(IDE)で保護を始め、GitHubアプリとの連携により、脆弱性の発見時に修正用のプルリクエストを自動で作成できます。また、セキュリティ上の脆弱性が新たに持ち込まれた場合にデプロイを防ぐため、継続的インテグレーション(CI)のビルドを失敗させることもできます。
関数の安全なデプロイを徹底する
CIやソースコードリポジトリの監視、脆弱性へのプロアクティブなパッチ適用に加えて、関数のデプロイワークフローにもセキュリティレビューを組み込む必要があります。デプロイ対象の関数に脆弱性が見つかった場合は、デプロイを中止しましょう。
Serverless Frameworkは、サーバーレス関数の開発とデプロイによく使われるツールキットです。プラグインアーキテクチャにより、関数のライフサイクルに独自のワークフローを組み込めます。Snykは、フレームワークとシームレスに連携するオープンソースのServerlessプラグインを提供しています。
以下の画像は、オープンソース依存関係にセキュリティ上の脆弱性が検出されたため、関数のデプロイを阻止するプラグインの動作を示しています。

Serverless Snykプラグインのセットアップや、CI/CDワークフロー用のプロジェクトスナップショットの作成、プロジェクトの監視について詳しくは、Serverless Frameworkプラグインをセットアップし、こちらの記事をご覧ください
2. 最小権限の原則を採用する
関数は小規模なため、それぞれの権限を必要最小限に絞り、関数ごとの動作に必要なアクセスだけを許可できます。これにより、攻撃が成功した場合に生じる被害を大幅に抑え、複数の関数を連携させたシステム全体の攻撃対象領域を縮小できます。たとえば、多くの関数はデータベースへのアクセスや外部サーバーへの接続を必要としません。これらは攻撃者や悪意あるユーザーが侵害後によく行う操作です。
最小権限の原則を徹底しましょう。攻撃対象領域を減らして環境を安全に保つため、関数には動作に必要な最小限の権限だけを付与してデプロイしてください。
必要以上の権限を誤って付与する
このプロジェクトでデプロイするすべての関数に、単一の権限ロールを定義する次のserverless.yml設定を見てみましょう。
上記の設定で問題となるのは、関数とともにデプロイされるIAMロールが、DynamoDBのすべての読み取りおよび書き込み操作へのアクセスを許可している点です。しかし実際には、読み取りだけが必要な関数もあれば、削除が必要な関数もあります。このような単純なサーバーレスプロジェクトのデフォルト設定は攻撃対象領域を広げます。関数ごとにデプロイを細分化し、必要に応じて権限を設定するほうが適切です。
関数ごとにロールとアクセス権限を適切に設定する
Serverless Frameworkでは、次のserverless.ymlファイルの例のように、関数ごとにロールを設定できます。
7行目から、func0とfunc1の2つの関数が宣言されています。各関数には、9行目と12行目のroleディレクティブで指定された、それぞれ固有のロールが割り当てられています。
ロール定義を分けると、各関数に必要な権限だけを付与し、よりきめ細かくアクセスを制御できます。たとえば、ある関数にはAWS関連のログ機能を許可し、別の関数にはAmazon S3バケットへのアクセスを許可するといった設定が可能です。
3. 関数ごとに独立した境界を維持する
複数の関数をデプロイして一連のワークフローを構成する場合でも、各関数を独立した境界として扱いましょう。ある関数の脆弱性がほかの関数に波及し、侵害につながるのを防げます。
例として、次のシナリオを見てみましょう。
subscribeToEmailNotification関数が入力をサニタイズし、その後sendNotification関数が呼び出されて、その入力を処理し配信します。最初の「subscribe」関数ですでにサニタイズしているため、2つ目の関数では入力イベントのサニタイズを省略してもよいと思うかもしれません。しかし、後に入力をサニタイズしないsubscribeToSMSNotification関数が新たに作られると、データをサニタイズしないままsendNotification関数がイベントデータを処理することになってしまいます。
関数をそれぞれの境界内で分離するには、次のガイドラインに従ってください。
関数のアクセスや呼び出し順序を前提にしない:ある関数が別の関数経由でしか呼び出されない、またはAPIゲートウェイからアクセスできないと決めつけないでください。関数の順序やアクセス方法は時間とともに変化します。
各関数を独立したセキュリティ境界として扱う:すべての関数で、イベント入力を信頼できないデータソースとして扱い、必ず入力をサニタイズしてください。
セキュリティライブラリを使用する:標準化されたセキュリティライブラリの作成や採用に時間をかけ、すべての関数での使用を徹底しましょう。
4. イベント入力をサニタイズしてインジェクションを防ぐ
サーバーレスアーキテクチャでは、クラウド関数にさまざまな種類のデータを取り込む必要がよくあります。同期、非同期、ストリーミングなどのデータには、異なるデータストアや関数を経由するユーザー制御のデータが含まれる場合があります。
APIゲートウェイ、ファイアウォール、その他のプロキシで保護されていても、APIサービスで使われる関数は、従来のAPIサーバーと同様にユーザー入力を扱います。さらに、メッセージキューなど非公開の通信からイベントデータを処理する関数も、間接的にユーザー入力を扱うことがあります。ただし、データの文脈やソースが曖昧になり、予測しにくくなります。
サーバーレスアーキテクチャの多くはイベント駆動型です。そのため、イベントインジェクションは主要な攻撃ベクトルとなります。関数は、イベントキューのデータ処理など、小規模なタスクを実行する目的で作られます。しかし、悪意あるデータがデータサニタイズ関数を迂回してイベントペイロードに到達すると、処理関数がデータ検証を適切に行わない場合、インジェクション攻撃に対して脆弱になる可能性があります。
関数の一般的なトリガーには、次のような従来とは異なるデータソースがあります。関数で信頼できないデータとして扱う必要があります。
ストレージ — S3バケットなどのクラウドストレージ内のファイル名やディレクトリ名はユーザーが制御できる場合があり、インタープリターに悪意ある入力を渡す可能性があります。
メッセージング — SNSやSQSなどのサービスを介した非同期メッセージのイベントデータペイロードは、サニタイズし、信頼できないデータとして扱う必要があります。
データベースストリーム — レコードの追加や削除などのデータベース更新が、関数のトリガーになる場合があります。こうしたイベントにはユーザー入力が含まれる可能性があるため、サニタイズが必要です。
イベントインジェクション攻撃を緩和するため、関数が扱うユーザー入力には次のベストプラクティスを適用してください。
データ転送オブジェクトやスキーマに基づいてデータを検証し、型、長さ、データ範囲が想定どおりか確認します。オブジェクトを無条件にシリアライズ/デシリアライズして、そのまま渡すのは避けましょう。
SQLデータベースと非SQLデータベースのどちらを使う場合も、ORMを必ず使用し、適切なエスケープ処理を行って、こうしたインジェクションを防ぎましょう。
イベント由来のデータはユーザー入力に由来する可能性があるため、システムプロセスの起動や実行時の動的コード評価に使用しないでください。また、関数に統合するサードパーティサービスにも注意が必要です。データソースやユーザー制御入力の範囲を把握しにくく、制御もほとんどできないためです。プロセスの起動や動的コードを扱う場合は、それぞれ適切なエンコーディングやサンドボックス化などの対策を講じてください。
5. APIゲートウェイをセキュリティバッファーとして活用する
デプロイされたクラウド関数は外部に公開されることが多く、イベントやデータ、ペイロード処理に必要なコンテキストを送信できるランダム生成のHTTPエンドポイントからアクセス可能です。関数を公開する際は、リバースプロキシとして機能し、ユーザーと関数の間に分離レイヤーを設けるAPIゲートウェイを経由させるのが有効です。
APIゲートウェイは利用者向けAPIの窓口として、少し設定するだけで複数のセキュリティ機能を提供できます。これらの機能を活用すると、関数を通じた攻撃対象領域を縮小できます。
フィルターとしてのAPIゲートウェイ
関数を公開する前にAPIゲートウェイを配置し、ゲートウェイポリシーに基づいて入力を制限するフィルターとして使いましょう。ポリシーを厳格にするほど、関数を介してリスクが入り込む可能性は低くなります。AWS API Gatewayでは、スキーマに準拠したリクエストとレスポンスのマッピングを定義できます。これはデータ転送オブジェクトと似たパターンです。関数とゲートウェイのマッピングを使えば、受信リクエストに厳格なルールを適用できます。
以下は、受信するJSONリクエストに対してAPIゲートウェイレベルで定義したスキーマの例です。
認証の入り口としてのAPIゲートウェイ
クラウド関数に届く前にHTTPリクエストをゲートウェイで制御することは、認証や認可など、アプリケーションへのユーザーアクセスを管理するうえで重要です。APIゲートウェイを設定し、関数に関する処理はクラウドプロバイダーとそのインフラストラクチャに任せましょう。
APIゲートウェイでユーザーを認証した後は、関数を呼び出さずに、ゲートウェイでリクエストを制限したりクォータを適用したりするなど、ユーザーに対する対策を講じられます。
DDoS対策としてのAPIゲートウェイ
APIゲートウェイは、クラウドプロバイダーのレベルでサービス拒否(DoS)攻撃への保護を追加し、関数に向かうすべてのリクエストを制限できます。本来関数やビジネスロジックで扱う必要のないレート制限をクラウドインフラストラクチャに任せられます。APIゲートウェイを導入すれば、リソースの枯渇による予想外のコストを防ぐのに役立ちます。
6. 関数を監視し、ログを記録する
関数の実行時間は非常に短く、デプロイする関数やスケールに伴う呼び出し数が増えると、エラーの原因を特定するためにイベントの流れを追跡するのが難しくなります。組織でサーバーレスの導入が進むほど、安全でない処理フローや、関数を危険なコードパスに誘導しようとする攻撃者の悪意ある試みを監視することも複雑になります。
AWSは最近、Lambda関数にタグを付ける機能を導入しました。これにより、関数を簡単に追跡してグループ化できます。Serverless Frameworkを使えば、YAMLファイルでタグを設定できます。serverless.yml内のすべての関数に一括で設定することも、個々の関数に設定することも可能です。
関数のセキュリティ脆弱性を監視する
SnykはFaaSプロバイダーと連携し、デプロイ済みの関数を監視して安全な状態に保ちます。これにより、関数とそのオープンソース依存関係に関連する既知のセキュリティ脆弱性に、ソフトウェア開発ライフサイクル全体を通じて対処できます。
次の画像は、スキャンを実施し、複数の高・中・低の脆弱性が検出されたGitHubプロジェクトlirantal/bazz-serverlessです。これは、複数の関数をデプロイする私のサーバーレスプロジェクトのソースコードです。GitHubのプロジェクトリポジトリの下には、デプロイした6つのAWS Lambda関数が表示されています。これらはAWSによってデプロイされ、実行されている実際の関数です。

Snykはこれらの個々の関数をスキャンして既知のセキュリティ脆弱性を検出しました。それぞれが同じ依存関係ツリーを使用しているため、すべての関数が脆弱なライブラリのバージョンとともにデプロイされていることが分かります。
クラウドプロバイダーは、アプリケーションや関数のリソースに関するこうした情報を得られるよう、監視機能を標準で提供していることがよくあります。MicrosoftのAzure Monitoringや、AmazonのAWS X-Rayがその例です。たとえばAWS X-Rayコンソールでは、関数やその他のクラウドリソース間でのデータの流れや、実行時間などのメトリクスを確認できます。
7. アプリケーションコードにセキュアコーディング規約を適用する
サーバーレスインフラストラクチャの大きなメリットは、基盤となるオペレーティングシステムの管理をアプリケーション所有者からクラウドプロバイダーに移せることです。クラウドプロバイダーはOSの保守とセキュリティパッチの適用を担います。しかし、その結果、攻撃者の関心は依然として露出している領域へと移り、その筆頭がアプリケーションコードそのものです。
アプリケーションコードには依然として脆弱性が存在します。開発者はセキュアコーディング規約に従い、コードレビューなどの作業でもガイドラインが守られていることを確認して、ソフトウェア開発の早い段階でコードのセキュリティ問題を見つける必要があります。開発チーム全体で共通のセキュリティライブラリを必須にすれば、セキュアコーディングのガイドラインを確実に順守できるだけでなく、標準的な解決策がすでにあるセキュリティ上の課題について、開発者が車輪の再発明をしてミスを犯すことも防げます。
OWASP Top 10は、アプリケーションコードの中で特にセキュリティに注意を払うべき領域を知るための優れた参考資料です。以下はその一部です。
インジェクション攻撃:データが適切なコンテキストでフィルタリングまたはエンコードされていないと、信頼された処理の一部として解釈される可能性があります。これにはSQLインジェクション、システムコマンドの実行、CSSやJavaScriptコードの実行などが含まれます。あらゆるコンテキストで悪意あるインジェクションを防ぐには、可能であればユーザー入力を完全に受け付けないようにし、安全な許可リストを使用するとともに、ユーザー提供データをすべて適切なコンテキストに合わせてエンコードしてください。
機密データの漏えい:攻撃者は、安全でない通信手段を悪用して機密情報を外部に持ち出したり、セキュリティ上重要な場面で安全性の低い暗号アルゴリズムを利用したりする可能性があります。対策として、通信には安全な手段であるTLSを使用し、パスワードなどの認証情報は常に、適切な暗号学的安全性を備えたアルゴリズムで暗号化またはハッシュ化してください。
アクセス制御の不備:攻撃者が本来アクセスできないリソースにアクセスできてしまいます。対策として、デフォルトでアクセスを拒否するなど、適切なアクセス制御の仕組みを実装し、許可を最小限にする認可モデルに従ってください。ブルートフォース攻撃やサービスの悪用を最小限に抑えるため、必要に応じてレート制限を適用します。
注:完全な一覧については、OWASP Top 10 2017ガイドを参照してください。
8. 通信中のデータを保護し、検証する
HTTPSの利用状況に関するhttparchiveの情報から分かるように、ベストプラクティスの普及に伴い、安全なWeb通信の利用が広がっています。同サービスが監視した全トラフィックの77%がHTTPSを利用しています。関数と連携先のサービスは、サードパーティかクラウドの境界内かを問わず例外ではなく、すべて安全な通信手段を使用する必要があります。
通信中のデータを安全に扱うため、次のガイドラインに従ってください。
HTTPSを活用して安全な通信を確保しましょう。内部ネットワーク内で呼び出す他の関数やサービスだけでなく、クラウド提供のサービスやクラウドベンダーの外部にあるサードパーティサービスとの通信にも使用してください。
SSL証明書を検証して、通信相手の身元を確認しましょう。サーバーの身元や真正性が証明書と一致しない場合は、すべての通信を停止してください。
対応しているクラウドベンダーでは、署名付きリクエストを有効にする
サードパーティサービスからの応答は信頼できないユーザー入力として扱い、サニタイズする
クラウド内サービスで安全な通信手段を使う
クラウドプロバイダーのインフラストラクチャ内にあるサービスやリソースへのアクセスでは、可能な限り安全な通信手段を使用してください。たとえば、AWS Lambda関数をSNSと連携させる場合は、SSL経由の通信を有効にします。
署名付きリクエスト
AWS SDKやAWS CLIなどのクラウドベンダーのツールを使ってHTTPリクエストを作成すると、HTTPヘッダーまたはクエリパラメーターに署名が自動的に追加され、リクエストの送信元を示します。これにより、通信中のデータがさらに保護され、HTTPリプレイ攻撃を防止できます。
たとえば、AWS署名付きリクエストでは、署名はAuthorizationヘッダーとしてHTTPメッセージに追加されます。
9. シークレットを安全なストレージで管理する
シークレットや機密性の高い認証情報には、大手クラウドおよびFaaSベンダーがサポートする安全なストレージを使用してください。代わりに、HashicorpのVaultなど独自のソリューションを導入することもできます。キーをシークレットストレージに保存すれば、ソースコードリポジトリ内の静的ファイルや環境変数に機密情報を保存するリスクを軽減し、情報漏えいの可能性を大幅に下げられます。
次の例では、AWS Simple Systems Managerを使って、AWS Parameter Storeで安全に暗号化されたシークレットを取得します。この例では、SSM変数を使って事前に作成した特定の値にアクセスし、環境変数を通じて関数から利用できるようにしています。
${ssm:/github/api-key}はキーの暗号化された値を返します。復号して実際の値を取得するには、${ssm:/github/api-key~true}を指定する必要があります。
Parameter StoreとKMSの詳細については、Amazonのドキュメントをご覧ください:https://docs.aws.amazon.com/kms/latest/developerguide/services-parameter-store.html
アプリケーション内のシークレット管理のセキュリティと柔軟性をさらに高めるには、新しい設定を適用するたびにプロセスの再起動が必要となる環境変数ではなく、実行時にシークレットやその他の設定情報にアクセスすることを検討してください。
シークレットストレージを使えばキーが漏えいする可能性は低くなりますが、完全になくなるわけではありません。幸い、関数でシークレットストレージを使っているなら、キーそのものにこだわる必要はありません。定期的にキーをローテーションすればよいのです。キーが漏えいしたり盗まれたりしても、悪用できる期間を短くできます。
10. 関数を最小単位でデプロイする
関数は小さくあるべきであり、それに伴ってデプロイするコードも少なくなります。これにより攻撃対象領域が縮小し、関数が侵害された場合に漏えいする情報も少なくなります。必要以上のコードや関数をまとめてデプロイすることは避けましょう。
同じサーバーレスプロジェクトに属する関数は、デフォルトで同じ依存関係を共有します。たとえばNode.jsのサーバーレスプロジェクトでは、依存関係を記載した単一のpackage.jsonファイルを共有し、すべての関数とともにデプロイします。しかし、その依存関係のすべてが各関数で明示的に必要とは限りません。
定型コードを何度も再利用する場合は、次のようなテンプレートを使ってプロジェクトを生成すると便利です。
すぐに参照できるよう、Serverless Security Best Practicesチートシートをダウンロードしてください。また、Snykの無料アカウントに登録して、今すぐコードのセキュリティ対策を始めましょう。
CTFを始めよう
オンデマンドのバーチャル入門ワークショップで、CTFチャレンジの解き方を学びましょう。