Save 37% off PRO during our Black Friday Sale! »

Introduction to Open Policy Agent

265bcbb56e831266de7a9f9281aab57a?s=47 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.

265bcbb56e831266de7a9f9281aab57a?s=128

Bo-Yi Wu

October 12, 2021
Tweet

Transcript

  1. Introduction to Open Policy Agent Bo-Yi Wu 2021/10/23

  2. 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.
  3. 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.
  4. Why we need Policy Engine?

  5. 1. RBAC is Hard

  6. 4JNQMF(SPVQ 1FSNJTTJPO

  7. None
  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"} ] } }
  9. 2. Cross Multi-Service

  10. 1FSNJTTJPOGPSNVMUJQMFTFSWJDF

  11. 3. Applying to Multi-System

  12. 1FSNJTTJPOGPSNVMUJQMFTZTUFN

  13. Why we choose Open Policy Agent?

  14. Open Policy Engine • Written in Golang • Easy to

    write policy testing • Easy to integrate with Go Application • Embed in GO • RESTful API
  15. Request Work fl ow with Open Policy Agent

  16.   Client Send Request

  17.    Client Send Request  Ask Permission

  18.     Client Send Request  Ask Permission

     Response Result
  19.      Client Send Request  Ask

    Permission  Response Result  Response to Client
  20. Query Input and Result with policy engine

  21. None
  22.   Upload Data

  23.    Upload Data  Upload Policy Rule

  24.     Upload Data  Upload Policy Rule

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

    Rule  Send Query Input  Get Query Result *OQVUDBOCF "/:+40/WBMVF 0VUQVUDBOCF "/:+40/WBMVF
  26. Policy Decision • Data (JSON) • Policy (Rego) • Query

    Input (JSON)
  27. RBAC Example (Role-base Access Control)

  28. { "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
  29. { "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
  30. { "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
  31. { "input": { "user": ["project_leader", "software_leader"], "action": "edit", "object": "design"

    } } *OQVU
  32. { "input": { "user": ["project_leader", "software_leader"], "action": "edit", "object": "design"

    } } 6TFSHSPVQT
  33. 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
  34. Policy Language (Rego)

  35. Rego Playground 
 https://play.openpolicyagent.org/

  36. 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
  37. rule01 { input.x < input.y } rule02 = "foobar" {

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

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

    { input.x < input.y } rule02 { input.y < input.z } "OE0SDPOEJUJPO BOEDPOEJUJPO PSDPOEJUJPO IUUQTQMBZPQFOQPMJDZBHFOUPSHQ-Q1STV
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. OPA Testing

  49. 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"}, }} }
  50. 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
  51. $ 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
  52. Upload Data and Policy

  53. None
  54. { "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
  55. { "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
  56. Query Result

  57. None
  58. { "input": { "user": ["admin", "system_group_kpi_editor"], "action": "edit", "object": "design"

    } } *OQVU
  59. { "input": { "user": ["admin", "system_group_kpi_editor"], "action": "edit", "object": "design"

    } } { "result": true } *OQVU 3FTVMU
  60. RBAC Design

  61. None
  62. Login Flow      

  63. / / 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 }
  64. 3PMF1FSNJTTJPO

  65. 3PMF1FSNJTTJPO 

  66. # 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"}, ], }
  67. 3PMF1FSNJTTJPO 

  68. # 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"], }
  69. 3PMF1FSNJTTJPO 

  70. # 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"], }
  71. RBAC Testing

  72. 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"} }
  73. $ 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
  74. How about Dynamic Actions and Dynamic Resources?

  75. "NB[POF8FC4FSWJDF*".

  76. { "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*".
  77. 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
  78. 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
  79. How to resolve Multiple Statement and Multiple Effect?

  80. { "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
  81. { "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
  82. 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 }
  83. Three Ways to Deploy Open Policy Agent

  84. Architectural Flexibility • Embed in Go Application • Deploy in

    Single Project (REST API) • Deploy one OPA service (REST API)
  85. Embed in Go Application

  86. Deploy in Single Project

  87. Deploy one OPA service

  88. AWS Lambda Function With OPA

  89. Thanks