OPA/Regoを活用して継続的監査を実現して、楽をしよう

あけましておめでとうございます。Open Policy Agent(以下OPA)/Rego x 監査で継続的監査をあたりまえにしていきたいと思っているMoney Forward Kessai(以下MFK)のshinofaraです。 Regoとは、OPAのポリシーを記述する言語です。

本日はOPA / Rego Advent Calendar 2021の影響を受けて、MFKでOPA/Regoを活用して実現している監査に関することの1つを紹介できればと思いブログを書き始めました。

そもそもOPAって何?に関しては、以下のZennに詳しく書かれておりますので、こちらのブログでは割愛させていただきます。 OPA/Rego入門: OPA/Regoとはなんなのか

MFKではOPAで何をチェックしているか

昨年「2021年に入ってやめた3つの開発に関わる仕組み」を書かせていただきました。今回はその中で書いた「Githubリポジトリ作成のIaC管理、もしくはチケットでの作成依頼」に関連しています。簡単に書くとMFK Organization内のリポジトリで毎日ポリシー違反が無いかチェックしています。ここからはより具体的に紹介させていただきます。

MFKでは主に以下の3点を定期的にチェックしています。

  1. リポジトリの公開・非公開などの設定にポリシー違反が無いこと
  2. リポジトリのProtected Branchの設定にポリシー違反が無いこと
  3. リポジトリにアクセスできるRoleやMemberにポリシー違反が無いこと

そもそもなぜ継続的にポリシーチェックを行うのか

先のブログでも触れた内容とかぶることにはなりますが、以前のMFKではTerraformを利用して、Githubのあらゆる構成を管理していました。

全員が同じ権限を持っていた初期の頃とTerraform導入前後での課題やリスクは以下の通りです。

  • 初期(2,3人の頃)
    • 意図しないPublic化や、意図しないメンバーの招待により社外にコードが漏洩するリスク
    • Protected Branchの設定漏れによるMain(Master)への直接コミットが出来てしまう事で、意図しない(あるいは悪意による)破壊が行われてしまうリスク
      • これはMain(Master)がProductionにデプロイされると同義である場合はとても大事
  • Terraform導入前(CTOに作業依頼)
    • Admin権限所有者へ作業依頼後に作成されるまでコミット出来ない問題
    • Admin権限所有者は依頼を早めに実現しないとブロック要因となるため割り込み作業を行う事になる問題
  • Terraform導入後
    • Githubリポジトリの管理という観点において本質的に関係の無い、Terraformのバージョンアップやパフォーマンスに関して意識するコスト
    • レビュアーがTFファイルの構成にポリシー違反が無いか確認するなど、負担がかかる状態
    • レビュー待ち時間コミットできない問題

初期の頃から比べてTerraform導入後は、知らない間にポリシー違反のリポジトリが生まれるリスクはなくなりました。その分ほしいときに作れなくなりました。 テンプレート作るなどすれば楽にリポジトリを追加できますが、自由にリソースの追加・削除が出来る状態になる為、承認者がレビューでポリシー違反が無いかチェックする運用は生まれました。その為、依頼者と承認者(作業者)という最低二人必要な状態からは抜け出せてませんでした。 TerraformをOPAでチェックするための方法は存在しています。しかしやりたいことはTerraformを使う事や、権限所有者しかリポジトリを作成できない状態ではありません。誰でもポリシーに準拠したリポジトリをセルフサービス的に作れる事だったため、Terraform自体がない世界を改めて考え直す事となりました。

近年Githubに関連したセキュリティインシデント(主に設定)が発生しています。そのため今後もGithub Organizationのガバナンスを効かせる事はとても大切だと考えています。ガバナンスを強化しながらも自由度を高めていく事は、組織としての生産を高めるためにはとても重要です。そのためOPAなどの仕組みを活用する事で、ガバナンスと自由を高めていければと考えてます。 ただしOPAはあくまでチェックするための仕組みですので、合わせてフローや仕組みの整備などは必要となります。

更に年度末の監査対応の手間問題もあった

