ãã®ç¯ã§ã¯å ·äœçãªãŠãŒã¹ã±ãŒã¹ã«åºã¥ããŠ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
ã«ä»»æã®å€ãå
¥ããŠãã¹ãããŠããŸãã
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
ãã®ä»ã«ããã¹ãã®å ¥åå€ã衚瀺ããããªã©ã«ããããªããã¹ãã倱æããã®ããããããããããããšãã§ããŸãã
-
ãã¹ã倱ææã«ã¹ã¿ãã¯ãã¬ãŒã¹çãªãã®ã¯è¡šç€ºãããã®ã§ããããããšèŠã§äœã倱æããŠããã®ãã¯å€å¥ãã¥ããã§ãã â©ïž