Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade Opa to v1.0.0 #76

Merged
merged 3 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading