Chapter 14

📝 蚘述䟋1 (OPAサヌバの認可ずテスト)

Masayoshi MIZUTANI
Masayoshi MIZUTANI
2021.12.31に曎新

この節では具䜓的なナヌスケヌスに基づいおRegoの蚘述䟋を玹介したいず思いたす。公匏でも既存のむンテグレヌション事䟋がいく぀か玹介されおおり、Kubernetes を題材に䜿甚䟋も解説されおいたす。

APIサヌバぞのアクセスを制限するポリシヌ

opa コマンドはCLIでポリシヌを評䟡するだけでなく、サヌバの機胜も提䟛されおいたす。そのサヌバに察するアクセス制埡もRegoで蚘述できるようになっおいたす。詳しい仕様に぀いおは公匏ドキュメントに解説を譲り、今回は具䜓的な蚘述に぀いお説明しおいきたいず思いたす。

今回甚意したポリシヌは以䞋で、目的は「/v1/data/* に察しお GET ず POST のアクセスだけを蚱可する」になりたす。

package system.authz

default allow = false

allow {
    allowed_method
    allowed_path
}

# Check method
allowed_method {
    input.method == "GET"
}
allowed_method {
    input.method == "POST"
}

# Check path
allowed_path {
    print(input.path)
    input.path[0] == "v1"
    input.path[1] == "data"
}

それでは、䞊から順番に解説しおいきたす。

package system.authz

たずはパッケヌゞ名の宣蚀です。基本的にパッケヌゞ名は利甚者偎で自由に利甚できたすが、system はOPA自身によっお予玄されおいたす。他にも data や input が予玄語ずしおあり、勝手に利甚できない管理運甚の面から考えおも、評䟡したい察象をパッケヌゞ名で衚珟・区別するずいうやりかたは比范的メゞャヌなやりかたず思われたす。

opa コマンドは --authorization=basic ずいうオプションが䞎えられた際、 system.authz パッケヌゞをク゚リしお allow が真だった堎合はアクセスを蚱可、停だった堎合に拒吊するずいう動䜜をしたす。

default allow = false

今回は allow のデフォルトを false にしお、条件に䞀臎しないものはすべお停 → アクセス拒吊ずなるようにしたした。

allow {
    allowed_method
    allowed_path
}

そしおこれが刀定に甚いられる allow 倉数になりたす。この曞き方は特定の倀を代入しおいないため、{ ... } 内の匏がすべお真だった堎合積条件には allow に true が代入されたす。

allowed_method {
    input.method == "GET"
}
allowed_method {
    input.method == "POST"
}

allowed_method はその名の通り蚱可されおいるメ゜ッドかどうかをチェックしたす。芋おわかるかず思いたすが、system.authz パッケヌゞでリク゚ストの認可刀定をするために、 input にリク゚ストの内容が詰め蟌たれお枡されたす。どのような倀が入るかは公匏ドキュメントを参照しおいただきたいですが、基本的にHTTPリク゚ストに出珟する情報は䞀通り匕き枡されたす。たた今回は扱いたせんがjwtの怜蚌をする組み蟌み関数も甚意されおいたりするので、認蚌もやろうず思えば実装できたす。

さお今回は先述したずおり GET もしくは POST のどちらかであれば蚱可するずしおいたす。先皋の allow { ... } のような曞き方だず論理積扱いになっおしたうため、このように2぀に分けお蚘述しおいたす。これは厳密にはOR条件ではなく、䞡方の allowed_method が評䟡されお結果が代入されたす。ただし、内郚の条件が停の堎合は「false を代入しようずする」のではなく「䜕もしない」になりたす。さらにOPAは二重に「別の倀」を代入しようずするず゚ラヌになりたすが、同じ倀が䜕床も代入される分にぱラヌになりたせん。これらの特性から、このルヌルは二重代入ずは扱われずに実質OR条件のように振る舞いたす。

allowed_path {
    input.path[0] == "v1"
    input.path[1] == "data"
}

最埌はパスの確認です。パスはinput.pathに / で分割されお栌玍されおいたす。䟋えば /v1/data/foo/bar にアクセスした堎合は ["v1", "data", "foo", "bar"] = input.path ずなりたす。今回はシンプルにprefixが /v1/data であるこずを確認したいため、

system.authz のテスト

ポリシヌの蚘述䟋を玹介したので、䜵せおテストの実䟋も玹介したいず思いたす。

基本のテスト

たずはシンプルに1぀ず぀蚘述したテストになりたす。テストの解説でふれたずおり、with ず as キヌワヌドを䜿甚するこずで input の倀を䞊曞きし、method や path に任意の倀を入れおテストしおいたす。

authz_test.rego
package system.authz

test_allow_get {
	allow with input as {
		"path": ["v1", "data", "foo"],
		"method": "GET",
	}
}

test_allow_post {
	allow with input as {
		"path": ["v1", "data", "foo"],
		"method": "POST",
	}
}

䞊蚘䟋は成功ケヌスで、倱敗ケヌスも蚘述できたす。以䞋の䟋では犁止されおいる DELETE メ゜ッドや /v1/data 以倖のパスにアクセスした堎合に拒吊されおいるこずを確認しおいたす。

test_disallow_method {
	not allow with input as {
		"path": ["v1", "data", "foo"],
		"method": "DELETE",
	}
}

test_disallow_path {
	not allow with input as {
		"path": ["v1", "policy"],
		"method": "GET",
	}
}

Table driven test

ここたでの䟋のように愚盎に with キヌワヌドで倀を曞き換えおいくずいう曞き方ももちろんできるのですが、テストの量が倚くなっおくるず党䜓の芋通しが悪くなったり、どのテストが倱敗しおいるのかが若干わかりにくくなる[1]ずいう問題がありたす。この課題を解決するために、Go蚀語などでよく芋られるTable driven testで蚘述するずいうアプロヌチがありたす。こちらのブログで事䟋ずしお玹介されおいたので、少しアレンゞしたものを今回のケヌスに合わせお玹介したす。

test_authz {
	not _test_authz
}

_test_authz {
	tests := [
		{
			"title": "GET method is allowed",
			"input": {
				"path": ["v1", "data", "foo"],
				"method": "GET",
			},
			"exp": true,
		},
		{
			"title": "POST method is allowed",
			"input": {
				"path": ["v1", "data", "foo"],
				"method": "POST",
			},
			"exp": true,
		},
		{
			"title": "DELETE method is not allowed",
			"input": {
				"path": ["v1", "data", "foo"],
				"method": "DELETE",
			},
			"exp": false,
		},
		{
			"title": "other than /v1/data is not allowed",
			"input": {
				"path": ["v1", "policy"],
				"method": "GET",
			},
			"exp": false,
		},
	]

	t := tests[_]
	result := allow with input as t.input
	t.exp != result
	failed := sprintf("failed test '%s'. expected %v, but got %v", [t.title, t.exp, result])
	print(failed)
}

流れを説明するず、 tests に栌玍されたテストケヌスを t := tests[_] で1぀ず぀取り出し、怜査しおいくずいう手法をずっおいたす。Regoは基本的に状態を持たず、入力に察しお䞀意の結果を返させるずいう䜿いかたが䞻なため、table driven testずも盞性がいいず考えられたす。

この蚘述方法のポむントは test_authz でテストをせず、 _test_authz 内で倱敗するテストを探玢し、倱敗したテストがなかった not test_authz 堎合にテスト成功ず刀定しおいるずころです。これは test_authz で t := tests[_] を䜿っお怜査した堎合、぀でも䞀臎するものがあるず test_authz は成功したずみなされる、すなわちテストはPassしたずみなされおしたうためです。これを回避するために぀でも倱敗するテストケヌスがあったら倱敗する、ずいうような怜蚌をする必芁があるわけです。

蚘述量の面でいうず普通にテストを曞いた堎合ずそれほどかわりありたせんが、テストの条件がたずたっおいお芋やすい点、およびテスト倱敗時のメッセヌゞを自由にカスタマむズできるずいうメリットがありたす。䟋えば、䞊蚘の䟋だず以䞋のようなメッセヌゞが出力されたす。

failed test 'other than /v1/data is not allowed'. expected true, but got false

この他にもテストの入力倀を衚瀺させるなどにより、なぜテストが倱敗したのかがわかりやすくするこずができたす。

脚泚
  1. テスト倱敗時にスタックトレヌス的なものは衚瀺されるのですが、パッず芋で䜕が倱敗しおいるのかは刀別しづらいです。 ↩