ここまではリポジトリの設定や権限に関して、セキュリティや運用観点でリスクやコストを書いてきました。しかし会社のフェーズが進む事で監査というイベントが発生します。MFKの監査では、以下のようなトイルも存在していました。詳細は省きますが概ね以下のとおりです。

  • 該当するリポジトリがPrivateである事をスクショなどで証票提出
    • これだけであれば、参照権限を担当者に付与して任せる事もできます
  • 該当するリポジトリにアクセス可能なメンバーと権限をスクショなどで証票提出
  • 該当するリポジトリのMasterやMainに直接コミットできる状態ではない事を証明する証票を提出(MFKではProtected Branchの設定で担保)

一部をピックアップしましたが、監査では第三者が「正しい状態」であることをチェックする事になるため、証票を提出する事が増えてきます。 ここではリポジトリを例にしていますが、これを毎期末に行う事は、いわゆるトイルです。

更にスポットで実施する監査自体も課題が存在していると考えています。毎日第三者が評価する状態ではないので、証票作成時に「正しい状態」へ戻す事も出来ます。GithubであればAuditLogを取得できますので、ログを監査する事でそういった問題行為がなかった事を保証することも出来ます。しかしそういったLogが取れないサービスもあると思います。監査は「自社で定める正しさに則って運用できている事を確認する」ということが本質だと考えると、スポットではなく短いサイクルでチェックし続ける事が一番いいのかもしれません。現実それは人力では難しいのが現実だと思います。

そのため継続的に監査を行うための仕組みを整える事で、これまでのトイルを0に近づける事はもちろんとして、継続してポリシーチェックできる状態を作っていく事にしました。

実際どのようなポリシーを管理しているか

ここからは実際に運用しているポリシーをブログ用にアレンジして書き進めていきます。

まずは、リポジトリに関してのアクセスポシリーを定義しています。

Rego 自体に見覚えない方にとっては、少し分かりづらいと思いますので、コメントを交えながら書いていきます。

OPA/Rego入門: 宣言的ポリシー記述言語 Rego

こちらを一度見ていただけると、イメージがしやすくなると思います。

リポジトリへのアクセス可能なRoleとRoleに許可しているPermission

package repository.access.authz

default allow = false

# 最終的にallowの結果がtrueかfalseかでポリシー違反かどうかを確認しています。
# この例ではallowというruleの中に、countの結果が0かどうかをチェックしている式が一つだけ存在しています。
# つまりcountが0の場合はポリシー準拠で、0以外の場合は違反という結果になります。
# count(not_valid_permission)の読み方は、not_valid_permissionの結果をcountすることになります。
allow {
	count(not_valid_permission) == 0
}

# allow_permissionsという変数に、role名と許可するpermissionを定義しています。
# admin permissionはadmin roleでしか許可していません。
# developerはpushとpull、designerはpullのみ許可
allow_permissions = {
	"admin": ["admin"],
	"developer": ["push", "pull"],
	"designer": ["pull"],
}

# valid_permissionではallow_permissionsという変数で定義した組み合わせにマッチしていればtrueを返します。
valid_permission[d.id] {
    d := input.data[i]
    # developer: ["push", "pull"]の時、input.data.permissionの値がpushかpullのどちらかとマッチするかチェック
    d.permission == allow_permissions[d.slug][_]
}

# not_valid_permissionではallow_permissionsで定義した条件にマッチしなかったroleとpermissionの組み合わせリストを返します。
# 結果のイメージとしては、`['push is not allowed for admin', 'hogehoge.....]`
not_valid_permission[msg] {
    # input.dataには `{'admin': 'admin', 'developer': 'push', 'designer': 'push' }`という構造データが含まれてると仮定して進めます。
    # d := input.data[_]と、`_` を書く事で、dには `['admin': 'admin']`, `['developer': 'push']` のように構造内の要素一つひとつが代入されるイメージとなります。
    d := input.data[_]

    # not valid_permission[d.id]では、valid_permissionにinput.data内の連番のIDを渡してるイメージです。
    # そしてvalid_permissionがfalseだった場合、notでtrueと変換して、後続処理に進めています。(notでfalseとなった場合はチェックを終了して次の行へ)
    not valid_permission[d.id]

    # msgにエラーメッセージを代入して戻す
    msg := sprintf("%s is not allowed for %s", [d.permission, d.slug])
}

とまぁ色々書きましたが、動くものを見なければわからない事も多いので、playgroundのコードを張っておきますね。

https://play.openpolicyagent.org/p/QIDOeTy6t6

