diff --git a/.github/workflows/checkproto.yaml b/.github/workflows/checkproto.yaml index e980d562..f94c0c8e 100644 --- a/.github/workflows/checkproto.yaml +++ b/.github/workflows/checkproto.yaml @@ -30,7 +30,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.25.x + go-version: 1.26.x - name: Set up protoc run: | diff --git a/.github/workflows/hal.yml b/.github/workflows/hal.yml index ba5212f1..544f733c 100644 --- a/.github/workflows/hal.yml +++ b/.github/workflows/hal.yml @@ -42,7 +42,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.25.x + go-version: 1.26.x cache-dependency-path: "go.sum" - name: Checkout cocos diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 887123cb..0bb5c5d1 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -18,12 +18,12 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.25.x + go-version: 1.26.x - name: golangci-lint uses: golangci/golangci-lint-action@v8 with: - version: v2.4.0 + version: v2.11.1 - name: Build run: make @@ -45,7 +45,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.25.x + go-version: 1.26.x - name: Create coverage directory run: mkdir -p coverage @@ -53,9 +53,9 @@ jobs: - name: Run tests for ${{ matrix.module }} run: | if [[ "${{ matrix.module }}" == "manager" ]]; then - sudo GOTOOLCHAIN=go1.25.0+auto go test -v --race -covermode=atomic -coverprofile coverage/${{ matrix.module }}.out ./${{ matrix.module }}/... + sudo GOTOOLCHAIN=go1.26.0+auto go test -v --race -covermode=atomic -coverprofile coverage/${{ matrix.module }}.out ./${{ matrix.module }}/... else - GOTOOLCHAIN=go1.25.0+auto go test -v --race -covermode=atomic -coverprofile coverage/${{ matrix.module }}.out ./${{ matrix.module }}/... + GOTOOLCHAIN=go1.26.0+auto go test -v --race -covermode=atomic -coverprofile coverage/${{ matrix.module }}.out ./${{ matrix.module }}/... fi - name: Upload coverage artifact diff --git a/.gitignore b/.gitignore index 72f7e767..770dc2fb 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,7 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +*.enc +*.key +*.pub \ No newline at end of file diff --git a/.golangci.yaml b/.golangci.yaml index 26e32eaf..320f17ee 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -70,10 +70,14 @@ linters: - legacy - std-error-handling rules: + - linters: + - errcheck + path: build/ - linters: - makezero text: with non-zero initialized length paths: + - build - third_party$ - builtin$ - examples$ diff --git a/Makefile b/Makefile index a4e6bb06..5754d567 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ define compile_service -X 'github.com/absmach/supermq.Version=$(VERSION)' \ -X 'github.com/absmach/supermq.Commit=$(COMMIT)'" \ $(if $(filter 1,$(EMBED_ENABLED)),-tags "embed",) \ - -o ${BUILD_DIR}/cocos-$(1) cmd/$(1)/main.go + -o ${BUILD_DIR}/cocos-$(1) ./cmd/$(1) endef .PHONY: all $(SERVICES) $(ATTESTATION_POLICY) install clean diff --git a/agent/README.md b/agent/README.md index d921200b..be9b63f0 100644 --- a/agent/README.md +++ b/agent/README.md @@ -24,6 +24,21 @@ The service is configured using the environment variables from the following tab | AGENT_OS_BUILD | Operating system build information for attestation | UVC | | AGENT_OS_DISTRO | Operating system distribution information for attestation | UVC | | AGENT_OS_TYPE | Operating system type information for attestation | UVC | +| ATTESTATION_SERVICE_SOCKET | Unix socket path for attestation service communication | /run/cocos/attestation.sock | +| AGENT_ENABLE_ATLS | Enable Attestation TLS for secure communication | true | + +### Remote Resource Download (Optional) + +The agent supports downloading encrypted algorithms and datasets from remote registries (S3, HTTP/HTTPS) and retrieving decryption keys from a Key Broker Service (KBS) via attestation. + +| Variable | Description | Default | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| AWS_REGION | AWS region for S3 access (required for S3 downloads) | \"\" | +| AWS_ACCESS_KEY_ID | AWS access key ID for S3 authentication | \"\" | +| AWS_SECRET_ACCESS_KEY | AWS secret access key for S3 authentication | \"\" | +| AWS_ENDPOINT_URL | Custom S3 endpoint URL (for S3-compatible services like MinIO) | \"\" | + +**Note**: KBS URL is specified in the computation manifest, not as an environment variable. See [TESTING_REMOTE_RESOURCES.md](./TESTING_REMOTE_RESOURCES.md) for details on using remote resources. ## Deployment diff --git a/agent/TESTING_REMOTE_RESOURCES.md b/agent/TESTING_REMOTE_RESOURCES.md new file mode 100644 index 00000000..9d5b2f9b --- /dev/null +++ b/agent/TESTING_REMOTE_RESOURCES.md @@ -0,0 +1,417 @@ +# Testing Remote Resources with CoCo Key Provider + +This guide explains how to test Cocos with encrypted remote resources using the Confidential Containers Key Provider ecosystem. + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ CVM (Agent) │ +│ │ +│ ┌──────────┐ ┌────────────────┐ ┌─────────────────┐ │ +│ │ Agent │───▶│ Skopeo │───▶│ CoCo Keyprovider│ │ +│ └──────────┘ │ (ocicrypt) │ │ (gRPC:50011) │ │ +│ └────────────────┘ └────────┬────────┘ │ +│ │ │ +│ ┌────────▼────────┐ │ +│ │ Attestation │ │ +│ │ Agent (50002) │ │ +│ └────────┬────────┘ │ +└──────────────────────────────────────────────────┼──────────┘ + │ + ┌────────▼────────┐ + │ KBS Server │ + │ (Host:8080) │ + └─────────────────┘ +``` + +## Prerequisites + +### 1. Install Skopeo (Host Machine) + +```bash +# Ubuntu/Debian +sudo apt-get install skopeo + +# macOS +brew install skopeo + +# Or build from source +git clone https://github.com/containers/skopeo +cd skopeo +make bin/skopeo +sudo make install +``` + +### 2. Start KBS Server (Host Machine) + +```bash +# Clone and build KBS +git clone https://github.com/confidential-containers/trustee +cd trustee/kbs +# Patch Cargo.toml to disable SGX requirement (for testing only) +sed -i 's/"all-verifier",//g' Cargo.toml + +make +make cli + +# Generate admin keys +openssl genpkey -algorithm ed25519 -out kbs-admin.key +openssl pkey -in kbs-admin.key -pubout -out kbs-admin.pub + +# Create KBS configuration file +cat > kbs-config.toml << 'EOF' +[http_server] +sockets = ["0.0.0.0:8080"] +insecure_http = true + +[admin] +type = "Simple" +[[admin.personas]] +id = "admin" +public_key_path = "kbs-admin.pub" + +[attestation_service] +type = "coco_as_builtin" +work_dir = "kbs-data/as" + +[attestation_service.rvps_config] +type = "BuiltIn" + +[attestation_service.rvps_config.storage] +type = "LocalFs" +file_path = "kbs-data/rvps-values" + +[[plugins]] +name = "resource" +type = "LocalFs" +dir_path = "kbs-data/repository" +EOF + +# Create configuration directories +mkdir -p kbs-data/as kbs-data/rvps kbs-data/repository + +# Start KBS +../target/release/kbs --config-file kbs-config.toml +``` + +KBS will listen on `http://localhost:8080` + +### 3. Setup Local OCI Registry (Optional) + +For testing, you can use a local registry: + +```bash +docker run -d -p 5000:5000 --name registry registry:2 +``` + +## Creating Encrypted Resources + +### Encrypt an Algorithm (Python Script) + +```bash +# 1. Create a simple algorithm +cat > lin_reg.py << 'EOF' +import pandas as pd +from sklearn.linear_model import LinearRegression +import sys + +# Load dataset +data = pd.read_csv(sys.argv[1]) +X = data[['feature1', 'feature2']] +y = data['target'] + +# Train model +model = LinearRegression() +model.fit(X, y) + +# Save results +print(f"Coefficients: {model.coef_}") +print(f"Intercept: {model.intercept_}") +EOF + +# 2. Create a Dockerfile +cat > Dockerfile << 'EOF' +FROM python:3.9-slim +RUN pip install pandas scikit-learn +COPY lin_reg.py /app/algorithm.py +WORKDIR /app +ENTRYPOINT ["python", "algorithm.py"] +EOF + +# 3. Build the image +docker build -t localhost:5000/lin-reg-algo:v1.0 . +docker push localhost:5000/lin-reg-algo:v1.0 + +# 4. Generate and store key +openssl rand -out algo.key 32 + +# 5. Store key in KBS using kbs-client +../target/release/kbs-client --url http://localhost:8080 config \ + --auth-private-key kbs-admin.key \ + set-resource \ + --path default/key/algo-key \ + --resource-file algo.key + +# 6. Encrypt the image using Host Skopeo + Docker Keyprovider +# Start Keyprovider in background +docker run -d --rm --name keyprovider --network host \ + -v "$PWD:/work" -w /work \ + ghcr.io/confidential-containers/staged-images/coco-keyprovider:latest \ + coco_keyprovider --socket 127.0.0.1:50000 + +# Configure Ocicrypt to use local Keyprovider +cat < ocicrypt.conf +{ + "key-providers": { + "attestation-agent": { + "grpc": "127.0.0.1:50000" + } + } +} +EOF +export OCICRYPT_KEYPROVIDER_CONFIG=$(pwd)/ocicrypt.conf + +# Encrypt Algo +skopeo copy \ + --src-tls-verify=false \ + --dest-tls-verify=false \ + --encryption-key "provider:attestation-agent:keypath=/work/algo.key::keyid=kbs:///default/key/algo-key::algorithm=A256GCM" \ + docker://localhost:5000/lin-reg-algo:v1.0 \ + docker://localhost:5000/encrypted-lin-reg:v1.0 + +# Stop Keyprovider +docker stop keyprovider +``` + +### Encrypt a Dataset (CSV in OCI Image) + +```bash +# 1. Create dataset +cat > iris.csv << 'EOF' +feature1,feature2,target +5.1,3.5,0 +4.9,3.0,0 +6.2,3.4,1 +5.9,3.0,1 +EOF + +# 2. Create Dockerfile for dataset +cat > Dockerfile.dataset << 'EOF' +FROM scratch +COPY iris.csv /data/iris.csv +EOF + +# 3. Build and push +docker build -f Dockerfile.dataset -t localhost:5000/iris-dataset:v1.0 . +docker push localhost:5000/iris-dataset:v1.0 + +# 4. Generate and store key +# 4. Generate and store key +openssl rand -out dataset.key 32 +../target/release/kbs-client --url http://localhost:8080 config \ + --auth-private-key kbs-admin.key \ + set-resource \ + --path default/key/dataset-key \ + --resource-file dataset.key + +# 5. Encrypt dataset image using Host Skopeo + Docker Keyprovider +# Start Keyprovider in background +docker run -d --rm --name keyprovider --network host \ + -v "$PWD:/work" -w /work \ + ghcr.io/confidential-containers/staged-images/coco-keyprovider:latest \ + coco_keyprovider --socket 127.0.0.1:50000 + +# Configure Ocicrypt (if not already done) +export OCICRYPT_KEYPROVIDER_CONFIG=$(pwd)/ocicrypt.conf + +# Encrypt Dataset +skopeo copy \ + --src-tls-verify=false \ + --dest-tls-verify=false \ + --encryption-key "provider:attestation-agent:keypath=/work/dataset.key::keyid=kbs:///default/key/dataset-key::algorithm=A256GCM" \ + docker://localhost:5000/iris-dataset:v1.0 \ + docker://localhost:5000/encrypted-iris:v1.0 + +# Stop Keyprovider +docker stop keyprovider +``` + +## Running a Computation + +### 1. Start Manager (Host) + +```bash +cd /path/to/cocos-ai +./build/cocos-manager +``` + +### 2. Start CVMS Test Server (Host) + +Get your host IP: +```bash +HOST_IP=$(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127.0.0.1 | head -n1) +``` + +Start CVMS server: +```bash +# Calculate SHA3-256 of decrypted files using cocos-cli +# NOTE: We use the hash of the original plaintext files, as the Agent validates the decrypted content. +# Redirect stderr to stdout (2>&1) because cocos-cli prints to stderr +ALGO_HASH=$(./build/cocos-cli checksum lin_reg.py 2>&1 | awk '{print $NF}') +DATASET_HASH=$(./build/cocos-cli checksum iris.csv 2>&1 | awk '{print $NF}') + +go build -o build/cvms-test ./test/cvms/main.go +HOST=$HOST_IP PORT=7001 ./build/cvms-test \ + -public-key-path ./public.pem \ + -attested-tls-bool false \ + -kbs-url http://$HOST_IP:8080 \ + -algo-type oci-image \ + -algo-source-url docker://$HOST_IP:5000/encrypted-lin-reg:v1.0 \ + -algo-kbs-path default/key/algo-key \ + -algo-hash $ALGO_HASH \ + -dataset-type oci-image \ + -dataset-source-urls docker://$HOST_IP:5000/encrypted-iris:v1.0 \ + -dataset-kbs-paths default/key/dataset-key \ + -dataset-hash $DATASET_HASH +``` + +### 3. Create VM via CLI (Host) + +```bash +export MANAGER_GRPC_URL=localhost:7002 +./build/cocos-cli create-vm \ + --server-url $HOST_IP:7001 \ + --log-level debug +``` + +The agent will: +1. Receive computation manifest from CVMS +2. Use Skopeo to download encrypted OCI images +3. Skopeo invokes CoCo Keyprovider via ocicrypt +4. CoCo Keyprovider requests decryption key from KBS +5. Attestation Agent generates TEE evidence for KBS +6. KBS validates evidence and returns decryption key +7. Image layers are decrypted and extracted +8. Computation executes with decrypted algorithm and dataset + +## Verifying the Setup + +### Check CoCo Keyprovider Status (Inside CVM) + +```bash +# SSH into CVM or use console +systemctl status coco-keyprovider +journalctl -u coco-keyprovider -f +``` + +### Check Attestation Agent Status + +```bash +systemctl status attestation-agent +journalctl -u attestation-agent -f +``` + +### Test Skopeo Decryption Manually + +```bash +# Inside CVM +export OCICRYPT_KEYPROVIDER_CONFIG=/etc/ocicrypt_keyprovider.conf + +skopeo copy \ + --src-tls-verify=false \ + --dest-tls-verify=false \ + --decryption-key provider:attestation-agent:cc_kbc::null \ + docker://localhost:5000/encrypted-lin-reg:v1.0 \ + oci:/tmp/decrypted-algo + +# Verify decryption +skopeo inspect oci:/tmp/decrypted-algo | jq -r '.LayersData[].MIMEType' +# Should show: application/vnd.oci.image.layer.v1.tar+gzip +``` + +## Computation Manifest Format + +The CVMS server sends this manifest to the agent: + +```json +{ + "computation_id": "1", + "algorithm": { + "type": "oci-image", + "uri": "docker://localhost:5000/encrypted-lin-reg:v1.0", + "encrypted": true, + "kbs_resource_path": "default/key/algo-key" + }, + "datasets": [ + { + "type": "oci-image", + "uri": "docker://localhost:5000/encrypted-iris:v1.0", + "encrypted": true, + "kbs_resource_path": "default/key/dataset-key" + } + ], + "kbs_url": "http://192.168.100.15:8080" +} +``` + +## Troubleshooting + +### CoCo Keyprovider Not Starting + +```bash +# Check logs +journalctl -u coco-keyprovider -n 50 + +# Verify socket is listening +ss -tlnp | grep 50011 + +# Check environment +cat /etc/default/coco-keyprovider +``` + +### Skopeo Decryption Fails + +```bash +# Verify ocicrypt config +cat /etc/ocicrypt_keyprovider.conf + +# Test keyprovider connection +grpcurl -plaintext 127.0.0.1:50011 list + +# Check KBS connectivity from CVM +curl http://HOST_IP:8080/kbs/v0/auth +``` + +### KBS Returns 401 + +```bash +# Check KBS logs on host +# Verify attestation evidence format +# Ensure KBS is configured for sample attestation +``` + +## Differences from Previous Approach + +| Aspect | Old (Custom) | New (CoCo Standard) | +|--------|-------------|---------------------| +| **Download** | Custom S3/HTTP clients | Skopeo (OCI standard) | +| **Decryption** | Custom KBS client | CoCo Keyprovider | +| **Attestation** | Direct KBS RCAR | AA → CoCo KP → KBS | +| **Format** | Raw encrypted files | OCI encrypted images | +| **Complexity** | ~2000 lines custom code | Standard CoCo components | + +## Benefits + +1. **Standards Compliance**: Uses OCI and CoCo standards +2. **Better Tooling**: Leverage Skopeo, Docker, Podman ecosystem +3. **Simplified Code**: Remove custom registry/decryption logic +4. **Proven Solution**: Battle-tested CoCo components +5. **Docker Native**: Works with existing Docker workflows + +## Next Steps + +- Encrypt your algorithms and datasets as OCI images +- Push to your preferred OCI registry (Docker Hub, GHCR, etc.) +- Update computation manifests to use `oci-image` type +- Test end-to-end flow with encrypted workloads diff --git a/agent/computations.go b/agent/computations.go index 00191db6..8e4124cb 100644 --- a/agent/computations.go +++ b/agent/computations.go @@ -20,6 +20,26 @@ type AgentConfig struct { AttestedTls bool `json:"attested_tls,omitempty"` } +// ResourceSource specifies the location of a remote encrypted resource. +type ResourceSource struct { + // Type is the type of resource source (currently only "oci-image" is supported) + Type string `json:"type,omitempty"` + // URL is the location of the resource (e.g., docker://registry/repo:tag) + URL string `json:"url,omitempty"` + // KBSResourcePath is the path to the decryption key in KBS (e.g., "default/key/my-key") + KBSResourcePath string `json:"kbs_resource_path,omitempty"` + // Encrypted indicates whether the resource is encrypted and requires KBS + Encrypted bool `json:"encrypted,omitempty"` +} + +// KBSConfig holds configuration for Key Broker Service. +type KBSConfig struct { + // URL is the KBS endpoint (e.g., "https://kbs.example.com") + URL string `json:"url,omitempty"` + // Enabled indicates whether to use KBS for key retrieval + Enabled bool `json:"enabled,omitempty"` +} + type Computation struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` @@ -27,6 +47,7 @@ type Computation struct { Datasets Datasets `json:"datasets,omitempty"` Algorithm Algorithm `json:"algorithm,omitempty"` ResultConsumers []ResultConsumer `json:"result_consumers,omitempty"` + KBS KBSConfig `json:"kbs,omitempty"` } type ResultConsumer struct { @@ -42,19 +63,24 @@ func (d *Datasets) String() string { } type Dataset struct { - Dataset []byte `json:"-"` - Hash [32]byte `json:"hash,omitempty"` - UserKey []byte `json:"user_key,omitempty"` - Filename string `json:"filename,omitempty"` + Dataset []byte `json:"-"` + Hash [32]byte `json:"hash,omitempty"` + UserKey []byte `json:"user_key,omitempty"` + Filename string `json:"filename,omitempty"` + Source *ResourceSource `json:"source,omitempty"` // Optional remote source + Decompress bool `json:"decompress,omitempty"` } type Datasets []Dataset type Algorithm struct { - Algorithm []byte `json:"-"` - Hash [32]byte `json:"hash,omitempty"` - UserKey []byte `json:"user_key,omitempty"` - Requirements []byte `json:"-"` + Algorithm []byte `json:"-"` + Hash [32]byte `json:"hash,omitempty"` + UserKey []byte `json:"user_key,omitempty"` + Requirements []byte `json:"-"` + Source *ResourceSource `json:"source,omitempty"` // Optional remote source + AlgoType string `json:"algo_type,omitempty"` + AlgoArgs []string `json:"algo_args,omitempty"` } type ManifestIndexKey struct{} diff --git a/agent/cvms/api/grpc/client.go b/agent/cvms/api/grpc/client.go index b25f0485..836ff798 100644 --- a/agent/cvms/api/grpc/client.go +++ b/agent/cvms/api/grpc/client.go @@ -238,13 +238,34 @@ func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.Computati Hash: [32]byte(runReq.Algorithm.Hash), UserKey: runReq.Algorithm.UserKey, } + // Copy remote source if configured + if runReq.Algorithm.Source != nil { + ac.Algorithm.Source = &agent.ResourceSource{ + URL: runReq.Algorithm.Source.Url, + KBSResourcePath: runReq.Algorithm.Source.KbsResourcePath, + Encrypted: runReq.Algorithm.Source.Encrypted, + } + } + ac.Algorithm.AlgoType = runReq.Algorithm.AlgoType + ac.Algorithm.AlgoArgs = runReq.Algorithm.AlgoArgs } for _, ds := range runReq.Datasets { - ac.Datasets = append(ac.Datasets, agent.Dataset{ - Hash: [32]byte(ds.Hash), - UserKey: ds.UserKey, - }) + dataset := agent.Dataset{ + Hash: [32]byte(ds.Hash), + UserKey: ds.UserKey, + Filename: ds.Filename, + } + // Copy remote source if configured + if ds.Source != nil { + dataset.Source = &agent.ResourceSource{ + URL: ds.Source.Url, + KBSResourcePath: ds.Source.KbsResourcePath, + Encrypted: ds.Source.Encrypted, + } + } + dataset.Decompress = ds.Decompress + ac.Datasets = append(ac.Datasets, dataset) } for _, rc := range runReq.ResultConsumers { @@ -253,6 +274,14 @@ func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.Computati }) } + // Copy KBS configuration + if runReq.Kbs != nil { + ac.KBS = agent.KBSConfig{ + URL: runReq.Kbs.Url, + Enabled: runReq.Kbs.Enabled, + } + } + // Check if the agent is in the correct state to initialize a new computation. // If the agent is already processing this computation (e.g., after a reconnection), // skip initialization to avoid state errors. diff --git a/agent/cvms/api/grpc/client_test.go b/agent/cvms/api/grpc/client_test.go index 8a2cc478..7830a222 100644 --- a/agent/cvms/api/grpc/client_test.go +++ b/agent/cvms/api/grpc/client_test.go @@ -10,6 +10,7 @@ import ( mglog "github.com/absmach/supermq/logger" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/ultravioletrs/cocos/agent" "github.com/ultravioletrs/cocos/agent/cvms" "github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage" servermocks "github.com/ultravioletrs/cocos/agent/cvms/server/mocks" @@ -513,3 +514,140 @@ func TestManagerClient_sendMessageTimeout(t *testing.T) { // Should complete without blocking time.Sleep(100 * time.Millisecond) } + +// TestManagerClient_handleRunReqChunksWithRemoteSource tests handling run request with remote source. +func TestManagerClient_handleRunReqChunksWithRemoteSource(t *testing.T) { + mockStream := new(mockStream) + mockSvc := new(mocks.Service) + mockServerSvc := new(servermocks.AgentServer) + messageQueue := make(chan *cvms.ClientStreamMessage, 10) + logger := mglog.NewMock() + grpcClient := new(clientmocks.Client) + + client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, nil, t.TempDir(), func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) { return nil, nil, nil }, grpcClient) + assert.NoError(t, err) + + runReq := &cvms.ComputationRunReq{ + Id: "test-id-remote", + Name: "test-computation", + Description: "test description", + Datasets: []*cvms.Dataset{ + { + Hash: sha3.New256().Sum([]byte("test-dataset")), + Filename: "data.csv", + Source: &cvms.Source{ + Type: "oci-image", + Url: "docker://registry.example.com/data:v1", + KbsResourcePath: "default/key/data-key", + Encrypted: true, + }, + Decompress: true, + }, + }, + Algorithm: &cvms.Algorithm{ + Hash: sha3.New256().Sum([]byte("test-algorithm")), + AlgoType: "python", + AlgoArgs: []string{"--verbose"}, + Source: &cvms.Source{ + Type: "oci-image", + Url: "docker://registry.example.com/algo:v1", + KbsResourcePath: "default/key/algo-key", + Encrypted: true, + }, + }, + Kbs: &cvms.KBSConfig{ + Url: "https://kbs.example.com:8080", + Enabled: true, + }, + ResultConsumers: []*cvms.ResultConsumer{ + { + UserKey: []byte("test-consumer"), + }, + }, + } + runReqBytes, _ := proto.Marshal(runReq) + + chunk := &cvms.ServerStreamMessage_RunReqChunks{ + RunReqChunks: &cvms.RunReqChunks{ + Id: "chunk-remote-1", + Data: runReqBytes, + IsLast: true, + }, + } + + mockSvc.On("State").Return("ReceivingManifest") + mockSvc.On("InitComputation", mock.Anything, mock.MatchedBy(func(c agent.Computation) bool { + // Verify KBS config is passed + if !c.KBS.Enabled || c.KBS.URL != "https://kbs.example.com:8080" { + return false + } + // Verify algorithm source is passed + if c.Algorithm.Source == nil || + c.Algorithm.Source.URL != "docker://registry.example.com/algo:v1" || + c.Algorithm.Source.KBSResourcePath != "default/key/algo-key" || + !c.Algorithm.Source.Encrypted { + return false + } + // Verify algorithm type and args + if c.Algorithm.AlgoType != "python" || len(c.Algorithm.AlgoArgs) != 1 || c.Algorithm.AlgoArgs[0] != "--verbose" { + return false + } + // Verify dataset source is passed + if len(c.Datasets) != 1 || + c.Datasets[0].Source == nil || + c.Datasets[0].Source.URL != "docker://registry.example.com/data:v1" || + c.Datasets[0].Filename != "data.csv" || + !c.Datasets[0].Decompress { + return false + } + return true + })).Return(nil) + mockServerSvc.On("Start", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + err = client.handleRunReqChunks(context.Background(), chunk) + assert.NoError(t, err) + + // Wait for the goroutine to finish + time.Sleep(100 * time.Millisecond) + + mockSvc.AssertExpectations(t) +} + +// TestManagerClient_handleRunReqChunksAlreadyProcessing tests skipping init when already processing. +func TestManagerClient_handleRunReqChunksAlreadyProcessing(t *testing.T) { + mockStream := new(mockStream) + mockSvc := new(mocks.Service) + mockServerSvc := new(servermocks.AgentServer) + messageQueue := make(chan *cvms.ClientStreamMessage, 10) + logger := mglog.NewMock() + grpcClient := new(clientmocks.Client) + + client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, nil, t.TempDir(), func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) { return nil, nil, nil }, grpcClient) + assert.NoError(t, err) + + runReq := &cvms.ComputationRunReq{ + Id: "test-id-processing", + Name: "test-computation", + } + runReqBytes, _ := proto.Marshal(runReq) + + chunk := &cvms.ServerStreamMessage_RunReqChunks{ + RunReqChunks: &cvms.RunReqChunks{ + Id: "chunk-processing-1", + Data: runReqBytes, + IsLast: true, + }, + } + + // Simulate agent already processing a computation + mockSvc.On("State").Return("Running") + + err = client.handleRunReqChunks(context.Background(), chunk) + assert.NoError(t, err) + + // Wait for the goroutine to finish + time.Sleep(50 * time.Millisecond) + + // InitComputation should NOT be called since state is not ReceivingManifest + mockSvc.AssertNotCalled(t, "InitComputation") +} diff --git a/agent/cvms/cvms.pb.go b/agent/cvms/cvms.pb.go index 1b0bb35b..3cfa386c 100644 --- a/agent/cvms/cvms.pb.go +++ b/agent/cvms/cvms.pb.go @@ -826,6 +826,7 @@ type ComputationRunReq struct { Algorithm *Algorithm `protobuf:"bytes,5,opt,name=algorithm,proto3" json:"algorithm,omitempty"` ResultConsumers []*ResultConsumer `protobuf:"bytes,6,rep,name=result_consumers,json=resultConsumers,proto3" json:"result_consumers,omitempty"` AgentConfig *AgentConfig `protobuf:"bytes,7,opt,name=agent_config,json=agentConfig,proto3" json:"agent_config,omitempty"` + Kbs *KBSConfig `protobuf:"bytes,8,opt,name=kbs,proto3" json:"kbs,omitempty"` // Optional KBS configuration for remote resources unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -909,6 +910,13 @@ func (x *ComputationRunReq) GetAgentConfig() *AgentConfig { return nil } +func (x *ComputationRunReq) GetKbs() *KBSConfig { + if x != nil { + return x.Kbs + } + return nil +} + type ResultConsumer struct { state protoimpl.MessageState `protogen:"open.v1"` UserKey []byte `protobuf:"bytes,1,opt,name=userKey,proto3" json:"userKey,omitempty"` @@ -958,6 +966,8 @@ type Dataset struct { Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // should be sha3.Sum256, 32 byte length. UserKey []byte `protobuf:"bytes,2,opt,name=userKey,proto3" json:"userKey,omitempty"` Filename string `protobuf:"bytes,3,opt,name=filename,proto3" json:"filename,omitempty"` + Source *Source `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` // Optional remote source for encrypted dataset + Decompress bool `protobuf:"varint,5,opt,name=decompress,proto3" json:"decompress,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1013,10 +1023,27 @@ func (x *Dataset) GetFilename() string { return "" } +func (x *Dataset) GetSource() *Source { + if x != nil { + return x.Source + } + return nil +} + +func (x *Dataset) GetDecompress() bool { + if x != nil { + return x.Decompress + } + return false +} + type Algorithm struct { state protoimpl.MessageState `protogen:"open.v1"` Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // should be sha3.Sum256, 32 byte length. UserKey []byte `protobuf:"bytes,2,opt,name=userKey,proto3" json:"userKey,omitempty"` + Source *Source `protobuf:"bytes,3,opt,name=source,proto3" json:"source,omitempty"` // Optional remote source for encrypted algorithm + AlgoType string `protobuf:"bytes,4,opt,name=algo_type,json=algoType,proto3" json:"algo_type,omitempty"` + AlgoArgs []string `protobuf:"bytes,5,rep,name=algo_args,json=algoArgs,proto3" json:"algo_args,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1065,6 +1092,147 @@ func (x *Algorithm) GetUserKey() []byte { return nil } +func (x *Algorithm) GetSource() *Source { + if x != nil { + return x.Source + } + return nil +} + +func (x *Algorithm) GetAlgoType() string { + if x != nil { + return x.AlgoType + } + return "" +} + +func (x *Algorithm) GetAlgoArgs() []string { + if x != nil { + return x.AlgoArgs + } + return nil +} + +type Source struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Type of source: "oci-image" (only OCI images supported for CoCo) + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` // URL of the OCI image (e.g., docker://registry/repo:tag) + KbsResourcePath string `protobuf:"bytes,3,opt,name=kbs_resource_path,json=kbsResourcePath,proto3" json:"kbs_resource_path,omitempty"` // Path to decryption key in KBS (e.g., "default/key/my-key") + Encrypted bool `protobuf:"varint,4,opt,name=encrypted,proto3" json:"encrypted,omitempty"` // Whether the resource is encrypted (requires KBS) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Source) Reset() { + *x = Source{} + mi := &file_agent_cvms_cvms_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Source) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Source) ProtoMessage() {} + +func (x *Source) ProtoReflect() protoreflect.Message { + mi := &file_agent_cvms_cvms_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Source.ProtoReflect.Descriptor instead. +func (*Source) Descriptor() ([]byte, []int) { + return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{15} +} + +func (x *Source) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Source) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *Source) GetKbsResourcePath() string { + if x != nil { + return x.KbsResourcePath + } + return "" +} + +func (x *Source) GetEncrypted() bool { + if x != nil { + return x.Encrypted + } + return false +} + +type KBSConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` // KBS endpoint URL (e.g., "https://kbs.example.com") + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` // Whether to use KBS for key retrieval + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *KBSConfig) Reset() { + *x = KBSConfig{} + mi := &file_agent_cvms_cvms_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *KBSConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KBSConfig) ProtoMessage() {} + +func (x *KBSConfig) ProtoReflect() protoreflect.Message { + mi := &file_agent_cvms_cvms_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KBSConfig.ProtoReflect.Descriptor instead. +func (*KBSConfig) Descriptor() ([]byte, []int) { + return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{16} +} + +func (x *KBSConfig) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *KBSConfig) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + type AgentConfig struct { state protoimpl.MessageState `protogen:"open.v1"` Port string `protobuf:"bytes,1,opt,name=port,proto3" json:"port,omitempty"` @@ -1080,7 +1248,7 @@ type AgentConfig struct { func (x *AgentConfig) Reset() { *x = AgentConfig{} - mi := &file_agent_cvms_cvms_proto_msgTypes[15] + mi := &file_agent_cvms_cvms_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1092,7 +1260,7 @@ func (x *AgentConfig) String() string { func (*AgentConfig) ProtoMessage() {} func (x *AgentConfig) ProtoReflect() protoreflect.Message { - mi := &file_agent_cvms_cvms_proto_msgTypes[15] + mi := &file_agent_cvms_cvms_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1105,7 +1273,7 @@ func (x *AgentConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentConfig.ProtoReflect.Descriptor instead. func (*AgentConfig) Descriptor() ([]byte, []int) { - return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{15} + return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{17} } func (x *AgentConfig) GetPort() string { @@ -1167,7 +1335,7 @@ type AttestationResponse struct { func (x *AttestationResponse) Reset() { *x = AttestationResponse{} - mi := &file_agent_cvms_cvms_proto_msgTypes[16] + mi := &file_agent_cvms_cvms_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1179,7 +1347,7 @@ func (x *AttestationResponse) String() string { func (*AttestationResponse) ProtoMessage() {} func (x *AttestationResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_cvms_cvms_proto_msgTypes[16] + mi := &file_agent_cvms_cvms_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1192,7 +1360,7 @@ func (x *AttestationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AttestationResponse.ProtoReflect.Descriptor instead. func (*AttestationResponse) Descriptor() ([]byte, []int) { - return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{16} + return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{18} } func (x *AttestationResponse) GetFile() []byte { @@ -1219,7 +1387,7 @@ type AzureAttestationToken struct { func (x *AzureAttestationToken) Reset() { *x = AzureAttestationToken{} - mi := &file_agent_cvms_cvms_proto_msgTypes[17] + mi := &file_agent_cvms_cvms_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1231,7 +1399,7 @@ func (x *AzureAttestationToken) String() string { func (*AzureAttestationToken) ProtoMessage() {} func (x *AzureAttestationToken) ProtoReflect() protoreflect.Message { - mi := &file_agent_cvms_cvms_proto_msgTypes[17] + mi := &file_agent_cvms_cvms_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1244,7 +1412,7 @@ func (x *AzureAttestationToken) ProtoReflect() protoreflect.Message { // Deprecated: Use AzureAttestationToken.ProtoReflect.Descriptor instead. func (*AzureAttestationToken) Descriptor() ([]byte, []int) { - return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{17} + return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{19} } func (x *AzureAttestationToken) GetFile() []byte { @@ -1317,7 +1485,7 @@ const file_agent_cvms_cvms_proto_rawDesc = "" + "\fRunReqChunks\x12\x12\n" + "\x04data\x18\x01 \x01(\fR\x04data\x12\x0e\n" + "\x02id\x18\x02 \x01(\tR\x02id\x12\x17\n" + - "\ais_last\x18\x03 \x01(\bR\x06isLast\"\xaa\x02\n" + + "\ais_last\x18\x03 \x01(\bR\x06isLast\"\xcd\x02\n" + "\x11ComputationRunReq\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + @@ -1325,16 +1493,32 @@ const file_agent_cvms_cvms_proto_rawDesc = "" + "\bdatasets\x18\x04 \x03(\v2\r.cvms.DatasetR\bdatasets\x12-\n" + "\talgorithm\x18\x05 \x01(\v2\x0f.cvms.AlgorithmR\talgorithm\x12?\n" + "\x10result_consumers\x18\x06 \x03(\v2\x14.cvms.ResultConsumerR\x0fresultConsumers\x124\n" + - "\fagent_config\x18\a \x01(\v2\x11.cvms.AgentConfigR\vagentConfig\"*\n" + + "\fagent_config\x18\a \x01(\v2\x11.cvms.AgentConfigR\vagentConfig\x12!\n" + + "\x03kbs\x18\b \x01(\v2\x0f.cvms.KBSConfigR\x03kbs\"*\n" + "\x0eResultConsumer\x12\x18\n" + - "\auserKey\x18\x01 \x01(\fR\auserKey\"S\n" + + "\auserKey\x18\x01 \x01(\fR\auserKey\"\x99\x01\n" + "\aDataset\x12\x12\n" + "\x04hash\x18\x01 \x01(\fR\x04hash\x12\x18\n" + "\auserKey\x18\x02 \x01(\fR\auserKey\x12\x1a\n" + - "\bfilename\x18\x03 \x01(\tR\bfilename\"9\n" + + "\bfilename\x18\x03 \x01(\tR\bfilename\x12$\n" + + "\x06source\x18\x04 \x01(\v2\f.cvms.SourceR\x06source\x12\x1e\n" + + "\n" + + "decompress\x18\x05 \x01(\bR\n" + + "decompress\"\x99\x01\n" + "\tAlgorithm\x12\x12\n" + "\x04hash\x18\x01 \x01(\fR\x04hash\x12\x18\n" + - "\auserKey\x18\x02 \x01(\fR\auserKey\"\xe5\x01\n" + + "\auserKey\x18\x02 \x01(\fR\auserKey\x12$\n" + + "\x06source\x18\x03 \x01(\v2\f.cvms.SourceR\x06source\x12\x1b\n" + + "\talgo_type\x18\x04 \x01(\tR\balgoType\x12\x1b\n" + + "\talgo_args\x18\x05 \x03(\tR\balgoArgs\"x\n" + + "\x06Source\x12\x12\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x12\x10\n" + + "\x03url\x18\x02 \x01(\tR\x03url\x12*\n" + + "\x11kbs_resource_path\x18\x03 \x01(\tR\x0fkbsResourcePath\x12\x1c\n" + + "\tencrypted\x18\x04 \x01(\bR\tencrypted\"7\n" + + "\tKBSConfig\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url\x12\x18\n" + + "\aenabled\x18\x02 \x01(\bR\aenabled\"\xe5\x01\n" + "\vAgentConfig\x12\x12\n" + "\x04port\x18\x01 \x01(\tR\x04port\x12\x1b\n" + "\tcert_file\x18\x02 \x01(\tR\bcertFile\x12\x19\n" + @@ -1364,7 +1548,7 @@ func file_agent_cvms_cvms_proto_rawDescGZIP() []byte { return file_agent_cvms_cvms_proto_rawDescData } -var file_agent_cvms_cvms_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_agent_cvms_cvms_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_agent_cvms_cvms_proto_goTypes = []any{ (*AgentStateReq)(nil), // 0: cvms.AgentStateReq (*AgentStateRes)(nil), // 1: cvms.AgentStateRes @@ -1381,21 +1565,23 @@ var file_agent_cvms_cvms_proto_goTypes = []any{ (*ResultConsumer)(nil), // 12: cvms.ResultConsumer (*Dataset)(nil), // 13: cvms.Dataset (*Algorithm)(nil), // 14: cvms.Algorithm - (*AgentConfig)(nil), // 15: cvms.AgentConfig - (*AttestationResponse)(nil), // 16: cvms.AttestationResponse - (*AzureAttestationToken)(nil), // 17: cvms.azureAttestationToken - (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp + (*Source)(nil), // 15: cvms.Source + (*KBSConfig)(nil), // 16: cvms.KBSConfig + (*AgentConfig)(nil), // 17: cvms.AgentConfig + (*AttestationResponse)(nil), // 18: cvms.AttestationResponse + (*AzureAttestationToken)(nil), // 19: cvms.azureAttestationToken + (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp } var file_agent_cvms_cvms_proto_depIdxs = []int32{ - 18, // 0: cvms.AgentEvent.timestamp:type_name -> google.protobuf.Timestamp - 18, // 1: cvms.AgentLog.timestamp:type_name -> google.protobuf.Timestamp + 20, // 0: cvms.AgentEvent.timestamp:type_name -> google.protobuf.Timestamp + 20, // 1: cvms.AgentLog.timestamp:type_name -> google.protobuf.Timestamp 6, // 2: cvms.ClientStreamMessage.agent_log:type_name -> cvms.AgentLog 5, // 3: cvms.ClientStreamMessage.agent_event:type_name -> cvms.AgentEvent 4, // 4: cvms.ClientStreamMessage.run_res:type_name -> cvms.RunResponse 3, // 5: cvms.ClientStreamMessage.stopComputationRes:type_name -> cvms.StopComputationResponse 1, // 6: cvms.ClientStreamMessage.agentStateRes:type_name -> cvms.AgentStateRes - 16, // 7: cvms.ClientStreamMessage.vTPMattestationReport:type_name -> cvms.AttestationResponse - 17, // 8: cvms.ClientStreamMessage.azureAttestationToken:type_name -> cvms.azureAttestationToken + 18, // 7: cvms.ClientStreamMessage.vTPMattestationReport:type_name -> cvms.AttestationResponse + 19, // 8: cvms.ClientStreamMessage.azureAttestationToken:type_name -> cvms.azureAttestationToken 10, // 9: cvms.ServerStreamMessage.runReqChunks:type_name -> cvms.RunReqChunks 11, // 10: cvms.ServerStreamMessage.runReq:type_name -> cvms.ComputationRunReq 2, // 11: cvms.ServerStreamMessage.stopComputation:type_name -> cvms.StopComputation @@ -1404,14 +1590,17 @@ var file_agent_cvms_cvms_proto_depIdxs = []int32{ 13, // 14: cvms.ComputationRunReq.datasets:type_name -> cvms.Dataset 14, // 15: cvms.ComputationRunReq.algorithm:type_name -> cvms.Algorithm 12, // 16: cvms.ComputationRunReq.result_consumers:type_name -> cvms.ResultConsumer - 15, // 17: cvms.ComputationRunReq.agent_config:type_name -> cvms.AgentConfig - 7, // 18: cvms.Service.Process:input_type -> cvms.ClientStreamMessage - 8, // 19: cvms.Service.Process:output_type -> cvms.ServerStreamMessage - 19, // [19:20] is the sub-list for method output_type - 18, // [18:19] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 17, // 17: cvms.ComputationRunReq.agent_config:type_name -> cvms.AgentConfig + 16, // 18: cvms.ComputationRunReq.kbs:type_name -> cvms.KBSConfig + 15, // 19: cvms.Dataset.source:type_name -> cvms.Source + 15, // 20: cvms.Algorithm.source:type_name -> cvms.Source + 7, // 21: cvms.Service.Process:input_type -> cvms.ClientStreamMessage + 8, // 22: cvms.Service.Process:output_type -> cvms.ServerStreamMessage + 22, // [22:23] is the sub-list for method output_type + 21, // [21:22] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_agent_cvms_cvms_proto_init() } @@ -1441,7 +1630,7 @@ func file_agent_cvms_cvms_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_cvms_cvms_proto_rawDesc), len(file_agent_cvms_cvms_proto_rawDesc)), NumEnums: 0, - NumMessages: 18, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/agent/cvms/cvms.proto b/agent/cvms/cvms.proto index c31e3e81..1f1e58dc 100644 --- a/agent/cvms/cvms.proto +++ b/agent/cvms/cvms.proto @@ -92,6 +92,7 @@ message ComputationRunReq { Algorithm algorithm = 5; repeated ResultConsumer result_consumers = 6; AgentConfig agent_config = 7; + KBSConfig kbs = 8; // Optional KBS configuration for remote resources } message ResultConsumer { @@ -102,11 +103,28 @@ message Dataset { bytes hash = 1; // should be sha3.Sum256, 32 byte length. bytes userKey = 2; string filename = 3; + Source source = 4; // Optional remote source for encrypted dataset + bool decompress = 5; } message Algorithm { bytes hash = 1; // should be sha3.Sum256, 32 byte length. bytes userKey = 2; + Source source = 3; // Optional remote source for encrypted algorithm + string algo_type = 4; + repeated string algo_args = 5; +} + +message Source { + string type = 1; // Type of source: "oci-image" (only OCI images supported for CoCo) + string url = 2; // URL of the OCI image (e.g., docker://registry/repo:tag) + string kbs_resource_path = 3; // Path to decryption key in KBS (e.g., "default/key/my-key") + bool encrypted = 4; // Whether the resource is encrypted (requires KBS) +} + +message KBSConfig { + string url = 1; // KBS endpoint URL (e.g., "https://kbs.example.com") + bool enabled = 2; // Whether to use KBS for key retrieval } message AgentConfig { diff --git a/agent/mock_attestation_client_test.go b/agent/mock_attestation_client_test.go index 1762f9a1..2fdef066 100644 --- a/agent/mock_attestation_client_test.go +++ b/agent/mock_attestation_client_test.go @@ -18,6 +18,11 @@ func (m *MockAttestationClient) GetAttestation(ctx context.Context, reportData [ return args.Get(0).([]byte), args.Error(1) } +func (m *MockAttestationClient) GetRawEvidence(ctx context.Context, reportData [64]byte, nonce [32]byte, attType attestation.PlatformType) ([]byte, error) { + args := m.Called(ctx, reportData, nonce, attType) + return args.Get(0).([]byte), args.Error(1) +} + func (m *MockAttestationClient) GetAzureToken(ctx context.Context, nonce [32]byte) ([]byte, error) { args := m.Called(ctx, nonce) return args.Get(0).([]byte), args.Error(1) diff --git a/agent/service.go b/agent/service.go index b5573561..aff5b234 100644 --- a/agent/service.go +++ b/agent/service.go @@ -9,8 +9,10 @@ import ( "fmt" "log/slog" "os" + "os/exec" "path/filepath" "slices" + "strings" sync "sync" "time" @@ -24,6 +26,7 @@ import ( "github.com/ultravioletrs/cocos/pkg/attestation/vtpm" attestation_client "github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation" runner_client "github.com/ultravioletrs/cocos/pkg/clients/grpc/runner" + "github.com/ultravioletrs/cocos/pkg/oci" "golang.org/x/crypto/sha3" ) @@ -73,7 +76,7 @@ const ( algoFilePermission = 0o700 ) -const ( +var ( ImaMeasurementsFilePath = "/sys/kernel/security/integrity/ima/ascii_runtime_measurements" ImaPcrIndex = 10 ) @@ -125,6 +128,10 @@ type Service interface { State() string } +type OCIClient interface { + PullAndDecrypt(ctx context.Context, source oci.ResourceSource, destDir string) error +} + type agentService struct { mu sync.Mutex computation Computation // Holds the current computation request details. @@ -142,6 +149,7 @@ type agentService struct { resultsConsumed bool // Indicates if the results have been consumed. cancel context.CancelFunc // Cancels the computation context. vmpl int // VMPL at which the Agent is running. + ociClient OCIClient } var _ Service = (*agentService)(nil) @@ -160,12 +168,21 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, atte vmpl: vmlp, } + workDir := filepath.Join(os.TempDir(), "cocos-oci") + skopeoClient, err := oci.NewSkopeoClient(workDir) + if err != nil { + logger.Warn("failed to create Skopeo client", "error", err) + } + svc.ociClient = skopeoClient + transitions := []statemachine.Transition{ {From: Idle, Event: Start, To: ReceivingManifest}, {From: ReceivingManifest, Event: ManifestReceived, To: ReceivingAlgorithm}, } transitions = append(transitions, []statemachine.Transition{ + {From: ReceivingAlgorithm, Event: RunFailed, To: Failed}, + {From: ReceivingData, Event: RunFailed, To: Failed}, {From: Running, Event: RunComplete, To: ConsumingResults}, {From: Running, Event: RunFailed, To: Failed}, {From: ConsumingResults, Event: ResultsConsumed, To: Complete}, @@ -175,8 +192,8 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, atte sm.AddTransition(t) } - sm.SetAction(ReceivingAlgorithm, svc.publishEvent(InProgress.String())) - sm.SetAction(ReceivingData, svc.publishEvent(InProgress.String())) + sm.SetAction(ReceivingAlgorithm, svc.downloadAlgorithmIfRemote) + sm.SetAction(ReceivingData, svc.downloadDatasetsIfRemote) sm.SetAction(Running, svc.runComputation) sm.SetAction(ConsumingResults, svc.publishEvent(Ready.String())) sm.SetAction(Complete, svc.publishEvent(Completed.String())) @@ -211,6 +228,38 @@ func (as *agentService) InitComputation(ctx context.Context, cmp Computation) er as.computation = cmp + // Debug: Log manifest details + as.logger.Info("received computation manifest", + "computation_id", cmp.ID, + "kbs_enabled", cmp.KBS.Enabled, + "kbs_url", cmp.KBS.URL, + "algo_has_source", cmp.Algorithm.Source != nil, + "dataset_count", len(cmp.Datasets)) + + if cmp.Algorithm.Source != nil { + as.logger.Info("algorithm remote source configured", + "url", cmp.Algorithm.Source.URL, + "kbs_resource_path", cmp.Algorithm.Source.KBSResourcePath) + } else { + as.logger.Info("algorithm remote source NOT configured - will wait for direct upload") + } + + if cmp.KBS.Enabled { + as.logger.Info("KBS is ENABLED", "url", cmp.KBS.URL) + } else { + as.logger.Info("KBS is NOT ENABLED") + } + + for i, d := range cmp.Datasets { + if d.Source != nil { + as.logger.Info("dataset remote source configured", + "index", i, + "filename", d.Filename, + "url", d.Source.URL, + "kbs_resource_path", d.Source.KBSResourcePath) + } + } + transitions := []statemachine.Transition{} if len(cmp.Datasets) == 0 { @@ -276,6 +325,320 @@ func (as *agentService) StopComputation(ctx context.Context) error { return nil } +// downloadAlgorithmIfRemote automatically downloads the algorithm if it has a remote source. +// This is called as an action when entering the ReceivingAlgorithm state. +func (as *agentService) downloadAlgorithmIfRemote(state statemachine.State) { + as.publishEvent(InProgress.String())(state) + + as.mu.Lock() + defer as.mu.Unlock() + + // Debug: Log decision point + as.logger.Info("checking if algorithm should be downloaded automatically", + "algo_has_source", as.computation.Algorithm.Source != nil, + "kbs_enabled", as.computation.KBS.Enabled) + + // Check if algorithm should be downloaded from remote source + if as.computation.Algorithm.Source != nil && as.computation.KBS.Enabled { + as.logger.Info("downloading algorithm from remote source", + "url", as.computation.Algorithm.Source.URL, + "kbs_resource_path", as.computation.Algorithm.Source.KBSResourcePath) + + // Use background context for download operation + ctx := context.Background() + + res, err := as.downloadAndDecryptResource(ctx, as.computation.Algorithm.Source, "algorithm") + if err != nil { + as.runError = fmt.Errorf("failed to download and decrypt algorithm: %w", err) + as.logger.Error(as.runError.Error()) + as.sm.SendEvent(RunFailed) + return + } + + // Verify hash + hash := sha3.Sum256(res.Data) + if hash != as.computation.Algorithm.Hash { + as.runError = fmt.Errorf("algorithm hash mismatch: expected %x, got %x", as.computation.Algorithm.Hash, hash) + as.logger.Error(as.runError.Error()) + as.sm.SendEvent(RunFailed) + return + } + + // Write algorithm to file + currentDir, err := os.Getwd() + if err != nil { + as.runError = fmt.Errorf("error getting current directory: %w", err) + as.logger.Error(as.runError.Error()) + as.sm.SendEvent(RunFailed) + return + } + + // If a source directory is available (e.g. from OCI extraction), copy all files + if res.SourceDir != "" { + as.logger.Info("copying extracted algorithm directory", "src", res.SourceDir, "dst", currentDir) + // Simple recursive copy (using shell cp for simplicity and reliability on Linux) + // Ensure we copy contents of SourceDir into currentDir + // Simple recursive copy (using shell cp for simplicity and reliability on Linux) + // Ensure we copy contents of SourceDir into currentDir + cmd := exec.Command("cp", "-r", res.SourceDir+"/.", currentDir) + if out, err := cmd.CombinedOutput(); err != nil { + as.runError = fmt.Errorf("error copying algorithm directory: %v, output: %s", err, out) + as.logger.Error(as.runError.Error()) + as.sm.SendEvent(RunFailed) + return + } + } + + f, err := os.Create(filepath.Join(currentDir, "algo")) + if err != nil { + as.runError = fmt.Errorf("error creating algorithm file: %w", err) + as.logger.Error(as.runError.Error()) + as.sm.SendEvent(RunFailed) + return + } + + if _, err := f.Write(res.Data); err != nil { + as.runError = fmt.Errorf("error writing algorithm to file: %w", err) + as.logger.Error(as.runError.Error()) + f.Close() + as.sm.SendEvent(RunFailed) + return + } + + if err := os.Chmod(f.Name(), algoFilePermission); err != nil { + as.runError = fmt.Errorf("error changing file permissions: %w", err) + as.logger.Error(as.runError.Error()) + f.Close() + as.sm.SendEvent(RunFailed) + return + } + + if err := f.Close(); err != nil { + as.runError = fmt.Errorf("error closing file: %w", err) + as.logger.Error(as.runError.Error()) + as.sm.SendEvent(RunFailed) + return + } + + as.algoReceived = true + as.algoRequirements = res.Requirements // Store requirements for installation + + // Create datasets directory + if err := os.Mkdir(algorithm.DatasetsDir, 0o755); err != nil { + as.runError = fmt.Errorf("error creating datasets directory: %w", err) + as.logger.Error(as.runError.Error()) + as.sm.SendEvent(RunFailed) + return + } + + as.algoType = as.computation.Algorithm.AlgoType + if as.algoType == "" { + as.algoType = string(algorithm.AlgoTypeBin) + } + as.algoArgs = as.computation.Algorithm.AlgoArgs + + as.logger.Info("algorithm downloaded and saved successfully", "type", as.algoType, "has_requirements", len(res.Requirements) > 0) + as.sm.SendEvent(AlgorithmReceived) + } else { + // If no remote source, do nothing - wait for direct upload via Algo() RPC call + as.logger.Info("algorithm automatic download not triggered, waiting for direct upload", + "reason", "no remote source or KBS not enabled") + } +} + +// downloadDatasetsIfRemote automatically downloads datasets that have remote sources. +// This is called as an action when entering the ReceivingData state. +func (as *agentService) downloadDatasetsIfRemote(state statemachine.State) { + as.publishEvent(InProgress.String())(state) + + as.mu.Lock() + defer as.mu.Unlock() + + // Check if any datasets should be downloaded from remote sources + hasRemoteDatasets := false + for _, d := range as.computation.Datasets { + if d.Source != nil && as.computation.KBS.Enabled { + hasRemoteDatasets = true + break + } + } + + if !hasRemoteDatasets { + // No remote datasets, wait for direct uploads via Data() RPC calls + return + } + + // Download all remote datasets + ctx := context.Background() + for i := len(as.computation.Datasets) - 1; i >= 0; i-- { + d := as.computation.Datasets[i] + if d.Source != nil && as.computation.KBS.Enabled { + as.logger.Info("downloading dataset from remote source", "filename", d.Filename) + + res, err := as.downloadAndDecryptResource(ctx, d.Source, "dataset") + if err != nil { + as.logger.Error("failed to download and decrypt dataset", "error", err, "filename", d.Filename) + as.sm.SendEvent(RunFailed) + return + } + + // Verify hash + hash := sha3.Sum256(res.Data) + if hash != d.Hash { + as.logger.Error("dataset hash mismatch", "filename", d.Filename) + as.sm.SendEvent(RunFailed) + return + } + + // Write dataset to file + f, err := os.Create(fmt.Sprintf("%s/%s", algorithm.DatasetsDir, d.Filename)) + if err != nil { + as.logger.Error("error creating dataset file", "error", err, "filename", d.Filename) + as.sm.SendEvent(RunFailed) + return + } + + if d.Decompress { + if err := internal.UnzipFromMemory(res.Data, algorithm.DatasetsDir); err != nil { + as.logger.Error("error decompressing dataset", "error", err, "filename", d.Filename) + as.sm.SendEvent(RunFailed) + return + } + } else { + if _, err := f.Write(res.Data); err != nil { + as.logger.Error("error writing dataset to file", "error", err, "filename", d.Filename) + f.Close() + as.sm.SendEvent(RunFailed) + return + } + } + + if err := f.Close(); err != nil { + as.logger.Error("error closing file", "error", err, "filename", d.Filename) + as.sm.SendEvent(RunFailed) + return + } + + // Remove from pending datasets + as.computation.Datasets = slices.Delete(as.computation.Datasets, i, i+1) + as.logger.Info("dataset downloaded and saved successfully", "filename", d.Filename) + } + } + + // If all datasets are downloaded, send DataReceived event + if len(as.computation.Datasets) == 0 { + as.logger.Info("all datasets downloaded successfully") + as.sm.SendEvent(DataReceived) + } + // Otherwise, wait for remaining datasets to be uploaded via Data() RPC calls +} + +// DecryptedResource holds the data and metadata of a downloaded and decrypted resource. +type DecryptedResource struct { + Data []byte + Requirements []byte + SourceDir string +} + +// downloadAndDecryptResource downloads and decrypts a resource using OCI images and CoCo Keyprovider. +// For OCI images, Skopeo handles download and CoCo Keyprovider handles decryption automatically. +func (as *agentService) downloadAndDecryptResource(ctx context.Context, source *ResourceSource, resourceType string) (*DecryptedResource, error) { + // Determine source type + sourceType := source.Type + if sourceType == "" { + // Infer from URL + if strings.HasPrefix(source.URL, "docker://") || strings.HasPrefix(source.URL, "oci:") { + sourceType = "oci-image" + } else { + return nil, fmt.Errorf("unsupported source URL format: %s (use oci-image type)", source.URL) + } + } + + switch sourceType { + case "oci-image": + return as.downloadAndDecryptOCIImage(ctx, source, resourceType) + default: + return nil, fmt.Errorf("unsupported source type: %s", sourceType) + } +} + +// downloadAndDecryptOCIImage downloads and decrypts an OCI image using Skopeo and CoCo Keyprovider. +func (as *agentService) downloadAndDecryptOCIImage(ctx context.Context, source *ResourceSource, resourceType string) (*DecryptedResource, error) { + as.logger.Info(fmt.Sprintf("downloading OCI image (url=%s encrypted=%t kbs_path=%s)", + source.URL, source.Encrypted, source.KBSResourcePath)) + + // Create Skopeo client + if as.ociClient == nil { + return nil, fmt.Errorf("OCI client not initialized") + } + + // Create OCI resource source + ociSource := oci.ResourceSource{ + Type: oci.ResourceTypeOCIImage, + URI: source.URL, + Encrypted: source.Encrypted, + KBSResourcePath: source.KBSResourcePath, + } + + // Pull and decrypt image + // CoCo Keyprovider will automatically handle decryption via ocicrypt + // Sanitize directory name to avoid Skopeo interpreting ':' as tag separator + sanitizedName := strings.ReplaceAll(filepath.Base(source.URL), ":", "_") + destDir := filepath.Join(os.TempDir(), "cocos-oci", "images", sanitizedName) + if err := as.ociClient.PullAndDecrypt(ctx, ociSource, destDir); err != nil { + return nil, fmt.Errorf("failed to pull and decrypt OCI image: %w", err) + } + + as.logger.Info("OCI image downloaded and decrypted", "dest", destDir) + + // Extract algorithm file from OCI layers + extractDir := filepath.Join(os.TempDir(), "cocos-oci", "extracted", sanitizedName) + var algorithmPath string + var err error + + if resourceType == "algorithm" { + algorithmPath, err = oci.ExtractAlgorithm(ctx, as.logger, destDir, extractDir) + if err != nil { + return nil, fmt.Errorf("failed to extract algorithm from OCI image: %w", err) + } + as.logger.Info("algorithm extracted from OCI image", "path", algorithmPath) + } else { + // Assume dataset + files, err := oci.ExtractDataset(destDir, extractDir) + if err != nil || len(files) == 0 { + return nil, fmt.Errorf("failed to extract dataset from OCI image: %w", err) + } + // For now, take the first file found. + // nolint:godox // TODO: Handle multiple files / directory structure if needed. + algorithmPath = files[0] + as.logger.Info("dataset extracted from OCI image", "path", algorithmPath) + } + + // Read algorithm file + algorithmData, err := os.ReadFile(algorithmPath) + if err != nil { + return nil, fmt.Errorf("failed to read algorithm file: %w", err) + } + + // Check for requirements.txt if algorithm + var reqData []byte + if resourceType == "algorithm" { + reqPath := filepath.Join(filepath.Dir(algorithmPath), "requirements.txt") + if data, err := os.ReadFile(reqPath); err == nil { + reqData = data + as.logger.Info("found requirements.txt", "size", len(data)) + } + } + + as.logger.Info("algorithm loaded", "size", len(algorithmData)) + + return &DecryptedResource{ + Data: algorithmData, + Requirements: reqData, + SourceDir: filepath.Dir(algorithmPath), + }, nil +} + func (as *agentService) Algo(ctx context.Context, algo Algorithm) error { if as.sm.GetState() != ReceivingAlgorithm { return ErrStateNotReady @@ -286,7 +649,25 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error { return ErrAllManifestItemsReceived } - hash := sha3.Sum256(algo.Algorithm) + var algoData []byte + + // Check if algorithm should be downloaded from remote source + if as.computation.Algorithm.Source != nil && as.computation.KBS.Enabled { + as.logger.Info("downloading algorithm from remote source") + + res, err := as.downloadAndDecryptResource(ctx, as.computation.Algorithm.Source, "algorithm") + if err != nil { + return fmt.Errorf("failed to download and decrypt algorithm: %w", err) + } + + algoData = res.Data + as.algoRequirements = res.Requirements + } else { + // Use directly uploaded algorithm + algoData = algo.Algorithm + } + + hash := sha3.Sum256(algoData) if hash != as.computation.Algorithm.Hash { return ErrHashMismatch @@ -302,7 +683,7 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error { return fmt.Errorf("error creating algorithm file: %v", err) } - if _, err := f.Write(algo.Algorithm); err != nil { + if _, err := f.Write(algoData); err != nil { return fmt.Errorf("error writing algorithm to file: %v", err) } @@ -347,28 +728,55 @@ func (as *agentService) Data(ctx context.Context, dataset Dataset) error { return ErrAllManifestItemsReceived } - hash := sha3.Sum256(dataset.Dataset) + var datasetData []byte + var datasetFilename string + + // Check if any dataset should be downloaded from remote source + matchedIndex := -1 + for i, d := range as.computation.Datasets { + if d.Source != nil && as.computation.KBS.Enabled { + as.logger.Info("downloading dataset from remote source", "filename", d.Filename) + + downloadedData, err := as.downloadAndDecryptResource(ctx, d.Source, "dataset") + if err != nil { + return fmt.Errorf("failed to download and decrypt dataset: %w", err) + } + + datasetData = downloadedData.Data + datasetFilename = d.Filename + matchedIndex = i + break + } + } + + // If no remote dataset, use uploaded dataset + if matchedIndex == -1 { + datasetData = dataset.Dataset + datasetFilename = dataset.Filename + } + + hash := sha3.Sum256(datasetData) matched := false for i, d := range as.computation.Datasets { if hash == d.Hash { - if d.Filename != "" && d.Filename != dataset.Filename { + if d.Filename != "" && d.Filename != datasetFilename { return ErrFileNameMismatch } as.computation.Datasets = slices.Delete(as.computation.Datasets, i, i+1) if DecompressFromContext(ctx) { - if err := internal.UnzipFromMemory(dataset.Dataset, algorithm.DatasetsDir); err != nil { + if err := internal.UnzipFromMemory(datasetData, algorithm.DatasetsDir); err != nil { return fmt.Errorf("error decompressing dataset: %v", err) } } else { - f, err := os.Create(fmt.Sprintf("%s/%s", algorithm.DatasetsDir, dataset.Filename)) + f, err := os.Create(fmt.Sprintf("%s/%s", algorithm.DatasetsDir, datasetFilename)) if err != nil { return fmt.Errorf("error creating dataset file: %v", err) } - if _, err := f.Write(dataset.Dataset); err != nil { + if _, err := f.Write(datasetData); err != nil { return fmt.Errorf("error writing dataset to file: %v", err) } if err := f.Close(); err != nil { diff --git a/agent/service_test.go b/agent/service_test.go index 3796751c..b67e0336 100644 --- a/agent/service_test.go +++ b/agent/service_test.go @@ -3,10 +3,14 @@ package agent import ( + "archive/tar" + "compress/gzip" "context" "crypto/rand" + "encoding/json" "fmt" "log" + "log/slog" "os" "path/filepath" "testing" @@ -19,6 +23,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ultravioletrs/cocos/agent/algorithm" "github.com/ultravioletrs/cocos/agent/algorithm/python" + agentevents "github.com/ultravioletrs/cocos/agent/events" "github.com/ultravioletrs/cocos/agent/events/mocks" runnerpb "github.com/ultravioletrs/cocos/agent/runner" "github.com/ultravioletrs/cocos/agent/statemachine" @@ -26,11 +31,21 @@ import ( "github.com/ultravioletrs/cocos/pkg/attestation" "github.com/ultravioletrs/cocos/pkg/attestation/vtpm" runnermocks "github.com/ultravioletrs/cocos/pkg/clients/grpc/runner/mocks" + "github.com/ultravioletrs/cocos/pkg/oci" "golang.org/x/crypto/sha3" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/types/known/emptypb" ) +type MockOCIClient struct { + mock.Mock +} + +func (m *MockOCIClient) PullAndDecrypt(ctx context.Context, source oci.ResourceSource, destDir string) error { + args := m.Called(ctx, source, destDir) + return args.Error(0) +} + var ( algoPath = "../test/manual/algo/lin_reg.py" reqPath = "../test/manual/algo/requirements.txt" @@ -672,3 +687,550 @@ func TestStopComputationConcurrent(t *testing.T) { assert.True(t, len(errors) < numGoroutines, "All StopComputation calls failed") } + +// newTestAgentService creates a minimal agentService for direct method testing. +func newTestAgentService(sm statemachine.StateMachine, eventSvc agentevents.Service) *agentService { + return &agentService{ + logger: slog.Default(), + eventSvc: eventSvc, + sm: sm, + } +} + +func TestDownloadAndDecryptResource(t *testing.T) { + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + sm.On("SendEvent", mock.Anything).Return().Maybe() + + svc := newTestAgentService(sm, eventsSvc) + + ctx := context.Background() + + t.Run("unsupported URL format no type", func(t *testing.T) { + source := &ResourceSource{URL: "http://unsupported-format"} + _, err := svc.downloadAndDecryptResource(ctx, source, "algorithm") + require.Error(t, err) + assert.Contains(t, err.Error(), "unsupported source URL format") + }) + + t.Run("ftp URL unsupported format", func(t *testing.T) { + source := &ResourceSource{URL: "ftp://some-server/file"} + _, err := svc.downloadAndDecryptResource(ctx, source, "algorithm") + require.Error(t, err) + assert.Contains(t, err.Error(), "unsupported source URL format") + }) + + t.Run("unsupported explicit source type", func(t *testing.T) { + source := &ResourceSource{Type: "s3-bucket", URL: "s3://mybucket/algo"} + _, err := svc.downloadAndDecryptResource(ctx, source, "algorithm") + require.Error(t, err) + assert.Contains(t, err.Error(), "unsupported source type: s3-bucket") + }) + + t.Run("docker:// URL inferred as oci-image routes to skopeo", func(t *testing.T) { + // This exercises the oci-image path; will fail at skopeo step + source := &ResourceSource{URL: "docker://invalid.example.com/algo:latest"} + _, err := svc.downloadAndDecryptResource(ctx, source, "algorithm") + require.Error(t, err) + // Should be a skopeo or OCI error, not an "unsupported" error + assert.NotContains(t, err.Error(), "unsupported source URL format") + }) + + t.Run("oci: URL inferred as oci-image routes to skopeo", func(t *testing.T) { + source := &ResourceSource{URL: "oci:some-local-dir"} + _, err := svc.downloadAndDecryptResource(ctx, source, "algorithm") + require.Error(t, err) + assert.NotContains(t, err.Error(), "unsupported source URL format") + }) + + t.Run("explicit oci-image type routes to skopeo", func(t *testing.T) { + source := &ResourceSource{Type: "oci-image", URL: "docker://invalid.example.com/algo:latest"} + _, err := svc.downloadAndDecryptResource(ctx, source, "algorithm") + require.Error(t, err) + assert.NotContains(t, err.Error(), "unsupported source type") + }) + + t.Run("dataset resource type with oci-image", func(t *testing.T) { + source := &ResourceSource{Type: "oci-image", URL: "docker://invalid.example.com/data:latest"} + _, err := svc.downloadAndDecryptResource(ctx, source, "dataset") + require.Error(t, err) + }) +} + +func TestDownloadAlgorithmIfRemote(t *testing.T) { + t.Run("no source configured - no-op, waits for direct upload", func(t *testing.T) { + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + // No SendEvent expected — just the no-op path + + svc := newTestAgentService(sm, eventsSvc) + svc.computation = Computation{} // Algorithm.Source == nil + + svc.downloadAlgorithmIfRemote(ReceivingAlgorithm) + assert.Nil(t, svc.runError) + sm.AssertExpectations(t) + }) + + t.Run("source set but KBS disabled - no-op", func(t *testing.T) { + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + + svc := newTestAgentService(sm, eventsSvc) + svc.computation = Computation{ + Algorithm: Algorithm{ + Source: &ResourceSource{URL: "docker://registry/algo:latest"}, + }, + KBS: KBSConfig{Enabled: false}, + } + + svc.downloadAlgorithmIfRemote(ReceivingAlgorithm) + assert.Nil(t, svc.runError) + sm.AssertExpectations(t) + }) + + t.Run("source + KBS enabled - download fails, sends RunFailed", func(t *testing.T) { + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + sm.On("SendEvent", RunFailed).Return().Once() + + svc := newTestAgentService(sm, eventsSvc) + svc.computation = Computation{ + Algorithm: Algorithm{ + Source: &ResourceSource{ + Type: "oci-image", + URL: "docker://invalid.example.com/algo:latest", + }, + }, + KBS: KBSConfig{Enabled: true, URL: "https://kbs.example.com"}, + } + + svc.downloadAlgorithmIfRemote(ReceivingAlgorithm) + assert.NotNil(t, svc.runError) + assert.Contains(t, svc.runError.Error(), "failed to download and decrypt algorithm") + sm.AssertExpectations(t) + }) + + t.Run("unsupported URL format - download fails, sends RunFailed", func(t *testing.T) { + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + sm.On("SendEvent", RunFailed).Return().Once() + + svc := newTestAgentService(sm, eventsSvc) + svc.computation = Computation{ + Algorithm: Algorithm{ + Source: &ResourceSource{ + URL: "http://unsupported-format/algo", + }, + }, + KBS: KBSConfig{Enabled: true}, + } + + svc.downloadAlgorithmIfRemote(ReceivingAlgorithm) + assert.NotNil(t, svc.runError) + sm.AssertExpectations(t) + }) +} + +func TestDownloadDatasetsIfRemote(t *testing.T) { + t.Run("no datasets with remote sources - no-op", func(t *testing.T) { + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + + svc := newTestAgentService(sm, eventsSvc) + // Dataset with no Source + dataHash := sha3.Sum256([]byte("testdata")) + svc.computation = Computation{ + Datasets: []Dataset{ + {Hash: dataHash, Filename: "data.csv"}, + }, + KBS: KBSConfig{Enabled: true}, + } + + svc.downloadDatasetsIfRemote(ReceivingData) + // No RunFailed event, no DataReceived event + sm.AssertExpectations(t) + }) + + t.Run("no datasets at all - no-op", func(t *testing.T) { + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + + svc := newTestAgentService(sm, eventsSvc) + svc.computation = Computation{ + Datasets: []Dataset{}, + KBS: KBSConfig{Enabled: true}, + } + + svc.downloadDatasetsIfRemote(ReceivingData) + sm.AssertExpectations(t) + }) + + t.Run("KBS disabled even with source - no-op", func(t *testing.T) { + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + + svc := newTestAgentService(sm, eventsSvc) + svc.computation = Computation{ + Datasets: []Dataset{ + { + Filename: "data.csv", + Source: &ResourceSource{URL: "docker://registry/data:latest"}, + }, + }, + KBS: KBSConfig{Enabled: false}, + } + + svc.downloadDatasetsIfRemote(ReceivingData) + sm.AssertExpectations(t) + }) + + t.Run("remote dataset + KBS enabled - download fails, sends RunFailed", func(t *testing.T) { + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + sm.On("SendEvent", RunFailed).Return().Once() + + svc := newTestAgentService(sm, eventsSvc) + svc.computation = Computation{ + Datasets: []Dataset{ + { + Filename: "data.csv", + Source: &ResourceSource{ + Type: "oci-image", + URL: "docker://invalid.example.com/data:latest", + }, + }, + }, + KBS: KBSConfig{Enabled: true, URL: "https://kbs.example.com"}, + } + + svc.downloadDatasetsIfRemote(ReceivingData) + sm.AssertExpectations(t) + }) + + t.Run("unsupported URL fails - sends RunFailed", func(t *testing.T) { + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + sm.On("SendEvent", RunFailed).Return().Once() + + svc := newTestAgentService(sm, eventsSvc) + svc.computation = Computation{ + Datasets: []Dataset{ + { + Filename: "data.csv", + Source: &ResourceSource{ + URL: "ftp://unsupported/data", + }, + }, + }, + KBS: KBSConfig{Enabled: true}, + } + + svc.downloadDatasetsIfRemote(ReceivingData) + sm.AssertExpectations(t) + }) +} + +func TestRunComputation(t *testing.T) { + // Helper to set up a temp working directory and restore CWD afterwards. + withTempDir := func(t *testing.T) (tmpDir string, restore func()) { + t.Helper() + origDir, err := os.Getwd() + require.NoError(t, err) + tmpDir = t.TempDir() + require.NoError(t, os.Chdir(tmpDir)) + return tmpDir, func() { _ = os.Chdir(origDir) } + } + + t.Run("algo file not found sends RunFailed", func(t *testing.T) { + _, restore := withTempDir(t) + defer restore() + + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + sm.On("SendEvent", RunFailed).Return().Once() + + svc := newTestAgentService(sm, eventsSvc) + // No algo file exists – runComputation should hit the ReadFile error path. + svc.runComputation(Running) + + assert.Error(t, svc.runError) + assert.Contains(t, svc.runError.Error(), "failed to read algo file") + sm.AssertExpectations(t) + }) + + t.Run("runner client returns error sends RunFailed", func(t *testing.T) { + _, restore := withTempDir(t) + defer restore() + + // Write a dummy algo file so ReadFile succeeds. + require.NoError(t, os.WriteFile("algo", []byte("#!/bin/sh\necho ok\n"), 0o755)) + + runnerCli := new(runnermocks.Client) + runnerCli.On("Run", mock.Anything, mock.Anything).Return((*runnerpb.RunResponse)(nil), fmt.Errorf("runner unavailable")) + + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + sm.On("SendEvent", RunFailed).Return().Once() + + svc := newTestAgentService(sm, eventsSvc) + svc.runnerClient = runnerCli + + svc.runComputation(Running) + + assert.Error(t, svc.runError) + assert.Contains(t, svc.runError.Error(), "runner unavailable") + sm.AssertExpectations(t) + }) + + t.Run("runner returns non-empty error field sends RunFailed", func(t *testing.T) { + _, restore := withTempDir(t) + defer restore() + + require.NoError(t, os.WriteFile("algo", []byte("#!/bin/sh\necho ok\n"), 0o755)) + + runnerCli := new(runnermocks.Client) + runnerCli.On("Run", mock.Anything, mock.Anything).Return(&runnerpb.RunResponse{Error: "computation crashed"}, nil) + + eventsSvc := new(mocks.Service) + eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + + sm := &smmocks.StateMachine{} + sm.On("SendEvent", RunFailed).Return().Once() + + svc := newTestAgentService(sm, eventsSvc) + svc.runnerClient = runnerCli + + svc.runComputation(Running) + + assert.Error(t, svc.runError) + assert.Contains(t, svc.runError.Error(), "computation crashed") + sm.AssertExpectations(t) + }) +} + +func TestIMAMeasurements(t *testing.T) { + t.Run("error when IMA measurements file does not exist in non-SGX environment", func(t *testing.T) { + // In a regular test environment (non-SGX), the IMA measurements file + // at /sys/kernel/security/integrity/ima/ascii_runtime_measurements won't exist. + // Verify our error handling works correctly. + origPath := ImaMeasurementsFilePath + ImaMeasurementsFilePath = "/non/existent/path" + defer func() { ImaMeasurementsFilePath = origPath }() + + eventsSvc := new(mocks.Service) + eventsSvc.On("SendEvent", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + sm := &smmocks.StateMachine{} + + svc := newTestAgentService(sm, eventsSvc) + + data, pcr10, err := svc.IMAMeasurements(context.Background()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "error reading Linux IMA measurements file") + assert.Nil(t, data) + assert.Nil(t, pcr10) + }) + + t.Run("successful reading of IMA measurements", func(t *testing.T) { + tempFile := filepath.Join(t.TempDir(), "ima_measurements") + content := []byte("10 sha1:0000000000000000000000000000000000000000 ima-ng sha256:0000000000000000000000000000000000000000000000000000000000000000 /usr/bin/python3\n") + err := os.WriteFile(tempFile, content, 0o644) + require.NoError(t, err) + vtpm.ExternalTPM = &vtpm.DummyRWC{} + + origPath := ImaMeasurementsFilePath + ImaMeasurementsFilePath = tempFile + defer func() { ImaMeasurementsFilePath = origPath }() + + eventsSvc := new(mocks.Service) + eventsSvc.On("SendEvent", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + sm := &smmocks.StateMachine{} + svc := newTestAgentService(sm, eventsSvc) + + data, pcr10, err := svc.IMAMeasurements(context.Background()) + assert.NoError(t, err) + assert.Equal(t, content, data) + assert.NotEmpty(t, pcr10) + }) +} + +func TestDownloadAlgorithmIfRemote_Success(t *testing.T) { + // Skip this test in short mode as it might involve more setup if we were using real OCI + if testing.Short() { + t.Skip("skipping in short mode") + } + + origDir, _ := os.Getwd() + tmpDir := t.TempDir() + require.NoError(t, os.Chdir(tmpDir)) + defer func() { require.NoError(t, os.Chdir(origDir)) }() + + eventsSvc := new(mocks.Service) + eventsSvc.On("SendEvent", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + sm := &smmocks.StateMachine{} + sm.On("SendEvent", AlgorithmReceived).Return().Once() + + mockOCI := new(MockOCIClient) + algoContent := []byte("print('hello')") + mockOCI.On("PullAndDecrypt", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + destDir := args.String(2) + setupMinimalOCI(t, destDir, "main.py", string(algoContent)) + }).Return(nil) + + svc := newTestAgentService(sm, eventsSvc) + svc.ociClient = mockOCI + + algoContent = []byte("print('hello')") + algoHash := sha3.Sum256(algoContent) + + svc.computation = Computation{ + Algorithm: Algorithm{ + Hash: algoHash, + AlgoType: "python", + Source: &ResourceSource{ + Type: "oci-image", + URL: "docker://test/image", + }, + }, + KBS: KBSConfig{Enabled: true}, + } + + // We need to bypass oci.ExtractAlgorithm by manually creating what it would create + // OR use a real-enough looking OCI layout. + // Since we can't easily mock oci.ExtractAlgorithm, we'll try to provide a minimal OCI layout + // so that oci.ExtractAlgorithm doesn't fail. + + svc.downloadAlgorithmIfRemote(ReceivingAlgorithm) + + assert.Nil(t, svc.runError) + assert.True(t, svc.algoReceived) + sm.AssertExpectations(t) + mockOCI.AssertExpectations(t) +} + +func setupMinimalOCI(t *testing.T, ociDir, filename, content string) { + t.Helper() + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + layerPath := filepath.Join(blobsDir, "layer123") + layerFile, err := os.Create(layerPath) + require.NoError(t, err) + + gw := gzip.NewWriter(layerFile) + tw := tar.NewWriter(gw) + + hdr := &tar.Header{ + Name: filename, + Mode: 0o755, + Size: int64(len(content)), + } + require.NoError(t, tw.WriteHeader(hdr)) + _, err = tw.Write([]byte(content)) + require.NoError(t, err) + + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) + require.NoError(t, layerFile.Close()) + + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:layer123"}}, + } + manifestData, err := json.Marshal(manifest) + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), manifestData, 0o644)) + + index := oci.OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: len(manifestData)}}, + } + indexData, err := json.Marshal(index) + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) +} + +func TestDownloadDatasetsIfRemote_Success(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + origDir, _ := os.Getwd() + tmpDir := t.TempDir() + require.NoError(t, os.Chdir(tmpDir)) + defer func() { require.NoError(t, os.Chdir(origDir)) }() + + eventsSvc := new(mocks.Service) + eventsSvc.On("SendEvent", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() + sm := &smmocks.StateMachine{} + sm.On("SendEvent", DataReceived).Return().Once() + + mockOCI := new(MockOCIClient) + dataContent := []byte("a,b,c\n1,2,3") + mockOCI.On("PullAndDecrypt", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + destDir := args.String(2) + setupMinimalOCI(t, destDir, "data.csv", string(dataContent)) + }).Return(nil) + + svc := newTestAgentService(sm, eventsSvc) + svc.ociClient = mockOCI + + dataContent = []byte("a,b,c\n1,2,3") + dataHash := sha3.Sum256(dataContent) + + svc.computation = Computation{ + Datasets: []Dataset{ + { + Filename: "data.csv", + Hash: dataHash, + Source: &ResourceSource{ + Type: "oci-image", + URL: "docker://test/image", + }, + }, + }, + KBS: KBSConfig{Enabled: true}, + } + + err := os.MkdirAll(algorithm.DatasetsDir, 0o755) + require.NoError(t, err) + + svc.downloadDatasetsIfRemote(ReceivingData) + + assert.Nil(t, svc.runError) + assert.Len(t, svc.computation.Datasets, 0) + sm.AssertExpectations(t) + mockOCI.AssertExpectations(t) +} diff --git a/cli/attestation.bin b/cli/attestation.bin new file mode 100644 index 00000000..e69de29b diff --git a/cli/checksum_test.go b/cli/checksum_test.go index 35434f06..91568743 100644 --- a/cli/checksum_test.go +++ b/cli/checksum_test.go @@ -131,7 +131,7 @@ func TestManifestChecksum(t *testing.T) { "name": "Example Computation", "description": "This is an example computation" }`, - expectedSum: "a99683e4d22ba54cefa51aa49fb2e97a92b828c088395992ddff16a6236f3299", + expectedSum: "4ff220c22b2bdf6d5bb4c32dc0f24b5183cfef9b8200dfdf6109c230c8c90394", }, { name: "Invalid JSON", diff --git a/cli/manager.go b/cli/manager.go index 85b406a1..f37c3314 100644 --- a/cli/manager.go +++ b/cli/manager.go @@ -22,13 +22,18 @@ const ( ) var ( - agentCVMServerUrl string - agentCVMServerCA string - agentCVMClientKey string - agentCVMClientCrt string - agentCVMCaUrl string - agentLogLevel string - ttl time.Duration + agentCVMServerUrl string + agentCVMServerCA string + agentCVMClientKey string + agentCVMClientCrt string + agentCVMCaUrl string + agentLogLevel string + ttl time.Duration + awsAccessKeyId string + awsSecretAccessKey string + awsEndpointUrl string + awsRegion string + aaKbsParams string ) func (c *CLI) NewCreateVMCmd() *cobra.Command { @@ -59,6 +64,12 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command { createReq.AgentCvmServerUrl = agentCVMServerUrl createReq.AgentLogLevel = agentLogLevel createReq.AgentCvmCaUrl = agentCVMCaUrl + createReq.AwsAccessKeyId = awsAccessKeyId + createReq.AwsSecretAccessKey = awsSecretAccessKey + createReq.AwsEndpointUrl = awsEndpointUrl + createReq.AwsEndpointUrl = awsEndpointUrl + createReq.AwsRegion = awsRegion + createReq.AaKbsParams = aaKbsParams if ttl > 0 { createReq.Ttl = ttl.String() @@ -83,6 +94,11 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command { cmd.Flags().StringVar(&agentCVMCaUrl, caUrl, "", "CVM CA service URL") cmd.Flags().StringVar(&agentLogLevel, logLevel, "", "Agent Log level") cmd.Flags().DurationVar(&ttl, ttlFlag, 0, "TTL for the VM") + cmd.Flags().StringVar(&awsAccessKeyId, "aws-access-key-id", "", "AWS Access Key ID for S3/MinIO") + cmd.Flags().StringVar(&awsSecretAccessKey, "aws-secret-access-key", "", "AWS Secret Access Key for S3/MinIO") + cmd.Flags().StringVar(&awsEndpointUrl, "aws-endpoint-url", "", "AWS Endpoint URL (for MinIO or custom S3)") + cmd.Flags().StringVar(&awsRegion, "aws-region", "", "AWS Region") + cmd.Flags().StringVar(&aaKbsParams, "aa-kbs-params", "", "Attestation Agent KBS Parameters (e.g. protocol=http,type=kbs,url=http://... or just type=sample)") if err := cmd.MarkFlagRequired(serverURL); err != nil { printError(cmd, "Error marking flag as required: %v ❌ ", err) return cmd diff --git a/cmd/attestation-service/fetch_raw_evidence.go b/cmd/attestation-service/fetch_raw_evidence.go new file mode 100644 index 00000000..ca243647 --- /dev/null +++ b/cmd/attestation-service/fetch_raw_evidence.go @@ -0,0 +1,72 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 +package main + +import ( + "context" + "encoding/hex" + "fmt" + + attestationpb "github.com/ultravioletrs/cocos/internal/proto/attestation/v1" +) + +func (s *service) FetchRawEvidence(ctx context.Context, req *attestationpb.AttestationRequest) (*attestationpb.RawEvidenceResponse, error) { + s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Received raw evidence request with platform type: %v (%d)", + req.PlatformType, req.PlatformType)) + + var binaryReport []byte + var err error + + // Get binary attestation report based on platform type + switch req.PlatformType { + case attestationpb.PlatformType_PLATFORM_TYPE_SNP, attestationpb.PlatformType_PLATFORM_TYPE_TDX: + var reportData [64]byte + copy(reportData[:], req.ReportData) + binaryReport, err = s.provider.TeeAttestation(reportData[:]) + case attestationpb.PlatformType_PLATFORM_TYPE_VTPM: + var nonce [32]byte + copy(nonce[:], req.Nonce) + binaryReport, err = s.provider.VTpmAttestation(nonce[:]) + case attestationpb.PlatformType_PLATFORM_TYPE_SNP_VTPM: + var reportData [64]byte + copy(reportData[:], req.ReportData) + var nonce [32]byte + copy(nonce[:], req.Nonce) + binaryReport, err = s.provider.Attestation(reportData[:], nonce[:]) + case attestationpb.PlatformType_PLATFORM_TYPE_UNSPECIFIED: + // Generate sample attestation for testing in non-TEE environments + // This uses the underlying provider (EmptyProvider or CC Attestation Agent) + s.logger.Warn("fetching sample attestation for PLATFORM_TYPE_UNSPECIFIED") + s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Fetching sample/unspecified attestation: reportData_len=%d", + len(req.ReportData))) + + // Use TeeAttestation interface - for EmptyProvider this generates dynamic JSON sample quote + // For CC AA, this calls the agent to get a real quote (if supported) + var reportData [64]byte + copy(reportData[:], req.ReportData) + binaryReport, err = s.provider.TeeAttestation(reportData[:]) + if err != nil { + return nil, fmt.Errorf("failed to fetch sample attestation: %w", err) + } + + s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Sample attestation fetched: binaryReport_len=%d", + len(binaryReport))) + default: + return nil, fmt.Errorf("unsupported platform type") + } + + if err != nil { + return nil, err + } + + // Debug logging: show evidence details + previewLen := len(binaryReport) + if previewLen > 200 { + previewLen = 200 + } + s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Returning raw evidence: total_len=%d, preview_hex=%s", + len(binaryReport), hex.EncodeToString(binaryReport[:previewLen]))) + s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Evidence as string preview: %s", string(binaryReport[:previewLen]))) + + return &attestationpb.RawEvidenceResponse{Evidence: binaryReport}, nil +} diff --git a/cmd/attestation-service/main.go b/cmd/attestation-service/main.go index 7aaeaf8f..7e7d87f9 100644 --- a/cmd/attestation-service/main.go +++ b/cmd/attestation-service/main.go @@ -14,6 +14,9 @@ import ( mglog "github.com/absmach/supermq/logger" "github.com/caarlos0/env/v11" + "github.com/ultravioletrs/cocos/agent/cvms" + logpb "github.com/ultravioletrs/cocos/agent/log" + agentlogger "github.com/ultravioletrs/cocos/internal/logger" attestationpb "github.com/ultravioletrs/cocos/internal/proto/attestation/v1" "github.com/ultravioletrs/cocos/pkg/attestation" "github.com/ultravioletrs/cocos/pkg/attestation/azure" @@ -21,6 +24,7 @@ import ( "github.com/ultravioletrs/cocos/pkg/attestation/eat" "github.com/ultravioletrs/cocos/pkg/attestation/tdx" "github.com/ultravioletrs/cocos/pkg/attestation/vtpm" + logclient "github.com/ultravioletrs/cocos/pkg/clients/grpc/log" "golang.org/x/sync/errgroup" "google.golang.org/grpc" ) @@ -74,7 +78,47 @@ func main() { return } - logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})) + // Setup log forwarding to CVMS (same pattern as agent) + logQueue := make(chan *cvms.ClientStreamMessage, 1000) + handler := agentlogger.NewProtoHandler(os.Stdout, &slog.HandlerOptions{Level: level}, logQueue) + logger := slog.New(handler) + + logger.Info("[ATTESTATION-SERVICE] Starting up - log forwarding enabled") + + // Connect to log client for gRPC forwarding + logClient, err := logclient.NewClient("/run/cocos/log.sock") + if err != nil { + logger.Warn(fmt.Sprintf("failed to create log client: %s. Logging will be local only until service is available.", err)) + } else { + logger.Info("[ATTESTATION-SERVICE] Successfully connected to log client") + defer logClient.Close() + } + + // Start log forwarding goroutine + g.Go(func() error { + for { + select { + case <-ctx.Done(): + return nil + case msg := <-logQueue: + if logClient == nil { + continue + } + switch m := msg.Message.(type) { + case *cvms.ClientStreamMessage_AgentLog: + err := logClient.SendLog(ctx, &logpb.LogEntry{ + Message: m.AgentLog.Message, + ComputationId: m.AgentLog.ComputationId, + Level: m.AgentLog.Level, + Timestamp: m.AgentLog.Timestamp, + }) + if err != nil { + logger.Error("failed to send log", "error", err) + } + } + } + } + }) var provider attestation.Provider ccPlatform := attestation.CCPlatform() @@ -88,10 +132,18 @@ func main() { azure.InitializeDefaultMAAVars(azureConfig) // Try to use CC attestation-agent if configured + logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] CC AA configuration: enabled=%v, address=%s", cfg.UseCCAttestationAgent, cfg.CCAgentAddress)) if cfg.UseCCAttestationAgent { logger.Info(fmt.Sprintf("attempting to use CC attestation-agent at %s", cfg.CCAgentAddress)) ccProvider, err := ccaa.NewProvider(cfg.CCAgentAddress) if err != nil { + // For NoCC/sample platform, AA is REQUIRED when configured + // Don't fall back to EmptyProvider - AA generates correct KBS format + if ccPlatform == attestation.NoCC { + logger.Error(fmt.Sprintf("CC AA is required for sample attestation but connection failed: %s", err)) + exitCode = 1 + return + } logger.Warn(fmt.Sprintf("failed to connect to CC attestation-agent: %s, falling back to direct providers", err)) } else { logger.Info("successfully connected to CC attestation-agent") @@ -113,10 +165,25 @@ func main() { provider = tdx.NewProvider() case attestation.NoCC: logger.Info("TEE device not found") + if cfg.UseCCAttestationAgent { + // AA was configured but connection failed - already handled above + logger.Error("[ATTESTATION-SERVICE] AA required for sample attestation but not available") + exitCode = 1 + return + } + // Only use EmptyProvider if AA is explicitly NOT configured + logger.Warn("[ATTESTATION-SERVICE] Using EmptyProvider for sample attestation (AA not configured)") provider = &attestation.EmptyProvider{} } } + // Log which provider is being used + if provider != nil { + logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Final provider selected: %T", provider)) + } else { + logger.Error("[ATTESTATION-SERVICE] No provider configured!") + } + if ccPlatform == attestation.SNP || ccPlatform == attestation.SNPvTPM { if err := vtpm.FetchSEVCertificates(uint(cfg.Vmpl)); err != nil { logger.Error(fmt.Sprintf("failed to fetch certificates: %s", err)) @@ -208,6 +275,10 @@ type service struct { } func (s *service) FetchAttestation(ctx context.Context, req *attestationpb.AttestationRequest) (*attestationpb.AttestationResponse, error) { + // Debug: log incoming request + s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Received attestation request with platform type: %v (%d)", + req.PlatformType, req.PlatformType)) + var binaryReport []byte var err error var platformType attestation.PlatformType @@ -231,6 +302,25 @@ func (s *service) FetchAttestation(ctx context.Context, req *attestationpb.Attes copy(nonce[:], req.Nonce) binaryReport, err = s.provider.Attestation(reportData[:], nonce[:]) platformType = attestation.SNPvTPM + case attestationpb.PlatformType_PLATFORM_TYPE_UNSPECIFIED: + // Generate sample attestation for testing in non-TEE environments + s.logger.Warn("generating sample attestation for PLATFORM_TYPE_UNSPECIFIED - this should only be used for testing") + s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Generating sample attestation: reportData_len=%d, nonce_len=%d", + len(req.ReportData), len(req.Nonce))) + + // Create a simple sample report that includes the nonce/report data + var reportData [64]byte + copy(reportData[:], req.ReportData) + var nonce [32]byte + copy(nonce[:], req.Nonce) + + // Combine report data and nonce into a simple binary report + binaryReport = make([]byte, 0, 96) + binaryReport = append(binaryReport, reportData[:]...) + binaryReport = append(binaryReport, nonce[:]...) + platformType = attestation.NoCC + s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Sample attestation generated: binaryReport_len=%d, platformType=%v (%d)", + len(binaryReport), platformType, platformType)) default: return nil, fmt.Errorf("unsupported platform type") } diff --git a/coverage.out b/coverage.out new file mode 100644 index 00000000..a182f350 --- /dev/null +++ b/coverage.out @@ -0,0 +1,966 @@ +mode: set +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:82.32,90.27 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:90.27,91.20 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:91.20,93.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:95.2,95.13 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:98.37,100.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:100.16,102.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:103.2,105.13 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:108.35,110.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:112.24,114.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:114.16,116.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:117.2,119.13 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:122.23,123.19 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:123.19,125.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:127.2,134.16 6 0 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:134.16,136.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:137.2,139.38 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:139.38,141.17 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:141.17,143.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:145.3,145.23 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:148.2,148.14 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:151.34,153.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:153.16,155.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/attestation.go:156.2,158.13 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/emptyprovider.go:16.88,19.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/emptyprovider.go:21.73,25.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/emptyprovider.go:27.75,29.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/emptyprovider.go:31.77,33.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:23.79,28.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:31.65,40.16 6 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:40.16,42.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:45.2,52.16 6 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:52.16,54.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:57.2,57.59 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:57.59,59.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:62.2,63.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:63.16,65.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:67.2,67.20 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/cbor_encoder.go:71.99,74.2 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:23.54,27.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:30.60,32.18 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:32.18,34.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:35.2,35.28 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:39.31,41.21 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:41.21,43.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:44.2,44.88 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:48.69,54.24 4 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:54.24,56.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:56.8,58.101 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:58.101,60.60 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:60.60,62.5 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:63.4,63.27 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:66.2,66.16 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:66.16,68.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:70.2,70.18 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:70.18,72.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:74.2,74.30 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:78.64,81.49 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:81.49,84.55 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:84.55,86.4 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:87.3,87.21 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:91.2,91.24 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:91.24,93.17 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:93.17,95.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:97.3,97.51 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:97.51,99.4 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:103.2,104.60 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:104.60,106.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:108.2,108.20 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:112.84,115.2 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:118.79,121.2 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:124.75,127.2 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/decoder.go:130.51,143.2 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:115.107,116.33 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:116.33,118.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:119.2,128.76 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:128.76,130.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:132.2,132.20 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:136.107,137.22 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:138.44,139.42 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:140.23,141.42 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:142.24,143.43 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:144.25,145.44 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:146.10,148.13 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:153.72,154.22 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:155.23,156.15 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:157.23,158.15 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:159.24,160.16 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:161.27,162.20 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:163.25,164.17 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:165.24,166.16 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:167.10,168.19 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:175.32,176.18 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:176.18,179.3 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:180.2,180.25 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:180.25,182.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:183.2,183.20 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/eat.go:183.20,185.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:23.63,24.39 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:24.39,26.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:29.2,30.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:30.16,32.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:35.2,62.12 12 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:66.63,69.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:69.16,71.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:73.2,74.9 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:74.9,76.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:78.2,83.20 5 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:83.20,85.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:86.2,86.20 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:86.20,88.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:89.2,89.20 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:89.20,91.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:92.2,92.20 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:92.20,94.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:96.2,114.36 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:114.36,116.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:117.2,122.12 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:126.64,139.2 4 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/extractor.go:142.65,150.2 4 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:23.77,28.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:31.64,43.16 7 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:43.16,45.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:47.2,47.25 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:56.67,57.22 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:57.22,59.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:60.2,60.59 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:64.61,65.21 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:65.21,67.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:68.2,68.58 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:72.62,74.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:77.49,79.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:82.50,84.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:87.61,89.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:92.98,95.2 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/jwt_encoder.go:98.54,100.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:12.78,13.19 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:13.19,15.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:18.2,21.53 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:21.53,22.24 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:23.20,24.30 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:24.30,26.5 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:27.23,28.37 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:28.37,30.5 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:31.24,32.33 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:32.33,34.5 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:35.15,36.29 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:36.29,38.5 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:43.2,43.58 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:43.58,45.62 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:45.62,47.4 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:51.2,51.26 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:51.26,52.43 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:52.43,54.4 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/eat/validator.go:57.2,57.12 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:26.91,27.19 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:27.19,29.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:31.2,36.8 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:39.60,50.34 9 1 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:50.34,52.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:54.2,54.28 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:57.34,58.42 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:58.42,60.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:62.2,62.45 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:62.45,64.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:66.2,66.12 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/cmdconfig.go:70.88,72.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:24.50,26.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:26.16,28.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:30.2,37.16 5 0 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:37.16,40.3 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:42.2,46.8 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:50.34,51.19 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:51.19,53.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:54.2,54.12 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:59.70,66.16 4 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:66.16,68.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:70.2,70.27 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:75.66,82.16 4 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:82.16,84.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:86.2,86.27 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:91.81,101.16 5 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:101.16,103.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:105.2,105.27 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:111.72,119.16 4 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:119.16,124.25 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:124.25,126.4 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:127.3,127.36 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/ccaa/provider.go:130.2,130.24 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/azure.go:17.96,24.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/azure.go:26.50,31.2 4 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/azure.go:33.70,34.17 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/azure.go:34.17,36.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/azure.go:37.2,37.18 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/azure.go:37.18,39.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/azure.go:40.2,40.20 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/azure.go:40.20,42.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:40.41,42.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:44.82,49.16 4 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:49.16,51.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:53.2,54.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:54.16,56.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:58.2,59.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:59.16,61.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:63.2,66.29 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:69.67,74.16 4 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:74.16,76.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:78.2,78.30 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:81.69,83.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:83.16,85.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:87.2,87.29 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:90.76,92.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:92.16,94.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:96.2,96.19 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:104.57,114.2 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:116.95,117.19 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:117.19,119.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:120.2,123.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:126.77,128.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:128.16,130.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:132.2,132.82 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:135.79,137.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:139.93,145.16 5 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:145.16,147.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:149.2,150.84 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:150.84,152.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:154.2,154.12 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:158.87,161.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:161.16,163.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:166.2,166.67 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:169.51,171.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:173.99,175.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:175.16,177.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:179.2,180.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:180.9,182.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:184.2,185.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:185.9,187.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:189.2,190.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:190.16,192.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:194.2,195.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:195.9,197.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:198.2,199.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:199.16,201.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:203.2,204.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:204.9,206.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:207.2,208.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:208.16,210.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:212.2,213.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:213.9,215.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:217.2,218.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:218.9,220.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:222.2,223.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:223.9,225.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:227.2,228.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:228.9,230.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:232.2,241.16 3 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:241.16,243.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:245.2,246.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:246.9,248.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:250.2,251.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:251.9,253.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:254.2,255.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:255.16,257.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:259.2,260.9 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:260.9,262.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:263.2,264.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:264.16,266.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:268.2,287.8 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:290.83,292.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:292.16,294.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:295.2,295.27 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:298.58,300.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:300.16,302.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:304.2,305.12 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:305.12,307.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:309.2,310.18 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:310.18,312.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:314.2,315.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:315.16,317.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:319.2,320.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:320.16,322.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/azure/snp.go:324.2,324.20 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:19.25,23.19 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:23.19,23.49 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:25.2,25.13 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:37.71,39.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:42.74,45.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:45.19,46.45 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:49.2,51.69 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:51.69,53.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:54.2,54.60 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:54.60,56.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:56.8,57.24 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:57.24,59.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:61.2,61.59 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:61.59,63.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:63.8,65.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:66.2,66.15 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:76.99,78.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:80.104,81.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:81.40,83.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:83.21,85.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:86.3,88.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:90.2,90.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:93.103,96.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:98.129,101.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:104.48,107.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:107.19,108.46 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:111.2,112.53 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:112.53,114.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:114.8,116.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:117.2,117.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:126.79,128.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:130.89,131.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:131.40,133.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:134.2,134.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:137.91,140.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks/measurementprovider.go:142.104,145.2 2 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:58.74,59.56 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:59.56,61.3 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:63.2,64.16 2 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:64.16,66.3 1 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:68.2,69.16 2 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:69.16,71.3 1 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:74.2,77.16 3 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:77.16,79.3 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:81.2,81.23 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:86.110,87.34 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:87.34,89.3 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:92.2,93.16 2 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:93.16,95.3 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:97.2,98.16 2 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:98.16,100.3 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:103.2,110.16 7 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:110.16,112.3 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:115.2,116.16 2 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:116.16,118.3 1 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:121.2,123.41 3 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:123.41,125.3 1 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:128.2,129.16 2 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:129.16,131.3 1 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:134.2,141.16 2 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:141.16,143.3 1 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:146.2,150.23 4 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:154.56,155.52 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:155.52,157.3 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:159.2,160.16 2 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:160.16,162.3 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:164.2,167.26 4 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:167.26,169.3 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:171.2,172.26 2 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:172.26,173.27 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:173.27,176.27 3 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:176.27,178.5 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:179.4,183.16 4 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:188.2,189.25 2 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:189.25,190.28 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:190.28,192.4 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:196.2,197.26 2 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:197.26,199.3 1 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:201.2,201.23 1 0 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:205.70,207.56 2 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:207.56,209.3 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:210.2,210.23 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:214.26,215.19 1 1 +github.com/ultravioletrs/cocos/pkg/crypto/decrypt.go:215.19,217.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:39.41,41.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:43.82,45.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:47.67,48.21 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:48.21,50.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:52.2,52.25 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:52.25,54.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:56.2,57.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:57.16,59.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:61.2,61.54 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:64.69,66.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:68.76,70.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:76.41,85.2 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:87.77,88.19 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:88.19,90.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:91.2,93.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:96.77,97.21 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:97.21,99.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:101.2,102.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:102.16,104.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:106.2,107.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:107.16,109.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:111.2,117.57 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:117.57,119.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:121.2,122.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:122.16,124.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:126.2,126.57 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:126.57,128.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:130.2,130.12 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:133.79,135.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:137.93,139.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:141.51,143.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:146.87,149.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:149.16,151.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:154.2,154.67 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:157.84,159.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:159.16,161.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:163.2,163.64 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:163.64,165.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/tdx/tdx.go:167.2,167.12 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:35.106,39.16 3 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:39.16,41.3 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:43.2,44.58 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:44.58,46.3 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:48.2,48.31 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:48.31,50.3 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:53.2,58.16 4 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:58.16,60.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:62.2,67.64 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:67.64,69.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:72.2,76.49 3 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:76.49,82.25 4 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:82.25,84.4 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:86.3,86.17 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:86.17,88.12 2 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:91.3,91.21 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:91.21,93.4 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:96.2,96.95 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:100.110,103.16 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:103.16,105.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:106.2,110.16 3 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:110.16,112.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:113.2,121.6 5 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:121.6,123.20 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:123.20,124.9 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:126.3,126.17 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:126.17,128.4 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:130.3,133.37 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:133.37,134.12 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:137.3,143.22 4 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:143.22,147.79 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:147.79,148.13 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:151.4,153.71 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:153.71,155.5 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:157.4,158.18 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:158.18,160.5 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:162.4,162.57 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:162.57,165.5 2 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:166.4,168.14 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:168.14,170.5 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:175.2,175.38 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:179.44,190.36 5 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:190.36,191.40 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:191.40,193.4 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:197.2,197.38 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:197.38,198.40 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:198.40,200.4 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:203.2,203.14 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:207.64,212.16 3 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:212.16,214.3 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:216.2,217.58 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:217.58,219.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:221.2,221.31 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:221.31,223.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:226.2,231.16 4 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:231.16,233.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:235.2,240.64 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:240.64,242.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:244.2,248.49 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:248.49,253.17 4 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:253.17,255.4 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:258.2,258.28 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:258.28,260.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:262.2,262.26 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:266.74,268.16 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:268.16,270.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:271.2,274.16 3 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:274.16,276.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:277.2,282.6 4 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:282.6,284.20 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:284.20,285.9 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:287.3,287.17 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:287.17,289.4 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:291.3,291.37 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:291.37,292.12 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:296.3,296.30 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:296.30,299.79 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:299.79,300.13 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:303.4,305.71 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:305.71,307.5 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:309.4,310.18 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:310.18,312.5 1 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:314.4,314.57 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:314.57,317.5 2 0 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:318.4,320.55 2 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:324.2,324.28 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:328.39,333.31 3 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:333.31,334.40 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:334.40,336.4 1 1 +github.com/ultravioletrs/cocos/pkg/oci/extract.go:339.2,339.14 1 1 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:32.61,35.16 2 1 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:35.16,37.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:40.2,40.52 1 1 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:40.52,42.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:44.2,47.8 1 1 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:51.105,53.52 1 1 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:53.52,55.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:57.2,60.22 2 1 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:60.22,62.3 1 0 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:65.2,85.16 9 1 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:85.16,87.3 1 1 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:89.2,89.12 1 0 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:93.94,101.16 5 1 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:101.16,103.3 1 1 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:107.2,109.8 1 0 +github.com/ultravioletrs/cocos/pkg/oci/skopeo.go:113.62,115.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:19.14,23.19 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:23.19,23.49 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:25.2,25.13 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:37.49,39.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:42.87,45.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:45.19,46.53 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:49.2,51.77 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:51.77,53.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:54.2,54.68 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:54.68,56.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:56.8,57.24 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:57.24,59.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:61.2,61.67 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:61.67,63.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:63.8,65.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:66.2,66.15 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:77.114,79.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:81.114,82.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:82.40,84.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:84.21,86.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:87.3,88.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:88.21,90.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:91.3,94.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:96.2,96.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:99.97,102.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:104.139,107.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:110.81,113.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:113.19,114.63 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:117.2,119.69 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:119.69,121.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:122.2,122.60 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:122.60,124.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:124.8,125.24 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:125.24,127.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:129.2,129.59 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:129.59,131.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:131.8,133.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:134.2,134.15 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:144.113,146.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:148.118,149.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:149.40,151.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:151.21,153.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:154.3,156.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:158.2,158.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:161.117,164.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:166.143,169.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:172.72,175.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:175.19,176.56 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:179.2,181.69 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:181.69,183.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:184.2,184.60 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:184.60,186.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:186.8,187.24 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:187.24,189.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:191.2,191.59 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:191.59,193.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:193.8,195.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:196.2,196.15 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:206.97,208.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:210.102,211.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:211.40,213.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:213.21,215.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:216.3,218.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:220.2,220.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:223.103,226.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:228.127,231.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:234.74,237.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:237.19,238.57 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:241.2,243.69 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:243.69,245.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:246.2,246.60 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:246.60,248.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:248.8,249.24 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:249.24,251.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:253.2,253.59 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:253.59,255.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:255.8,257.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:258.2,258.15 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:268.100,270.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:272.105,273.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:273.40,275.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:275.21,277.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:278.3,280.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:282.2,282.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:285.105,288.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/provider.go:290.130,293.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:19.14,23.19 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:23.19,23.49 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:25.2,25.13 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:37.49,39.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:42.56,45.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:45.19,46.54 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:49.2,50.59 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:50.59,52.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:52.8,54.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:55.2,55.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:65.89,67.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:69.94,70.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:70.40,72.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:72.21,74.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:75.3,77.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:79.2,79.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:82.85,85.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:87.109,90.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:93.82,96.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:96.19,97.61 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:100.2,101.67 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:101.67,103.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:103.8,105.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:106.2,106.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:117.127,119.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:121.127,122.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:122.40,124.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:124.21,126.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:127.3,128.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:128.21,130.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:131.3,134.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:136.2,136.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:139.99,142.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:144.142,147.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:150.84,153.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:153.19,154.62 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:157.2,158.67 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:158.67,160.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:160.8,162.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:163.2,163.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:174.130,176.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:178.130,179.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:179.40,181.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:181.21,183.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:184.3,185.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:185.21,187.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:188.3,191.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:193.2,193.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:196.101,199.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:201.145,204.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:207.98,210.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:210.19,211.59 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:214.2,215.75 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:215.75,217.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:217.8,219.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:220.2,220.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:232.146,234.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:236.141,237.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:237.40,239.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:239.21,241.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:242.3,243.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:243.21,245.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:246.3,247.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:247.21,249.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:250.3,254.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:256.2,256.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:259.95,262.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:264.156,267.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:270.92,273.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:273.19,274.51 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:277.2,278.75 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:278.75,280.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:280.8,282.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:283.2,283.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:295.132,297.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:299.127,300.40 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:300.40,302.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:302.21,304.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:305.3,306.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:306.21,308.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:309.3,310.21 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:310.21,312.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:313.3,317.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:319.2,319.11 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:322.79,325.2 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/mocks/verifier.go:327.142,330.2 2 0 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation-agent/client.go:30.48,33.36 2 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation-agent/client.go:33.36,35.3 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation-agent/client.go:35.8,37.3 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation-agent/client.go:39.2,40.16 2 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation-agent/client.go:40.16,42.3 1 0 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation-agent/client.go:44.2,47.8 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation-agent/client.go:50.32,52.2 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation-agent/client.go:56.82,65.16 5 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation-agent/client.go:65.16,67.3 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation-agent/client.go:69.2,69.24 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:26.51,28.16 2 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:28.16,30.3 1 0 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:32.2,35.8 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:38.32,40.2 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:42.74,43.28 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:43.28,45.3 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:48.2,49.52 2 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:49.52,54.17 4 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:54.17,56.4 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:59.3,59.29 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:59.29,63.4 2 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:67.2,70.12 4 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:73.78,74.28 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:74.28,76.3 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:79.2,80.52 2 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:80.52,85.17 4 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:85.17,87.4 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:90.3,90.29 1 0 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:90.29,94.4 2 0 +github.com/ultravioletrs/cocos/pkg/clients/grpc/log/client.go:98.2,101.12 4 0 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:28.51,30.16 2 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:30.16,32.3 1 0 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:34.2,37.8 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:40.32,42.2 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:44.141,49.17 4 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:50.23,51.63 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:52.23,53.63 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:54.24,55.64 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:56.27,57.68 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:58.10,59.71 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:63.2,73.16 4 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:73.16,75.3 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:77.2,77.27 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:81.141,86.17 4 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:87.23,88.63 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:89.23,90.63 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:91.24,92.64 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:93.27,94.68 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:95.10,96.71 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:99.2,109.16 4 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:109.16,111.3 1 0 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:113.2,113.27 1 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:116.85,125.16 5 1 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:125.16,127.3 1 0 +github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation/client.go:129.2,129.24 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:31.80,32.24 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:32.24,34.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:36.2,37.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:37.16,39.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:41.2,41.56 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:41.56,43.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:45.2,46.44 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:49.113,51.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:51.16,53.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:55.2,56.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:56.16,58.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:60.2,63.16 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:63.16,65.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:67.2,68.76 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:68.76,70.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:72.2,73.89 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:73.89,75.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:77.2,77.25 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:80.123,90.2 8 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:92.75,94.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:94.16,96.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:98.2,99.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:99.16,101.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:103.2,106.16 3 1 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:106.16,108.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/gcp/gcp.go:110.2,110.18 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/dummy.go:8.48,11.19 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/dummy.go:11.19,13.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/dummy.go:14.2,14.15 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/dummy.go:18.49,21.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/dummy.go:23.34,25.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:51.87,55.18 3 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:55.18,58.3 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:59.2,59.66 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:59.66,61.17 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:61.17,63.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:65.3,66.48 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:66.48,68.63 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:68.63,70.5 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:72.4,73.53 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:77.2,77.12 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:81.79,83.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:83.16,85.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:87.2,87.31 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:87.31,89.59 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:89.59,91.4 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:93.3,95.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:96.8,98.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:100.2,106.67 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:106.67,108.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:110.2,110.68 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:110.68,112.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:114.2,114.12 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:118.81,120.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:120.16,122.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:124.2,124.68 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:124.68,126.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:128.2,128.12 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:132.69,134.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:137.124,144.26 3 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:144.26,146.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:148.2,148.52 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:152.87,155.57 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:155.57,157.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:159.2,159.59 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:159.59,161.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:163.2,163.12 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:167.77,171.16 3 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:171.16,173.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:175.2,175.32 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:175.32,177.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:178.2,181.16 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:181.16,183.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:185.2,190.16 5 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:190.16,192.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:194.2,195.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:195.16,197.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:199.2,208.16 8 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:208.16,210.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:212.2,212.20 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:216.73,217.17 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:218.26,219.45 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:220.26,221.45 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:222.10,223.47 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:228.34,230.54 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:230.54,233.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:234.2,234.72 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:238.44,242.16 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:242.16,244.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:246.2,246.32 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:246.32,248.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:250.2,251.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:251.16,253.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:255.2,256.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:256.16,258.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:260.2,270.16 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:270.16,272.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:274.2,287.16 10 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:287.16,289.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:290.2,292.16 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:292.16,294.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:296.2,297.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:297.16,299.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:301.2,302.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:302.16,304.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/sev.go:306.2,306.12 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:65.42,67.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:69.44,70.24 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:70.24,72.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:74.2,78.24 4 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:78.24,80.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:82.2,82.16 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:85.50,87.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:87.16,89.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:90.2,93.110 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:93.110,95.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:97.2,98.110 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:98.110,100.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:102.2,102.12 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:110.71,115.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:117.82,119.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:121.67,123.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:125.69,127.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:127.16,129.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:131.2,131.29 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:134.76,136.2 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:143.57,153.2 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:155.110,156.19 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:156.19,158.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:160.2,163.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:166.77,168.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:168.16,170.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:172.2,173.78 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:176.79,178.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:180.93,182.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:184.52,186.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:189.88,192.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:192.16,194.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:197.2,197.67 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:200.95,202.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:202.16,204.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:206.2,206.19 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:206.19,208.17 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:208.17,210.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:213.2,213.34 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:216.118,217.70 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:217.70,219.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:221.2,224.16 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:224.16,226.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:228.2,236.113 6 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:236.113,238.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:240.2,240.12 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:243.102,247.16 3 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:247.16,249.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:251.2,253.16 3 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:253.16,255.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:257.2,258.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:258.16,260.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:262.2,265.16 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:265.16,267.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:269.2,269.68 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:269.68,271.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:273.2,273.19 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:273.19,277.17 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:277.17,279.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:281.3,281.46 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:281.46,283.4 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:286.2,286.12 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:289.68,291.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:291.16,293.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:295.2,295.17 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:298.60,300.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:300.16,302.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:303.2,306.16 3 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:306.16,308.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:309.2,317.16 7 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:317.16,319.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:321.2,322.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:322.16,324.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:326.2,326.25 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:329.88,339.16 7 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:339.16,341.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:343.2,345.16 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:345.16,347.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:348.2,352.12 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:355.93,357.24 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:357.24,361.26 3 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:362.29,363.46 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:364.29,365.46 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:366.27,367.44 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:368.11,369.105 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:372.3,372.28 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:372.28,374.18 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:374.18,376.5 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:377.4,378.18 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:378.18,380.5 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:381.4,381.59 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:381.59,383.5 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:386.2,386.12 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:389.71,391.16 2 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:391.16,393.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:394.2,397.16 3 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:397.16,399.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:401.2,401.22 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:404.49,406.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:408.51,410.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:412.51,414.2 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:416.88,417.22 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:417.22,419.17 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:419.17,421.4 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:423.3,423.66 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:426.2,426.36 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:429.96,432.96 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:432.96,434.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:436.2,436.87 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:436.87,438.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:440.2,440.12 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:443.88,445.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:445.16,447.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:449.2,450.55 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:450.55,452.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:454.2,455.16 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:455.16,457.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:459.2,460.57 2 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:460.57,462.3 1 0 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:464.2,464.27 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:464.27,466.3 1 1 +github.com/ultravioletrs/cocos/pkg/attestation/vtpm/vtpm.go:468.2,468.44 1 1 diff --git a/go.mod b/go.mod index fd6cae8e..d072e9bd 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/ultravioletrs/cocos -go 1.25.5 +go 1.26.0 require ( - github.com/caarlos0/env/v11 v11.3.1 + github.com/caarlos0/env/v11 v11.4.0 github.com/fatih/color v1.18.0 github.com/go-kit/kit v0.13.0 github.com/gofrs/uuid v4.4.0+incompatible @@ -13,20 +13,20 @@ require ( github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/virtee/sev-snp-measure-go v0.0.0-20240530153610-e6e8dc9b6877 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 - go.opentelemetry.io/otel/trace v1.39.0 - golang.org/x/crypto v0.47.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 + go.opentelemetry.io/otel/trace v1.41.0 + golang.org/x/crypto v0.48.0 golang.org/x/sync v0.19.0 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) require ( cloud.google.com/go/storage v1.57.2 - github.com/absmach/supermq v0.18.4 + github.com/absmach/supermq v0.19.0 github.com/caarlos0/env/v10 v10.0.0 github.com/fxamacker/cbor/v2 v2.9.0 - github.com/go-chi/chi/v5 v5.2.3 + github.com/go-chi/chi/v5 v5.2.5 github.com/go-jose/go-jose/v4 v4.1.3 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/gce-tcb-verifier v0.3.1 @@ -34,7 +34,7 @@ require ( ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect cloud.google.com/go v0.121.6 // indirect cloud.google.com/go/auth v0.16.5 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect @@ -46,14 +46,14 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect - github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect + github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect @@ -77,15 +77,15 @@ require ( github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect - go.opentelemetry.io/otel/sdk v1.39.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.41.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.41.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/time v0.12.0 // indirect google.golang.org/api v0.247.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect @@ -109,24 +109,24 @@ require ( github.com/google/go-tpm-tools v0.4.7 github.com/google/logger v1.1.1 github.com/google/uuid v1.6.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.4 // indirect - github.com/prometheus/procfs v0.17.0 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/stretchr/objx v0.5.3 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.48.0 - golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 - golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + golang.org/x/net v0.49.0 + golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 + golang.org/x/text v0.34.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f4588bd4..86208315 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= @@ -20,8 +20,6 @@ cloud.google.com/go/storage v1.57.2 h1:sVlym3cHGYhrp6XZKkKb+92I1V42ks2qKKpB0CF5M cloud.google.com/go/storage v1.57.2/go.mod h1:n5ijg4yiRXXpCu0sJTD6k+eMf7GRrJmPyr9YxLXGHOk= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= -dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= -dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= @@ -34,30 +32,24 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/absmach/certs v0.18.2 h1:s6KKL3/KfDZ6z0IxvNCksIOUwRnEgQyCpeAonuR15No= github.com/absmach/certs v0.18.2/go.mod h1:scqVZsmW2xPScnpMTtE70oN6cn0LLjFcJVPi4JKZ4+E= -github.com/absmach/supermq v0.18.4 h1:7GG0O6pgadR2xmpm9rhXDXJFdU2xnLUwqE6unN4pEEY= -github.com/absmach/supermq v0.18.4/go.mod h1:RdAohsDpSIn78d+F68RYSOKI3Dc0hwqsQixyqzIRsuI= +github.com/absmach/supermq v0.19.0 h1:sbqfzmSiMp9GEaCWgpREiLC0tFsSntgLIyAaZs7SnRY= +github.com/absmach/supermq v0.19.0/go.mod h1:SG2yIzlJmc26ZjDVSkoapc6HZ6W13SUsaN3sAErfgC4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= -github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= -github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/caarlos0/env/v11 v11.4.0 h1:Kcb6t5kIIr4XkoQC9AF2j+8E1Jsrl3Wz/hhm1LtoGAc= +github.com/caarlos0/env/v11 v11.4.0/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= -github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= -github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -68,14 +60,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI= -github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -84,10 +70,10 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/edgelesssys/go-azguestattestation v0.0.0-20250408071817-8c4457b235ff h1:V6A5kD0+c1Qg4X72Lg+zxhCZk+par436sQdgLvMCBBc= github.com/edgelesssys/go-azguestattestation v0.0.0-20250408071817-8c4457b235ff/go.mod h1:Lz4QaomI4wU2YbatD4/W7vatW2Q35tnkoJezB1clscc= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs= -github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= -github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= @@ -98,8 +84,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= @@ -117,10 +103,6 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= @@ -159,8 +141,6 @@ github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9 github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= @@ -169,8 +149,8 @@ github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81 github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -185,26 +165,14 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= -github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= -github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= -github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= -github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= -github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= -github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA= -github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU= -github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= -github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -215,8 +183,6 @@ github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= -github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -227,10 +193,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q= -github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI= -github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= -github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 h1:xzZOeCMQLA/W198ZkdVdt4EKFKJtS26B773zNU377ZY= @@ -242,12 +204,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= -github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= -github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI= -github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0= @@ -255,8 +215,6 @@ github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAt github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sammyoina/sev-snp-measure-go v0.0.0-20241202151803-ef189f0ff825 h1:SqNaL9udBIc026SGNEuEuiVL0/hw9fXxM5qrFhWGkdE= github.com/sammyoina/sev-snp-measure-go v0.0.0-20241202151803-ef189f0ff825/go.mod h1:dEkBe8JnxU5itNjZDEQINFd7f7l4DtjfqRuzPQcit4w= -github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= -github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY= @@ -280,37 +238,31 @@ github.com/veraison/go-cose v1.3.0 h1:2/H5w8kdSpQJyVtIhx8gmwPJ2uSz1PkyWFx0idbd7r github.com/veraison/go-cose v1.3.0/go.mod h1:df09OV91aHoQWLmy1KsDdYiagtXgyAwAl8vFeFn1gMc= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= -go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 h1:PnV4kVnw0zOmwwFkAzCN5O07fw1YOIQor120zrh0AVo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0/go.mod h1:ofAwF4uinaf8SXdVzzbL4OsxJ3VfeEg3f/F6CeF49/Y= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= +go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= +go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= +go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -323,8 +275,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -332,10 +284,10 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -351,23 +303,23 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -381,19 +333,17 @@ google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc= google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hal/linux/Config.in b/hal/linux/Config.in index feb4a813..6cc33f89 100644 --- a/hal/linux/Config.in +++ b/hal/linux/Config.in @@ -1,7 +1,7 @@ source "$BR2_EXTERNAL_COCOS_PATH/package/agent/Config.in" source "$BR2_EXTERNAL_COCOS_PATH/package/attestation-service/Config.in" source "$BR2_EXTERNAL_COCOS_PATH/package/cc-attestation-agent/Config.in" -source "$BR2_EXTERNAL_COCOS_PATH/package/cc-attestation-adapter/Config.in" +source "$BR2_EXTERNAL_COCOS_PATH/package/coco-keyprovider/Config.in" source "$BR2_EXTERNAL_COCOS_PATH/package/wasmedge/Config.in" source "$BR2_EXTERNAL_COCOS_PATH/package/log-forwarder/Config.in" source "$BR2_EXTERNAL_COCOS_PATH/package/computation-runner/Config.in" diff --git a/hal/linux/board/rootfs-overlay/etc/ocicrypt_keyprovider.conf b/hal/linux/board/rootfs-overlay/etc/ocicrypt_keyprovider.conf new file mode 100644 index 00000000..6499cb27 --- /dev/null +++ b/hal/linux/board/rootfs-overlay/etc/ocicrypt_keyprovider.conf @@ -0,0 +1,7 @@ +{ + "key-providers": { + "attestation-agent": { + "grpc": "127.0.0.1:50011" + } + } +} diff --git a/hal/linux/configs/cocos_defconfig b/hal/linux/configs/cocos_defconfig index 6aa1244c..7bf91c50 100644 --- a/hal/linux/configs/cocos_defconfig +++ b/hal/linux/configs/cocos_defconfig @@ -55,6 +55,12 @@ BR2_PACKAGE_CONTAINERD=y BR2_PACKAGE_RUNC=y BR2_PACKAGE_IPTABLES=y +# Skopeo for OCI image handling with CoCo Keyprovider +BR2_PACKAGE_SKOPEO=y +BR2_PACKAGE_GPGME=y +BR2_PACKAGE_LVM2=y +BR2_PACKAGE_LVM2_STANDARD_INSTALL=y + # Python BR2_PACKAGE_PYTHON3=y BR2_PACKAGE_PYTHON_PIP=y @@ -72,6 +78,10 @@ BR2_PACKAGE_GCC=y BR2_PACKAGE_GCC_TARGET=y BR2_PACKAGE_LIBSTDCPP=y -# Attestation Backend -BR2_PACKAGE_ATTESTATION_BACKEND_COCOS=y -# BR2_PACKAGE_ATTESTATION_BACKEND_CC is not set +# Host tools +BR2_PACKAGE_HOST_RUSTC=y +BR2_PACKAGE_HOST_RUST_BIN=y + +# Cocos AI Packages +BR2_PACKAGE_AGENT=y +# BR2_PACKAGE_CC_ATTESTATION_AGENT is not set diff --git a/hal/linux/package/agent/agent.mk b/hal/linux/package/agent/agent.mk index 66a9eb87..e4d8ab53 100644 --- a/hal/linux/package/agent/agent.mk +++ b/hal/linux/package/agent/agent.mk @@ -4,8 +4,8 @@ # ################################################################################ -AGENT_VERSION = main -AGENT_SITE = $(call github,ultravioletrs,cocos,$(AGENT_VERSION)) +AGENT_VERSION = 913bbccf3a22053e1979da004c732007336fc890 +AGENT_SITE = $(call github,sammyoina,cocos-ai,$(AGENT_VERSION)) define AGENT_BUILD_CMDS $(MAKE) -C $(@D) agent EMBED_ENABLED=$(AGENT_EMBED_ENABLED) diff --git a/hal/linux/package/attestation-service/Config.in b/hal/linux/package/attestation-service/Config.in index 051dc3a0..90287be1 100644 --- a/hal/linux/package/attestation-service/Config.in +++ b/hal/linux/package/attestation-service/Config.in @@ -1,42 +1,11 @@ -choice - prompt "Attestation Backend" - default BR2_PACKAGE_ATTESTATION_BACKEND_COCOS - help - Select the attestation backend to use for confidential computing. - - The Cocos AI attestation service is the native implementation - that generates EAT (Entity Attestation Token) tokens locally. - - The Confidential Containers attestation-agent is an alternative - implementation that supports the KBS (Key Broker Service) protocol - for optional remote attestation and secret provisioning. - -config BR2_PACKAGE_ATTESTATION_BACKEND_COCOS - bool "Cocos AI Attestation Service" - help - Native Cocos AI attestation service that generates EAT tokens - locally for TEE attestation (SNP, TDX, vTPM, Azure). - - This is the default and recommended option for most use cases. - - https://github.com/ultravioletrs/cocos - -config BR2_PACKAGE_ATTESTATION_BACKEND_CC - bool "Confidential Containers Attestation Agent" - depends on BR2_PACKAGE_HOST_RUSTC - help - Confidential Containers attestation-agent with optional KBS - protocol support for remote attestation and secret provisioning. - - Can operate in local mode (without KBS) or with KBS endpoint - configured per-computation for encrypted data retrieval. - - Requires Rust toolchain for building. - - https://github.com/confidential-containers/guest-components - -endchoice - config BR2_PACKAGE_ATTESTATION_SERVICE bool - default y if BR2_PACKAGE_ATTESTATION_BACKEND_COCOS + default y + help + Cocos AI attestation service that generates EAT tokens + for TEE attestation (SNP, TDX, vTPM, Azure). + + This service can optionally use the Confidential Containers + attestation-agent as a backend provider via gRPC. + + https://github.com/ultravioletrs/cocos diff --git a/hal/linux/package/attestation-service/attestation-service.mk b/hal/linux/package/attestation-service/attestation-service.mk index 6028f1e6..70983079 100644 --- a/hal/linux/package/attestation-service/attestation-service.mk +++ b/hal/linux/package/attestation-service/attestation-service.mk @@ -4,8 +4,8 @@ # ################################################################################ -ATTESTATION_SERVICE_VERSION = main -ATTESTATION_SERVICE_SITE = $(call github,ultravioletrs,cocos,$(ATTESTATION_SERVICE_VERSION)) +ATTESTATION_SERVICE_VERSION = 913bbccf3a22053e1979da004c732007336fc890 +ATTESTATION_SERVICE_SITE = $(call github,sammyoina,cocos-ai,$(ATTESTATION_SERVICE_VERSION)) define ATTESTATION_SERVICE_BUILD_CMDS $(MAKE) -C $(@D) attestation-service @@ -15,9 +15,20 @@ define ATTESTATION_SERVICE_INSTALL_TARGET_CMDS $(INSTALL) -D -m 0755 $(@D)/build/cocos-attestation-service $(TARGET_DIR)/usr/bin/attestation-service endef +ifeq ($(BR2_PACKAGE_CC_ATTESTATION_AGENT),y) define ATTESTATION_SERVICE_INSTALL_INIT_SYSTEMD $(INSTALL) -D -m 0640 $(@D)/init/systemd/attestation-service.service $(TARGET_DIR)/usr/lib/systemd/system/attestation-service.service $(INSTALL) -D -m 0750 $(@D)/init/systemd/attestation_setup.sh $(TARGET_DIR)/cocos_init/attestation_setup.sh + # CC attestation agent is already enabled by default endef +else +define ATTESTATION_SERVICE_INSTALL_INIT_SYSTEMD + $(INSTALL) -D -m 0640 $(@D)/init/systemd/attestation-service.service $(TARGET_DIR)/usr/lib/systemd/system/attestation-service.service + $(INSTALL) -D -m 0750 $(@D)/init/systemd/attestation_setup.sh $(TARGET_DIR)/cocos_init/attestation_setup.sh + # Disable CC attestation agent backend if not selected + sed -i 's/USE_CC_ATTESTATION_AGENT=true/USE_CC_ATTESTATION_AGENT=false/' $(TARGET_DIR)/usr/lib/systemd/system/attestation-service.service + sed -i '/Wants=attestation-agent.service/d' $(TARGET_DIR)/usr/lib/systemd/system/attestation-service.service +endef +endif $(eval $(generic-package)) diff --git a/hal/linux/package/cc-attestation-agent/Config.in b/hal/linux/package/cc-attestation-agent/Config.in index 0229ff73..c9fbfd44 100644 --- a/hal/linux/package/cc-attestation-agent/Config.in +++ b/hal/linux/package/cc-attestation-agent/Config.in @@ -1,16 +1,14 @@ config BR2_PACKAGE_CC_ATTESTATION_AGENT - bool - default y if BR2_PACKAGE_ATTESTATION_BACKEND_CC - depends on BR2_PACKAGE_HOST_RUSTC + bool "cc-attestation-agent" select BR2_PACKAGE_PROTOBUF select BR2_PACKAGE_OPENSSL + select BR2_PACKAGE_TPM2_TSS help - Confidential Containers attestation-agent for TEE attestation - with optional KBS (Key Broker Service) protocol support. + Confidential Containers attestation-agent for TEE attestation. - This package provides an alternative attestation backend that - can operate in local mode or connect to a KBS for remote - attestation and encrypted secret provisioning. + Optional backend for the Cocos AI attestation service that + provides KBS protocol support for remote attestation and + encrypted secret provisioning. https://github.com/confidential-containers/guest-components @@ -24,7 +22,6 @@ config BR2_PACKAGE_CC_ATTESTATION_AGENT_KBS_URL attestation and secret provisioning. Leave empty to operate in local attestation mode only. - Can be overridden per-computation via environment variables. Example: https://kbs.example.com:8080 diff --git a/hal/linux/package/cc-attestation-agent/cc-attestation-agent.mk b/hal/linux/package/cc-attestation-agent/cc-attestation-agent.mk index 95447aba..bebd5081 100644 --- a/hal/linux/package/cc-attestation-agent/cc-attestation-agent.mk +++ b/hal/linux/package/cc-attestation-agent/cc-attestation-agent.mk @@ -4,12 +4,12 @@ # ################################################################################ -CC_ATTESTATION_AGENT_VERSION = v0.16.0 -CC_ATTESTATION_AGENT_SITE = $(call github,confidential-containers,guest-components,$(CC_ATTESTATION_AGENT_VERSION)) +CC_ATTESTATION_AGENT_VERSION = mvp-runner +CC_ATTESTATION_AGENT_SITE = $(call github,rodneyosodo,guest-components,$(CC_ATTESTATION_AGENT_VERSION)) CC_ATTESTATION_AGENT_LICENSE = Apache-2.0 CC_ATTESTATION_AGENT_LICENSE_FILES = LICENSE -CC_ATTESTATION_AGENT_DEPENDENCIES = host-rustc openssl protobuf +CC_ATTESTATION_AGENT_DEPENDENCIES = host-rustc openssl protobuf tpm2-tss # Build the attestation-agent from the guest-components repository with gRPC support define CC_ATTESTATION_AGENT_BUILD_CMDS diff --git a/hal/linux/package/coco-keyprovider/Config.in b/hal/linux/package/coco-keyprovider/Config.in new file mode 100644 index 00000000..5925aad4 --- /dev/null +++ b/hal/linux/package/coco-keyprovider/Config.in @@ -0,0 +1,11 @@ +config BR2_PACKAGE_COCO_KEYPROVIDER + bool "coco-keyprovider" + depends on BR2_PACKAGE_HOST_RUSTC_ARCH_SUPPORTS + select BR2_PACKAGE_HOST_RUSTC + help + CoCo Keyprovider is a keyprovider tool for generating and + decrypting CoCo-compatible encrypted images. It implements + the ocicrypt keyprovider protocol to decrypt OCI image layers + using the Key Broker Service (KBS). + + https://github.com/confidential-containers/guest-components diff --git a/hal/linux/package/coco-keyprovider/coco-keyprovider-setup.sh b/hal/linux/package/coco-keyprovider/coco-keyprovider-setup.sh new file mode 100644 index 00000000..2572c40c --- /dev/null +++ b/hal/linux/package/coco-keyprovider/coco-keyprovider-setup.sh @@ -0,0 +1,28 @@ +#!/bin/sh +set -e + +# Read kernel command line +CMDLINE=$(cat /proc/cmdline) + +# Extract agent.aa_kbc_params value +# Format: agent.aa_kbc_params=cc_kbc::URL +PARAMS=$(echo "$CMDLINE" | tr ' ' '\n' | grep '^agent.aa_kbc_params=' | cut -d= -f2-) + +if [ -n "$PARAMS" ]; then + # Extract URL part (after ::) + KBS_URL="${PARAMS#*::}" + if [ -n "$KBS_URL" ]; then + echo "[coco-keyprovider-setup] Detected KBS URL from kernel cmdline: $KBS_URL" + KBS_ARG="--kbs $KBS_URL" + fi +else + echo "[coco-keyprovider-setup] No agent.aa_kbc_params found in kernel cmdline. Starting without --kbs." +fi + +# COCO_KP_SOCKET is set by EnvironmentFile in .service +if [ -z "$COCO_KP_SOCKET" ]; then + COCO_KP_SOCKET="127.0.0.1:50011" +fi + +echo "[coco-keyprovider-setup] Starting coco_keyprovider listening on $COCO_KP_SOCKET $KBS_ARG" +exec /usr/local/bin/coco_keyprovider --socket "$COCO_KP_SOCKET" $KBS_ARG diff --git a/hal/linux/package/coco-keyprovider/coco-keyprovider.default b/hal/linux/package/coco-keyprovider/coco-keyprovider.default new file mode 100644 index 00000000..4aa0d31c --- /dev/null +++ b/hal/linux/package/coco-keyprovider/coco-keyprovider.default @@ -0,0 +1,3 @@ +# CoCo Keyprovider Environment Variables +COCO_KP_SOCKET=127.0.0.1:50011 +RUST_LOG=info diff --git a/hal/linux/package/coco-keyprovider/coco-keyprovider.mk b/hal/linux/package/coco-keyprovider/coco-keyprovider.mk new file mode 100644 index 00000000..a706c05d --- /dev/null +++ b/hal/linux/package/coco-keyprovider/coco-keyprovider.mk @@ -0,0 +1,34 @@ +################################################################################ +# +# coco-keyprovider +# +################################################################################ + +COCO_KEYPROVIDER_VERSION = mvp-runner +COCO_KEYPROVIDER_SITE = $(call github,rodneyosodo,guest-components,$(COCO_KEYPROVIDER_VERSION)) +COCO_KEYPROVIDER_LICENSE = Apache-2.0 +COCO_KEYPROVIDER_LICENSE_FILES = LICENSE + +COCO_KEYPROVIDER_DEPENDENCIES = host-rustc + +define COCO_KEYPROVIDER_BUILD_CMDS + cd $(@D)/attestation-agent/coco_keyprovider && \ + $(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) \ + CARGO_HOME=$(HOST_DIR)/share/cargo \ + cargo build --release --target=$(RUSTC_TARGET_NAME) +endef + +define COCO_KEYPROVIDER_INSTALL_TARGET_CMDS + $(INSTALL) -D -m 0755 $(@D)/target/$(RUSTC_TARGET_NAME)/release/coco_keyprovider \ + $(TARGET_DIR)/usr/local/bin/coco_keyprovider + $(INSTALL) -D -m 0755 $(BR2_EXTERNAL_COCOS_PATH)/package/coco-keyprovider/coco-keyprovider-setup.sh \ + $(TARGET_DIR)/usr/local/bin/coco-keyprovider-setup.sh + $(INSTALL) -D -m 0644 $(BR2_EXTERNAL_COCOS_PATH)/package/coco-keyprovider/coco-keyprovider.service \ + $(TARGET_DIR)/etc/systemd/system/coco-keyprovider.service + $(INSTALL) -D -m 0644 $(BR2_EXTERNAL_COCOS_PATH)/package/coco-keyprovider/coco-keyprovider.default \ + $(TARGET_DIR)/etc/default/coco-keyprovider + mkdir -p $(TARGET_DIR)/etc + echo '{"key-providers": {"attestation-agent": {"grpc": "127.0.0.1:50011"}}}' > $(TARGET_DIR)/etc/ocicrypt_keyprovider.conf +endef + +$(eval $(generic-package)) diff --git a/hal/linux/package/coco-keyprovider/coco-keyprovider.service b/hal/linux/package/coco-keyprovider/coco-keyprovider.service new file mode 100644 index 00000000..9e90ebe7 --- /dev/null +++ b/hal/linux/package/coco-keyprovider/coco-keyprovider.service @@ -0,0 +1,25 @@ +[Unit] +Description=CoCo Keyprovider for Confidential Containers +Documentation=https://github.com/confidential-containers/guest-components +After=network-online.target attestation-agent.service +Wants=network-online.target +Requires=attestation-agent.service + +[Service] +Type=simple +EnvironmentFile=/etc/default/coco-keyprovider +RuntimeDirectory=coco-keyprovider +ExecStart=/usr/local/bin/coco-keyprovider-setup.sh +Restart=on-failure +RestartSec=5s +StandardOutput=journal +StandardError=journal + +# Security hardening +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true + +[Install] +WantedBy=multi-user.target diff --git a/hal/linux/package/computation-runner/computation-runner.mk b/hal/linux/package/computation-runner/computation-runner.mk index 1850695a..d1a40319 100644 --- a/hal/linux/package/computation-runner/computation-runner.mk +++ b/hal/linux/package/computation-runner/computation-runner.mk @@ -4,8 +4,8 @@ # ################################################################################ -COMPUTATION_RUNNER_VERSION = main -COMPUTATION_RUNNER_SITE = $(call github,ultravioletrs,cocos,$(COMPUTATION_RUNNER_VERSION)) +COMPUTATION_RUNNER_VERSION = 913bbccf3a22053e1979da004c732007336fc890 +COMPUTATION_RUNNER_SITE = $(call github,sammyoina,cocos-ai,$(COMPUTATION_RUNNER_VERSION)) define COMPUTATION_RUNNER_BUILD_CMDS $(MAKE) -C $(@D) computation-runner diff --git a/hal/linux/package/egress-proxy/egress-proxy.mk b/hal/linux/package/egress-proxy/egress-proxy.mk index eb5e5866..34cab06c 100644 --- a/hal/linux/package/egress-proxy/egress-proxy.mk +++ b/hal/linux/package/egress-proxy/egress-proxy.mk @@ -4,8 +4,8 @@ # ################################################################################ -EGRESS_PROXY_VERSION = main -EGRESS_PROXY_SITE = $(call github,ultravioletrs,cocos,$(EGRESS_PROXY_VERSION)) +EGRESS_PROXY_VERSION = 913bbccf3a22053e1979da004c732007336fc890 +EGRESS_PROXY_SITE = $(call github,sammyoina,cocos-ai,$(EGRESS_PROXY_VERSION)) define EGRESS_PROXY_BUILD_CMDS $(MAKE) -C $(@D) egress-proxy diff --git a/hal/linux/package/ingress-proxy/ingress-proxy.mk b/hal/linux/package/ingress-proxy/ingress-proxy.mk index e407a572..b9604106 100644 --- a/hal/linux/package/ingress-proxy/ingress-proxy.mk +++ b/hal/linux/package/ingress-proxy/ingress-proxy.mk @@ -4,8 +4,8 @@ # ################################################################################ -INGRESS_PROXY_VERSION = main -INGRESS_PROXY_SITE = $(call github,ultravioletrs,cocos,$(INGRESS_PROXY_VERSION)) +INGRESS_PROXY_VERSION = 913bbccf3a22053e1979da004c732007336fc890 +INGRESS_PROXY_SITE = $(call github,sammyoina,cocos-ai,$(INGRESS_PROXY_VERSION)) define INGRESS_PROXY_BUILD_CMDS $(MAKE) -C $(@D) ingress-proxy diff --git a/hal/linux/package/log-forwarder/log-forwarder.mk b/hal/linux/package/log-forwarder/log-forwarder.mk index 47545880..0c2af471 100644 --- a/hal/linux/package/log-forwarder/log-forwarder.mk +++ b/hal/linux/package/log-forwarder/log-forwarder.mk @@ -4,8 +4,8 @@ # ################################################################################ -LOG_FORWARDER_VERSION = main -LOG_FORWARDER_SITE = $(call github,ultravioletrs,cocos,$(LOG_FORWARDER_VERSION)) +LOG_FORWARDER_VERSION = 913bbccf3a22053e1979da004c732007336fc890 +LOG_FORWARDER_SITE = $(call github,sammyoina,cocos-ai,$(LOG_FORWARDER_VERSION)) define LOG_FORWARDER_BUILD_CMDS $(MAKE) -C $(@D) log-forwarder diff --git a/init/systemd/attestation-service.service b/init/systemd/attestation-service.service index 7c6353ee..5c8f762f 100644 --- a/init/systemd/attestation-service.service +++ b/init/systemd/attestation-service.service @@ -8,11 +8,13 @@ Type=simple ExecStart=/usr/bin/attestation-service Restart=always RestartSec=5 +StandardOutput=file:/var/log/cocos/attestation-service.stdout +StandardError=file:/var/log/cocos/attestation-service.stderr Environment=ATTESTATION_LOG_LEVEL=debug Environment=ATTESTATION_SERVICE_SOCKET=/run/cocos/attestation.sock Environment=ATTESTATION_VMPL=2 Environment=ATTESTATION_EAT_FORMAT=CBOR -# Enable CC Attestation-Agent backend +# CC Attestation-Agent backend (enabled by default for sample attestation) Environment=USE_CC_ATTESTATION_AGENT=true Environment=CC_AGENT_ADDRESS=127.0.0.1:50002 ExecStartPre=/cocos_init/attestation_setup.sh diff --git a/init/systemd/cocos-agent.service b/init/systemd/cocos-agent.service index 0b809153..602cf681 100644 --- a/init/systemd/cocos-agent.service +++ b/init/systemd/cocos-agent.service @@ -1,7 +1,7 @@ [Unit] Description=Cocos AI agent -After=network.target attestation-service.service log-forwarder.service computation-runner.service egress-proxy.service -Requires=log-forwarder.service computation-runner.service egress-proxy.service +After=network.target attestation-service.service log-forwarder.service computation-runner.service egress-proxy.service coco-keyprovider.service +Requires=log-forwarder.service computation-runner.service egress-proxy.service coco-keyprovider.service Before=docker.service [Service] diff --git a/internal/proto/attestation-agent/attestation-agent.proto b/internal/proto/attestation-agent/attestation-agent.proto index 2ac430a0..13b30c2b 100644 --- a/internal/proto/attestation-agent/attestation-agent.proto +++ b/internal/proto/attestation-agent/attestation-agent.proto @@ -71,3 +71,4 @@ service AttestationAgentService { rpc BindInitData(BindInitDataRequest) returns (BindInitDataResponse) {}; rpc GetTeeType(GetTeeTypeRequest) returns (GetTeeTypeResponse) {}; } + diff --git a/internal/proto/attestation/v1/attestation.pb.go b/internal/proto/attestation/v1/attestation.pb.go index af24a194..aa2621fe 100644 --- a/internal/proto/attestation/v1/attestation.pb.go +++ b/internal/proto/attestation/v1/attestation.pb.go @@ -186,6 +186,50 @@ func (x *AttestationResponse) GetEatToken() []byte { return nil } +type RawEvidenceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Evidence []byte `protobuf:"bytes,1,opt,name=evidence,proto3" json:"evidence,omitempty"` // Raw binary evidence (for KBS) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RawEvidenceResponse) Reset() { + *x = RawEvidenceResponse{} + mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RawEvidenceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RawEvidenceResponse) ProtoMessage() {} + +func (x *RawEvidenceResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RawEvidenceResponse.ProtoReflect.Descriptor instead. +func (*RawEvidenceResponse) Descriptor() ([]byte, []int) { + return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{2} +} + +func (x *RawEvidenceResponse) GetEvidence() []byte { + if x != nil { + return x.Evidence + } + return nil +} + type AzureTokenRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Nonce []byte `protobuf:"bytes,1,opt,name=nonce,proto3" json:"nonce,omitempty"` @@ -195,7 +239,7 @@ type AzureTokenRequest struct { func (x *AzureTokenRequest) Reset() { *x = AzureTokenRequest{} - mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[2] + mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -207,7 +251,7 @@ func (x *AzureTokenRequest) String() string { func (*AzureTokenRequest) ProtoMessage() {} func (x *AzureTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[2] + mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -220,7 +264,7 @@ func (x *AzureTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AzureTokenRequest.ProtoReflect.Descriptor instead. func (*AzureTokenRequest) Descriptor() ([]byte, []int) { - return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{2} + return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{3} } func (x *AzureTokenRequest) GetNonce() []byte { @@ -239,7 +283,7 @@ type AzureTokenResponse struct { func (x *AzureTokenResponse) Reset() { *x = AzureTokenResponse{} - mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[3] + mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -251,7 +295,7 @@ func (x *AzureTokenResponse) String() string { func (*AzureTokenResponse) ProtoMessage() {} func (x *AzureTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[3] + mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -264,7 +308,7 @@ func (x *AzureTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AzureTokenResponse.ProtoReflect.Descriptor instead. func (*AzureTokenResponse) Descriptor() ([]byte, []int) { - return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{3} + return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{4} } func (x *AzureTokenResponse) GetToken() []byte { @@ -285,7 +329,9 @@ const file_internal_proto_attestation_v1_attestation_proto_rawDesc = "" + "\x05nonce\x18\x02 \x01(\fR\x05nonce\x12A\n" + "\rplatform_type\x18\x03 \x01(\x0e2\x1c.attestation.v1.PlatformTypeR\fplatformType\"2\n" + "\x13AttestationResponse\x12\x1b\n" + - "\teat_token\x18\x01 \x01(\fR\beatToken\")\n" + + "\teat_token\x18\x01 \x01(\fR\beatToken\"1\n" + + "\x13RawEvidenceResponse\x12\x1a\n" + + "\bevidence\x18\x01 \x01(\fR\bevidence\")\n" + "\x11AzureTokenRequest\x12\x14\n" + "\x05nonce\x18\x01 \x01(\fR\x05nonce\"*\n" + "\x12AzureTokenResponse\x12\x14\n" + @@ -297,9 +343,10 @@ const file_internal_proto_attestation_v1_attestation_proto_rawDesc = "" + "\x12PLATFORM_TYPE_VTPM\x10\x03\x12\x1a\n" + "\x16PLATFORM_TYPE_SNP_VTPM\x10\x04\x12\x17\n" + "\x13PLATFORM_TYPE_AZURE\x10\x05\x12\x17\n" + - "\x13PLATFORM_TYPE_NO_CC\x10\x062\xcb\x01\n" + + "\x13PLATFORM_TYPE_NO_CC\x10\x062\xa8\x02\n" + "\x12AttestationService\x12[\n" + - "\x10FetchAttestation\x12\".attestation.v1.AttestationRequest\x1a#.attestation.v1.AttestationResponse\x12X\n" + + "\x10FetchAttestation\x12\".attestation.v1.AttestationRequest\x1a#.attestation.v1.AttestationResponse\x12[\n" + + "\x10FetchRawEvidence\x12\".attestation.v1.AttestationRequest\x1a#.attestation.v1.RawEvidenceResponse\x12X\n" + "\x0fFetchAzureToken\x12!.attestation.v1.AzureTokenRequest\x1a\".attestation.v1.AzureTokenResponseBJZHgithub.com/ultravioletrs/cocos/internal/proto/attestation/v1;attestationb\x06proto3" var ( @@ -315,22 +362,25 @@ func file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP() []byte { } var file_internal_proto_attestation_v1_attestation_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_internal_proto_attestation_v1_attestation_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_internal_proto_attestation_v1_attestation_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_internal_proto_attestation_v1_attestation_proto_goTypes = []any{ (PlatformType)(0), // 0: attestation.v1.PlatformType (*AttestationRequest)(nil), // 1: attestation.v1.AttestationRequest (*AttestationResponse)(nil), // 2: attestation.v1.AttestationResponse - (*AzureTokenRequest)(nil), // 3: attestation.v1.AzureTokenRequest - (*AzureTokenResponse)(nil), // 4: attestation.v1.AzureTokenResponse + (*RawEvidenceResponse)(nil), // 3: attestation.v1.RawEvidenceResponse + (*AzureTokenRequest)(nil), // 4: attestation.v1.AzureTokenRequest + (*AzureTokenResponse)(nil), // 5: attestation.v1.AzureTokenResponse } var file_internal_proto_attestation_v1_attestation_proto_depIdxs = []int32{ 0, // 0: attestation.v1.AttestationRequest.platform_type:type_name -> attestation.v1.PlatformType 1, // 1: attestation.v1.AttestationService.FetchAttestation:input_type -> attestation.v1.AttestationRequest - 3, // 2: attestation.v1.AttestationService.FetchAzureToken:input_type -> attestation.v1.AzureTokenRequest - 2, // 3: attestation.v1.AttestationService.FetchAttestation:output_type -> attestation.v1.AttestationResponse - 4, // 4: attestation.v1.AttestationService.FetchAzureToken:output_type -> attestation.v1.AzureTokenResponse - 3, // [3:5] is the sub-list for method output_type - 1, // [1:3] is the sub-list for method input_type + 1, // 2: attestation.v1.AttestationService.FetchRawEvidence:input_type -> attestation.v1.AttestationRequest + 4, // 3: attestation.v1.AttestationService.FetchAzureToken:input_type -> attestation.v1.AzureTokenRequest + 2, // 4: attestation.v1.AttestationService.FetchAttestation:output_type -> attestation.v1.AttestationResponse + 3, // 5: attestation.v1.AttestationService.FetchRawEvidence:output_type -> attestation.v1.RawEvidenceResponse + 5, // 6: attestation.v1.AttestationService.FetchAzureToken:output_type -> attestation.v1.AzureTokenResponse + 4, // [4:7] is the sub-list for method output_type + 1, // [1:4] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name @@ -347,7 +397,7 @@ func file_internal_proto_attestation_v1_attestation_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_internal_proto_attestation_v1_attestation_proto_rawDesc), len(file_internal_proto_attestation_v1_attestation_proto_rawDesc)), NumEnums: 1, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/proto/attestation/v1/attestation.proto b/internal/proto/attestation/v1/attestation.proto index 0499991d..b6cdea6f 100644 --- a/internal/proto/attestation/v1/attestation.proto +++ b/internal/proto/attestation/v1/attestation.proto @@ -6,6 +6,7 @@ option go_package = "github.com/ultravioletrs/cocos/internal/proto/attestation/v service AttestationService { rpc FetchAttestation (AttestationRequest) returns (AttestationResponse); + rpc FetchRawEvidence (AttestationRequest) returns (RawEvidenceResponse); rpc FetchAzureToken (AzureTokenRequest) returns (AzureTokenResponse); } @@ -19,6 +20,10 @@ message AttestationResponse { bytes eat_token = 1; // EAT token (JWT or CBOR format) } +message RawEvidenceResponse { + bytes evidence = 1; // Raw binary evidence (for KBS) +} + message AzureTokenRequest { bytes nonce = 1; } diff --git a/internal/proto/attestation/v1/attestation_grpc.pb.go b/internal/proto/attestation/v1/attestation_grpc.pb.go index 5d4dbc87..a5f26324 100644 --- a/internal/proto/attestation/v1/attestation_grpc.pb.go +++ b/internal/proto/attestation/v1/attestation_grpc.pb.go @@ -20,6 +20,7 @@ const _ = grpc.SupportPackageIsVersion9 const ( AttestationService_FetchAttestation_FullMethodName = "/attestation.v1.AttestationService/FetchAttestation" + AttestationService_FetchRawEvidence_FullMethodName = "/attestation.v1.AttestationService/FetchRawEvidence" AttestationService_FetchAzureToken_FullMethodName = "/attestation.v1.AttestationService/FetchAzureToken" ) @@ -28,6 +29,7 @@ const ( // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type AttestationServiceClient interface { FetchAttestation(ctx context.Context, in *AttestationRequest, opts ...grpc.CallOption) (*AttestationResponse, error) + FetchRawEvidence(ctx context.Context, in *AttestationRequest, opts ...grpc.CallOption) (*RawEvidenceResponse, error) FetchAzureToken(ctx context.Context, in *AzureTokenRequest, opts ...grpc.CallOption) (*AzureTokenResponse, error) } @@ -49,6 +51,16 @@ func (c *attestationServiceClient) FetchAttestation(ctx context.Context, in *Att return out, nil } +func (c *attestationServiceClient) FetchRawEvidence(ctx context.Context, in *AttestationRequest, opts ...grpc.CallOption) (*RawEvidenceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RawEvidenceResponse) + err := c.cc.Invoke(ctx, AttestationService_FetchRawEvidence_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *attestationServiceClient) FetchAzureToken(ctx context.Context, in *AzureTokenRequest, opts ...grpc.CallOption) (*AzureTokenResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AzureTokenResponse) @@ -64,6 +76,7 @@ func (c *attestationServiceClient) FetchAzureToken(ctx context.Context, in *Azur // for forward compatibility. type AttestationServiceServer interface { FetchAttestation(context.Context, *AttestationRequest) (*AttestationResponse, error) + FetchRawEvidence(context.Context, *AttestationRequest) (*RawEvidenceResponse, error) FetchAzureToken(context.Context, *AzureTokenRequest) (*AzureTokenResponse, error) mustEmbedUnimplementedAttestationServiceServer() } @@ -78,6 +91,9 @@ type UnimplementedAttestationServiceServer struct{} func (UnimplementedAttestationServiceServer) FetchAttestation(context.Context, *AttestationRequest) (*AttestationResponse, error) { return nil, status.Error(codes.Unimplemented, "method FetchAttestation not implemented") } +func (UnimplementedAttestationServiceServer) FetchRawEvidence(context.Context, *AttestationRequest) (*RawEvidenceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method FetchRawEvidence not implemented") +} func (UnimplementedAttestationServiceServer) FetchAzureToken(context.Context, *AzureTokenRequest) (*AzureTokenResponse, error) { return nil, status.Error(codes.Unimplemented, "method FetchAzureToken not implemented") } @@ -120,6 +136,24 @@ func _AttestationService_FetchAttestation_Handler(srv interface{}, ctx context.C return interceptor(ctx, in, info, handler) } +func _AttestationService_FetchRawEvidence_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AttestationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AttestationServiceServer).FetchRawEvidence(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AttestationService_FetchRawEvidence_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AttestationServiceServer).FetchRawEvidence(ctx, req.(*AttestationRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _AttestationService_FetchAzureToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AzureTokenRequest) if err := dec(in); err != nil { @@ -149,6 +183,10 @@ var AttestationService_ServiceDesc = grpc.ServiceDesc{ MethodName: "FetchAttestation", Handler: _AttestationService_FetchAttestation_Handler, }, + { + MethodName: "FetchRawEvidence", + Handler: _AttestationService_FetchRawEvidence_Handler, + }, { MethodName: "FetchAzureToken", Handler: _AttestationService_FetchAzureToken_Handler, diff --git a/manager/README.md b/manager/README.md index 998429ed..4d1871f1 100644 --- a/manager/README.md +++ b/manager/README.md @@ -132,7 +132,7 @@ cp /usr/share/OVMF/OVMF_VARS.fd . mkdir env mkdir certs -# Enter the env directory and create the environemnt file. +# Enter the env directory and create the environment file. cd env touch environment @@ -142,6 +142,16 @@ echo AGENT_CVM_GRPC_URL=localhost:7001 >> ./environment # Define log level for the agent. echo AGENT_LOG_LEVEL=debug >> ./environment +# Optional: Add AWS/S3 credentials for remote resource access +# NOTE: AWS credentials can also be passed via the CreateVM API using CLI flags +# (--aws-access-key-id, --aws-secret-access-key, --aws-endpoint-url, --aws-region) +# If using the API approach, you don't need to add them to this file. +# Replace HOST_IP with your host machine IP address (not localhost) +echo AWS_ACCESS_KEY_ID=minioadmin >> ./environment +echo AWS_SECRET_ACCESS_KEY=minioadmin >> ./environment +echo AWS_ENDPOINT_URL=http://HOST_IP:9000 >> ./environment +echo AWS_REGION=us-east-1 >> ./environment + # Return to cmd/manager cd .. diff --git a/manager/manager.pb.go b/manager/manager.pb.go index 40455970..aca9048e 100644 --- a/manager/manager.pb.go +++ b/manager/manager.pb.go @@ -35,6 +35,11 @@ type CreateReq struct { AgentCvmCaUrl string `protobuf:"bytes,6,opt,name=agent_cvm_ca_url,json=agentCvmCaUrl,proto3" json:"agent_cvm_ca_url,omitempty"` Ttl string `protobuf:"bytes,7,opt,name=ttl,proto3" json:"ttl,omitempty"` AgentCertsToken string `protobuf:"bytes,8,opt,name=agent_certs_token,json=agentCertsToken,proto3" json:"agent_certs_token,omitempty"` + AwsAccessKeyId string `protobuf:"bytes,9,opt,name=aws_access_key_id,json=awsAccessKeyId,proto3" json:"aws_access_key_id,omitempty"` + AwsSecretAccessKey string `protobuf:"bytes,10,opt,name=aws_secret_access_key,json=awsSecretAccessKey,proto3" json:"aws_secret_access_key,omitempty"` + AwsEndpointUrl string `protobuf:"bytes,11,opt,name=aws_endpoint_url,json=awsEndpointUrl,proto3" json:"aws_endpoint_url,omitempty"` + AwsRegion string `protobuf:"bytes,12,opt,name=aws_region,json=awsRegion,proto3" json:"aws_region,omitempty"` + AaKbsParams string `protobuf:"bytes,13,opt,name=aa_kbs_params,json=aaKbsParams,proto3" json:"aa_kbs_params,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -125,6 +130,41 @@ func (x *CreateReq) GetAgentCertsToken() string { return "" } +func (x *CreateReq) GetAwsAccessKeyId() string { + if x != nil { + return x.AwsAccessKeyId + } + return "" +} + +func (x *CreateReq) GetAwsSecretAccessKey() string { + if x != nil { + return x.AwsSecretAccessKey + } + return "" +} + +func (x *CreateReq) GetAwsEndpointUrl() string { + if x != nil { + return x.AwsEndpointUrl + } + return "" +} + +func (x *CreateReq) GetAwsRegion() string { + if x != nil { + return x.AwsRegion + } + return "" +} + +func (x *CreateReq) GetAaKbsParams() string { + if x != nil { + return x.AaKbsParams + } + return "" +} + type CreateRes struct { state protoimpl.MessageState `protogen:"open.v1"` ForwardedPort string `protobuf:"bytes,1,opt,name=forwarded_port,json=forwardedPort,proto3" json:"forwarded_port,omitempty"` @@ -449,7 +489,7 @@ var File_manager_manager_proto protoreflect.FileDescriptor const file_manager_manager_proto_rawDesc = "" + "\n" + - "\x15manager/manager.proto\x12\amanager\x1a\x1bgoogle/protobuf/empty.proto\"\xe7\x02\n" + + "\x15manager/manager.proto\x12\amanager\x1a\x1bgoogle/protobuf/empty.proto\"\xb2\x04\n" + "\tCreateReq\x12&\n" + "\x0fagent_log_level\x18\x01 \x01(\tR\ragentLogLevel\x126\n" + "\x18agent_cvm_server_ca_cert\x18\x02 \x01(\fR\x14agentCvmServerCaCert\x12/\n" + @@ -458,7 +498,14 @@ const file_manager_manager_proto_rawDesc = "" + "\x14agent_cvm_server_url\x18\x05 \x01(\tR\x11agentCvmServerUrl\x12'\n" + "\x10agent_cvm_ca_url\x18\x06 \x01(\tR\ragentCvmCaUrl\x12\x10\n" + "\x03ttl\x18\a \x01(\tR\x03ttl\x12*\n" + - "\x11agent_certs_token\x18\b \x01(\tR\x0fagentCertsToken\"I\n" + + "\x11agent_certs_token\x18\b \x01(\tR\x0fagentCertsToken\x12)\n" + + "\x11aws_access_key_id\x18\t \x01(\tR\x0eawsAccessKeyId\x121\n" + + "\x15aws_secret_access_key\x18\n" + + " \x01(\tR\x12awsSecretAccessKey\x12(\n" + + "\x10aws_endpoint_url\x18\v \x01(\tR\x0eawsEndpointUrl\x12\x1d\n" + + "\n" + + "aws_region\x18\f \x01(\tR\tawsRegion\x12\"\n" + + "\raa_kbs_params\x18\r \x01(\tR\vaaKbsParams\"I\n" + "\tCreateRes\x12%\n" + "\x0eforwarded_port\x18\x01 \x01(\tR\rforwardedPort\x12\x15\n" + "\x06cvm_id\x18\x02 \x01(\tR\x05cvmId\"\"\n" + diff --git a/manager/manager.proto b/manager/manager.proto index f65f261c..a2ce1374 100644 --- a/manager/manager.proto +++ b/manager/manager.proto @@ -25,6 +25,11 @@ message CreateReq{ string agent_cvm_ca_url = 6; string ttl = 7; string agent_certs_token = 8; + string aws_access_key_id = 9; + string aws_secret_access_key = 10; + string aws_endpoint_url = 11; + string aws_region = 12; + string aa_kbs_params = 13; } message CreateRes{ diff --git a/manager/qemu/config.go b/manager/qemu/config.go index 5608f77f..8ce9aca2 100644 --- a/manager/qemu/config.go +++ b/manager/qemu/config.go @@ -4,14 +4,13 @@ package qemu import ( "fmt" - "strconv" "github.com/caarlos0/env/v10" ) const ( - KernelCommandLine = "quiet console=null" - TDXObject = "{\"qom-type\":\"tdx-guest\",\"id\":\"%s\",\"quote-generation-socket\":{\"type\": \"vsock\", \"cid\":\"2\",\"port\":\"%d\"}}" + defaultKernelCommandLine = "quiet console=null" + TDXObject = "{\"qom-type\":\"tdx-guest\",\"id\":\"%s\",\"quote-generation-socket\":{\"type\": \"vsock\", \"cid\":\"2\",\"port\":\"%d\"}}" ) type MemoryConfig struct { @@ -113,6 +112,9 @@ type Config struct { NoGraphic bool `env:"NO_GRAPHIC" envDefault:"true"` Monitor string `env:"MONITOR" envDefault:"pty"` + // kernel + KernelCommandLine string `env:"KERNEL_CMDLINE" envDefault:"quiet console=null"` + // ports HostFwdRange string `env:"HOST_FWD_RANGE" envDefault:"6100-6200"` @@ -232,7 +234,7 @@ func (config Config) ConstructQemuArgs() []string { } args = append(args, "-kernel", config.DiskImgConfig.KernelFile) - args = append(args, "-append", strconv.Quote(KernelCommandLine)) + args = append(args, "-append", config.KernelCommandLine) args = append(args, "-initrd", config.DiskImgConfig.RootFsFile) // display diff --git a/manager/qemu/config_test.go b/manager/qemu/config_test.go index 3a554965..d49d96c8 100644 --- a/manager/qemu/config_test.go +++ b/manager/qemu/config_test.go @@ -55,8 +55,9 @@ func TestConstructQemuArgs(t *testing.T) { KernelFile: "img/bzImage", RootFsFile: "img/rootfs.cpio.gz", }, - NoGraphic: true, - Monitor: "pty", + KernelCommandLine: "quiet console=null", + NoGraphic: true, + Monitor: "pty", }, expected: []string{ "-enable-kvm", @@ -69,7 +70,7 @@ func TestConstructQemuArgs(t *testing.T) { "-netdev", "user,id=vmnic,hostfwd=tcp::7020-:7002", "-device", "virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,addr=0x2,romfile=", "-kernel", "img/bzImage", - "-append", "\"quiet console=null\"", + "-append", "quiet console=null", "-initrd", "img/rootfs.cpio.gz", "-nographic", "-monitor", "pty", @@ -127,8 +128,9 @@ func TestConstructQemuArgs(t *testing.T) { ID: "igvm0", File: "/test/path/cocos-igvm.igvm", }, - NoGraphic: true, - Monitor: "pty", + KernelCommandLine: "quiet console=null", + NoGraphic: true, + Monitor: "pty", }, expected: []string{ "-enable-kvm", @@ -143,7 +145,7 @@ func TestConstructQemuArgs(t *testing.T) { "-object", "sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1", "-object", "igvm-cfg,id=igvm0,file=/test/path/cocos-igvm.igvm", "-kernel", "img/bzImage", - "-append", "\"quiet console=null\"", + "-append", "quiet console=null", "-initrd", "img/rootfs.cpio.gz", "-nographic", "-monitor", "pty", diff --git a/manager/service.go b/manager/service.go index 7584f4f5..4b3e1074 100644 --- a/manager/service.go +++ b/manager/service.go @@ -36,6 +36,10 @@ const ( agentCvmId = "AGENT_CVM_ID" agentCaToken = "AGENT_CERTS_TOKEN" agentCvmCaUrl = "AGENT_CVM_CA_URL" + awsAccessKeyIdKey = "AWS_ACCESS_KEY_ID" + awsSecretAccessKeyKey = "AWS_SECRET_ACCESS_KEY" + awsEndpointUrlKey = "AWS_ENDPOINT_URL" + awsRegionKey = "AWS_REGION" defClientCertPath = "/etc/certs/cert.pem" defClientKeyPath = "/etc/certs/key.pem" defServerCaCertPath = "/etc/certs/ca.pem" @@ -156,6 +160,9 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string, Config: ms.qemuCfg, LaunchTCB: 0, } + if req.AaKbsParams != "" { + cfg.Config.KernelCommandLine = fmt.Sprintf("%s agent.aa_kbc_params=%s", cfg.Config.KernelCommandLine, req.AaKbsParams) + } ms.mu.Unlock() tmpCertsDir, err := tempCertMount(id, req) @@ -460,6 +467,20 @@ func tmpEnvironment(id string, req *CreateReq) (string, error) { envMap[agentCvmServerCaCertKey] = defServerCaCertPath } + // Add AWS credentials if provided + if req.AwsAccessKeyId != "" { + envMap[awsAccessKeyIdKey] = req.AwsAccessKeyId + } + if req.AwsSecretAccessKey != "" { + envMap[awsSecretAccessKeyKey] = req.AwsSecretAccessKey + } + if req.AwsEndpointUrl != "" { + envMap[awsEndpointUrlKey] = req.AwsEndpointUrl + } + if req.AwsRegion != "" { + envMap[awsRegionKey] = req.AwsRegion + } + envFile, err := os.OpenFile(fmt.Sprintf("%s/%s", dir, cvmEnvironmentFile), os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return "", err diff --git a/manager/service_test.go b/manager/service_test.go index 1b8683b1..c76727ed 100644 --- a/manager/service_test.go +++ b/manager/service_test.go @@ -310,6 +310,70 @@ func TestProcessExists(t *testing.T) { } } +func TestTmpEnvironmentWithAWSCredentials(t *testing.T) { + tests := []struct { + name string + req *CreateReq + wantEnvKeys []string + wantEnvNotKeys []string + }{ + { + name: "with all AWS credentials", + req: &CreateReq{ + AgentLogLevel: "debug", + AgentCvmServerUrl: "localhost:7001", + AwsAccessKeyId: "AKIAIOSFODNN7EXAMPLE", + AwsSecretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + AwsEndpointUrl: "http://localhost:9000", + AwsRegion: "us-east-1", + }, + wantEnvKeys: []string{"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_ENDPOINT_URL", "AWS_REGION"}, + wantEnvNotKeys: []string{}, + }, + { + name: "with partial AWS credentials", + req: &CreateReq{ + AgentLogLevel: "info", + AwsAccessKeyId: "AKIAIOSFODNN7EXAMPLE", + AwsRegion: "eu-west-1", + }, + wantEnvKeys: []string{"AWS_ACCESS_KEY_ID", "AWS_REGION"}, + wantEnvNotKeys: []string{"AWS_SECRET_ACCESS_KEY", "AWS_ENDPOINT_URL"}, + }, + { + name: "without AWS credentials", + req: &CreateReq{ + AgentLogLevel: "info", + AgentCvmServerUrl: "localhost:7001", + }, + wantEnvKeys: []string{}, + wantEnvNotKeys: []string{"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_ENDPOINT_URL", "AWS_REGION"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir, err := tmpEnvironment("test-id", tt.req) + require.NoError(t, err) + defer os.RemoveAll(dir) + + // Read the environment file + envContent, err := os.ReadFile(path.Join(dir, "environment")) + require.NoError(t, err) + + envStr := string(envContent) + + for _, key := range tt.wantEnvKeys { + assert.Contains(t, envStr, key+"=", "expected key %s to be present", key) + } + + for _, key := range tt.wantEnvNotKeys { + assert.NotContains(t, envStr, key+"=", "expected key %s to NOT be present", key) + } + }) + } +} + func TestShutdown(t *testing.T) { ms := &managerService{ vms: make(map[string]vm.VM), @@ -326,3 +390,83 @@ func TestShutdown(t *testing.T) { assert.Len(t, ms.vms, 0) } + +func TestCreateVMWithAaKbsParams(t *testing.T) { + vmf := new(mocks.Provider) + vmMock := new(mocks.VM) + persistence := new(persistenceMocks.Persistence) + + tests := []struct { + name string + aaKbsParams string + expectedKernelArg string + }{ + { + name: "with AaKbsParams", + aaKbsParams: "cc_kbc::http://kbs.example.com:8080", + expectedKernelArg: "agent.aa_kbc_params=cc_kbc::http://kbs.example.com:8080", + }, + { + name: "without AaKbsParams", + aaKbsParams: "", + expectedKernelArg: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var capturedConfig any + + vmf.On("Execute", mock.Anything, mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + capturedConfig = args.Get(0) + }). + Return(vmMock).Once() + + vmMock.On("Start").Return(nil).Once() + vmMock.On("GetProcess").Return(1234).Once() + vmMock.On("Transition", mock.Anything).Return(nil).Once() + persistence.On("SaveVM", mock.Anything).Return(nil).Once() + + tempDir := CreateDummyAttestationPolicyBinary(t, "success") + defer os.RemoveAll(tempDir) + + qemuCfg := qemu.Config{ + EnableSEVSNP: true, + KernelCommandLine: "quiet console=null", + } + + ms := &managerService{ + qemuCfg: qemuCfg, + attestationPolicyBinaryPath: path.Join(tempDir, "attestation_policy"), + pcrValuesFilePath: tempDir, + logger: slog.Default(), + vms: make(map[string]vm.VM), + vmFactory: vmf.Execute, + persistence: persistence, + ttlManager: NewTTLManager(), + } + + ctx := context.Background() + + _, _, err := ms.CreateVM(ctx, &CreateReq{ + AaKbsParams: tt.aaKbsParams, + }) + + require.NoError(t, err) + require.NotNil(t, capturedConfig) + + vmInfo, ok := capturedConfig.(qemu.VMInfo) + require.True(t, ok, "expected capturedConfig to be qemu.VMInfo") + + if tt.expectedKernelArg != "" { + assert.Contains(t, vmInfo.Config.KernelCommandLine, tt.expectedKernelArg) + } else { + assert.NotContains(t, vmInfo.Config.KernelCommandLine, "agent.aa_kbc_params=") + } + + vmf.AssertExpectations(t) + vmMock.AssertExpectations(t) + }) + } +} diff --git a/pkg/atls/atls_test.go b/pkg/atls/atls_test.go index 18163cf7..598de883 100644 --- a/pkg/atls/atls_test.go +++ b/pkg/atls/atls_test.go @@ -57,6 +57,14 @@ func (m *mockAttestationClient) GetAttestation(ctx context.Context, reportData [ return args.Get(0).([]byte), args.Error(1) } +func (m *mockAttestationClient) GetRawEvidence(ctx context.Context, reportData [64]byte, nonce [32]byte, attType attestation.PlatformType) ([]byte, error) { + args := m.Called(ctx, reportData, nonce, attType) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]byte), args.Error(1) +} + func (m *mockAttestationClient) GetAzureToken(ctx context.Context, nonce [32]byte) ([]byte, error) { args := m.Called(ctx, nonce) if args.Get(0) == nil { diff --git a/pkg/attestation/emptyprovider.go b/pkg/attestation/emptyprovider.go index 1add06fd..fe2f181b 100644 --- a/pkg/attestation/emptyprovider.go +++ b/pkg/attestation/emptyprovider.go @@ -3,18 +3,25 @@ package attestation -import cocosai "github.com/ultravioletrs/cocos" +import ( + "fmt" + + cocosai "github.com/ultravioletrs/cocos" +) var _ Provider = (*EmptyProvider)(nil) type EmptyProvider struct{} func (e *EmptyProvider) Attestation(teeNonce []byte, vTpmNonce []byte) ([]byte, error) { - return cocosai.EmbeddedAttestation, nil + // For Sample/Empty provider, we treat the teeNonce as reportData + return e.TeeAttestation(teeNonce) } func (e *EmptyProvider) TeeAttestation(teeNonce []byte) ([]byte, error) { - return cocosai.EmbeddedAttestation, nil + // EmptyProvider should not be used for attestation + // The CC Attestation Agent's sample attester should be used instead + return nil, fmt.Errorf("EmptyProvider should not be used - configure USE_CC_ATTESTATION_AGENT=true to use the CC Attestation Agent's sample attester") } func (e *EmptyProvider) VTpmAttestation(vTpmNonce []byte) ([]byte, error) { diff --git a/pkg/attestation/emptyprovider_test.go b/pkg/attestation/emptyprovider_test.go new file mode 100644 index 00000000..4985658c --- /dev/null +++ b/pkg/attestation/emptyprovider_test.go @@ -0,0 +1,163 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 + +package attestation + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + cocosai "github.com/ultravioletrs/cocos" +) + +func TestEmptyProvider_Attestation(t *testing.T) { + tests := []struct { + name string + teeNonce []byte + vTpmNonce []byte + wantErr bool + }{ + { + name: "should return error for empty nonces", + teeNonce: []byte{}, + vTpmNonce: []byte{}, + wantErr: true, + }, + { + name: "should return error for valid nonces", + teeNonce: make([]byte, 64), + vTpmNonce: make([]byte, 32), + wantErr: true, + }, + { + name: "should return error for nil nonces", + teeNonce: nil, + vTpmNonce: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &EmptyProvider{} + got, err := p.Attestation(tt.teeNonce, tt.vTpmNonce) + + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, got) + assert.Contains(t, err.Error(), "EmptyProvider should not be used") + } else { + assert.NoError(t, err) + assert.NotNil(t, got) + } + }) + } +} + +func TestEmptyProvider_TeeAttestation(t *testing.T) { + tests := []struct { + name string + teeNonce []byte + wantErr bool + }{ + { + name: "should return error for empty nonce", + teeNonce: []byte{}, + wantErr: true, + }, + { + name: "should return error for valid nonce", + teeNonce: make([]byte, 64), + wantErr: true, + }, + { + name: "should return error for nil nonce", + teeNonce: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &EmptyProvider{} + got, err := p.TeeAttestation(tt.teeNonce) + + assert.Error(t, err) + assert.Nil(t, got) + assert.Contains(t, err.Error(), "EmptyProvider should not be used") + }) + } +} + +func TestEmptyProvider_VTpmAttestation(t *testing.T) { + tests := []struct { + name string + vTpmNonce []byte + wantErr bool + }{ + { + name: "should return embedded attestation for empty nonce", + vTpmNonce: []byte{}, + wantErr: false, + }, + { + name: "should return embedded attestation for valid nonce", + vTpmNonce: make([]byte, 32), + wantErr: false, + }, + { + name: "should return embedded attestation for nil nonce", + vTpmNonce: nil, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &EmptyProvider{} + got, err := p.VTpmAttestation(tt.vTpmNonce) + + require.NoError(t, err) + assert.Equal(t, cocosai.EmbeddedAttestation, got) + }) + } +} + +func TestEmptyProvider_AzureAttestationToken(t *testing.T) { + tests := []struct { + name string + nonce []byte + wantErr bool + }{ + { + name: "should return nil for empty nonce", + nonce: []byte{}, + wantErr: false, + }, + { + name: "should return nil for valid nonce", + nonce: make([]byte, 32), + wantErr: false, + }, + { + name: "should return nil for nil nonce", + nonce: nil, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &EmptyProvider{} + got, err := p.AzureAttestationToken(tt.nonce) + + require.NoError(t, err) + assert.Nil(t, got) + }) + } +} + +func TestEmptyProvider_ImplementsProvider(t *testing.T) { + var _ Provider = (*EmptyProvider)(nil) +} diff --git a/pkg/attestation/vtpm/vtpm.go b/pkg/attestation/vtpm/vtpm.go index a777d671..05b7abff 100644 --- a/pkg/attestation/vtpm/vtpm.go +++ b/pkg/attestation/vtpm/vtpm.go @@ -395,6 +395,9 @@ func getPCRValue(index int, algorithm tpm2.Algorithm) ([]byte, error) { pcrValue, err := tpm2.ReadPCR(rwc, index, algorithm) if err != nil { + if _, ok := ExternalTPM.(*DummyRWC); ok { + return make([]byte, 20), nil + } return nil, err } diff --git a/pkg/clients/grpc/attestation-agent/client.go b/pkg/clients/grpc/attestation-agent/client.go new file mode 100644 index 00000000..dd30fc90 --- /dev/null +++ b/pkg/clients/grpc/attestation-agent/client.go @@ -0,0 +1,70 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 +package attestation_agent + +import ( + "context" + "fmt" + "strings" + "time" + + aa "github.com/ultravioletrs/cocos/internal/proto/attestation-agent" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +// Client provides access to attestation-agent services. +type Client interface { + // GetToken gets a token from the attestation-agent (e.g., KBS token). + GetToken(ctx context.Context, tokenType string) ([]byte, error) + Close() error +} + +type client struct { + conn *grpc.ClientConn + client aa.AttestationAgentServiceClient +} + +// NewClient creates a new attestation-agent client. +// address can be either a TCP address (e.g., "127.0.0.1:50002") or Unix socket path (e.g., "/run/aa.sock"). +func NewClient(address string) (Client, error) { + var target string + // If address contains ":", it's a TCP address, otherwise it's a Unix socket + if strings.Contains(address, ":") { + target = address + } else { + target = "unix://" + address + } + + conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, fmt.Errorf("failed to connect to attestation-agent: %w", err) + } + + return &client{ + conn: conn, + client: aa.NewAttestationAgentServiceClient(conn), + }, nil +} + +func (c *client) Close() error { + return c.conn.Close() +} + +// GetToken gets a token from the attestation-agent. +// tokenType should be "kbs" for KBS tokens. +func (c *client) GetToken(ctx context.Context, tokenType string) ([]byte, error) { + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + req := &aa.GetTokenRequest{ + TokenType: tokenType, + } + + resp, err := c.client.GetToken(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get token from attestation-agent: %w", err) + } + + return resp.Token, nil +} diff --git a/pkg/clients/grpc/attestation-agent/client_test.go b/pkg/clients/grpc/attestation-agent/client_test.go new file mode 100644 index 00000000..f45581db --- /dev/null +++ b/pkg/clients/grpc/attestation-agent/client_test.go @@ -0,0 +1,194 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 + +package attestation_agent + +import ( + "context" + "net" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + aa "github.com/ultravioletrs/cocos/internal/proto/attestation-agent" + "google.golang.org/grpc" +) + +type mockAttestationAgentServer struct { + aa.UnimplementedAttestationAgentServiceServer + getTokenCalled bool + lastTokenType string + tokenErr error + tokenResponse []byte +} + +func (m *mockAttestationAgentServer) GetToken(ctx context.Context, req *aa.GetTokenRequest) (*aa.GetTokenResponse, error) { + m.getTokenCalled = true + m.lastTokenType = req.TokenType + if m.tokenErr != nil { + return nil, m.tokenErr + } + return &aa.GetTokenResponse{Token: m.tokenResponse}, nil +} + +func TestNewClientUnixSocket(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "aa-test.sock") + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationAgentServer{tokenResponse: []byte("mock-token")} + aa.RegisterAttestationAgentServiceServer(grpcServer, mockServer) + + go func() { _ = grpcServer.Serve(listener) }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(socketPath) + require.NoError(t, err) + require.NotNil(t, client) + + err = client.Close() + assert.NoError(t, err) +} + +func TestNewClientTCPAddress(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationAgentServer{tokenResponse: []byte("mock-token")} + aa.RegisterAttestationAgentServiceServer(grpcServer, mockServer) + + go func() { _ = grpcServer.Serve(listener) }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(listener.Addr().String()) + require.NoError(t, err) + require.NotNil(t, client) + + err = client.Close() + assert.NoError(t, err) +} + +func TestGetToken(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "aa-gettoken.sock") + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationAgentServer{tokenResponse: []byte("kbs-token-response")} + aa.RegisterAttestationAgentServiceServer(grpcServer, mockServer) + + go func() { _ = grpcServer.Serve(listener) }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(socketPath) + require.NoError(t, err) + defer client.Close() + + ctx := context.Background() + token, err := client.GetToken(ctx, "kbs") + require.NoError(t, err) + assert.Equal(t, []byte("kbs-token-response"), token) + assert.True(t, mockServer.getTokenCalled) + assert.Equal(t, "kbs", mockServer.lastTokenType) +} + +func TestGetTokenError(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "aa-error.sock") + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationAgentServer{tokenErr: assert.AnError} + aa.RegisterAttestationAgentServiceServer(grpcServer, mockServer) + + go func() { _ = grpcServer.Serve(listener) }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(socketPath) + require.NoError(t, err) + defer client.Close() + + ctx := context.Background() + token, err := client.GetToken(ctx, "kbs") + assert.Error(t, err) + assert.Nil(t, token) + assert.Contains(t, err.Error(), "failed to get token from attestation-agent") +} + +func TestGetTokenCanceledContext(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "aa-cancel.sock") + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationAgentServer{tokenResponse: []byte("token")} + aa.RegisterAttestationAgentServiceServer(grpcServer, mockServer) + + go func() { _ = grpcServer.Serve(listener) }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(socketPath) + require.NoError(t, err) + defer client.Close() + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + _, err = client.GetToken(ctx, "kbs") + assert.Error(t, err) +} + +func TestClientClose(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "aa-close.sock") + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationAgentServer{} + aa.RegisterAttestationAgentServiceServer(grpcServer, mockServer) + + go func() { _ = grpcServer.Serve(listener) }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(socketPath) + require.NoError(t, err) + + err = client.Close() + assert.NoError(t, err) +} + +func TestClientInterface(t *testing.T) { + var _ Client = (*client)(nil) +} diff --git a/pkg/clients/grpc/attestation/client.go b/pkg/clients/grpc/attestation/client.go index 43a8dc53..4357c902 100644 --- a/pkg/clients/grpc/attestation/client.go +++ b/pkg/clients/grpc/attestation/client.go @@ -4,6 +4,7 @@ package attestation import ( "context" + "fmt" "time" attestation_v1 "github.com/ultravioletrs/cocos/internal/proto/attestation/v1" @@ -14,6 +15,7 @@ import ( type Client interface { GetAttestation(ctx context.Context, reportData [64]byte, nonce [32]byte, attType attestation.PlatformType) ([]byte, error) + GetRawEvidence(ctx context.Context, reportData [64]byte, nonce [32]byte, attType attestation.PlatformType) ([]byte, error) GetAzureToken(ctx context.Context, nonce [32]byte) ([]byte, error) Close() error } @@ -57,6 +59,10 @@ func (c *client) GetAttestation(ctx context.Context, reportData [64]byte, nonce platformType = attestation_v1.PlatformType_PLATFORM_TYPE_UNSPECIFIED } + // Debug: log platform type conversion + fmt.Printf("[ATTESTATION-CLIENT] Platform type conversion: agent=%v (%d) -> proto=%v (%d)\n", + attType, attType, platformType, platformType) + req := &attestation_v1.AttestationRequest{ ReportData: reportData[:], Nonce: nonce[:], @@ -71,6 +77,42 @@ func (c *client) GetAttestation(ctx context.Context, reportData [64]byte, nonce return resp.EatToken, nil } +// GetRawEvidence gets raw binary evidence (for KBS) instead of EAT token. +func (c *client) GetRawEvidence(ctx context.Context, reportData [64]byte, nonce [32]byte, attType attestation.PlatformType) ([]byte, error) { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + var platformType attestation_v1.PlatformType + switch attType { + case attestation.SNP: + platformType = attestation_v1.PlatformType_PLATFORM_TYPE_SNP + case attestation.TDX: + platformType = attestation_v1.PlatformType_PLATFORM_TYPE_TDX + case attestation.VTPM: + platformType = attestation_v1.PlatformType_PLATFORM_TYPE_VTPM + case attestation.SNPvTPM: + platformType = attestation_v1.PlatformType_PLATFORM_TYPE_SNP_VTPM + default: + platformType = attestation_v1.PlatformType_PLATFORM_TYPE_UNSPECIFIED + } + + fmt.Printf("[ATTESTATION-CLIENT] Getting raw evidence: platform=%v (%d)\n", + attType, platformType) + + req := &attestation_v1.AttestationRequest{ + ReportData: reportData[:], + Nonce: nonce[:], + PlatformType: platformType, + } + + resp, err := c.client.FetchRawEvidence(ctx, req) + if err != nil { + return nil, err + } + + return resp.Evidence, nil +} + func (c *client) GetAzureToken(ctx context.Context, nonce [32]byte) ([]byte, error) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() diff --git a/pkg/clients/grpc/attestation/client_test.go b/pkg/clients/grpc/attestation/client_test.go index e517010e..ed3a8f86 100644 --- a/pkg/clients/grpc/attestation/client_test.go +++ b/pkg/clients/grpc/attestation/client_test.go @@ -20,11 +20,13 @@ import ( type mockAttestationServer struct { attestation_v1.UnimplementedAttestationServiceServer fetchAttestationCalled bool + fetchRawEvidenceCalled bool fetchAzureTokenCalled bool lastReportData []byte lastNonce []byte lastPlatformType attestation_v1.PlatformType attestationErr error + rawEvidenceErr error azureTokenErr error } @@ -43,6 +45,21 @@ func (m *mockAttestationServer) FetchAttestation(ctx context.Context, req *attes }, nil } +func (m *mockAttestationServer) FetchRawEvidence(ctx context.Context, req *attestation_v1.AttestationRequest) (*attestation_v1.RawEvidenceResponse, error) { + m.fetchRawEvidenceCalled = true + m.lastReportData = req.ReportData + m.lastNonce = req.Nonce + m.lastPlatformType = req.PlatformType + + if m.rawEvidenceErr != nil { + return nil, m.rawEvidenceErr + } + + return &attestation_v1.RawEvidenceResponse{ + Evidence: []byte("mock-raw-evidence"), + }, nil +} + func (m *mockAttestationServer) FetchAzureToken(ctx context.Context, req *attestation_v1.AzureTokenRequest) (*attestation_v1.AzureTokenResponse, error) { m.fetchAzureTokenCalled = true m.lastNonce = req.Nonce @@ -390,3 +407,176 @@ func TestClientOperationsAfterClose(t *testing.T) { _, err = client.GetAttestation(ctx, reportData, nonce, attestation.SNP) assert.Error(t, err) } + +// TestGetRawEvidenceSNP tests getting raw evidence for SNP platform. +func TestGetRawEvidenceSNP(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "raw-evidence-snp.sock") + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationServer{} + attestation_v1.RegisterAttestationServiceServer(grpcServer, mockServer) + + go func() { + _ = grpcServer.Serve(listener) + }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(socketPath) + require.NoError(t, err) + defer client.Close() + + ctx := context.Background() + var reportData [64]byte + var nonce [32]byte + copy(reportData[:], []byte("test-report-data")) + copy(nonce[:], []byte("test-nonce")) + + evidence, err := client.GetRawEvidence(ctx, reportData, nonce, attestation.SNP) + require.NoError(t, err) + assert.Equal(t, []byte("mock-raw-evidence"), evidence) + assert.True(t, mockServer.fetchRawEvidenceCalled) + assert.Equal(t, attestation_v1.PlatformType_PLATFORM_TYPE_SNP, mockServer.lastPlatformType) +} + +// TestGetRawEvidenceTDX tests getting raw evidence for TDX platform. +func TestGetRawEvidenceTDX(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "raw-evidence-tdx.sock") + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationServer{} + attestation_v1.RegisterAttestationServiceServer(grpcServer, mockServer) + + go func() { + _ = grpcServer.Serve(listener) + }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(socketPath) + require.NoError(t, err) + defer client.Close() + + ctx := context.Background() + var reportData [64]byte + var nonce [32]byte + + evidence, err := client.GetRawEvidence(ctx, reportData, nonce, attestation.TDX) + require.NoError(t, err) + assert.NotNil(t, evidence) + assert.Equal(t, attestation_v1.PlatformType_PLATFORM_TYPE_TDX, mockServer.lastPlatformType) +} + +// TestGetRawEvidenceVTPM tests getting raw evidence for VTPM platform. +func TestGetRawEvidenceVTPM(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "raw-evidence-vtpm.sock") + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationServer{} + attestation_v1.RegisterAttestationServiceServer(grpcServer, mockServer) + + go func() { + _ = grpcServer.Serve(listener) + }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(socketPath) + require.NoError(t, err) + defer client.Close() + + ctx := context.Background() + var reportData [64]byte + var nonce [32]byte + + evidence, err := client.GetRawEvidence(ctx, reportData, nonce, attestation.VTPM) + require.NoError(t, err) + assert.NotNil(t, evidence) + assert.Equal(t, attestation_v1.PlatformType_PLATFORM_TYPE_VTPM, mockServer.lastPlatformType) +} + +// TestGetRawEvidenceSNPvTPM tests getting raw evidence for SNPvTPM platform. +func TestGetRawEvidenceSNPvTPM(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "raw-evidence-snpvtpm.sock") + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationServer{} + attestation_v1.RegisterAttestationServiceServer(grpcServer, mockServer) + + go func() { + _ = grpcServer.Serve(listener) + }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(socketPath) + require.NoError(t, err) + defer client.Close() + + ctx := context.Background() + var reportData [64]byte + var nonce [32]byte + + evidence, err := client.GetRawEvidence(ctx, reportData, nonce, attestation.SNPvTPM) + require.NoError(t, err) + assert.NotNil(t, evidence) + assert.Equal(t, attestation_v1.PlatformType_PLATFORM_TYPE_SNP_VTPM, mockServer.lastPlatformType) +} + +// TestGetRawEvidenceUnspecified tests getting raw evidence with unspecified platform. +func TestGetRawEvidenceUnspecified(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "raw-evidence-unspec.sock") + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + defer listener.Close() + + grpcServer := grpc.NewServer() + mockServer := &mockAttestationServer{} + attestation_v1.RegisterAttestationServiceServer(grpcServer, mockServer) + + go func() { + _ = grpcServer.Serve(listener) + }() + defer grpcServer.Stop() + + time.Sleep(100 * time.Millisecond) + + client, err := NewClient(socketPath) + require.NoError(t, err) + defer client.Close() + + ctx := context.Background() + var reportData [64]byte + var nonce [32]byte + + evidence, err := client.GetRawEvidence(ctx, reportData, nonce, attestation.PlatformType(999)) + require.NoError(t, err) + assert.NotNil(t, evidence) + assert.Equal(t, attestation_v1.PlatformType_PLATFORM_TYPE_UNSPECIFIED, mockServer.lastPlatformType) +} diff --git a/pkg/clients/grpc/log/client.go b/pkg/clients/grpc/log/client.go index 017fff5b..1dc01d6b 100644 --- a/pkg/clients/grpc/log/client.go +++ b/pkg/clients/grpc/log/client.go @@ -40,25 +40,63 @@ func (c *client) Close() error { } func (c *client) SendLog(ctx context.Context, entry *log.LogEntry) error { - ctx, cancel := context.WithTimeout(ctx, 5*time.Second) - defer cancel() - if entry.Timestamp == nil { entry.Timestamp = timestamppb.Now() } + // Retry with exponential backoff for concurrent request handling + maxRetries := 3 + for attempt := 0; attempt < maxRetries; attempt++ { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + _, err := c.client.SendLog(ctx, entry) + cancel() + + if err == nil { + return nil + } + + // Don't retry on last attempt + if attempt < maxRetries-1 { + // Exponential backoff: 10ms, 20ms, 40ms + backoff := time.Duration(10*(1<= 0; j-- { + for i := n; i >= 1; i-- { + t := uint64(n*j + i) + b := make([]byte, 16) + for k := 0; k < 8; k++ { + b[k] = a[k] ^ byte(t>>(56-8*k)) + } + copy(b[8:], r[i]) + + block.Decrypt(b, b) + a = b[:8] + r[i] = b[8:] + } + } + + // Check integrity value + expectedIV := []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6} + for i := 0; i < 8; i++ { + if a[i] != expectedIV[i] { + return nil, errors.Wrap(ErrDecryptionFailed, errors.New("key unwrap integrity check failed")) + } + } + + // Concatenate unwrapped key + unwrapped := make([]byte, 0, n*8) + for i := 1; i <= n; i++ { + unwrapped = append(unwrapped, r[i]...) + } + + return unwrapped, nil +} + +// ParseEncryptedResource parses a JSON-encoded encrypted resource. +func ParseEncryptedResource(data []byte) (*EncryptedResource, error) { + var resource EncryptedResource + if err := json.Unmarshal(data, &resource); err != nil { + return nil, errors.Wrap(ErrInvalidFormat, err) + } + return &resource, nil +} + +// zeroBytes securely zeros out a byte slice. +func zeroBytes(b []byte) { + for i := range b { + b[i] = 0 + } +} diff --git a/pkg/crypto/decrypt_test.go b/pkg/crypto/decrypt_test.go new file mode 100644 index 00000000..dbd89b4f --- /dev/null +++ b/pkg/crypto/decrypt_test.go @@ -0,0 +1,712 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 + +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/ecdh" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/hkdf" +) + +// testAESKeyWrap implements RFC 3394 AES Key Wrap for use in test setup. +func testAESKeyWrap(kek, key []byte) ([]byte, error) { + block, err := aes.NewCipher(kek) + if err != nil { + return nil, err + } + + n := len(key) / 8 + a := []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6} + r := make([][]byte, n+1) + for i := 1; i <= n; i++ { + r[i] = make([]byte, 8) + copy(r[i], key[(i-1)*8:i*8]) + } + + for j := 0; j <= 5; j++ { + for i := 1; i <= n; i++ { + t := uint64(n*j + i) + b := make([]byte, 16) + copy(b[:8], a) + copy(b[8:], r[i]) + block.Encrypt(b, b) + for k := 0; k < 8; k++ { + a[k] = b[k] ^ byte(t>>(56-8*k)) + } + r[i] = make([]byte, 8) + copy(r[i], b[8:]) + } + } + + result := make([]byte, (n+1)*8) + copy(result[:8], a) + for i := 1; i <= n; i++ { + copy(result[i*8:(i+1)*8], r[i]) + } + return result, nil +} + +func TestDecryptAESGCM(t *testing.T) { + // Generate a valid key + key := make([]byte, 32) + _, err := rand.Read(key) + require.NoError(t, err) + + // Generate valid plaintext + plaintext := []byte("test plaintext data") + + // Create cipher and encrypt + block, err := aes.NewCipher(key) + require.NoError(t, err) + + aesgcm, err := cipher.NewGCM(block) + require.NoError(t, err) + + iv := make([]byte, aesgcm.NonceSize()) + _, err = rand.Read(iv) + require.NoError(t, err) + + aad := []byte("additional data") + ciphertext := aesgcm.Seal(nil, iv, plaintext, aad) + // Split ciphertext and tag + tag := ciphertext[len(ciphertext)-aesgcm.Overhead():] + ciphertextOnly := ciphertext[:len(ciphertext)-aesgcm.Overhead()] + + tests := []struct { + name string + ciphertext []byte + key []byte + iv []byte + tag []byte + aad []byte + wantErr bool + errContain string + }{ + { + name: "valid decryption", + ciphertext: ciphertextOnly, + key: key, + iv: iv, + tag: tag, + aad: aad, + wantErr: false, + }, + { + name: "invalid key length", + ciphertext: ciphertextOnly, + key: []byte("short"), + iv: iv, + tag: tag, + aad: aad, + wantErr: true, + errContain: "key must be 16, 24, or 32 bytes", + }, + { + name: "wrong key", + ciphertext: ciphertextOnly, + key: make([]byte, 32), + iv: iv, + tag: tag, + aad: aad, + wantErr: true, + errContain: "decryption failed", + }, + { + name: "corrupted tag", + ciphertext: ciphertextOnly, + key: key, + iv: iv, + tag: make([]byte, len(tag)), + aad: aad, + wantErr: true, + errContain: "decryption failed", + }, + { + name: "wrong aad", + ciphertext: ciphertextOnly, + key: key, + iv: iv, + tag: tag, + aad: []byte("wrong aad"), + wantErr: true, + errContain: "decryption failed", + }, + { + name: "16 byte key", + ciphertext: nil, + key: make([]byte, 16), + iv: nil, + tag: nil, + aad: nil, + wantErr: false, + }, + { + name: "24 byte key", + ciphertext: nil, + key: make([]byte, 24), + iv: nil, + tag: nil, + aad: nil, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // For tests with nil ciphertext, create new cipher with specified key + if tt.ciphertext == nil && !tt.wantErr { + _, err := rand.Read(tt.key) + require.NoError(t, err) + + block, err := aes.NewCipher(tt.key) + require.NoError(t, err) + + aesgcm, err := cipher.NewGCM(block) + require.NoError(t, err) + + tt.iv = make([]byte, aesgcm.NonceSize()) + _, err = rand.Read(tt.iv) + require.NoError(t, err) + + tt.aad = []byte("test aad") + ciphertext := aesgcm.Seal(nil, tt.iv, plaintext, tt.aad) + tt.tag = ciphertext[len(ciphertext)-aesgcm.Overhead():] + tt.ciphertext = ciphertext[:len(ciphertext)-aesgcm.Overhead()] + } + + got, err := DecryptAESGCM(tt.ciphertext, tt.key, tt.iv, tt.tag, tt.aad) + + if tt.wantErr { + assert.Error(t, err) + if tt.errContain != "" { + assert.Contains(t, err.Error(), tt.errContain) + } + } else { + require.NoError(t, err) + assert.Equal(t, plaintext, got) + } + }) + } +} + +func TestParseEncryptedResource(t *testing.T) { + tests := []struct { + name string + data []byte + wantErr bool + }{ + { + name: "valid encrypted resource", + data: func() []byte { + resource := EncryptedResource{ + Ciphertext: []byte("encrypted data"), + EncryptedKey: []byte("wrapped key"), + IV: []byte("initialization vector"), + Tag: []byte("auth tag"), + AAD: []byte("additional data"), + } + data, err := json.Marshal(resource) + if err != nil { + panic(err) + } + return data + }(), + wantErr: false, + }, + { + name: "valid encrypted resource with EPK", + data: func() []byte { + resource := EncryptedResource{ + Ciphertext: []byte("encrypted data"), + EncryptedKey: []byte("wrapped key"), + IV: []byte("initialization vector"), + Tag: []byte("auth tag"), + EPK: &EphemeralPublicKey{ + Curve: "P-256", + X: "AAAA", + Y: "BBBB", + }, + } + data, err := json.Marshal(resource) + if err != nil { + panic(err) + } + return data + }(), + wantErr: false, + }, + { + name: "invalid JSON", + data: []byte("not valid json"), + wantErr: true, + }, + { + name: "empty JSON", + data: []byte("{}"), + wantErr: false, + }, + { + name: "empty data", + data: []byte{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseEncryptedResource(tt.data) + + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, got) + } else { + require.NoError(t, err) + assert.NotNil(t, got) + } + }) + } +} + +func TestZeroBytes(t *testing.T) { + tests := []struct { + name string + input []byte + }{ + { + name: "zero empty slice", + input: []byte{}, + }, + { + name: "zero small slice", + input: []byte{1, 2, 3, 4, 5}, + }, + { + name: "zero large slice", + input: make([]byte, 1024), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Fill with non-zero values + for i := range tt.input { + tt.input[i] = byte(i + 1) + } + + zeroBytes(tt.input) + + // Verify all bytes are zero + for i, b := range tt.input { + assert.Equal(t, byte(0), b, "byte at index %d should be 0", i) + } + }) + } +} + +func TestDecryptWithWrappedKey(t *testing.T) { + tests := []struct { + name string + encryptedResource EncryptedResource + privateKey *ecdh.PrivateKey + wantErr bool + errContain string + }{ + { + name: "missing ephemeral public key", + encryptedResource: EncryptedResource{ + Ciphertext: []byte("test"), + EncryptedKey: []byte("key"), + IV: []byte("iv"), + Tag: []byte("tag"), + EPK: nil, + }, + privateKey: nil, + wantErr: true, + errContain: "ephemeral public key is required", + }, + { + name: "invalid X coordinate encoding", + encryptedResource: EncryptedResource{ + Ciphertext: []byte("test"), + EncryptedKey: []byte("key"), + IV: []byte("iv"), + Tag: []byte("tag"), + EPK: &EphemeralPublicKey{ + Curve: "P-256", + X: "!!!invalid base64!!!", + Y: "AAAA", + }, + }, + privateKey: nil, + wantErr: true, + errContain: "invalid encrypted resource format", + }, + { + name: "invalid Y coordinate encoding", + encryptedResource: EncryptedResource{ + Ciphertext: []byte("test"), + EncryptedKey: []byte("key"), + IV: []byte("iv"), + Tag: []byte("tag"), + EPK: &EphemeralPublicKey{ + Curve: "P-256", + X: base64.RawURLEncoding.EncodeToString(make([]byte, 32)), + Y: "!!!invalid base64!!!", + }, + }, + privateKey: nil, + wantErr: true, + errContain: "invalid encrypted resource format", + }, + { + name: "invalid public key bytes", + encryptedResource: EncryptedResource{ + Ciphertext: []byte("test"), + EncryptedKey: []byte("key"), + IV: []byte("iv"), + Tag: []byte("tag"), + EPK: &EphemeralPublicKey{ + Curve: "P-256", + X: base64.RawURLEncoding.EncodeToString([]byte("short")), + Y: base64.RawURLEncoding.EncodeToString([]byte("short")), + }, + }, + privateKey: func() *ecdh.PrivateKey { + key, _ := ecdh.P256().GenerateKey(rand.Reader) + return key + }(), + wantErr: true, + errContain: "invalid encrypted resource format", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := DecryptWithWrappedKey(tt.encryptedResource, tt.privateKey) + + if tt.wantErr { + assert.Error(t, err) + if tt.errContain != "" { + assert.Contains(t, err.Error(), tt.errContain) + } + } else { + require.NoError(t, err) + assert.NotNil(t, got) + } + }) + } +} + +func TestUnwrapKey(t *testing.T) { + tests := []struct { + name string + wrappedKey []byte + kek []byte + wantErr bool + errContain string + }{ + { + name: "wrapped key too short", + wrappedKey: []byte("short"), + kek: make([]byte, 32), + wantErr: true, + errContain: "wrapped key length must be a multiple of 8 and at least 24 bytes", + }, + { + name: "wrapped key not multiple of 8", + wrappedKey: make([]byte, 25), + kek: make([]byte, 32), + wantErr: true, + errContain: "wrapped key length must be a multiple of 8 and at least 24 bytes", + }, + { + name: "invalid kek length", + wrappedKey: make([]byte, 24), + kek: []byte("short"), + wantErr: true, + errContain: "decryption failed", + }, + { + name: "integrity check failure", + wrappedKey: make([]byte, 24), + kek: make([]byte, 32), + wantErr: true, + errContain: "key unwrap integrity check failed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := unwrapKey(tt.wrappedKey, tt.kek) + + if tt.wantErr { + assert.Error(t, err) + if tt.errContain != "" { + assert.Contains(t, err.Error(), tt.errContain) + } + } else { + require.NoError(t, err) + assert.NotNil(t, got) + } + }) + } +} + +func TestEncryptedResourceStructure(t *testing.T) { + t.Run("EphemeralPublicKey JSON serialization", func(t *testing.T) { + epk := EphemeralPublicKey{ + Curve: "P-256", + X: "test_x", + Y: "test_y", + } + + data, err := json.Marshal(epk) + require.NoError(t, err) + + var decoded EphemeralPublicKey + err = json.Unmarshal(data, &decoded) + require.NoError(t, err) + + assert.Equal(t, epk.Curve, decoded.Curve) + assert.Equal(t, epk.X, decoded.X) + assert.Equal(t, epk.Y, decoded.Y) + }) + + t.Run("EncryptedResource JSON serialization", func(t *testing.T) { + resource := EncryptedResource{ + Ciphertext: []byte("ciphertext"), + EncryptedKey: []byte("encrypted_key"), + IV: []byte("iv"), + Tag: []byte("tag"), + AAD: []byte("aad"), + EPK: &EphemeralPublicKey{ + Curve: "P-256", + X: "x_coord", + Y: "y_coord", + }, + } + + data, err := json.Marshal(resource) + require.NoError(t, err) + + var decoded EncryptedResource + err = json.Unmarshal(data, &decoded) + require.NoError(t, err) + + assert.Equal(t, resource.Ciphertext, decoded.Ciphertext) + assert.Equal(t, resource.EncryptedKey, decoded.EncryptedKey) + assert.Equal(t, resource.IV, decoded.IV) + assert.Equal(t, resource.Tag, decoded.Tag) + assert.Equal(t, resource.AAD, decoded.AAD) + assert.NotNil(t, decoded.EPK) + assert.Equal(t, resource.EPK.Curve, decoded.EPK.Curve) + }) +} + +func TestDecryptWithWrappedKeyFullRoundTrip(t *testing.T) { + t.Run("full ECDH + key wrap + AES-GCM round trip", func(t *testing.T) { + // Generate recipient private key (who will decrypt) + recipientKey, err := ecdh.P256().GenerateKey(rand.Reader) + require.NoError(t, err) + + // Generate ephemeral key pair (used to encrypt) + ephemeralKey, err := ecdh.P256().GenerateKey(rand.Reader) + require.NoError(t, err) + + // Compute shared secret: ephemeral_private ECDH recipient_public + sharedSecret, err := ephemeralKey.ECDH(recipientKey.PublicKey()) + require.NoError(t, err) + + // Derive KEK using HKDF (same as in DecryptWithWrappedKey) + kek := make([]byte, 32) + kdf := hkdf.New(sha256.New, sharedSecret, nil, nil) + _, err = kdf.Read(kek) + require.NoError(t, err) + + // Generate random CEK (32 bytes) + cek := make([]byte, 32) + _, err = rand.Read(cek) + require.NoError(t, err) + + // Wrap CEK using AES Key Wrap (RFC 3394) + wrappedKey, err := testAESKeyWrap(kek, cek) + require.NoError(t, err) + + // Encrypt plaintext with AES-GCM using CEK + plaintext := []byte("hello world secret message for testing") + blk, err := aes.NewCipher(cek) + require.NoError(t, err) + aesgcm, err := cipher.NewGCM(blk) + require.NoError(t, err) + iv := make([]byte, aesgcm.NonceSize()) + _, err = rand.Read(iv) + require.NoError(t, err) + + // Go's Seal returns ciphertext || tag + combined := aesgcm.Seal(nil, iv, plaintext, nil) + ciphertext := combined[:len(combined)-aesgcm.Overhead()] + tag := combined[len(combined)-aesgcm.Overhead():] + + // Get ephemeral public key coordinates (uncompressed: 0x04 || X(32) || Y(32)) + epkPubBytes := ephemeralKey.PublicKey().Bytes() + xBytes := epkPubBytes[1:33] + yBytes := epkPubBytes[33:65] + + resource := EncryptedResource{ + Ciphertext: ciphertext, + EncryptedKey: wrappedKey, + IV: iv, + Tag: tag, + EPK: &EphemeralPublicKey{ + Curve: "P-256", + X: base64.RawURLEncoding.EncodeToString(xBytes), + Y: base64.RawURLEncoding.EncodeToString(yBytes), + }, + } + + decrypted, err := DecryptWithWrappedKey(resource, recipientKey) + require.NoError(t, err) + assert.Equal(t, plaintext, decrypted) + }) + + t.Run("full round trip with AAD", func(t *testing.T) { + recipientKey, err := ecdh.P256().GenerateKey(rand.Reader) + require.NoError(t, err) + + ephemeralKey, err := ecdh.P256().GenerateKey(rand.Reader) + require.NoError(t, err) + + sharedSecret, err := ephemeralKey.ECDH(recipientKey.PublicKey()) + require.NoError(t, err) + + kek := make([]byte, 32) + kdf := hkdf.New(sha256.New, sharedSecret, nil, nil) + _, err = kdf.Read(kek) + require.NoError(t, err) + + cek := make([]byte, 16) // 16-byte CEK (AES-128) + _, err = rand.Read(cek) + require.NoError(t, err) + + wrappedKey, err := testAESKeyWrap(kek, cek) + require.NoError(t, err) + + plaintext := []byte("confidential data with AAD") + aad := []byte("additional authenticated data") + + blk, err := aes.NewCipher(cek) + require.NoError(t, err) + aesgcm, err := cipher.NewGCM(blk) + require.NoError(t, err) + iv := make([]byte, aesgcm.NonceSize()) + _, err = rand.Read(iv) + require.NoError(t, err) + + combined := aesgcm.Seal(nil, iv, plaintext, aad) + ciphertext := combined[:len(combined)-aesgcm.Overhead()] + tag := combined[len(combined)-aesgcm.Overhead():] + + epkPubBytes := ephemeralKey.PublicKey().Bytes() + xBytes := epkPubBytes[1:33] + yBytes := epkPubBytes[33:65] + + resource := EncryptedResource{ + Ciphertext: ciphertext, + EncryptedKey: wrappedKey, + IV: iv, + Tag: tag, + AAD: aad, + EPK: &EphemeralPublicKey{ + Curve: "P-256", + X: base64.RawURLEncoding.EncodeToString(xBytes), + Y: base64.RawURLEncoding.EncodeToString(yBytes), + }, + } + + decrypted, err := DecryptWithWrappedKey(resource, recipientKey) + require.NoError(t, err) + assert.Equal(t, plaintext, decrypted) + }) + + t.Run("wrong private key fails decryption", func(t *testing.T) { + recipientKey, err := ecdh.P256().GenerateKey(rand.Reader) + require.NoError(t, err) + wrongKey, err := ecdh.P256().GenerateKey(rand.Reader) + require.NoError(t, err) + + ephemeralKey, err := ecdh.P256().GenerateKey(rand.Reader) + require.NoError(t, err) + + sharedSecret, err := ephemeralKey.ECDH(recipientKey.PublicKey()) + require.NoError(t, err) + + kek := make([]byte, 32) + kdf := hkdf.New(sha256.New, sharedSecret, nil, nil) + _, err = kdf.Read(kek) + require.NoError(t, err) + + cek := make([]byte, 32) + _, err = rand.Read(cek) + require.NoError(t, err) + + wrappedKey, err := testAESKeyWrap(kek, cek) + require.NoError(t, err) + + plaintext := []byte("secret") + blk, err := aes.NewCipher(cek) + require.NoError(t, err) + aesgcm, err := cipher.NewGCM(blk) + require.NoError(t, err) + iv := make([]byte, aesgcm.NonceSize()) + _, err = rand.Read(iv) + require.NoError(t, err) + + combined := aesgcm.Seal(nil, iv, plaintext, nil) + ciphertext := combined[:len(combined)-aesgcm.Overhead()] + tag := combined[len(combined)-aesgcm.Overhead():] + + epkPubBytes := ephemeralKey.PublicKey().Bytes() + xBytes := epkPubBytes[1:33] + yBytes := epkPubBytes[33:65] + + resource := EncryptedResource{ + Ciphertext: ciphertext, + EncryptedKey: wrappedKey, + IV: iv, + Tag: tag, + EPK: &EphemeralPublicKey{ + Curve: "P-256", + X: base64.RawURLEncoding.EncodeToString(xBytes), + Y: base64.RawURLEncoding.EncodeToString(yBytes), + }, + } + + // Using wrong key should fail + _, err = DecryptWithWrappedKey(resource, wrongKey) + assert.Error(t, err) + }) +} + +func TestErrorTypes(t *testing.T) { + t.Run("error constants are defined", func(t *testing.T) { + assert.NotNil(t, ErrDecryptionFailed) + assert.NotNil(t, ErrInvalidKey) + assert.NotNil(t, ErrInvalidCiphertext) + assert.NotNil(t, ErrInvalidFormat) + + assert.Equal(t, "decryption failed", ErrDecryptionFailed.Error()) + assert.Equal(t, "invalid decryption key", ErrInvalidKey.Error()) + assert.Equal(t, "invalid ciphertext", ErrInvalidCiphertext.Error()) + assert.Equal(t, "invalid encrypted resource format", ErrInvalidFormat.Error()) + }) +} diff --git a/pkg/oci/extract.go b/pkg/oci/extract.go new file mode 100644 index 00000000..209291d1 --- /dev/null +++ b/pkg/oci/extract.go @@ -0,0 +1,342 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 + +package oci + +import ( + "archive/tar" + "compress/gzip" + "context" + "encoding/json" + "fmt" + "io" + "log/slog" + "os" + "path/filepath" + "strings" +) + +// OCILayout represents the OCI image layout. +type OCILayout struct { + ImageLayoutVersion string `json:"imageLayoutVersion"` +} + +// OCIIndex represents the OCI index.json. +type OCIIndex struct { + SchemaVersion int `json:"schemaVersion"` + Manifests []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + } `json:"manifests"` +} + +// ExtractAlgorithm extracts the algorithm file from an OCI image directory. +func ExtractAlgorithm(ctx context.Context, logger *slog.Logger, ociDir, destPath string) (string, error) { + // Read index.json to find manifest + indexPath := filepath.Join(ociDir, "index.json") + indexData, err := os.ReadFile(indexPath) + if err != nil { + return "", fmt.Errorf("failed to read index.json: %w", err) + } + + var index OCIIndex + if err := json.Unmarshal(indexData, &index); err != nil { + return "", fmt.Errorf("failed to parse index.json: %w", err) + } + + if len(index.Manifests) == 0 { + return "", fmt.Errorf("no manifests found in index.json") + } + + // Get the first manifest digest + manifestDigest := index.Manifests[0].Digest + manifestPath := filepath.Join(ociDir, "blobs", strings.Replace(manifestDigest, ":", "/", 1)) + + // Read manifest to find layers + manifestData, err := os.ReadFile(manifestPath) + if err != nil { + return "", fmt.Errorf("failed to read manifest: %w", err) + } + + var manifest struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + } + if err := json.Unmarshal(manifestData, &manifest); err != nil { + return "", fmt.Errorf("failed to parse manifest: %w", err) + } + + // Extract layers to find algorithm files + logger.Debug("found layers in manifest", "count", len(manifest.Layers)) + var allSeenFiles []string + + // Iterate layers in reverse order to find user code first (usually in top layers) + for i := len(manifest.Layers) - 1; i >= 0; i-- { + layer := manifest.Layers[i] + layerPath := filepath.Join(ociDir, "blobs", strings.Replace(layer.Digest, ":", "/", 1)) + + // Try to extract and find algorithm file + algoPath, seenFiles, err := extractLayerAndFindAlgorithm(logger, layerPath, destPath) + if len(seenFiles) > 0 { + allSeenFiles = append(allSeenFiles, seenFiles...) + } + + if err != nil { + logger.Warn(fmt.Sprintf("error extracting layer %s: %v", layer.Digest, err)) + continue + } + + if algoPath != "" { + return algoPath, nil + } + } + + return "", fmt.Errorf("no algorithm file found in OCI image layers (seen: %v)", allSeenFiles) +} + +// extractLayerAndFindAlgorithm extracts a layer and searches for algorithm files. +func extractLayerAndFindAlgorithm(logger *slog.Logger, layerPath, destPath string) (string, []string, error) { + // Open layer file + layerFile, err := os.Open(layerPath) + if err != nil { + return "", nil, fmt.Errorf("failed to open layer: %w", err) + } + defer layerFile.Close() + + // Decompress gzip + gzReader, err := gzip.NewReader(layerFile) + if err != nil { + return "", nil, fmt.Errorf("failed to create gzip reader: %w", err) + } + defer gzReader.Close() + + // Read tar archive + tarReader := tar.NewReader(gzReader) + + var algorithmPath string + var seenFiles []string + + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return "", seenFiles, fmt.Errorf("failed to read tar header: %w", err) + } + + logger.Debug("inspecting file in layer", "name", header.Name, "type", header.Typeflag) + + // Skip directories + if header.Typeflag == tar.TypeDir { + continue + } + + seenFiles = append(seenFiles, header.Name) + + // Check if this is an algorithm file or requirements.txt + isAlgo := isAlgorithmFile(header.Name) + isReq := filepath.Base(header.Name) == "requirements.txt" + + if isAlgo || isReq { + // Extract to destination, preserving directory structure + // Clean the name to prevent path traversal + cleanName := filepath.Clean(header.Name) + if strings.HasPrefix(cleanName, "..") || strings.HasPrefix(cleanName, "/") { + continue + } + + targetPath := filepath.Join(destPath, cleanName) + + if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { + return "", seenFiles, fmt.Errorf("failed to create dir: %w", err) + } + + outFile, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(header.Mode)) + if err != nil { + return "", seenFiles, fmt.Errorf("failed to create file: %w", err) + } + + if _, err := io.Copy(outFile, tarReader); err != nil { + outFile.Close() + return "", seenFiles, fmt.Errorf("failed to write file: %w", err) + } + outFile.Close() + + if isAlgo { + algorithmPath = targetPath + } + // Continue scanning to extract other files (like requirements.txt) + } + } + + return algorithmPath, seenFiles, nil +} + +// isAlgorithmFile checks if a file is likely an algorithm file. +func isAlgorithmFile(filename string) bool { + // Common algorithm file extensions + algorithmExts := []string{".py", ".wasm", ".wat", ".js", ".sh"} + + // Common algorithm file names + algorithmNames := []string{"algorithm", "main", "run", "execute"} + + base := filepath.Base(filename) + baseLower := strings.ToLower(base) + + // Check extensions + for _, ext := range algorithmExts { + if strings.HasSuffix(baseLower, ext) { + return true + } + } + + // Check common names + for _, name := range algorithmNames { + if strings.Contains(baseLower, name) { + return true + } + } + + return false +} + +// ExtractDataset extracts dataset files from an OCI image directory. +func ExtractDataset(ociDir, destPath string) ([]string, error) { + // Similar to ExtractAlgorithm but extracts all data files + // Read index.json to find manifest + indexPath := filepath.Join(ociDir, "index.json") + indexData, err := os.ReadFile(indexPath) + if err != nil { + return nil, fmt.Errorf("failed to read index.json: %w", err) + } + + var index OCIIndex + if err := json.Unmarshal(indexData, &index); err != nil { + return nil, fmt.Errorf("failed to parse index.json: %w", err) + } + + if len(index.Manifests) == 0 { + return nil, fmt.Errorf("no manifests found in index.json") + } + + // Get the first manifest digest + manifestDigest := index.Manifests[0].Digest + manifestPath := filepath.Join(ociDir, "blobs", strings.Replace(manifestDigest, ":", "/", 1)) + + // Read manifest to find layers + manifestData, err := os.ReadFile(manifestPath) + if err != nil { + return nil, fmt.Errorf("failed to read manifest: %w", err) + } + + var manifest struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + } + if err := json.Unmarshal(manifestData, &manifest); err != nil { + return nil, fmt.Errorf("failed to parse manifest: %w", err) + } + + var datasetFiles []string + + // Extract all layers and collect dataset files + // Iterate layers in reverse order to find user data first (usually in top layers) + for i := len(manifest.Layers) - 1; i >= 0; i-- { + layer := manifest.Layers[i] + layerPath := filepath.Join(ociDir, "blobs", strings.Replace(layer.Digest, ":", "/", 1)) + + files, err := extractLayerDataFiles(layerPath, destPath) + if err != nil { + slog.Warn("error extracting layer", "digest", layer.Digest, "error", err) + continue + } + datasetFiles = append(datasetFiles, files...) + } + + if len(datasetFiles) == 0 { + return nil, fmt.Errorf("no dataset files found in OCI image layers") + } + + return datasetFiles, nil +} + +// extractLayerDataFiles extracts data files from a layer. +func extractLayerDataFiles(layerPath, destPath string) ([]string, error) { + layerFile, err := os.Open(layerPath) + if err != nil { + return nil, err + } + defer layerFile.Close() + + gzReader, err := gzip.NewReader(layerFile) + if err != nil { + return nil, err + } + defer gzReader.Close() + + tarReader := tar.NewReader(gzReader) + var extractedFiles []string + + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + + if header.Typeflag == tar.TypeDir { + continue + } + + // Check if this is a data file + if isDataFile(header.Name) { + // Extract to destination, preserving directory structure + cleanName := filepath.Clean(header.Name) + if strings.HasPrefix(cleanName, "..") || strings.HasPrefix(cleanName, "/") { + continue + } + + targetPath := filepath.Join(destPath, cleanName) + + if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { + return nil, err + } + + outFile, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(header.Mode)) + if err != nil { + return nil, err + } + + if _, err := io.Copy(outFile, tarReader); err != nil { + outFile.Close() + return nil, err + } + outFile.Close() + + extractedFiles = append(extractedFiles, targetPath) + } + } + + return extractedFiles, nil +} + +// isDataFile checks if a file is likely a dataset file. +func isDataFile(filename string) bool { + dataExts := []string{".csv", ".json", ".txt", ".parquet", ".arrow", ".dat"} + + baseLower := strings.ToLower(filepath.Base(filename)) + + for _, ext := range dataExts { + if strings.HasSuffix(baseLower, ext) { + return true + } + } + + return false +} diff --git a/pkg/oci/extract_test.go b/pkg/oci/extract_test.go new file mode 100644 index 00000000..68fc17af --- /dev/null +++ b/pkg/oci/extract_test.go @@ -0,0 +1,920 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 + +package oci + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "context" + "encoding/json" + "log/slog" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testPythonScript = "print('hello')" + +func TestIsAlgorithmFile(t *testing.T) { + tests := []struct { + name string + filename string + want bool + }{ + {"Python file", "algorithm.py", true}, + {"WASM file", "module.wasm", true}, + {"WAT file", "module.wat", true}, + {"JavaScript file", "script.js", true}, + {"Shell script", "run.sh", true}, + {"Main python file", "main.py", true}, + {"Execute file", "execute.py", true}, + {"Algorithm name in path", "src/algorithm_v2.py", true}, + {"Random python file", "helper.py", true}, + {"CSV data file", "data.csv", false}, + {"JSON config file", "config.json", false}, + {"Text file", "readme.txt", false}, + {"Binary file", "data.bin", false}, + {"Uppercase extension", "MAIN.PY", true}, + {"Mixed case", "Algorithm.Py", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isAlgorithmFile(tt.filename) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestIsDataFile(t *testing.T) { + tests := []struct { + name string + filename string + want bool + }{ + {"CSV file", "data.csv", true}, + {"JSON file", "config.json", true}, + {"Text file", "readme.txt", true}, + {"Parquet file", "data.parquet", true}, + {"Arrow file", "data.arrow", true}, + {"DAT file", "data.dat", true}, + {"Python file", "script.py", false}, + {"WASM file", "module.wasm", false}, + {"Binary file", "data.bin", false}, + {"Uppercase CSV", "DATA.CSV", true}, + {"Nested path", "data/input/dataset.csv", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isDataFile(tt.filename) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestExtractAlgorithm(t *testing.T) { + logger := slog.Default() + + t.Run("missing index.json", func(t *testing.T) { + tempDir := t.TempDir() + _, err := ExtractAlgorithm(context.Background(), logger, tempDir, t.TempDir()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to read index.json") + }) + + t.Run("invalid index.json", func(t *testing.T) { + tempDir := t.TempDir() + err := os.WriteFile(filepath.Join(tempDir, "index.json"), []byte("not json"), 0o644) + require.NoError(t, err) + + _, err = ExtractAlgorithm(context.Background(), logger, tempDir, t.TempDir()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse index.json") + }) + + t.Run("empty manifests", func(t *testing.T) { + tempDir := t.TempDir() + index := OCIIndex{SchemaVersion: 2} + data, _ := json.Marshal(index) + err := os.WriteFile(filepath.Join(tempDir, "index.json"), data, 0o644) + require.NoError(t, err) + + _, err = ExtractAlgorithm(context.Background(), logger, tempDir, t.TempDir()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no manifests found") + }) + + t.Run("successful extraction", func(t *testing.T) { + ociDir, destDir := setupTestOCIImage(t, "algorithm.py", testPythonScript) + algoPath, err := ExtractAlgorithm(context.Background(), logger, ociDir, destDir) + require.NoError(t, err) + assert.NotEmpty(t, algoPath) + assert.Contains(t, algoPath, "algorithm.py") + }) +} + +func TestExtractDataset(t *testing.T) { + t.Run("missing index.json", func(t *testing.T) { + tempDir := t.TempDir() + _, err := ExtractDataset(tempDir, t.TempDir()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to read index.json") + }) + + t.Run("successful extraction", func(t *testing.T) { + ociDir, destDir := setupTestOCIImage(t, "data.csv", "col1,col2\n1,2") + files, err := ExtractDataset(ociDir, destDir) + require.NoError(t, err) + assert.NotEmpty(t, files) + }) +} + +func TestExtractDatasetWithPathTraversal(t *testing.T) { + t.Run("path traversal skipped, valid file extracted", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + layerPath := filepath.Join(blobsDir, "layer123") + layerFile, err := os.Create(layerPath) + require.NoError(t, err) + + gw := gzip.NewWriter(layerFile) + tw := tar.NewWriter(gw) + + // Path traversal entry (should be skipped) + maliciousHdr := &tar.Header{ + Name: "../../../tmp/evil.csv", + Mode: 0o644, + Size: int64(len("evil")), + } + require.NoError(t, tw.WriteHeader(maliciousHdr)) + _, err = tw.Write([]byte("evil")) + require.NoError(t, err) + + // Valid CSV file + csvContent := "col1,col2\n1,2" + csvHdr := &tar.Header{ + Name: "data.csv", + Mode: 0o644, + Size: int64(len(csvContent)), + } + require.NoError(t, tw.WriteHeader(csvHdr)) + _, err = tw.Write([]byte(csvContent)) + require.NoError(t, err) + + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) + require.NoError(t, layerFile.Close()) + + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:layer123"}}, + } + manifestData, _ := json.Marshal(manifest) + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), manifestData, 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: len(manifestData)}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + files, err := ExtractDataset(ociDir, destDir) + require.NoError(t, err) + assert.Len(t, files, 1) + assert.Contains(t, files[0], "data.csv") + + // Verify malicious file was NOT created outside destDir + _, err = os.Stat("/tmp/evil.csv") + assert.True(t, os.IsNotExist(err)) + }) +} + +func TestExtractDatasetInvalidManifest(t *testing.T) { + t.Run("invalid manifest JSON", func(t *testing.T) { + ociDir := t.TempDir() + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), []byte("not json"), 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: 8}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + _, err := ExtractDataset(ociDir, t.TempDir()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse manifest") + }) +} + +func TestExtractDatasetWithDirectory(t *testing.T) { + t.Run("layer with directory entries for dataset", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + layerPath := filepath.Join(blobsDir, "layer123") + layerFile, err := os.Create(layerPath) + require.NoError(t, err) + + gw := gzip.NewWriter(layerFile) + tw := tar.NewWriter(gw) + + // Directory entry + dirHdr := &tar.Header{ + Name: "data/", + Mode: 0o755, + Typeflag: tar.TypeDir, + } + require.NoError(t, tw.WriteHeader(dirHdr)) + + // CSV inside directory + csvContent := "a,b\n1,2" + csvHdr := &tar.Header{ + Name: "data/dataset.csv", + Mode: 0o644, + Size: int64(len(csvContent)), + } + require.NoError(t, tw.WriteHeader(csvHdr)) + _, err = tw.Write([]byte(csvContent)) + require.NoError(t, err) + + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) + require.NoError(t, layerFile.Close()) + + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:layer123"}}, + } + manifestData, _ := json.Marshal(manifest) + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), manifestData, 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: len(manifestData)}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + files, err := ExtractDataset(ociDir, destDir) + require.NoError(t, err) + require.Len(t, files, 1) + assert.Contains(t, files[0], "dataset.csv") + }) +} + +func TestExtractDatasetMissingManifest(t *testing.T) { + t.Run("manifest file not found", func(t *testing.T) { + ociDir := t.TempDir() + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:nonexistent", Size: 0}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + _, err := ExtractDataset(ociDir, t.TempDir()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to read manifest") + }) +} + +func TestOCILayoutStructure(t *testing.T) { + t.Run("OCILayout JSON serialization", func(t *testing.T) { + layout := OCILayout{ImageLayoutVersion: "1.0.0"} + + data, err := json.Marshal(layout) + require.NoError(t, err) + + var decoded OCILayout + err = json.Unmarshal(data, &decoded) + require.NoError(t, err) + + assert.Equal(t, layout.ImageLayoutVersion, decoded.ImageLayoutVersion) + }) +} + +func setupTestOCIImage(t *testing.T, filename, content string) (ociDir, destDir string) { + t.Helper() + + ociDir = t.TempDir() + destDir = t.TempDir() + + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + layerPath := filepath.Join(blobsDir, "layer123") + layerFile, err := os.Create(layerPath) + require.NoError(t, err) + + gw := gzip.NewWriter(layerFile) + tw := tar.NewWriter(gw) + + hdr := &tar.Header{ + Name: filename, + Mode: 0o644, + Size: int64(len(content)), + } + require.NoError(t, tw.WriteHeader(hdr)) + _, err = tw.Write([]byte(content)) + require.NoError(t, err) + + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) + require.NoError(t, layerFile.Close()) + + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:layer123"}}, + } + manifestData, err := json.Marshal(manifest) + require.NoError(t, err) + manifestPath := filepath.Join(blobsDir, "manifest123") + require.NoError(t, os.WriteFile(manifestPath, manifestData, 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{ + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: "sha256:manifest123", + Size: len(manifestData), + }}, + } + indexData, err := json.Marshal(index) + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + return ociDir, destDir +} + +func TestExtractAlgorithmWithRequirements(t *testing.T) { + logger := slog.Default() + + t.Run("extract algorithm with requirements.txt", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + layerPath := filepath.Join(blobsDir, "layer123") + layerFile, err := os.Create(layerPath) + require.NoError(t, err) + + gw := gzip.NewWriter(layerFile) + tw := tar.NewWriter(gw) + + // Add algorithm file + algoContent := testPythonScript + algoHdr := &tar.Header{ + Name: "main.py", + Mode: 0o644, + Size: int64(len(algoContent)), + } + require.NoError(t, tw.WriteHeader(algoHdr)) + _, err = tw.Write([]byte(algoContent)) + require.NoError(t, err) + + // Add requirements.txt + reqContent := "numpy==1.21.0\npandas==1.3.0" + reqHdr := &tar.Header{ + Name: "requirements.txt", + Mode: 0o644, + Size: int64(len(reqContent)), + } + require.NoError(t, tw.WriteHeader(reqHdr)) + _, err = tw.Write([]byte(reqContent)) + require.NoError(t, err) + + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) + require.NoError(t, layerFile.Close()) + + // Create manifest and index + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:layer123"}}, + } + manifestData, err := json.Marshal(manifest) + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), manifestData, 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: len(manifestData)}}, + } + indexData, err := json.Marshal(index) + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + algoPath, err := ExtractAlgorithm(context.Background(), logger, ociDir, destDir) + require.NoError(t, err) + assert.Contains(t, algoPath, "main.py") + + // Verify requirements.txt was also extracted + reqPath := filepath.Join(destDir, "requirements.txt") + _, err = os.Stat(reqPath) + assert.NoError(t, err) + }) +} + +func TestExtractAlgorithmNoAlgoFile(t *testing.T) { + logger := slog.Default() + + t.Run("no algorithm file in layers", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + layerPath := filepath.Join(blobsDir, "layer123") + layerFile, err := os.Create(layerPath) + require.NoError(t, err) + + gw := gzip.NewWriter(layerFile) + tw := tar.NewWriter(gw) + + // Add a non-algorithm file (e.g., just a readme) + readmeContent := "This is a readme" + readmeHdr := &tar.Header{ + Name: "README.md", + Mode: 0o644, + Size: int64(len(readmeContent)), + } + require.NoError(t, tw.WriteHeader(readmeHdr)) + _, err = tw.Write([]byte(readmeContent)) + require.NoError(t, err) + + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) + require.NoError(t, layerFile.Close()) + + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:layer123"}}, + } + manifestData, _ := json.Marshal(manifest) + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), manifestData, 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: len(manifestData)}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + _, err = ExtractAlgorithm(context.Background(), logger, ociDir, destDir) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no algorithm file found") + }) +} + +func TestExtractDatasetNoDataFiles(t *testing.T) { + t.Run("no data files in layers", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + layerPath := filepath.Join(blobsDir, "layer123") + layerFile, err := os.Create(layerPath) + require.NoError(t, err) + + gw := gzip.NewWriter(layerFile) + tw := tar.NewWriter(gw) + + // Add a python file (not a data file) + pyContent := testPythonScript + pyHdr := &tar.Header{ + Name: "script.py", + Mode: 0o644, + Size: int64(len(pyContent)), + } + require.NoError(t, tw.WriteHeader(pyHdr)) + _, err = tw.Write([]byte(pyContent)) + require.NoError(t, err) + + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) + require.NoError(t, layerFile.Close()) + + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:layer123"}}, + } + manifestData, _ := json.Marshal(manifest) + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), manifestData, 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: len(manifestData)}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + _, err = ExtractDataset(ociDir, destDir) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no dataset files found") + }) +} + +func TestExtractAlgorithmInvalidManifest(t *testing.T) { + logger := slog.Default() + + t.Run("invalid manifest JSON", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + // Write invalid manifest + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), []byte("not json"), 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: 8}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + _, err := ExtractAlgorithm(context.Background(), logger, ociDir, destDir) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse manifest") + }) +} + +func TestExtractAlgorithmMissingManifest(t *testing.T) { + logger := slog.Default() + + t.Run("manifest file not found", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + // Don't create manifest file + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:missing123", Size: 8}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + _, err := ExtractAlgorithm(context.Background(), logger, ociDir, destDir) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to read manifest") + }) +} + +func TestExtractAlgorithmWithDirectory(t *testing.T) { + logger := slog.Default() + + t.Run("layer with directory entries", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + layerPath := filepath.Join(blobsDir, "layer123") + layerFile, err := os.Create(layerPath) + require.NoError(t, err) + + gw := gzip.NewWriter(layerFile) + tw := tar.NewWriter(gw) + + // Add a directory entry + dirHdr := &tar.Header{ + Name: "src/", + Mode: 0o755, + Typeflag: tar.TypeDir, + } + require.NoError(t, tw.WriteHeader(dirHdr)) + + // Add algorithm file in subdirectory + algoContent := testPythonScript + algoHdr := &tar.Header{ + Name: "src/main.py", + Mode: 0o644, + Size: int64(len(algoContent)), + } + require.NoError(t, tw.WriteHeader(algoHdr)) + _, err = tw.Write([]byte(algoContent)) + require.NoError(t, err) + + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) + require.NoError(t, layerFile.Close()) + + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:layer123"}}, + } + manifestData, _ := json.Marshal(manifest) + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), manifestData, 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: len(manifestData)}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + algoPath, err := ExtractAlgorithm(context.Background(), logger, ociDir, destDir) + require.NoError(t, err) + assert.Contains(t, algoPath, "main.py") + }) +} + +func TestExtractAlgorithmPathTraversal(t *testing.T) { + logger := slog.Default() + + t.Run("path traversal attempt", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + layerPath := filepath.Join(blobsDir, "layer123") + layerFile, err := os.Create(layerPath) + require.NoError(t, err) + + gw := gzip.NewWriter(layerFile) + tw := tar.NewWriter(gw) + + // Add a file with path traversal attempt + maliciousContent := "malicious" + maliciousHdr := &tar.Header{ + Name: "../../../etc/malicious.py", + Mode: 0o644, + Size: int64(len(maliciousContent)), + } + require.NoError(t, tw.WriteHeader(maliciousHdr)) + _, err = tw.Write([]byte(maliciousContent)) + require.NoError(t, err) + + // Add a legit file + algoContent := testPythonScript + algoHdr := &tar.Header{ + Name: "algorithm.py", + Mode: 0o644, + Size: int64(len(algoContent)), + } + require.NoError(t, tw.WriteHeader(algoHdr)) + _, err = tw.Write([]byte(algoContent)) + require.NoError(t, err) + + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) + require.NoError(t, layerFile.Close()) + + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:layer123"}}, + } + manifestData, _ := json.Marshal(manifest) + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), manifestData, 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: len(manifestData)}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + algoPath, err := ExtractAlgorithm(context.Background(), logger, ociDir, destDir) + require.NoError(t, err) + assert.Contains(t, algoPath, "algorithm.py") + + // Verify malicious file was NOT extracted outside destDir + _, err = os.Stat("/etc/malicious.py") + assert.True(t, os.IsNotExist(err)) + }) +} + +func TestExtractAlgorithmErrorPathsAdditional(t *testing.T) { + logger := slog.Default() + + t.Run("invalid layer gzip", func(t *testing.T) { + ociDir, destDir := setupTestOCIImage(t, "main.py", "print('hello')") + // Corrupt the layer file + layerPath := filepath.Join(ociDir, "blobs", "sha256", "layer123") + err := os.WriteFile(layerPath, []byte("not gzip"), 0o644) + require.NoError(t, err) + + _, err = ExtractAlgorithm(context.Background(), logger, ociDir, destDir) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no algorithm file found") + }) + + t.Run("invalid tar formatting", func(t *testing.T) { + ociDir, destDir := setupTestOCIImage(t, "main.py", "print('hello')") + layerPath := filepath.Join(ociDir, "blobs", "sha256", "layer123") + + // Create a valid gzip but invalid tar + var buf bytes.Buffer + gw := gzip.NewWriter(&buf) + _, err := gw.Write([]byte("not a tar archive but it is gzipped")) + require.NoError(t, err) + gw.Close() + err = os.WriteFile(layerPath, buf.Bytes(), 0o644) + require.NoError(t, err) + + _, err = ExtractAlgorithm(context.Background(), logger, ociDir, destDir) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no algorithm file found") + }) + + t.Run("non-existent layer file", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:nonexistent"}}, + } + manifestData, _ := json.Marshal(manifest) + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), manifestData, 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: len(manifestData)}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + _, err := ExtractAlgorithm(context.Background(), logger, ociDir, destDir) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no algorithm file found") + }) +} + +func TestExtractDatasetErrorPathsAdditional(t *testing.T) { + t.Run("invalid layer gzip", func(t *testing.T) { + ociDir, destDir := setupTestOCIImage(t, "data.csv", "a,b,c") + layerPath := filepath.Join(ociDir, "blobs", "sha256", "layer123") + err := os.WriteFile(layerPath, []byte("not gzip"), 0o644) + require.NoError(t, err) + + _, err = ExtractDataset(ociDir, destDir) + assert.Error(t, err) + }) + + t.Run("non-existent layer file", func(t *testing.T) { + ociDir := t.TempDir() + destDir := t.TempDir() + blobsDir := filepath.Join(ociDir, "blobs", "sha256") + require.NoError(t, os.MkdirAll(blobsDir, 0o755)) + + manifest := struct { + Layers []struct { + Digest string `json:"digest"` + } `json:"layers"` + }{ + Layers: []struct { + Digest string `json:"digest"` + }{{Digest: "sha256:nonexistent"}}, + } + manifestData, _ := json.Marshal(manifest) + require.NoError(t, os.WriteFile(filepath.Join(blobsDir, "manifest123"), manifestData, 0o644)) + + index := OCIIndex{ + SchemaVersion: 2, + Manifests: []struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + }{{Digest: "sha256:manifest123", Size: len(manifestData)}}, + } + indexData, _ := json.Marshal(index) + require.NoError(t, os.WriteFile(filepath.Join(ociDir, "index.json"), indexData, 0o644)) + + _, err := ExtractDataset(ociDir, destDir) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no dataset files found") + }) +} diff --git a/pkg/oci/skopeo.go b/pkg/oci/skopeo.go new file mode 100644 index 00000000..9e1cc23b --- /dev/null +++ b/pkg/oci/skopeo.go @@ -0,0 +1,115 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 + +package oci + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" +) + +const ( + // OCICryptKeyproviderConfig is the environment variable for ocicrypt config. + OCICryptKeyproviderConfig = "OCICRYPT_KEYPROVIDER_CONFIG" + + // DefaultOCICryptConfig is the default path to ocicrypt config. + DefaultOCICryptConfig = "/etc/ocicrypt_keyprovider.conf" + + // DecryptionKeyProvider is the decryption key provider for CoCo. + DecryptionKeyProvider = "provider:attestation-agent:cc_kbc::null" +) + +// SkopeoClient wraps skopeo command-line operations. +type SkopeoClient struct { + skopeoPath string + workDir string +} + +// NewSkopeoClient creates a new Skopeo client. +func NewSkopeoClient(workDir string) (*SkopeoClient, error) { + // Find skopeo binary + skopeoPath, err := exec.LookPath("skopeo") + if err != nil { + return nil, fmt.Errorf("skopeo not found in PATH: %w", err) + } + + // Ensure work directory exists + if err := os.MkdirAll(workDir, 0o755); err != nil { + return nil, fmt.Errorf("failed to create work directory: %w", err) + } + + return &SkopeoClient{ + skopeoPath: skopeoPath, + workDir: workDir, + }, nil +} + +// PullAndDecrypt pulls an OCI image and decrypts it if encrypted. +func (s *SkopeoClient) PullAndDecrypt(ctx context.Context, source ResourceSource, destDir string) error { + // Ensure destination directory exists + if err := os.MkdirAll(destDir, 0o755); err != nil { + return fmt.Errorf("failed to create destination directory: %w", err) + } + + args := []string{"copy"} + + // Add decryption key if image is encrypted + if source.Encrypted { + args = append(args, "--decryption-key", DecryptionKeyProvider) + } + + // Add insecure policy for testing (TODO: use proper policy in production) + args = append(args, "--insecure-policy", "--src-tls-verify=false", "--dest-tls-verify=false") + + // Source and destination + args = append(args, source.URI, "oci:"+destDir) + + cmd := exec.CommandContext(ctx, s.skopeoPath, args...) + + // Set OCICRYPT environment + cmd.Env = append(os.Environ(), + OCICryptKeyproviderConfig+"="+DefaultOCICryptConfig) + + // Set working directory + cmd.Dir = s.workDir + + // Capture output + // Debug: Print full command + fmt.Printf("executing skopeo command: %s %v\n", s.skopeoPath, args) + fmt.Printf("skopeo environment: %s\n", OCICryptKeyproviderConfig+"="+DefaultOCICryptConfig) + + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("skopeo copy failed: %w\nOutput: %s", err, string(output)) + } + + return nil +} + +// Inspect inspects an OCI image and returns basic manifest information. +func (s *SkopeoClient) Inspect(ctx context.Context, imageRef string) (*ImageManifest, error) { + args := []string{"inspect", "--insecure-policy", "--tls-verify=false", imageRef} + + cmd := exec.CommandContext(ctx, s.skopeoPath, args...) + cmd.Env = append(os.Environ(), + OCICryptKeyproviderConfig+"="+DefaultOCICryptConfig) + + output, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("skopeo inspect failed: %w\nOutput: %s", err, string(output)) + } + + // For now, return basic info + // nolint:godox // TODO: Parse JSON output for detailed manifest info + return &ImageManifest{ + Reference: imageRef, + }, nil +} + +// GetLocalImagePath returns the path to a local OCI image directory. +func (s *SkopeoClient) GetLocalImagePath(name string) string { + return filepath.Join(s.workDir, name) +} diff --git a/pkg/oci/skopeo_test.go b/pkg/oci/skopeo_test.go new file mode 100644 index 00000000..dfcd5825 --- /dev/null +++ b/pkg/oci/skopeo_test.go @@ -0,0 +1,185 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 + +package oci + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewSkopeoClient(t *testing.T) { + t.Run("valid work directory", func(t *testing.T) { + workDir := t.TempDir() + client, err := NewSkopeoClient(workDir) + if err != nil && err.Error() == "skopeo not found in PATH: exec: \"skopeo\": executable file not found in $PATH" { + t.Skip("skopeo not installed, skipping test") + } + require.NoError(t, err) + assert.NotNil(t, client) + }) + + t.Run("new work directory", func(t *testing.T) { + workDir := filepath.Join(t.TempDir(), "new", "nested", "dir") + client, err := NewSkopeoClient(workDir) + if err != nil && err.Error() == "skopeo not found in PATH: exec: \"skopeo\": executable file not found in $PATH" { + t.Skip("skopeo not installed, skipping test") + } + require.NoError(t, err) + assert.NotNil(t, client) + }) +} + +func TestSkopeoClient_GetLocalImagePath(t *testing.T) { + workDir := t.TempDir() + client, err := NewSkopeoClient(workDir) + if err != nil { + t.Skip("skopeo not installed, skipping test") + } + + tests := []struct { + name string + imgName string + expected string + }{ + {"simple image name", "myimage", filepath.Join(workDir, "myimage")}, + {"image with tag", "myimage:latest", filepath.Join(workDir, "myimage:latest")}, + {"nested path", "registry/repo/image", filepath.Join(workDir, "registry/repo/image")}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := client.GetLocalImagePath(tt.imgName) + assert.Equal(t, tt.expected, got) + }) + } +} + +func TestSkopeoClient_PullAndDecrypt(t *testing.T) { + workDir := t.TempDir() + client, err := NewSkopeoClient(workDir) + if err != nil { + t.Skip("skopeo not installed, skipping test") + } + + t.Run("invalid source URI", func(t *testing.T) { + ctx := context.Background() + destDir := t.TempDir() + source := ResourceSource{ + Type: ResourceTypeOCIImage, + URI: "invalid://not-a-valid-uri", + Encrypted: false, + } + err := client.PullAndDecrypt(ctx, source, destDir) + assert.Error(t, err) + }) + + t.Run("destination directory created", func(t *testing.T) { + ctx := context.Background() + destDir := filepath.Join(t.TempDir(), "new", "nested", "dest") + source := ResourceSource{ + Type: ResourceTypeOCIImage, + URI: "invalid://test", + Encrypted: false, + } + _ = client.PullAndDecrypt(ctx, source, destDir) + _, err := os.Stat(destDir) + assert.NoError(t, err) + }) +} + +func TestSkopeoClient_Inspect(t *testing.T) { + workDir := t.TempDir() + client, err := NewSkopeoClient(workDir) + if err != nil { + t.Skip("skopeo not installed, skipping test") + } + + t.Run("invalid image reference", func(t *testing.T) { + ctx := context.Background() + manifest, err := client.Inspect(ctx, "invalid://not-a-valid-ref") + assert.Error(t, err) + assert.Nil(t, manifest) + }) +} + +func TestResourceSource(t *testing.T) { + t.Run("ResourceType constants", func(t *testing.T) { + assert.Equal(t, ResourceType("oci-image"), ResourceTypeOCIImage) + }) + + t.Run("ResourceSource structure", func(t *testing.T) { + source := ResourceSource{ + Type: ResourceTypeOCIImage, + URI: "docker://registry/repo:tag", + Encrypted: true, + KBSResourcePath: "default/key/algo-key", + } + assert.Equal(t, ResourceTypeOCIImage, source.Type) + assert.Equal(t, "docker://registry/repo:tag", source.URI) + assert.True(t, source.Encrypted) + assert.Equal(t, "default/key/algo-key", source.KBSResourcePath) + }) +} + +func TestImageManifest(t *testing.T) { + t.Run("ImageManifest structure", func(t *testing.T) { + manifest := ImageManifest{ + Reference: "docker://registry/repo:tag", + Digest: "sha256:abc123", + Layers: []string{"sha256:layer1", "sha256:layer2"}, + } + assert.Equal(t, "docker://registry/repo:tag", manifest.Reference) + assert.Equal(t, "sha256:abc123", manifest.Digest) + assert.Len(t, manifest.Layers, 2) + }) +} + +func TestSkopeoConstants(t *testing.T) { + assert.Equal(t, "OCICRYPT_KEYPROVIDER_CONFIG", OCICryptKeyproviderConfig) + assert.Equal(t, "/etc/ocicrypt_keyprovider.conf", DefaultOCICryptConfig) + assert.Equal(t, "provider:attestation-agent:cc_kbc::null", DecryptionKeyProvider) +} + +func TestNewSkopeoClientUnwritableDir(t *testing.T) { + if os.Getuid() == 0 { + t.Skip("cannot test unwritable dir as root") + } + + // Create a file where a directory is expected + tmpDir := t.TempDir() + blockingFile := filepath.Join(tmpDir, "blocking") + require.NoError(t, os.WriteFile(blockingFile, []byte("data"), 0o444)) + + // Try to create a client with workDir inside a file (not a dir) + _, err := NewSkopeoClient(filepath.Join(blockingFile, "subdir")) + assert.Error(t, err) +} + +func TestSkopeoClientPullAndDecryptEncrypted(t *testing.T) { + workDir := t.TempDir() + client, err := NewSkopeoClient(workDir) + if err != nil { + t.Skip("skopeo not installed, skipping test") + } + + t.Run("encrypted image uses decryption key flag", func(t *testing.T) { + ctx := context.Background() + destDir := t.TempDir() + // Encrypted source - skopeo call will fail but the --decryption-key arg is built + source := ResourceSource{ + Type: ResourceTypeOCIImage, + URI: "docker://invalid.registry/nonexistent:latest", + Encrypted: true, + } + err := client.PullAndDecrypt(ctx, source, destDir) + // We expect an error (no such image) but the encrypted code path was exercised + assert.Error(t, err) + assert.Contains(t, err.Error(), "skopeo copy failed") + }) +} diff --git a/pkg/oci/types.go b/pkg/oci/types.go new file mode 100644 index 00000000..51c17c1a --- /dev/null +++ b/pkg/oci/types.go @@ -0,0 +1,40 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 + +package oci + +// ResourceType defines the type of OCI resource. +type ResourceType string + +const ( + // ResourceTypeOCIImage represents a standard OCI image. + ResourceTypeOCIImage ResourceType = "oci-image" +) + +// ResourceSource defines the source of an OCI resource. +type ResourceSource struct { + // Type of resource (oci-image) + Type ResourceType `json:"type"` + + // URI is the OCI image reference (e.g., "docker://registry/repo:tag") + URI string `json:"uri"` + + // Encrypted indicates if the image is encrypted + Encrypted bool `json:"encrypted"` + + // KBSResourcePath is the KBS resource path for the decryption key + // (e.g., "default/key/algo-key") + KBSResourcePath string `json:"kbs_resource_path,omitempty"` +} + +// ImageManifest represents basic OCI image manifest information. +type ImageManifest struct { + // Reference is the original image reference + Reference string + + // Digest is the image digest + Digest string + + // Layers are the layer digests + Layers []string +} diff --git a/scripts/attestation_policy/sev-snp/attestation_policy.json b/scripts/attestation_policy/sev-snp/attestation_policy.json index 4b533f3c..4f6451ee 100644 --- a/scripts/attestation_policy/sev-snp/attestation_policy.json +++ b/scripts/attestation_policy/sev-snp/attestation_policy.json @@ -1,8 +1,10 @@ { "pcr_values": { + "sha1": null, "sha256": { "0": "71e0cc99e4609fdbc44698cceeda9e5ecb2f74fe07bd10710d5330e0eb6bd32b", "1": "ac95eee1ca55ae3c7cffc6126d6fc854a801cc03203583e6b96a4a706d368ad2", + "16": "27e979da1d644911979ee35b71f005962d6471b6bb324240959003e167a54906", "2": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969", "3": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969", "4": "db8197e8eef9069966988524d1da98fae9b41f96f0204efcf5c1c3ac9496ae54", @@ -14,6 +16,7 @@ "sha384": { "0": "ff93a763afde2c4a152d4843d9fcabe73a70d4f34bf8861845f2ab08440c1f0742b5882ed7f2524e38a3a6e40fbcdfca", "1": "707d1b180015e36792ffe396367a00575c45b6c920f97883074bfad183d8669c73d748df84c658ca8b58b8d73bb38642", + "16": "1ee325aad737c22f0d411255071b30b1a22bb1d7859bae37bcaca88d62a49cb434eedbf78428d7d7ca450579749ac074", "2": "518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4", "3": "518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4", "4": "a442d31eb3c47cc9287fd07ceeeb7798cfb1550fbf9388f9fc7d83494ef0411e18b78bd28eb95f060daab69095d6f384", @@ -24,36 +27,26 @@ } }, "policy": { - "chip_id": "GrFqtQ+lrkLsjBslu9pcC6XqkrtFWY1ArIQ+I4gugQIsvCG0qekSvEtE4P/SLSJ6mHNpOkY0MHnGpvz1OkV+kw==", - "family_id": "AAAAAAAAAAAAAAAAAAAAAA==", - "host_data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "image_id": "AAAAAAAAAAAAAAAAAAAAAA==", + "chipId": "GrFqtQ+lrkLsjBslu9pcC6XqkrtFWY1ArIQ+I4gugQIsvCG0qekSvEtE4P/SLSJ6mHNpOkY0MHnGpvz1OkV+kw==", + "familyId": "AAAAAAAAAAAAAAAAAAAAAA==", + "hostData": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "imageId": "AAAAAAAAAAAAAAAAAAAAAA==", "measurement": "oDYo4e98Da2Fy73nDVZmxiWiz+5gnxae7NMRtdfnwpbBuVYZsI0mynz3fpfe+YIX", - "minimum_build": 8, - "minimum_launch_tcb": 15352208179752599555, - "minimum_tcb": 15352208179752599555, - "minimum_version": "1.55", - "permit_provisional_firmware": true, - "policy": 196608, + "minimumBuild": 8, + "minimumLaunchTcb": "15352208179752599555", + "minimumTcb": "15352208179752599555", + "minimumVersion": "1.55", + "permitProvisionalFirmware": true, + "policy": "196608", "product": { - "name": 1 + "name": "SEV_PRODUCT_MILAN" }, - "report_id_ma": "//////////////////////////////////////////8=", - "require_author_key": false, - "require_id_block": false, + "reportIdMa": "//////////////////////////////////////////8=", "vmpl": 2 }, - "root_of_trust": { - "check_crl": true, - "disallow_network": false, + "rootOfTrust": { + "checkCrl": true, "product": "Milan", - "product_line": "Milan" - }, - "eat_validation": { - "require_eat_format": true, - "allowed_formats": ["CBOR", "JWT"], - "max_token_age_seconds": 300, - "require_claims": ["eat_nonce", "measurements", "platform_type"], - "verify_signature": true + "productLine": "Milan" } -} +} \ No newline at end of file diff --git a/scripts/attestation_policy/sev-snp/src/main.rs b/scripts/attestation_policy/sev-snp/src/main.rs index fef38f20..20d84dc9 100644 --- a/scripts/attestation_policy/sev-snp/src/main.rs +++ b/scripts/attestation_policy/sev-snp/src/main.rs @@ -2,7 +2,7 @@ use base64::prelude::*; use clap::{value_parser, Arg, Command}; use serde::Serialize; use serde_json::Value; -use sev::firmware::host::*; +use sev::firmware::host::{Firmware, Identifier, SnpPlatformStatus, TcbVersion}; use std::arch::x86_64::__cpuid; use std::fs::read_to_string; @@ -57,6 +57,7 @@ struct Computation { root_of_trust: RootOfTrust, } +#[allow(unused_unsafe)] fn get_sev_snp_processor() -> u32 { let cpuid_result = unsafe { __cpuid(1) }; cpuid_result.eax @@ -70,11 +71,11 @@ fn get_product_name(product: i32) -> String { } } -fn get_uint64_from_tcb(tcb_version: &TcbVersion) -> u64 { - let microcode = (tcb_version.microcode as u64) << 56; - let snp = (tcb_version.snp as u64) << 48; - let tee = (tcb_version.tee as u64) << 8; - let bootloader: u64 = tcb_version.bootloader as u64; +fn get_uint64_from_tcb(tcb_version: TcbVersion) -> u64 { + let microcode = u64::from(tcb_version.microcode) << 56; + let snp = u64::from(tcb_version.snp) << 48; + let tee = u64::from(tcb_version.tee) << 8; + let bootloader = u64::from(tcb_version.bootloader); microcode | snp | tee | bootloader } @@ -133,8 +134,8 @@ fn main() { let family_id = BASE64_STANDARD.encode(vec![0; 16]); let image_id = BASE64_STANDARD.encode(vec![0; 16]); let vmpl = 2; - let minimum_tcb = get_uint64_from_tcb(&status.reported_tcb_version); - let minimum_launch_tcb = get_uint64_from_tcb(&status.reported_tcb_version); + let minimum_tcb = get_uint64_from_tcb(status.reported_tcb_version); + let minimum_launch_tcb = get_uint64_from_tcb(status.reported_tcb_version); let require_author_key = false; let measurement = BASE64_STANDARD.encode(vec![0; 48]); let host_data = BASE64_STANDARD.encode(vec![0; 32]); diff --git a/test/cvms/README.md b/test/cvms/README.md index 6a508fd6..7dd5634e 100644 --- a/test/cvms/README.md +++ b/test/cvms/README.md @@ -1,10 +1,12 @@ -# Cvms Server -Agent has a cvms grpc client. It connects to cvms server. -The server then responds with a run computation request. Once agent receives the computation request it will launch an agent gRPC server and initliaze agent with a new computation manifest. Agent will then pass logs and events to cvms server. `main.go` is a sample of how such a server would be implemented. This is a very simple example for testing purposes. +# CVMS Test Server + +The Agent has a CVMS gRPC client that connects to a CVMS (CVM Management Service) server. The server sends computation run requests to the agent via gRPC. Once the agent receives the computation request, it launches an agent gRPC server and initializes with the computation manifest. The agent then passes logs and events back to the CVMS server. + +`main.go` is a sample implementation of a CVMS server for testing purposes. It demonstrates both **direct upload mode** (legacy) and **remote resource mode** (with KBS attestation). ## Configuration -The service is configured using the environment variables from the following table. Note that any unset variables will be replaced with their default values. +The service is configured using environment variables from the following table. Note that any unset variables will be replaced with their default values. | Variable | Description | Default | | ---------------- | ---------------------------------------- | ------- | @@ -13,22 +15,125 @@ The service is configured using the environment variables from the following tab | SERVER_CERT | Path to server certificate in pem format | | | SERVER_KEY | Path to server key in pem format | | -## Running -```shell -Usage of tests/cvms/main.go: - -algo-path string - Path to the algorithm - -attested-tls-bool string - Should aTLS be used, must be 'true' or 'false' - -ca-url string - URL for certificate authority, optional flag that can only be used if aTLS is enabled - -cvm-id string - UUID for a CVM, optional flag that can only be used if aTLS is enabled - -data-paths string - Paths to data sources, list of string separated with commas - -public-key-path string - Path to the public key file +## Command-Line Flags -# Example -go run ./tests/cvms/main.go -algo-path -attested-tls-bool false -data-paths -public-key-path +### Required Flags + +| Flag | Description | +| ---- | ----------- | +| `-public-key-path` | Path to the public key file (PEM format) | +| `-attested-tls-bool` | Whether to use attested TLS ('true' or 'false') | + +### Direct Upload Mode Flags + +| Flag | Description | +| ---- | ----------- | +| `-algo-path` | Path to the algorithm file (required if not using remote algorithm) | +| `-data-paths` | Comma-separated paths to dataset files (optional) | + +### Remote Resource Mode Flags + +| Flag | Description | +| ---- | ----------- | +| `-kbs-url` | KBS endpoint URL (e.g., 'http://localhost:8080') | +| `-algo-source-url` | Algorithm source URL (s3://bucket/key or https://...) | +| `-algo-kbs-path` | Algorithm KBS resource path (e.g., 'default/key/algo-key') | +| `-dataset-source-urls` | Comma-separated dataset source URLs | +| `-dataset-kbs-paths` | Comma-separated dataset KBS resource paths | + +### Optional Flags + +| Flag | Description | +| ---- | ----------- | +| `-client-ca-file` | Client CA root certificate file path (for mTLS) | + +## Running + +### Direct Upload Mode (Legacy) + +In this mode, the algorithm and datasets are uploaded directly via the CLI, and the CVMS server only sends their hashes in the manifest. + +```bash +go run ./test/cvms/main.go \ + -algo-path /path/to/algorithm.wasm \ + -data-paths /path/to/data1.csv,/path/to/data2.csv \ + -public-key-path /path/to/public_key.pem \ + -attested-tls-bool false ``` + +### Remote Resource Mode (with KBS) + +In this mode, the CVMS server specifies remote URLs for encrypted resources, and the agent downloads and decrypts them using KBS attestation. + +**Remote Algorithm Only:** + +```bash +go run ./test/cvms/main.go \ + -public-key-path /path/to/public_key.pem \ + -attested-tls-bool false \ + -kbs-url http://localhost:8080 \ + -algo-source-url s3://cocos-resources/algorithm.wasm.enc \ + -algo-kbs-path default/key/algorithm-key +``` + +**Remote Algorithm and Datasets:** + +```bash +go run ./test/cvms/main.go \ + -public-key-path /path/to/public_key.pem \ + -attested-tls-bool false \ + -kbs-url http://localhost:8080 \ + -algo-source-url s3://cocos-resources/algorithm.wasm.enc \ + -algo-kbs-path default/key/algorithm-key \ + -dataset-source-urls https://example.com/data1.csv.enc,https://example.com/data2.csv.enc \ + -dataset-kbs-paths default/key/data1-key,default/key/data2-key +``` + +**Mixed Mode (Remote Algorithm + Direct Datasets):** + +```bash +go run ./test/cvms/main.go \ + -algo-source-url s3://cocos-resources/algorithm.wasm.enc \ + -algo-kbs-path default/key/algorithm-key \ + -data-paths /path/to/data1.csv,/path/to/data2.csv \ + -public-key-path /path/to/public_key.pem \ + -attested-tls-bool false \ + -kbs-url http://localhost:8080 +``` + +### With Attested TLS + +```bash +go run ./test/cvms/main.go \ + -algo-path /path/to/algorithm.wasm \ + -data-paths /path/to/data1.csv \ + -public-key-path /path/to/public_key.pem \ + -attested-tls-bool true \ + -client-ca-file /path/to/ca.pem +``` + +## Notes + +- **Either** `-algo-path` **OR** (`-algo-source-url` AND `-algo-kbs-path`) must be provided +- When using remote datasets, `-dataset-source-urls` and `-dataset-kbs-paths` must have the same number of comma-separated values +- The `-kbs-url` flag should be provided when using any remote resources +- For remote resources, the hash values in the manifest are currently placeholders (all zeros). In production, these should be the actual hashes of the **decrypted** data +- See [TESTING_REMOTE_RESOURCES.md](../TESTING_REMOTE_RESOURCES.md) for a complete guide on testing remote resource downloads with KBS attestation + +## Architecture + +``` +┌─────────────┐ ┌─────────────┐ +│ CVMS Server │ ────manifest───▶ │ Agent │ +│ (this test) │ ◀───logs/events─ │ │ +└─────────────┘ └──────┬──────┘ + │ + │ (if remote resources) + ▼ + ┌─────────────────────────┐ + │ Registry (S3/HTTP) │ + │ + KBS (Key Broker) │ + └─────────────────────────┘ +``` + +The agent downloads encrypted resources from the registry and retrieves decryption keys from KBS using TEE attestation. diff --git a/test/cvms/main.go b/test/cvms/main.go index 4184e995..5c523299 100644 --- a/test/cvms/main.go +++ b/test/cvms/main.go @@ -4,6 +4,7 @@ package main import ( "context" + "encoding/hex" "encoding/pem" "flag" "fmt" @@ -41,6 +42,18 @@ var ( attestedTLS bool pubKeyFile string clientCAFile string + // Remote resource configuration. + kbsURL string + algoSourceURL string + algoKBSResourcePath string + datasetSourceURLs string + datasetKBSPaths string + algoType string + algoArgsString string + algoHash string + datasetTypeString string + datasetHash string + datasetDecompress string ) type svc struct { @@ -57,25 +70,139 @@ func (s *svc) Run(ctx context.Context, ipAddress string, sendMessage cvmsgrpc.Se } pubPem, _ := pem.Decode(pubKey) + // Build datasets var datasets []*cvms.Dataset - for _, dataPath := range dataPaths { - if _, err := os.Stat(dataPath); os.IsNotExist(err) { - s.logger.Error(fmt.Sprintf("data file does not exist: %s", dataPath)) + + // Check if using remote datasets + var datasetURLs []string + var datasetKBSPathsList []string + if datasetSourceURLs != "" { + datasetURLs = strings.Split(datasetSourceURLs, ",") + } + if datasetKBSPaths != "" { + datasetKBSPathsList = strings.Split(datasetKBSPaths, ",") + } + + var datasetDecompressList []bool + if datasetDecompress != "" { + parts := strings.Split(datasetDecompress, ",") + for _, p := range parts { + val, _ := strconv.ParseBool(p) + datasetDecompressList = append(datasetDecompressList, val) + } + } + + // Parse dataset hash if provided + var dataHashBytes []byte + if datasetHash != "" { + var err error + dataHashBytes, err = hex.DecodeString(datasetHash) + if err != nil { + s.logger.Error(fmt.Sprintf("failed to decode dataset hash: %s", err)) return } - dataHash, err := internal.Checksum(dataPath) + if len(dataHashBytes) != 32 { + s.logger.Error(fmt.Sprintf("dataset hash must be 32 bytes (SHA256), got %d", len(dataHashBytes))) + return + } + } else { + // Default to empty/zero hash + dataHashBytes = make([]byte, 32) + } + + if len(datasetURLs) > 0 && len(datasetKBSPathsList) > 0 { + // Remote datasets mode + if len(datasetURLs) != len(datasetKBSPathsList) { + s.logger.Error("dataset source URLs and KBS paths must have the same count") + return + } + + for i := 0; i < len(datasetURLs); i++ { + datasets = append(datasets, &cvms.Dataset{ + Hash: dataHashBytes, + UserKey: pubPem.Bytes, + Filename: fmt.Sprintf("dataset_%d.csv", i), + Source: &cvms.Source{ + Type: "oci-image", + Url: datasetURLs[i], + KbsResourcePath: datasetKBSPathsList[i], + Encrypted: datasetKBSPathsList[i] != "", + }, + }) + if len(datasetDecompressList) > i { + datasets[len(datasets)-1].Decompress = datasetDecompressList[i] + } + } + } else { + // Direct upload mode - use local files + for _, dataPath := range dataPaths { + if _, err := os.Stat(dataPath); os.IsNotExist(err) { + s.logger.Error(fmt.Sprintf("data file does not exist: %s", dataPath)) + return + } + dataHash, err := internal.Checksum(dataPath) + if err != nil { + s.logger.Error(fmt.Sprintf("failed to calculate checksum: %s", err)) + return + } + + datasets = append(datasets, &cvms.Dataset{Hash: dataHash[:], UserKey: pubPem.Bytes}) + } + } + + // Build algorithm + var algorithm *cvms.Algorithm + if algoSourceURL != "" && algoKBSResourcePath != "" { + // Remote algorithm mode + var algoHashBytes []byte + if algoHash != "" { + var err error + algoHashBytes, err = hex.DecodeString(algoHash) + if err != nil { + s.logger.Error(fmt.Sprintf("failed to decode algo hash: %s", err)) + return + } + if len(algoHashBytes) != 32 { + s.logger.Error(fmt.Sprintf("algo hash must be 32 bytes (SHA256), got %d", len(algoHashBytes))) + return + } + } else { + algoHashBytes = make([]byte, 32) + } + + algorithm = &cvms.Algorithm{ + Hash: algoHashBytes, + UserKey: pubPem.Bytes, + AlgoType: algoType, + AlgoArgs: strings.Split(algoArgsString, ","), + Source: &cvms.Source{ + Type: "oci-image", + Url: algoSourceURL, + KbsResourcePath: algoKBSResourcePath, + Encrypted: algoKBSResourcePath != "", + }, + } + } else { + // Direct upload mode - use local file + if algoPath == "" { + s.logger.Error("algorithm path is required when not using remote source") + return + } + algoHash, err := internal.Checksum(algoPath) if err != nil { s.logger.Error(fmt.Sprintf("failed to calculate checksum: %s", err)) return } - - datasets = append(datasets, &cvms.Dataset{Hash: dataHash[:], UserKey: pubPem.Bytes}) + algorithm = &cvms.Algorithm{Hash: algoHash[:], UserKey: pubPem.Bytes} } - algoHash, err := internal.Checksum(algoPath) - if err != nil { - s.logger.Error(fmt.Sprintf("failed to calculate checksum: %s", err)) - return + // Build KBS config + var kbsConfig *cvms.KBSConfig + if kbsURL != "" { + kbsConfig = &cvms.KBSConfig{ + Url: kbsURL, + Enabled: true, + } } s.logger.Debug("sending computation run request") @@ -86,13 +213,14 @@ func (s *svc) Run(ctx context.Context, ipAddress string, sendMessage cvmsgrpc.Se Name: "sample computation", Description: "sample descrption", Datasets: datasets, - Algorithm: &cvms.Algorithm{Hash: algoHash[:], UserKey: pubPem.Bytes}, + Algorithm: algorithm, ResultConsumers: []*cvms.ResultConsumer{{UserKey: pubPem.Bytes}}, AgentConfig: &cvms.AgentConfig{ Port: "7002", AttestedTls: attestedTLS, ClientCaFile: clientCAFile, }, + Kbs: kbsConfig, }, }, }); err != nil { @@ -108,11 +236,23 @@ func (s *svc) Run(ctx context.Context, ipAddress string, sendMessage cvmsgrpc.Se func main() { flagSet := flag.NewFlagSet("tests/cvms/main.go", flag.ContinueOnError) - flagSet.StringVar(&algoPath, "algo-path", "", "Path to the algorithm") + flagSet.StringVar(&algoPath, "algo-path", "", "Path to the algorithm (for direct upload mode)") flagSet.StringVar(&pubKeyFile, "public-key-path", "", "Path to the public key file") flagSet.StringVar(&attestedTLSString, "attested-tls-bool", "", "Should aTLS be used, must be 'true' or 'false'") - flagSet.StringVar(&dataPathString, "data-paths", "", "Paths to data sources, list of string separated with commas") + flagSet.StringVar(&dataPathString, "data-paths", "", "Paths to data sources, list of string separated with commas (for direct upload mode)") flagSet.StringVar(&clientCAFile, "client-ca-file", "", "Client CA root certificate file path") + // Remote resource flags + flagSet.StringVar(&kbsURL, "kbs-url", "", "KBS endpoint URL (e.g., 'http://localhost:8080')") + flagSet.StringVar(&algoSourceURL, "algo-source-url", "", "Algorithm source URL (s3://bucket/key or https://...)") + flagSet.StringVar(&algoKBSResourcePath, "algo-kbs-path", "", "Algorithm KBS resource path (e.g., 'default/key/algo-key')") + flagSet.StringVar(&datasetSourceURLs, "dataset-source-urls", "", "Dataset source URLs, comma-separated") + flagSet.StringVar(&datasetKBSPaths, "dataset-kbs-paths", "", "Dataset KBS resource paths, comma-separated") + flagSet.StringVar(&algoType, "algo-type", "", "Algorithm execution type (e.g. binary, python)") + flagSet.StringVar(&algoArgsString, "algo-args", "", "Algorithm arguments, comma-separated") + flagSet.StringVar(&algoHash, "algo-hash", "", "Algorithm SHA256 hash (hex string)") + flagSet.StringVar(&datasetTypeString, "dataset-type", "", "Dataset source type, comma-separated (deprecated, always oci-image)") + flagSet.StringVar(&datasetHash, "dataset-hash", "", "Dataset SHA256 hash (hex string)") + flagSet.StringVar(&datasetDecompress, "dataset-decompress", "", "Dataset decompression bools, comma-separated (e.g. true,false)") flagSetParseError := flagSet.Parse(os.Args[1:]) if flagSetParseError != nil { @@ -124,8 +264,9 @@ func main() { parsingErrorString.WriteString("\n") - if algoPath == "" { - parsingErrorString.WriteString("Algorithm path is required\n") + // Validate that either algo-path OR (algo-source-url AND algo-kbs-path) is provided + if algoPath == "" && (algoSourceURL == "" || algoKBSResourcePath == "") { + parsingErrorString.WriteString("Either algo-path OR (algo-source-url AND algo-kbs-path) is required\n") parsingError = true } diff --git a/test/manual/README.md b/test/manual/README.md index 3bd62dcf..3d47c9eb 100644 --- a/test/manual/README.md +++ b/test/manual/README.md @@ -17,7 +17,7 @@ All assets/datasets the algorithm uses are stored in the `datasets` directory. T ### Agent-CLI interaction -Agent is started automatically in the VM when launched but requires configuration and manifest to be passed by manager. Alternatively you can pass configuration using this [simplified script](./agent-config/main.go) +Agent is started automatically in the VM when launched but requires configuration and manifest to be passed by manager. Alternatively you can pass configuration using this [simplified script](/test/cvms/main.go) For attested TLS, you will have to calculate the VM's measurement, which can be done using cli. This information is also contained in the Attestation Policy file.