NFLabs. エンジニアブログ

セキュリティやソフトウェア開発に関する情報を発信する技術者向けのブログです。

MWS Cup 2022でスコアサーバ(CTFd)をAWSに用意してみた

はじめに

皆さんこんにちは|д゚)
NFLabs.の北村と市岡です。
今回は当社の須賀の管理下で、AWS上にMWS Cupのスコアサーバ環境の構築をやってみたので、本ブログで紹介したいなと思いました(´▽`*)ぜひぜひ見てください~
去年のノウハウをベースに工夫したところや挑戦してみて失敗しちゃったところがあるので、「こんなことやったんだ~」って感じの軽いノリで見てもらえればありがたいです!
まだ2年目と1年目のぴちぴちの若手なので温かい目で見ていただければと思います(>_<)

MWS Cupってなに?

まずはMWS Cupについてご存じない方もいるかと思うので、紹介から始めようと思います。
本記事で少しでもMWS Cupに興味を持っていただければ幸いです(^^♪
興味を持っていただけたらぜひMWS Cupホームページを見てください。

MWS Cupは情報処理学会のCSEC研究会・SPT研究会が開催するコンピュータセキュリティシンポジウムの併設ワークショップが開催するコンテストで、マルウェア対策技術を競う内容になっています。

参加のハードルは少し高く、「研究責任者」がMWSと契約したうえで、MWS Cupに参加登録します。また、コンピュータセキュリティシンポジウムに参加登録(有料)することでMWS Cupに参加できます。

今年のMWS Cupは2022-10-25に熊本で開催され、オンサイトとオンラインのハイブリッドでの開催でした!

MWS Cup オンサイト

スコアサーバのアーキテクチャ設計を若手だけで考えてみた

ここからは実際に構築したアーキテクチャについて紹介していきます。

スコアサーバの構成

昨年から引き続き、スコアサーバにはCTFd(https://ctfd.io/)を使いました。 CTFdにはdocker-compose.ymlが同梱されていて、これを使うことで以下の4つのコンテナが起動します。

  1. CTFdのサーバ
  2. HTTPプロキシサーバ
  3. キャッシュサーバ
  4. データベースサーバ

docker-composeさえ起動すれば、サーバのスペックをある程度発揮できるようになるため、docker-compose.ymlを使うことにしました!

昨年はEC2インスタンス上にdockerとdocker-composeをインストールし、CTFdを起動していました。

最終的に考えたアーキテクチャ構成

スコアサーバの冗長化やインシデント発生時にいち早く気づくため、最終的に以下の構成にしました! EC2インスタンスにCTFdをインストールし、CTFdに同梱されているdocker-compose.ymlをもとにCTFdを起動しました。
この際、docker-compose.ymlでCTFdサーバ、HTTPプロキシサーバ、およびキャッシュサーバのみにして、データベースは削除しました。
削除したデータベースサーバの代わりにRDSを設置し、CTFdの設定で、データベースサーバがRDSを指すように設定しました。

AWS CloudWatchやAWS SNS、AWS ChatBotを利用してEC2インスタンスのCPU利用率が高くなった際には、Slackに通知されるようにしました!
また、データベースサーバとしてRDSマルチAZインスタンスを使いました。
この構成ではデータベースだけ冗長構成になっています。

MWS Cup本番!!

競技開始直後に会場の様子を見ていましたが、特に悲鳴が上がることもなく、参加者が静かにもくもくと問題を解き始めた様子が確認できました!
ピーク時でもCPUの使用率が10%未満でした(´▽`) ホッ

また、終了直前のCPU使用率にも余裕がありました。

結果として、開始直後も終了直前も余裕でした。少しオーバースペックだったかもしれないです(;^ω^)

工夫してみたところ

MWS Cup 運営会議で出されたスコアサーバに対する要望は「落ちない」ことだけでした。 このため、どうすれば落ちないか、もし落ちたとしてもどのような手順で復旧できるかを考えました。
私たちは要望を満たすために以下の2つのリスクについての対策を考え、実際に実行できるまで準備を行いました!

  • リスク1: スコアサーバを配置したアベイラビリティゾーンでデータが全損すると復旧ができない
  • リスク2: アクセスが集中して、スコアサーバにアクセスできない

復旧できる?

リスク1の「スコアサーバを配置したアベイラビリティゾーンでデータが全損すると復旧ができない」について、バックアップを取り、手作業で復旧することにしました。
CTFdにはバックアップ機能があるため、バックアップ機能で出力されるファイルを見ました。
この結果、データベースとアップロードされたファイルが含まれることが分かりました。

バックアップ対象 データの生成タイミング
データベース 参加者がフラグを送信したときなど
アップロードされたファイル 問題を登録したとき・CTFdにロゴなどをアップロードしたとき

アップロードされたファイルは、競技中に変更が無いことが分かりました。
そこで、EC2インスタンスからデータベースを外したうえで、スナップショット(AMI)を作成することにしました。
また、データベースはRDSのマルチAZインスタンスを使うことにしました。
これにより、アベイラビリティゾーン(AZ)ごと全損した場合でもAMIから別のAZにインスタンスを作るだけで、完全に復旧できることを確認できました。
訓練の結果、復旧作業を5分で完了できるようになりました!!ガンバッタ

