Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4b92db6
refact: support secrets from env vars and include vault namespace header
v-rocheleau Oct 16, 2025
40943f9
format
v-rocheleau Oct 16, 2025
639be74
Fix method call
daisieh Oct 17, 2025
6c05da2
Merge branch 'main' into refact/secrets-env-vars
v-rocheleau Oct 17, 2025
6550644
fix(gh-actions/pr-build): repo lower casefor image name
v-rocheleau Oct 17, 2025
18f4522
fix pr build image name
v-rocheleau Oct 17, 2025
0c38a6b
explicit ghcr image name for consictency
v-rocheleau Oct 17, 2025
a06dd86
build pr images to repo package path
v-rocheleau Oct 17, 2025
69361c6
refact: copy permissions engine files to dir that can be safely shared
v-rocheleau Oct 17, 2025
1de505c
fix permissions engine dir name
v-rocheleau Oct 17, 2025
16de84f
recursive copy of permissions engine files
v-rocheleau Oct 17, 2025
5a9a946
init policies dirs and permissions
v-rocheleau Oct 17, 2025
a5915c7
fix permissions engine path copy
v-rocheleau Oct 17, 2025
c1137ea
fix copying of permissions engine files again
v-rocheleau Oct 17, 2025
607ad69
rm root token usage in renew_token
v-rocheleau Oct 17, 2025
0da6d1f
logging for secret id retrieval debug
v-rocheleau Oct 20, 2025
834b934
debug logs
v-rocheleau Oct 20, 2025
f6c368f
rm debug logs
v-rocheleau Oct 20, 2025
063bf2c
comment out test roleid
v-rocheleau Oct 20, 2025
fbf6d4c
include namespace in token renewal script
v-rocheleau Oct 20, 2025
dff3bfb
add missing vault namespace header
v-rocheleau Oct 20, 2025
4ceee96
refact(rego): customize vault.rego to include namespace headers
v-rocheleau Oct 21, 2025
ee5b7f4
sed vault namespace in entrypoint
v-rocheleau Oct 21, 2025
dcc7787
fix rego syntax err
v-rocheleau Oct 21, 2025
b5c0e3c
rego syntax
v-rocheleau Oct 21, 2025
0aa4ef9
rego syntax fixes
v-rocheleau Oct 21, 2025
5e82a14
more rego syntax fixes
v-rocheleau Oct 21, 2025
ce1fb72
rego syntax fix with parenthesis wrap
v-rocheleau Oct 21, 2025
f043069
rego syntax fix attempt
v-rocheleau Oct 21, 2025
ee6b053
rego syntax fix again
v-rocheleau Oct 21, 2025
2421ec5
sed vault ns
v-rocheleau Oct 21, 2025
cb7a63d
cleanup
v-rocheleau Oct 21, 2025
464eb13
rm redundant authz prefix
v-rocheleau Oct 21, 2025
854c1ee
fix pr build workflow
v-rocheleau Oct 21, 2025
ac8ae29
set default vault namespace for docker compose and tests
v-rocheleau Oct 22, 2025
b763264
tests fix attempt
v-rocheleau Oct 22, 2025
f79e262
change permissions-engine to permissions_engine
daisieh Nov 13, 2025
4daeeab
Update app/src/auth.py
daisieh Nov 13, 2025
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
11 changes: 7 additions & 4 deletions .github/workflows/build-pr-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@ jobs:
id: pr
run: echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV

- name: Prepare image name
run: |
IMAGE_NAME="ghcr.io/${GITHUB_REPOSITORY,,}/authz:pr-${{ github.event.pull_request.number }}"
echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV

- name: Build Docker image
run: |
IMAGE_NAME=ghcr.io/${{ github.repository }}/authz:pr-${PR_NUMBER}
echo "Building image: $IMAGE_NAME"
docker build -t $IMAGE_NAME .

- name: Push Docker image
run: |
IMAGE_NAME=ghcr.io/${{ github.repository }}/authz:pr-${PR_NUMBER}
echo "Pushing image: $IMAGE_NAME"
docker push $IMAGE_NAME

