@opensoha/contracts 0.1.23 → 0.1.25
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/COMPATIBILITY.md +95 -0
- package/README.md +24 -6
- package/agent/environment/README.md +55 -0
- package/agent/environment/export.go +139 -0
- package/agent/environment/export_test.go +62 -0
- package/agent/environment/preflight.go +62 -0
- package/agent/environment/preflight_test.go +41 -0
- package/agent/environment/protocol.go +221 -0
- package/agent/environment/protocol_test.go +81 -0
- package/agent/environment-lease.schema.json +2 -2
- package/agent/environment-template.schema.json +1 -1
- package/agent/guest/browser_network.go +77 -0
- package/agent/guest/data.go +62 -0
- package/agent/guest/protocol.go +235 -0
- package/agent/guest/protocol_test.go +142 -0
- package/agent/guest/renewal.go +56 -0
- package/agent/guest/renewal_test.go +53 -0
- package/agent/guest/runtime.go +315 -0
- package/agent/guest/runtime_json.go +75 -0
- package/agent/guest/runtime_test.go +110 -0
- package/agent/personal-browser-network-request.schema.json +40 -0
- package/agent/personal-browser-network-response.schema.json +44 -0
- package/agent/personal-environment-export.schema.json +55 -0
- package/agent/personal-environment-preflight-request.schema.json +52 -0
- package/agent/personal-environment-preflight-result.schema.json +69 -0
- package/agent/personal-environment-request.schema.json +137 -0
- package/agent/personal-environment-result.schema.json +101 -0
- package/agent/personal-guest-binding.schema.json +92 -0
- package/agent/personal-guest-data-plan.schema.json +32 -0
- package/agent/personal-guest-enrollment.schema.json +21 -0
- package/agent/personal-guest-heartbeat.schema.json +44 -0
- package/agent/personal-guest-provisioning.schema.json +71 -0
- package/agent/personal-guest-renewal.schema.json +33 -0
- package/agent/personal-guest-runtime-result.schema.json +164 -0
- package/agent/personal-guest-runtime.schema.json +304 -0
- package/agent/personal-guest-tools.schema.json +75 -0
- package/auth/catalog_generated.go +2 -2
- package/auth/gateway_permissions_test.go +48 -0
- package/auth/permission-catalog.json +331 -1
- package/auth/token-claims.schema.json +5 -0
- package/compat/consumer-revisions.json +3 -2
- package/compat/json-schema-ecosystem-0.1.0.json +1 -1
- package/compat/openapi-loose-boundaries-0.1.0.json +1 -1
- package/connectors/connector-action.schema.json +138 -0
- package/fixtures/json-schema/connector-action/invalid/empty-values.json +11 -0
- package/fixtures/json-schema/connector-action/invalid/invalid-expiry.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/lowercase-alias.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/unknown-entry-field.json +17 -0
- package/fixtures/json-schema/connector-action/invalid/unknown-frame-field.json +17 -0
- package/fixtures/json-schema/connector-action/invalid/unknown-version.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/unversioned-reference.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/zero-version.json +16 -0
- package/fixtures/json-schema/connector-action/valid/pinned.json +16 -0
- package/fixtures/json-schema/connector-definition/invalid/credential-userinfo.json +8 -0
- package/fixtures/json-schema/connector-definition/invalid/reference-traversal.json +8 -0
- package/fixtures/json-schema/connector-definition/invalid/zero-version.json +8 -0
- package/fixtures/json-schema/connector-definition/valid/canonical.json +8 -0
- package/fixtures/json-schema/connector-definition/valid/pinned.json +8 -0
- package/fixtures/json-schema/personal-browser-network-request/invalid/bounded.json +4 -0
- package/fixtures/json-schema/personal-browser-network-request/valid/bounded.json +4 -0
- package/fixtures/json-schema/personal-browser-network-response/invalid/bounded.json +5 -0
- package/fixtures/json-schema/personal-browser-network-response/valid/bounded.json +5 -0
- package/fixtures/json-schema/personal-environment-export/invalid/extra.json +11 -0
- package/fixtures/json-schema/personal-environment-export/invalid/ownerId.json +10 -0
- package/fixtures/json-schema/personal-environment-export/invalid/sha256.json +10 -0
- package/fixtures/json-schema/personal-environment-export/invalid/sizeBytes.json +10 -0
- package/fixtures/json-schema/personal-environment-export/valid/complete.json +10 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/disk.json +11 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/network.json +11 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/node.json +12 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/owner.json +12 -0
- package/fixtures/json-schema/personal-environment-preflight-request/valid/bounded.json +11 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/disk.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/network.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/no-hard-quota.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/node.json +16 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/not-ready.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/owner.json +16 -0
- package/fixtures/json-schema/personal-environment-preflight-result/valid/bounded.json +15 -0
- package/fixtures/json-schema/personal-environment-request/invalid/delete-data-argv.json +24 -0
- package/fixtures/json-schema/personal-environment-request/invalid/delete-data-createData.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/delete-data-taskId.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/export-argv.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/export-createData.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/export-taskId.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/extra-command.json +24 -0
- package/fixtures/json-schema/personal-environment-request/invalid/host-workspace.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/image-injection.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/network-egress.json +21 -0
- package/fixtures/json-schema/personal-environment-request/invalid/no-command.json +21 -0
- package/fixtures/json-schema/personal-environment-request/invalid/recreate-on-release.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/relative-command.json +24 -0
- package/fixtures/json-schema/personal-environment-request/invalid/released-identity.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-empty.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-host-path.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-noncanonical.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-null.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-on-inspection.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/unbounded-quota.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/acquire.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/delete-data.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/execute.json +26 -0
- package/fixtures/json-schema/personal-environment-request/valid/export-data.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/inspect.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/provision.json +22 -0
- package/fixtures/json-schema/personal-environment-request/valid/release.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/task-workspace.json +27 -0
- package/fixtures/json-schema/personal-environment-result/invalid/active-without-observation.json +7 -0
- package/fixtures/json-schema/personal-environment-result/invalid/deleted-output.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/deleted-snapshot.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/deleted-taskId.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/private-handle.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/stopped-fake-observation.json +20 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-empty.json +22 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-host-path.json +22 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-null.json +22 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-on-stopped.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-without-command-output.json +21 -0
- package/fixtures/json-schema/personal-environment-result/valid/active.json +21 -0
- package/fixtures/json-schema/personal-environment-result/valid/deleted.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/expired.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/missing.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/released.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/task-workspace.json +22 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/host-path.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/invalid-policy.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/invalid-serial.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/legacy-version.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/missing-generation.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/missing-identity.json +16 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/missing-provider-identity.json +16 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/raw-provider-identity.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/valid/minimal.json +17 -0
- package/fixtures/json-schema/personal-guest-data-plan/invalid/quota.json +1 -0
- package/fixtures/json-schema/personal-guest-data-plan/valid/basic.json +1 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/capabilities.json +7 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/legacy-version.json +4 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/missing-identity.json +3 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/ownerId.json +5 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/token.json +5 -0
- package/fixtures/json-schema/personal-guest-enrollment/valid/minimal.json +4 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/argv.json +11 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/legacy-version.json +8 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/missing-identity.json +7 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/owner.json +9 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/self-ready.json +9 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/zero-sequence.json +8 -0
- package/fixtures/json-schema/personal-guest-heartbeat/valid/minimal.json +8 -0
- package/fixtures/json-schema/personal-guest-provisioning/invalid/network.json +1 -0
- package/fixtures/json-schema/personal-guest-provisioning/valid/basic.json +1 -0
- package/fixtures/json-schema/personal-guest-renewal/invalid/unknown.json +1 -0
- package/fixtures/json-schema/personal-guest-renewal/valid/basic.json +1 -0
- package/fixtures/json-schema/personal-guest-runtime/invalid/minimal.json +45 -0
- package/fixtures/json-schema/personal-guest-runtime/valid/minimal.json +45 -0
- package/fixtures/json-schema/personal-guest-runtime-result/invalid/minimal.json +28 -0
- package/fixtures/json-schema/personal-guest-runtime-result/valid/minimal.json +27 -0
- package/fixtures/json-schema/personal-guest-tools/invalid/minimal.json +31 -0
- package/fixtures/json-schema/personal-guest-tools/valid/minimal.json +30 -0
- package/fixtures/json-schema/source-import-policy/invalid/case-variant-scope.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/case-variant-version.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/duplicate-grant.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/empty-id.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/future-version.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/unknown-field.json +4 -0
- package/fixtures/json-schema/source-import-policy/valid/deny-all.json +4 -0
- package/fixtures/json-schema/source-import-policy/valid/shared.json +4 -0
- package/fixtures/json-schema/token-claims/invalid/authz-negative.json +23 -0
- package/fixtures/json-schema/token-claims/invalid/authz-string.json +23 -0
- package/fixtures/json-schema/token-claims/invalid/authz-zero.json +23 -0
- package/fixtures/json-schema/token-claims/valid/versioned-session.json +23 -0
- package/fixtures/openapi/AgentRunEventListEnvelope/invalid/basic.json +8 -0
- package/fixtures/openapi/AgentRunEventListEnvelope/valid/basic.json +8 -0
- package/fixtures/openapi/AgentRunEventListEnvelope/valid/native.json +1 -0
- package/fixtures/openapi/AgentRunResumeRequest/invalid/basic.json +3 -0
- package/fixtures/openapi/AgentRunResumeRequest/valid/basic.json +3 -0
- package/fixtures/openapi/AgentSSHCommandRequest/invalid/task51.json +24 -0
- package/fixtures/openapi/AgentSSHCommandRequest/valid/task51.json +23 -0
- package/fixtures/openapi/AgentSSHCommandResult/invalid/task51.json +8 -0
- package/fixtures/openapi/AgentSSHCommandResult/valid/task51.json +7 -0
- package/fixtures/openapi/AgentSSHSessionRequest/invalid/task51.json +20 -0
- package/fixtures/openapi/AgentSSHSessionRequest/valid/task51.json +19 -0
- package/fixtures/openapi/CapabilityTaskInput/invalid/duplicate-scope.json +36 -0
- package/fixtures/openapi/CapabilityTaskInput/valid/scoped.json +32 -0
- package/fixtures/openapi/DockerQuickCreateHostInput/invalid/guest-access.json +7 -0
- package/fixtures/openapi/DockerQuickCreateHostInput/valid/guest-access.json +9 -0
- package/fixtures/openapi/KnowledgeSourceDocument/invalid/tombstone-with-content.json +1 -0
- package/fixtures/openapi/KnowledgeSourceDocument/valid/tombstone.json +1 -0
- package/fixtures/openapi/KubernetesAgentActionCheck/invalid/unproven-state.json +6 -0
- package/fixtures/openapi/KubernetesAgentActionCheck/valid/configured-denied.json +9 -0
- package/fixtures/openapi/KubernetesAgentActionProfile/invalid/wildcard.json +1 -0
- package/fixtures/openapi/KubernetesAgentActionProfile/valid/workbench.json +1 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/invalid/action-profile.json +3 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/action-profile.json +5 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/invalid/condition-status-boolean.json +17 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/invalid/taint-effect-missing.json +16 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/legacy-fields-absent.json +11 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/no-taints.json +13 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/ready-cordoned-tainted.json +28 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/unknown-fields-null.json +13 -0
- package/fixtures/openapi/KubernetesResourceUpdatePlanRequest/invalid/missing-required.json +1 -0
- package/fixtures/openapi/KubernetesResourceYaml/invalid/missing-required.json +1 -0
- package/fixtures/openapi/KubernetesTopologyCoverage/invalid/empty-as-success.json +5 -0
- package/fixtures/openapi/KubernetesTopologyCoverage/valid/not-installed.json +6 -0
- package/fixtures/openapi/KubernetesTopologyGraphEdge/invalid/negative-weight.json +8 -0
- package/fixtures/openapi/KubernetesTopologyGraphEdge/valid/zero-weight.json +20 -0
- package/fixtures/openapi/KubernetesTopologyGraphNode/invalid/fake-flow-kind.json +42 -0
- package/fixtures/openapi/KubernetesTopologyGraphNode/valid/rule.json +42 -0
- package/fixtures/openapi/KubernetesTopologySnapshot/invalid/raw-source-field.json +33 -0
- package/fixtures/openapi/KubernetesTopologySnapshot/valid/projected-source.json +32 -0
- package/fixtures/openapi/KubernetesTopologySnapshotRequest/invalid/arbitrary-gvr.json +8 -0
- package/fixtures/openapi/KubernetesTopologySnapshotRequest/valid/bounded-sources.json +11 -0
- package/fixtures/openapi/KubernetesTopologySourceQuery/invalid/cluster-namespace.json +4 -0
- package/fixtures/openapi/KubernetesTopologySourceQuery/valid/all-namespaces.json +4 -0
- package/fixtures/openapi/ObservabilityDataSource/invalid/missing-required.json +1 -0
- package/fixtures/openapi/PersonalAgentAccess/invalid/invalid.json +1 -0
- package/fixtures/openapi/PersonalAgentAccess/valid/valid.json +1 -0
- package/fixtures/openapi/PersonalAgentAccessDecision/invalid/invalid.json +1 -0
- package/fixtures/openapi/PersonalAgentAccessDecision/valid/valid.json +1 -0
- package/fixtures/openapi/PersonalAssistantArtifactListEnvelope/invalid/owner.json +8 -0
- package/fixtures/openapi/PersonalAssistantArtifactListEnvelope/valid/side-chat.json +9 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/motion.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/owner.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/remote.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/svg.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/valid/raster.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/valid/reset.json +1 -0
- package/fixtures/openapi/PersonalAssistantUpdateRequest/invalid/empty.json +1 -0
- package/fixtures/openapi/PersonalAssistantUpdateRequest/invalid/owner.json +1 -0
- package/fixtures/openapi/PersonalAssistantUpdateRequest/valid/name.json +1 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/host-url.json +12 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/lifetime.json +11 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/unknown-agent.json +11 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/valid/basic.json +11 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/invented-state.json +6 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/missing-state.json +5 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/negative-generation.json +7 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/network.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/private-node.json +24 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/unsupported-backend.json +7 -0
- package/fixtures/openapi/PersonalEnvironment/valid/deleted.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/deleting.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/deletion_unknown.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/frozen-binding.json +13 -0
- package/fixtures/openapi/PersonalEnvironment/valid/ready.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/unknown.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/unprovisioned.json +6 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/command.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/node.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/owner.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/quota.json +10 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/reconcile.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/sleep.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/wake.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/empty-name.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/long-name.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/missing-name.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/owner.json +6 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/stale-version.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/valid/confirmed.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/assistantId.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/environmentVersion.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/ownerId.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/valid/current.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentEnvelope/invalid/missing-data.json +1 -0
- package/fixtures/openapi/PersonalEnvironmentEnvelope/valid/state.json +8 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/cpu.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/disk.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/network.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/processes.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/valid/basic.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/mode.json +13 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/network.json +13 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/node.json +14 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/valid/template.json +13 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/invalid/missing.json +1 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/invalid/unbounded.json +1317 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/valid/choices.json +17 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/valid/empty.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentUsage/invalid/negative.json +6 -0
- package/fixtures/openapi/PersonalEnvironmentUsage/valid/usage.json +6 -0
- package/fixtures/openapi/PersonalFeedFeedback/invalid/owner.json +4 -0
- package/fixtures/openapi/PersonalFeedFeedback/valid/liked.json +3 -0
- package/fixtures/openapi/PersonalFeedItem/invalid/no-source.json +9 -0
- package/fixtures/openapi/PersonalFeedItem/valid/post.json +15 -0
- package/fixtures/openapi/PersonalFeedSettings/invalid/owner.json +6 -0
- package/fixtures/openapi/PersonalFeedSettings/valid/default.json +5 -0
- package/fixtures/openapi/PersonalGuestPreparationInput/invalid/foreign-owner.json +5 -0
- package/fixtures/openapi/PersonalGuestPreparationInput/invalid/unsafe-counter.json +4 -0
- package/fixtures/openapi/PersonalGuestPreparationInput/valid/ed25519-csr.json +4 -0
- package/fixtures/openapi/PersonalGuestRuntimeEnvelope/invalid/minimal.json +10 -0
- package/fixtures/openapi/PersonalGuestRuntimeEnvelope/valid/minimal.json +9 -0
- package/fixtures/openapi/PersonalGuestRuntimeInput/invalid/minimal.json +6 -0
- package/fixtures/openapi/PersonalGuestRuntimeInput/valid/minimal.json +8 -0
- package/fixtures/openapi/PersonalGuestToolsInput/invalid/minimal.json +5 -0
- package/fixtures/openapi/PersonalGuestToolsInput/valid/minimal.json +4 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/device.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/fractional-size.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/network.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/owner.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/provider.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/unknown.json +16 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/valid/pve.json +15 -0
- package/fixtures/openapi/PersonalWorkspaceRuntime/invalid/shared.json +3 -0
- package/fixtures/openapi/PersonalWorkspaceRuntime/valid/shared.json +4 -0
- package/fixtures/openapi/PersonalWorkspaceRuntimeEnvelope/invalid/shared.json +5 -0
- package/fixtures/openapi/PersonalWorkspaceRuntimeEnvelope/valid/shared.json +6 -0
- package/fixtures/openapi/ResourceMonitoringPanel/invalid/unknown-quality.json +7 -0
- package/fixtures/openapi/ResourceMonitoringPanel/valid/not-collected.json +7 -0
- package/fixtures/openapi/ResourceMonitoringPoint/invalid/string-zero.json +4 -0
- package/fixtures/openapi/ResourceMonitoringPoint/valid/missing.json +4 -0
- package/fixtures/openapi/ResourceMonitoringPoint/valid/zero.json +4 -0
- package/fixtures/openapi/ResourceMonitoringView/invalid/raw-query.json +12 -0
- package/fixtures/openapi/ResourceMonitoringView/valid/unconfigured.json +12 -0
- package/fixtures/openapi/SecretGenerateSSHKeyRequest/invalid/task51.json +6 -0
- package/fixtures/openapi/SecretGenerateSSHKeyRequest/valid/task51.json +12 -0
- package/fixtures/openapi/SecretReferenceUsage/invalid/version.json +6 -0
- package/fixtures/openapi/SecretReferenceUsage/valid/version.json +8 -0
- package/fixtures/openapi/VMSSHCredentialRedemptionInput/invalid/task51.json +7 -0
- package/fixtures/openapi/VMSSHCredentialRedemptionInput/valid/task51.json +6 -0
- package/fixtures/openapi/VirtualMachineAccessBindingInput/invalid/task51.json +10 -0
- package/fixtures/openapi/VirtualMachineAccessBindingInput/valid/task51.json +10 -0
- package/fixtures/openapi/VirtualMachineActionInput/invalid/capacity-not-boolean.json +1 -0
- package/fixtures/openapi/VirtualMachineActionInput/valid/new-data-with-admission.json +1 -0
- package/fixtures/openapi/VirtualMachineCreateInput/valid/cloud-image.json +12 -0
- package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-config-injection.json +1 -0
- package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-existing-disk.json +1 -0
- package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-too-long.json +1 -0
- package/fixtures/openapi/VirtualMachineDiskChange/valid/personal-data.json +1 -0
- package/fixtures/openapi/VirtualMachineGuestAccessInput/invalid/task51.json +4 -0
- package/fixtures/openapi/VirtualMachineGuestAccessInput/valid/task51.json +6 -0
- package/fixtures/openapi/VirtualMachineSSHCommand/invalid/task51.json +9 -0
- package/fixtures/openapi/VirtualMachineSSHCommand/valid/task51.json +8 -0
- package/fixtures/openapi/VirtualMachineSSHCommandInput/invalid/task51.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHCommandInput/valid/task51.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/invalid/encoding.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/invalid/task51.json +4 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/valid/binary.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/valid/task51.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHSessionInput/invalid/task51.json +4 -0
- package/fixtures/openapi/VirtualMachineSSHSessionInput/valid/task51.json +5 -0
- package/fixtures/openapi/VirtualizationCapacityPolicyInput/invalid/task51.json +12 -0
- package/fixtures/openapi/VirtualizationCapacityPolicyInput/valid/task51.json +13 -0
- package/fixtures/openapi/VirtualizationImageInput/invalid/cloud-image-config-type.json +6 -0
- package/fixtures/openapi/VirtualizationImageInput/valid/cloud-image.json +11 -0
- package/fixtures/openapi/WorkbenchAssistantTask/invalid/kind.json +1 -0
- package/fixtures/openapi/WorkbenchAssistantTask/invalid/owner.json +1 -0
- package/fixtures/openapi/WorkbenchAssistantTask/valid/generate.json +1 -0
- package/fixtures/openapi/WorkbenchAssistantTask/valid/undo.json +1 -0
- package/fixtures/openapi/WorkbenchContextSelection/invalid/remote-image.json +1 -0
- package/fixtures/openapi/WorkbenchContextSelection/invalid/too-many-images.json +1 -0
- package/fixtures/openapi/WorkbenchContextSelection/valid/chat-image.json +9 -0
- package/fixtures/openapi/WorkbenchContextUsage/invalid/fractional-capacity.json +1 -0
- package/fixtures/openapi/WorkbenchContextUsage/invalid/negative.json +1 -0
- package/fixtures/openapi/WorkbenchContextUsage/valid/reported.json +1 -0
- package/fixtures/openapi/WorkbenchContextUsage/valid/unknown.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/invalid/owner.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/invalid/provider.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/valid/empty.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/valid/provider.json +1 -0
- package/fixtures/openapi/WorkbenchMessageSearchPage/invalid/role.json +12 -0
- package/fixtures/openapi/WorkbenchMessageSearchPage/valid/page.json +12 -0
- package/fixtures/openapi/WorkbenchMessageWindow/invalid/missing.json +1 -0
- package/fixtures/openapi/WorkbenchMessageWindow/valid/empty.json +3 -0
- package/fixtures/openapi/WorkbenchModelOption/invalid/zero-capacity.json +1 -0
- package/fixtures/openapi/WorkbenchModelOption/valid/capacity.json +1 -0
- package/fixtures/openapi/WorkbenchModelOption/valid/unknown.json +1 -0
- package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/invalid/basic.json +7 -0
- package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/valid/basic.json +11 -0
- package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/valid/selected-profile.json +23 -0
- package/fixtures/openapi/WorkbenchSendMessageStreamRequest/invalid/missing-required.json +1 -0
- package/fixtures/openapi/WorkbenchSendMessageStreamRequest/valid/mixed-professional-task.json +22 -0
- package/fixtures/openapi/WorkbenchTaskIntent/invalid/basic.json +5 -0
- package/fixtures/openapi/WorkbenchTaskIntent/invalid/oversized-capability-source.json +3 -0
- package/fixtures/openapi/WorkbenchTaskIntent/valid/basic.json +9 -0
- package/fixtures/openapi/WorkbenchTaskIntent/valid/independent-sources.json +17 -0
- package/gen/go/sohaapi/client.go +1 -1
- package/gen/go/sohaapi/kubernetes_monitoring_test.go +67 -0
- package/gen/go/sohaapi/personal_environment_test.go +25 -0
- package/gen/go/sohaapi/types.go +3460 -433
- package/gen/go/sohaapi/vm_disk_test.go +21 -0
- package/gen/ts/sohaapi/index.ts +5454 -1397
- package/go.mod +61 -49
- package/go.sum +202 -176
- package/knowledge/connector-definition.schema.json +6 -1
- package/knowledge/source-import-policy.schema.json +32 -0
- package/memory/memory-record.schema.json +1 -1
- package/openapi/soha-api.json +24199 -16810
- package/openapi/soha-api.yaml +2537 -14
- package/openapi/spec_generated.go +3 -3
- package/openapi/spec_test.go +34 -0
- package/package.json +19 -3
- package/resource/agent_access.go +12 -0
- package/resource/agent_profiles.go +62 -0
- package/resource/agent_profiles_test.go +36 -0
- package/resource/kubernetes_kinds.go +73 -0
- package/resource/kubernetes_kinds_test.go +38 -0
- package/resource/models.go +23 -14
- package/resource/network_topology.go +166 -0
- package/resource/network_topology_projection.go +208 -0
- package/resource/network_topology_projection_test.go +53 -0
- package/resource/network_topology_test.go +51 -0
- package/resource/network_topology_validation.go +29 -0
- package/resource/node_drain.go +34 -0
- package/resource/resource_monitoring.go +99 -0
- package/resource/runtime/network_topology.go +232 -0
- package/resource/runtime/network_topology_test.go +62 -0
- package/resource/runtime/node_drain.go +124 -0
- package/resource/runtime/node_drain_test.go +95 -0
- package/resource/runtime/resource_edit.go +128 -0
- package/resource/runtime/resource_edit_test.go +183 -0
- package/resource/runtime/resource_monitoring.go +243 -0
- package/resource/runtime/resource_monitoring_test.go +88 -0
- /package/fixtures/openapi/DeliveryExecutionHistoryPage/{invalid-kind.json → invalid/invalid-kind.json} +0 -0
- /package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-build.json → valid/valid-build.json} +0 -0
- /package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-empty.json → valid/valid-empty.json} +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Package guest carries the personal VM guest registration and heartbeat protocol.
|
|
2
|
+
// Schemas in agent/ own the wire shapes. This protocol grants no task execution.
|
|
3
|
+
package guest
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"bytes"
|
|
7
|
+
"crypto/sha256"
|
|
8
|
+
"encoding/base64"
|
|
9
|
+
"encoding/json"
|
|
10
|
+
"errors"
|
|
11
|
+
"fmt"
|
|
12
|
+
"io"
|
|
13
|
+
"regexp"
|
|
14
|
+
"slices"
|
|
15
|
+
"strings"
|
|
16
|
+
"time"
|
|
17
|
+
"unicode/utf8"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const (
|
|
21
|
+
BindingVersion = "opensoha.dev/personal-guest-binding/v1"
|
|
22
|
+
EnrollmentVersion = "opensoha.dev/personal-guest-enrollment/v1"
|
|
23
|
+
HeartbeatVersion = "opensoha.dev/personal-guest-heartbeat/v1"
|
|
24
|
+
MaxClockSkew = 30 * time.Second
|
|
25
|
+
MaxCounter = 1<<53 - 1 // Exact integer range shared by Go and JavaScript consumers.
|
|
26
|
+
EnrollmentMinTTL = time.Minute
|
|
27
|
+
EnrollmentMaxTTL = 10 * time.Minute
|
|
28
|
+
BindingsPath = "/api/personal-guest/v1/bindings"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
var (
|
|
32
|
+
uuidPattern = regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)
|
|
33
|
+
digestPattern = regexp.MustCompile(`^sha256:[a-f0-9]{64}$`)
|
|
34
|
+
serialPattern = regexp.MustCompile(`^[A-Za-z0-9_-]{1,20}$`)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
// Binding is derived by Core and immutable for this guest registration. Version
|
|
38
|
+
// is the identity revision, independent of environment operation CAS versions.
|
|
39
|
+
type Binding struct {
|
|
40
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
41
|
+
ID string `json:"id"`
|
|
42
|
+
Version int64 `json:"version"`
|
|
43
|
+
AssistantID string `json:"assistantId"`
|
|
44
|
+
OwnerID string `json:"ownerId"`
|
|
45
|
+
Provider string `json:"provider"`
|
|
46
|
+
ComputeRef string `json:"computeRef"`
|
|
47
|
+
ComputeGeneration int64 `json:"computeGeneration"`
|
|
48
|
+
ProviderIdentityHash string `json:"providerIdentityHash"`
|
|
49
|
+
DataRef string `json:"dataRef"`
|
|
50
|
+
DataDiskSerial string `json:"dataDiskSerial"`
|
|
51
|
+
DataDiskBytes int64 `json:"dataDiskBytes"`
|
|
52
|
+
PolicyRevision string `json:"policyRevision"`
|
|
53
|
+
IssuedAt time.Time `json:"issuedAt"`
|
|
54
|
+
ExpiresAt time.Time `json:"expiresAt"`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Enrollment is sent over mutually authenticated TLS using a short, one-time
|
|
58
|
+
// challenge in the Authorization header. The certificate pins guest identity.
|
|
59
|
+
// No owner, capability, filesystem path or provider credentials are client input.
|
|
60
|
+
type Enrollment struct {
|
|
61
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
62
|
+
EnrollmentID string `json:"enrollmentId"`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// EnrollmentTokenHash accepts exactly Bearer plus a canonical unpadded base64url
|
|
66
|
+
// 32-byte challenge. Core generates it with crypto/rand and stores only the hash.
|
|
67
|
+
// Its 60–600 second grant pins the binding and guest certificate fingerprint;
|
|
68
|
+
// atomic consumption, expiry and current authorization remain service duties.
|
|
69
|
+
func EnrollmentTokenHash(authorization string) (string, error) {
|
|
70
|
+
token, ok := strings.CutPrefix(authorization, "Bearer ")
|
|
71
|
+
if !ok || len(token) != 43 {
|
|
72
|
+
return "", errors.New("invalid personal guest enrollment authorization")
|
|
73
|
+
}
|
|
74
|
+
random, err := base64.RawURLEncoding.Strict().DecodeString(token)
|
|
75
|
+
if err != nil || len(random) != 32 {
|
|
76
|
+
return "", errors.New("invalid personal guest enrollment authorization")
|
|
77
|
+
}
|
|
78
|
+
digest := sha256.Sum256([]byte(token))
|
|
79
|
+
return fmt.Sprintf("sha256:%x", digest), nil
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Heartbeat proves only current authenticated liveness. Core independently
|
|
83
|
+
// checks current authorization, frozen binding and monotonic sequence in storage.
|
|
84
|
+
// A guest cannot claim data/tool/browser readiness with this message.
|
|
85
|
+
type Heartbeat struct {
|
|
86
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
87
|
+
BindingID string `json:"bindingId"`
|
|
88
|
+
BindingVersion int64 `json:"bindingVersion"`
|
|
89
|
+
ComputeGeneration int64 `json:"computeGeneration"`
|
|
90
|
+
Sequence int64 `json:"sequence"`
|
|
91
|
+
ObservedAt time.Time `json:"observedAt"`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func (b Binding) Validate() error {
|
|
95
|
+
if b.SchemaVersion != BindingVersion || b.Version < 1 || b.Version > MaxCounter || b.ComputeGeneration < 1 || b.ComputeGeneration > MaxCounter || b.Provider != "pve" && b.Provider != "kubevirt" {
|
|
96
|
+
return errors.New("invalid personal guest binding version or provider")
|
|
97
|
+
}
|
|
98
|
+
for _, id := range []string{b.ID, b.AssistantID, b.OwnerID, b.ComputeRef, b.DataRef} {
|
|
99
|
+
if !uuidPattern.MatchString(id) {
|
|
100
|
+
return errors.New("invalid personal guest binding identity")
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if !serialPattern.MatchString(b.DataDiskSerial) || b.DataDiskBytes < 16<<20 || b.DataDiskBytes > 1<<40 || !digestPattern.MatchString(b.PolicyRevision) || !digestPattern.MatchString(b.ProviderIdentityHash) {
|
|
104
|
+
return errors.New("invalid personal guest data identity or policy revision")
|
|
105
|
+
}
|
|
106
|
+
if b.IssuedAt.IsZero() || !b.ExpiresAt.After(b.IssuedAt) || b.ExpiresAt.Sub(b.IssuedAt) > 24*time.Hour {
|
|
107
|
+
return errors.New("invalid personal guest binding lifetime")
|
|
108
|
+
}
|
|
109
|
+
return nil
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func (e Enrollment) Validate() error {
|
|
113
|
+
if e.SchemaVersion != EnrollmentVersion || !uuidPattern.MatchString(e.EnrollmentID) {
|
|
114
|
+
return errors.New("invalid personal guest enrollment")
|
|
115
|
+
}
|
|
116
|
+
return nil
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func (h Heartbeat) Validate() error {
|
|
120
|
+
if h.SchemaVersion != HeartbeatVersion || !uuidPattern.MatchString(h.BindingID) || h.BindingVersion < 1 || h.BindingVersion > MaxCounter || h.ComputeGeneration < 1 || h.ComputeGeneration > MaxCounter || h.Sequence < 1 || h.Sequence > MaxCounter || h.ObservedAt.IsZero() {
|
|
121
|
+
return errors.New("invalid personal guest heartbeat")
|
|
122
|
+
}
|
|
123
|
+
return nil
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ValidateFor is not authentication or a readiness grant. Callers must first
|
|
127
|
+
// authenticate the pinned guest certificate and recheck authoritative ownership.
|
|
128
|
+
func (h Heartbeat) ValidateFor(binding Binding, now time.Time, lastSequence int64) error {
|
|
129
|
+
if err := binding.Validate(); err != nil {
|
|
130
|
+
return err
|
|
131
|
+
}
|
|
132
|
+
if err := h.Validate(); err != nil {
|
|
133
|
+
return err
|
|
134
|
+
}
|
|
135
|
+
if now.IsZero() || now.Before(binding.IssuedAt) || !now.Before(binding.ExpiresAt) || lastSequence < 0 {
|
|
136
|
+
return errors.New("personal guest binding is not currently valid")
|
|
137
|
+
}
|
|
138
|
+
if h.BindingID != binding.ID || h.BindingVersion != binding.Version || h.ComputeGeneration != binding.ComputeGeneration || h.Sequence <= lastSequence {
|
|
139
|
+
return errors.New("personal guest heartbeat binding or sequence mismatch")
|
|
140
|
+
}
|
|
141
|
+
if h.ObservedAt.Before(binding.IssuedAt) || !h.ObservedAt.Before(binding.ExpiresAt) || h.ObservedAt.Before(now.Add(-MaxClockSkew)) || h.ObservedAt.After(now.Add(MaxClockSkew)) {
|
|
142
|
+
return errors.New("personal guest heartbeat observation is stale")
|
|
143
|
+
}
|
|
144
|
+
return nil
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
func decodeStrict(data []byte, value any, keys ...string) error {
|
|
148
|
+
return decodeStrictBounded(data, value, 4096, keys...)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
func decodeStrictBounded(data []byte, value any, limit int, keys ...string) error {
|
|
152
|
+
if len(data) > limit || !utf8.Valid(data) {
|
|
153
|
+
return errors.New("personal guest message must be bounded UTF-8")
|
|
154
|
+
}
|
|
155
|
+
if err := validateObjectKeys(data, keys); err != nil {
|
|
156
|
+
return err
|
|
157
|
+
}
|
|
158
|
+
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
159
|
+
decoder.DisallowUnknownFields()
|
|
160
|
+
if err := decoder.Decode(value); err != nil {
|
|
161
|
+
return err
|
|
162
|
+
}
|
|
163
|
+
if decoder.Decode(new(any)) != io.EOF {
|
|
164
|
+
return errors.New("personal guest message must contain one JSON object")
|
|
165
|
+
}
|
|
166
|
+
return nil
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
func validateObjectKeys(data []byte, keys []string) error {
|
|
170
|
+
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
171
|
+
start, err := decoder.Token()
|
|
172
|
+
if err != nil || start != json.Delim('{') {
|
|
173
|
+
return errors.New("personal guest message must be a JSON object")
|
|
174
|
+
}
|
|
175
|
+
seen := make(map[string]bool, len(keys))
|
|
176
|
+
for decoder.More() {
|
|
177
|
+
key, err := decoder.Token()
|
|
178
|
+
if err != nil {
|
|
179
|
+
return err
|
|
180
|
+
}
|
|
181
|
+
name, ok := key.(string)
|
|
182
|
+
if !ok || seen[name] || !slices.Contains(keys, name) {
|
|
183
|
+
return errors.New("personal guest message contains a duplicate or unknown field")
|
|
184
|
+
}
|
|
185
|
+
seen[name] = true
|
|
186
|
+
var value json.RawMessage
|
|
187
|
+
if err := decoder.Decode(&value); err != nil {
|
|
188
|
+
return err
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
_, err = decoder.Token()
|
|
192
|
+
return err
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func (b *Binding) UnmarshalJSON(data []byte) error {
|
|
196
|
+
type wire Binding
|
|
197
|
+
var value wire
|
|
198
|
+
if err := decodeStrict(data, &value, "schemaVersion", "id", "version", "assistantId", "ownerId", "provider", "computeRef", "computeGeneration", "providerIdentityHash", "dataRef", "dataDiskSerial", "dataDiskBytes", "policyRevision", "issuedAt", "expiresAt"); err != nil {
|
|
199
|
+
return err
|
|
200
|
+
}
|
|
201
|
+
decoded := Binding(value)
|
|
202
|
+
if err := decoded.Validate(); err != nil {
|
|
203
|
+
return err
|
|
204
|
+
}
|
|
205
|
+
*b = decoded
|
|
206
|
+
return nil
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
func (e *Enrollment) UnmarshalJSON(data []byte) error {
|
|
210
|
+
type wire Enrollment
|
|
211
|
+
var value wire
|
|
212
|
+
if err := decodeStrict(data, &value, "schemaVersion", "enrollmentId"); err != nil {
|
|
213
|
+
return err
|
|
214
|
+
}
|
|
215
|
+
decoded := Enrollment(value)
|
|
216
|
+
if err := decoded.Validate(); err != nil {
|
|
217
|
+
return err
|
|
218
|
+
}
|
|
219
|
+
*e = decoded
|
|
220
|
+
return nil
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
func (h *Heartbeat) UnmarshalJSON(data []byte) error {
|
|
224
|
+
type wire Heartbeat
|
|
225
|
+
var value wire
|
|
226
|
+
if err := decodeStrict(data, &value, "schemaVersion", "bindingId", "bindingVersion", "computeGeneration", "sequence", "observedAt"); err != nil {
|
|
227
|
+
return err
|
|
228
|
+
}
|
|
229
|
+
decoded := Heartbeat(value)
|
|
230
|
+
if err := decoded.Validate(); err != nil {
|
|
231
|
+
return err
|
|
232
|
+
}
|
|
233
|
+
*h = decoded
|
|
234
|
+
return nil
|
|
235
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
package guest
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
"time"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func TestEnrollmentAuthorizationUsesCanonicalBoundedChallenge(t *testing.T) {
|
|
13
|
+
valid := "Bearer " + strings.Repeat("A", 43)
|
|
14
|
+
digest, err := EnrollmentTokenHash(valid)
|
|
15
|
+
if err != nil || !digestPattern.MatchString(digest) {
|
|
16
|
+
t.Fatalf("challenge hash: %q %v", digest, err)
|
|
17
|
+
}
|
|
18
|
+
for _, invalid := range []string{"", "Basic " + strings.Repeat("A", 43), valid + "=", valid + " ", " " + valid, "Bearer " + strings.Repeat("A", 42) + "B"} {
|
|
19
|
+
if _, err := EnrollmentTokenHash(invalid); err == nil {
|
|
20
|
+
t.Fatal("noncanonical enrollment challenge accepted")
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func TestSchemaFixtures(t *testing.T) {
|
|
26
|
+
for name, decode := range map[string]func([]byte) error{
|
|
27
|
+
"binding": func(data []byte) error { return json.Unmarshal(data, new(Binding)) },
|
|
28
|
+
"enrollment": func(data []byte) error { return json.Unmarshal(data, new(Enrollment)) },
|
|
29
|
+
"heartbeat": func(data []byte) error { return json.Unmarshal(data, new(Heartbeat)) },
|
|
30
|
+
} {
|
|
31
|
+
for _, kind := range []string{"valid", "invalid"} {
|
|
32
|
+
files, err := filepath.Glob(filepath.Join("../../fixtures/json-schema/personal-guest-"+name, kind, "*.json"))
|
|
33
|
+
if err != nil || len(files) == 0 {
|
|
34
|
+
t.Fatalf("missing %s/%s fixtures: %v", name, kind, err)
|
|
35
|
+
}
|
|
36
|
+
for _, file := range files {
|
|
37
|
+
t.Run(name+"/"+kind+"/"+filepath.Base(file), func(t *testing.T) {
|
|
38
|
+
data, err := os.ReadFile(file)
|
|
39
|
+
if err != nil {
|
|
40
|
+
t.Fatal(err)
|
|
41
|
+
}
|
|
42
|
+
if err := decode(data); (err == nil) != (kind == "valid") {
|
|
43
|
+
t.Fatalf("fixture result: %v", err)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func guestFixture[T any](t *testing.T, name string) T {
|
|
52
|
+
t.Helper()
|
|
53
|
+
data, err := os.ReadFile("../../fixtures/json-schema/personal-guest-" + name + "/valid/minimal.json")
|
|
54
|
+
if err != nil {
|
|
55
|
+
t.Fatal(err)
|
|
56
|
+
}
|
|
57
|
+
var value T
|
|
58
|
+
if err := json.Unmarshal(data, &value); err != nil {
|
|
59
|
+
t.Fatal(err)
|
|
60
|
+
}
|
|
61
|
+
return value
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func TestHeartbeatRequiresCurrentBindingAndFreshMonotonicObservation(t *testing.T) {
|
|
65
|
+
binding := guestFixture[Binding](t, "binding")
|
|
66
|
+
heartbeat := guestFixture[Heartbeat](t, "heartbeat")
|
|
67
|
+
now := heartbeat.ObservedAt
|
|
68
|
+
if err := heartbeat.ValidateFor(binding, now, 0); err != nil {
|
|
69
|
+
t.Fatal(err)
|
|
70
|
+
}
|
|
71
|
+
for name, mutate := range map[string]func(*Heartbeat){
|
|
72
|
+
"different binding": func(h *Heartbeat) { h.BindingID = binding.DataRef },
|
|
73
|
+
"old revision": func(h *Heartbeat) { h.BindingVersion++ },
|
|
74
|
+
"old compute": func(h *Heartbeat) { h.ComputeGeneration++ },
|
|
75
|
+
"zero sequence": func(h *Heartbeat) { h.Sequence = 0 },
|
|
76
|
+
"inexact sequence": func(h *Heartbeat) { h.Sequence = MaxCounter + 1 },
|
|
77
|
+
"future clock": func(h *Heartbeat) { h.ObservedAt = now.Add(MaxClockSkew + time.Nanosecond) },
|
|
78
|
+
"old clock": func(h *Heartbeat) { h.ObservedAt = now.Add(-MaxClockSkew - time.Nanosecond) },
|
|
79
|
+
"expired observation": func(h *Heartbeat) {
|
|
80
|
+
h.ObservedAt = binding.ExpiresAt
|
|
81
|
+
},
|
|
82
|
+
} {
|
|
83
|
+
t.Run(name, func(t *testing.T) {
|
|
84
|
+
h := heartbeat
|
|
85
|
+
mutate(&h)
|
|
86
|
+
if h.ValidateFor(binding, now, 0) == nil {
|
|
87
|
+
t.Fatal("invalid heartbeat accepted")
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
for name, serverNow := range map[string]time.Time{
|
|
92
|
+
"zero clock": {}, "not yet valid": binding.IssuedAt.Add(-time.Nanosecond), "expired": binding.ExpiresAt,
|
|
93
|
+
} {
|
|
94
|
+
t.Run(name, func(t *testing.T) {
|
|
95
|
+
if heartbeat.ValidateFor(binding, serverNow, 0) == nil {
|
|
96
|
+
t.Fatal("invalid binding deadline accepted")
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
if heartbeat.ValidateFor(binding, now, heartbeat.Sequence) == nil || heartbeat.ValidateFor(binding, now, -1) == nil {
|
|
101
|
+
t.Fatal("replay or invalid server sequence accepted")
|
|
102
|
+
}
|
|
103
|
+
heartbeat.Sequence++
|
|
104
|
+
if err := heartbeat.ValidateFor(binding, now, heartbeat.Sequence-1); err != nil {
|
|
105
|
+
t.Fatal(err)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func TestBindingRejectsInvalidLifetime(t *testing.T) {
|
|
110
|
+
binding := guestFixture[Binding](t, "binding")
|
|
111
|
+
for _, expiresAt := range []time.Time{binding.IssuedAt, binding.IssuedAt.Add(-time.Second), binding.IssuedAt.Add(24*time.Hour + time.Nanosecond)} {
|
|
112
|
+
binding.ExpiresAt = expiresAt
|
|
113
|
+
if binding.Validate() == nil {
|
|
114
|
+
t.Fatal("invalid credential lifetime accepted")
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func TestDecodeRejectsUnknownFieldsInvalidUTF8AndOversizedMessages(t *testing.T) {
|
|
120
|
+
valid, err := json.Marshal(guestFixture[Heartbeat](t, "heartbeat"))
|
|
121
|
+
if err != nil {
|
|
122
|
+
t.Fatal(err)
|
|
123
|
+
}
|
|
124
|
+
for name, data := range map[string][]byte{
|
|
125
|
+
"invalid UTF8": append(append([]byte{}, valid[:len(valid)-1]...), 0xff, '}'),
|
|
126
|
+
"null": []byte("null"),
|
|
127
|
+
"oversized": append(valid, make([]byte, 4096)...),
|
|
128
|
+
"legacy owner": append(append([]byte{}, valid[:len(valid)-1]...), []byte(",\"ownerId\":\"other\"}")...),
|
|
129
|
+
"duplicate": append(append([]byte{}, valid[:len(valid)-1]...), []byte(",\"sequence\":2}")...),
|
|
130
|
+
"wrong case": []byte(strings.Replace(string(valid), "\"sequence\"", "\"Sequence\"", 1)),
|
|
131
|
+
} {
|
|
132
|
+
t.Run(name, func(t *testing.T) {
|
|
133
|
+
if json.Unmarshal(data, new(Heartbeat)) == nil {
|
|
134
|
+
t.Fatal("invalid wire message accepted")
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
type wireHeartbeat Heartbeat
|
|
139
|
+
if decodeStrict(append(valid, []byte(" {}")...), new(wireHeartbeat), "schemaVersion", "bindingId", "bindingVersion", "computeGeneration", "sequence", "observedAt") == nil {
|
|
140
|
+
t.Fatal("trailing object accepted")
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
package guest
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"errors"
|
|
5
|
+
"time"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
const RenewalVersion = "opensoha.dev/personal-guest-renewal/v1"
|
|
9
|
+
|
|
10
|
+
// Renewal is authenticated with the current certificate. Core derives every
|
|
11
|
+
// identity field; requestId makes a lost issuance response recoverable.
|
|
12
|
+
type Renewal struct {
|
|
13
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
14
|
+
RequestID string `json:"requestId"`
|
|
15
|
+
BindingVersion int64 `json:"bindingVersion"`
|
|
16
|
+
CSRPEM string `json:"csrPEM"`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type RenewalResult struct {
|
|
20
|
+
RequestID string `json:"requestId"`
|
|
21
|
+
Binding Binding `json:"binding"`
|
|
22
|
+
CertificatePEM string `json:"certificatePEM"`
|
|
23
|
+
ClientCAPEM string `json:"clientCAPEM"`
|
|
24
|
+
CommitBefore time.Time `json:"commitBefore"`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func (r Renewal) Validate() error {
|
|
28
|
+
if r.SchemaVersion != RenewalVersion || !uuidPattern.MatchString(r.RequestID) || r.BindingVersion < 1 || r.BindingVersion >= MaxCounter || len(r.CSRPEM) == 0 || len(r.CSRPEM) > 2048 {
|
|
29
|
+
return errors.New("invalid personal guest renewal")
|
|
30
|
+
}
|
|
31
|
+
return nil
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func (r *Renewal) UnmarshalJSON(data []byte) error {
|
|
35
|
+
type wire Renewal
|
|
36
|
+
var value wire
|
|
37
|
+
if err := decodeStrict(data, &value, "schemaVersion", "requestId", "bindingVersion", "csrPEM"); err != nil {
|
|
38
|
+
return err
|
|
39
|
+
}
|
|
40
|
+
decoded := Renewal(value)
|
|
41
|
+
if err := decoded.Validate(); err != nil {
|
|
42
|
+
return err
|
|
43
|
+
}
|
|
44
|
+
*r = decoded
|
|
45
|
+
return nil
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// SameWorkspace excludes only registration and lease fields. It is not an
|
|
49
|
+
// authorization check and must never authorize a disk format by itself.
|
|
50
|
+
func (b Binding) SameWorkspace(other Binding) bool {
|
|
51
|
+
b.ID, other.ID = "", ""
|
|
52
|
+
b.Version, other.Version = 0, 0
|
|
53
|
+
b.IssuedAt = other.IssuedAt
|
|
54
|
+
b.ExpiresAt = other.ExpiresAt
|
|
55
|
+
return b == other
|
|
56
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package guest
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"testing"
|
|
6
|
+
"time"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func TestRenewalRejectsDuplicateAndUnknownAuthority(t *testing.T) {
|
|
10
|
+
for _, raw := range []string{`{"schemaVersion":"opensoha.dev/personal-guest-renewal/v1","requestId":"11111111-1111-4111-8111-111111111111","bindingVersion":1,"bindingVersion":2,"csrPEM":"public"}`, `{"schemaVersion":"opensoha.dev/personal-guest-renewal/v1","requestId":"11111111-1111-4111-8111-111111111111","bindingVersion":1,"csrPEM":"public","ownerId":"other"}`} {
|
|
11
|
+
var request Renewal
|
|
12
|
+
if json.Unmarshal([]byte(raw), &request) == nil {
|
|
13
|
+
t.Fatal("untrusted renewal fields accepted")
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func TestSameWorkspaceIgnoresOnlyRegistrationAndLeaseFields(t *testing.T) {
|
|
19
|
+
binding := guestFixture[Binding](t, "binding")
|
|
20
|
+
renewed := binding
|
|
21
|
+
renewed.ID = "another-registration"
|
|
22
|
+
renewed.Version++
|
|
23
|
+
renewed.IssuedAt = renewed.IssuedAt.Add(time.Hour)
|
|
24
|
+
renewed.ExpiresAt = renewed.ExpiresAt.Add(time.Hour)
|
|
25
|
+
beforeBinding, beforeRenewed := binding, renewed
|
|
26
|
+
if !binding.SameWorkspace(renewed) || !renewed.SameWorkspace(binding) {
|
|
27
|
+
t.Fatal("registration or lease changes changed workspace identity")
|
|
28
|
+
}
|
|
29
|
+
if binding != beforeBinding || renewed != beforeRenewed {
|
|
30
|
+
t.Fatal("workspace comparison mutated a binding")
|
|
31
|
+
}
|
|
32
|
+
for name, mutate := range map[string]func(*Binding){
|
|
33
|
+
"schema": func(b *Binding) { b.SchemaVersion += "-other" },
|
|
34
|
+
"assistant": func(b *Binding) { b.AssistantID += "-other" },
|
|
35
|
+
"owner": func(b *Binding) { b.OwnerID += "-other" },
|
|
36
|
+
"provider": func(b *Binding) { b.Provider += "-other" },
|
|
37
|
+
"compute": func(b *Binding) { b.ComputeRef += "-other" },
|
|
38
|
+
"generation": func(b *Binding) { b.ComputeGeneration++ },
|
|
39
|
+
"provider identity": func(b *Binding) { b.ProviderIdentityHash += "-other" },
|
|
40
|
+
"data": func(b *Binding) { b.DataRef += "-other" },
|
|
41
|
+
"disk serial": func(b *Binding) { b.DataDiskSerial += "-other" },
|
|
42
|
+
"disk size": func(b *Binding) { b.DataDiskBytes++ },
|
|
43
|
+
"policy": func(b *Binding) { b.PolicyRevision += "-other" },
|
|
44
|
+
} {
|
|
45
|
+
t.Run(name, func(t *testing.T) {
|
|
46
|
+
other := renewed
|
|
47
|
+
mutate(&other)
|
|
48
|
+
if binding.SameWorkspace(other) || other.SameWorkspace(binding) {
|
|
49
|
+
t.Fatal("different workspace identity accepted")
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|