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

WIP: Constraints #233

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions anubis-management-api/anubis/policies/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class Policy(Base):
access_to = Column(String, index=True)
resource_type = Column(String, index=True)
mode = relationship("Mode", secondary=policy_to_mode)
constraint = Column(String, index=True)
service_path = relationship(ServicePath)
service_path_id = Column(
String,
Expand Down
3 changes: 2 additions & 1 deletion anubis-management-api/anubis/policies/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ def create_policy(
db_policy = models.Policy(
id=pid,
access_to=policy.access_to,
constraint=policy.constraint,
resource_type=policy.resource_type,
service_path_id=service_path_id)
db.add(db_policy)
Expand All @@ -266,7 +267,7 @@ def update_policy(
policy_id: str,
policy: schemas.PolicyCreate):
db.query(models.Policy).filter(models.Policy.id == policy_id). update(
{"access_to": policy.access_to, "resource_type": policy.resource_type})
{"access_to": policy.access_to, "resource_type": policy.resource_type, "constraint": policy.constraint})
db.commit()
db.query(models.policy_to_mode).filter(
models.policy_to_mode.c.policy_id == policy_id). delete()
Expand Down
1 change: 1 addition & 0 deletions anubis-management-api/anubis/policies/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def serialize_policy(policy: models.Policy):
return schemas.Policy(
id=policy.id,
access_to=policy.access_to,
constraint=policy.constraint,
resource_type=policy.resource_type,
mode=modes,
agent=agents)
Expand Down
8 changes: 8 additions & 0 deletions anubis-management-api/anubis/policies/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Config:
class PolicyBase(BaseModel):
id: Optional[str] = None
access_to: str
constraint: Optional[str] = None
resource_type: str
mode: List[str] = []
agent: List[str] = []
Expand Down Expand Up @@ -67,6 +68,13 @@ def valid_iri(cls, access_to):
parse(access_to)
return access_to

@validator('constraint')
def valid_constraint(cls, constraint):
if not constraint:
return None
parse(constraint)
return constraint

@validator('resource_type')
def valid_resource_type(cls, resource_type, values):
parse(resource_type, rule='relative_part')
Expand Down
12 changes: 6 additions & 6 deletions config/opa-service/rego/anubis_management_api_policy_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -161,25 +161,25 @@ service_path_data = {
}

test_policy_permissions_all {
allow.allowed with request as {"user":"[email protected]", "action":"POST", "resource":"/v1/policies/", "tenant":"Tenant1", "service_path":"/"} with input.parsed_body as {"access_to":"test","resource_type":"entity"} with data as policy_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"POST", "resource":"/v1/policies/", "tenant":"Tenant1", "service_path":"/"} with input.parsed_body as {"access_to":"test","resource_type":"entity"} with policies as policy_data with bearer_token as bearer_token with testing as true
}

test_policy_permissions_one {
allow.allowed with request as {"user":"[email protected]", "action":"GET", "resource":"/v1/policies/test", "tenant":"Tenant1", "service_path":"/"} with data as policy_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v1/policies/test", "tenant":"Tenant1", "service_path":"/"} with policies as policy_data with bearer_token as bearer_token with testing as true
}

test_tenant_permissions_all {
allow.allowed with request as {"user":"[email protected]", "action":"GET", "resource":"/v1/tenants", "tenant":"Tenant1", "service_path":"/"} with data as tenant_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v1/tenants", "tenant":"Tenant1", "service_path":"/"} with policies as tenant_data with bearer_token as bearer_token with testing as true
}

test_tenant_permissions_one {
allow.allowed with request as {"user":"[email protected]", "action":"PUT", "resource":"/v1/tenants/Tenant1", "tenant":"Tenant1", "service_path":"/"} with data as tenant_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"PUT", "resource":"/v1/tenants/Tenant1", "tenant":"Tenant1", "service_path":"/"} with policies as tenant_data with bearer_token as bearer_token with testing as true
}

test_service_path_permissions_all {
allow.allowed with request as {"user":"[email protected]", "action":"GET", "resource":"/v1/tenants/Tenant1/service_paths", "tenant":"Tenant1", "service_path":"/"} with data as service_path_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v1/tenants/Tenant1/service_paths", "tenant":"Tenant1", "service_path":"/"} with policies as service_path_data with bearer_token as bearer_token with testing as true
}

