OPAとは何か

ミッチェル・ロバート氏(以下、ロバート):京都開発室のロバートと申します。よろしくお願いします。今日はOPAとRegoの紹介をしたいと思います。

まずは今日のアジェンダなんですが、OPAの簡単な説明と、特徴と長所から始めたいと思います。あとはRego言語の紹介と、一緒にポリシーを書いてみようと思っています。最後にポリシーの実行とまとめに移りたいと思います。

まずOPAとは何でしょうか? OPAは「Open Policy Agent」の省略で、サーバースタックに(対して)全体的にポリシー適用を標準化するために使用されるポリシー・エンジンです。

例えば、APIゲートウェイを含むCI/CDパイプラインなどのポリシーを定義・適用するために使用できます。RegoはOPAポリシーを記述する言語になります。

「ちょっと待って! ポリシーとは何でしょうか?」と多分みなさんが思っているところなので説明したいと思います。OPAにおいてポリシーは、基本的に入力(インプット)とクエリを評価して判断(decision)を生成する、一連のルールになります。decisionは簡単なbooleanだけではなくて、複雑なオブジェクトにもなれます。

それとインプットとクエリに加えて、ポリシーが他のデータを参考にできます。ポリシーはソフトウェアサービスの動作を制御するために使われています。ACLみたいなものですね。

(スライドを示して)これは簡単な流れの図です。まず、あるサービスにリクエストが入ってきます。そのサービスはインプットとクエリをOPAに送信して、OPAはそれを参考にしながら、ポリシーとデータを評価して判断を返すような流れになります。サービスは何でもいいですが、代表的なのはIngress Gatewayですね。

OPAの特徴と長所

ロバート:じゃあ、OPAの特徴と長所は何でしょうか。OPAは故意にポリシーとアプリケーションコードを分離しています。つまり、「どうやってコードを使えばいいか」を判断するセキュリティルールは、コード自体とは別に保存されています。

そうすることでアクセスポリシーを記述せずに、業務ロジックのみを記述できるため、コードがよりクリーンになります。

そして、ポリシーとコードが別々に管理されているため、アプリケーションを再デプロイしなくても、ポリシーの修正や変更を反映できます。これは英語でdecouplingと呼ばれていて、多くの場合はベストプラクティスとみなされます。

Rego言語の基本概念

ロバート:続いてRego言語ですね。まず基本概念ですが、Regoは本格的なプログラミング言語なので、定番の機能をサポートしています。例えば変数、関数、反復などです。それに加えて独特な概念もあります。一番重要なのはルールです。

注意したいことですが、RegoはOOP言語ではないことに注意してください。OOPに馴染んでいる方は最初は少し奇妙に感じるかもしれませんが、すぐに慣れます。

ルールとはいったい何でしょう。ルールとは関数と同じで、再利用のためにロジックを抽出できます。入力パラメータに対応し、出力も生成できます。

ルールとファンクションの基本的な違いは何かですが、ルールは自動的な反復に対応するのと、有限数の入力のみに対して定義されています。これはけっこうポイントですね。そして、できるだけルールを使用することがベストプラクティスになっています。

(スライドを示して)これはルールの構成です。まずは2つの部分からできています。ヘッドとボディですね。コーディングの中にあるステートメントすべてをtrueと評価すると、ルール自体もtrueとなります。

1つのHeadに複数のBodyを定義することが可能です。その場合は論理的なORとして評価をされます。ということは、このルールの結果はどうなるでしょうか。trueになります。和食で少しの予算を超えても大丈夫ということですね。

ポリシーを書いてみよう

ロバート:一緒にポリシーを書いてみましょう。まずはこのポリシーの前提ですが、許可されていないAPIアクセスについて、Envoyなどのプロキシサーバーのログを監査する必要があるとします。確認しないといけないのは、すべてのリクエストはHTTPS経由で送信されたことと、APIのv2のみがアクセスされたことと、最後に内部APIがアクセスされなかったことです。

実はOPAにとってちょうど良いユースケースになります。単なるregular expressionよりも柔軟性があって、とても合うと思います。

