From 79e776ebe7822f09d330721912be1eff07cb08d9 Mon Sep 17 00:00:00 2001 From: Schnitz <12687466+CptSchnitz@users.noreply.github.com> Date: Sun, 14 Jun 2026 09:38:28 +0300 Subject: [PATCH 1/4] chore(global): started working on opa lint --- .regal/config.yaml | 9 +++ .vscode/settings.json | 4 +- example/README.md | 11 --- example/nginx/Dockerfile | 4 -- example/nginx/auth.js | 70 ------------------- example/nginx/default.conf | 34 --------- example/nginx/nginx.conf | 34 --------- example/opa.yaml | 8 --- policy/.gitignore | 2 + policy/Makefile | 19 +++++ {example/data => policy}/data.json | 0 {example/data => policy}/logs.rego | 0 {example/data => policy}/policy.rego | 60 ++++++++-------- {example/data => policy}/policy_test.rego | 0 .../possibleLocations.template | 0 {example/data => policy}/users.template | 0 16 files changed, 62 insertions(+), 193 deletions(-) create mode 100644 .regal/config.yaml delete mode 100644 example/README.md delete mode 100644 example/nginx/Dockerfile delete mode 100644 example/nginx/auth.js delete mode 100644 example/nginx/default.conf delete mode 100644 example/nginx/nginx.conf delete mode 100644 example/opa.yaml create mode 100644 policy/.gitignore create mode 100644 policy/Makefile rename {example/data => policy}/data.json (100%) rename {example/data => policy}/logs.rego (100%) rename {example/data => policy}/policy.rego (59%) rename {example/data => policy}/policy_test.rego (100%) rename {example/data => policy}/possibleLocations.template (100%) rename {example/data => policy}/users.template (100%) diff --git a/.regal/config.yaml b/.regal/config.yaml new file mode 100644 index 00000000..a96e5677 --- /dev/null +++ b/.regal/config.yaml @@ -0,0 +1,9 @@ +rules: + imports: + unresolved-reference: + level: error + except-paths: + - data.keys + - data.possibleLocations + - data.users + - data.keys.kid diff --git a/.vscode/settings.json b/.vscode/settings.json index 9121c9f0..cdff7df8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,7 @@ ], "[github-actions-workflow]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "opa.dependency_paths.opa": "${workspaceFolder}/policy/opa", + "opa.dependency_paths.regal": "${workspaceFolder}/policy/regal" } diff --git a/example/README.md b/example/README.md deleted file mode 100644 index 1945d060..00000000 --- a/example/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# End to end example - -_Before starting make sure you have auth-manager and auth-cron deployed._ - -1. Post all the files in the data folder into the auth-manager api. - -2. Deploy OPA cli as a server. `opa run -s -c opa.yaml` - -3. Deploy nginx with the files in the nginx folder. - -4. Make requests to OPA and check if they are authenticated or not. diff --git a/example/nginx/Dockerfile b/example/nginx/Dockerfile deleted file mode 100644 index e06f2daf..00000000 --- a/example/nginx/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM nginx -COPY *.js /etc/nginx/ -COPY nginx.conf /etc/nginx/nginx.conf -COPY default.conf /etc/nginx/conf.d/ diff --git a/example/nginx/auth.js b/example/nginx/auth.js deleted file mode 100644 index e6a93351..00000000 --- a/example/nginx/auth.js +++ /dev/null @@ -1,70 +0,0 @@ -import qs from 'querystring'; - -async function opaAuth(r) { - try { - if (r.variables.original_method == 'OPTIONS') { - return r.return(204); - } - - const body = { - input: { - method: r.variables.original_method, - headers: r.headersIn, - // uri: r.variables, - // host: r.variables, - query: qs.parse(r.variables.original_args), - domain: '', - }, - }; - - const response = await r.subrequest('/opa', { - body: JSON.stringify(body), - method: 'POST', - }); - - if (response.status > 500) { - return r.return(response.status); - } - - const opaResult = JSON.parse(response.responseText).result; - if (!opaResult.allowed) { - r.error(opaResult.reason); - const returnCode = opaResult.reason.includes('no token supplied') ? 401 : 403; - - return r.return(returnCode); - } - - r.return(204); - } catch (error) { - r.error(error); - r.return(500); - } -} - -function jwt(data) { - if (data) { - var parts = data - .split('.') - .slice(0, 2) - .map((v) => Buffer.from(v, 'base64url').toString()) - .map(JSON.parse); - return { headers: parts[0], payload: parts[1] }; - } else { - return; - } -} - -function jwt_payload_sub(r) { - try { - let token; - if (r.args['token']) token = jwt(r.args['token']); - else if (r.headersIn['x-api-key']) token = jwt(r.headersIn['x-api-key']); - else return ''; - - return token.payload.sub; - } catch (error) { - return ''; - } -} - -export default { opaAuth, jwt_payload_sub }; diff --git a/example/nginx/default.conf b/example/nginx/default.conf deleted file mode 100644 index 30ca7d70..00000000 --- a/example/nginx/default.conf +++ /dev/null @@ -1,34 +0,0 @@ -server { - listen 8080; - server_name localhost; - - js_set $jwt_payload_sub auth.jwt_payload_sub; - - location / { - set $original_method $request_method; - set $original_args $args; - - auth_request /_validate_jwt; - - root /usr/share/nginx/html; - index index.html index.htm; - } - - location = /_validate_jwt { - internal; - - js_content auth.opaAuth; - } - - location = /opa { - internal; - - proxy_set_header Content-Type application/json; - proxy_pass http://localhost:8181/v1/data/http/authz/decision; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } -} \ No newline at end of file diff --git a/example/nginx/nginx.conf b/example/nginx/nginx.conf deleted file mode 100644 index 80213fd5..00000000 --- a/example/nginx/nginx.conf +++ /dev/null @@ -1,34 +0,0 @@ -#user nginx; -load_module modules/ngx_http_js_module.so; -worker_processes 4; - -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - - -events { - worker_connections 1024; -} - - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - js_import auth from auth.js; - - log_format main '$jwt_payload_sub $remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for" $request_time $upstream_response_time $pipe '; - - access_log /var/log/nginx/access.log main; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - #gzip on; - - include /etc/nginx/conf.d/*.conf; -} \ No newline at end of file diff --git a/example/opa.yaml b/example/opa.yaml deleted file mode 100644 index 9ae796ee..00000000 --- a/example/opa.yaml +++ /dev/null @@ -1,8 +0,0 @@ -services: - bundleStore: - url: http://127.0.0.1:5500 - -bundles: - authz: - service: bundleStore - resource: /bundle.tar.gz diff --git a/policy/.gitignore b/policy/.gitignore new file mode 100644 index 00000000..d2370b4b --- /dev/null +++ b/policy/.gitignore @@ -0,0 +1,2 @@ +regal +opa diff --git a/policy/Makefile b/policy/Makefile new file mode 100644 index 00000000..85caa785 --- /dev/null +++ b/policy/Makefile @@ -0,0 +1,19 @@ +.PHONY: lint test check all + +all: lint check test + +lint: + ./regal lint . + +check: + ./opa check . --strict + +test: + ./opa test . -v + +init: + curl -L -o regal https://github.com/open-policy-agent/regal/releases/latest/download/regal_Linux_x86_64 + chmod 755 ./regal + curl -L -o opa https://openpolicyagent.org/downloads/v1.17.1/opa_linux_amd64_static + chmod 755 ./opa + diff --git a/example/data/data.json b/policy/data.json similarity index 100% rename from example/data/data.json rename to policy/data.json diff --git a/example/data/logs.rego b/policy/logs.rego similarity index 100% rename from example/data/logs.rego rename to policy/logs.rego diff --git a/example/data/policy.rego b/policy/policy.rego similarity index 59% rename from example/data/policy.rego rename to policy/policy.rego index 2557aff4..8750fe29 100644 --- a/example/data/policy.rego +++ b/policy/policy.rego @@ -1,12 +1,11 @@ package http.authz -lower_object_keys(obj) := newObj if { - newObj := {k: v | +lower_object_keys(obj) := {k: v | some i v := obj[i] k := lower(i) } -} + constraints := { "cert": json.marshal({"keys": [data.keys]}), @@ -17,8 +16,8 @@ headers := lower_object_keys(input.headers) tokens := [token | keys := data.possibleLocations[_] - lowerObj := lower_object_keys(object.get(input, keys[0], "")) - temp := object.get(lowerObj, keys[1], null) + lower_obj := lower_object_keys(object.get(input, keys[0], "")) + temp := object.get(lower_obj, keys[1], null) temp != null token := temp ] @@ -27,15 +26,15 @@ claims := {"payload": payload, "valid": valid, "kid": kid} if { token := tokens[_] [header, payload, _] := io.jwt.decode(token) kid := object.get(header, "kid", null) # Extract kid from JWT header - [valid, _, _] := io.jwt.decode_verify(token, constraints) + [valid, _, _] := io.jwt.decode_verify(token, constraints) } -userData := {"domains": ["raster"], "allowNoOrigin": true, "allowNoBrowser": true, "origins": []} if { - claims.payload.sub == "c2b" +user_data := {"domains": ["raster"], "allowNoOrigin": true, "allowNoBrowser": true, "origins": []} if { + claims.payload.sub == "c2b" } -userData := data.users[claims.payload.sub] if { - claims.payload.sub != "c2b" +user_data := data.users[claims.payload.sub] if { + claims.payload.sub != "c2b" } deny contains "no token supplied in any of the possible locations" if { @@ -43,7 +42,7 @@ deny contains "no token supplied in any of the possible locations" if { } deny contains "token environment mismatch" if { - claims.kid != data.keys.kid + claims.kid != data.keys.kid } deny contains "token not valid" if { @@ -52,8 +51,8 @@ deny contains "token not valid" if { } deny contains "the token is valid, but the user is not found" if { - claims.valid - not userData + claims.valid + not user_data } deny contains "domain missing" if { @@ -61,7 +60,7 @@ deny contains "domain missing" if { } deny contains "domain check failed" if { - every domain in userData.domains { domain != input.domain } + every domain in user_data.domains { domain != input.domain } } is_origin_invalid(originHeader, allowedOrigin) if { @@ -69,42 +68,41 @@ is_origin_invalid(originHeader, allowedOrigin) if { not glob.match(allowedOrigin, [], originHeader) } - deny contains "origin check failed" if { originHeader := object.get(headers, "origin", "A") - every origin in userData.origins { is_origin_invalid(originHeader, origin) } + every origin in user_data.origins { is_origin_invalid(originHeader, origin) } - not userData.allowNoOrigin - claims.payload.sub != "c2b" + not user_data.allowNoOrigin + claims.payload.sub != "c2b" object.get(headers, "sec-fetch-site", "avi") != "same-origin" } -need_user_agent := true if { - not userData.allowNoBrowser +need_user_agent if { + not user_data.allowNoBrowser } -need_user_agent := true if { - claims.payload.sub == "c2b" +need_user_agent if { + claims.payload.sub == "c2b" } - deny contains "user-agent missing" if { - not headers["user-agent"] - need_user_agent + not headers["user-agent"] + need_user_agent } deny contains "user-agent is not from allowed browsers" if { - not userData.allowNoBrowser - not regex.match(".*(Gecko|AppleWebKit|Opera|Trident|Edge|Chrome)\\/\\d.*$", headers["user-agent"]) + not user_data.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 { - userAgent := lower(headers["user-agent"]) - claims.payload.sub == "c2b" - not contains(userAgent, "qgis") - not contains(userAgent, "arcgis") + + user_agent := lower(headers["user-agent"]) + + not contains(user_agent, "qgis") + not contains(user_agent, "arcgis") } decision := {"allowed": true, "sub": claims.payload.sub, "kid": claims.kid} if { diff --git a/example/data/policy_test.rego b/policy/policy_test.rego similarity index 100% rename from example/data/policy_test.rego rename to policy/policy_test.rego diff --git a/example/data/possibleLocations.template b/policy/possibleLocations.template similarity index 100% rename from example/data/possibleLocations.template rename to policy/possibleLocations.template diff --git a/example/data/users.template b/policy/users.template similarity index 100% rename from example/data/users.template rename to policy/users.template From 5e87b2f926a4e4849a68453d0da4ad1b60af962f Mon Sep 17 00:00:00 2001 From: Schnitz <12687466+CptSchnitz@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:15:55 +0300 Subject: [PATCH 2/4] chore(global): done --- .github/workflows/pull_request.yaml | 23 ++++++ .regal/config.yaml | 8 ++ policy/Makefile | 2 +- policy/logs.rego | 3 +- policy/policy.rego | 54 ++++++------ policy/policy_test.rego | 124 ++++++++++++++-------------- 6 files changed, 124 insertions(+), 90 deletions(-) diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 9e06ba19..53247e6a 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -85,3 +85,26 @@ jobs: - name: Run knip run: pnpm run knip + lint-rego: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: open-policy-agent/setup-regal@v2.0.0 + with: + version: 0.41.1 + + - name: Lint + run: regal lint --format=github ./policy + test-rego: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v6 + + - name: Setup OPA + uses: open-policy-agent/setup-opa@v2 + with: + version: 1.17.1 + + - name: Run OPA Tests + run: opa test ./policy -v diff --git a/.regal/config.yaml b/.regal/config.yaml index a96e5677..b55ace5a 100644 --- a/.regal/config.yaml +++ b/.regal/config.yaml @@ -7,3 +7,11 @@ rules: - data.possibleLocations - data.users - data.keys.kid + - data.users.avi.allowNoBrowser + - data.users.avi.allowNoOrigin + style: + line-length: + level: ignore + idiomatic: + directory-package-mismatch: + level: ignore diff --git a/policy/Makefile b/policy/Makefile index 85caa785..a9e5c2b0 100644 --- a/policy/Makefile +++ b/policy/Makefile @@ -12,7 +12,7 @@ test: ./opa test . -v init: - curl -L -o regal https://github.com/open-policy-agent/regal/releases/latest/download/regal_Linux_x86_64 + curl -L -o regal https://github.com/open-policy-agent/regal/releases/download/v0.41.1/regal_Linux_x86_64 chmod 755 ./regal curl -L -o opa https://openpolicyagent.org/downloads/v1.17.1/opa_linux_amd64_static chmod 755 ./opa diff --git a/policy/logs.rego b/policy/logs.rego index 9752d25e..51b9eaea 100644 --- a/policy/logs.rego +++ b/policy/logs.rego @@ -1,7 +1,8 @@ package system.log # Mask the 'token' field in the query parameters -mask contains "/input/query/token" +mask contains "/input/query/token" + # Mask the 'x-api-key' field in the headers parameters mask contains "/input/headers/x-api-key" diff --git a/policy/policy.rego b/policy/policy.rego index 8750fe29..24c846f7 100644 --- a/policy/policy.rego +++ b/policy/policy.rego @@ -1,11 +1,10 @@ package http.authz -lower_object_keys(obj) := {k: v | - some i - v := obj[i] - k := lower(i) - } - +lower_object_keys(obj) := {k: v | + some i + v := obj[i] + k := lower(i) +} constraints := { "cert": json.marshal({"keys": [data.keys]}), @@ -15,15 +14,16 @@ constraints := { headers := lower_object_keys(input.headers) tokens := [token | - keys := data.possibleLocations[_] - lower_obj := lower_object_keys(object.get(input, keys[0], "")) - temp := object.get(lower_obj, keys[1], null) - temp != null - token := temp + # keys := data.possibleLocations[_] + some key in data.possibleLocations + lower_obj := lower_object_keys(object.get(input, key[0], "")) + token := object.get(lower_obj, key[1], null) + token != null ] claims := {"payload": payload, "valid": valid, "kid": kid} if { - token := tokens[_] + # token := tokens[_] + some token in tokens [header, payload, _] := io.jwt.decode(token) kid := object.get(header, "kid", null) # Extract kid from JWT header [valid, _, _] := io.jwt.decode_verify(token, constraints) @@ -37,6 +37,19 @@ user_data := data.users[claims.payload.sub] if { claims.payload.sub != "c2b" } +is_origin_invalid(origin_header, allowed_origin) if { + allowed_origin != origin_header + not glob.match(allowed_origin, [], origin_header) +} + +need_user_agent if { + not user_data.allowNoBrowser +} + +need_user_agent if { + claims.payload.sub == "c2b" +} + deny contains "no token supplied in any of the possible locations" if { count(tokens) == 0 } @@ -63,14 +76,9 @@ deny contains "domain check failed" if { every domain in user_data.domains { domain != input.domain } } -is_origin_invalid(originHeader, allowedOrigin) if { - allowedOrigin != originHeader - not glob.match(allowedOrigin, [], originHeader) -} - deny contains "origin check failed" if { - originHeader := object.get(headers, "origin", "A") - every origin in user_data.origins { is_origin_invalid(originHeader, origin) } + origin_header := object.get(headers, "origin", "A") + every origin in user_data.origins { is_origin_invalid(origin_header, origin) } not user_data.allowNoOrigin claims.payload.sub != "c2b" @@ -78,14 +86,6 @@ deny contains "origin check failed" if { object.get(headers, "sec-fetch-site", "avi") != "same-origin" } -need_user_agent if { - not user_data.allowNoBrowser -} - -need_user_agent if { - claims.payload.sub == "c2b" -} - deny contains "user-agent missing" if { not headers["user-agent"] need_user_agent diff --git a/policy/policy_test.rego b/policy/policy_test.rego index 4c2f8277..98404278 100644 --- a/policy/policy_test.rego +++ b/policy/policy_test.rego @@ -1,4 +1,6 @@ -package http.authz +package http.authz_test + +import data.http.authz private_key_1 := { "kty": "RSA", @@ -43,35 +45,35 @@ postman_agent := "PostmanRuntime/7.32.2" qgis_agent := "Mozilla/5.0 QGIS/34400/Ubuntu 22.04.5 LTS" users := { - "avi": { + "avi": { "allowNoBrowser": false, "allowNoOrigin": false, "domains": ["avi"], "origins": ["https://avi.com"], - }, - "itzik": { - "allowNoBrowser": false, - "allowNoOrigin": false, - "domains": ["avi"], - "origins": ["*.itzik.maps.com"], - }, - "zvika": { - "allowNoBrowser": false, - "allowNoOrigin": false, - "domains": ["avi"], - "origins": ["https://google.com", "*.zvika.maps.com"], - } + }, + "itzik": { + "allowNoBrowser": false, + "allowNoOrigin": false, + "domains": ["avi"], + "origins": ["*.itzik.maps.com"], + }, + "zvika": { + "allowNoBrowser": false, + "allowNoOrigin": false, + "domains": ["avi"], + "origins": ["https://google.com", "*.zvika.maps.com"], + }, } generate_token(key, payload) := token if { - newPayload := json.patch(payload, [{"op": "add", "path": "iss", "value": "mapcolonies-token-cli"}]) + new_payload := json.patch(payload, [{"op": "add", "path": "iss", "value": "mapcolonies-token-cli"}]) token := io.jwt.encode_sign( { "typ": "JWT", "alg": "RS256", - "kid": key.kid, + "kid": key.kid, }, - newPayload, + new_payload, key, ) } @@ -81,33 +83,33 @@ generate_token(key, payload) := token if { # ========================================================= test_deny_no_token if { - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users not res.allowed - res.reason == "no token supplied in any of the possible locations" + res.reason == "no token supplied in any of the possible locations" } test_deny_malformed_token if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": substring(token, 0, 40), "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": substring(token, 0, 40), "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users not res.allowed - - res.reason == "token not valid" + + res.reason == "token not valid" } test_deny_token_signed_with_wrong_key if { token := generate_token(private_key_2, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.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 not res.allowed - contains(res.reason, "token environment mismatch") + contains(res.reason, "token environment mismatch") } # ========================================================= @@ -116,18 +118,18 @@ test_deny_token_signed_with_wrong_key if { test_allow_token_supplied_in_header 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}} + res := authz.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 + with data.users as users res.allowed } test_allow_token_supplied_in_query_param if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "User-Agent": chrome_agent}, "query": {"token": token}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "User-Agent": chrome_agent}, "query": {"token": token}} with data.keys as public_key_1 - with data.users as users + with data.users as users res.allowed } @@ -138,12 +140,12 @@ test_allow_token_supplied_in_query_param if { test_deny_user_not_found if { token := generate_token(private_key_1, {"sub": "nx_user"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.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 not res.allowed - res.reason == "the token is valid, but the user is not found" + res.reason == "the token is valid, but the user is not found" } # ========================================================= @@ -152,22 +154,22 @@ test_deny_user_not_found if { test_deny_domain_missing_from_request if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"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 not res.allowed - contains(res.reason, "domain missing") + contains(res.reason, "domain missing") } test_deny_domain_not_allowed_for_user if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"itzik", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "itzik", "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 not res.allowed - contains(res.reason, "domain check failed") + contains(res.reason, "domain check failed") } # ========================================================= @@ -176,7 +178,7 @@ test_deny_domain_not_allowed_for_user if { test_deny_wrong_origin if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"Origin": "https://avi2.com", "X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi2.com", "X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users @@ -185,7 +187,7 @@ test_deny_wrong_origin if { test_deny_missing_origin if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users @@ -195,7 +197,7 @@ test_deny_missing_origin if { # sec-fetch-site: same-origin bypasses origin header check (browser same-origin requests don't always send Origin) test_allow_same_origin_fetch_site_bypasses_origin_check if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Sec-Fetch-Site": "same-origin"}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Sec-Fetch-Site": "same-origin"}} with data.keys as public_key_1 with data.users as users @@ -204,17 +206,17 @@ test_allow_same_origin_fetch_site_bypasses_origin_check if { test_allow_no_origin_when_user_permits_it if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 - with data.users as users - with data.users.avi.allowNoOrigin as true + with data.users as users + with data.users.avi.allowNoOrigin as true res.allowed } test_allow_wildcard_origin_matching_subdomain if { token := generate_token(private_key_1, {"sub": "itzik"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://meow.itzik.maps.com"}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://meow.itzik.maps.com"}} with data.keys as public_key_1 with data.users as users @@ -223,7 +225,7 @@ test_allow_wildcard_origin_matching_subdomain if { test_deny_wildcard_origin_not_matching_subdomain if { token := generate_token(private_key_1, {"sub": "itzik"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://meow.avi.maps.com"}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://meow.avi.maps.com"}} with data.keys as public_key_1 with data.users as users @@ -232,7 +234,7 @@ test_deny_wildcard_origin_not_matching_subdomain if { test_allow_specific_origin_for_user_with_multiple_origins if { token := generate_token(private_key_1, {"sub": "zvika"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://google.com"}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent, "Origin": "https://google.com"}} with data.keys as public_key_1 with data.users as users @@ -241,7 +243,7 @@ test_allow_specific_origin_for_user_with_multiple_origins if { test_deny_unrecognised_origin_for_user_with_multiple_origins if { token := generate_token(private_key_1, {"sub": "zvika"}) - res := decision with input as {"domain":"avi", "headers": { "User-Agent": chrome_agent, "Origin": "https://ggl.com"}, "query": {"token": token}} + res := authz.decision with input as {"domain": "avi", "headers": {"User-Agent": chrome_agent, "Origin": "https://ggl.com"}, "query": {"token": token}} with data.keys as public_key_1 with data.users as users @@ -250,7 +252,7 @@ test_deny_unrecognised_origin_for_user_with_multiple_origins if { test_deny_origin_matching_wrong_glob_pattern if { token := generate_token(private_key_1, {"sub": "zvika"}) - res := decision with input as {"domain":"avi", "headers": { "User-Agent": chrome_agent, "Origin": "https://avi.bla.zvika.maps.com"}, "query": {"token": token}} + res := authz.decision with input as {"domain": "avi", "headers": {"User-Agent": chrome_agent, "Origin": "https://avi.bla.zvika.maps.com"}, "query": {"token": token}} with data.keys as public_key_1 with data.users as users @@ -263,7 +265,7 @@ test_deny_origin_matching_wrong_glob_pattern if { test_deny_non_browser_user_agent 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": postman_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token, "User-Agent": postman_agent}} with data.keys as public_key_1 with data.users as users @@ -273,7 +275,7 @@ test_deny_non_browser_user_agent if { test_deny_missing_user_agent 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}} + res := authz.decision with input as {"domain": "avi", "headers": {"Origin": "https://avi.com", "X-Api-Key": token}} with data.keys as public_key_1 with data.users as users @@ -283,22 +285,22 @@ test_deny_missing_user_agent if { test_allow_non_browser_user_agent_when_user_permits_it if { token := generate_token(private_key_1, {"sub": "avi"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": postman_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": postman_agent}} with data.keys as public_key_1 with data.users as users - with data.users.avi.allowNoBrowser as true - with data.users.avi.allowNoOrigin as true + with data.users.avi.allowNoBrowser as true + with data.users.avi.allowNoOrigin as true res.allowed } # ========================================================= -# Decision response shape tests +# authz.decision response shape tests # ========================================================= test_allowed_decision_contains_sub 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}} + res := authz.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 @@ -308,7 +310,7 @@ test_allowed_decision_contains_sub if { 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}} + res := authz.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 @@ -322,7 +324,7 @@ test_allowed_decision_contains_kid if { test_allow_c2b_connection_from_qgis if { token := generate_token(private_key_1, {"sub": "c2b"}) - res := decision with input as {"domain":"raster", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} + res := authz.decision with input as {"domain": "raster", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} with data.keys as public_key_1 with data.users as users @@ -331,7 +333,7 @@ test_allow_c2b_connection_from_qgis if { test_allow_c2b_connection_from_arcgis if { token := generate_token(private_key_1, {"sub": "c2b"}) - res := decision with input as {"domain":"raster", "headers": {"X-Api-Key": token, "User-Agent": "ArcGIS Map Viewer/10.9.1"}} + res := authz.decision with input as {"domain": "raster", "headers": {"X-Api-Key": token, "User-Agent": "ArcGIS Map Viewer/10.9.1"}} with data.keys as public_key_1 with data.users as users @@ -340,7 +342,7 @@ test_allow_c2b_connection_from_arcgis if { test_deny_c2b_connection_from_browser if { token := generate_token(private_key_1, {"sub": "c2b"}) - res := decision with input as {"domain":"raster", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} + res := authz.decision with input as {"domain": "raster", "headers": {"X-Api-Key": token, "User-Agent": chrome_agent}} with data.keys as public_key_1 with data.users as users @@ -349,7 +351,7 @@ test_deny_c2b_connection_from_browser if { test_deny_c2b_connection_no_user_agent if { token := generate_token(private_key_1, {"sub": "c2b"}) - res := decision with input as {"domain":"raster", "headers": {"X-Api-Key": token}} + res := authz.decision with input as {"domain": "raster", "headers": {"X-Api-Key": token}} with data.keys as public_key_1 with data.users as users @@ -359,7 +361,7 @@ test_deny_c2b_connection_no_user_agent if { test_deny_c2b_connection_wrong_domain if { token := generate_token(private_key_1, {"sub": "c2b"}) - res := decision with input as {"domain":"avi", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} + res := authz.decision with input as {"domain": "avi", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} with data.keys as public_key_1 with data.users as users @@ -368,8 +370,8 @@ test_deny_c2b_connection_wrong_domain if { } test_deny_c2b_connection_if_token_expired if { - token := generate_token(private_key_1, {"sub": "c2b", "exp": time.now_ns() / 1000000000 - 60}) # 1 minute ago - res := decision with input as {"domain":"raster", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} + token := generate_token(private_key_1, {"sub": "c2b", "exp": (time.now_ns() / 1000000000) - 60}) # 1 minute ago + res := authz.decision with input as {"domain": "raster", "headers": {"X-Api-Key": token, "User-Agent": qgis_agent}} with data.keys as public_key_1 with data.users as users From e6db13b4c3b49fd9cb83ed731763f6e2d3e77df2 Mon Sep 17 00:00:00 2001 From: Schnitz <12687466+CptSchnitz@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:02:42 +0300 Subject: [PATCH 3/4] chore(global): define entrypoint for opa policy --- policy/policy.rego | 2 ++ 1 file changed, 2 insertions(+) diff --git a/policy/policy.rego b/policy/policy.rego index 24c846f7..29723773 100644 --- a/policy/policy.rego +++ b/policy/policy.rego @@ -105,6 +105,8 @@ deny contains "c2b user only allowed from QGIS or ARCGIS" if { not contains(user_agent, "arcgis") } +# METADATA +# entrypoint: true decision := {"allowed": true, "sub": claims.payload.sub, "kid": claims.kid} if { count(deny) == 0 claims.payload.sub != null From 48188d8746db41b973dbbf8ee851b65360bbb50e Mon Sep 17 00:00:00 2001 From: Ofer <12687466+CptSchnitz@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:40:19 +0300 Subject: [PATCH 4/4] chore(global): update policy/Makefile --- policy/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policy/Makefile b/policy/Makefile index a9e5c2b0..44d77278 100644 --- a/policy/Makefile +++ b/policy/Makefile @@ -11,7 +11,7 @@ check: test: ./opa test . -v -init: +init: curl -L -o regal https://github.com/open-policy-agent/regal/releases/download/v0.41.1/regal_Linux_x86_64 chmod 755 ./regal curl -L -o opa https://openpolicyagent.org/downloads/v1.17.1/opa_linux_amd64_static