@opensoha/contracts 0.1.25 → 0.1.27
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 +34 -0
- package/README.md +1 -1
- package/agent/environment/export.go +1 -0
- package/agent/environment/protocol_test.go +1 -1
- package/agent/guest/protocol_test.go +2 -2
- package/auth/catalog_generated.go +2 -2
- package/auth/permission-catalog.json +481 -1
- package/compat/json-schema-ecosystem-0.1.0.json +1 -1
- package/compat/openapi-loose-boundaries-0.1.0.json +1 -1
- package/delivery/document.schema.json +127 -9
- package/endpointcontrol/decode.go +64 -0
- package/endpointcontrol/models.go +138 -0
- package/endpointcontrol/models_test.go +51 -0
- package/endpointcontrol/protocol.schema.json +397 -0
- package/examples/relations_compat_test.go +18 -0
- package/fixtures/json-schema/endpoint-controls/invalid/controls.json +16 -0
- package/fixtures/json-schema/endpoint-controls/invalid/missing-channel-decision.json +15 -0
- package/fixtures/json-schema/endpoint-controls/invalid/receipt-content.json +36 -0
- package/fixtures/json-schema/endpoint-controls/valid/controls.json +16 -0
- package/fixtures/json-schema/endpoint-controls/valid/receipt.json +35 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/eap-activate-bad-fingerprint.json +14 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/eap-gateway-request.json +14 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/eap-private-key-request.json +15 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/eap-profile-without-eap-credential.json +22 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/endpoint-dns-observation.json +25 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/gateway-presence.json +22 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/mihomo-protection-zero-version.json +45 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/presence-raw-key.json +23 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/presence-zero-sequence.json +22 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/workload-wide-vpn.json +17 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/eap-certificate-activate.json +14 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/eap-certificate-request.json +14 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/eap-profile-enrollment.json +24 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/endpoint-presence.json +22 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/ipv6-configuration.json +102 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/mihomo-protected-policy.json +45 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/site-dns-ipv6.json +25 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/workload-direct-ztna.json +20 -0
- package/fixtures/openapi/DLPShadowDecision/invalid/step-up-type.json +14 -0
- package/fixtures/openapi/DLPShadowDecision/valid/archive-v2.json +14 -0
- package/fixtures/openapi/DLPShadowDecision/valid/step-up.json +14 -0
- package/fixtures/openapi/DLPShadowInput/invalid/self-reported-result.json +7 -0
- package/fixtures/openapi/DLPShadowInput/invalid/too-many-samples.json +14 -0
- package/fixtures/openapi/DLPShadowInput/valid/bounded.json +7 -0
- package/fixtures/openapi/DeliveryDocument/invalid/migration-missing-guide-build-template.json +30 -0
- package/fixtures/openapi/DeliveryDocument/invalid/migration-missing-guide-deployment-template.json +30 -0
- package/fixtures/openapi/DeliveryDocument/invalid/migration-missing-guide-workflow-template.json +27 -0
- package/fixtures/openapi/DeliveryDocument/valid/migration-build-template.json +31 -0
- package/fixtures/openapi/DeliveryDocument/valid/migration-deployment-template.json +31 -0
- package/fixtures/openapi/DeliveryDocument/valid/migration-workflow-template.json +28 -0
- package/fixtures/openapi/DeliveryPlanInput/invalid/manual-plan.json +7 -0
- package/fixtures/openapi/DeliveryPlanInput/valid/manual-plan.json +6 -0
- package/fixtures/openapi/DeliveryTargetInput/invalid/candidate-on-runtime-deploy.json +9 -0
- package/fixtures/openapi/DeliveryTargetInput/invalid/config-update-missing-service.json +7 -0
- package/fixtures/openapi/DeliveryTargetInput/valid/template-configuration-only.json +8 -0
- package/fixtures/openapi/DeliveryTargetInput/valid/template-configuration-without-runtime-environment.json +1 -0
- package/fixtures/openapi/DockerVolumeDLPAppealInput/invalid/p4-response.json +3 -0
- package/fixtures/openapi/DockerVolumeDLPAppealInput/valid/p4-response.json +3 -0
- package/fixtures/openapi/DockerVolumeDLPAppealReviewInput/invalid/p4-response.json +5 -0
- package/fixtures/openapi/DockerVolumeDLPAppealReviewInput/valid/p4-response.json +5 -0
- package/fixtures/openapi/DockerVolumeDLPAuthorizationInput/invalid/p4-response.json +3 -0
- package/fixtures/openapi/DockerVolumeDLPAuthorizationInput/valid/p4-response.json +3 -0
- package/fixtures/openapi/DockerVolumeDLPContent/invalid/archive-unsupported-channel.json +9 -0
- package/fixtures/openapi/DockerVolumeDLPContent/invalid/p4.json +6 -0
- package/fixtures/openapi/DockerVolumeDLPContent/invalid/step-up-audit.json +9 -0
- package/fixtures/openapi/DockerVolumeDLPContent/invalid/unavailable-engine.json +9 -0
- package/fixtures/openapi/DockerVolumeDLPContent/invalid/unknown-data-level.json +12 -0
- package/fixtures/openapi/DockerVolumeDLPContent/valid/classified-rule.json +16 -0
- package/fixtures/openapi/DockerVolumeDLPContent/valid/p4.json +8 -0
- package/fixtures/openapi/DockerVolumeDLPContent/valid/step-up.json +9 -0
- package/fixtures/openapi/DockerVolumeDLPDecision/invalid/p4.json +9 -0
- package/fixtures/openapi/DockerVolumeDLPDecision/invalid/step-up-type.json +12 -0
- package/fixtures/openapi/DockerVolumeDLPDecision/valid/archive-v2.json +12 -0
- package/fixtures/openapi/DockerVolumeDLPDecision/valid/p4.json +11 -0
- package/fixtures/openapi/DockerVolumeDLPDecision/valid/step-up.json +12 -0
- package/fixtures/openapi/DockerVolumeDLPSaveInput/invalid/p4.json +11 -0
- package/fixtures/openapi/DockerVolumeDLPSaveInput/valid/p4.json +11 -0
- package/fixtures/openapi/DockerVolumeFileReadInput/invalid/p4-response.json +5 -0
- package/fixtures/openapi/DockerVolumeFileReadInput/valid/p4-response.json +4 -0
- package/fixtures/openapi/EndpointDeviceInput/invalid/reported-management.json +5 -0
- package/fixtures/openapi/EndpointDeviceInput/valid/management-state.json +5 -0
- package/fixtures/openapi/EndpointDeviceRegistrationInput/invalid/self-promote-management.json +5 -0
- package/fixtures/openapi/KubernetesImportCandidateListEnvelope/invalid/source-with-spec.json +33 -0
- package/fixtures/openapi/KubernetesImportCandidateListEnvelope/invalid/source-without-uid.json +29 -0
- package/fixtures/openapi/KubernetesImportCandidateListEnvelope/valid/stable-source.json +30 -0
- package/fixtures/openapi/KubernetesPod/invalid/pod-uid.json +9 -0
- package/fixtures/openapi/KubernetesPod/valid/pod-uid.json +9 -0
- package/fixtures/openapi/KubernetesPodDetail/invalid/pod-uid.json +6 -0
- package/fixtures/openapi/KubernetesPodDetail/valid/pod-uid.json +6 -0
- package/fixtures/openapi/KubernetesServiceImportRequest/invalid/empty-source-uid.json +48 -0
- package/fixtures/openapi/KubernetesServiceImportRequest/valid/pinned-workloads-and-cr.json +48 -0
- package/fixtures/openapi/ManagedContentPasteInput/invalid/boundary.json +5 -0
- package/fixtures/openapi/ManagedContentPasteInput/valid/boundary.json +6 -0
- package/fixtures/openapi/ManagedContentPolicy/invalid/self-reported-allow.json +8 -0
- package/fixtures/openapi/ManagedContentPolicy/invalid/step-up-audit.json +8 -0
- package/fixtures/openapi/ManagedContentPolicy/invalid/unknown-extractor.json +8 -0
- package/fixtures/openapi/ManagedContentPolicy/valid/archive-v2.json +8 -0
- package/fixtures/openapi/ManagedContentPolicy/valid/network-and-level.json +10 -0
- package/fixtures/openapi/ManagedContentPolicy/valid/step-up.json +8 -0
- package/fixtures/openapi/ManagedContentReviewInput/invalid/boundary.json +5 -0
- package/fixtures/openapi/ManagedContentReviewInput/valid/boundary.json +5 -0
- package/fixtures/openapi/ManagedContentSaveInput/invalid/boundary.json +14 -0
- package/fixtures/openapi/ManagedContentSaveInput/valid/boundary.json +16 -0
- package/fixtures/openapi/ManagedContentUploadInput/invalid/boundary.json +7 -0
- package/fixtures/openapi/ManagedContentUploadInput/valid/boundary.json +6 -0
- package/fixtures/openapi/ManifestExecutionTaskPayload/invalid/initialization.json +7 -0
- package/fixtures/openapi/ManifestExecutionTaskPayload/valid/initialization.json +7 -0
- package/fixtures/openapi/ManifestRootResource/invalid/root.json +1 -0
- package/fixtures/openapi/ManifestRootResource/valid/root.json +1 -0
- package/fixtures/openapi/NetworkAccessPolicyInput/invalid/unknown-management-state.json +35 -0
- package/fixtures/openapi/NetworkAccessPolicyInput/valid/managed-device.json +35 -0
- package/fixtures/openapi/NetworkAdmissionPreviewRequest/invalid/deviceId-characters.json +4 -0
- package/fixtures/openapi/NetworkAdmissionPreviewRequest/invalid/nasBindingId-characters.json +4 -0
- package/fixtures/openapi/NetworkAdmissionPreviewRequest/invalid/subject-override.json +1 -0
- package/fixtures/openapi/NetworkAdmissionPreviewRequest/valid/device-nas.json +1 -0
- package/fixtures/openapi/NetworkAdmissionPreviewResult/invalid/missing-attributeVersion.json +13 -0
- package/fixtures/openapi/NetworkAdmissionPreviewResult/invalid/missing-evaluatedAt.json +13 -0
- package/fixtures/openapi/NetworkAdmissionPreviewResult/valid/versioned.json +14 -0
- package/fixtures/openapi/NetworkConflictAnalysisRequest/invalid/missing-runtime-ranges.json +1 -0
- package/fixtures/openapi/NetworkConflictAnalysisRequest/invalid/unknown-source-type.json +9 -0
- package/fixtures/openapi/NetworkConflictAnalysisRequest/valid/empty-runtime-ranges.json +3 -0
- package/fixtures/openapi/NetworkConflictAnalysisRequest/{invalid → valid}/ipv6-range.json +1 -1
- package/fixtures/openapi/NetworkEAPServerTrust/invalid/no-server-validation.json +5 -0
- package/fixtures/openapi/NetworkEAPServerTrust/valid/named-server.json +9 -0
- package/fixtures/openapi/NetworkEndpointResponsePolicyInput/invalid/all-sites-implicit.json +45 -0
- package/fixtures/openapi/NetworkEndpointResponsePolicyInput/invalid/unbounded-stages.json +54 -0
- package/fixtures/openapi/NetworkEndpointResponsePolicyInput/invalid/zero-heartbeat.json +47 -0
- package/fixtures/openapi/NetworkEndpointResponsePolicyInput/valid/staged-protection.json +47 -0
- package/fixtures/openapi/NetworkEndpointResponseRestoreInput/invalid/missing-reason.json +3 -0
- package/fixtures/openapi/NetworkEndpointResponseRestoreInput/valid/expected-current.json +4 -0
- package/fixtures/openapi/NetworkPolicyPreviewResult/invalid/attribute-version.json +1 -0
- package/fixtures/openapi/NetworkPolicyPreviewResult/valid/admission-version.json +1 -0
- package/fixtures/openapi/NetworkPolicySubjects/invalid/duplicate-role.json +1 -0
- package/fixtures/openapi/NetworkPolicySubjects/invalid/role-characters.json +5 -0
- package/fixtures/openapi/NetworkPolicySubjects/valid/role-bound.json +1 -0
- package/fixtures/openapi/NetworkProxyConfigurationInput/invalid/missing-content.json +1 -0
- package/fixtures/openapi/NetworkProxyConfigurationPatchInput/invalid/bad-binding-id.json +9 -0
- package/fixtures/openapi/NetworkProxyConfigurationPatchInput/invalid/empty-content.json +1 -0
- package/fixtures/openapi/NetworkProxyConfigurationPatchInput/valid/binding-only.json +11 -0
- package/fixtures/openapi/NetworkProxyConfigurationPatchInput/valid/clear-binding-disabled.json +5 -0
- package/fixtures/openapi/NetworkProxyConfigurationPatchInput/valid/enabled.json +1 -0
- package/fixtures/openapi/NetworkSiteInput/invalid/unknown-governance.json +6 -0
- package/fixtures/openapi/NetworkSiteInput/valid/restricted-unmanaged.json +6 -0
- package/fixtures/openapi/ResourceRelationConfig/invalid/executable-rule.json +17 -0
- package/fixtures/openapi/ResourceRelationConfig/valid/unknown-kind.json +13 -0
- package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/migration-missing-guide-valid-yaml.json +38 -0
- package/fixtures/openapi/ServiceDeploymentTemplateInput/valid/migration-valid-yaml.json +39 -0
- package/fixtures/openapi/TemplateMigrationAssessment/invalid/initialization.json +9 -0
- package/fixtures/openapi/TemplateMigrationAssessment/valid/initialization.json +9 -0
- package/fixtures/openapi/TemplateMigrationPolicy/invalid/blank-guide.json +5 -0
- package/fixtures/openapi/TemplateMigrationPolicy/invalid/missing-guide.json +4 -0
- package/fixtures/openapi/TemplateMigrationPolicy/invalid/unknown-escape-hatch.json +6 -0
- package/fixtures/openapi/TemplateMigrationPolicy/valid/legacy-safe.json +4 -0
- package/fixtures/openapi/TemplateMigrationPolicy/valid/ordered-data-change.json +5 -0
- package/fixtures/openapi/TemplateUpdateBatchInput/invalid/duplicate-candidates.json +11 -0
- package/fixtures/openapi/TemplateUpdateBatchInput/invalid/empty-identities.json +10 -0
- package/fixtures/openapi/TemplateUpdateBatchInput/valid/staged-adoption.json +10 -0
- package/fixtures/openapi/TemplateUpdatePolicyInput/invalid/unknown-mode.json +8 -0
- package/fixtures/openapi/TemplateUpdatePolicyInput/valid/explicit-automatic.json +8 -0
- package/fixtures/openapi/TemplateUpdatePreviewInput/invalid/empty-identities.json +10 -0
- package/fixtures/openapi/TemplateUpdatePreviewInput/invalid/empty-targets.json +6 -0
- package/fixtures/openapi/TemplateUpdatePreviewInput/valid/cross-version-manual.json +14 -0
- package/gen/go/sohaapi/client.go +1 -1
- package/gen/go/sohaapi/client_test.go +2 -2
- package/gen/go/sohaapi/import_source_compat_test.go +12 -0
- package/gen/go/sohaapi/proxy_configuration_test.go +38 -0
- package/gen/go/sohaapi/types.go +3760 -345
- package/gen/ts/sohaapi/index.ts +5573 -181
- package/go.mod +2 -2
- package/helmrelease/delivery.go +4 -1
- package/helmrelease/initialization.go +10 -0
- package/helmrelease/runtime/delivery.go +40 -10
- package/helmrelease/runtime/delivery_test.go +4 -0
- package/helmrelease/runtime/initialization.go +186 -0
- package/helmrelease/runtime/initialization_test.go +203 -0
- package/network/README.md +34 -0
- package/network/network-radius-accounting.schema.json +4 -1
- package/network/network-runtime-protocol.schema.json +2166 -317
- package/network/proxy-runtime.schema.json +482 -67
- package/openapi/network_control_test.go +64 -0
- package/openapi/soha-api.json +53524 -43459
- package/openapi/soha-api.yaml +26431 -20491
- package/openapi/spec_generated.go +3 -3
- package/openapi/spec_test.go +42 -0
- package/package.json +6 -3
- package/proxytransport/handler.go +271 -0
- package/proxytransport/probe.go +30 -0
- package/proxytransport/scope.go +89 -0
- package/proxytransport/transport_test.go +60 -0
- package/resource/models.go +2 -0
- package/resource/relations.go +225 -0
- package/resource/relations_test.go +47 -0
- package/resource/root_cr.go +38 -0
- package/resource/runtime/argocd_integration_test.go +1 -1
- package/resource/runtime/initialization.go +66 -0
- package/resource/runtime/initialization_test.go +80 -0
- package/resource/runtime/relations.go +161 -0
- package/resource/runtime/relations_test.go +100 -0
- package/resource/runtime/root_cr.go +79 -0
- package/resource/runtime/root_cr_test.go +118 -0
|
@@ -177,6 +177,9 @@
|
|
|
177
177
|
"default": true,
|
|
178
178
|
"type": "boolean"
|
|
179
179
|
},
|
|
180
|
+
"migrationPolicy": {
|
|
181
|
+
"$ref": "#/$defs/TemplateMigrationPolicy"
|
|
182
|
+
},
|
|
180
183
|
"variableSchema": {
|
|
181
184
|
"additionalProperties": {
|
|
182
185
|
"$ref": "#/$defs/DeliveryBuildVariableSchema"
|
|
@@ -317,6 +320,9 @@
|
|
|
317
320
|
"health": {
|
|
318
321
|
"$ref": "#/$defs/DeploymentTemplateHealth"
|
|
319
322
|
},
|
|
323
|
+
"migrationPolicy": {
|
|
324
|
+
"$ref": "#/$defs/TemplateMigrationPolicy"
|
|
325
|
+
},
|
|
320
326
|
"parameterSchema": {
|
|
321
327
|
"$ref": "#/$defs/TemplateParameterSchema"
|
|
322
328
|
},
|
|
@@ -804,21 +810,68 @@
|
|
|
804
810
|
"additionalProperties": false,
|
|
805
811
|
"allOf": [
|
|
806
812
|
{
|
|
807
|
-
"
|
|
813
|
+
"else": {
|
|
814
|
+
"allOf": [
|
|
815
|
+
{
|
|
816
|
+
"if": {
|
|
817
|
+
"properties": {
|
|
818
|
+
"action": {
|
|
819
|
+
"enum": [
|
|
820
|
+
"build_deploy",
|
|
821
|
+
"deploy",
|
|
822
|
+
"config_update"
|
|
823
|
+
]
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
"then": {
|
|
828
|
+
"required": [
|
|
829
|
+
"applicationEnvironmentId"
|
|
830
|
+
]
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
],
|
|
808
834
|
"properties": {
|
|
809
|
-
"
|
|
810
|
-
"
|
|
811
|
-
|
|
812
|
-
"deploy",
|
|
813
|
-
"config_update"
|
|
814
|
-
]
|
|
835
|
+
"serviceId": {
|
|
836
|
+
"minLength": 1,
|
|
837
|
+
"type": "string"
|
|
815
838
|
}
|
|
816
839
|
}
|
|
817
840
|
},
|
|
818
|
-
"
|
|
841
|
+
"if": {
|
|
819
842
|
"required": [
|
|
820
|
-
"
|
|
843
|
+
"templateCandidateId"
|
|
821
844
|
]
|
|
845
|
+
},
|
|
846
|
+
"then": {
|
|
847
|
+
"not": {
|
|
848
|
+
"anyOf": [
|
|
849
|
+
{
|
|
850
|
+
"required": [
|
|
851
|
+
"helmRevision"
|
|
852
|
+
]
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
"required": [
|
|
856
|
+
"buildArgs"
|
|
857
|
+
]
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
"required": [
|
|
861
|
+
"repositoryRefs"
|
|
862
|
+
]
|
|
863
|
+
}
|
|
864
|
+
]
|
|
865
|
+
},
|
|
866
|
+
"properties": {
|
|
867
|
+
"action": {
|
|
868
|
+
"const": "config_update"
|
|
869
|
+
},
|
|
870
|
+
"releaseBundleId": {
|
|
871
|
+
"maxLength": 0,
|
|
872
|
+
"type": "string"
|
|
873
|
+
}
|
|
874
|
+
}
|
|
822
875
|
}
|
|
823
876
|
},
|
|
824
877
|
{
|
|
@@ -897,6 +950,10 @@
|
|
|
897
950
|
"type": "array"
|
|
898
951
|
},
|
|
899
952
|
"serviceId": {
|
|
953
|
+
"type": "string"
|
|
954
|
+
},
|
|
955
|
+
"templateCandidateId": {
|
|
956
|
+
"maxLength": 256,
|
|
900
957
|
"minLength": 1,
|
|
901
958
|
"type": "string"
|
|
902
959
|
}
|
|
@@ -1068,6 +1125,9 @@
|
|
|
1068
1125
|
"enabled": {
|
|
1069
1126
|
"default": true,
|
|
1070
1127
|
"type": "boolean"
|
|
1128
|
+
},
|
|
1129
|
+
"migrationPolicy": {
|
|
1130
|
+
"$ref": "#/$defs/TemplateMigrationPolicy"
|
|
1071
1131
|
}
|
|
1072
1132
|
},
|
|
1073
1133
|
"required": [
|
|
@@ -1376,6 +1436,64 @@
|
|
|
1376
1436
|
},
|
|
1377
1437
|
"type": "object"
|
|
1378
1438
|
},
|
|
1439
|
+
"TemplateMigrationPolicy": {
|
|
1440
|
+
"additionalProperties": false,
|
|
1441
|
+
"if": {
|
|
1442
|
+
"anyOf": [
|
|
1443
|
+
{
|
|
1444
|
+
"properties": {
|
|
1445
|
+
"sequential": {
|
|
1446
|
+
"const": true
|
|
1447
|
+
}
|
|
1448
|
+
},
|
|
1449
|
+
"required": [
|
|
1450
|
+
"sequential"
|
|
1451
|
+
]
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
"properties": {
|
|
1455
|
+
"irreversible": {
|
|
1456
|
+
"const": true
|
|
1457
|
+
}
|
|
1458
|
+
},
|
|
1459
|
+
"required": [
|
|
1460
|
+
"irreversible"
|
|
1461
|
+
]
|
|
1462
|
+
}
|
|
1463
|
+
]
|
|
1464
|
+
},
|
|
1465
|
+
"properties": {
|
|
1466
|
+
"guide": {
|
|
1467
|
+
"description": "Human migration/recovery instructions. Never executed as a script.",
|
|
1468
|
+
"maxLength": 2000,
|
|
1469
|
+
"type": "string"
|
|
1470
|
+
},
|
|
1471
|
+
"irreversible": {
|
|
1472
|
+
"description": "Forward changes require approval; ordinary rollback across this boundary is forbidden.",
|
|
1473
|
+
"type": "boolean"
|
|
1474
|
+
},
|
|
1475
|
+
"sequential": {
|
|
1476
|
+
"description": "Must cross this boundary as adjacent published versions; approval cannot waive it.",
|
|
1477
|
+
"type": "boolean"
|
|
1478
|
+
}
|
|
1479
|
+
},
|
|
1480
|
+
"required": [
|
|
1481
|
+
"sequential",
|
|
1482
|
+
"irreversible"
|
|
1483
|
+
],
|
|
1484
|
+
"then": {
|
|
1485
|
+
"properties": {
|
|
1486
|
+
"guide": {
|
|
1487
|
+
"minLength": 1,
|
|
1488
|
+
"pattern": "\\S"
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
"required": [
|
|
1492
|
+
"guide"
|
|
1493
|
+
]
|
|
1494
|
+
},
|
|
1495
|
+
"type": "object"
|
|
1496
|
+
},
|
|
1379
1497
|
"TemplateParameterSchema": {
|
|
1380
1498
|
"additionalProperties": false,
|
|
1381
1499
|
"description": "Restricted JSON Schema. Objects reject undeclared keys unless mapValues specifies their schema. secret_reference accepts only a namespaced Secret name/key reference, never a plaintext credential. External references and executable expressions are unsupported.",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package endpointcontrol
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"errors"
|
|
5
|
+
"time"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// Required booleans must be present, including explicit false. Missing input
|
|
9
|
+
// must never become a newer all-off policy through Go zero values.
|
|
10
|
+
func (c *Controls) UnmarshalJSON(raw []byte) error {
|
|
11
|
+
var w struct {
|
|
12
|
+
USB *bool `json:"usb"`
|
|
13
|
+
Print *bool `json:"print"`
|
|
14
|
+
Clipboard *bool `json:"clipboard"`
|
|
15
|
+
}
|
|
16
|
+
if err := Decode(raw, &w); err != nil {
|
|
17
|
+
return err
|
|
18
|
+
}
|
|
19
|
+
if w.USB == nil || w.Print == nil || w.Clipboard == nil {
|
|
20
|
+
return errors.New("all native channel decisions are required")
|
|
21
|
+
}
|
|
22
|
+
*c = Controls{USB: *w.USB, Print: *w.Print, Clipboard: *w.Clipboard}
|
|
23
|
+
return nil
|
|
24
|
+
}
|
|
25
|
+
func (d *Desired) UnmarshalJSON(raw []byte) error {
|
|
26
|
+
var w struct {
|
|
27
|
+
Configured *bool `json:"configured"`
|
|
28
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
29
|
+
DeviceID string `json:"deviceId"`
|
|
30
|
+
RuntimeID string `json:"runtimeId"`
|
|
31
|
+
CredentialID string `json:"credentialId"`
|
|
32
|
+
PolicyVersion *int64 `json:"policyVersion"`
|
|
33
|
+
Controls *Controls `json:"controls"`
|
|
34
|
+
Challenge string `json:"challenge"`
|
|
35
|
+
IssuedAt *time.Time `json:"issuedAt"`
|
|
36
|
+
ValidUntil *time.Time `json:"validUntil"`
|
|
37
|
+
}
|
|
38
|
+
if err := Decode(raw, &w); err != nil {
|
|
39
|
+
return err
|
|
40
|
+
}
|
|
41
|
+
if w.Configured == nil || w.PolicyVersion == nil || w.Controls == nil || w.IssuedAt == nil || w.ValidUntil == nil {
|
|
42
|
+
return errors.New("native channel envelope has missing required fields")
|
|
43
|
+
}
|
|
44
|
+
*d = Desired{Configured: *w.Configured, SchemaVersion: w.SchemaVersion, DeviceID: w.DeviceID, RuntimeID: w.RuntimeID, CredentialID: w.CredentialID, PolicyVersion: *w.PolicyVersion, Controls: *w.Controls, Challenge: w.Challenge, IssuedAt: *w.IssuedAt, ValidUntil: *w.ValidUntil}
|
|
45
|
+
return nil
|
|
46
|
+
}
|
|
47
|
+
func (c *Channel) UnmarshalJSON(raw []byte) error {
|
|
48
|
+
var w struct {
|
|
49
|
+
Name string `json:"name"`
|
|
50
|
+
Scope string `json:"scope"`
|
|
51
|
+
State string `json:"state"`
|
|
52
|
+
Targets *int `json:"targets"`
|
|
53
|
+
Controlled *int `json:"controlled"`
|
|
54
|
+
ReasonCode string `json:"reasonCode"`
|
|
55
|
+
}
|
|
56
|
+
if err := Decode(raw, &w); err != nil {
|
|
57
|
+
return err
|
|
58
|
+
}
|
|
59
|
+
if w.Targets == nil || w.Controlled == nil {
|
|
60
|
+
return errors.New("native channel counts are required")
|
|
61
|
+
}
|
|
62
|
+
*c = Channel{Name: w.Name, Scope: w.Scope, State: w.State, Targets: *w.Targets, Controlled: *w.Controlled, ReasonCode: w.ReasonCode}
|
|
63
|
+
return nil
|
|
64
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Package endpointcontrol owns the bounded native endpoint-channel protocol.
|
|
2
|
+
package endpointcontrol
|
|
3
|
+
|
|
4
|
+
import (
|
|
5
|
+
"bytes"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"errors"
|
|
8
|
+
"io"
|
|
9
|
+
"regexp"
|
|
10
|
+
"time"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const Version = "endpoint-controls/v1alpha1"
|
|
14
|
+
const Capability = "windows-channel-controls-v1"
|
|
15
|
+
|
|
16
|
+
type Controls struct {
|
|
17
|
+
USB bool `json:"usb"`
|
|
18
|
+
Print bool `json:"print"`
|
|
19
|
+
Clipboard bool `json:"clipboard"`
|
|
20
|
+
}
|
|
21
|
+
type Policy struct {
|
|
22
|
+
DeviceID string `json:"deviceId"`
|
|
23
|
+
Revision int64 `json:"revision"`
|
|
24
|
+
PublishedVersion int64 `json:"publishedVersion"`
|
|
25
|
+
Draft Controls `json:"draft"`
|
|
26
|
+
Published *Controls `json:"published,omitempty"`
|
|
27
|
+
UpdatedAt time.Time `json:"updatedAt"`
|
|
28
|
+
}
|
|
29
|
+
type SaveInput struct {
|
|
30
|
+
ExpectedRevision int64 `json:"expectedRevision"`
|
|
31
|
+
Draft Controls `json:"draft"`
|
|
32
|
+
}
|
|
33
|
+
type PublishInput struct {
|
|
34
|
+
ExpectedRevision int64 `json:"expectedRevision"`
|
|
35
|
+
}
|
|
36
|
+
type Desired struct {
|
|
37
|
+
Configured bool `json:"configured"`
|
|
38
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
39
|
+
DeviceID string `json:"deviceId"`
|
|
40
|
+
RuntimeID string `json:"runtimeId"`
|
|
41
|
+
CredentialID string `json:"credentialId"`
|
|
42
|
+
PolicyVersion int64 `json:"policyVersion"`
|
|
43
|
+
Controls Controls `json:"controls"`
|
|
44
|
+
Challenge string `json:"challenge"`
|
|
45
|
+
IssuedAt time.Time `json:"issuedAt"`
|
|
46
|
+
ValidUntil time.Time `json:"validUntil"`
|
|
47
|
+
}
|
|
48
|
+
type Channel struct {
|
|
49
|
+
Name string `json:"name"`
|
|
50
|
+
Scope string `json:"scope"`
|
|
51
|
+
State string `json:"state"`
|
|
52
|
+
Targets int `json:"targets"`
|
|
53
|
+
Controlled int `json:"controlled"`
|
|
54
|
+
ReasonCode string `json:"reasonCode"`
|
|
55
|
+
}
|
|
56
|
+
type Receipt struct {
|
|
57
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
58
|
+
DeviceID string `json:"deviceId"`
|
|
59
|
+
RuntimeID string `json:"runtimeId"`
|
|
60
|
+
CredentialID string `json:"credentialId"`
|
|
61
|
+
PolicyVersion int64 `json:"policyVersion"`
|
|
62
|
+
Challenge string `json:"challenge"`
|
|
63
|
+
ObservedAt time.Time `json:"observedAt"`
|
|
64
|
+
Channels []Channel `json:"channels"`
|
|
65
|
+
}
|
|
66
|
+
type View struct {
|
|
67
|
+
Policy Policy `json:"policy"`
|
|
68
|
+
Receipt *Observation `json:"receipt,omitempty"`
|
|
69
|
+
ReceivedAt *time.Time `json:"receivedAt,omitempty"`
|
|
70
|
+
Fresh bool `json:"fresh"`
|
|
71
|
+
RuntimeCount int `json:"runtimeCount"`
|
|
72
|
+
PendingRuntimeCount int `json:"pendingRuntimeCount"`
|
|
73
|
+
}
|
|
74
|
+
type Observation struct {
|
|
75
|
+
RuntimeID string `json:"runtimeId"`
|
|
76
|
+
CredentialID string `json:"credentialId"`
|
|
77
|
+
PolicyVersion int64 `json:"policyVersion"`
|
|
78
|
+
ObservedAt time.Time `json:"observedAt"`
|
|
79
|
+
Channels []Channel `json:"channels"`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
var identifier = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`)
|
|
83
|
+
var challenge = regexp.MustCompile(`^[A-Za-z0-9_-]{43}$`)
|
|
84
|
+
var reason = regexp.MustCompile(`^[a-z][a-z0-9_]{0,63}$`)
|
|
85
|
+
|
|
86
|
+
func Decode(raw []byte, out any) error {
|
|
87
|
+
if len(raw) > 32768 {
|
|
88
|
+
return errors.New("endpoint channel message too large")
|
|
89
|
+
}
|
|
90
|
+
d := json.NewDecoder(bytes.NewReader(raw))
|
|
91
|
+
d.DisallowUnknownFields()
|
|
92
|
+
if err := d.Decode(out); err != nil {
|
|
93
|
+
return err
|
|
94
|
+
}
|
|
95
|
+
if d.Decode(new(any)) != io.EOF {
|
|
96
|
+
return errors.New("trailing channel message")
|
|
97
|
+
}
|
|
98
|
+
return nil
|
|
99
|
+
}
|
|
100
|
+
func ValidDesired(d Desired, now time.Time) bool {
|
|
101
|
+
return (d.Configured && d.PolicyVersion > 0 || !d.Configured && d.PolicyVersion == 0) && validIdentity(d.SchemaVersion, d.DeviceID, d.RuntimeID, d.CredentialID, d.PolicyVersion, d.Challenge) && !d.IssuedAt.After(now.Add(30*time.Second)) && d.ValidUntil.After(now) && d.ValidUntil.After(d.IssuedAt) && d.ValidUntil.Sub(d.IssuedAt) <= time.Minute
|
|
102
|
+
}
|
|
103
|
+
func ValidReceipt(r Receipt, now time.Time) bool {
|
|
104
|
+
if !validIdentity(r.SchemaVersion, r.DeviceID, r.RuntimeID, r.CredentialID, r.PolicyVersion, r.Challenge) || len(r.Channels) != 3 || r.ObservedAt.Before(now.Add(-time.Minute)) || r.ObservedAt.After(now.Add(30*time.Second)) {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
seen := map[string]bool{}
|
|
108
|
+
for _, c := range r.Channels {
|
|
109
|
+
if seen[c.Name] || !ValidChannel(c) {
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
seen[c.Name] = true
|
|
113
|
+
}
|
|
114
|
+
return seen["usb"] && seen["print"] && seen["clipboard"]
|
|
115
|
+
}
|
|
116
|
+
func validIdentity(version, device, runtime, credential string, revision int64, nonce string) bool {
|
|
117
|
+
return version == Version && identifier.MatchString(device) && identifier.MatchString(runtime) && identifier.MatchString(credential) && revision >= 0 && challenge.MatchString(nonce)
|
|
118
|
+
}
|
|
119
|
+
func ValidChannel(c Channel) bool {
|
|
120
|
+
scopes := map[string]string{"usb": "current_usb_volumes", "print": "local_new_print_jobs", "clipboard": "enrolled_console_win32"}
|
|
121
|
+
if scopes[c.Name] == "" || c.Scope != scopes[c.Name] || c.Targets < 0 || c.Targets > 256 || c.Controlled < 0 || c.Controlled > c.Targets || !reason.MatchString(c.ReasonCode) {
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
if c.State == "controlled" && (c.Targets == 0 || c.Controlled != c.Targets) {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
if (c.State == "off" || c.State == "unconfigured" || c.State == "not_present") && c.Controlled != 0 {
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
if (c.State == "unconfigured" || c.State == "not_present") && c.Targets != 0 {
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
switch c.State {
|
|
134
|
+
case "off", "unconfigured", "not_present", "controlled", "partial", "failed", "unsupported", "conflict":
|
|
135
|
+
return true
|
|
136
|
+
}
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
package endpointcontrol
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"testing"
|
|
5
|
+
"time"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
func TestNativeReceiptCannotClaimContradictoryControl(t *testing.T) {
|
|
9
|
+
cases := []Channel{
|
|
10
|
+
{Name: "usb", Scope: "current_usb_volumes", State: "controlled", Targets: 1, Controlled: 0, ReasonCode: "native_scope_only"},
|
|
11
|
+
{Name: "usb", Scope: "current_usb_volumes", State: "controlled", Targets: 0, Controlled: 0, ReasonCode: "native_scope_only"},
|
|
12
|
+
{Name: "clipboard", Scope: "current_usb_volumes", State: "controlled", Targets: 1, Controlled: 1, ReasonCode: "native_scope_only"},
|
|
13
|
+
{Name: "print", Scope: "local_new_print_jobs", State: "off", Targets: 1, Controlled: 1, ReasonCode: "disabled"},
|
|
14
|
+
}
|
|
15
|
+
for _, c := range cases {
|
|
16
|
+
if ValidChannel(c) {
|
|
17
|
+
t.Fatalf("contradictory control accepted: %+v", c)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if !ValidChannel(Channel{Name: "clipboard", Scope: "enrolled_console_win32", State: "controlled", Targets: 1, Controlled: 1, ReasonCode: "native_scope_only"}) {
|
|
21
|
+
t.Fatal("bounded native control rejected")
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
func TestUnconfiguredIsNotAPublishedRelease(t *testing.T) {
|
|
25
|
+
now := time.Now().UTC()
|
|
26
|
+
d := Desired{SchemaVersion: Version, DeviceID: "d", RuntimeID: "r", CredentialID: "c", Challenge: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", IssuedAt: now, ValidUntil: now.Add(time.Minute)}
|
|
27
|
+
if !ValidDesired(d, now) {
|
|
28
|
+
t.Fatal("unconfigured envelope rejected")
|
|
29
|
+
}
|
|
30
|
+
d.PolicyVersion = 1
|
|
31
|
+
if ValidDesired(d, now) {
|
|
32
|
+
t.Fatal("positive unpublished revision accepted")
|
|
33
|
+
}
|
|
34
|
+
d.Configured = true
|
|
35
|
+
if !ValidDesired(d, now) {
|
|
36
|
+
t.Fatal("explicit published release rejected")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func TestMissingBooleanCannotDecodeAsRelease(t *testing.T) {
|
|
41
|
+
var controls Controls
|
|
42
|
+
for _, raw := range []string{`{}`, `{"usb":false,"print":false}`, `{"usb":false,"print":false,"clipboard":null}`} {
|
|
43
|
+
if Decode([]byte(raw), &controls) == nil {
|
|
44
|
+
t.Fatal("incomplete channel decisions accepted", raw)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
var desired Desired
|
|
48
|
+
if Decode([]byte(`{"configured":true,"schemaVersion":"endpoint-controls/v1alpha1","deviceId":"device","runtimeId":"runtime","credentialId":"credential","policyVersion":99,"challenge":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","issuedAt":"2026-10-28T00:00:00Z","validUntil":"2026-10-28T00:01:00Z"}`), &desired) == nil {
|
|
49
|
+
t.Fatal("missing controls became release")
|
|
50
|
+
}
|
|
51
|
+
}
|