Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions docs/scope-d-delivery-envelopes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# SCOPE-D Delivery Envelopes

CloudShell Fog participates in the SCOPE-D platform as an authorized edge operator bastion and mesh assurance node.

This document defines the first delivery boundary between SCOPE-D and CloudShell Fog.

## Role

CloudShell Fog is not a scanner and not an autonomous execution agent in this flow. It accepts reviewable, policy-gated SCOPE-D delivery envelopes for edge assurance workflows.

The initial envelope validator accepts only non-executing review packages.

## Accepted source

`sourceSystem` must be:

```text
scope-d
```

## Accepted purposes

- `edge_assurance_review`
- `policy_gated_delivery_review`

## Required fields

A delivery envelope must include:

- schema version;
- envelope id;
- source system;
- purpose;
- artifact references;
- required policy references;
- operator approval requirement;
- non-execution flags;
- receipt hash.

## Prohibited capabilities in v0.1

The validator rejects any envelope that requests:

- execution;
- prior execution;
- network access;
- mutation;
- credential access;
- payload delivery.

## Expected SCOPE-D artifact references

SCOPE-D should reference artifacts such as:

- intelligence enrichment export;
- detection candidate export;
- cyber graph export;
- operator case bundle;
- client assurance report;
- PolicyFabric approval record.

## Validation path

The Go package is:

```text
internal/delivery
```

The validator is:

```go
Validate(envelope DeliveryEnvelope) error
```

The receipt helper is:

```go
ComputeReceiptHash(envelope DeliveryEnvelope) string
```

## Test path

```bash
go test ./internal/delivery
```

## Next slices

1. Add API endpoint for staging delivery envelopes.
2. Persist staged envelopes in the existing session/audit subsystem.
3. Add PolicyFabric signature verification.
4. Add SCOPE-D receipt verification.
5. Add edge sync status for Noetica.
6. Add read-only operator review endpoint.
7. Add deployment receipt generation after approved future delivery modes.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
Expand All @@ -45,6 +46,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.23.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
Expand Down Expand Up @@ -77,6 +79,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE=
Expand Down
97 changes: 97 additions & 0 deletions internal/delivery/envelope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Package delivery validates policy-gated SCOPE-D delivery envelopes before
// they are accepted by CloudShell Fog as edge assurance work.
package delivery

import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"strings"
)

// DeliveryEnvelope describes a non-executing SCOPE-D work package that may be
// staged for operator review at a CloudShell Fog edge node.
type DeliveryEnvelope struct {
SchemaVersion string `json:"schemaVersion"`
EnvelopeID string `json:"envelopeId"`
SourceSystem string `json:"sourceSystem"`
Purpose string `json:"purpose"`
ArtifactRefs []string `json:"artifactRefs"`
RequiredPolicyRefs []string `json:"requiredPolicyRefs"`
OperatorApprovalRequired bool `json:"operatorApprovalRequired"`
ExecutionAllowed bool `json:"executionAllowed"`
ExecutionPerformed bool `json:"executionPerformed"`
NetworkAccessAllowed bool `json:"networkAccessAllowed"`
MutationAllowed bool `json:"mutationAllowed"`
CredentialAccessAllowed bool `json:"credentialAccessAllowed"`
PayloadDeliveryAllowed bool `json:"payloadDeliveryAllowed"`
ReceiptHash string `json:"receiptHash"`
}

// Sentinel validation errors.
var (
ErrUnsupportedSchema = errors.New("unsupported delivery envelope schema")
ErrInvalidEnvelopeID = errors.New("invalid delivery envelope id")
ErrInvalidSourceSystem = errors.New("invalid source system")
ErrInvalidPurpose = errors.New("invalid delivery purpose")
ErrMissingArtifacts = errors.New("missing artifact references")
ErrMissingPolicies = errors.New("missing required policy references")
ErrOperatorApprovalMissing = errors.New("operator approval requirement missing")
ErrUnsafeCapability = errors.New("unsafe delivery capability requested")
ErrInvalidReceiptHash = errors.New("invalid receipt hash")
)

// Validate enforces CloudShell Fog's default posture for SCOPE-D delivery:
// reviewable, receipt-backed, policy-gated, and non-executing.
func Validate(envelope DeliveryEnvelope) error {
if envelope.SchemaVersion != "0.1.0" {
return ErrUnsupportedSchema
}
if !strings.HasPrefix(envelope.EnvelopeID, "cloudshell-fog-delivery:") {
return ErrInvalidEnvelopeID
}
if envelope.SourceSystem != "scope-d" {
return ErrInvalidSourceSystem
}
if envelope.Purpose != "edge_assurance_review" && envelope.Purpose != "policy_gated_delivery_review" {
return ErrInvalidPurpose
}
if len(envelope.ArtifactRefs) == 0 {
return ErrMissingArtifacts
}
if len(envelope.RequiredPolicyRefs) == 0 {
return ErrMissingPolicies
}
if !envelope.OperatorApprovalRequired {
return ErrOperatorApprovalMissing
}
if envelope.ExecutionAllowed || envelope.ExecutionPerformed || envelope.NetworkAccessAllowed || envelope.MutationAllowed || envelope.CredentialAccessAllowed || envelope.PayloadDeliveryAllowed {
return ErrUnsafeCapability
}
if !strings.HasPrefix(envelope.ReceiptHash, "sha256:") || len(strings.TrimPrefix(envelope.ReceiptHash, "sha256:")) != 64 {
return ErrInvalidReceiptHash
}
return nil
}

