SECCON CTF 2022 Quals を支える LifeMemoryTeam のインフラ

atpons2022/12/05に公開

この記事は MIXI DEVELOPERS Advent Calendar 2022 6 日目の記事です。

はじめに

LifeMemoryTeam の @atpons です。まず、SECCON CTF 2022 Quals へご参加いただいたみなさま、ありがとうございました。

LifeMemoryTeam では CTF に関わるインフラの提供を行っており、今回は新しい取り組みをいくつか行いましたので、そちらについて紹介したいと思います。

SECCON CTF 2022 Quals について

SECCON CTF 2022 はオンラインによる予選大会 (Quals) と決勝大会 (Final) から構成される CTF です。SECCON CTF 2022 予選は 2022/11/12 ~ 2022/11/13 の 2 日間 (24 時間) を通して開催されました。

Welcome 問題を突破したチームは 726 チームあり、数千人の方に登録いただきました。

今回の予選では国際 (International) チームと国内 (Domestic) チームから 10 チームずつ選出し、2023 年 2 月に行われる決勝大会へ出場する権利を与えています。

この記事では、SECCON CTF 2022 Quals のインフラにフォーカスをして LifeMemoryTeam (LMT) がどのような活動をしたのかをご紹介します。なお、現在 LifeMemoryTeam は SECCON CTF のインフラを構築するために集まったグループとして活動をしています。

問題サーバーの自動払い出し

SECCON CTF では、さくらインターネット様にインフラのご支援をいただいており、さくらのクラウドを全面的に活用させていただいております。

そのため、問題サーバーの払い出しについては「Terraform for さくらのクラウド (v2)」を利用し、GitHub リポジトリと GitHub Actions を活用した Infrastructure as a Code の実践と Continuous Delivery を行っています。

また、Terraform 経由で付与したインスタンスのメタデータ(タグ)を用いて Ansible インベントリを usacloud から自動生成し、必要としているプロビジョニングを自動で行うような仕組みも整備しています。これによって、例えば Terraform 経由で作成したインスタンスについてはまずベースとなるプロビジョニングを行い、追加で「docker」のようなタグが付与されている場合は Docker がインストールされるといった仕組みを整備しています。

問題サーバー払い出しの課題

この形式は普段クラウドインフラに触れている、もしくは Terraform に慣れているような開発者であれば、非常に便利なのですが、以下の課題がありました。

  1. 全ステークホルダーが必ずしも Terraform (HCL) に慣れているわけではない
  2. 問題のデプロイは手動で行う必要がある

1 については全員が必ずしも Terraform (さくらのクラウド)に慣れているわけではないのでファイルのレビューが必ず必要になります。

もちろん GitHub Actions で plan はしていますが、上記で書いたようなプロビジョニングに関係するテストが出来ているわけでもないのでインフラチームが必ずレビューしなくてはなりません。

2 については、これまで作問者のタイミングで問題のデプロイを行ってもらっていました。これはあまり制約をつけないためにやっていたのですが、最近は Docker を利用してデプロイできる問題が多くなってきたので、今回見直して問題の自動デプロイをする、という決断を行いました。

また、例えば問題サーバーが途中でダウンしてしまったり、スケールアウトさせる必要がある場合にデプロイ方法が自動されていた方が復旧へのリードタイムを少なくすることができます。前回まではデプロイされたサーバーのイメージを取得しておくことで対応していましたが、今回はそこを Immutable にできるかを検討しました。

サーバー払い出しの自動化

まず、問題サーバーの払い出しを自動化することを考えます。問題については LifeMemoryTeam が開発するスコアサーバー LMTd ではこのようなマニフェストファイルを書いてもらっています。

---
apiVersion: lmtd.lifememory.team/v1beta1
kind: Challenge
labels:
  importer.lmtd.lifememory.team/unique-key: challenge-hoge
spec:
  name: hoge
  tag:
    - author:atpons
  order: 1
  isPublic: true
  ownerCategoryUniqueKey: category-test
  description: |
    Markdown is supported! :)

