Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to Open Policy Agent

Bo-Yi Wu
October 12, 2021

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
Tweet

More Decks by Bo-Yi Wu

Other Decks in Programming

Transcript

  1. 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.
  2. 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.
  3. { "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"} ] } }
  4. Open Policy Engine • Written in Golang • Easy to

    write policy testing • Easy to integrate with Go Application • Embed in GO • RESTful API
  5.      Client Send Request  Ask

    Permission  Response Result  Response to Client
  6.     Upload Data  Upload Policy Rule

     Send Query Input *OQVUDBOCF "/:+40/WBMVF
  7.      Upload Data  Upload Policy

    Rule  Send Query Input  Get Query Result *OQVUDBOCF "/:+40/WBMVF 0VUQVUDBOCF "/:+40/WBMVF
  8. { "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
  9. { "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
  10. { "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
  11. 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
  12. 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
  13. rule01 { input.x < input.y } rule02 = "foobar" {

    input.x < input.y } rule03 = x { x : = input.x x < input.y } %F fi OF3VMF SFUVSOCPPMFBO
  14. rule01 { input.x < input.y } rule02 = "foobar" {

    input.x < input.y } rule03 = x { x : = input.x x < input.y } %F fi OF3VMF SFUVSOCPPMFBO SFUVSOTUSJOH
  15. 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
  16. rule01 { input.x < input.y input.y < input.z } rule02

    { input.x < input.y } rule02 { input.y < input.z } "OE0SDPOEJUJPO BOEDPOEJUJPO PSDPOEJUJPO IUUQTQMBZPQFOQPMJDZBHFOUPSHQ-Q1STV
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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"}, }} }
  25. 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
  26. $ 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
  27. { "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
  28. { "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
  29. / / 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 }
  30. # 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"}, ], }
  31. # 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"], }
  32. # 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"], }
  33. 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"} }
  34. $ 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
  35. { "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*".
  36. 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
  37. 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
  38. { "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
  39. { "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
  40. 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 }
  41. Architectural Flexibility • Embed in Go Application • Deploy in

    Single Project (REST API) • Deploy one OPA service (REST API)