Skip to content
Open
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
8 changes: 6 additions & 2 deletions example/data/logs.rego
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package system.log

# Mask the 'token' field in the query parameters
mask contains "/input/query/token"
mask contains {"op":"upsert","path":"/input/query/token", "value": "**redacted**"} if {
input.input.query.token
}
# Mask the 'x-api-key' field in the headers parameters
mask contains "/input/headers/x-api-key"
mask contains {"op":"upsert","path":"/input/headers/x-api-key", "value": "**redacted**"} if {
input.input.headers["x-api-key"]
}

drop if {
input.result.allowed == true
Expand Down
35 changes: 21 additions & 14 deletions example/data/policy.rego
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ tokens := [token |
token := temp
]

default claims = {"payload": {}}

claims := {"payload": payload, "valid": valid, "kid": kid} if {
token := tokens[_]
[header, payload, _] := io.jwt.decode(token)
Expand All @@ -38,29 +40,29 @@ userData := data.users[claims.payload.sub] if {
claims.payload.sub != "c2b"
}

deny contains "no token supplied in any of the possible locations" if {
deny contains {"reason":"no token supplied in any of the possible locations", "code": "TOKEN_MISSING"} if {
count(tokens) == 0
}

deny contains "token environment mismatch" if {
claims.kid != data.keys.kid
}
# deny contains {"reason":"token environment mismatch", "code": "TOKEN_MISMATCH"} if {
# claims.kid != data.keys.kid
# }

deny contains "token not valid" if {
deny contains {"reason":"token not valid", "code": "TOKEN_INVALID"} if {
not claims.valid
count(tokens) > 0
}

deny contains "the token is valid, but the user is not found" if {
deny contains {"reason":"the token is valid, but the user is not found", "code": "USER_NOT_FOUND"} if {
claims.valid
not userData
}

deny contains "domain missing" if {
deny contains {"reason":"domain missing", "code": "DOMAIN_MISSING"} if {
not input.domain
}

deny contains "domain check failed" if {
deny contains {"reason":"domain not allowed for this user", "code": "DOMAIN_INCORRECT"} if {
every domain in userData.domains { domain != input.domain }
}

Expand All @@ -70,7 +72,7 @@ is_origin_invalid(originHeader, allowedOrigin) if {
}


deny contains "origin check failed" if {
deny contains {"reason":"origin check failed", "code": "ORIGIN_CHECK_FAILED"} if {
originHeader := object.get(headers, "origin", "A")
every origin in userData.origins { is_origin_invalid(originHeader, origin) }

Expand All @@ -89,17 +91,17 @@ need_user_agent := true if {
}


deny contains "user-agent missing" if {
deny contains {"reason":"user-agent missing", "code": "USER_AGENT_MISSING"} if {
not headers["user-agent"]
need_user_agent
}

deny contains "user-agent is not from allowed browsers" if {
deny contains {"reason":"user-agent is not from allowed browsers", "code": "USER_AGENT_NOT_ALLOWED"} if {
not userData.allowNoBrowser
not regex.match(".*(Gecko|AppleWebKit|Opera|Trident|Edge|Chrome)\\/\\d.*$", headers["user-agent"])
}

deny contains "c2b user only allowed from QGIS or ARCGIS" if {
deny contains {"reason":"c2b user only allowed from QGIS or ARCGIS", "code": "C2B_USER_AGENT_NOT_ALLOWED"} if {
userAgent := lower(headers["user-agent"])

claims.payload.sub == "c2b"
Expand All @@ -112,7 +114,12 @@ decision := {"allowed": true, "sub": claims.payload.sub, "kid": claims.kid} if {
claims.payload.sub != null
}

decision := {"allowed": false, "reason": reason} if {
decision := {"allowed": false, "reason": reason, "sub": sub, "codes": codes} if {
count(deny) > 0
reason := concat(", ", deny)

reasons := [d.reason | some d in deny]
codes := [d.code | some d in deny]

reason := concat(", ", reasons)
sub := object.get(claims.payload, "sub", "N/A")
}
13 changes: 2 additions & 11 deletions example/data/policy_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ test_deny_no_token if {
with data.users as users

not res.allowed
res.reason == "no token supplied in any of the possible locations"
print(res)
res.reason == "no token supplied in any of the possible locations"
}

test_deny_malformed_token if {
Expand Down Expand Up @@ -306,16 +307,6 @@ test_allowed_decision_contains_sub if {
res.sub == "avi"
}

test_allowed_decision_contains_kid if {
token := generate_token(private_key_1, {"sub": "avi"})
res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}}
with data.keys as public_key_1
with data.users as users

res.allowed
res.kid == "opa_test_key_1"
}

# =========================================================
# c2b (client-to-backend) tests
# =========================================================
Expand Down
14 changes: 7 additions & 7 deletions helm/Chart.lock
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
dependencies:
- name: opa
repository: file://charts/opa
version: 1.10.0
version: 1.12.0
- name: auth-cron
repository: file://../packages/auth-cron/helm
version: 1.10.0
version: 1.12.0
- name: auth-manager
repository: file://../packages/auth-manager/helm
version: 1.10.0
version: 1.12.0
- name: auth-ui
repository: file://../packages/auth-ui/helm
version: 1.10.0
version: 1.12.0
- name: token-kiosk
repository: file://../packages/token-kiosk/helm
version: 1.10.0
digest: sha256:164eb2c4310304fd4d51860fe117047c570e721db34f97747a7a958bef3a9c7a
generated: "2026-01-11T19:22:51.855858+02:00"
version: 1.12.0
digest: sha256:3ae8431a5a9943c4d5cf1ec92cef60423db6eea085f2f82eabbb6818292da670
generated: "2026-03-18T15:30:39.641484226+02:00"
7 changes: 5 additions & 2 deletions helm/charts/opa/Chart.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ dependencies:
- name: mclabels
repository: oci://acrarolibotnonprod.azurecr.io/helm/infra
version: 1.0.1
digest: sha256:a97237cd8966ab9d4f8c0b8dda2ad110fbff5d485da868124fdce2a5dbbfa208
generated: "2025-11-20T10:36:07.259160863+02:00"
- name: fluent-bit
repository: oci://ghcr.io/fluent/helm-charts
version: 0.56.0
digest: sha256:cc01fd5ba8b22052738e6b6baec3816b5510a09837c44957ef9dca66212e3df8
generated: "2026-03-17T09:10:00.789970582+02:00"
5 changes: 5 additions & 0 deletions helm/charts/opa/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ dependencies:
- name: mclabels
version: 1.0.1
repository: oci://acrarolibotnonprod.azurecr.io/helm/infra
- name: fluent-bit
repository: oci://ghcr.io/fluent/helm-charts
version: 0.56.0
condition: fluent-bit.enabled
alias: fluentbit
13 changes: 11 additions & 2 deletions helm/charts/opa/config/opa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ services:
s3_signing:
# Required for finding the envs for S3
environment_credentials: {}

{{- if .Values.fluentbit.enabled }}
fluent-bit:
url: {{ .Values.fluentbit.url | default (printf "http://%s-fluentbit:9880" .Release.Name)}}
{{- end }}
bundles:
authz:
service: s3
Expand All @@ -21,8 +24,13 @@ status:

decision_logs:
console: {{ .Values.decisionLogs.console }}
{{- if .Values.fluentbit.enabled }}
service: fluent-bit
{{- end }}
reporting:
buffer_size_limit_bytes: {{ .Values.decisionLogs.maxBufferSize }}
buffer_size_limit_bytes: {{ .Values.decisionLogs.maxBufferSize | int }}
min_delay_seconds: {{ .Values.decisionLogs.minDelaySeconds | int }}
max_delay_seconds: {{ .Values.decisionLogs.maxDelaySeconds | int }}

{{ if .Values.tracing.enabled }}
distributed_tracing:
Expand All @@ -40,6 +48,7 @@ storage:


labels:
environment: {{ .Values.opaEnvironment | required "opaEnvironment is required" | quote }}
{{- range $key, $value := .Values.labels }}
{{ $key }}: {{ $value | quote }}
{{- end }}
2 changes: 0 additions & 2 deletions helm/charts/opa/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ spec:
{{- end }}
annotations:
{{ include "mclabels.annotations" . | nindent 8 }}
{{- if .Values.resetOnConfigChange }}
checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- end }}
{{- if .Values.additionalPodAnnotations }}
{{- toYaml .Values.additionalPodAnnotations | nindent 8}}
{{- end }}
Expand Down
141 changes: 141 additions & 0 deletions helm/charts/opa/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ status:
decisionLogs:
console: true
maxBufferSize: 5242880 # 5MB
min_delay_seconds: 5
max_delay_seconds: 60

tracing:
enabled: false
Expand Down Expand Up @@ -135,3 +137,142 @@ ingress:

# Add any label you want as `KEY: VALUE`
labels: {}

fluentbit:
enabled: true
kind: Deployment
testFramework:
enabled: false
imagePullSecrets:
- name: my-registry-secret
serviceAccount:
create: false
rbac:
create: false
openshift:
enabled: true
resources:
limits:
cpu: 400m
memory: 256Mi
requests:
cpu: 100m
memory: 256Mi
autoscaling:
enabled: true
labels:
mapcolonies.io/environment: dev
mapcolonies.io/component: "infrastructure"
mapcolonies.io/part-of: "monitoring"
mapcolonies.io/owner: "infra"
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "2020"
prometheus.io/path: "/api/v1/metrics/prometheus"
loki:
url: loki.namespace.svc.cluster.local
port: "3100"
envWithTpl:
- name: FLUENT_LOKI_HOST
value: "{{ .Values.loki.url }}"
- name: FLUENT_LOKI_PORT
value: "{{ .Values.loki.port }}"
extraPorts:
- name: input
port: 9880
containerPort: 9880
protocol: TCP
luaScripts:
transform.lua: |
function transform_opa_log(tag, timestamp, record)
local new_record = {}
-- 1. Input and Bundles
new_record["input"] = record["input"]
new_record["bundles"] = record["bundles"]

-- 2. Trace & Span IDs
new_record["trace_id"] = record["trace_id"]
new_record["span_id"] = record["span_id"]

-- 3. Extract Environment and Domain
if record["labels"] ~= nil then
new_record["env"] = record["labels"]["environment"]
end

new_record["opa_domain"] = record["input"]["domain"]


new_record["token_location"] = "missing"
if record["input"]["headers"] ~= nil and record["input"]["headers"]["x-api-key"] ~= nil then
new_record["token_location"] = "header"
end

if record["input"]["query"] ~= nil and record["input"]["query"]["token"] ~= nil then
if new_record["token_location"] == "header" then
new_record["token_location"] = "both"
else
new_record["token_location"] = "query"
end
end

if record["input"]["headers"] ~= nil then
if record["input"]["headers"]["origin"] ~= nil then
new_record["origin"] = record["input"]["headers"]["origin"]
end
if record["input"]["headers"]["user-agent"] ~= nil then
new_record["user_agent"] = record["input"]["headers"]["user-agent"]
end
end

-- 4. Requested By
new_record["requested_by"] = record["requested_by"]

-- 5. Result splits
if record["result"] ~= nil then
-- Loki metadata requires strings, so we cast the boolean
new_record["allowed"] = tostring(record["result"]["allowed"])
new_record["reason"] = record["result"]["reason"]
new_record["sub"] = record["result"]["sub"]
new_record["codes"] = record["result"]["codes"]
end

-- 6. The original OPA timestamp string
new_record["opa_timestamp"] = record["timestamp"]

-- 7. Total processing time
if record["metrics"] ~= nil then
new_record["total_time_ns"] = tostring(record["metrics"]["timer_server_handler_ns"])
end

-- We return the Fluent Bit ingestion timestamp as the official log time.
return 1, timestamp, new_record
end

config:
inputs: |
[INPUT]
Name http
Listen 0.0.0.0
Port 9880
filters: |
[FILTER]
Name lua
Match *
script /fluent-bit/scripts/transform.lua
call transform_opa_log
outputs: |
[OUTPUT]
Name loki
Match *
Host ${FLUENT_LOKI_HOST}
Port ${FLUENT_LOKI_PORT}

# LABELS: Domain added here for fast stream routing
Labels job=opa, environment=$env, opa_domain=$opa_domain

# METADATA: Allowed moved here, along with other high-cardinality data
Structured_metadata trace_id=$trace_id, span_id=$span_id, requested_by=$requested_by, reason=$reason, subject=$sub, total_time_ns=$total_time_ns, allowed=$allowed, token_location=$token_location, origin=$origin, user_agent=$user_agent

# CLEANUP: Strip these fields from the main JSON log body
Remove_Keys env, domain, allowed, trace_id, span_id, requested_by, reason, sub, total_time_ns, opa_domain, token_location, origin, user_agent

Loading