NOISSUE - Agent Pull mode for remote resources (#575)
CI / checkproto (push) Has been cancelled
CI / lint (push) Has been cancelled
Rust CI Pipeline / rust-check (push) Has been cancelled
CI / test (agent) (push) Has been cancelled
CI / test (cli) (push) Has been cancelled
CI / test (cmd) (push) Has been cancelled
CI / test (internal) (push) Has been cancelled
CI / test (manager, true) (push) Has been cancelled
CI / test (pkg) (push) Has been cancelled
CI / upload-coverage (push) Has been cancelled

* feat(kbs): implement KBS client for attestation and resource retrieval

- Added KBS client implementation in pkg/kbs/client.go with methods for attestation and resource retrieval.
- Introduced necessary data structures for requests and responses.
- Implemented error handling for various scenarios.

test(kbs): add unit tests for KBS client

- Created comprehensive tests for the KBS client in pkg/kbs/client_test.go.
- Included tests for attestation success and failure cases, as well as resource retrieval.

feat(registry): introduce HTTP and S3 registry implementations

- Added HTTPRegistry for downloading resources over HTTP/HTTPS with retry logic in pkg/registry/http.go.
- Implemented S3Registry for downloading resources from AWS S3 and S3-compatible services in pkg/registry/s3.go.
- Included error handling and configuration options for both registries.

chore(registry): define registry interface and configuration

- Created registry interface and configuration struct in pkg/registry/registry.go.
- Added default configuration settings for registry clients.

docs(cvms): update README for CVMS server configuration and usage

- Enhanced documentation for CVMS server with detailed command-line flags and usage examples.
- Clarified direct upload and remote resource modes, including KBS integration.

fix(cvms): integrate KBS for remote resource handling in main.go

- Updated main.go to support remote datasets and algorithms using KBS.
- Added validation for command-line flags to ensure proper configuration.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: Move ifeq conditional outside define block in attestation-service.mk

Make conditionals cannot be evaluated inside define...endef blocks
when used as recipe bodies. Restructured to define the
ATTESTATION_SERVICE_INSTALL_INIT_SYSTEMD block conditionally based
on BR2_PACKAGE_CC_ATTESTATION_AGENT configuration.

* feat: Implement remote resource downloading for algorithms and datasets using AWS S3/MinIO credentials.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add comprehensive documentation and agent support for testing remote resource download with KBS attestation.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Improve agent logging for remote resource configuration and KBS status, and add a testing guide for remote resource downloads with KBS attestation.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add a comprehensive guide for testing remote resource download with KBS attestation and update multiple package versions to a specific commit.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add failure transitions for resource reception states and a comprehensive guide for testing remote resource downloads with KBS attestation.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Implement remote resource download with KBS attestation in the agent and add a comprehensive testing guide.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: Add comprehensive guide for testing remote resource download with KBS attestation and include a debug log in the attestation client.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Delegate KBS attestation and token retrieval to a new attestation-agent service and document remote resource testing.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* client fixes

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* raw evidence

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: Build all Go files in cmd directories, not just main.go

This fixes the issue where fetch_raw_evidence.go wasn't being included
in the attestation-service build.

* fix: Wrap binary evidence in JSON for KBS compatibility

Fixes 'invalid character' error by wrapping raw binary evidence
in a JSON structure with base64 encoding, as expected by KBS.

* chore: Update buildroot packages to c28cefae

Includes fixes for:
1. attestation-service build (including fetch_raw_evidence.go)
2. Agent KBS evidence format (wrapping binary in JSON)

* fix: Implement KBS RCAR handshake with cookies

Fixes 'cookie not found' error (401) from KBS by:
1. Adding CookieJar support to KBS client
2. Implementing GetChallenge() to perform /auth handshake and capture session cookie
3. Updating Agent to get challenge, decode nonce, and use it for evidence generation
4. Regenerating mocks

* chore: Update buildroot packages to f6981ac5

Includes KBS RCAR handshake fix (cookie support + GetChallenge loop)

* fix: Update KBS client JSON tags to kebab-case

Fixes deserialization error (401) from KBS by:
1. Using kebab-case (e.g. extra-params) for JSON tags as per protocol.
2. Initializing ExtraParams as empty object {} instead of null/omitted.

* fix: Wrap attestation evidence in primary_evidence format

Updates Agent to construct 'tee-evidence' payload with:
- primary_evidence: containing the actual quote/data
- additional_evidence: empty JSON object

This matches the Confidential Containers KBS Attestation Protocol requirements.

* fix: Update KBS protocol version to 0.4.0

KBS rejected 0.1.0 with a version mismatch error. Bumping to 0.4.0 to match server expectation.

* fix: Generate ephemeral key for KBS RuntimeData

Updates RuntimeData to include a valid ephemeral EC P-256 public key in JWK format, as required by the KBS RCAR protocol.
Also fixes the KBS client struct to support TEEPubKey as an object.

* fix: Update sample attestation quote to valid JSON

The default attestation.bin was binary, but the KBS Sample Verifier expects a valid JSON quote containing 'svn' and 'report_data'.
Updated the embedded bin file to contain this JSON structure.

* fix: Generate dynamic JSON quote for Sample TEE in FetchRawEvidence

The KBS Sample Verifier expects a JSON object with 'svn' and 'report_data'.
Previously, we were returning raw binary data (reportData+nonce).
This commit updates FetchRawEvidence to return a marshaled JSON structure with:
- svn: "1"
- report_data: base64(req.ReportData)

* refactor: Delegate Sample Attestation to Provider

Refactored sample attestation logic:
- Moved JSON Quote generation into EmptyProvider (standalone mode).
- Updated FetchRawEvidence to call provider.TeeAttestation instead of manual generation.
This enables using the real CC Attestation Agent for UNSPECIFIED platform if configured.

* feat: Add comprehensive debug logging and enforce CC AA usage

Changes:
- Updated EmptyProvider to return error instead of generating mock data
  This forces proper use of CC Attestation Agent's sample attester
- Added detailed logging to attestation-service FetchRawEvidence:
  * Hex dump of evidence (first 200 bytes)
  * String preview of evidence
  * Total evidence length
- Added detailed logging to agent service:
  * Raw evidence hex and string previews
  * KBS evidence JSON preview (first 500 bytes)
  * Evidence lengths at each transformation step

This logging will help diagnose why KBS Sample Verifier is rejecting evidence.

* fix: Enable CC AA by default and add attestation-service log forwarding

Changes:
- Set USE_CC_ATTESTATION_AGENT=true by default in systemd service
- Added StandardOutput/StandardError to forward logs to /var/log/cocos/
- Updated HAL makefile to handle new default value
- This ensures attestation-service uses CC AA's sample attester
- Logs will now be visible in CVMS output for debugging

* feat: Add gRPC log forwarding to attestation-service

Implemented the same log forwarding mechanism used by the agent:
- Added ProtoHandler to write logs to both stdout and logQueue
- Connected to log client (/run/cocos/log.sock) for gRPC forwarding
- Added goroutine to forward logs to CVMS via log client
- Logs will now appear in CVMS output during computation runs

This enables visibility into attestation-service debug output including:
- CC AA connection status
- Evidence generation details (hex dumps, string previews)
- Any errors from providers

* fix: Parse sample evidence JSON instead of base64-encoding it

The attestation-service returns sample evidence as JSON:
{"svn":"1","report_data":"base64..."}

The agent was incorrectly base64-encoding this JSON string again.
KBS Sample Verifier expects the parsed JSON object directly.

Fixed by:
- Parsing the JSON evidence from attestation-service
- Passing the parsed object directly in primary_evidence.evidence
- This matches what KBS Sample Verifier expects

* debug: Increase KBS evidence logging preview to 1000 bytes

Show the complete JSON structure being sent to KBS to debug
the attestation failure.

* debug: Add comprehensive CC AA configuration logging

Added debug logs to show:
- Whether CC AA is enabled in config
- CC AA address being used
- Connection success/failure
- Which provider is ultimately selected
- Warning when falling back to EmptyProvider

This will help diagnose why EmptyProvider is being used
instead of CC Attestation Agent.

* debug: Add startup logging for log client connection

Added log message to show if log client connection succeeds
at attestation-service startup. This will help diagnose why
logs aren't appearing in CVMS output.

* feat: Add retry logic with exponential backoff to log client

Added simple retry mechanism to handle concurrent log requests:
- 3 retry attempts with exponential backoff (10ms, 20ms, 40ms)
- Applies to both SendLog and SendEvent methods
- Centralized in log client so all services benefit
- Should eliminate 'failed to send log' errors from concurrent requests

This fixes the issue where attestation-service logs weren't
appearing in CVMS output due to dropped messages.

* fix: Flatten sample evidence fields in primary_evidence for KBS

KBS Sample Verifier expects svn and report_data at the top level
of primary_evidence, not nested under an 'evidence' key.

Changed structure from:
{"primary_evidence": {"tee": "sample", "evidence": {"svn": "1", ...}}}

To:
{"primary_evidence": {"tee": "sample", "svn": "1", "report_data": "...", ...}}

This matches what KBS expects when deserializing the Quote structure.

* fix: Use sample quote directly as primary_evidence per KBS protocol

According to KBS attestation protocol spec, for sample TEE type,
primary_evidence should be the sample quote JSON directly:
{"svn": "1", "report_data": "..."}

Removed extra 'tee' and 'platform' fields that were causing KBS
to fail deserializing the Quote structure. The 'tee' field is
already sent in the Request payload during RCAR handshake.

Refs:
- https://github.com/confidential-containers/trustee/blob/main/kbs/docs/kbs_attestation_protocol.md
- https://github.com/confidential-containers/guest-components/blob/main/attestation-agent/attester/src/sample/mod.rs

* fix: Make CC AA required for sample attestation when configured

When USE_CC_ATTESTATION_AGENT=true, attestation-service now
requires AA to be available for NoCC/sample platform. This ensures
sample evidence always comes from AA with the correct KBS format.

Changes:
- Error out if AA connection fails for NoCC platform when AA is configured
- Only use EmptyProvider if AA is explicitly NOT configured
- Prevents incorrect sample evidence format from EmptyProvider

This ensures attestation-service delegates to AA for sample evidence
generation instead of creating it itself.

* fix: Implement proper RCAR protocol with tee-pubkey and runtime-data hash

Fixed KBS attestation error 'REPORT_DATA is different from that in Sample Quote'

Changes:
1. Generate ephemeral EC key pair BEFORE getting evidence from AA
2. Create runtime-data with nonce + tee-pubkey (JWK format)
3. Hash runtime-data (SHA-256) and use as report_data for AA
4. This binds the tee-pubkey to the TEE evidence per RCAR protocol

The report_data in the evidence now matches what KBS expects:
hash(runtime-data) instead of computation ID.

This completes the full RCAR protocol implementation:
- Request → Challenge → Attestation (with bound tee-pubkey) → Response

* fix(agent): use simple nonce for Sample attestation report_data

For Sample/NoCC attestation, use the raw nonce bytes directly as
report_data instead of hashing runtime-data. This avoids JSON
serialization mismatches with the KBS Sample verifier.

Real TEEs (TDX/SNP) still use runtime-data hash binding to
cryptographically bind the ephemeral tee-pubkey to the evidence.

* fix(agent): use RFC 8785 canonical JSON for runtime-data hashing

The KBS Sample attestation verifier (and likely others) expects the
report_data to be the SHA-256 hash of the *canonical* JSON serialization
(RFC 8785) of the runtime-data. Standard Go JSON marshaling does not
guarantee key ordering, leading to hash mismatches.

This change uses github.com/gowebpki/jcs to canonicalize the runtime-data
before hashing, ensuring compatibility with the KBS RCAR implementation.
Also reverted the temporary 'simple nonce' workaround.

* feat(hal): add CoCo Keyprovider and Skopeo packages

- Add coco-keyprovider buildroot package with systemd service
- Add skopeo buildroot package for OCI image handling
- Add ocicrypt_keyprovider.conf for encrypted image decryption
- Update Config.in to include new packages

This enables standard CoCo ecosystem integration for encrypted
OCI images instead of custom S3/HTTP registry clients.

* feat(oci): add OCI image handling package with Skopeo integration

- Add pkg/oci/types.go with ResourceSource and ImageManifest types
- Add pkg/oci/skopeo.go with Skopeo wrapper for pull/decrypt
- Add pkg/oci/extract.go for extracting algorithms and datasets from layers

This package provides OCI image handling using Skopeo and CoCo
Keyprovider for encrypted image decryption, replacing custom
S3/HTTP registry clients.

* chore: regenerate protobuf files for updated cvms.proto

* refactor(agent): replace S3/HTTP/KBS with OCI package

- Remove pkg/kbs and pkg/registry imports
- Add pkg/oci import for OCI image handling
- Replace downloadAndDecryptResource with OCI-based implementation
- Use Skopeo + CoCo Keyprovider for automatic decryption
- Reduce code from ~240 lines to ~70 lines

This eliminates custom KBS RCAR handshake, S3/HTTP registry clients,
and manual decryption logic. CoCo Keyprovider handles all decryption
automatically via ocicrypt protocol.

* chore: remove obsolete pkg/kbs and pkg/registry packages

- Delete pkg/kbs/ (custom KBS client, ~300 lines)
- Delete pkg/registry/ (S3/HTTP registry clients, ~400 lines)
- Remove unused imports from agent/service.go
- Run go mod tidy to clean up dependencies

These packages have been replaced by pkg/oci with Skopeo and
CoCo Keyprovider for standard CoCo ecosystem integration.

* fix(agent): update ResourceSource struct to include type and encryption fields

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix(hal): update CoCo Keyprovider to v0.16.0 and fix build path

- Update version from v0.11.0 to v0.16.0 (matches attestation agent)
- Fix install path: target is at repo root, not in coco_keyprovider subdir
- This fixes the build error where coco_keyprovider binary wasn't found

The cargo workspace in guest-components builds to a shared target/
directory at the repository root, not within each crate's subdirectory.

* feat: Update remote resources testing guide to use kbs-client and coco-keyprovider for key management and encryption, enable insecure TLS for Skopeo, and enhance CVMS with

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Update component versions, revise image encryption documentation, and sanitize OCI image paths for Skopeo compatibility.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add `decompress` option to Dataset and `algo_type`/`algo_args` to Algorithm protobuf messages, updating client, test, and build configurations.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Update multiple package versions and enhance OCI image extraction error reporting for missing algorithm files.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* chore: Bump package versions, improve OCI image extraction debugging by returning seen files, and remove unused dataset type parsing from test code.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* refactor: Migrate OCI extraction to use structured logging with `slog` and `context`, and update package versions.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Bump multiple component versions, add encrypted status for computation inputs and algorithms, and refine OCI layer extraction warnings.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* logging

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add `Encrypted` field to algorithm and dataset resource sources and update all component versions.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: update component versions, integrate coco-keyprovider service, and configure ocicrypt key provider.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: add support for KBS parameters and dataset/algorithm hash calculations in CVMS

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: update resource download and extraction logic to support requirements.txt and improve hash verification

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* chore: Update dependencies, improve code style, and add GetRawEvidence to attestation client mocks.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Refactor code structure for improved readability and maintainability

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: update golangci configuration to include errcheck for build path and remove unnecessary exclusions

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: streamline kernel command line handling in QEMU args construction

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: add attestation binary and update checksum tests and policy structure

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add unit tests for attestation agent, attestation, log, crypto, OCI, and Skopeo clients

- Implement tests for the attestation agent client including Unix socket and TCP address handling, token retrieval, and error scenarios.
- Enhance attestation client tests to cover fetching raw evidence for various platforms (SNP, TDX, VTPM, SNPvTPM) and validate error handling.
- Introduce log client tests to verify retry behavior for sending logs and events.
- Create comprehensive tests for crypto package focusing on AES-GCM decryption, encrypted resource parsing, and key unwrapping.
- Add tests for OCI package to validate algorithm and dataset extraction, including JSON serialization of OCILayout.
- Implement Skopeo client tests to ensure proper functionality for image pulling, inspecting, and resource source handling.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: handle JSON marshal errors in test cases for decrypt and extract functions

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: add comprehensive tests for algorithm and dataset extraction with various scenarios

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* refactor: replace hardcoded Python script content with constant variable

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: remove redundant mock expectation for SendAgentConfig in TestCreateVMWithAaKbsParams

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: add tests for event sending failure, dataset extraction with path traversal, and Skopeo client behavior

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: add tests for download and decryption of resources with various URL formats

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* refactor: Introduce OCIClient interface for agent service to improve testability of OCI image operations and enhance related tests.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* refactor: Change `get_uint64_from_tcb` to accept `TcbVersion` by value and use `u64::from` for type conversions.

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
This commit is contained in:
Sammy Kerata Oina
2026-03-16 16:48:55 +03:00
committed by GitHub
parent f77ec5644a
commit da31d76c94
76 changed files with 7464 additions and 392 deletions
+1 -1
View File
@@ -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: |
+1 -1
View File
@@ -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
+5 -5
View File
@@ -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
+4
View File
@@ -25,3 +25,7 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
*.enc
*.key
*.pub
+4
View File
@@ -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$
+1 -1
View File
@@ -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
+15
View File
@@ -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
+417
View File
@@ -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 <<EOF > 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
+34 -8
View File
@@ -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{}
+33 -4
View File
@@ -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.
+138
View File
@@ -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")
}
+221 -32
View File
@@ -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,
},
+18
View File
@@ -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 {
+5
View File
@@ -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)
+418 -10
View File
@@ -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 {
+562
View File
@@ -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)
}
View File
+1 -1
View File
@@ -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",
+23 -7
View File
@@ -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
@@ -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
}
+91 -1
View File
@@ -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")
}
+966
View File
@@ -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
+29 -29
View File
@@ -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
)
+60 -110
View File
@@ -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=
+1 -1
View File
@@ -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"
@@ -0,0 +1,7 @@
{
"key-providers": {
"attestation-agent": {
"grpc": "127.0.0.1:50011"
}
}
}
+13 -3
View File
@@ -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
+2 -2
View File
@@ -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)
@@ -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
@@ -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))
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
# CoCo Keyprovider Environment Variables
COCO_KP_SOCKET=127.0.0.1:50011
RUST_LOG=info
@@ -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))
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+3 -1
View File
@@ -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
+2 -2
View File
@@ -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]
@@ -71,3 +71,4 @@ service AttestationAgentService {
rpc BindInitData(BindInitDataRequest) returns (BindInitDataResponse) {};
rpc GetTeeType(GetTeeTypeRequest) returns (GetTeeTypeResponse) {};
}
+68 -18
View File
@@ -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,
},
@@ -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;
}
@@ -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,
+11 -1
View File
@@ -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 ..
+49 -2
View File
@@ -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" +
+5
View File
@@ -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{
+6 -4
View File
@@ -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
+8 -6
View File
@@ -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",
+21
View File
@@ -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
+144
View File
@@ -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)
})
}
}
+8
View File
@@ -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 {
+10 -3
View File
@@ -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) {
+163
View File
@@ -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)
}
+3
View File
@@ -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
}
@@ -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
}
@@ -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)
}
+42
View File
@@ -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()
+190
View File
@@ -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)
}
+44 -6
View File
@@ -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<<uint(attempt))) * time.Millisecond
time.Sleep(backoff)
}
}
// Return error after all retries exhausted
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
_, err := c.client.SendLog(ctx, entry)
return err
}
func (c *client) SendEvent(ctx context.Context, entry *log.EventEntry) 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.SendEvent(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<<uint(attempt))) * time.Millisecond
time.Sleep(backoff)
}
}
// Return error after all retries exhausted
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
_, err := c.client.SendEvent(ctx, entry)
return err
}
+254
View File
@@ -6,6 +6,7 @@ import (
"context"
"net"
"path/filepath"
"sync"
"testing"
"time"
@@ -330,3 +331,256 @@ func TestClientOperationsAfterClose(t *testing.T) {
err = client.SendLog(ctx, entry)
assert.Error(t, err)
}
// TestClientSendLogRetrySuccess tests SendLog retry behavior.
func TestClientSendLogRetrySuccess(t *testing.T) {
tmpDir := t.TempDir()
socketPath := filepath.Join(tmpDir, "log-retry-success.sock")
listener, err := net.Listen("unix", socketPath)
require.NoError(t, err)
defer listener.Close()
grpcServer := grpc.NewServer()
mockServer := &mockLogCollectorServer{}
log.RegisterLogCollectorServer(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()
entry := &log.LogEntry{
Level: "INFO",
Message: "retry test",
}
err = client.SendLog(ctx, entry)
require.NoError(t, err)
assert.True(t, mockServer.sendLogCalled)
}
// TestClientSendEventRetrySuccess tests SendEvent retry behavior.
func TestClientSendEventRetrySuccess(t *testing.T) {
tmpDir := t.TempDir()
socketPath := filepath.Join(tmpDir, "log-event-retry.sock")
listener, err := net.Listen("unix", socketPath)
require.NoError(t, err)
defer listener.Close()
grpcServer := grpc.NewServer()
mockServer := &mockLogCollectorServer{}
log.RegisterLogCollectorServer(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()
entry := &log.EventEntry{
EventType: "test.retry",
}
err = client.SendEvent(ctx, entry)
require.NoError(t, err)
}
// TestClientSendLogRetryWithFailures tests SendLog retry with intermittent failures.
func TestClientSendLogRetryWithFailures(t *testing.T) {
tmpDir := t.TempDir()
socketPath := filepath.Join(tmpDir, "log-retry-failures.sock")
listener, err := net.Listen("unix", socketPath)
require.NoError(t, err)
defer listener.Close()
grpcServer := grpc.NewServer()
mockServer := &retryMockLogCollectorServer{
failCount: 2, // Fail first 2 attempts
maxFailCount: 2,
}
log.RegisterLogCollectorServer(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()
entry := &log.LogEntry{
Level: "INFO",
Message: "retry test",
}
// With retry logic, this should succeed on 3rd attempt
err = client.SendLog(ctx, entry)
require.NoError(t, err)
assert.Equal(t, 3, mockServer.callCount)
}
// TestClientSendEventRetryWithFailures tests SendEvent retry with intermittent failures.
func TestClientSendEventRetryWithFailures(t *testing.T) {
tmpDir := t.TempDir()
socketPath := filepath.Join(tmpDir, "event-retry-failures.sock")
listener, err := net.Listen("unix", socketPath)
require.NoError(t, err)
defer listener.Close()
grpcServer := grpc.NewServer()
mockServer := &retryMockLogCollectorServer{
failCount: 2, // Fail first 2 attempts
maxFailCount: 2,
}
log.RegisterLogCollectorServer(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()
entry := &log.EventEntry{
EventType: "test.retry",
}
// With retry logic, this should succeed on 3rd attempt
err = client.SendEvent(ctx, entry)
require.NoError(t, err)
assert.Equal(t, 3, mockServer.eventCallCount)
}
// TestClientSendLogAllRetriesFail tests SendLog when all retries fail.
func TestClientSendLogAllRetriesFail(t *testing.T) {
tmpDir := t.TempDir()
socketPath := filepath.Join(tmpDir, "log-all-fail.sock")
listener, err := net.Listen("unix", socketPath)
require.NoError(t, err)
defer listener.Close()
grpcServer := grpc.NewServer()
mockServer := &retryMockLogCollectorServer{
failCount: 10, // Fail all attempts
maxFailCount: 10,
}
log.RegisterLogCollectorServer(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()
entry := &log.LogEntry{
Level: "ERROR",
Message: "will fail",
}
// Should fail after all retries
err = client.SendLog(ctx, entry)
assert.Error(t, err)
// 3 retries + 1 final attempt = 4 calls
assert.Equal(t, 4, mockServer.callCount)
}
func TestClientSendEventAllRetriesFail(t *testing.T) {
tmpDir := t.TempDir()
socketPath := filepath.Join(tmpDir, "log-event-all-fail.sock")
listener, err := net.Listen("unix", socketPath)
require.NoError(t, err)
defer listener.Close()
grpcServer := grpc.NewServer()
mockServer := &retryMockLogCollectorServer{
failCount: 10,
maxFailCount: 10,
}
log.RegisterLogCollectorServer(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()
entry := &log.EventEntry{
EventType: "TestEvent",
}
// Should fail after all retries
err = client.SendEvent(ctx, entry)
assert.Error(t, err)
// 3 retries + 1 final attempt = 4 calls
assert.Equal(t, 4, mockServer.eventCallCount)
}
// retryMockLogCollectorServer is a mock server that fails a specified number of times.
type retryMockLogCollectorServer struct {
log.UnimplementedLogCollectorServer
failCount int
maxFailCount int
callCount int
eventCallCount int
mu sync.Mutex
}
func (m *retryMockLogCollectorServer) SendLog(ctx context.Context, entry *log.LogEntry) (*emptypb.Empty, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.callCount++
if m.callCount <= m.maxFailCount {
return nil, assert.AnError
}
return &emptypb.Empty{}, nil
}
func (m *retryMockLogCollectorServer) SendEvent(ctx context.Context, entry *log.EventEntry) (*emptypb.Empty, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.eventCallCount++
if m.eventCallCount <= m.maxFailCount {
return nil, assert.AnError
}
return &emptypb.Empty{}, nil
}
+218
View File
@@ -0,0 +1,218 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdh"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"github.com/absmach/supermq/pkg/errors"
"golang.org/x/crypto/hkdf"
)
var (
// ErrDecryptionFailed indicates a decryption operation failed.
ErrDecryptionFailed = errors.New("decryption failed")
// ErrInvalidKey indicates the provided key is invalid.
ErrInvalidKey = errors.New("invalid decryption key")
// ErrInvalidCiphertext indicates the ciphertext is invalid or corrupted.
ErrInvalidCiphertext = errors.New("invalid ciphertext")
// ErrInvalidFormat indicates the encrypted resource format is invalid.
ErrInvalidFormat = errors.New("invalid encrypted resource format")
)
// EncryptedResource represents an encrypted resource from KBS.
// This matches the format used by Confidential Containers KBS.
type EncryptedResource struct {
// Ciphertext is the encrypted data.
Ciphertext []byte `json:"ciphertext"`
// EncryptedKey is the wrapped encryption key.
EncryptedKey []byte `json:"encrypted_key"`
// IV is the initialization vector for AES-GCM.
IV []byte `json:"iv"`
// Tag is the authentication tag for AES-GCM.
Tag []byte `json:"tag"`
// AAD is the additional authenticated data.
AAD []byte `json:"aad,omitempty"`
// EPK is the ephemeral public key for ECDH key derivation.
EPK *EphemeralPublicKey `json:"epk,omitempty"`
}
// EphemeralPublicKey represents an ephemeral EC P-256 public key.
type EphemeralPublicKey struct {
// Curve is the elliptic curve (should be "P-256").
Curve string `json:"crv"`
// X is the X coordinate of the public key.
X string `json:"x"`
// Y is the Y coordinate of the public key.
Y string `json:"y"`
}
// DecryptAESGCM decrypts data using AES-GCM with the provided key.
// This is used when the decryption key is provided directly (not wrapped).
func DecryptAESGCM(ciphertext, key, iv, tag, aad []byte) ([]byte, error) {
if len(key) != 16 && len(key) != 24 && len(key) != 32 {
return nil, errors.Wrap(ErrInvalidKey, errors.New("key must be 16, 24, or 32 bytes"))
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, errors.Wrap(ErrDecryptionFailed, err)
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, errors.Wrap(ErrDecryptionFailed, err)
}
// Combine ciphertext and tag for GCM
combined := append(ciphertext, tag...)
plaintext, err := aesgcm.Open(nil, iv, combined, aad)
if err != nil {
return nil, errors.Wrap(ErrDecryptionFailed, err)
}
return plaintext, nil
}
// DecryptWithWrappedKey decrypts data using a wrapped key and ECDH key derivation.
// This matches the KBS encryption format with ephemeral key exchange.
func DecryptWithWrappedKey(encryptedResource EncryptedResource, privateKey *ecdh.PrivateKey) ([]byte, error) {
if encryptedResource.EPK == nil {
return nil, errors.Wrap(ErrInvalidFormat, errors.New("ephemeral public key is required"))
}
// Decode ephemeral public key coordinates
xBytes, err := base64.RawURLEncoding.DecodeString(encryptedResource.EPK.X)
if err != nil {
return nil, errors.Wrap(ErrInvalidFormat, err)
}
yBytes, err := base64.RawURLEncoding.DecodeString(encryptedResource.EPK.Y)
if err != nil {
return nil, errors.Wrap(ErrInvalidFormat, err)
}
// Reconstruct ephemeral public key (uncompressed format: 0x04 || X || Y)
epkBytes := make([]byte, 1+len(xBytes)+len(yBytes))
epkBytes[0] = 0x04
copy(epkBytes[1:], xBytes)
copy(epkBytes[1+len(xBytes):], yBytes)
curve := ecdh.P256()
epk, err := curve.NewPublicKey(epkBytes)
if err != nil {
return nil, errors.Wrap(ErrInvalidFormat, err)
}
// Perform ECDH to derive shared secret
sharedSecret, err := privateKey.ECDH(epk)
if err != nil {
return nil, errors.Wrap(ErrDecryptionFailed, err)
}
// Derive KEK (Key Encryption Key) using HKDF
kek := make([]byte, 32)
kdf := hkdf.New(sha256.New, sharedSecret, nil, nil)
if _, err := kdf.Read(kek); err != nil {
return nil, errors.Wrap(ErrDecryptionFailed, err)
}
// Unwrap the content encryption key (CEK)
cek, err := unwrapKey(encryptedResource.EncryptedKey, kek)
if err != nil {
return nil, err
}
// Decrypt the actual content using the CEK
plaintext, err := DecryptAESGCM(
encryptedResource.Ciphertext,
cek,
encryptedResource.IV,
encryptedResource.Tag,
encryptedResource.AAD,
)
if err != nil {
return nil, err
}
// Zero out sensitive key material
zeroBytes(kek)
zeroBytes(cek)
zeroBytes(sharedSecret)
return plaintext, nil
}
// unwrapKey unwraps an encrypted key using AES Key Wrap (RFC 3394).
func unwrapKey(wrappedKey, kek []byte) ([]byte, error) {
if len(wrappedKey)%8 != 0 || len(wrappedKey) < 24 {
return nil, errors.Wrap(ErrInvalidKey, errors.New("wrapped key length must be a multiple of 8 and at least 24 bytes"))
}
block, err := aes.NewCipher(kek)
if err != nil {
return nil, errors.Wrap(ErrDecryptionFailed, err)
}
n := len(wrappedKey)/8 - 1
r := make([][]byte, n+1)
r[0] = wrappedKey[:8]
for i := 1; i <= n; i++ {
r[i] = wrappedKey[i*8 : (i+1)*8]
}
a := r[0]
for j := 5; j >= 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
}
}
+712
View File
@@ -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())
})
}
+342
View File
@@ -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
}
+920
View File
@@ -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")
})
}
+115
View File
@@ -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)
}
+185
View File
@@ -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")
})
}
+40
View File
@@ -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
}
@@ -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"
}
}
@@ -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]);
+126 -21
View File
@@ -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 <alog_path> -attested-tls-bool false -data-paths <data_paths> -public-key-path <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.
+156 -15
View File
@@ -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
}
+1 -1
View File
@@ -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.