test_service_path_permissions_one {
allow.allowed with request as {"user":"[email protected]", "action":"PUT", "resource":"/v1/tenants/Tenant1/service_paths/foo/bar", "tenant":"Tenant1", "service_path":"/"} with data as service_path_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"PUT", "resource":"/v1/tenants/Tenant1/service_paths/foo/bar", "tenant":"Tenant1", "service_path":"/"} with policies as service_path_data with bearer_token as bearer_token with testing as true
}
16 changes: 8 additions & 8 deletions config/opa-service/rego/common.rego
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ fiware_servicepath := p {
request = {"user":subject, "action": method, "resource":path, "tenant":fiware_service, "service_path":fiware_servicepath}

# Compute link
default header_link = ""
header_link = ""

# Auth defaults to false
default allow = {
allow = {
"allowed": false,
}

Expand Down Expand Up @@ -112,17 +112,17 @@ is_token_valid {
issuer = valid_iss[_]
}

# Test for valid audiences in token
valid_audience(aud_entry) {
token.payload.azp = aud_entry
}

# Token valid when testing (default is false)
testing = false
default testing = false
is_token_valid {
testing
}

# Test for valid audiences in token
valid_audience(aud_entry) {
token.payload.azp = aud_entry
}

# Check if service path in policy is equal to the request path
service_path_matches_policy(entry_path, request_path) {
entry_path == request_path
Expand Down
31 changes: 31 additions & 0 deletions config/opa-service/rego/constraints.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package envoy.authz
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, but what about e.g. boolean operators? e.g. what would I do if I wanted to express a constraint like (x < 5 && x > 10) || x > 20...


check_constraint(left, operator, right) {
operator == "acl:operator:hasPart"
contains(left, right)
}

check_constraint(left, operator, right) {
operator == "acl:operator:eq"
left == right
}

check_constraint(left, operator, right) {
operator == "acl:operator:gt"
left >= right
}

check_constraint(left, operator, right) {
operator == "acl:operator:gteq"
left >= right
}

check_constraint(left, operator, right) {
operator == "acl:operator:lt"
left < right
}

check_constraint(left, operator, right) {
operator == "acl:operator:lteq"
left <= right
}
16 changes: 15 additions & 1 deletion config/opa-service/rego/context_broker_policy.rego
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import input.attributes.request.http.method as method
import input.attributes.request.http.path as path
import input.attributes.request.http.headers.authorization as authorization


# Checks if the policy has the wildcard asterisks, thus matching paths to any entity or all
path_matches_policy(entry, request) {
entry.resource == "*"
entry.resource_type == "entity"
current_path := split(request.resource, "/")
current_path[1] == "v2"
current_path[2] == "entities"
not entry.constraint
}

# Checks if the policy is a default
Expand All @@ -31,6 +33,18 @@ path_matches_policy(entry, request) {
current_path[1] == "v2"
current_path[2] == "entities"
current_path[3] == entry.resource
not entry.constraint
}

path_matches_policy(entry, request) {
entry.resource_type == "entity"
current_path := split(request.resource, "/")
current_path[1] == "v2"
current_path[2] == "entities"
current_path[3] == entry.resource
constraints := split(entry.constraint, " ")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if there's extra whitespace in the constraint? e.g. " > 5 "

constraints[0] == "acl-oc:ResourceName"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how would we use this in teadal? every API would need to have its own rego file w/ a path_matches_policy definition I suppose. That's fair enough, but then we'd have to duplicate the constraints logic in each and every file? also, what if I want to check request fields (e.g. method) or data fields in the JSON payload in the case of a POST?

check_constraint(current_path[3], constraints[1], constraints[2])
}

# Set the header link for the entities
Expand Down Expand Up @@ -148,4 +162,4 @@ header_link = link {
current_path[2] == "subscriptions"
not current_path[3]
link := sprintf("<%s/me?resource=%s&&type=%s>; rel=\"acl\"", [api_uri,"*","subscription"])
}
}
71 changes: 61 additions & 10 deletions config/opa-service/rego/context_broker_policy_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,37 @@ user_data = {
}
}

user_data_constraint = {
"user_permissions": {
"[email protected]": [
{
"action": "acl:Read",
"resource": "test",
"resource_type": "entity",
"tenant": "Tenant1",
"service_path": "/",
"constraint": "acl-oc:ResourceName acl:operator:hasPart test"
},
{
"action": "acl:Read",
"resource": "foo",
"resource_type": "entity",
"tenant": "Tenant1",
"service_path": "/",
"constraint": "acl-oc:ResourceName acl:operator:eq foo"
},
{
"action": "acl:Read",
"resource": "6",
"resource_type": "entity",
"tenant": "Tenant1",
"service_path": "/",
"constraint": "acl-oc:ResourceName acl:operator:gt 5"
}
]
}
}

group_data = {
"group_permissions": {
"/Group1": [
Expand Down Expand Up @@ -94,43 +125,63 @@ default_data = {
}

test_user_permissions {
allow.allowed with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with data as user_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with policies as user_data with bearer_token as bearer_token with testing as true
}

test_user_permissions_contraint_1 {
check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with policies as user_data_constraint with bearer_token as bearer_token with testing as true
}

test_user_permissions_contraint_2 {
check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/foo", "tenant":"Tenant1", "service_path":"/"} with policies as user_data_constraint with bearer_token as bearer_token with testing as true
}

test_user_permissions_contraint_3 {
not check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/foobar", "tenant":"Tenant1", "service_path":"/"} with policies as user_data_constraint with bearer_token as bearer_token with testing as true
}

test_user_permissions_contraint_4 {
check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/6", "tenant":"Tenant1", "service_path":"/"} with policies as user_data_constraint with bearer_token as bearer_token with testing as true
}

test_user_permissions_contraint_4 {
not check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/5", "tenant":"Tenant1", "service_path":"/"} with policies as user_data_constraint with bearer_token as bearer_token with testing as true
}

test_user_permissions_unathorized {
not allow.allowed with request as {"user":"[email protected]", "action":"PATCH", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with data as user_data with bearer_token as bearer_token with testing as true
not check_policy with request as {"user":"[email protected]", "action":"PATCH", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with policies as user_data with bearer_token as bearer_token with testing as true
}

test_user_permissions_entity_type {
allow.allowed with request as {"user":"[email protected]", "action":"POST", "resource":"/v2/types/test", "tenant":"Tenant1", "service_path":"/"} with data as user_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"POST", "resource":"/v2/types/test", "tenant":"Tenant1", "service_path":"/"} with policies as user_data with bearer_token as bearer_token with testing as true
}

test_group_permissions {
allow.allowed with request as {"user":"[email protected]", "action":"POST", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with data as group_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"POST", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with policies as group_data with bearer_token as bearer_token with testing as true
}

test_group_permissions_unathorized {
not allow.allowed with request as {"user":"[email protected]", "action":"PATCH", "resource":"/v2/entities/test2", "tenant":"Tenant1", "service_path":"/"} with data as group_data with bearer_token as bearer_token with testing as true
not check_policy with request as {"user":"[email protected]", "action":"PATCH", "resource":"/v2/entities/test2", "tenant":"Tenant1", "service_path":"/"} with policies as group_data with bearer_token as bearer_token with testing as true
}

test_role_permissions {
allow.allowed with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with data as role_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with policies as role_data with bearer_token as bearer_token with testing as true
}

test_role_permissions_unathorized {
not allow.allowed with request as {"user":"[email protected]", "action":"PATCH", "resource":"/v2/entities/test2", "tenant":"Tenant1", "service_path":"/"} with data as role_data with bearer_token as bearer_token with testing as true
not check_policy with request as {"user":"[email protected]", "action":"PATCH", "resource":"/v2/entities/test2", "tenant":"Tenant1", "service_path":"/"} with policies as role_data with bearer_token as bearer_token with testing as true
}

test_authenticated_agent_permissions {
allow.allowed with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with data as authenticated_agent_data with bearer_token as bearer_token with testing as true
check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with policies as authenticated_agent_data with bearer_token as bearer_token with testing as true
}

test_foaf_agent_permissions {
allow.allowed with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with data as foaf_agent_data with testing as true
check_policy with request as {"user":"[email protected]", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/"} with policies as foaf_agent_data with testing as true
}

test_default_agent_permissions {
allow.allowed with request as {"user":"foobar", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/test/foobar"} with data as default_data with testing as true
check_policy with request as {"user":"foobar", "action":"GET", "resource":"/v2/entities/test", "tenant":"Tenant1", "service_path":"/test/foobar"} with policies as default_data with testing as true
}

# test_api {
Expand Down
2 changes: 2 additions & 0 deletions docs/user/walkthrough.md
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,7 @@ Simple healthcheck endpoint
| ---- | ---- | ----------- | -------- |
| id | string | | Yes |
| access_to | string | | Yes |
| constraint | string | | No |
| resource_type | string | | Yes |
| mode | [ string ] | | No |
| agent | [ string ] | | No |
Expand All @@ -825,6 +826,7 @@ Simple healthcheck endpoint
| ---- | ---- | ----------- | -------- |
| id | string | | No |
| access_to | string | | Yes |
| constraint | string | | No |
| resource_type | string | | Yes |
| mode | [ string ] | | No |
| agent | [ string ] | | No |
Expand Down
8 changes: 8 additions & 0 deletions open-api-spec/api-manager/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2220,6 +2220,10 @@
"title": "Access To",
"type": "string"
},
"constraint": {
"title": "Constraint",
"type": "string"
},
"resource_type": {
"title": "Resource Type",
"type": "string"
Expand Down Expand Up @@ -2258,6 +2262,10 @@
"title": "Access To",
"type": "string"
},
"constraint": {
"title": "Constraint",
"type": "string"
},
"resource_type": {
"title": "Resource Type",
"type": "string"
Expand Down