@cotal-ai/auth 0.73.0 → 0.77.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-service-contract.d.ts +4 -4
- package/dist/auth-service-contract.d.ts.map +1 -1
- package/dist/auth-service-contract.js +7 -38
- package/dist/auth-service-contract.js.map +1 -1
- package/dist/barrier-evict.d.ts.map +1 -1
- package/dist/barrier-evict.js +17 -75
- package/dist/barrier-evict.js.map +1 -1
- package/dist/commands.js +11 -25
- package/dist/commands.js.map +1 -1
- package/dist/delivery-admin.d.ts +20 -0
- package/dist/delivery-admin.d.ts.map +1 -0
- package/dist/delivery-admin.js +46 -0
- package/dist/delivery-admin.js.map +1 -0
- package/dist/idp.d.ts +0 -2
- package/dist/idp.d.ts.map +1 -1
- package/dist/idp.js +3 -4
- package/dist/idp.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/ledger-scanner.d.ts +1 -1
- package/dist/ledger-scanner.d.ts.map +1 -1
- package/dist/ledger-scanner.js +7 -19
- package/dist/ledger-scanner.js.map +1 -1
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +7 -1
- package/dist/ledger.js.map +1 -1
- package/dist/manager-authority.d.ts +20 -1
- package/dist/manager-authority.d.ts.map +1 -1
- package/dist/manager-authority.js +62 -34
- package/dist/manager-authority.js.map +1 -1
- package/dist/manager-maintenance.d.ts.map +1 -1
- package/dist/manager-maintenance.js +6 -19
- package/dist/manager-maintenance.js.map +1 -1
- package/dist/permissions.d.ts +5 -3
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +5 -4
- package/dist/permissions.js.map +1 -1
- package/dist/plane-claim.d.ts +5 -0
- package/dist/plane-claim.d.ts.map +1 -1
- package/dist/plane-claim.js +40 -72
- package/dist/plane-claim.js.map +1 -1
- package/dist/platform-control.d.ts.map +1 -1
- package/dist/platform-control.js +2 -2
- package/dist/platform-control.js.map +1 -1
- package/dist/platform-supervisor.d.ts +51 -0
- package/dist/platform-supervisor.d.ts.map +1 -0
- package/dist/platform-supervisor.js +83 -0
- package/dist/platform-supervisor.js.map +1 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +40 -19
- package/dist/provider.js.map +1 -1
- package/dist/records-scanner.d.ts +1 -1
- package/dist/records-scanner.d.ts.map +1 -1
- package/dist/records-scanner.js +3 -18
- package/dist/records-scanner.js.map +1 -1
- package/dist/service.d.ts +34 -9
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +207 -45
- package/dist/service.js.map +1 -1
- package/dist/store.d.ts +9 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +32 -2
- package/dist/store.js.map +1 -1
- package/dist/token.d.ts +3 -2
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js +6 -4
- package/dist/token.js.map +1 -1
- package/package.json +4 -4
package/dist/service.js
CHANGED
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
*
|
|
26
26
|
* REMOTE EXCHANGE — the OPTIONAL second listener (`--exchange-public-port`, also binding
|
|
27
27
|
* 127.0.0.1; TLS terminates at a reverse proxy — in-process TLS was rejected: it duplicates cert
|
|
28
|
-
* renewal and forks deploy). It serves ONLY `GET /health`, `GET /jwks`, `POST /exchange`,
|
|
29
|
-
* `GET /.well-known/cotal-mesh` (the generated discovery
|
|
28
|
+
* renewal and forks deploy). It serves ONLY `GET /health`, `GET /jwks`, `POST /exchange`,
|
|
29
|
+
* `POST /manager-service-authority`, and `GET /.well-known/cotal-mesh` (the generated discovery
|
|
30
|
+
* bundle); everything else 404s. Its
|
|
30
31
|
* `/exchange` demands NO capability — honestly: the 0600 cap is a same-uid file-ACL boundary with
|
|
31
32
|
* no remote meaning, so requiring its bytes from a remote caller would prove nothing. The proof on
|
|
32
33
|
* the public face is the credential itself: the human arm presents an EdDSA IdP JWT verified
|
|
@@ -59,19 +60,20 @@ import { resolve } from "node:path";
|
|
|
59
60
|
import { connect, credsAuthenticator } from "@nats-io/transport-node";
|
|
60
61
|
import { jetstream, jetstreamManager } from "@nats-io/jetstream";
|
|
61
62
|
import { Kvm } from "@nats-io/kv";
|
|
62
|
-
import { CotalEndpoint, deprovisionAgent, isConcreteChannel, provisionAgentDurables, contractDigest, contractRefToHex, contractStoreContext, endpointToken, fetchContractArtifact, verifyClusterManifest, verifyClusterRoot, admissionBucket, admissionMediatorGrants, assertDerivedOwnerToken, assertLifecycleToken, assertValidOwnerToken, authorizeTrustedServeSnapshot, commitSiblingIssuance, credsClaims, EpEnvelopeError, ensureAuthorityStores, epAuthBucket, isReachable, jwtFromCreds, managedRetirementOpId, mintCreds, mintPublicUserJwt, newIdentity, newTakeoverId, observeHostedRunAttempt, openRecordsBucket, parseEndpointGate, parseServiceSpec, parseServiceStatus, rawDigest, parseSecretStoreIdentity, readCheckpointStatus, readRunAdmission, readRunRecord, readSvcRecordLeader, reconcileEndpointGate, replayRunJournal, sameSecretStoreIdentity, recordSpecKey, recordStatusKey, RECORD_KINDS, recordsBucket, remoteManagerActors, retirementFrontierStreams, runDriverCaller, serveIssuanceGateKv, standaloneConnectOpts, STANDING_RENEWABLE_TTL_SEC, withIssuerSession, acceptedReadGrant, actorLedgerSource, connectionAcceptedToken, importNativeSubjectPermissions, mintGeneration, parseActorLedgerSource, writeAcceptedRow, invokeCommand, resolveService, contractArtifactCanonicalBytes, DEV_OWNER, endpointRegistrationBarrier, mintLifecycleUid, principalKey, provisionEndpointGateOpen, publishContractArtifact, registerServiceInstance, registerServingInstance } from "@cotal-ai/core";
|
|
63
|
+
import { CotalEndpoint, deprovisionAgent, isConcreteChannel, provisionAgentDurables, contractDigest, contractRefToHex, contractStoreContext, endpointToken, fetchContractArtifact, verifyClusterManifest, verifyClusterRoot, admissionBucket, admissionMediatorGrants, assertDerivedOwnerToken, assertLifecycleToken, assertValidOwnerToken, authorizeTrustedServeSnapshot, commitSiblingIssuance, credsClaims, EpEnvelopeError, ensureAuthorityStores, epAuthBucket, isReachable, jwtFromCreds, managedRetirementOpId, mintCreds, mintPublicUserJwt, newIdentity, newTakeoverId, observeHostedRunAttempt, openRecordsBucket, parseEndpointGate, parseServiceSpec, parseServiceStatus, rawDigest, parseSecretStoreIdentity, readCheckpointStatus, readRunAdmission, readRunRevocation, readRunRecord, readSvcRecordLeader, reconcileEndpointGate, replayRunJournal, sameSecretStoreIdentity, recordSpecKey, recordStatusKey, RECORD_KINDS, recordsBucket, remoteManagerActors, retirementFrontierStreams, runDriverCaller, serveIssuanceGateKv, standaloneConnectOpts, STANDING_RENEWABLE_TTL_SEC, withIssuerSession, acceptedReadGrant, actorLedgerSource, connectionAcceptedToken, importNativeSubjectPermissions, mintGeneration, parseActorLedgerSource, writeAcceptedRow, invokeCommand, resolveService, contractArtifactCanonicalBytes, DEV_OWNER, endpointRegistrationBarrier, mintLifecycleUid, principalKey, provisionEndpointGateOpen, publishContractArtifact, registerServiceInstance, registerServingInstance, epgateKey, isCasLoss } from "@cotal-ai/core";
|
|
63
64
|
import { findCotalRoot, loadManagerInstanceIdentity, userAuthStateDir, workspaceSecretStore, createAuthInstanceIdentity, loadAuthInstanceIdentity } from "@cotal-ai/workspace";
|
|
64
65
|
import { decodeJwt } from "jose";
|
|
65
66
|
import { deriveOwnerForIdpSubject, platformControlOwner } from "./derive.js";
|
|
66
67
|
import { makePlatformControlAuthority, makePlatformControlReadiness, requireManagerAuthorityHolder } from "./platform-control.js";
|
|
67
68
|
import { OwnedConnections } from "./owned-connections.js";
|
|
69
|
+
import { makePlatformSupervisorAuthority, makePlatformSupervisorEnd, platformSupervisorEnded, requirePlatformSupervisorAdmin } from "./platform-supervisor.js";
|
|
68
70
|
import { startAuthCallout } from "./callout.js";
|
|
69
71
|
import { createIdpBridge, verifyIdpToken } from "./idp.js";
|
|
70
72
|
import { PUBLIC_EXCHANGE_VIEWS, assertTransferWriterClaim } from "./token.js";
|
|
71
73
|
import { grantCoordinates, verifySessionRedemption } from "./session-redemption.js";
|
|
72
74
|
import { pinnedJwksResolver } from "./issuer.js";
|
|
73
75
|
import { calloutPermissions } from "./permissions.js";
|
|
74
|
-
import { admitRemoteRun, authorizeRemoteManagerRenewal, authorizeRemoteRunAttempt, issueRemoteManagerAuthority, observedRunRequest, parseRemoteRunAdmissionRequest, parseRemoteRunAttemptRequest } from "./manager-authority.js";
|
|
76
|
+
import { admitRemoteRun, authorizeRemoteManagerRenewal, authorizeRemoteRunAttempt, holdsRunFence, issueRemoteManagerAuthority, observedRunRequest, parseRemoteRunAdmissionRequest, parseRemoteRunAttemptRequest, parseRemoteRunRevokeRequest, revokeRemoteRun } from "./manager-authority.js";
|
|
75
77
|
import { authorizeRemoteRetainedAgentValidation, completeRemoteRetainedAgentValidation, remoteManagerCurrentRegistrationProof } from "./retained-manager-validation.js";
|
|
76
78
|
import { authorizeRemoteManagedAgentEnrollment, authorizeRemoteManagedAgentPrepareRetirement, authorizeRemoteManagedAgentRuntimeCreate, authorizeRemoteManagedAgentRuntimeStatus } from "./managed-agent-enrollment.js";
|
|
77
79
|
import { authorizeRemoteManagerGoalIndexScan, completeRemoteManagerGoalIndexScan } from "./manager-goal-index.js";
|
|
@@ -96,7 +98,7 @@ import { joinOrStartRetirement, openAuthAdminListener } from "./auth-admin.js";
|
|
|
96
98
|
import { AUTH_SERVICE_ENDPOINT, authClusterArtifacts, authContractArtifactValues } from "./auth-service-contract.js";
|
|
97
99
|
import { drainTargetForEndpoint, openAdmissionMediator } from "./admission-mediator.js";
|
|
98
100
|
import { AGENT_BEARER_TTL_SEC, assertWithinSpawnerGrant, findInteractiveActor, findManagedActor, findActorUnified, grantManagedActor, ledgerAclResolver, ledgerActorSourceIsLive, ledgerAuthorizeAgentExchange, ledgerAuthorizeConnect, ledgerAuthorizeGrant, revokeManagedActorAt, } from "./ledger.js";
|
|
99
|
-
import { AUTH_PROVIDER_NAME, clearAuthServiceInfo, loadCalloutAuth, loadIssuer, loadOwnerSecret, loadPinnedIdp, loadServiceKeys, saveAuthServiceInfo, spaceIssuer, } from "./store.js";
|
|
101
|
+
import { AUTH_PROVIDER_NAME, claimAuthInstanceIdentity, clearAuthServiceInfo, loadCalloutAuth, loadIssuer, loadOwnerSecret, loadPinnedIdp, loadServiceKeys, saveAuthServiceInfo, spaceIssuer, } from "./store.js";
|
|
100
102
|
/** JWKS max-age seconds — the cache contract's knob. Exported so a rotation tool can compute the
|
|
101
103
|
* retire floor (max-age + max bearer TTL) from it. */
|
|
102
104
|
export const JWKS_MAX_AGE_SEC = 300;
|
|
@@ -187,6 +189,24 @@ async function registerSelfAuthorizedInstance(nc, args, register) {
|
|
|
187
189
|
throw new EpEnvelopeError("conflict", `the issuance gate of ${endpoint}/${instanceId} is no longer open at this registration (process epoch ${processEpoch}, revision ${registrationRevision}); a later barrier fenced this start (SPEC 13.1)`);
|
|
188
190
|
return registered;
|
|
189
191
|
}
|
|
192
|
+
/** The accepted-row token one platform supervisor duty's issued generation is recorded under. */
|
|
193
|
+
function supervisorAcceptedToken(owner, instanceId, lifecycleUid, duty) {
|
|
194
|
+
return connectionAcceptedToken(`platform-supervisor:${owner}:${instanceId}:${lifecycleUid}:${duty}`);
|
|
195
|
+
}
|
|
196
|
+
/** The issued generation recorded for one platform supervisor duty, or undefined when none was.
|
|
197
|
+
* The accepted store is write-once per key, so the first row is the only row. */
|
|
198
|
+
async function supervisorGeneration(session, space, owner, instanceId, lifecycleUid, duty) {
|
|
199
|
+
const response = await session.nc.request(acceptedReadGrant(space, supervisorAcceptedToken(owner, instanceId, lifecycleUid, duty)), new Uint8Array(0), { timeout: 3000 });
|
|
200
|
+
const status = response.headers?.get("Status");
|
|
201
|
+
if (response.headers?.code === 404 || status?.startsWith("404"))
|
|
202
|
+
return undefined;
|
|
203
|
+
if (status)
|
|
204
|
+
throw new EpEnvelopeError("unavailable", "platform supervisor accepted-row read failed");
|
|
205
|
+
const accepted = JSON.parse(new TextDecoder().decode(response.data));
|
|
206
|
+
if (accepted.version !== 1 || accepted.ref.space !== space || accepted.ref.owner !== owner || accepted.ref.actor !== remoteManagerActors(instanceId)[duty] || accepted.ref.uid !== lifecycleUid)
|
|
207
|
+
throw new EpEnvelopeError("permission-denied", "platform supervisor accepted row names another owner or lifecycle");
|
|
208
|
+
return accepted.ref;
|
|
209
|
+
}
|
|
190
210
|
/**
|
|
191
211
|
* Open the authority plane — the PRODUCTION connect/exchange composition (exported so the live
|
|
192
212
|
* deny-new smoke exercises exactly what the daemon runs). Boot order is the readiness contract:
|
|
@@ -228,17 +248,16 @@ async function buildAuthAuthorityPlane(opts, unwind, onConnection) {
|
|
|
228
248
|
// #399 M2: register the auth plane itself as an ordinary `auth` service endpoint — the SAME
|
|
229
249
|
// §13.7 registration ceremony the manager runs (`manager.ts:6562-6690`), before the listener
|
|
230
250
|
// below serves a single request. The persisted instance id + serve nkey (in `identityRoot`,
|
|
231
|
-
// hardened the way the manager's own instance identity is
|
|
232
|
-
// SAME instance: `registerServiceInstance` advances the process epoch
|
|
233
|
-
// fences the predecessor; a first registration stays at epoch 0.
|
|
251
|
+
// hardened the way the manager's own instance identity is, or in a hosted context's store) so a
|
|
252
|
+
// restart re-registers the SAME instance: `registerServiceInstance` advances the process epoch
|
|
253
|
+
// on a re-registration and fences the predecessor; a first registration stays at epoch 0.
|
|
234
254
|
let authServeInstanceId;
|
|
235
255
|
let authServeGrant;
|
|
236
256
|
{
|
|
237
|
-
const
|
|
238
|
-
const authIdentity =
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
});
|
|
257
|
+
const mint = () => ({ instanceId: mintLifecycleUid(), serveIdentity: newIdentity() });
|
|
258
|
+
const authIdentity = opts.identityStore
|
|
259
|
+
? await claimAuthInstanceIdentity(opts.identityStore, space, opts.identityRoot, mint)
|
|
260
|
+
: loadAuthInstanceIdentity(opts.identityRoot, space) ?? createAuthInstanceIdentity(opts.identityRoot, space, mint());
|
|
242
261
|
const iid = authIdentity.instanceId;
|
|
243
262
|
const artifacts = authClusterArtifacts();
|
|
244
263
|
const values = [...authContractArtifactValues(), artifacts.document, artifacts.manifest];
|
|
@@ -683,23 +702,47 @@ async function buildAuthAuthorityPlane(opts, unwind, onConnection) {
|
|
|
683
702
|
issueManagerServiceAuthority: async (holder) => {
|
|
684
703
|
refuseIfFenced();
|
|
685
704
|
const { owner } = holder;
|
|
686
|
-
|
|
705
|
+
// The ended-assignment fence (SPEC 13.16): read on every call, before anything is signed. The
|
|
706
|
+
// retired gate and the retired generations are terminal rows, so no host view reopens them.
|
|
707
|
+
if (holder.supervisorExpiresAt !== undefined) {
|
|
708
|
+
const gate = await observeManagerGate(holder.request.instanceId);
|
|
709
|
+
if (gate?.state === "retired")
|
|
710
|
+
throw platformSupervisorEnded(owner);
|
|
711
|
+
await withIssuerSession({ servers: server, space, auth: issuerAuth(), tls: false }, async (session) => {
|
|
712
|
+
for (const duty of ["supervisor", "executor"]) {
|
|
713
|
+
const ref = await supervisorGeneration(session, space, owner, holder.request.instanceId, holder.request.managerLifecycleUid, duty);
|
|
714
|
+
if (ref === undefined)
|
|
715
|
+
continue;
|
|
716
|
+
try {
|
|
717
|
+
await session.store.resolve(ref, session.sourceIsLive);
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
throw platformSupervisorEnded(owner, error instanceof Error ? error.message : String(error));
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
const observeManagerRun = async (r, runId) => observeHostedRunAttempt(await openRecordsBucket(remoteIssuer.nc, space), "manager", runId, { supervisorId: r.identities.supervisor.id, instanceId: r.instanceId });
|
|
726
|
+
const material = await issueRemoteManagerAuthority({
|
|
687
727
|
...holder,
|
|
688
728
|
authorizeRenewal: ({ owner: o, request: r }) => authorizeRemoteManagerRenewal({
|
|
689
729
|
request: r, owner: o, space, accountPublicKey: dataAccount.pub, proofSecret: dataAccount.signingSeed,
|
|
690
730
|
observeManagerGate,
|
|
691
|
-
observeRun:
|
|
692
|
-
const recordsKv = await openRecordsBucket(remoteIssuer.nc, space);
|
|
693
|
-
return await observeHostedRunAttempt(recordsKv, "manager", runId, {
|
|
694
|
-
supervisorId: r.identities.supervisor.id,
|
|
695
|
-
instanceId: r.instanceId,
|
|
696
|
-
});
|
|
697
|
-
},
|
|
731
|
+
observeRun: (runId) => observeManagerRun(r, runId),
|
|
698
732
|
}),
|
|
699
733
|
issue: async ({ actors, request: r }) => {
|
|
700
734
|
const signer = { space, account: dataAccount };
|
|
735
|
+
const bounded = (opts) => {
|
|
736
|
+
if (holder.supervisorExpiresAt === undefined)
|
|
737
|
+
return opts;
|
|
738
|
+
const now = Math.floor(Date.now() / 1000);
|
|
739
|
+
if (holder.supervisorExpiresAt <= now)
|
|
740
|
+
throw new EpEnvelopeError("permission-denied", `platform supervisor lifecycle has ended for owner ${owner}`);
|
|
741
|
+
const { expiresInSeconds, expiresAt, ...rest } = opts ?? {};
|
|
742
|
+
return { ...rest, expiresAt: Math.min(holder.supervisorExpiresAt, expiresAt ?? now + (expiresInSeconds ?? standingTtl)) };
|
|
743
|
+
};
|
|
701
744
|
const registration = { space: r.space, instanceId: r.instanceId, lifecycleUid: r.managerLifecycleUid, identities: r.identities };
|
|
702
|
-
const credential = async (key, profile, actor, opts) => mintPublicUserJwt(signer, r.identities[key].id, profile, { ...opts, principal: { owner, actor }, lifecycleUid: r.managerLifecycleUid });
|
|
745
|
+
const credential = async (key, profile, actor, opts) => mintPublicUserJwt(signer, r.identities[key].id, profile, bounded({ ...opts, principal: { owner, actor }, lifecycleUid: r.managerLifecycleUid }));
|
|
703
746
|
const credentials = {};
|
|
704
747
|
const siblingOn = (gate, observed) => async (key, profile, actor) => {
|
|
705
748
|
const issued = await credential(key, profile, actor, profile === "goal-writer" ? { goalWriter: { endpoint: "manager" }, expiresInSeconds: standingTtl } : { expiresInSeconds: standingTtl });
|
|
@@ -803,13 +846,13 @@ async function buildAuthAuthorityPlane(opts, unwind, onConnection) {
|
|
|
803
846
|
remoteManager: { instanceId: r.instanceId, owner, actor: actors.executor },
|
|
804
847
|
expiresInSeconds: 5 * 60,
|
|
805
848
|
});
|
|
806
|
-
credentials.serve = await mintPublicUserJwt(signer, r.identities.serve.id, "endpoint-serve", {
|
|
849
|
+
credentials.serve = await mintPublicUserJwt(signer, r.identities.serve.id, "endpoint-serve", bounded({
|
|
807
850
|
principal: { owner, actor: actors.serve },
|
|
808
851
|
lifecycleUid: r.managerLifecycleUid,
|
|
809
852
|
expiresInSeconds: standingTtl,
|
|
810
853
|
endpointServe: remoteManagerServeGrantFromCluster(r, owner, await registeredManagerCluster(owner, r.instanceId, observed), observed),
|
|
811
854
|
serveIssuance: gate,
|
|
812
|
-
});
|
|
855
|
+
}));
|
|
813
856
|
const sibling = siblingOn(gate, observed);
|
|
814
857
|
credentials.goalWriter = await sibling("goalWriter", "goal-writer", actors.goalWriter);
|
|
815
858
|
credentials.sessionLedger = await sibling("sessionLedger", "session-ledger", actors.sessionLedger);
|
|
@@ -820,14 +863,7 @@ async function buildAuthAuthorityPlane(opts, unwind, onConnection) {
|
|
|
820
863
|
const observed = await gate.observe();
|
|
821
864
|
if (!observed || observed.state !== "open" || observed.processEpoch !== r.processEpoch)
|
|
822
865
|
throw new EpEnvelopeError("conflict", "manager-service run renewal gate moved before issuance");
|
|
823
|
-
|
|
824
|
-
const run = await observeHostedRunAttempt(recordsKv, "manager", r.run.runId, {
|
|
825
|
-
supervisorId: r.identities.supervisor.id,
|
|
826
|
-
instanceId: r.instanceId,
|
|
827
|
-
});
|
|
828
|
-
if (!run || run.state !== "running" || run.instanceId !== r.instanceId ||
|
|
829
|
-
run.holder !== r.run.holder || run.takeoverId !== r.run.takeoverId ||
|
|
830
|
-
run.epoch !== r.run.epoch || run.fencingToken !== r.run.fencingToken)
|
|
866
|
+
if (!holdsRunFence(await observeManagerRun(r, r.run.runId), r))
|
|
831
867
|
throw new EpEnvelopeError("conflict", "manager-service run renewal authority moved before issuance");
|
|
832
868
|
const caller = runDriverCaller(r.run.runId, owner);
|
|
833
869
|
const binding = {
|
|
@@ -838,19 +874,19 @@ async function buildAuthAuthorityPlane(opts, unwind, onConnection) {
|
|
|
838
874
|
epoch: r.run.epoch,
|
|
839
875
|
owner,
|
|
840
876
|
};
|
|
841
|
-
credentials.runDriver = await mintPublicUserJwt(signer, r.run.driverId, "run-driver", {
|
|
877
|
+
credentials.runDriver = await mintPublicUserJwt(signer, r.run.driverId, "run-driver", bounded({
|
|
842
878
|
principal: { owner, actor: caller.actor },
|
|
843
879
|
lifecycleUid: r.managerLifecycleUid,
|
|
844
880
|
runDriver: binding,
|
|
845
881
|
expiresInSeconds: standingTtl,
|
|
846
|
-
});
|
|
847
|
-
credentials.runMediator = await mintPublicUserJwt(signer, r.run.mediatorId, "run-mediator", {
|
|
882
|
+
}));
|
|
883
|
+
credentials.runMediator = await mintPublicUserJwt(signer, r.run.mediatorId, "run-mediator", bounded({
|
|
848
884
|
principal: { owner, actor: caller.actor },
|
|
849
885
|
lifecycleUid: r.managerLifecycleUid,
|
|
850
886
|
// Renewed with the placement the attempt was issued with (authorizeRemoteRunAttempt).
|
|
851
887
|
runMediator: { ...binding, placement: { instanceId: r.instanceId } },
|
|
852
888
|
expiresInSeconds: standingTtl,
|
|
853
|
-
});
|
|
889
|
+
}));
|
|
854
890
|
return { credentials };
|
|
855
891
|
}
|
|
856
892
|
if (r.operation === "activate") {
|
|
@@ -866,13 +902,13 @@ async function buildAuthAuthorityPlane(opts, unwind, onConnection) {
|
|
|
866
902
|
const observed = await gate.observe();
|
|
867
903
|
if (!observed)
|
|
868
904
|
throw new EpEnvelopeError("failed-precondition", "manager-service activation found no issuance gate");
|
|
869
|
-
credentials.serve = await mintPublicUserJwt(signer, r.identities.serve.id, "endpoint-serve", {
|
|
905
|
+
credentials.serve = await mintPublicUserJwt(signer, r.identities.serve.id, "endpoint-serve", bounded({
|
|
870
906
|
principal: { owner, actor: actors.serve },
|
|
871
907
|
lifecycleUid: r.managerLifecycleUid,
|
|
872
908
|
expiresInSeconds: standingTtl,
|
|
873
909
|
endpointServe: reconstructRemoteManagerServeGrant(r, owner, observed),
|
|
874
910
|
serveIssuance: gate,
|
|
875
|
-
});
|
|
911
|
+
}));
|
|
876
912
|
const sibling = siblingOn(gate, observed);
|
|
877
913
|
credentials.goalWriter = await sibling("goalWriter", "goal-writer", actors.goalWriter);
|
|
878
914
|
credentials.sessionLedger = await sibling("sessionLedger", "session-ledger", actors.sessionLedger);
|
|
@@ -887,6 +923,82 @@ async function buildAuthAuthorityPlane(opts, unwind, onConnection) {
|
|
|
887
923
|
return { credentials };
|
|
888
924
|
},
|
|
889
925
|
});
|
|
926
|
+
if (holder.supervisorExpiresAt !== undefined) {
|
|
927
|
+
await withIssuerSession({ servers: server, space, auth: issuerAuth(), tls: false }, async (session) => {
|
|
928
|
+
for (const duty of ["supervisor", "executor"]) {
|
|
929
|
+
const credential = material.credentials[duty];
|
|
930
|
+
if (!credential)
|
|
931
|
+
continue;
|
|
932
|
+
const payload = decodeJwt(credential.jwt);
|
|
933
|
+
const permissions = importNativeSubjectPermissions({ pub: payload.nats?.pub, sub: payload.nats?.sub });
|
|
934
|
+
const actor = remoteManagerActors(holder.request.instanceId)[duty];
|
|
935
|
+
if (!actor || payload.sub !== holder.request.identities[duty]?.id)
|
|
936
|
+
throw new EpEnvelopeError("internal", "platform supervisor issued a duty outside its fixed standing identities");
|
|
937
|
+
const acceptedToken = supervisorAcceptedToken(owner, holder.request.instanceId, holder.request.managerLifecycleUid, duty);
|
|
938
|
+
const existing = await supervisorGeneration(session, space, owner, holder.request.instanceId, holder.request.managerLifecycleUid, duty);
|
|
939
|
+
if (existing === undefined) {
|
|
940
|
+
const ref = { space, owner, actor, uid: holder.request.managerLifecycleUid, generation: mintGeneration() };
|
|
941
|
+
const prepared = await session.store.stage({ version: 1, ref, sources: [], permissions, expiresAt: holder.supervisorExpiresAt });
|
|
942
|
+
await session.store.release(prepared, async () => { });
|
|
943
|
+
await writeAcceptedRow(session.accepted, acceptedToken, ref);
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
await session.store.resolve(existing, session.sourceIsLive);
|
|
947
|
+
await session.store.confirm(existing, permissions);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
return material;
|
|
953
|
+
},
|
|
954
|
+
endPlatformSupervisor: async ({ owner, instanceId, lifecycleUid }) => {
|
|
955
|
+
refuseIfFenced();
|
|
956
|
+
// Retire the issued generations first, then the gate. Each step is idempotent, so a retry after
|
|
957
|
+
// a partial end completes it. The retired gate alone refuses a lifecycle that issued nothing.
|
|
958
|
+
await withIssuerSession({ servers: server, space, auth: issuerAuth(), tls: false }, async (session) => {
|
|
959
|
+
for (const duty of ["supervisor", "executor"]) {
|
|
960
|
+
const ref = await supervisorGeneration(session, space, owner, instanceId, lifecycleUid, duty);
|
|
961
|
+
if (ref !== undefined)
|
|
962
|
+
await session.store.retire(ref);
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
const key = epgateKey("manager", instanceId);
|
|
966
|
+
const opId = managedRetirementOpId(lifecycleUid);
|
|
967
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
968
|
+
const entry = await authKv.get(key);
|
|
969
|
+
if (entry === null) {
|
|
970
|
+
// No gate yet: create it retired, so a later registration finds the terminal row.
|
|
971
|
+
const row = { state: "retired", generation: 0, processEpoch: 0, registrationRevision: 0, nameAuthorityRevision: 0, principal: `${owner}.${remoteManagerActors(instanceId).serve}`, op: { opId, kind: "retirement" } };
|
|
972
|
+
parseEndpointGate(new TextEncoder().encode(JSON.stringify(row)), key);
|
|
973
|
+
try {
|
|
974
|
+
await authKv.create(key, new TextEncoder().encode(JSON.stringify(row)));
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
catch (error) {
|
|
978
|
+
if (isCasLoss(error))
|
|
979
|
+
continue;
|
|
980
|
+
throw error;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (entry.operation !== "PUT")
|
|
984
|
+
throw new EpEnvelopeError("failed-precondition", `the manager gate ${key} carries a ${entry.operation} marker; a gate is never deleted (SPEC 13.12)`);
|
|
985
|
+
const row = parseEndpointGate(entry.value, key);
|
|
986
|
+
if (row.principal !== `${owner}.${remoteManagerActors(instanceId).serve}`)
|
|
987
|
+
throw new EpEnvelopeError("permission-denied", `the manager gate ${key} belongs to ${row.principal}, not owner ${owner}`);
|
|
988
|
+
if (row.state === "retired")
|
|
989
|
+
return;
|
|
990
|
+
const { op: _op, ...rest } = row;
|
|
991
|
+
void _op;
|
|
992
|
+
try {
|
|
993
|
+
await authKv.update(key, new TextEncoder().encode(JSON.stringify({ ...rest, state: "retired", op: { opId, kind: "retirement" } })), entry.revision);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
catch (error) {
|
|
997
|
+
if (!isCasLoss(error))
|
|
998
|
+
throw error;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
throw new EpEnvelopeError("conflict", `ending the platform supervisor of ${owner} lost repeated CAS attempts on ${key}; retry`);
|
|
890
1002
|
},
|
|
891
1003
|
maintainRemoteManager: async (holder) => {
|
|
892
1004
|
refuseIfFenced();
|
|
@@ -1147,6 +1259,31 @@ async function buildAuthAuthorityPlane(opts, unwind, onConnection) {
|
|
|
1147
1259
|
}
|
|
1148
1260
|
});
|
|
1149
1261
|
},
|
|
1262
|
+
revokeManagerRun: async ({ request, ...holder }) => {
|
|
1263
|
+
refuseIfFenced();
|
|
1264
|
+
const req = parseRemoteRunRevokeRequest(request);
|
|
1265
|
+
const admitterNc = await connect({
|
|
1266
|
+
servers: server,
|
|
1267
|
+
...standaloneConnectOpts({
|
|
1268
|
+
creds: await mintCreds(issuerAuth(), newIdentity(), "run-admitter", { runAdmitter: { endpoint: "manager", runId: req.revoke.runId }, expiresInSeconds: 60 }),
|
|
1269
|
+
tls: false,
|
|
1270
|
+
}),
|
|
1271
|
+
maxReconnectAttempts: 0,
|
|
1272
|
+
});
|
|
1273
|
+
onConnection?.(admitterNc, `cotal:run-revoker:${space}`);
|
|
1274
|
+
try {
|
|
1275
|
+
return await revokeRemoteRun({
|
|
1276
|
+
...holder, request: req, space, accountPublicKey: dataAccount.pub, proofSecret: dataAccount.signingSeed, endpoint: "manager",
|
|
1277
|
+
observeManagerGate,
|
|
1278
|
+
readAdmission: (runId) => readRunAdmission(recordsJsm, space, "manager", runId),
|
|
1279
|
+
readRevocation: (runId) => readRunRevocation(recordsJsm, space, "manager", runId),
|
|
1280
|
+
admissions: await new Kvm(admitterNc).open(admissionBucket(space)),
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
finally {
|
|
1284
|
+
await admitterNc.drain().catch(() => admitterNc.close());
|
|
1285
|
+
}
|
|
1286
|
+
},
|
|
1150
1287
|
issueManagerRunAttempt: async ({ request, ...holder }) => {
|
|
1151
1288
|
refuseIfFenced();
|
|
1152
1289
|
const { owner } = holder;
|
|
@@ -1551,6 +1688,7 @@ export async function startAuthService(inputs) {
|
|
|
1551
1688
|
context: inputs.context,
|
|
1552
1689
|
...(inputs.publicFace !== undefined ? { publicFace: inputs.publicFace } : {}),
|
|
1553
1690
|
...(inputs.platformControl !== undefined ? { platformControl: inputs.platformControl } : {}),
|
|
1691
|
+
...(inputs.platformSupervisor !== undefined ? { platformSupervisor: inputs.platformSupervisor } : {}),
|
|
1554
1692
|
...(inputs.standingRenewableTtlSeconds !== undefined ? { standingRenewableTtlSeconds: inputs.standingRenewableTtlSeconds } : {}),
|
|
1555
1693
|
});
|
|
1556
1694
|
return { ...started.handle, readiness: () => ({ ...started.status(), context: inputs.context }) };
|
|
@@ -1587,6 +1725,7 @@ async function startAuthContext(o) {
|
|
|
1587
1725
|
space,
|
|
1588
1726
|
dir,
|
|
1589
1727
|
identityRoot: o.identityRoot,
|
|
1728
|
+
...(o.context !== undefined ? { identityStore: secrets } : {}),
|
|
1590
1729
|
dataAccount: { pub: keys.dataAccount.pub, signingSeed: keys.dataAccount.signingSeed },
|
|
1591
1730
|
log: (l) => console.error(l),
|
|
1592
1731
|
localManager: o.localManager,
|
|
@@ -1664,6 +1803,7 @@ async function startAuthContext(o) {
|
|
|
1664
1803
|
scanManagerGoalIndex: plane.scanManagerGoalIndex,
|
|
1665
1804
|
authorizeManagerAdmin: plane.authorizeManagerAdmin,
|
|
1666
1805
|
admitManagerRun: plane.admitManagerRun,
|
|
1806
|
+
revokeManagerRun: plane.revokeManagerRun,
|
|
1667
1807
|
issueManagerRunAttempt: plane.issueManagerRunAttempt,
|
|
1668
1808
|
secrets,
|
|
1669
1809
|
retireInteractiveLifecycle: plane.retireInteractiveLifecycle,
|
|
@@ -1844,6 +1984,25 @@ async function startAuthContext(o) {
|
|
|
1844
1984
|
cap,
|
|
1845
1985
|
closed: owned.closed,
|
|
1846
1986
|
connections: () => owned.connections(),
|
|
1987
|
+
...(o.platformSupervisor !== undefined ? {
|
|
1988
|
+
platformSupervisorAuthority: async (owner) => {
|
|
1989
|
+
refuseUnlessReady();
|
|
1990
|
+
assertDerivedOwnerToken(owner);
|
|
1991
|
+
const input = o.platformSupervisor;
|
|
1992
|
+
await requirePlatformSupervisorAdmin(input, owner);
|
|
1993
|
+
return makePlatformSupervisorAuthority({
|
|
1994
|
+
...input, owner, space, accountPublicKey: keys.dataAccount.pub, ready: refuseUnlessReady,
|
|
1995
|
+
observePrincipal: async (instanceId) => (await plane.observeManagerInstance(instanceId)).gate?.principal,
|
|
1996
|
+
issue: (boundOwner, supervisorExpiresAt, request) => plane.issueManagerServiceAuthority({
|
|
1997
|
+
owner: boundOwner, scope: ["supervise"], supervisorExpiresAt, request,
|
|
1998
|
+
}),
|
|
1999
|
+
});
|
|
2000
|
+
},
|
|
2001
|
+
endPlatformSupervisorAssignment: makePlatformSupervisorEnd({
|
|
2002
|
+
...o.platformSupervisor, space, accountPublicKey: keys.dataAccount.pub, ready: refuseUnlessReady,
|
|
2003
|
+
end: (owner, instanceId, lifecycleUid) => plane.endPlatformSupervisor({ owner, instanceId, lifecycleUid }),
|
|
2004
|
+
}),
|
|
2005
|
+
} : {}),
|
|
1847
2006
|
...(platformDeps !== undefined ? {
|
|
1848
2007
|
platformControlAuthority: makePlatformControlAuthority({
|
|
1849
2008
|
...platformDeps,
|
|
@@ -1951,6 +2110,11 @@ export async function dispatchManagerAuthorityRequest(ctx, owner, body) {
|
|
|
1951
2110
|
return ctx.authorizeManagerAdmin({ owner, scope: row.scope ?? [], request: body.request });
|
|
1952
2111
|
if (request.kind === "manager-run-admission")
|
|
1953
2112
|
return ctx.admitManagerRun({ owner, scope: row.scope ?? [], request: body.request });
|
|
2113
|
+
if (request.kind === "manager-run-revoke") {
|
|
2114
|
+
if (ctx.revokeManagerRun === undefined)
|
|
2115
|
+
throw new EpEnvelopeError("unimplemented", "this issuing host has no run revoke handler");
|
|
2116
|
+
return ctx.revokeManagerRun({ owner, scope: row.scope ?? [], request: body.request });
|
|
2117
|
+
}
|
|
1954
2118
|
if (request.kind === "manager-run-attempt")
|
|
1955
2119
|
return ctx.issueManagerRunAttempt({ owner, scope: row.scope ?? [], request: body.request });
|
|
1956
2120
|
if (request.kind === "manager-service-maintenance")
|
|
@@ -1962,7 +2126,6 @@ export async function dispatchManagerAuthorityRequest(ctx, owner, body) {
|
|
|
1962
2126
|
const LOOPBACK_POLICY = {
|
|
1963
2127
|
requireCapability: true,
|
|
1964
2128
|
refuseViews: false,
|
|
1965
|
-
allowManagerAuthority: true,
|
|
1966
2129
|
peerKey: (req) => req.socket.remoteAddress ?? "loopback",
|
|
1967
2130
|
throttled: (ctx) => {
|
|
1968
2131
|
const now = Date.now();
|
|
@@ -1996,7 +2159,6 @@ function makePublicPolicy(trustedProxy) {
|
|
|
1996
2159
|
return {
|
|
1997
2160
|
requireCapability: false,
|
|
1998
2161
|
refuseViews: true,
|
|
1999
|
-
allowManagerAuthority: true,
|
|
2000
2162
|
peerKey: (req) => {
|
|
2001
2163
|
if (trustedProxy) {
|
|
2002
2164
|
const xff = req.headers["x-forwarded-for"];
|
|
@@ -2384,8 +2546,9 @@ async function handleExchange(req, res, ctx, policy) {
|
|
|
2384
2546
|
return send(res, 401, { error: reason });
|
|
2385
2547
|
}
|
|
2386
2548
|
}
|
|
2387
|
-
/**
|
|
2388
|
-
*
|
|
2549
|
+
/** The typed manager authority exchange, served on both faces. The face's policy decides whether the
|
|
2550
|
+
* loopback capability is demanded; on the public face the verified IdP JWT is the proof, and the
|
|
2551
|
+
* dispatcher reads the caller's scope from the ledger either way. */
|
|
2389
2552
|
export async function handleManagerServiceAuthority(req, res, ctx, policy) {
|
|
2390
2553
|
if (req.method !== "POST")
|
|
2391
2554
|
return send(res, 405, { error: "POST only" });
|
|
@@ -2393,8 +2556,6 @@ export async function handleManagerServiceAuthority(req, res, ctx, policy) {
|
|
|
2393
2556
|
return send(res, 403, { error: "browser-origin requests are not served here" });
|
|
2394
2557
|
if (!/^application\/json\b/.test(req.headers["content-type"] ?? ""))
|
|
2395
2558
|
return send(res, 415, { error: "content-type must be application/json" });
|
|
2396
|
-
if (!policy.allowManagerAuthority)
|
|
2397
|
-
return send(res, 403, { error: "manager-service authority is not enabled on this exchange face" });
|
|
2398
2559
|
if (policy.requireCapability && req.headers.authorization !== `Bearer ${ctx.cap}`)
|
|
2399
2560
|
return send(res, 401, { error: "missing/invalid exchange capability - manager-service authority requires the operator exchange capability on this face" });
|
|
2400
2561
|
const body = await readJsonBody(req);
|
|
@@ -2489,7 +2650,8 @@ export function checkAgentProvisioningUrl(raw) {
|
|
|
2489
2650
|
return undefined;
|
|
2490
2651
|
}
|
|
2491
2652
|
/** Build the PUBLIC listener's request handler: its own closed route table (/health, /jwks,
|
|
2492
|
-
* /exchange under the public policy, /.well-known/cotal-mesh — 404
|
|
2653
|
+
* /exchange and /manager-service-authority under the public policy, /.well-known/cotal-mesh — 404
|
|
2654
|
+
* everything else), behind a
|
|
2493
2655
|
* global concurrent-admission cap and a hard wall-clock deadline. All of it is public-face-local
|
|
2494
2656
|
* state: nothing here touches the loopback face's windows. */
|
|
2495
2657
|
function makePublicHandler(ctx, policy, bundle) {
|