@harperfast/harper 5.2.13 → 5.3.0-beta.1
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/agent/mcpTools.ts +1 -1
- package/agent/session.ts +25 -14
- package/bin/cliOperations.ts +58 -9
- package/bin/copyDb.ts +282 -59
- package/bin/deploySetup.ts +16 -5
- package/bin/harper.ts +1 -1
- package/bin/help.ts +4 -1
- package/bin/lite.ts +4 -1
- package/bin/restart.ts +120 -4
- package/bin/run.ts +6 -11
- package/bin/upgrade.js +7 -3
- package/bin/workloadIdentity.ts +119 -0
- package/components/Application.ts +3436 -242
- package/components/ApplicationScope.ts +8 -0
- package/components/EntryHandler.ts +59 -39
- package/components/OptionsWatcher.ts +440 -98
- package/components/RuntimeModuleTracker.ts +38 -7
- package/components/Scope.ts +56 -15
- package/components/awaitRestart.ts +84 -0
- package/components/componentLoader.ts +381 -32
- package/components/componentPreparationLock.ts +16 -5
- package/components/deploymentOperations.ts +4 -1
- package/components/deploymentRecorder.ts +9 -2
- package/components/mcp/adapters/harperHttp.ts +4 -0
- package/components/mcp/listChanged.ts +4 -0
- package/components/mcp/toolRegistry.ts +2 -0
- package/components/mcp/tools/operations.ts +9 -0
- package/components/mcp/tools/schemas/operationDescriptions.ts +2 -2
- package/components/operations.js +537 -113
- package/components/operationsValidation.js +98 -3
- package/components/packageComponent.ts +25 -1
- package/components/requestRestart.ts +11 -0
- package/components/status/ComponentStatusRegistry.ts +59 -0
- package/config/RootConfigWatcher.ts +240 -40
- package/config/configReadRetry.ts +62 -0
- package/config/configUtils.ts +357 -48
- package/config/harperConfigEnvVars.ts +170 -27
- package/config/parseConfigFile.ts +34 -0
- package/config/readConfigFileSync.ts +44 -0
- package/config/watcherArming.ts +59 -0
- package/config-root.schema.json +33 -0
- package/dataLayer/blobBackup.ts +160 -50
- package/dataLayer/delete.ts +6 -1
- package/dataLayer/harperBridge/ResourceBridge.ts +80 -10
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
- package/dataLayer/hdbInfoController.ts +34 -1
- package/dataLayer/insert.ts +44 -1
- package/dataLayer/rocksdbBackup.ts +53 -10
- package/dataLayer/schema.ts +11 -1
- package/dataLayer/schemaDescribe.ts +8 -1
- package/dist/agent/mcpTools.js +1 -1
- package/dist/agent/mcpTools.js.map +1 -1
- package/dist/agent/session.d.ts +22 -0
- package/dist/agent/session.js +26 -15
- package/dist/agent/session.js.map +1 -1
- package/dist/bin/cliOperations.js +61 -9
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/copyDb.d.ts +12 -1
- package/dist/bin/copyDb.js +248 -60
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/bin/deploySetup.d.ts +2 -0
- package/dist/bin/deploySetup.js +11 -3
- package/dist/bin/deploySetup.js.map +1 -1
- package/dist/bin/harper.js +1 -1
- package/dist/bin/harper.js.map +1 -1
- package/dist/bin/help.js +4 -1
- package/dist/bin/help.js.map +1 -1
- package/dist/bin/lite.js +4 -1
- package/dist/bin/lite.js.map +1 -1
- package/dist/bin/restart.js +91 -6
- package/dist/bin/restart.js.map +1 -1
- package/dist/bin/run.js +4 -10
- package/dist/bin/run.js.map +1 -1
- package/dist/bin/upgrade.js +4 -3
- package/dist/bin/upgrade.js.map +1 -1
- package/dist/bin/workloadIdentity.d.ts +18 -0
- package/dist/bin/workloadIdentity.js +100 -0
- package/dist/bin/workloadIdentity.js.map +1 -0
- package/dist/components/Application.d.ts +235 -17
- package/dist/components/Application.js +3038 -238
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +8 -0
- package/dist/components/ApplicationScope.js +7 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/EntryHandler.js +26 -10
- package/dist/components/EntryHandler.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +4 -0
- package/dist/components/OptionsWatcher.js +440 -99
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/RuntimeModuleTracker.js +40 -6
- package/dist/components/RuntimeModuleTracker.js.map +1 -1
- package/dist/components/Scope.js +52 -13
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/awaitRestart.d.ts +33 -0
- package/dist/components/awaitRestart.js +61 -0
- package/dist/components/awaitRestart.js.map +1 -0
- package/dist/components/componentLoader.d.ts +38 -1
- package/dist/components/componentLoader.js +313 -24
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentPreparationLock.d.ts +5 -0
- package/dist/components/componentPreparationLock.js +14 -6
- package/dist/components/componentPreparationLock.js.map +1 -1
- package/dist/components/deploymentOperations.js +4 -1
- package/dist/components/deploymentOperations.js.map +1 -1
- package/dist/components/deploymentRecorder.d.ts +4 -2
- package/dist/components/deploymentRecorder.js +1 -0
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.js +4 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/listChanged.js +5 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +1 -0
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +5 -0
- package/dist/components/mcp/tools/operations.js +9 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -2
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/operations.d.ts +28 -0
- package/dist/components/operations.js +476 -113
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +97 -3
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/packageComponent.js +24 -0
- package/dist/components/packageComponent.js.map +1 -1
- package/dist/components/requestRestart.d.ts +1 -0
- package/dist/components/requestRestart.js +7 -0
- package/dist/components/requestRestart.js.map +1 -1
- package/dist/components/status/ComponentStatusRegistry.d.ts +0 -4
- package/dist/components/status/ComponentStatusRegistry.js +63 -0
- package/dist/components/status/ComponentStatusRegistry.js.map +1 -1
- package/dist/config/RootConfigWatcher.d.ts +9 -1
- package/dist/config/RootConfigWatcher.js +228 -37
- package/dist/config/RootConfigWatcher.js.map +1 -1
- package/dist/config/configReadRetry.d.ts +8 -0
- package/dist/config/configReadRetry.js +62 -0
- package/dist/config/configReadRetry.js.map +1 -0
- package/dist/config/configUtils.d.ts +11 -2
- package/dist/config/configUtils.js +304 -47
- package/dist/config/configUtils.js.map +1 -1
- package/dist/config/harperConfigEnvVars.d.ts +16 -0
- package/dist/config/harperConfigEnvVars.js +162 -25
- package/dist/config/harperConfigEnvVars.js.map +1 -1
- package/dist/config/parseConfigFile.d.ts +4 -0
- package/dist/config/parseConfigFile.js +35 -0
- package/dist/config/parseConfigFile.js.map +1 -0
- package/dist/config/readConfigFileSync.d.ts +1 -0
- package/dist/config/readConfigFileSync.js +47 -0
- package/dist/config/readConfigFileSync.js.map +1 -0
- package/dist/config/watcherArming.d.ts +15 -0
- package/dist/config/watcherArming.js +59 -0
- package/dist/config/watcherArming.js.map +1 -0
- package/dist/dataLayer/blobBackup.d.ts +49 -20
- package/dist/dataLayer/blobBackup.js +139 -50
- package/dist/dataLayer/blobBackup.js.map +1 -1
- package/dist/dataLayer/delete.js +1 -1
- package/dist/dataLayer/delete.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +14 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +72 -12
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.d.ts +3 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js.map +1 -1
- package/dist/dataLayer/hdbInfoController.d.ts +10 -0
- package/dist/dataLayer/hdbInfoController.js +30 -1
- package/dist/dataLayer/hdbInfoController.js.map +1 -1
- package/dist/dataLayer/insert.d.ts +9 -1
- package/dist/dataLayer/insert.js +30 -0
- package/dist/dataLayer/insert.js.map +1 -1
- package/dist/dataLayer/rocksdbBackup.d.ts +2 -2
- package/dist/dataLayer/rocksdbBackup.js +45 -8
- package/dist/dataLayer/rocksdbBackup.js.map +1 -1
- package/dist/dataLayer/schema.js +8 -0
- package/dist/dataLayer/schema.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +8 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +55 -0
- package/dist/resources/DatabaseTransaction.d.ts +77 -0
- package/dist/resources/DatabaseTransaction.js +586 -54
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -1
- package/dist/resources/LMDBTransaction.js +43 -6
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
- package/dist/resources/PrimaryRocksDatabase.js +49 -5
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +20 -0
- package/dist/resources/RecordEncoder.js +110 -9
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +2 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.js +117 -24
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +28 -1
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/RocksIndexStore.d.ts +6 -1
- package/dist/resources/RocksIndexStore.js +24 -9
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +24 -1
- package/dist/resources/RocksTransactionLogStore.js +160 -49
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +165 -10
- package/dist/resources/Table.js +1805 -303
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +10 -3
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +181 -2
- package/dist/resources/auditStore.js +640 -29
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/blob.d.ts +129 -9
- package/dist/resources/blob.js +992 -114
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/branchDatabase.d.ts +48 -0
- package/dist/resources/branchDatabase.js +892 -0
- package/dist/resources/branchDatabase.js.map +1 -0
- package/dist/resources/crdt.js +50 -12
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/dataLoader.js +3 -4
- package/dist/resources/dataLoader.js.map +1 -1
- package/dist/resources/databases.d.ts +157 -14
- package/dist/resources/databases.js +1462 -325
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/defineTable.d.ts +10 -2
- package/dist/resources/defineTable.js +9 -1
- package/dist/resources/defineTable.js.map +1 -1
- package/dist/resources/derivedIndexRegistry.d.ts +5 -0
- package/dist/resources/derivedIndexRegistry.js +68 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -0
- package/dist/resources/derivedIndexRuntime.d.ts +215 -0
- package/dist/resources/derivedIndexRuntime.js +2027 -0
- package/dist/resources/derivedIndexRuntime.js.map +1 -0
- package/dist/resources/graphql.d.ts +1 -1
- package/dist/resources/graphql.js +52 -16
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +103 -9
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +854 -41
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
- package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
- package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
- package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
- package/dist/resources/longLivedTransactions.d.ts +71 -0
- package/dist/resources/longLivedTransactions.js +358 -0
- package/dist/resources/longLivedTransactions.js.map +1 -0
- package/dist/resources/models/backendRegistry.d.ts +26 -0
- package/dist/resources/models/backendRegistry.js +60 -2
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/bootstrap.d.ts +33 -1
- package/dist/resources/models/bootstrap.js +416 -31
- package/dist/resources/models/bootstrap.js.map +1 -1
- package/dist/resources/nodeIdMapping.d.ts +5 -0
- package/dist/resources/nodeIdMapping.js +49 -0
- package/dist/resources/nodeIdMapping.js.map +1 -1
- package/dist/resources/recordLock.d.ts +123 -0
- package/dist/resources/recordLock.js +315 -0
- package/dist/resources/recordLock.js.map +1 -0
- package/dist/resources/recordLockCoordinator.d.ts +557 -0
- package/dist/resources/recordLockCoordinator.js +2565 -0
- package/dist/resources/recordLockCoordinator.js.map +1 -0
- package/dist/resources/replayLogs.d.ts +14 -1
- package/dist/resources/replayLogs.js +157 -24
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/replayLogsGuards.d.ts +94 -7
- package/dist/resources/replayLogsGuards.js +111 -7
- package/dist/resources/replayLogsGuards.js.map +1 -1
- package/dist/resources/replicatedApplyFailure.d.ts +16 -0
- package/dist/resources/replicatedApplyFailure.js +63 -0
- package/dist/resources/replicatedApplyFailure.js.map +1 -0
- package/dist/resources/scheduler/scheduler.js +3 -3
- package/dist/resources/scheduler/scheduler.js.map +1 -1
- package/dist/resources/search.d.ts +10 -4
- package/dist/resources/search.js +283 -37
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/tracked.d.ts +5 -1
- package/dist/resources/tracked.js +74 -23
- package/dist/resources/tracked.js.map +1 -1
- package/dist/resources/transactionBroadcast.d.ts +1 -1
- package/dist/resources/transactionBroadcast.js +2 -2
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/auth.js +61 -30
- package/dist/security/auth.js.map +1 -1
- package/dist/security/authn/oidc/claims.d.ts +22 -0
- package/dist/security/authn/oidc/claims.js +71 -0
- package/dist/security/authn/oidc/claims.js.map +1 -0
- package/dist/security/authn/oidc/identityToken.d.ts +27 -0
- package/dist/security/authn/oidc/identityToken.js +111 -0
- package/dist/security/authn/oidc/identityToken.js.map +1 -0
- package/dist/security/authn/oidc/jwks.d.ts +25 -0
- package/dist/security/authn/oidc/jwks.js +261 -0
- package/dist/security/authn/oidc/jwks.js.map +1 -0
- package/dist/security/authn/oidc/providers/generic.d.ts +13 -0
- package/dist/security/authn/oidc/providers/generic.js +34 -0
- package/dist/security/authn/oidc/providers/generic.js.map +1 -0
- package/dist/security/authn/oidc/providers/githubActions.d.ts +11 -0
- package/dist/security/authn/oidc/providers/githubActions.js +129 -0
- package/dist/security/authn/oidc/providers/githubActions.js.map +1 -0
- package/dist/security/authn/oidc/providers/index.d.ts +37 -0
- package/dist/security/authn/oidc/providers/index.js +24 -0
- package/dist/security/authn/oidc/providers/index.js.map +1 -0
- package/dist/security/authn/oidc/tokenExchange.d.ts +12 -0
- package/dist/security/authn/oidc/tokenExchange.js +306 -0
- package/dist/security/authn/oidc/tokenExchange.js.map +1 -0
- package/dist/security/authn/oidc/trustPolicyOperations.d.ts +49 -0
- package/dist/security/authn/oidc/trustPolicyOperations.js +358 -0
- package/dist/security/authn/oidc/trustPolicyOperations.js.map +1 -0
- package/dist/security/authn/oidc/types.d.ts +38 -0
- package/dist/security/authn/oidc/types.js +6 -0
- package/dist/security/authn/oidc/types.js.map +1 -0
- package/dist/security/certificateVerification/index.js +40 -11
- package/dist/security/certificateVerification/index.js.map +1 -1
- package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
- package/dist/security/certificateVerification/trustedIssuers.js +79 -0
- package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
- package/dist/security/certificateVerification/types.d.ts +1 -0
- package/dist/security/credentialProvenance.d.ts +35 -0
- package/dist/security/credentialProvenance.js +51 -0
- package/dist/security/credentialProvenance.js.map +1 -0
- package/dist/security/credentialRejection.d.ts +4 -0
- package/dist/security/credentialRejection.js +24 -0
- package/dist/security/credentialRejection.js.map +1 -0
- package/dist/security/deferredAuthentication.d.ts +36 -0
- package/dist/security/deferredAuthentication.js +70 -0
- package/dist/security/deferredAuthentication.js.map +1 -0
- package/dist/security/impersonation.d.ts +21 -0
- package/dist/security/impersonation.js +108 -9
- package/dist/security/impersonation.js.map +1 -1
- package/dist/security/jsLoader.d.ts +6 -0
- package/dist/security/jsLoader.js +77 -15
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/keys.js +301 -71
- package/dist/security/keys.js.map +1 -1
- package/dist/security/operationScope.d.ts +21 -0
- package/dist/security/operationScope.js +36 -0
- package/dist/security/operationScope.js.map +1 -0
- package/dist/security/permissionsTranslator.js +21 -0
- package/dist/security/permissionsTranslator.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +19 -1
- package/dist/security/tokenAuthentication.js +191 -10
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/security/user.js +4 -3
- package/dist/security/user.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +2 -2
- package/dist/server/DurableSubscriptionsSession.js +67 -10
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +106 -2
- package/dist/server/REST.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -0
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +16 -1
- package/dist/server/http.js +122 -17
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +8 -1
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/jobs/jobProcess.js +6 -2
- package/dist/server/jobs/jobProcess.js.map +1 -1
- package/dist/server/jobs/jobs.js +4 -1
- package/dist/server/jobs/jobs.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +26 -4
- package/dist/server/liveSubscriptionAuth.js +105 -39
- package/dist/server/liveSubscriptionAuth.js.map +1 -1
- package/dist/server/loadRootComponents.js +49 -10
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/mqtt.d.ts +2 -0
- package/dist/server/mqtt.js +165 -30
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/nodeName.d.ts +2 -0
- package/dist/server/nodeName.js +107 -23
- package/dist/server/nodeName.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +27 -0
- package/dist/server/serverHelpers/Headers.js +145 -1
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
- package/dist/server/serverHelpers/Request.d.ts +5 -10
- package/dist/server/serverHelpers/Request.js +38 -136
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.d.ts +11 -0
- package/dist/server/serverHelpers/contentTypes.js +221 -40
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/deployValidationState.d.ts +3 -0
- package/dist/server/serverHelpers/deployValidationState.js +9 -19
- package/dist/server/serverHelpers/deployValidationState.js.map +1 -1
- package/dist/server/serverHelpers/operationAuthorizationState.d.ts +12 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js +24 -2
- package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +5 -4
- package/dist/server/serverHelpers/registeredOperations.js +74 -21
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
- package/dist/server/serverHelpers/requestSanitization.d.ts +11 -0
- package/dist/server/serverHelpers/requestSanitization.js +20 -0
- package/dist/server/serverHelpers/requestSanitization.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +6 -3
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +18 -0
- package/dist/server/serverHelpers/serverUtilities.js +178 -29
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/sharedMessageEncoding.d.ts +67 -0
- package/dist/server/serverHelpers/sharedMessageEncoding.js +280 -0
- package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -0
- package/dist/server/serverHelpers/uwsServer.js +19 -1
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/static.js +24 -28
- package/dist/server/static.js.map +1 -1
- package/dist/server/storageReclamation.d.ts +5 -0
- package/dist/server/storageReclamation.js +17 -1
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/isolatedApplications.d.ts +47 -0
- package/dist/server/threads/isolatedApplications.js +171 -0
- package/dist/server/threads/isolatedApplications.js.map +1 -0
- package/dist/server/threads/itc.d.ts +7 -2
- package/dist/server/threads/itc.js +5 -1
- package/dist/server/threads/itc.js.map +1 -1
- package/dist/server/threads/logRotationTransport.d.ts +1 -0
- package/dist/server/threads/logRotationTransport.js +33 -0
- package/dist/server/threads/logRotationTransport.js.map +1 -0
- package/dist/server/threads/manageThreads.d.ts +83 -3
- package/dist/server/threads/manageThreads.js +769 -75
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.d.ts +1 -0
- package/dist/server/threads/socketRouter.js +277 -31
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadHeapMemory.d.ts +2 -0
- package/dist/server/threads/threadHeapMemory.js +31 -0
- package/dist/server/threads/threadHeapMemory.js.map +1 -0
- package/dist/server/threads/threadServer.js +76 -22
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/sqlEngine/config.d.ts +1 -3
- package/dist/sqlEngine/config.js +19 -16
- package/dist/sqlEngine/config.js.map +1 -1
- package/dist/sqlTranslator/index.d.ts +1 -1
- package/dist/sqlTranslator/index.js +30 -7
- package/dist/sqlTranslator/index.js.map +1 -1
- package/dist/upgrade/directives/5-3-0.d.ts +7 -0
- package/dist/upgrade/directives/5-3-0.js +148 -0
- package/dist/upgrade/directives/5-3-0.js.map +1 -0
- package/dist/upgrade/directives/directivesController.js +2 -1
- package/dist/upgrade/directives/directivesController.js.map +1 -1
- package/dist/utility/OperationFunctionCaller.js +2 -1
- package/dist/utility/OperationFunctionCaller.js.map +1 -1
- package/dist/utility/common_utils.d.ts +16 -0
- package/dist/utility/common_utils.js +32 -6
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/componentNames.d.ts +8 -0
- package/dist/utility/componentNames.js +12 -1
- package/dist/utility/componentNames.js.map +1 -1
- package/dist/utility/environment/environmentManager.js +3 -6
- package/dist/utility/environment/environmentManager.js.map +1 -1
- package/dist/utility/environment/systemInformation.d.ts +1 -0
- package/dist/utility/environment/systemInformation.js +1 -0
- package/dist/utility/environment/systemInformation.js.map +1 -1
- package/dist/utility/errors/commonErrors.d.ts +2 -0
- package/dist/utility/errors/commonErrors.js +2 -0
- package/dist/utility/errors/commonErrors.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +33 -0
- package/dist/utility/errors/hdbError.js +58 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/globalSchema.d.ts +18 -0
- package/dist/utility/hdbTerms.d.ts +18 -0
- package/dist/utility/hdbTerms.js +20 -2
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +2 -0
- package/dist/utility/logging/harper_logger.js +286 -30
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
- package/dist/utility/logging/logGenerationCoordinator.js +184 -0
- package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
- package/dist/utility/logging/logRotation.d.ts +46 -0
- package/dist/utility/logging/logRotation.js +365 -0
- package/dist/utility/logging/logRotation.js.map +1 -0
- package/dist/utility/logging/logRotator.d.ts +1 -1
- package/dist/utility/logging/logRotator.js +192 -85
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/nodeIdentity.d.ts +9 -0
- package/dist/utility/nodeIdentity.js +58 -0
- package/dist/utility/nodeIdentity.js.map +1 -0
- package/dist/utility/npmUtilities.js +11 -7
- package/dist/utility/npmUtilities.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +3 -1
- package/dist/utility/operationPermissions.js +16 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +10 -7
- package/dist/utility/operation_authorization.js +212 -41
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/watchPath.d.ts +29 -0
- package/dist/utility/watchPath.js +68 -0
- package/dist/utility/watchPath.js.map +1 -0
- package/dist/utility/watcherFallback.d.ts +41 -0
- package/dist/utility/watcherFallback.js +153 -0
- package/dist/utility/watcherFallback.js.map +1 -1
- package/dist/validation/configValidator.d.ts +12 -0
- package/dist/validation/configValidator.js +205 -75
- package/dist/validation/configValidator.js.map +1 -1
- package/dist/validation/installValidator.js +12 -0
- package/dist/validation/installValidator.js.map +1 -1
- package/dist/validation/validationWrapper.d.ts +11 -0
- package/dist/validation/validationWrapper.js +16 -3
- package/dist/validation/validationWrapper.js.map +1 -1
- package/index.ts +8 -0
- package/json/systemSchema.json +55 -0
- package/npm-shrinkwrap.json +286 -194
- package/package.json +15 -7
- package/resources/DESIGN.md +219 -52
- package/resources/DatabaseTransaction.ts +657 -52
- package/resources/LMDBTransaction.ts +46 -6
- package/resources/PrimaryRocksDatabase.ts +46 -7
- package/resources/RecordEncoder.ts +124 -9
- package/resources/RequestTarget.ts +2 -0
- package/resources/Resource.ts +114 -22
- package/resources/ResourceInterface.ts +31 -0
- package/resources/RocksIndexStore.ts +30 -9
- package/resources/RocksTransactionLogStore.ts +188 -51
- package/resources/Table.ts +1960 -301
- package/resources/analytics/write.ts +10 -3
- package/resources/auditStore.ts +647 -32
- package/resources/blob.ts +1029 -110
- package/resources/branchDatabase.ts +941 -0
- package/resources/crdt.ts +76 -12
- package/resources/dataLoader.ts +3 -4
- package/resources/databases.ts +1584 -320
- package/resources/defineTable.ts +18 -2
- package/resources/derivedIndexRegistry.ts +56 -0
- package/resources/derivedIndexRuntime.ts +2292 -0
- package/resources/graphql.ts +72 -17
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +911 -48
- package/resources/indexes/hnswDerivedIndex.ts +531 -0
- package/resources/indexes/hnswPlaneBinding.ts +174 -0
- package/resources/longLivedTransactions.ts +360 -0
- package/resources/models/backendRegistry.ts +84 -2
- package/resources/models/bootstrap.ts +473 -28
- package/resources/nodeIdMapping.ts +50 -0
- package/resources/recordLock.ts +419 -0
- package/resources/recordLockCoordinator.ts +3043 -0
- package/resources/replayLogs.ts +152 -26
- package/resources/replayLogsGuards.ts +171 -8
- package/resources/replicatedApplyFailure.ts +77 -0
- package/resources/scheduler/scheduler.ts +4 -4
- package/resources/search.ts +288 -46
- package/resources/tracked.ts +73 -22
- package/resources/transactionBroadcast.ts +3 -3
- package/security/auth.ts +68 -29
- package/security/authn/oidc/claims.ts +72 -0
- package/security/authn/oidc/identityToken.ts +129 -0
- package/security/authn/oidc/jwks.ts +260 -0
- package/security/authn/oidc/providers/generic.ts +40 -0
- package/security/authn/oidc/providers/githubActions.ts +137 -0
- package/security/authn/oidc/providers/index.ts +52 -0
- package/security/authn/oidc/tokenExchange.ts +300 -0
- package/security/authn/oidc/trustPolicyOperations.ts +343 -0
- package/security/authn/oidc/types.ts +41 -0
- package/security/certificateVerification/index.ts +54 -13
- package/security/certificateVerification/trustedIssuers.ts +76 -0
- package/security/certificateVerification/types.ts +1 -0
- package/security/credentialProvenance.ts +47 -0
- package/security/credentialRejection.ts +22 -0
- package/security/deferredAuthentication.ts +71 -0
- package/security/impersonation.ts +117 -12
- package/security/jsLoader.ts +83 -18
- package/security/keys.ts +298 -72
- package/security/operationScope.ts +33 -0
- package/security/permissionsTranslator.js +23 -0
- package/security/tokenAuthentication.ts +233 -12
- package/security/user.ts +4 -3
- package/server/DESIGN.md +194 -16
- package/server/DurableSubscriptionsSession.ts +71 -11
- package/server/REST.ts +115 -4
- package/server/graphqlQuerying.ts +4 -0
- package/server/http.ts +133 -20
- package/server/itc/serverHandlers.js +8 -1
- package/server/jobs/jobProcess.ts +8 -2
- package/server/jobs/jobs.ts +4 -1
- package/server/liveSubscriptionAuth.ts +129 -46
- package/server/loadRootComponents.js +50 -8
- package/server/mqtt.ts +179 -38
- package/server/nodeName.ts +103 -21
- package/server/serverHelpers/Headers.ts +135 -0
- package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
- package/server/serverHelpers/Request.ts +33 -131
- package/server/serverHelpers/contentTypes.ts +217 -36
- package/server/serverHelpers/deployValidationState.ts +24 -13
- package/server/serverHelpers/operationAuthorizationState.ts +34 -3
- package/server/serverHelpers/registeredOperations.ts +79 -22
- package/server/serverHelpers/requestSanitization.ts +15 -0
- package/server/serverHelpers/serverHandlers.js +6 -3
- package/server/serverHelpers/serverUtilities.ts +232 -40
- package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
- package/server/serverHelpers/uwsServer.ts +17 -2
- package/server/static.ts +23 -29
- package/server/storageReclamation.ts +15 -2
- package/server/threads/isolatedApplications.ts +157 -0
- package/server/threads/itc.js +11 -1
- package/server/threads/logRotationTransport.ts +40 -0
- package/server/threads/manageThreads.js +771 -66
- package/server/threads/socketRouter.ts +291 -30
- package/server/threads/threadHeapMemory.ts +26 -0
- package/server/threads/threadServer.js +73 -22
- package/sqlTranslator/index.ts +31 -8
- package/studio/web/assets/{Chat-4RrB5134.js → Chat-D3j-1yY1.js} +1 -1
- package/studio/web/assets/{FloatingChat-omlNMDcJ.js → FloatingChat-BxJGYcfB.js} +3 -3
- package/studio/web/assets/{apiToken-Bke3wvfZ.js → apiToken-CT55oWOe.js} +1 -1
- package/studio/web/assets/{applications-DvFDYJgK.js → applications-D9Ct9_vm.js} +1 -1
- package/studio/web/assets/{cssMode-C1vRa7zh.js → cssMode-DV8H7VwA.js} +1 -1
- package/studio/web/assets/{editor-3XRWEDWX.js → editor-uatc0unt.js} +1 -1
- package/studio/web/assets/{html-CTY5tdMr.js → html-Bm6D6paN.js} +1 -1
- package/studio/web/assets/{htmlMode-CG1vSD9t.js → htmlMode-CEn7tpLG.js} +1 -1
- package/studio/web/assets/{index-Dfpeofdu.js → index-BIXW6Pu4.js} +5 -5
- package/studio/web/assets/{index.lazy-B1VOIv-t.js → index.lazy-UI7L-Vrk.js} +1 -1
- package/studio/web/assets/{javascript-CJeJzGnI.js → javascript-CJ0G3AFZ.js} +1 -1
- package/studio/web/assets/{jsonMode-CYPBwM82.js → jsonMode-DQADAYEa.js} +1 -1
- package/studio/web/assets/{languageServices-IKH4GUHl.js → languageServices-CAQJXWcI.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-BC8_gxKG.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
- package/studio/web/assets/{notifications-BfKBpYcq.js → notifications-BbxTU6Aw.js} +1 -1
- package/studio/web/assets/{notifications-DFbArTfC.js → notifications-Cvb3P1lB.js} +1 -1
- package/studio/web/assets/{profile-Q4-T6c-S.js → profile-Yyb7gsvL.js} +1 -1
- package/studio/web/assets/{regions-CtkV0xje.js → regions-OgjGHlU5.js} +1 -1
- package/studio/web/assets/{register-1ZZuMsiA.js → register-6qwNEOY3.js} +2 -2
- package/studio/web/assets/{setComponentFile-ZWLcWv5X.js → setComponentFile-BilDMtgB.js} +1 -1
- package/studio/web/assets/{setup-DWprJyJy.js → setup-J6qJ7OIU.js} +2 -2
- package/studio/web/assets/{status--aNm8isn.js → status-0RWGcfyD.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-Cq_lt3XD.js → toggleHighContrast-BIn-vErT.js} +1 -1
- package/studio/web/assets/{tsMode-pjgytARx.js → tsMode-DgUXku4d.js} +1 -1
- package/studio/web/assets/{typescript-Cdg0mqUh.js → typescript-C9orXcsM.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-RZhaY8Rn.js → useEntityRestURL-BEoXXbUB.js} +1 -1
- package/studio/web/assets/{workers-BgoXIqQe.js → workers-JVzSDmgx.js} +1 -1
- package/studio/web/assets/{xml-BwSeDMiP.js → xml-Cq-S8S4X.js} +1 -1
- package/studio/web/assets/{yaml-DotCUG5l.js → yaml-sfoRdh1M.js} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-3-0.ts +132 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/OperationFunctionCaller.ts +2 -1
- package/utility/common_utils.ts +30 -5
- package/utility/componentNames.ts +12 -0
- package/utility/environment/environmentManager.ts +3 -7
- package/utility/environment/systemInformation.ts +7 -0
- package/utility/errors/commonErrors.ts +4 -0
- package/utility/errors/hdbError.ts +57 -0
- package/utility/hdbTerms.ts +19 -0
- package/utility/logging/harper_logger.ts +278 -26
- package/utility/logging/logGenerationCoordinator.ts +196 -0
- package/utility/logging/logRotation.ts +367 -0
- package/utility/logging/logRotator.ts +213 -81
- package/utility/nodeIdentity.ts +45 -0
- package/utility/npmUtilities.ts +12 -8
- package/utility/operationPermissions.ts +18 -1
- package/utility/operation_authorization.ts +231 -42
- package/utility/watchPath.ts +63 -0
- package/utility/watcherFallback.ts +148 -0
- package/validation/configValidator.ts +215 -75
- package/validation/installValidator.ts +15 -0
- package/validation/validationWrapper.ts +18 -4
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Operations against system.hdb_oidc_trust — the trust policies for OIDC trusted publishing (#2171).
|
|
4
|
+
//
|
|
5
|
+
// A policy lets an external CI run authenticate as a Harper user without holding any Harper
|
|
6
|
+
// credential, so these are super_user only. Following components/secretOperations.ts, super_user is
|
|
7
|
+
// enforced in-handler as well as via requiredPermissions: a role's `operations` allowlist can
|
|
8
|
+
// otherwise delegate an SU-only operation (see the gate-2 bypass in utility/operation_authorization.ts).
|
|
9
|
+
//
|
|
10
|
+
// Rows reach peers through normal system-table replication; these operations deliberately do not
|
|
11
|
+
// call replicateOperation, which would double-apply on top of it.
|
|
12
|
+
|
|
13
|
+
import Joi from 'joi';
|
|
14
|
+
import { databases } from '../../../resources/databases.ts';
|
|
15
|
+
import * as terms from '../../../utility/hdbTerms.ts';
|
|
16
|
+
import { ClientError, hdbErrors } from '../../../utility/errors/hdbError.ts';
|
|
17
|
+
import { validateBySchema } from '../../../validation/validationWrapper.ts';
|
|
18
|
+
import { loggerWithTag } from '../../../utility/logging/logger.ts';
|
|
19
|
+
import { getUsersWithRolesCache } from '../../user.ts';
|
|
20
|
+
import { validateClaimConstraintShape } from './claims.ts';
|
|
21
|
+
import { normalizeIssuer } from './jwks.ts';
|
|
22
|
+
import { profileForIssuer } from './providers/index.ts';
|
|
23
|
+
import { validateOperations } from '../../../utility/operationPermissions.ts';
|
|
24
|
+
import type { OidcTrustPolicy } from './types.ts';
|
|
25
|
+
|
|
26
|
+
const { HTTP_STATUS_CODES } = hdbErrors;
|
|
27
|
+
const OIDC_TRUST_TABLE = terms.SYSTEM_TABLE_NAMES.OIDC_TRUST_TABLE_NAME;
|
|
28
|
+
// Same tag as tokenExchange: an ignored row and the exchange that ignored it belong in one stream.
|
|
29
|
+
const logger = loggerWithTag('oidc-trust');
|
|
30
|
+
|
|
31
|
+
const POLICY_ID = Joi.string()
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(128)
|
|
34
|
+
.pattern(/^[\w.-]+$/)
|
|
35
|
+
.required()
|
|
36
|
+
.messages({ 'string.pattern.base': "'id' may contain only letters, numbers, '_', '-', and '.'" });
|
|
37
|
+
|
|
38
|
+
function requireSuperUser(req: any): void {
|
|
39
|
+
if (!req?.hdb_user?.role?.permission?.super_user) {
|
|
40
|
+
throw new ClientError(
|
|
41
|
+
`Operation '${req?.operation}' is restricted to super_user roles`,
|
|
42
|
+
HTTP_STATUS_CODES.FORBIDDEN
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validate(validation: any): void {
|
|
48
|
+
if (validation) throw new ClientError(validation.message);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A typo would otherwise fail closed at request time, in CI, with nothing to point at — so it is
|
|
53
|
+
* caught here, where the reader is the administrator who wrote it. Delegates to the same helper
|
|
54
|
+
* add_role/alter_role use, so group names resolve identically rather than through a second
|
|
55
|
+
* definition that could drift.
|
|
56
|
+
*/
|
|
57
|
+
function assertOperationsAreKnown(operations: string[]): void {
|
|
58
|
+
const invalidOperation = validateOperations(operations);
|
|
59
|
+
if (invalidOperation != null) {
|
|
60
|
+
throw new ClientError(`operations contains '${invalidOperation}', which is not a Harper operation`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The audience is matched by exact string equality at verification time (`jwt.verify`), and the CLI
|
|
66
|
+
* asks its provider for a token whose audience is `normalizeTarget(target)` — which supplies `:9925`
|
|
67
|
+
* when no port was written, and a trailing slash. So `audience=https://my-instance.example.com`, the
|
|
68
|
+
* natural reading of "the instance URL the CI client targets", stores a policy that can never match
|
|
69
|
+
* anything; the exchange then refuses with the same opaque message it gives every other failure,
|
|
70
|
+
* leaving the operator nothing to look at, in CI. Caught here instead, where the reader is the
|
|
71
|
+
* administrator who wrote it — the same bargain assertOperationsAreKnown makes.
|
|
72
|
+
*
|
|
73
|
+
* Rejected rather than rewritten: silently canonicalizing a value whose whole job is to be compared
|
|
74
|
+
* byte-for-byte is worse than refusing it, and it would also have to run after the profile's
|
|
75
|
+
* shared-audience guard to avoid disarming it. Stating the requirement keeps this independent of
|
|
76
|
+
* normalizeTarget's exact spelling rules, which live in the CLI; a unit test pins the two together.
|
|
77
|
+
*
|
|
78
|
+
* Only http(s) URLs are shaped this way. An issuer-specific audience (an `api://` identifier, a bare
|
|
79
|
+
* GUID) is not ours to constrain, so anything that is not an http(s) URL passes untouched.
|
|
80
|
+
*/
|
|
81
|
+
function assertAudienceIsCanonical(audience: string): void {
|
|
82
|
+
let url: URL;
|
|
83
|
+
try {
|
|
84
|
+
url = new URL(audience);
|
|
85
|
+
} catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') return;
|
|
89
|
+
|
|
90
|
+
// `url.port` is empty for a default port, so consult the raw authority — with any userinfo
|
|
91
|
+
// removed first, since that colon is not a port.
|
|
92
|
+
const authority = audience.slice(audience.indexOf('://') + 3).split(/[/?#]/)[0];
|
|
93
|
+
const hasExplicitPort = !!url.port || authority.slice(authority.lastIndexOf('@') + 1).includes(':');
|
|
94
|
+
if (hasExplicitPort && audience.endsWith('/')) return;
|
|
95
|
+
|
|
96
|
+
throw new ClientError(
|
|
97
|
+
`'audience' must be the exact string the CI client requests its token for, which includes an ` +
|
|
98
|
+
`explicit port and a trailing slash (for example 'https://my-instance.example.com:9925/'). ` +
|
|
99
|
+
`'${audience}' is missing ${!hasExplicitPort ? 'a port' : 'a trailing slash'}, and the match is ` +
|
|
100
|
+
`byte-for-byte, so the policy could never authenticate.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function trustTable() {
|
|
105
|
+
const table = (databases as any).system?.[OIDC_TRUST_TABLE];
|
|
106
|
+
if (!table) {
|
|
107
|
+
throw new ClientError(
|
|
108
|
+
`OIDC trust policies are not initialized on this node (system.${OIDC_TRUST_TABLE} missing). ` +
|
|
109
|
+
`Run upgrade or restart the server to provision the table.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return table;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Rebuild a plain record from a stored row's known attributes. Never spread rows — RecordObject
|
|
117
|
+
* prototype fields don't survive a spread reliably (see DESIGN.md).
|
|
118
|
+
*/
|
|
119
|
+
function toRecord(row: any, problem?: string): OidcTrustPolicy & Record<string, unknown> {
|
|
120
|
+
return {
|
|
121
|
+
id: row.id,
|
|
122
|
+
issuer: row.issuer,
|
|
123
|
+
audience: row.audience,
|
|
124
|
+
claims: row.claims ?? {},
|
|
125
|
+
user: row.user,
|
|
126
|
+
operations: row.operations ?? null,
|
|
127
|
+
// Reported as stored rather than coerced. `row.enabled !== false` would render the string
|
|
128
|
+
// 'false' — one of the shapes the exchange refuses — as `enabled: true`, telling the operator
|
|
129
|
+
// a dead trust is live. Absent still means enabled, which is the documented default.
|
|
130
|
+
enabled: row.enabled === undefined ? true : row.enabled,
|
|
131
|
+
// Present only on a row the exchange will refuse, so a listing says why the trust is dead
|
|
132
|
+
// instead of leaving a per-attempt log line as the only signal. Shape problems are set here;
|
|
133
|
+
// listOidcTrust adds the ones that depend on state outside the row (a missing or inactive user).
|
|
134
|
+
...(problem ? { invalid_reason: problem } : {}),
|
|
135
|
+
description: row.description ?? null,
|
|
136
|
+
updated_by: row.updated_by ?? null,
|
|
137
|
+
__createdtime__: row.__createdtime__,
|
|
138
|
+
__updatedtime__: row.__updatedtime__,
|
|
139
|
+
} as any;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The policy set is small and administrator-managed, so a scan beats maintaining an index — and
|
|
144
|
+
* sorting by id keeps both the listing and the exchange's match order deterministic rather than
|
|
145
|
+
* dependent on an index's iteration order.
|
|
146
|
+
*/
|
|
147
|
+
async function readPolicies(
|
|
148
|
+
includeDisabled: boolean,
|
|
149
|
+
issuer?: string,
|
|
150
|
+
forExchange = false
|
|
151
|
+
): Promise<OidcTrustPolicy[]> {
|
|
152
|
+
const table = trustTable();
|
|
153
|
+
const policies: OidcTrustPolicy[] = [];
|
|
154
|
+
for await (const row of table.search([])) {
|
|
155
|
+
if (!includeDisabled && row.enabled === false) continue;
|
|
156
|
+
if (issuer !== undefined && row.issuer !== issuer) continue;
|
|
157
|
+
// Validated on BOTH paths, always against the RAW row — normalizing first is precisely what
|
|
158
|
+
// hides the shapes that fail open (see storedPolicyProblem). The two paths then differ in what
|
|
159
|
+
// they do with the answer: the exchange refuses the row, while a listing reports it. A listing
|
|
160
|
+
// that silently skipped a refused policy, or that showed it as healthy, would leave the
|
|
161
|
+
// operator's belief that the trust works corroborated by the very command they ran to check.
|
|
162
|
+
const problem = storedPolicyProblem(row);
|
|
163
|
+
if (forExchange) {
|
|
164
|
+
if (problem) {
|
|
165
|
+
logger.warn?.(`Ignoring trust policy '${row.id}': ${problem}`);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
policies.push(toRecord(row, problem));
|
|
170
|
+
}
|
|
171
|
+
return policies.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Why a stored row must not be honored, or undefined if it is usable.
|
|
176
|
+
*
|
|
177
|
+
* add_oidc_trust enforces all of this at write time, but a row can reach the table another way —
|
|
178
|
+
* replication from a node predating a check, a restored backup, a direct write to the system table —
|
|
179
|
+
* so the exchange re-runs the same validators rather than trusting that every row was validated when
|
|
180
|
+
* written. Two shapes in particular fail OPEN if merely normalized instead of rejected:
|
|
181
|
+
*
|
|
182
|
+
* operations: 'deploy_component' — a scalar rather than an array. hasOperationScope tests
|
|
183
|
+
* Array.isArray, so the scope is silently dropped and the token is minted UNSCOPED, carrying the
|
|
184
|
+
* policy user's entire role. A malformed narrowing must never widen.
|
|
185
|
+
* enabled: 'false' — a string. `row.enabled !== false` is true for it, so a policy
|
|
186
|
+
* an operator meant to disable keeps minting tokens.
|
|
187
|
+
*
|
|
188
|
+
* Shares validateOperations and validateClaimConstraintShape with the add path deliberately: two
|
|
189
|
+
* implementations of "is this row valid" is how the write path and the read path drift apart.
|
|
190
|
+
*/
|
|
191
|
+
export function storedPolicyProblem(row: any): string | undefined {
|
|
192
|
+
if (row.enabled !== undefined && typeof row.enabled !== 'boolean') {
|
|
193
|
+
return `'enabled' is a ${typeof row.enabled}, not a boolean`;
|
|
194
|
+
}
|
|
195
|
+
if (row.operations != null) {
|
|
196
|
+
if (!Array.isArray(row.operations)) return `'operations' is a ${typeof row.operations}, not an array`;
|
|
197
|
+
const invalidOperation = validateOperations(row.operations);
|
|
198
|
+
if (invalidOperation != null) return `operations contains '${invalidOperation}', which is not a Harper operation`;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
validateClaimConstraintShape(row.claims);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return (error as Error).message;
|
|
204
|
+
}
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The policies the exchange will consider, narrowed to one issuer.
|
|
210
|
+
*
|
|
211
|
+
* Filtered inside the scan rather than by the caller: the exchange is unauthenticated and reaches
|
|
212
|
+
* here having done nothing but `jwt.decode`, so an anonymous caller presenting any syntactically
|
|
213
|
+
* valid JWT would otherwise drive a `toRecord()` allocation for every stored policy before anything
|
|
214
|
+
* has been verified. The scan itself stays — the table is administrator-sized, and an index here
|
|
215
|
+
* would buy little (see readPolicies) — but non-matching rows now cost nothing beyond the compare.
|
|
216
|
+
*
|
|
217
|
+
* A missing table yields no policies rather than throwing. On a node where the upgrade directive has
|
|
218
|
+
* not run, trustTable()'s descriptive ClientError would surface to an anonymous caller as a 400 with
|
|
219
|
+
* a body unlike the uniform 401 every other rejection returns, which both leaks node state and
|
|
220
|
+
* breaks the single-message property. The SU-only add/list/drop handlers still get that error, where
|
|
221
|
+
* it is the useful thing to say.
|
|
222
|
+
*/
|
|
223
|
+
export function loadEnabledPolicies(issuer: string): Promise<OidcTrustPolicy[]> {
|
|
224
|
+
if (!(databases as any).system?.[OIDC_TRUST_TABLE]) return Promise.resolve([]);
|
|
225
|
+
return readPolicies(false, issuer, true);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Creates or replaces a trust policy.
|
|
230
|
+
*
|
|
231
|
+
* Replace rather than merge: a partial update to a claim set is how an over-broad policy gets
|
|
232
|
+
* created by accident, and the whole point of `claims` is that every constraint in it was written
|
|
233
|
+
* deliberately.
|
|
234
|
+
*/
|
|
235
|
+
export async function addOidcTrust(req: any) {
|
|
236
|
+
requireSuperUser(req);
|
|
237
|
+
validate(
|
|
238
|
+
validateBySchema(
|
|
239
|
+
req,
|
|
240
|
+
Joi.object({
|
|
241
|
+
id: POLICY_ID,
|
|
242
|
+
issuer: Joi.string().min(1).max(512).required(),
|
|
243
|
+
audience: Joi.string().min(1).max(512).required(),
|
|
244
|
+
claims: Joi.object().min(1).required(),
|
|
245
|
+
user: Joi.string().min(1).max(512).required(),
|
|
246
|
+
operations: Joi.array().items(Joi.string().min(1)).min(1).max(100).unique(),
|
|
247
|
+
// `.strict()` — Joi coerces by default, but validateBySchema keeps only `result.error`
|
|
248
|
+
// and discards the converted value, so `"false"` would validate cleanly and then be
|
|
249
|
+
// stored as the string it arrived as. `req.enabled !== false` is true for that string,
|
|
250
|
+
// which silently leaves a policy an operator meant to disable still minting tokens.
|
|
251
|
+
// A revocation control has to fail closed, so reject the string outright.
|
|
252
|
+
enabled: Joi.boolean().strict(),
|
|
253
|
+
description: Joi.string().allow('').max(1024),
|
|
254
|
+
}).unknown(true)
|
|
255
|
+
)
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const issuer = normalizeIssuer(req.issuer);
|
|
259
|
+
// Issuer-specific rules live in the provider profile; an unregistered issuer gets the strict
|
|
260
|
+
// generic profile rather than a permissive default. Each throws ClientError naming the problem.
|
|
261
|
+
const profile = profileForIssuer(issuer);
|
|
262
|
+
// On the RAW audience, before the canonical-form check below. GitHub's shared-audience guard
|
|
263
|
+
// matches `https://github.com/<owner>`, and a canonicalized form of that would no longer match
|
|
264
|
+
// the regex — checking the other way round would silently disarm it.
|
|
265
|
+
profile.assertAudienceIsSpecific(req.audience);
|
|
266
|
+
assertAudienceIsCanonical(req.audience);
|
|
267
|
+
if (req.operations) assertOperationsAreKnown(req.operations);
|
|
268
|
+
validateClaimConstraintShape(req.claims);
|
|
269
|
+
profile.assertPolicyIsSpecific(req.claims);
|
|
270
|
+
|
|
271
|
+
// Resolve the target user now: a policy pointing at a user that does not exist would fail only at
|
|
272
|
+
// exchange time, in CI, with nothing to point at. Read the users cache directly rather than
|
|
273
|
+
// findAndValidateUser — with validatePassword false, that returns a bare `{ username }` for an
|
|
274
|
+
// unknown user instead of failing, so it cannot answer "does this user exist".
|
|
275
|
+
const users = await getUsersWithRolesCache();
|
|
276
|
+
const targetUser = users?.get(req.user);
|
|
277
|
+
if (!targetUser) {
|
|
278
|
+
throw new ClientError(`No such user '${req.user}'; create the user before granting it to a workflow`);
|
|
279
|
+
}
|
|
280
|
+
if (targetUser.active === false) {
|
|
281
|
+
throw new ClientError(`User '${req.user}' is inactive; a policy naming it could never authenticate`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const table = trustTable();
|
|
285
|
+
await table.put({
|
|
286
|
+
id: req.id,
|
|
287
|
+
issuer,
|
|
288
|
+
audience: req.audience,
|
|
289
|
+
claims: req.claims,
|
|
290
|
+
user: req.user,
|
|
291
|
+
operations: req.operations ?? null,
|
|
292
|
+
enabled: req.enabled !== false,
|
|
293
|
+
description: req.description ?? null,
|
|
294
|
+
updated_by: req.hdb_user?.username ?? null,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const result: Record<string, unknown> = { message: `Successfully set OIDC trust policy '${req.id}'` };
|
|
298
|
+
// Not an error — an admin may genuinely want this — but a policy that hands super_user to a
|
|
299
|
+
// workflow deserves to be said out loud rather than discovered later.
|
|
300
|
+
if (targetUser.role?.permission?.super_user) {
|
|
301
|
+
result.warning =
|
|
302
|
+
`Policy '${req.id}' authenticates as '${req.user}', which is a super_user. Any run matching this ` +
|
|
303
|
+
`policy gains full administrative access; consider a user whose role grants only the operations CI needs.`;
|
|
304
|
+
}
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export async function listOidcTrust(req: any) {
|
|
309
|
+
requireSuperUser(req);
|
|
310
|
+
const policies = await readPolicies(true);
|
|
311
|
+
|
|
312
|
+
// The exchange also refuses a well-formed row whose user has since been deleted or deactivated
|
|
313
|
+
// (tokenExchange resolves it before spending the token), with the same opaque 401 — so without
|
|
314
|
+
// this the listing would still say the trust is fine for the most mundane arrival of all: someone
|
|
315
|
+
// deletes or deactivates the CI user and every deploy starts failing.
|
|
316
|
+
//
|
|
317
|
+
// Annotated here rather than in readPolicies, and deliberately: this is one cache read for the
|
|
318
|
+
// whole listing on an SU-only path, where doing it per row in readPolicies would put a user lookup
|
|
319
|
+
// on the unauthenticated exchange path, which already resolves the user itself at the right moment.
|
|
320
|
+
// A shape problem already reported wins, since it is the more fundamental complaint.
|
|
321
|
+
const users = await getUsersWithRolesCache();
|
|
322
|
+
for (const policy of policies as any[]) {
|
|
323
|
+
if (policy.invalid_reason) continue;
|
|
324
|
+
const user = users?.get(policy.user);
|
|
325
|
+
if (!user) policy.invalid_reason = `names user '${policy.user}', which does not exist`;
|
|
326
|
+
else if (user.active === false) policy.invalid_reason = `names inactive user '${policy.user}'`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return { policies };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export async function dropOidcTrust(req: any) {
|
|
333
|
+
requireSuperUser(req);
|
|
334
|
+
validate(validateBySchema(req, Joi.object({ id: POLICY_ID }).unknown(true)));
|
|
335
|
+
|
|
336
|
+
const table = trustTable();
|
|
337
|
+
const row = await table.get(req.id);
|
|
338
|
+
if (!row) {
|
|
339
|
+
throw new ClientError(`No OIDC trust policy found with id '${req.id}'`, HTTP_STATUS_CODES.NOT_FOUND);
|
|
340
|
+
}
|
|
341
|
+
await table.delete(req.id);
|
|
342
|
+
return { message: `Successfully dropped OIDC trust policy '${req.id}'` };
|
|
343
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for OIDC trusted publishing (#2171).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** One accepted value, or a set of them. */
|
|
6
|
+
export type ClaimConstraint = string | string[];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A stored trust policy. Matching one lets an external CI run act as `user` without holding any
|
|
10
|
+
* Harper credential, so `claims` is validated at write time rather than trusted as written — see
|
|
11
|
+
* validateClaimConstraintShape (claims.ts) and the profile's assertPolicyIsSpecific for the
|
|
12
|
+
* structural requirements, and addOidcTrust for the rest.
|
|
13
|
+
*/
|
|
14
|
+
export interface OidcTrustPolicy {
|
|
15
|
+
id: string;
|
|
16
|
+
/** Expected `iss`, and the base for OIDC discovery. */
|
|
17
|
+
issuer: string;
|
|
18
|
+
/** Expected `aud`. Must identify this instance — see SHARED_DEFAULT_AUDIENCE. */
|
|
19
|
+
audience: string;
|
|
20
|
+
claims: Record<string, ClaimConstraint>;
|
|
21
|
+
/** The exchanged token authenticates as this user, whose role is the least-privilege boundary. */
|
|
22
|
+
user: string;
|
|
23
|
+
/**
|
|
24
|
+
* Optional narrowing of that boundary, enforced on the **operations API and SQL** paths (the
|
|
25
|
+
* verifyPerms / verifyPermsAST gate): the minted token may invoke only these operations, even
|
|
26
|
+
* where the role allows more. Never widens — an operation the role forbids stays forbidden.
|
|
27
|
+
*
|
|
28
|
+
* NOT enforced on the application REST/GraphQL resource path, which authorizes through the
|
|
29
|
+
* table-level checkPermission and does not consult this scope — so a scoped operation token still
|
|
30
|
+
* carries the role's full CRUD there. Extending the scope to the resource path is a follow-up on
|
|
31
|
+
* the same surface as the GraphQL-bypasses-ops-allowlist gap (CORE-3061); until then, scope a
|
|
32
|
+
* policy's `user` to a role that is itself least-privilege for the data the token can reach.
|
|
33
|
+
*/
|
|
34
|
+
operations?: string[];
|
|
35
|
+
/** Defaults to true; false keeps the policy for reference without honoring it. */
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
description?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A verified token's payload, plus the entries the profile's normalizeClaims derives. */
|
|
41
|
+
export type TokenClaims = Record<string, unknown>;
|
|
@@ -27,10 +27,14 @@ import { extractCertificateChain, extractRevocationUrls, bufferToPem } from './v
|
|
|
27
27
|
import { getCachedCertificateVerificationConfig } from './verificationConfig.ts';
|
|
28
28
|
import { verifyOCSP } from './ocspVerification.ts';
|
|
29
29
|
import { verifyCRL } from './crlVerification.ts';
|
|
30
|
-
import
|
|
30
|
+
import { resolveTrustedIssuer } from './trustedIssuers.ts';
|
|
31
|
+
import type { PeerCertificate, CertificateVerificationResult, FailureMode } from './types.ts';
|
|
31
32
|
|
|
32
33
|
const logger = loggerWithTag('cert-verification');
|
|
33
34
|
|
|
35
|
+
const warnedUnresolvedLeaves = new Set<string>();
|
|
36
|
+
const MAX_WARNED_LEAVES = 10_000;
|
|
37
|
+
|
|
34
38
|
/**
|
|
35
39
|
* Verify certificate revocation status using OCSP and/or CRL
|
|
36
40
|
* @param peerCertificate - Peer certificate object from TLS connection
|
|
@@ -52,14 +56,32 @@ export async function verifyCertificate(
|
|
|
52
56
|
return { valid: true, status: 'disabled', method: 'disabled' };
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
// ahead of the chain check so an explicitly disabled control can never reject
|
|
60
|
+
if (config.crl.enabled === false && config.ocsp.enabled === false) {
|
|
61
|
+
logger.debug?.('Both CRL and OCSP disabled - verification disabled');
|
|
62
|
+
return { valid: true, status: 'disabled', method: 'disabled' };
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
// Extract certificate chain
|
|
56
66
|
const certChain = extractCertificateChain(peerCertificate);
|
|
57
67
|
logger.trace?.(`Certificate chain length: ${certChain.length}`);
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
if (certChain.length === 0) {
|
|
70
|
+
return unresolvedIssuerResult(config.failureMode, peerCertificate, 'no certificate data');
|
|
71
|
+
}
|
|
72
|
+
// Revocation checking needs the issuer. The socket omits it on every resumed TLS session and on
|
|
73
|
+
// Node 26.8.0/26.8.1 (nodejs/node#65579); recover it from the CA set that authorized the client.
|
|
74
|
+
if (!certChain[0].issuer) {
|
|
75
|
+
const issuer = resolveTrustedIssuer(certChain[0].cert, peerCertificate.fingerprint256);
|
|
76
|
+
if (!issuer) {
|
|
77
|
+
return unresolvedIssuerResult(
|
|
78
|
+
config.failureMode,
|
|
79
|
+
peerCertificate,
|
|
80
|
+
'issuer is neither in the presented chain nor among the configured certificate authorities'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
logger.debug?.('Issuer certificate resolved from the configured certificate authorities');
|
|
84
|
+
certChain[0].issuer = issuer;
|
|
63
85
|
}
|
|
64
86
|
|
|
65
87
|
// Extract certificate revocation URLs in single parse operation
|
|
@@ -117,17 +139,36 @@ export async function verifyCertificate(
|
|
|
117
139
|
logger.debug?.('Skipping OCSP - no responder URLs in certificate');
|
|
118
140
|
}
|
|
119
141
|
|
|
120
|
-
// All methods tried or skipped -
|
|
121
|
-
// If both methods are explicitly disabled (not just unavailable), treat as disabled
|
|
122
|
-
if (config.crl.enabled === false && config.ocsp.enabled === false) {
|
|
123
|
-
logger.debug?.('Both CRL and OCSP disabled - verification disabled');
|
|
124
|
-
return { valid: true, status: 'disabled', method: 'disabled' };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Methods are enabled but unavailable - apply failure mode
|
|
142
|
+
// All methods tried or skipped - apply failure mode
|
|
128
143
|
if (config.failureMode === 'fail-closed') {
|
|
129
144
|
return { valid: false, status: 'no-verification-available', method: 'disabled' };
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
return { valid: true, status: 'verification-unavailable-allowed', method: 'disabled' };
|
|
133
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Revocation status cannot be established without the issuer, so the configured failure mode
|
|
152
|
+
* decides: refuse under fail-closed, allow under fail-open. Either way this is a security control
|
|
153
|
+
* not running, so it is logged at warn (once per certificate) rather than debug.
|
|
154
|
+
*/
|
|
155
|
+
function unresolvedIssuerResult(
|
|
156
|
+
failureMode: FailureMode | undefined,
|
|
157
|
+
peerCertificate: PeerCertificate,
|
|
158
|
+
reason: string
|
|
159
|
+
): CertificateVerificationResult {
|
|
160
|
+
const valid = failureMode !== 'fail-closed';
|
|
161
|
+
const leafKey = peerCertificate.fingerprint256 ?? peerCertificate.subject?.CN ?? '';
|
|
162
|
+
if (!warnedUnresolvedLeaves.has(leafKey)) {
|
|
163
|
+
if (warnedUnresolvedLeaves.size >= MAX_WARNED_LEAVES) warnedUnresolvedLeaves.clear();
|
|
164
|
+
warnedUnresolvedLeaves.add(leafKey);
|
|
165
|
+
logger.warn?.(
|
|
166
|
+
`Cannot check revocation status for client certificate ${peerCertificate.subject?.CN ?? 'unknown'}: ${reason}; ${
|
|
167
|
+
valid ? 'allowing connection (fail-open)' : 'rejecting connection (fail-closed)'
|
|
168
|
+
}`
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
logger.debug?.(`Cannot check revocation status for ${peerCertificate.subject?.CN ?? 'unknown'}: ${reason}`);
|
|
172
|
+
}
|
|
173
|
+
return { valid, status: 'no-issuer-cert', method: 'disabled' };
|
|
174
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a client certificate's issuer from Harper's own trusted certificate authorities.
|
|
3
|
+
*
|
|
4
|
+
* Node completes a peer chain from the trust store only through the listener's default TLS
|
|
5
|
+
* context; Harper's client CAs live on the SNI contexts built by createTLSSelector, so whenever the
|
|
6
|
+
* client-sent chain is unavailable (every resumed TLS session; Node 26.8.0/26.8.1 on every
|
|
7
|
+
* connection, nodejs/node#65579) getPeerCertificate(true) exposes the leaf alone. The CA set that
|
|
8
|
+
* authorized the connection is the same set published here, so the issuer is recoverable from it
|
|
9
|
+
* unless it is an intermediate the client sent but Harper does not trust directly.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { X509Certificate, createHash } from 'node:crypto';
|
|
13
|
+
|
|
14
|
+
const PEM_CERTIFICATE_PATTERN =
|
|
15
|
+
/-----BEGIN (?:TRUSTED |X509 )?CERTIFICATE-----[\s\S]*?-----END (?:TRUSTED |X509 )?CERTIFICATE-----/g;
|
|
16
|
+
const MAX_RESOLVED_LEAVES = 10_000;
|
|
17
|
+
|
|
18
|
+
let trustedAuthorities: X509Certificate[] = [];
|
|
19
|
+
let publishedAuthorityPems = '';
|
|
20
|
+
let resolvedIssuers = new Map<string, Buffer | null>();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Publish the current trusted authority set (PEM strings, each possibly a bundle of several
|
|
24
|
+
* certificates). Replaces the previous generation atomically and drops its resolution cache,
|
|
25
|
+
* unless the set is unchanged. Unparseable certificates are skipped so one bad record cannot
|
|
26
|
+
* disable resolution for the rest; this never throws, because it runs inside the TLS selector's
|
|
27
|
+
* publication step.
|
|
28
|
+
*/
|
|
29
|
+
export function publishTrustedAuthorities(authorityPems: Iterable<unknown>): void {
|
|
30
|
+
const pems = Array.from(authorityPems).filter((pem): pem is string => typeof pem === 'string');
|
|
31
|
+
const joined = pems.join('\n');
|
|
32
|
+
if (joined === publishedAuthorityPems) return;
|
|
33
|
+
publishedAuthorityPems = joined;
|
|
34
|
+
const parsed: X509Certificate[] = [];
|
|
35
|
+
for (const pem of pems) {
|
|
36
|
+
for (const block of pem.match(PEM_CERTIFICATE_PATTERN) ?? []) {
|
|
37
|
+
try {
|
|
38
|
+
parsed.push(new X509Certificate(block));
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
trustedAuthorities = parsed;
|
|
43
|
+
resolvedIssuers = new Map();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Find the trusted authority that issued `leafDer`: subject/AKI match plus a signature check, so a
|
|
48
|
+
* same-named authority with a different key never counts, and a certificate is never its own
|
|
49
|
+
* issuer. Returns the issuer's DER, or undefined.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveTrustedIssuer(leafDer: Buffer, fingerprint256?: string): Buffer | undefined {
|
|
52
|
+
if (trustedAuthorities.length === 0) return undefined;
|
|
53
|
+
const key = fingerprint256 ?? createHash('sha256').update(leafDer).digest('hex');
|
|
54
|
+
const cached = resolvedIssuers.get(key);
|
|
55
|
+
if (cached !== undefined) return cached ?? undefined;
|
|
56
|
+
|
|
57
|
+
let issuer: Buffer | null = null;
|
|
58
|
+
try {
|
|
59
|
+
const leaf = new X509Certificate(leafDer);
|
|
60
|
+
for (const authority of trustedAuthorities) {
|
|
61
|
+
try {
|
|
62
|
+
if (
|
|
63
|
+
authority.fingerprint256 !== leaf.fingerprint256 &&
|
|
64
|
+
leaf.checkIssued(authority) &&
|
|
65
|
+
leaf.verify(authority.publicKey)
|
|
66
|
+
) {
|
|
67
|
+
issuer = authority.raw;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
} catch {}
|
|
73
|
+
if (resolvedIssuers.size >= MAX_RESOLVED_LEAVES) resolvedIssuers.clear();
|
|
74
|
+
resolvedIssuers.set(key, issuer);
|
|
75
|
+
return issuer ?? undefined;
|
|
76
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provenance for a credential minted from a workload identity exchange (#2171) rather than from a
|
|
3
|
+
* password.
|
|
4
|
+
*
|
|
5
|
+
* The exchange's whole guarantee is that CI holds nothing durable: a one-hour operation token, no
|
|
6
|
+
* refresh token, nothing on disk to leak. That guarantee is only as strong as the paths that turn
|
|
7
|
+
* one credential into another. `create_authentication_tokens` is such a path and does not look like
|
|
8
|
+
* one: it is in `NO_AUTH_OPERATIONS`, but `serverHandlers.js` special-cases it so a call with no
|
|
9
|
+
* username/password authenticates by Bearer token instead — so an exchanged token is accepted there
|
|
10
|
+
* as if it were a password, honors a caller-supplied `expires_in` verbatim, and returns a 30-day
|
|
11
|
+
* refresh token. That converts a minutes-long leak into a month-long one, which is the exact
|
|
12
|
+
* exposure this feature exists to remove.
|
|
13
|
+
*
|
|
14
|
+
* A scope (operationScope.ts) cannot carry this weight. A trust policy names `operations` only when
|
|
15
|
+
* the operator opts in, so the ordinary exchanged token is UNSCOPED and a scope check finds nothing
|
|
16
|
+
* to deny — the common case would sail through a scope-only guard. Provenance is therefore recorded
|
|
17
|
+
* independently of scope, on every exchanged token.
|
|
18
|
+
*
|
|
19
|
+
* Like the scope, it lives under two names depending on the carrier: the claim `workload_identity`
|
|
20
|
+
* on a JWT payload, and `fromWorkloadIdentity` on an in-memory principal (validateToken lifts the
|
|
21
|
+
* claim across). And like the scope, every path that produces a credential or a principal has to
|
|
22
|
+
* carry it forward — impersonation included, or a workload token launders its provenance by
|
|
23
|
+
* impersonating and then mints freely.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** The JWT claim name. Signed with the rest of the payload, so it cannot be stripped in transit. */
|
|
27
|
+
export const WORKLOAD_IDENTITY_CLAIM = 'workload_identity';
|
|
28
|
+
|
|
29
|
+
/** Stamps a token payload as workload-identity provenance. Returns the payload. */
|
|
30
|
+
export function markTokenAsWorkloadIdentity<T extends object>(payload: T): T {
|
|
31
|
+
(payload as any)[WORKLOAD_IDENTITY_CLAIM] = true;
|
|
32
|
+
return payload;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Lifts the claim from a verified token onto a user principal. Returns the user. */
|
|
36
|
+
export function attachWorkloadIdentityToUser<T extends object>(user: T, claim: unknown): T {
|
|
37
|
+
if (claim === true) (user as any).fromWorkloadIdentity = true;
|
|
38
|
+
return user;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* True when this principal authenticated with a workload-identity token. Checked strictly against
|
|
43
|
+
* `true` so a forged string or object on a user record cannot widen it.
|
|
44
|
+
*/
|
|
45
|
+
export function isWorkloadIdentityPrincipal(user: unknown): boolean {
|
|
46
|
+
return (user as any)?.fromWorkloadIdentity === true;
|
|
47
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ClientError } from '../utility/errors/hdbError.ts';
|
|
2
|
+
|
|
3
|
+
// Explicit provenance prevents internal faults with a 4xx status from being deferred as rejected credentials.
|
|
4
|
+
const CREDENTIAL_REJECTION = Symbol('harper.credentialRejection');
|
|
5
|
+
|
|
6
|
+
export function markCredentialRejection<E extends object>(error: E): E {
|
|
7
|
+
Object.defineProperty(error, CREDENTIAL_REJECTION, {
|
|
8
|
+
value: true,
|
|
9
|
+
enumerable: false,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: false,
|
|
12
|
+
});
|
|
13
|
+
return error;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function credentialRejectionError(message: string, statusCode: number): ClientError {
|
|
17
|
+
return markCredentialRejection(new ClientError(message, statusCode));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isCredentialRejection(error: unknown): boolean {
|
|
21
|
+
return (error as Record<symbol, unknown> | null | undefined)?.[CREDENTIAL_REJECTION] === true;
|
|
22
|
+
}
|