Expand All @@ -48,8 +51,8 @@ jobs:
steps:
- name: Delete Docker image from GHCR
run: |
IMAGE_NAME=ghcr.io/${{ github.repository }}/authz:pr-${{ github.event.pull_request.number }}
IMAGE_NAME="ghcr.io/${GITHUB_REPOSITORY,,}/authz:pr-${{ github.event.pull_request.number }}"
echo "Deleting image: $IMAGE_NAME"
curl -X DELETE \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
"https://ghcr.io/v2/${{ github.repository }}/authz/manifests/pr-${{ github.event.pull_request.number }}"
"https://ghcr.io/${GITHUB_REPOSITORY,,}/authz/manifests/pr-${{ github.event.pull_request.number }}"
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ WORKDIR /vault/
RUN mkdir -p /vault/config
RUN mkdir -p /vault/data
RUN chmod 777 /vault/data

WORKDIR /app/
RUN chown -R pcgl:pcgl /app

USER pcgl
RUN mkdir -p /permissions_engine
RUN chown -R pcgl:pcgl /permissions_engine

WORKDIR /app/
USER pcgl

RUN curl -L -o opa https://openpolicyagent.org/downloads/v1.1.0/opa_linux_amd64_static

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ curl -X "POST" "https://cilogon.org/oauth2/token" \

3. Use the access token in an Authorization header for any of the API calls:
```
curl "http://localhost:1235/authz/group/admin" \
curl "http://localhost:1235/group/admin" \
-H 'Authorization: Bearer <token>'
```

Expand Down
70 changes: 70 additions & 0 deletions app/dev.entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash

# Container entrypoint for local development with root access to a local Vault server.

set -Euo pipefail


if [[ -f "/app/initial_setup" ]]; then