このマニフェストファイルは、問題リポジトリから自動的に上記のファイルを読み取って LMTd の DB にインポートを行っています。このマニフェストファイルには問題に紐づいた配布ファイルを自動で圧縮してアップロードしたり、チェックサムを入れる機能があったり、GitOps が可能なように、基本的に闇雲に上書きしないように依存関係とリビジョンを管理する機能(Kubernetes の CD ツールには大体あるやつ)が実装されています。

そこで今回は、この問題のメタデータに、以下のようなディレクティブを追加しました。

server:
  ports:
    - 8080
protocol: tcp
docker: true
plan: medium
tags: [""]

サーバーのプランを用意したものから選んでもらい、Docker の利用可否などを書いてもらいました。これを自作した Terraform 生成ツールに読み取ってもらうことで Terraform ファイルを自動生成することにしました。

Terraform の自動生成のロジックはとても単純で YAML を読み取って Go テンプレートするだけです。ベースとなるファイルはすでにリポジトリにあるので、サーバーの tf ファイルの生成に集中させています。

GitHub Actions では Jenkins のようなパラメータ付きビルドをサポートしているので手動トリガーさせて生成させるようにしました。(レビューするのがインフラチームなので自動ではなく手動にしています)

これで、問題サーバーの自動構築が可能になりました。

Ansistrano による自動デプロイ

次に問題の自動デプロイを検討します。問題は規則的なディレクトリ構成となっており、マニフェストファイルの位置から問題のデプロイに必要なファイルを特定することができます。

つまり、特定の場所に docker-compose.ymlを配置してもらうことで Docker Compose サービスを起動すればよいだけにしました。

これを実現するためには、そのような Ansible のロールを記載すればよいですが、問題ファイルの転送はちゃんとやりたいです。ちゃんとやりたいというのはどういうことかというと、ダウンタイムをできるだけ少なくしたいのです。

普通に copy などで上書きをしてしまうと、予期せぬファイルが残ったままになったりしてしまいます。そこで Capistrano のようなデプロイ戦略を取り、コピーしたあとに symlink を張り替えて、再起動して欲しいのです。

今回は Ansistrano というロールを利用しました。Capistrano 2 のような体験でデプロイが可能になり、symlink の張り替えによってデプロイを実現しています。なので、デプロイの after hook で Docker Compose の再ビルドと起動を行うことでダウンタイムの少ないデプロイの自動化が可能になります。

まとめ

ここでは、このような施策をとって問題の自動デプロイの実装を紹介しました。

  • Terraform ファイルを自動生成するツールを作った
  • 自動デプロイを実装するツールと CD パイプラインを作った

これにより、問題の Immutable なデプロイを実現することができました。

SECCON ID (Beta) の開発

続いて今回、LifeMemoryTeam では初めての取り組みとなる SECCON における認証認可基盤の自作と運用について紹介します。

SECCON CTF のスコアサーバーでは 2020 年、2021 年と独自実装の LMTd を利用してきましたが、以下のような IDaaS を利用してきました。

  • Firebase Authentication (SECCON CTF 2021)
  • Azure AD B2C (SECCON Beginners CTF 2020、SECCON CTF 2020 などに採用)

これらの IDaaS では、これまでの開催において以下のような懸念がありました。

  • Firebase Authentication の場合、無料のプランだと内部のクオータに引っかかってしまいサービスの継続に重大な懸念が生じた
    • 認証機能をフロントエンドに委譲できる点では良かった
  • Azure AD B2C の場合、ライブラリなどを用いて標準化できる部分もある一方、一部を独自実装する必要があり、致命的なバグを発生させてしまった
    • 追加属性を収集し、エクスポートできる点では良かった
      • Microsoft Graph API が難しかった

このような点から、自作を行う案が上がってきました。また、SECCON CTF では統計情報を収集するため、アンケートを開催毎に集計しており、これを認証フローに組み込むことでスコアサーバーからそのようなロジックを剥がすことができるという事情もありました。

IdP、認可サーバーの実装

https://platform.id.seccon.jp/

そこで認証認可基盤を SECCON ID として作成しました。

