UPDATE
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
# CropLogic Authorization Service
|
||||||
|
|
||||||
|
This service runs OPA as a standalone authorization engine for `backend/access_control`.
|
||||||
|
|
||||||
|
## Run standalone
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f accsess/docker-compose.yaml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Decision endpoints
|
||||||
|
|
||||||
|
- Single feature: `POST /v1/data/croplogic/authz/decision`
|
||||||
|
- Batch features: `POST /v1/data/croplogic/authz/batch_decision`
|
||||||
|
|
||||||
|
The backend uses the batch endpoint and sends the farm context only. Users are treated as `farmer` by default inside the service, and features are allowed unless there is a feature-specific rule in `policies/authz.rego`.
|
||||||
|
|
||||||
|
## Example request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://127.0.0.1:8181/v1/data/croplogic/authz/batch_decision \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d @- <<'EOF'
|
||||||
|
{
|
||||||
|
"input": {
|
||||||
|
"resource": {
|
||||||
|
"farm_id": "farm-1001",
|
||||||
|
"subscription_plan_codes": ["gold"],
|
||||||
|
"farm_types": ["greenhouse"],
|
||||||
|
"crop_types": ["tomato"],
|
||||||
|
"cultivation_types": ["soil"],
|
||||||
|
"sensor_codes": ["sensor-7-in-1"],
|
||||||
|
"power_sensor": ["main-power"],
|
||||||
|
"customization": ["default-layout"]
|
||||||
|
},
|
||||||
|
"features": ["sensor-7-in-1"],
|
||||||
|
"action": "view"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add new rules in code
|
||||||
|
|
||||||
|
Define feature-specific checks directly in `policies/authz.rego`.
|
||||||
|
|
||||||
|
- If a feature has no rule, every action is allowed.
|
||||||
|
- If a feature rule exists, its conditions are evaluated and any failing condition denies access.
|
||||||
|
- `sensor-7-in-1` currently requires `resource.sensor_codes` to include `sensor-7-in-1`.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
services: {}
|
||||||
|
labels:
|
||||||
|
app: croplogic-authz
|
||||||
|
plugins: {}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
services:
|
||||||
|
opa:
|
||||||
|
image: mirror-docker.runflare.com/openpolicyagent/opa
|
||||||
|
container_name: croplogic-accsess-opa
|
||||||
|
command:
|
||||||
|
- run
|
||||||
|
- --server
|
||||||
|
- --addr=0.0.0.0:8181
|
||||||
|
- /policies
|
||||||
|
ports:
|
||||||
|
- "8181:8181"
|
||||||
|
volumes:
|
||||||
|
- ./policies:/policies:ro
|
||||||
|
- ./config/opa-config.yaml:/config/opa-config.yaml:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- crop_network
|
||||||
|
networks:
|
||||||
|
crop_network:
|
||||||
|
external: true
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package croplogic.authz
|
||||||
|
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
default allow := false
|
||||||
|
|
||||||
|
allow if {
|
||||||
|
decision.allow
|
||||||
|
}
|
||||||
|
|
||||||
|
decision := feature_decision(input.feature)
|
||||||
|
|
||||||
|
batch_decision := {
|
||||||
|
"features": {
|
||||||
|
feature: result |
|
||||||
|
feature := input.features[_]
|
||||||
|
result := feature_decision(feature)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
feature_decision(feature) := {
|
||||||
|
"allow": true,
|
||||||
|
"matched_rules": [],
|
||||||
|
"deny_rules": [],
|
||||||
|
"allow_rules": [],
|
||||||
|
} if {
|
||||||
|
not has_feature_rule(feature)
|
||||||
|
}
|
||||||
|
|
||||||
|
feature_decision(feature) := result if {
|
||||||
|
has_feature_rule(feature)
|
||||||
|
rule := feature_rule(feature)
|
||||||
|
matched := [matched_rule | matched_rule := rule; action_match(matched_rule)]
|
||||||
|
deny_rules := [matched_rule | matched_rule := matched[_]; not object.get(matched_rule, "allow", false)]
|
||||||
|
allow_rules := [matched_rule | matched_rule := matched[_]; object.get(matched_rule, "allow", false)]
|
||||||
|
count(deny_rules) == 0
|
||||||
|
result := {
|
||||||
|
"allow": true,
|
||||||
|
"matched_rules": matched,
|
||||||
|
"deny_rules": deny_rules,
|
||||||
|
"allow_rules": allow_rules,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
feature_decision(feature) := result if {
|
||||||
|
has_feature_rule(feature)
|
||||||
|
rule := feature_rule(feature)
|
||||||
|
matched := [matched_rule | matched_rule := rule; action_match(matched_rule)]
|
||||||
|
deny_rules := [matched_rule | matched_rule := matched[_]; not object.get(matched_rule, "allow", false)]
|
||||||
|
allow_rules := [matched_rule | matched_rule := matched[_]; object.get(matched_rule, "allow", false)]
|
||||||
|
count(deny_rules) > 0
|
||||||
|
result := {
|
||||||
|
"allow": false,
|
||||||
|
"matched_rules": matched,
|
||||||
|
"deny_rules": deny_rules,
|
||||||
|
"allow_rules": allow_rules,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_match(rule) if {
|
||||||
|
count(object.get(rule, "actions_any", [])) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
action_match(rule) if {
|
||||||
|
requested_action := lower(sprintf("%v", [object.get(input, "action", "view")]))
|
||||||
|
action := object.get(rule, "actions_any", [])[_]
|
||||||
|
lower(sprintf("%v", [action])) == requested_action
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"authz": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package croplogic.authz
|
||||||
|
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
has_feature_rule(feature) if {
|
||||||
|
is_sensor_7_in_1_feature(feature)
|
||||||
|
}
|
||||||
|
|
||||||
|
feature_rule(feature) := {
|
||||||
|
"code": "sensor-7-in-1-requires-sensor-code",
|
||||||
|
"allow": true,
|
||||||
|
"reason": "sensor-7-in-1 feature requires sensor_codes to include sensor-7-in-1",
|
||||||
|
} if {
|
||||||
|
is_sensor_7_in_1_feature(feature)
|
||||||
|
has_sensor_code("sensor-7-in-1")
|
||||||
|
}
|
||||||
|
|
||||||
|
feature_rule(feature) := {
|
||||||
|
"code": "sensor-7-in-1-requires-sensor-code",
|
||||||
|
"allow": false,
|
||||||
|
"reason": "sensor-7-in-1 feature requires sensor_codes to include sensor-7-in-1",
|
||||||
|
} if {
|
||||||
|
is_sensor_7_in_1_feature(feature)
|
||||||
|
not has_sensor_code("sensor-7-in-1")
|
||||||
|
}
|
||||||
|
|
||||||
|
is_sensor_7_in_1_feature(feature) if {
|
||||||
|
lower(sprintf("%v", [feature])) == "sensor-7-in-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
has_sensor_code(code) if {
|
||||||
|
sensor_codes := object.get(input.resource, "sensor_codes", [])
|
||||||
|
is_array(sensor_codes)
|
||||||
|
sensor_code := sensor_codes[_]
|
||||||
|
lower(sprintf("%v", [sensor_code])) == lower(sprintf("%v", [code]))
|
||||||
|
}
|
||||||
|
|
||||||
|
has_sensor_code(code) if {
|
||||||
|
sensor_code := object.get(input.resource, "sensor_codes", null)
|
||||||
|
sensor_code != null
|
||||||
|
not is_array(sensor_code)
|
||||||
|
lower(sprintf("%v", [sensor_code])) == lower(sprintf("%v", [code]))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user