Skip to content

Commit

Permalink
Merge pull request #76 from CanDIG/daisieh/opa-v1
Browse files Browse the repository at this point in the history
Upgrade Opa to v1.0.0
  • Loading branch information
daisieh authored Dec 31, 2024
2 parents 15a86a7 + 2e3b177 commit 5ed15a6
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 195 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG venv_python
ARG venv_python=3.12
FROM python:${venv_python}

LABEL Maintainer="CanDIG Project"
Expand Down Expand Up @@ -28,7 +28,7 @@ USER candig

WORKDIR /app/

RUN curl -L -o opa https://openpolicyagent.org/downloads/v0.63.0/opa_linux_amd64_static
RUN curl -L -o opa https://openpolicyagent.org/downloads/v1.0.0/opa_linux_amd64_static

RUN chmod 755 ./opa

Expand Down
44 changes: 23 additions & 21 deletions permissions_engine/authz.rego
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
package system.authz

import rego.v1

# this defines authentication to have access to opa at all
# from: https://www.openpolicyagent.org/docs/v0.22.0/security/#token-based-authentication-example

# Reject requests by default
default allow = false
default allow := false

# Site admin should be able to see anything
allow {
data.permissions.site_admin == true
allow if {
data.permissions.site_admin == true
}

# Any service should be able to verify that a service is who it says it is:
allow {
input.path == ["v1", "data", "service", "verified"]
input.method == "POST"
allow if {
input.path == ["v1", "data", "service", "verified"]
input.method == "POST"
}

# Opa should be able to store its vault token
allow {
input.path == ["v1", "data", "store_token"]
input.method == "PUT"
input.headers["X-Opa"][_] == data.opa_secret
allow if {
input.path == ["v1", "data", "store_token"]
input.method == "PUT"
input.headers["X-Opa"][_] == data.opa_secret
}

# Service-info path for healthcheck
allow {
input.path == ["v1", "data", "service", "service-info"]
input.method == "GET"
allow if {
input.path == ["v1", "data", "service", "service-info"]
input.method == "GET"
}

# The authx library uses these paths:
authx_paths = {
"permissions": ["v1", "data", "permissions"],
"user_id": ["v1", "data", "idp", "user_key"]
authx_paths := {
"permissions": ["v1", "data", "permissions"],
"user_id": ["v1", "data", "idp", "user_key"],
}

# An authorized user has a valid token (and passes in that same token for both bearer and body)
# Authz users can access the authx paths
allow {
input.path == authx_paths[_]
input.method == "POST"
data.permissions.valid_token == true
input.body.input.token == input.identity
allow if {
input.path == authx_paths[_]
input.method == "POST"
data.permissions.valid_token == true
input.body.input.token == input.identity
}
141 changes: 69 additions & 72 deletions permissions_engine/calculate.rego
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package calculate

#
# This is the set of policy definitions for the permissions engine.
#
Expand All @@ -13,18 +14,20 @@ package calculate
# }
#
import data.idp.user_key as user_key
import future.keywords.in

import rego.v1

#
# This user is a site admin if they have the site_admin role
#
import data.vault.site_roles as site_roles
site_admin = true {
user_key in site_roles.admin

site_admin if {
user_key in site_roles.admin
}

site_curator = true {
user_key in site_roles.curator
site_curator if {
user_key in site_roles.curator
}

#
Expand All @@ -36,113 +39,107 @@ import data.vault.program_auths as program_auths
import data.vault.user_programs as user_programs

# compile list of programs specifically authorized for the user by DACs and within the authorized time period
user_readable_programs[p["program_id"]] := output {
some p in user_programs
time.parse_ns("2006-01-02", p["start_date"]) <= time.now_ns()
time.parse_ns("2006-01-02", p["end_date"]) >= time.now_ns()
output := p
user_readable_programs[p.program_id] := output if {
some p in user_programs
time.parse_ns("2006-01-02", p.start_date) <= time.now_ns()
time.parse_ns("2006-01-02", p.end_date) >= time.now_ns()
output := p
}

# compile list of programs that list the user as a team member
team_readable_programs[p] := output {
some p in all_programs
user_key in program_auths[p].team_members
output := program_auths[p].team_members
team_readable_programs[p] := output if {
some p in all_programs
user_key in program_auths[p].team_members
output := program_auths[p].team_members
}

# user can read programs that are either team-readable or user-readable
readable_programs := object.keys(object.union(team_readable_programs, user_readable_programs))

# user can curate programs that list the user as a program curator
curateable_programs[p] {
some p in all_programs
user_key in program_auths[p].program_curators
curateable_programs[p] if {
some p in all_programs
user_key in program_auths[p].program_curators
}

import data.vault.paths as paths

# debugging
readable_get[p] := output {
some p in paths.read.get
output := regex.match(p, input.body.path)
readable_get[p] := output if {
some p in paths.read.get
output := regex.match(p, input.body.path)
}
readable_post[p] := output {
some p in paths.read.post
output := regex.match(p, input.body.path)

readable_post[p] := output if {
some p in paths.read.post
output := regex.match(p, input.body.path)
}
curateable_get[p] := output {
some p in paths.curate.get
output := regex.match(p, input.body.path)

curateable_get[p] := output if {
some p in paths.curate.get
output := regex.match(p, input.body.path)
}
curateable_post[p] := output {
some p in paths.curate.post
output := regex.match(p, input.body.path)

curateable_post[p] := output if {
some p in paths.curate.post
output := regex.match(p, input.body.path)
}
curateable_delete[p] := output {
some p in paths.curate.delete
output := regex.match(p, input.body.path)

curateable_delete[p] := output if {
some p in paths.curate.delete
output := regex.match(p, input.body.path)
}

# which datasets can this user see for this method, path
default datasets = []
default datasets := []

# site admins can see all programs
datasets := all_programs
{
site_admin
datasets := all_programs if {
site_admin
}

# if user is a team_member, they can access programs that allow read access for this method, path
else := readable_programs
{
input.body.method = "GET"
regex.match(paths.read.get[_], input.body.path) == true
else := readable_programs if {
input.body.method = "GET"
regex.match(paths.read.get[_], input.body.path) == true
}

else := readable_programs
{
input.body.method = "POST"
regex.match(paths.read.post[_], input.body.path) == true
else := readable_programs if {
input.body.method = "POST"
regex.match(paths.read.post[_], input.body.path) == true
}

# if user is a site curator, they can access all programs that allow curate access for this method, path
else := all_programs
{
user_key in site_roles.curator
input.body.method = "GET"
regex.match(paths.curate.get[_], input.body.path) == true
else := all_programs if {
user_key in site_roles.curator
input.body.method = "GET"
regex.match(paths.curate.get[_], input.body.path) == true
}

else := all_programs
{
user_key in site_roles.curator
input.body.method = "POST"
regex.match(paths.curate.post[_], input.body.path) == true
else := all_programs if {
user_key in site_roles.curator
input.body.method = "POST"
regex.match(paths.curate.post[_], input.body.path) == true
}

else := all_programs
{
user_key in site_roles.curator
input.body.method = "DELETE"
regex.match(paths.curate.delete[_], input.body.path) == true
else := all_programs if {
user_key in site_roles.curator
input.body.method = "DELETE"
regex.match(paths.curate.delete[_], input.body.path) == true
}

# if user is a program_curator, they can access programs that allow curate access for them for this method, path
else := curateable_programs
{
input.body.method = "GET"
regex.match(paths.curate.get[_], input.body.path) == true
else := curateable_programs if {
input.body.method = "GET"
regex.match(paths.curate.get[_], input.body.path) == true
}

else := curateable_programs
{
input.body.method = "POST"
regex.match(paths.curate.post[_], input.body.path) == true
else := curateable_programs if {
input.body.method = "POST"
regex.match(paths.curate.post[_], input.body.path) == true
}

else := curateable_programs
{
input.body.method = "DELETE"
regex.match(paths.curate.delete[_], input.body.path) == true
else := curateable_programs if {
input.body.method = "DELETE"
regex.match(paths.curate.delete[_], input.body.path) == true
}

Loading

0 comments on commit 5ed15a6

Please sign in to comment.