SECCON Beginners CTF 2020のインフラ構築自動化を支える技術

LifeMemoryTeamのak1ra24です。今回のSECCON Beginners CTF 2020はお楽しみいただけましたでしょうか。

先日、@atpons先生によるブログが公開され、書いてよと言われたのでたまには書こうと思います。

atpons.hateblo.jp

今回は、自分が担当していたサーバーやディスク、パケットフィルタ等さくらのクラウド側での操作をinfrastructure as codeによる構築自動化について書いていきたいと思います。

以下のGitHub Actionsでさくらのクラウドプロバイダーを利用したTerraformについては、以下の記事のようにやるとより効率よくバージョンを変えたりできるので、ぜひ読んでください。 id:febc_yamamoto さん、ありがとうございます!!

febc-yamamoto.hatenablog.jp

TerraformとCI

SECCON Beiginnersではインフラスポンサーであるさくらインターネット株式会社様の「さくらのクラウド」を使用させてもらっております。

インフラの構築を自動化するために、GitHubを利用し、PRからmasterにマージされるとインフラの構築がTerraformとansibleによって実行される、CI/CDを試してきました。

Drone.io

はじめは、OSSのDrone.ioを使用して行いました。 terraform plan の結果に関しては、tfnotifyを使用し、planの結果などをインフラチームで確認してから、masterにマージを行ってきました。 いろいろなチャットツールやCIに対応していて、大変便利でした。

github.com

これは、Terraformを使用するのみなら使い心地はよかったのですが、SECCON Beginnersでは、問題サーバーが稼働しているか、ソルバーはちゃんと動き解けるようになっているのかなども同様にTerraformで使うものと同じCIツールを使ってきました。

しかし、Drone.ioでは@atpons先生のブログの通り

また、Drone.ioはCIツールですので、VCSリポジトリと強く紐付いています。そのため、問題リポジトリとソルバーの共存が難しかったりしたので、もっと汎用的なCI/CDツール(ConcourseやJenkins)を使おうかと思っていました

Concourse CI

上記のため、次はConcourse CIをすることに決まりました。 Concourse CIを使っていましたが、微妙に使いづらい点があったのですが、忘れてしまったのでたぶん自分たちの使い方が悪かったのだと思います。 Concourse CIでも同様にソルバーチェックを行う予定でしたが、いろいろありできませんでした。

このあたりのソルバーチェックの話は、atpons先生のブログで書かれています。

GitHub Actions

SECCON Beginners CTF 2020では、GitHub Actionsを使用するようになりました。

Terraform用のGitHub Actionsがありますが、terraform-provider-sakuracloudはCommunityプロバイダーのため、その場合terraform-github-actionsではそのプロバイダーを入れるオプションがないので自作しないといけません。

github.com

github.com

そのため、以下のterraform-provider-sakuracloud用のGitHub Actionsを作成しました。

github.com

基本は以下のブログ記事とterraform-github-actionsを読むといろいろカスタマイズできます。

qiita.com

terraform-sacloud-github-actionsは、ベースとなるDocker Imageを用意して、そこにENTRYPOINTでshell scriptを書いていろいろ設定するだけです。

しかしここで問題だったのが、terraform-provider-sakuracloudではversion2から書き方がversion1のときとは変わってしまった問題があり、Beginners CTF 2020ではversion2を使用し、 Beginners NOCが管理するインスタンスのTerraformではversion1で管理していたため、GitHub ActionsでCIを回すときに、terraform-provider-sakuracloudのバージョンを指定して実行したいというのがあったので、できるようにしてあります。

上記のブログのとおりのシェルスクリプトに、今回はさくらのクラウドのTerraformプロバイダーのバージョンを環境変数で渡すことで、バージョンを指定してTerraformのプロバイダーをダウンロードし、特定のプロバイダーバージョンを使用できるようにしました。

ENTRYPOINTで実行するシェルスクリプトに以下のように追加することで解決しました。

