こんにちは。19Bの@mazreanです。普段はSysAd班でanke-toというアプリケーションの開発・運用を行なっています。
CPCTFでは@hijiki51、@reyuとインフラを担当していました。
この記事ではCPCTFでのインフラ環境について書いていきます。
構成
サーバーは全てConoHaのVPSを利用しており、
- メモリ1GB CPU2コア(Web問用サーバー)
- メモリ2GB CPU3コア(mainサーバー)
- メモリ4GB CPU4コア(Webshellサーバー)
- メモリ4GB CPU4コア(Judgeサーバー)
の4台構成でした。
リバースプロキシにはCaddyを利用していました。CaddyはACMEプロトコルに則ってSSL証明書を自動取得してくれるので、今回のように複数の証明書が必要な場面で、かなり楽ができます。
簡単に各アプリケーションの説明を書いていきます。
スコアサーバー
ユーザー管理やflagの提出、点数計算、ランキング表示などを行う、CPCTFの中核となるアプリケーションです。本イベントのために去年のものを書き換える形で作成されました。Vue.jsを利用したクライアントと、MySQLをデータベースとして使うGo言語で書かれたサーバーで構成されます。
5月中はhttps://cpctf.space で公開しています。
ビジュアライザー
スコアサーバーからWebsocketでイベントを受け取り、得点や順位の変動を可視化するアプリケーションです。UnityのWebGLビルドを静的配信していました。
現在は停止していますが、大会中は以下のように動いていました。
PPC Judge
今年3月に卒業した@nariさんが開発された、PPC問(競技プログラミング系の問題)用のアプリケーションです。このアプリケーションは提出されたプログラムの実行を行い、AC(Accepted)であればflagを表示するアプリケーションです。提出のキューの管理やクライアント向けのAPIを提供するcentralサーバーと、提出をcentralサーバーから取得し実際に実行して結果を返すjudgeサーバー、Reactを利用したクライアントで構成されています。分離した環境でのジャッジの実行にはyosupo judgeを用いました。
Webshell
ユーザーごとに別々の環境を持つWebshellを提供するアプリケーションです。SSH接続をしてブラウザで動くUIを提供するWeTTYに、@mazreanが開発したユーザーごとのDockerコンテナを作りSSH接続を各コンテナに振り分けるssh-separatorを組み合わせて実現しています。WeTTY自体には暗号化の仕組みはなく、HTTPのままインターネットを通ると中間者攻撃により実行したコマンドなどの情報が漏れてしまうので、HTTPS化が必須です。
各問題環境
CTFのWeb問などのサーバーが必要な問題の環境です。HTTPで通信する問題はCaddyを利用することでHTTPSでアクセスできるようにしました。SSHなどのHTTP以外の方法で通信する問題ではリバースプロキシは利用しませんでした。
監視基盤
Prometheus、Loki、Grafanaを組み合わせて実現した、いわゆるPLGスタックの監視基盤です。blackbox_exporter、ssl_exporter、domain_exporterなどを用いて死活監視やssl証明書の有効期限の監視をできるようにしているほか、WebshellやPPC Judge、スコアサーバーからのメトリクスの取得を行なっています。
構成図
IaC
CPCTFのサーバーは全てAnsibleにより管理していました。
Ansibleで自動化した背景
複数サーバーを利用した構成になっていることや、動作検証のために各サービスを複数回デプロイすることから、手作業でのデプロイ作業では対応が難しく、より簡単で安全な方法を取る必要がありました。
また、スコアサーバーの開発はセルフホストのGitサーバーであるGiteaを利用して行われていたわけですが、デフォルトではCIの機能が存在せず、CIを利用したDockerイメージのビルドなどは難しい状況でした。
これらの状況を踏まえたうえで、CIを利用できるようにする手間を考えると、手元でのDockerイメージのビルドをIaCにより自動化するほうが簡単だと考え、Ansibleを採用しました。
採用した結果
想定通りデプロイが複数回発生し、かなりの作業効率の向上につながりました。実はビジュアライザーは本番中にもバグの解消のために複数回デプロイを行っていました。しかしデプロイをansibleコマンドを叩くのみで行うことが出来たため、一回のデプロイにかかる時間を数分程度に抑えることができました。さらに、デプロイ起因のダウンタイムはAnsibleによりファイルの移動が行われる1sかからない程度の時間に抑えることができました。
また、競技終了後にサーバー規模の縮小作業を行ったのですが、この作業もAnsibleのyamlをわずかに修正しansibleコマンドを叩くのみで行うことができ、かなり楽に行えました。
ローカルPCからのDockerイメージの転送
Dockerイメージをそのままサーバー上へ送ることは難しいため、docker save
でtarに固めてAnsibleのcopyモジュールでサーバー上へ送りdocker load
でサーバー上に読み込むことで行いました。以下は実際にWebshellのユーザーログイン用Dockerイメージの転送に用いたAnsibleのタスクです。
# wetty_ssh.image_versionにはDockerイメージのバージョンが入ります
- name: Save docker image central
delegate_to: localhost
docker_image:
name: cpctf-ubuntu
tag: "{{ wetty_ssh.image_version }}"
state: present
archive_path: /tmp/cpctf/cpctf-ubuntu.tar
source: local
- name: Copy docker images
become: yes
copy:
src: /tmp/cpctf/cpctf-ubuntu.tar
dest: /srv/webshell/cpctf-ubuntu.tar
- name: Load images
become: yes
docker_image:
source: load
load_path: /srv/webshell/cpctf-ubuntu.tar
name: cpctf-ubuntu
tag: "{{ wetty_ssh.image_version }}"
nssによるSSH権限管理
CPCTFで用いた全てのサーバーのユーザー管理はlibnss-jsonを用いて行っていました。
libnss-jsonはLinuxのName Space Switchという機能のJSONバインディングモジュールで、これを利用することでリモートから読み込んだJSONを用いてサーバーのユーザー管理を行えます。
traPのSysAd班ではこれとsshのAuthorizedKeysCommandを組み合わせてGitHubに登録された公開鍵を用いてサーバーへSSHができるようになっています。
今回のCPCTFでもこの仕組みを用いることで、ユーザー管理を行いました。ユーザー情報のJSONは、Gitea上で管理し部内PaaSのShowcaseを用いて静的配信しました。
準備期間中短期間のみサーバーへのログイン権限を与えたい場面が複数回発生したのですが、その処理がかなり楽になりました。
監視基盤
今年のCPCTFではPrometheus、Loki、Grafanaを用いて監視基盤を構築しました。
これまでのCPCTFでは監視基盤が存在しなかったため、何らかのトラブルが発生していても、報告を受けるまで気づけませんでした。また、原因の調査も直接サーバーに入って行う必要がありました。
今回のCPCTFではnode_exporterを用いての次のような情報を取得し、トラブル発生により早く気づくことのできる環境を作りました。
- ホストのCPU使用率
- ホストのメモリ使用率
- Dockerのloki driverを用いたログの取得
- トラブル発生可能性の高い箇所のメトリクスの取得
トラブル発生可能性が高いと判断し、カスタムエクスポーターによりメトリクスを取得したのは
- Webshellの起動中・停止中コンテナ数
- スコアサーバーの登録ユーザー数
- スコアサーバーのメソッド別ステータスコード数
- PPC Judgeの提出のジャッジ中・ジャッジ待ち・ジャッジ済みなどの状態
です。
このほかにWebshell、スコアサーバーではgoroutine数、メモリ使用量なども取得していました。取得にはPrometheus公式のGoクライアントライブラリを用いました。
これらの監視により、大会中のトラブルにいち早く気づくことができました。実際にCTFのWeb問のアプリケーションが暴走し、CPU使用率が100%に到達する事態が発生したのですが、数分で気づき対処することができ、影響が出るのを10分に満たない期間に抑えることができました。
また、昨年度まではリクエスト数やWebsocketのコネクション数などのデータが残っていなかったため、今年のスコアサーバーの開発ではどの程度の負荷に耐えることができれば良いのかの予測が難しく、目算で負荷対策を行っていました。しかし、今年はPrometheusによりデータを取りそのデータのバックアップを残しているため、来年のスコアサーバーなどの負荷対策は今年より行いやすくなると思われます。
他にも、大会終了から数時間で下記の記事で統計情報の公開を行えたのも、Grafana、Prometheusによりデータの取得、可視化を行っていたためです。
余談
Prometheusのカスタムメトリクスではトラブル発生要因の排除以外にも運営人が楽しむためのメトリクスもとっていました。具体的には、各問題の提出数や正答率で、登録ユーザー数も実は半分ぐらい参加者が増える様子を見たかったためにとっていました。
大会開始直後に一気に参加者数が増える様子や、OSINT問の「Student ID Card」の提出数が増加していく様子を見て、運営も大会中盛り上がっていました。個人的に監視基盤を整備してよかったと思った瞬間でした。
まとめ
今年のCPCTFでは全インフラのAnsible化、nssによる権限管理、監視基盤の整備などで、昨年度までと比べて大きく安定性を向上させることができました。参加者アンケートでインフラの安定性を褒めてくださる声もあり、実際に安定性を向上させることができたと考えています。
各アプリケーションのデプロイのフローなど、まだまだ改善の余地はあると考えているので、来年度以降もより良いインフラ基盤を作っていきたいと考えています。
5/19(水)にスコアサーバーについてのブログ記事も予定されています。また、日付は決まっていませんがWebshellについてのブログ記事も出る予定なのでお楽しみに!
追記:ブログ公開されました!!!
スコアサーバー:https://trap.jp/post/1308/
Webshell: https://trap.jp/post/1343/