feat(foundry): close the operational map data loop
This commit is contained in:
@@ -38,6 +38,21 @@ function targetDigest(target) {
|
||||
.digest("hex");
|
||||
}
|
||||
|
||||
function grantGeneration(value) {
|
||||
const generation = Number(value ?? 1);
|
||||
if (!Number.isSafeInteger(generation) || generation < 1 || generation > 2_147_483_647) {
|
||||
throw provisionerError("foundry_reader_grant_generation_invalid", 400);
|
||||
}
|
||||
return generation;
|
||||
}
|
||||
|
||||
function readerTokenPath(tokenRoot, digest, generation) {
|
||||
// Generation one keeps the deployed path for a zero-copy migration. Every
|
||||
// successor gets a separate immutable capability file so a new snapshot can
|
||||
// be accepted before the predecessor is revoked.
|
||||
return join(resolve(tokenRoot), generation === 1 ? digest : `${digest}.g${generation}`);
|
||||
}
|
||||
|
||||
function signingPayload({ audience, serviceId, keyId, method, path, timestamp, nonce, bodySha256 }) {
|
||||
return JSON.stringify({
|
||||
schemaVersion: SIGNATURE_SCHEMA,
|
||||
@@ -137,10 +152,11 @@ export function createFoundryReaderGrantProvisioner({
|
||||
return { product, sourceScope: "resolved-server-side" };
|
||||
}
|
||||
|
||||
async function ensure(target) {
|
||||
async function ensure(target, options = {}) {
|
||||
const identity = targetIdentity(target);
|
||||
const digest = targetDigest(identity);
|
||||
const token = await ensureReaderToken(join(resolve(tokenRoot), digest), {
|
||||
const generation = grantGeneration(options.generation);
|
||||
const token = await ensureReaderToken(readerTokenPath(tokenRoot, digest, generation), {
|
||||
production,
|
||||
randomBytesImpl,
|
||||
});
|
||||
@@ -151,26 +167,44 @@ export function createFoundryReaderGrantProvisioner({
|
||||
{
|
||||
allowedDataProductIds: [identity.dataProductId],
|
||||
expiresAt: null,
|
||||
generation: 1,
|
||||
generation,
|
||||
capabilityDigest: createHash("sha256").update(token, "utf8").digest("hex"),
|
||||
},
|
||||
);
|
||||
const binding = payload?.readerBinding;
|
||||
if (binding?.bindingKey !== bindingKey || binding?.generation !== 1 || binding?.active !== true
|
||||
if (binding?.bindingKey !== bindingKey || binding?.generation !== generation || binding?.active !== true
|
||||
|| binding?.expiresAt !== null || binding?.sourceScope !== "resolved-server-side"
|
||||
|| !Array.isArray(binding?.allowedDataProductIds)
|
||||
|| !binding.allowedDataProductIds.includes(identity.dataProductId)) {
|
||||
throw provisionerError("foundry_reader_grant_ensure_response_invalid", 502);
|
||||
}
|
||||
return { ensured: true, idempotent: payload?.idempotent === true, generation: 1, sourceScope: "resolved-server-side" };
|
||||
return { ensured: true, idempotent: payload?.idempotent === true, generation, sourceScope: "resolved-server-side" };
|
||||
}
|
||||
|
||||
async function readToken(target) {
|
||||
async function revoke(target, options = {}) {
|
||||
const digest = targetDigest(target);
|
||||
const generation = grantGeneration(options.generation);
|
||||
const bindingKey = `fndrc-${digest}`;
|
||||
const payload = await signedRequest(
|
||||
"POST",
|
||||
`/internal/data-plane/v1/consumer-reader-bindings/by-key/${encodeURIComponent(bindingKey)}/generations/${generation}/revoke`,
|
||||
{},
|
||||
);
|
||||
const binding = payload?.readerBinding;
|
||||
if (binding?.bindingKey !== bindingKey || binding?.generation !== generation || binding?.active !== false
|
||||
|| binding?.sourceScope !== "resolved-server-side") {
|
||||
throw provisionerError("foundry_reader_grant_revoke_response_invalid", 502);
|
||||
}
|
||||
return { revoked: true, idempotent: payload?.idempotent === true, generation, sourceScope: "resolved-server-side" };
|
||||
}
|
||||
|
||||
async function readToken(target, options = {}) {
|
||||
if (!tokenRoot) return null;
|
||||
return readReaderToken(join(resolve(tokenRoot), targetDigest(target)), { production, missing: null });
|
||||
const generation = grantGeneration(options.generation);
|
||||
return readReaderToken(readerTokenPath(tokenRoot, targetDigest(target), generation), { production, missing: null });
|
||||
}
|
||||
|
||||
return { configured, plan, ensure, readToken };
|
||||
return { configured, plan, ensure, revoke, readToken };
|
||||
}
|
||||
|
||||
async function readPrivateKeySecurely(path, { production }) {
|
||||
|
||||
Reference in New Issue
Block a user