auth-yes/server/spire_ffi.ts

181 lines
4.8 KiB
TypeScript

import { X509Certificate } from "npm:@peculiar/x509";
import { AsnParser } from "npm:@peculiar/asn1-schema";
import { SubjectAlternativeName } from "npm:@peculiar/asn1-x509";
// Deno binding for the spire_ffi Rust crate
if (Deno.build.arch !== "x86_64" && Deno.build.arch !== "aarch64") {
throw new Error("Unsupported architecture");
}
const libPath = (() => {
if (Deno.build.os === "windows") return "./spire_ffi.dll";
if (Deno.build.os === "darwin") return "./libspire_ffi.dylib";
return "./libspire_ffi.so";
})();
let dylib: Deno.DynamicLibrary<any> | null = null;
try {
dylib = Deno.dlopen(libPath, {
fetch_svid: {
parameters: ["pointer"],
result: "pointer",
nonblocking: true,
},
free_svid: {
parameters: ["pointer"],
result: "void",
},
});
} catch (_e) {
console.warn(
`Failed to load ${libPath}. Workload API fetching will be mocked/disabled if used.`,
);
}
export interface SvidResponse {
spiffe_id: string;
x509_svid: Uint8Array;
x509_svid_key: Uint8Array;
bundle: Uint8Array;
}
export async function fetchSpiffeIdentity(
socketPath: string = "/var/run/spire/agent.sock",
): Promise<SvidResponse> {
if (!dylib) {
console.warn(
`[SPIRE FFI] Dynamic library (${libPath}) is not loaded. Mocking SVID response for local development.`,
);
return {
spiffe_id: "spiffe://local.dev/mock",
x509_svid: new Uint8Array(),
x509_svid_key: new Uint8Array(),
bundle: new Uint8Array(),
};
}
const encoder = new TextEncoder();
const encodedPath = encoder.encode(socketPath + "\0");
const pathPtr = Deno.UnsafePointer.of(encodedPath);
const fetch_svid = dylib.symbols
.fetch_svid as unknown as ((
ptr: Deno.PointerValue,
) => Promise<Deno.PointerValue>);
const free_svid = dylib.symbols
.free_svid as unknown as ((ptr: Deno.PointerValue) => void);
const resPtr = await fetch_svid(pathPtr);
if (resPtr === null) {
throw new Error("fetch_svid returned a null pointer");
}
const resView = new Deno.UnsafePointerView(resPtr);
let errorMsg: string | null = null;
let spiffe_id: string | null = null;
let offset = 0;
const ptrSize = 8; // 64-bit pointers
const spiffe_id_ptr = resView.getPointer(offset);
offset += ptrSize;
const x509_svid_ptr = resView.getPointer(offset);
offset += ptrSize;
const x509_svid_len = Number(resView.getBigUint64(offset));
offset += ptrSize;
const x509_svid_key_ptr = resView.getPointer(offset);
offset += ptrSize;
const x509_svid_key_len = Number(resView.getBigUint64(offset));
offset += ptrSize;
const bundle_ptr = resView.getPointer(offset);
offset += ptrSize;
const bundle_len = Number(resView.getBigUint64(offset));
offset += ptrSize;
const error_ptr = resView.getPointer(offset);
if (error_ptr !== null) {
errorMsg = new Deno.UnsafePointerView(error_ptr).getCString();
}
if (errorMsg !== null) {
free_svid(resPtr);
throw new Error(errorMsg);
}
if (spiffe_id_ptr !== null) {
spiffe_id = new Deno.UnsafePointerView(spiffe_id_ptr).getCString();
}
if (!spiffe_id) {
free_svid(resPtr);
throw new Error("spiffe_id is null");
}
const x509_svid = x509_svid_ptr !== null && x509_svid_len > 0
? new Uint8Array(
new Deno.UnsafePointerView(x509_svid_ptr).getArrayBuffer(x509_svid_len),
)
: new Uint8Array();
const x509_svid_key = x509_svid_key_ptr !== null && x509_svid_key_len > 0
? new Uint8Array(
new Deno.UnsafePointerView(x509_svid_key_ptr).getArrayBuffer(
x509_svid_key_len,
),
)
: new Uint8Array();
const bundle = bundle_ptr !== null && bundle_len > 0
? new Uint8Array(
new Deno.UnsafePointerView(bundle_ptr).getArrayBuffer(bundle_len),
)
: new Uint8Array();
// Create copies of the typed arrays before freeing the memory
const svidData = {
spiffe_id,
x509_svid: new Uint8Array(x509_svid),
x509_svid_key: new Uint8Array(x509_svid_key),
bundle: new Uint8Array(bundle),
};
// Free the memory on the Rust side
free_svid(resPtr);
return svidData;
}
/**
* Extracts the SPIFFE ID from an incoming client TLS connection.
*/
export function extractSpiffeIdFromCert(certBundle: string): string | null {
try {
const cert = new X509Certificate(certBundle);
const sanExtension = cert.extensions.find((ext) =>
ext.type === "2.5.29.17"
); // Subject Alternative Name
if (!sanExtension) {
return null;
}
const san = AsnParser.parse(sanExtension.value, SubjectAlternativeName);
for (const name of san) {
if (
name.uniformResourceIdentifier &&
name.uniformResourceIdentifier.startsWith("spiffe://")
) {
return name.uniformResourceIdentifier;
}
}
} catch (e) {
console.error("Failed to parse certificate:", e);
return null;
}
return null;
}