Add initial Portal version
This commit is contained in:
92
src/lib/openbao.ts
Normal file
92
src/lib/openbao.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const OPENBAO_ADDR =
|
||||
process.env.OPENBAO_ADDR || "http://openbao.openbao.svc:8200";
|
||||
const SA_TOKEN_PATH =
|
||||
"/var/run/secrets/kubernetes.io/serviceaccount/token";
|
||||
const K8S_AUTH_ROLE = process.env.OPENBAO_K8S_ROLE || "pieced-portal";
|
||||
const K8S_AUTH_MOUNT = process.env.OPENBAO_K8S_MOUNT || "kubernetes";
|
||||
|
||||
let cachedToken: { token: string; expiresAt: number } | null = null;
|
||||
|
||||
async function authenticate(): Promise<string> {
|
||||
if (cachedToken && Date.now() < cachedToken.expiresAt - 30_000) {
|
||||
return cachedToken.token;
|
||||
}
|
||||
|
||||
const jwt = readFileSync(SA_TOKEN_PATH, "utf-8").trim();
|
||||
|
||||
const res = await fetch(
|
||||
`${OPENBAO_ADDR}/v1/auth/${K8S_AUTH_MOUNT}/login`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ role: K8S_AUTH_ROLE, jwt }),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.text();
|
||||
throw new Error(`OpenBao K8s auth failed: ${res.status} ${body}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const token = data.auth.client_token as string;
|
||||
const leaseDuration = (data.auth.lease_duration as number) || 3600;
|
||||
|
||||
cachedToken = {
|
||||
token,
|
||||
expiresAt: Date.now() + leaseDuration * 1000,
|
||||
};
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write secrets for a tenant package to OpenBao KV v2.
|
||||
* Path: secret/data/tenants/{tenantId}/{packageId}
|
||||
*/
|
||||
export async function writePackageSecrets(
|
||||
tenantId: string,
|
||||
packageId: string,
|
||||
secrets: Record<string, string>
|
||||
): Promise<void> {
|
||||
const token = await authenticate();
|
||||
const path = `secret/data/tenants/${tenantId}/${packageId}`;
|
||||
|
||||
const res = await fetch(`${OPENBAO_ADDR}/v1/${path}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Vault-Token": token,
|
||||
},
|
||||
body: JSON.stringify({ data: secrets }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.text();
|
||||
throw new Error(`OpenBao write failed: ${res.status} ${body}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete secrets for a tenant package from OpenBao KV v2.
|
||||
* Uses metadata delete to remove all versions.
|
||||
*/
|
||||
export async function deletePackageSecrets(
|
||||
tenantId: string,
|
||||
packageId: string
|
||||
): Promise<void> {
|
||||
const token = await authenticate();
|
||||
const path = `secret/metadata/tenants/${tenantId}/${packageId}`;
|
||||
|
||||
const res = await fetch(`${OPENBAO_ADDR}/v1/${path}`, {
|
||||
method: "DELETE",
|
||||
headers: { "X-Vault-Token": token },
|
||||
});
|
||||
|
||||
if (!res.ok && res.status !== 404) {
|
||||
const body = await res.text();
|
||||
throw new Error(`OpenBao delete failed: ${res.status} ${body}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user