はじめに
こんにちは、TIG DXユニット真野です。
CNCF連載2回目はOpen Policy Agent がテーマです。前回は伊藤さんによる、k3sを知る、動かす、感じるでした。
Open Policy Agentとは
Open Policy Agent(OPA)は汎用的なポリシーエンジンで、Rego と呼ばれるポリシー言語で定義されたルールに従って、入力がポリシーに沿っているか否かの判定を移譲させることができます。Regoで宣言的にポリシーを実装し、Policy as Code を実現できます。
OPAは汎用的というだけあって、Kubernetes上でしか動かせないと言った制約は無いです。Go言語で書かれていることもあって、普通の外部パッケージと同様に関数呼び出しができます。また、公式ドキュメントにも適用ドメインを選ばないと書かかれており、いくつかの活用例も挙げられています。
- どのユーザーがどのリソースにアクセスできるか
- どのサブネットの出力トラフィックが許可されているか
- コンテナが実行できるOS機能
- システムにアクセスできる時間帯
ポリシーエンジンと聞くと、なんとなくOK/NGだけ返すだけなのねと思いがちですが、OPAのAPIはクエリに対してレスポンスを返すような設計になっていて、JSONのような構造データを入出力することもできます。
2020/09/27時点で CNCF projectsの Incubating
、バージョンは v0.23.2
が最新でした。
Policy as Code
Policy as Codeの先駆けは自分が知る限り HashiCorp の Sentinelだと認識しています。Terraformは Infrastructure as Codeを実現しますが、Sentinelのような Policy as Codeなツールと組み合わせ、インフラ構成全体のアクセスポリシーを設定することで、より安全にインフラ作成を自動化したり、不用意な破壊を防ぐことできるとされています。古いイメージを使わないといったセキュリティの観点や、あまり高すぎるインスタンスを立ち上げすぎないと言ったクラウド破産を防ぐといった使い方もよく聞きますよね。
Sentinelは非常に気になっていて、最近バイナリがダウンロードできるぞ! と、伊藤さんに教えてもらいましたが、利用ライセンスがよく分からないため触れずでした(ご存知の方は教えて下さい)
Sentinelと同様にOpen Policy AgentはPolicy as Codeを掲げています。個人的にはチーム開発において大小様々なポリシーが明示的にも暗黙的にも存在するため、これをポリシーコード化することで、良い成果を生み出せるのではと期待しています。
今回は後で記載している通り、コーディング規約も一種のポリシーとみなして、Open API Spec(Swagger)をLinter的にチェックするツールを題材に、OPAを用いて開発してみたいと思います。
Rego概要
RegoはDatalogというクエリ言語にインスパイアされて開発された言語です。Datalogは聞き慣れないですが、Prologの流れを組む言語です。RegoはDatalogを拡張してJSONのような構造化モデルに対応させたようです。
基本的な文法はこちらにまとめられています。
Rego Playgroundというサイトがあり、簡単に動作検証できます。何はともあれ色々触ってみるのが良いと思います。
見たまんまですが、画像の左側がRegoエディタ、右枠のINPUTが入力、DATAがRegoで参照する外部データ、OUTPUTがEvaluateボタンを押した後の実行結果です。
ご覧のように入力も出力も構造化データ(JSON)なのがよく分かります。
公式ドキュメントでは以下3つの例が載っていました。
- https://play.openpolicyagent.org/p/ikesWCFIH8
- https://play.openpolicyagent.org/p/DqXNKeLm20
- https://play.openpolicyagent.org/p/qUkvgJRpIU
PlaygroudのExamplesをクリックすると、他にも色々な例が載っています。
Regoの文法さわり
Prologをやってれば当たり前かもしれませんが、JavaやGoやJSくらいしか書いたことが無い私から見て、特徴的だなと思ったRegoの文法のつかみを紹介します。かなり異次元だなと思いました。
まずは 変数 pi に 3.14159を代入したコードです。:=
ですでに変数宣言済みかどうかチェックしてくれます。 {"pi":3.14159}
というJSONが実行結果です。まぁそういうものかと納得できます。
package test |
次は式が入りました。 x > y が最初にきて、 x,yの代入がその後になっていて実行時エラーになりそうですが、問題なく判定できます。公式ドキュメントに The order of expressions in a rule does not affect the document’s content.
と書かれている通り、書いた順番は影響ないようです。なるほど。
package test |
次は sites
というネストしたデータを使ってルールr1
, r2
, r3
, r4
, r5
を作りました。site[_]でループを回すような処理になり、r1
は prod
が存在するので true
です。r2
は false
となってほしいところですが、出力されません。一度も true と評価されなかったのでドキュメントが生成されないようです。r3
のようにルールを作って、r4
から利用すると言った事もできます。r4
は true
ですが r5
は一度も true
にならなかったので出力されません。
package test |
次は予約語のdefaultを利用して、allowの初期値をfalseにします。
allowの宣言が2箇所にありますが、ブロック同士はOR条件になります。allowのBody内はAND条件になります。
package test |
上記のルールに、以下の入力1.jsonで評価すると、{"allow": true}
になります。1つ目のallowが true
になるためです。
{ |
予約後は他にも some
、with
、else
があります。使いこなせばSQLの自己結合みたいな表現もできるようですが、慣れないうちは道のりがとてつもなく長く感じます。パズルみたいで楽しいと思えた人は才能だなと思います。
GoからOPAを呼ぶ
OPAはgithub.com/open-policy-agent/opa/rego
パッケージを利用することで、Goから組み込みライブラリ形式で呼び出せます。
ドキュメントそのままですが、転載します。いわゆるルールは module 変数に代入しています。rego.New
で rego.Rego
を作成してから、PrepareForEval
で PreparedEvalQuery
を作成すると、Eval
で評価できます。OPAからするとRegoはモジュールと呼ばれているので、ここの表現は慣れかなと思います。
Regoモジュールの内容は、HTTP Requestが指定のパスか、Adminだったら評価するというものです。リクエストが1つ目の条件を満たしているので、評価結果は x:true
を取得できています(最後のコメント部分)
全文はこちらに載せています。
package main |
この構成を利用すれば、他の領域にも展開できそうです。
Open API Spec(Swagger)にポリシーを適用してみる
Open API Specを用いてチームで開発する際、API定義の設定方法で揺れることは無いでしょうか? 以下のようなブログ記事が出るくらい、フューチャーでは設計の揺れを無くす努力をしています。
一方でこの手の規約は生み出してしまえば、チェックするのはLinterにやらせたいものです。上記の設計規約の一部をOPAで実装してみたいと思います。Open API SpecはYAML or JSONで記載するので入力としてはOPAにフィットすると思います。
とりあえずルールは上から2つにしぼり、tagsとoperationIdについてのルールを書きます。
- paths/tags
- 1URIで1つのタグのみ定義する
- paths/operationId
- {HTTPメソッド}{機能物理名}を記載する
- キャメルケース
Rego設計
tagsの数=1を実現するためにはビルトイン関数である count
を利用します。
package test |
operationIdのcamelCaseのチェック方法は、あまり良い手じゃないですが、snake_caseでないことと、最初の1文字が小文字であることだけチェックします(単語の区切りがムズカシイので)。他にも、split
、object.get
など多数の組み込み関数を利用しています。
package test |
これらを1つのファイルとしてまとめて、policy.rego
に保存しておきます。
入力とする Open API Spec
OAIのexamplesを参考に入力となる違反した定義を作成します。
https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/api-with-examples.yaml
openapi: "3.0.0" |
これを input.yml
に保存しておきます。
これを先ほどのRegoモジュールを利用したOPA評価をGoから行います。
Go実装
先ほど定義したregoとYAMLは外部ファイルから読み込めるようにしておく。今回は雑にハードコードしています。
ほとんど公式ドキュメントに合ったコードと同じで動かせました。
package main |
全文はこちらにコミットしておきました。参考までに。
https://github.com/laqiiz/openpolicyagent-example
実行結果
さきほどのGoのプログラムを動かすと以下のJSONが出力されます!
メッセージは各ルールごとに、コメントが出せています。行番号は出力できていませんが、どのパスでどの関数なのかは指定できるようにしています。
出力結果を見ると、tagsはtagsでまとめて表示するなど、Regoのルールを束ねるなど工夫をすると、もっと扱いやすい結果が作れそうです。
{ |
その他
利用したのと同じRegoと入力を、PlaygroundでもPublishしておきました。お手軽に触ってみたい人はどうぞ。
https://play.openpolicyagent.org/p/1ZhZasqT22
まとめ
- Open Policy Agent(OPA)は汎用的なポリシーエンジンで、Policy as Codeの実現を手伝ってくれる
- OPAが利用するRego言語の文法は特徴的(だと大半の人は思うと思う)
- OPAはGoから組み込みライブラリとして呼び出せるので、これを活用したLinterを開発可能
長い記事を最後まで読んでいただき、ありがとうございました!