エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

AWS Firewall Manager を導入してみた話

この記事はエムスリーSREがお届けするブログリレーの18日目です。

こんにちは、エムスリーエンジニアリンググループの高澤です。 Unit4(医療系ポータルサイトm3.comの開発・運営が担当のチーム)でチームSREを担当しています。 こちらのサイトのセキュリティ施策の一環として、Firewall Managerを導入してみたので、その話を紹介したいと思います。

チームSRE/コアSREについては、リレー初日の記事をご覧ください。 www.m3tech.blog

AWS Firewall Manager とは

AWS Firewall Manager は、AWS Organizations にあるアカウントとアプリケーション全体で一元的にファイアウォールのルールを設定、管理できるようにするセキュリティ管理サービスです。新規アプリケーションが作成されると、Firewall Manager はセキュリティルールの共通セットを適用することで、新規アプリケーションとリソースを簡単にこれらに準拠させることができます。
 
出典: AWS Firewall Manager(ファイアウォールルールの一元管理)| AWS

上記の通り、AWSのファイアウォールルールを一括で設定するための機能です*1。 多数のAWSアカウントでファイアウォールを管理しており、それらの設定・管理を集約したい場合には効果が期待できます。

エムスリーでは、数百のマイクロサービスを開発・運用しています。 各サービスはそれぞれ担当するチームで運用していることから、チームごとにAWSのアカウントも分かれています。

基本的にファイアウォールの設定は、各アカウントでリソースを作成して適用する必要があり、管理もそれぞれのアカウントで実施する必要があります。 例えば、新たにアタックがあったアドレスをブロックしたい、という変更を加え得る際には、各アカウントでそれぞれ変更作業が発生してしまいます。 ただ、ファイアウォールでブロックしたいアクセス元の情報は、どのサービスでも共通して利用できるはずです。 Firewall Managerを導入することで、このような設定を共通化して一元管理することで、変更作業にかかる更新作業や、設定の更新漏れの防止などのメリットが期待できます。

導入からWeb ACLの作成まで

今回の導入の背景としては、サービスのクラウド移行が進んでいることがあります。 以前はロードバランサおよびファイアウォールは、オンプレミス環境のものを利用していました。 クラウド移行が進むにつれてロードバランサもAWSに移っていきましたが、ファイアウォールの整備は遅れていた部分がありました。

大まかな導入のイメージは以下の図のようになります。Firewall Managerで作成したアクセスリストが、そのまま他のアカウントにWAF(Web ACL)として配布されているようなイメージとなります。 各サービスのアカウントとは別に、Firewall Manager 管理者アカウントを設定して作業します*2

f:id:gumfum:20210203093402p:plain

導入の流れは以下のようになります*3

  1. IP Setsを作成する
  2. Rule Groupsを作成し、IP Setsに対してのルールを設定する
  3. Security Policyを作成し、どのアカウントに適用するかを設定する

f:id:gumfum:20210203100438p:plain

それぞれ順を追って説明していきます。

1. IP Setsの作成

IPアドレスのリストを作成します。ここではどのようなルールを適用するかは設定しません。リスト名とリージョン、IPアドレス群を入力して作成します。

f:id:gumfum:20210203100651p:plain

2. Rule Groupsの作成

1.で作成したIP Setsに対して、どのようなルールを適用するかを設定します。ルールは3種類あり、1つのIP Setsに対してどれか1つを適用します。

  • Allow: アクセスを許可する。
  • Block: アクセスをブロックする。
  • Count: アクセスは許可するが、アクセスがあったことを検知する。

f:id:gumfum:20210203102045p:plain

1つのRule Groupには複数のルールを設定できますが、今後の管理のしやすさを考慮し、Allow用とBlock用の2つを作成しました。

f:id:gumfum:20210203100955p:plain

3. Security Policy の作成

2.で作成したRule Group を、Web ACLに設定するかどうかを選択します。
ここでは、作成したルールを「Set rules action to count(Countに上書きする)」として設定できます。これは、最初はCountに設定しておき、目的通り実行できていたらBlockに変更する、というテストを可能にするものであり、この方法はベストプラクティスとしても推奨されています*4

f:id:gumfum:20210203102557p:plain

次にSecurity Policyのスコープを設定します。適用したいアカウントのIDと、適用したいリソースを選択します。 デフォルトの設定では、アカウント内の全てのリソースにWeb ACLが適用されてしまうため、除外したいリソースがある場合は適宜変更します。

