Menu
Public repository / 2c509452a4982321959914cab8d52b881e8368de
AkurAI-PassVault / scripts/provision-oidc.sh
Latest change 7c78f978fe08927d630148b42144b05c1b01ba99 - feat: add authenticated PassVault UI by Ólafur Búi Ólafsson
#!/usr/bin/env bash
set -euo pipefail
ssh akurai-ec2 'sudo bash -s' <<'REMOTE'
set -euo pipefail
set -a
. /etc/akurai-idp/idp.env
. /etc/akurai-passvault/env
set +a
python3 - <<'PY'
import json
import os
import pathlib
import stat
import tempfile
import urllib.request
ENV_PATH = pathlib.Path("/etc/akurai-passvault/env")
ENTRY_NAME = "AkurAI-PassVault OIDC client"
CALLBACK = "https://akurai-passvault.olibuijr.com/auth/callback"
PREFIX = "AKURAI_PASSVAULT_IDP_"
def read_env(path):
values = {}
for line in path.read_text().splitlines():
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
values[key] = value.strip().strip("'\"")
return values
def request(url, *, token=None, payload=None):
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = "Bearer " + token
data = None if payload is None else json.dumps(payload).encode()
with urllib.request.urlopen(urllib.request.Request(url, data=data, headers=headers), timeout=15) as response:
return json.load(response)
def mcp(token, name, arguments):
response = request(
"http://127.0.0.1:8102/mcp",
token=token,
payload={
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": name, "arguments": arguments},
},
)
if "error" in response:
raise RuntimeError("PassVault MCP operation failed")
return json.loads(response["result"]["content"][0]["text"])
def install_env(client_id, client_secret):
current = ENV_PATH.read_text().splitlines()
keys = {
PREFIX + "ISSUER",
PREFIX + "INTERNAL_URL",
PREFIX + "CLIENT_ID",
PREFIX + "CLIENT_SECRET",
PREFIX + "REDIRECT",
PREFIX + "ALLOW",
}
kept = [line for line in current if line.split("=", 1)[0] not in keys]
kept.extend(
[
PREFIX + "ISSUER=https://auth.olibuijr.com",
PREFIX + "INTERNAL_URL=http://127.0.0.1:3500",
PREFIX + "CLIENT_ID=" + client_id,
PREFIX + "CLIENT_SECRET=" + client_secret,
PREFIX + "REDIRECT=" + CALLBACK,
PREFIX + "ALLOW=olibuijr@olibuijr.com",
]
)
metadata = ENV_PATH.stat()
with tempfile.NamedTemporaryFile("w", dir=ENV_PATH.parent, delete=False) as handle:
handle.write("\n".join(kept) + "\n")
temp_path = pathlib.Path(handle.name)
os.chmod(temp_path, stat.S_IMODE(metadata.st_mode))
os.chown(temp_path, metadata.st_uid, metadata.st_gid)
os.replace(temp_path, ENV_PATH)
values = read_env(ENV_PATH)
if values.get(PREFIX + "CLIENT_ID") and values.get(PREFIX + "CLIENT_SECRET"):
print("PassVault OIDC environment already configured.")
raise SystemExit(0)
vault_token = values["AKURAI_PASSVAULT_TOKEN"]
entries = mcp(vault_token, "search_entries", {"query": ENTRY_NAME})
existing = next((entry for entry in entries if entry.get("name") == ENTRY_NAME), None)
if existing:
credential = mcp(vault_token, "get_entry", {"id": existing["id"]})
client_id = credential["username"]
client_secret = credential["password"]
else:
admin_token = os.environ["IDP_ADMIN_TOKEN"]
tenants = request(
"http://127.0.0.1:3500/admin/tenants",
token=admin_token,
)
if isinstance(tenants, dict):
tenants = tenants.get("data", tenants.get("tenants", []))
tenant = next((item for item in tenants if item.get("domain") == "olibuijr.com"), tenants[0])
client = request(
"http://127.0.0.1:3500/admin/clients",
token=admin_token,
payload={
"name": "AkurAI-PassVault",
"tenant_id": tenant["id"],
"redirect_uris": [CALLBACK],
"grant_types": ["authorization_code", "refresh_token"],
"scopes": ["openid", "profile", "email", "groups"],
"first_party": True,
},
)
client_id = client["id"]
client_secret = client["client_secret"]
mcp(
vault_token,
"create_entry",
{
"name": ENTRY_NAME,
"username": client_id,
"password": client_secret,
"uri": "https://auth.olibuijr.com",
"notes": "Managed OIDC client; callback " + CALLBACK,
"folderId": 14,
},
)
install_env(client_id, client_secret)
client_secret = ""
print("PassVault OIDC environment configured.")
PY
REMOTE