@opensoha/contracts 0.1.26 → 0.1.29
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 +14 -3
- package/README.md +8 -9
- package/auth/catalog_generated.go +2 -2
- package/auth/permission-catalog.json +481 -1
- package/compat/consumer-revisions.json +1 -1
- package/compat/json-schema-ecosystem-0.1.0.json +1 -1
- package/compat/openapi-loose-boundaries-0.1.0.json +1 -1
- 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/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/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/KubernetesClusterRegistrationRequest/invalid/metric-cluster-value-length.json +3 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/invalid/metric-cluster-value-type.json +3 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/metric-cluster-alias.json +5 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/metric-cluster-reset.json +3 -0
- package/fixtures/openapi/KubernetesHelmReleaseValuesUpdateInput/invalid/revision-overflow.json +4 -0
- package/fixtures/openapi/KubernetesHelmReleaseValuesUpdateInput/valid/legacy.json +3 -0
- package/fixtures/openapi/KubernetesHelmReleaseValuesUpdateInput/valid/observed-revision.json +4 -0
- package/fixtures/openapi/KubernetesJobDetail/invalid/execution-observation.json +23 -0
- package/fixtures/openapi/KubernetesJobDetail/valid/execution-observation.json +23 -0
- package/fixtures/openapi/KubernetesNode/invalid/labels.json +10 -0
- package/fixtures/openapi/KubernetesNode/valid/labels.json +11 -0
- package/fixtures/openapi/KubernetesPodListEnvelope/invalid/empty-uid.json +13 -0
- package/fixtures/openapi/KubernetesPodListEnvelope/invalid/invalid-container-state.json +20 -0
- package/fixtures/openapi/KubernetesPodListEnvelope/invalid/missing-container-ready.json +19 -0
- package/fixtures/openapi/KubernetesPodListEnvelope/valid/container-statuses.json +67 -0
- package/fixtures/openapi/KubernetesPodListEnvelope/valid/running-pod.json +1 -0
- package/fixtures/openapi/KubernetesPrometheusDetail/invalid/metric-cluster-value-type.json +4 -0
- package/fixtures/openapi/KubernetesPrometheusDetail/valid/metric-cluster-alias.json +5 -0
- package/fixtures/openapi/KubernetesResourceGraphEnvelope/invalid/network-edge-source-payload.json +73 -0
- package/fixtures/openapi/KubernetesResourceGraphEnvelope/valid/network-edge-facts.json +67 -0
- package/fixtures/openapi/KubernetesSecretCertificate/invalid/secret-data.json +1 -0
- package/fixtures/openapi/KubernetesSecretCertificate/invalid/status.json +1 -0
- package/fixtures/openapi/KubernetesSecretCertificate/valid/available.json +1 -0
- package/fixtures/openapi/KubernetesSecretCertificate/valid/missing.json +1 -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/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/gen/go/sohaapi/client.go +1 -1
- package/gen/go/sohaapi/client_test.go +13 -0
- package/gen/go/sohaapi/kubernetes_workload_optional_test.go +40 -0
- package/gen/go/sohaapi/pod_status_compat_test.go +36 -0
- package/gen/go/sohaapi/proxy_configuration_test.go +38 -0
- package/gen/go/sohaapi/types.go +3222 -260
- package/gen/ts/sohaapi/index.ts +15506 -10104
- package/helmrelease/runtime/values_upgrade.go +78 -0
- package/helmrelease/runtime/values_upgrade_test.go +123 -0
- package/helmrelease/values_update.go +31 -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 +50412 -41310
- package/openapi/soha-api.yaml +16859 -10699
- package/openapi/spec_generated.go +3 -3
- 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/agent_profiles.go +9 -15
- package/resource/agent_profiles_test.go +9 -3
- package/resource/custom_graph_page.go +18 -0
- package/resource/models.go +102 -57
- package/resource/network_topology.go +22 -0
- package/resource/network_topology_projection.go +36 -22
- package/resource/network_topology_projection_test.go +4 -0
- package/resource/network_topology_test.go +20 -0
- package/resource/network_topology_validation.go +5 -0
- package/resource/observed_workload.go +18 -0
- package/resource/pod_status_test.go +22 -0
- package/resource/rbac_graph.go +4 -0
- package/resource/rollout_optional_test.go +33 -0
- package/resource/runtime/network_relationship_primitives.go +66 -0
- package/resource/runtime/network_relationship_primitives_test.go +147 -0
- package/resource/runtime/network_topology.go +17 -5
- package/resource/runtime/network_topology_builder.go +405 -0
- package/resource/runtime/network_topology_builder_backends.go +195 -0
- package/resource/runtime/network_topology_builder_gateway_l4_test.go +86 -0
- package/resource/runtime/network_topology_builder_ingress.go +302 -0
- package/resource/runtime/network_topology_builder_legacy.go +66 -0
- package/resource/runtime/network_topology_builder_providers.go +242 -0
- package/resource/runtime/network_topology_builder_test.go +186 -0
- package/resource/runtime/network_topology_declarations.go +329 -0
- package/resource/runtime/resource_edit.go +22 -0
- package/resource/runtime/resource_edit_test.go +32 -0
- package/resource/runtime/resource_graph_collect.go +325 -0
- package/resource/runtime/resource_graph_configuration.go +59 -0
- package/resource/runtime/resource_graph_configuration_collect.go +198 -0
- package/resource/runtime/resource_graph_configuration_test.go +470 -0
- package/resource/runtime/resource_graph_custom_page.go +159 -0
- package/resource/runtime/resource_graph_custom_root.go +132 -0
- package/resource/runtime/resource_graph_custom_root_test.go +197 -0
- package/resource/runtime/resource_graph_enrichment.go +417 -0
- package/resource/runtime/resource_graph_enrichment_hpa_test.go +117 -0
- package/resource/runtime/resource_graph_enrichment_test.go +201 -0
- package/resource/runtime/resource_graph_metadata.go +125 -0
- package/resource/runtime/resource_graph_metadata_test.go +204 -0
- package/resource/runtime/resource_graph_network.go +224 -0
- package/resource/runtime/resource_graph_network_collect.go +174 -0
- package/resource/runtime/resource_graph_network_collect_test.go +147 -0
- package/resource/runtime/resource_graph_network_test.go +342 -0
- package/resource/runtime/resource_graph_rbac.go +278 -0
- package/resource/runtime/resource_graph_rbac_test.go +372 -0
- package/resource/runtime/resource_graph_rules.go +834 -0
- package/resource/runtime/resource_graph_storage.go +144 -0
- package/resource/runtime/resource_graph_storage_collect.go +163 -0
- package/resource/runtime/resource_graph_storage_test.go +397 -0
- package/resource/runtime/resource_graph_test.go +627 -0
- package/resource/runtime/secret_certificate.go +44 -0
- package/resource/runtime/secret_certificate_test.go +87 -0
- package/resource/storage_graph.go +4 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"errors"
|
|
5
|
+
"fmt"
|
|
6
|
+
|
|
7
|
+
"github.com/opensoha/soha-contracts/helmrelease"
|
|
8
|
+
"helm.sh/helm/v4/pkg/action"
|
|
9
|
+
"helm.sh/helm/v4/pkg/release"
|
|
10
|
+
helmreleasev1 "helm.sh/helm/v4/pkg/release/v1"
|
|
11
|
+
"helm.sh/helm/v4/pkg/storage/driver"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// GuardValuesUpgradeRevision protects a single Values upgrade on a request-local Helm configuration.
|
|
15
|
+
func GuardValuesUpgradeRevision(cfg *action.Configuration, upgrade *action.Upgrade, current *helmreleasev1.Release, expected int) error {
|
|
16
|
+
if expected == 0 {
|
|
17
|
+
return nil
|
|
18
|
+
}
|
|
19
|
+
if !helmrelease.ValuesExpectedRevision(expected).Valid() || current.Version != expected ||
|
|
20
|
+
(current.Info != nil && current.Info.Status.IsPending()) {
|
|
21
|
+
return helmValuesRevisionConflict()
|
|
22
|
+
}
|
|
23
|
+
// Storage.Create otherwise prunes history before it attempts to reserve the revision.
|
|
24
|
+
upgrade.MaxHistory = 0
|
|
25
|
+
cfg.Releases.Driver = valuesRevisionDriver{Driver: cfg.Releases.Driver, name: current.Name, expected: expected}
|
|
26
|
+
return nil
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type valuesRevisionDriver struct {
|
|
30
|
+
driver.Driver
|
|
31
|
+
name string
|
|
32
|
+
expected int
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func (d valuesRevisionDriver) Query(labels map[string]string) ([]release.Releaser, error) {
|
|
36
|
+
items, err := d.Driver.Query(labels)
|
|
37
|
+
// Helm's preparation reads history again and can encounter a newer pending release.
|
|
38
|
+
if len(labels) != 2 || labels["name"] != d.name || labels["owner"] != "helm" {
|
|
39
|
+
return items, err
|
|
40
|
+
}
|
|
41
|
+
if errors.Is(err, driver.ErrReleaseNotFound) {
|
|
42
|
+
return nil, helmValuesRevisionConflict()
|
|
43
|
+
}
|
|
44
|
+
if err != nil {
|
|
45
|
+
return nil, err
|
|
46
|
+
}
|
|
47
|
+
latest := 0
|
|
48
|
+
for _, item := range items {
|
|
49
|
+
revision, err := release.NewAccessor(item)
|
|
50
|
+
if err != nil {
|
|
51
|
+
return nil, err
|
|
52
|
+
}
|
|
53
|
+
latest = max(latest, revision.Version())
|
|
54
|
+
}
|
|
55
|
+
if latest != d.expected {
|
|
56
|
+
return nil, helmValuesRevisionConflict()
|
|
57
|
+
}
|
|
58
|
+
return items, nil
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func (d valuesRevisionDriver) Create(key string, item release.Releaser) error {
|
|
62
|
+
revision, err := release.NewAccessor(item)
|
|
63
|
+
if err != nil {
|
|
64
|
+
return err
|
|
65
|
+
}
|
|
66
|
+
if revision.Name() != d.name || revision.Version() != d.expected+1 {
|
|
67
|
+
return helmValuesRevisionConflict()
|
|
68
|
+
}
|
|
69
|
+
err = d.Driver.Create(key, item)
|
|
70
|
+
if errors.Is(err, driver.ErrReleaseExists) {
|
|
71
|
+
return helmValuesRevisionConflict()
|
|
72
|
+
}
|
|
73
|
+
return err
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func helmValuesRevisionConflict() error {
|
|
77
|
+
return fmt.Errorf("%w: Helm release revision changed or is pending; refresh its values before saving", ErrConflict)
|
|
78
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"io"
|
|
7
|
+
"testing"
|
|
8
|
+
|
|
9
|
+
"helm.sh/helm/v4/pkg/action"
|
|
10
|
+
"helm.sh/helm/v4/pkg/chart/common"
|
|
11
|
+
chartv2 "helm.sh/helm/v4/pkg/chart/v2"
|
|
12
|
+
"helm.sh/helm/v4/pkg/kube"
|
|
13
|
+
kubefake "helm.sh/helm/v4/pkg/kube/fake"
|
|
14
|
+
"helm.sh/helm/v4/pkg/release"
|
|
15
|
+
releasecommon "helm.sh/helm/v4/pkg/release/common"
|
|
16
|
+
releasev1 "helm.sh/helm/v4/pkg/release/v1"
|
|
17
|
+
"helm.sh/helm/v4/pkg/storage"
|
|
18
|
+
"helm.sh/helm/v4/pkg/storage/driver"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
func TestGuardValuesUpgradeRevision(t *testing.T) {
|
|
22
|
+
for _, tc := range []struct {
|
|
23
|
+
name string
|
|
24
|
+
expected int
|
|
25
|
+
change bool
|
|
26
|
+
collision bool
|
|
27
|
+
wantRevisions int
|
|
28
|
+
wantWrites int
|
|
29
|
+
}{
|
|
30
|
+
{name: "stale before upgrade", expected: 1, wantRevisions: 2},
|
|
31
|
+
{name: "external pending revision after editor read", expected: 2, change: true, wantRevisions: 3},
|
|
32
|
+
{name: "same revision storage collision", expected: 2, collision: true, wantRevisions: 3},
|
|
33
|
+
{name: "current revision", expected: 2, wantRevisions: 3, wantWrites: 1},
|
|
34
|
+
} {
|
|
35
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
36
|
+
chart := &chartv2.Chart{Metadata: &chartv2.Metadata{APIVersion: "v2", Name: "app", Version: "1.0.0"}}
|
|
37
|
+
first := &releasev1.Release{Name: "app", Namespace: "test", Version: 1, Chart: chart, Info: &releasev1.Info{Status: releasecommon.StatusSuperseded}}
|
|
38
|
+
current := &releasev1.Release{Name: "app", Namespace: "test", Version: 2, Chart: chart, Info: &releasev1.Info{Status: releasecommon.StatusDeployed}}
|
|
39
|
+
winner := &releasev1.Release{Name: "app", Namespace: "test", Version: 3, Chart: chart, Info: &releasev1.Info{Status: releasecommon.StatusPendingUpgrade}, Config: map[string]any{"winner": true}}
|
|
40
|
+
history := storage.Init(driver.NewMemory())
|
|
41
|
+
for _, item := range []*releasev1.Release{first, current} {
|
|
42
|
+
if err := history.Create(item); err != nil {
|
|
43
|
+
t.Fatal(err)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
kubeClient := &valuesUpgradeKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, history: history}
|
|
47
|
+
racingDriver := valuesUpgradeRaceDriver{Driver: history.Driver}
|
|
48
|
+
if tc.collision {
|
|
49
|
+
racingDriver.winner = winner
|
|
50
|
+
}
|
|
51
|
+
cfg := &action.Configuration{Releases: storage.Init(racingDriver), KubeClient: kubeClient, Capabilities: common.DefaultCapabilities}
|
|
52
|
+
upgrade := action.NewUpgrade(cfg)
|
|
53
|
+
upgrade.Namespace = current.Namespace
|
|
54
|
+
upgrade.MaxHistory = 1
|
|
55
|
+
err := GuardValuesUpgradeRevision(cfg, upgrade, current, tc.expected)
|
|
56
|
+
if tc.change {
|
|
57
|
+
if err := history.Create(winner); err != nil {
|
|
58
|
+
t.Fatal(err)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if err == nil {
|
|
62
|
+
_, err = upgrade.RunWithContext(context.Background(), current.Name, chart, map[string]any{"edited": true})
|
|
63
|
+
}
|
|
64
|
+
if tc.wantWrites == 0 && !errors.Is(err, ErrConflict) || tc.wantWrites > 0 && err != nil {
|
|
65
|
+
t.Fatalf("upgrade error = %v", err)
|
|
66
|
+
}
|
|
67
|
+
if kubeClient.writes != tc.wantWrites {
|
|
68
|
+
t.Fatalf("Kubernetes updates = %d, want %d", kubeClient.writes, tc.wantWrites)
|
|
69
|
+
}
|
|
70
|
+
if kubeClient.writes > 0 && !kubeClient.reservedBeforeUpdate {
|
|
71
|
+
t.Fatal("Kubernetes updated before revision 3 was reserved")
|
|
72
|
+
}
|
|
73
|
+
items, err := history.History(current.Name)
|
|
74
|
+
if err != nil || len(items) != tc.wantRevisions {
|
|
75
|
+
t.Fatalf("history contains %d revisions, want %d, err=%v", len(items), tc.wantRevisions, err)
|
|
76
|
+
}
|
|
77
|
+
if tc.change || tc.collision {
|
|
78
|
+
latest, err := history.Last(current.Name)
|
|
79
|
+
if err != nil {
|
|
80
|
+
t.Fatal(err)
|
|
81
|
+
}
|
|
82
|
+
latestRelease, ok := latest.(*releasev1.Release)
|
|
83
|
+
if !ok {
|
|
84
|
+
t.Fatalf("latest revision has unexpected type %T", latest)
|
|
85
|
+
}
|
|
86
|
+
if latestRelease.Config["winner"] != true {
|
|
87
|
+
t.Fatal("concurrent writer's Values were overwritten")
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type valuesUpgradeRaceDriver struct {
|
|
95
|
+
driver.Driver
|
|
96
|
+
winner *releasev1.Release
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func (d valuesUpgradeRaceDriver) Create(key string, item release.Releaser) error {
|
|
100
|
+
if d.winner != nil {
|
|
101
|
+
if err := d.Driver.Create(key, d.winner); err != nil {
|
|
102
|
+
return err
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return d.Driver.Create(key, item)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type valuesUpgradeKubeClient struct {
|
|
109
|
+
kubefake.PrintingKubeClient
|
|
110
|
+
history *storage.Storage
|
|
111
|
+
writes int
|
|
112
|
+
reservedBeforeUpdate bool
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func (c *valuesUpgradeKubeClient) Update(original, target kube.ResourceList, opts ...kube.ClientUpdateOption) (*kube.Result, error) {
|
|
116
|
+
c.writes++
|
|
117
|
+
latest, err := c.history.Last("app")
|
|
118
|
+
if err == nil {
|
|
119
|
+
latestRelease, ok := latest.(*releasev1.Release)
|
|
120
|
+
c.reservedBeforeUpdate = ok && latestRelease.Version == 3 && latestRelease.Info.Status == releasecommon.StatusPendingUpgrade
|
|
121
|
+
}
|
|
122
|
+
return c.PrintingKubeClient.Update(original, target, opts...)
|
|
123
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package helmrelease
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"errors"
|
|
6
|
+
"math"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
type ValuesUpdateRequest struct {
|
|
10
|
+
Content string `json:"content"`
|
|
11
|
+
ExpectedRevision ValuesExpectedRevision `json:"expectedRevision,omitempty"`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ValuesExpectedRevision uses zero internally for legacy requests that omit the field.
|
|
15
|
+
type ValuesExpectedRevision int
|
|
16
|
+
|
|
17
|
+
func (r ValuesExpectedRevision) Valid() bool {
|
|
18
|
+
return r > 0 && r < math.MaxInt32
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func (r *ValuesExpectedRevision) UnmarshalJSON(data []byte) error {
|
|
22
|
+
var value int
|
|
23
|
+
if err := json.Unmarshal(data, &value); err != nil {
|
|
24
|
+
return err
|
|
25
|
+
}
|
|
26
|
+
if !ValuesExpectedRevision(value).Valid() {
|
|
27
|
+
return errors.New("expectedRevision must be between 1 and 2147483646")
|
|
28
|
+
}
|
|
29
|
+
*r = ValuesExpectedRevision(value)
|
|
30
|
+
return nil
|
|
31
|
+
}
|
package/network/README.md
CHANGED
|
@@ -57,6 +57,11 @@ for authorization, routing, firewall configuration or accounting.
|
|
|
57
57
|
reply attribute and require NAS accounting to echo it as `sessionId` when
|
|
58
58
|
available. Session commands repeat the bound `subjectId` and `deviceId` so
|
|
59
59
|
the adapter can target CoA/Disconnect without querying management APIs.
|
|
60
|
+
- Capability `nas-session-accounting-v1` enables the optional command target
|
|
61
|
+
`stationId`, `accountingSessionId` and `nasPortId`. For EAP-TLS, station identity
|
|
62
|
+
comes from saved authorization/accounting, never the opaque Soha device ID.
|
|
63
|
+
A runtime that cannot receive the required target binding must report
|
|
64
|
+
unsupported rather than send a command to a guessed station.
|
|
60
65
|
- If a requested CoA is unsupported, issue a disconnect command when the NAS
|
|
61
66
|
binding supports it; otherwise fail closed and record the unsupported
|
|
62
67
|
enforcement result.
|
|
@@ -166,3 +171,32 @@ for authorization, routing, firewall configuration or accounting.
|
|
|
166
171
|
Consumers must cover these rules with deterministic tests. Failure is
|
|
167
172
|
fail-closed for control messages and bounded reject/drop-with-counter behavior
|
|
168
173
|
for telemetry.
|
|
174
|
+
|
|
175
|
+
## Endpoint presence and RADIUS response
|
|
176
|
+
|
|
177
|
+
- `endpoint.presence.request` goes to
|
|
178
|
+
`POST /api/network-control/v1/runtimes/{runtimeId}/endpoint:presence` using the
|
|
179
|
+
enrolled endpoint's control identity and `endpoint-presence-v1` capability.
|
|
180
|
+
`bootId` identifies the protected service incarnation; a new incarnation
|
|
181
|
+
starts a new sequence and retires the previous one. A duplicate must not
|
|
182
|
+
refresh server-side liveness. UI presence never implies a healthy service.
|
|
183
|
+
- `POST /api/network-control/v1/runtimes/{runtimeId}/nas:accounting` uses
|
|
184
|
+
the enrolled NAS control identity and the existing RADIUS Accounting schema.
|
|
185
|
+
The control plane checks NAS, subject, station and accounting-session binding
|
|
186
|
+
against the saved authorization before updating session evidence. The
|
|
187
|
+
separate ingest submission remains telemetry and cannot change admission.
|
|
188
|
+
- A Stop remains terminal for that Core session. A reused accounting ID,
|
|
189
|
+
conflicting binding, restart after Start, or missing Start remains unknown.
|
|
190
|
+
Received time alone cannot refresh an old observation. Accounting evidence
|
|
191
|
+
cannot authorize access or prove that a VLAN/ACL was applied.
|
|
192
|
+
- Persist response restrictions before delivering NAS commands. A new
|
|
193
|
+
authentication must observe every active restriction. Policy retirement
|
|
194
|
+
never removes an existing restriction, and restoration requires current
|
|
195
|
+
health plus authorization rather than replaying a previous allow decision.
|
|
196
|
+
|
|
197
|
+
## Dedicated EAP certificates and native profiles
|
|
198
|
+
|
|
199
|
+
- `eap.certificate.request` uses `POST /api/network-control/v1/runtimes/{runtimeId}/eap:certificate`; `eap.certificate.activate.request` uses the same prefix with `eap:activate`. Both require the endpoint control identity and `eap-profile-v1` plus `radius-eap-tls-v1`. CSR private keys stay on the endpoint. Core binds the subject and device from enrollment and ignores CSR identity extensions.
|
|
200
|
+
- A dedicated EAP issuer signs only client authentication certificates with a `network-eap` URI. EAP certificates cannot authenticate control or ingest requests. Without an issuer, issuance is unavailable. Existing runtime certificates are accepted for legacy EAP only when enrollment explicitly declares `radius-eap-tls-v1`; ordinary control credentials do not imply EAP permission.
|
|
201
|
+
- Issuance is idempotent per runtime/request ID and CSR. Certificates start pending and cannot grant network access. After native installation and readback, activation atomically replaces the old EAP certificate under the parent runtime credential lock. A retired or expired parent invalidates every child. Replaying an older activation cannot revive it.
|
|
202
|
+
- `admission.profiles` is delivered only to `eap-profile-v1` endpoints, with exact EAP server names, CA certificates, subject and the dedicated active EAP certificate fingerprint. Configuration export, installation, 802.1X connection and actual NAS enforcement are separate results.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://contracts.opensoha.dev/network/network-radius-accounting.schema.json",
|
|
4
4
|
"title": "OpenSoha FreeRADIUS Accounting Adapter Request",
|
|
5
|
-
"description": "A strict credential-free request converted by soha-ingest into
|
|
5
|
+
"description": "A strict credential-free request converted by soha-ingest into telemetry. When submitted to network-control with an enrolled NAS identity, its NAS and session binding are independently verified before updating admission evidence.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": [
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"accountingSessionId": { "$ref": "#/$defs/Identifier" },
|
|
21
21
|
"sessionId": { "$ref": "#/$defs/Identifier" },
|
|
22
22
|
"nasId": { "$ref": "#/$defs/Identifier" },
|
|
23
|
+
"accountingDelaySeconds": { "type": "integer", "minimum": 0, "maximum": 86400 },
|
|
24
|
+
"observedVlanId": { "type": "integer", "minimum": 1, "maximum": 4094 },
|
|
25
|
+
"observedFilterId": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
23
26
|
"nasPortId": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
24
27
|
"userId": { "$ref": "#/$defs/Identifier" },
|
|
25
28
|
"deviceId": { "$ref": "#/$defs/Identifier" },
|