RoleとPermission編で長くなってしまったので、ここからはplaygroundを貼っていきますね。

リポジトリのProtected Branchの設定

  • Protected Branchが有効であること
  • Apploveが1つ以上必須であること
  • PR中に別のコミットがBaseに積まれた場合は追従してること

https://play.openpolicyagent.org/p/6gTItB6hb2

リポジトリの公開非公開をはじめ様々な基本設定

  • OSSリポジトリ以外の場合はPrivateであること
  • Wikiなどは利用しないはずなので使えない状態であること
  • Forkしたリポジトリの設定はチェック対象外

https://play.openpolicyagent.org/p/oR9wsevsMk

ポリシーのチェックをどうやって実現しているか

ここまではポリシーチェックする事に至った背景と、ポリシー内容を記述してきましたが、これだけではドキュメントとしてポリシーを定義しているだけとなってしまいます。ここからはそのポリシーをどうやってチェックし続けているか仕組み部分もお話します。

用意するものは2つです。

  1. github.com/open-policy-agent/opa/rego を利用したGoのコード
  2. 1を毎日やリポジトリ作成都度実行するためのGithub Actions

Goのコードでは以下の順で処理を実行しています。

  1. Github Organization内のリポジトリ一覧を取得
    1. github.com/google/go-github/v33/github を利用しています
  2. リポジトリ毎にポリシーチェックさせたい情報を取得して、open-policy-agent/opa/rego を利用してチェック
  3. 1つでも違反が存在すればSlackへ通知。何もなければ正常終了

Goのコードも紹介したい気持ちではあったのですが、ブログの長さも長くなりそうですので、割愛させていただきます。またGoでopaをまず動かしてみるという点に関しては、以下のブログを参照していただけるととてもわかり易いかと思います。

OPA/Rego入門: Go言語によるRego runtimeの組み込み

監査観点での影響

先の方に書かせていただいたとおり、証票提出というトイルはなくなりました。 監査担当者にはポリシー設定を説明して理解していただき、そして継続的にGreenであることがそれを保証している状態という合意も取れているためリポジトリに関しての監査はほぼ0になった状態とも言えます。 仕組み自体の監査は生まれたので、厳密的には0ではないですがPolicy管理しているリポジトリへのアクセス権限を担当者の方に付与するため、MFKとしては特別何かをする事は無いとも言えます。

そろそろ振り返りと、現状について

OPAを利用する事で、チェックするコードからポリシー定義を切り離す事ができた為、Regoがそのままポリシードキュメントとなるという状態が作り出せました。(学習コストは発生しますが)

とはいえOPAではinputとなるjsonデータをチェックしているだけになりますので、jsonの構造が変わってしまっても比較的対応は簡単になると思います。仮にGoなどのコードですべて実現していたとしたら構造体などの変更や、バリデーションルールの修正、そしてテストコード修正など変更箇所が多くなると思います。(その方が楽な場合ももちろん多くあります)

今回の例ではGithubのリポジトリに関してのみ紹介させていただきました。今後社内の様々なポリシーが集約して管理されている状態が整うことで、ドキュメントツールやコードにポリシーが散らばり迷子になる事を回避できるのではと考えています。

しかしまだ全員Githubをポチポチできる権限を所有している状態までは実現できておりません。現時点では以下の記述で始まるGithub Actionsがリポジトリ作成用のGoコードを実行している状態となっています。

name: Repository Creator

on:
  workflow_dispatch:
    inputs:
      name:
        description: 'name'
        required: true
      description:
        description: 'description'
        required: true

理想の未来としては、こういったコードがなくても簡単にリポジトリ作成ができて、ポリシー違反を見つけると勝手に修復に向かうコードを書くことなのかもしれないと思っています。

現時点ではPublicリポジトリを作れるメンバーを限定していますが、Privateなリポジトリであれば誰でも作れるようになりました。依頼やPR作成後待つしかなかった過去と比べるとだいぶ体験は変わったと思います。

今回はOPA/Regoをテーマとしていた事もあって、このあたりの体験や仕組みに関してはあまりかけませんでしたが、より良くなった未来では改めてお話できればいいなと思っています。

長文でしたが、最後まで読んでいただきありがとうございました。2022年もどうぞよろしくお願いいたします。