f:id:gumfum:20210203103529p:plain

ここまで設定してSecurity Policyの作成を完了すると、指定したアカウントのWeb ACLに、Firewall Managerで作成したルールが現れるようになります。 これをALBに紐づけることで、設定は完了となります。

f:id:gumfum:20210203104611p:plain

このWeb ACLは各アカウントでは変更できず、Firewall Manager側でのみ変更が可能になっています。 変更内容はそのまま各アカウントのWeb ACLに反映されるため、個別に変更する必要なく管理ができるようになりました。

設定後は、Web ACLの利用状況として、アクセス数やAllow/Block/Countしたアクセスの情報が確認できるようになります。 Countに設定している場合は、ここで期待通りに動作していることを確認して、Blockに切り替えることができます。

f:id:gumfum:20210203110007p:plain

導入後: ログの改善

ここまででFirewall Managerの導入は完了しましたが、導入後に1つ問題が出てきました。 それはログの管理です。

導入当初は、アクセス元やAllow/Blockの判断などの情報を、全てのアクセスに対してログファイルとして残すことを検討していました。 ただ、ほとんどのアクセスは許可されているアクセスであることから、参照される可能性が低いログがどんどん溜まっていくことになり、 コスト的にも優しくありませんでした。 ALB側でもアクセス情報のログは取得していたということもあり、Block/Countしたもののみをフィルタリングして保存する仕組みを導入しました。

具体的には、以下のような仕組みをWeb ACLを利用しているアカウント側に導入します。 WAFのログ情報はKinesis Data Firehoseで受け取り、その中の情報をLambdaでフィルタリングし、S3に保存するという構成です。

f:id:gumfum:20210203110940p:plain

フィルタリングするためのLambdaは以下のようなコードを作成しました。 Firehoseに流れてきたログの情報を確認し、actionの内容を見て処理内容を変えています*5

import base64
import json
import boto3

def handler(event, context):
  output = []
  all_allowed = True

  for record in event['records']:
    record_data =  base64.b64decode(record['data'])
    try:
      json_data = json.loads(record_data)
    except ValueError as e:
      pass
    else:
      # BLOCK
      if json_data['action'] != 'ALLOW':
        all_allowed = False
        print('[INFO] BLOCK: ' + record['recordId'])
        output_data = {
          'recordId': record['recordId'],
          'result': 'Ok', # Dropped, Ok, or ProcessingFailed
          'data': record['data']
        }
        output.append(output_data)
      # COUNT
      elif len(json_data['nonTerminatingMatchingRules']) > 0:
        all_allowed = False
        print('[INFO] COUNT: ' + record['recordId'])
        output_data = {
          'recordId': record['recordId'],
          'result': 'Ok',
          'data': record['data']
        }
        output.append(output_data)
      # ALLOW -> Drop
      else:
        output_data = {
          'recordId': record['recordId'],
          'result': 'Dropped'
        }
        output.append(output_data)

  # 全てALLOWだった場合はログファイルが出力されないので、Cloudwatch Logsにその旨を出力する
  if all_allowed:
    print('[INFO] All allowed. Nothing is output to the log file.')

  return {'records': output}

こちらのLambda関数を、WAFのログの出力先として作成した Firehose 配信ストリームに設定します。 これで、Block/Countと判定されたアクセスの情報のみが出力され、ログの容量も格段に小さくなりコスト的にも優しくなりました。

まとめ

本記事では、Firewall Managerの導入と、導入後のログの管理方法についてをご紹介しました。

Firewall Managerは比較的新しいサービスであり、現在も継続して機能が追加が行われています。 今回導入した仕組みもこれで完成形というわけではなく、今後の運用を通してより良い形に改善していければ、と思っています。

We are Hiring!

エムスリーでは一緒にサービスを開発・改善してくれるエンジニアを大募集しています。 技術を使った課題解決に興味のある方、以下のリンクからお気軽にエントリーしてみてください!

jobs.m3.com

*1:利用の前提として、AWS Organizationsに参加しており、Firewall Managerと各サービスのアカウントが同じ組織に所属している必要があります

*2:このあたりは全社的なインフラ管理となるため、エムスリーではコアSREの担当となります

*3:1. 2. の作業は、WAFを単体で利用する場合の設定と同じになります

*4:AWS マネージドルール for AWS WAFより引用

*5:ログフォーマットの詳細はウェブ ACL トラフィック情報のログ記録 に記載があります