SECCON Beginners CTF 2021のスコアサーバを支える技術(サーバ編・その1)
LifeMemoryTeamの@atponsです。今回のSECCON Beginners CTF 2021も参加いただきありがとうございました。
LMTd(サーバ)の技術スタック: Azure Kubernetes Service、Azure AD B2C、FluxCD、Azure Key Vault、Go、Grafana Loki、Fluent Bit、Prometheus、Azure Database for MariaDBなどでした。ありがとうございました。 #ctf4b #seccon
— atpons (@atpons) 2021年5月23日
こちらに書いた通り、今回はAzureを中心としたサービスの利用を行いました。今回は自分の担当部分であるスコアサーバのバックエンドのインフラ部分について書いておきます。
スコアサーバ基盤
Flux CDロゴ引用元: https://fluxcd.io/
今回のスコアサーバは完全自作のLMTdと呼ばれるスコアサーバを投入しました。Go製のWebアプリケーションで、かなり尖ったライブラリや開発環境を採用しています。かなり尖ったせいで自分以外の人に触らせる余地を作れず、結果としてスコアサーバについて心配いただく結果となってしまったことは申し訳ないと思っています。このあたりは今後LMTd使う場合、リファクタへフルコミットして今後もスコアサーバとして安定したものにしていきたいと思っています。
スコアサーバのデプロイ先としてはAzure Kubernetes Seviceを利用しています。今回はAzureを中心としたサービスの利用を行っており、
- Azure Kubernetes Service
- スコアサーバ等の基盤
- Azure Databases for MariaDB
- データベースインスタンス
- Azure Container Registry
- コンテナレジストリ
- Azure DNS
- Azure Key Vault
- Azure Monitor
等を利用しました。AKSを利用することで、複数の環境をすぐにデプロイすることができましたし、external-secretsやcert-manager等はAzureに対応しているため、複雑なコストをかけずに様々な環境をすぐに使えたことが初めてAKSを触りましたが便利だと感じました。サーバのトラフィックを受けるIngressについては、Azure Application Gatewayについては利用を検討したものの、期間等を検討しNGINX Ingress Controllerになりました。
スコアサーバのデプロイ
今回はGitOpsを採用しています。
. ├── cluster │ ├── cert-manager │ ├── flux-system │ ├── ingress-nginx │ ├── kube-system │ ├── limebot │ ├── lmtd │ ├── monitoring │ └── proxytest ├── lmtd │ ├── base │ ├── dev-base │ ├── front-base │ └── overlays ├── secrets └── tools └── limebot
このようなリポジトリが用意され、Flux CDが定期的にReconcileをしてくれて適用される仕組みになっています。Kustomizeなどを書いているのでイメージを更新したらここにプッシュして更新、入れ替えとすることができました。すべての設定をGitで管理することで、不具合発生時のアップデートもしくはクローズがすぐにできるようになったことは非常に便利でした。
スコアサーバ実装
今回のスコアサーバの目玉となる部分については、
- Prometheus Exporterが統合されている
- 問題のGitOps(?)が可能
な部分です。この二つについて今回は説明します。
Prometheus Exporterの統合
LMTdは、このようなPromQLを用いて現状のスコア等を集計することが出来ます。
# チームスコア上位10チーム topk(10, (sum(lmtd_team_score{namespace="production", name!=""}) by (name))) # 問題毎のサブミッション sum(lmtd_challenge_submission_correct{namespace="production", name!=""}) by (name)
これをGrafana等に出すことでメトリクスとして表示することができます。その他にもGrafana LokiとPromtailをクラスタにデプロイしておくことで、リクエストログから様々なメトリクスを取得し監視することができました。
また、問題の生ログについてはFluent Bitを別でデプロイして保存しています。特に出番はなかったですが、何かあったときに使えるかなと思って残せるようにしておきました。
問題のGitOps(?)
これはLMTdを作るときから狙っていたものです。先に優秀なCTFチームであるところのWani Hackaseさんのところで実装されていましたが、今回はLMTdにもこれを導入して問題のCDを可能にしました。各種のYAMLを用意しておき、Gitリポジトリと同期する点では共通しています。
--- apiVersion: lmtd.lifememory.team/v1beta1 kind: Category labels: importer.lmtd.lifememory.team/unique-key: category-crypto spec: name: crypto order: 1 --- apiVersion: lmtd.lifememory.team/v1beta1 kind: Challenge labels: importer.lmtd.lifememory.team/unique-key: challenge-encrypter spec: name: encrypter order: 1 isPublic: true ownerCategoryUniqueKey: category-crypto description: | ### hoge fuga ### piyo maru [LMT](https://lifememory.team) --- apiVersion: lmtd.lifememory.team/v1beta1 kind: Flag labels: importer.lmtd.lifememory.team/unique-key: flag-encrypter spec: ownerChallengeUniqueKey: challenge-encrypter content: SECCON point: 869
また、YAMLに配布物を以下のように定義することでさくらのクラウドのオブジェクトストレージにアップロードし公開することも可能にしました。
apiVersion: lmtd.lifememory.team/v1beta1 kind: Challenge labels: importer.lmtd.lifememory.team/unique-key: challenge-werewolf spec: ... dists: - name: "app.py" # 配布したいファイル名をfiles以下に置いて書く uploadType: raw replaceKey: "<URL>" - name: "Dockerfile" # 配布したいファイル名をfiles以下に置いて書く uploadType: raw
この他に配布ファイル以下をtar.gzに固める等も実装しており、できるだけ作問者が意識せずに済むようにしました。(この辺は実は突貫になってしまいました)
このCDの方法については、GitHub ActionsなどのCIツールを使用せず、kubernetes/git-syncを用いて実装しました。LMTdのサイドカーとしてWebhookを受け取って指定されたディレクトリをインポートするだけのアプリケーションを書いて、あとはgit-syncコンテナが自動で継続的にPullを行ってWebhookを投げてくれることで、CIツールなどを用意せずに継続的デリバリーを可能にしました。
また、デプロイの冪等性を担保するために今回はKubernetesマニフェスト風のYAMLなものの、ユニークなキーをYAMLごとに付与させることでファイル名やディレクトリ構造にできるだけ依存しないようなデプロイが可能なようにしました。(ファイルのアップロードを突貫で作ったため、このあたりは微妙になってしまいましたが)
※デプロイ時に差分計算も行っています
Kubernetes ControllerでなくてもKubernetes マニフェストのような書き方をしてCDをするアイデアは、PipeCDからアイデアを得ました。また、git-syncの場合は完全なReconcileにはならないことが注意が必要です。LMTd v2になったらきっとCTF Controllerができるでしょう。
おしまい
スコアサーバについては、まだまだ語りたいことがあり、ここには余白が足りないので第二弾の記事も書こうと思っています。まず、今回はどのようなインフラで動いていたのか、忘れないうちに書いておこうと思い備忘録的に書きました。今回は、参加ありがとうございました!
スコアサーバのオープン時、不具合でユーザーの一部の方々にはご心配とご迷惑をおかけしたことを申し訳なく思っております。オープン時の不具合については再発防止を行い、安心してCTFをお楽しみいただけるよう改善していく予定です。