cp -r /app/permissions_engine/* /permissions_engine/
chmod 777 /permissions_engine

mkdir /app/data
chmod 777 /app/data

# set up our default values
sed -i s@PCGL_ADMIN_GROUP@$PCGL_ADMIN_GROUP@ /permissions_engine/calculate.rego

token=$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | base64 | tr -d '\n\r+' | sed s/[^A-Za-z0-9]//g)
echo { \"opa_secret\": \"$token\" } > /permissions_engine/opa_secret.json
# set up vault URL and namespace
sed -i s@VAULT_URL@$VAULT_URL@ /permissions_engine/vault.rego
sed -i s@VAULT_NAMESPACE@$VAULT_NAMESPACE@ /permissions_engine/vault.rego

echo "initializing stores"
python3 /app/initialize_vault_store.py
if [[ $? -eq 0 ]]; then
rm /app/initial_setup
echo "setup complete"
else
echo "!!!!!! INITIALIZATION FAILED, TRY AGAIN !!!!!!"
fi

else
sleep 10
# unseal vault
KEY=$(head -n 2 /app/config/keys.txt | tail -n 1)
echo '{ "key": "'$KEY'" }' > payload.json
curl --request POST --data @payload.json http://vault:8200/v1/sys/unseal
KEY=$(head -n 3 /app/config/keys.txt | tail -n 1)
echo '{ "key": "'$KEY'" }' > payload.json
curl --request POST --data @payload.json http://vault:8200/v1/sys/unseal
KEY=$(head -n 4 /app/config/keys.txt | tail -n 1)
echo '{ "key": "'$KEY'" }' > payload.json
curl --request POST --data @payload.json http://vault:8200/v1/sys/unseal
fi

# make sure that our vault stores have the latest values
python3 /app/refresh_stores.py


# start server
cd /app/src
gunicorn -k uvicorn.workers.UvicornWorker server:app &


while [ 0 -eq 0 ]
do
echo "storing vault token"
date
bash /app/renew_token.sh
python3 /app/refresh_stores.py
if [[ $? -eq 0 ]]; then
echo "vault token stored"
sleep 300
else
echo "vault token not stored"
sleep 30
fi
done
25 changes: 9 additions & 16 deletions app/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ set -Euo pipefail


if [[ -f "/app/initial_setup" ]]; then

cp -r /app/permissions_engine/* /permissions_engine/
chmod 777 /permissions_engine

mkdir /app/data
chmod 777 /app/data

# set up our default values
sed -i s@PCGL_ADMIN_GROUP@$PCGL_ADMIN_GROUP@ /app/permissions_engine/calculate.rego
sed -i s@PCGL_ADMIN_GROUP@$PCGL_ADMIN_GROUP@ /permissions_engine/calculate.rego

token=$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | base64 | tr -d '\n\r+' | sed s/[^A-Za-z0-9]//g)
echo { \"opa_secret\": \"$token\" } > /app/permissions_engine/opa_secret.json
# set up vault URL
sed -i s@VAULT_URL@$VAULT_URL@ /app/permissions_engine/vault.rego
echo { \"opa_secret\": \"$token\" } > /permissions_engine/opa_secret.json
# set up vault URL and namespace
sed -i s@VAULT_URL@$VAULT_URL@ /permissions_engine/vault.rego
sed -i s@VAULT_NAMESPACE@$VAULT_NAMESPACE@ /permissions_engine/vault.rego

echo "initializing stores"
python3 /app/initialize_vault_store.py
Expand All @@ -23,18 +28,6 @@ if [[ -f "/app/initial_setup" ]]; then
else
echo "!!!!!! INITIALIZATION FAILED, TRY AGAIN !!!!!!"
fi
else
sleep 10
# unseal vault
KEY=$(head -n 2 /app/config/keys.txt | tail -n 1)
echo '{ "key": "'$KEY'" }' > payload.json
curl --request POST --data @payload.json http://vault:8200/v1/sys/unseal
KEY=$(head -n 3 /app/config/keys.txt | tail -n 1)
echo '{ "key": "'$KEY'" }' > payload.json
curl --request POST --data @payload.json http://vault:8200/v1/sys/unseal
KEY=$(head -n 4 /app/config/keys.txt | tail -n 1)
echo '{ "key": "'$KEY'" }' > payload.json
curl --request POST --data @payload.json http://vault:8200/v1/sys/unseal
fi

# make sure that our vault stores have the latest values
Expand Down
33 changes: 26 additions & 7 deletions app/permissions_engine/vault.rego
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,39 @@ test := "test" if {

else := "opa"

ns := "VAULT_NAMESPACE"

vault_headers := {"X-Vault-Token": vault_token} if {
ns == ""
}

vault_headers := {"X-Vault-Token": vault_token, "X-Vault-Namespace": ns} if {
ns != ""
}

vault_service_headers := {"X-Vault-Token": input.token} if {
ns == ""
}

vault_service_headers := {"X-Vault-Token": input.token, "X-Vault-Namespace": ns} if {
ns != ""
}


# paths are the paths authorized for methods, used by permissions.rego
paths := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "paths"]), "headers": {"X-Vault-Token": vault_token}}).body.data.paths
paths := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "paths"]), "headers": vault_headers}).body.data.paths

# groups are site-wide authorizations, used by permissions.rego and authz.rego
groups := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "groups"]), "headers": {"X-Vault-Token": vault_token}}).body.data
groups := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "groups"]), "headers": vault_headers}).body.data

all_studies := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "studies"]), "headers": {"X-Vault-Token": vault_token}}).body.data.studies
all_studies := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "studies"]), "headers": vault_headers}).body.data.studies

study_auths[p] := study if {
some p in all_studies
study := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "studies", p]), "headers": {"X-Vault-Token": vault_token}}).body.data[p]
study := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "studies", p]), "headers": vault_headers}).body.data[p]
}

user_index := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "users/index"]), "headers": {"X-Vault-Token": vault_token}, "raise_error": false}).body.data
user_index := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "users/index"]), "headers": vault_headers, "raise_error": false}).body.data

user_id := user_index[data.idp.user_sub] if {
not input.body.user_pcglid
Expand All @@ -39,7 +58,7 @@ user_id := user_index[data.idp.user_sub] if {
else := user_index[input.body.user_pcglid]

# check to see if the user is authorized for any other studies via DACs
user_auth := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "users", user_id]), "headers": {"X-Vault-Token": vault_token}, "raise_error": false})
user_auth := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1", test, "users", user_id]), "headers": vault_headers, "raise_error": false})

user_pcglid := user_auth.body.data.pcglid

Expand All @@ -51,4 +70,4 @@ user_studies := user_auth.body.data.study_authorizations if {

default service := ""
# if there is a service associated with this token:
service := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1/cubbyhole", input.token]), "headers": {"X-Vault-Token": input.token}}).body.data.service
service := http.send({"method": "get", "url": concat("/", ["VAULT_URL/v1/cubbyhole", input.token]), "headers": vault_service_headers}).body.data.service
25 changes: 14 additions & 11 deletions app/refresh_stores.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import json
import os
from src.auth import get_vault_token_for_service, reload_comanage
from src.auth import get_secret_file_value_or_env, get_vault_token_for_service, reload_comanage
import sys
import requests


# get the token for the opa store
try:
with open("/app/permissions_engine/opa_secret.json") as f:
with open("/permissions_engine/opa_secret.json") as f:
opa_json = json.load(f)
headers = {
"X-Opa": opa_json["opa_secret"],
"Content-Type": "application/json; charset=utf-8"
}
with open("/home/pcgl/opa-roleid") as f:
role_id = f.read().strip()
payload = f"{{\"token\": \"{get_vault_token_for_service("opa", role_id=role_id)}\"}}"
response = requests.put(url=f"{os.getenv('OPA_URL')}/v1/data/opa_token", headers=headers, data=payload)
print(response.text)
with open("/home/pcgl/test-roleid") as f:
role_id = f.read().strip()
payload = f"{{\"token\": \"{get_vault_token_for_service("test", role_id=role_id)}\"}}"

# OPA Role-ID
opa_role_id = get_secret_file_value_or_env("/home/pcgl/opa-roleid", "OPA_ROLEID")
payload = f"{{\"token\": \"{get_vault_token_for_service("opa", role_id=opa_role_id)}\"}}"
response = requests.put(url=f"{os.getenv('OPA_URL')}/v1/data/opa_token", headers=headers, data=payload)
print(response.text)

# TODO: can we remove this? Feel that this should be in tests rather than baked in
# Test Role-ID
test_role_id = get_secret_file_value_or_env("/home/pcgl/test-roleid", "TEST_ROLEID")
if test_role_id:
payload = f"{{\"token\": \"{get_vault_token_for_service("test", role_id=test_role_id)}\"}}"
response = requests.put(url=f"{os.getenv('OPA_URL')}/v1/data/test_token", headers=headers, data=payload)
print(response.text)

Expand Down
12 changes: 5 additions & 7 deletions app/renew_token.sh
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
#!/usr/bin/env bash

export VAULT_APPROLE_TOKEN=$(cat /vault/config/approle-token)
export KEY_ROOT=$(tail -n 1 /vault/config/keys.txt)


echo "renewing approle token"
curl --request POST --header "X-Vault-Token: ${VAULT_APPROLE_TOKEN}" $VAULT_URL/v1/auth/token/renew-self > finish.json
curl --request POST \
--header "X-Vault-Token: ${VAULT_APPROLE_TOKEN}" \
--header "X-Vault-Namespace: ${VAULT_NAMESPACE}" \
$VAULT_URL/v1/auth/token/renew-self > finish.json
cat finish.json | jq
grep "error" finish.json
if [[ $? -eq 0 ]]; then
echo "creating approle token"
date
echo "{\"id\": \"${VAULT_APPROLE_TOKEN}\", \"policies\": [\"approle\"], \"periodic\": \"24h\"}" > token.json
curl --request POST --header "X-Vault-Token: ${KEY_ROOT}" --data @token.json $VAULT_URL/v1/auth/token/create/approle > finish.json
echo "Approle token renewal error:"
cat finish.json | jq
fi
rm finish.json
Loading
Loading