Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions app/permissions_engine/vault.rego
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ test := "test" if {

else := "opa"

# TODO: need to pass the 'X-Vault-Namespace' header to the vault requests
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not super familiar with rego, so I would like to know your opinion on the best way to do this @daisieh .
The name of the Vault namespace will be set as an environment variable VAULT_NAMESPACE in the container.
When the env var is present, we simply need to add its value in the X-Vault-Namespace header.


# 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

Expand Down
25 changes: 13 additions & 12 deletions app/refresh_stores.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
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:
Expand All @@ -13,16 +12,18 @@
"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)}\"}}"
response = requests.put(url=f"{os.getenv('OPA_URL')}/v1/data/test_token", headers=headers, data=payload)
print(response.text)

# 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)

# Test Role-ID
test_role_id = get_secret_file_value_or_env("/home/pcgl/test-roleid", "TEST_ROLEID")
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)

print(reload_comanage())
except Exception as e:
Expand Down
63 changes: 44 additions & 19 deletions app/src/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
## Env vars for most auth methods:
OPA_URL = os.getenv('OPA_URL', "http://localhost:8181")
VAULT_URL = os.getenv('VAULT_URL', "http://localhost:8200")
VAULT_NAMESPACE = os.getenv("VAULT_NAMESPACE", "")
SERVICE_NAME = os.getenv("SERVICE_NAME")
APPROLE_TOKEN_FILE = os.getenv("APPROLE_TOKEN_FILE", "/home/pcgl/approle-token")
VERIFY_ROLE_ID_FILE = "/home/pcgl/verify-roleid"
Expand Down Expand Up @@ -114,6 +115,41 @@ def exchange_refresh_token(refresh_token, client_id=PCGL_CLIENT_ID, client_secre
raise AuthzError(response.text)


def get_secret_file_value_or_env(file_path: str, env_var: str) -> str:
"""
Tries to reads and return the value contained at `file_path` if it exists.\n
Otherwise tries to read the value from the `env_var` environment variable if it exists.\n
Raises an `AuthzError` if neither can be found.
"""
if file_path and os.path.exists(file_path):
with open(file_path, 'r') as f:
secret = f.read().strip()
return secret
if env_var:
return os.getenv(env_var)
raise AuthzError(f"Couldn't read the secret from the {file_path} path or the {env_var} env variable.")


def get_vault_namespace_header() -> dict:
if VAULT_NAMESPACE:
return {
"X-Vault-Namespace": VAULT_NAMESPACE
}
return {}


def get_vault_headers(token: str) -> dict:
"""
Returns authentication headers for Vault API HTTP requests.\n
The `token` argument is passed to the `X-Vault-Token` header.\n
If the `VAULT_NAMESPACE` env var is set, includes its value in the `X-Vault-Namespace` header.
"""
return {
"X-Vault-Token": token,
**get_vault_namespace_header()
}


######
# General authorization methods
######
Expand Down Expand Up @@ -520,16 +556,14 @@ def get_vault_token_for_service(service=SERVICE_NAME, approle_token=None, role_i
raise AuthzError("no SERVICE_NAME specified")
# in CanDIGv2 docker stack, approle token should have been passed in
if approle_token is None:
with open(APPROLE_TOKEN_FILE) as f:
approle_token = f.read().strip()
approle_token = get_secret_file_value_or_env(APPROLE_TOKEN_FILE, "APPROLE_TOKEN")
if approle_token is None:
raise AuthzError("no approle token found")

# in CanDIGv2 docker stack, roleid should have been passed in
if role_id is None:
try:
with open(f"/home/pcgl/{service}-roleid") as f:
role_id = f.read().strip()
role_id = get_secret_file_value_or_env(f"/home/pcgl/{service}-roleid", f"{service.upper()}-ROLEID")
except Exception as e:
raise AuthzError(str(e))
if role_id is None:
Expand All @@ -538,7 +572,7 @@ def get_vault_token_for_service(service=SERVICE_NAME, approle_token=None, role_i
# get the secret_id
if secret_id is None:
url = f"{VAULT_URL}/v1/auth/approle/role/{service}/secret-id"
headers = { "X-Vault-Token": approle_token }
headers = get_vault_headers(token=approle_token)
response = requests.post(url=url, headers=headers)
if response.status_code == 200:
secret_id = response.json()["data"]["secret_id"]
Expand Down Expand Up @@ -573,9 +607,7 @@ def set_service_store_secret(service=SERVICE_NAME, key=None, value=None, role_id
if key is None:
return {"error": "no key specified"}, 400

headers = {
"X-Vault-Token": token
}
headers = get_vault_headers(token=token)
url = f"{VAULT_URL}/v1/{service}/{key}"
if "dict" in str(type(value)):
value = json.dumps(value)
Expand All @@ -599,9 +631,7 @@ def get_service_store_secret(service=SERVICE_NAME, key=None, role_id=None, secre
if key is None:
return {"error": "no key specified"}, 400

headers = {
"X-Vault-Token": token
}
headers = get_vault_headers(token)
url = f"{VAULT_URL}/v1/{service}/{key}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
Expand All @@ -624,9 +654,7 @@ def delete_service_store_secret(service=SERVICE_NAME, key=None, role_id=None, se
if key is None:
return {"error": "no key specified"}, 400

headers = {
"X-Vault-Token": token
}
headers = get_vault_headers(token)
url = f"{VAULT_URL}/v1/{service}/{key}"
response = requests.delete(url, headers=headers)
return response.text, response.status_code
Expand All @@ -639,16 +667,13 @@ def create_service_token(service_uuid):
# this will get us a fresh approle token for this service
role_id = None
try:
with open(VERIFY_ROLE_ID_FILE) as f:
role_id = f.read().strip()
role_id = get_secret_file_value_or_env(VERIFY_ROLE_ID_FILE, "VERIFY_ROLE_ID")
except Exception as e:
raise AuthzError(str(e))

token = get_vault_token_for_service("verify", role_id=role_id)

headers = {
"X-Vault-Token": token
}
headers = get_vault_headers(token)

# create the random service-token:
url = f"{VAULT_URL}/v1/cubbyhole/{token}"
Expand Down