195 lines
5.0 KiB
TypeScript
195 lines
5.0 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 let extractSpiffeIdFromCert = function extractSpiffeIdFromCert(
|
|
certBundle: string,
|
|
): string | null {
|
|
if (!certBundle || typeof certBundle !== "string") {
|
|
return 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) {
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const spireWrapper = {
|
|
get extractSpiffeIdFromCert() {
|
|
return extractSpiffeIdFromCert;
|
|
},
|
|
set extractSpiffeIdFromCert(val: any) {
|
|
extractSpiffeIdFromCert = val;
|
|
},
|
|
};
|