サーバはアクセス集中しても耐えられる?

リスク2の「アクセスが集中して、スコアサーバにアクセスできない」について、対策はオートスケーリングか負荷テストをもとに大きいサーバを用意するの2択を思いつきました。 昨年のHTTPプロキシサーバのログがあったので負荷を予想できることと、競技時間が4時間だったので、オートスケーリングではなく負荷テストによって最適な大きさのサーバを用意することに決めました。

負荷テストでサーバにかける負荷の量を決めるため、昨年のHTTPプロキシサーバのログを確認しました。nginxのログから1秒あたり最大で81アクセスがあることが分かりました。

$ cat access.log | grep "26/Oct/2021" |cut -d " " -f 4|uniq -c|sort -n|tail
     35 [26/Oct/2021:00:00:20
     35 [26/Oct/2021:00:01:38
     35 [26/Oct/2021:00:01:52
     37 [26/Oct/2021:00:00:36
     37 [26/Oct/2021:00:01:43
     38 [26/Oct/2021:00:00:14
     38 [26/Oct/2021:00:00:53
     41 [26/Oct/2021:00:00:10
     50 [26/Oct/2021:00:00:16
     52 [26/Oct/2021:00:00:41
     53 [26/Oct/2021:00:00:09
     56 [26/Oct/2021:00:00:11
     58 [26/Oct/2021:00:00:17
     81 [26/Oct/2021:00:00:15

また、キャッシュが効かないフラグ提出(DB書き込み)の回数を調べてみると、最大で6回/秒でした。26/Oct/2021:04:00:12は競技終了12秒後なので、滑り込みでフラグを送った方が多かったのかもしれません(゚Д゚;)

$ cat access.log |grep "26/Oct/2021" |grep "attempt" |cut -d " " -f 4|uniq -c|sort -n|tail
      3 [26/Oct/2021:03:56:21
      3 [26/Oct/2021:03:59:22
      3 [26/Oct/2021:03:59:34
      3 [26/Oct/2021:03:59:41
      3 [26/Oct/2021:03:59:54
      3 [26/Oct/2021:04:00:02
      3 [26/Oct/2021:04:00:08
      4 [26/Oct/2021:03:13:09
      6 [26/Oct/2021:04:00:11
      6 [26/Oct/2021:04:00:12

負荷テストツールであるApache benchを使って負荷テストをやってみました。 評価の対象としたのは、t3.mediumとm5.xlargeの2つのインスタンスタイプです。 また、負荷テストで再現したい内容は以下の3つでした。

  1. 開始直後のテスト/challengesで一斉に問題を読み込む
  2. 大きい問題ファイルを参加者が一斉にダウンロードする
  3. 終了直前にフラグを一斉に送る

それぞれを同時接続数を調整することで、1秒あたりの接続数を調整して、平均応答時間を調べました。

項番 項目 t3.medium [#/sec] t3.medium平均応答時間 [ms] m5.xlarge [#/sec] m5.xlargeの平均応答時間 [ms]
1 /challenge 36.29 1377.627 80.12 1248.103
2 問題ファイル(1.4MB) 84.96 1176.987 188.33 530.979
3 /attempt 9.23 541.877 10.30 485.208

この結果、/challengeはt3.mediumでは、同時接続数を大きくしても、36.29件/秒であり、去年の開始直後の実績の81アクセス/秒に足りていないことが分かりました。このため、m5.xlargeにしました。

IaC化してみた

来年の引継ぎとテスト用環境を気軽に立てられるようにするため、AWSで作成するリソースのデプロイをTerraformで実行しました。Chatbotの設定以外は以下の3コマンドだけで実行できるようにしました! 来年はだいぶ楽になりそうです笑

terraform init
terraform plan
terraform apply

失敗しちゃったところ

失敗しちゃったところは「本番環境をこわしちゃった」ことです。

昨年まではスコアサーバがEC2インスタンス1つだったことに対して、今年はAWS上で様々なサービスを利用することになりました。 このため、TerraformでIaC化して管理していました。
terraform applyを実行した後、もう一度terraform applyを実行すると、EC2インスタンスがreplaceされてしまいました。
このEC2インスタンスのEBS(ディスク)は保護されていませんでした。 つまり、全データが削除されてしまいました。
これをきっかけにバックアップの大切さを強く感じました。

本番環境を壊したときは、まだ参加者や作問者にスコアサーバの案内を送る直前だったので具体的な被害はありませんでした。 今後もやりそうだったので、本番環境のterraformを実行するディレクトリでsudo chmod 000 terraform.tfstateを実行して、sudoを使わないと操作できないように設定しました。

感想

万全を期して準備をしていたのですが、やはりアクセスが集中する時間帯にサーバのCPU使用率やアクセス数を見るのはすごくドキドキしました。乗り切れて本当に良かったです(>_<)
また、スコアサーバに取り組むことでAWSに慣れることができました。
当社には自由に挑戦ができるような社風があります。今回のMWS Cupのスコアサーバ構築では自由に挑戦をさせてもらい、心配なところは先輩方に助けていただき無事に乗り切ることが出来ました(*^^)v
さらにチームを横断したMWS Cup担当委員同士でも仲良くなる機会になって良かったです!
来年もぜひ運営に携わりたいと思います!