Share
Embed
Introduction to Open Policy Agent
1. Why do we need a Policy Engine?
2. Why do we choose Open Policy Agent?
3. Workflow with Open Policy Agent?
4. What is Policy Language (Rego)?
5. RBAC and IAM Role Design
6. Three ways to deploy an Open Policy Agent.
Bo-Yi Wu
October 12, 2021
Transcript
89 slides
-
About me • Software Engineer in Mediatek • Member of
Drone CI/CD Platform • Member of Gitea Platform • Member of Gin Golang Framework • Maintain Some GitHub Actions Plugins. -
Outline • Why we need Policy Engine? • Why we
choose Open Policy Agent? • Work fl ow with Open Policy Agent? • What is Policy Language (Rego)? • RBAC and IAM Role Design • Three ways to deploy Open Policy Agent. -
{ "group_roles": { "project_leader": ["kpi_editor_design", "viewer_limit_ds"], "software_leader": ["kpi_editor_design", "kpi_editor_system"] },
"role_permissions": { "kpi_editor_design": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"} ], "viewer_limit_ds": [ {"action": "view_all", "object": "design"}, {"action": "view_all", "object": "system"} ], "kpi_editor_design": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"} ], "kpi_editor_system": [ {"action": "view_all", "object": "system"}, {"action": "edit", "object": "system"} ] } } -
Open Policy Engine • Written in Golang • Easy to
write policy testing • Easy to integrate with Go Application • Embed in GO • RESTful API -
Client Send Request Ask Permission
Response Result -
Client Send Request Ask
Permission Response Result Response to Client -
Upload Data Upload Policy Rule
Send Query Input *OQVUDBOCF "/:+40/WBMVF -
Upload Data Upload Policy
Rule Send Query Input Get Query Result *OQVUDBOCF "/:+40/WBMVF 0VUQVUDBOCF "/:+40/WBMVF -
{ "group_roles": { "project_leader": ["kpi_editor_design", "viewer_limit_ds"], "software_leader": ["kpi_editor_design", "kpi_editor_system"] },
"role_permissions": { "kpi_editor_design": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"} ], "viewer_limit_ds": [ {"action": "view_all", "object": "design"}, {"action": "view_all", "object": "system"} ], "kpi_editor_design": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"} ], "kpi_editor_system": [ {"action": "view_all", "object": "system"}, {"action": "edit", "object": "system"} ] } } %BUB -
{ "group_roles": { "project_leader": ["kpi_editor_design", "viewer_limit_ds"], "software_leader": ["kpi_editor_design", "kpi_editor_system"] },
"role_permissions": { "kpi_editor_design": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"} ], "viewer_limit_ds": [ {"action": "view_all", "object": "design"}, {"action": "view_all", "object": "system"} ], "kpi_editor_design": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"} ], "kpi_editor_system": [ {"action": "view_all", "object": "system"}, {"action": "edit", "object": "system"} ] } } 3PMF -
{ "group_roles": { "project_leader": ["kpi_editor_design", "viewer_limit_ds"], "software_leader": ["kpi_editor_design", "kpi_editor_system"] },
"role_permissions": { "kpi_editor_design": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"} ], "viewer_limit_ds": [ {"action": "view_all", "object": "design"}, {"action": "view_all", "object": "system"} ], "kpi_editor_design": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"} ], "kpi_editor_system": [ {"action": "view_all", "object": "system"}, {"action": "edit", "object": "system"} ] } } (SPVQ -
package rbac.authz import data.rbac.authz.acl import input # logic that implements
RBAC. default allow = false allow { # lookup the list of roles for the user roles : = acl.group_roles[input.user[_]] # for each role in that list r : = roles[_] # lookup the permissions list for role r permissions : = acl.role_permissions[r] # for each permission p : = permissions[_] # check if the permission granted to r matches the user's request p = = {"action": input.action, "object": input.object} } 1PMJDZ -
rule01 { input.x < input.y } rule02 = "foobar" {
input.x < input.y } rule03 = x { x : = input.x x < input.y } %F fi OF3VMF IUUQTQMBZPQFOQPMJDZBHFOUPSHQ7"IJ/[YN -
rule01 { input.x < input.y } rule02 = "foobar" {
input.x < input.y } rule03 = x { x : = input.x x < input.y } %F fi OF3VMF SFUVSOCPPMFBO -
rule01 { input.x < input.y } rule02 = "foobar" {
input.x < input.y } rule03 = x { x : = input.x x < input.y } %F fi OF3VMF SFUVSOCPPMFBO SFUVSOTUSJOH -
rule01 { input.x < input.y } rule02 = "foobar" {
input.x < input.y } rule03 = x { x : = input.x x < input.y } %F fi OF3VMF SFUVSOCPPMFBO SFUVSOTUSJOH SFUVSOWBSJBCMF -
rule01 { input.x < input.y input.y < input.z } rule02
{ input.x < input.y } rule02 { input.y < input.z } "OE0SDPOEJUJPO BOEDPOEJUJPO PSDPOEJUJPO IUUQTQMBZPQFOQPMJDZBHFOUPSHQ-Q1STV -
default rule01 = false default rule02 = false rule01 {
input.x < input.y input.y < input.z } rule02 { input.x < input.y } rule02 { input.y < input.z } EFGBVMUWBMVF -
func allow() bool { if input.group = = "admin" {
return true } if input.user = = data.admin { return true } return false } default allow = false allow { input.group = = "admin" } allow { input.user = = data.admin } (PMBOH 3FHP -
numbers = [1, 2, 3, 4, 5] rule[x] { x
: = numbers[_] x % 2 = = 0 } containers = { "app": { "image": "app:18.04" }, "db": { "image": "db:latest" } } rule01[key] = image { image : = containers[key].image endswith(image, ":latest") } 'JOEFWFOOVNCFS 'JOEMBUFTUJNBHF -
numbers = [1, 2, 3, 4, 5] rule01 { x
: = numbers[_] x < 0 } rule02 { not rule01 } positives { negative : = [x | x : = numbers[_]; x < 0] count(negative) = = 0 } numbers = [1, 2, 3, 4, 5] rule { x : = numbers[_] x > 0 } "OZOVNCFS HSFBUFSUIBO;FSP numbers = [-1, 2, 3, 4, 5] rule { x : = numbers[_] x > 0 } "MMOVNCFS HSFBUFSUIBO;FSP -
numbers = [1, 2, 3, 4, 5] rule01 { x
: = numbers[_] x < 0 } rule02 { not rule01 } positives { negative : = [x | x : = numbers[_]; x < 0] count(negative) = = 0 } numbers = [1, 2, 3, 4, 5] rule { x : = numbers[_] x > 0 } numbers = [-1, 2, 3, 4, 5] rule { x : = numbers[_] x > 0 } "OZOVNCFS HSFBUFSUIBO;FSP "MMOVNCFS HSFBUFSUIBO;FSP -
numbers = [1, 2, 3, 4, 5] rule01 { x
: = numbers[_] x < 0 } rule02 { not rule01 } positives { negative : = [x | x : = numbers[_]; x < 0] count(negative) = = 0 } numbers = [1, 2, 3, 4, 5] rule { x : = numbers[_] x > 0 } numbers = [-1, 2, 3, 4, 5] rule { x : = numbers[_] x > 0 } "OZOVNCFS HSFBUFSUIBO;FSP "MMOVNCFS HSFBUFSUIBO;FSP -
func rule() []string { results : = []string{} for _,
img : = range images { for _, repo : = range repos { if strings.HasPref i x(img, repo) { results = append(results, img) } } } return results } images = [ "docker.io/nginx", "quay.io/ubuntu", "localhost/nginx" ] repos = [ "docker.io", "quay.io" ] rule[image] { image = images[_] startswith(image, repos[_]) } 3FHP (P-BOHVBHF -
rule07verify(answer) { rule07.db = = answer } test_rule07 { rule07verify("db:latest")
with input as {"containers": { "app": {"image": "app:18.04"}, "db": {"image": "db:latest"}, }} not rule07verify("db:latest") with input as {"containers": { "app": {"image": "app:18.04"}, "db": {"image": "postgres:latest"}, }} } -
rule12verify(answer) { count(rule12) = = answer } test_rule12 { rule12verify(2)
with input as { "images": [ "docker.io/nginx", "quay.io/ubuntu", "localhost/nginx", ], "repos": [ "docker.io", "quay.io", ], } } rule12[image] { image = input.images[_] startswith(image, input.repos[_]) } 3VMF 5FTUJOH -
$ opa test - v . data.example.test_rule01 : PASS (5.9055ms)
data.example.test_rule02 : PASS (157.292µs) data.example.test_rule03 : PASS (839.958µs) data.example.test_rule04 : PASS (150.125µs) data.example.test_rule05 : PASS (3.447417ms) data.example.test_rule06 : PASS (224.833µs) data.example.test_rule07 : PASS (721.959µs) data.example.test_rule08 : PASS (354.917µs) data.example.test_rule09 : PASS (207.583µs) data.example.test_rule11 : PASS (169.708µs) data.example.test_rule12 : PASS (198.042µs) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PASS : 11/11 IUUQTHJUIVCDPNHPUSBJOJOHPQBEFNP -
{ "group_roles": { "admin": ["admin"], "project_leader": ["viewer_limit_ds"] }, "role_permissions": {
"admin": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"}, ], "viewer_limit_ds": [ {"action": "view_all", "object": "design"}, {"action": "view_all", "object": "system"} ], "viewer_limit_m": [{"action": "view_l3_project"}] } } %BUB -
{ "group_roles": { "admin": ["admin"], "project_leader": ["viewer_limit_ds"] }, "role_permissions": {
"admin": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"}, ], "viewer_limit_ds": [ {"action": "view_all", "object": "design"}, {"action": "view_all", "object": "system"} ], "viewer_limit_m": [{"action": "view_l3_project"}] } } package rbac.authz import data.rbac.authz.acl import input default allow = false allow { roles := acl.group_roles[input.user[_]] r := roles[_] permissions := acl.role_permissions[r] p := permissions[_] p == {"action": input.action, "object": input.object} } %BUB 1PMJDZ -
{ "input": { "user": ["admin", "system_group_kpi_editor"], "action": "edit", "object": "design"
} } { "result": true } *OQVU 3FTVMU -
/ / GetUserGroupNames get user groups func GetUserGroupNames(username string) ([]string,
error) { if !helper.IsUsername(username) { return []string{}, errors.EBadRequest(errors.ErrUserNotExist, nil) } result : = &getUserGroups{} response, err : = resty.New().R(). SetHeader("Content-Type", "application/json"). SetHeader("Accept", "application/json"). SetQueryParam("username", username). SetBasicAuth(conf i g.Crowd.BasicUsername, conf i g.Crowd.BasicPassword). SetResult(result). Get(conf i g.Crowd.Address + "/user/group/nested") if err ! = nil { return []string{}, err } if response.StatusCode() ! = http.StatusOK { log.Error().Msg("failed to get user groups from crowd:" + response.String()) } groups : = []string{} for _, v : = range result.Groups { if strings.HasPref i x(v.Name, "prime_") { groups = append(groups, v.Name) } } return groups, err } -
# role - permissions assignments role_permissions : = { "admin":
[ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"}, {"action": "view_all", "object": "system"}, {"action": "edit", "object": "system"}, {"action": "view_all", "object": "manufacture"}, {"action": "edit", "object": "manufacture"}, ], "quality_head_design": [ {"action": "view_all", "object": "design"}, {"action": "edit", "object": "design"}, {"action": "view_all", "object": "system"}, {"action": "view_all", "object": "manufacture"}, ], "quality_head_system": [ {"action": "view_all", "object": "design"}, {"action": "view_all", "object": "system"}, {"action": "edit", "object": "system"}, {"action": "view_all", "object": "manufacture"}, ], "quality_head_manufacture": [ {"action": "view_all", "object": "design"}, {"action": "view_all", "object": "system"}, {"action": "view_all", "object": "manufacture"}, {"action": "edit", "object": "manufacture"}, ], } -
# user - role assignments group_roles : = { "prime_cqc_admin":
["admin"], "prime_cqc_design_quality_head": ["quality_head_design"], "prime_cqc_system_quality_head": ["quality_head_system"], "prime_cqc_manufacturing_quality_head": ["quality_head_manufacture"], "prime_cqc_design_kpi_editor": ["kpi_editor_design"], "prime_cqc_system_kpi_editor": ["kpi_editor_system"], "prime_cqc_manufacturing_kpi_editor": ["kpi_editor_manufacture"], "prime_cqc_viewer": ["viewer"], "prime_cqc_limitedviewer_design_system": ["viewer_limit_ds"], "prime_cqc_limitedviewer_manufacturing": ["viewer_limit_m"], } -
# user - role assignments group_roles : = { #
for testing "design_group_kpi_editor": ["kpi_editor_design", "viewer_limit_ds"], "system_group_kpi_editor": ["kpi_editor_system", "viewer_limit_ds"], "manufacture_group_kpi_editor": ["kpi_editor_manufacture", "viewer"], "project_leader": ["viewer_limit_ds", "viewer_limit_m"], } -
test_design_group_kpi_editor { allow with input as {"user": ["design_group_kpi_editor"], "action": "view_all",
"object": "design"} allow with input as {"user": ["design_group_kpi_editor"], "action": "edit", "object": "design"} allow with input as {"user": ["design_group_kpi_editor"], "action": "view_all", "object": "system"} not allow with input as {"user": ["design_group_kpi_editor"], "action": "edit", "object": "system"} not allow with input as {"user": ["design_group_kpi_editor"], "action": "view_all", "object": "manufacture"} not allow with input as {"user": ["design_group_kpi_editor"], "action": "edit", "object": "manufacture"} } -
$ opa test - v ./module/opa/policy / * .rego data.rbac.authz.test_design_group_kpi_editor:
PASS (13.0495ms) data.rbac.authz.test_system_group_kpi_editor: PASS (2.158583ms) data.rbac.authz.test_manufacture_group_kpi_editor: PASS (2.235167ms) data.rbac.authz.test_project_leader: PASS (1.90625ms) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PASS : 4/4 -
{ "Version": "2012-10-17", "Statement": [ { "Sid": "FirstStatement", "Effect": "Allow",
"Action": ["iam:ChangePassword"], "Resource": "*" }, { "Sid": "SecondStatement", "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" }, { "Sid": "ThirdStatement", "Effect": "Allow", "Action": [ "s3:List*", "s3:Get*" ], "Resource": [ "arn:aws:s3:::confidential-data", "arn:aws:s3:::confidential-data/*" ] } ] } "NB[POF8FC 4FSWJDF*". -
package aws default allow = false allow { actions_match resources_match
} actions_match { # iterate over the actions in the list actions : = ["s3 : List.*","s3 : Get.*"] action : = actions[_] # check if input.action matches an action regex.globs_match(input.action, action) } resources_match { # iterate over the resources in the list resources : = ["arn:aws:s3 : : : conf i dential - data","arn:aws:s3 : : : conf i dential - data/.*"] resource : = resources[_] # check if input.resource matches a resource regex.globs_match(input.resource, resource) } IUUQTXXXPQFOQPMJDZBHFOUPSHEPDTMBUFTUDPNQBSJTPOUPPUIFSTZTUFNTBNB[POXFCTFSWJDFTJBN { "Version": "2012-10-17", "Statement": [ { "Sid": "FirstStatement", "Effect": "Allow", "Action": ["iam:ChangePassword"], "Resource": "*" }, { "Sid": "SecondStatement", "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" }, { "Sid": "ThirdStatement", "Effect": "Allow", "Action": [ "s3:List*", "s3:Get*" ], "Resource": [ "arn:aws:s3:::confidential-data", "arn:aws:s3:::confidential-data/*" ] } ] } 1PMJDZ %BUB -
package aws default allow = false allow { actions_match resources_match
} actions_match { # iterate over the actions in the list actions : = ["s3 : List.*","s3 : Get.*"] action : = actions[_] # check if input.action matches an action regex.globs_match(input.action, action) } resources_match { # iterate over the resources in the list resources : = ["arn:aws:s3 : : : conf i dential - data","arn:aws:s3 : : : conf i dential - data/.*"] resource : = resources[_] # check if input.resource matches a resource regex.globs_match(input.resource, resource) } IUUQTXXXPQFOQPMJDZBHFOUPSHEPDTMBUFTUDPNQBSJTPOUPPUIFSTZTUFNTBNB[POXFCTFSWJDFTJBN { "Version": "2012-10-17", "Statement": [ { "Sid": "FirstStatement", "Effect": "Allow", "Action": ["iam:ChangePassword"], "Resource": "*" }, { "Sid": "SecondStatement", "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" }, { "Sid": "ThirdStatement", "Effect": "Allow", "Action": [ "s3:List*", "s3:Get*" ], "Resource": [ "arn:aws:s3:::confidential-data", "arn:aws:s3:::confidential-data/*" ] } ] } 1PMJDZ %BUB -
{ "Version": "2012-10-17", "Statement": [ { "Sid": "FirstStatement", "Effect": "Allow",
"Action": ["iam:ChangePassword"], "Resource": "*" }, { "Sid": "SecondStatement", "Effect": "Deny", "Action": "s3:GetFile", "Resource": "*" }, { "Sid": "ThirdStatement", "Effect": "Allow", "Action": [ "s3:List*", "s3:Get*" ], "Resource": [ "arn:aws:s3:::bucket", "arn:aws:s3:::bucket/*" ] } ] } 1FSNJTTJPO%BUB { "action": "s3:GetFile", "Resource": "arn:aws:s3:::bucket" } *OQVU%BUB -
{ "Version": "2012-10-17", "Statement": [ { "Sid": "FirstStatement", "Effect": "Allow",
"Action": ["iam:ChangePassword"], "Resource": "*" }, { "Sid": "SecondStatement", "Effect": "Deny", "Action": "s3:GetFile", "Resource": "*" }, { "Sid": "ThirdStatement", "Effect": "Allow", "Action": [ "s3:List*", "s3:Get*" ], "Resource": [ "arn:aws:s3:::bucket", "arn:aws:s3:::bucket/*" ] } ] } 1FSNJTTJPO%BUB { "action": "s3:GetFile", "Resource": "arn:aws:s3:::bucket" } *OQVU%BUB -
package iam.authz default authorized = false has_resource[statement_id] { statement_resource :
= data.statements[statement_id].resources[_] regex.globs_match(input.resource, statement_resource) } has_action[statement_id] { statement_resource : = data.statements[statement_id].actions[_] regex.globs_match(input.action, statement_resource) } match[[effect, statement_id]] { effect : = data.statements[statement_id].effect has_resource[statement_id] has_action[statement_id] } allow { match[["allow", _]] } deny { match[["deny", _]] } authorized { allow not deny } -
Architectural Flexibility • Embed in Go Application • Deploy in
Single Project (REST API) • Deploy one OPA service (REST API)