mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-22 20:00:18 +00:00
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
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 toc28cefaeIncludes 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 tof6981ac5Includes 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:
committed by
GitHub
parent
f77ec5644a
commit
da31d76c94
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,3 +25,7 @@ Cargo.lock
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
*.enc
|
||||
*.key
|
||||
*.pub
|
||||
@@ -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$
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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{}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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" +
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user