This commit is contained in:
2026-04-09 23:43:41 +03:30
parent 8579f9ae91
commit 83e20bf67e
8 changed files with 97 additions and 2 deletions
+9
View File
@@ -8,6 +8,15 @@ This service runs OPA as a standalone authorization engine for `backend/access_c
docker compose -f accsess/docker-compose.yaml up -d docker compose -f accsess/docker-compose.yaml up -d
``` ```
If you want request logging only on development, start the stack with
`APP_ENV=DEVELOP` and enable the `develop` profile. In that mode, OPA sends
decision logs to a sidecar service, and the log file is written to
`accsess/logs/opa.log` on the host through a Docker volume.
```bash
APP_ENV=DEVELOP COMPOSE_PROFILES=develop docker compose -f accsess/docker-compose.yaml up -d
```
## Decision endpoints ## Decision endpoints
- Single feature: `POST /v1/data/croplogic/authz/decision` - Single feature: `POST /v1/data/croplogic/authz/decision`
+11
View File
@@ -0,0 +1,11 @@
services:
requestlog:
url: http://opa-log-receiver:8282/logs
labels:
app: croplogic-authz
plugins: {}
decision_logs:
service: requestlog
reporting:
min_delay_seconds: 1
max_delay_seconds: 5
+4
View File
@@ -0,0 +1,4 @@
services: {}
labels:
app: croplogic-authz
plugins: {}
+24 -2
View File
@@ -6,16 +6,38 @@ services:
- run - run
- --server - --server
- --addr=0.0.0.0:8181 - --addr=0.0.0.0:8181
- --config-file=/config/opa-config.${APP_ENV:-default}.yaml
- /policies - /policies
environment:
APP_ENV: ${APP_ENV:-}
ports: ports:
- "8181:8181" - "8181:8181"
volumes: volumes:
- ./policies:/policies:ro - ./policies:/policies:ro
- ./config/opa-config.yaml:/config/opa-config.yaml:ro - ./config/opa-config.default.yaml:/config/opa-config.default.yaml:ro
- ./config/opa-config.DEVELOP.yaml:/config/opa-config.DEVELOP.yaml:ro
restart: unless-stopped restart: unless-stopped
networks: networks:
- crop_network - crop_network
opa-log-receiver:
image: docker.iranserver.com/python:3.10
container_name: croplogic-accsess-opa-log-receiver
profiles:
- develop
command:
- python
- /app/scripts/opa_log_receiver.py
environment:
OPA_REQUEST_LOG_FILE: /logs/opa.log
OPA_REQUEST_LOG_PORT: "8282"
volumes:
- ./scripts/opa_log_receiver.py:/app/scripts/opa_log_receiver.py:ro
- ./logs:/logs
restart: unless-stopped
networks:
- crop_network
networks: networks:
crop_network: crop_network:
external: true external: true
+1
View File
@@ -0,0 +1 @@
+4
View File
@@ -0,0 +1,4 @@
{"timestamp": "2026-04-09T20:07:30.617741+00:00", "path": "/logs/logs", "headers": {"Host": "opa-log-receiver:8282", "User-Agent": "Open Policy Agent/1.15.2 (linux, amd64)", "Content-Length": "401", "Content-Encoding": "gzip", "Content-Type": "application/json", "Accept-Encoding": "gzip"}, "body": [{"labels": {"app": "croplogic-authz", "id": "c211530e-d6bb-4067-abed-57fe193b6e5b", "version": "1.15.2"}, "decision_id": "3ee2fa07-ce00-4c79-9782-76edae020652", "path": "croplogic/authz/batch_decision", "input": {"action": "view", "features": ["feature1", "feature2", "feature3"]}, "result": {"features": {"feature1": {"allow": true, "allow_rules": [], "deny_rules": [], "matched_rules": []}, "feature2": {"allow": true, "allow_rules": [], "deny_rules": [], "matched_rules": []}, "feature3": {"allow": true, "allow_rules": [], "deny_rules": [], "matched_rules": []}}}, "requested_by": "172.29.0.1:59682", "timestamp": "2026-04-09T20:07:29.762128957Z", "metrics": {"counter_server_query_cache_hit": 0, "timer_rego_input_parse_ns": 57527, "timer_rego_query_compile_ns": 93329, "timer_rego_query_eval_ns": 199196, "timer_server_handler_ns": 471175}, "req_id": 1}]}
{"timestamp": "2026-04-09T20:08:21.624001+00:00", "path": "/logs/logs", "headers": {"Host": "opa-log-receiver:8282", "User-Agent": "Open Policy Agent/1.15.2 (linux, amd64)", "Content-Length": "544", "Content-Encoding": "gzip", "Content-Type": "application/json", "Accept-Encoding": "gzip"}, "body": [{"labels": {"app": "croplogic-authz", "id": "c211530e-d6bb-4067-abed-57fe193b6e5b", "version": "1.15.2"}, "decision_id": "b6ca9264-4576-4826-ac70-067ad6850019", "path": "croplogic/authz/batch_decision", "input": {"action": "view", "features": ["farm_management"], "resource": {"crop_types": [], "cultivation_types": [], "customization": [], "farm_id": null, "farm_types": [], "power_sensor": [], "sensor_codes": [], "subscription_plan_codes": []}, "route": "/api/farm-hub/", "user": {"email": "admin@example.com", "id": 1, "is_staff": true, "is_superuser": true, "phone_number": "0912345678", "role": "farmer", "username": "admin"}}, "result": {"features": {"farm_management": {"allow": true, "allow_rules": [], "deny_rules": [], "matched_rules": []}}}, "requested_by": "172.29.0.6:35524", "timestamp": "2026-04-09T20:08:19.762624801Z", "metrics": {"counter_server_query_cache_hit": 1, "timer_rego_input_parse_ns": 71833, "timer_rego_query_eval_ns": 127252, "timer_server_handler_ns": 231704}, "req_id": 2}]}
{"timestamp": "2026-04-09T20:08:43.385941+00:00", "path": "/logs/logs", "headers": {"Host": "opa-log-receiver:8282", "User-Agent": "Open Policy Agent/1.15.2 (linux, amd64)", "Content-Length": "621", "Content-Encoding": "gzip", "Content-Type": "application/json", "Accept-Encoding": "gzip"}, "body": [{"labels": {"app": "croplogic-authz", "id": "c211530e-d6bb-4067-abed-57fe193b6e5b", "version": "1.15.2"}, "decision_id": "b52a1754-aaf8-4c7d-9bfb-1d25d803f007", "path": "croplogic/authz/batch_decision", "input": {"action": "view", "features": ["farm_dashboard"], "resource": {"crop_types": ["\u0630\u0631\u062a", "\u06af\u0646\u062f\u0645"], "cultivation_types": [], "customization": [], "farm_id": "11111111-1111-1111-1111-111111111111", "farm_types": ["\u0632\u0631\u0627\u0639\u06cc"], "power_sensor": ["solar"], "sensor_codes": ["sensor_7_soil_moisture_sensor_v1_2"], "subscription_plan_codes": []}, "route": "/api/farm-dashboard/", "user": {"email": "admin@example.com", "id": 1, "is_staff": true, "is_superuser": true, "phone_number": "0912345678", "role": "farmer", "username": "admin"}}, "result": {"features": {"farm_dashboard": {"allow": true, "allow_rules": [], "deny_rules": [], "matched_rules": []}}}, "requested_by": "172.29.0.6:41130", "timestamp": "2026-04-09T20:08:43.104063998Z", "metrics": {"counter_server_query_cache_hit": 1, "timer_rego_input_parse_ns": 83718, "timer_rego_query_eval_ns": 141627, "timer_server_handler_ns": 263982}, "req_id": 3}]}
{"timestamp": "2026-04-09T20:12:48.961473+00:00", "path": "/logs/logs", "headers": {"Host": "opa-log-receiver:8282", "User-Agent": "Open Policy Agent/1.15.2 (linux, amd64)", "Content-Length": "625", "Content-Encoding": "gzip", "Content-Type": "application/json", "Accept-Encoding": "gzip"}, "body": [{"labels": {"app": "croplogic-authz", "id": "c211530e-d6bb-4067-abed-57fe193b6e5b", "version": "1.15.2"}, "decision_id": "98adca8f-68fb-47d6-8162-59c2cb83d18b", "path": "croplogic/authz/batch_decision", "input": {"action": "view", "features": ["farm_management"], "resource": {"crop_types": ["\u0630\u0631\u062a", "\u06af\u0646\u062f\u0645"], "cultivation_types": [], "customization": [], "farm_id": "11111111-1111-1111-1111-111111111111", "farm_types": ["\u0632\u0631\u0627\u0639\u06cc"], "power_sensor": ["solar"], "sensor_codes": ["sensor_7_soil_moisture_sensor_v1_2"], "subscription_plan_codes": []}, "route": "/api/farm-hub/11111111-1111-1111-1111-111111111111/", "user": {"email": "admin@example.com", "id": 1, "is_staff": true, "is_superuser": true, "phone_number": "0912345678", "role": "farmer", "username": "admin"}}, "result": {"features": {"farm_management": {"allow": true, "allow_rules": [], "deny_rules": [], "matched_rules": []}}}, "requested_by": "172.29.0.6:46450", "timestamp": "2026-04-09T20:12:47.941138139Z", "metrics": {"counter_server_query_cache_hit": 1, "timer_rego_input_parse_ns": 97369, "timer_rego_query_eval_ns": 181108, "timer_server_handler_ns": 317548}, "req_id": 4}]}
Binary file not shown.
+44
View File
@@ -0,0 +1,44 @@
import json
import os
import gzip
from datetime import datetime, timezone
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
LOG_FILE = os.environ.get("OPA_REQUEST_LOG_FILE", "/logs/opa.log")
PORT = int(os.environ.get("OPA_REQUEST_LOG_PORT", "8282"))
class DecisionLogHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers.get("Content-Length", "0"))
raw_payload = self.rfile.read(content_length) if content_length else b""
content_encoding = self.headers.get("Content-Encoding", "").lower()
if content_encoding == "gzip" and raw_payload:
raw_payload = gzip.decompress(raw_payload)
payload = raw_payload.decode("utf-8") if raw_payload else ""
entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"path": self.path,
"headers": dict(self.headers.items()),
"body": json.loads(payload) if payload else None,
}
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
with open(LOG_FILE, "a", encoding="utf-8") as log_file:
log_file.write(json.dumps(entry, ensure_ascii=True) + "\n")
self.send_response(200)
self.end_headers()
self.wfile.write(b"ok")
def log_message(self, format, *args):
return
if __name__ == "__main__":
server = ThreadingHTTPServer(("0.0.0.0", PORT), DecisionLogHandler)
server.serve_forever()