(スライドを示して)これは入力のフォーマットになりますが、よくあるJSONオブジェクトですね。

じゃあ初めてのルールを定義しましょう。まずはすべてのリクエストがHTTPS経由で送信されたかどうかの確認から始めましょう。some...inというシンタックスでリクエストを反復できます。1つの値以上をマッチングしたい場合はsome...inを使えばいいんですが、すべての値をマッチングしたい場合はevery...inを使わなければなりません。

これはルールになりますが、まずはprotocol_violationsを新しく定義して、そのルールのバリューの中にリクエストを反復して、プロトコルがHTTPSじゃない場合は、返却するセットにリクエストオブジェクトを追加します。

2つ目の要件は、APIのv2のみがアクセスされたかを確認することです。これはAPIパスをチェックすることによって行われます。

まずはパスのプレフィックスを変数に入れておいて、そのあとはpath_violationsという新しいルールを定義します。そのルールのボディの中でリクエストを反復して、/v2/から始まらない場合は返却セットに入れておきます。このスライドのポイントはbooleanを否定したい場合は、notというキーワードを使わなければなりません。

続いて3つ目の要件ですが、内部APIがアクセスされたことを確認することですね。これはAPIパスをチェックすることによって行われます。ルールに新しいBodyを追加することで実現できます。

まずはdataオブジェクトからAPIパターンを取得して変数に入れます。path_violationsの新しいルールに新しいバリューを定義します。そのバリューの中にリクエストを反復して、APIパターンにマッチングしたら返却セットに入れておきます。ポイントは、このセットは自動的に1つ目のセットと結合されます。

続いてルール出力の結合について話します。気づいているかもしれませんが、protocol_violationsとpath_violationsは両方ともpartial rule、部分的なルールなのでセットを出力します。それゆえにセット処理が使えます。ということは、和集合処理で結合できます。

all_violationsを新しく定義するのですが、そのルールの中にprotocol_violationsのセットとpath_violationsのセットを和集合します。Vertical barはこの和集合オペレーターになります。

続いて、最終レスポンスの組み立てについて話したいと思います。今まで4つのルールを作成できましたね。protocol_violationsとpath_violationsの2つの部分と、その全部を結合するall_violationsです。すべてのリクエストが許可されるばずだったかどうかを確認するにはall_violationsをクエリして、count==0をチェックすることで(確認)できます。

こういう構造化オブジェクトを返却したいとします。やはり毎回確認するのは面倒くさいので。このオブジェクトの中に2つのプロパティがあります。まずはallowedですね。これはすべてのリクエストが許可された場合はtrueになります。violationsは違反の配列になります。

(スライドを示して)これはコードになりますが、まずはレスポンスオブジェクトを組み立てるファンクションを定義します。その下にall_allowedという新しいルールを定義して、そのレスポンスオブジェクトを返却します。

この際のポイントは、2つのバリューがあるんですね。可能性のあるブランチをすべて定義しないとundefinedというレスポンスが返ってくる可能性があるので、それに注意してください。

続いてポリシーを実行してみましょう。基本的にOPAは、CIツールでポリシーを実行できます。これはあとでデモを行う予定なので、その時にもう少し詳細を説明しようと思います。

OPAとRego言語はインフラに導入すると真価を発揮する

ロバート:最後にまとめです。OPAは汎用ポリシーエンジンです。ポリシーはRego言語で書かれていて、1つ以上のルールで構成されています。Callerは入力とクエリを基にOPAに送信します。OPAはそれを用いてポリシーを評価します。他のデータも参考可能です。

最後に伝えたいのは、今日の例は簡単で、ローカル実行しかカバーしていませんが、インフラに導入すると真価を発揮します。例えば、事後ではなくてリアルタイムでAPIリクエストを許可・拒否できます。あとはIstioサービスメッシュと相性がとても良くて、sidecarとしてデプロイすることも可能です。

ちょっと時間がオーバーしてしまったと思いますが、ご清聴ありがとうございました。

(会場拍手)