AkurAI Build
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