Compare commits

...

15 Commits

Author SHA1 Message Date
Sammy Kerata Oina 412472943d feat: add CVM ID to gRPC metadata and implement log-forwarder stream receiving (#611)
CI / lint (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
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2026-07-14 12:51:01 +02:00
Sammy Kerata Oina 13f7e97d82 NOISSUE - Add FetchKbsToken RPC support, update protobuf generation, and include additional binaries in CI workflow. (#610)
CI / checkproto (push) Has been cancelled
CI / lint (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: add FetchKbsToken RPC support, update protobuf generation, and include additional binaries in CI workflow.

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

* chore: update protoc version and add GetKbsToken mock method with updated kbsHTTPGet signature

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

* test: inject mock attestation client into agentService for resource and KBS tests

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

* test: update key derivation in tests to use Concat KDF instead of HKDF

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2026-07-06 14:26:15 +02:00
Copilot cbf2a44c6a Fix HAL release workflow Go setup ordering and cache configuration (#609)
CI / lint (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
* Initial plan

* fix: repair hal workflow setup-go step

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-02 10:52:32 +02:00
Sammy Kerata Oina 5c3561f85d NOISSUE - Support non-chunked computation requests and add KBS decryption for uploaded algorithms and datasets. (#608)
CI / lint (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: support non-chunked computation requests and add KBS decryption for uploaded algorithms and datasets.

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

* feat: update dataset resolution logic with context-based index verification and add extensive service error handling tests

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

* feat: add AES encryption script and update package sources to connector-mods fork

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

* fix: update file permission syntax in encrypt.go

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2026-07-01 15:52:43 +02:00
Sammy Kerata Oina 6169766666 NOISSUE - Fix agent startup issues (#605)
CI / lint (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
* Update attestationFromCert function to include ccPlatform parameter for enhanced attestation processing

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

* chore: migrate dependencies from supermq to magistrala and update build configurations

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

* chore: update project dependencies, repository source, and support TDX QuoteV5 attestation

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2026-06-11 17:08:24 +02:00
Sammy Kerata Oina 5f339d2fab NOISSUE - Refactor test functions to use testing.TB interface and enhance SNP claims extraction logic (#599)
CI / lint (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
* Refactor test functions to use testing.TB interface and enhance SNP claims extraction logic

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
Co-authored-by: Danko Miladinovic <72250944+danko-miladinovic@users.noreply.github.com>
Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Enhance VTPM claims extraction logic and update test to use proto.Marshal for report generation

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
Co-authored-by: Danko Miladinovic <72250944+danko-miladinovic@users.noreply.github.com>
2026-06-08 17:29:04 +02:00
dependabot[bot] 7e8eab77e7 Bump cloud.google.com/go/storage from 1.57.2 to 1.62.3 (#581)
CI / lint (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
Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.57.2 to 1.62.3.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/storage/v1.57.2...storage/v1.62.3)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/storage
  dependency-version: 1.61.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 15:28:37 +03:00
dependabot[bot] 9f31e2472b Bump github.com/absmach/supermq from 0.19.0 to 0.19.1 (#580)
Bumps [github.com/absmach/supermq](https://github.com/absmach/supermq) from 0.19.0 to 0.19.1.
- [Commits](https://github.com/absmach/supermq/commits/v0.19.1)

---
updated-dependencies:
- dependency-name: github.com/absmach/supermq
  dependency-version: 0.19.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 15:18:16 +03:00
dependabot[bot] e8e616ff62 Bump github.com/absmach/certs from 0.18.2 to 0.18.5 (#574)
Bumps [github.com/absmach/certs](https://github.com/absmach/certs) from 0.18.2 to 0.18.5.
- [Release notes](https://github.com/absmach/certs/releases)
- [Commits](https://github.com/absmach/certs/compare/v0.18.2...v0.18.5)

---
updated-dependencies:
- dependency-name: github.com/absmach/certs
  dependency-version: 0.18.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 15:07:54 +03:00
dependabot[bot] 0dce9d3083 Bump github.com/google/go-tpm from 0.9.6 to 0.9.8 (#572)
Bumps [github.com/google/go-tpm](https://github.com/google/go-tpm) from 0.9.6 to 0.9.8.
- [Release notes](https://github.com/google/go-tpm/releases)
- [Commits](https://github.com/google/go-tpm/compare/v0.9.6...v0.9.8)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm
  dependency-version: 0.9.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 15:07:36 +03:00
dependabot[bot] a37121dc7b Bump github.com/go-chi/chi/v5 from 5.2.3 to 5.2.4 (#570)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.2.3 to 5.2.4.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.2.3...v5.2.4)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 15:07:17 +03:00
Jovan Djukic 1f0eccfae7 Manager can start CVM with NVIDIA GPU support (#595)
CI / lint (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
2026-05-25 12:32:00 +02:00
Danko Miladinovic 02aa7d7d85 NOISSUE - Azure TDX Support (#596)
* initial Azure TDX support

* add tests

* update documentation

---------

Co-authored-by: Ubuntu <danko@cocos.nbzvzgavv4yeximq0jorvcggfd.dx.internal.cloudapp.net>
2026-05-25 12:22:29 +02:00
Jovan Djukic 27db9b29eb COCOS-591: Add support for GPU CC attestation (#592)
CI / lint (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
* Added GPU evidence collection

* Added GPU evidence verification

* Added make command for nvattest helper

* Added command for installing all services

* changed attestion-service.service so it knows where the helper is

* Possible IGVM script bug

* Possible bug

* Bug

* bug

* Revert "bug"

This reverts commit d81d67e73d.

* Revert "Bug"

This reverts commit 5e566d53c1.

* Revert "Possible bug"

This reverts commit 47d13fe583.

* Revert "Possible IGVM script bug"

This reverts commit 3fb1b79537.

* Revert "changed attestion-service.service so it knows where the helper is"

This reverts commit f9f11ed183.

* Revert "Added command for installing all services"

This reverts commit 5dcf7a5c0a.

* NOISSUE - Enforce binding label check (#589)

* NOISSUE - Implement extensible resource downloader framework with support for S3, GCS, and OCI sources (#590)

* feat: implement extensible resource downloader framework with support for S3, GCS, and OCI sources

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

* refactor: improve resource URL parsing and add support for bare OCI image references

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

* fix: add empty string check and slash requirement for OCI image inference, and update python unit tests with event mock expectations

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

* refactor: introduce OCIClient interface, add test coverage for decryption, and improve resource download error handling

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

* chore: remove trailing whitespace in OCI downloader and HTTP tests

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

---------

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

* Refactored baed on comments

* Added GPU evidence collection

* Added GPU evidence verification

* Added make command for nvattest helper

* Added command for installing all services

* changed attestion-service.service so it knows where the helper is

* Possible IGVM script bug

* Possible bug

* Bug

* bug

* Revert "bug"

This reverts commit d81d67e73d.

* Revert "Bug"

This reverts commit 5e566d53c1.

* Revert "Possible bug"

This reverts commit 47d13fe583.

* Revert "Possible IGVM script bug"

This reverts commit 3fb1b79537.

* Revert "changed attestion-service.service so it knows where the helper is"

This reverts commit f9f11ed183.

* Revert "Added command for installing all services"

This reverts commit 5dcf7a5c0a.

* Refactored baed on comments

* fixed lint error

* fixed tests

* Fixed according to comments

* COCOS-584 - Support multiple kbs (#587)

* feat: Implement per-resource KBS configuration, allowing algorithms and datasets to specify individual KBS URLs.

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

* refactor: Encapsulate CLI error handling and CVM certificate paths within the CLI struct, and add algorithm type to agent's algorithm structure.

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

* style: Remove blank lines and fix indentation in CLI commands.

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

* refactor: Update downloadAndDecryptGenericResource to accept KBS URL as a parameter and adjust related tests

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

* refactor: group CLI configuration into structured types and simplify skopeo decryption key handling

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

---------

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

* Added GPU evidence collection

* Added GPU evidence verification

* Added make command for nvattest helper

* Added command for installing all services

* changed attestion-service.service so it knows where the helper is

* Possible IGVM script bug

* Possible bug

* Bug

* bug

* Revert "bug"

This reverts commit d81d67e73d.

* Revert "Bug"

This reverts commit 5e566d53c1.

* Revert "Possible bug"

This reverts commit 47d13fe583.

* Revert "Possible IGVM script bug"

This reverts commit 3fb1b79537.

* Revert "changed attestion-service.service so it knows where the helper is"

This reverts commit f9f11ed183.

* Revert "Added command for installing all services"

This reverts commit 5dcf7a5c0a.

* Refactored baed on comments

* Added GPU evidence collection

* Added GPU evidence verification

* Added make command for nvattest helper

* Added command for installing all services

* changed attestion-service.service so it knows where the helper is

* Possible IGVM script bug

* Possible bug

* Bug

* bug

* Revert "bug"

This reverts commit d81d67e73d.

* Revert "Bug"

This reverts commit 5e566d53c1.

* Revert "Possible bug"

This reverts commit 47d13fe583.

* Revert "Possible IGVM script bug"

This reverts commit 3fb1b79537.

* Revert "changed attestion-service.service so it knows where the helper is"

This reverts commit f9f11ed183.

* Revert "Added command for installing all services"

This reverts commit 5dcf7a5c0a.

* Refactored baed on comments

* fixed lint error

* fixed tests

* Fixed according to comments

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
Co-authored-by: Danko Miladinovic <72250944+danko-miladinovic@users.noreply.github.com>
Co-authored-by: Sammy Kerata Oina <44265300+SammyOina@users.noreply.github.com>
2026-05-08 16:35:04 +02:00
Danko Miladinovic 81fe0b11b5 COCOS-432 - FDE support (#553)
CI / lint (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
* initial FDE setup

* add Manager support

* fix igvmmeasure build

* rebase on main

* add tests

* NOISSUE - Allow interoperability with CC Attestation Agent (#568)

* feat: Add Confidential Containers attestation agent as an alternative attestation backend with new proto definitions and build system integration.

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

* fix: Update protoc-gen-go and protoc-gen-go-grpc versions in CI workflow

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

* feat: Add mock implementation for AttestationAgentServiceClient and corresponding tests

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

* fix: Add missing periods to test function comments in provider_test.go

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

---------

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

* NOISSUE - Agent Pull mode for remote resources (#575)

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

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

test(kbs): add unit tests for KBS client

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

feat(registry): introduce HTTP and S3 registry implementations

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

chore(registry): define registry interface and configuration

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* client fixes

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

* raw evidence

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

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

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

* fix: Wrap binary evidence in JSON for KBS compatibility

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

* chore: Update buildroot packages to c28cefae

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

* fix: Implement KBS RCAR handshake with cookies

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

* chore: Update buildroot packages to f6981ac5

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

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

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

* fix: Wrap attestation evidence in primary_evidence format

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

This matches the Confidential Containers KBS Attestation Protocol requirements.

* fix: Update KBS protocol version to 0.4.0

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

* fix: Generate ephemeral key for KBS RuntimeData

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

* fix: Update sample attestation quote to valid JSON

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

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

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

* refactor: Delegate Sample Attestation to Provider

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

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

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

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

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

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

* feat: Add gRPC log forwarding to attestation-service

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

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

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

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

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

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

* debug: Increase KBS evidence logging preview to 1000 bytes

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

* debug: Add comprehensive CC AA configuration logging

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

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

* debug: Add startup logging for log client connection

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

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

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

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

* fix: Flatten sample evidence fields in primary_evidence for KBS

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

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

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

This matches what KBS expects when deserializing the Quote structure.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore: regenerate protobuf files for updated cvms.proto

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* logging

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

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

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

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

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

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

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

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

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

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

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

* Refactor code structure for improved readability and maintainability

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* refactor: replace hardcoded Python script content with constant variable

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

* fix: remove redundant mock expectation for SendAgentConfig in TestCreateVMWithAaKbsParams

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

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

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

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

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

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

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

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

---------

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

* initial FDE setup

* add Manager support

* add cloud-init script

* rebase onto main

* add blank lines

* add tdx rtmr support

* add FDE flow

* use DiskConfig.Format instead of fixed values

* add tests and expand Manager README.md

* add curl command

* add encrypted partition support

* remove nbd

* add dm-verity

* fix manager boot sequence

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
Co-authored-by: ultraviolet <cocosai@worker-52.local.pragmatic-it.com>
Co-authored-by: Sammy Kerata Oina <44265300+SammyOina@users.noreply.github.com>
2026-05-08 14:59:13 +02:00
146 changed files with 7107 additions and 462 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
- name: Set up protoc
run: |
PROTOC_VERSION=33.1
PROTOC_VERSION=35.0
PROTOC_GEN_VERSION=v1.36.11
PROTOC_GRPC_VERSION=v1.6.0
+17 -9
View File
@@ -17,6 +17,9 @@ jobs:
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
mkdir -p "$RUNNER_TEMP/hostedtoolcache"
echo "RUNNER_TOOL_CACHE=$RUNNER_TEMP/hostedtoolcache" >> "$GITHUB_ENV"
echo "AGENT_TOOLSDIRECTORY=$RUNNER_TEMP/hostedtoolcache" >> "$GITHUB_ENV"
df -h
- name: Update Ubuntu
@@ -39,17 +42,16 @@ jobs:
echo "Free space:"
df -h
- name: Checkout cocos
uses: actions/checkout@v4
with:
path: cocos
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.26.x
cache-dependency-path: "go.sum"
- name: Checkout cocos
uses: actions/checkout@v4
with:
repository: "ultravioletrs/cocos"
path: cocos
cache-dependency-path: "cocos/go.sum"
- name: Checkout buildroot
uses: actions/checkout@v4
@@ -75,6 +77,12 @@ jobs:
files: |
buildroot/output/images/bzImage
buildroot/output/images/rootfs.cpio.gz
cocos/build/cocos-agent
cocos/build/cocos-cli
cocos/build/cocos-agent
cocos/build/cocos-cli
cocos/build/cocos-manager
cocos/build/cocos-attestation-service
cocos/build/cocos-log-forwarder
cocos/build/cocos-computation-runner
cocos/build/cocos-egress-proxy
cocos/build/cocos-ingress-proxy
cocos/build/igvmmeasure
+3 -1
View File
@@ -19,6 +19,7 @@ target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
!tools/nvidia-attestation-helper/Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
@@ -28,4 +29,5 @@ Cargo.lock
*.enc
*.key
*.pub
*.pub
.codex
+50 -5
View File
@@ -1,11 +1,24 @@
BUILD_DIR = build
SERVICES = manager agent cli attestation-service log-forwarder computation-runner egress-proxy ingress-proxy
NVIDIA_ATTESTATION_HELPER = nvidia-attestation-helper
NVIDIA_ATTESTATION_HELPER_DIR = tools/$(NVIDIA_ATTESTATION_HELPER)
NVIDIA_ATTESTATION_HELPER_MANIFEST = $(NVIDIA_ATTESTATION_HELPER_DIR)/Cargo.toml
NVIDIA_ATTESTATION_HELPER_BINARY = $(BUILD_DIR)/$(NVIDIA_ATTESTATION_HELPER)
NVIDIA_ATTESTATION_HELPER_LIB_DIR = $(BUILD_DIR)/lib
NVAT_SDK_CPP_DIR ?= $(firstword $(wildcard $(HOME)/.cargo/git/checkouts/attestation-sdk-*/*/nv-attestation-sdk-cpp))
NVAT_SDK_CPP_BUILD_DIR ?= $(NVAT_SDK_CPP_DIR)/build
NVAT_SDK_HEADER ?= $(NVAT_SDK_CPP_BUILD_DIR)/include/nvat.h
NVAT_SDK_SHARED_LIB ?= $(NVAT_SDK_CPP_BUILD_DIR)/libnvat.so.1
NVAT_SYSTEM_HEADER ?= /usr/include/nvat.h
CARGO ?= cargo
CMAKE ?= cmake
CGO_ENABLED ?= 0
GOARCH ?= amd64
VERSION ?= $(shell git describe --abbrev=0 --tags --always)
COMMIT ?= $(shell git rev-parse HEAD)
TIME ?= $(shell date +%F_%T)
EMBED_ENABLED ?= 0
NVAT_USE_SYSTEM_LIB ?=
INSTALL_DIR ?= /usr/local/bin
CONFIG_DIR ?= /etc/cocos
SERVICE_NAME ?= cocos-manager
@@ -16,21 +29,53 @@ IGVM_BUILD_SCRIPT := ./scripts/igvmmeasure/igvm.sh
define compile_service
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
go build -ldflags "-s -w \
-X 'github.com/absmach/supermq.BuildTime=$(TIME)' \
-X 'github.com/absmach/supermq.Version=$(VERSION)' \
-X 'github.com/absmach/supermq.Commit=$(COMMIT)'" \
-X 'github.com/absmach/magistrala.BuildTime=$(TIME)' \
-X 'github.com/absmach/magistrala.Version=$(VERSION)' \
-X 'github.com/absmach/magistrala.Commit=$(COMMIT)'" \
$(if $(filter 1,$(EMBED_ENABLED)),-tags "embed",) \
-o ${BUILD_DIR}/cocos-$(1) ./cmd/$(1)
endef
.PHONY: all $(SERVICES) install clean
NVIDIA_ATTESTATION_HELPER_CARGO_ENV = $(if $(filter 1,$(NVAT_USE_SYSTEM_LIB)),NVAT_USE_SYSTEM_LIB=1,)
NVIDIA_ATTESTATION_HELPER_RUSTFLAGS = $(strip $(RUSTFLAGS) $(if $(filter 1,$(NVAT_USE_SYSTEM_LIB)),,-C link-arg=-Wl,-rpath,$$ORIGIN/lib))
.PHONY: all $(SERVICES) $(NVIDIA_ATTESTATION_HELPER) nvidia-attestation-helper-prereqs install clean
all: $(SERVICES)
$(SERVICES):
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(SERVICES): | $(BUILD_DIR)
$(call compile_service,$@)
@if [ "$@" = "cli" ] || [ "$@" = "manager" ]; then $(MAKE) build-igvm; fi
nvidia-attestation-helper-prereqs:
ifeq ($(filter 1,$(NVAT_USE_SYSTEM_LIB)),1)
@test -f $(NVAT_SYSTEM_HEADER) || \
( echo "Missing $(NVAT_SYSTEM_HEADER). Install the NVAT development package or run without NVAT_USE_SYSTEM_LIB=1."; exit 1 )
@ldconfig -p | grep -q libnvat.so.1 || \
( echo "libnvat.so.1 not found in the dynamic linker cache. Install the NVAT runtime package or run without NVAT_USE_SYSTEM_LIB=1."; exit 1 )
else
@if [ -z "$(NVAT_SDK_CPP_DIR)" ]; then \
echo "Unable to locate nv-attestation-sdk-cpp under $$HOME/.cargo/git/checkouts."; \
echo "Run 'cargo fetch --manifest-path $(NVIDIA_ATTESTATION_HELPER_MANIFEST)' first, or install NVAT and use 'make NVAT_USE_SYSTEM_LIB=1 $(NVIDIA_ATTESTATION_HELPER)'."; \
exit 1; \
fi
@if [ ! -f "$(NVAT_SDK_HEADER)" ] || [ ! -f "$(NVAT_SDK_SHARED_LIB)" ]; then \
$(CMAKE) -S $(NVAT_SDK_CPP_DIR) -B $(NVAT_SDK_CPP_BUILD_DIR) && \
$(CMAKE) --build $(NVAT_SDK_CPP_BUILD_DIR); \
fi
endif
$(NVIDIA_ATTESTATION_HELPER): nvidia-attestation-helper-prereqs | $(BUILD_DIR)
RUSTFLAGS='$(NVIDIA_ATTESTATION_HELPER_RUSTFLAGS)' $(NVIDIA_ATTESTATION_HELPER_CARGO_ENV) $(CARGO) build --manifest-path $(NVIDIA_ATTESTATION_HELPER_MANIFEST) --release
install -m 755 $(NVIDIA_ATTESTATION_HELPER_DIR)/target/release/$(NVIDIA_ATTESTATION_HELPER) $(NVIDIA_ATTESTATION_HELPER_BINARY)
@if [ "$(filter 1,$(NVAT_USE_SYSTEM_LIB))" != "1" ]; then \
install -d $(NVIDIA_ATTESTATION_HELPER_LIB_DIR); \
install -m 755 $(NVAT_SDK_SHARED_LIB) $(NVIDIA_ATTESTATION_HELPER_LIB_DIR)/libnvat.so.1; \
fi
protoc:
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative agent/agent.proto
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative manager/manager.proto
+8
View File
@@ -21,12 +21,20 @@ The service is configured using the environment variables from the following tab
| AGENT_CVM_ID | Unique identifier for the CVM (Confidential Virtual Machine) | "" |
| AGENT_CERTS_TOKEN | Authentication token for certificate service access | "" |
| AGENT_MAA_URL | Microsoft Azure Attestation service URL for Azure attestation | https://sharedeus2.eus2.attest.azure.net |
| AZURE_TDX_IMDS_URL | Azure TDX quote endpoint used by direct Azure TDX attestation | http://169.254.169.254/acc/tdquote |
| AZURE_HCL_REFRESH_WAIT | Wait after writing TDX report data to Azure HCL vTPM storage before reading the refreshed HCL report | 3s |
| 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 |
### Azure TDX Attestation
When the agent runs on an Azure TDX CVM, Azure attestation uses the direct Azure TDX flow. The agent writes TDX report data to Azure HCL vTPM storage, reads the refreshed HCL report, requests a TD quote from Azure IMDS, and submits the quote plus HCL runtime data to Microsoft Azure Attestation. This path does not depend on Confidential Containers attestation-agent `GetEvidence` or KBS token retrieval.
`AGENT_MAA_URL` selects the Microsoft Azure Attestation endpoint. `AZURE_TDX_IMDS_URL` can override the Azure IMDS TDX quote endpoint, and `AZURE_HCL_REFRESH_WAIT` controls the wait used to avoid reading a stale HCL report after report-data is written.
### 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.
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.1
// protoc v7.35.0
// source: agent/agent.proto
package agent
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc v6.33.1
// - protoc v7.35.0
// source: agent/agent.proto
package agent
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"strings"
"testing"
mglog "github.com/absmach/supermq/logger"
mglog "github.com/absmach/magistrala/logger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent/events/mocks"
+1 -1
View File
@@ -60,7 +60,7 @@ func (req azureAttestationTokenReq) validate() error {
func validateAttestationType(attType attestation.PlatformType) error {
switch attType {
case attestation.SNP, attestation.VTPM, attestation.SNPvTPM, attestation.TDX:
case attestation.SNP, attestation.VTPM, attestation.SNPvTPM, attestation.Azure, attestation.TDX:
return nil
default:
return errors.New("invalid attestation type")
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"crypto/x509"
"encoding/base64"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/magistrala/pkg/errors"
"github.com/ultravioletrs/cocos/agent"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"encoding/base64"
"testing"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/magistrala/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ultravioletrs/cocos/agent"
+4 -1
View File
@@ -10,7 +10,7 @@ import (
"sync"
"time"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/magistrala/pkg/errors"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/cvms"
"github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage"
@@ -178,6 +178,9 @@ func (client *CVMSClient) processIncomingMessage(ctx context.Context, req *cvms.
switch mes := req.Message.(type) {
case *cvms.ServerStreamMessage_RunReqChunks:
return client.handleRunReqChunks(ctx, mes)
case *cvms.ServerStreamMessage_RunReq:
client.logger.Info("Starting computation execution from non-chunked run request", "computationId", mes.RunReq.Id, "name", mes.RunReq.Name)
go client.executeRun(ctx, mes.RunReq)
case *cvms.ServerStreamMessage_StopComputation:
go client.handleStopComputation(ctx, mes)
case *cvms.ServerStreamMessage_AgentStateReq:
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"testing"
"time"
mglog "github.com/absmach/supermq/logger"
mglog "github.com/absmach/magistrala/logger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent"
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"testing"
"time"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/magistrala/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent/cvms"
+3 -3
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.1
// protoc v7.35.0
// source: agent/cvms/cvms.proto
package cvms
@@ -1123,8 +1123,8 @@ func (x *Algorithm) GetKbs() *KBSConfig {
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)
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Type of source: "oci-image", "s3", "gcs", "https", "http"
Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` // URL of the resource (e.g., docker://registry/repo:tag, s3://bucket/key, https://host/path)
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
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc v6.33.1
// - protoc v7.35.0
// source: agent/cvms/cvms.proto
package cvms
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.1
// protoc v7.35.0
// source: agent/events/events.proto
package events
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.1
// protoc v7.35.0
// source: agent/log/log.proto
package log
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc v6.33.1
// - protoc v7.35.0
// source: agent/log/log.proto
package log
+5
View File
@@ -28,6 +28,11 @@ func (m *MockAttestationClient) GetAzureToken(ctx context.Context, nonce [32]byt
return args.Get(0).([]byte), args.Error(1)
}
func (m *MockAttestationClient) GetKbsToken(ctx context.Context) ([]byte, error) {
args := m.Called(ctx)
return args.Get(0).([]byte), args.Error(1)
}
func (m *MockAttestationClient) Close() error {
args := m.Called()
return args.Error(0)
+11 -3
View File
@@ -51,9 +51,13 @@ func TestDownloadAndDecryptGenericResource(t *testing.T) {
mockDownloader.On("Type").Return(resource.SourceTypeHTTP)
registry.Register(mockDownloader)
attestationClient := new(MockAttestationClient)
attestationClient.On("GetKbsToken", mock.Anything).Return([]byte("mockToken"), nil).Maybe()
svc := &agentService{
logger: slog.Default(),
resourceRegistry: registry,
logger: slog.Default(),
resourceRegistry: registry,
attestationClient: attestationClient,
computation: Computation{
Algorithm: &Algorithm{
KBS: &KBSConfig{
@@ -122,8 +126,12 @@ func TestDownloadAndDecryptGenericResource(t *testing.T) {
}
func TestGetKeyFromKBS(t *testing.T) {
attestationClient := new(MockAttestationClient)
attestationClient.On("GetKbsToken", mock.Anything).Return([]byte("mockToken"), nil).Maybe()
svc := &agentService{
logger: slog.Default(),
logger: slog.Default(),
attestationClient: attestationClient,
computation: Computation{
Algorithm: &Algorithm{
KBS: &KBSConfig{
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.1
// protoc v7.35.0
// source: agent/runner/runner.proto
package runner
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc v6.33.1
// - protoc v7.35.0
// source: agent/runner/runner.proto
package runner
+198 -51
View File
@@ -5,7 +5,11 @@ package agent
import (
"context"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"log/slog"
@@ -19,7 +23,7 @@ import (
sync "sync"
"time"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/magistrala/pkg/errors"
"github.com/ultravioletrs/cocos/agent/algorithm"
"github.com/ultravioletrs/cocos/agent/events"
runnerpb "github.com/ultravioletrs/cocos/agent/runner"
@@ -29,6 +33,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/crypto"
"github.com/ultravioletrs/cocos/pkg/oci"
"github.com/ultravioletrs/cocos/pkg/resource"
"golang.org/x/crypto/sha3"
@@ -85,6 +90,29 @@ var (
ImaPcrIndex = 10
)
func ensureDir(path string, mode os.FileMode) error {
info, err := os.Stat(path)
switch {
case err == nil:
if info.IsDir() {
return nil
}
if err := os.Remove(path); err != nil {
return fmt.Errorf("removing non-directory path %q: %w", path, err)
}
case os.IsNotExist(err):
// Continue and create it below.
default:
return fmt.Errorf("stating path %q: %w", path, err)
}
if err := os.MkdirAll(path, mode); err != nil {
return fmt.Errorf("creating directory %q: %w", path, err)
}
return nil
}
var (
// ErrMalformedEntity indicates malformed entity specification (e.g.
// invalid username or password).
@@ -393,7 +421,7 @@ func (as *agentService) downloadAlgorithmIfRemote(state statemachine.State) {
"kbs_enabled", kbsEnabled)
// Check if algorithm should be downloaded from remote source
if as.computation.Algorithm.Source != nil && kbsEnabled {
if as.computation.Algorithm.Source != nil && kbsEnabled && as.computation.Algorithm.Source.URL != "" {
as.logger.Info("downloading algorithm from remote source",
"url", as.computation.Algorithm.Source.URL,
"kbs_resource_path", as.computation.Algorithm.Source.KBSResourcePath,
@@ -478,8 +506,8 @@ func (as *agentService) downloadAlgorithmIfRemote(state statemachine.State) {
as.algoReceived = true
as.algoRequirements = res.Requirements // Store requirements for installation
// Create datasets directory
if err := os.Mkdir(algorithm.DatasetsDir, 0o755); err != nil {
// The initramfs may have already provisioned /cocos/datasets.
if err := ensureDir(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)
@@ -513,7 +541,7 @@ func (as *agentService) downloadDatasetsIfRemote(state statemachine.State) {
hasRemoteDatasets := false
for _, d := range as.computation.Datasets {
kbsEnabled := d.KBS != nil && d.KBS.Enabled
if d.Source != nil && kbsEnabled {
if d.Source != nil && kbsEnabled && d.Source.URL != "" {
hasRemoteDatasets = true
break
}
@@ -534,7 +562,7 @@ func (as *agentService) downloadDatasetsIfRemote(state statemachine.State) {
kbsURL = d.KBS.URL
}
if d.Source != nil && kbsEnabled {
if d.Source != nil && kbsEnabled && d.Source.URL != "" {
as.logger.Info("downloading dataset from remote source", "filename", d.Filename, "kbs_url", kbsURL)
res, err := as.downloadAndDecryptResource(ctx, d.Source, kbsURL, "dataset")
@@ -739,26 +767,91 @@ func (as *agentService) getKeyFromKBS(ctx context.Context, kbsURL, resourcePath
as.logger.Info("fetching key from KBS", "url", kbsResourceURL)
// Use a simple HTTP GET to KBS for now.
// In a full CoCo deployment, this would go through the Attestation Agent
// which performs attestation before KBS releases the key.
// For non-OCI resources, the AA/KBS handshake may need to be handled
// differently than via ocicrypt.
resp, err := kbsHTTPGet(ctx, kbsResourceURL)
// Fetch token from attestation service via the initialized client
tokenBytes, err := as.attestationClient.GetKbsToken(ctx)
if err != nil {
return nil, fmt.Errorf("failed to retrieve token from attestation service: %w", err)
}
// Unmarshal the JSON from attestation service containing token and private key
var msg struct {
Token string `json:"token"`
TeeKeyPair string `json:"tee_keypair"`
}
var token string
var teeKeyPairPem string
if err := json.Unmarshal(tokenBytes, &msg); err == nil {
token = strings.TrimSpace(msg.Token)
teeKeyPairPem = msg.TeeKeyPair
as.logger.Info("retrieved token and keypair from attestation service successfully")
} else {
// Fallback for non-JSON token bytes
token = strings.TrimSpace(string(tokenBytes))
as.logger.Warn("attestation service token is not in JSON format; using raw token without local JWE decryption fallback", "error", err)
}
resp, err := kbsHTTPGet(ctx, kbsResourceURL, token)
if err != nil {
return nil, fmt.Errorf("failed to fetch key from KBS at %s: %w", kbsResourceURL, err)
}
return resp, nil
// If we don't have a private key, return the response as is (could be plaintext)
if teeKeyPairPem == "" {
as.logger.Info("no session keypair found, returning KBS response directly")
return resp, nil
}
// Parse JWE response
encryptedRes, err := crypto.ParseEncryptedResource(resp)
if err != nil {
as.logger.Warn("failed to parse JWE resource response, returning raw response", "error", err)
return resp, nil
}
// Parse PKCS#8 private key PEM
block, _ := pem.Decode([]byte(teeKeyPairPem))
if block == nil {
return nil, fmt.Errorf("failed to decode private key PEM")
}
privKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key PKCS8: %w", err)
}
var ecdhKey *ecdh.PrivateKey
switch k := privKey.(type) {
case *ecdsa.PrivateKey:
ecdhKey, err = k.ECDH()
if err != nil {
return nil, fmt.Errorf("failed to get ECDH private key: %w", err)
}
default:
return nil, fmt.Errorf("private key is not ECDSA (unsupported curve or type)")
}
// Decrypt JWE resource using the private key
decryptedKey, err := crypto.DecryptWithWrappedKey(*encryptedRes, ecdhKey)
if err != nil {
return nil, fmt.Errorf("failed to decrypt KBS resource: %w", err)
}
as.logger.Info("successfully decrypted KBS key", "key_len", len(decryptedKey))
return decryptedKey, nil
}
// kbsHTTPGet performs an HTTP GET to the KBS endpoint.
func kbsHTTPGet(ctx context.Context, url string) ([]byte, error) {
func kbsHTTPGet(ctx context.Context, url string, token string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
@@ -921,7 +1014,7 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
kbsURL = as.computation.Algorithm.KBS.URL
}
if as.computation.Algorithm.Source != nil && kbsEnabled {
if as.computation.Algorithm.Source != nil && kbsEnabled && as.computation.Algorithm.Source.URL != "" {
as.logger.Info("downloading algorithm from remote source", "kbs_url", kbsURL)
res, err := as.downloadAndDecryptResource(ctx, as.computation.Algorithm.Source, kbsURL, "algorithm")
@@ -934,6 +1027,19 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
} else {
// Use directly uploaded algorithm
algoData = algo.Algorithm
if as.computation.Algorithm.Source != nil && as.computation.Algorithm.Source.Encrypted && kbsEnabled {
as.logger.Info("directly uploaded algorithm is encrypted, retrieving key from KBS")
key, err := as.getKeyFromKBS(ctx, kbsURL, as.computation.Algorithm.Source.KBSResourcePath)
if err != nil {
return fmt.Errorf("failed to retrieve key from KBS for uploaded algorithm: %w", err)
}
decrypted, err := resource.DecryptData(algoData, key)
if err != nil {
return fmt.Errorf("failed to decrypt uploaded algorithm: %w", err)
}
algoData = decrypted
}
}
hash := sha3.Sum256(algoData)
@@ -976,7 +1082,7 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
as.algoRequirements = algo.Requirements
as.algoReceived = true
if err := os.Mkdir(algorithm.DatasetsDir, 0o755); err != nil {
if err := ensureDir(algorithm.DatasetsDir, 0o755); err != nil {
return fmt.Errorf("error creating datasets directory: %v", err)
}
@@ -1009,7 +1115,7 @@ func (as *agentService) Data(ctx context.Context, dataset Dataset) error {
kbsURL = d.KBS.URL
}
if d.Source != nil && kbsEnabled {
if d.Source != nil && kbsEnabled && d.Source.URL != "" {
as.logger.Info("downloading dataset from remote source", "filename", d.Filename, "kbs_url", kbsURL)
downloadedData, err := as.downloadAndDecryptResource(ctx, d.Source, kbsURL, "dataset")
@@ -1028,44 +1134,85 @@ func (as *agentService) Data(ctx context.Context, dataset Dataset) error {
if matchedIndex == -1 {
datasetData = dataset.Dataset
datasetFilename = dataset.Filename
index, ok := IndexFromContext(ctx)
if ok {
if index < 0 || index >= len(as.computation.Datasets) {
return ErrUndeclaredDataset
}
if as.computation.Datasets[index].Filename != datasetFilename {
return ErrFileNameMismatch
}
matchedIndex = index
} else {
matchedIndex = -1
for i, d := range as.computation.Datasets {
if d.Filename == datasetFilename {
matchedIndex = i
break
}
}
if matchedIndex == -1 {
return ErrUndeclaredDataset
}
}
} else {
remoteIndex := -1
for i, d := range as.computation.Datasets {
if d.Filename == datasetFilename {
remoteIndex = i
break
}
}
if remoteIndex == -1 {
return ErrUndeclaredDataset
}
matchedIndex = remoteIndex
}
d := as.computation.Datasets[matchedIndex]
kbsEnabled := d.KBS != nil && d.KBS.Enabled
kbsURL := ""
if d.KBS != nil {
kbsURL = d.KBS.URL
}
if d.Source != nil && d.Source.Encrypted && kbsEnabled {
as.logger.Info("directly uploaded dataset is encrypted, retrieving key from KBS", "filename", d.Filename)
key, err := as.getKeyFromKBS(ctx, kbsURL, d.Source.KBSResourcePath)
if err != nil {
return fmt.Errorf("failed to retrieve key from KBS for dataset %s: %w", d.Filename, err)
}
decrypted, err := resource.DecryptData(datasetData, key)
if err != nil {
return fmt.Errorf("failed to decrypt dataset %s: %w", d.Filename, err)
}
datasetData = decrypted
}
hash := sha3.Sum256(datasetData)
matched := false
for i, d := range as.computation.Datasets {
if hash == d.Hash {
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(datasetData, algorithm.DatasetsDir); err != nil {
return fmt.Errorf("error decompressing dataset: %v", err)
}
} else {
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(datasetData); err != nil {
return fmt.Errorf("error writing dataset to file: %v", err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("error closing file: %v", err)
}
}
matched = true
break
}
if hash != d.Hash {
return ErrHashMismatch
}
if !matched {
return ErrUndeclaredDataset
as.computation.Datasets = slices.Delete(as.computation.Datasets, matchedIndex, matchedIndex+1)
if DecompressFromContext(ctx) {
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, datasetFilename))
if err != nil {
return fmt.Errorf("error creating dataset file: %v", err)
}
if _, err := f.Write(datasetData); err != nil {
return fmt.Errorf("error writing dataset to file: %v", err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("error closing file: %v", err)
}
}
if len(as.computation.Datasets) == 0 {
@@ -1145,7 +1292,7 @@ func (as *agentService) runComputation(state statemachine.State) {
}
}()
if err := os.Mkdir(algorithm.ResultsDir, 0o755); err != nil {
if err := ensureDir(algorithm.ResultsDir, 0o755); err != nil {
as.mu.Lock()
as.runError = fmt.Errorf("error creating results directory: %s", err.Error())
as.mu.Unlock()
+91 -3
View File
@@ -18,8 +18,8 @@ import (
"testing"
"time"
mglog "github.com/absmach/supermq/logger"
"github.com/absmach/supermq/pkg/errors"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
@@ -218,7 +218,7 @@ func TestData(t *testing.T) {
{
name: "Test dataset not declared in manifest",
data: Dataset{
Filename: datasetFile,
Filename: "undeclared.csv",
},
err: ErrUndeclaredDataset,
},
@@ -1808,3 +1808,91 @@ func TestInferSourceType(t *testing.T) {
})
}
}
func TestInitComputation_Errors(t *testing.T) {
sm := &smmocks.StateMachine{}
sm.On("GetState").Return(ReceivingAlgorithm) // Not ReceivingManifest
svc := &agentService{
sm: sm,
}
err := svc.InitComputation(context.Background(), Computation{})
assert.ErrorIs(t, err, ErrStateNotReady)
}
func TestAlgo_Errors(t *testing.T) {
t.Run("state not ready", func(t *testing.T) {
sm := &smmocks.StateMachine{}
sm.On("GetState").Return(ReceivingManifest) // Not ReceivingAlgorithm
svc := &agentService{
sm: sm,
}
err := svc.Algo(context.Background(), Algorithm{})
assert.ErrorIs(t, err, ErrStateNotReady)
})
t.Run("all manifest items received", func(t *testing.T) {
sm := &smmocks.StateMachine{}
sm.On("GetState").Return(ReceivingAlgorithm)
svc := &agentService{
sm: sm,
algoReceived: true,
}
err := svc.Algo(context.Background(), Algorithm{})
assert.ErrorIs(t, err, ErrAllManifestItemsReceived)
})
t.Run("undeclared algorithm", func(t *testing.T) {
sm := &smmocks.StateMachine{}
sm.On("GetState").Return(ReceivingAlgorithm)
svc := &agentService{
sm: sm,
algoReceived: false,
computation: Computation{
Algorithm: nil, // Not declared
},
}
err := svc.Algo(context.Background(), Algorithm{})
assert.ErrorIs(t, err, ErrUndeclaredAlgorithm)
})
}
func TestData_ErrorsExtra(t *testing.T) {
t.Run("all manifest items received", func(t *testing.T) {
sm := &smmocks.StateMachine{}
sm.On("GetState").Return(ReceivingData)
svc := &agentService{
sm: sm,
computation: Computation{
Datasets: nil, // length 0
},
}
err := svc.Data(context.Background(), Dataset{})
assert.ErrorIs(t, err, ErrAllManifestItemsReceived)
})
}
func TestEnsureDir_Error(t *testing.T) {
tmpFile, err := os.CreateTemp("", "ensureDirTest")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())
tmpFile.Close()
// ensureDir should fail because the parent path is a file, not a directory
err = ensureDir(filepath.Join(tmpFile.Name(), "subdir"), 0o755)
assert.Error(t, err)
}
func TestKbsHTTPGet_Error(t *testing.T) {
_, err := kbsHTTPGet(context.Background(), "%%", "")
assert.Error(t, err)
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"os"
"strings"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/magistrala/pkg/errors"
"github.com/fatih/color"
"github.com/google/go-sev-guest/abi"
tpmAttest "github.com/google/go-tpm-tools/proto/attest"
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"os"
"testing"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/magistrala/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"os"
"path"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/magistrala/pkg/errors"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/agent"
+1 -1
View File
@@ -3,7 +3,7 @@
package cli
import (
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/magistrala/pkg/errors"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/agent/auth"
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"errors"
"testing"
mgerrors "github.com/absmach/supermq/pkg/errors"
mgerrors "github.com/absmach/magistrala/pkg/errors"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/agent/auth"
+20 -6
View File
@@ -17,8 +17,8 @@ import (
"syscall"
"github.com/absmach/certs/sdk"
mglog "github.com/absmach/supermq/logger"
"github.com/absmach/supermq/pkg/prometheus"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/caarlos0/env/v11"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/api"
@@ -39,6 +39,7 @@ import (
runnerclient "github.com/ultravioletrs/cocos/pkg/clients/grpc/runner"
"github.com/ultravioletrs/cocos/pkg/ingress"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc/metadata"
)
const (
@@ -170,6 +171,9 @@ func main() {
}
// Don't defer close here as we want to keep the connection open
if cfg.CVMId != "" {
ctx = metadata.AppendToOutgoingContext(ctx, "job-id", cfg.CVMId)
}
pc, err := newClient.Process(ctx)
if err != nil {
grpcClient.Close()
@@ -236,7 +240,11 @@ func main() {
}
ingressProxy := ingress.NewProxyServer(logger, backendURL, certProvider)
pc, err := cvmsClient.Process(ctx)
agentCtx := ctx
if cfg.CVMId != "" {
agentCtx = metadata.AppendToOutgoingContext(ctx, "job-id", cfg.CVMId)
}
pc, err := cvmsClient.Process(agentCtx)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to cvm server: %s", err))
exitCode = 1
@@ -269,7 +277,7 @@ func main() {
return mc.Process(ctx, cancel)
})
attest, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc)
attest, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc, ccPlatform)
if err != nil {
logger.Error(fmt.Sprintf("failed to get attestation: %s", err))
exitCode = 1
@@ -317,7 +325,7 @@ func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Servic
return svc
}
func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Service) ([]byte, string, error) {
func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Service, ccPlatform attestation.PlatformType) ([]byte, string, error) {
if certFilePath == "" {
return nil, "", nil
}
@@ -328,6 +336,9 @@ func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Ser
}
certPem, _ := pem.Decode(certFile)
if certPem == nil {
return nil, "", fmt.Errorf("failed to decode certificate PEM")
}
certx509, err := x509.ParseCertificate(certPem.Bytes)
if err != nil {
return nil, "", err
@@ -335,7 +346,7 @@ func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Ser
nonceSNP := sha512.Sum512(certFile)
nonceVTPM := sha256.Sum256(certFile)
attest, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, attestation.SNPvTPM)
attest, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, ccPlatform)
if err != nil {
return nil, "", err
}
@@ -354,6 +365,9 @@ func azureAttestationFromCert(ctx context.Context, certFilePath string, svc agen
}
certPem, _ := pem.Decode(certFile)
if certPem == nil {
return nil, "", fmt.Errorf("failed to decode certificate PEM")
}
certx509, err := x509.ParseCertificate(certPem.Bytes)
if err != nil {
return nil, "", err
@@ -33,6 +33,12 @@ func (s *service) FetchRawEvidence(ctx context.Context, req *attestationpb.Attes
var nonce [32]byte
copy(nonce[:], req.Nonce)
binaryReport, err = s.provider.Attestation(reportData[:], nonce[:])
case attestationpb.PlatformType_PLATFORM_TYPE_AZURE:
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)
+76
View File
@@ -0,0 +1,76 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"crypto/sha256"
"fmt"
"strings"
attestationpb "github.com/ultravioletrs/cocos/internal/proto/attestation/v1"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/eat"
attestationgpu "github.com/ultravioletrs/cocos/pkg/attestation/gpu"
)
func newGPUCollector(cfg config) (attestationgpu.Collector, error) {
if strings.TrimSpace(cfg.GPUHelperPath) == "" {
return nil, nil
}
return attestationgpu.NewCommandCollector(cfg.GPUHelperPath, cfg.GPUHelperTimeout)
}
func (s *service) claimOptions(ctx context.Context, req *attestationpb.AttestationRequest, platformType attestation.PlatformType) ([]eat.ClaimsOption, error) {
var opts []eat.ClaimsOption
if s.gpuCollector != nil && shouldCollectGPU(platformType) {
sessionNonce := requestNonce(req)
gpuNonce := deriveComponentNonce(sessionNonce, "gpu")
evidence, err := s.gpuCollector.Collect(ctx, gpuNonce)
if err != nil {
// GPU evidence is opportunistic: if no supported CC-capable GPU is
// attached, or the helper cannot collect evidence, we continue with
// the root CPU/TEE attestation instead of failing the whole request.
s.logger.Warn(fmt.Sprintf("[ATTESTATION-SERVICE] Skipping optional GPU evidence collection: %s", err))
return opts, nil
}
s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Collected GPU evidence: format=%s bytes=%d",
evidence.EvidenceFormat, len(evidence.RawEvidence)))
opts = append(opts, eat.WithGPU(&eat.GPUExtensions{
Vendor: evidence.Vendor,
EvidenceFormat: evidence.EvidenceFormat,
Nonce: gpuNonce,
EvidenceJSON: evidence.RawEvidence,
}))
}
return opts, nil
}
func shouldCollectGPU(platformType attestation.PlatformType) bool {
switch platformType {
case attestation.SNP, attestation.SNPvTPM, attestation.TDX, attestation.Azure:
return true
default:
return false
}
}
func requestNonce(req *attestationpb.AttestationRequest) []byte {
if len(req.Nonce) > 0 {
return append([]byte(nil), req.Nonce...)
}
return append([]byte(nil), req.ReportData...)
}
func deriveComponentNonce(sessionNonce []byte, component string) []byte {
digest := sha256.Sum256(append(append([]byte(nil), sessionNonce...), []byte(":"+component)...))
return digest[:]
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"io"
"log/slog"
"testing"
"github.com/stretchr/testify/assert"
attestationpb "github.com/ultravioletrs/cocos/internal/proto/attestation/v1"
"github.com/ultravioletrs/cocos/pkg/attestation"
attestationgpu "github.com/ultravioletrs/cocos/pkg/attestation/gpu"
)
func TestRequestNonce(t *testing.T) {
req := &attestationpb.AttestationRequest{
ReportData: []byte("report"),
Nonce: []byte("nonce"),
}
assert.Equal(t, []byte("nonce"), requestNonce(req))
req.Nonce = nil
assert.Equal(t, []byte("report"), requestNonce(req))
}
func TestDeriveComponentNonce(t *testing.T) {
sessionNonce := []byte("session-nonce")
gpuNonce := deriveComponentNonce(sessionNonce, "gpu")
gpuNonceAgain := deriveComponentNonce(sessionNonce, "gpu")
teeNonce := deriveComponentNonce(sessionNonce, "tee")
assert.Len(t, gpuNonce, 32)
assert.Equal(t, gpuNonce, gpuNonceAgain)
assert.NotEqual(t, gpuNonce, teeNonce)
}
func TestShouldCollectGPU(t *testing.T) {
assert.True(t, shouldCollectGPU(attestation.SNP))
assert.True(t, shouldCollectGPU(attestation.SNPvTPM))
assert.True(t, shouldCollectGPU(attestation.TDX))
assert.False(t, shouldCollectGPU(attestation.VTPM))
assert.False(t, shouldCollectGPU(attestation.NoCC))
}
func TestNewGPUCollector(t *testing.T) {
collector, err := newGPUCollector(config{})
assert.NoError(t, err)
assert.Nil(t, collector)
collector, err = newGPUCollector(config{
GPUHelperPath: "/tmp/helper",
GPUHelperTimeout: 0,
})
assert.NoError(t, err)
assert.NotNil(t, collector)
}
func TestClaimOptions_SkipsOptionalGPUFailure(t *testing.T) {
svc := &service{
logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
gpuCollector: failingCollector{},
}
req := &attestationpb.AttestationRequest{
ReportData: []byte("report-data"),
Nonce: []byte("nonce-data"),
}
opts, err := svc.claimOptions(context.Background(), req, attestation.TDX)
assert.NoError(t, err)
assert.Empty(t, opts)
}
type failingCollector struct{}
func (failingCollector) Collect(context.Context, []byte) (*attestationgpu.Evidence, error) {
return nil, assert.AnError
}
+76 -27
View File
@@ -11,8 +11,9 @@ import (
"os"
"os/signal"
"syscall"
"time"
mglog "github.com/absmach/supermq/logger"
mglog "github.com/absmach/magistrala/logger"
"github.com/caarlos0/env/v11"
"github.com/ultravioletrs/cocos/agent/cvms"
logpb "github.com/ultravioletrs/cocos/agent/log"
@@ -22,6 +23,7 @@ import (
"github.com/ultravioletrs/cocos/pkg/attestation/azure"
"github.com/ultravioletrs/cocos/pkg/attestation/ccaa"
"github.com/ultravioletrs/cocos/pkg/attestation/eat"
attestationgpu "github.com/ultravioletrs/cocos/pkg/attestation/gpu"
"github.com/ultravioletrs/cocos/pkg/attestation/tdx"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
logclient "github.com/ultravioletrs/cocos/pkg/clients/grpc/log"
@@ -35,16 +37,29 @@ const (
)
type config struct {
LogLevel string `env:"ATTESTATION_LOG_LEVEL" envDefault:"debug"`
Vmpl int `env:"ATTESTATION_VMPL" envDefault:"2"`
AgentMaaURL string `env:"AGENT_MAA_URL" envDefault:"https://sharedeus2.eus2.attest.azure.net"`
AgentOSBuild string `env:"AGENT_OS_BUILD" envDefault:"UVC"`
AgentOSDistro string `env:"AGENT_OS_DISTRO" envDefault:"UVC"`
AgentOSType string `env:"AGENT_OS_TYPE" envDefault:"UVC"`
EATFormat string `env:"ATTESTATION_EAT_FORMAT" envDefault:"CBOR"` // JWT or CBOR
EATIssuer string `env:"ATTESTATION_EAT_ISSUER" envDefault:"cocos-attestation-service"`
UseCCAttestationAgent bool `env:"USE_CC_ATTESTATION_AGENT" envDefault:"false"`
CCAgentAddress string `env:"CC_AGENT_ADDRESS" envDefault:"127.0.0.1:50002"`
LogLevel string `env:"ATTESTATION_LOG_LEVEL" envDefault:"debug"`
Vmpl int `env:"ATTESTATION_VMPL" envDefault:"2"`
AgentMaaURL string `env:"AGENT_MAA_URL" envDefault:"https://sharedeus2.eus2.attest.azure.net"`
AgentOSBuild string `env:"AGENT_OS_BUILD" envDefault:"UVC"`
AgentOSDistro string `env:"AGENT_OS_DISTRO" envDefault:"UVC"`
AgentOSType string `env:"AGENT_OS_TYPE" envDefault:"UVC"`
EATFormat string `env:"ATTESTATION_EAT_FORMAT" envDefault:"CBOR"` // JWT or CBOR
EATIssuer string `env:"ATTESTATION_EAT_ISSUER" envDefault:"cocos-attestation-service"`
UseCCAttestationAgent bool `env:"USE_CC_ATTESTATION_AGENT" envDefault:"false"`
CCAgentAddress string `env:"CC_AGENT_ADDRESS" envDefault:"127.0.0.1:50002"`
GPUHelperPath string `env:"ATTESTATION_GPU_HELPER_PATH" envDefault:""`
GPUHelperTimeout time.Duration `env:"ATTESTATION_GPU_HELPER_TIMEOUT" envDefault:"30s"`
// Future KBS Integration Configuration
// When KBS support is added, these fields will enable:
// - Remote attestation verification via KBS
// - Encrypted algorithm/dataset retrieval
// - Per-computation secret provisioning
//
// Example future fields:
// KBSEndpoint string `env:"KBS_ENDPOINT" envDefault:""` // Optional KBS URL
// KBSEnabled bool `env:"KBS_ENABLED" envDefault:"false"`
// KBSTimeout int `env:"KBS_TIMEOUT_SECONDS" envDefault:"30"`
}
func main() {
@@ -141,8 +156,10 @@ func main() {
}
}
isDirectProvider := false
// Fallback to direct providers if CC AA not configured or unavailable
if provider == nil {
isDirectProvider = true
switch ccPlatform {
case attestation.SNP:
provider = vtpm.NewProvider(false, uint(cfg.Vmpl))
@@ -173,7 +190,7 @@ func main() {
logger.Error("[ATTESTATION-SERVICE] No provider configured!")
}
if ccPlatform == attestation.SNP || ccPlatform == attestation.SNPvTPM {
if (ccPlatform == attestation.SNP || ccPlatform == attestation.SNPvTPM) && isDirectProvider {
if err := vtpm.FetchSEVCertificates(uint(cfg.Vmpl)); err != nil {
logger.Error(fmt.Sprintf("failed to fetch certificates: %s", err))
exitCode = 1
@@ -218,13 +235,24 @@ func main() {
return
}
gpuCollector, err := newGPUCollector(cfg)
if err != nil {
logger.Error(fmt.Sprintf("failed to configure GPU attestation collector: %s", err))
exitCode = 1
return
}
if gpuCollector != nil {
logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] GPU evidence collection enabled via helper %s", cfg.GPUHelperPath))
}
grpcServer := grpc.NewServer()
svc := &service{
provider: provider,
logger: logger,
signingKey: signingKey,
eatFormat: cfg.EATFormat,
eatIssuer: cfg.EATIssuer,
provider: provider,
logger: logger,
signingKey: signingKey,
eatFormat: cfg.EATFormat,
eatIssuer: cfg.EATIssuer,
gpuCollector: gpuCollector,
}
attestationpb.RegisterAttestationServiceServer(grpcServer, svc)
@@ -256,11 +284,12 @@ func main() {
type service struct {
attestationpb.UnimplementedAttestationServiceServer
provider attestation.Provider
logger *slog.Logger
signingKey *ecdsa.PrivateKey
eatFormat string
eatIssuer string
provider attestation.Provider
logger *slog.Logger
signingKey *ecdsa.PrivateKey
eatFormat string
eatIssuer string
gpuCollector attestationgpu.Collector
}
func (s *service) FetchAttestation(ctx context.Context, req *attestationpb.AttestationRequest) (*attestationpb.AttestationResponse, error) {
@@ -291,6 +320,13 @@ 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_AZURE:
var reportData [64]byte
copy(reportData[:], req.ReportData)
var nonce [32]byte
copy(nonce[:], req.Nonce)
binaryReport, err = s.provider.Attestation(reportData[:], nonce[:])
platformType = attestation.Azure
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")
@@ -319,12 +355,14 @@ func (s *service) FetchAttestation(ctx context.Context, req *attestationpb.Attes
}
// Create EAT claims from binary report
nonce := req.ReportData
if len(req.Nonce) > 0 {
nonce = req.Nonce
nonce := requestNonce(req)
claimOpts, err := s.claimOptions(ctx, req, platformType)
if err != nil {
return nil, err
}
claims, err := eat.NewEATClaims(binaryReport, nonce, platformType)
claims, err := eat.NewEATClaims(binaryReport, nonce, platformType, claimOpts...)
if err != nil {
s.logger.Error(fmt.Sprintf("failed to create EAT claims: %s", err))
return nil, fmt.Errorf("failed to create EAT claims: %w", err)
@@ -371,7 +409,7 @@ func convertPlatformType(pt attestationpb.PlatformType) attestation.PlatformType
}
}
func (s *service) GetAzureToken(ctx context.Context, req *attestationpb.AzureTokenRequest) (*attestationpb.AzureTokenResponse, error) {
func (s *service) FetchAzureToken(ctx context.Context, req *attestationpb.AzureTokenRequest) (*attestationpb.AzureTokenResponse, error) {
var nonce [32]byte
copy(nonce[:], req.Nonce)
token, err := s.provider.AzureAttestationToken(nonce[:])
@@ -380,3 +418,14 @@ func (s *service) GetAzureToken(ctx context.Context, req *attestationpb.AzureTok
}
return &attestationpb.AzureTokenResponse{Token: token}, nil
}
func (s *service) FetchKbsToken(ctx context.Context, req *attestationpb.KbsTokenRequest) (*attestationpb.KbsTokenResponse, error) {
s.logger.Info("[ATTESTATION-SERVICE] FetchKbsToken RPC called")
token, err := s.provider.KbsToken()
if err != nil {
s.logger.Error(fmt.Sprintf("[ATTESTATION-SERVICE] FetchKbsToken failed: %v", err))
return nil, err
}
s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] FetchKbsToken succeeded, token len: %d", len(token)))
return &attestationpb.KbsTokenResponse{Token: token}, nil
}
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"os/signal"
"syscall"
mglog "github.com/absmach/supermq/logger"
mglog "github.com/absmach/magistrala/logger"
"github.com/caarlos0/env/v11"
"github.com/ultravioletrs/cocos/agent/cvms"
logpb "github.com/ultravioletrs/cocos/agent/log"
+18 -2
View File
@@ -11,7 +11,7 @@ import (
"os/signal"
"syscall"
mglog "github.com/absmach/supermq/logger"
mglog "github.com/absmach/magistrala/logger"
"github.com/caarlos0/env/v11"
"github.com/ultravioletrs/cocos/agent/cvms"
pb "github.com/ultravioletrs/cocos/agent/log"
@@ -20,6 +20,7 @@ import (
cvmsgrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/cvm"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
const (
@@ -30,6 +31,7 @@ const (
type config struct {
LogLevel string `env:"LOG_FORWARDER_LOG_LEVEL" envAlternate:"AGENT_LOG_LEVEL" envDefault:"debug"`
CVMId string `env:"AGENT_CVM_ID" envDefault:""`
}
func main() {
@@ -100,6 +102,9 @@ func main() {
defer cvmClient.Close()
// Create stream to Manager
if cfg.CVMId != "" {
ctx = metadata.AppendToOutgoingContext(ctx, "job-id", cfg.CVMId, "connection-type", "log-forwarder")
}
stream, err := cvmsClient.Process(ctx)
if err != nil {
logger.Error(fmt.Sprintf("failed to create stream to manager: %s", err))
@@ -122,12 +127,23 @@ func main() {
case msg := <-logQueue:
if err := stream.Send(msg); err != nil {
logger.Error(fmt.Sprintf("failed to send log to manager: %s", err))
// Reconnect logic would go here
return err
}
}
}
})
// Stream Receiver Goroutine
g.Go(func() error {
for {
_, err := stream.Recv()
if err != nil {
logger.Error(fmt.Sprintf("stream connection lost: %s", err))
return err
}
}
})
g.Go(func() error {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
+7 -7
View File
@@ -12,13 +12,13 @@ import (
"os"
"strings"
mglog "github.com/absmach/supermq/logger"
"github.com/absmach/supermq/pkg/jaeger"
"github.com/absmach/supermq/pkg/prometheus"
smqserver "github.com/absmach/supermq/pkg/server"
grpcserver "github.com/absmach/supermq/pkg/server/grpc"
httpserver "github.com/absmach/supermq/pkg/server/http"
"github.com/absmach/supermq/pkg/uuid"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/prometheus"
smqserver "github.com/absmach/magistrala/pkg/server"
grpcserver "github.com/absmach/magistrala/pkg/server/grpc"
httpserver "github.com/absmach/magistrala/pkg/server/http"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/caarlos0/env/v11"
"github.com/go-chi/chi/v5"
"github.com/ultravioletrs/cocos/manager"
+46 -45
View File
@@ -4,48 +4,51 @@ go 1.26.0
require (
github.com/caarlos0/env/v11 v11.4.0
github.com/fatih/color v1.18.0
github.com/fatih/color v1.19.0
github.com/go-kit/kit v0.13.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/google/go-sev-guest v0.14.1
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843
github.com/google/go-tdx-guest v0.3.2-0.20260605221019-34f07ec666c4
github.com/spf13/cobra v1.10.2
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.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.79.1
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0
go.opentelemetry.io/otel/trace v1.43.0
golang.org/x/crypto v0.50.0
golang.org/x/sync v0.20.0
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
)
require (
cloud.google.com/go/storage v1.57.2
github.com/absmach/supermq v0.19.0
cloud.google.com/go/storage v1.62.3
github.com/absmach/magistrala v0.20.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.5
github.com/go-jose/go-jose/v4 v4.1.3
github.com/go-jose/go-jose/v4 v4.1.4
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/gce-tcb-verifier v0.3.1
github.com/veraison/corim v1.1.2
github.com/veraison/go-cose v1.3.0
github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca
google.golang.org/api v0.274.0
)
require (
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 v0.123.0 // indirect
cloud.google.com/go/auth v0.19.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
cloud.google.com/go/iam v1.7.0 // indirect
cloud.google.com/go/monitoring v1.24.3 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
@@ -53,7 +56,7 @@ require (
github.com/containerd/log v0.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
@@ -65,8 +68,8 @@ require (
github.com/google/go-attestation v0.5.1 // indirect
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
github.com/googleapis/gax-go/v2 v2.21.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect
@@ -80,33 +83,31 @@ require (
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff // indirect
github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca // 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.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.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // 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
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect
gotest.tools/v3 v3.5.1 // indirect
moul.io/http2curl v1.0.0 // indirect
)
require (
github.com/absmach/certs v0.18.2
github.com/absmach/certs v0.18.5
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
@@ -117,11 +118,11 @@ require (
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc // indirect
github.com/google/go-tpm v0.9.6
github.com/google/go-tpm-tools v0.4.7
github.com/google/go-tpm v0.9.8
github.com/google/go-tpm-tools v0.4.4
github.com/google/logger v1.1.1 // indirect
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // 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
@@ -130,15 +131,15 @@ require (
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.41.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // 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
golang.org/x/net v0.53.0
golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.42.0
golang.org/x/text v0.36.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+110 -110
View File
@@ -1,43 +1,45 @@
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=
cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.19.0 h1:DGYwtbcsGsT1ywuxsIoWi1u/vlks0moIblQHgSDgQkQ=
cloud.google.com/go/auth v0.19.0/go.mod h1:2Aph7BT2KnaSFOM0JDPyiYgNh6PL9vGMiP8CUIXZ+IY=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/storage v1.57.2 h1:sVlym3cHGYhrp6XZKkKb+92I1V42ks2qKKpB0CF5Mb4=
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=
cloud.google.com/go/iam v1.7.0 h1:JD3zh0C6LHl16aCn5Akff0+GELdp1+4hmh6ndoFLl8U=
cloud.google.com/go/iam v1.7.0/go.mod h1:tetWZW1PD/m6vcuY2Zj/aU0eCHNPuxedbnbRTyKXvdY=
cloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA=
cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak=
cloud.google.com/go/longrunning v0.9.0 h1:0EzbDEGsAvOZNbqXopgniY0w0a1phvu5IdUFq8grmqY=
cloud.google.com/go/longrunning v0.9.0/go.mod h1:pkTz846W7bF4o2SzdWJ40Hu0Re+UoNT6Q5t+igIcb8E=
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
cloud.google.com/go/storage v1.62.3 h1:SZq1t23NCI+e96dH77Dg3PEfsNNEjqO8zE5AnD8gVD0=
cloud.google.com/go/storage v1.62.3/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA=
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
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=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
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.19.0 h1:sbqfzmSiMp9GEaCWgpREiLC0tFsSntgLIyAaZs7SnRY=
github.com/absmach/supermq v0.19.0/go.mod h1:SG2yIzlJmc26ZjDVSkoapc6HZ6W13SUsaN3sAErfgC4=
github.com/absmach/certs v0.18.5 h1:eYlvitou+LoDtt7ETVLTp6d/1xCejGL3EmVOg+rHGTU=
github.com/absmach/certs v0.18.5/go.mod h1:31dtVe1VYF16W+IvjAE/uPAIz4f3uLHgh+moBezjqIc=
github.com/absmach/magistrala v0.20.0 h1:3AQ0C2AMoOCc1UuJLhPNJLMrNRLZoN0ibSOERqEkM98=
github.com/absmach/magistrala v0.20.0/go.mod h1:lnuO4fSngMiRYyNYL4yz5UP8DX3bbXRm87b2KHFGwJU=
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4 h1:533pRc6R7perWDqJuZq+ofBQfYfmyj7n49V4LFY4zpo=
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4/go.mod h1:xDAX/O3VcOsHWCx2fk85VD7FI17hAUOvoOhho7DA7g0=
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=
@@ -58,7 +60,6 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/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=
@@ -67,8 +68,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
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=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
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=
@@ -81,10 +82,12 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an
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=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@@ -95,8 +98,8 @@ 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=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
@@ -130,16 +133,14 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc h1:SG12DWUUM5igxm+//YX5Yq4vhdoRnOG9HkCodkOn+YU=
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba h1:05m5+kgZjxYUZrx3bZfkKHl6wkch+Khao6N21rFHInk=
github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba/go.mod h1:7huE5P8w2NTObSwSJjboHmB7ioBNblkijdzoVa2skfQ=
github.com/google/go-sev-guest v0.14.1 h1:j/DXy9jk1qSW/dEV9vDiQnhAVFD1zqnWNVu6p1J0Jgo=
github.com/google/go-sev-guest v0.14.1/go.mod h1:SK9vW+uyfuzYdVN0m8BShL3OQCtXZe/JPF7ZkpD3760=
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 h1:+MoPobRN9HrDhGyn6HnF5NYo4uMBKaiFqAtf/D/OB4A=
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g=
github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA=
github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/go-tdx-guest v0.3.2-0.20260605221019-34f07ec666c4 h1:OX2Mksz5ZHxawvZskqYX18Xy/q292EoiyUAT4WXg/gU=
github.com/google/go-tdx-guest v0.3.2-0.20260605221019-34f07ec666c4/go.mod h1:uHy3VaNXNXhl0fiPxKqTxieeouqQmW6A0EfLcaeCYBk=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
@@ -151,30 +152,30 @@ github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl/wMbiI=
github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4=
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.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
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=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
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.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
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=
@@ -203,16 +204,16 @@ 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/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
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/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
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=
@@ -243,8 +244,8 @@ github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTC
github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -255,7 +256,6 @@ github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xI
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -276,28 +276,28 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
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.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.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -308,8 +308,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.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
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=
@@ -317,15 +317,15 @@ 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.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/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
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=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -336,42 +336,42 @@ 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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
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.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/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
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-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=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.274.0 h1:aYhycS5QQCwxHLwfEHRRLf9yNsfvp1JadKKWBE54RFA=
google.golang.org/api v0.274.0/go.mod h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
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=
+9
View File
@@ -0,0 +1,9 @@
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/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"
source "$BR2_EXTERNAL_COCOS_PATH/package/egress-proxy/Config.in"
source "$BR2_EXTERNAL_COCOS_PATH/package/ingress-proxy/Config.in"
+214
View File
@@ -0,0 +1,214 @@
# Disk Image Workflow
This directory is the Buildroot external tree for the current Cocos disk test
VM image and its runtime configuration.
## Layout
- [configs/cocos_defconfig](./configs/cocos_defconfig):
Buildroot configuration for the bootable image.
- [board/rootfs-overlay/init](./board/rootfs-overlay/init):
early initramfs script that provisions `/cocos`, mounts the real root, and
switches into the installed system.
- [board/cocos/genimage.cfg](./board/cocos/genimage.cfg):
GPT disk layout for the final `disk.img`.
- [board/cocos/post-image.sh](./board/cocos/post-image.sh):
builds the minimal initramfs, stages EFI files, signs boot artifacts, and
assembles `disk.img`.
- [external.desc](./external.desc): Buildroot external tree descriptor.
- [external.mk](./external.mk): includes package makefiles from `package/*`.
## Current Buildroot Image
The current Buildroot flow produces a bootable GPT disk image:
- `efi` partition: FAT EFI system partition with GRUB, kernel, and initramfs
- `root` partition: ext4 root filesystem protected by dm-verity
- `verity` partition: dm-verity hash tree for the root filesystem
- `cocos` partition: blank partition provisioned at boot as an encrypted ext4
filesystem mounted at `/cocos`
The final image is written to:
```bash
output/images/disk.img
```
The root filesystem image is also available separately as:
```bash
output/images/rootfs.ext4
```
## Current Boot Flow
At boot, GRUB loads:
- `bzImage`
- `initrd.cpio.gz`
The initramfs script in
[board/rootfs-overlay/init](./board/rootfs-overlay/init)
then:
1. mounts `/proc`, `/sys`, `devtmpfs`, and `devpts`
2. assumes the boot disk is `/dev/sda`
3. opens a dm-verity mapping for the root filesystem using:
- `/dev/sda2` as the data partition
- `/dev/sda3` as the verity hash partition
- `roothash=` from the kernel command line
4. mounts `/dev/mapper/root_verity` read-only at `/root`
5. generates a fresh ephemeral key
6. formats `/dev/sda4` as LUKS2
7. opens it as `/dev/mapper/cocos_crypt`
8. formats that mapper as ext4 and mounts it at `/root/cocos`
9. creates working directories on `/cocos`, including:
- `/cocos/.cache/oci`
- `/cocos/datasets`
- `/cocos/docker`
- `/cocos/cocos_init`
10. mounts `tmpfs` on `/tmp` and `/var` because the root filesystem is
intentionally read-only
11. bind-mounts `/cocos/docker` onto `/var/lib/docker`
12. bind-mounts `/cocos/cocos_init` onto `/cocos_init`
13. rewrites `/etc/fstab` in the mounted root to describe the live runtime
14. preserves or adds 9P mounts for:
- `certs_share` -> `/etc/certs`
- `env_share` -> `/etc/cocos`
15. securely wipes the temporary LUKS key file
16. runs `switch_root /root /sbin/init`
Important details:
- the root filesystem is verified through dm-verity before it is mounted
- `/cocos` is encrypted with an ephemeral per-boot key
- that key is not persisted, so `/cocos` is provisioned fresh on each boot
## Runtime Filesystem Model
The running system is split into:
- read-only root on `/`
- encrypted writable storage on `/cocos`
- `tmpfs` on `/tmp`
- `tmpfs` on `/var`
Service state that must survive within a boot session is redirected away from
the read-only root:
- Docker data lives on `/cocos/docker`
- agent setup scripts work through `/cocos_init`, which is backed by
`/cocos/cocos_init`
- algorithm datasets and results live under `/cocos`
This means services can use `/cocos` like a regular directory tree after boot,
even though it is backed by an encrypted mapper created in early userspace.
## systemd Runtime Expectations
Several services depend on files mounted from 9P shares under `/etc/certs` and
`/etc/cocos`. To avoid boot-order races, the rootfs overlay includes systemd
drop-ins under:
```bash
board/rootfs-overlay/usr/lib/systemd/system/*service.d/
```
These drop-ins require the relevant mount points before starting services such
as:
- `egress-proxy.service`
- `log-forwarder.service`
- `computation-runner.service`
- `cocos-agent.service`
The overlay also ships tmpfiles rules in
[board/rootfs-overlay/usr/lib/tmpfiles.d/cocos.conf](./board/rootfs-overlay/usr/lib/tmpfiles.d/cocos.conf)
to create:
- `/var/log/cocos`
- `/run/cocos`
## Agent Packaging In Buildroot
The Buildroot `agent` package is wired to build the binary from the local Cocos
checkout, not only from a downloaded release snapshot. The package definition is
in [package/agent/agent.mk](./package/agent/agent.mk).
That package currently:
- builds `cocos-agent` from the local source tree
- installs the local
[cocos-agent.service](../../init/systemd/cocos-agent.service)
- installs the local
[agent_setup.sh](../../init/systemd/agent_setup.sh)
- installs the local
[agent_start_script.sh](../../init/systemd/agent_start_script.sh)
So changes under:
- `cocos/agent/...`
- `cocos/init/systemd/...`
are intended to be picked up by the next Buildroot rebuild.
## Buildroot Packages And Tools
The current `cocos_defconfig` includes the components needed by the boot flow
and runtime image, including:
- systemd
- DHCP client
- `cryptsetup`
- `eudev`
- `e2fsprogs`
- Docker, containerd, and runc
- `skopeo`
- TPM2 tools
- 9P filesystem support
- GRUB2 EFI boot support
- host `genimage`
The initramfs built in `post-image.sh` is intentionally minimal and contains
only the binaries needed for early boot, dm-verity root verification, and
`/cocos` provisioning.
## Secure Boot Notes
During `post-image.sh`:
- GRUB is rebuilt with `--disable-shim-lock`
- `bootx64.efi` and `bzImage` are signed with the configured Secure Boot keys
when those keys are present
This flow is designed for booting directly through OVMF with your own enrolled
keys. It does not currently rely on booting through `shim`.
## Rebuilding
This directory is meant to be used as a Buildroot external tree. From this
directory, configure a Buildroot checkout with:
```bash
make -C /path/to/buildroot BR2_EXTERNAL=$PWD cocos_defconfig
```
Then build with:
```bash
make -C /path/to/buildroot BR2_EXTERNAL=$PWD -j$(nproc)
```
The resulting boot image is:
```bash
/path/to/buildroot/output/images/disk.img
```
Additional generated artifacts include:
```bash
/path/to/buildroot/output/images/rootfs.ext4
/path/to/buildroot/output/images/rootfs.verity
/path/to/buildroot/output/images/rootfs.roothash
```
+42
View File
@@ -0,0 +1,42 @@
image efi-part.vfat {
vfat {
file EFI {
image = "efi-part/EFI"
}
file bzImage {
image = "efi-part/bzImage"
}
file initrd.cpio.gz {
image = "efi-part/initrd.cpio.gz"
}
}
size = 256M
}
image disk.img {
hdimage {
partition-table-type = "gpt"
}
partition efi {
image = "efi-part.vfat"
partition-type-uuid = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
offset = 1M
bootable = true
}
partition root {
image = "rootfs.ext4"
partition-type-uuid = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
}
partition verity {
image = "rootfs.verity"
partition-type-uuid = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
}
partition cocos {
size = "20480M"
partition-type-uuid = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
}
}
+279
View File
@@ -0,0 +1,279 @@
###
# Architecture / base
###
CONFIG_SYSVIPC=y
CONFIG_SMP=y
CONFIG_EXPERT=y
CONFIG_LOCALVERSION_AUTO=n
###
# Modules
###
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
###
# Virtualization
###
CONFIG_HYPERVISOR_GUEST=y
CONFIG_PARAVIRT=y
CONFIG_VIRTUALIZATION=y
CONFIG_KVM=y
CONFIG_KVM_SW_PROTECTED_VM=y
CONFIG_KVM_INTEL=y
CONFIG_VIRT_DRIVERS=y
###
# Cgroups — base + Docker/container subsystems
###
CONFIG_CGROUPS=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_MISC=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_BPF=y
CONFIG_CGROUP_NET_PRIO=y
CONFIG_CGROUP_NET_CLASSID=y
CONFIG_CPUSETS=y
CONFIG_MEMCG=y
CONFIG_BLK_CGROUP=y
###
# Namespaces — required by containerd / runc
###
CONFIG_NAMESPACES=y
CONFIG_UTS_NS=y
CONFIG_IPC_NS=y
CONFIG_USER_NS=y
CONFIG_PID_NS=y
CONFIG_NET_NS=y
###
# PCI
###
CONFIG_PCI=y
CONFIG_PCI_MSI=y
CONFIG_IRQ_REMAP=y
###
# Initramfs
###
CONFIG_BLK_DEV_INITRD=y
CONFIG_RD_GZIP=y
###
# Block devices
###
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_BLK_DEV_SD=y
CONFIG_SCSI_VIRTIO=y
CONFIG_ATA=y
CONFIG_ATA_PIIX=y
CONFIG_VIRTIO_BLK=y
# Loop device (used by containerd image mounts)
CONFIG_BLK_DEV_LOOP=y
CONFIG_BLK_DEV_LOOP_MIN_COUNT=8
###
# Device mapper — FDE, dm-verity, dm-crypt, dm-integrity
# These must be built-in (y) because they are needed before the
# rootfs is mounted, during the initramfs FDE init stage.
###
CONFIG_MD=y
CONFIG_BLK_DEV_DM_BUILTIN=y
CONFIG_BLK_DEV_DM=y
CONFIG_DM_CRYPT=y
CONFIG_DM_VERITY=y
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
# CONFIG_DM_VERITY_FEC is not set
CONFIG_DM_INTEGRITY=y
CONFIG_DM_INIT=y
###
# Networking — base
###
CONFIG_NET=y
CONFIG_PACKET=y
CONFIG_UNIX=y
CONFIG_INET=y
# CONFIG_WIRELESS is not set
CONFIG_NETDEVICES=y
CONFIG_VIRTIO_NET=y
CONFIG_NE2K_PCI=y
CONFIG_8139CP=y
# CONFIG_WLAN is not set
CONFIG_VSOCKETS=y
CONFIG_VIRTIO_VSOCKETS=y
# Virtual Ethernet pairs and bridge (Docker networking)
CONFIG_VETH=m
CONFIG_BRIDGE=m
CONFIG_BRIDGE_NETFILTER=m
###
# Netfilter — Docker NAT, iptables, conntrack (modules, loaded on demand)
###
CONFIG_NETFILTER=y
CONFIG_NETFILTER_ADVANCED=y
CONFIG_NF_CONNTRACK=m
CONFIG_NF_CONNTRACK_MARK=y
CONFIG_NF_NAT=m
CONFIG_NF_NAT_MASQUERADE=y
CONFIG_NF_TABLES=y
CONFIG_IP_NF_IPTABLES=m
CONFIG_IP_NF_FILTER=m
CONFIG_IP_NF_TARGET_MASQUERADE=m
CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
###
# BPF
###
CONFIG_BPF_SYSCALL=y
###
# Filesystems
###
CONFIG_EXT4_FS=y
CONFIG_OVERLAY_FS=y
CONFIG_AUTOFS4_FS=y
CONFIG_TMPFS=y
CONFIG_TMPFS_POSIX_ACL=y
CONFIG_PROC_FS=y
CONFIG_SYSFS=y
###
# 9P filesystem (virtio shares for certs and env)
###
CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y
CONFIG_9P_FS=y
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_9P_FS_SECURITY=y
###
# Virtio devices
###
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BALLOON=y
CONFIG_VIRTIO_INPUT=y
CONFIG_VIRTIO_CONSOLE=y
CONFIG_VIRTIO_MMIO=y
CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
CONFIG_HW_RANDOM_VIRTIO=m
###
# Console / Input
###
CONFIG_INPUT_EVDEV=y
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
###
# Kernel features required by systemd
###
CONFIG_FHANDLE=y
CONFIG_INOTIFY_USER=y
CONFIG_SIGNALFD=y
CONFIG_TIMERFD=y
CONFIG_EPOLL=y
CONFIG_POSIX_MQUEUE=y
CONFIG_POSIX_MQUEUE_SYSCTL=y
CONFIG_UNWINDER_FRAME_POINTER=y
###
# Security
###
CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y
CONFIG_SECURITY=y
CONFIG_SECURITYFS=y
###
# EFI
###
CONFIG_EFI=y
CONFIG_EFI_STUB=y
###
# AMD SEV-SNP
###
CONFIG_AMD_MEM_ENCRYPT=y
CONFIG_AMD_MEM_ENCRYPT_ACTIVE_BY_DEFAULT=n
CONFIG_SEV_GUEST=y
CONFIG_IOMMU_DEFAULT_PASSTHROUGH=n
###
# Intel TDX
###
CONFIG_X86_X2APIC=y
CONFIG_X86_CPUID=y
CONFIG_X86_SGX=y
CONFIG_X86_SGX_KVM=y
CONFIG_INTEL_TDX_GUEST=y
CONFIG_TDX_GUEST_DRIVER=y
###
# Preemption (disabled for VM performance)
###
CONFIG_PREEMPT_COUNT=n
CONFIG_PREEMPT=n
CONFIG_PREEMPT_DYNAMIC=n
CONFIG_DEBUG_PREEMPT=n
###
# Key/signature management
###
CONFIG_SYSTEM_TRUSTED_KEYS=n
CONFIG_SYSTEM_REVOCATION_KEYS=n
CONFIG_MODULE_SIG_KEY=n
CONFIG_KEYS=y
CONFIG_ENCRYPTED_KEYS=y
###
# Crypto — AES-GCM (LUKS2 cipher) + SHA-256 (dm-verity hash)
###
CONFIG_CRYPTO_AES=y
CONFIG_CRYPTO_SHA256=y
CONFIG_CRYPTO_GCM=y
CONFIG_CRYPTO_GHASH=y
CONFIG_CRYPTO_SEQIV=y
CONFIG_CRYPTO_ECHAINIV=y
CONFIG_CRYPTO_XTS=y
CONFIG_CRYPTO_CBC=y
CONFIG_CRYPTO_AUTHENC=y
CONFIG_CRYPTO_ESSIV=y
CONFIG_CRYPTO_USER_API=y
CONFIG_CRYPTO_USER_API_HASH=y
CONFIG_CRYPTO_USER_API_SKCIPHER=y
CONFIG_CRYPTO_USER_API_AEAD=y
CONFIG_CRYPTO_AES_NI_INTEL=m
CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL=m
###
# TPM
###
CONFIG_TCG_TPM=y
CONFIG_TCG_TPM2_HMAC=y
CONFIG_TCG_PLATFORM=y
###
# IMA (Linux Integrity Measurement Architecture)
###
CONFIG_INTEGRITY=y
CONFIG_INTEGRITY_SIGNATURE=y
CONFIG_IMA=y
CONFIG_IMA_MEASURE_PCR_IDX=10
CONFIG_IMA_LSM_RULES=y
CONFIG_IMA_APPRAISE=y
CONFIG_IMA_DEFAULT_TEMPLATE="ima-ng"
CONFIG_IMA_DEFAULT_HASH="sha256"
###
# Disabled options
###
CONFIG_KSM=n
CONFIG_EISA=n
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
set -u
set -e
# Add a console on tty1
if [ -e ${TARGET_DIR}/etc/inittab ]; then
grep -qE '^tty1::' ${TARGET_DIR}/etc/inittab || \
sed -i '/GENERIC_SERIAL/a\
tty1::respawn:/sbin/getty -L tty1 0 vt100 # QEMU graphical window' ${TARGET_DIR}/etc/inittab
fi
+273
View File
@@ -0,0 +1,273 @@
#!/bin/bash
COCOS_BOARD_DIR="$(dirname "$0")"
DEFCONFIG_NAME="$(basename "$2")"
README_FILES="${COCOS_BOARD_DIR}/readme.txt"
START_QEMU_SCRIPT="${BINARIES_DIR}/start-qemu.sh"
# ---------------------------------------------------------------------------
# Build a minimal FDE initramfs (rootfs.cpio.gz) containing only the tools
# needed to mount the root partition read-only, provision LUKS2, and switch_root.
# All other packages live
# on the ext4 disk image and are available after switch_root.
# ---------------------------------------------------------------------------
echo "[post-image] Building minimal FDE initramfs..."
INITRAMFS_STAGE="${BUILD_DIR}/initramfs-staging"
rm -rf "${INITRAMFS_STAGE}"
# Merged-usr layout: bin/sbin/lib/lib64 are symlinks into usr/, matching the
# Buildroot target layout so that hardcoded ELF interpreter paths (ld-linux)
# and the #!/bin/sh shebang both resolve correctly inside the initramfs.
mkdir -p "${INITRAMFS_STAGE}/usr/bin" \
"${INITRAMFS_STAGE}/usr/sbin" \
"${INITRAMFS_STAGE}/usr/lib" \
"${INITRAMFS_STAGE}/dev" \
"${INITRAMFS_STAGE}/proc" \
"${INITRAMFS_STAGE}/sys" \
"${INITRAMFS_STAGE}/tmp" \
"${INITRAMFS_STAGE}/run" \
"${INITRAMFS_STAGE}/root" \
"${INITRAMFS_STAGE}/etc/udev/rules.d"
ln -s usr/bin "${INITRAMFS_STAGE}/bin"
ln -s usr/sbin "${INITRAMFS_STAGE}/sbin"
ln -s usr/lib "${INITRAMFS_STAGE}/lib"
ln -s usr/lib "${INITRAMFS_STAGE}/lib64"
# init script (PID 1)
install -m 0755 "${BR2_EXTERNAL_COCOS_PATH}/board/rootfs-overlay/init" \
"${INITRAMFS_STAGE}/init"
# Binaries required by the init script
FDE_BINS="
bash
cryptsetup
veritysetup
mkfs.ext4
mount
umount
losetup
switch_root
dd
shred
tr
cut
grep
awk
cat
ls
cp
mkdir
readlink
dirname
lsblk
udevadm
blkid
rm
"
for BIN in ${FDE_BINS}; do
SRC="$(find "${TARGET_DIR}/usr/bin" "${TARGET_DIR}/usr/sbin" \
"${TARGET_DIR}/bin" "${TARGET_DIR}/sbin" \
-name "${BIN}" \( -type f -o -type l \) 2>/dev/null | head -1)"
if [ -n "${SRC}" ]; then
cp -P "${SRC}" "${INITRAMFS_STAGE}/usr/bin/${BIN}"
chmod 0755 "${INITRAMFS_STAGE}/usr/bin/${BIN}" 2>/dev/null || true
# If this is a symlink, also copy the resolved target binary (e.g. busybox, coreutils, mke2fs)
# so that other applet symlinks pointing to the same target also work at runtime.
if [ -L "${SRC}" ]; then
REAL_SRC="$(readlink -f "${SRC}")"
REAL_NAME="$(basename "${REAL_SRC}")"
if [ -f "${REAL_SRC}" ] && [ ! -e "${INITRAMFS_STAGE}/usr/bin/${REAL_NAME}" ]; then
cp "${REAL_SRC}" "${INITRAMFS_STAGE}/usr/bin/${REAL_NAME}"
chmod 0755 "${INITRAMFS_STAGE}/usr/bin/${REAL_NAME}" 2>/dev/null || true
fi
fi
else
echo "[post-image] WARNING: ${BIN} not found in target, skipping"
fi
done
# sh symlink so #!/bin/sh in the init script resolves correctly
ln -sf bash "${INITRAMFS_STAGE}/usr/bin/sh"
# Shared libraries from usr/lib (TARGET_DIR uses merged-usr so lib → usr/lib)
# Skip large runtimes that are only needed on the real root.
find "${TARGET_DIR}/usr/lib" \( \
-path "*/python3*" -o \
-path "*/gcc*" -o \
-path "*/wasmedge*" \
\) -prune -o \
\( -name "*.so" -o -name "*.so.*" \) -print | while read -r LIB; do
REL="${LIB#${TARGET_DIR}/usr/lib/}"
DEST="${INITRAMFS_STAGE}/usr/lib/${REL}"
mkdir -p "$(dirname "${DEST}")"
cp -P "${LIB}" "${DEST}"
done
# udev rules (needed for udevadm settle)
if [ -d "${TARGET_DIR}/etc/udev" ]; then
cp -a "${TARGET_DIR}/etc/udev/." "${INITRAMFS_STAGE}/etc/udev/"
fi
# /dev seed nodes
mknod -m 0600 "${INITRAMFS_STAGE}/dev/console" c 5 1 2>/dev/null || true
mknod -m 0666 "${INITRAMFS_STAGE}/dev/null" c 1 3 2>/dev/null || true
echo "[post-image] Packing initramfs..."
( cd "${INITRAMFS_STAGE}" && \
find . | cpio --quiet -o -H newc -R 0:0 | gzip -9 \
> "${BINARIES_DIR}/rootfs.cpio.gz" )
echo "[post-image] rootfs.cpio.gz: $(du -sh "${BINARIES_DIR}/rootfs.cpio.gz" | cut -f1)"
ROOTFS_IMAGE="${BINARIES_DIR}/rootfs.ext4"
VERITY_IMAGE="${BINARIES_DIR}/rootfs.verity"
ROOT_HASH_FILE="${BINARIES_DIR}/rootfs.roothash"
VERITYSETUP_BIN="${HOST_DIR}/bin/veritysetup"
if [ ! -x "${VERITYSETUP_BIN}" ]; then
VERITYSETUP_BIN="${HOST_DIR}/sbin/veritysetup"
fi
if [ ! -x "${VERITYSETUP_BIN}" ]; then
echo "[post-image] FATAL: host veritysetup not found at ${VERITYSETUP_BIN}"
exit 1
fi
echo "[post-image] Building dm-verity hash image..."
rm -f "${VERITY_IMAGE}" "${ROOT_HASH_FILE}"
truncate -s 256M "${VERITY_IMAGE}"
VERITY_FORMAT_OUTPUT="$("${VERITYSETUP_BIN}" format "${ROOTFS_IMAGE}" "${VERITY_IMAGE}")" || {
echo "[post-image] FATAL: veritysetup format failed"
exit 1
}
ROOT_HASH="$(printf '%s\n' "${VERITY_FORMAT_OUTPUT}" | awk -F': ' '/^Root hash:/ {print $2}' | tr -d '[:space:]')"
if [ -z "${ROOT_HASH}" ]; then
echo "[post-image] FATAL: failed to parse dm-verity root hash"
printf '%s\n' "${VERITY_FORMAT_OUTPUT}"
exit 1
fi
printf '%s\n' "${ROOT_HASH}" > "${ROOT_HASH_FILE}"
echo "[post-image] dm-verity root hash: ${ROOT_HASH}"
# Stage kernel and initramfs for the EFI partition.
# Buildroot's GRUB2 package has already placed bootx64.efi at
# ${BINARIES_DIR}/efi-part/EFI/BOOT/bootx64.efi; we add the kernel,
# initramfs, and overwrite the default grub.cfg with our boot entry.
echo "[post-image] Staging EFI partition files..."
mkdir -p "${BINARIES_DIR}/efi-part/EFI/BOOT"
cp "${BINARIES_DIR}/bzImage" "${BINARIES_DIR}/efi-part/bzImage"
cp "${BINARIES_DIR}/rootfs.cpio.gz" "${BINARIES_DIR}/efi-part/initrd.cpio.gz"
cat > "${BINARIES_DIR}/efi-part/EFI/BOOT/grub.cfg" << GRUBCFG
set default=0
set timeout=0
menuentry "Cocos" {
linux /bzImage console=ttyS0 roothash=${ROOT_HASH} systemd.verity=0 systemd.gpt_auto=0
initrd /initrd.cpio.gz
}
GRUBCFG
# Regenerate bootx64.efi with --disable-shim-lock so GRUB can load the kernel
# directly without requiring the shim bootloader (OVMF still verifies GRUB via
# Secure Boot; shim is not needed when booting from a custom OVMF with own DB key).
GRUB_CORE="$(ls -d "${BUILD_DIR}"/grub2-*/build-x86_64-efi/grub-core 2>/dev/null | head -1)"
if [ -n "${GRUB_CORE}" ]; then
echo "[post-image] Regenerating bootx64.efi with --disable-shim-lock..."
"${HOST_DIR}/bin/grub-mkimage" \
-d "${GRUB_CORE}" \
-O x86_64-efi \
-o "${BINARIES_DIR}/efi-part/EFI/BOOT/bootx64.efi" \
-p "/EFI/BOOT" \
--disable-shim-lock \
boot linux echo normal part_gpt fat ls search || {
echo "[post-image] FATAL: grub-mkimage failed"
exit 1
}
else
echo "[post-image] WARNING: GRUB core dir not found, skipping --disable-shim-lock rebuild"
fi
# Sign GRUB and kernel for UEFI Secure Boot.
# Keys are resolved in order: env var → board/secure-boot/ defaults.
SB_KEY="${SB_KEY:-${COCOS_BOARD_DIR}/secure-boot/db.key}"
SB_CERT="${SB_CERT:-${COCOS_BOARD_DIR}/secure-boot/db.crt}"
if [ -f "${SB_KEY}" ] && [ -f "${SB_CERT}" ]; then
echo "[post-image] Signing EFI binaries for Secure Boot..."
sbsign --key "${SB_KEY}" --cert "${SB_CERT}" \
--output "${BINARIES_DIR}/efi-part/EFI/BOOT/bootx64.efi" \
"${BINARIES_DIR}/efi-part/EFI/BOOT/bootx64.efi" || {
echo "[post-image] FATAL: Failed to sign bootx64.efi"
exit 1
}
sbsign --key "${SB_KEY}" --cert "${SB_CERT}" \
--output "${BINARIES_DIR}/efi-part/bzImage" \
"${BINARIES_DIR}/efi-part/bzImage" || {
echo "[post-image] FATAL: Failed to sign bzImage"
exit 1
}
echo "[post-image] Secure Boot signing complete"
else
echo "[post-image] WARNING: Secure Boot keys not found — EFI binaries are unsigned"
echo "[post-image] Default location: ${COCOS_BOARD_DIR}/secure-boot/db.key + db.crt"
echo "[post-image] Override: SB_KEY=/path/to/db.key SB_CERT=/path/to/db.crt make"
fi
GENIMAGE_CFG="${COCOS_BOARD_DIR}/genimage.cfg"
if [ -f "${GENIMAGE_CFG}" ]; then
GENIMAGE_TMP="${BUILD_DIR}/genimage.tmp"
rm -rf "${GENIMAGE_TMP}"
genimage \
--rootpath "${TARGET_DIR}" \
--tmppath "${GENIMAGE_TMP}" \
--inputpath "${BINARIES_DIR}" \
--outputpath "${BINARIES_DIR}" \
--config "${GENIMAGE_CFG}"
fi
if [[ "${DEFCONFIG_NAME}" =~ ^"cocos_*" ]]; then
# Not a Qemu defconfig, can't test.
exit 0
fi
# Search for "# qemu_*_defconfig" tag in all readme.txt files.
# Qemu command line on multilines using back slash are accepted.
# shellcheck disable=SC2086 # glob over each readme file
QEMU_CMD_LINE="$(sed -r ':a; /\\$/N; s/\\\n//; s/\t/ /; ta; /# '"${DEFCONFIG_NAME}"'$/!d; s/#.*//' ${README_FILES})"
if [ -z "${QEMU_CMD_LINE}" ]; then
# No Qemu cmd line found, can't test.
exit 0
fi
# Remove output/images path since the script will be in
# the same directory as the kernel and the rootfs images.
QEMU_CMD_LINE="${QEMU_CMD_LINE//output\/images\//}"
# Remove -serial stdio if present, keep it as default args
DEFAULT_ARGS="$(sed -r -e '/-serial stdio/!d; s/.*(-serial stdio).*/\1/' <<<"${QEMU_CMD_LINE}")"
QEMU_CMD_LINE="${QEMU_CMD_LINE//-serial stdio/}"
# Remove any string before qemu-system-*
QEMU_CMD_LINE="$(sed -r -e 's/^.*(qemu-system-)/\1/' <<<"${QEMU_CMD_LINE}")"
# Disable graphical output and redirect serial I/Os to console
case ${DEFCONFIG_NAME} in
(qemu_sh4eb_r2d_defconfig|qemu_sh4_r2d_defconfig)
# Special case for SH4
SERIAL_ARGS="-serial stdio -display none"
;;
(*)
SERIAL_ARGS="-nographic"
;;
esac
sed -e "s|@SERIAL_ARGS@|${SERIAL_ARGS}|g" \
-e "s|@DEFAULT_ARGS@|${DEFAULT_ARGS}|g" \
-e "s|@QEMU_CMD_LINE@|${QEMU_CMD_LINE}|g" \
-e "s|@HOST_DIR@|${HOST_DIR}|g" \
<"${COCOS_BOARD_DIR}/start-qemu.sh.in" \
>"${START_QEMU_SCRIPT}"
chmod +x "${START_QEMU_SCRIPT}"
+7
View File
@@ -0,0 +1,7 @@
Run the emulation with:
qemu-system-x86_64 -M pc -kernel output/images/bzImage -drive file=output/images/rootfs.ext2,if=virtio,format=raw -append "rootwait root=/dev/vda console=tty1 console=ttyS0" -serial stdio -net nic,model=virtio -net user # cocos_defconfig
Optionally add -smp N to emulate a SMP system with N CPUs.
The login prompt will appear in the graphical window.
@@ -0,0 +1,3 @@
# Private key must not be committed
db.key
db.crt
+28
View File
@@ -0,0 +1,28 @@
#!/bin/sh
BINARIES_DIR="${0%/*}/"
# shellcheck disable=SC2164
cd "${BINARIES_DIR}"
mode_serial=false
mode_sys_qemu=false
while [ "$1" ]; do
case "$1" in
--serial-only|serial-only) mode_serial=true; shift;;
--use-system-qemu) mode_sys_qemu=true; shift;;
--) shift; break;;
*) echo "unknown option: $1" >&2; exit 1;;
esac
done
if ${mode_serial}; then
EXTRA_ARGS='@SERIAL_ARGS@'
else
EXTRA_ARGS='@DEFAULT_ARGS@'
fi
if ! ${mode_sys_qemu}; then
export PATH="@HOST_DIR@/bin:${PATH}"
fi
exec @QEMU_CMD_LINE@ ${EXTRA_ARGS} "$@"
+7
View File
@@ -0,0 +1,7 @@
# Root is mounted read-only by the initramfs through dm-verity.
# /cocos, /var, /tmp, and bind mounts are set up by the initramfs init script.
/dev/mapper/root_verity / ext4 ro,defaults 0 0
# 9P virtio shares — provided by the hypervisor, optional (nofail)
certs_share /etc/certs 9p trans=virtio,version=9p2000.L,cache=mmap,nofail 0 0
env_share /etc/cocos 9p trans=virtio,version=9p2000.L,cache=mmap,nofail 0 0
@@ -0,0 +1,7 @@
{
"key-providers": {
"attestation-agent": {
"grpc": "127.0.0.1:50011"
}
}
}
+265
View File
@@ -0,0 +1,265 @@
#!/bin/sh
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
if (exec 0</dev/console) 2>/dev/null; then
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
fi
echo "Welcome to the Cocos FDE test VM initramfs!"
echo "This is a minimal initramfs environment used for testing the FDE provisioning flow."
echo "If you see this message, the initramfs was loaded and executed successfully."
echo "The initramfs will now attempt to provision the disk and mount the real root filesystem."
echo "If any step fails, it will drop to a shell for debugging."
[ -d /dev ] || mkdir -m 0755 /dev
[ -d /etc ] || mkdir -m 0755 /etc
[ -d /root ] || mkdir -m 0700 /root
[ -d /run ] || mkdir -m 0755 /run
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
if [ -L /etc/resolv.conf ]; then
RESOLV_TARGET="$(readlink /etc/resolv.conf)"
case "$RESOLV_TARGET" in
/*)
RESOLV_PATH="$RESOLV_TARGET"
;;
*)
RESOLV_PATH="/etc/$RESOLV_TARGET"
;;
esac
mkdir -p "$(dirname "$RESOLV_PATH")"
[ -e "$RESOLV_PATH" ] || : > "$RESOLV_PATH"
else
[ -e /etc/resolv.conf ] || : > /etc/resolv.conf
fi
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t proc -o nodev,noexec,nosuid proc /proc
mkdir -p /sys/kernel/config
if ! grep -q ' /sys/kernel/config ' /proc/mounts; then
mount -t configfs configfs /sys/kernel/config 2>/dev/null || true
fi
mount -t devtmpfs -o nosuid,mode=0755 udev /dev
mkdir /dev/pts
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true
MNT_DIR=/root
BASE=$(pwd)
DST=/dev/sda
ROOTFS_TYPE="ext4"
ROOT_VERITY_MAP=root_verity
ROOT_VERITY_MAPPER="/dev/mapper/$ROOT_VERITY_MAP"
COCOS_MOUNT=/cocos
COCOS_MAP=cocos_crypt
COCOS_MAPPER="/dev/mapper/$COCOS_MAP"
LUKS_PARAMS="--cipher aes-gcm-random --integrity aead"
settle_devices() {
echo "[init] Waiting for devices to settle..."
if command -v udevadm >/dev/null 2>&1; then
udevadm settle --timeout=10 || sleep 2
else
sleep 2
fi
}
wipe_file() {
file_path="$1"
if [ -z "$file_path" ] || [ ! -e "$file_path" ]; then
return 0
fi
shred -vfz -n 3 "$file_path" 2>/dev/null || dd if=/dev/zero of="$file_path" bs=64 count=1
rm -f "$file_path"
}
partition_path() {
disk="$1"
partition="$2"
case "$disk" in
*[0-9])
printf '%sp%s\n' "$disk" "$partition"
;;
*)
printf '%s%s\n' "$disk" "$partition"
;;
esac
}
append_9p_entry() {
pattern="$1"
default_entry="$2"
existing_entry=""
if [ -f "$FSTAB_BAK" ]; then
existing_entry="$(grep -E "$pattern" "$FSTAB_BAK" | head -n 1 || true)"
fi
if [ -n "$existing_entry" ]; then
printf '%s\n' "$existing_entry" >> "$FSTAB"
else
printf '%s\n' "$default_entry" >> "$FSTAB"
fi
}
cmdline_arg() {
key="$1"
for arg in $(cat /proc/cmdline); do
case "$arg" in
"$key="*)
printf '%s\n' "${arg#*=}"
return 0
;;
esac
done
return 1
}
echo "[init] Starting disk provisioning..."
ROOT_PART="$(partition_path "$DST" 2)"
VERITY_PART="$(partition_path "$DST" 3)"
COCOS_PART="$(partition_path "$DST" 4)"
ROOT_HASH="$(cmdline_arg roothash)"
if [ -z "$ROOT_HASH" ]; then
echo "[init] FATAL: Missing roothash= on kernel command line"
exec /bin/sh
fi
settle_devices
for part in "$ROOT_PART" "$VERITY_PART" "$COCOS_PART"; do
if [ ! -b "$part" ]; then
echo "[init] FATAL: Could not find partition $part"
echo "[init] Available block devices:"
lsblk || ls -la /dev/ || true
echo "[init] Dropping to shell."
exec /bin/sh
fi
done
echo "[init] Opening dm-verity root mapping..."
veritysetup open "$ROOT_PART" "$ROOT_VERITY_MAP" "$VERITY_PART" "$ROOT_HASH" || {
echo "[init] FATAL: Failed to open dm-verity mapping for root"
exec /bin/sh
}
echo "[init] Mounting root at $MNT_DIR (read-only)..."
mount -o ro -t "$ROOTFS_TYPE" "$ROOT_VERITY_MAPPER" "$MNT_DIR" || {
echo "[init] FATAL: Failed to mount verity root"
veritysetup close "$ROOT_VERITY_MAP" 2>/dev/null || true
exec /bin/sh
}
echo "[init] Generating ephemeral key for $COCOS_MOUNT..."
dd if=/dev/urandom of=kk.bin bs=64 count=1 || {
echo "[init] FATAL: Failed to generate encryption key"
umount "$MNT_DIR" 2>/dev/null || true
exec /bin/sh
}
KK_BIN=$BASE/kk.bin
cryptsetup luksFormat "$COCOS_PART" --type luks2 $LUKS_PARAMS --key-file="$KK_BIN" -q || {
echo "[init] FATAL: LUKS format failed"
umount "$MNT_DIR" 2>/dev/null || true
veritysetup close "$ROOT_VERITY_MAP" 2>/dev/null || true
wipe_file "$KK_BIN"
exec /bin/sh
}
cryptsetup open "$COCOS_PART" "$COCOS_MAP" --key-file="$KK_BIN" || {
echo "[init] FATAL: Failed to open LUKS container for $COCOS_MOUNT"
umount "$MNT_DIR" 2>/dev/null || true
veritysetup close "$ROOT_VERITY_MAP" 2>/dev/null || true
wipe_file "$KK_BIN"
exec /bin/sh
}
mkfs.ext4 -F -m 0 "$COCOS_MAPPER" >/dev/null || {
echo "[init] FATAL: Failed to create ext4 filesystem for $COCOS_MOUNT"
cryptsetup close "$COCOS_MAP" 2>/dev/null || true
umount "$MNT_DIR" 2>/dev/null || true
veritysetup close "$ROOT_VERITY_MAP" 2>/dev/null || true
wipe_file "$KK_BIN"
exec /bin/sh
}
echo "[init] Mounting encrypted $COCOS_MOUNT..."
mkdir -p "$MNT_DIR$COCOS_MOUNT"
mount -t ext4 "$COCOS_MAPPER" "$MNT_DIR$COCOS_MOUNT" || {
echo "[init] FATAL: Failed to mount encrypted $COCOS_MOUNT filesystem"
cryptsetup close "$COCOS_MAP" 2>/dev/null || true
umount "$MNT_DIR" 2>/dev/null || true
veritysetup close "$ROOT_VERITY_MAP" 2>/dev/null || true
wipe_file "$KK_BIN"
exec /bin/sh
}
mkdir -p \
"$MNT_DIR$COCOS_MOUNT/.cache/oci" \
"$MNT_DIR$COCOS_MOUNT/datasets" \
"$MNT_DIR$COCOS_MOUNT/docker" \
"$MNT_DIR$COCOS_MOUNT/cocos_init"
# The root is read-only; provide tmpfs for writable system directories.
mount -t tmpfs tmpfs "$MNT_DIR/tmp"
mount -t tmpfs -o mode=0755 tmpfs "$MNT_DIR/var"
# Bind Docker's data root onto /cocos so large images don't exhaust RAM.
mkdir -p "$MNT_DIR/var/lib/docker"
mount --bind "$MNT_DIR$COCOS_MOUNT/docker" "$MNT_DIR/var/lib/docker"
# /cocos_init is on the read-only root; shadow it with a writable
# copy on /cocos so agent setup scripts can write state alongside the scripts.
if [ -d "$MNT_DIR/cocos_init" ]; then
cp -a "$MNT_DIR/cocos_init/." "$MNT_DIR$COCOS_MOUNT/cocos_init/" 2>/dev/null || true
mount --bind "$MNT_DIR$COCOS_MOUNT/cocos_init" "$MNT_DIR/cocos_init" || true
fi
mount --move /proc $MNT_DIR/proc
mount --move /sys $MNT_DIR/sys
FSTAB="$MNT_DIR/etc/fstab"
FSTAB_BAK="$MNT_DIR/etc/fstab.bak"
mkdir -p "$MNT_DIR/etc/certs" "$MNT_DIR/etc/cocos" 2>/dev/null || true
if [ -f "$FSTAB" ]; then
mv "$FSTAB" "$FSTAB_BAK"
fi
cat > "$FSTAB" << EOF
# Generated by init script
$ROOT_VERITY_MAPPER / $ROOTFS_TYPE ro,defaults 0 0
EOF
append_9p_entry \
'^certs_share[[:space:]]+/etc/certs[[:space:]]+9p([[:space:]]|$)' \
'certs_share /etc/certs 9p trans=virtio,version=9p2000.L,cache=mmap,nofail 0 0'
append_9p_entry \
'^env_share[[:space:]]+/etc/cocos[[:space:]]+9p([[:space:]]|$)' \
'env_share /etc/cocos 9p trans=virtio,version=9p2000.L,cache=mmap,nofail 0 0'
printf '%s\n' '# /cocos is mounted by the FDE initramfs using an ephemeral LUKS key.' >> "$FSTAB"
# Securely wipe the encryption key before switching root.
echo "[init] Securely wiping the $COCOS_MOUNT encryption key..."
wipe_file "$KK_BIN"
echo "[init] Switching to real root..."
exec switch_root $MNT_DIR/ /sbin/init
# If switch_root somehow returns:
echo "[init] switch_root failed, dropping to shell"
exec /bin/sh
@@ -0,0 +1,3 @@
[Unit]
RequiresMountsFor=/etc/cocos /etc/certs
@@ -0,0 +1,3 @@
[Unit]
RequiresMountsFor=/etc/cocos
@@ -0,0 +1,3 @@
[Unit]
RequiresMountsFor=/etc/cocos
@@ -0,0 +1,3 @@
[Unit]
RequiresMountsFor=/etc/cocos
@@ -0,0 +1,2 @@
d /var/log/cocos 0755 root root -
d /run/cocos 0755 root root -
+117
View File
@@ -0,0 +1,117 @@
# Architecture
BR2_x86_64=y
# System
BR2_TARGET_GENERIC_HOSTNAME="cocos"
BR2_TARGET_GENERIC_ISSUE="Welcome to Cocos"
BR2_PACKAGE_DHCP=y
BR2_PACKAGE_DHCP_CLIENT=y
BR2_INIT_SYSTEMD=y
BR2_SYSTEM_BIN_SH_BASH=y
# Filesystem
# BR2_TARGET_ROOTFS_TAR is not set
# Initramfs (rootfs.cpio.gz) is built by post-image.sh from only the FDE tools,
# not from the full target rootfs. The full rootfs goes to rootfs.ext4 (disk image).
BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_COCOS_PATH)/board/rootfs-overlay"
# Patches for existing Buildroot packages
BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_COCOS_PATH)/patches"
# Bootloader
BR2_TARGET_GRUB2=y
BR2_TARGET_GRUB2_X86_64_EFI=y
BR2_TARGET_GRUB2_BUILTIN_MODULES_EFI="boot linux echo normal part_gpt fat ls search"
# Disk image
BR2_TARGET_ROOTFS_EXT2=y
BR2_TARGET_ROOTFS_EXT2_4=y
BR2_TARGET_ROOTFS_EXT2_SIZE="10G"
BR2_PACKAGE_HOST_GENIMAGE=y
BR2_PACKAGE_HOST_CRYPTSETUP=y
# Image
BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL_COCOS_PATH)/board/cocos/post-build.sh"
# Image
BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_COCOS_PATH)/board/cocos/post-image.sh"
BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)"
# Linux headers same as kernel
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_11=y
BR2_TOOLCHAIN_HEADERS_LATEST=y
BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.11-rc7"
# Kernel
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_GIT=y
BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://github.com/coconut-svsm/linux.git"
BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="svsm"
BR2_LINUX_KERNEL_VERSION="svsm"
BR2_LINUX_KERNEL_PATCH=""
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_COCOS_PATH)/board/cocos/linux.config"
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
# host-qemu for gitlab testing
BR2_PACKAGE_HOST_QEMU=y
BR2_PACKAGE_HOST_QEMU_SYSTEM_MODE=y
# Python
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_PYTHON_PIP=y
BR2_PACKAGE_BZIP2=y
BR2_PACKAGE_XZ=y
BR2_PACKAGE_ZIP=y
BR2_PACKAGE_PYTHON3_ZLIB=y
BR2_PACKAGE_PYTHON3_XZ=y
BR2_PACKAGE_PYTHON3_BZIP2=y
BR2_INSTALL_LIBSTDCPP=y
BR2_TOOLCHAIN_BUILDROOT_CXX=y
BR2_PACKAGE_HOST_GCC_TARGET=y
BR2_TOOLCHAIN_BUILDROOT_LIBSTDCPP=y
BR2_PACKAGE_GCC=y
BR2_PACKAGE_GCC_TARGET=y
BR2_PACKAGE_LIBSTDCPP=y
# FDE
BR2_PACKAGE_NBD=y
BR2_PACKAGE_NBD_CLIENT=y
BR2_PACKAGE_CRYPTSETUP=y
BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y
BR2_PACKAGE_EUDEV=y
BR2_PACKAGE_HAS_UDEV=y
BR2_PACKAGE_MULTIPATH_TOOLS=y
BR2_PACKAGE_UTIL_LINUX_BINARIES=y
BR2_PACKAGE_E2FSPROGS=y
BR2_LINUX_KERNEL_NEEDS_HOST_PAHOLE=y
# TPM2
BR2_PACKAGE_TPM2_TOOLS=y
BR2_PACKAGE_COREUTILS=y
# Docker
BR2_PACKAGE_LIBSECCOMP_ARCH_SUPPORTS=y
BR2_PACKAGE_LIBSECCOMP=y
BR2_PACKAGE_CA_CERTIFICATES=y
BR2_PACKAGE_DOCKER_CLI=y
BR2_PACKAGE_DOCKER_COMPOSE=y
BR2_PACKAGE_DOCKER_ENGINE=y
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
BR2_PACKAGE_9PFS=y
# Host tools
BR2_PACKAGE_HOST_RUSTC=y
BR2_PACKAGE_HOST_RUST_BIN=y
# Cocos AI Packages
BR2_PACKAGE_AGENT=y
# BR2_PACKAGE_CC_ATTESTATION_AGENT is not set
+2
View File
@@ -0,0 +1,2 @@
name: COCOS
desc: External buildroot tree for Cocos AI
+1
View File
@@ -0,0 +1 @@
include $(sort $(wildcard $(BR2_EXTERNAL_COCOS_PATH)/package/*/*.mk))
+13
View File
@@ -0,0 +1,13 @@
config BR2_PACKAGE_AGENT
bool "agent"
default y
select BR2_PACKAGE_ATTESTATION_SERVICE
select BR2_PACKAGE_LOG_FORWARDER
select BR2_PACKAGE_COMPUTATION_RUNNER
select BR2_PACKAGE_INGRESS_PROXY
select BR2_PACKAGE_EGRESS_PROXY
help
Confidential Computing Agent is a state machine capable of
receiving datasets and algorithm, running computations, and
fetching the attestation report from within the
Confidential VM.
+27
View File
@@ -0,0 +1,27 @@
################################################################################
#
# Cocos AI Agent
#
################################################################################
AGENT_VERSION = main
AGENT_SITE = $(call github,ultravioletrs,cocos,$(AGENT_VERSION))
define AGENT_BUILD_CMDS
$(MAKE) -C $(@D) agent EMBED_ENABLED=$(AGENT_EMBED_ENABLED)
endef
define AGENT_INSTALL_TARGET_CMDS
mkdir -p $(TARGET_DIR)/cocos/
mkdir -p $(TARGET_DIR)/var/log/cocos
mkdir -p $(TARGET_DIR)/cocos_init/
$(INSTALL) -D -m 0750 $(@D)/build/cocos-agent $(TARGET_DIR)/bin
endef
define AGENT_INSTALL_INIT_SYSTEMD
$(INSTALL) -D -m 0640 $(@D)/init/systemd/cocos-agent.service $(TARGET_DIR)/usr/lib/systemd/system/cocos-agent.service
$(INSTALL) -D -m 0750 $(@D)/init/systemd/agent_setup.sh $(TARGET_DIR)/cocos_init/agent_setup.sh
$(INSTALL) -D -m 0750 $(@D)/init/systemd/agent_start_script.sh $(TARGET_DIR)/cocos_init/agent_start_script.sh
endef
$(eval $(generic-package))
@@ -0,0 +1,11 @@
config BR2_PACKAGE_ATTESTATION_SERVICE
bool
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
@@ -0,0 +1,34 @@
################################################################################
#
# attestation-service
#
################################################################################
ATTESTATION_SERVICE_VERSION = main
ATTESTATION_SERVICE_SITE = $(call github,ultravioletrs,cocos,$(ATTESTATION_SERVICE_VERSION))
define ATTESTATION_SERVICE_BUILD_CMDS
$(MAKE) -C $(@D) attestation-service
endef
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))
@@ -0,0 +1,28 @@
config BR2_PACKAGE_CC_ATTESTATION_AGENT
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.
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
if BR2_PACKAGE_CC_ATTESTATION_AGENT
config BR2_PACKAGE_CC_ATTESTATION_AGENT_KBS_URL
string "Default KBS URL (optional)"
default ""
help
Optional default KBS (Key Broker Service) URL for remote
attestation and secret provisioning.
Leave empty to operate in local attestation mode only.
Example: https://kbs.example.com:8080
endif
@@ -0,0 +1,5 @@
#!/bin/bash
# Setup permissions for attestation socket directory
mkdir -p /run/cocos
chmod 755 /run/cocos
@@ -0,0 +1,37 @@
################################################################################
#
# cc-attestation-agent
#
################################################################################
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 tpm2-tss
# Build the attestation-agent from the guest-components repository with gRPC support
define CC_ATTESTATION_AGENT_BUILD_CMDS
cd $(@D)/attestation-agent && \
$(TARGET_MAKE_ENV) \
CARGO_HOME=$(@D)/.cargo \
make ATTESTER=all-attesters ttrpc=false
endef
define CC_ATTESTATION_AGENT_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0755 \
$(@D)/target/$(RUSTC_TARGET_NAME)/release/attestation-agent \
$(TARGET_DIR)/usr/bin/attestation-agent
endef
define CC_ATTESTATION_AGENT_INSTALL_INIT_SYSTEMD
$(INSTALL) -D -m 0644 \
$(BR2_EXTERNAL_COCOS_PATH)/package/cc-attestation-agent/cc-attestation-agent.service \
$(TARGET_DIR)/usr/lib/systemd/system/attestation-agent.service
$(INSTALL) -D -m 0750 \
$(BR2_EXTERNAL_COCOS_PATH)/package/cc-attestation-agent/cc-attestation-agent-setup.sh \
$(TARGET_DIR)/cocos_init/attestation_setup.sh
endef
$(eval $(generic-package))
@@ -0,0 +1,13 @@
[Unit]
Description=Confidential Containers Attestation Agent (gRPC)
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/attestation-agent --attestation_sock 127.0.0.1:50002
Restart=always
RestartSec=5
Environment=RUST_LOG=info
[Install]
WantedBy=multi-user.target
@@ -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
@@ -0,0 +1,5 @@
config BR2_PACKAGE_COMPUTATION_RUNNER
bool "computation-runner"
select BR2_PACKAGE_LOG_FORWARDER
help
Cocos AI Computation Runner service.
@@ -0,0 +1,22 @@
################################################################################
#
# computation-runner
#
################################################################################
COMPUTATION_RUNNER_VERSION = main
COMPUTATION_RUNNER_SITE = $(call github,ultravioletrs,cocos,$(COMPUTATION_RUNNER_VERSION))
define COMPUTATION_RUNNER_BUILD_CMDS
$(MAKE) -C $(@D) computation-runner
endef
define COMPUTATION_RUNNER_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0750 $(@D)/build/cocos-computation-runner $(TARGET_DIR)/usr/bin/computation-runner
endef
define COMPUTATION_RUNNER_INSTALL_INIT_SYSTEMD
$(INSTALL) -D -m 0640 $(@D)/init/systemd/computation-runner.service $(TARGET_DIR)/usr/lib/systemd/system/computation-runner.service
endef
$(eval $(generic-package))
+6
View File
@@ -0,0 +1,6 @@
config BR2_PACKAGE_EGRESS_PROXY
bool "egress-proxy"
help
Cocos AI Egress Proxy Service.
https://github.com/ultravioletrs/cocos
@@ -0,0 +1,22 @@
################################################################################
#
# Cocos AI Egress Proxy
#
################################################################################
EGRESS_PROXY_VERSION = main
EGRESS_PROXY_SITE = $(call github,ultravioletrs,cocos,$(EGRESS_PROXY_VERSION))
define EGRESS_PROXY_BUILD_CMDS
$(MAKE) -C $(@D) egress-proxy
endef
define EGRESS_PROXY_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0755 $(@D)/build/cocos-egress-proxy $(TARGET_DIR)/usr/bin/egress-proxy
endef
define EGRESS_PROXY_INSTALL_INIT_SYSTEMD
$(INSTALL) -D -m 0644 $(@D)/init/systemd/egress-proxy.service $(TARGET_DIR)/usr/lib/systemd/system/egress-proxy.service
endef
$(eval $(generic-package))
+4
View File
@@ -0,0 +1,4 @@
config BR2_PACKAGE_INGRESS_PROXY
bool "ingress-proxy"
help
Cocos Ingress Proxy service.
@@ -0,0 +1,22 @@
################################################################################
#
# ingress-proxy
#
################################################################################
INGRESS_PROXY_VERSION = main
INGRESS_PROXY_SITE = $(call github,ultravioletrs,cocos,$(INGRESS_PROXY_VERSION))
define INGRESS_PROXY_BUILD_CMDS
$(MAKE) -C $(@D) ingress-proxy
endef
define INGRESS_PROXY_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0750 $(@D)/build/cocos-ingress-proxy $(TARGET_DIR)/usr/bin/ingress-proxy
endef
# NOTE: The ingress-proxy is managed per-computation by the agent, not as a standalone
# systemd service. The binary is installed for use by the agent, but no systemd service
# is created.
$(eval $(generic-package))
+4
View File
@@ -0,0 +1,4 @@
config BR2_PACKAGE_LOG_FORWARDER
bool "log-forwarder"
help
Cocos AI Log Forwarder service.
@@ -0,0 +1,22 @@
################################################################################
#
# log-forwarder
#
################################################################################
LOG_FORWARDER_VERSION = main
LOG_FORWARDER_SITE = $(call github,ultravioletrs,cocos,$(LOG_FORWARDER_VERSION))
define LOG_FORWARDER_BUILD_CMDS
$(MAKE) -C $(@D) log-forwarder
endef
define LOG_FORWARDER_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0750 $(@D)/build/cocos-log-forwarder $(TARGET_DIR)/usr/bin/log-forwarder
endef
define LOG_FORWARDER_INSTALL_INIT_SYSTEMD
$(INSTALL) -D -m 0640 $(@D)/init/systemd/log-forwarder.service $(TARGET_DIR)/usr/lib/systemd/system/log-forwarder.service
endef
$(eval $(generic-package))
+6
View File
@@ -0,0 +1,6 @@
config BR2_PACKAGE_WASMEDGE
bool "wasmedge"
default y
help
Wasmedge is a standalone runtime for WebAssembly.
https://wasmedge.org/docs/
+8
View File
@@ -0,0 +1,8 @@
WASMEDGE_DOWNLOAD_URL = https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh
define WASMEDGE_INSTALL_TARGET_CMDS
curl -sSf $(WASMEDGE_DOWNLOAD_URL) | bash -s -- -p $(TARGET_DIR)/usr -v 0.14.1
echo "source /usr/env" >> $(TARGET_DIR)/etc/profile
endef
$(eval $(generic-package))
@@ -0,0 +1,34 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
Subject: [PATCH] efi: skip lockdown when built with --disable-shim-lock
When GRUB is built with --disable-shim-lock, grub_shim_lock_verifier_setup()
returns early without registering the shim_lock verifier. However
grub_lockdown() was called unconditionally before that, registering the
lockdown_verifier which marks kernel files as DEFER_AUTH. With no verifier
present to approve them, every kernel load fails with "verification requested
but nobody cares".
Fix by calling grub_shim_lock_verifier_setup() first and only calling
grub_lockdown() if shim_lock is actually active. This preserves full
lockdown behaviour in shim-based chains while allowing direct
OVMF->GRUB->kernel boot with a custom DB key and no shim.
Signed-off-by: Cocos AI <build@cocos.ai>
---
grub-core/kern/efi/init.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/grub-core/kern/efi/init.c b/grub-core/kern/efi/init.c
--- a/grub-core/kern/efi/init.c
+++ b/grub-core/kern/efi/init.c
@@ -122,8 +122,9 @@
*/
if (grub_efi_get_secureboot () == GRUB_EFI_SECUREBOOT_MODE_ENABLED)
{
- grub_lockdown ();
grub_shim_lock_verifier_setup ();
+ if (grub_is_shim_lock_enabled ())
+ grub_lockdown ();
}
grub_efi_system_table->boot_services->set_watchdog_timer (0, 0, 0, NULL);
+1 -1
View File
@@ -10,7 +10,7 @@ HAL uses [Buildroot](https://buildroot.org/)'s [_External Tree_ mechanism](https
git clone git@github.com:ultravioletrs/cocos.git
git clone git@github.com:buildroot/buildroot.git
cd buildroot
git checkout 2025.08-rc3
git checkout 2025.11
make BR2_EXTERNAL=../cocos/hal/linux cocos_defconfig
# Execute 'make menuconfig' only if you want to make additional configuration changes to Buildroot.
make menuconfig
+5 -2
View File
@@ -22,5 +22,8 @@ if [ ! -d "$WORK_DIR" ]; then
mkdir -p $WORK_DIR
fi
# Resize the root file system to 100%
mount -o remount,size=100% /
# RAM-only agent images use tmpfs as the root filesystem
ROOT_FSTYPE=$(awk '$2 == "/" { print $3; exit }' /proc/mounts)
if [ "$ROOT_FSTYPE" = "tmpfs" ]; then
mount -o remount,size=100% /
fi
+2 -2
View File
@@ -1,7 +1,7 @@
[Unit]
Description=Cocos AI agent
After=network.target attestation-service.service log-forwarder.service computation-runner.service egress-proxy.service coco-keyprovider.service
Requires=log-forwarder.service computation-runner.service egress-proxy.service coco-keyprovider.service
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
Before=docker.service
[Service]
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"testing"
"time"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/magistrala/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/ultravioletrs/cocos/agent/cvms"
)
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.1
// protoc v7.35.0
// source: internal/proto/attestation-agent/attestation-agent.proto
package attestation_agent
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc v6.33.1
// - protoc v7.35.0
// source: internal/proto/attestation-agent/attestation-agent.proto
package attestation_agent
+122 -34
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.1
// protoc v7.35.0
// source: internal/proto/attestation/v1/attestation.proto
package attestation
@@ -82,6 +82,86 @@ func (PlatformType) EnumDescriptor() ([]byte, []int) {
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{0}
}
type KbsTokenRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *KbsTokenRequest) Reset() {
*x = KbsTokenRequest{}
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *KbsTokenRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KbsTokenRequest) ProtoMessage() {}
func (x *KbsTokenRequest) ProtoReflect() protoreflect.Message {
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[0]
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 KbsTokenRequest.ProtoReflect.Descriptor instead.
func (*KbsTokenRequest) Descriptor() ([]byte, []int) {
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{0}
}
type KbsTokenResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Token []byte `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *KbsTokenResponse) Reset() {
*x = KbsTokenResponse{}
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *KbsTokenResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KbsTokenResponse) ProtoMessage() {}
func (x *KbsTokenResponse) ProtoReflect() protoreflect.Message {
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[1]
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 KbsTokenResponse.ProtoReflect.Descriptor instead.
func (*KbsTokenResponse) Descriptor() ([]byte, []int) {
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{1}
}
func (x *KbsTokenResponse) GetToken() []byte {
if x != nil {
return x.Token
}
return nil
}
type AttestationRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
ReportData []byte `protobuf:"bytes,1,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // 64 bytes for SNP/TDX
@@ -93,7 +173,7 @@ type AttestationRequest struct {
func (x *AttestationRequest) Reset() {
*x = AttestationRequest{}
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[0]
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -105,7 +185,7 @@ func (x *AttestationRequest) String() string {
func (*AttestationRequest) ProtoMessage() {}
func (x *AttestationRequest) ProtoReflect() protoreflect.Message {
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[0]
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -118,7 +198,7 @@ func (x *AttestationRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AttestationRequest.ProtoReflect.Descriptor instead.
func (*AttestationRequest) Descriptor() ([]byte, []int) {
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{0}
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{2}
}
func (x *AttestationRequest) GetReportData() []byte {
@@ -151,7 +231,7 @@ type AttestationResponse struct {
func (x *AttestationResponse) Reset() {
*x = AttestationResponse{}
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[1]
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -163,7 +243,7 @@ func (x *AttestationResponse) String() string {
func (*AttestationResponse) ProtoMessage() {}
func (x *AttestationResponse) ProtoReflect() protoreflect.Message {
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[1]
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -176,7 +256,7 @@ func (x *AttestationResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AttestationResponse.ProtoReflect.Descriptor instead.
func (*AttestationResponse) Descriptor() ([]byte, []int) {
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{1}
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{3}
}
func (x *AttestationResponse) GetEatToken() []byte {
@@ -195,7 +275,7 @@ type RawEvidenceResponse struct {
func (x *RawEvidenceResponse) Reset() {
*x = RawEvidenceResponse{}
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[2]
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -207,7 +287,7 @@ func (x *RawEvidenceResponse) String() string {
func (*RawEvidenceResponse) ProtoMessage() {}
func (x *RawEvidenceResponse) ProtoReflect() protoreflect.Message {
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[2]
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -220,7 +300,7 @@ func (x *RawEvidenceResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use RawEvidenceResponse.ProtoReflect.Descriptor instead.
func (*RawEvidenceResponse) Descriptor() ([]byte, []int) {
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{2}
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{4}
}
func (x *RawEvidenceResponse) GetEvidence() []byte {
@@ -239,7 +319,7 @@ type AzureTokenRequest struct {
func (x *AzureTokenRequest) Reset() {
*x = AzureTokenRequest{}
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[3]
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -251,7 +331,7 @@ func (x *AzureTokenRequest) String() string {
func (*AzureTokenRequest) ProtoMessage() {}
func (x *AzureTokenRequest) ProtoReflect() protoreflect.Message {
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[3]
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -264,7 +344,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{3}
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{5}
}
func (x *AzureTokenRequest) GetNonce() []byte {
@@ -283,7 +363,7 @@ type AzureTokenResponse struct {
func (x *AzureTokenResponse) Reset() {
*x = AzureTokenResponse{}
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[4]
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -295,7 +375,7 @@ func (x *AzureTokenResponse) String() string {
func (*AzureTokenResponse) ProtoMessage() {}
func (x *AzureTokenResponse) ProtoReflect() protoreflect.Message {
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[4]
mi := &file_internal_proto_attestation_v1_attestation_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -308,7 +388,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{4}
return file_internal_proto_attestation_v1_attestation_proto_rawDescGZIP(), []int{6}
}
func (x *AzureTokenResponse) GetToken() []byte {
@@ -322,7 +402,10 @@ var File_internal_proto_attestation_v1_attestation_proto protoreflect.FileDescri
const file_internal_proto_attestation_v1_attestation_proto_rawDesc = "" +
"\n" +
"/internal/proto/attestation/v1/attestation.proto\x12\x0eattestation.v1\"\x8e\x01\n" +
"/internal/proto/attestation/v1/attestation.proto\x12\x0eattestation.v1\"\x11\n" +
"\x0fKbsTokenRequest\"(\n" +
"\x10KbsTokenResponse\x12\x14\n" +
"\x05token\x18\x01 \x01(\fR\x05token\"\x8e\x01\n" +
"\x12AttestationRequest\x12\x1f\n" +
"\vreport_data\x18\x01 \x01(\fR\n" +
"reportData\x12\x14\n" +
@@ -343,11 +426,12 @@ 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\xa8\x02\n" +
"\x13PLATFORM_TYPE_NO_CC\x10\x062\xfc\x02\n" +
"\x12AttestationService\x12[\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"
"\x0fFetchAzureToken\x12!.attestation.v1.AzureTokenRequest\x1a\".attestation.v1.AzureTokenResponse\x12R\n" +
"\rFetchKbsToken\x12\x1f.attestation.v1.KbsTokenRequest\x1a .attestation.v1.KbsTokenResponseBJZHgithub.com/ultravioletrs/cocos/internal/proto/attestation/v1;attestationb\x06proto3"
var (
file_internal_proto_attestation_v1_attestation_proto_rawDescOnce sync.Once
@@ -362,25 +446,29 @@ 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, 5)
var file_internal_proto_attestation_v1_attestation_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
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
(*RawEvidenceResponse)(nil), // 3: attestation.v1.RawEvidenceResponse
(*AzureTokenRequest)(nil), // 4: attestation.v1.AzureTokenRequest
(*AzureTokenResponse)(nil), // 5: attestation.v1.AzureTokenResponse
(*KbsTokenRequest)(nil), // 1: attestation.v1.KbsTokenRequest
(*KbsTokenResponse)(nil), // 2: attestation.v1.KbsTokenResponse
(*AttestationRequest)(nil), // 3: attestation.v1.AttestationRequest
(*AttestationResponse)(nil), // 4: attestation.v1.AttestationResponse
(*RawEvidenceResponse)(nil), // 5: attestation.v1.RawEvidenceResponse
(*AzureTokenRequest)(nil), // 6: attestation.v1.AzureTokenRequest
(*AzureTokenResponse)(nil), // 7: 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
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
3, // 1: attestation.v1.AttestationService.FetchAttestation:input_type -> attestation.v1.AttestationRequest
3, // 2: attestation.v1.AttestationService.FetchRawEvidence:input_type -> attestation.v1.AttestationRequest
6, // 3: attestation.v1.AttestationService.FetchAzureToken:input_type -> attestation.v1.AzureTokenRequest
1, // 4: attestation.v1.AttestationService.FetchKbsToken:input_type -> attestation.v1.KbsTokenRequest
4, // 5: attestation.v1.AttestationService.FetchAttestation:output_type -> attestation.v1.AttestationResponse
5, // 6: attestation.v1.AttestationService.FetchRawEvidence:output_type -> attestation.v1.RawEvidenceResponse
7, // 7: attestation.v1.AttestationService.FetchAzureToken:output_type -> attestation.v1.AzureTokenResponse
2, // 8: attestation.v1.AttestationService.FetchKbsToken:output_type -> attestation.v1.KbsTokenResponse
5, // [5:9] is the sub-list for method output_type
1, // [1:5] 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
@@ -397,7 +485,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: 5,
NumMessages: 7,
NumExtensions: 0,
NumServices: 1,
},
@@ -8,6 +8,13 @@ service AttestationService {
rpc FetchAttestation (AttestationRequest) returns (AttestationResponse);
rpc FetchRawEvidence (AttestationRequest) returns (RawEvidenceResponse);
rpc FetchAzureToken (AzureTokenRequest) returns (AzureTokenResponse);
rpc FetchKbsToken (KbsTokenRequest) returns (KbsTokenResponse);
}
message KbsTokenRequest {}
message KbsTokenResponse {
bytes token = 1;
}
message AttestationRequest {
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc v6.33.1
// - protoc v7.35.0
// source: internal/proto/attestation/v1/attestation.proto
package attestation
@@ -22,6 +22,7 @@ const (
AttestationService_FetchAttestation_FullMethodName = "/attestation.v1.AttestationService/FetchAttestation"
AttestationService_FetchRawEvidence_FullMethodName = "/attestation.v1.AttestationService/FetchRawEvidence"
AttestationService_FetchAzureToken_FullMethodName = "/attestation.v1.AttestationService/FetchAzureToken"
AttestationService_FetchKbsToken_FullMethodName = "/attestation.v1.AttestationService/FetchKbsToken"
)
// AttestationServiceClient is the client API for AttestationService service.
@@ -31,6 +32,7 @@ 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)
FetchKbsToken(ctx context.Context, in *KbsTokenRequest, opts ...grpc.CallOption) (*KbsTokenResponse, error)
}
type attestationServiceClient struct {
@@ -71,6 +73,16 @@ func (c *attestationServiceClient) FetchAzureToken(ctx context.Context, in *Azur
return out, nil
}
func (c *attestationServiceClient) FetchKbsToken(ctx context.Context, in *KbsTokenRequest, opts ...grpc.CallOption) (*KbsTokenResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(KbsTokenResponse)
err := c.cc.Invoke(ctx, AttestationService_FetchKbsToken_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// AttestationServiceServer is the server API for AttestationService service.
// All implementations must embed UnimplementedAttestationServiceServer
// for forward compatibility.
@@ -78,6 +90,7 @@ type AttestationServiceServer interface {
FetchAttestation(context.Context, *AttestationRequest) (*AttestationResponse, error)
FetchRawEvidence(context.Context, *AttestationRequest) (*RawEvidenceResponse, error)
FetchAzureToken(context.Context, *AzureTokenRequest) (*AzureTokenResponse, error)
FetchKbsToken(context.Context, *KbsTokenRequest) (*KbsTokenResponse, error)
mustEmbedUnimplementedAttestationServiceServer()
}
@@ -97,6 +110,9 @@ func (UnimplementedAttestationServiceServer) FetchRawEvidence(context.Context, *
func (UnimplementedAttestationServiceServer) FetchAzureToken(context.Context, *AzureTokenRequest) (*AzureTokenResponse, error) {
return nil, status.Error(codes.Unimplemented, "method FetchAzureToken not implemented")
}
func (UnimplementedAttestationServiceServer) FetchKbsToken(context.Context, *KbsTokenRequest) (*KbsTokenResponse, error) {
return nil, status.Error(codes.Unimplemented, "method FetchKbsToken not implemented")
}
func (UnimplementedAttestationServiceServer) mustEmbedUnimplementedAttestationServiceServer() {}
func (UnimplementedAttestationServiceServer) testEmbeddedByValue() {}
@@ -172,6 +188,24 @@ func _AttestationService_FetchAzureToken_Handler(srv interface{}, ctx context.Co
return interceptor(ctx, in, info, handler)
}
func _AttestationService_FetchKbsToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(KbsTokenRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AttestationServiceServer).FetchKbsToken(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AttestationService_FetchKbsToken_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AttestationServiceServer).FetchKbsToken(ctx, req.(*KbsTokenRequest))
}
return interceptor(ctx, in, info, handler)
}
// AttestationService_ServiceDesc is the grpc.ServiceDesc for AttestationService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -191,6 +225,10 @@ var AttestationService_ServiceDesc = grpc.ServiceDesc{
MethodName: "FetchAzureToken",
Handler: _AttestationService_FetchAzureToken_Handler,
},
{
MethodName: "FetchKbsToken",
Handler: _AttestationService_FetchKbsToken_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "internal/proto/attestation/v1/attestation.proto",
+36 -4
View File
@@ -49,6 +49,12 @@ The service is configured using the environment variables from the following tab
| MANAGER_QEMU_VIRTIO_NET_PCI_ROMFILE | The file path for the ROM image for the virtio-net PCI device. | |
| MANAGER_QEMU_DISK_IMG_KERNEL_FILE | The file path for the kernel image. | img/bzImage |
| MANAGER_QEMU_DISK_IMG_ROOTFS_FILE | The file path for the root filesystem image. | img/rootfs.cpio.gz |
| MANAGER_QEMU_ENABLE_DISK | Whether to attach a writable qcow2 disk to the CVM. | false |
| MANAGER_QEMU_SRC_DISK_FILE | Path to a qcow2 image whose virtual size is used to size the per-VM writable disk. | img/enc_os.qcow2 |
| MANAGER_QEMU_DST_DISK_FILE | Runtime path of the per-VM writable disk created by the manager. | |
| MANAGER_QEMU_DISK_ID | The QEMU drive identifier for the attached disk. | disk0 |
| MANAGER_QEMU_DISK_FORMAT | The format of the attached disk image. | qcow2 |
| MANAGER_QEMU_DISK_SCSI_ID | The SCSI controller identifier used for the attached disk. | scsi0 |
| MANAGER_QEMU_SEV_SNP_ID | The ID for the Secure Encrypted Virtualization (SEV-SNP) device. | sev0 |
| MANAGER_QEMU_SEV_SNP_CBITPOS | The position of the C-bit in the physical address. | 51 |
| MANAGER_QEMU_SEV_SNP_REDUCED_PHYS_BITS | The number of reduced physical address bits for SEV-SNP. | 1 |
@@ -112,6 +118,20 @@ Once the image is built copy the kernel and rootfs image to `cmd/manager/img` fr
Another option is to use release versions of EOS that can be downloaded from the [Cocos GitHub repository](https://github.com/ultravioletrs/cocos/releases).
#### Optional writable disk
If you want the manager to attach a writable disk to each CVM, place a qcow2 reference image at `cmd/manager/img/enc_os.qcow2`, or point `MANAGER_QEMU_SRC_DISK_FILE` to another qcow2 file.
When `MANAGER_QEMU_ENABLE_DISK=true`, the manager:
- reads the virtual size of `MANAGER_QEMU_SRC_DISK_FILE` with `qemu-img info`
- creates a per-VM qcow2 disk under `/tmp/cvmDisk-<uuid>.qcow2`
- sizes the disk to the source image size plus 1 GiB, leaving room for the LUKS header
- attaches the disk through a virtio-scsi controller
- removes the temporary disk again when the VM stops
`MANAGER_QEMU_DST_DISK_FILE` is primarily a runtime value. In the normal manager flow it is populated automatically and usually does not need to be set manually.
#### Test VM creation
```sh
@@ -207,7 +227,7 @@ nc -zv localhost 7020
#### Conclusion
Now you are able to use `Manager` with `Agent`. Namely, `Manager` will create a VM with a separate OVMF variables file on manager `/run` request.
Now you are able to use `Manager` with `Agent`. On each manager `/run` request, the manager creates a VM with a separate OVMF variables file and, when enabled, a per-VM writable qcow2 disk.
### OVMF
@@ -284,6 +304,18 @@ MANAGER_QEMU_OVMF_FILE=<path to OVMF file> \
./build/cocos-manager
```
To enable writable disk support, start manager like this
```sh
MANAGER_GRPC_URL=localhost:7001 \
MANAGER_LOG_LEVEL=debug \
MANAGER_QEMU_ENABLE_DISK=true \
MANAGER_QEMU_SRC_DISK_FILE=<path to reference qcow2 image> \
./build/cocos-manager
```
The reference qcow2 image is used to determine the disk size. The manager creates a fresh writable qcow2 disk for each VM under `/tmp` and deletes it on shutdown.
### Troubleshooting
If the `ps aux | grep qemu-system-x86_64` give you something like this
@@ -294,16 +326,16 @@ darko 13913 0.0 0.0 0 0 pts/2 Z+ 20:17 0:00 [qemu-system-
means that the a QEMU virtual machine that is currently defunct, meaning that it is no longer running. More precisely, the defunct process in the output is also known as a ["zombie" process](https://en.wikipedia.org/wiki/Zombie_process).
You can troubleshoot the VM launch procedure by running directly `qemu-system-x86_64` command. When you run `manager` with `MANAGER_LOG_LEVEL=info` env var set, it prints out the entire command used to launch a VM. The relevant part of the log might look like this
You can troubleshoot the VM launch procedure by running directly `qemu-system-x86_64` command. When you run `manager` with `MANAGER_LOG_LEVEL=info` env var set, it prints out the entire command used to launch a VM. When writable disk support is enabled, the relevant part of the log might look like this
```
{"level":"info","message":"/usr/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 4096M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=img/OVMF_VARS.fd -device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true -drive file=img/focal-server-cloudimg-amd64.img,if=none,id=disk0,format=qcow2 -device scsi-hd,drive=disk0 -netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::9301-:9031,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= -nographic -monitor pty","ts":"2023-08-14T18:29:19.2653908Z"}
{"level":"info","message":"/usr/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 4096M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=/tmp/OVMF_VARS-<uuid>.fd -netdev user,id=vmnic-<uuid>,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic-<uuid>,addr=0x2,romfile= -drive file=/tmp/cvmDisk-<uuid>.qcow2,if=none,id=disk0,format=qcow2 -device virtio-scsi-pci,id=scsi0,disable-legacy=on,iommu_platform=true -device scsi-hd,drive=disk0,bus=scsi0.0 -kernel img/bzImage -append quiet console=null -initrd img/rootfs.cpio.gz -nographic -monitor pty","ts":"2026-04-27T00:00:00Z"}
```
You can run the command - the value of the `"message"` key - directly in the terminal:
```sh
/usr/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 4096M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=img/OVMF_VARS.fd -device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true -drive file=img/focal-server-cloudimg-amd64.img,if=none,id=disk0,format=qcow2 -device scsi-hd,drive=disk0 -netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::9301-:9031,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= -nographic -monitor pty
/usr/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 4096M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=/tmp/OVMF_VARS-<uuid>.fd -netdev user,id=vmnic-<uuid>,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic-<uuid>,addr=0x2,romfile= -drive file=/tmp/cvmDisk-<uuid>.qcow2,if=none,id=disk0,format=qcow2 -device virtio-scsi-pci,id=scsi0,disable-legacy=on,iommu_platform=true -device scsi-hd,drive=disk0,bus=scsi0.0 -kernel img/bzImage -append "quiet console=null" -initrd img/rootfs.cpio.gz -nographic -monitor pty
```
and look for the possible problems. This problems can usually be solved by using the adequate env var assignments. Look in the `manager/qemu/config.go` file to see the recognized env vars. Don't forget to prepend `MANAGER_QEMU_` to the name of the env vars.
+2 -2
View File
@@ -6,14 +6,14 @@ package http
import (
"net/http"
"github.com/absmach/supermq"
"github.com/absmach/magistrala"
"github.com/go-chi/chi/v5"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(r *chi.Mux, svcName, instanceID string) http.Handler {
r.Get("/health", supermq.Health(svcName, instanceID))
r.Get("/health", magistrala.Health(svcName, instanceID))
r.Handle("/metrics", promhttp.Handler())
return r
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.1
// protoc v7.35.0
// source: manager/manager.proto
package manager
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc v6.33.1
// - protoc v7.35.0
// source: manager/manager.proto
package manager
+92 -6
View File
@@ -4,6 +4,7 @@ package qemu
import (
"fmt"
"strings"
"github.com/caarlos0/env/v10"
)
@@ -48,7 +49,7 @@ type VirtioNetPciConfig struct {
ROMFile string `env:"VIRTIO_NET_PCI_ROMFILE"`
}
type DiskImgConfig struct {
type KernelConfig struct {
KernelFile string `env:"DISK_IMG_KERNEL_FILE" envDefault:"img/bzImage"`
RootFsFile string `env:"DISK_IMG_ROOTFS_FILE" envDefault:"img/rootfs.cpio.gz"`
}
@@ -72,9 +73,26 @@ type IGVMConfig struct {
File string `env:"IGVM_FILE" envDefault:"/root/coconut-qemu.igvm"`
}
type DiskConfig struct {
SrcFile string `env:"SRC_DISK_FILE" envDefault:"img/enc_os.qcow2"`
DstFile string `env:"DST_DISK_FILE" envDefault:""`
ID string `env:"DISK_ID" envDefault:"disk0"`
Format string `env:"DISK_FORMAT" envDefault:"qcow2"`
SCSIID string `env:"DISK_SCSI_ID" envDefault:"scsi0"`
}
type GPUConfig struct {
EnableGPU bool
GPUBDF string `env:"GPU_BDF" envDefault:""`
PCIeRootPort string `env:"GPU_PCIE_ROOT_PORT" envDefault:"pci.1"`
PCIeBus string `env:"GPU_PCIE_BUS" envDefault:"pcie.0"`
FWCfgPciMmio string `env:"GPU_FW_CFG_MMIO_MB" envDefault:"262144"`
}
type Config struct {
EnableSEVSNP bool
EnableTDX bool
EnableDisk bool `env:"ENABLE_DISK" envDefault:"false"`
QemuBinPath string `env:"BIN_PATH" envDefault:"qemu-system-x86_64"`
UseSudo bool `env:"USE_SUDO" envDefault:"false"`
@@ -96,8 +114,11 @@ type Config struct {
NetDevConfig
VirtioNetPciConfig
// disk
DiskImgConfig
// disk config
DiskConfig
// kernel and initramfs
KernelConfig
// SEV-SNP
SEVSNPConfig
@@ -108,6 +129,9 @@ type Config struct {
// vTPM
IGVMConfig
// GPU passthrough
GPUConfig
// display
NoGraphic bool `env:"NO_GRAPHIC" envDefault:"true"`
Monitor string `env:"MONITOR" envDefault:"pty"`
@@ -123,6 +147,25 @@ type Config struct {
EnvMount string `env:"ENV_MOUNT" envDefault:""`
}
func (config Config) ValidateBootConfig() error {
if config.EnableDisk {
if strings.TrimSpace(config.DiskConfig.DstFile) == "" {
return fmt.Errorf("disk boot enabled but destination disk image is not set")
}
return nil
}
if strings.TrimSpace(config.KernelConfig.KernelFile) == "" {
return fmt.Errorf("kernel boot enabled but kernel image is not set")
}
if strings.TrimSpace(config.KernelConfig.RootFsFile) == "" {
return fmt.Errorf("kernel boot enabled but initramfs image is not set")
}
return nil
}
func (config Config) ConstructQemuArgs() []string {
args := []string{}
@@ -179,6 +222,39 @@ func (config Config) ConstructQemuArgs() []string {
config.VirtioNetPciConfig.Addr,
config.VirtioNetPciConfig.ROMFile))
if config.EnableDisk {
// disk image
args = append(args, "-drive",
fmt.Sprintf("file=%s,if=none,id=%s,format=%s",
config.DiskConfig.DstFile,
config.DiskConfig.ID,
config.DiskConfig.Format))
args = append(args, "-device",
fmt.Sprintf("virtio-scsi-pci,id=%s,disable-legacy=on,iommu_platform=true",
config.DiskConfig.SCSIID))
args = append(args, "-device",
fmt.Sprintf("scsi-hd,drive=%s,bus=%s.0",
config.DiskConfig.ID,
config.DiskConfig.SCSIID))
}
// GPU passthrough via VFIO
if config.GPUConfig.EnableGPU {
args = append(args, "-device",
fmt.Sprintf("pcie-root-port,id=%s,bus=%s",
config.GPUConfig.PCIeRootPort,
config.GPUConfig.PCIeBus))
args = append(args, "-device",
fmt.Sprintf("vfio-pci,host=%s,bus=%s",
config.GPUConfig.GPUBDF,
config.GPUConfig.PCIeRootPort))
args = append(args, "-fw_cfg",
fmt.Sprintf("name=opt/ovmf/X-PciMmio64Mb,string=%s",
config.GPUConfig.FWCfgPciMmio))
}
// SEV-SNP
if config.EnableSEVSNP {
sevSnpType := "sev-snp-guest"
@@ -233,9 +309,11 @@ func (config Config) ConstructQemuArgs() []string {
args = append(args, "-nodefaults")
}
args = append(args, "-kernel", config.DiskImgConfig.KernelFile)
args = append(args, "-append", config.KernelCommandLine)
args = append(args, "-initrd", config.DiskImgConfig.RootFsFile)
if !config.EnableDisk {
args = append(args, "-kernel", config.KernelConfig.KernelFile)
args = append(args, "-append", config.KernelCommandLine)
args = append(args, "-initrd", config.KernelConfig.RootFsFile)
}
// display
if config.NoGraphic {
@@ -267,5 +345,13 @@ func NewConfig() (*Config, error) {
cfg.EnableSEVSNP = SEVSNPEnabledOnHost()
cfg.EnableTDX = TDXEnabledOnHost()
bdf, detected := GPUPassthroughAvailable()
if cfg.GPUConfig.GPUBDF != "" {
cfg.GPUConfig.EnableGPU = true
} else if detected {
cfg.GPUConfig.EnableGPU = true
cfg.GPUConfig.GPUBDF = bdf
}
return &cfg, nil
}
+226 -2
View File
@@ -51,7 +51,7 @@ func TestConstructQemuArgs(t *testing.T) {
IOMMUPlatform: true,
Addr: "0x2",
},
DiskImgConfig: DiskImgConfig{
KernelConfig: KernelConfig{
KernelFile: "img/bzImage",
RootFsFile: "img/rootfs.cpio.gz",
},
@@ -115,7 +115,7 @@ func TestConstructQemuArgs(t *testing.T) {
IOMMUPlatform: true,
Addr: "0x2",
},
DiskImgConfig: DiskImgConfig{
KernelConfig: KernelConfig{
KernelFile: "img/bzImage",
RootFsFile: "img/rootfs.cpio.gz",
},
@@ -151,6 +151,79 @@ func TestConstructQemuArgs(t *testing.T) {
"-monitor", "pty",
},
},
{
name: "GPU passthrough configuration",
config: Config{
QemuBinPath: "qemu-system-x86_64",
EnableKVM: true,
Machine: "q35",
CPU: "EPYC",
SMPCount: 4,
MaxCPUs: 64,
MemID: "ram1",
MemoryConfig: MemoryConfig{
Size: "2048M",
Slots: 5,
Max: "30G",
},
OVMFCodeConfig: OVMFCodeConfig{
If: "pflash",
Format: "raw",
Unit: 0,
File: "/usr/share/OVMF/OVMF_CODE.fd",
ReadOnly: "on",
},
OVMFVarsConfig: OVMFVarsConfig{
If: "pflash",
Format: "raw",
Unit: 1,
File: "/usr/share/OVMF/OVMF_VARS.fd",
},
NetDevConfig: NetDevConfig{
ID: "vmnic",
HostFwdAgent: 7020,
GuestFwdAgent: 7002,
},
VirtioNetPciConfig: VirtioNetPciConfig{
DisableLegacy: "on",
IOMMUPlatform: true,
Addr: "0x2",
},
KernelConfig: KernelConfig{
KernelFile: "img/bzImage",
RootFsFile: "img/rootfs.cpio.gz",
},
GPUConfig: GPUConfig{
EnableGPU: true,
GPUBDF: "0000:02:00.0",
PCIeRootPort: "pci.1",
PCIeBus: "pcie.0",
FWCfgPciMmio: "262144",
},
KernelCommandLine: "quiet console=null",
NoGraphic: true,
Monitor: "pty",
},
expected: []string{
"-enable-kvm",
"-machine", "q35",
"-cpu", "EPYC",
"-smp", "4,maxcpus=64",
"-m", "2048M,slots=5,maxmem=30G",
"-drive", "if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on",
"-drive", "if=pflash,format=raw,unit=1,file=/usr/share/OVMF/OVMF_VARS.fd",
"-netdev", "user,id=vmnic,hostfwd=tcp::7020-:7002",
"-device", "virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,addr=0x2,romfile=",
"-device", "pcie-root-port,id=pci.1,bus=pcie.0",
"-device", "vfio-pci,host=0000:02:00.0,bus=pci.1",
"-fw_cfg", "name=opt/ovmf/X-PciMmio64Mb,string=262144",
"-kernel", "img/bzImage",
"-append", "quiet console=null",
"-initrd", "img/rootfs.cpio.gz",
"-nographic",
"-monitor", "pty",
},
},
}
for _, tt := range tests {
@@ -194,3 +267,154 @@ func TestConstructQemuArgs_HostData(t *testing.T) {
t.Errorf("ConstructQemuArgs() did not contain expected SEV-SNP configuration with host data")
}
}
func TestConstructQemuArgs_TDX(t *testing.T) {
config := Config{
EnableKVM: true,
EnableTDX: true,
Machine: "q35",
CPU: "EPYC",
SMPCount: 4,
MaxCPUs: 64,
MemID: "ram1",
MemoryConfig: MemoryConfig{
Size: "4096M",
Slots: 8,
Max: "64G",
},
NetDevConfig: NetDevConfig{
ID: "vmnic",
HostFwdAgent: 7020,
GuestFwdAgent: 7002,
},
VirtioNetPciConfig: VirtioNetPciConfig{
DisableLegacy: "on",
IOMMUPlatform: true,
Addr: "0x2",
},
TDXConfig: TDXConfig{
ID: "tdx0",
QuoteGenerationPort: 4050,
OVMF: "/usr/share/ovmf/OVMF.fd",
},
KernelConfig: KernelConfig{
KernelFile: "img/bzImage",
RootFsFile: "img/rootfs.cpio.gz",
},
KernelCommandLine: "quiet console=null",
NoGraphic: true,
Monitor: "pty",
}
expected := []string{
"-enable-kvm",
"-machine", "q35",
"-cpu", "EPYC",
"-smp", "4,maxcpus=64",
"-m", "4096M,slots=8,maxmem=64G",
"-netdev", "user,id=vmnic,hostfwd=tcp::7020-:7002",
"-device", "virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,addr=0x2,romfile=",
"-object", "{\"qom-type\":\"tdx-guest\",\"id\":\"tdx0\",\"quote-generation-socket\":{\"type\": \"vsock\", \"cid\":\"2\",\"port\":\"4050\"}}",
"-machine", "confidential-guest-support=tdx0,memory-backend=ram1,hpet=off",
"-object", "memory-backend-memfd,id=ram1,size=4096M,share=true,prealloc=false",
"-bios", "/usr/share/ovmf/OVMF.fd",
"-nodefaults",
"-kernel", "img/bzImage",
"-append", "quiet console=null",
"-initrd", "img/rootfs.cpio.gz",
"-nographic",
"-monitor", "pty",
}
result := config.ConstructQemuArgs()
if !reflect.DeepEqual(result, expected) {
t.Errorf("ConstructQemuArgs() = %v, want %v", result, expected)
}
}
func TestConstructQemuArgs_DiskBootSkipsKernelAndInitrd(t *testing.T) {
config := Config{
EnableKVM: true,
EnableDisk: true,
Machine: "q35",
CPU: "EPYC",
SMPCount: 4,
MaxCPUs: 64,
MemID: "ram1",
MemoryConfig: MemoryConfig{
Size: "2048M",
Slots: 5,
Max: "30G",
},
NetDevConfig: NetDevConfig{
ID: "vmnic",
HostFwdAgent: 7020,
GuestFwdAgent: 7002,
},
VirtioNetPciConfig: VirtioNetPciConfig{
DisableLegacy: "on",
IOMMUPlatform: true,
Addr: "0x2",
},
DiskConfig: DiskConfig{
DstFile: "img/disk.img",
ID: "disk0",
Format: "qcow2",
SCSIID: "scsi0",
},
KernelConfig: KernelConfig{
KernelFile: "img/bzImage",
RootFsFile: "img/rootfs.cpio.gz",
},
NoGraphic: true,
Monitor: "pty",
}
result := config.ConstructQemuArgs()
for _, forbidden := range []string{"-kernel", "-append", "-initrd"} {
for _, arg := range result {
if arg == forbidden {
t.Fatalf("ConstructQemuArgs() unexpectedly contained %s during disk boot: %v", forbidden, result)
}
}
}
}
func TestConstructQemuArgs_EnableDisk(t *testing.T) {
config := Config{
EnableDisk: true,
DiskConfig: DiskConfig{
SrcFile: "img/enc_os.qcow2",
DstFile: "img/enc_os_dst.qcow2",
ID: "disk0",
Format: "qcow2",
SCSIID: "scsi0",
},
}
result := config.ConstructQemuArgs()
expected := []string{
"-drive", "file=img/enc_os_dst.qcow2,if=none,id=disk0,format=qcow2",
"-device", "virtio-scsi-pci,id=scsi0,disable-legacy=on,iommu_platform=true",
"-device", "scsi-hd,drive=disk0,bus=scsi0.0",
}
var found []bool = make([]bool, len(expected))
for i, arg := range result {
for j := 0; j < len(expected); j += 2 {
if arg == expected[j] && i+1 < len(result) && result[i+1] == expected[j+1] {
found[j] = true
found[j+1] = true
break
}
}
}
for j, f := range found {
if !f {
t.Errorf("ConstructQemuArgs() did not contain expected disk configuration: %s", expected[j])
}
}
}
+127 -6
View File
@@ -3,10 +3,12 @@
package qemu
import (
"encoding/json"
"fmt"
"log/slog"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
@@ -18,12 +20,15 @@ import (
)
const (
firmwareVars = "OVMF_VARS"
KernelFile = "bzImage"
rootfsFile = "rootfs.cpio"
tmpDir = "/tmp"
interval = 5 * time.Second
shutdownTimeout = 30 * time.Second
firmwareVars = "OVMF_VARS"
KernelFile = "bzImage"
rootfsFile = "rootfs.cpio"
tmpDir = "/tmp"
diskDstName = "cvmDisk"
interval = 5 * time.Second
shutdownTimeout = 30 * time.Second
encryptedPartitionSizeDeltaGB = 1
sourceDiskFormat = "qcow2"
)
type VMInfo struct {
@@ -39,6 +44,10 @@ type qemuVM struct {
vm.StateMachine
}
type qemuInfo struct {
VirtualSize int64 `json:"virtual-size"`
}
func NewVM(config any, cvmId string, logger *slog.Logger) vm.VM {
return &qemuVM{
vmi: config.(VMInfo),
@@ -75,6 +84,44 @@ func (v *qemuVM) Start() (err error) {
v.vmi.Config.OVMFVarsConfig.File = dstFile
}
if v.vmi.Config.EnableDisk {
srcDiskFile, err := filepath.Abs(v.vmi.Config.SrcFile)
if err != nil {
return err
}
sizeGB, err := GetVirtualSizeGB(srcDiskFile)
if err != nil {
return err
}
dstDiskFile := fmt.Sprintf("%s/%s-%s.%s", tmpDir, diskDstName, id, v.vmi.Config.DiskConfig.Format)
sizeArg := fmt.Sprintf("%dG", sizeGB+encryptedPartitionSizeDeltaGB)
cmd := exec.Command(
"qemu-img",
"convert",
"-f", sourceDiskFormat,
"-O", v.vmi.Config.DiskConfig.Format,
srcDiskFile,
dstDiskFile,
)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("qemu-img convert failed: %w: %s", err, string(out))
}
cmd = exec.Command(
"qemu-img",
"resize",
dstDiskFile,
sizeArg,
)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("qemu-img resize failed: %w: %s", err, string(out))
}
v.vmi.Config.DstFile = dstDiskFile
}
exe, args, err := v.executableAndArgs()
if err != nil {
return err
@@ -111,6 +158,14 @@ func (v *qemuVM) Stop() error {
}
}
if v.vmi.Config.EnableDisk {
if v.vmi.Config.DstFile != "" {
if err := os.RemoveAll(v.vmi.Config.DstFile); err != nil {
return fmt.Errorf("failed to remove disk file: %v", err)
}
}
}
done := make(chan error, 1)
go func() {
_, err := v.cmd.Process.Wait()
@@ -156,6 +211,10 @@ func (v *qemuVM) executableAndArgs() (string, []string, error) {
return "", nil, err
}
if err := v.vmi.Config.ValidateBootConfig(); err != nil {
return "", nil, err
}
args := v.vmi.Config.ConstructQemuArgs()
if v.vmi.Config.UseSudo {
@@ -231,3 +290,65 @@ func TDXEnabledOnHost() bool {
return TDXEnabled(string(cpuinfo), string(kernelParam))
}
func GetVirtualSizeBytes(path string) (int64, error) {
cmd := exec.Command("qemu-img", "info", "--output=json", path)
out, err := cmd.Output()
if err != nil {
return 0, fmt.Errorf("qemu-img info failed: %w", err)
}
var info qemuInfo
if err := json.Unmarshal(out, &info); err != nil {
return 0, fmt.Errorf("failed to parse qemu-img JSON: %w", err)
}
if info.VirtualSize <= 0 {
return 0, fmt.Errorf("invalid virtual size: %d", info.VirtualSize)
}
return info.VirtualSize, nil
}
func GetVirtualSizeGB(path string) (int, error) {
bytes, err := GetVirtualSizeBytes(path)
if err != nil {
return 0, err
}
gb := (bytes + (1<<30 - 1)) >> 30
return int(gb), nil
}
// GPUPassthroughAvailable scans for NVIDIA GPU devices bound to the vfio-pci
// driver and returns the BDF of the first one found.
func GPUPassthroughAvailable() (string, bool) {
const vfioPCIPath = "/sys/bus/pci/drivers/vfio-pci"
entries, err := os.ReadDir(vfioPCIPath)
if err != nil {
return "", false
}
for _, entry := range entries {
bdf := entry.Name()
if !strings.Contains(bdf, ":") {
continue
}
vendor, err := os.ReadFile(fmt.Sprintf("/sys/bus/pci/devices/%s/vendor", bdf))
if err != nil || strings.TrimSpace(string(vendor)) != "0x10de" {
continue
}
class, err := os.ReadFile(fmt.Sprintf("/sys/bus/pci/devices/%s/class", bdf))
if err != nil {
continue
}
classStr := strings.TrimSpace(string(class))
// 0x0302xx = 3D Controller (e.g. H100), 0x0300xx = VGA Compatible Controller
if strings.HasPrefix(classStr, "0x0302") || strings.HasPrefix(classStr, "0x0300") {
return bdf, true
}
}
return "", false
}

Some files were not shown because too many files have changed in this diff Show More