// ComputeReceiptHash returns a deterministic hash over the policy-significant
// fields. It intentionally excludes ReceiptHash itself.
func ComputeReceiptHash(envelope DeliveryEnvelope) string {
material := strings.Join([]string{
envelope.SchemaVersion,
envelope.EnvelopeID,
envelope.SourceSystem,
envelope.Purpose,
strings.Join(envelope.ArtifactRefs, ","),
strings.Join(envelope.RequiredPolicyRefs, ","),
fmt.Sprintf("approval=%t", envelope.OperatorApprovalRequired),
fmt.Sprintf("executionAllowed=%t", envelope.ExecutionAllowed),
fmt.Sprintf("networkAccessAllowed=%t", envelope.NetworkAccessAllowed),
fmt.Sprintf("mutationAllowed=%t", envelope.MutationAllowed),
fmt.Sprintf("credentialAccessAllowed=%t", envelope.CredentialAccessAllowed),
fmt.Sprintf("payloadDeliveryAllowed=%t", envelope.PayloadDeliveryAllowed),
}, "|")
sum := sha256.Sum256([]byte(material))
return "sha256:" + hex.EncodeToString(sum[:])
}
86 changes: 86 additions & 0 deletions internal/delivery/envelope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package delivery

import (
"errors"
"testing"
)

func validEnvelope() DeliveryEnvelope {
envelope := DeliveryEnvelope{
SchemaVersion: "0.1.0",
EnvelopeID: "cloudshell-fog-delivery:scope-d-demo-001",
SourceSystem: "scope-d",
Purpose: "edge_assurance_review",
ArtifactRefs: []string{"scope-d://cyber-graph-export/demo", "scope-d://detection-candidate-export/demo"},
RequiredPolicyRefs: []string{"policyfabric://approval/operator-review-required"},
OperatorApprovalRequired: true,
ExecutionAllowed: false,
ExecutionPerformed: false,
NetworkAccessAllowed: false,
MutationAllowed: false,
CredentialAccessAllowed: false,
PayloadDeliveryAllowed: false,
}
envelope.ReceiptHash = ComputeReceiptHash(envelope)
return envelope
}

func TestValidateAcceptsSafeScopeDEnvelope(t *testing.T) {
if err := Validate(validEnvelope()); err != nil {
t.Fatalf("expected valid envelope, got %v", err)
}
}

func TestValidateRejectsExecutionAllowed(t *testing.T) {
envelope := validEnvelope()
envelope.ExecutionAllowed = true
envelope.ReceiptHash = ComputeReceiptHash(envelope)
if err := Validate(envelope); !errors.Is(err, ErrUnsafeCapability) {
t.Fatalf("expected unsafe capability error, got %v", err)
}
}

func TestValidateRejectsNetworkMutationCredentialAndPayloadCapabilities(t *testing.T) {
cases := map[string]func(*DeliveryEnvelope){
"network": func(e *DeliveryEnvelope) { e.NetworkAccessAllowed = true },
"mutation": func(e *DeliveryEnvelope) { e.MutationAllowed = true },
"credential": func(e *DeliveryEnvelope) { e.CredentialAccessAllowed = true },
"payload": func(e *DeliveryEnvelope) { e.PayloadDeliveryAllowed = true },
}
for name, mutate := range cases {
t.Run(name, func(t *testing.T) {
envelope := validEnvelope()
mutate(&envelope)
envelope.ReceiptHash = ComputeReceiptHash(envelope)
if err := Validate(envelope); !errors.Is(err, ErrUnsafeCapability) {
t.Fatalf("expected unsafe capability error, got %v", err)
}
})
}
}

func TestValidateRequiresOperatorApproval(t *testing.T) {
envelope := validEnvelope()
envelope.OperatorApprovalRequired = false
envelope.ReceiptHash = ComputeReceiptHash(envelope)
if err := Validate(envelope); !errors.Is(err, ErrOperatorApprovalMissing) {
t.Fatalf("expected operator approval error, got %v", err)
}
}

func TestValidateRequiresPolicyRefs(t *testing.T) {
envelope := validEnvelope()
envelope.RequiredPolicyRefs = nil
envelope.ReceiptHash = ComputeReceiptHash(envelope)
if err := Validate(envelope); !errors.Is(err, ErrMissingPolicies) {
t.Fatalf("expected missing policies error, got %v", err)
}
}

func TestValidateRequiresReceiptHashShape(t *testing.T) {
envelope := validEnvelope()
envelope.ReceiptHash = "sha256:not-a-real-hash"
if err := Validate(envelope); !errors.Is(err, ErrInvalidReceiptHash) {
t.Fatalf("expected invalid receipt hash error, got %v", err)
}
}
Loading