通常の IdP の機能に加えて OAuth 2.0 Authorization Server を実装したものになります。といっても、特別なことをしている訳ではありません。強いていうのであれば、追加属性を収集できるようになっているというところです。現在、SECCON 以外での活用を考えていないため、OpenID Conenct については実装していませんが、今後 OpenID Connect を用いた属性のアクセスや、CIBA フローを組み込むことによるリアル会場での活用などを今後提供できればと思っています。

初期実装では IdP としての機能を実装し、OpenID Connect 関連の機能を Hanko という OSS を用いて実装する予定でした。しかし、そのままサービスに載せるための懸念がいくつか生じたため、実装を断念し、独自実装に切り替えました。Hanko は、Passkey (WebAuthn) を利用できる非常に便利なで協力な OSS なので、今後機会があれば利用してみたいです。

メールの配信には SendGrid Pro を契約して配信を行いました。当初、Microsoft 宛てのメールが配信できずにご迷惑をおかけしました。現在は解除されており、送信に問題がないことを確認しています。

フロント面では Next.js と daisyUI を使って自分で作成しました。daisyUI はかなり使いやすくて良かったです。フロントエンド分からんけどそれっぽいものが作れました。

インフラのリプレイス

Kubernetes の縮退とそれに伴う監視基盤のリプレイス

今回は、スコアサーバーや問題サーバーのアップデートを実施したことをご紹介しました。最終的な構成としてはこのような構成となっています。

SECCON CTF 2021 までは Kubernetes 上にインフラをデプロイしていましたが、今回はさくらのクラウドをフル活用するため、そしてリアル会場における開催に向けた準備をするためにあえて 複雑さを取り除くということで Kubernetes をやめるという選択肢を取り、使えるインフラを最大限に活用する形で準備をすすめてきました。これまでは Kubernetes を利用できるといっても、限られたサイズのノードしか利用できていませんし、特にリソースをフル活用できていたかというとそうではありません。もちろん、デプロイパイプラインが非常に楽でしたが、それ以上に今回はリソースの活用最大化にフォーカスをすることを目指しました。

デプロイには Ansible と先述した Ansistrano を用いてデプロイを行い、サーバー自体は Go で書かれているので systemd を活用してログのファイルへの書き出しとサービス化を行いました。

VictoriaMetrics の採用

また、内部の監視スタックは Thanos から VictoriaMetrics へ切り替えを実施しました。ただし、スポットで開催するものであり、リテンションやスケーラビリティなどに気を遣う必要がないのでシングルノードで動作させています。

Grafana Loki から Elasticsearch への移行

ログ収集基盤については、Grafana Loki から Fluentd + Elasticsearch へ変更を実施しました。

Grafana Loki は非常に便利で協力なログ収集基盤であることは間違いないですが、高スループット下でのチューニングが非常に難しくカジュアルに運用するのが難しいため、今回は安定性を重視して Fluentd から Elasticsearch クラスタへ向かってログを集めることにしました。

Fluentd Forwarder を3 台準備し、各サーバの Fluentd から Forwarder へ転送して3 台で構成された Elasticsearch クラスタに転送するという非常に古典的な構成ですが、ダウンタイムが起きることなくログを収集していってくれました。

1Password を用いた秘匿情報の管理

LifeMemoryTeam は 1Password for Open Source Projects へ参加させていただき、1Password の Team を無償で利用させていただいています。

これによって秘匿情報の管理に 1Password と 1Password Secrets Automation を利用することができ、CI または CD においても秘匿情報の管理を一元化することができ非常に良い体験をすることができました。

最後に

こちらはスコアサーバーのトラフィックと API アクセスのメトリクスです。

開催当初、ロードバランサーの負荷が高くなり一時的にレイテンシが悪化したものの、競技が続行不能になるようなバグが生じることなく終わることができました。

参加いただいたみなさま、SECCON CTF WG のみなさま、インフラを提供していただいた さくらインターネット 様、本当にありがとうございました!

次回は本戦を 2023/2/11 より浅草橋で開催します。また、併催されるイベントではカンファレンスなども準備していますので、ぜひお越しください!

https://www.seccon.jp/2022/

MIXI DEVELOPERS NOTE

株式会社MIXIのZenn版エンジニアブログです。実務で得た知見や個人的に調べたことなど思い思いに発信します。

Discussion

ログインするとコメントできます