guanghu-ice-heart/server-tools/lake-lamp-authz/ghdr-authorizer.js

75 lines
2.5 KiB
JavaScript

"use strict";
const crypto = require("node:crypto");
const fs = require("node:fs");
const path = require("node:path");
class GhdrAuthorizer {
constructor({
privateKeyFile = "/var/lib/guanghu/lake-lamp-authz/ghdr-authorizer-private.pem",
now = () => Math.floor(Date.now() / 1000),
} = {}) {
this.privateKeyFile = privateKeyFile;
this.now = now;
this.privateKey = this.loadOrCreate();
this.publicKey = crypto.createPublicKey(this.privateKey);
}
loadOrCreate() {
if (fs.existsSync(this.privateKeyFile)) {
const metadata = fs.lstatSync(this.privateKeyFile);
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.mode & 0o077) {
throw new Error("ghdr_authorizer_private_key_permissions_invalid");
}
return crypto.createPrivateKey(fs.readFileSync(this.privateKeyFile));
}
fs.mkdirSync(path.dirname(this.privateKeyFile), { recursive: true, mode: 0o700 });
const { privateKey } = crypto.generateKeyPairSync("ed25519");
const pem = privateKey.export({ type: "pkcs8", format: "pem" });
const temporary = `${this.privateKeyFile}.${process.pid}.tmp`;
fs.writeFileSync(temporary, pem, { mode: 0o600, flag: "wx" });
fs.renameSync(temporary, this.privateKeyFile);
return privateKey;
}
publicBinding() {
const publicPem = this.publicKey.export({ type: "spki", format: "pem" });
const fingerprint = crypto.createHash("sha256").update(publicPem).digest("hex");
return {
schema: "guanghu.ghdr-authorizer-public-binding/v1",
authorizer_id: "JD-FD-PRIMARY-LAKE-LAMP",
algorithm: "Ed25519",
public_key_pem: publicPem,
public_key_sha256: fingerprint,
};
}
issue({
controllerNodeId,
targetNodeId,
layoutPayloadSha256,
resource,
workorderId,
}) {
const issued = this.now();
const capability = {
schema: "guanghu.ghdr-signing-capability/v1",
authorizer_id: "JD-FD-PRIMARY-LAKE-LAMP",
controller_node_id: controllerNodeId,
target_node_id: targetNodeId,
layout_payload_sha256: layoutPayloadSha256,
resource,
workorder_id: workorderId,
issued_at_unix: issued,
expires_at_unix: issued + 120,
nonce: crypto.randomBytes(24).toString("base64url"),
};
const canonical = Buffer.from(JSON.stringify(capability));
return {
capability,
capability_signature_base64url: crypto.sign(null, canonical, this.privateKey).toString("base64url"),
};
}
}
module.exports = { GhdrAuthorizer };