if [ -z "$TF_SACLOUD_PROVIDER_VERSION" ]; then
    TF_SACLOUD_PROVIDER_VERSION=$(curl -s https://api.github.com/repos/sacloud/terraform-provider-sakuracloud/releases | jq -r '.[0] | .tag_name' | sed -e 's/[^0-9\.]//g')
fi

echo "$TF_SACLOUD_PROVIDER_VERSION download..."
wget https://github.com/sacloud/terraform-provider-sakuracloud/releases/download/v${TF_SACLOUD_PROVIDER_VERSION}/terraform-provider-sakuracloud_${TF_SACLOUD_PROVIDER_VERSION}_linux-amd64.zip
unzip terraform-provider-sakuracloud_${TF_SACLOUD_PROVIDER_VERSION}_linux-amd64.zip
rm terraform-provider-sakuracloud_${TF_SACLOUD_PROVIDER_VERSION}_linux-amd64.zip
rm -rf /terraform-provider-sakuracloud
mv terraform-provider-sakuracloud* /terraform-provider-sakuracloud

実際に、 terraform-sacloud-github-actions を使った GitHub Actionsの設定で以下のように設定します。

masterにPRの設定

name: pr_master
on:
  pull_request:
    branches:
    - master
    type:
    - opened
    - synchronize
    - rerequested
env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  SAKURACLOUD_ACCESS_TOKEN: ${{ secrets.SAKURACLOUD_ACCESS_TOKEN }}
  SAKURACLOUD_ACCESS_TOKEN_SECRET: ${{ secrets.SAKURACLOUD_ACCESS_TOKEN_SECRET }}
  SAKURACLOUD_ZONE: ${{ secrets.SAKURACLOUD_ZONE }}
  TF_ACTION_WORKING_DIR: "terraform/production"
  TZ: Asia/Tokyo
  TF_SACLOUD_PROVIDER_VERSION: "2.3.1"
jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout'
        uses: actions/checkout@master
      - name: 'Terraform Format'
        uses: "lifememoryteam/terraform-sacloud-github-actions/fmt@master"
      - name: 'Terraform Init'
        uses: "lifememoryteam/terraform-sacloud-github-actions/init@master"
      - name: "Terraform Plan"
        uses: "lifememoryteam/terraform-sacloud-github-actions/plan@master"

PRが作られると、GitHub Actionsでterraform fmt, planが実行され、PRにコメントが以下のように来る

f:id:ak1ra24:20200526111053p:plain

masterにpush時に, terraform apply にとってインスタンス構築を行う場合の設定

name: push_master
on:
  push:
    branches:
    - master
env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  SAKURACLOUD_ACCESS_TOKEN: ${{ secrets.SAKURACLOUD_ACCESS_TOKEN }}
  SAKURACLOUD_ACCESS_TOKEN_SECRET: ${{ secrets.SAKURACLOUD_ACCESS_TOKEN_SECRET }}
  SAKURACLOUD_ZONE: ${{ secrets.SAKURACLOUD_ZONE }}
  TZ: Asia/Tokyo
  TF_SACLOUD_PROVIDER_VERSION: "2.3.1"
jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout'
        uses: actions/checkout@master
      - name: 'Terraform Format'
        uses: "lifememoryteam/terraform-sacloud-github-actions/fmt@master"
      - name: 'Terraform Init'
        uses: "lifememoryteam/terraform-sacloud-github-actions/init@master"
      - name: "Terraform Apply"
        uses: "lifememoryteam/terraform-sacloud-github-actions/apply@master"

試行錯誤してきましたが、これらによって問題作成者、インフラチームになるべく負荷がかからないようにインフラ構築の自動化を行ってきました。

試行錯誤したこと

tfstatediff

TerraformとCIを使った自動化できましたが、インフラチームがいちいち作られたインスタンスIPアドレスはこれだよって問題作成者にお知らせするのは、時間がかかりインフラチームの負担も増えるため面倒です。

そこで、問題作成者に必要な情報としては以下が必要と考えました。

それだけをお知らせしてくれるやつ作ればいいか~ということで、 tfstatediff を作りました。

github.com

tfstatediffは、terraform apply 前と後の二つのtfstateファイルを取得し、Drone.ioの環境変数を参照しDrone.ioで回した対象のPRを取得します。

そのPRに対して、二つのtfstateを比較して増えたサーバー情報からサーバー名とIPアドレスをPRにコメントするというのを行いました。

以下のような画像でPRにコメントされます。

f:id:ak1ra24:20200526114848p:plain

tfstatediffの問題点、それは閉じたPRは誰も見ないため、「どこにインスタンスの情報あるの?」というメッセージが大量発生しました。。。

tfstatediffからの改善

確かに閉じたPRは、見ない人が多いなってことになり、GitHub Wikiにすべてのサーバー情報を書き込むことで、問題作成者がそこを参照すればいいのかっていうのは無事解決しました。

alertmanager-webhook

LifeMemoryTeamでは、監視は基本Prometheus + alertmanager + Grafanaで可視化をやっています。

Alertも全部GitHubにまとめれば、見るところ少なくて済むのではというアイデアの元、alertmanager-webhookを作りました。

github.com

仕組みは、WebHookのサーバーがalermanagerからのアラートのjsonを受け取り、WebHookサーバーはGitHubAPIを叩いて、GitHub Issueにそのアラート情報とそこに書かれているラベルを書き込むという流れです。

alertmanager-webhookの問題点、それはGitHub Issue誰も見ない

そもそも開催時は、運営陣が集まり、インフラチームがGrafanaで監視していたため、GitHub Issueを見る前に「やばい」っていうのは問題作成者に伝えていたのもあって、これ必要ないやんってことになりました。

alertmanager-webhookからの改善

ここはatpons先生のブログにも書かれていましたが、今回はDiscordに問題のソルバーの状態についてはお知らせがBotによってお知らせしてくれていたので、改善をしてくれました。

おしまい

今回もまた試行錯誤しながら新しい試みもでき、そして概ね上手くいって良かったです。

特に監視周りは、atpons先生がいろいろやっておかげで、運営中も寝れました。ありがとうございました。

今回さらっとですが、インフラの足回り的なことをかけたので、スコアサーバーについても他のメンバーが頑張ってくれたので書いてくれるはず!!(たぶん)

インフラチームの皆さんには知識・技術力不足の自分にいろいろ教えていただき、協力してもらっているのでいつも感謝しております。

また問題作成者の方々にも、毎回インフラ作成の方法が変わってしまい申し訳ないのですが、すぐに対応してもらくれて、いつも感謝しております。

本当は上記以外にも、 tfgenerator とか他にも作ったりしたのですが、これは書いても面白くないので書くのはやめます。

最初はSECCON Beginners CTF 2020のことだけ書こうと思ったら、経緯も書かないとわからないかなって思い、気づいたらSECCONでやったインフラの振り返り記事になりました。

よりきれいにまとまったSECCONインフラの全体的な記事は、以下の記事を読むと全体的になんとなくわかるかなと思います。(会員登録しないと見れないのは申し訳ないです。。)

www.atmarkit.co.jp

拙い記事でしたが、ここまでお読みいただきありがとうございました。

補足