Menu
AkurAI-Build
publicLatest change ea08e3ba17523a5b26efdd60506b98131c7ed68b - fix: provision arbitrary app OIDC environments (AKURAI-PLATFORM-39) by Ólafur Búi Ólafsson
#!/usr/bin/env python3
import base64
import json
import os
import secrets
import shlex
import stat
import subprocess
import sys
import tempfile
import textwrap
import urllib.request
from pathlib import Path
if len(sys.argv) not in (6, 7):
raise SystemExit("usage: provision-app-oidc.py NAME REDIRECT_URI ENV_PREFIX ALLOWED_EMAIL PASSVAULT_FOLDER [REMOTE_ENV_FILE]")
name, redirect_uri, prefix, allowed_email, folder_text = sys.argv[1:6]
remote_env = sys.argv[6] if len(sys.argv) == 7 else None
if remote_env is not None and (not remote_env.startswith("/etc/") or "\n" in remote_env):
raise SystemExit("REMOTE_ENV_FILE must be an absolute path below /etc")
folder_id = int(folder_text)
remote = r'''set -euo pipefail
set -a
. /etc/akurai-idp/idp.env
set +a
python3 - "$1" "$2" <<'PY'
import json, os, sys, urllib.request
base="http://127.0.0.1:3500"
token=os.environ["IDP_ADMIN_TOKEN"]
def call(path, method="GET", body=None):
data=None if body is None else json.dumps(body).encode()
req=urllib.request.Request(base+path, data=data, method=method, headers={"Authorization":"Bearer "+token,"Content-Type":"application/json"})
with urllib.request.urlopen(req) as response: return json.load(response)
tenants=call("/admin/tenants")
if isinstance(tenants, dict): tenants=tenants.get("data", tenants.get("tenants", []))
if not tenants: raise SystemExit("no IDP tenant available")
tenant=next((t for t in tenants if t.get("domain")=="olibuijr.com"), tenants[0])
client=call("/admin/clients", "POST", {"name":sys.argv[1],"tenant_id":tenant["id"],"redirect_uris":[sys.argv[2]],"grant_types":["authorization_code","refresh_token"],"scopes":["openid","profile","email","groups"],"first_party":True})
print(json.dumps(client, separators=(",",":")))
PY
'''
cli = str(Path.home()/".local/bin/akurai-ec2")
created = subprocess.run(
[cli, "ssh", shlex.join(["sudo", "bash", "-s", "--", name, redirect_uri])],
input=remote,
text=True,
capture_output=True,
)
if created.returncode:
raise SystemExit(created.stderr.strip().splitlines()[-1] if created.stderr.strip() else "remote OIDC provisioning failed")
client = json.loads(created.stdout)
client_id = client.get("id") or client.get("client_id")
client_secret = client["client_secret"]
vault_env = Path.home() / ".config/omp/akurai-passvault-mcp.env"
if stat.S_IMODE(vault_env.stat().st_mode) != 0o600:
raise SystemExit("PassVault environment must be mode 0600")
values = {}
for line in vault_env.read_text().splitlines():
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
values[key] = value.strip().strip("'\"")
vault_token = values["AKURAI_PASSVAULT_MCP_TOKEN"]
def vault_entry(entry_name, username, password, notes):
payload = {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_entry","arguments":{"name":entry_name,"username":username,"password":password,"uri":"https://auth.olibuijr.com","notes":notes,"folderId":folder_id}}}
request = urllib.request.Request("https://akurai-passvault.olibuijr.com/mcp", data=json.dumps(payload).encode(), headers={"Authorization":"Bearer "+vault_token,"Content-Type":"application/json"})
with urllib.request.urlopen(request) as response:
result = json.load(response)
if "error" in result:
raise RuntimeError("PassVault rejected credential storage")
vault_entry(f"{name} OIDC client", client_id, client_secret, f"Managed OIDC client; callback {redirect_uri}")
if remote_env is None:
mcp_token = secrets.token_urlsafe(48)
vault_entry(f"{name} MCP token", "omp-mcp", mcp_token, "Bearer token for the AkurAI Tasks MCP endpoint")
env_lines = [
f"{prefix}_IDP_ISSUER=https://auth.olibuijr.com",
f"{prefix}_IDP_INTERNAL_URL=http://127.0.0.1:3500",
f"{prefix}_IDP_CLIENT_ID={client_id}",
f"{prefix}_IDP_CLIENT_SECRET={client_secret}",
f"{prefix}_IDP_REDIRECT={redirect_uri}",
f"{prefix}_IDP_ALLOW={allowed_email}",
f"{prefix}_ALLOWED_ORIGIN=https://akurai-tasks.olibuijr.com",
f"{prefix}_MCP_TOKEN={mcp_token}",
]
remote_env = "/etc/akurai-tasks/akurai-tasks.env"
else:
proxy_secret = secrets.token_urlsafe(48)
vault_entry(f"{name} trusted proxy secret", "platform-proxy", proxy_secret, "HMAC secret for signed identity headers")
jwks = json.load(urllib.request.urlopen("https://auth.olibuijr.com/jwks"))
signing_key = next(key for key in jwks["keys"] if key.get("alg") == "EdDSA" and key.get("crv") == "Ed25519")
raw_key = base64.urlsafe_b64decode(signing_key["x"] + "==")
der_key = bytes.fromhex("302a300506032b6570032100") + raw_key
public_key = "-----BEGIN PUBLIC KEY-----\n" + "\n".join(textwrap.wrap(base64.b64encode(der_key).decode(), 64)) + "\n-----END PUBLIC KEY-----\n"
public_url = redirect_uri.removesuffix("/auth/callback")
env_lines = [
f"{prefix}_PUBLIC_URL={public_url}",
f"{prefix}_TRUSTED_PROXY_SECRET={proxy_secret}",
f"{prefix}_IDP_PUBLIC_KEY={public_key.replace(chr(10), r'\n')}",
f"{prefix}_OIDC_ISSUER=https://auth.olibuijr.com",
f"{prefix}_OIDC_CLIENT_ID={client_id}",
f"{prefix}_OIDC_CLIENT_SECRET={client_secret}",
f"{prefix}_OIDC_REDIRECT_URI={redirect_uri}",
]
with tempfile.NamedTemporaryFile("w", delete=False) as handle:
handle.write("\n".join(env_lines)+"\n")
local_env = handle.name
os.chmod(local_env, 0o600)
remote_update = "/tmp/akurai-app-oidc.env"
remote_install = r'''set -euo pipefail
target="$1"
update="$2"
tmp="$(mktemp)"
python3 - "$target" "$update" >"$tmp" <<'PY'
from pathlib import Path
import sys
target, update = map(Path, sys.argv[1:])
existing = target.read_text().splitlines() if target.exists() else []
updates = update.read_text().splitlines()
keys = {line.split("=", 1)[0] for line in updates}
print("\n".join([line for line in existing if line.split("=", 1)[0] not in keys] + updates))
PY
install -d -m 0750 -o root -g ubuntu "$(dirname "$target")"
install -m 0640 -o root -g ubuntu "$tmp" "$target"
rm -f "$tmp" "$update"
'''
try:
subprocess.run([cli, "ship", local_env, remote_update], check=True, stdout=subprocess.DEVNULL)
subprocess.run(
[cli, "ssh", shlex.join(["sudo", "bash", "-s", "--", remote_env, remote_update])],
input=remote_install,
text=True,
check=True,
stdout=subprocess.DEVNULL,
)
finally:
os.unlink(local_env)
print("OIDC client, PassVault references, and protected service environment provisioned.")