Compare commits

..

81 Commits

Author SHA1 Message Date
Drasko DRASKOVIC c641cbebf8 Update ELASTIC reference 2025-07-27 16:48:13 +02:00
Sammy Kerata Oina 4e8057f481 COCOS-460 - Restore test coverage to 65% (#465)
CI / ci (push) Has been cancelled
* Implement IMAMeasurements method in agentSDK and add corresponding unit tests

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

* Add unit tests for NewIMAMeasurements command in CLI

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

* Add error assertion for command execution in NewIMAMeasurements test

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

* Fix nil pointer dereference in Close method and update NewCreateVMCmd logic for manager client initialization

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

* Refactor file permission settings to use octal notation and improve cleanup handling in NewCreateVMCmd test

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

* Add comprehensive unit tests for state machine functionality

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

* Add mock implementation for Algorithm interface and corresponding test cases

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

* Refactor file permission settings to use octal notation in TestStopComputationIntegration

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

* Remove redundant reset test cases from TestStateMachine_Reset

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

* Fix race condition in action call verification in TestStateMachine_HandleEvent

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

* Enhance state machine with reset functionality and improve thread safety in event handling

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

* Improve error handling in state machine start function during tests

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

* Remove concurrent reset and send event test from state machine tests

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

* Remove error logging for Start function in transition tests

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

* Add mock implementations for AgentService_IMAMeasurementsClient and Service Shutdown method; enhance progress tests for IMA measurements handling

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

* Add comprehensive tests for FileStorage functionality including loading, saving, and concurrent access

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

* Enhance tests by adding dataset and algorithm hashes in handleRunReqChunks; improve error handling in TestFileStorage_ErrorHandling cleanup

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

* Enhance TestManagerClient_Process by adding new test cases for Agent state and Disconnect requests; update setupMocks to include grpcClient

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

* Fix graceful shutdown in gRPC server by adding nil checks for health and server instances

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

* Enhance TestAttestation by adding mock expectations for VTpmAttestation and Attestation methods; update service call to include platform parameter

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

* Enhance gRPC Server by adding synchronization for start/stop methods; prevent multiple starts and ensure graceful shutdown

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

* Add unit tests for gRPC server methods including VM creation, removal, and info retrieval

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

* Add tests for SEVSNP and TDX host capabilities; remove unused vsock code

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

* Add a newline for better readability in vm_test.go

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

* Add integration tests for gRPC client in cvm_test.go

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

* Remove unused vsock dependencies and add comprehensive unit tests for GCP attestation functions

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

* Skip GCP tests if credentials are not set

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

* Add tests for error handling in attestation configuration and GCP commands

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

* Improve error handling in Azure VM test response writing

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

* Skip tests in GCP functions if credentials are not set

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

* Add comprehensive unit tests for Azure attestation provider and verifier

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

* Add unit tests for TPM functionality and improve error handling

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

* Add comprehensive tests for attestation functionality and improve error handling

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

* Add validation for teeNonce in TeeAttestation and implement comprehensive tests for provider methods

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

* Refactor error messages in TDX attestation tests for clarity

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

* Fix error message in TeeAttestation test for valid nonce case

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

* Add MeasurementProvider mock and update mockery configuration

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

* Add logging for product in parseUints and rename test functions for clarity

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

* Refactor TestSevsnpverify to reset configuration and improve error logging

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-07-25 15:35:37 +02:00
Sammy Kerata Oina 85a2b7a6c8 COCOS-430 - Refactor gRPC server handlers to use a map for improved organization and add validation for nonce lengths in attestation requests (#477)
* Refactor gRPC server handlers to use a map for improved organization and add validation for nonce lengths in attestation requests

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

* Enhance nonce validation and improve error handling in gRPC server methods

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-07-14 13:46:02 +02:00
Sammy Kerata Oina 45187d7f41 COCOS-454 - Implement graceful shutdown for services and add TTL management for VMs (#473)
* Implement graceful shutdown for services and add TTL management for VMs

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

* Remove unnecessary comment from go-tdx-guest dependency in go.mod

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

* Update manager/api/logging.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add TTL manager initialization in TestStop

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

* Fix logging format in Shutdown method for consistency

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

* Add unit tests for TTL manager functionality

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

* Enhance TTL tests with mutex for thread safety in expiration checks

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

* Add TTL parameter to CreateVM in TestRun for improved testing scenarios

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

* Add Shutdown test to verify VM cleanup and TTL manager integration

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-11 16:21:29 +02:00
dorcaslitunya f543cb4363 COCOS-456 Remove SEV support from repo (#472)
* Remove SEV support from repo

* Remove SEV references
2025-07-10 10:58:36 +02:00
dependabot[bot] cef47baed7 Bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc (#471)
Bumps [go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.61.0 to 0.62.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.61.0...zpages/v0.62.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
  dependency-version: 0.62.0
  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>
2025-07-08 19:32:36 +02:00
Danko Miladinovic 698bd948ed COCOS-474 - New aTLS implementation (#475)
* initial new aTLS

* add CA API call for aTLS
2025-07-08 14:54:57 +02:00
Danko Miladinovic 9c8ddfd2b1 COCOS-439 - TDX Attestation support (#459)
CI / ci (push) Has been cancelled
* inital tdx attestation support

* fix fetching and verification errors

* fix tests

* fix lint problems

* fix reading attestation policy

* add tdx package

* remove Makefile configuration for staticly built binaries
2025-07-01 00:44:29 +02:00
Sammy Kerata Oina 79c66a89c3 NOISSUE - Fix TDX qemu command (#468)
CI / ci (push) Has been cancelled
* Fix condition to check for both SEVSNP and TDX configurations before copying firmware vars file

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

* Fix TDXObject string formatting by removing unnecessary quotes

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-06-27 18:29:31 +02:00
Sammy Kerata Oina f52702b631 NOISSUE - Fix TDXEnabled function to check for 'Y' instead of '1' in kernel parameter (#467)
* Fix TDXEnabled function to check for 'Y' instead of '1' in kernel parameter

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

* Fix TDXEnabled test to check for 'Y' instead of '1' in CPU flags

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-06-27 11:43:07 +02:00
Jovan Djukic 31c7833c3d NOISSUE - Make data paths optional (#466)
CI / ci (push) Has been cancelled
2025-06-25 14:48:55 +02:00
Sammy Kerata Oina 64bf7a56ac NOISSUE - Fix SEVSNPEnabled function to check for 'Y' instead of '1' in kernel parameter (#461)
CI / ci (push) Has been cancelled
* Fix SEVSNPEnabled function to check for 'Y' instead of '1' in kernel parameter

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

* Fix TestSEVSNPEnabled to check for 'Y' instead of '1' in KVM parameter

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-06-23 16:33:17 +02:00
Danko Miladinovic bd59a4a617 NOISSUE - Fix attestation policy to JSON (#453)
CI / ci (push) Has been cancelled
2025-06-17 17:12:28 +02:00
dependabot[bot] c9af8a166b Bump cloud.google.com/go/storage from 1.51.0 to 1.55.0 (#448)
Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.51.0 to 1.55.0.
- [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/spanner/v1.51.0...spanner/v1.55.0)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/storage
  dependency-version: 1.55.0
  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>
2025-06-17 14:50:41 +02:00
dependabot[bot] 17c6accbff Bump github.com/docker/docker (#447)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.0.4+incompatible to 28.2.2+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.0.4...v28.2.2)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.2.2+incompatible
  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>
2025-06-17 14:49:59 +02:00
dependabot[bot] 2d6d276061 Bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc (#450)
Bumps [go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.60.0 to 0.61.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.60.0...zpages/v0.61.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
  dependency-version: 0.61.0
  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>
2025-06-17 14:49:33 +02:00
dependabot[bot] e8c2ccc071 Bump github.com/google/go-tpm from 0.9.3 to 0.9.5 (#451)
Bumps [github.com/google/go-tpm](https://github.com/google/go-tpm) from 0.9.3 to 0.9.5.
- [Release notes](https://github.com/google/go-tpm/releases)
- [Commits](https://github.com/google/go-tpm/compare/v0.9.3...v0.9.5)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm
  dependency-version: 0.9.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>
2025-06-17 14:47:54 +02:00
dependabot[bot] f1af397aa0 Bump google.golang.org/grpc from 1.72.1 to 1.73.0 (#455)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.72.1 to 1.73.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.72.1...v1.73.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.73.0
  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>
2025-06-17 14:47:45 +02:00
dorcaslitunya 77325753f8 NOISSUE - Add TDX support to Manager (#446)
CI / ci (push) Has been cancelled
* Add TDX support on manager

* Add functions to check platform

* Search for tdx in kernel parameters

* Modify based on comments
2025-06-17 11:34:30 +02:00
Danko Miladinovic 3e474338c5 NOISSUE - Extend the PCR16 with computation manifest JSON hash (#457)
CI / ci (push) Has been cancelled
* extend PCR16 with manifest

* fix ci

* fix PCR16 extension

* move pcr extension to executeRun

* put extension in a defer block
2025-06-12 18:49:14 +02:00
Sammy Kerata Oina 5960b06126 NOISSUE - Include context parameter in New and main functions (#452)
CI / ci (push) Has been cancelled
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-06-06 12:00:37 +02:00
Sammy Kerata Oina 636d3dcaa0 NOISSUE - Fix log message in InitializeAgentSDK to improve clarity (#444)
CI / ci (push) Has been cancelled
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-06-04 11:52:23 +02:00
dorcaslitunya 92f4f0535a NOISSUE - Add Intel TDX support for EoS (#445)
CI / checkproto (push) Has been cancelled
CI / ci (push) Has been cancelled
2025-05-30 14:06:05 +02:00
Sammy Kerata Oina bf84f45306 Update Buildroot reference in HAL workflow and README to 2025.05-rc1; add nil check in Stop methods for binary, python, and wasm algorithms (#443)
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-05-29 10:40:23 +02:00
Sammy Kerata Oina f5b67ca35b Fix AzureAttestationToken method to return nil instead of EmbeddedAttestation (#442)
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-05-29 09:31:40 +02:00
Danko Miladinovic 3bb0b2a315 add agent restart policy (#441) 2025-05-28 16:29:03 +02:00
dependabot[bot] 434d58f890 NOISSUE - Bump golang.org/x/crypto from 0.35.0 to 0.38.0 (#427)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.35.0 to 0.38.0.
- [Commits](https://github.com/golang/crypto/compare/v0.35.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.38.0
  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>
2025-05-27 12:43:18 +02:00
dependabot[bot] 32e2bfb881 NOISSUE - Bump go.opentelemetry.io/otel/trace from 1.35.0 to 1.36.0 (#440)
Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.35.0 to 1.36.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.35.0...v1.36.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-version: 1.36.0
  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>
2025-05-27 12:08:05 +02:00
dependabot[bot] 26bf5dc643 NOISSUE - Bump google.golang.org/grpc from 1.71.1 to 1.72.1 (#433)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.71.1 to 1.72.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.71.1...v1.72.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.72.1
  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>
2025-05-26 22:32:17 +02:00
Jovan Djukic bda3968fdf COCOS-160: Enable mTLS when using aTLS (#434)
* added maTLS feature to agent and cli

* added maTLS feature to agent and cli

* added tests and fixed one bug

* fixed according to comment

* fixed test

* fixed
2025-05-26 21:54:15 +02:00
Sammy Kerata Oina 90807d9576 PRISM-821 - Fix attestation policy for azure cvms (#437)
* Refactor attestation handling: update logging messages, adjust command arguments, and enhance provider initialization with MaaURL support

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

* Add default PcrConfig to attestation policy generation

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

* Remove unused validateClaims function and its dependencies from snp.go

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

* Fix GenerateAttestationPolicy: update TCB composition handling and remove unused minimalTCB assignment

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

* Refactor vtpm provider initialization: remove unused MaaURL parameter and update related function calls

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-05-22 17:33:10 +02:00
dorcaslitunya 94c169febb COCOS-394 Cloud Provider Attestation Service Integration (#421)
* Add token measurement command

Add Azure cloud attestation fetching

Add ability to fetch azure attestation token

Remove gcp changes

Remove gcp changes

Add Azure attestation support

Modify pipeline proto checks

Update protoc version

Fix failing CI

fetch token as a file

Convert jwt to json

Small bug fix -- correct file name for attestation token

Fix failing CI

Modify protoc version

Update protoc version

Update protoc version

Update protoc version

Add changes to allow passing vtpm nonce

Add PR review changes to refactor the code

Refactor name change to AttestationResult

Refactor name change to AttestationResult

Return report as json

Format files properly

Fix attestaton changes

Modify changes based on PR review

Add more test coverage

Correct bug in Server test

Rename "FetchAttestationResult" to "AttestationResult"

Send token as part of stream

Fix CI

NOISSUE -  Add DisconnectReq message and TTL support for VM creation (#428)

* feat: Add DisconnectReq message and TTL support for VM creation

- Introduced DisconnectReq message in cvms.proto to handle disconnection requests.
- Enhanced CreateReq in manager.proto to include a TTL field for virtual machines.
- Updated CLI to accept TTL as a command-line flag during VM creation.
- Modified manager service to remove VMs after the specified TTL duration.
- Adjusted gRPC client connection handling in agent main.go to support new client structure.
- Added mock implementation for gRPC client to facilitate testing.

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

* fix: Mark server URL flag as required with error handling

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

---------

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

COCOS-407 - Add support for Linux IMA (#429)

* Added a feature which enables users to fetch IMA measurements and verify them

* Added a feature which enables users to fetch IMA measurements and verify them

* fixed lint error

* fixed according to comments

* fixed according to comments

* fixed according to comments

* fixed according to comments

* final bug fix

Add token measurement command

Add Azure cloud attestation fetching

Add ability to fetch azure attestation token

Remove gcp changes

Remove gcp changes

Add Azure attestation support

Modify pipeline proto checks

Update protoc version

Fix failing CI

fetch token as a file

Convert jwt to json

Small bug fix -- correct file name for attestation token

Fix failing CI

Modify protoc version

Update protoc version

Update protoc version

Update protoc version

Add changes to allow passing vtpm nonce

Add PR review changes to refactor the code

Refactor name change to AttestationResult

Refactor name change to AttestationResult

Return report as json

Format files properly

Fix attestaton changes

Modify changes based on PR review

Add more test coverage

Correct bug in Server test

Rename "FetchAttestationResult" to "AttestationResult"

Send token as part of stream

Fix CI

Rebase changes to main

Refactor after rebase

* Add Azure attestation

* COCOS-395 - Cloud Provider Firmware Integration (#415)

* add CC platform identification capability

* add token verification

* add snp azure

* add azure snp report verification

* fix linter errors

* fix agent tests

* expand the CC provider

* fix azure atls

* rebase branch

* add nonce check for azure token

* rename package attestations

* remove alias attestations

---------

Co-authored-by: Ubuntu <azureuser@UVCTestCVM.bu0p0zdolasezg1jifpyqhaxuc.dx.internal.cloudapp.net>

* Add token measurement command

Add Azure cloud attestation fetching

Add ability to fetch azure attestation token

Remove gcp changes

Remove gcp changes

Add Azure attestation support

Modify pipeline proto checks

Update protoc version

Fix failing CI

fetch token as a file

Convert jwt to json

Small bug fix -- correct file name for attestation token

Fix failing CI

Modify protoc version

Update protoc version

Update protoc version

Update protoc version

Add changes to allow passing vtpm nonce

Add PR review changes to refactor the code

Refactor name change to AttestationResult

Refactor name change to AttestationResult

Return report as json

Format files properly

Fix attestaton changes

Modify changes based on PR review

Add more test coverage

Correct bug in Server test

Rename "FetchAttestationResult" to "AttestationResult"

Send token as part of stream

Fix CI

NOISSUE -  Add DisconnectReq message and TTL support for VM creation (#428)

* feat: Add DisconnectReq message and TTL support for VM creation

- Introduced DisconnectReq message in cvms.proto to handle disconnection requests.
- Enhanced CreateReq in manager.proto to include a TTL field for virtual machines.
- Updated CLI to accept TTL as a command-line flag during VM creation.
- Modified manager service to remove VMs after the specified TTL duration.
- Adjusted gRPC client connection handling in agent main.go to support new client structure.
- Added mock implementation for gRPC client to facilitate testing.

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

* fix: Mark server URL flag as required with error handling

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

---------

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

COCOS-407 - Add support for Linux IMA (#429)

* Added a feature which enables users to fetch IMA measurements and verify them

* Added a feature which enables users to fetch IMA measurements and verify them

* fixed lint error

* fixed according to comments

* fixed according to comments

* fixed according to comments

* fixed according to comments

* final bug fix

Add token measurement command

Add Azure cloud attestation fetching

Add ability to fetch azure attestation token

Remove gcp changes

Remove gcp changes

Add Azure attestation support

Modify pipeline proto checks

Update protoc version

Fix failing CI

fetch token as a file

Convert jwt to json

Small bug fix -- correct file name for attestation token

Fix failing CI

Modify protoc version

Update protoc version

Update protoc version

Update protoc version

Add changes to allow passing vtpm nonce

Add PR review changes to refactor the code

Refactor name change to AttestationResult

Refactor name change to AttestationResult

Return report as json

Format files properly

Fix attestaton changes

Modify changes based on PR review

Add more test coverage

Correct bug in Server test

Rename "FetchAttestationResult" to "AttestationResult"

Send token as part of stream

Fix CI

Rebase changes to main

Refactor after rebase

* Rebase with main

* Modify tests to accomodate changes

* Use env vars appropriately

* Use env vars appropriately

* Use caps in err name

---------

Co-authored-by: Danko Miladinovic <72250944+danko-miladinovic@users.noreply.github.com>
Co-authored-by: Ubuntu <azureuser@UVCTestCVM.bu0p0zdolasezg1jifpyqhaxuc.dx.internal.cloudapp.net>
2025-05-21 12:01:49 +02:00
Danko Miladinovic 3102114ff3 COCOS-395 - Cloud Provider Firmware Integration (#415)
* add CC platform identification capability

* add token verification

* add snp azure

* add azure snp report verification

* fix linter errors

* fix agent tests

* expand the CC provider

* fix azure atls

* rebase branch

* add nonce check for azure token

* rename package attestations

* remove alias attestations

---------

Co-authored-by: Ubuntu <azureuser@UVCTestCVM.bu0p0zdolasezg1jifpyqhaxuc.dx.internal.cloudapp.net>
2025-05-19 16:42:39 +02:00
Jovan Djukic 5c60bc2a48 COCOS-407 - Add support for Linux IMA (#429)
* Added a feature which enables users to fetch IMA measurements and verify them

* Added a feature which enables users to fetch IMA measurements and verify them

* fixed lint error

* fixed according to comments

* fixed according to comments

* fixed according to comments

* fixed according to comments

* final bug fix
2025-05-14 13:03:56 +02:00
Sammy Kerata Oina 93f2f2ab46 NOISSUE - Add DisconnectReq message and TTL support for VM creation (#428)
* feat: Add DisconnectReq message and TTL support for VM creation

- Introduced DisconnectReq message in cvms.proto to handle disconnection requests.
- Enhanced CreateReq in manager.proto to include a TTL field for virtual machines.
- Updated CLI to accept TTL as a command-line flag during VM creation.
- Modified manager service to remove VMs after the specified TTL duration.
- Adjusted gRPC client connection handling in agent main.go to support new client structure.
- Added mock implementation for gRPC client to facilitate testing.

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

* fix: Mark server URL flag as required with error handling

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-05-09 12:10:01 +02:00
Sammy Kerata Oina dffefc45d8 NOISSUE - Change logging level from Info to Debug for State method (#422)
CI / ci (push) Has been cancelled
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-04-16 10:11:29 +02:00
Dusan Borovcanin 9a5cc44b2a NOISSUE - Add EU support banner
CI / ci (push) Has been cancelled
Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com>
2025-04-07 12:50:27 +02:00
Dusan Borovcanin 676c5f154c NOISSUE - Add EU support banner
Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com>
2025-04-07 12:48:55 +02:00
Dusan Borovcanin 71104a210c NOISSUE - Add EU support banner
Signed-off-by: Dusan Borovcanin <borovcanindusan1@gmail.com>
2025-04-07 12:47:00 +02:00
Jovan Djukic ec306c72b9 COCOS-397 - Agent certificate generation via CA service (#410)
CI / checkproto (push) Has been cancelled
CI / ci (push) Has been cancelled
* Initial commit, will be tested before creating a PR

* Initial commit, will be tested before creating a PR

* Fixed all issues

* Initial commit, will be tested before creating a PR

* Updated agent docs

* Fixed based on comments

* Fixed based on comments

* Initial commit, will be tested before creating a PR

* Updated agent docs

* Fixed based on comments

* Fixed based on comments

* added certificate verification

* Initial commit, will be tested before creating a PR

* Fixed all issues

* Initial commit, will be tested before creating a PR

* Initial commit, will be tested before creating a PR

* Updated agent docs

* Fixed based on comments

* Fixed based on comments

* added certificate verification

* Fixed rebase errors

* Fixed proto issues

* fixed proto issues

* Fixed format error

* Fixed based on comments

* NOISSUE - Simplify local agent running in non sev-snp environment (#411)

* Add vtpm attestation support to agent service and server

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

* Update mockery version to v2.53.2 and refactor VM factory to include logger

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

* Send event notification when computation is stopped in agentService

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

* Remove redundant assignment of Stderr in qemuVM Start method

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

* Rename SVM references to CVM in tracing, logging, metrics, and service layers

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

---------

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

* Bump github.com/docker/docker (#416)

Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.0.1+incompatible to 28.0.4+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.0.1...v28.0.4)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  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>

* Bump google.golang.org/protobuf from 1.36.5 to 1.36.6 (#412)

Bumps google.golang.org/protobuf from 1.36.5 to 1.36.6.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  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>

* COCOS-393 - Disable SSH service and update user shell in cloud config (#396)

* Disable SSH service and update user shell in cloud config

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

* Remove SSH server and clean up dependencies in cloud config

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

* Add firewall configuration and ensure iptables rules persist after reboot

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

* Add algo_user configuration and setup script for container execution

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

---------

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

* Initial commit, will be tested before creating a PR

* Fixed all issues

* Initial commit, will be tested before creating a PR

* Initial commit, will be tested before creating a PR

* Fixed based on comments

* Fixed based on comments

* added certificate verification

* Initial commit, will be tested before creating a PR

* Fixed all issues

* Initial commit, will be tested before creating a PR

* Initial commit, will be tested before creating a PR

* Fixed based on comments

* Fixed rebase errors

* Fixed format error

* Fixed based on comments

* Fixed rebase errors

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Sammy Kerata Oina <44265300+SammyOina@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-02 16:52:57 +02:00
Sammy Kerata Oina 12a20c74f7 NOISSUE - Reset agent state machine when computation is stopped (#417)
* Bump mockery version to 2.53.3 in generated mock files

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

* Update golangci-lint action to v7 and bump version to v1.64.7

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

* Update golangci-lint action to v7 and configure linters in .golangci.yaml

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-04-02 14:36:35 +02:00
Sammy Kerata Oina d17eba35eb COCOS-393 - Disable SSH service and update user shell in cloud config (#396)
* Disable SSH service and update user shell in cloud config

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

* Remove SSH server and clean up dependencies in cloud config

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

* Add firewall configuration and ensure iptables rules persist after reboot

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

* Add algo_user configuration and setup script for container execution

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-04-01 22:00:23 +02:00
dependabot[bot] cab2799741 Bump google.golang.org/protobuf from 1.36.5 to 1.36.6 (#412)
Bumps google.golang.org/protobuf from 1.36.5 to 1.36.6.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  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>
2025-04-01 21:59:48 +02:00
dependabot[bot] e217331e5e Bump github.com/docker/docker (#416)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.0.1+incompatible to 28.0.4+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.0.1...v28.0.4)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  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>
2025-04-01 21:59:39 +02:00
Sammy Kerata Oina 7e63921896 NOISSUE - Simplify local agent running in non sev-snp environment (#411)
* Add vtpm attestation support to agent service and server

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

* Update mockery version to v2.53.2 and refactor VM factory to include logger

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

* Send event notification when computation is stopped in agentService

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

* Remove redundant assignment of Stderr in qemuVM Start method

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

* Rename SVM references to CVM in tracing, logging, metrics, and service layers

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-04-01 21:59:11 +02:00
Sammy Kerata Oina ebe119686f NOISSUE - Fix nil pointer dereference in Stop methods for agentServer and agentService (#409)
CI / checkproto (push) Has been cancelled
CI / ci (push) Has been cancelled
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-03-19 11:44:21 +01:00
dependabot[bot] 3fe7507529 Bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc (#401)
Bumps [go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.59.0 to 0.60.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.59.0...zpages/v0.60.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
  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>
2025-03-19 11:38:04 +01:00
dependabot[bot] 1b4a510eb2 Bump go.opentelemetry.io/otel/trace from 1.34.0 to 1.35.0 (#402)
Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.34.0 to 1.35.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  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>
2025-03-19 11:34:22 +01:00
dependabot[bot] ff0fd71e7d Bump golang.org/x/crypto from 0.35.0 to 0.36.0 (#400)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/crypto/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  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>
2025-03-19 11:32:05 +01:00
Danko Miladinovic bfaa489bdb NOISSUE - Change the CLI SNP verification (#406) 2025-03-19 11:30:49 +01:00
Sammy Kerata Oina c14f1d7b6c COCOS-391- GCP Attestation policy (#405)
* Add AgentGrpcHost configuration to agent server

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

* Add SHA1 support to PcrValues and implement GCP attestation functions

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

* Add GCP attestation policy and OVMF download commands

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

* Add vTPM attestation support and update protobuf versions

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

* Remove Host field from AgentConfig and update related references

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

* Update GCP attestation policy to accept vCPU count as an argument

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

* Add SHA512 digest verification for OVMF file in GCP download command

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

* Update OVMF object name format in GCP attestation package

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

* Refactor attestation policy structure to use nested Config field

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-03-19 09:39:46 +01:00
Danko Miladinovic ebc8f1bba4 NOISSUE - Update documentation for vTPM changes (#408)
* change readme according to vTPM changes

* rebase
2025-03-19 09:38:48 +01:00
Danko Miladinovic 293c65a3aa NOISSUE - Add path to expected PCR values (#398)
CI / ci (push) Has been cancelled
Rust CI Pipeline / rust-check (push) Has been cancelled
* add path to expected PCR values

* change rust scrtip for attestation policy to print policy to stdout

* fix cli test

* remove stdout from cmd config struct

* fix manager test

* fix manager readme
2025-03-17 12:25:42 +01:00
Jovan Djukic 33744a12a8 COCOS-390 - Add IGVM measurement on manager (#404)
CI / ci (push) Has been cancelled
* resolved issue 390

* updated readme.md for issue 390

* resolved issue 390

* updated readme.md for issue 390

* implemented suggested changes

* refactored code so it passes the linter test

* change the Run fn so it prints the meassurement t a buffer

* refactored code so it passes the linter test

* fixed the test Run_-_Failure_Execution

* changed recipe so it builds igvmmeasure binary when building manager
2025-03-12 13:43:26 +01:00
dorcaslitunya 4bb732ebf9 Add igvm measurement (#379)
Add copyright information to package

Add testing to igvm measurements

Remove trailing white space

Improve testing

Resolve PR comments

Add measure to cli

Add README  for feature

Fix PR comments

Added new line to shell script

Add measurement interface

Fix ci

Refactor code for IgvmMeasurement to become a CLI dependency

Refactor code for IgvmMeasurement to become a CLI dependency

Refactor based on ci failures

Fix error handling

Add header

Fix ci
2025-03-12 10:24:51 +01:00
Danko Miladinovic 67f939fc66 COCOS-326 - Add vTPM support to CoCoS (#376)
CI / checkproto (push) Has been cancelled
CI / ci (push) Has been cancelled
Rust CI Pipeline / rust-check (push) Has been cancelled
* manager, cli and agent vtpm support

* rebase and changed atls for vtpm

* deleted unused code

* changed chekproto.yaml script so it find the manager proto file correctly

* fixe manager proto version

* fix agent tests

* fix server agent test

* fix attestation test

* fix attestation test gofumpt

* created dummy RWC for TPM

* fix comment

* add default PCR values

* rebase main

* fix rust ci and missing header

* changed embedded  attestation to VMPL 2

* fix unused impot

* fix pkg test

* address attestation type

* fix agent attestation test

* add prc15 check

* fix comments

* fix cli tests

* add doc

* add mock for LeveledQuoteProvider when SEV-SNP device is not found

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

* fix manager reading attestation policy

* refactor PCR value checks and update attestation policy values

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

* fix tests for sev and grpc

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
Co-authored-by: Sammy Oina <sammyoina@gmail.com>
2025-03-07 16:36:47 +01:00
dependabot[bot] fa26573643 NOISSUE - Bump github.com/docker/docker (#387)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.0.0+incompatible to 28.0.1+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.0.0...v28.0.1)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  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>
2025-03-05 13:08:55 +01:00
Sammy Kerata Oina 7d063aa48d NOISSUE - Dependabot docker fix (#386)
* Bump github.com/docker/docker

Bumps [github.com/docker/docker](https://github.com/docker/docker) from 27.5.1+incompatible to 28.0.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v27.5.1...v28.0.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update ImageLoad call to use client.ImageLoadWithQuiet option

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-27 10:04:18 +01:00
dependabot[bot] 12bd19b4c6 Bump golang.org/x/crypto from 0.32.0 to 0.35.0 (#383)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.32.0 to 0.35.0.
- [Commits](https://github.com/golang/crypto/compare/v0.32.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  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>
2025-02-26 15:16:24 +01:00
dependabot[bot] 1210e238b4 Bump golang.org/x/term from 0.28.0 to 0.29.0 (#382)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.28.0 to 0.29.0.
- [Commits](https://github.com/golang/term/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  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>
2025-02-26 15:16:15 +01:00
dependabot[bot] 93f9c4df1c Bump github.com/spf13/cobra from 1.8.1 to 1.9.1 (#384)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.1 to 1.9.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.8.1...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  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>
2025-02-26 15:16:01 +01:00
Sammy Kerata Oina 913c6138cf NOISSUE - Update Dependabot configuration and Go dependencies (#378)
* Update Dependabot configuration and Go dependencies; modify test command to include embed tag

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

* Refactor CVMSClient computation handling and improve test message queue capacity

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

* Refactor TestManagerClient_Process to remove buffer from messageQueue and ensure proper handling of messages

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

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-02-26 14:04:09 +01:00
Sammy Kerata Oina c6308f080b NOISSUE - Downgrade go for buildroot supported version (#380)
* Update Go version to 1.23.2 in go.mod

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

* Update sev-snp-measure-go dependency to v0.0.0-20241202151803-ef189f0ff825

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

* Update agent README to reflect new gRPC configuration variables and default log level

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

* Refactor CVMSClient computation handling and improve test case structure

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

* Refactor messageQueue channel in TestManagerClient_Process for improved concurrency handling

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-02-26 14:02:19 +01:00
Sammy Kerata Oina cef9cbbecd COCOS-364 - Make agent more resilient to gRPC disconnection on cvms cloud server (#375)
CI / ci (push) Has been cancelled
* Refactor AgentServer interface and update related implementations; adjust dependency versions in go.mod

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

* Implement State method for agent.Service and enhance metrics and logging middleware to track state changes

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

* Update file permission modes to use octal notation in CVMS client and agent main

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

* Refactor CVMS client and agent main to improve function signatures and variable naming for clarity

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

* Remove unnecessary fmt import and logging statement in CVMSClient Process method

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

* Add file-based storage implementation for message persistence in CVMSClient

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

* Update README and main.go for CVMS service: correct references and improve clarity

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

* Update README to clarify gRPC host and port descriptions for CVMS server

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

* Rename sendMessageWithRetry to sendStreamMessage for clarity and consistency in CVMSClient

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

* Update README to add detailed descriptions for algorithm and dataset paths

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-02-16 18:02:17 +01:00
Sammy Kerata Oina 7e17a00cb5 COCOS-369 - Enable compiling gRPC clients without cGo (#372)
CI / ci (push) Has been cancelled
* Add atls module and update import paths for consistency

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

* Add uuid dependency to go.work.sum

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

* Update go.mod and go.sum to downgrade go-sev-guest to v0.11.1 and add pborman/uuid v1.2.1

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

* Refactor ATLS implementation: remove old atls module, add new grpc/atls functionality, and update related configurations

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

* Update mockery configuration and enhance QuoteProvider mock with additional methods

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

* Update error message in TestAgentClientIntegration for clarity

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-02-13 11:04:29 +01:00
dependabot[bot] 0e3c0c04af Bump the go-dependency group across 1 directory with 11 updates (#373)
CI / ci (push) Waiting to run
Bumps the go-dependency group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/caarlos0/env/v11](https://github.com/caarlos0/env) | `11.2.2` | `11.3.1` |
| [github.com/google/go-sev-guest](https://github.com/google/go-sev-guest) | `0.11.1` | `0.12.1` |
| [github.com/spf13/pflag](https://github.com/spf13/pflag) | `1.0.5` | `1.0.6` |
| [go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc](https://github.com/open-telemetry/opentelemetry-go-contrib) | `0.57.0` | `0.59.0` |
| [golang.org/x/sync](https://github.com/golang/sync) | `0.10.0` | `0.11.0` |
| [github.com/docker/docker](https://github.com/docker/docker) | `27.4.0+incompatible` | `27.5.1+incompatible` |



Updates `github.com/caarlos0/env/v11` from 11.2.2 to 11.3.1
- [Release notes](https://github.com/caarlos0/env/releases)
- [Changelog](https://github.com/caarlos0/env/blob/main/.goreleaser.yml)
- [Commits](https://github.com/caarlos0/env/compare/v11.2.2...v11.3.1)

Updates `github.com/google/go-sev-guest` from 0.11.1 to 0.12.1
- [Release notes](https://github.com/google/go-sev-guest/releases)
- [Changelog](https://github.com/google/go-sev-guest/blob/main/.goreleaser.yaml)
- [Commits](https://github.com/google/go-sev-guest/compare/v0.11.1...v0.12.1)

Updates `github.com/spf13/pflag` from 1.0.5 to 1.0.6
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.5...v1.0.6)

Updates `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` from 0.57.0 to 0.59.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.57.0...zpages/v0.59.0)

Updates `go.opentelemetry.io/otel/trace` from 1.32.0 to 1.34.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.32.0...v1.34.0)

Updates `golang.org/x/crypto` from 0.30.0 to 0.32.0
- [Commits](https://github.com/golang/crypto/compare/v0.30.0...v0.32.0)

Updates `golang.org/x/sync` from 0.10.0 to 0.11.0
- [Commits](https://github.com/golang/sync/compare/v0.10.0...v0.11.0)

Updates `google.golang.org/grpc` from 1.68.1 to 1.69.4
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.68.1...v1.69.4)

Updates `google.golang.org/protobuf` from 1.35.2 to 1.36.3

Updates `github.com/docker/docker` from 27.4.0+incompatible to 27.5.1+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v27.4.0...v27.5.1)

Updates `golang.org/x/term` from 0.27.0 to 0.28.0
- [Commits](https://github.com/golang/term/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: github.com/caarlos0/env/v11
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: github.com/google/go-sev-guest
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: github.com/spf13/pflag
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependency
- dependency-name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-12 09:04:38 +01:00
dorcaslitunya bb0ad293e6 COCOS-363 - Add verification of vTPM attestation to CLI (#363)
CI / ci (push) Waiting to run
* CLI attestation changes

CLI attestation changes

Add all modes to CLI changes updates

Remove old attestation validateion command

Make updates based on PR reviews

Disable depreciated linter

Linter fixes

Resolve conflicts

resolve go.mod errors

Use previous version of go-sev-guest

Modify tests

Remove tdx attestation

Fix error messages printing

Add tests

Fix CI failures

Do proper error handling

* Fix CI failures

* Add string constants
2025-02-11 21:08:04 +01:00
dorcaslitunya 3e99214d2a NOISSUE - Modify buildroot config to enable vTPM attestations (#370)
CI / ci (push) Has been cancelled
* Modify buildroot config to enable vTPM attestations

* Add kernel changes

* Formatting changes

* Modify go-sev-guest version

* Uncomment out kernel version
2025-02-10 15:36:34 +01:00
dependabot[bot] 132bfdf76a NOISSUE - Bump the go-dependency group across 1 directory with 10 updates (#366)
CI / ci (push) Has been cancelled
Bumps the go-dependency group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/caarlos0/env/v11](https://github.com/caarlos0/env) | `11.2.2` | `11.3.1` |
| [github.com/google/go-sev-guest](https://github.com/google/go-sev-guest) | `0.11.1` | `0.12.1` |
| [github.com/spf13/pflag](https://github.com/spf13/pflag) | `1.0.5` | `1.0.6` |
| [go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc](https://github.com/open-telemetry/opentelemetry-go-contrib) | `0.57.0` | `0.59.0` |
| [github.com/docker/docker](https://github.com/docker/docker) | `27.4.0+incompatible` | `27.5.1+incompatible` |



Updates `github.com/caarlos0/env/v11` from 11.2.2 to 11.3.1
- [Release notes](https://github.com/caarlos0/env/releases)
- [Changelog](https://github.com/caarlos0/env/blob/main/.goreleaser.yml)
- [Commits](https://github.com/caarlos0/env/compare/v11.2.2...v11.3.1)

Updates `github.com/google/go-sev-guest` from 0.11.1 to 0.12.1
- [Release notes](https://github.com/google/go-sev-guest/releases)
- [Changelog](https://github.com/google/go-sev-guest/blob/main/.goreleaser.yaml)
- [Commits](https://github.com/google/go-sev-guest/compare/v0.11.1...v0.12.1)

Updates `github.com/spf13/pflag` from 1.0.5 to 1.0.6
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.5...v1.0.6)

Updates `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` from 0.57.0 to 0.59.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.57.0...zpages/v0.59.0)

Updates `go.opentelemetry.io/otel/trace` from 1.32.0 to 1.34.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.32.0...v1.34.0)

Updates `golang.org/x/crypto` from 0.30.0 to 0.32.0
- [Commits](https://github.com/golang/crypto/compare/v0.30.0...v0.32.0)

Updates `google.golang.org/grpc` from 1.68.1 to 1.69.4
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.68.1...v1.69.4)

Updates `google.golang.org/protobuf` from 1.35.2 to 1.36.3

Updates `github.com/docker/docker` from 27.4.0+incompatible to 27.5.1+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v27.4.0...v27.5.1)

Updates `golang.org/x/term` from 0.27.0 to 0.28.0
- [Commits](https://github.com/golang/term/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: github.com/caarlos0/env/v11
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: github.com/google/go-sev-guest
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: github.com/spf13/pflag
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependency
- dependency-name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-04 15:06:23 +01:00
Washington Kigani Kamadi 51f2a02e4a NOISSUE - Update env for new manager deployment (#367)
CI / ci (push) Waiting to run
* fix env

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* update kernel and rootfs location

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* update manager host

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

---------

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>
2025-02-04 14:25:02 +01:00
Smith Jilks da88fe1e45 COCOS-346 - Explore cloud init for Cloud setup (#357)
CI / ci (push) Has been cancelled
Rust CI Pipeline / rust-check (push) Has been cancelled
* Add qemu cloud init

Signed-off-by: Jilks Smith <smithjilks@gmail.com>

* Update qemu cloud init

Signed-off-by: Jilks Smith <smithjilks@gmail.com>

* Add qemu cloud init

Signed-off-by: Jilks Smith <smithjilks@gmail.com>

* Update qemu cloud init

Signed-off-by: Jilks Smith <smithjilks@gmail.com>

* Update qemu cloud config

* Update cloud init

Signed-off-by: Jilks Smith <smithjilks@gmail.com>

* Update cloud init

Signed-off-by: Jilks Smith <smithjilks@gmail.com>

* Add cloud init README.md

Signed-off-by: Jilks Smith <smithjilks@gmail.com>

* Add cocos release workflow

Signed-off-by: Jilks Smith <smithjilks@gmail.com>

---------

Signed-off-by: Jilks Smith <smithjilks@gmail.com>
2025-01-31 15:48:26 +01:00
dependabot[bot] 5969ae3bcb NOISSUE - Update SEV requirement (#330)
Updates the requirements on [sev](https://github.com/virtee/sev) to permit the latest version.

Updates `sev` to 5.0.0
- [Commits](https://github.com/virtee/sev/compare/v4.0.0...v5.0.0)

---
updated-dependencies:
- dependency-name: sev
  dependency-type: direct:production
  dependency-group: rs-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-31 15:47:12 +01:00
Sammy Kerata Oina b5c65f6c3f Update agent CVM gRPC certificate keys for consistency (#361)
CI / ci (push) Has been cancelled
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-01-29 12:25:21 +01:00
Washington Kigani Kamadi 5bc7eb2c8a Add manager service client mocks (#359)
CI / ci (push) Has been cancelled
Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>
2025-01-27 09:49:25 +01:00
Sammy Kerata Oina 58b401e0de Update dependency for sev-snp-measure-go to latest version (#358)
CI / checkproto (push) Has been cancelled
CI / ci (push) Has been cancelled
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-01-21 15:19:41 +01:00
Sammy Kerata Oina 881aaaab0f NOISSUE - Set env automatically (#355)
* new agent structure

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

* fix lint

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

* fix tests

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

* cvm tests fix

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

* fix test

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

* add cli and test

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

* restore result cli

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

* fix tests

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

* pass certs and env

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

* update go

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

* downgrade

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

* downgrade again

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

* simplify

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

* simplify

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

* configure cvms

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

* remove unused gRPC API files and server implementation

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

* refactor: use constants for CLI command flags and environment variables

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-01-20 13:46:18 +01:00
Sammy Kerata Oina 1f32f516b0 NOISSUE - Simplify manager to vm provision only (#353)
* new agent structure

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

* fix lint

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

* fix tests

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

* cvm tests fix

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

* fix test

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

* manager server, for vm provisioning

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

* fix lint

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

* add cli and test

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

* restore result cli

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

* fix tests

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

* fix failing tests

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

* fix failing test

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

* refactor: remove context from docker struct and use local context in Run method

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

* delete: remove unused gRPC API and related server implementation

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-01-20 11:56:18 +01:00
Sammy Kerata Oina ecad6514f3 COCOS-344 - New agent structure (#350)
CI / checkproto (push) Has been cancelled
CI / ci (push) Has been cancelled
* new agent structure

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

* minor fixes and testing

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

* fix lint

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

* fix tests

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

* cvm tests fix

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

* fix test

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

* fix cli test

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

* rename

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

* rename cvm to cvms plural

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

* rename service

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

* fix tests

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

* remove context

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

* refactor: reorder parameters in NewAlgorithm functions and update CVMClient to CVMSClient

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

* fix(tests): update SendEvent mock to include an additional parameter

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

* move expectations

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

* fix(tests): move event initialization to the correct scope in service tests

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

* fix(tests): update SendEvent mock to use EXPECT instead of On in service tests

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

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-01-17 12:50:53 +01:00
Drasko DRASKOVIC 59b8057e5c Update README.md (#348) 2024-12-31 01:12:01 +01:00
dorcaslitunya 961f8025ca Update README.md (#341) 2024-12-16 15:54:45 +01:00
Sammy Kerata Oina 35c09be0d9 fix test (#335)
Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2024-12-16 15:52:59 +01:00
200 changed files with 24788 additions and 9581 deletions
-4
View File
@@ -16,7 +16,3 @@ updates:
schedule:
interval: "weekly"
day: "monday"
groups:
go-dependency:
patterns:
- "*"
+5 -4
View File
@@ -9,6 +9,7 @@ on:
- "pkg/manager/*.pb.go"
- "agent/agent.proto"
- "agent/*.pb.go"
pull_request:
branches:
- main
@@ -33,8 +34,8 @@ jobs:
- name: Set up protoc
run: |
PROTOC_VERSION=28.1
PROTOC_GEN_VERSION=v1.34.2
PROTOC_VERSION=29.0
PROTOC_GEN_VERSION=v1.36.5
PROTOC_GRPC_VERSION=v1.5.1
# Download and install protoc
@@ -55,7 +56,7 @@ jobs:
- name: Set up Cocos-AI
run: |
# Rename .pb.go files to .pb.go.tmp to prevent conflicts
for p in $(ls pkg/manager/*.pb.go); do
for p in $(ls manager/*.pb.go); do
mv $p $p.tmp
done
@@ -67,7 +68,7 @@ jobs:
make protoc
# Compare generated Go files with the original ones
for p in $(ls pkg/manager/*.pb.go); do
for p in $(ls manager/*.pb.go); do
if ! cmp -s $p $p.tmp; then
echo "Proto file and generated Go file $p are out of sync!"
exit 1
+16 -9
View File
@@ -1,9 +1,9 @@
name: Build and Release
name: Build and Release Hal
on:
push:
tags:
- '*'
- "*"
jobs:
build:
@@ -32,8 +32,8 @@ jobs:
with:
root-reserve-mb: 35000
swap-size-mb: 1024
remove-dotnet: 'true'
remove-android: 'true'
remove-dotnet: "true"
remove-android: "true"
- name: Check free space
run: |
echo "Free space:"
@@ -48,26 +48,33 @@ jobs:
- name: Checkout cocos
uses: actions/checkout@v4
with:
repository: 'ultravioletrs/cocos'
repository: "ultravioletrs/cocos"
path: cocos
- name: Checkout buildroot
uses: actions/checkout@v4
with:
repository: 'buildroot/buildroot'
repository: "buildroot/buildroot"
path: buildroot
ref: 2024.11-rc2
ref: 2025.05-rc1
- name: Build
- name: Build hal
run: |
cd buildroot
make BR2_EXTERNAL=../cocos/hal/linux cocos_defconfig
make
- name: Build cocos
run: |
cd cocos
make
- name: Release
uses: softprops/action-gh-release@v2
with:
files: |
buildroot/output/images/bzImage
buildroot/output/images/rootfs.cpio.gz
cocos/build/cocos-agent
cocos/build/cocos-cli
cocos/build/cocos-manager
+3 -2
View File
@@ -22,10 +22,11 @@ jobs:
go-version: 1.23.x
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
uses: golangci/golangci-lint-action@v7
with:
version: v1.60
version: v2.0.2
- name: Build
run: |
make
+83 -63
View File
@@ -1,80 +1,100 @@
run:
timeout: 3m
issues:
max-issues-per-linter: 10
max-same-issues: 10
linters-settings:
importas:
no-unaliased: true
no-extra-aliases: false
alias:
- pkg: github.com/absmach/magistrala/logger
alias: mglog
gocritic:
enabled-checks:
- dupImport
- importShadow
- httpNoBody
- paramTypeCombine
- emptyStringTest
- builtinShadow
- exposedSyncMutex
disabled-checks:
- appendAssign
enabled-tags:
- diagnostic
disabled-tags:
- performance
- style
- experimental
- opinionated
stylecheck:
checks: ["-ST1000", "-ST1003", "-ST1020", "-ST1021", "-ST1022"]
goheader:
template: |-
Copyright (c) Ultraviolet
SPDX-License-Identifier: Apache-2.0
version: "2"
linters:
disable-all: true
default: none
enable:
- importas
- gocritic
- gosimple
- errcheck
- govet
- unused
- goconst
- godot
- godox
- ineffassign
- misspell
- stylecheck
- whitespace
- gci
- gofmt
- goimports
- loggercheck
- goheader
- asasalint
- asciicheck
- bidichk
- contextcheck
- copyloopvar
- decorder
- dogsled
- dupword
- errcheck
- errchkjson
- errname
- execinquery
- copyloopvar
- ginkgolinter
- gocheckcompilerdirectives
- gofumpt
- goconst
- gocritic
- godot
- godox
- goheader
- goprintffuncname
- govet
- importas
- ineffassign
- loggercheck
- makezero
- mirror
- misspell
- nakedret
- dupword
- staticcheck
- unused
- whitespace
settings:
gocritic:
enabled-checks:
- dupImport
- importShadow
- httpNoBody
- paramTypeCombine
- emptyStringTest
- builtinShadow
- exposedSyncMutex
disabled-checks:
- appendAssign
enabled-tags:
- diagnostic
disabled-tags:
- performance
- style
- experimental
- opinionated
goheader:
template: |-
Copyright (c) Ultraviolet
SPDX-License-Identifier: Apache-2.0
importas:
alias:
- pkg: github.com/absmach/magistrala/logger
alias: mglog
no-unaliased: true
no-extra-aliases: false
staticcheck:
checks:
- -ST1000
- -ST1003
- -ST1020
- -ST1021
- -ST1022
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- makezero
text: with non-zero initialized length
paths:
- third_party$
- builtin$
- examples$
issues:
max-issues-per-linter: 10
max-same-issues: 10
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+9 -2
View File
@@ -1,7 +1,7 @@
BUILD_DIR = build
SERVICES = manager agent cli
ATTESTATION_POLICY = attestation_policy
CGO_ENABLED ?= 1
CGO_ENABLED ?= 0
GOARCH ?= amd64
VERSION ?= $(shell git describe --abbrev=0 --tags --always)
COMMIT ?= $(shell git rev-parse HEAD)
@@ -12,6 +12,7 @@ CONFIG_DIR ?= /etc/cocos
SERVICE_NAME ?= cocos-manager
SERVICE_DIR ?= /etc/systemd/system
SERVICE_FILE = init/systemd/$(SERVICE_NAME).service
IGVM_BUILD_SCRIPT := ./scripts/igvmmeasure/igvm.sh
define compile_service
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
@@ -27,8 +28,9 @@ endef
all: $(SERVICES)
$(SERVICES):
$(SERVICES):
$(call compile_service,$@)
@if [ "$@" = "cli" ] || [ "$@" = "manager" ]; then $(MAKE) build-igvm; fi
$(ATTESTATION_POLICY):
$(MAKE) -C ./scripts/attestation_policy
@@ -37,6 +39,7 @@ 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
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative agent/events/events.proto
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative agent/cvms/cvms.proto
mocks:
mockery --config ./mockery.yml
@@ -60,3 +63,7 @@ stop:
install_service:
sudo install -m 644 $(SERVICE_FILE) $(SERVICE_DIR)/$(SERVICE_NAME).service
sudo systemctl daemon-reload
build-igvm:
@echo "Running build script for igvmmeasure..."
@$(IGVM_BUILD_SCRIPT)
+54 -39
View File
@@ -1,65 +1,80 @@
# Cocos AI
<div align="center">
# Cocos AI 🥥
**Confidential Computing System for AI**
**Made with ❤️ by [Ultraviolet](https://ultraviolet.rs/)**
[![codecov](https://codecov.io/gh/ultravioletrs/cocos/graph/badge.svg?token=HX01LR01K9)](https://codecov.io/gh/ultravioletrs/cocos)
![Go report card](https://goreportcard.com/badge/github.com/ultravioletrs/cocos)
[![Go report card](https://goreportcard.com/badge/github.com/ultravioletrs/cocos)](https://goreportcard.com/report/github.com/ultravioletrs/cocos)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
[Cocos AI (Confdential Computing System for AI/ML)][cocos] is a platform for secure multiparty computation (SMPC)
based on the [Confidential Computing][cc] and [Trusted Execution Environments (TEEs)][tee].
### [Guide](https://docs.cocos.ultraviolet.rs) | [Contributing](CONTRIBUTING.md) | [Website](https://cocos.ai/)
</div>
## Introduction 🚀
Cocos AI is a **cutting-edge platform** designed to enable secure multiparty computation (SMPC) using **Confidential Computing** and **Trusted Execution Environments (TEEs)**.
It empowers organizations to collaboratively process sensitive data for AI/ML workloads while ensuring:
- 🔒 **Data Privacy**: Your data stays encrypted and secure throughout the computation.
- 🛡️ **Trust and Integrity**: Protected by hardware enclaves with robust remote attestation protocols.
- 🤝 **Seamless Collaboration**: Multiple organizations can work together without exposing sensitive information.
<p align="center">
<img src="https://cocos.ai/images/Collaborative%20AI.drawio.svg" width="500" height="500">
<img src="https://cocos.ai/images/Collaborative%20AI.drawio.svg" alt="Cocos AI Illustration" width="400" height="400">
</p>
With Cocos AI it becomes possible to run AI/ML workloads on combined datasets from multiple organizations
while guaranteeing the privacy and security of the data and the algorithm.
Data is always encrypted, protected by hardware secure enclaves (Trusted Execution Environments),
attested via secure remote attestation protocols, and invisible to cloud processors or any other
3rd party to which computation is offloaded.
## Features 🛠️
## Features
Cocos AI provides essential features for secure and efficient collaborative AI/ML:
Cocos AI is implementing the following features:
- 🖥️ **TEE Enablement and Monitoring**: Secure VM management for deploying and monitoring workloads.
- 🛡️ **Hardware Abstraction Layer (HAL)**: Built on a hardened Linux kernel, secure bootloader, and minimal root filesystem (minimal TCB).
- 🕵️ **In-Enclave Agent and Networking Controller**: Essential system software for managing secure workloads.
- 🔒 **Encrypted Data Transfer**: Asynchronous data transfer and secure result delivery.
- 🛠️ **API for Platform Manipulation**: Programmatic control for managing workloads.
-**Attestation and Verification Tools**: Hardware- and software-supported attestation for integrity assurance.
- 🖱️ **Command-Line Interface (CLI)**: A user-friendly CLI for system interaction.
- TEE enablement, deployment and monitoring (secure VM manager)
- HAL for TEEs based on hardened Linux kernel, secure bootloader and custom-tailored embedded rootfs for minimal TCB
- In-enclave agent, netowrking controller and other system software
- Encrypted asynchronous data transfer and result delivery
- API for programmable platform manipulation
- HW and SW supported attestation with verification tools
- CLI for system interaction
## Usage
Clone the repo and create binaries:
## 🚀 Quick Start
### Clone the Repository and Build Binaries
```bash
git clone git@github.com:ultravioletrs/cocos.git
make
```
This will create 3 binaries:
This will generate three binaries:
```bash
ls build/
# cocos-agent cocos-cli cocos-manager
```
- Manager can be deployed on the AMD SEV-SNP host
- Agent can be built into [EOS][eos]-based HAL
- CLI can be used to communicate to remote Agent.
### Deployment Overview:
- **Manager**: Deploy on the AMD SEV-SNP host to orchestrate workloads.
- **Agent**: Build into the [EOS](https://github.com/ultravioletrs/eos)-based HAL for secure enclave management.
- **CLI**: Interact with remote agents to control operations.
## Documentation
## 📚 Documentation
Project documentation is hosted at [Cocos AI official docs page][docs].
Comprehensive documentation is available at the [official documentation page](https://docs.cocos.ultraviolet.rs).
For CLI usage details, visit the [CLI Documentation](https://docs.cocos.ultraviolet.rs/cli).
Documentation is generated from the [docs repository](https://github.com/ultravioletrs/docs).
Documentation is automatically generated from the [docs repository](https://github.com/ultravioletrs/docs). Contributions to documentation are welcome!
## License
Cocos AI is published under permissive open-source [Apache-2.0](LICENSE) license.
## 🛡️ License
[cc]: https://confidentialcomputing.io/white-papers-reports/
[cocos]: https://cocos.ai/
[rel]: https://github.com/ultravioletrs/cocos/releases
[tee]: https://en.wikipedia.org/wiki/Trusted_execution_environment
[docs]: https://docs.cocos.ultraviolet.rs
[cli]: https://docs.cocos.ultraviolet.rs/cli
[eos]: https://github.com/ultravioletrs/eos
Cocos AI is published under the permissive open-source [Apache-2.0](LICENSE) license. Contributions are encouraged and appreciated!
## 🌐 Links and Resources
- [Cocos AI Website](https://cocos.ai/)
- [Official Releases](https://github.com/ultravioletrs/cocos/releases)
- [Confidential Computing Overview](https://confidentialcomputing.io/white-papers-reports/)
- [Trusted Execution Environments (TEEs)](https://en.wikipedia.org/wiki/Trusted_execution_environment)
>This work has been partially supported by the [ELASTIC project](https://elasticproject.eu/), which received funding from the Smart Networks and Services Joint Undertaking (SNS JU) under the European Unions Horizon Europe research and innovation programme under [Grant Agreement No. 101139067](https://cordis.europa.eu/project/id/101139067). Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union. Neither the European Union nor the granting authority can be held responsible for them.
+10 -10
View File
@@ -6,16 +6,16 @@ Agent service provides a barebones HTTP and gRPC API and Service interface imple
The service is configured using the environment variables from the following table. Note that any unset variables will be replaced with their default values.
| Variable | Description | Default |
| ----------------------------- | ------------------------------------------------------ | ------------------------------ |
| AGENT_LOG_LEVEL | Log level for agent service (debug, info, warn, error) | info |
| AGENT_GRPC_HOST | Agent service gRPC host | "" |
| AGENT_GRPC_PORT | Agent service gRPC port | 7002 |
| AGENT_GRPC_SERVER_CERT | Path to gRPC server certificate in pem format | "" |
| AGENT_GRPC_SERVER_KEY | Path to gRPC server key in pem format | "" |
| AGENT_GRPC_SERVER_CA_CERTS | Path to gRPC server CA certificate | "" |
| AGENT_GRPC_CLIENT_CA_CERTS | Path to gRPC client CA certificate | "" |
| Variable | Description | Default |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| AGENT_LOG_LEVEL | Log level for agent service (debug, info, warn, error) | debug |
| AGENT_CVM_GRPC_HOST | Agent service gRPC host | "" |
| AGENT_CVM_GRPC_PORT | Agent service gRPC port | 7001 |
| AGENT_CVM_GRPC_SERVER_CERT | Path to gRPC server certificate in pem format | "" |
| AGENT_CVM_GRPC_SERVER_KEY | Path to gRPC server key in pem format | "" |
| AGENT_CVM_GRPC_SERVER_CA_CERTS | Path to gRPC server CA certificate | "" |
| AGENT_CVM_GRPC_CLIENT_CA_CERTS | Path to gRPC client CA certificate | "" |
| AGENT_CVM_CA_URL | URL for CA service, if provided it will be used for certificate generation, used only with aTLS at the moment | "" |
## Deployment
+349 -232
View File
@@ -3,8 +3,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc v5.28.1
// protoc-gen-go v1.36.5
// protoc v5.29.0
// source: agent/agent.proto
package agent
@@ -14,6 +14,7 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@@ -24,21 +25,18 @@ const (
)
type AlgoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Algorithm []byte `protobuf:"bytes,1,opt,name=algorithm,proto3" json:"algorithm,omitempty"`
Requirements []byte `protobuf:"bytes,2,opt,name=requirements,proto3" json:"requirements,omitempty"`
unknownFields protoimpl.UnknownFields
Algorithm []byte `protobuf:"bytes,1,opt,name=algorithm,proto3" json:"algorithm,omitempty"`
Requirements []byte `protobuf:"bytes,2,opt,name=requirements,proto3" json:"requirements,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *AlgoRequest) Reset() {
*x = AlgoRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_agent_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_agent_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AlgoRequest) String() string {
@@ -49,7 +47,7 @@ func (*AlgoRequest) ProtoMessage() {}
func (x *AlgoRequest) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -79,18 +77,16 @@ func (x *AlgoRequest) GetRequirements() []byte {
}
type AlgoResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AlgoResponse) Reset() {
*x = AlgoResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_agent_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_agent_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AlgoResponse) String() string {
@@ -101,7 +97,7 @@ func (*AlgoResponse) ProtoMessage() {}
func (x *AlgoResponse) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -117,21 +113,18 @@ func (*AlgoResponse) Descriptor() ([]byte, []int) {
}
type DataRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Dataset []byte `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
Filename string `protobuf:"bytes,2,opt,name=filename,proto3" json:"filename,omitempty"`
unknownFields protoimpl.UnknownFields
Dataset []byte `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
Filename string `protobuf:"bytes,2,opt,name=filename,proto3" json:"filename,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *DataRequest) Reset() {
*x = DataRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_agent_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_agent_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DataRequest) String() string {
@@ -142,7 +135,7 @@ func (*DataRequest) ProtoMessage() {}
func (x *DataRequest) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -172,18 +165,16 @@ func (x *DataRequest) GetFilename() string {
}
type DataResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DataResponse) Reset() {
*x = DataResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_agent_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_agent_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DataResponse) String() string {
@@ -194,7 +185,7 @@ func (*DataResponse) ProtoMessage() {}
func (x *DataResponse) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -210,18 +201,16 @@ func (*DataResponse) Descriptor() ([]byte, []int) {
}
type ResultRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResultRequest) Reset() {
*x = ResultRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_agent_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_agent_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResultRequest) String() string {
@@ -232,7 +221,7 @@ func (*ResultRequest) ProtoMessage() {}
func (x *ResultRequest) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -248,20 +237,17 @@ func (*ResultRequest) Descriptor() ([]byte, []int) {
}
type ResultResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
unknownFields protoimpl.UnknownFields
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *ResultResponse) Reset() {
*x = ResultResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_agent_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_agent_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResultResponse) String() string {
@@ -272,7 +258,7 @@ func (*ResultResponse) ProtoMessage() {}
func (x *ResultResponse) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -295,20 +281,19 @@ func (x *ResultResponse) GetFile() []byte {
}
type AttestationRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
TeeNonce []byte `protobuf:"bytes,1,opt,name=teeNonce,proto3" json:"teeNonce,omitempty"` // Should be less or equal 64 bytes.
VtpmNonce []byte `protobuf:"bytes,2,opt,name=vtpmNonce,proto3" json:"vtpmNonce,omitempty"` // Should be less or equal 32 bytes.
Type int32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"`
unknownFields protoimpl.UnknownFields
ReportData []byte `protobuf:"bytes,1,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // Should be of length 64.
sizeCache protoimpl.SizeCache
}
func (x *AttestationRequest) Reset() {
*x = AttestationRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_agent_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_agent_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AttestationRequest) String() string {
@@ -319,7 +304,7 @@ func (*AttestationRequest) ProtoMessage() {}
func (x *AttestationRequest) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -334,28 +319,39 @@ func (*AttestationRequest) Descriptor() ([]byte, []int) {
return file_agent_agent_proto_rawDescGZIP(), []int{6}
}
func (x *AttestationRequest) GetReportData() []byte {
func (x *AttestationRequest) GetTeeNonce() []byte {
if x != nil {
return x.ReportData
return x.TeeNonce
}
return nil
}
type AttestationResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
func (x *AttestationRequest) GetVtpmNonce() []byte {
if x != nil {
return x.VtpmNonce
}
return nil
}
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
func (x *AttestationRequest) GetType() int32 {
if x != nil {
return x.Type
}
return 0
}
type AttestationResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AttestationResponse) Reset() {
*x = AttestationResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_agent_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_agent_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AttestationResponse) String() string {
@@ -366,7 +362,7 @@ func (*AttestationResponse) ProtoMessage() {}
func (x *AttestationResponse) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -388,9 +384,193 @@ func (x *AttestationResponse) GetFile() []byte {
return nil
}
type IMAMeasurementsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *IMAMeasurementsRequest) Reset() {
*x = IMAMeasurementsRequest{}
mi := &file_agent_agent_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *IMAMeasurementsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IMAMeasurementsRequest) ProtoMessage() {}
func (x *IMAMeasurementsRequest) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[8]
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 IMAMeasurementsRequest.ProtoReflect.Descriptor instead.
func (*IMAMeasurementsRequest) Descriptor() ([]byte, []int) {
return file_agent_agent_proto_rawDescGZIP(), []int{8}
}
type IMAMeasurementsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
Pcr10 []byte `protobuf:"bytes,2,opt,name=pcr10,proto3" json:"pcr10,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *IMAMeasurementsResponse) Reset() {
*x = IMAMeasurementsResponse{}
mi := &file_agent_agent_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *IMAMeasurementsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IMAMeasurementsResponse) ProtoMessage() {}
func (x *IMAMeasurementsResponse) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[9]
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 IMAMeasurementsResponse.ProtoReflect.Descriptor instead.
func (*IMAMeasurementsResponse) Descriptor() ([]byte, []int) {
return file_agent_agent_proto_rawDescGZIP(), []int{9}
}
func (x *IMAMeasurementsResponse) GetFile() []byte {
if x != nil {
return x.File
}
return nil
}
func (x *IMAMeasurementsResponse) GetPcr10() []byte {
if x != nil {
return x.Pcr10
}
return nil
}
type AttestationResultRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
TokenNonce []byte `protobuf:"bytes,1,opt,name=tokenNonce,proto3" json:"tokenNonce,omitempty"` // Should be less or equal 32 bytes
Type int32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AttestationResultRequest) Reset() {
*x = AttestationResultRequest{}
mi := &file_agent_agent_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AttestationResultRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AttestationResultRequest) ProtoMessage() {}
func (x *AttestationResultRequest) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[10]
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 AttestationResultRequest.ProtoReflect.Descriptor instead.
func (*AttestationResultRequest) Descriptor() ([]byte, []int) {
return file_agent_agent_proto_rawDescGZIP(), []int{10}
}
func (x *AttestationResultRequest) GetTokenNonce() []byte {
if x != nil {
return x.TokenNonce
}
return nil
}
func (x *AttestationResultRequest) GetType() int32 {
if x != nil {
return x.Type
}
return 0
}
type AttestationResultResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AttestationResultResponse) Reset() {
*x = AttestationResultResponse{}
mi := &file_agent_agent_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AttestationResultResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AttestationResultResponse) ProtoMessage() {}
func (x *AttestationResultResponse) ProtoReflect() protoreflect.Message {
mi := &file_agent_agent_proto_msgTypes[11]
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 AttestationResultResponse.ProtoReflect.Descriptor instead.
func (*AttestationResultResponse) Descriptor() ([]byte, []int) {
return file_agent_agent_proto_rawDescGZIP(), []int{11}
}
func (x *AttestationResultResponse) GetFile() []byte {
if x != nil {
return x.File
}
return nil
}
var File_agent_agent_proto protoreflect.FileDescriptor
var file_agent_agent_proto_rawDesc = []byte{
var file_agent_agent_proto_rawDesc = string([]byte{
0x0a, 0x11, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x4f, 0x0a, 0x0b, 0x41, 0x6c,
0x67, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x6c, 0x67,
@@ -407,69 +587,105 @@ var file_agent_agent_proto_rawDesc = []byte{
0x22, 0x0f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x24, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x35, 0x0a, 0x12, 0x41, 0x74, 0x74, 0x65, 0x73,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a,
0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0x29,
0x0a, 0x13, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xfd, 0x01, 0x0a, 0x0c, 0x41, 0x67,
0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c,
0x67, 0x6f, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41,
0x6c, 0x67, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12,
0x33, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x28, 0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14,
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12,
0x48, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19,
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61,
0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x62, 0x0a, 0x12, 0x41, 0x74, 0x74, 0x65, 0x73,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
0x08, 0x74, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x08, 0x74, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x76, 0x74, 0x70,
0x6d, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x76, 0x74,
0x70, 0x6d, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x29, 0x0a, 0x13, 0x41,
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x18, 0x0a, 0x16, 0x49, 0x4d, 0x41, 0x4d, 0x65, 0x61,
0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x22, 0x43, 0x0a, 0x17, 0x49, 0x4d, 0x41, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66,
0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12,
0x14, 0x0a, 0x05, 0x70, 0x63, 0x72, 0x31, 0x30, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
0x70, 0x63, 0x72, 0x31, 0x30, 0x22, 0x4e, 0x0a, 0x18, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x4e, 0x6f, 0x6e, 0x63,
0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2f, 0x0a, 0x19, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xad, 0x03, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c, 0x67, 0x6f, 0x12,
0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x33, 0x0a, 0x04,
0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74,
0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28,
0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0b,
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41,
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x54, 0x0a, 0x0f, 0x49, 0x4d, 0x41, 0x4d, 0x65, 0x61,
0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x2e, 0x49, 0x4d, 0x41, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
0x2e, 0x49, 0x4d, 0x41, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x58, 0x0a, 0x11,
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c,
0x74, 0x12, 0x1f, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
file_agent_agent_proto_rawDescOnce sync.Once
file_agent_agent_proto_rawDescData = file_agent_agent_proto_rawDesc
file_agent_agent_proto_rawDescData []byte
)
func file_agent_agent_proto_rawDescGZIP() []byte {
file_agent_agent_proto_rawDescOnce.Do(func() {
file_agent_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_agent_proto_rawDescData)
file_agent_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_agent_proto_rawDesc), len(file_agent_agent_proto_rawDesc)))
})
return file_agent_agent_proto_rawDescData
}
var file_agent_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_agent_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_agent_agent_proto_goTypes = []any{
(*AlgoRequest)(nil), // 0: agent.AlgoRequest
(*AlgoResponse)(nil), // 1: agent.AlgoResponse
(*DataRequest)(nil), // 2: agent.DataRequest
(*DataResponse)(nil), // 3: agent.DataResponse
(*ResultRequest)(nil), // 4: agent.ResultRequest
(*ResultResponse)(nil), // 5: agent.ResultResponse
(*AttestationRequest)(nil), // 6: agent.AttestationRequest
(*AttestationResponse)(nil), // 7: agent.AttestationResponse
(*AlgoRequest)(nil), // 0: agent.AlgoRequest
(*AlgoResponse)(nil), // 1: agent.AlgoResponse
(*DataRequest)(nil), // 2: agent.DataRequest
(*DataResponse)(nil), // 3: agent.DataResponse
(*ResultRequest)(nil), // 4: agent.ResultRequest
(*ResultResponse)(nil), // 5: agent.ResultResponse
(*AttestationRequest)(nil), // 6: agent.AttestationRequest
(*AttestationResponse)(nil), // 7: agent.AttestationResponse
(*IMAMeasurementsRequest)(nil), // 8: agent.IMAMeasurementsRequest
(*IMAMeasurementsResponse)(nil), // 9: agent.IMAMeasurementsResponse
(*AttestationResultRequest)(nil), // 10: agent.AttestationResultRequest
(*AttestationResultResponse)(nil), // 11: agent.AttestationResultResponse
}
var file_agent_agent_proto_depIdxs = []int32{
0, // 0: agent.AgentService.Algo:input_type -> agent.AlgoRequest
2, // 1: agent.AgentService.Data:input_type -> agent.DataRequest
4, // 2: agent.AgentService.Result:input_type -> agent.ResultRequest
6, // 3: agent.AgentService.Attestation:input_type -> agent.AttestationRequest
1, // 4: agent.AgentService.Algo:output_type -> agent.AlgoResponse
3, // 5: agent.AgentService.Data:output_type -> agent.DataResponse
5, // 6: agent.AgentService.Result:output_type -> agent.ResultResponse
7, // 7: agent.AgentService.Attestation:output_type -> agent.AttestationResponse
4, // [4:8] is the sub-list for method output_type
0, // [0:4] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
0, // 0: agent.AgentService.Algo:input_type -> agent.AlgoRequest
2, // 1: agent.AgentService.Data:input_type -> agent.DataRequest
4, // 2: agent.AgentService.Result:input_type -> agent.ResultRequest
6, // 3: agent.AgentService.Attestation:input_type -> agent.AttestationRequest
8, // 4: agent.AgentService.IMAMeasurements:input_type -> agent.IMAMeasurementsRequest
10, // 5: agent.AgentService.AttestationResult:input_type -> agent.AttestationResultRequest
1, // 6: agent.AgentService.Algo:output_type -> agent.AlgoResponse
3, // 7: agent.AgentService.Data:output_type -> agent.DataResponse
5, // 8: agent.AgentService.Result:output_type -> agent.ResultResponse
7, // 9: agent.AgentService.Attestation:output_type -> agent.AttestationResponse
9, // 10: agent.AgentService.IMAMeasurements:output_type -> agent.IMAMeasurementsResponse
11, // 11: agent.AgentService.AttestationResult:output_type -> agent.AttestationResultResponse
6, // [6:12] is the sub-list for method output_type
0, // [0:6] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_agent_agent_proto_init() }
@@ -477,111 +693,13 @@ func file_agent_agent_proto_init() {
if File_agent_agent_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_agent_agent_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*AlgoRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_agent_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*AlgoResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_agent_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*DataRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_agent_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*DataResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_agent_proto_msgTypes[4].Exporter = func(v any, i int) any {
switch v := v.(*ResultRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_agent_proto_msgTypes[5].Exporter = func(v any, i int) any {
switch v := v.(*ResultResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_agent_proto_msgTypes[6].Exporter = func(v any, i int) any {
switch v := v.(*AttestationRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_agent_proto_msgTypes[7].Exporter = func(v any, i int) any {
switch v := v.(*AttestationResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_agent_agent_proto_rawDesc,
RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_agent_proto_rawDesc), len(file_agent_agent_proto_rawDesc)),
NumEnums: 0,
NumMessages: 8,
NumMessages: 12,
NumExtensions: 0,
NumServices: 1,
},
@@ -590,7 +708,6 @@ func file_agent_agent_proto_init() {
MessageInfos: file_agent_agent_proto_msgTypes,
}.Build()
File_agent_agent_proto = out.File
file_agent_agent_proto_rawDesc = nil
file_agent_agent_proto_goTypes = nil
file_agent_agent_proto_depIdxs = nil
}
+21 -1
View File
@@ -12,6 +12,8 @@ service AgentService {
rpc Data(stream DataRequest) returns (DataResponse) {}
rpc Result(ResultRequest) returns (stream ResultResponse) {}
rpc Attestation(AttestationRequest) returns (stream AttestationResponse) {}
rpc IMAMeasurements(IMAMeasurementsRequest) returns (stream IMAMeasurementsResponse) {}
rpc AttestationResult(AttestationResultRequest) returns (AttestationResultResponse) {}
}
message AlgoRequest {
@@ -36,9 +38,27 @@ message ResultResponse {
}
message AttestationRequest {
bytes report_data = 1; // Should be of length 64.
bytes teeNonce = 1; // Should be less or equal 64 bytes.
bytes vtpmNonce = 2; // Should be less or equal 32 bytes.
int32 type = 3;
}
message AttestationResponse {
bytes file = 1;
}
message IMAMeasurementsRequest {
}
message IMAMeasurementsResponse {
bytes file = 1;
bytes pcr10 = 2;
}
message AttestationResultRequest{
bytes tokenNonce = 1; // Should be less or equal 32 bytes
int32 type = 3;
}
message AttestationResultResponse{
bytes file = 1;
}
+86 -6
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.28.1
// - protoc v5.29.0
// source: agent/agent.proto
package agent
@@ -22,10 +22,12 @@ import (
const _ = grpc.SupportPackageIsVersion9
const (
AgentService_Algo_FullMethodName = "/agent.AgentService/Algo"
AgentService_Data_FullMethodName = "/agent.AgentService/Data"
AgentService_Result_FullMethodName = "/agent.AgentService/Result"
AgentService_Attestation_FullMethodName = "/agent.AgentService/Attestation"
AgentService_Algo_FullMethodName = "/agent.AgentService/Algo"
AgentService_Data_FullMethodName = "/agent.AgentService/Data"
AgentService_Result_FullMethodName = "/agent.AgentService/Result"
AgentService_Attestation_FullMethodName = "/agent.AgentService/Attestation"
AgentService_IMAMeasurements_FullMethodName = "/agent.AgentService/IMAMeasurements"
AgentService_AttestationResult_FullMethodName = "/agent.AgentService/AttestationResult"
)
// AgentServiceClient is the client API for AgentService service.
@@ -36,6 +38,8 @@ type AgentServiceClient interface {
Data(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[DataRequest, DataResponse], error)
Result(ctx context.Context, in *ResultRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ResultResponse], error)
Attestation(ctx context.Context, in *AttestationRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AttestationResponse], error)
IMAMeasurements(ctx context.Context, in *IMAMeasurementsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[IMAMeasurementsResponse], error)
AttestationResult(ctx context.Context, in *AttestationResultRequest, opts ...grpc.CallOption) (*AttestationResultResponse, error)
}
type agentServiceClient struct {
@@ -110,6 +114,35 @@ func (c *agentServiceClient) Attestation(ctx context.Context, in *AttestationReq
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type AgentService_AttestationClient = grpc.ServerStreamingClient[AttestationResponse]
func (c *agentServiceClient) IMAMeasurements(ctx context.Context, in *IMAMeasurementsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[IMAMeasurementsResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &AgentService_ServiceDesc.Streams[4], AgentService_IMAMeasurements_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[IMAMeasurementsRequest, IMAMeasurementsResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type AgentService_IMAMeasurementsClient = grpc.ServerStreamingClient[IMAMeasurementsResponse]
func (c *agentServiceClient) AttestationResult(ctx context.Context, in *AttestationResultRequest, opts ...grpc.CallOption) (*AttestationResultResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AttestationResultResponse)
err := c.cc.Invoke(ctx, AgentService_AttestationResult_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// AgentServiceServer is the server API for AgentService service.
// All implementations must embed UnimplementedAgentServiceServer
// for forward compatibility.
@@ -118,6 +151,8 @@ type AgentServiceServer interface {
Data(grpc.ClientStreamingServer[DataRequest, DataResponse]) error
Result(*ResultRequest, grpc.ServerStreamingServer[ResultResponse]) error
Attestation(*AttestationRequest, grpc.ServerStreamingServer[AttestationResponse]) error
IMAMeasurements(*IMAMeasurementsRequest, grpc.ServerStreamingServer[IMAMeasurementsResponse]) error
AttestationResult(context.Context, *AttestationResultRequest) (*AttestationResultResponse, error)
mustEmbedUnimplementedAgentServiceServer()
}
@@ -140,6 +175,12 @@ func (UnimplementedAgentServiceServer) Result(*ResultRequest, grpc.ServerStreami
func (UnimplementedAgentServiceServer) Attestation(*AttestationRequest, grpc.ServerStreamingServer[AttestationResponse]) error {
return status.Errorf(codes.Unimplemented, "method Attestation not implemented")
}
func (UnimplementedAgentServiceServer) IMAMeasurements(*IMAMeasurementsRequest, grpc.ServerStreamingServer[IMAMeasurementsResponse]) error {
return status.Errorf(codes.Unimplemented, "method IMAMeasurements not implemented")
}
func (UnimplementedAgentServiceServer) AttestationResult(context.Context, *AttestationResultRequest) (*AttestationResultResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AttestationResult not implemented")
}
func (UnimplementedAgentServiceServer) mustEmbedUnimplementedAgentServiceServer() {}
func (UnimplementedAgentServiceServer) testEmbeddedByValue() {}
@@ -197,13 +238,47 @@ func _AgentService_Attestation_Handler(srv interface{}, stream grpc.ServerStream
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type AgentService_AttestationServer = grpc.ServerStreamingServer[AttestationResponse]
func _AgentService_IMAMeasurements_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(IMAMeasurementsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(AgentServiceServer).IMAMeasurements(m, &grpc.GenericServerStream[IMAMeasurementsRequest, IMAMeasurementsResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type AgentService_IMAMeasurementsServer = grpc.ServerStreamingServer[IMAMeasurementsResponse]
func _AgentService_AttestationResult_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AttestationResultRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServiceServer).AttestationResult(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AgentService_AttestationResult_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServiceServer).AttestationResult(ctx, req.(*AttestationResultRequest))
}
return interceptor(ctx, in, info, handler)
}
// AgentService_ServiceDesc is the grpc.ServiceDesc for AgentService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var AgentService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "agent.AgentService",
HandlerType: (*AgentServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Methods: []grpc.MethodDesc{
{
MethodName: "AttestationResult",
Handler: _AgentService_AttestationResult_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "Algo",
@@ -225,6 +300,11 @@ var AgentService_ServiceDesc = grpc.ServiceDesc{
Handler: _AgentService_Attestation_Handler,
ServerStreams: true,
},
{
StreamName: "IMAMeasurements",
Handler: _AgentService_IMAMeasurements_Handler,
ServerStreams: true,
},
},
Metadata: "agent/agent.proto",
}
+3
View File
@@ -46,4 +46,7 @@ func AlgorithmArgsFromContext(ctx context.Context) []string {
type Algorithm interface {
// Run executes the algorithm and returns the result.
Run() error
// Stop stops the algorithm.
Stop() error
}
+28 -7
View File
@@ -20,29 +20,50 @@ type binary struct {
stderr io.Writer
stdout io.Writer
args []string
cmd *exec.Cmd
}
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile string, args []string) algorithm.Algorithm {
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile string, args []string, cmpID string) algorithm.Algorithm {
return &binary{
algoFile: algoFile,
stderr: &logging.Stderr{Logger: logger, EventSvc: eventsSvc},
stderr: &logging.Stderr{Logger: logger, EventSvc: eventsSvc, CmpID: cmpID},
stdout: &logging.Stdout{Logger: logger},
args: args,
}
}
func (b *binary) Run() error {
cmd := exec.Command(b.algoFile, b.args...)
cmd.Stderr = b.stderr
cmd.Stdout = b.stdout
b.cmd = exec.Command(b.algoFile, b.args...)
b.cmd.Stderr = b.stderr
b.cmd.Stdout = b.stdout
if err := cmd.Start(); err != nil {
if err := b.cmd.Start(); err != nil {
return fmt.Errorf("error starting algorithm: %v", err)
}
if err := cmd.Wait(); err != nil {
if err := b.cmd.Wait(); err != nil {
return fmt.Errorf("algorithm execution error: %v", err)
}
return nil
}
func (b *binary) Stop() error {
if b.cmd == nil {
return nil
}
if b.cmd.ProcessState != nil && b.cmd.ProcessState.Exited() {
return nil
}
if b.cmd.Process == nil {
return nil
}
if err := b.cmd.Process.Kill(); err != nil {
return fmt.Errorf("error stopping algorithm: %v", err)
}
return nil
}
+2 -2
View File
@@ -18,7 +18,7 @@ func TestNewAlgorithm(t *testing.T) {
algoFile := "/path/to/algo"
args := []string{"arg1", "arg2"}
algo := NewAlgorithm(logger, eventsSvc, algoFile, args)
algo := NewAlgorithm(logger, eventsSvc, algoFile, args, "")
b, ok := algo.(*binary)
if !ok {
@@ -74,7 +74,7 @@ func TestBinaryRun(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventsSvc := new(mocks.Service)
b := NewAlgorithm(logger, eventsSvc, tt.algoFile, tt.args).(*binary)
b := NewAlgorithm(logger, eventsSvc, tt.algoFile, tt.args, "").(*binary)
var stdout, stderr bytes.Buffer
b.stdout = &stdout
+9 -5
View File
@@ -35,11 +35,11 @@ type docker struct {
stdout io.Writer
}
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile string) algorithm.Algorithm {
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile, cmpID string) algorithm.Algorithm {
d := &docker{
algoFile: algoFile,
logger: logger,
stderr: &logging.Stderr{Logger: logger, EventSvc: eventsSvc},
stderr: &logging.Stderr{Logger: logger, EventSvc: eventsSvc, CmpID: cmpID},
stdout: &logging.Stdout{Logger: logger},
}
@@ -47,8 +47,6 @@ func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile string
}
func (d *docker) Run() error {
ctx := context.Background()
// Create a new Docker client.
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
@@ -62,8 +60,9 @@ func (d *docker) Run() error {
}
defer imageFile.Close()
ctx := context.Background()
// Load the Docker image from the tar file.
resp, err := cli.ImageLoad(ctx, imageFile, true)
resp, err := cli.ImageLoad(ctx, imageFile, client.ImageLoadWithQuiet(true))
if err != nil {
return fmt.Errorf("could not load Docker image from file: %v", err)
}
@@ -176,3 +175,8 @@ func writeToOut(readCloser io.ReadCloser, ioWriter io.Writer) error {
return nil
}
func (d *docker) Stop() error {
// To be supported later.
return nil
}
+1 -1
View File
@@ -18,7 +18,7 @@ func TestNewAlgorithm(t *testing.T) {
eventsSvc := new(mocks.Service)
algoFile := "/path/to/algo.tar"
algo := NewAlgorithm(logger, eventsSvc, algoFile)
algo := NewAlgorithm(logger, eventsSvc, algoFile, "")
d, ok := algo.(*docker)
assert.True(t, ok, "NewAlgorithm should return a *docker")
+2 -3
View File
@@ -50,6 +50,7 @@ func (s *Stdout) Write(p []byte) (n int, err error) {
type Stderr struct {
Logger *slog.Logger
EventSvc events.Service
CmpID string
}
// Write implements io.Writer.
@@ -70,9 +71,7 @@ func (s *Stderr) Write(p []byte) (n int, err error) {
s.Logger.Error(string(buf[:n]))
}
if err := s.EventSvc.SendEvent(algorithmRun, warningStatus, json.RawMessage{}); err != nil {
return len(p), err
}
s.EventSvc.SendEvent(s.CmpID, algorithmRun, warningStatus, json.RawMessage{})
return len(p), nil
}
+1 -1
View File
@@ -73,7 +73,7 @@ func TestStderrWrite(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockEventService := mocks.NewService(t)
mockEventService.On("SendEvent", "AlgorithmRun", manager.Warning.String(), mock.Anything).Return(nil)
mockEventService.On("SendEvent", mock.Anything, "AlgorithmRun", manager.Warning.String(), mock.Anything).Return(nil)
stderr := &Stderr{Logger: mglog.NewMock(), EventSvc: mockEventService}
n, err := stderr.Write([]byte(tt.input))
+125
View File
@@ -0,0 +1,125 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
// Algorithm is an autogenerated mock type for the Algorithm type
type Algorithm struct {
mock.Mock
}
type Algorithm_Expecter struct {
mock *mock.Mock
}
func (_m *Algorithm) EXPECT() *Algorithm_Expecter {
return &Algorithm_Expecter{mock: &_m.Mock}
}
// Run provides a mock function with no fields
func (_m *Algorithm) Run() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Run")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Algorithm_Run_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Run'
type Algorithm_Run_Call struct {
*mock.Call
}
// Run is a helper method to define mock.On call
func (_e *Algorithm_Expecter) Run() *Algorithm_Run_Call {
return &Algorithm_Run_Call{Call: _e.mock.On("Run")}
}
func (_c *Algorithm_Run_Call) Run(run func()) *Algorithm_Run_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Algorithm_Run_Call) Return(_a0 error) *Algorithm_Run_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Algorithm_Run_Call) RunAndReturn(run func() error) *Algorithm_Run_Call {
_c.Call.Return(run)
return _c
}
// Stop provides a mock function with no fields
func (_m *Algorithm) Stop() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Stop")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Algorithm_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop'
type Algorithm_Stop_Call struct {
*mock.Call
}
// Stop is a helper method to define mock.On call
func (_e *Algorithm_Expecter) Stop() *Algorithm_Stop_Call {
return &Algorithm_Stop_Call{Call: _e.mock.On("Stop")}
}
func (_c *Algorithm_Stop_Call) Run(run func()) *Algorithm_Stop_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Algorithm_Stop_Call) Return(_a0 error) *Algorithm_Stop_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Algorithm_Stop_Call) RunAndReturn(run func() error) *Algorithm_Stop_Call {
_c.Call.Return(run)
return _c
}
// NewAlgorithm creates a new instance of Algorithm. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewAlgorithm(t interface {
mock.TestingT
Cleanup(func())
}) *Algorithm {
mock := &Algorithm{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+28 -7
View File
@@ -39,12 +39,13 @@ type python struct {
runtime string
requirementsFile string
args []string
cmd *exec.Cmd
}
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, runtime, requirementsFile, algoFile string, args []string) algorithm.Algorithm {
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, runtime, requirementsFile, algoFile string, args []string, cmpID string) algorithm.Algorithm {
p := &python{
algoFile: algoFile,
stderr: &logging.Stderr{Logger: logger, EventSvc: eventsSvc},
stderr: &logging.Stderr{Logger: logger, EventSvc: eventsSvc, CmpID: cmpID},
stdout: &logging.Stdout{Logger: logger},
requirementsFile: requirementsFile,
args: args,
@@ -85,15 +86,15 @@ func (p *python) Run() error {
}
args := append([]string{p.algoFile}, p.args...)
cmd := exec.Command(pythonPath, args...)
cmd.Stderr = p.stderr
cmd.Stdout = p.stdout
p.cmd = exec.Command(pythonPath, args...)
p.cmd.Stderr = p.stderr
p.cmd.Stdout = p.stdout
if err := cmd.Start(); err != nil {
if err := p.cmd.Start(); err != nil {
return fmt.Errorf("error starting algorithm: %v", err)
}
if err := cmd.Wait(); err != nil {
if err := p.cmd.Wait(); err != nil {
return fmt.Errorf("algorithm execution error: %v", err)
}
@@ -103,3 +104,23 @@ func (p *python) Run() error {
return nil
}
func (p *python) Stop() error {
if p.cmd == nil {
return nil
}
if p.cmd.ProcessState != nil && p.cmd.ProcessState.Exited() {
return nil
}
if p.cmd.Process == nil {
return nil
}
if err := p.cmd.Process.Kill(); err != nil {
return fmt.Errorf("error stopping algorithm: %v", err)
}
return nil
}
+1 -1
View File
@@ -50,7 +50,7 @@ func TestNewAlgorithm(t *testing.T) {
algoFile := "algorithm.py"
args := []string{"--arg1", "value1"}
algo := NewAlgorithm(logger, eventsSvc, runtime, requirementsFile, algoFile, args)
algo := NewAlgorithm(logger, eventsSvc, runtime, requirementsFile, algoFile, args, "")
p, ok := algo.(*python)
if !ok {
+28 -7
View File
@@ -24,12 +24,13 @@ type wasm struct {
stderr io.Writer
stdout io.Writer
args []string
cmd *exec.Cmd
}
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile string, args []string) algorithm.Algorithm {
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, args []string, algoFile, cmpID string) algorithm.Algorithm {
return &wasm{
algoFile: algoFile,
stderr: &logging.Stderr{Logger: logger, EventSvc: eventsSvc},
stderr: &logging.Stderr{Logger: logger, EventSvc: eventsSvc, CmpID: cmpID},
stdout: &logging.Stdout{Logger: logger},
args: args,
}
@@ -38,17 +39,37 @@ func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile string
func (w *wasm) Run() error {
args := append(mapDirOption, w.algoFile)
args = append(args, w.args...)
cmd := exec.Command(wasmRuntime, args...)
cmd.Stderr = w.stderr
cmd.Stdout = w.stdout
w.cmd = exec.Command(wasmRuntime, args...)
w.cmd.Stderr = w.stderr
w.cmd.Stdout = w.stdout
if err := cmd.Start(); err != nil {
if err := w.cmd.Start(); err != nil {
return fmt.Errorf("error starting algorithm: %v", err)
}
if err := cmd.Wait(); err != nil {
if err := w.cmd.Wait(); err != nil {
return fmt.Errorf("algorithm execution error: %v", err)
}
return nil
}
func (w *wasm) Stop() error {
if w.cmd == nil {
return nil
}
if w.cmd.ProcessState != nil && w.cmd.ProcessState.Exited() {
return nil
}
if w.cmd.Process == nil {
return nil
}
if err := w.cmd.Process.Kill(); err != nil {
return fmt.Errorf("error stopping algorithm: %v", err)
}
return nil
}
+2 -2
View File
@@ -18,7 +18,7 @@ func TestNewAlgorithm(t *testing.T) {
algoFile := "test.wasm"
args := []string{"arg1", "arg2"}
algo := NewAlgorithm(logger, eventsSvc, algoFile, args)
algo := NewAlgorithm(logger, eventsSvc, args, algoFile, "")
w, ok := algo.(*wasm)
if !ok {
@@ -54,7 +54,7 @@ func TestRunError(t *testing.T) {
algoFile := "test.wasm"
args := []string{"arg1", "arg2"}
w := NewAlgorithm(logger, eventsSvc, algoFile, args).(*wasm)
w := NewAlgorithm(logger, eventsSvc, args, algoFile, "").(*wasm)
err := w.Run()
if err == nil {
+32 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/go-kit/kit/endpoint"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/pkg/attestation"
)
func algoEndpoint(svc agent.Service) endpoint.Endpoint {
@@ -70,7 +71,7 @@ func attestationEndpoint(svc agent.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return attestationRes{}, err
}
file, err := svc.Attestation(ctx, req.ReportData)
file, err := svc.Attestation(ctx, req.TeeNonce, req.VtpmNonce, attestation.PlatformType(req.AttType))
if err != nil {
return attestationRes{}, err
}
@@ -78,3 +79,33 @@ func attestationEndpoint(svc agent.Service) endpoint.Endpoint {
return attestationRes{File: file}, nil
}
}
func imaMeasurementsEndpoint(svc agent.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(imaMeasurementsReq)
if err := req.validate(); err != nil {
return imaMeasurementsRes{}, err
}
file, pcr10, err := svc.IMAMeasurements(ctx)
if err != nil {
return imaMeasurementsRes{}, err
}
return imaMeasurementsRes{File: file, PCR10: pcr10}, nil
}
}
func attestationResultEndpoint(svc agent.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(FetchAttestationResultReq)
if err := req.validate(); err != nil {
return fetchAttestationResultRes{}, err
}
file, err := svc.AttestationResult(ctx, req.tokenNonce, attestation.PlatformType(req.AttType))
if err != nil {
return fetchAttestationResultRes{}, err
}
return fetchAttestationResultRes{File: file}, nil
}
}
+58 -4
View File
@@ -7,8 +7,10 @@ import (
"errors"
"testing"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/mocks"
"github.com/ultravioletrs/cocos/pkg/attestation"
"golang.org/x/crypto/sha3"
)
@@ -141,11 +143,11 @@ func TestAttestationEndpoint(t *testing.T) {
}{
{
name: "Success",
req: attestationReq{ReportData: sha3.Sum512([]byte("report data"))},
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: attestation.SNP},
},
{
name: "Service Error",
req: attestationReq{ReportData: sha3.Sum512([]byte("report data"))},
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: attestation.SNP},
expectedErr: true,
},
}
@@ -153,9 +155,9 @@ func TestAttestationEndpoint(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.name == svcErr {
svc.On("Attestation", context.Background(), tt.req.ReportData).Return([]byte{}, errors.New("")).Once()
svc.On("Attestation", context.Background(), tt.req.TeeNonce, tt.req.VtpmNonce, tt.req.AttType).Return([]byte{}, errors.New("")).Once()
} else {
svc.On("Attestation", context.Background(), tt.req.ReportData).Return([]byte{}, nil).Once()
svc.On("Attestation", context.Background(), tt.req.TeeNonce, tt.req.VtpmNonce, tt.req.AttType).Return([]byte{}, nil).Once()
}
endpoint := attestationEndpoint(svc)
res, err := endpoint(context.Background(), tt.req)
@@ -171,3 +173,55 @@ func TestAttestationEndpoint(t *testing.T) {
})
}
}
func TestAttestationResultEndpoint(t *testing.T) {
svc := new(mocks.Service)
tests := []struct {
name string
req FetchAttestationResultReq
mockErr error
expectedErr bool
}{
{
name: "Success",
req: FetchAttestationResultReq{tokenNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: attestation.AzureToken},
mockErr: nil,
expectedErr: false,
},
{
name: "Service Error",
req: FetchAttestationResultReq{tokenNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: attestation.AzureToken},
mockErr: errors.New("mock failure"),
expectedErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Only call service mock if validation is expected to pass
if err := tt.req.validate(); err == nil {
svc.On("AttestationResult", mock.Anything, tt.req.tokenNonce, attestation.PlatformType(tt.req.AttType)).
Return([]byte("mock file"), tt.mockErr).Once()
}
endpoint := attestationResultEndpoint(svc)
res, err := endpoint(context.Background(), tt.req)
if (err != nil) != tt.expectedErr {
t.Errorf("attestationResultEndpoint() error = %v, expectedErr %v", err, tt.expectedErr)
}
if !tt.expectedErr {
r, ok := res.(fetchAttestationResultRes)
if !ok {
t.Errorf("attestationResultEndpoint() returned unexpected type %T", res)
}
if string(r.File) != "mock file" {
t.Errorf("expected file content 'mock file', got %s", r.File)
}
}
svc.AssertExpectations(t)
})
}
}
+32 -1
View File
@@ -4,6 +4,10 @@ package grpc
import (
"errors"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
type algoReq struct {
@@ -38,9 +42,36 @@ func (req resultReq) validate() error {
}
type attestationReq struct {
ReportData [64]byte
TeeNonce [quoteprovider.Nonce]byte
VtpmNonce [vtpm.Nonce]byte
AttType attestation.PlatformType
}
type FetchAttestationResultReq struct {
tokenNonce [vtpm.Nonce]byte
AttType attestation.PlatformType
}
func (req attestationReq) validate() error {
return validateAttestationType(req.AttType)
}
func (req FetchAttestationResultReq) validate() error {
return validateAttestationType(req.AttType)
}
func validateAttestationType(attType attestation.PlatformType) error {
switch attType {
case attestation.SNP, attestation.VTPM, attestation.SNPvTPM, attestation.TDX, attestation.AzureToken:
return nil
default:
return errors.New("invalid attestation type")
}
}
type imaMeasurementsReq struct{}
func (req imaMeasurementsReq) validate() error {
// No request parameters to validate, so no validation logic needed
return nil
}
+9
View File
@@ -13,3 +13,12 @@ type resultRes struct {
type attestationRes struct {
File []byte
}
type imaMeasurementsRes struct {
File []byte
PCR10 []byte
}
type fetchAttestationResultRes struct {
File []byte `protobuf:"bytes,1,opt,name=AttestationResult,proto3" json:"AttestationResult,omitempty"`
}
+317 -94
View File
@@ -8,9 +8,14 @@ import (
"errors"
"fmt"
"io"
"strconv"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/transport/grpc"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
@@ -21,45 +26,78 @@ const (
FileSizeKey = "file-size"
)
var (
ErrTEENonceLength = errors.New("malformed report data, expect less or equal to 64 bytes")
ErrVTPMNonceLength = errors.New("malformed vTPM nonce, expect less or equal to 32 bytes")
ErrTokenNonceLength = errors.New("malformed token nonce, expect less or equal to 32 bytes")
)
var _ agent.AgentServiceServer = (*grpcServer)(nil)
type grpcServer struct {
algo grpc.Handler
data grpc.Handler
result grpc.Handler
attestation grpc.Handler
handlers map[string]grpc.Handler
agent.UnimplementedAgentServiceServer
}
type endpointConfig struct {
endpoint func(agent.Service) endpoint.Endpoint
decodeRequest grpc.DecodeRequestFunc
encodeResponse grpc.EncodeResponseFunc
}
// NewServer returns new AgentServiceServer instance.
func NewServer(svc agent.Service) agent.AgentServiceServer {
// Define endpoint configurations
endpoints := map[string]endpointConfig{
"algo": {
endpoint: algoEndpoint,
decodeRequest: decodeAlgoRequest,
encodeResponse: encodeAlgoResponse,
},
"data": {
endpoint: dataEndpoint,
decodeRequest: decodeDataRequest,
encodeResponse: encodeDataResponse,
},
"result": {
endpoint: resultEndpoint,
decodeRequest: decodeResultRequest,
encodeResponse: encodeResultResponse,
},
"attestation": {
endpoint: attestationEndpoint,
decodeRequest: decodeAttestationRequest,
encodeResponse: encodeAttestationResponse,
},
"imaMeasurements": {
endpoint: imaMeasurementsEndpoint,
decodeRequest: decodeIMAMeasurementsRequest,
encodeResponse: encodeIMAMeasurementsResponse,
},
"attestationResult": {
endpoint: attestationResultEndpoint,
decodeRequest: decodeAttestationResultRequest,
encodeResponse: encodeAttestationResultResponse,
},
}
// Create handlers using the configurations
handlers := make(map[string]grpc.Handler)
for name, config := range endpoints {
handlers[name] = grpc.NewServer(
config.endpoint(svc),
config.decodeRequest,
config.encodeResponse,
)
}
return &grpcServer{
algo: grpc.NewServer(
algoEndpoint(svc),
decodeAlgoRequest,
encodeAlgoResponse,
),
data: grpc.NewServer(
dataEndpoint(svc),
decodeDataRequest,
encodeDataResponse,
),
result: grpc.NewServer(
resultEndpoint(svc),
decodeResultRequest,
encodeResultResponse,
),
attestation: grpc.NewServer(
attestationEndpoint(svc),
decodeAttestationRequest,
encodeAttestationResponse,
),
handlers: handlers,
}
}
func decodeAlgoRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*agent.AlgoRequest)
return algoReq{
Algorithm: req.Algorithm,
Requirements: req.Requirements,
@@ -72,7 +110,6 @@ func encodeAlgoResponse(_ context.Context, response interface{}) (interface{}, e
func decodeDataRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*agent.DataRequest)
return dataReq{
Dataset: req.Dataset,
Filename: req.Filename,
@@ -94,12 +131,47 @@ func encodeResultResponse(_ context.Context, response interface{}) (interface{},
}, nil
}
func validateNonce(nonce []byte, maxLen int, target interface{}) error {
if len(nonce) > maxLen {
switch maxLen {
case quoteprovider.Nonce:
return ErrTEENonceLength
case vtpm.Nonce:
return ErrVTPMNonceLength
default:
return ErrTokenNonceLength
}
}
switch t := target.(type) {
case *[quoteprovider.Nonce]byte:
copy(t[:], nonce)
case *[vtpm.Nonce]byte:
copy(t[:], nonce)
default:
return fmt.Errorf("unsupported target type for nonce validation: %T", target)
}
return nil
}
func decodeAttestationRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*agent.AttestationRequest)
if len(req.ReportData) != agent.ReportDataSize {
return nil, errors.New("malformed report data, expect 64 bytes")
var reportData [quoteprovider.Nonce]byte
var nonce [vtpm.Nonce]byte
if err := validateNonce(req.TeeNonce, quoteprovider.Nonce, &reportData); err != nil {
return nil, err
}
return attestationReq{ReportData: [agent.ReportDataSize]byte(req.ReportData)}, nil
if err := validateNonce(req.VtpmNonce, vtpm.Nonce, &nonce); err != nil {
return nil, err
}
return attestationReq{
TeeNonce: reportData,
VtpmNonce: nonce,
AttType: attestation.PlatformType(req.Type),
}, nil
}
func encodeAttestationResponse(_ context.Context, response interface{}) (interface{}, error) {
@@ -109,111 +181,262 @@ func encodeAttestationResponse(_ context.Context, response interface{}) (interfa
}, nil
}
// Algo implements agent.AgentServiceServer.
func (s *grpcServer) Algo(stream agent.AgentService_AlgoServer) error {
var algoFile, reqFile []byte
func decodeAttestationResultRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*agent.AttestationResultRequest)
var nonce [vtpm.Nonce]byte
if err := validateNonce(req.TokenNonce, vtpm.Nonce, &nonce); err != nil {
return nil, err
}
return FetchAttestationResultReq{
tokenNonce: nonce,
AttType: attestation.PlatformType(req.Type),
}, nil
}
func encodeAttestationResultResponse(_ context.Context, response interface{}) (interface{}, error) {
res := response.(fetchAttestationResultRes)
return &agent.AttestationResultResponse{
File: res.File,
}, nil
}
func decodeIMAMeasurementsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
return imaMeasurementsReq{}, nil
}
func encodeIMAMeasurementsResponse(_ context.Context, response interface{}) (interface{}, error) {
res := response.(imaMeasurementsRes)
return &agent.IMAMeasurementsResponse{
File: res.File,
Pcr10: res.PCR10,
}, nil
}
func (s *grpcServer) streamingHandler(
ctx context.Context,
handlerName string,
req interface{},
stream interface{},
sendFn func([]byte) error,
getFileData func(interface{}) []byte,
) error {
handler, ok := s.handlers[handlerName]
if !ok {
return status.Errorf(codes.NotFound, "handler %q not found", handlerName)
}
_, res, err := handler.ServeGRPC(ctx, req)
if err != nil {
return err
}
fileData := getFileData(res)
// Set file size header
if setter, ok := stream.(interface{ SetHeader(metadata.MD) error }); ok {
if err := setter.SetHeader(metadata.New(map[string]string{
FileSizeKey: fmt.Sprint(len(fileData)),
})); err != nil {
return status.Error(codes.Internal, err.Error())
}
}
// Stream the file data
return s.streamFileData(bytes.NewBuffer(fileData), sendFn)
}
func (s *grpcServer) streamFileData(buffer *bytes.Buffer, sendFn func([]byte) error) error {
buf := make([]byte, bufferSize)
for {
algoChunk, err := stream.Recv()
n, err := buffer.Read(buf)
if err == io.EOF {
break
}
if err != nil {
return status.Error(codes.Internal, err.Error())
}
algoFile = append(algoFile, algoChunk.Algorithm...)
reqFile = append(reqFile, algoChunk.Requirements...)
if err := sendFn(buf[:n]); err != nil {
return status.Error(codes.Internal, err.Error())
}
}
_, res, err := s.algo.ServeGRPC(stream.Context(), &agent.AlgoRequest{Algorithm: algoFile, Requirements: reqFile})
return nil
}
func receiveStreamingData(getData func() ([]byte, string, error)) ([]byte, string, error) {
var data []byte
var filename string
for {
chunk, fname, err := getData()
if err == io.EOF {
break
}
if err != nil {
return nil, "", status.Error(codes.Internal, err.Error())
}
data = append(data, chunk...)
if fname != "" {
filename = fname
}
}
return data, filename, nil
}
// Algo implements agent.AgentServiceServer.
func (s *grpcServer) Algo(stream agent.AgentService_AlgoServer) error {
algoFile, reqFile, err := s.receiveAlgoData(stream)
if err != nil {
return err
}
ar := res.(*agent.AlgoResponse)
return stream.SendAndClose(ar)
_, res, err := s.handlers["algo"].ServeGRPC(stream.Context(), &agent.AlgoRequest{
Algorithm: algoFile,
Requirements: reqFile,
})
if err != nil {
return err
}
return stream.SendAndClose(res.(*agent.AlgoResponse))
}
func (s *grpcServer) receiveAlgoData(stream agent.AgentService_AlgoServer) ([]byte, []byte, error) {
var algoFile, reqFile []byte
for {
chunk, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return nil, nil, status.Error(codes.Internal, err.Error())
}
algoFile = append(algoFile, chunk.Algorithm...)
reqFile = append(reqFile, chunk.Requirements...)
}
return algoFile, reqFile, nil
}
// Data implements agent.AgentServiceServer.
func (s *grpcServer) Data(stream agent.AgentService_DataServer) error {
var dataFile []byte
var filename string
for {
dataChunk, err := stream.Recv()
if err == io.EOF {
break
}
dataFile, filename, err := receiveStreamingData(func() ([]byte, string, error) {
chunk, err := stream.Recv()
if err != nil {
return status.Error(codes.Internal, err.Error())
return nil, "", err
}
dataFile = append(dataFile, dataChunk.Dataset...)
filename = dataChunk.Filename
}
_, res, err := s.data.ServeGRPC(stream.Context(), &agent.DataRequest{Dataset: dataFile, Filename: filename})
return chunk.Dataset, chunk.Filename, nil
})
if err != nil {
return err
}
ar := res.(*agent.DataResponse)
return stream.SendAndClose(ar)
_, res, err := s.handlers["data"].ServeGRPC(stream.Context(), &agent.DataRequest{
Dataset: dataFile,
Filename: filename,
})
if err != nil {
return err
}
return stream.SendAndClose(res.(*agent.DataResponse))
}
func (s *grpcServer) Result(req *agent.ResultRequest, stream agent.AgentService_ResultServer) error {
_, res, err := s.result.ServeGRPC(stream.Context(), req)
if err != nil {
return err
}
rr := res.(*agent.ResultResponse)
if err := stream.SetHeader(metadata.New(map[string]string{FileSizeKey: fmt.Sprint(len(rr.File))})); err != nil {
return status.Error(codes.Internal, err.Error())
}
resultBuffer := bytes.NewBuffer(rr.File)
buf := make([]byte, bufferSize)
for {
n, err := resultBuffer.Read(buf)
if err == io.EOF {
break
}
if err != nil {
return status.Error(codes.Internal, err.Error())
}
if err := stream.Send(&agent.ResultResponse{File: buf[:n]}); err != nil {
return status.Error(codes.Internal, err.Error())
}
}
return nil
return s.streamingHandler(
stream.Context(),
"result",
req,
stream,
func(data []byte) error {
return stream.Send(&agent.ResultResponse{File: data})
},
func(res interface{}) []byte {
return res.(*agent.ResultResponse).File
},
)
}
func (s *grpcServer) Attestation(req *agent.AttestationRequest, stream agent.AgentService_AttestationServer) error {
_, res, err := s.attestation.ServeGRPC(stream.Context(), req)
return s.streamingHandler(
stream.Context(),
"attestation",
req,
stream,
func(data []byte) error {
return stream.Send(&agent.AttestationResponse{File: data})
},
func(res interface{}) []byte {
return res.(*agent.AttestationResponse).File
},
)
}
func (s *grpcServer) IMAMeasurements(req *agent.IMAMeasurementsRequest, stream agent.AgentService_IMAMeasurementsServer) error {
_, res, err := s.handlers["imaMeasurements"].ServeGRPC(stream.Context(), req)
if err != nil {
return err
}
rr := res.(*agent.AttestationResponse)
rr := res.(*agent.IMAMeasurementsResponse)
if err := stream.SetHeader(metadata.New(map[string]string{FileSizeKey: fmt.Sprint(len(rr.File))})); err != nil {
if err := stream.SetHeader(metadata.New(map[string]string{
FileSizeKey: strconv.Itoa(len(rr.File)),
})); err != nil {
return status.Error(codes.Internal, err.Error())
}
attestationBuffer := bytes.NewBuffer(rr.File)
return s.streamDualBuffers(
bytes.NewBuffer(rr.File),
bytes.NewBuffer(rr.Pcr10),
func(fileData, pcr10Data []byte) error {
return stream.Send(&agent.IMAMeasurementsResponse{
File: fileData,
Pcr10: pcr10Data,
})
},
)
}
buf := make([]byte, bufferSize)
func (s *grpcServer) streamDualBuffers(
buf1, buf2 *bytes.Buffer,
sendFn func([]byte, []byte) error,
) error {
buff1 := make([]byte, bufferSize)
buff2 := make([]byte, bufferSize)
for {
n, err := attestationBuffer.Read(buf)
if err == io.EOF {
break
}
if err != nil {
return status.Error(codes.Internal, err.Error())
n1, err1 := buf1.Read(buff1)
if err1 != nil && err1 != io.EOF {
return status.Error(codes.Internal, err1.Error())
}
if err := stream.Send(&agent.AttestationResponse{File: buf[:n]}); err != nil {
n2, err2 := buf2.Read(buff2)
if err2 != nil && err2 != io.EOF {
return status.Error(codes.Internal, err2.Error())
}
if n1 == 0 && err1 == io.EOF && n2 == 0 && err2 == io.EOF {
break
}
if err := sendFn(buff1[:n1], buff2[:n2]); err != nil {
return status.Error(codes.Internal, err.Error())
}
}
return nil
}
func (s *grpcServer) AttestationResult(ctx context.Context, req *agent.AttestationResultRequest) (*agent.AttestationResultResponse, error) {
_, res, err := s.handlers["attestationResult"].ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
rr, ok := res.(*agent.AttestationResultResponse)
if !ok {
return nil, status.Error(codes.Internal, "failed to cast response to AttestationResultResponse")
}
return rr, nil
}
+307 -17
View File
@@ -11,6 +11,9 @@ import (
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/mocks"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
@@ -65,8 +68,9 @@ func (m *MockAgentService_ResultServer) Context() context.Context {
return m.ctx
}
func (m *MockAgentService_ResultServer) SetHeader(metadata.MD) error {
return nil
func (m *MockAgentService_ResultServer) SetHeader(md metadata.MD) error {
args := m.Called(md)
return args.Error(0)
}
func (m *MockAgentService_ResultServer) Send(resp *agent.ResultResponse) error {
@@ -89,8 +93,46 @@ func (m *MockAgentService_AttestationServer) Send(resp *agent.AttestationRespons
return args.Error(0)
}
func (m *MockAgentService_AttestationServer) SetHeader(metadata.MD) error {
return nil
func (m *MockAgentService_AttestationServer) SetHeader(md metadata.MD) error {
args := m.Called(md)
return args.Error(0)
}
type MockAgentService_IMAMeasurementsServer struct {
grpc.ServerStream
mock.Mock
ctx context.Context
}
func (m *MockAgentService_IMAMeasurementsServer) Context() context.Context {
return m.ctx
}
func (m *MockAgentService_IMAMeasurementsServer) Send(resp *agent.IMAMeasurementsResponse) error {
args := m.Called(resp)
return args.Error(0)
}
func (m *MockAgentService_IMAMeasurementsServer) SetHeader(md metadata.MD) error {
args := m.Called(md)
return args.Error(0)
}
func TestNewServer(t *testing.T) {
mockService := new(mocks.Service)
server := NewServer(mockService)
grpcServer, ok := server.(*grpcServer)
assert.True(t, ok)
assert.NotNil(t, grpcServer.handlers)
assert.Len(t, grpcServer.handlers, 6) // Should have 6 handlers
// Check that all expected handlers are present
expectedHandlers := []string{"algo", "data", "result", "attestation", "imaMeasurements", "attestationResult"}
for _, handler := range expectedHandlers {
assert.Contains(t, grpcServer.handlers, handler)
assert.NotNil(t, grpcServer.handlers[handler])
}
}
func TestAlgo(t *testing.T) {
@@ -99,8 +141,8 @@ func TestAlgo(t *testing.T) {
mockStream := &MockAgentService_AlgoServer{ctx: context.Background()}
mockStream.On("Recv").Return(&agent.AlgoRequest{Algorithm: []byte("algo"), Requirements: []byte("req")}, nil).Once()
mockStream.On("Recv").Return(&agent.AlgoRequest{}, io.EOF)
mockStream.On("SendAndClose", &agent.AlgoResponse{}).Return(nil)
mockStream.On("Recv").Return(&agent.AlgoRequest{}, io.EOF).Once()
mockStream.On("SendAndClose", &agent.AlgoResponse{}).Return(nil).Once()
mockService.On("Algo", context.Background(), agent.Algorithm{Algorithm: []byte("algo"), Requirements: []byte("req")}).Return(nil)
@@ -111,14 +153,33 @@ func TestAlgo(t *testing.T) {
mockService.AssertExpectations(t)
}
func TestAlgoWithMultipleChunks(t *testing.T) {
mockService := new(mocks.Service)
server := NewServer(mockService)
mockStream := &MockAgentService_AlgoServer{ctx: context.Background()}
mockStream.On("Recv").Return(&agent.AlgoRequest{Algorithm: []byte("algo"), Requirements: []byte("req")}, nil).Once()
mockStream.On("Recv").Return(&agent.AlgoRequest{Algorithm: []byte("2"), Requirements: []byte("2")}, nil).Once()
mockStream.On("Recv").Return(&agent.AlgoRequest{}, io.EOF).Once()
mockStream.On("SendAndClose", &agent.AlgoResponse{}).Return(nil).Once()
mockService.On("Algo", context.Background(), agent.Algorithm{Algorithm: []byte("algo2"), Requirements: []byte("req2")}).Return(nil)
err := server.Algo(mockStream)
assert.NoError(t, err)
mockStream.AssertExpectations(t)
mockService.AssertExpectations(t)
}
func TestData(t *testing.T) {
mockService := new(mocks.Service)
server := NewServer(mockService)
mockStream := &MockAgentService_DataServer{ctx: context.Background()}
mockStream.On("Recv").Return(&agent.DataRequest{Dataset: []byte("data"), Filename: "test.txt"}, nil).Once()
mockStream.On("Recv").Return(&agent.DataRequest{}, io.EOF)
mockStream.On("SendAndClose", &agent.DataResponse{}).Return(nil)
mockStream.On("Recv").Return(&agent.DataRequest{}, io.EOF).Once()
mockStream.On("SendAndClose", &agent.DataResponse{}).Return(nil).Once()
mockService.On("Data", context.Background(), agent.Dataset{Dataset: []byte("data"), Filename: "test.txt"}).Return(nil)
@@ -133,9 +194,18 @@ func TestResult(t *testing.T) {
mockService := new(mocks.Service)
server := NewServer(mockService)
resultData := []byte("result data")
mockStream := &MockAgentService_ResultServer{ctx: context.Background()}
mockService.On("Result", mock.Anything).Return([]byte("result data"), nil)
mockStream.On("Send", mock.AnythingOfType("*agent.ResultResponse")).Return(nil)
// Mock the SetHeader call
mockStream.On("SetHeader", mock.AnythingOfType("metadata.MD")).Return(nil).Once()
// Mock the Send call - it should be called with the result data
mockStream.On("Send", mock.MatchedBy(func(resp *agent.ResultResponse) bool {
return len(resp.File) > 0
})).Return(nil).Once()
mockService.On("Result", mock.Anything).Return(resultData, nil)
err := server.Result(&agent.ResultRequest{}, mockStream)
assert.NoError(t, err)
@@ -148,16 +218,135 @@ func TestAttestation(t *testing.T) {
mockService := new(mocks.Service)
server := NewServer(mockService)
attestationData := []byte("attestation data")
mockStream := &MockAgentService_AttestationServer{ctx: context.Background()}
mockStream.On("Send", mock.AnythingOfType("*agent.AttestationResponse")).Return(nil)
reportData := [agent.ReportDataSize]byte{}
mockService.On("Attestation", mock.Anything, reportData).Return([]byte("attestation data"), nil)
// Mock the SetHeader call
mockStream.On("SetHeader", mock.AnythingOfType("metadata.MD")).Return(nil).Once()
err := server.Attestation(&agent.AttestationRequest{ReportData: reportData[:]}, mockStream)
// Mock the Send call
mockStream.On("Send", mock.MatchedBy(func(resp *agent.AttestationResponse) bool {
return len(resp.File) > 0
})).Return(nil).Once()
reportData := [quoteprovider.Nonce]byte{}
vtpmNonce := [vtpm.Nonce]byte{}
attestationType := attestation.SNP
mockService.On("Attestation", mock.Anything, reportData, vtpmNonce, attestationType).Return(attestationData, nil)
err := server.Attestation(&agent.AttestationRequest{TeeNonce: reportData[:], Type: int32(attestationType)}, mockStream)
assert.NoError(t, err)
mockService.AssertExpectations(t)
mockStream.AssertExpectations(t)
}
func TestIMAMeasurements(t *testing.T) {
mockService := new(mocks.Service)
server := NewServer(mockService)
imaData := []byte("ima data")
pcr10Data := []byte("pcr10 data")
mockStream := &MockAgentService_IMAMeasurementsServer{ctx: context.Background()}
// Mock the SetHeader call
mockStream.On("SetHeader", mock.AnythingOfType("metadata.MD")).Return(nil).Once()
// Mock the Send call
mockStream.On("Send", mock.MatchedBy(func(resp *agent.IMAMeasurementsResponse) bool {
return len(resp.File) > 0 || len(resp.Pcr10) > 0
})).Return(nil).Once()
mockService.On("IMAMeasurements", mock.Anything).Return(imaData, pcr10Data, nil)
err := server.IMAMeasurements(&agent.IMAMeasurementsRequest{}, mockStream)
assert.NoError(t, err)
mockService.AssertExpectations(t)
mockStream.AssertExpectations(t)
}
func TestAttestationResult(t *testing.T) {
mockService := new(mocks.Service)
server := NewServer(mockService)
attestationData := []byte("attestation result data")
vtpmNonce := [vtpm.Nonce]byte{}
attestationType := attestation.SNP
mockService.On("AttestationResult", mock.Anything, vtpmNonce, attestationType).Return(attestationData, nil)
resp, err := server.AttestationResult(context.Background(), &agent.AttestationResultRequest{
TokenNonce: vtpmNonce[:],
Type: int32(attestationType),
})
assert.NoError(t, err)
assert.Equal(t, attestationData, resp.File)
mockService.AssertExpectations(t)
}
func TestValidateNonce(t *testing.T) {
tests := []struct {
name string
nonce []byte
maxLen int
shouldError bool
expectedErr error
}{
{
name: "valid TEE nonce",
nonce: make([]byte, quoteprovider.Nonce),
maxLen: quoteprovider.Nonce,
shouldError: false,
},
{
name: "valid vTPM nonce",
nonce: make([]byte, vtpm.Nonce),
maxLen: vtpm.Nonce,
shouldError: false,
},
{
name: "TEE nonce too long",
nonce: make([]byte, quoteprovider.Nonce+1),
maxLen: quoteprovider.Nonce,
shouldError: true,
expectedErr: ErrTEENonceLength,
},
{
name: "vTPM nonce too long",
nonce: make([]byte, vtpm.Nonce+1),
maxLen: vtpm.Nonce,
shouldError: true,
expectedErr: ErrVTPMNonceLength,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.maxLen == quoteprovider.Nonce {
var target [quoteprovider.Nonce]byte
err := validateNonce(tt.nonce, tt.maxLen, &target)
if tt.shouldError {
assert.Error(t, err)
assert.Equal(t, tt.expectedErr, err)
} else {
assert.NoError(t, err)
}
} else {
var target [vtpm.Nonce]byte
err := validateNonce(tt.nonce, tt.maxLen, &target)
if tt.shouldError {
assert.Error(t, err)
assert.Equal(t, tt.expectedErr, err)
} else {
assert.NoError(t, err)
}
}
})
}
}
func TestDecodeAlgoRequest(t *testing.T) {
@@ -199,11 +388,38 @@ func TestEncodeResultResponse(t *testing.T) {
}
func TestDecodeAttestationRequest(t *testing.T) {
reportData := [agent.ReportDataSize]byte{}
req := &agent.AttestationRequest{ReportData: reportData[:]}
teeNonce := make([]byte, quoteprovider.Nonce)
vtpmNonce := make([]byte, vtpm.Nonce)
req := &agent.AttestationRequest{
TeeNonce: teeNonce,
VtpmNonce: vtpmNonce,
Type: int32(attestation.SNP),
}
decoded, err := decodeAttestationRequest(context.Background(), req)
assert.NoError(t, err)
assert.Equal(t, attestationReq{ReportData: reportData}, decoded)
decodedReq := decoded.(attestationReq)
assert.Equal(t, attestation.SNP, decodedReq.AttType)
}
func TestDecodeAttestationRequestWithInvalidNonce(t *testing.T) {
// Test with TEE nonce too long
teeNonce := make([]byte, quoteprovider.Nonce+1)
req := &agent.AttestationRequest{TeeNonce: teeNonce}
_, err := decodeAttestationRequest(context.Background(), req)
assert.Error(t, err)
assert.Equal(t, ErrTEENonceLength, err)
// Test with vTPM nonce too long
vtpmNonce := make([]byte, vtpm.Nonce+1)
req = &agent.AttestationRequest{VtpmNonce: vtpmNonce}
_, err = decodeAttestationRequest(context.Background(), req)
assert.Error(t, err)
assert.Equal(t, ErrVTPMNonceLength, err)
}
func TestEncodeAttestationResponse(t *testing.T) {
@@ -211,3 +427,77 @@ func TestEncodeAttestationResponse(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, &agent.AttestationResponse{File: []byte("attestation")}, encoded)
}
func TestDecodeAttestationResultRequest(t *testing.T) {
tokenNonce := make([]byte, vtpm.Nonce)
req := &agent.AttestationResultRequest{
TokenNonce: tokenNonce,
Type: int32(attestation.SNP),
}
decoded, err := decodeAttestationResultRequest(context.Background(), req)
assert.NoError(t, err)
decodedReq := decoded.(FetchAttestationResultReq)
assert.Equal(t, attestation.SNP, decodedReq.AttType)
}
func TestDecodeAttestationResultRequestWithInvalidNonce(t *testing.T) {
// Test with token nonce too long
tokenNonce := make([]byte, vtpm.Nonce+1)
req := &agent.AttestationResultRequest{TokenNonce: tokenNonce}
_, err := decodeAttestationResultRequest(context.Background(), req)
assert.Error(t, err)
assert.Equal(t, ErrVTPMNonceLength, err)
}
func TestEncodeAttestationResultResponse(t *testing.T) {
encoded, err := encodeAttestationResultResponse(context.Background(), fetchAttestationResultRes{File: []byte("attestation")})
assert.NoError(t, err)
assert.Equal(t, &agent.AttestationResultResponse{File: []byte("attestation")}, encoded)
}
func TestDecodeIMAMeasurementsRequest(t *testing.T) {
decoded, err := decodeIMAMeasurementsRequest(context.Background(), &agent.IMAMeasurementsRequest{})
assert.NoError(t, err)
assert.Equal(t, imaMeasurementsReq{}, decoded)
}
func TestEncodeIMAMeasurementsResponse(t *testing.T) {
encoded, err := encodeIMAMeasurementsResponse(context.Background(), imaMeasurementsRes{
File: []byte("ima"),
PCR10: []byte("pcr10"),
})
assert.NoError(t, err)
assert.Equal(t, &agent.IMAMeasurementsResponse{
File: []byte("ima"),
Pcr10: []byte("pcr10"),
}, encoded)
}
func TestAlgoWithStreamError(t *testing.T) {
mockService := new(mocks.Service)
server := NewServer(mockService)
mockStream := &MockAgentService_AlgoServer{ctx: context.Background()}
mockStream.On("Recv").Return(&agent.AlgoRequest{}, assert.AnError).Once()
err := server.Algo(mockStream)
assert.Error(t, err)
mockStream.AssertExpectations(t)
}
func TestDataWithStreamError(t *testing.T) {
mockService := new(mocks.Service)
server := NewServer(mockService)
mockStream := &MockAgentService_DataServer{ctx: context.Background()}
mockStream.On("Recv").Return(&agent.DataRequest{}, assert.AnError).Once()
err := server.Data(mockStream)
assert.Error(t, err)
mockStream.AssertExpectations(t)
}
+68 -2
View File
@@ -13,6 +13,9 @@ import (
"time"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
var _ agent.Service = (*loggingMiddleware)(nil)
@@ -27,6 +30,43 @@ func LoggingMiddleware(svc agent.Service, logger *slog.Logger) agent.Service {
return &loggingMiddleware{logger, svc}
}
// State implements agent.Service.
func (lm *loggingMiddleware) State() (state string) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method State took %s to complete with state %s", time.Since(begin), state)
lm.logger.Debug(message)
}(time.Now())
return lm.svc.State()
}
// InitComputation implements agent.Service.
func (lm *loggingMiddleware) InitComputation(ctx context.Context, cmp agent.Computation) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method InitComputation for computation id %s took %s to complete", cmp.ID, time.Since(begin))
if err != nil {
lm.logger.WithGroup(cmp.ID).Warn(fmt.Sprintf("%s with error: %s", message, err))
return
}
lm.logger.WithGroup(cmp.ID).Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
return lm.svc.InitComputation(ctx, cmp)
}
// StopComputation implements agent.Service.
func (lm *loggingMiddleware) StopComputation(ctx context.Context) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method StopComputation took %s to complete", time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
return lm.svc.StopComputation(ctx)
}
func (lm *loggingMiddleware) Algo(ctx context.Context, algorithm agent.Algorithm) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method Algo took %s to complete", time.Since(begin))
@@ -66,7 +106,7 @@ func (lm *loggingMiddleware) Result(ctx context.Context) (response []byte, err e
return lm.svc.Result(ctx)
}
func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [agent.ReportDataSize]byte) (response []byte, err error) {
func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) (response []byte, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method Attestation took %s to complete", time.Since(begin))
if err != nil {
@@ -76,5 +116,31 @@ func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [agent.
lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
return lm.svc.Attestation(ctx, reportData)
return lm.svc.Attestation(ctx, reportData, nonce, attType)
}
func (lm *loggingMiddleware) IMAMeasurements(ctx context.Context) (file []byte, pcr10 []byte, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method IMAMeasurements took %s to complete", time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
return lm.svc.IMAMeasurements(ctx)
}
func (lm *loggingMiddleware) AttestationResult(ctx context.Context, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) (response []byte, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method AttestationResult took %s to complete", time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
return lm.svc.AttestationResult(ctx, nonce, attType)
}
+53 -2
View File
@@ -12,6 +12,9 @@ import (
"github.com/go-kit/kit/metrics"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
var _ agent.Service = (*metricsMiddleware)(nil)
@@ -32,6 +35,36 @@ func MetricsMiddleware(svc agent.Service, counter metrics.Counter, latency metri
}
}
// State implements agent.Service.
func (ms *metricsMiddleware) State() string {
defer func(begin time.Time) {
ms.counter.With("method", "state").Add(1)
ms.latency.With("method", "state").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.State()
}
// InitComputation implements agent.Service.
func (ms *metricsMiddleware) InitComputation(ctx context.Context, cmp agent.Computation) error {
defer func(begin time.Time) {
ms.counter.With("method", "init_computation").Add(1)
ms.latency.With("method", "init_computation").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.InitComputation(ctx, cmp)
}
// StopComputation implements agent.Service.
func (ms *metricsMiddleware) StopComputation(ctx context.Context) error {
defer func(begin time.Time) {
ms.counter.With("method", "stop_computation").Add(1)
ms.latency.With("method", "stop_computation").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.StopComputation(ctx)
}
func (ms *metricsMiddleware) Algo(ctx context.Context, algorithm agent.Algorithm) error {
defer func(begin time.Time) {
ms.counter.With("method", "algo").Add(1)
@@ -59,11 +92,29 @@ func (ms *metricsMiddleware) Result(ctx context.Context) ([]byte, error) {
return ms.svc.Result(ctx)
}
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [agent.ReportDataSize]byte) ([]byte, error) {
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error) {
defer func(begin time.Time) {
ms.counter.With("method", "attestation").Add(1)
ms.latency.With("method", "attestation").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Attestation(ctx, reportData)
return ms.svc.Attestation(ctx, reportData, nonce, attType)
}
func (ms *metricsMiddleware) AttestationResult(ctx context.Context, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error) {
defer func(begin time.Time) {
ms.counter.With("method", "attestation_result").Add(1)
ms.latency.With("method", "attestation_result").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.AttestationResult(ctx, nonce, attType)
}
func (ms *metricsMiddleware) IMAMeasurements(ctx context.Context) ([]byte, []byte, error) {
defer func(begin time.Time) {
ms.counter.With("method", "imameasurements").Add(1)
ms.latency.With("method", "imameasurements").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.IMAMeasurements(ctx)
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
-3
View File
@@ -13,8 +13,6 @@ import (
var _ fmt.Stringer = (*Datasets)(nil)
type AgentConfig struct {
LogLevel string `json:"log_level,omitempty"`
Host string `json:"host,omitempty"`
Port string `json:"port,omitempty"`
CertFile string `json:"cert_file,omitempty"`
KeyFile string `json:"server_key,omitempty"`
@@ -30,7 +28,6 @@ type Computation struct {
Datasets Datasets `json:"datasets,omitempty"`
Algorithm Algorithm `json:"algorithm,omitempty"`
ResultConsumers []ResultConsumer `json:"result_consumers,omitempty"`
AgentConfig AgentConfig `json:"agent_config,omitempty"`
}
type ResultConsumer struct {
-2
View File
@@ -106,8 +106,6 @@ func TestDecompressToContext(t *testing.T) {
func TestAgentConfigJSON(t *testing.T) {
config := AgentConfig{
LogLevel: "info",
Host: "localhost",
Port: "8080",
CertFile: "cert.pem",
KeyFile: "key.pem",
+379
View File
@@ -0,0 +1,379 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
"encoding/json"
"log/slog"
"sync"
"time"
"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"
"github.com/ultravioletrs/cocos/agent/cvms/server"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
"golang.org/x/sync/errgroup"
"google.golang.org/protobuf/proto"
)
const (
reconnectInterval = 5 * time.Second
sendTimeout = 5 * time.Second
pendingMsgFile = "pending_messages.json"
)
var (
errCorruptedManifest = errors.New("received manifest may be corrupted")
errUnknonwMessageType = errors.New("unknown message type")
)
type PendingMessage struct {
Message *cvms.ClientStreamMessage
Time time.Time
}
type CVMSClient struct {
mu sync.Mutex
stream cvms.Service_ProcessClient
svc agent.Service
messageQueue chan *cvms.ClientStreamMessage
logger *slog.Logger
runReqManager *runRequestManager
sp server.AgentServer
storage storage.Storage
reconnectFn func(context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error)
grpcClient pkggrpc.Client
}
// NewClient returns new gRPC client instance.
func NewClient(stream cvms.Service_ProcessClient, svc agent.Service, messageQueue chan *cvms.ClientStreamMessage, logger *slog.Logger, sp server.AgentServer, storageDir string, reconnectFn func(context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error), grpcClient pkggrpc.Client) (*CVMSClient, error) {
store, err := storage.NewFileStorage(storageDir)
if err != nil {
return nil, err
}
return &CVMSClient{
stream: stream,
svc: svc,
messageQueue: messageQueue,
logger: logger,
runReqManager: newRunRequestManager(),
sp: sp,
storage: store,
reconnectFn: reconnectFn,
grpcClient: grpcClient,
}, nil
}
func (client *CVMSClient) Process(ctx context.Context, cancel context.CancelFunc) error {
for {
err := client.processWithRetry(ctx)
if ctx.Err() != nil {
return ctx.Err()
}
client.logger.Info("Connection lost, attempting to reconnect...", "error", err)
time.Sleep(reconnectInterval)
grpcClient, stream, err := client.reconnectFn(ctx)
if err != nil {
client.logger.Error("Failed to reconnect", "error", err)
continue
}
client.mu.Lock()
client.stream = stream
client.grpcClient = grpcClient
client.mu.Unlock()
}
}
func (client *CVMSClient) processWithRetry(ctx context.Context) error {
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
return client.handleIncomingMessages(ctx)
})
eg.Go(func() error {
return client.handleOutgoingMessages(ctx)
})
return eg.Wait()
}
func (client *CVMSClient) handleIncomingMessages(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
req, err := client.stream.Recv()
if err != nil {
return err
}
if err := client.processIncomingMessage(ctx, req); err != nil {
return err
}
}
}
}
func (client *CVMSClient) handleOutgoingMessages(ctx context.Context) error {
pendingMsgs, err := client.storage.Load()
if err != nil {
client.logger.Error("Failed to load pending messages", "error", err)
} else {
client.sendPendingMessages(pendingMsgs)
}
for {
select {
case <-ctx.Done():
return ctx.Err()
case msg := <-client.messageQueue:
if err := client.sendStreamMessage(msg); err != nil {
if err := client.storage.Add(msg); err != nil {
client.logger.Error("Failed to store pending message", "error", err)
}
client.logger.Error("Failed to send message, stored for retry", "error", err)
}
}
}
}
func (client *CVMSClient) sendStreamMessage(msg *cvms.ClientStreamMessage) error {
client.mu.Lock()
defer client.mu.Unlock()
return client.stream.Send(msg)
}
func (client *CVMSClient) sendPendingMessages(pending []storage.Message) {
for _, pm := range pending {
if err := client.sendStreamMessage(pm.Message); err != nil {
if err := client.storage.Add(pm.Message); err != nil {
client.logger.Error("Failed to store pending message", "error", err)
}
client.logger.Error("Failed to resend pending message", "error", err)
} else {
client.logger.Info("Successfully resent pending message")
}
}
if err := client.storage.Clear(); err != nil {
client.logger.Error("Failed to clear pending messages", "error", err)
}
}
func (client *CVMSClient) processIncomingMessage(ctx context.Context, req *cvms.ServerStreamMessage) error {
switch mes := req.Message.(type) {
case *cvms.ServerStreamMessage_RunReqChunks:
return client.handleRunReqChunks(ctx, mes)
case *cvms.ServerStreamMessage_StopComputation:
go client.handleStopComputation(ctx, mes)
case *cvms.ServerStreamMessage_AgentStateReq:
client.handleAgentStateReq(mes)
case *cvms.ServerStreamMessage_DisconnectReq:
client.logger.Info("Received disconnect request")
client.mu.Lock()
if err := client.grpcClient.Close(); err != nil {
client.logger.Error("Failed to close gRPC client", "error", err)
}
client.mu.Unlock()
default:
return errUnknonwMessageType
}
return nil
}
func (client *CVMSClient) handleAgentStateReq(mes *cvms.ServerStreamMessage_AgentStateReq) {
state := client.svc.State()
msg := &cvms.ClientStreamMessage_AgentStateRes{
AgentStateRes: &cvms.AgentStateRes{
State: state,
Id: mes.AgentStateReq.Id,
},
}
client.sendMessage(&cvms.ClientStreamMessage{Message: msg})
}
func (client *CVMSClient) handleRunReqChunks(ctx context.Context, msg *cvms.ServerStreamMessage_RunReqChunks) error {
buffer, complete := client.runReqManager.addChunk(msg.RunReqChunks.Id, msg.RunReqChunks.Data, msg.RunReqChunks.IsLast)
if complete {
var runReq cvms.ComputationRunReq
if err := proto.Unmarshal(buffer, &runReq); err != nil {
return errors.Wrap(err, errCorruptedManifest)
}
go client.executeRun(ctx, &runReq)
}
return nil
}
func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.ComputationRunReq) {
ac := agent.Computation{
ID: runReq.Id,
Name: runReq.Name,
Description: runReq.Description,
}
if runReq.Algorithm != nil {
ac.Algorithm = agent.Algorithm{
Hash: [32]byte(runReq.Algorithm.Hash),
UserKey: runReq.Algorithm.UserKey,
}
}
for _, ds := range runReq.Datasets {
ac.Datasets = append(ac.Datasets, agent.Dataset{
Hash: [32]byte(ds.Hash),
UserKey: ds.UserKey,
})
}
for _, rc := range runReq.ResultConsumers {
ac.ResultConsumers = append(ac.ResultConsumers, agent.ResultConsumer{
UserKey: rc.UserKey,
})
}
if err := client.svc.InitComputation(ctx, ac); err != nil {
client.logger.Warn(err.Error())
return
}
ccPlatform := attestation.CCPlatform()
client.mu.Lock()
defer client.mu.Unlock()
if runReq.AgentConfig == nil {
runReq.AgentConfig = &cvms.AgentConfig{}
}
runRes := &cvms.ClientStreamMessage_RunRes{
RunRes: &cvms.RunResponse{
ComputationId: runReq.Id,
},
}
if err := client.sp.Start(agent.AgentConfig{
Port: runReq.AgentConfig.Port,
CertFile: runReq.AgentConfig.CertFile,
KeyFile: runReq.AgentConfig.KeyFile,
ServerCAFile: runReq.AgentConfig.ServerCaFile,
ClientCAFile: runReq.AgentConfig.ClientCaFile,
AttestedTls: runReq.AgentConfig.AttestedTls,
}, ac); err != nil {
client.logger.Warn(err.Error())
runRes.RunRes.Error = err.Error()
}
defer func() {
if ccPlatform == attestation.Azure || ccPlatform == attestation.SNPvTPM {
cmpJson, err := json.Marshal(ac)
if err != nil {
client.logger.Error(err.Error())
return
}
if err = vtpm.ExtendPCR(vtpm.PCR16, cmpJson); err != nil {
client.logger.Error(err.Error())
return
}
}
}()
client.sendMessage(&cvms.ClientStreamMessage{Message: runRes})
}
func (client *CVMSClient) handleStopComputation(ctx context.Context, mes *cvms.ServerStreamMessage_StopComputation) {
msg := &cvms.ClientStreamMessage_StopComputationRes{
StopComputationRes: &cvms.StopComputationResponse{
ComputationId: mes.StopComputation.ComputationId,
},
}
if err := client.svc.StopComputation(ctx); err != nil {
msg.StopComputationRes.Message = err.Error()
}
client.mu.Lock()
if err := client.sp.Stop(); err != nil {
msg.StopComputationRes.Message = err.Error()
}
client.mu.Unlock()
client.sendMessage(&cvms.ClientStreamMessage{Message: msg})
}
func (client *CVMSClient) sendMessage(mes *cvms.ClientStreamMessage) {
ctx, cancel := context.WithTimeout(context.Background(), sendTimeout)
defer cancel()
select {
case client.messageQueue <- mes:
case <-ctx.Done():
client.logger.Warn("Failed to send message: timeout exceeded")
}
}
type runRequestManager struct {
requests map[string]*runRequest
mu sync.Mutex
}
type runRequest struct {
buffer []byte
lastChunk time.Time
timer *time.Timer
}
func newRunRequestManager() *runRequestManager {
return &runRequestManager{
requests: make(map[string]*runRequest),
}
}
func (m *runRequestManager) addChunk(id string, chunk []byte, isLast bool) ([]byte, bool) {
m.mu.Lock()
defer m.mu.Unlock()
req, exists := m.requests[id]
if !exists {
req = &runRequest{
buffer: make([]byte, 0),
lastChunk: time.Now(),
timer: time.AfterFunc(runReqTimeout, func() { m.timeoutRequest(id) }),
}
m.requests[id] = req
}
req.buffer = append(req.buffer, chunk...)
req.lastChunk = time.Now()
req.timer.Reset(runReqTimeout)
if isLast {
delete(m.requests, id)
req.timer.Stop()
return req.buffer, true
}
return nil, false
}
func (m *runRequestManager) timeoutRequest(id string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.requests, id)
// Log timeout or handle it as needed
}
+257
View File
@@ -0,0 +1,257 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
"testing"
"time"
mglog "github.com/absmach/magistrala/logger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent/cvms"
servermocks "github.com/ultravioletrs/cocos/agent/cvms/server/mocks"
"github.com/ultravioletrs/cocos/agent/mocks"
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
clientmocks "github.com/ultravioletrs/cocos/pkg/clients/grpc/mocks"
"golang.org/x/crypto/sha3"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
)
type mockStream struct {
mock.Mock
grpc.ClientStream
}
func (m *mockStream) Recv() (*cvms.ServerStreamMessage, error) {
args := m.Called()
return args.Get(0).(*cvms.ServerStreamMessage), args.Error(1)
}
func (m *mockStream) Send(msg *cvms.ClientStreamMessage) error {
args := m.Called(msg)
return args.Error(0)
}
func TestManagerClient_Process(t *testing.T) {
tests := []struct {
name string
setupMocks func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer, grpcClient *clientmocks.Client)
expectError bool
errorMsg string
}{
{
name: "Stop computation",
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer, grpcClient *clientmocks.Client) {
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{
Message: &cvms.ServerStreamMessage_StopComputation{
StopComputation: &cvms.StopComputation{},
},
}, nil)
mockStream.On("Send", mock.Anything).Return(nil)
mockSvc.On("StopComputation", mock.Anything).Return(nil)
mockServerSvc.On("Stop").Return(nil)
},
expectError: true,
errorMsg: "context deadline exceeded",
},
{
name: "Run request chunks",
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer, grpcClient *clientmocks.Client) {
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{
Message: &cvms.ServerStreamMessage_RunReqChunks{
RunReqChunks: &cvms.RunReqChunks{},
},
}, nil)
mockStream.On("Send", mock.Anything).Return(nil).Once()
mockSvc.On("Run", mock.Anything, mock.Anything).Return("", assert.AnError).Once()
},
expectError: true,
},
{
name: "Agent state request",
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer, grpcClient *clientmocks.Client) {
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{
Message: &cvms.ServerStreamMessage_AgentStateReq{
AgentStateReq: &cvms.AgentStateReq{
Id: "test-agent",
},
},
}, nil)
mockStream.On("Send", mock.Anything).Return(nil)
mockSvc.On("State").Return("test-state")
},
expectError: true,
errorMsg: "context deadline exceeded",
},
{
name: "Disconnect request",
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer, grpcClient *clientmocks.Client) {
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{
Message: &cvms.ServerStreamMessage_DisconnectReq{},
}, nil)
mockStream.On("Send", mock.Anything).Return(nil)
grpcClient.On("Close").Return(nil)
},
expectError: true,
errorMsg: "context deadline exceeded",
},
{
name: "Receive error",
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer, grpcClient *clientmocks.Client) {
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{}, assert.AnError)
},
expectError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mockStream := new(mockStream)
mockSvc := new(mocks.Service)
mockServerSvc := new(servermocks.AgentServer)
messageQueue := make(chan *cvms.ClientStreamMessage)
logger := mglog.NewMock()
go func() {
<-messageQueue
}()
grpcClient := new(clientmocks.Client)
client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, t.TempDir(), func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) { return nil, nil, nil }, grpcClient)
assert.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
tc.setupMocks(mockStream, mockSvc, mockServerSvc, grpcClient)
err = client.Process(ctx, cancel)
if tc.expectError {
assert.Error(t, err)
if tc.errorMsg != "" {
assert.Contains(t, err.Error(), tc.errorMsg)
}
} else {
assert.NoError(t, err)
}
})
}
}
func TestManagerClient_handleRunReqChunks(t *testing.T) {
mockStream := new(mockStream)
mockSvc := new(mocks.Service)
mockServerSvc := new(servermocks.AgentServer)
messageQueue := make(chan *cvms.ClientStreamMessage, 10)
logger := mglog.NewMock()
grpcClient := new(clientmocks.Client)
client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, t.TempDir(), func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) { return nil, nil, nil }, grpcClient)
assert.NoError(t, err)
runReq := &cvms.ComputationRunReq{
Id: "test-id",
Datasets: []*cvms.Dataset{
{
Hash: sha3.New256().Sum([]byte("test-dataset")),
},
},
Algorithm: &cvms.Algorithm{
Hash: sha3.New256().Sum([]byte("test-algorithm")),
},
ResultConsumers: []*cvms.ResultConsumer{
{
UserKey: []byte("test-consumer"),
},
},
}
runReqBytes, _ := proto.Marshal(runReq)
chunk1 := &cvms.ServerStreamMessage_RunReqChunks{
RunReqChunks: &cvms.RunReqChunks{
Id: "chunk-1",
Data: runReqBytes[:len(runReqBytes)/2],
IsLast: false,
},
}
chunk2 := &cvms.ServerStreamMessage_RunReqChunks{
RunReqChunks: &cvms.RunReqChunks{
Id: "chunk-1",
Data: runReqBytes[len(runReqBytes)/2:],
IsLast: true,
},
}
mockSvc.On("InitComputation", mock.Anything, mock.Anything).Return(nil)
mockServerSvc.On("Start", mock.Anything, mock.Anything, mock.Anything).Return(nil)
err = client.handleRunReqChunks(context.Background(), chunk1)
assert.NoError(t, err)
err = client.handleRunReqChunks(context.Background(), chunk2)
assert.NoError(t, err)
// Wait for the goroutine to finish
time.Sleep(50 * time.Millisecond)
mockSvc.AssertExpectations(t)
assert.Len(t, messageQueue, 1)
msg := <-messageQueue
runRes, ok := msg.Message.(*cvms.ClientStreamMessage_RunRes)
assert.True(t, ok)
assert.Equal(t, "test-id", runRes.RunRes.ComputationId)
}
func TestManagerClient_handleStopComputation(t *testing.T) {
mockStream := new(mockStream)
mockSvc := new(mocks.Service)
mockServerSvc := new(servermocks.AgentServer)
messageQueue := make(chan *cvms.ClientStreamMessage, 10)
logger := mglog.NewMock()
grpcClient := new(clientmocks.Client)
client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, t.TempDir(), func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) { return nil, nil, nil }, grpcClient)
assert.NoError(t, err)
stopReq := &cvms.ServerStreamMessage_StopComputation{
StopComputation: &cvms.StopComputation{
ComputationId: "test-comp-id",
},
}
mockSvc.On("StopComputation", mock.Anything).Return(nil)
mockServerSvc.On("Stop").Return(nil)
client.handleStopComputation(context.Background(), stopReq)
// Wait for the goroutine to finish
time.Sleep(50 * time.Millisecond)
mockSvc.AssertExpectations(t)
assert.Len(t, messageQueue, 1)
msg := <-messageQueue
stopRes, ok := msg.Message.(*cvms.ClientStreamMessage_StopComputationRes)
assert.True(t, ok)
assert.Equal(t, "test-comp-id", stopRes.StopComputationRes.ComputationId)
assert.Empty(t, stopRes.StopComputationRes.Message)
}
func TestManagerClient_timeoutRequest(t *testing.T) {
rm := newRunRequestManager()
rm.requests["test-id"] = &runRequest{
timer: time.NewTimer(100 * time.Millisecond),
buffer: []byte("test-data"),
lastChunk: time.Now(),
}
rm.timeoutRequest("test-id")
assert.Len(t, rm.requests, 0)
}
+5
View File
@@ -0,0 +1,5 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Package grpc contains implementation of kit service gRPC API.
package grpc
+133
View File
@@ -0,0 +1,133 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"bytes"
"context"
"errors"
"io"
"time"
"github.com/ultravioletrs/cocos/agent/cvms"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/peer"
"google.golang.org/protobuf/proto"
)
var (
_ cvms.ServiceServer = (*grpcServer)(nil)
ErrUnexpectedMsg = errors.New("unknown message type")
)
const (
bufferSize = 1024 * 1024 // 1 MB
runReqTimeout = 30 * time.Second
)
type SendFunc func(*cvms.ServerStreamMessage) error
type grpcServer struct {
cvms.UnimplementedServiceServer
incoming chan *cvms.ClientStreamMessage
svc Service
}
type Service interface {
Run(ctx context.Context, ipAddress string, sendMessage SendFunc, authInfo credentials.AuthInfo)
}
// NewServer returns new AuthServiceServer instance.
func NewServer(incoming chan *cvms.ClientStreamMessage, svc Service) cvms.ServiceServer {
return &grpcServer{
incoming: incoming,
svc: svc,
}
}
func (s *grpcServer) Process(stream cvms.Service_ProcessServer) error {
client, ok := peer.FromContext(stream.Context())
if !ok {
return errors.New("failed to get peer info")
}
eg, ctx := errgroup.WithContext(stream.Context())
eg.Go(func() error {
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
req, err := stream.Recv()
if err != nil {
return err
}
s.incoming <- req
}
}
})
eg.Go(func() error {
sendMessage := func(msg *cvms.ServerStreamMessage) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
switch m := msg.Message.(type) {
case *cvms.ServerStreamMessage_RunReq:
return s.sendRunReqInChunks(stream, m.RunReq)
default:
return stream.Send(msg)
}
}
}
s.svc.Run(ctx, client.Addr.String(), sendMessage, client.AuthInfo)
return nil
})
return eg.Wait()
}
func (s *grpcServer) sendRunReqInChunks(stream cvms.Service_ProcessServer, runReq *cvms.ComputationRunReq) error {
data, err := proto.Marshal(runReq)
if err != nil {
return err
}
dataBuffer := bytes.NewBuffer(data)
buf := make([]byte, bufferSize)
for {
n, err := dataBuffer.Read(buf)
isLast := false
if err == io.EOF {
isLast = true
} else if err != nil {
return err
}
chunk := &cvms.ServerStreamMessage{
Message: &cvms.ServerStreamMessage_RunReqChunks{
RunReqChunks: &cvms.RunReqChunks{
Id: runReq.Id,
Data: buf[:n],
IsLast: isLast,
},
},
}
if err := stream.Send(chunk); err != nil {
return err
}
if isLast {
break
}
}
return nil
}
+273
View File
@@ -0,0 +1,273 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
"testing"
"time"
"github.com/absmach/magistrala/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent/cvms"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/peer"
)
type mockServerStream struct {
mock.Mock
cvms.Service_ProcessServer
}
func (m *mockServerStream) Send(msg *cvms.ServerStreamMessage) error {
args := m.Called(msg)
return args.Error(0)
}
func (m *mockServerStream) Recv() (*cvms.ClientStreamMessage, error) {
args := m.Called()
return args.Get(0).(*cvms.ClientStreamMessage), args.Error(1)
}
func (m *mockServerStream) Context() context.Context {
args := m.Called()
return args.Get(0).(context.Context)
}
type mockService struct {
mock.Mock
}
func (m *mockService) Run(ctx context.Context, ipAddress string, sendMessage SendFunc, authInfo credentials.AuthInfo) {
m.Called(ctx, ipAddress, sendMessage, authInfo)
}
func TestNewServer(t *testing.T) {
incoming := make(chan *cvms.ClientStreamMessage)
mockSvc := new(mockService)
server := NewServer(incoming, mockSvc)
assert.NotNil(t, server)
assert.IsType(t, &grpcServer{}, server)
}
func TestGrpcServer_Process(t *testing.T) {
tests := []struct {
name string
recvReturn *cvms.ClientStreamMessage
recvError error
expectedError string
}{
{
name: "Process with context deadline exceeded",
recvReturn: &cvms.ClientStreamMessage{},
recvError: nil,
expectedError: "context deadline exceeded",
},
{
name: "Process with Recv error",
recvReturn: &cvms.ClientStreamMessage{},
recvError: errors.New("recv error"),
expectedError: "recv error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
incoming := make(chan *cvms.ClientStreamMessage, 1)
mockSvc := new(mockService)
server := NewServer(incoming, mockSvc).(*grpcServer)
mockStream := new(mockServerStream)
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
mockStream.On("Context").Return(peer.NewContext(ctx, &peer.Peer{
Addr: mockAddr{},
AuthInfo: mockAuthInfo{},
}))
if tt.recvError == nil {
go func() {
for mes := range incoming {
assert.NotNil(t, mes)
}
}()
}
mockStream.On("Recv").Return(tt.recvReturn, tt.recvError)
mockSvc.On("Run", mock.Anything, "test", mock.Anything, mock.AnythingOfType("mockAuthInfo")).Return()
err := server.Process(mockStream)
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
mockStream.AssertExpectations(t)
mockSvc.AssertExpectations(t)
})
}
}
func TestGrpcServer_sendRunReqInChunks(t *testing.T) {
incoming := make(chan *cvms.ClientStreamMessage)
mockSvc := new(mockService)
server := NewServer(incoming, mockSvc).(*grpcServer)
mockStream := new(mockServerStream)
runReq := &cvms.ComputationRunReq{
Id: "test-id",
}
largePayload := make([]byte, bufferSize*2)
for i := range largePayload {
largePayload[i] = byte(i % 256)
}
runReq.Algorithm = &cvms.Algorithm{}
runReq.Algorithm.UserKey = largePayload
mockStream.On("Send", mock.AnythingOfType("*cvms.ServerStreamMessage")).Return(nil).Times(4)
err := server.sendRunReqInChunks(mockStream, runReq)
assert.NoError(t, err)
mockStream.AssertExpectations(t)
calls := mockStream.Calls
assert.Equal(t, 4, len(calls))
for i, call := range calls {
msg := call.Arguments[0].(*cvms.ServerStreamMessage)
chunk := msg.GetRunReqChunks()
assert.NotNil(t, chunk)
assert.Equal(t, "test-id", chunk.Id)
if i < 3 {
assert.False(t, chunk.IsLast)
} else {
assert.Equal(t, 0, len(chunk.Data))
assert.True(t, chunk.IsLast)
}
}
}
type mockAddr struct{}
func (mockAddr) Network() string { return "test network" }
func (mockAddr) String() string { return "test" }
type mockAuthInfo struct{}
func (mockAuthInfo) AuthType() string { return "test auth" }
func TestGrpcServer_ProcessWithMockService(t *testing.T) {
tests := []struct {
name string
setupMockFn func(*mockService, *mockServerStream)
}{
{
name: "Run Request Test",
setupMockFn: func(mockSvc *mockService, mockStream *mockServerStream) {
mockSvc.On("Run", mock.Anything, "test", mock.Anything, mock.AnythingOfType("mockAuthInfo")).
Run(func(args mock.Arguments) {
sendFunc := args.Get(2).(SendFunc)
runReq := &cvms.ComputationRunReq{Id: "test-run-id"}
err := sendFunc(&cvms.ServerStreamMessage{
Message: &cvms.ServerStreamMessage_RunReq{
RunReq: runReq,
},
})
assert.NoError(t, err)
}).
Return()
mockStream.On("Send", mock.MatchedBy(func(msg *cvms.ServerStreamMessage) bool {
chunks := msg.GetRunReqChunks()
return chunks != nil && chunks.Id == "test-run-id"
})).Return(nil)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
incoming := make(chan *cvms.ClientStreamMessage, 10)
mockSvc := new(mockService)
server := NewServer(incoming, mockSvc).(*grpcServer)
go func() {
for mes := range incoming {
assert.NotNil(t, mes)
}
}()
mockStream := new(mockServerStream)
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
peerCtx := peer.NewContext(ctx, &peer.Peer{
Addr: mockAddr{},
AuthInfo: mockAuthInfo{},
})
mockStream.On("Context").Return(peerCtx)
mockStream.On("Recv").Return(&cvms.ClientStreamMessage{}, nil).Maybe()
tt.setupMockFn(mockSvc, mockStream)
go func() {
time.Sleep(150 * time.Millisecond)
cancel()
}()
err := server.Process(mockStream)
assert.Error(t, err)
assert.Contains(t, err.Error(), "context canceled")
mockStream.AssertExpectations(t)
mockSvc.AssertExpectations(t)
})
}
}
func TestGrpcServer_sendRunReqInChunksError(t *testing.T) {
incoming := make(chan *cvms.ClientStreamMessage)
mockSvc := new(mockService)
server := NewServer(incoming, mockSvc).(*grpcServer)
mockStream := new(mockServerStream)
runReq := &cvms.ComputationRunReq{
Id: "test-id",
}
// Simulate an error when sending
mockStream.On("Send", mock.AnythingOfType("*cvms.ServerStreamMessage")).Return(errors.New("send error")).Once()
err := server.sendRunReqInChunks(mockStream, runReq)
assert.Error(t, err)
assert.Contains(t, err.Error(), "send error")
mockStream.AssertExpectations(t)
}
func TestGrpcServer_ProcessMissingPeerInfo(t *testing.T) {
incoming := make(chan *cvms.ClientStreamMessage)
mockSvc := new(mockService)
server := NewServer(incoming, mockSvc).(*grpcServer)
mockStream := new(mockServerStream)
ctx := context.Background()
// Return a context without peer info
mockStream.On("Context").Return(ctx)
err := server.Process(mockStream)
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to get peer info")
mockStream.AssertExpectations(t)
}
@@ -0,0 +1,234 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
cvms "github.com/ultravioletrs/cocos/agent/cvms"
storage "github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage"
)
// Storage is an autogenerated mock type for the Storage type
type Storage struct {
mock.Mock
}
type Storage_Expecter struct {
mock *mock.Mock
}
func (_m *Storage) EXPECT() *Storage_Expecter {
return &Storage_Expecter{mock: &_m.Mock}
}
// Add provides a mock function with given fields: msg
func (_m *Storage) Add(msg *cvms.ClientStreamMessage) error {
ret := _m.Called(msg)
if len(ret) == 0 {
panic("no return value specified for Add")
}
var r0 error
if rf, ok := ret.Get(0).(func(*cvms.ClientStreamMessage) error); ok {
r0 = rf(msg)
} else {
r0 = ret.Error(0)
}
return r0
}
// Storage_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add'
type Storage_Add_Call struct {
*mock.Call
}
// Add is a helper method to define mock.On call
// - msg *cvms.ClientStreamMessage
func (_e *Storage_Expecter) Add(msg interface{}) *Storage_Add_Call {
return &Storage_Add_Call{Call: _e.mock.On("Add", msg)}
}
func (_c *Storage_Add_Call) Run(run func(msg *cvms.ClientStreamMessage)) *Storage_Add_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*cvms.ClientStreamMessage))
})
return _c
}
func (_c *Storage_Add_Call) Return(_a0 error) *Storage_Add_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Storage_Add_Call) RunAndReturn(run func(*cvms.ClientStreamMessage) error) *Storage_Add_Call {
_c.Call.Return(run)
return _c
}
// Clear provides a mock function with no fields
func (_m *Storage) Clear() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Clear")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Storage_Clear_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clear'
type Storage_Clear_Call struct {
*mock.Call
}
// Clear is a helper method to define mock.On call
func (_e *Storage_Expecter) Clear() *Storage_Clear_Call {
return &Storage_Clear_Call{Call: _e.mock.On("Clear")}
}
func (_c *Storage_Clear_Call) Run(run func()) *Storage_Clear_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Storage_Clear_Call) Return(_a0 error) *Storage_Clear_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Storage_Clear_Call) RunAndReturn(run func() error) *Storage_Clear_Call {
_c.Call.Return(run)
return _c
}
// Load provides a mock function with no fields
func (_m *Storage) Load() ([]storage.Message, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Load")
}
var r0 []storage.Message
var r1 error
if rf, ok := ret.Get(0).(func() ([]storage.Message, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() []storage.Message); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]storage.Message)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Storage_Load_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Load'
type Storage_Load_Call struct {
*mock.Call
}
// Load is a helper method to define mock.On call
func (_e *Storage_Expecter) Load() *Storage_Load_Call {
return &Storage_Load_Call{Call: _e.mock.On("Load")}
}
func (_c *Storage_Load_Call) Run(run func()) *Storage_Load_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Storage_Load_Call) Return(_a0 []storage.Message, _a1 error) *Storage_Load_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Storage_Load_Call) RunAndReturn(run func() ([]storage.Message, error)) *Storage_Load_Call {
_c.Call.Return(run)
return _c
}
// Save provides a mock function with given fields: messages
func (_m *Storage) Save(messages []storage.Message) error {
ret := _m.Called(messages)
if len(ret) == 0 {
panic("no return value specified for Save")
}
var r0 error
if rf, ok := ret.Get(0).(func([]storage.Message) error); ok {
r0 = rf(messages)
} else {
r0 = ret.Error(0)
}
return r0
}
// Storage_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save'
type Storage_Save_Call struct {
*mock.Call
}
// Save is a helper method to define mock.On call
// - messages []storage.Message
func (_e *Storage_Expecter) Save(messages interface{}) *Storage_Save_Call {
return &Storage_Save_Call{Call: _e.mock.On("Save", messages)}
}
func (_c *Storage_Save_Call) Run(run func(messages []storage.Message)) *Storage_Save_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].([]storage.Message))
})
return _c
}
func (_c *Storage_Save_Call) Return(_a0 error) *Storage_Save_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Storage_Save_Call) RunAndReturn(run func([]storage.Message) error) *Storage_Save_Call {
_c.Call.Return(run)
return _c
}
// NewStorage creates a new instance of Storage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewStorage(t interface {
mock.TestingT
Cleanup(func())
}) *Storage {
mock := &Storage{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package storage
import (
"encoding/json"
"os"
"path/filepath"
"sync"
"time"
"github.com/ultravioletrs/cocos/agent/cvms"
)
// Message represents a pending message with its timestamp.
type Message struct {
Message *cvms.ClientStreamMessage
Time time.Time
}
// Storage defines the interface for message persistence operations.
type Storage interface {
// Load retrieves all pending messages from storage.
Load() ([]Message, error)
// Save persists the given messages to storage.
Save(messages []Message) error
// Add appends a new message to storage.
Add(msg *cvms.ClientStreamMessage) error
// Clear removes all messages from storage.
Clear() error
}
// FileStorage implements Storage interface using file-based persistence.
type FileStorage struct {
mu sync.Mutex
path string
msgs []Message
}
// NewFileStorage creates a new file-based storage instance.
func NewFileStorage(storageDir string) (*FileStorage, error) {
if err := os.MkdirAll(storageDir, 0o755); err != nil {
return nil, err
}
return &FileStorage{
path: filepath.Join(storageDir, "pending_messages.json"),
msgs: make([]Message, 0),
}, nil
}
func (fs *FileStorage) Load() ([]Message, error) {
fs.mu.Lock()
defer fs.mu.Unlock()
data, err := os.ReadFile(fs.path)
if os.IsNotExist(err) {
return nil, nil
}
if err != nil {
return nil, err
}
if err := json.Unmarshal(data, &fs.msgs); err != nil {
return nil, err
}
return fs.msgs, nil
}
func (fs *FileStorage) Save(messages []Message) error {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.msgs = messages
data, err := json.Marshal(messages)
if err != nil {
return err
}
return os.WriteFile(fs.path, data, 0o644)
}
func (fs *FileStorage) Add(msg *cvms.ClientStreamMessage) error {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.msgs = append(fs.msgs, Message{
Message: msg,
Time: time.Now(),
})
data, err := json.Marshal(fs.msgs)
if err != nil {
return err
}
return os.WriteFile(fs.path, data, 0o644)
}
func (fs *FileStorage) Clear() error {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.msgs = make([]Message, 0)
return os.WriteFile(fs.path, []byte("[]"), 0o644)
}
+450
View File
@@ -0,0 +1,450 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package storage
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ultravioletrs/cocos/agent/cvms"
)
func createTempDir(t *testing.T) string {
tmpDir, err := os.MkdirTemp("", "storage_test_*")
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(tmpDir)
})
return tmpDir
}
func createTestMessage(content string) *cvms.ClientStreamMessage {
return &cvms.ClientStreamMessage{
Message: &cvms.ClientStreamMessage_RunRes{
RunRes: &cvms.RunResponse{
Error: "",
ComputationId: content,
},
},
}
}
func TestNewFileStorage(t *testing.T) {
tests := []struct {
name string
storageDir string
expectError bool
}{
{
name: "valid directory",
storageDir: createTempDir(t),
expectError: false,
},
{
name: "non-existent directory gets created",
storageDir: filepath.Join(createTempDir(t), "subdir"),
expectError: false,
},
{
name: "invalid directory path",
storageDir: "/invalid/path/that/cannot/be/created",
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
storage, err := NewFileStorage(tt.storageDir)
if tt.expectError {
assert.Error(t, err)
assert.Nil(t, storage)
} else {
assert.NoError(t, err)
assert.NotNil(t, storage)
assert.Equal(t, filepath.Join(tt.storageDir, "pending_messages.json"), storage.path)
assert.Empty(t, storage.msgs)
}
})
}
}
func TestFileStorage_Load(t *testing.T) {
tests := []struct {
name string
setupFile func(string) error
expectedMsgs int
expectError bool
}{
{
name: "load from non-existent file",
setupFile: func(path string) error {
// Don't create file
return nil
},
expectedMsgs: 0,
expectError: false,
},
{
name: "load from empty file",
setupFile: func(path string) error {
return os.WriteFile(path, []byte("[]"), 0o644)
},
expectedMsgs: 0,
expectError: false,
},
{
name: "load from corrupted file",
setupFile: func(path string) error {
return os.WriteFile(path, []byte("invalid json"), 0o644)
},
expectedMsgs: 0,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := createTempDir(t)
storage, err := NewFileStorage(tmpDir)
require.NoError(t, err)
err = tt.setupFile(storage.path)
require.NoError(t, err)
msgs, err := storage.Load()
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Len(t, msgs, tt.expectedMsgs)
}
})
}
}
func TestFileStorage_Save(t *testing.T) {
tests := []struct {
name string
messages []Message
expectError bool
}{
{
name: "save empty messages",
messages: []Message{},
expectError: false,
},
{
name: "save single message",
messages: []Message{
{
Message: createTestMessage("test"),
Time: time.Now(),
},
},
expectError: false,
},
{
name: "save multiple messages",
messages: []Message{
{
Message: createTestMessage("test1"),
Time: time.Now(),
},
{
Message: createTestMessage("test2"),
Time: time.Now().Add(time.Second),
},
},
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := createTempDir(t)
storage, err := NewFileStorage(tmpDir)
require.NoError(t, err)
err = storage.Save(tt.messages)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
// Verify file was written correctly
_, err := os.ReadFile(storage.path)
assert.NoError(t, err)
// Verify internal state was updated
assert.Equal(t, tt.messages, storage.msgs)
}
})
}
}
func TestFileStorage_Add(t *testing.T) {
tests := []struct {
name string
initialMsgs []Message
newMessage *cvms.ClientStreamMessage
expectError bool
expectedCount int
}{
{
name: "add to empty storage",
initialMsgs: []Message{},
newMessage: createTestMessage("new"),
expectError: false,
expectedCount: 1,
},
{
name: "add to existing messages",
initialMsgs: []Message{
{
Message: createTestMessage("existing"),
Time: time.Now(),
},
},
newMessage: createTestMessage("new"),
expectError: false,
expectedCount: 2,
},
{
name: "add nil message",
initialMsgs: []Message{},
newMessage: nil,
expectError: false,
expectedCount: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := createTempDir(t)
storage, err := NewFileStorage(tmpDir)
require.NoError(t, err)
// Setup initial messages
if len(tt.initialMsgs) > 0 {
err = storage.Save(tt.initialMsgs)
require.NoError(t, err)
}
beforeTime := time.Now()
err = storage.Add(tt.newMessage)
afterTime := time.Now()
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
// Verify message was added to internal state
assert.Len(t, storage.msgs, tt.expectedCount)
// Verify timestamp is reasonable
if tt.expectedCount > 0 {
lastMsg := storage.msgs[len(storage.msgs)-1]
assert.True(t, lastMsg.Time.After(beforeTime) || lastMsg.Time.Equal(beforeTime))
assert.True(t, lastMsg.Time.Before(afterTime) || lastMsg.Time.Equal(afterTime))
assert.Equal(t, tt.newMessage, lastMsg.Message)
}
_, err := os.ReadFile(storage.path)
assert.NoError(t, err)
}
})
}
}
func TestFileStorage_Clear(t *testing.T) {
tests := []struct {
name string
initialMsgs []Message
expectError bool
}{
{
name: "clear empty storage",
initialMsgs: []Message{},
expectError: false,
},
{
name: "clear storage with messages",
initialMsgs: []Message{
{
Message: createTestMessage("test1"),
Time: time.Now(),
},
{
Message: createTestMessage("test2"),
Time: time.Now(),
},
},
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := createTempDir(t)
storage, err := NewFileStorage(tmpDir)
require.NoError(t, err)
// Setup initial messages
if len(tt.initialMsgs) > 0 {
err = storage.Save(tt.initialMsgs)
require.NoError(t, err)
}
err = storage.Clear()
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
// Verify internal state is cleared
assert.Empty(t, storage.msgs)
// Verify file contains empty array
data, err := os.ReadFile(storage.path)
assert.NoError(t, err)
assert.Equal(t, "[]", string(data))
}
})
}
}
func TestFileStorage_ConcurrentAccess(t *testing.T) {
tmpDir := createTempDir(t)
storage, err := NewFileStorage(tmpDir)
require.NoError(t, err)
// Test concurrent Add operations
numGoroutines := 10
done := make(chan bool, numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(id int) {
defer func() { done <- true }()
msg := createTestMessage(string(rune('A' + id)))
err := storage.Add(msg)
assert.NoError(t, err)
}(i)
}
// Wait for all goroutines to complete
for i := 0; i < numGoroutines; i++ {
<-done
}
// Verify all messages were added
msgs, err := storage.Load()
assert.NoError(t, err)
assert.Len(t, msgs, numGoroutines)
}
func TestFileStorage_IntegrationFlow(t *testing.T) {
tmpDir := createTempDir(t)
storage, err := NewFileStorage(tmpDir)
require.NoError(t, err)
// Test full workflow
// 1. Load from empty storage
msgs, err := storage.Load()
assert.NoError(t, err)
assert.Empty(t, msgs)
// 2. Add some messages
msg1 := createTestMessage("message1")
err = storage.Add(msg1)
assert.NoError(t, err)
msg2 := createTestMessage("message2")
err = storage.Add(msg2)
assert.NoError(t, err)
// 3. Load and verify
msgs, err = storage.Load()
assert.NoError(t, err)
assert.Len(t, msgs, 2)
// 4. Save new set of messages
newMsgs := []Message{
{
Message: createTestMessage("new1"),
Time: time.Now(),
},
}
err = storage.Save(newMsgs)
assert.NoError(t, err)
// 5. Load and verify replacement
msgs, err = storage.Load()
assert.NoError(t, err)
assert.Len(t, msgs, 1)
// 6. Clear storage
err = storage.Clear()
assert.NoError(t, err)
// 7. Verify empty
msgs, err = storage.Load()
assert.NoError(t, err)
assert.Empty(t, msgs)
}
func TestFileStorage_FilePermissions(t *testing.T) {
tmpDir := createTempDir(t)
storage, err := NewFileStorage(tmpDir)
require.NoError(t, err)
// Add a message to create the file
msg := createTestMessage("test")
err = storage.Add(msg)
assert.NoError(t, err)
// Check file permissions
info, err := os.Stat(storage.path)
assert.NoError(t, err)
assert.Equal(t, os.FileMode(0o644), info.Mode().Perm())
}
func TestFileStorage_ErrorHandling(t *testing.T) {
tmpDir := createTempDir(t)
storage, err := NewFileStorage(tmpDir)
require.NoError(t, err)
// Make directory read-only to trigger write errors
err = os.Chmod(tmpDir, 0o555)
require.NoError(t, err)
// Restore permissions for cleanup
t.Cleanup(func() {
if err := os.Chmod(tmpDir, 0o755); err != nil {
t.Errorf("Failed to restore permissions: %v", err)
}
})
// Try to add a message - should fail due to write permissions
msg := createTestMessage("test")
err = storage.Add(msg)
assert.Error(t, err)
// Try to save - should fail due to write permissions
err = storage.Save([]Message{})
assert.Error(t, err)
// Try to clear - should fail due to write permissions
err = storage.Clear()
assert.Error(t, err)
}
File diff suppressed because it is too large Load Diff
+130
View File
@@ -0,0 +1,130 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
syntax = "proto3";
import "google/protobuf/timestamp.proto";
package cvms;
option go_package = "./cvms";
service Service {
rpc Process(stream ClientStreamMessage) returns (stream ServerStreamMessage) {}
}
message AgentStateReq {
string id = 1;
}
message AgentStateRes {
string id = 1;
string state = 2;
}
message StopComputation {
string computation_id = 1;
}
message StopComputationResponse {
string computation_id = 1;
string message = 2;
}
message RunResponse{
string computation_id = 1;
string error = 2;
}
message AgentEvent {
string event_type = 1;
google.protobuf.Timestamp timestamp = 2;
string computation_id = 3;
bytes details = 4;
string originator = 5;
string status = 6;
}
message AgentLog {
string message = 1;
string computation_id = 2;
string level = 3;
google.protobuf.Timestamp timestamp = 4;
}
message ClientStreamMessage {
oneof message {
AgentLog agent_log = 1;
AgentEvent agent_event = 2;
RunResponse run_res = 3;
StopComputationResponse stopComputationRes = 4;
AgentStateRes agentStateRes = 5;
AttestationResponse vTPMattestationReport = 6;
azureAttestationResponse azureAttestationResult = 7;
}
}
message ServerStreamMessage {
oneof message {
RunReqChunks runReqChunks = 1;
ComputationRunReq runReq = 2;
StopComputation stopComputation = 3;
AgentStateReq agentStateReq = 4;
DisconnectReq disconnectReq = 5;
}
}
message DisconnectReq {
string id = 1;
}
message RunReqChunks {
bytes data = 1;
string id = 2;
bool is_last = 3;
}
message ComputationRunReq {
string id = 1;
string name = 2;
string description = 3;
repeated Dataset datasets = 4;
Algorithm algorithm = 5;
repeated ResultConsumer result_consumers = 6;
AgentConfig agent_config = 7;
}
message ResultConsumer {
bytes userKey = 1;
}
message Dataset {
bytes hash = 1; // should be sha3.Sum256, 32 byte length.
bytes userKey = 2;
string filename = 3;
}
message Algorithm {
bytes hash = 1; // should be sha3.Sum256, 32 byte length.
bytes userKey = 2;
}
message AgentConfig {
string port = 1;
string cert_file = 2;
string key_file = 3;
string client_ca_file = 4;
string server_ca_file = 5;
string log_level = 6;
bool attested_tls = 7;
}
message AttestationResponse {
bytes file = 1;
string certSerialNumber = 2;
}
message azureAttestationResponse {
bytes file = 1;
string certSerialNumber = 2;
}
+118
View File
@@ -0,0 +1,118 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.0
// source: agent/cvms/cvms.proto
package cvms
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Service_Process_FullMethodName = "/cvms.Service/Process"
)
// ServiceClient is the client API for Service service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ServiceClient interface {
Process(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ClientStreamMessage, ServerStreamMessage], error)
}
type serviceClient struct {
cc grpc.ClientConnInterface
}
func NewServiceClient(cc grpc.ClientConnInterface) ServiceClient {
return &serviceClient{cc}
}
func (c *serviceClient) Process(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ClientStreamMessage, ServerStreamMessage], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Service_ServiceDesc.Streams[0], Service_Process_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[ClientStreamMessage, ServerStreamMessage]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Service_ProcessClient = grpc.BidiStreamingClient[ClientStreamMessage, ServerStreamMessage]
// ServiceServer is the server API for Service service.
// All implementations must embed UnimplementedServiceServer
// for forward compatibility.
type ServiceServer interface {
Process(grpc.BidiStreamingServer[ClientStreamMessage, ServerStreamMessage]) error
mustEmbedUnimplementedServiceServer()
}
// UnimplementedServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedServiceServer struct{}
func (UnimplementedServiceServer) Process(grpc.BidiStreamingServer[ClientStreamMessage, ServerStreamMessage]) error {
return status.Errorf(codes.Unimplemented, "method Process not implemented")
}
func (UnimplementedServiceServer) mustEmbedUnimplementedServiceServer() {}
func (UnimplementedServiceServer) testEmbeddedByValue() {}
// UnsafeServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ServiceServer will
// result in compilation errors.
type UnsafeServiceServer interface {
mustEmbedUnimplementedServiceServer()
}
func RegisterServiceServer(s grpc.ServiceRegistrar, srv ServiceServer) {
// If the following call pancis, it indicates UnimplementedServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Service_ServiceDesc, srv)
}
func _Service_Process_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(ServiceServer).Process(&grpc.GenericServerStream[ClientStreamMessage, ServerStreamMessage]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Service_ProcessServer = grpc.BidiStreamingServer[ClientStreamMessage, ServerStreamMessage]
// Service_ServiceDesc is the grpc.ServiceDesc for Service service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Service_ServiceDesc = grpc.ServiceDesc{
ServiceName: "cvms.Service",
HandlerType: (*ServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "Process",
Handler: _Service_Process_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "agent/cvms/cvms.proto",
}
+98
View File
@@ -0,0 +1,98 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package server
import (
context "context"
"fmt"
"log/slog"
"github.com/ultravioletrs/cocos/agent"
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
"github.com/ultravioletrs/cocos/agent/auth"
"github.com/ultravioletrs/cocos/internal/server"
grpcserver "github.com/ultravioletrs/cocos/internal/server/grpc"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
const (
svcName = "agent"
defSvcGRPCPort = "7002"
)
type AgentServer interface {
Start(cfg agent.AgentConfig, cmp agent.Computation) error
Stop() error
}
type agentServer struct {
gs server.Server
logger *slog.Logger
svc agent.Service
host string
caUrl string
cvmId string
}
func NewServer(logger *slog.Logger, svc agent.Service, host string, caUrl string, cvmId string) AgentServer {
return &agentServer{
logger: logger,
svc: svc,
host: host,
caUrl: caUrl,
cvmId: cvmId,
}
}
func (as *agentServer) Start(cfg agent.AgentConfig, cmp agent.Computation) error {
if cfg.Port == "" {
cfg.Port = defSvcGRPCPort
}
agentGrpcServerConfig := server.AgentConfig{
ServerConfig: server.ServerConfig{
BaseConfig: server.BaseConfig{
Host: as.host,
Port: cfg.Port,
CertFile: cfg.CertFile,
KeyFile: cfg.KeyFile,
ServerCAFile: cfg.ServerCAFile,
ClientCAFile: cfg.ClientCAFile,
},
},
AttestedTLS: cfg.AttestedTls,
}
registerAgentServiceServer := func(srv *grpc.Server) {
reflection.Register(srv)
agent.RegisterAgentServiceServer(srv, agentgrpc.NewServer(as.svc))
}
authSvc, err := auth.New(cmp)
if err != nil {
as.logger.WithGroup(cmp.ID).Error(fmt.Sprintf("failed to create auth service %s", err.Error()))
return err
}
ctx, cancel := context.WithCancel(context.Background())
as.gs = grpcserver.New(ctx, cancel, svcName, agentGrpcServerConfig, registerAgentServiceServer, as.logger, authSvc, as.caUrl, as.cvmId)
go func() {
err := as.gs.Start()
if err != nil {
as.logger.Error(fmt.Sprintf("failed to start grpc server %s", err.Error()))
}
}()
return nil
}
func (as *agentServer) Stop() error {
if as.gs == nil {
return nil
}
return as.gs.Stop()
}
+544
View File
@@ -0,0 +1,544 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package server
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"log/slog"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/mocks"
)
func setupTest(t *testing.T) (*slog.Logger, *mocks.Service, string, string, string, []byte) {
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
mockSvc := new(mocks.Service)
host := "localhost"
caUrl := "https://ca.example.com"
cvmId := "test-cvm-id"
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.NoError(t, err, "Failed to generate ECDSA key")
pubkey, err := x509.MarshalPKIXPublicKey(privateKey.Public())
assert.NoError(t, err, "Failed to marshal public key")
return logger, mockSvc, host, caUrl, cvmId, pubkey
}
func TestNewServer(t *testing.T) {
logger, svc, host, caUrl, cvmId, _ := setupTest(t)
tests := []struct {
name string
logger *slog.Logger
svc agent.Service
host string
caUrl string
cvmId string
expected AgentServer
}{
{
name: "valid server creation",
logger: logger,
svc: svc,
host: host,
caUrl: caUrl,
cvmId: cvmId,
},
{
name: "server with empty host",
logger: logger,
svc: svc,
host: "",
caUrl: caUrl,
cvmId: cvmId,
},
{
name: "server with empty caUrl",
logger: logger,
svc: svc,
host: host,
caUrl: "",
cvmId: cvmId,
},
{
name: "server with empty cvmId",
logger: logger,
svc: svc,
host: host,
caUrl: caUrl,
cvmId: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := NewServer(tt.logger, tt.svc, tt.host, tt.caUrl, tt.cvmId)
assert.NotNil(t, server)
agentSrv, ok := server.(*agentServer)
assert.True(t, ok)
assert.Equal(t, tt.logger, agentSrv.logger)
assert.Equal(t, tt.svc, agentSrv.svc)
assert.Equal(t, tt.host, agentSrv.host)
assert.Equal(t, tt.caUrl, agentSrv.caUrl)
assert.Equal(t, tt.cvmId, agentSrv.cvmId)
})
}
}
func TestAgentServer_Start(t *testing.T) {
logger, svc, host, caUrl, cvmId, pubKey := setupTest(t)
tests := []struct {
name string
cfg agent.AgentConfig
cmp agent.Computation
setupMocks func(*mocks.Service)
expectedError bool
errorContains string
}{
{
name: "successful start with default port",
cfg: agent.AgentConfig{
Port: "",
CertFile: "cert.pem",
KeyFile: "key.pem",
ServerCAFile: "server-ca.pem",
ClientCAFile: "client-ca.pem",
AttestedTls: true,
},
cmp: agent.Computation{
ID: "test-computation-1",
Name: "Test Computation",
Description: "A test computation",
Algorithm: agent.Algorithm{
Hash: [32]byte{0x01, 0x02, 0x03},
UserKey: pubKey,
},
Datasets: []agent.Dataset{
{
Hash: [32]byte{0x04, 0x05, 0x06},
UserKey: pubKey,
},
},
ResultConsumers: []agent.ResultConsumer{
{
UserKey: pubKey,
},
},
},
setupMocks: func(m *mocks.Service) {
},
expectedError: false,
},
{
name: "successful start with custom port",
cfg: agent.AgentConfig{
Port: "8080",
CertFile: "cert.pem",
KeyFile: "key.pem",
ServerCAFile: "server-ca.pem",
ClientCAFile: "client-ca.pem",
AttestedTls: false,
},
cmp: agent.Computation{
ID: "test-computation-2",
Name: "Test Computation 2",
Description: "Another test computation",
Algorithm: agent.Algorithm{
Hash: [32]byte{0x07, 0x08, 0x09},
UserKey: pubKey,
},
Datasets: []agent.Dataset{
{
Hash: [32]byte{0x0a, 0x0b, 0x0c},
UserKey: pubKey,
},
},
ResultConsumers: []agent.ResultConsumer{
{
UserKey: pubKey,
},
},
},
setupMocks: func(m *mocks.Service) {
},
expectedError: false,
},
{
name: "start with minimal config",
cfg: agent.AgentConfig{
Port: "9090",
AttestedTls: false,
},
cmp: agent.Computation{
ID: "test-computation-3",
Name: "Minimal Test",
Algorithm: agent.Algorithm{
Hash: [32]byte{0x0d, 0x0e, 0x0f},
UserKey: pubKey,
},
Datasets: []agent.Dataset{
{
Hash: [32]byte{0x10, 0x11, 0x12},
UserKey: pubKey,
},
},
ResultConsumers: []agent.ResultConsumer{
{
UserKey: pubKey,
},
},
},
setupMocks: func(m *mocks.Service) {
},
expectedError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setupMocks(svc)
server := NewServer(logger, svc, host, caUrl, cvmId)
err := server.Start(tt.cfg, tt.cmp)
if tt.expectedError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
// Verify the port was set correctly
agentSrv := server.(*agentServer)
assert.NotNil(t, agentSrv.gs)
if err := server.Stop(); err != nil {
t.Fatalf("Failed to stop server after start: %v", err)
}
}
svc.AssertExpectations(t)
})
}
}
func TestAgentServer_Stop(t *testing.T) {
logger, svc, host, caUrl, cvmId, pubKey := setupTest(t)
tests := []struct {
name string
setupServer func(AgentServer) error
expectedError bool
errorContains string
}{
{
name: "stop unstarted server",
setupServer: func(server AgentServer) error {
// Don't start the server
return nil
},
expectedError: false,
},
{
name: "stop started server",
setupServer: func(server AgentServer) error {
cfg := agent.AgentConfig{
Port: "7004",
}
cmp := agent.Computation{
ID: "test-stop-computation",
Name: "Stop Test",
Algorithm: agent.Algorithm{
Hash: [32]byte{0x19, 0x1a, 0x1b},
UserKey: pubKey,
},
Datasets: []agent.Dataset{
{
Hash: [32]byte{0x1c, 0x1d, 0x1e},
UserKey: pubKey,
},
},
ResultConsumers: []agent.ResultConsumer{
{
UserKey: pubKey,
},
},
}
return server.Start(cfg, cmp)
},
expectedError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := NewServer(logger, svc, host, caUrl, cvmId)
err := tt.setupServer(server)
if err != nil {
t.Fatalf("Setup failed: %v", err)
}
// Give the server a moment to start if it was started
time.Sleep(10 * time.Millisecond)
err = server.Stop()
if tt.expectedError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
}
svc.AssertExpectations(t)
})
}
}
func TestAgentServer_StopMultipleTimes(t *testing.T) {
logger, svc, host, caUrl, cvmId, pubKey := setupTest(t)
server := NewServer(logger, svc, host, caUrl, cvmId)
// Start the server
cfg := agent.AgentConfig{Port: "7005"}
cmp := agent.Computation{
ID: "test-multiple-stop",
Name: "Multiple Stop Test",
Algorithm: agent.Algorithm{
Hash: [32]byte{0x1f, 0x20, 0x21},
UserKey: pubKey,
},
Datasets: []agent.Dataset{
{
Hash: [32]byte{0x22, 0x23, 0x24},
UserKey: pubKey,
},
},
ResultConsumers: []agent.ResultConsumer{
{
UserKey: pubKey,
},
},
}
err := server.Start(cfg, cmp)
assert.NoError(t, err)
// Give the server a moment to start
time.Sleep(10 * time.Millisecond)
// Stop the server multiple times
err1 := server.Stop()
err2 := server.Stop()
err3 := server.Stop()
assert.NoError(t, err1)
assert.NoError(t, err2)
assert.NoError(t, err3)
svc.AssertExpectations(t)
}
func TestAgentServer_StartAfterStop(t *testing.T) {
logger, svc, host, caUrl, cvmId, pubKey := setupTest(t)
server := NewServer(logger, svc, host, caUrl, cvmId)
cfg := agent.AgentConfig{Port: "7006"}
cmp := agent.Computation{
ID: "test-restart",
Name: "Restart Test",
Algorithm: agent.Algorithm{
Hash: [32]byte{0x25, 0x26, 0x27},
UserKey: pubKey,
},
Datasets: []agent.Dataset{
{
Hash: [32]byte{0x28, 0x29, 0x2a},
UserKey: pubKey,
},
},
ResultConsumers: []agent.ResultConsumer{
{
UserKey: pubKey,
},
},
}
// Start, stop, then start again
err := server.Start(cfg, cmp)
assert.NoError(t, err)
time.Sleep(10 * time.Millisecond)
err = server.Stop()
assert.NoError(t, err)
// Start again with different config
cfg2 := agent.AgentConfig{Port: "7007"}
cmp2 := agent.Computation{
ID: "test-restart-2",
Name: "Restart Test 2",
Algorithm: agent.Algorithm{
Hash: [32]byte{0x2b, 0x2c, 0x2d},
UserKey: pubKey,
},
Datasets: []agent.Dataset{
{
Hash: [32]byte{0x2e, 0x2f, 0x30},
UserKey: pubKey,
},
},
ResultConsumers: []agent.ResultConsumer{
{
UserKey: pubKey,
},
},
}
err = server.Start(cfg2, cmp2)
assert.NoError(t, err)
time.Sleep(10 * time.Millisecond)
err = server.Stop()
assert.NoError(t, err)
svc.AssertExpectations(t)
}
func TestAgentServer_ConfigValidation(t *testing.T) {
logger, svc, host, caUrl, cvmId, pubKey := setupTest(t)
tests := []struct {
name string
config agent.AgentConfig
cmp agent.Computation
valid bool
}{
{
name: "valid config with all fields",
config: agent.AgentConfig{
Port: "8080",
CertFile: "cert.pem",
KeyFile: "key.pem",
ServerCAFile: "server-ca.pem",
ClientCAFile: "client-ca.pem",
AttestedTls: true,
},
cmp: agent.Computation{
ID: "valid-config-test",
Name: "Valid Config Test",
Algorithm: agent.Algorithm{
Hash: [32]byte{0x31, 0x32, 0x33},
UserKey: pubKey,
},
Datasets: []agent.Dataset{
{
Hash: [32]byte{0x34, 0x35, 0x36},
UserKey: pubKey,
},
},
ResultConsumers: []agent.ResultConsumer{
{
UserKey: pubKey,
},
},
},
valid: true,
},
{
name: "valid config with minimal fields",
config: agent.AgentConfig{
Port: "9090",
},
cmp: agent.Computation{
ID: "minimal-config-test",
Name: "Minimal Config Test",
Algorithm: agent.Algorithm{
Hash: [32]byte{0x37, 0x38, 0x39},
UserKey: pubKey,
},
Datasets: []agent.Dataset{
{
Hash: [32]byte{0x3a, 0x3b, 0x3c},
UserKey: pubKey,
},
},
ResultConsumers: []agent.ResultConsumer{
{
UserKey: pubKey,
},
},
},
valid: true,
},
{
name: "config with empty port uses default",
config: agent.AgentConfig{
Port: "",
},
cmp: agent.Computation{
ID: "default-port-test",
Name: "Default Port Test",
Algorithm: agent.Algorithm{Hash: [32]byte{0x3d, 0x3e, 0x3f}, UserKey: pubKey},
Datasets: []agent.Dataset{
{Hash: [32]byte{0x40, 0x41, 0x42}, UserKey: pubKey},
},
ResultConsumers: []agent.ResultConsumer{
{UserKey: pubKey},
},
},
valid: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := NewServer(logger, svc, host, caUrl, cvmId)
err := server.Start(tt.config, tt.cmp)
if tt.valid {
assert.NoError(t, err)
// Verify default port is used when empty
if tt.config.Port == "" {
agentSrv := server.(*agentServer)
assert.NotNil(t, agentSrv.gs)
}
time.Sleep(10 * time.Millisecond)
if err := server.Stop(); err != nil {
t.Fatalf("Failed to stop server after start: %v", err)
}
} else {
assert.Error(t, err)
}
svc.AssertExpectations(t)
})
}
}
func TestConstants(t *testing.T) {
assert.Equal(t, "agent", svcName)
assert.Equal(t, "7002", defSvcGRPCPort)
}
+130
View File
@@ -0,0 +1,130 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
agent "github.com/ultravioletrs/cocos/agent"
)
// AgentServer is an autogenerated mock type for the AgentServer type
type AgentServer struct {
mock.Mock
}
type AgentServer_Expecter struct {
mock *mock.Mock
}
func (_m *AgentServer) EXPECT() *AgentServer_Expecter {
return &AgentServer_Expecter{mock: &_m.Mock}
}
// Start provides a mock function with given fields: cfg, cmp
func (_m *AgentServer) Start(cfg agent.AgentConfig, cmp agent.Computation) error {
ret := _m.Called(cfg, cmp)
if len(ret) == 0 {
panic("no return value specified for Start")
}
var r0 error
if rf, ok := ret.Get(0).(func(agent.AgentConfig, agent.Computation) error); ok {
r0 = rf(cfg, cmp)
} else {
r0 = ret.Error(0)
}
return r0
}
// AgentServer_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start'
type AgentServer_Start_Call struct {
*mock.Call
}
// Start is a helper method to define mock.On call
// - cfg agent.AgentConfig
// - cmp agent.Computation
func (_e *AgentServer_Expecter) Start(cfg interface{}, cmp interface{}) *AgentServer_Start_Call {
return &AgentServer_Start_Call{Call: _e.mock.On("Start", cfg, cmp)}
}
func (_c *AgentServer_Start_Call) Run(run func(cfg agent.AgentConfig, cmp agent.Computation)) *AgentServer_Start_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(agent.AgentConfig), args[1].(agent.Computation))
})
return _c
}
func (_c *AgentServer_Start_Call) Return(_a0 error) *AgentServer_Start_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentServer_Start_Call) RunAndReturn(run func(agent.AgentConfig, agent.Computation) error) *AgentServer_Start_Call {
_c.Call.Return(run)
return _c
}
// Stop provides a mock function with no fields
func (_m *AgentServer) Stop() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Stop")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// AgentServer_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop'
type AgentServer_Stop_Call struct {
*mock.Call
}
// Stop is a helper method to define mock.On call
func (_e *AgentServer_Expecter) Stop() *AgentServer_Stop_Call {
return &AgentServer_Stop_Call{Call: _e.mock.On("Stop")}
}
func (_c *AgentServer_Stop_Call) Run(run func()) *AgentServer_Stop_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentServer_Stop_Call) Return(_a0 error) *AgentServer_Stop_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentServer_Stop_Call) RunAndReturn(run func() error) *AgentServer_Stop_Call {
_c.Call.Return(run)
return _c
}
// NewAgentServer creates a new instance of AgentServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewAgentServer(t interface {
mock.TestingT
Cleanup(func())
}) *AgentServer {
mock := &AgentServer{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+19 -24
View File
@@ -4,43 +4,38 @@ package events
import (
"encoding/json"
"io"
"google.golang.org/protobuf/proto"
"github.com/ultravioletrs/cocos/agent/cvms"
"google.golang.org/protobuf/types/known/timestamppb"
)
type service struct {
service string
computationID string
conn io.Writer
service string
queue chan *cvms.ClientStreamMessage
}
type Service interface {
SendEvent(event, status string, details json.RawMessage) error
SendEvent(cmpID, event, status string, details json.RawMessage)
}
func New(svc, computationID string, conn io.Writer) (Service, error) {
func New(svc string, queue chan *cvms.ClientStreamMessage) (Service, error) {
return &service{
service: svc,
computationID: computationID,
conn: conn,
service: svc,
queue: queue,
}, nil
}
func (s *service) SendEvent(event, status string, details json.RawMessage) error {
body := EventsLogs{Message: &EventsLogs_AgentEvent{AgentEvent: &AgentEvent{
EventType: event,
Timestamp: timestamppb.Now(),
ComputationId: s.computationID,
Originator: s.service,
Status: status,
Details: details,
}}}
protoBody, err := proto.Marshal(&body)
if err != nil {
return err
func (s *service) SendEvent(cmpID, event, status string, details json.RawMessage) {
s.queue <- &cvms.ClientStreamMessage{
Message: &cvms.ClientStreamMessage_AgentEvent{
AgentEvent: &cvms.AgentEvent{
EventType: event,
Timestamp: timestamppb.Now(),
ComputationId: cmpID,
Originator: s.service,
Status: status,
Details: details,
},
},
}
_, err = s.conn.Write(protoBody)
return err
}
+42 -85
View File
@@ -3,8 +3,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc v5.28.1
// protoc-gen-go v1.36.5
// protoc v5.29.0
// source: agent/events/events.proto
package events
@@ -15,6 +15,7 @@ import (
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@@ -25,25 +26,22 @@ const (
)
type AgentEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
EventType string `protobuf:"bytes,1,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"`
Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
ComputationId string `protobuf:"bytes,3,opt,name=computation_id,json=computationId,proto3" json:"computation_id,omitempty"`
Details []byte `protobuf:"bytes,4,opt,name=details,proto3" json:"details,omitempty"`
Originator string `protobuf:"bytes,5,opt,name=originator,proto3" json:"originator,omitempty"`
Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AgentEvent) Reset() {
*x = AgentEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_events_events_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_events_events_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AgentEvent) String() string {
@@ -54,7 +52,7 @@ func (*AgentEvent) ProtoMessage() {}
func (x *AgentEvent) ProtoReflect() protoreflect.Message {
mi := &file_agent_events_events_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -112,23 +110,20 @@ func (x *AgentEvent) GetStatus() string {
}
type AgentLog struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
ComputationId string `protobuf:"bytes,2,opt,name=computation_id,json=computationId,proto3" json:"computation_id,omitempty"`
Level string `protobuf:"bytes,3,opt,name=level,proto3" json:"level,omitempty"`
Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AgentLog) Reset() {
*x = AgentLog{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_events_events_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_events_events_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AgentLog) String() string {
@@ -139,7 +134,7 @@ func (*AgentLog) ProtoMessage() {}
func (x *AgentLog) ProtoReflect() protoreflect.Message {
mi := &file_agent_events_events_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -183,24 +178,21 @@ func (x *AgentLog) GetTimestamp() *timestamppb.Timestamp {
}
type EventsLogs struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Message:
state protoimpl.MessageState `protogen:"open.v1"`
// Types that are valid to be assigned to Message:
//
// *EventsLogs_AgentLog
// *EventsLogs_AgentEvent
Message isEventsLogs_Message `protobuf_oneof:"message"`
Message isEventsLogs_Message `protobuf_oneof:"message"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EventsLogs) Reset() {
*x = EventsLogs{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_events_events_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_agent_events_events_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EventsLogs) String() string {
@@ -211,7 +203,7 @@ func (*EventsLogs) ProtoMessage() {}
func (x *EventsLogs) ProtoReflect() protoreflect.Message {
mi := &file_agent_events_events_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -226,23 +218,27 @@ func (*EventsLogs) Descriptor() ([]byte, []int) {
return file_agent_events_events_proto_rawDescGZIP(), []int{2}
}
func (m *EventsLogs) GetMessage() isEventsLogs_Message {
if m != nil {
return m.Message
func (x *EventsLogs) GetMessage() isEventsLogs_Message {
if x != nil {
return x.Message
}
return nil
}
func (x *EventsLogs) GetAgentLog() *AgentLog {
if x, ok := x.GetMessage().(*EventsLogs_AgentLog); ok {
return x.AgentLog
if x != nil {
if x, ok := x.Message.(*EventsLogs_AgentLog); ok {
return x.AgentLog
}
}
return nil
}
func (x *EventsLogs) GetAgentEvent() *AgentEvent {
if x, ok := x.GetMessage().(*EventsLogs_AgentEvent); ok {
return x.AgentEvent
if x != nil {
if x, ok := x.Message.(*EventsLogs_AgentEvent); ok {
return x.AgentEvent
}
}
return nil
}
@@ -265,7 +261,7 @@ func (*EventsLogs_AgentEvent) isEventsLogs_Message() {}
var File_agent_events_events_proto protoreflect.FileDescriptor
var file_agent_events_events_proto_rawDesc = []byte{
var file_agent_events_events_proto_rawDesc = string([]byte{
0x0a, 0x19, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x65,
0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x65, 0x76, 0x65,
0x6e, 0x74, 0x73, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
@@ -304,16 +300,16 @@ var file_agent_events_events_proto_rawDesc = []byte{
0x67, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
})
var (
file_agent_events_events_proto_rawDescOnce sync.Once
file_agent_events_events_proto_rawDescData = file_agent_events_events_proto_rawDesc
file_agent_events_events_proto_rawDescData []byte
)
func file_agent_events_events_proto_rawDescGZIP() []byte {
file_agent_events_events_proto_rawDescOnce.Do(func() {
file_agent_events_events_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_events_events_proto_rawDescData)
file_agent_events_events_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_events_events_proto_rawDesc), len(file_agent_events_events_proto_rawDesc)))
})
return file_agent_events_events_proto_rawDescData
}
@@ -342,44 +338,6 @@ func file_agent_events_events_proto_init() {
if File_agent_events_events_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_agent_events_events_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*AgentEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_events_events_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*AgentLog); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_events_events_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*EventsLogs); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_agent_events_events_proto_msgTypes[2].OneofWrappers = []any{
(*EventsLogs_AgentLog)(nil),
(*EventsLogs_AgentEvent)(nil),
@@ -388,7 +346,7 @@ func file_agent_events_events_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_agent_events_events_proto_rawDesc,
RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_events_events_proto_rawDesc), len(file_agent_events_events_proto_rawDesc)),
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
@@ -399,7 +357,6 @@ func file_agent_events_events_proto_init() {
MessageInfos: file_agent_events_events_proto_msgTypes,
}.Build()
File_agent_events_events_proto = out.File
file_agent_events_events_proto_rawDesc = nil
file_agent_events_events_proto_goTypes = nil
file_agent_events_events_proto_depIdxs = nil
}
+17 -43
View File
@@ -3,62 +3,36 @@
package events
import (
"bytes"
"encoding/json"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/proto"
"github.com/ultravioletrs/cocos/agent/cvms"
)
type mockConn struct {
writeErr error
buf bytes.Buffer
}
func (m *mockConn) Write(p []byte) (n int, err error) {
if m.writeErr != nil {
return 0, m.writeErr
}
return m.buf.Write(p)
}
func TestSendEventSuccess(t *testing.T) {
mockConnection := &mockConn{}
svc, err := New("test_service", "12345", mockConnection)
queue := make(chan *cvms.ClientStreamMessage, 1)
svc, err := New("test_service", queue)
assert.NoError(t, err)
details := json.RawMessage(`{"key": "value"}`)
err = svc.SendEvent("test_event", "success", details)
assert.NoError(t, err)
go func() {
msg := <-queue
assert.NotNil(t, msg)
assert.NotNil(t, msg.GetAgentEvent())
assert.Equal(t, "test_event", msg.GetAgentEvent().EventType)
assert.Equal(t, "testid", msg.GetAgentEvent().ComputationId)
assert.Equal(t, "test_service", msg.GetAgentEvent().Originator)
assert.Equal(t, "success", msg.GetAgentEvent().Status)
var writtenMessage EventsLogs
err = proto.Unmarshal(mockConnection.buf.Bytes(), &writtenMessage)
assert.NoError(t, err)
now := time.Now()
eventTimestamp := msg.GetAgentEvent().GetTimestamp().AsTime()
assert.WithinDuration(t, now, eventTimestamp, 1*time.Second)
}()
assert.Equal(t, "test_event", writtenMessage.GetAgentEvent().EventType)
assert.Equal(t, "12345", writtenMessage.GetAgentEvent().ComputationId)
assert.Equal(t, "test_service", writtenMessage.GetAgentEvent().Originator)
assert.Equal(t, "success", writtenMessage.GetAgentEvent().Status)
svc.SendEvent("testid", "test_event", "success", details)
now := time.Now()
eventTimestamp := writtenMessage.GetAgentEvent().GetTimestamp().AsTime()
assert.WithinDuration(t, now, eventTimestamp, 1*time.Second)
}
func TestSendEventFailure(t *testing.T) {
mockConnection := &mockConn{writeErr: errors.New("write error")}
svc, err := New("test_service", "12345", mockConnection)
assert.NoError(t, err)
details := json.RawMessage(`{"key": "value"}`)
err = svc.SendEvent("test_event", "failure", details)
assert.Error(t, err)
assert.Equal(t, "write error", err.Error())
time.Sleep(1 * time.Second)
}
+13 -25
View File
@@ -1,7 +1,7 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
@@ -24,22 +24,9 @@ func (_m *Service) EXPECT() *Service_Expecter {
return &Service_Expecter{mock: &_m.Mock}
}
// SendEvent provides a mock function with given fields: event, status, details
func (_m *Service) SendEvent(event string, status string, details json.RawMessage) error {
ret := _m.Called(event, status, details)
if len(ret) == 0 {
panic("no return value specified for SendEvent")
}
var r0 error
if rf, ok := ret.Get(0).(func(string, string, json.RawMessage) error); ok {
r0 = rf(event, status, details)
} else {
r0 = ret.Error(0)
}
return r0
// SendEvent provides a mock function with given fields: cmpID, event, status, details
func (_m *Service) SendEvent(cmpID string, event string, status string, details json.RawMessage) {
_m.Called(cmpID, event, status, details)
}
// Service_SendEvent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendEvent'
@@ -48,27 +35,28 @@ type Service_SendEvent_Call struct {
}
// SendEvent is a helper method to define mock.On call
// - cmpID string
// - event string
// - status string
// - details json.RawMessage
func (_e *Service_Expecter) SendEvent(event interface{}, status interface{}, details interface{}) *Service_SendEvent_Call {
return &Service_SendEvent_Call{Call: _e.mock.On("SendEvent", event, status, details)}
func (_e *Service_Expecter) SendEvent(cmpID interface{}, event interface{}, status interface{}, details interface{}) *Service_SendEvent_Call {
return &Service_SendEvent_Call{Call: _e.mock.On("SendEvent", cmpID, event, status, details)}
}
func (_c *Service_SendEvent_Call) Run(run func(event string, status string, details json.RawMessage)) *Service_SendEvent_Call {
func (_c *Service_SendEvent_Call) Run(run func(cmpID string, event string, status string, details json.RawMessage)) *Service_SendEvent_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(string), args[2].(json.RawMessage))
run(args[0].(string), args[1].(string), args[2].(string), args[3].(json.RawMessage))
})
return _c
}
func (_c *Service_SendEvent_Call) Return(_a0 error) *Service_SendEvent_Call {
_c.Call.Return(_a0)
func (_c *Service_SendEvent_Call) Return() *Service_SendEvent_Call {
_c.Call.Return()
return _c
}
func (_c *Service_SendEvent_Call) RunAndReturn(run func(string, string, json.RawMessage) error) *Service_SendEvent_Call {
_c.Call.Return(run)
func (_c *Service_SendEvent_Call) RunAndReturn(run func(string, string, string, json.RawMessage)) *Service_SendEvent_Call {
_c.Run(run)
return _c
}
+285 -17
View File
@@ -1,14 +1,15 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
context "context"
agent "github.com/ultravioletrs/cocos/agent"
attestation "github.com/ultravioletrs/cocos/pkg/attestation"
context "context"
mock "github.com/stretchr/testify/mock"
)
@@ -73,9 +74,9 @@ func (_c *Service_Algo_Call) RunAndReturn(run func(context.Context, agent.Algori
return _c
}
// Attestation provides a mock function with given fields: ctx, reportData
func (_m *Service) Attestation(ctx context.Context, reportData [64]byte) ([]byte, error) {
ret := _m.Called(ctx, reportData)
// Attestation provides a mock function with given fields: ctx, reportData, nonce, attType
func (_m *Service) Attestation(ctx context.Context, reportData [64]byte, nonce [32]byte, attType attestation.PlatformType) ([]byte, error) {
ret := _m.Called(ctx, reportData, nonce, attType)
if len(ret) == 0 {
panic("no return value specified for Attestation")
@@ -83,19 +84,19 @@ func (_m *Service) Attestation(ctx context.Context, reportData [64]byte) ([]byte
var r0 []byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, [64]byte) ([]byte, error)); ok {
return rf(ctx, reportData)
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, attestation.PlatformType) ([]byte, error)); ok {
return rf(ctx, reportData, nonce, attType)
}
if rf, ok := ret.Get(0).(func(context.Context, [64]byte) []byte); ok {
r0 = rf(ctx, reportData)
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, attestation.PlatformType) []byte); ok {
r0 = rf(ctx, reportData, nonce, attType)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context, [64]byte) error); ok {
r1 = rf(ctx, reportData)
if rf, ok := ret.Get(1).(func(context.Context, [64]byte, [32]byte, attestation.PlatformType) error); ok {
r1 = rf(ctx, reportData, nonce, attType)
} else {
r1 = ret.Error(1)
}
@@ -111,13 +112,15 @@ type Service_Attestation_Call struct {
// Attestation is a helper method to define mock.On call
// - ctx context.Context
// - reportData [64]byte
func (_e *Service_Expecter) Attestation(ctx interface{}, reportData interface{}) *Service_Attestation_Call {
return &Service_Attestation_Call{Call: _e.mock.On("Attestation", ctx, reportData)}
// - nonce [32]byte
// - attType attestation.PlatformType
func (_e *Service_Expecter) Attestation(ctx interface{}, reportData interface{}, nonce interface{}, attType interface{}) *Service_Attestation_Call {
return &Service_Attestation_Call{Call: _e.mock.On("Attestation", ctx, reportData, nonce, attType)}
}
func (_c *Service_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte)) *Service_Attestation_Call {
func (_c *Service_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte, nonce [32]byte, attType attestation.PlatformType)) *Service_Attestation_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].([64]byte))
run(args[0].(context.Context), args[1].([64]byte), args[2].([32]byte), args[3].(attestation.PlatformType))
})
return _c
}
@@ -127,7 +130,67 @@ func (_c *Service_Attestation_Call) Return(_a0 []byte, _a1 error) *Service_Attes
return _c
}
func (_c *Service_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte) ([]byte, error)) *Service_Attestation_Call {
func (_c *Service_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte, [32]byte, attestation.PlatformType) ([]byte, error)) *Service_Attestation_Call {
_c.Call.Return(run)
return _c
}
// AttestationResult provides a mock function with given fields: ctx, nonce, attType
func (_m *Service) AttestationResult(ctx context.Context, nonce [32]byte, attType attestation.PlatformType) ([]byte, error) {
ret := _m.Called(ctx, nonce, attType)
if len(ret) == 0 {
panic("no return value specified for AttestationResult")
}
var r0 []byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, [32]byte, attestation.PlatformType) ([]byte, error)); ok {
return rf(ctx, nonce, attType)
}
if rf, ok := ret.Get(0).(func(context.Context, [32]byte, attestation.PlatformType) []byte); ok {
r0 = rf(ctx, nonce, attType)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context, [32]byte, attestation.PlatformType) error); ok {
r1 = rf(ctx, nonce, attType)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_AttestationResult_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AttestationResult'
type Service_AttestationResult_Call struct {
*mock.Call
}
// AttestationResult is a helper method to define mock.On call
// - ctx context.Context
// - nonce [32]byte
// - attType attestation.PlatformType
func (_e *Service_Expecter) AttestationResult(ctx interface{}, nonce interface{}, attType interface{}) *Service_AttestationResult_Call {
return &Service_AttestationResult_Call{Call: _e.mock.On("AttestationResult", ctx, nonce, attType)}
}
func (_c *Service_AttestationResult_Call) Run(run func(ctx context.Context, nonce [32]byte, attType attestation.PlatformType)) *Service_AttestationResult_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].([32]byte), args[2].(attestation.PlatformType))
})
return _c
}
func (_c *Service_AttestationResult_Call) Return(_a0 []byte, _a1 error) *Service_AttestationResult_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Service_AttestationResult_Call) RunAndReturn(run func(context.Context, [32]byte, attestation.PlatformType) ([]byte, error)) *Service_AttestationResult_Call {
_c.Call.Return(run)
return _c
}
@@ -179,6 +242,120 @@ func (_c *Service_Data_Call) RunAndReturn(run func(context.Context, agent.Datase
return _c
}
// IMAMeasurements provides a mock function with given fields: ctx
func (_m *Service) IMAMeasurements(ctx context.Context) ([]byte, []byte, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for IMAMeasurements")
}
var r0 []byte
var r1 []byte
var r2 error
if rf, ok := ret.Get(0).(func(context.Context) ([]byte, []byte, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) []byte); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context) []byte); ok {
r1 = rf(ctx)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).([]byte)
}
}
if rf, ok := ret.Get(2).(func(context.Context) error); ok {
r2 = rf(ctx)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// Service_IMAMeasurements_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IMAMeasurements'
type Service_IMAMeasurements_Call struct {
*mock.Call
}
// IMAMeasurements is a helper method to define mock.On call
// - ctx context.Context
func (_e *Service_Expecter) IMAMeasurements(ctx interface{}) *Service_IMAMeasurements_Call {
return &Service_IMAMeasurements_Call{Call: _e.mock.On("IMAMeasurements", ctx)}
}
func (_c *Service_IMAMeasurements_Call) Run(run func(ctx context.Context)) *Service_IMAMeasurements_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *Service_IMAMeasurements_Call) Return(_a0 []byte, _a1 []byte, _a2 error) *Service_IMAMeasurements_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *Service_IMAMeasurements_Call) RunAndReturn(run func(context.Context) ([]byte, []byte, error)) *Service_IMAMeasurements_Call {
_c.Call.Return(run)
return _c
}
// InitComputation provides a mock function with given fields: ctx, cmp
func (_m *Service) InitComputation(ctx context.Context, cmp agent.Computation) error {
ret := _m.Called(ctx, cmp)
if len(ret) == 0 {
panic("no return value specified for InitComputation")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, agent.Computation) error); ok {
r0 = rf(ctx, cmp)
} else {
r0 = ret.Error(0)
}
return r0
}
// Service_InitComputation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InitComputation'
type Service_InitComputation_Call struct {
*mock.Call
}
// InitComputation is a helper method to define mock.On call
// - ctx context.Context
// - cmp agent.Computation
func (_e *Service_Expecter) InitComputation(ctx interface{}, cmp interface{}) *Service_InitComputation_Call {
return &Service_InitComputation_Call{Call: _e.mock.On("InitComputation", ctx, cmp)}
}
func (_c *Service_InitComputation_Call) Run(run func(ctx context.Context, cmp agent.Computation)) *Service_InitComputation_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(agent.Computation))
})
return _c
}
func (_c *Service_InitComputation_Call) Return(_a0 error) *Service_InitComputation_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Service_InitComputation_Call) RunAndReturn(run func(context.Context, agent.Computation) error) *Service_InitComputation_Call {
_c.Call.Return(run)
return _c
}
// Result provides a mock function with given fields: ctx
func (_m *Service) Result(ctx context.Context) ([]byte, error) {
ret := _m.Called(ctx)
@@ -237,6 +414,97 @@ func (_c *Service_Result_Call) RunAndReturn(run func(context.Context) ([]byte, e
return _c
}
// State provides a mock function with no fields
func (_m *Service) State() string {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for State")
}
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// Service_State_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'State'
type Service_State_Call struct {
*mock.Call
}
// State is a helper method to define mock.On call
func (_e *Service_Expecter) State() *Service_State_Call {
return &Service_State_Call{Call: _e.mock.On("State")}
}
func (_c *Service_State_Call) Run(run func()) *Service_State_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Service_State_Call) Return(_a0 string) *Service_State_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Service_State_Call) RunAndReturn(run func() string) *Service_State_Call {
_c.Call.Return(run)
return _c
}
// StopComputation provides a mock function with given fields: ctx
func (_m *Service) StopComputation(ctx context.Context) error {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for StopComputation")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// Service_StopComputation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StopComputation'
type Service_StopComputation_Call struct {
*mock.Call
}
// StopComputation is a helper method to define mock.On call
// - ctx context.Context
func (_e *Service_Expecter) StopComputation(ctx interface{}) *Service_StopComputation_Call {
return &Service_StopComputation_Call{Call: _e.mock.On("StopComputation", ctx)}
}
func (_c *Service_StopComputation_Call) Run(run func(ctx context.Context)) *Service_StopComputation_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *Service_StopComputation_Call) Return(_a0 error) *Service_StopComputation_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Service_StopComputation_Call) RunAndReturn(run func(context.Context) error) *Service_StopComputation_Call {
_c.Call.Return(run)
return _c
}
// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewService(t interface {
+246 -19
View File
@@ -1,8 +1,8 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
@@ -16,12 +16,20 @@ import (
)
// AgentService_AlgoClient is an autogenerated mock type for the AgentService_AlgoClient type
type AgentService_AlgoClient struct {
type AgentService_AlgoClient[Req interface{}, Res interface{}] struct {
mock.Mock
}
// CloseAndRecv provides a mock function with given fields:
func (_m *AgentService_AlgoClient) CloseAndRecv() (*agent.AlgoResponse, error) {
type AgentService_AlgoClient_Expecter[Req interface{}, Res interface{}] struct {
mock *mock.Mock
}
func (_m *AgentService_AlgoClient[Req, Res]) EXPECT() *AgentService_AlgoClient_Expecter[Req, Res] {
return &AgentService_AlgoClient_Expecter[Req, Res]{mock: &_m.Mock}
}
// CloseAndRecv provides a mock function with no fields
func (_m *AgentService_AlgoClient[Req, Res]) CloseAndRecv() (*agent.AlgoResponse, error) {
ret := _m.Called()
if len(ret) == 0 {
@@ -50,8 +58,35 @@ func (_m *AgentService_AlgoClient) CloseAndRecv() (*agent.AlgoResponse, error) {
return r0, r1
}
// CloseSend provides a mock function with given fields:
func (_m *AgentService_AlgoClient) CloseSend() error {
// AgentService_AlgoClient_CloseAndRecv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseAndRecv'
type AgentService_AlgoClient_CloseAndRecv_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// CloseAndRecv is a helper method to define mock.On call
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) CloseAndRecv() *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res] {
return &AgentService_AlgoClient_CloseAndRecv_Call[Req, Res]{Call: _e.mock.On("CloseAndRecv")}
}
func (_c *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res]) Run(run func()) *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res]) Return(_a0 *agent.AlgoResponse, _a1 error) *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res] {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res]) RunAndReturn(run func() (*agent.AlgoResponse, error)) *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// CloseSend provides a mock function with no fields
func (_m *AgentService_AlgoClient[Req, Res]) CloseSend() error {
ret := _m.Called()
if len(ret) == 0 {
@@ -68,8 +103,35 @@ func (_m *AgentService_AlgoClient) CloseSend() error {
return r0
}
// Context provides a mock function with given fields:
func (_m *AgentService_AlgoClient) Context() context.Context {
// AgentService_AlgoClient_CloseSend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseSend'
type AgentService_AlgoClient_CloseSend_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// CloseSend is a helper method to define mock.On call
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) CloseSend() *AgentService_AlgoClient_CloseSend_Call[Req, Res] {
return &AgentService_AlgoClient_CloseSend_Call[Req, Res]{Call: _e.mock.On("CloseSend")}
}
func (_c *AgentService_AlgoClient_CloseSend_Call[Req, Res]) Run(run func()) *AgentService_AlgoClient_CloseSend_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_AlgoClient_CloseSend_Call[Req, Res]) Return(_a0 error) *AgentService_AlgoClient_CloseSend_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_AlgoClient_CloseSend_Call[Req, Res]) RunAndReturn(run func() error) *AgentService_AlgoClient_CloseSend_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// Context provides a mock function with no fields
func (_m *AgentService_AlgoClient[Req, Res]) Context() context.Context {
ret := _m.Called()
if len(ret) == 0 {
@@ -88,8 +150,35 @@ func (_m *AgentService_AlgoClient) Context() context.Context {
return r0
}
// Header provides a mock function with given fields:
func (_m *AgentService_AlgoClient) Header() (metadata.MD, error) {
// AgentService_AlgoClient_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'
type AgentService_AlgoClient_Context_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// Context is a helper method to define mock.On call
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) Context() *AgentService_AlgoClient_Context_Call[Req, Res] {
return &AgentService_AlgoClient_Context_Call[Req, Res]{Call: _e.mock.On("Context")}
}
func (_c *AgentService_AlgoClient_Context_Call[Req, Res]) Run(run func()) *AgentService_AlgoClient_Context_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_AlgoClient_Context_Call[Req, Res]) Return(_a0 context.Context) *AgentService_AlgoClient_Context_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_AlgoClient_Context_Call[Req, Res]) RunAndReturn(run func() context.Context) *AgentService_AlgoClient_Context_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// Header provides a mock function with no fields
func (_m *AgentService_AlgoClient[Req, Res]) Header() (metadata.MD, error) {
ret := _m.Called()
if len(ret) == 0 {
@@ -118,8 +207,35 @@ func (_m *AgentService_AlgoClient) Header() (metadata.MD, error) {
return r0, r1
}
// AgentService_AlgoClient_Header_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Header'
type AgentService_AlgoClient_Header_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// Header is a helper method to define mock.On call
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) Header() *AgentService_AlgoClient_Header_Call[Req, Res] {
return &AgentService_AlgoClient_Header_Call[Req, Res]{Call: _e.mock.On("Header")}
}
func (_c *AgentService_AlgoClient_Header_Call[Req, Res]) Run(run func()) *AgentService_AlgoClient_Header_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_AlgoClient_Header_Call[Req, Res]) Return(_a0 metadata.MD, _a1 error) *AgentService_AlgoClient_Header_Call[Req, Res] {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AgentService_AlgoClient_Header_Call[Req, Res]) RunAndReturn(run func() (metadata.MD, error)) *AgentService_AlgoClient_Header_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// RecvMsg provides a mock function with given fields: m
func (_m *AgentService_AlgoClient) RecvMsg(m interface{}) error {
func (_m *AgentService_AlgoClient[Req, Res]) RecvMsg(m interface{}) error {
ret := _m.Called(m)
if len(ret) == 0 {
@@ -136,8 +252,36 @@ func (_m *AgentService_AlgoClient) RecvMsg(m interface{}) error {
return r0
}
// AgentService_AlgoClient_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'
type AgentService_AlgoClient_RecvMsg_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// RecvMsg is a helper method to define mock.On call
// - m interface{}
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) RecvMsg(m interface{}) *AgentService_AlgoClient_RecvMsg_Call[Req, Res] {
return &AgentService_AlgoClient_RecvMsg_Call[Req, Res]{Call: _e.mock.On("RecvMsg", m)}
}
func (_c *AgentService_AlgoClient_RecvMsg_Call[Req, Res]) Run(run func(m interface{})) *AgentService_AlgoClient_RecvMsg_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(interface{}))
})
return _c
}
func (_c *AgentService_AlgoClient_RecvMsg_Call[Req, Res]) Return(_a0 error) *AgentService_AlgoClient_RecvMsg_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_AlgoClient_RecvMsg_Call[Req, Res]) RunAndReturn(run func(interface{}) error) *AgentService_AlgoClient_RecvMsg_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// Send provides a mock function with given fields: _a0
func (_m *AgentService_AlgoClient) Send(_a0 *agent.AlgoRequest) error {
func (_m *AgentService_AlgoClient[Req, Res]) Send(_a0 *agent.AlgoRequest) error {
ret := _m.Called(_a0)
if len(ret) == 0 {
@@ -154,8 +298,36 @@ func (_m *AgentService_AlgoClient) Send(_a0 *agent.AlgoRequest) error {
return r0
}
// AgentService_AlgoClient_Send_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Send'
type AgentService_AlgoClient_Send_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// Send is a helper method to define mock.On call
// - _a0 *agent.AlgoRequest
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) Send(_a0 interface{}) *AgentService_AlgoClient_Send_Call[Req, Res] {
return &AgentService_AlgoClient_Send_Call[Req, Res]{Call: _e.mock.On("Send", _a0)}
}
func (_c *AgentService_AlgoClient_Send_Call[Req, Res]) Run(run func(_a0 *agent.AlgoRequest)) *AgentService_AlgoClient_Send_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*agent.AlgoRequest))
})
return _c
}
func (_c *AgentService_AlgoClient_Send_Call[Req, Res]) Return(_a0 error) *AgentService_AlgoClient_Send_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_AlgoClient_Send_Call[Req, Res]) RunAndReturn(run func(*agent.AlgoRequest) error) *AgentService_AlgoClient_Send_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// SendMsg provides a mock function with given fields: m
func (_m *AgentService_AlgoClient) SendMsg(m interface{}) error {
func (_m *AgentService_AlgoClient[Req, Res]) SendMsg(m interface{}) error {
ret := _m.Called(m)
if len(ret) == 0 {
@@ -172,8 +344,36 @@ func (_m *AgentService_AlgoClient) SendMsg(m interface{}) error {
return r0
}
// Trailer provides a mock function with given fields:
func (_m *AgentService_AlgoClient) Trailer() metadata.MD {
// AgentService_AlgoClient_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'
type AgentService_AlgoClient_SendMsg_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// SendMsg is a helper method to define mock.On call
// - m interface{}
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) SendMsg(m interface{}) *AgentService_AlgoClient_SendMsg_Call[Req, Res] {
return &AgentService_AlgoClient_SendMsg_Call[Req, Res]{Call: _e.mock.On("SendMsg", m)}
}
func (_c *AgentService_AlgoClient_SendMsg_Call[Req, Res]) Run(run func(m interface{})) *AgentService_AlgoClient_SendMsg_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(interface{}))
})
return _c
}
func (_c *AgentService_AlgoClient_SendMsg_Call[Req, Res]) Return(_a0 error) *AgentService_AlgoClient_SendMsg_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_AlgoClient_SendMsg_Call[Req, Res]) RunAndReturn(run func(interface{}) error) *AgentService_AlgoClient_SendMsg_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// Trailer provides a mock function with no fields
func (_m *AgentService_AlgoClient[Req, Res]) Trailer() metadata.MD {
ret := _m.Called()
if len(ret) == 0 {
@@ -192,13 +392,40 @@ func (_m *AgentService_AlgoClient) Trailer() metadata.MD {
return r0
}
// AgentService_AlgoClient_Trailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Trailer'
type AgentService_AlgoClient_Trailer_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// Trailer is a helper method to define mock.On call
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) Trailer() *AgentService_AlgoClient_Trailer_Call[Req, Res] {
return &AgentService_AlgoClient_Trailer_Call[Req, Res]{Call: _e.mock.On("Trailer")}
}
func (_c *AgentService_AlgoClient_Trailer_Call[Req, Res]) Run(run func()) *AgentService_AlgoClient_Trailer_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_AlgoClient_Trailer_Call[Req, Res]) Return(_a0 metadata.MD) *AgentService_AlgoClient_Trailer_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_AlgoClient_Trailer_Call[Req, Res]) RunAndReturn(run func() metadata.MD) *AgentService_AlgoClient_Trailer_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// NewAgentService_AlgoClient creates a new instance of AgentService_AlgoClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewAgentService_AlgoClient(t interface {
func NewAgentService_AlgoClient[Req interface{}, Res interface{}](t interface {
mock.TestingT
Cleanup(func())
}) *AgentService_AlgoClient {
mock := &AgentService_AlgoClient{}
}) *AgentService_AlgoClient[Req, Res] {
mock := &AgentService_AlgoClient[Req, Res]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
+246 -19
View File
@@ -1,8 +1,8 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
@@ -16,12 +16,20 @@ import (
)
// AgentService_DataClient is an autogenerated mock type for the AgentService_DataClient type
type AgentService_DataClient struct {
type AgentService_DataClient[Req interface{}, Res interface{}] struct {
mock.Mock
}
// CloseAndRecv provides a mock function with given fields:
func (_m *AgentService_DataClient) CloseAndRecv() (*agent.DataResponse, error) {
type AgentService_DataClient_Expecter[Req interface{}, Res interface{}] struct {
mock *mock.Mock
}
func (_m *AgentService_DataClient[Req, Res]) EXPECT() *AgentService_DataClient_Expecter[Req, Res] {
return &AgentService_DataClient_Expecter[Req, Res]{mock: &_m.Mock}
}
// CloseAndRecv provides a mock function with no fields
func (_m *AgentService_DataClient[Req, Res]) CloseAndRecv() (*agent.DataResponse, error) {
ret := _m.Called()
if len(ret) == 0 {
@@ -50,8 +58,35 @@ func (_m *AgentService_DataClient) CloseAndRecv() (*agent.DataResponse, error) {
return r0, r1
}
// CloseSend provides a mock function with given fields:
func (_m *AgentService_DataClient) CloseSend() error {
// AgentService_DataClient_CloseAndRecv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseAndRecv'
type AgentService_DataClient_CloseAndRecv_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// CloseAndRecv is a helper method to define mock.On call
func (_e *AgentService_DataClient_Expecter[Req, Res]) CloseAndRecv() *AgentService_DataClient_CloseAndRecv_Call[Req, Res] {
return &AgentService_DataClient_CloseAndRecv_Call[Req, Res]{Call: _e.mock.On("CloseAndRecv")}
}
func (_c *AgentService_DataClient_CloseAndRecv_Call[Req, Res]) Run(run func()) *AgentService_DataClient_CloseAndRecv_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_DataClient_CloseAndRecv_Call[Req, Res]) Return(_a0 *agent.DataResponse, _a1 error) *AgentService_DataClient_CloseAndRecv_Call[Req, Res] {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AgentService_DataClient_CloseAndRecv_Call[Req, Res]) RunAndReturn(run func() (*agent.DataResponse, error)) *AgentService_DataClient_CloseAndRecv_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// CloseSend provides a mock function with no fields
func (_m *AgentService_DataClient[Req, Res]) CloseSend() error {
ret := _m.Called()
if len(ret) == 0 {
@@ -68,8 +103,35 @@ func (_m *AgentService_DataClient) CloseSend() error {
return r0
}
// Context provides a mock function with given fields:
func (_m *AgentService_DataClient) Context() context.Context {
// AgentService_DataClient_CloseSend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseSend'
type AgentService_DataClient_CloseSend_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// CloseSend is a helper method to define mock.On call
func (_e *AgentService_DataClient_Expecter[Req, Res]) CloseSend() *AgentService_DataClient_CloseSend_Call[Req, Res] {
return &AgentService_DataClient_CloseSend_Call[Req, Res]{Call: _e.mock.On("CloseSend")}
}
func (_c *AgentService_DataClient_CloseSend_Call[Req, Res]) Run(run func()) *AgentService_DataClient_CloseSend_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_DataClient_CloseSend_Call[Req, Res]) Return(_a0 error) *AgentService_DataClient_CloseSend_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_DataClient_CloseSend_Call[Req, Res]) RunAndReturn(run func() error) *AgentService_DataClient_CloseSend_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// Context provides a mock function with no fields
func (_m *AgentService_DataClient[Req, Res]) Context() context.Context {
ret := _m.Called()
if len(ret) == 0 {
@@ -88,8 +150,35 @@ func (_m *AgentService_DataClient) Context() context.Context {
return r0
}
// Header provides a mock function with given fields:
func (_m *AgentService_DataClient) Header() (metadata.MD, error) {
// AgentService_DataClient_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'
type AgentService_DataClient_Context_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// Context is a helper method to define mock.On call
func (_e *AgentService_DataClient_Expecter[Req, Res]) Context() *AgentService_DataClient_Context_Call[Req, Res] {
return &AgentService_DataClient_Context_Call[Req, Res]{Call: _e.mock.On("Context")}
}
func (_c *AgentService_DataClient_Context_Call[Req, Res]) Run(run func()) *AgentService_DataClient_Context_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_DataClient_Context_Call[Req, Res]) Return(_a0 context.Context) *AgentService_DataClient_Context_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_DataClient_Context_Call[Req, Res]) RunAndReturn(run func() context.Context) *AgentService_DataClient_Context_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// Header provides a mock function with no fields
func (_m *AgentService_DataClient[Req, Res]) Header() (metadata.MD, error) {
ret := _m.Called()
if len(ret) == 0 {
@@ -118,8 +207,35 @@ func (_m *AgentService_DataClient) Header() (metadata.MD, error) {
return r0, r1
}
// AgentService_DataClient_Header_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Header'
type AgentService_DataClient_Header_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// Header is a helper method to define mock.On call
func (_e *AgentService_DataClient_Expecter[Req, Res]) Header() *AgentService_DataClient_Header_Call[Req, Res] {
return &AgentService_DataClient_Header_Call[Req, Res]{Call: _e.mock.On("Header")}
}
func (_c *AgentService_DataClient_Header_Call[Req, Res]) Run(run func()) *AgentService_DataClient_Header_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_DataClient_Header_Call[Req, Res]) Return(_a0 metadata.MD, _a1 error) *AgentService_DataClient_Header_Call[Req, Res] {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AgentService_DataClient_Header_Call[Req, Res]) RunAndReturn(run func() (metadata.MD, error)) *AgentService_DataClient_Header_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// RecvMsg provides a mock function with given fields: m
func (_m *AgentService_DataClient) RecvMsg(m interface{}) error {
func (_m *AgentService_DataClient[Req, Res]) RecvMsg(m interface{}) error {
ret := _m.Called(m)
if len(ret) == 0 {
@@ -136,8 +252,36 @@ func (_m *AgentService_DataClient) RecvMsg(m interface{}) error {
return r0
}
// AgentService_DataClient_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'
type AgentService_DataClient_RecvMsg_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// RecvMsg is a helper method to define mock.On call
// - m interface{}
func (_e *AgentService_DataClient_Expecter[Req, Res]) RecvMsg(m interface{}) *AgentService_DataClient_RecvMsg_Call[Req, Res] {
return &AgentService_DataClient_RecvMsg_Call[Req, Res]{Call: _e.mock.On("RecvMsg", m)}
}
func (_c *AgentService_DataClient_RecvMsg_Call[Req, Res]) Run(run func(m interface{})) *AgentService_DataClient_RecvMsg_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(interface{}))
})
return _c
}
func (_c *AgentService_DataClient_RecvMsg_Call[Req, Res]) Return(_a0 error) *AgentService_DataClient_RecvMsg_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_DataClient_RecvMsg_Call[Req, Res]) RunAndReturn(run func(interface{}) error) *AgentService_DataClient_RecvMsg_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// Send provides a mock function with given fields: _a0
func (_m *AgentService_DataClient) Send(_a0 *agent.DataRequest) error {
func (_m *AgentService_DataClient[Req, Res]) Send(_a0 *agent.DataRequest) error {
ret := _m.Called(_a0)
if len(ret) == 0 {
@@ -154,8 +298,36 @@ func (_m *AgentService_DataClient) Send(_a0 *agent.DataRequest) error {
return r0
}
// AgentService_DataClient_Send_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Send'
type AgentService_DataClient_Send_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// Send is a helper method to define mock.On call
// - _a0 *agent.DataRequest
func (_e *AgentService_DataClient_Expecter[Req, Res]) Send(_a0 interface{}) *AgentService_DataClient_Send_Call[Req, Res] {
return &AgentService_DataClient_Send_Call[Req, Res]{Call: _e.mock.On("Send", _a0)}
}
func (_c *AgentService_DataClient_Send_Call[Req, Res]) Run(run func(_a0 *agent.DataRequest)) *AgentService_DataClient_Send_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*agent.DataRequest))
})
return _c
}
func (_c *AgentService_DataClient_Send_Call[Req, Res]) Return(_a0 error) *AgentService_DataClient_Send_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_DataClient_Send_Call[Req, Res]) RunAndReturn(run func(*agent.DataRequest) error) *AgentService_DataClient_Send_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// SendMsg provides a mock function with given fields: m
func (_m *AgentService_DataClient) SendMsg(m interface{}) error {
func (_m *AgentService_DataClient[Req, Res]) SendMsg(m interface{}) error {
ret := _m.Called(m)
if len(ret) == 0 {
@@ -172,8 +344,36 @@ func (_m *AgentService_DataClient) SendMsg(m interface{}) error {
return r0
}
// Trailer provides a mock function with given fields:
func (_m *AgentService_DataClient) Trailer() metadata.MD {
// AgentService_DataClient_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'
type AgentService_DataClient_SendMsg_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// SendMsg is a helper method to define mock.On call
// - m interface{}
func (_e *AgentService_DataClient_Expecter[Req, Res]) SendMsg(m interface{}) *AgentService_DataClient_SendMsg_Call[Req, Res] {
return &AgentService_DataClient_SendMsg_Call[Req, Res]{Call: _e.mock.On("SendMsg", m)}
}
func (_c *AgentService_DataClient_SendMsg_Call[Req, Res]) Run(run func(m interface{})) *AgentService_DataClient_SendMsg_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(interface{}))
})
return _c
}
func (_c *AgentService_DataClient_SendMsg_Call[Req, Res]) Return(_a0 error) *AgentService_DataClient_SendMsg_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_DataClient_SendMsg_Call[Req, Res]) RunAndReturn(run func(interface{}) error) *AgentService_DataClient_SendMsg_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// Trailer provides a mock function with no fields
func (_m *AgentService_DataClient[Req, Res]) Trailer() metadata.MD {
ret := _m.Called()
if len(ret) == 0 {
@@ -192,13 +392,40 @@ func (_m *AgentService_DataClient) Trailer() metadata.MD {
return r0
}
// AgentService_DataClient_Trailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Trailer'
type AgentService_DataClient_Trailer_Call[Req interface{}, Res interface{}] struct {
*mock.Call
}
// Trailer is a helper method to define mock.On call
func (_e *AgentService_DataClient_Expecter[Req, Res]) Trailer() *AgentService_DataClient_Trailer_Call[Req, Res] {
return &AgentService_DataClient_Trailer_Call[Req, Res]{Call: _e.mock.On("Trailer")}
}
func (_c *AgentService_DataClient_Trailer_Call[Req, Res]) Run(run func()) *AgentService_DataClient_Trailer_Call[Req, Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_DataClient_Trailer_Call[Req, Res]) Return(_a0 metadata.MD) *AgentService_DataClient_Trailer_Call[Req, Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_DataClient_Trailer_Call[Req, Res]) RunAndReturn(run func() metadata.MD) *AgentService_DataClient_Trailer_Call[Req, Res] {
_c.Call.Return(run)
return _c
}
// NewAgentService_DataClient creates a new instance of AgentService_DataClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewAgentService_DataClient(t interface {
func NewAgentService_DataClient[Req interface{}, Res interface{}](t interface {
mock.TestingT
Cleanup(func())
}) *AgentService_DataClient {
mock := &AgentService_DataClient{}
}) *AgentService_DataClient[Req, Res] {
mock := &AgentService_DataClient[Req, Res]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
+388
View File
@@ -0,0 +1,388 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
context "context"
agent "github.com/ultravioletrs/cocos/agent"
metadata "google.golang.org/grpc/metadata"
mock "github.com/stretchr/testify/mock"
)
// AgentService_IMAMeasurementsClient is an autogenerated mock type for the AgentService_IMAMeasurementsClient type
type AgentService_IMAMeasurementsClient[Res interface{}] struct {
mock.Mock
}
type AgentService_IMAMeasurementsClient_Expecter[Res interface{}] struct {
mock *mock.Mock
}
func (_m *AgentService_IMAMeasurementsClient[Res]) EXPECT() *AgentService_IMAMeasurementsClient_Expecter[Res] {
return &AgentService_IMAMeasurementsClient_Expecter[Res]{mock: &_m.Mock}
}
// CloseSend provides a mock function with no fields
func (_m *AgentService_IMAMeasurementsClient[Res]) CloseSend() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for CloseSend")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// AgentService_IMAMeasurementsClient_CloseSend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseSend'
type AgentService_IMAMeasurementsClient_CloseSend_Call[Res interface{}] struct {
*mock.Call
}
// CloseSend is a helper method to define mock.On call
func (_e *AgentService_IMAMeasurementsClient_Expecter[Res]) CloseSend() *AgentService_IMAMeasurementsClient_CloseSend_Call[Res] {
return &AgentService_IMAMeasurementsClient_CloseSend_Call[Res]{Call: _e.mock.On("CloseSend")}
}
func (_c *AgentService_IMAMeasurementsClient_CloseSend_Call[Res]) Run(run func()) *AgentService_IMAMeasurementsClient_CloseSend_Call[Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_IMAMeasurementsClient_CloseSend_Call[Res]) Return(_a0 error) *AgentService_IMAMeasurementsClient_CloseSend_Call[Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_IMAMeasurementsClient_CloseSend_Call[Res]) RunAndReturn(run func() error) *AgentService_IMAMeasurementsClient_CloseSend_Call[Res] {
_c.Call.Return(run)
return _c
}
// Context provides a mock function with no fields
func (_m *AgentService_IMAMeasurementsClient[Res]) Context() context.Context {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Context")
}
var r0 context.Context
if rf, ok := ret.Get(0).(func() context.Context); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(context.Context)
}
}
return r0
}
// AgentService_IMAMeasurementsClient_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'
type AgentService_IMAMeasurementsClient_Context_Call[Res interface{}] struct {
*mock.Call
}
// Context is a helper method to define mock.On call
func (_e *AgentService_IMAMeasurementsClient_Expecter[Res]) Context() *AgentService_IMAMeasurementsClient_Context_Call[Res] {
return &AgentService_IMAMeasurementsClient_Context_Call[Res]{Call: _e.mock.On("Context")}
}
func (_c *AgentService_IMAMeasurementsClient_Context_Call[Res]) Run(run func()) *AgentService_IMAMeasurementsClient_Context_Call[Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_IMAMeasurementsClient_Context_Call[Res]) Return(_a0 context.Context) *AgentService_IMAMeasurementsClient_Context_Call[Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_IMAMeasurementsClient_Context_Call[Res]) RunAndReturn(run func() context.Context) *AgentService_IMAMeasurementsClient_Context_Call[Res] {
_c.Call.Return(run)
return _c
}
// Header provides a mock function with no fields
func (_m *AgentService_IMAMeasurementsClient[Res]) Header() (metadata.MD, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Header")
}
var r0 metadata.MD
var r1 error
if rf, ok := ret.Get(0).(func() (metadata.MD, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() metadata.MD); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(metadata.MD)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AgentService_IMAMeasurementsClient_Header_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Header'
type AgentService_IMAMeasurementsClient_Header_Call[Res interface{}] struct {
*mock.Call
}
// Header is a helper method to define mock.On call
func (_e *AgentService_IMAMeasurementsClient_Expecter[Res]) Header() *AgentService_IMAMeasurementsClient_Header_Call[Res] {
return &AgentService_IMAMeasurementsClient_Header_Call[Res]{Call: _e.mock.On("Header")}
}
func (_c *AgentService_IMAMeasurementsClient_Header_Call[Res]) Run(run func()) *AgentService_IMAMeasurementsClient_Header_Call[Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_IMAMeasurementsClient_Header_Call[Res]) Return(_a0 metadata.MD, _a1 error) *AgentService_IMAMeasurementsClient_Header_Call[Res] {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AgentService_IMAMeasurementsClient_Header_Call[Res]) RunAndReturn(run func() (metadata.MD, error)) *AgentService_IMAMeasurementsClient_Header_Call[Res] {
_c.Call.Return(run)
return _c
}
// Recv provides a mock function with no fields
func (_m *AgentService_IMAMeasurementsClient[Res]) Recv() (*agent.IMAMeasurementsResponse, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Recv")
}
var r0 *agent.IMAMeasurementsResponse
var r1 error
if rf, ok := ret.Get(0).(func() (*agent.IMAMeasurementsResponse, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() *agent.IMAMeasurementsResponse); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*agent.IMAMeasurementsResponse)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AgentService_IMAMeasurementsClient_Recv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Recv'
type AgentService_IMAMeasurementsClient_Recv_Call[Res interface{}] struct {
*mock.Call
}
// Recv is a helper method to define mock.On call
func (_e *AgentService_IMAMeasurementsClient_Expecter[Res]) Recv() *AgentService_IMAMeasurementsClient_Recv_Call[Res] {
return &AgentService_IMAMeasurementsClient_Recv_Call[Res]{Call: _e.mock.On("Recv")}
}
func (_c *AgentService_IMAMeasurementsClient_Recv_Call[Res]) Run(run func()) *AgentService_IMAMeasurementsClient_Recv_Call[Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_IMAMeasurementsClient_Recv_Call[Res]) Return(_a0 *agent.IMAMeasurementsResponse, _a1 error) *AgentService_IMAMeasurementsClient_Recv_Call[Res] {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AgentService_IMAMeasurementsClient_Recv_Call[Res]) RunAndReturn(run func() (*agent.IMAMeasurementsResponse, error)) *AgentService_IMAMeasurementsClient_Recv_Call[Res] {
_c.Call.Return(run)
return _c
}
// RecvMsg provides a mock function with given fields: m
func (_m *AgentService_IMAMeasurementsClient[Res]) RecvMsg(m interface{}) error {
ret := _m.Called(m)
if len(ret) == 0 {
panic("no return value specified for RecvMsg")
}
var r0 error
if rf, ok := ret.Get(0).(func(interface{}) error); ok {
r0 = rf(m)
} else {
r0 = ret.Error(0)
}
return r0
}
// AgentService_IMAMeasurementsClient_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'
type AgentService_IMAMeasurementsClient_RecvMsg_Call[Res interface{}] struct {
*mock.Call
}
// RecvMsg is a helper method to define mock.On call
// - m interface{}
func (_e *AgentService_IMAMeasurementsClient_Expecter[Res]) RecvMsg(m interface{}) *AgentService_IMAMeasurementsClient_RecvMsg_Call[Res] {
return &AgentService_IMAMeasurementsClient_RecvMsg_Call[Res]{Call: _e.mock.On("RecvMsg", m)}
}
func (_c *AgentService_IMAMeasurementsClient_RecvMsg_Call[Res]) Run(run func(m interface{})) *AgentService_IMAMeasurementsClient_RecvMsg_Call[Res] {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(interface{}))
})
return _c
}
func (_c *AgentService_IMAMeasurementsClient_RecvMsg_Call[Res]) Return(_a0 error) *AgentService_IMAMeasurementsClient_RecvMsg_Call[Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_IMAMeasurementsClient_RecvMsg_Call[Res]) RunAndReturn(run func(interface{}) error) *AgentService_IMAMeasurementsClient_RecvMsg_Call[Res] {
_c.Call.Return(run)
return _c
}
// SendMsg provides a mock function with given fields: m
func (_m *AgentService_IMAMeasurementsClient[Res]) SendMsg(m interface{}) error {
ret := _m.Called(m)
if len(ret) == 0 {
panic("no return value specified for SendMsg")
}
var r0 error
if rf, ok := ret.Get(0).(func(interface{}) error); ok {
r0 = rf(m)
} else {
r0 = ret.Error(0)
}
return r0
}
// AgentService_IMAMeasurementsClient_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'
type AgentService_IMAMeasurementsClient_SendMsg_Call[Res interface{}] struct {
*mock.Call
}
// SendMsg is a helper method to define mock.On call
// - m interface{}
func (_e *AgentService_IMAMeasurementsClient_Expecter[Res]) SendMsg(m interface{}) *AgentService_IMAMeasurementsClient_SendMsg_Call[Res] {
return &AgentService_IMAMeasurementsClient_SendMsg_Call[Res]{Call: _e.mock.On("SendMsg", m)}
}
func (_c *AgentService_IMAMeasurementsClient_SendMsg_Call[Res]) Run(run func(m interface{})) *AgentService_IMAMeasurementsClient_SendMsg_Call[Res] {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(interface{}))
})
return _c
}
func (_c *AgentService_IMAMeasurementsClient_SendMsg_Call[Res]) Return(_a0 error) *AgentService_IMAMeasurementsClient_SendMsg_Call[Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_IMAMeasurementsClient_SendMsg_Call[Res]) RunAndReturn(run func(interface{}) error) *AgentService_IMAMeasurementsClient_SendMsg_Call[Res] {
_c.Call.Return(run)
return _c
}
// Trailer provides a mock function with no fields
func (_m *AgentService_IMAMeasurementsClient[Res]) Trailer() metadata.MD {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Trailer")
}
var r0 metadata.MD
if rf, ok := ret.Get(0).(func() metadata.MD); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(metadata.MD)
}
}
return r0
}
// AgentService_IMAMeasurementsClient_Trailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Trailer'
type AgentService_IMAMeasurementsClient_Trailer_Call[Res interface{}] struct {
*mock.Call
}
// Trailer is a helper method to define mock.On call
func (_e *AgentService_IMAMeasurementsClient_Expecter[Res]) Trailer() *AgentService_IMAMeasurementsClient_Trailer_Call[Res] {
return &AgentService_IMAMeasurementsClient_Trailer_Call[Res]{Call: _e.mock.On("Trailer")}
}
func (_c *AgentService_IMAMeasurementsClient_Trailer_Call[Res]) Run(run func()) *AgentService_IMAMeasurementsClient_Trailer_Call[Res] {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentService_IMAMeasurementsClient_Trailer_Call[Res]) Return(_a0 metadata.MD) *AgentService_IMAMeasurementsClient_Trailer_Call[Res] {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentService_IMAMeasurementsClient_Trailer_Call[Res]) RunAndReturn(run func() metadata.MD) *AgentService_IMAMeasurementsClient_Trailer_Call[Res] {
_c.Call.Return(run)
return _c
}
// NewAgentService_IMAMeasurementsClient creates a new instance of AgentService_IMAMeasurementsClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewAgentService_IMAMeasurementsClient[Res interface{}](t interface {
mock.TestingT
Cleanup(func())
}) *AgentService_IMAMeasurementsClient[Res] {
mock := &AgentService_IMAMeasurementsClient[Res]{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+168 -34
View File
@@ -6,15 +6,15 @@ package agent
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"slices"
sync "sync"
"time"
"github.com/google/go-sev-guest/client"
"github.com/absmach/magistrala/pkg/errors"
"github.com/ultravioletrs/cocos/agent/algorithm"
"github.com/ultravioletrs/cocos/agent/algorithm/binary"
"github.com/ultravioletrs/cocos/agent/algorithm/docker"
@@ -23,6 +23,9 @@ import (
"github.com/ultravioletrs/cocos/agent/events"
"github.com/ultravioletrs/cocos/agent/statemachine"
"github.com/ultravioletrs/cocos/internal"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"golang.org/x/crypto/sha3"
)
@@ -69,11 +72,14 @@ const (
)
const (
// ReportDataSize is the size of the report data expected by the attestation service.
ReportDataSize = 64
algoFilePermission = 0o700
)
const (
ImaMeasurementsFilePath = "/sys/kernel/security/integrity/ima/ascii_runtime_measurements"
ImaPcrIndex = 10
)
var (
// ErrMalformedEntity indicates malformed entity specification (e.g.
// invalid username or password).
@@ -99,15 +105,26 @@ var (
ErrAllResultsConsumed = errors.New("all results have been consumed by declared consumers")
// ErrAttestationFailed attestation failed.
ErrAttestationFailed = errors.New("failed to get raw quote")
// ErrAttestationVTpmFailed vTPM attestation failed.
ErrAttestationVTpmFailed = errors.New("failed to get vTPM quote")
// ErrFetchAzureToken azure token fetch failed.
ErrFetchAzureToken = errors.New("failed to get azure token")
// ErrAttType indicates that the attestation type that is requested does not exist or is not supported.
ErrAttestationType = errors.New("attestation type does not exist or is not supported")
)
// Service specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
type Service interface {
InitComputation(ctx context.Context, cmp Computation) error
StopComputation(ctx context.Context) error
Algo(ctx context.Context, algorithm Algorithm) error
Data(ctx context.Context, dataset Dataset) error
Result(ctx context.Context) ([]byte, error)
Attestation(ctx context.Context, reportData [ReportDataSize]byte) ([]byte, error)
Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error)
IMAMeasurements(ctx context.Context) ([]byte, []byte, error)
AttestationResult(ctx context.Context, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error)
State() string
}
type agentService struct {
@@ -118,22 +135,26 @@ type agentService struct {
sm statemachine.StateMachine // Manages the state transitions of the agent service.
runError error // Stores any error encountered during the computation run.
eventSvc events.Service // Service for publishing events related to computation.
quoteProvider client.QuoteProvider // Provider for generating attestation quotes.
provider attestation.Provider // Provider for generating attestation quotes.
logger *slog.Logger // Logger for the agent service.
resultsConsumed bool // Indicates if the results have been consumed.
cancel context.CancelFunc // Cancels the computation context.
vmpl int // VMPL at which the Agent is running.
}
var _ Service = (*agentService)(nil)
// New instantiates the agent service implementation.
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp Computation, quoteProvider client.QuoteProvider) Service {
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, provider attestation.Provider, vmlp int) Service {
sm := statemachine.NewStateMachine(Idle)
ctx, cancel := context.WithCancel(ctx)
svc := &agentService{
sm: sm,
eventSvc: eventSvc,
quoteProvider: quoteProvider,
logger: logger,
computation: cmp,
sm: sm,
eventSvc: eventSvc,
provider: provider,
logger: logger,
cancel: cancel,
vmpl: vmlp,
}
transitions := []statemachine.Transition{
@@ -141,13 +162,6 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp
{From: ReceivingManifest, Event: ManifestReceived, To: ReceivingAlgorithm},
}
if len(cmp.Datasets) == 0 {
transitions = append(transitions, statemachine.Transition{From: ReceivingAlgorithm, Event: AlgorithmReceived, To: Running})
} else {
transitions = append(transitions, statemachine.Transition{From: ReceivingAlgorithm, Event: AlgorithmReceived, To: ReceivingData})
transitions = append(transitions, statemachine.Transition{From: ReceivingData, Event: DataReceived, To: Running})
}
transitions = append(transitions, []statemachine.Transition{
{From: Running, Event: RunComplete, To: ConsumingResults},
{From: Running, Event: RunFailed, To: Failed},
@@ -158,8 +172,6 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp
sm.AddTransition(t)
}
sm.SetAction(Idle, svc.publishEvent(IdleState.String()))
sm.SetAction(ReceivingManifest, svc.publishEvent(InProgress.String()))
sm.SetAction(ReceivingAlgorithm, svc.publishEvent(InProgress.String()))
sm.SetAction(ReceivingData, svc.publishEvent(InProgress.String()))
sm.SetAction(Running, svc.runComputation)
@@ -172,12 +184,93 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp
logger.Error(err.Error())
}
}()
time.Sleep(100 * time.Millisecond)
sm.SendEvent(Start)
defer sm.SendEvent(ManifestReceived)
time.Sleep(100 * time.Millisecond)
return svc
}
func (as *agentService) State() string {
return as.sm.GetState().String()
}
func (as *agentService) InitComputation(ctx context.Context, cmp Computation) error {
if as.sm.GetState() != ReceivingManifest {
return ErrStateNotReady
}
defer as.sm.SendEvent(ManifestReceived)
as.mu.Lock()
defer as.mu.Unlock()
as.computation = cmp
transitions := []statemachine.Transition{}
if len(cmp.Datasets) == 0 {
transitions = append(transitions, statemachine.Transition{From: ReceivingAlgorithm, Event: AlgorithmReceived, To: Running})
} else {
transitions = append(transitions, statemachine.Transition{From: ReceivingAlgorithm, Event: AlgorithmReceived, To: ReceivingData})
transitions = append(transitions, statemachine.Transition{From: ReceivingData, Event: DataReceived, To: Running})
}
for _, t := range transitions {
as.sm.AddTransition(t)
}
return nil
}
func (as *agentService) StopComputation(ctx context.Context) error {
as.mu.Lock()
defer as.mu.Unlock()
as.eventSvc.SendEvent(as.computation.ID, "Stopped", "Stopped", json.RawMessage{})
as.cancel()
if as.algorithm != nil {
if err := as.algorithm.Stop(); err != nil {
return fmt.Errorf("error stopping computation: %v", err)
}
}
if err := os.RemoveAll(algorithm.DatasetsDir); err != nil {
return fmt.Errorf("error removing datasets directory: %v", err)
}
if err := os.RemoveAll(algorithm.ResultsDir); err != nil {
return fmt.Errorf("error removing results directory: %v", err)
}
as.sm.Reset(Idle)
as.computation = Computation{}
as.algorithm = nil
as.result = nil
as.runError = nil
as.resultsConsumed = false
ctx, cancel := context.WithCancel(ctx)
as.cancel = cancel
go func() {
if err := as.sm.Start(ctx); err != nil {
as.logger.Error(err.Error())
}
}()
time.Sleep(100 * time.Millisecond)
as.sm.SendEvent(Start)
time.Sleep(100 * time.Millisecond)
return nil
}
func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
if as.sm.GetState() != ReceivingAlgorithm {
return ErrStateNotReady
@@ -225,7 +318,7 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
switch algoType {
case string(algorithm.AlgoTypeBin):
as.algorithm = binary.NewAlgorithm(as.logger, as.eventSvc, f.Name(), args)
as.algorithm = binary.NewAlgorithm(as.logger, as.eventSvc, f.Name(), args, as.computation.ID)
case string(algorithm.AlgoTypePython):
var requirementsFile string
if len(algo.Requirements) > 0 {
@@ -243,11 +336,11 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
requirementsFile = fr.Name()
}
runtime := python.PythonRunTimeFromContext(ctx)
as.algorithm = python.NewAlgorithm(as.logger, as.eventSvc, runtime, requirementsFile, f.Name(), args)
as.algorithm = python.NewAlgorithm(as.logger, as.eventSvc, runtime, requirementsFile, f.Name(), args, as.computation.ID)
case string(algorithm.AlgoTypeWasm):
as.algorithm = wasm.NewAlgorithm(as.logger, as.eventSvc, f.Name(), args)
as.algorithm = wasm.NewAlgorithm(as.logger, as.eventSvc, args, f.Name(), as.computation.ID)
case string(algorithm.AlgoTypeDocker):
as.algorithm = docker.NewAlgorithm(as.logger, as.eventSvc, f.Name())
as.algorithm = docker.NewAlgorithm(as.logger, as.eventSvc, f.Name(), as.computation.ID)
}
if err := os.Mkdir(algorithm.DatasetsDir, 0o755); err != nil {
@@ -341,13 +434,42 @@ func (as *agentService) Result(ctx context.Context) ([]byte, error) {
return as.result, as.runError
}
func (as *agentService) Attestation(ctx context.Context, reportData [ReportDataSize]byte) ([]byte, error) {
rawQuote, err := as.quoteProvider.GetRawQuote(reportData)
if err != nil {
return []byte{}, err
func (as *agentService) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error) {
switch attType {
case attestation.SNP, attestation.TDX:
rawQuote, err := as.provider.TeeAttestation(reportData[:])
if err != nil {
return []byte{}, errors.Wrap(ErrAttestationFailed, err)
}
return rawQuote, nil
case attestation.VTPM:
vTPMQuote, err := as.provider.VTpmAttestation(nonce[:])
if err != nil {
return []byte{}, errors.Wrap(ErrAttestationVTpmFailed, err)
}
return vTPMQuote, nil
case attestation.SNPvTPM:
vTPMQuote, err := as.provider.Attestation(reportData[:], nonce[:])
if err != nil {
return []byte{}, errors.Wrap(ErrAttestationVTpmFailed, err)
}
return vTPMQuote, nil
default:
return []byte{}, ErrAttestationType
}
}
return rawQuote, nil
func (as *agentService) AttestationResult(ctx context.Context, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error) {
switch attType {
case attestation.AzureToken:
token, err := as.provider.AzureAttestationToken(nonce[:])
if err != nil {
return []byte{}, err
}
return token, nil
default:
return []byte{}, ErrAttestationType
}
}
func (as *agentService) runComputation(state statemachine.State) {
@@ -400,8 +522,20 @@ func (as *agentService) runComputation(state statemachine.State) {
func (as *agentService) publishEvent(status string) statemachine.Action {
return func(state statemachine.State) {
if err := as.eventSvc.SendEvent(state.String(), status, json.RawMessage{}); err != nil {
as.logger.Warn(err.Error())
}
as.eventSvc.SendEvent(as.computation.ID, state.String(), status, json.RawMessage{})
}
}
func (as *agentService) IMAMeasurements(ctx context.Context) ([]byte, []byte, error) {
data, err := os.ReadFile(ImaMeasurementsFilePath)
if err != nil {
return nil, nil, fmt.Errorf("error reading Linux IMA measurements file: %s", err.Error())
}
pcr10, err := vtpm.GetPCRSHA1Value(ImaPcrIndex)
if err != nil {
return nil, nil, fmt.Errorf("error reading TPM PCR #10: %s", err.Error())
}
return data, pcr10, nil
}
+325 -53
View File
@@ -5,8 +5,10 @@ package agent
import (
"context"
"crypto/rand"
"fmt"
"log"
"os"
"path/filepath"
"testing"
"time"
@@ -16,12 +18,15 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/ultravioletrs/cocos/agent/algorithm"
algomocks "github.com/ultravioletrs/cocos/agent/algorithm/mocks"
"github.com/ultravioletrs/cocos/agent/algorithm/python"
"github.com/ultravioletrs/cocos/agent/events/mocks"
"github.com/ultravioletrs/cocos/agent/statemachine"
smmocks "github.com/ultravioletrs/cocos/agent/statemachine/mocks"
"github.com/ultravioletrs/cocos/pkg/attestation"
mocks2 "github.com/ultravioletrs/cocos/pkg/attestation/mocks"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
mocks2 "github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"golang.org/x/crypto/sha3"
"google.golang.org/grpc/metadata"
)
@@ -35,18 +40,11 @@ var (
const datasetFile = "iris.csv"
func TestAlgo(t *testing.T) {
events := new(mocks.Service)
evCall := events.On("SendEvent", mock.Anything, mock.Anything, mock.Anything).Return(nil)
defer evCall.Unset()
qp, err := quoteprovider.GetQuoteProvider()
require.NoError(t, err)
algo, err := os.ReadFile(algoPath)
require.NoError(t, err)
algoHash := sha3.Sum256(algo)
vtpm.ExternalTPM = &vtpm.DummyRWC{}
reqFile, err := os.ReadFile(reqPath)
require.NoError(t, err)
@@ -120,9 +118,15 @@ func TestAlgo(t *testing.T) {
metadata.Pairs(algorithm.AlgoTypeKey, tc.algoType, python.PyRuntimeKey, python.PyRuntime),
)
events := new(mocks.Service)
events.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
svc := New(ctx, mglog.NewMock(), events, testComputation(t), qp)
svc := New(ctx, mglog.NewMock(), events, &attestation.EmptyProvider{}, 0)
err := svc.InitComputation(ctx, testComputation(t))
require.NoError(t, err)
time.Sleep(300 * time.Millisecond)
@@ -138,18 +142,11 @@ func TestAlgo(t *testing.T) {
}
func TestData(t *testing.T) {
events := new(mocks.Service)
evCall := events.On("SendEvent", mock.Anything, mock.Anything, mock.Anything).Return(nil)
defer evCall.Unset()
qp, err := quoteprovider.GetQuoteProvider()
require.NoError(t, err)
algo, err := os.ReadFile(algoPath)
require.NoError(t, err)
algoHash := sha3.Sum256(algo)
vtpm.ExternalTPM = &vtpm.DummyRWC{}
alg := Algorithm{
Hash: algoHash,
@@ -209,6 +206,9 @@ func TestData(t *testing.T) {
python.PyRuntimeKey, python.PyRuntime),
)
events := new(mocks.Service)
events.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
if tc.err != ErrUndeclaredDataset {
ctx = IndexToContext(ctx, 0)
}
@@ -216,13 +216,16 @@ func TestData(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
comp := testComputation(t)
svc := New(ctx, mglog.NewMock(), events, &attestation.EmptyProvider{}, 0)
err := svc.InitComputation(ctx, testComputation(t))
require.NoError(t, err)
svc := New(ctx, mglog.NewMock(), events, comp, qp)
time.Sleep(300 * time.Millisecond)
if tc.err != ErrStateNotReady {
_ = svc.Algo(ctx, alg)
err = svc.Algo(ctx, alg)
require.NoError(t, err)
time.Sleep(300 * time.Millisecond)
}
err = svc.Data(ctx, tc.data)
@@ -238,14 +241,6 @@ func TestData(t *testing.T) {
}
func TestResult(t *testing.T) {
events := new(mocks.Service)
evCall := events.On("SendEvent", mock.Anything, mock.Anything, mock.Anything).Return(nil)
defer evCall.Unset()
qp, err := quoteprovider.GetQuoteProvider()
require.NoError(t, err)
cases := []struct {
name string
err error
@@ -285,6 +280,9 @@ func TestResult(t *testing.T) {
}
for _, tc := range cases {
events := new(mocks.Service)
events.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
t.Run(tc.name, func(t *testing.T) {
ctx := metadata.NewIncomingContext(context.Background(),
metadata.Pairs(algorithm.AlgoTypeKey, "python", python.PyRuntimeKey, python.PyRuntime),
@@ -300,10 +298,10 @@ func TestResult(t *testing.T) {
sm.On("SendEvent", mock.Anything).Return()
svc := &agentService{
sm: sm,
eventSvc: events,
quoteProvider: qp,
computation: testComputation(t),
sm: sm,
eventSvc: events,
provider: &attestation.EmptyProvider{},
computation: testComputation(t),
}
go func() {
@@ -323,55 +321,150 @@ func TestResult(t *testing.T) {
}
func TestAttestation(t *testing.T) {
events := new(mocks.Service)
qp := new(mocks2.QuoteProvider)
evCall := events.On("SendEvent", mock.Anything, mock.Anything, mock.Anything).Return(nil)
defer evCall.Unset()
provider := new(mocks2.Provider)
cases := []struct {
name string
reportData [ReportDataSize]byte
reportData [quoteprovider.Nonce]byte
nonce [vtpm.Nonce]byte
rawQuote []uint8
platform attestation.PlatformType
err error
}{
{
name: "Test attestation successful",
name: "Test SNP attestation successful",
reportData: generateReportData(),
nonce: [32]byte{},
rawQuote: make([]uint8, 0),
platform: attestation.SNP,
err: nil,
},
{
name: "Test attestation failed",
name: "Test SNP attestation failed",
reportData: generateReportData(),
nonce: [32]byte{},
rawQuote: nil,
platform: attestation.SNP,
err: ErrAttestationFailed,
},
{
name: "Test vTPM attestation successful",
reportData: generateReportData(),
nonce: [32]byte{},
rawQuote: make([]uint8, 0),
platform: attestation.VTPM,
err: nil,
},
{
name: "Test vTPM attestation failed",
reportData: generateReportData(),
nonce: [32]byte{},
rawQuote: nil,
platform: attestation.VTPM,
err: ErrAttestationVTpmFailed,
},
{
name: "Test SNP-vTPM attestation successful",
reportData: generateReportData(),
nonce: [32]byte{},
rawQuote: make([]uint8, 0),
platform: attestation.SNPvTPM,
err: nil,
},
{
name: "Test SNP-vTPM attestation failed",
reportData: generateReportData(),
nonce: [32]byte{},
rawQuote: nil,
platform: attestation.SNPvTPM,
err: ErrAttestationVTpmFailed,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
events := new(mocks.Service)
events.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
ctx := metadata.NewIncomingContext(context.Background(),
metadata.Pairs(algorithm.AlgoTypeKey, "python", python.PyRuntimeKey, python.PyRuntime),
)
ctx, cancel := context.WithCancel(ctx)
defer cancel()
getQuote := qp.On("GetRawQuote", mock.Anything).Return(tc.rawQuote, tc.err)
if tc.err != ErrAttestationFailed {
getQuote = qp.On("GetRawQuote", mock.Anything).Return(tc.reportData, nil)
getQuote := provider.On("TeeAttestation", mock.Anything).Return(tc.rawQuote, tc.err)
vtpmQuote := provider.On("VTpmAttestation", mock.Anything).Return(tc.rawQuote, tc.err)
snpVtpm := provider.On("Attestation", mock.Anything, mock.Anything).Return(tc.rawQuote, tc.err)
if tc.err != ErrAttestationFailed && tc.err != ErrAttestationVTpmFailed {
getQuote = provider.On("TeeAttestation", mock.Anything).Return(tc.nonce, nil)
vtpmQuote = provider.On("VTpmAttestation", mock.Anything).Return(tc.nonce[:], nil)
snpVtpm = provider.On("Attestation", mock.Anything, mock.Anything).Return(tc.nonce[:], nil)
}
defer getQuote.Unset()
defer vtpmQuote.Unset()
defer snpVtpm.Unset()
svc := New(ctx, mglog.NewMock(), events, testComputation(t), qp)
svc := New(ctx, mglog.NewMock(), events, provider, 0)
time.Sleep(300 * time.Millisecond)
_, err := svc.Attestation(ctx, tc.reportData)
_, err := svc.Attestation(ctx, tc.reportData, tc.nonce, tc.platform)
assert.True(t, errors.Contains(err, tc.err), "expected %v, got %v", tc.err, err)
})
}
}
func generateReportData() [ReportDataSize]byte {
bytes := make([]byte, ReportDataSize)
func TestAttestationResult(t *testing.T) {
provider := new(mocks2.Provider)
cases := []struct {
name string
nonce [vtpm.Nonce]byte
platform attestation.PlatformType
token []byte
err error
}{
{
name: "Azure token fetch successful",
nonce: [32]byte{1, 2, 3}, // any test nonce
platform: attestation.AzureToken,
token: []byte("mockToken"),
err: nil,
},
{
name: "Azure token fetch failed",
nonce: [32]byte{4, 5, 6},
platform: attestation.AzureToken,
token: []byte{},
err: ErrFetchAzureToken,
},
{
name: "Invalid attestation type",
nonce: [32]byte{7, 8, 9},
platform: attestation.SNP,
token: []byte{},
err: ErrAttestationType,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
events := new(mocks.Service)
events.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
if tc.platform == attestation.AzureToken {
provider.On("AzureAttestationToken", tc.nonce[:]).Return(tc.token, tc.err)
}
ctx := context.Background()
svc := New(ctx, mglog.NewMock(), events, provider, 0)
result, err := svc.AttestationResult(ctx, tc.nonce, tc.platform)
assert.True(t, errors.Contains(err, tc.err), "expected error %v, got %v", tc.err, err)
assert.Equal(t, tc.token, result)
})
}
}
func generateReportData() [quoteprovider.Nonce]byte {
bytes := make([]byte, quoteprovider.Nonce)
_, err := rand.Read(bytes)
if err != nil {
log.Fatalf("Failed to generate random bytes: %v", err)
@@ -397,10 +490,189 @@ func testComputation(t *testing.T) Computation {
Datasets: []Dataset{{Hash: dataHash, UserKey: []byte("key"), Dataset: data, Filename: datasetFile}},
Algorithm: Algorithm{Hash: algoHash, UserKey: []byte("key"), Algorithm: algo},
ResultConsumers: []ResultConsumer{{UserKey: []byte("key")}},
AgentConfig: AgentConfig{
Port: "7002",
LogLevel: "debug",
AttestedTls: false,
},
}
}
func TestStopComputation(t *testing.T) {
testDataDir := "test_datasets"
testResultsDir := "test_results"
cases := []struct {
name string
setupDirs bool
setupAlgo bool
algoStopErr error
expectedErr error
}{
{
name: "Stop computation successfully",
setupDirs: true,
setupAlgo: true,
algoStopErr: nil,
expectedErr: nil,
},
{
name: "Stop computation with algorithm stop error",
setupDirs: true,
setupAlgo: true,
algoStopErr: fmt.Errorf("algorithm stop failed"),
expectedErr: fmt.Errorf("error stopping computation: algorithm stop failed"),
},
{
name: "Stop computation without algorithm",
setupDirs: true,
setupAlgo: false,
algoStopErr: nil,
expectedErr: nil,
},
{
name: "Stop computation with missing directories",
setupDirs: false,
setupAlgo: false,
algoStopErr: nil,
expectedErr: nil, // os.RemoveAll doesn't error on non-existing directories
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
events := new(mocks.Service)
events.On("SendEvent", mock.Anything, "Stopped", "Stopped", mock.Anything).Return()
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
svc := New(ctx, mglog.NewMock(), events, &attestation.EmptyProvider{}, 0).(*agentService)
svc.computation = Computation{
ID: "test-computation",
Name: "test",
}
if tc.setupDirs {
err := os.MkdirAll(testDataDir, 0o755)
require.NoError(t, err)
err = os.MkdirAll(testResultsDir, 0o755)
require.NoError(t, err)
}
if tc.setupAlgo {
mockAlgo := new(algomocks.Algorithm)
mockAlgo.On("Stop").Return(tc.algoStopErr)
svc.algorithm = mockAlgo
}
err := svc.StopComputation(ctx)
if tc.expectedErr != nil {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.expectedErr.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, ReceivingManifest, svc.sm.GetState())
assert.Nil(t, svc.result)
assert.Nil(t, svc.runError)
assert.False(t, svc.resultsConsumed)
events.AssertExpectations(t)
_ = os.RemoveAll(testDataDir)
_ = os.RemoveAll(testResultsDir)
})
}
}
func TestStopComputationIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
algo := []byte("#!/bin/bash\necho 'test algorithm'")
algoHash := sha3.Sum256(algo)
testDir := "test_integration"
err := os.MkdirAll(testDir, 0o755)
require.NoError(t, err)
defer os.RemoveAll(testDir)
algoFile := filepath.Join(testDir, "test_algo")
err = os.WriteFile(algoFile, algo, 0o755)
require.NoError(t, err)
events := new(mocks.Service)
events.On("SendEvent", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
ctx := metadata.NewIncomingContext(context.Background(),
metadata.Pairs(algorithm.AlgoTypeKey, "bin"),
)
ctx, cancel := context.WithCancel(ctx)
defer cancel()
svc := New(ctx, mglog.NewMock(), events, &attestation.EmptyProvider{}, 0)
computation := Computation{
ID: "integration-test",
Name: "Integration Test",
Algorithm: Algorithm{
Hash: algoHash,
Algorithm: algo,
},
}
err = svc.InitComputation(ctx, computation)
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
err = svc.Algo(ctx, Algorithm{
Hash: algoHash,
Algorithm: algo,
})
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
err = svc.StopComputation(ctx)
assert.NoError(t, err)
assert.Equal(t, "ReceivingManifest", svc.State())
}
func TestStopComputationConcurrent(t *testing.T) {
events := new(mocks.Service)
events.On("SendEvent", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
svc := New(ctx, mglog.NewMock(), events, &attestation.EmptyProvider{}, 0)
svc.(*agentService).computation = Computation{
ID: "concurrent-test",
Name: "Concurrent Test",
}
const numGoroutines = 10
errChan := make(chan error, numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func() {
err := svc.StopComputation(ctx)
errChan <- err
}()
}
var errors []error
for i := 0; i < numGoroutines; i++ {
err := <-errChan
if err != nil {
errors = append(errors, err)
}
}
assert.True(t, len(errors) < numGoroutines, "All StopComputation calls failed")
}
+9 -3
View File
@@ -54,10 +54,11 @@ func TestAddTransition(t *testing.T) {
go func() {
if err := sm.Start(ctx); err != context.Canceled {
t.Errorf("Start returned error: %v", err)
}
}()
time.Sleep(50 * time.Millisecond)
sm.SendEvent(Event1)
time.Sleep(50 * time.Millisecond)
@@ -79,7 +80,7 @@ func TestSetAction(t *testing.T) {
sm.AddTransition(statemachine.Transition{From: State1, Event: Event1, To: State2})
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
ctx, cancel := context.WithTimeout(context.Background(), 150*time.Millisecond)
defer cancel()
go func() {
@@ -88,8 +89,12 @@ func TestSetAction(t *testing.T) {
}
}()
time.Sleep(50 * time.Millisecond)
sm.SendEvent(Event1)
time.Sleep(50 * time.Millisecond)
wg.Wait()
if ctx.Err() != nil {
@@ -132,10 +137,11 @@ func TestMultipleTransitions(t *testing.T) {
go func() {
if err := sm.Start(ctx); err != context.Canceled {
t.Errorf("Start returned error: %v", err)
}
}()
time.Sleep(50 * time.Millisecond)
transitions := []struct {
event MockEvent
want MockState
+38 -5
View File
@@ -1,7 +1,7 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
@@ -54,11 +54,11 @@ func (_c *StateMachine_AddTransition_Call) Return() *StateMachine_AddTransition_
}
func (_c *StateMachine_AddTransition_Call) RunAndReturn(run func(statemachine.Transition)) *StateMachine_AddTransition_Call {
_c.Call.Return(run)
_c.Run(run)
return _c
}
// GetState provides a mock function with given fields:
// GetState provides a mock function with no fields
func (_m *StateMachine) GetState() statemachine.State {
ret := _m.Called()
@@ -105,6 +105,39 @@ func (_c *StateMachine_GetState_Call) RunAndReturn(run func() statemachine.State
return _c
}
// Reset provides a mock function with given fields: initialState
func (_m *StateMachine) Reset(initialState statemachine.State) {
_m.Called(initialState)
}
// StateMachine_Reset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reset'
type StateMachine_Reset_Call struct {
*mock.Call
}
// Reset is a helper method to define mock.On call
// - initialState statemachine.State
func (_e *StateMachine_Expecter) Reset(initialState interface{}) *StateMachine_Reset_Call {
return &StateMachine_Reset_Call{Call: _e.mock.On("Reset", initialState)}
}
func (_c *StateMachine_Reset_Call) Run(run func(initialState statemachine.State)) *StateMachine_Reset_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(statemachine.State))
})
return _c
}
func (_c *StateMachine_Reset_Call) Return() *StateMachine_Reset_Call {
_c.Call.Return()
return _c
}
func (_c *StateMachine_Reset_Call) RunAndReturn(run func(statemachine.State)) *StateMachine_Reset_Call {
_c.Run(run)
return _c
}
// SendEvent provides a mock function with given fields: event
func (_m *StateMachine) SendEvent(event statemachine.Event) {
_m.Called(event)
@@ -134,7 +167,7 @@ func (_c *StateMachine_SendEvent_Call) Return() *StateMachine_SendEvent_Call {
}
func (_c *StateMachine_SendEvent_Call) RunAndReturn(run func(statemachine.Event)) *StateMachine_SendEvent_Call {
_c.Call.Return(run)
_c.Run(run)
return _c
}
@@ -168,7 +201,7 @@ func (_c *StateMachine_SetAction_Call) Return() *StateMachine_SetAction_Call {
}
func (_c *StateMachine_SetAction_Call) RunAndReturn(run func(statemachine.State, statemachine.Action)) *StateMachine_SetAction_Call {
_c.Call.Return(run)
_c.Run(run)
return _c
}
+37 -2
View File
@@ -30,6 +30,7 @@ type StateMachine interface {
GetState() State
SendEvent(event Event)
Start(ctx context.Context) error
Reset(initialState State)
}
type stateMachine struct {
@@ -38,6 +39,7 @@ type stateMachine struct {
transitions map[State]map[Event]State
actions map[State]Action
eventChan chan Event
resetChan chan struct{}
}
func NewStateMachine(initialState State) StateMachine {
@@ -46,6 +48,7 @@ func NewStateMachine(initialState State) StateMachine {
transitions: make(map[State]map[Event]State),
actions: make(map[State]Action),
eventChan: make(chan Event),
resetChan: make(chan struct{}),
}
}
@@ -73,22 +76,54 @@ func (sm *stateMachine) GetState() State {
}
func (sm *stateMachine) SendEvent(event Event) {
sm.eventChan <- event
sm.mu.Lock()
eventChan := sm.eventChan
sm.mu.Unlock()
select {
case eventChan <- event:
default:
// Channel might be closed or full, ignore the event
}
}
func (sm *stateMachine) Start(ctx context.Context) error {
for {
sm.mu.Lock()
eventChan := sm.eventChan
resetChan := sm.resetChan
sm.mu.Unlock()
select {
case event := <-sm.eventChan:
case event := <-eventChan:
if err := sm.handleEvent(event); err != nil {
return err
}
case <-resetChan:
continue
case <-ctx.Done():
return ctx.Err()
}
}
}
func (sm *stateMachine) Reset(initialState State) {
sm.mu.Lock()
defer sm.mu.Unlock()
// Reset current state to initial state
sm.currentState = initialState
// Close the existing event channel to stop processing events
close(sm.eventChan)
// Close the reset channel to signal Start() to restart
close(sm.resetChan)
sm.eventChan = make(chan Event)
sm.resetChan = make(chan struct{})
}
func (sm *stateMachine) handleEvent(event Event) error {
sm.mu.Lock()
currentState := sm.currentState
+607
View File
@@ -0,0 +1,607 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package statemachine
import (
"context"
"sync"
"testing"
"time"
)
type testState string
func (s testState) String() string {
return string(s)
}
type testEvent string
func (e testEvent) String() string {
return string(e)
}
const (
StateIdle testState = "idle"
StateRunning testState = "running"
StatePaused testState = "paused"
StateStopped testState = "stopped"
StateError testState = "error"
)
const (
EventStart testEvent = "start"
EventPause testEvent = "pause"
EventStop testEvent = "stop"
EventReset testEvent = "reset"
EventError testEvent = "error"
)
func TestNewStateMachine(t *testing.T) {
tests := []struct {
name string
initialState State
want State
}{
{
name: "create with idle state",
initialState: StateIdle,
want: StateIdle,
},
{
name: "create with running state",
initialState: StateRunning,
want: StateRunning,
},
{
name: "create with custom state",
initialState: testState("custom"),
want: testState("custom"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sm := NewStateMachine(tt.initialState)
if got := sm.GetState(); got != tt.want {
t.Errorf("NewStateMachine() initial state = %v, want %v", got, tt.want)
}
})
}
}
func TestStateMachine_AddTransition(t *testing.T) {
tests := []struct {
name string
transitions []Transition
from State
event Event
expectTo State
expectValid bool
}{
{
name: "single transition",
transitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
},
from: StateIdle,
event: EventStart,
expectTo: StateRunning,
expectValid: true,
},
{
name: "multiple transitions from same state",
transitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
{From: StateIdle, Event: EventError, To: StateError},
},
from: StateIdle,
event: EventError,
expectTo: StateError,
expectValid: true,
},
{
name: "overwrite existing transition",
transitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
{From: StateIdle, Event: EventStart, To: StatePaused}, // Overwrite
},
from: StateIdle,
event: EventStart,
expectTo: StatePaused,
expectValid: true,
},
{
name: "transition not found",
transitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
},
from: StateRunning,
event: EventPause,
expectValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sm := NewStateMachine(StateIdle).(*stateMachine)
for _, transition := range tt.transitions {
sm.AddTransition(transition)
}
sm.mu.Lock()
nextState, valid := sm.transitions[tt.from][tt.event]
sm.mu.Unlock()
if valid != tt.expectValid {
t.Errorf("Transition validity = %v, want %v", valid, tt.expectValid)
}
if tt.expectValid && nextState != tt.expectTo {
t.Errorf("Transition destination = %v, want %v", nextState, tt.expectTo)
}
})
}
}
func TestStateMachine_SetAction(t *testing.T) {
tests := []struct {
name string
state State
action Action
expectAction bool
}{
{
name: "set action for state",
state: StateRunning,
action: func(s State) {
},
expectAction: true,
},
{
name: "set nil action",
state: StatePaused,
action: nil,
expectAction: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sm := NewStateMachine(StateIdle).(*stateMachine)
sm.SetAction(tt.state, tt.action)
sm.mu.Lock()
action := sm.actions[tt.state]
sm.mu.Unlock()
if tt.expectAction && action == nil {
t.Error("Expected action to be set, but it was nil")
}
if !tt.expectAction && action != nil {
t.Error("Expected action to be nil, but it was set")
}
})
}
}
func TestStateMachine_GetState(t *testing.T) {
tests := []struct {
name string
initialState State
transitions []Transition
events []Event
finalState State
}{
{
name: "get initial state",
initialState: StateIdle,
finalState: StateIdle,
},
{
name: "get state after transition",
initialState: StateIdle,
transitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
},
events: []Event{EventStart},
finalState: StateRunning,
},
{
name: "get state after multiple transitions",
initialState: StateIdle,
transitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
{From: StateRunning, Event: EventPause, To: StatePaused},
{From: StatePaused, Event: EventStart, To: StateRunning},
},
events: []Event{EventStart, EventPause, EventStart},
finalState: StateRunning,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sm := NewStateMachine(tt.initialState)
for _, transition := range tt.transitions {
sm.AddTransition(transition)
}
smImpl := sm.(*stateMachine)
for _, event := range tt.events {
if err := smImpl.handleEvent(event); err != nil {
t.Fatalf("Failed to handle event %v: %v", event, err)
}
}
if got := sm.GetState(); got != tt.finalState {
t.Errorf("GetState() = %v, want %v", got, tt.finalState)
}
})
}
}
func TestStateMachine_Start(t *testing.T) {
tests := []struct {
name string
initialState State
transitions []Transition
events []Event
cancelAfter time.Duration
expectError bool
expectedStates []State
}{
{
name: "start and cancel immediately",
initialState: StateIdle,
cancelAfter: 10 * time.Millisecond,
expectError: true, // context.Canceled
},
{
name: "process events then cancel",
initialState: StateIdle,
transitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
{From: StateRunning, Event: EventStop, To: StateStopped},
},
events: []Event{EventStart, EventStop},
cancelAfter: 100 * time.Millisecond,
expectError: true, // context.Canceled
expectedStates: []State{StateRunning, StateStopped},
},
{
name: "invalid transition error",
initialState: StateIdle,
transitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
},
events: []Event{EventPause}, // Invalid from StateIdle
cancelAfter: 50 * time.Millisecond,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sm := NewStateMachine(tt.initialState)
for _, transition := range tt.transitions {
sm.AddTransition(transition)
}
var states []State
var mu sync.Mutex
for _, state := range tt.expectedStates {
sm.SetAction(state, func(s State) {
mu.Lock()
states = append(states, s)
mu.Unlock()
})
}
ctx, cancel := context.WithCancel(context.Background())
errChan := make(chan error, 1)
go func() {
errChan <- sm.Start(ctx)
}()
time.Sleep(5 * time.Millisecond)
for _, event := range tt.events {
sm.SendEvent(event)
time.Sleep(5 * time.Millisecond)
}
time.Sleep(tt.cancelAfter)
cancel()
err := <-errChan
if tt.expectError && err == nil {
t.Error("Expected error but got none")
}
if !tt.expectError && err != nil {
t.Errorf("Unexpected error: %v", err)
}
time.Sleep(10 * time.Millisecond)
mu.Lock()
if len(states) != len(tt.expectedStates) {
t.Errorf("Expected %d state changes, got %d", len(tt.expectedStates), len(states))
}
for i, expectedState := range tt.expectedStates {
if i < len(states) && states[i] != expectedState {
t.Errorf("State change %d = %v, want %v", i, states[i], expectedState)
}
}
mu.Unlock()
})
}
}
func TestStateMachine_Reset(t *testing.T) {
tests := []struct {
name string
initialState State
resetState State
setupTransitions []Transition
eventsBeforeReset []Event
eventsAfterReset []Event
expectedState State
}{
{
name: "reset to same state",
initialState: StateIdle,
resetState: StateIdle,
expectedState: StateIdle,
},
{
name: "reset to different state",
initialState: StateIdle,
resetState: StateRunning,
expectedState: StateRunning,
},
{
name: "reset after state changes",
initialState: StateIdle,
resetState: StateIdle,
setupTransitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
},
eventsBeforeReset: []Event{EventStart},
expectedState: StateIdle,
},
{
name: "reset and send new events",
initialState: StateIdle,
resetState: StateIdle,
setupTransitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
{From: StateRunning, Event: EventStop, To: StateStopped},
},
eventsBeforeReset: []Event{EventStart},
eventsAfterReset: []Event{EventStart},
expectedState: StateIdle,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sm := NewStateMachine(tt.initialState)
smImpl := sm.(*stateMachine)
for _, transition := range tt.setupTransitions {
sm.AddTransition(transition)
}
for _, event := range tt.eventsBeforeReset {
if err := smImpl.handleEvent(event); err != nil {
// Ignore errors for this test
}
}
sm.Reset(tt.resetState)
if got := sm.GetState(); got != tt.expectedState {
t.Errorf("State after reset = %v, want %v", got, tt.expectedState)
}
for _, event := range tt.eventsAfterReset {
sm.SendEvent(event)
}
// For events after reset, we can't easily check the channel length
// due to the synchronization changes, so we just verify the reset worked
if len(tt.eventsAfterReset) > 0 {
time.Sleep(5 * time.Millisecond)
}
})
}
}
func TestStateMachine_Reset_WithRunningStateMachine(t *testing.T) {
sm := NewStateMachine(StateIdle)
sm.AddTransition(Transition{From: StateIdle, Event: EventStart, To: StateRunning})
sm.AddTransition(Transition{From: StateRunning, Event: EventStop, To: StateStopped})
var stateChanges []State
var mu sync.Mutex
sm.SetAction(StateRunning, func(s State) {
mu.Lock()
stateChanges = append(stateChanges, s)
mu.Unlock()
})
sm.SetAction(StateStopped, func(s State) {
mu.Lock()
stateChanges = append(stateChanges, s)
mu.Unlock()
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
if err := sm.Start(ctx); err != nil {
}
}()
// Give it time to start
time.Sleep(5 * time.Millisecond)
// Send an event
sm.SendEvent(EventStart)
time.Sleep(10 * time.Millisecond)
// Reset while running
sm.Reset(StateIdle)
// Verify state was reset
if got := sm.GetState(); got != StateIdle {
t.Errorf("State after reset = %v, want %v", got, StateIdle)
}
// Send another event after reset
sm.SendEvent(EventStart)
time.Sleep(10 * time.Millisecond)
mu.Lock()
changes := len(stateChanges)
mu.Unlock()
// Should have at least processed the first event
if changes < 1 {
t.Errorf("Expected at least 1 state change, got %d", changes)
}
}
func TestStateMachine_HandleEvent(t *testing.T) {
tests := []struct {
name string
initialState State
transitions []Transition
event Event
expectedState State
expectError bool
expectActionCall bool
}{
{
name: "valid transition",
initialState: StateIdle,
transitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
},
event: EventStart,
expectedState: StateRunning,
expectError: false,
expectActionCall: true,
},
{
name: "invalid transition",
initialState: StateIdle,
transitions: []Transition{
{From: StateRunning, Event: EventPause, To: StatePaused},
},
event: EventStart,
expectedState: StateIdle,
expectError: true,
expectActionCall: false,
},
{
name: "transition with no action",
initialState: StateIdle,
transitions: []Transition{
{From: StateIdle, Event: EventStart, To: StateRunning},
},
event: EventStart,
expectedState: StateRunning,
expectError: false,
expectActionCall: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sm := NewStateMachine(tt.initialState).(*stateMachine)
for _, transition := range tt.transitions {
sm.AddTransition(transition)
}
var actionCalled bool
var mu sync.Mutex
if tt.expectActionCall {
sm.SetAction(tt.expectedState, func(s State) {
mu.Lock()
actionCalled = true
mu.Unlock()
})
}
err := sm.handleEvent(tt.event)
if tt.expectError && err == nil {
t.Error("Expected error but got none")
}
if !tt.expectError && err != nil {
t.Errorf("Unexpected error: %v", err)
}
if sm.GetState() != tt.expectedState {
t.Errorf("State after handleEvent = %v, want %v", sm.GetState(), tt.expectedState)
}
if tt.expectActionCall {
time.Sleep(10 * time.Millisecond)
mu.Lock()
called := actionCalled
mu.Unlock()
if !called {
t.Error("Expected action to be called but it wasn't")
}
}
})
}
}
func TestStateMachine_SendEvent_ThreadSafety(t *testing.T) {
sm := NewStateMachine(StateIdle)
sm.AddTransition(Transition{From: StateIdle, Event: EventStart, To: StateRunning})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
if err := sm.Start(ctx); err != nil {
}
}()
time.Sleep(5 * time.Millisecond)
var wg sync.WaitGroup
numGoroutines := 10
eventsPerGoroutine := 100
// Send events concurrently
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < eventsPerGoroutine; j++ {
sm.SendEvent(EventStart)
}
}()
}
wg.Wait()
time.Sleep(10 * time.Millisecond)
// If we reach here without panicking, the test passes
}
BIN
View File
Binary file not shown.
-3
View File
@@ -1,9 +1,6 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
//go:build embed
// +build embed
package cocosai
import _ "embed"
+19
View File
@@ -100,3 +100,22 @@ When defining the manifest dataset and algorithm checksums are required. This ca
```bash
./build/cocos-cli checksum <path_to_dataset_or_algorithm>
```
#### Measure IGVM file
We assume that our current working directory is the root of the cocos repository, both on the host machine and in the VM.
`igvmmeasure` calculates the launch measurement for an IGVM file and can generate a signed version. It ensures integrity by precomputing the expected launch digest, which can be verified against the attestation report. The tool parses IGVM directives, outputs the measurement as a hex string, or creates a signed file for verification at guest launch.
##### Example
We measure an IGVM file using our measure command, run:
```bash
./build/cocos-cli igvmmeasure /path/to/igvm/file
```
The tool will parse the directives in the IGVM file, calculate the launch measurement, and output the computed digest. If successful, it prints the measurement to standard output.
Here is a sample output
```
91c4929bec2d0ecf11a708e09f0a57d7d82208bcba2451564444a4b01c22d047995ca27f9053f86de4e8063e9f810548
```
+412 -523
View File
File diff suppressed because it is too large Load Diff
+194 -14
View File
@@ -3,15 +3,24 @@
package cli
import (
"bytes"
"crypto/sha512"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strconv"
"github.com/absmach/magistrala/pkg/errors"
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-tpm-tools/proto/attest"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"google.golang.org/protobuf/encoding/protojson"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/azure"
"github.com/ultravioletrs/cocos/pkg/attestation/gcp"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"google.golang.org/protobuf/proto"
)
type fieldType int
@@ -30,13 +39,14 @@ const (
)
var (
errDecode = errors.New("base64 string could not be decoded")
errDataLength = errors.New("data does not have an adequate length")
errReadingAttestationPolicyFile = errors.New("error while reading the attestation policy file")
errUnmarshalJSON = errors.New("failed to unmarshal json")
errMarshalJSON = errors.New("failed to marshal json")
errWriteFile = errors.New("failed to write to file")
errAttestationPolicyField = errors.New("the specified field type does not exist in the attestation policy")
errDecode = errors.New("base64 string could not be decoded")
errDataLength = errors.New("data does not have an adequate length")
errReadingAttestationPolicyFile = errors.New("error while reading the attestation policy file")
errUnmarshalJSON = errors.New("failed to unmarshal json")
errMarshalJSON = errors.New("failed to marshal json")
errWriteFile = errors.New("failed to write to file")
errAttestationPolicyField = errors.New("the specified field type does not exist in the attestation policy")
policy uint64 = 196639
)
func (cli *CLI) NewAttestationPolicyCmd() *cobra.Command {
@@ -99,6 +109,172 @@ func (cli *CLI) NewAddHostDataCmd() *cobra.Command {
}
}
func (cli *CLI) NewGCPAttestationPolicy() *cobra.Command {
return &cobra.Command{
Use: "gcp",
Short: "Get attestation policy for GCP CVM",
Example: `gcp <bin_vtmp_attestation_report_file> <vcpu_count>`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
attestationBin, err := os.ReadFile(args[0])
if err != nil {
printError(cmd, "Error reading attestation report file: %v ❌ ", err)
return
}
vcpuCount, err := strconv.Atoi(args[1])
if err != nil {
printError(cmd, "Error converting vCPU count to integer: %v ❌ ", err)
return
}
attestation := &attest.Attestation{}
if err := proto.Unmarshal(attestationBin, attestation); err != nil {
printError(cmd, "Error unmarshaling attestation report: %v ❌ ", err)
return
}
attestationPB := attestation.GetSevSnpAttestation()
measurement, err := gcp.Extract384BitMeasurement(attestationPB)
if err != nil {
printError(cmd, "Error extracting 384-bit measurement: %v ❌ ", err)
return
}
launchEndorsement, err := gcp.GetLaunchEndorsement(cmd.Context(), measurement)
if err != nil {
printError(cmd, "Error getting launch endorsement: %v ❌ ", err)
return
}
attestationPolicy, err := gcp.GenerateAttestationPolicy(launchEndorsement, uint32(vcpuCount))
if err != nil {
printError(cmd, "Error generating attestation policy: %v ❌ ", err)
return
}
attestationPolicyJson, err := json.MarshalIndent(attestationPolicy, "", " ")
if err != nil {
printError(cmd, "Error marshaling attestation policy: %v ❌ ", err)
return
}
if err := os.WriteFile("attestation_policy.json", attestationPolicyJson, filePermission); err != nil {
printError(cmd, "Error writing attestation policy file: %v ❌ ", err)
return
}
cmd.Println("Attestation policy file generated successfully ✅")
},
}
}
func (cli *CLI) NewDownloadGCPOvmfFile() *cobra.Command {
return &cobra.Command{
Use: "download",
Short: "Download GCP OVMF file",
Example: `download <bin_vtmp_attestation_report_file>`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
attestationBin, err := os.ReadFile(args[0])
if err != nil {
printError(cmd, "Error reading attestation report file: %v ❌ ", err)
return
}
attestation := &attest.Attestation{}
if err := proto.Unmarshal(attestationBin, attestation); err != nil {
printError(cmd, "Error unmarshaling attestation report: %v ❌ ", err)
return
}
attestationPB := attestation.GetSevSnpAttestation()
measurement, err := gcp.Extract384BitMeasurement(attestationPB)
if err != nil {
printError(cmd, "Error extracting 384-bit measurement: %v ❌ ", err)
return
}
launchEndorsement, err := gcp.GetLaunchEndorsement(cmd.Context(), measurement)
if err != nil {
printError(cmd, "Error getting launch endorsement: %v ❌ ", err)
return
}
ovmf, err := gcp.DownloadOvmfFile(cmd.Context(), fmt.Sprintf("%x", launchEndorsement.Digest))
if err != nil {
printError(cmd, "Error downloading OVMF file: %v ❌ ", err)
return
}
sum384 := sha512.Sum384(ovmf)
if !bytes.Equal(sum384[:], launchEndorsement.Digest) {
printError(cmd, "Error OVMF file does not match the measurement: %v ❌ ", fmt.Errorf("digest mismatch"))
} else {
cmd.Println("OVMF firmware in vm is unmodified ✅")
}
if err := os.WriteFile("ovmf.fd", ovmf, filePermission); err != nil {
printError(cmd, "Error writing OVMF file: %v ❌ ", err)
return
}
cmd.Println("OVMF file downloaded successfully ✅")
},
}
}
func (cli *CLI) NewAzureAttestationPolicy() *cobra.Command {
cmd := &cobra.Command{
Use: "azure",
Short: "Get attestation policy for Azure CVM",
Example: `azure <azure_maa_token_file> <product_name>`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
token, err := os.ReadFile(args[0])
if err != nil {
printError(cmd, "Error reading attestation report file: %v ❌ ", err)
return
}
product := args[1]
config, err := azure.GenerateAttestationPolicy(string(token), product, policy)
if err != nil {
printError(cmd, "Error generating attestation policy: %v ❌ ", err)
return
}
attestationPolicyJson, err := json.MarshalIndent(&config, "", " ")
if err != nil {
printError(cmd, "Error marshaling attestation policy: %v ❌ ", err)
return
}
if err := os.WriteFile("attestation_policy.json", attestationPolicyJson, filePermission); err != nil {
printError(cmd, "Error writing attestation policy file: %v ❌ ", err)
return
}
cmd.Println("Attestation policy file generated successfully ✅")
},
}
cmd.Flags().Uint64Var(
&policy,
"policy",
policy,
"Policy of the guest CVM",
)
return cmd
}
func changeAttestationConfiguration(fileName, base64Data string, expectedLength int, field fieldType) error {
data, err := base64.StdEncoding.DecodeString(base64Data)
if err != nil {
@@ -109,27 +285,31 @@ func changeAttestationConfiguration(fileName, base64Data string, expectedLength
return errDataLength
}
ac := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
ac := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
attestationPolicy, err := os.ReadFile(fileName)
f, err := os.ReadFile(fileName)
if err != nil {
return errors.Wrap(errReadingAttestationPolicyFile, err)
}
if err = protojson.Unmarshal(attestationPolicy, &ac); err != nil {
if err = vtpm.ReadPolicyFromByte(f, &ac); err != nil {
return errors.Wrap(errUnmarshalJSON, err)
}
if ac.Config.Policy == nil {
ac.Config.Policy = &check.Policy{}
}
switch field {
case measurementField:
ac.Policy.Measurement = data
ac.Config.Policy.Measurement = data
case hostDataField:
ac.Policy.HostData = data
ac.Config.Policy.HostData = data
default:
return errAttestationPolicyField
}
fileJson, err := protojson.Marshal(&ac)
fileJson, err := vtpm.ConvertPolicyToJSON(&ac)
if err != nil {
return errors.Wrap(errMarshalJSON, err)
}
+254 -7
View File
@@ -3,14 +3,17 @@
package cli
import (
"bytes"
"encoding/base64"
"encoding/json"
"os"
"testing"
"github.com/google/go-sev-guest/proto/check"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/encoding/protojson"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
func TestChangeAttestationConfiguration(t *testing.T) {
@@ -18,9 +21,9 @@ func TestChangeAttestationConfiguration(t *testing.T) {
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
initialConfig := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
initialConfig := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
initialJSON, err := protojson.Marshal(&initialConfig)
initialJSON, err := json.Marshal(initialConfig)
require.NoError(t, err)
err = os.WriteFile(tmpfile.Name(), initialJSON, 0o644)
require.NoError(t, err)
@@ -86,15 +89,15 @@ func TestChangeAttestationConfiguration(t *testing.T) {
content, err := os.ReadFile(tmpfile.Name())
require.NoError(t, err)
config := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
err = protojson.Unmarshal(content, &config)
ap := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
err = vtpm.ReadPolicyFromByte(content, &ap)
require.NoError(t, err)
decodedData, _ := base64.StdEncoding.DecodeString(tt.base64Data)
if tt.field == measurementField {
assert.Equal(t, decodedData, config.Policy.Measurement)
assert.Equal(t, decodedData, ap.Config.Policy.Measurement)
} else if tt.field == hostDataField {
assert.Equal(t, decodedData, config.Policy.HostData)
assert.Equal(t, decodedData, ap.Config.Policy.HostData)
}
}
})
@@ -129,3 +132,247 @@ func TestNewAddHostDataCmd(t *testing.T) {
assert.Equal(t, "hostdata <host-data> <attestation_policy.json>", cmd.Example)
assert.NotNil(t, cmd.Run)
}
func TestChangeAttestationConfigurationFileErrors(t *testing.T) {
t.Run("File Not Found", func(t *testing.T) {
err := changeAttestationConfiguration("nonexistent.json", base64.StdEncoding.EncodeToString(make([]byte, measurementLength)), measurementLength, measurementField)
assert.Error(t, err)
assert.Contains(t, err.Error(), "error while reading the attestation policy file")
})
t.Run("Invalid JSON Content", func(t *testing.T) {
tmpfile, err := os.CreateTemp("", "invalid.json")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
err = os.WriteFile(tmpfile.Name(), []byte("invalid json"), 0o644)
require.NoError(t, err)
err = changeAttestationConfiguration(tmpfile.Name(), base64.StdEncoding.EncodeToString(make([]byte, measurementLength)), measurementLength, measurementField)
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to unmarshal json")
})
}
func TestNewGCPAttestationPolicy(t *testing.T) {
cli := &CLI{}
cmd := cli.NewGCPAttestationPolicy()
assert.Equal(t, "gcp", cmd.Use)
assert.Equal(t, "Get attestation policy for GCP CVM", cmd.Short)
assert.Equal(t, "gcp <bin_vtmp_attestation_report_file> <vcpu_count>", cmd.Example)
assert.NotNil(t, cmd.Run)
t.Run("File Not Found", func(t *testing.T) {
cmd.SetArgs([]string{"nonexistent.bin", "4"})
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err := cmd.Execute()
assert.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "Error reading attestation report file")
assert.Contains(t, output, "❌")
})
t.Run("Invalid vCPU Count", func(t *testing.T) {
tmpfile, err := os.CreateTemp("", "attestation.bin")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
err = os.WriteFile(tmpfile.Name(), []byte("dummy content"), 0o644)
require.NoError(t, err)
cmd.SetArgs([]string{tmpfile.Name(), "invalid"})
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err = cmd.Execute()
assert.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "Error converting vCPU count to integer")
assert.Contains(t, output, "❌")
})
t.Run("Invalid Attestation Data", func(t *testing.T) {
tmpfile, err := os.CreateTemp("", "attestation.bin")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
err = os.WriteFile(tmpfile.Name(), []byte("invalid protobuf data"), 0o644)
require.NoError(t, err)
cmd.SetArgs([]string{tmpfile.Name(), "4"})
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err = cmd.Execute()
assert.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "Error unmarshaling attestation report")
assert.Contains(t, output, "❌")
})
}
func TestNewDownloadGCPOvmfFile(t *testing.T) {
cli := &CLI{}
cmd := cli.NewDownloadGCPOvmfFile()
assert.Equal(t, "download", cmd.Use)
assert.Equal(t, "Download GCP OVMF file", cmd.Short)
assert.Equal(t, "download <bin_vtmp_attestation_report_file>", cmd.Example)
assert.NotNil(t, cmd.Run)
t.Run("File Not Found", func(t *testing.T) {
cmd.SetArgs([]string{"nonexistent.bin"})
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err := cmd.Execute()
assert.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "Error reading attestation report file")
assert.Contains(t, output, "❌")
})
t.Run("Invalid Attestation Data", func(t *testing.T) {
tmpfile, err := os.CreateTemp("", "attestation.bin")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
err = os.WriteFile(tmpfile.Name(), []byte("invalid protobuf data"), 0o644)
require.NoError(t, err)
cmd.SetArgs([]string{tmpfile.Name()})
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err = cmd.Execute()
assert.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "Error unmarshaling attestation report")
assert.Contains(t, output, "❌")
})
}
func TestNewAzureAttestationPolicy(t *testing.T) {
cli := &CLI{}
cmd := cli.NewAzureAttestationPolicy()
assert.Equal(t, "azure", cmd.Use)
assert.Equal(t, "Get attestation policy for Azure CVM", cmd.Short)
assert.Equal(t, "azure <azure_maa_token_file> <product_name>", cmd.Example)
assert.NotNil(t, cmd.Run)
flag := cmd.Flags().Lookup("policy")
assert.NotNil(t, flag)
assert.Equal(t, "Policy of the guest CVM", flag.Usage)
t.Run("File Not Found", func(t *testing.T) {
cmd.SetArgs([]string{"nonexistent.token", "test-product"})
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err := cmd.Execute()
assert.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "Error reading attestation report file")
assert.Contains(t, output, "❌")
})
t.Run("Valid Token File", func(t *testing.T) {
tmpfile, err := os.CreateTemp("", "token.maa")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
err = os.WriteFile(tmpfile.Name(), []byte("dummy.token.content"), 0o644)
require.NoError(t, err)
defer os.Remove("attestation_policy.json")
cmd.SetArgs([]string{tmpfile.Name(), "test-product"})
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err = cmd.Execute()
assert.NoError(t, err)
})
t.Run("Custom Policy Flag", func(t *testing.T) {
tmpfile, err := os.CreateTemp("", "token.maa")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
err = os.WriteFile(tmpfile.Name(), []byte("dummy.token.content"), 0o644)
require.NoError(t, err)
cmd.SetArgs([]string{"--policy", "123456", tmpfile.Name(), "test-product"})
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err = cmd.Execute()
assert.NoError(t, err)
flag := cmd.Flags().Lookup("policy")
assert.NotNil(t, flag)
assert.Equal(t, "123456", flag.Value.String())
})
}
func TestCommandErrorHandling(t *testing.T) {
cli := &CLI{}
t.Run("Measurement Command Error", func(t *testing.T) {
cmd := cli.NewAddMeasurementCmd()
cmd.SetArgs([]string{"invalid-base64", "nonexistent.json"})
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err := cmd.Execute()
assert.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "Error could not change measurement data")
assert.Contains(t, output, "❌")
})
t.Run("Host Data Command Error", func(t *testing.T) {
cmd := cli.NewAddHostDataCmd()
cmd.SetArgs([]string{"invalid-base64", "nonexistent.json"})
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err := cmd.Execute()
assert.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "Error could not change host data")
assert.Contains(t, output, "❌")
})
}
+597
View File
@@ -0,0 +1,597 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"encoding/hex"
"fmt"
"io"
"os"
"strconv"
"strings"
"time"
"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/proto/check"
tpmAttest "github.com/google/go-tpm-tools/proto/attest"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/pkg/attestation"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/wrapperspb"
)
const (
defaultMinimumTcb = 0
defaultMinimumLaunchTcb = 0
defaultMinimumGuestSvn = 0
defaultGuestPolicy = 0x0000000000030000
defaultMinimumBuild = 0
defaultCheckCrl = false
defaultTimeout = 2 * time.Minute
defaultMaxRetryDelay = 30 * time.Second
defaultRequireAuthor = false
defaultRequireIdBlock = false
defaultMinVersion = "0.0"
vtpmFilePath = "../quote.dat"
attestationReportJson = "attestation.json"
sevSnpProductMilan = "Milan"
sevSnpProductGenoa = "Genoa"
FormatBinaryPB = "binarypb"
FormatTextProto = "textproto"
exampleJSONConfig = `
{
"rootOfTrust":{
"product":"test_product",
"cabundlePaths":[
"test_cabundlePaths"
],
"cabundles":[
"test_Cabundles"
],
"checkCrl":true,
"disallowNetwork":true
},
"policy":{
"minimumGuestSvn":1,
"policy":"1",
"familyId":"AQIDBAUGBwgJCgsMDQ4PEA==",
"imageId":"AQIDBAUGBwgJCgsMDQ4PEA==",
"vmpl":0,
"minimumTcb":"1",
"minimumLaunchTcb":"1",
"platformInfo":"1",
"requireAuthorKey":true,
"reportData":"J+60aXs8btm8VcGgaJYURGeNCu0FIyWMFXQ7ZUlJDC0FJGJizJsOzDIXgQ75UtPC+Zqe0A3dvnnf5VEeQ61RTg==",
"measurement":"8s78ewoX7Xkfy1qsgVnkZwLDotD768Nqt6qTL5wtQOxHsLczipKM6bhDmWiHLdP4",
"hostData":"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw=",
"reportId":"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw=",
"reportIdMa":"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw=",
"chipId":"J+60aXs8btm8VcGgaJYURGeNCu0FIyWMFXQ7ZUlJDC0FJGJizJsOzDIXgQ75UtPC+Zqe0A3dvnnf5VEeQ61RTg==",
"minimumBuild":1,
"minimumVersion":"0.90",
"permitProvisionalFirmware":true,
"requireIdBlock":true,
"trustedAuthorKeys":[
"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw="
],
"trustedAuthorKeyHashes":[
"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw="
],
"trustedIdKeys":[
"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw="
],
"trustedIdKeyHashes":[
"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw="
],
"product":{
"name":1,
"stepping":1,
"machineStepping":1
}
}
}
`
)
var cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
func addSEVSNPVerificationOptions(cmd *cobra.Command) *cobra.Command {
cmd.Flags().BytesHexVar(
&cfg.Policy.HostData,
"host_data",
empty32[:],
"The expected HOST_DATA field as a hex string. Must encode 32 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfg.Policy.FamilyId,
"family_id",
empty16[:],
"The expected FAMILY_ID field as a hex string. Must encode 16 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfg.Policy.ImageId,
"image_id",
empty16[:],
"The expected IMAGE_ID field as a hex string. Must encode 16 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfg.Policy.ReportId,
"report_id",
nil,
"The expected REPORT_ID field as a hex string. Must encode 32 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfg.Policy.ReportIdMa,
"report_id_ma",
defaultReportIdMa,
"The expected REPORT_ID_MA field as a hex string. Must encode 32 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfg.Policy.Measurement,
"measurement",
nil,
"The expected MEASUREMENT field as a hex string. Must encode 48 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfg.Policy.ChipId,
"chip_id",
nil,
"The expected MEASUREMENT field as a hex string. Must encode 48 bytes. Unchecked if unset.",
)
cmd.Flags().Uint64Var(
&cfg.Policy.MinimumTcb,
"minimum_tcb",
defaultMinimumTcb,
"The minimum acceptable value for CURRENT_TCB, COMMITTED_TCB, and REPORTED_TCB.",
)
cmd.Flags().Uint64Var(
&cfg.Policy.MinimumLaunchTcb,
"minimum_lauch_tcb",
defaultMinimumLaunchTcb,
"The minimum acceptable value for LAUNCH_TCB.",
)
cmd.Flags().Uint64Var(
&cfg.Policy.Policy,
"guest_policy",
defaultGuestPolicy,
"The most acceptable guest SnpPolicy.",
)
cmd.Flags().Uint32Var(
&cfg.Policy.MinimumGuestSvn,
"minimum_guest_svn",
defaultMinimumGuestSvn,
"The most acceptable GUEST_SVN.",
)
cmd.Flags().Uint32Var(
&cfg.Policy.MinimumBuild,
"minimum_build",
defaultMinimumBuild,
"The 8-bit minimum build number for AMD-SP firmware",
)
cmd.Flags().BoolVar(
&checkCrl,
"check_crl",
defaultCheckCrl,
"Download and check the CRL for revoked certificates.",
)
cmd.Flags().DurationVar(
&timeout,
"timeout",
defaultTimeout,
"Duration to continue to retry failed HTTP requests.",
)
cmd.Flags().DurationVar(
&maxRetryDelay,
"max_retry_delay",
defaultMaxRetryDelay,
"Maximum Duration to wait between HTTP request retries.",
)
cmd.Flags().BoolVar(
&cfg.Policy.RequireAuthorKey,
"require_author_key",
defaultRequireAuthor,
"Require that AUTHOR_KEY_EN is 1.",
)
cmd.Flags().BoolVar(
&cfg.Policy.RequireIdBlock,
"require_id_block",
defaultRequireIdBlock,
"Require that the VM was launch with an ID_BLOCK signed by a trusted id key or author key",
)
cmd.Flags().StringVar(
&platformInfo,
"platform_info",
"",
"The maximum acceptable PLATFORM_INFO field bit-wise. May be empty or a 64-bit unsigned integer",
)
cmd.Flags().StringVar(
&cfg.Policy.MinimumVersion,
"minimum_version",
defaultMinVersion,
"Minimum AMD-SP firmware API version (major.minor). Each number must be 8-bit non-negative.",
)
cmd.Flags().StringArrayVar(
&trustedAuthorKeys,
"trusted_author_keys",
[]string{},
"Paths to x.509 certificates of trusted author keys",
)
cmd.Flags().StringArrayVar(
&trustedAuthorHashes,
"trusted_author_key_hashes",
[]string{},
"Hex-encoded SHA-384 hash values of trusted author keys in AMD public key format",
)
cmd.Flags().StringArrayVar(
&trustedIdKeys,
"trusted_id_keys",
[]string{},
"Paths to x.509 certificates of trusted author keys",
)
cmd.Flags().StringArrayVar(
&trustedIdKeyHashes,
"trusted_id_key_hashes",
[]string{},
"Hex-encoded SHA-384 hash values of trusted identity keys in AMD public key format",
)
cmd.Flags().StringVar(
&cfg.RootOfTrust.ProductLine,
"product",
"",
"The AMD product name for the chip that generated the attestation report.",
)
cmd.Flags().StringVar(
&stepping,
"stepping",
"",
"The machine stepping for the chip that generated the attestation report. Default unchecked.",
)
cmd.Flags().StringArrayVar(
&cfg.RootOfTrust.CabundlePaths,
"CA_bundles_paths",
[]string{},
"Paths to CA bundles for the AMD product. Must be in PEM format, ASK, then ARK certificates. If unset, uses embedded root certificates.",
)
cmd.Flags().StringArrayVar(
&cfg.RootOfTrust.Cabundles,
"CA_bundles",
[]string{},
"PEM format CA bundles for the AMD product. Combined with contents of cabundle_paths.",
)
return cmd
}
func validateInput() error {
if len(cfg.RootOfTrust.CabundlePaths) != 0 || len(cfg.RootOfTrust.Cabundles) != 0 && cfg.RootOfTrust.ProductLine == "" {
return fmt.Errorf("product name must be set if CA bundles are provided")
}
if err := validateFieldLength("report_data", cfg.Policy.ReportData, size64); err != nil {
return err
}
if err := validateFieldLength("host_data", cfg.Policy.HostData, size32); err != nil {
return err
}
if err := validateFieldLength("family_id", cfg.Policy.FamilyId, size16); err != nil {
return err
}
if err := validateFieldLength("image_id", cfg.Policy.ImageId, size16); err != nil {
return err
}
if err := validateFieldLength("report_id", cfg.Policy.ReportId, size32); err != nil {
return err
}
if err := validateFieldLength("report_id_ma", cfg.Policy.ReportIdMa, size32); err != nil {
return err
}
if err := validateFieldLength("measurement", cfg.Policy.Measurement, size48); err != nil {
return err
}
if err := validateFieldLength("chip_id", cfg.Policy.ChipId, size64); err != nil {
return err
}
for _, hash := range cfg.Policy.TrustedAuthorKeyHashes {
if err := validateFieldLength("trusted_author_key_hash", hash, size48); err != nil {
return err
}
}
for _, hash := range cfg.Policy.TrustedIdKeyHashes {
if err := validateFieldLength("trusted_id_key_hash", hash, size48); err != nil {
return err
}
}
return nil
}
func parseTrustedKeys() error {
for _, path := range trustedAuthorKeys {
file, err := os.ReadFile(path)
if err != nil {
return err
}
cfg.Policy.TrustedAuthorKeys = append(cfg.Policy.TrustedAuthorKeys, file)
}
for _, path := range trustedIdKeys {
file, err := os.ReadFile(path)
if err != nil {
return err
}
cfg.Policy.TrustedIdKeys = append(cfg.Policy.TrustedIdKeys, file)
}
return nil
}
func parseUints() error {
if stepping != "" {
if base := getBase(stepping); base == 10 {
num, err := strconv.ParseUint(stepping, getBase(stepping), 8)
if err != nil {
return err
}
cfg.Policy.Product.MachineStepping = wrapperspb.UInt32(uint32(num))
} else {
num, err := strconv.ParseUint(stepping[2:], base, 8)
if err != nil {
return err
}
cfg.Policy.Product.MachineStepping = wrapperspb.UInt32(uint32(num))
}
}
if platformInfo != "" {
if base := getBase(platformInfo); base == 10 {
num, err := strconv.ParseUint(platformInfo, getBase(platformInfo), 8)
if err != nil {
return err
}
cfg.Policy.PlatformInfo = wrapperspb.UInt64(num)
} else {
num, err := strconv.ParseUint(platformInfo[2:], base, 8)
if err != nil {
return err
}
cfg.Policy.PlatformInfo = wrapperspb.UInt64(num)
}
}
return nil
}
func getBase(val string) int {
switch {
case strings.HasPrefix(val, "0x"):
return 16
case strings.HasPrefix(val, "0o"):
return 8
case strings.HasPrefix(val, "0b"):
return 2
default:
return 10
}
}
// parseConfig decodes config passed as json for check.Config struct.
// example
/* {
"rootOfTrust":{
"product":"test_product",
"cabundlePaths":[
"test_cabundlePaths"
],
"cabundles":[
"test_Cabundles"
],
"checkCrl":true,
"disallowNetwork":true
},
"policy":{
"minimumGuestSvn":1,
"policy":"1",
"familyId":"AQIDBAUGBwgJCgsMDQ4PEA==",
"imageId":"AQIDBAUGBwgJCgsMDQ4PEA==",
"vmpl":0,
"minimumTcb":"1",
"minimumLaunchTcb":"1",
"platformInfo":"1",
"requireAuthorKey":true,
"reportData":"J+60aXs8btm8VcGgaJYURGeNCu0FIyWMFXQ7ZUlJDC0FJGJizJsOzDIXgQ75UtPC+Zqe0A3dvnnf5VEeQ61RTg==",
"measurement":"8s78ewoX7Xkfy1qsgVnkZwLDotD768Nqt6qTL5wtQOxHsLczipKM6bhDmWiHLdP4",
"hostData":"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw=",
"reportId":"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw=",
"reportIdMa":"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw=",
"chipId":"J+60aXs8btm8VcGgaJYURGeNCu0FIyWMFXQ7ZUlJDC0FJGJizJsOzDIXgQ75UtPC+Zqe0A3dvnnf5VEeQ61RTg==",
"minimumBuild":1,
"minimumVersion":"0.90",
"permitProvisionalFirmware":true,
"requireIdBlock":true,
"trustedAuthorKeys":[
"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw="
],
"trustedAuthorKeyHashes":[
"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw="
],
"trustedIdKeys":[
"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw="
],
"trustedIdKeyHashes":[
"GSvLKpfu59Y9QOF6vhq0vQsOIvb4+5O/UOHLGLBTkdw="
],
"product":{
"name":"1",
"stepping":1,
"machineStepping":1
}
}
}*/
func parseConfig() error {
if cfgString == "" {
return nil
}
policyByte, err := os.ReadFile(cfgString)
if err != nil {
return err
}
if err := protojson.Unmarshal(policyByte, &cfg); err != nil {
return err
}
// Populate fields that should not be nil
if cfg.RootOfTrust == nil {
cfg.RootOfTrust = &check.RootOfTrust{}
}
if cfg.Policy == nil {
cfg.Policy = &check.Policy{}
}
return nil
}
func parseHashes() error {
for _, hash := range trustedAuthorHashes {
hashBytes, err := hex.DecodeString(hash)
if err != nil {
return err
}
cfg.Policy.TrustedAuthorKeyHashes = append(cfg.Policy.TrustedAuthorKeyHashes, hashBytes)
}
for _, hash := range trustedIdKeyHashes {
hashBytes, err := hex.DecodeString(hash)
if err != nil {
return err
}
cfg.Policy.TrustedIdKeyHashes = append(cfg.Policy.TrustedIdKeyHashes, hashBytes)
}
return nil
}
func parseAttestationFile() error {
file, err := os.ReadFile(attestationFile)
if err != nil {
return err
}
attestationRaw = file
if isFileJSON(attestationFile) {
attestationRaw, err = attesationFromJSON(attestationRaw)
if err != nil {
return err
}
}
return nil
}
func sevsnpverify(cmd *cobra.Command, verifier attestation.Verifier, args []string) error {
cmd.Println("Checking attestation")
attestationFile = string(args[0])
if err := parseAttestationFile(); err != nil {
return fmt.Errorf("error parsing config: %v ❌ ", err)
}
// This format is the attestation report in AMD's specified ABI format, immediately
// followed by the certificate table bytes.
if len(attestationRaw) < abi.ReportSize {
return fmt.Errorf("attestation too small: got 0x%x bytes, need at least 0x%x bytes", len(attestationRaw), abi.ReportSize)
}
if err := parseAttestationConfig(); err != nil {
return err
}
if err := verifier.VerifTeeAttestation(attestationRaw, cfg.Policy.ReportData); err != nil {
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
}
cmd.Println("Attestation validation and verification is successful!")
return nil
}
func parseAttestationConfig() error {
if err := parseConfig(); err != nil {
return fmt.Errorf("error parsing config: %v ❌ ", err)
}
if err := parseHashes(); err != nil {
return fmt.Errorf("error parsing hashes: %v ❌ ", err)
}
if err := parseTrustedKeys(); err != nil {
return fmt.Errorf("error parsing files: %v ❌ ", err)
}
if err := parseUints(); err != nil {
return fmt.Errorf("error parsing uints: %v ❌ ", err)
}
if err := validateInput(); err != nil {
return fmt.Errorf("error validating input: %v ❌ ", err)
}
return nil
}
func vtpmSevSnpverify(args []string, verifier attestation.Verifier) error {
attest, err := returnvTPMAttestation(args)
if err != nil {
return err
}
if err := parseAttestationConfig(); err != nil {
return err
}
if err := verifier.VerifyAttestation(attest, cfg.Policy.ReportData, nonce); err != nil {
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
}
return nil
}
func vtpmverify(args []string, verifier attestation.Verifier) error {
attestation, err := returnvTPMAttestation(args)
if err != nil {
return err
}
if err := verifier.VerifVTpmAttestation(attestation, nonce); err != nil {
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
}
return nil
}
func returnvTPMAttestation(args []string) ([]byte, error) {
attestationFile = string(args[0])
input, err := openInputFile()
if err != nil {
return nil, err
}
if closer, ok := input.(*os.File); ok {
defer closer.Close()
}
attestationBytes, err := io.ReadAll(input)
if err != nil {
return nil, err
}
attestation := &tpmAttest.Attestation{}
if format == FormatBinaryPB {
return attestationBytes, nil
} else if format == FormatTextProto {
unmarshalOptions := prototext.UnmarshalOptions{}
err = unmarshalOptions.Unmarshal(attestationBytes, attestation)
} else {
return nil, fmt.Errorf("format should be either binarypb or textproto")
}
if err != nil {
return nil, fmt.Errorf("fail to unmarshal attestation report: %v", err)
}
attestationBytes, err = proto.Marshal(attestation)
if err != nil {
return nil, fmt.Errorf("fail to marshal vTPM attestation report: %v", err)
}
return attestationBytes, nil
}
+870
View File
@@ -0,0 +1,870 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-sev-guest/proto/sevsnp"
tpmAttest "github.com/google/go-tpm-tools/proto/attest"
"github.com/google/go-tpm-tools/proto/tpm"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/ultravioletrs/cocos/pkg/attestation/mocks"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
)
func TestAddSEVSNPVerificationOptions(t *testing.T) {
cmd := &cobra.Command{
Use: "test",
}
result := addSEVSNPVerificationOptions(cmd)
assert.Equal(t, cmd, result)
// Check that important flags are added
flags := []string{
"host_data",
"family_id",
"image_id",
"report_id",
"report_id_ma",
"measurement",
"chip_id",
"minimum_tcb",
"minimum_lauch_tcb",
"guest_policy",
"minimum_guest_svn",
"minimum_build",
"check_crl",
"timeout",
"max_retry_delay",
"require_author_key",
"require_id_block",
"platform_info",
"minimum_version",
"trusted_author_keys",
"trusted_author_key_hashes",
"trusted_id_keys",
"trusted_id_key_hashes",
"product",
"stepping",
"CA_bundles_paths",
"CA_bundles",
}
for _, flagName := range flags {
flag := cmd.Flags().Lookup(flagName)
assert.NotNil(t, flag, "Flag %s should exist", flagName)
}
}
func TestValidateInput(t *testing.T) {
tests := []struct {
name string
setupCfg func()
expectErr bool
errMsg string
}{
{
name: "valid empty config",
setupCfg: func() {
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
},
expectErr: false,
},
{
name: "CA bundles without product name",
setupCfg: func() {
cfg = check.Config{
Policy: &check.Policy{},
RootOfTrust: &check.RootOfTrust{
CabundlePaths: []string{"test.pem"},
ProductLine: "",
},
}
},
expectErr: true,
errMsg: "product name must be set if CA bundles are provided",
},
{
name: "invalid report_data length",
setupCfg: func() {
cfg = check.Config{
Policy: &check.Policy{
ReportData: []byte("invalid"),
},
RootOfTrust: &check.RootOfTrust{},
}
},
expectErr: true,
errMsg: "report_data",
},
{
name: "invalid host_data length",
setupCfg: func() {
cfg = check.Config{
Policy: &check.Policy{
HostData: []byte("invalid"),
},
RootOfTrust: &check.RootOfTrust{},
}
},
expectErr: true,
errMsg: "host_data",
},
{
name: "invalid family_id length",
setupCfg: func() {
cfg = check.Config{
Policy: &check.Policy{
FamilyId: []byte("invalid"),
},
RootOfTrust: &check.RootOfTrust{},
}
},
expectErr: true,
errMsg: "family_id",
},
{
name: "invalid image_id length",
setupCfg: func() {
cfg = check.Config{
Policy: &check.Policy{
ImageId: []byte("invalid"),
},
RootOfTrust: &check.RootOfTrust{},
}
},
expectErr: true,
errMsg: "image_id",
},
{
name: "invalid trusted author key hash",
setupCfg: func() {
cfg = check.Config{
Policy: &check.Policy{
TrustedAuthorKeyHashes: [][]byte{[]byte("invalid")},
},
RootOfTrust: &check.RootOfTrust{},
}
},
expectErr: true,
errMsg: "trusted_author_key_hash",
},
{
name: "invalid trusted id key hash",
setupCfg: func() {
cfg = check.Config{
Policy: &check.Policy{
TrustedIdKeyHashes: [][]byte{[]byte("invalid")},
},
RootOfTrust: &check.RootOfTrust{},
}
},
expectErr: true,
errMsg: "trusted_id_key_hash",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setupCfg()
err := validateInput()
if tt.expectErr {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.errMsg)
} else {
assert.NoError(t, err)
}
})
}
}
func TestParseTrustedKeys(t *testing.T) {
tempDir := t.TempDir()
authorKeyFile := filepath.Join(tempDir, "author.pem")
idKeyFile := filepath.Join(tempDir, "id.pem")
nonExistentFile := filepath.Join(tempDir, "nonexistent.pem")
authorKeyContent := "-----BEGIN CERTIFICATE-----\nMIIBkTCB+wIJAOI..."
idKeyContent := "-----BEGIN CERTIFICATE-----\nMIIBkTCB+wIJAOI..."
require.NoError(t, os.WriteFile(authorKeyFile, []byte(authorKeyContent), 0o644))
require.NoError(t, os.WriteFile(idKeyFile, []byte(idKeyContent), 0o644))
tests := []struct {
name string
trustedAuthorKeys []string
trustedIdKeys []string
expectErr bool
}{
{
name: "valid files",
trustedAuthorKeys: []string{authorKeyFile},
trustedIdKeys: []string{idKeyFile},
expectErr: false,
},
{
name: "nonexistent author key file",
trustedAuthorKeys: []string{nonExistentFile},
trustedIdKeys: []string{},
expectErr: true,
},
{
name: "nonexistent id key file",
trustedAuthorKeys: []string{},
trustedIdKeys: []string{nonExistentFile},
expectErr: true,
},
{
name: "empty file lists",
trustedAuthorKeys: []string{},
trustedIdKeys: []string{},
expectErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
trustedAuthorKeys = tt.trustedAuthorKeys
trustedIdKeys = tt.trustedIdKeys
err := parseTrustedKeys()
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
if len(tt.trustedAuthorKeys) > 0 {
assert.Len(t, cfg.Policy.TrustedAuthorKeys, len(tt.trustedAuthorKeys))
assert.Equal(t, []byte(authorKeyContent), cfg.Policy.TrustedAuthorKeys[0])
}
if len(tt.trustedIdKeys) > 0 {
assert.Len(t, cfg.Policy.TrustedIdKeys, len(tt.trustedIdKeys))
assert.Equal(t, []byte(idKeyContent), cfg.Policy.TrustedIdKeys[0])
}
}
})
}
}
func TestParseUints(t *testing.T) {
tests := []struct {
name string
stepping string
platformInfo string
expectErr bool
expectedStep *uint32
expectedPlatform *uint64
}{
{
name: "empty values",
stepping: "",
platformInfo: "",
expectErr: false,
},
{
name: "decimal values",
stepping: "5",
platformInfo: "10",
expectErr: false,
expectedStep: uint32Ptr(5),
expectedPlatform: uint64Ptr(10),
},
{
name: "hex values",
stepping: "0x5",
platformInfo: "0xa",
expectErr: false,
expectedStep: uint32Ptr(5),
expectedPlatform: uint64Ptr(10),
},
{
name: "octal values",
stepping: "0o7",
platformInfo: "0o12",
expectErr: false,
expectedStep: uint32Ptr(7),
expectedPlatform: uint64Ptr(10),
},
{
name: "binary values",
stepping: "0b101",
platformInfo: "0b1010",
expectErr: false,
expectedStep: uint32Ptr(5),
expectedPlatform: uint64Ptr(10),
},
{
name: "invalid stepping",
stepping: "invalid",
platformInfo: "",
expectErr: true,
},
{
name: "invalid platform info",
stepping: "",
platformInfo: "invalid",
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg = check.Config{Policy: &check.Policy{Product: &sevsnp.SevProduct{}}, RootOfTrust: &check.RootOfTrust{}}
stepping = tt.stepping
platformInfo = tt.platformInfo
err := parseUints()
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
if tt.expectedStep != nil {
assert.Equal(t, *tt.expectedStep, cfg.Policy.Product.MachineStepping.Value)
}
if tt.expectedPlatform != nil {
assert.Equal(t, *tt.expectedPlatform, cfg.Policy.PlatformInfo.Value)
}
}
})
}
}
func TestGetBase(t *testing.T) {
tests := []struct {
input string
expected int
}{
{"0x10", 16},
{"0o10", 8},
{"0b10", 2},
{"10", 10},
{"", 10},
{"abc", 10},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := getBase(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestParseConfig(t *testing.T) {
tempDir := t.TempDir()
validConfig := map[string]interface{}{
"rootOfTrust": map[string]interface{}{
"product": "test_product",
"cabundlePaths": []string{"test_path"},
"cabundles": []string{"test_bundle"},
"checkCrl": true,
"disallowNetwork": true,
},
"policy": map[string]interface{}{
"minimumGuestSvn": 1,
"policy": "1",
"minimumBuild": 1,
"minimumVersion": "0.90",
"requireAuthorKey": true,
"requireIdBlock": true,
},
}
tests := []struct {
name string
setupConfig func() string
expectErr bool
}{
{
name: "empty config string",
setupConfig: func() string {
return ""
},
expectErr: false,
},
{
name: "valid config file",
setupConfig: func() string {
configFile := filepath.Join(tempDir, "valid_config.json")
configBytes, err := json.Marshal(validConfig)
assert.NoError(t, err)
if err := os.WriteFile(configFile, configBytes, 0o644); err != nil {
t.Errorf("failed to write config file: %v", err)
}
return configFile
},
expectErr: false,
},
{
name: "nonexistent config file",
setupConfig: func() string {
return filepath.Join(tempDir, "nonexistent.json")
},
expectErr: true,
},
{
name: "invalid JSON config",
setupConfig: func() string {
configFile := filepath.Join(tempDir, "invalid_config.json")
if err := os.WriteFile(configFile, []byte("invalid json"), 0o644); err != nil {
t.Errorf("failed to write invalid config file: %v", err)
}
return configFile
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
cfgString = tt.setupConfig()
err := parseConfig()
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.NotNil(t, cfg.Policy)
assert.NotNil(t, cfg.RootOfTrust)
}
})
}
}
func TestParseHashes(t *testing.T) {
tests := []struct {
name string
trustedAuthorHashes []string
trustedIdKeyHashes []string
expectErr bool
}{
{
name: "valid hashes",
trustedAuthorHashes: []string{"deadbeef", "cafebabe"},
trustedIdKeyHashes: []string{"12345678", "87654321"},
expectErr: false,
},
{
name: "empty hashes",
trustedAuthorHashes: []string{},
trustedIdKeyHashes: []string{},
expectErr: false,
},
{
name: "invalid author hash",
trustedAuthorHashes: []string{"invalid_hex"},
trustedIdKeyHashes: []string{},
expectErr: true,
},
{
name: "invalid id key hash",
trustedAuthorHashes: []string{},
trustedIdKeyHashes: []string{"invalid_hex"},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
trustedAuthorHashes = tt.trustedAuthorHashes
trustedIdKeyHashes = tt.trustedIdKeyHashes
err := parseHashes()
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Len(t, cfg.Policy.TrustedAuthorKeyHashes, len(tt.trustedAuthorHashes))
assert.Len(t, cfg.Policy.TrustedIdKeyHashes, len(tt.trustedIdKeyHashes))
for i, hash := range tt.trustedAuthorHashes {
expected, _ := hex.DecodeString(hash)
assert.Equal(t, expected, cfg.Policy.TrustedAuthorKeyHashes[i])
}
for i, hash := range tt.trustedIdKeyHashes {
expected, _ := hex.DecodeString(hash)
assert.Equal(t, expected, cfg.Policy.TrustedIdKeyHashes[i])
}
}
})
}
}
func TestParseAttestationFile(t *testing.T) {
tempDir := t.TempDir()
binaryFile := filepath.Join(tempDir, "attestation.bin")
jsonFile := filepath.Join(tempDir, "attestation.json")
binaryData := make([]byte, 1024)
for i := range binaryData {
binaryData[i] = byte(i % 256)
}
jsonData := &sevsnp.Attestation{
Report: &sevsnp.Report{
FamilyId: make([]byte, 16),
ImageId: make([]byte, 16),
ReportData: make([]byte, 64),
Measurement: make([]byte, 48),
HostData: make([]byte, 32),
IdKeyDigest: make([]byte, 48),
AuthorKeyDigest: make([]byte, 48),
ReportId: make([]byte, 32),
ReportIdMa: make([]byte, 32),
ChipId: make([]byte, 64),
Signature: make([]byte, 512),
},
}
jsonBytes, err := json.Marshal(jsonData)
require.NoError(t, err)
require.NoError(t, os.WriteFile(binaryFile, binaryData, 0o644))
require.NoError(t, os.WriteFile(jsonFile, jsonBytes, 0o644))
tests := []struct {
name string
attestationFile string
expectErr bool
}{
{
name: "valid binary file",
attestationFile: binaryFile,
expectErr: false,
},
{
name: "valid JSON file",
attestationFile: jsonFile,
expectErr: false,
},
{
name: "nonexistent file",
attestationFile: filepath.Join(tempDir, "nonexistent.bin"),
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
attestationFile = tt.attestationFile
err := parseAttestationFile()
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.NotNil(t, attestationRaw)
assert.NotEmpty(t, attestationRaw)
}
})
}
}
func TestSevsnpverify(t *testing.T) {
trustedAuthorHashes = []string{}
trustedIdKeyHashes = []string{}
stepping = ""
platformInfo = ""
tempDir := t.TempDir()
cfg = check.Config{Policy: &check.Policy{Product: &sevsnp.SevProduct{}}, RootOfTrust: &check.RootOfTrust{}}
attestationFile := filepath.Join(tempDir, "attestation.bin")
attestationData := make([]byte, abi.ReportSize+100)
for i := range attestationData {
attestationData[i] = byte(i % 256)
}
require.NoError(t, os.WriteFile(attestationFile, attestationData, 0o644))
tests := []struct {
name string
args []string
setupMock func(*mocks.Verifier)
expectErr bool
expectedMsg string
}{
{
name: "successful verification",
args: []string{attestationFile},
setupMock: func(m *mocks.Verifier) {
m.On("VerifTeeAttestation", mock.Anything, mock.Anything).Return(nil)
},
expectErr: false,
expectedMsg: "Attestation validation and verification is successful!",
},
{
name: "verification failure",
args: []string{attestationFile},
setupMock: func(m *mocks.Verifier) {
m.On("VerifTeeAttestation", mock.Anything, mock.Anything).Return(fmt.Errorf("verification failed"))
},
expectErr: true,
expectedMsg: "attestation validation and verification failed",
},
{
name: "nonexistent file",
args: []string{filepath.Join(tempDir, "nonexistent.bin")},
setupMock: func(m *mocks.Verifier) {},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfgString = ""
mockVerifier := new(mocks.Verifier)
tt.setupMock(mockVerifier)
var output bytes.Buffer
cmd := &cobra.Command{}
cmd.SetOut(&output)
err := sevsnpverify(cmd, mockVerifier, tt.args)
fmt.Println("error1", err)
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
if tt.expectedMsg != "" {
assert.Contains(t, output.String(), tt.expectedMsg)
}
}
mockVerifier.AssertExpectations(t)
})
}
}
func TestReturnvTPMAttestation(t *testing.T) {
tempDir := t.TempDir()
attestation := &tpmAttest.Attestation{
Quotes: []*tpm.Quote{
{
Quote: []byte("test quote"),
RawSig: []byte("test signature"),
},
},
}
binaryData, err := proto.Marshal(attestation)
require.NoError(t, err)
binaryFile := filepath.Join(tempDir, "attestation.pb")
require.NoError(t, os.WriteFile(binaryFile, binaryData, 0o644))
textData, err := prototext.Marshal(attestation)
require.NoError(t, err)
textFile := filepath.Join(tempDir, "attestation.txtpb")
require.NoError(t, os.WriteFile(textFile, textData, 0o644))
tests := []struct {
name string
args []string
format string
expectErr bool
}{
{
name: "binary protobuf format",
args: []string{binaryFile},
format: FormatBinaryPB,
expectErr: false,
},
{
name: "text protobuf format",
args: []string{textFile},
format: FormatTextProto,
expectErr: false,
},
{
name: "invalid format",
args: []string{binaryFile},
format: "invalid",
expectErr: true,
},
{
name: "nonexistent file",
args: []string{filepath.Join(tempDir, "nonexistent.pb")},
format: FormatBinaryPB,
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
format = tt.format
result, err := returnvTPMAttestation(tt.args)
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.NotNil(t, result)
assert.NotEmpty(t, result)
}
})
}
}
func TestVtpmSevSnpverify(t *testing.T) {
stepping = ""
platformInfo = ""
trustedAuthorHashes = []string{}
trustedIdKeyHashes = []string{}
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
tempDir := t.TempDir()
attestation := &tpmAttest.Attestation{
Quotes: []*tpm.Quote{
{
Quote: []byte("test quote"),
RawSig: []byte("test signature"),
},
},
}
binaryData, err := proto.Marshal(attestation)
require.NoError(t, err)
attestationFile := filepath.Join(tempDir, "vtpm_attestation.pb")
require.NoError(t, os.WriteFile(attestationFile, binaryData, 0o644))
tests := []struct {
name string
args []string
setupMock func(*mocks.Verifier)
expectErr bool
}{
{
name: "successful verification",
args: []string{attestationFile},
setupMock: func(m *mocks.Verifier) {
m.On("VerifyAttestation", mock.Anything, mock.Anything, mock.Anything).Return(nil)
},
expectErr: false,
},
{
name: "verification failure",
args: []string{attestationFile},
setupMock: func(m *mocks.Verifier) {
m.On("VerifyAttestation", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("verification failed"))
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
cfgString = ""
format = FormatBinaryPB
mockVerifier := new(mocks.Verifier)
tt.setupMock(mockVerifier)
err := vtpmSevSnpverify(tt.args, mockVerifier)
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
mockVerifier.AssertExpectations(t)
})
}
}
func TestVtpmverify(t *testing.T) {
tempDir := t.TempDir()
attestation := &tpmAttest.Attestation{
Quotes: []*tpm.Quote{
{
Quote: []byte("test quote"),
RawSig: []byte("test signature"),
},
},
}
binaryData, err := proto.Marshal(attestation)
require.NoError(t, err)
attestationFile := filepath.Join(tempDir, "vtpm_attestation.pb")
require.NoError(t, os.WriteFile(attestationFile, binaryData, 0o644))
tests := []struct {
name string
args []string
setupMock func(*mocks.Verifier)
expectErr bool
}{
{
name: "successful verification",
args: []string{attestationFile},
setupMock: func(m *mocks.Verifier) {
m.On("VerifVTpmAttestation", mock.Anything, mock.Anything).Return(nil)
},
expectErr: false,
},
{
name: "verification failure",
args: []string{attestationFile},
setupMock: func(m *mocks.Verifier) {
m.On("VerifVTpmAttestation", mock.Anything, mock.Anything).Return(fmt.Errorf("verification failed"))
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
format = FormatBinaryPB
mockVerifier := new(mocks.Verifier)
tt.setupMock(mockVerifier)
err := vtpmverify(tt.args, mockVerifier)
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
mockVerifier.AssertExpectations(t)
})
}
}
func uint32Ptr(v uint32) *uint32 {
return &v
}
func uint64Ptr(v uint64) *uint64 {
return &v
}
+258
View File
@@ -0,0 +1,258 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"encoding/hex"
"fmt"
"io"
"os"
"strings"
"github.com/absmach/magistrala/pkg/errors"
ccpb "github.com/google/go-tdx-guest/proto/checkconfig"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/pkg/attestation"
"google.golang.org/protobuf/encoding/protojson"
)
var (
cfgTDX = &ccpb.Config{
RootOfTrust: &ccpb.RootOfTrust{},
Policy: &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}},
}
rtmrsS string
trustedRootS string
errNumberRtmrs = fmt.Errorf("expected 4 RTMRS values")
errDecodeRtmrs = fmt.Errorf("failed to decode RTMRS hex string")
errTrustedRootPath = fmt.Errorf("trusted root path must be a file, not a directory")
errNotAFile = fmt.Errorf("trusted root path must be a file")
)
func addTDXVerificationOptions(cmd *cobra.Command) *cobra.Command {
cmd.Flags().BytesHexVar(
&cfgTDX.Policy.HeaderPolicy.QeVendorId,
"qe_vendor_id",
[]byte{},
"The expected QE_VENDOR_ID field as a hex string. Must encode 16 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfgTDX.Policy.TdQuoteBodyPolicy.MrSeam,
"mr_seam",
[]byte{},
"The expected MR_SEAM field as a hex string. Must encode 48 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfgTDX.Policy.TdQuoteBodyPolicy.TdAttributes,
"td_attributes",
[]byte{},
"The expected TD_ATTRIBUTES field as a hex string. Must encode 8 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfgTDX.Policy.TdQuoteBodyPolicy.Xfam,
"xfam",
[]byte{},
"The expected XFAM field as a hex string. Must encode 8 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfgTDX.Policy.TdQuoteBodyPolicy.MrTd,
"mr_td",
[]byte{},
"The expected MR_TD field as a hex string. Must encode 48 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfgTDX.Policy.TdQuoteBodyPolicy.MrConfigId,
"mr_config_id",
[]byte{},
"The expected MR_CONFIG_ID field as a hex string. Must encode 48 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfgTDX.Policy.TdQuoteBodyPolicy.MrOwnerConfig,
"mr_owner",
[]byte{},
"The expected MR_OWNER field as a hex string. Must encode 48 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfgTDX.Policy.TdQuoteBodyPolicy.MrOwnerConfig,
"mr_config_owner",
[]byte{},
"The expected MR_OWNER_CONFIG field as a hex string. Must encode 48 bytes. Unchecked if unset.",
)
cmd.Flags().BytesHexVar(
&cfgTDX.Policy.TdQuoteBodyPolicy.MinimumTeeTcbSvn,
"minimum_tee_tcb_svn",
[]byte{},
"The minimum acceptable value for TEE_TCB_SVN field as a hex string. Must encode 16 bytes. Unchecked if unset.",
)
cmd.Flags().StringVar(
&rtmrsS,
"rtmrs",
"",
"Comma-separated hex strings representing expected values of RTMRS field. Expected 4 strings, either empty or each must encode 48 bytes. Unchecked if unset",
)
cmd.Flags().StringVar(
&trustedRootS,
"trusted_root",
"",
"Comma-separated paths to CA bundles for the Intel TDX. Must be in PEM format, Root CA certificate. If unset, uses embedded root certificate.",
)
cmd.Flags().Uint32Var(
&cfgTDX.Policy.HeaderPolicy.MinimumQeSvn,
"minimum_qe_svn",
0,
"The minimum acceptable value for QE_SVN field.",
)
cmd.Flags().Uint32Var(
&cfgTDX.Policy.HeaderPolicy.MinimumPceSvn,
"minimum_pce_svn",
0,
"The minimum acceptable value for PCE_SVN field.",
)
cmd.Flags().BoolVar(
&cfgTDX.RootOfTrust.GetCollateral,
"get_collateral",
false,
"If true, then permitted to download necessary collaterals for additional checks.",
)
return cmd
}
func parseRtmrs() ([][]byte, error) {
if rtmrsS == "" {
return nil, nil // No RTMRS provided, return nil
}
hexString := strings.Split(rtmrsS, ",")
if len(hexString) != 4 {
return nil, errNumberRtmrs
}
var result [][]byte
for _, hexStr := range hexString {
h, err := hex.DecodeString(strings.TrimSpace(hexStr))
if err != nil {
return nil, errors.Wrap(errDecodeRtmrs, err)
}
result = append(result, h)
}
return result, nil
}
func parseTrustedRoot() ([]string, error) {
if trustedRootS == "" {
return nil, nil // No trusted roots provided, return nil
}
roots := strings.Split(trustedRootS, ",")
var result []string
for _, root := range roots {
p := strings.TrimSpace(root)
state, err := os.Stat(p)
if err != nil {
return nil, errors.Wrap(errTrustedRootPath, err)
}
if state.IsDir() {
return nil, errNotAFile
}
result = append(result, p)
}
return result, nil
}
func parseTDXConfig() error {
if cfgString == "" {
return nil // No config provided, return nil
}
policyByte, err := os.ReadFile(cfgString)
if err != nil {
return err
}
if err := protojson.Unmarshal(policyByte, cfgTDX); err != nil {
return err
}
return nil
}
func validateTDXFlags() error {
if err := parseTDXConfig(); err != nil {
return err
}
rtrms, err := parseRtmrs()
if err != nil {
return err
}
if rtrms != nil {
cfgTDX.Policy.TdQuoteBodyPolicy.Rtmrs = rtrms
}
trustedRoots, err := parseTrustedRoot()
if err != nil {
return err
}
if trustedRoots != nil {
cfgTDX.RootOfTrust.CabundlePaths = trustedRoots
}
if err := validateTDXinput(); err != nil {
return err
}
return nil
}
func tdxVerify(reportFilePath string, verifier attestation.Verifier) error {
attestationFile = reportFilePath
input, err := openInputFile()
if err != nil {
return err
}
if closer, ok := input.(*os.File); ok {
defer closer.Close()
}
attestationBytes, err := io.ReadAll(input)
if err != nil {
return err
}
return verifier.VerifyAttestation(attestationBytes, reportData, nil)
}
func validateTDXinput() error {
if err := validateFieldLength("qe_vendor_id", cfgTDX.Policy.HeaderPolicy.QeVendorId, size16); err != nil {
return err
}
if err := validateFieldLength("mr_seam", cfgTDX.Policy.TdQuoteBodyPolicy.MrSeam, size48); err != nil {
return err
}
if err := validateFieldLength("td_attributes", cfgTDX.Policy.TdQuoteBodyPolicy.TdAttributes, size8); err != nil {
return err
}
if err := validateFieldLength("xfam", cfgTDX.Policy.TdQuoteBodyPolicy.Xfam, size8); err != nil {
return err
}
if err := validateFieldLength("mr_td", cfgTDX.Policy.TdQuoteBodyPolicy.MrTd, size48); err != nil {
return err
}
if err := validateFieldLength("mr_config_id", cfgTDX.Policy.TdQuoteBodyPolicy.MrConfigId, size48); err != nil {
return err
}
if err := validateFieldLength("mr_owner", cfgTDX.Policy.TdQuoteBodyPolicy.MrOwnerConfig, size48); err != nil {
return err
}
if err := validateFieldLength("mr_config_owner", cfgTDX.Policy.TdQuoteBodyPolicy.MrOwnerConfig, size48); err != nil {
return err
}
if err := validateFieldLength("minimum_tee_tcb_svn", cfgTDX.Policy.TdQuoteBodyPolicy.MinimumTeeTcbSvn, size16); err != nil {
return err
}
return nil
}
+979 -40
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -12,7 +12,8 @@ import (
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-sev-guest/verify/trust"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
const (
@@ -27,14 +28,14 @@ func (cli *CLI) NewCABundleCmd(fileSavePath string) *cobra.Command {
Example: "ca-bundle <path_to_platform_info_json>",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
attestationConfiguration := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
err := grpc.ReadAttestationPolicy(args[0], &attestationConfiguration)
attestationConfiguration := attestation.Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &attestation.PcrConfig{}}
err := vtpm.ReadPolicy(args[0], &attestationConfiguration)
if err != nil {
printError(cmd, "Error while reading manifest: %v ❌ ", err)
return
}
product := attestationConfiguration.RootOfTrust.ProductLine
product := attestationConfiguration.Config.RootOfTrust.ProductLine
getter := trust.DefaultHTTPSGetter()
caURL := kds.ProductCertChainURL(abi.VcekReportSigner, product)
+1 -1
View File
@@ -131,7 +131,7 @@ func TestManifestChecksum(t *testing.T) {
"name": "Example Computation",
"description": "This is an example computation"
}`,
expectedSum: "868825367c32c4b6d621d5d95e2890f233d8554df2348ab743aac2663a936f08",
expectedSum: "a99683e4d22ba54cefa51aa49fb2e97a92b828c088395992ddff16a6236f3299",
},
{
name: "Invalid JSON",
+96
View File
@@ -0,0 +1,96 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"bufio"
"crypto/sha1"
"encoding/hex"
"os"
"strings"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
const (
imaMeasurementsFilename = "ima_measurements"
)
func (cli *CLI) NewIMAMeasurementsCmd() *cobra.Command {
return &cobra.Command{
Use: "ima-measurements",
Short: "Retrieve Linux IMA measurements file",
Example: "ima-measurements <optional_file_name>",
Run: func(cmd *cobra.Command, args []string) {
if cli.connectErr != nil {
printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
return
}
cmd.Println("⏳ Retrieving computation Linux IMA measurements file")
filename := imaMeasurementsFilename
if len(args) >= 1 {
filename = args[0]
}
imaMeasurementsFile, err := os.Create(filename)
if err != nil {
printError(cmd, "Error creating imaMeasurements file: %v ❌ ", err)
return
}
defer imaMeasurementsFile.Close()
pcr10, err := cli.agentSDK.IMAMeasurements(cmd.Context(), imaMeasurementsFile)
if err != nil {
printError(cmd, "Error retrieving Linux IMA measurements file: %v ❌ ", err)
return
}
cmd.Println(color.New(color.FgGreen).Sprintf("Linux IMA measurements file retrieved and saved successfully as %s! PCR10 = %s ✔ ", filename, hex.EncodeToString(pcr10)))
calculatedPCR10 := make([]byte, vtpm.Hash1)
file, err := os.Open(filename)
if err != nil {
printError(cmd, "Failed to open file: %v ❌ ", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if parts[0] != "10" {
continue
}
digestHex := parts[1]
if digestHex == strings.Repeat("0", 40) {
digestHex = strings.Repeat("f", 40)
}
digest, err := hex.DecodeString(digestHex)
if err != nil {
printError(cmd, "Failed to decode digest: %v ❌ ", err)
continue
}
hasher := sha1.New()
hasher.Write(calculatedPCR10)
hasher.Write(digest)
calculatedPCR10 = hasher.Sum(nil)
}
if hex.EncodeToString(pcr10) != hex.EncodeToString(calculatedPCR10) {
printError(cmd, "Measurements file not verified ❌ ", err)
} else {
cmd.Println(color.New(color.FgGreen).Sprintf("Measurements file verified!"))
}
},
}
}
+169
View File
@@ -0,0 +1,169 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"bytes"
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/pkg/sdk/mocks"
)
func TestCLI_NewIMAMeasurementsCmd(t *testing.T) {
testCases := []struct {
name string
args []string
connectErr error
mockIMAData string
mockError error
expectedFilename string
expectedOutput []string
expectedError []string
shouldCreateFile bool
fileCreationError bool
invalidDigestData bool
setupCustomFile func(filename string) error
}{
{
name: "successful_retrieval_default_filename",
args: []string{},
connectErr: nil,
mockIMAData: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
mockError: nil,
expectedFilename: imaMeasurementsFilename,
expectedOutput: []string{"⏳ Retrieving computation Linux IMA measurements file", "Linux IMA measurements file retrieved and saved successfully", "PCR10 = 0000000000000000000000000000000000000000", "Measurements file verified!"},
shouldCreateFile: true,
},
{
name: "successful_retrieval_custom_filename",
args: []string{"custom_ima_file.txt"},
connectErr: nil,
mockIMAData: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
mockError: nil,
expectedFilename: "custom_ima_file.txt",
expectedOutput: []string{"⏳ Retrieving computation Linux IMA measurements file", "custom_ima_file.txt", "Measurements file verified!"},
shouldCreateFile: true,
},
{
name: "connection_error",
args: []string{},
connectErr: fmt.Errorf("connection failed"),
expectedError: []string{"Failed to connect to agent: connection failed ❌"},
},
{
name: "file_creation_error",
args: []string{"/invalid/path/file.txt"},
connectErr: nil,
fileCreationError: true,
expectedError: []string{"Error creating imaMeasurements file:"},
},
{
name: "sdk_error",
args: []string{},
connectErr: nil,
mockError: fmt.Errorf("SDK communication failed"),
expectedError: []string{"Error retrieving Linux IMA measurements file: SDK communication failed ❌"},
},
{
name: "verification_failure_wrong_pcr",
args: []string{},
connectErr: nil,
mockIMAData: "10 9999999999999999999999999999999999999999 ima-ng sha1:0000000000000000000000000000000000000000 /usr/bin/test",
mockError: nil,
expectedOutput: []string{"⏳ Retrieving computation Linux IMA measurements file", "Linux IMA measurements file retrieved and saved successfully"},
expectedError: []string{"Measurements file not verified ❌"},
shouldCreateFile: true,
},
{
name: "empty_measurements_file",
args: []string{},
connectErr: nil,
mockIMAData: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
mockError: nil,
expectedOutput: []string{"⏳ Retrieving computation Linux IMA measurements file", "Linux IMA measurements file retrieved and saved successfully", "Measurements file verified!"},
shouldCreateFile: true,
},
{
name: "measurements_with_non_pcr10_entries",
args: []string{},
connectErr: nil,
mockIMAData: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
mockError: nil,
expectedOutput: []string{"⏳ Retrieving computation Linux IMA measurements file", "Linux IMA measurements file retrieved and saved successfully", "Measurements file verified!"},
shouldCreateFile: true,
},
{
name: "measurements_with_zero_digest_replacement",
args: []string{},
connectErr: nil,
mockIMAData: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
mockError: nil,
expectedOutput: []string{"⏳ Retrieving computation Linux IMA measurements file", "Linux IMA measurements file retrieved and saved successfully", "Measurements file verified!"},
shouldCreateFile: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mockSDK := new(mocks.SDK)
cli := &CLI{
agentSDK: mockSDK,
connectErr: tc.connectErr,
}
if tc.connectErr == nil && !tc.fileCreationError {
mockSDK.On("IMAMeasurements", mock.Anything, mock.Anything).Return([]byte(tc.mockIMAData), tc.mockError)
}
cmd := cli.NewIMAMeasurementsCmd()
var output bytes.Buffer
cmd.SetOut(&output)
cmd.SetErr(&output)
expectedFilename := tc.expectedFilename
if expectedFilename == "" {
if len(tc.args) > 0 {
expectedFilename = tc.args[0]
} else {
expectedFilename = imaMeasurementsFilename
}
}
if tc.setupCustomFile != nil {
err := tc.setupCustomFile(expectedFilename)
assert.NoError(t, err)
}
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err, "Command execution failed")
outputStr := output.String()
for _, expectedMsg := range tc.expectedOutput {
assert.Contains(t, outputStr, expectedMsg, "Expected output message not found")
}
for _, expectedErr := range tc.expectedError {
assert.Contains(t, outputStr, expectedErr, "Expected error message not found")
}
if tc.shouldCreateFile && tc.connectErr == nil && !tc.fileCreationError && tc.mockError == nil {
if _, err := os.Stat(expectedFilename); err == nil {
os.Remove(expectedFilename)
}
}
if tc.connectErr == nil && !tc.fileCreationError {
mockSDK.AssertExpectations(t)
}
})
}
}
+150
View File
@@ -0,0 +1,150 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"os"
"time"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/manager"
)
const (
serverURL = "server-url"
serverCA = "server-ca"
clientKey = "client-key"
clientCrt = "client-crt"
caUrl = "ca-url"
logLevel = "log-level"
ttlFlag = "ttl"
)
var (
agentCVMServerUrl string
agentCVMServerCA string
agentCVMClientKey string
agentCVMClientCrt string
agentCVMCaUrl string
agentLogLevel string
ttl time.Duration
)
func (c *CLI) NewCreateVMCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create-vm",
Short: "Create a new virtual machine",
Example: `create-vm`,
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
if c.managerClient == nil || c.connectErr != nil {
if err := c.InitializeManagerClient(cmd); err != nil {
printError(cmd, "Failed to connect to manager: %v ❌ ", c.connectErr)
return
}
}
defer c.Close()
createReq, err := loadCerts()
if err != nil {
printError(cmd, "Error loading certs: %v ❌ ", err)
return
}
createReq.AgentCvmServerUrl = agentCVMServerUrl
createReq.AgentLogLevel = agentLogLevel
createReq.AgentCvmCaUrl = agentCVMCaUrl
if ttl > 0 {
createReq.Ttl = ttl.String()
}
cmd.Println("🔗 Creating a new virtual machine")
res, err := c.managerClient.CreateVm(cmd.Context(), createReq)
if err != nil {
printError(cmd, "Error creating virtual machine: %v ❌ ", err)
return
}
cmd.Println(color.New(color.FgGreen).Sprintf("✅ Virtual machine created successfully with id %s and port %s", res.CvmId, res.ForwardedPort))
},
}
cmd.Flags().StringVar(&agentCVMServerUrl, serverURL, "", "CVM server URL")
cmd.Flags().StringVar(&agentCVMServerCA, serverCA, "", "CVM server CA")
cmd.Flags().StringVar(&agentCVMClientKey, clientKey, "", "CVM client key")
cmd.Flags().StringVar(&agentCVMClientCrt, clientCrt, "", "CVM client crt")
cmd.Flags().StringVar(&agentCVMCaUrl, caUrl, "", "CVM CA service URL")
cmd.Flags().StringVar(&agentLogLevel, logLevel, "", "Agent Log level")
cmd.Flags().DurationVar(&ttl, ttlFlag, 0, "TTL for the VM")
if err := cmd.MarkFlagRequired(serverURL); err != nil {
printError(cmd, "Error marking flag as required: %v ❌ ", err)
return cmd
}
return cmd
}
func (c *CLI) NewRemoveVMCmd() *cobra.Command {
return &cobra.Command{
Use: "remove-vm",
Short: "Remove a virtual machine",
Example: `remove-vm <cvm_id>`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
if c.managerClient == nil || c.connectErr != nil {
if err := c.InitializeManagerClient(cmd); err == nil {
defer c.Close()
}
}
if c.connectErr != nil {
printError(cmd, "Failed to connect to manager: %v ❌ ", c.connectErr)
return
}
cmd.Println("🔗 Removing virtual machine")
_, err := c.managerClient.RemoveVm(cmd.Context(), &manager.RemoveReq{CvmId: args[0]})
if err != nil {
printError(cmd, "Error removing virtual machine: %v ❌ ", err)
return
}
cmd.Println(color.New(color.FgGreen).Sprintf("✅ Virtual machine removed successfully"))
},
}
}
func fileReader(path string) ([]byte, error) {
if path == "" {
return nil, nil
}
return os.ReadFile(path)
}
func loadCerts() (*manager.CreateReq, error) {
clientKey, err := fileReader(agentCVMClientKey)
if err != nil {
return nil, err
}
clientCrt, err := fileReader(agentCVMClientCrt)
if err != nil {
return nil, err
}
serverCA, err := fileReader(agentCVMServerCA)
if err != nil {
return nil, err
}
return &manager.CreateReq{
AgentCvmServerCaCert: serverCA,
AgentCvmClientKey: clientKey,
AgentCvmClientCert: clientCrt,
}, nil
}
+600
View File
@@ -0,0 +1,600 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"bytes"
"errors"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/ultravioletrs/cocos/manager"
"github.com/ultravioletrs/cocos/manager/mocks"
"google.golang.org/protobuf/types/known/emptypb"
)
func TestCLI_NewCreateVMCmd(t *testing.T) {
tests := []struct {
name string
setupMock func(*mocks.ManagerServiceClient)
setupCLI func(*CLI)
setupFiles func(string) error
flags map[string]string
expectedOutput string
expectedError string
expectError bool
}{
{
name: "successful VM creation with all flags",
setupMock: func(m *mocks.ManagerServiceClient) {
m.On("CreateVm", mock.Anything, mock.MatchedBy(func(req *manager.CreateReq) bool {
return req.AgentCvmServerUrl == "https://server.com" &&
req.AgentLogLevel == "debug" &&
req.AgentCvmCaUrl == "https://ca.com" &&
req.Ttl == "1h0m0s" &&
string(req.AgentCvmServerCaCert) == "ca-cert-content" &&
string(req.AgentCvmClientKey) == "client-key-content" &&
string(req.AgentCvmClientCert) == "client-cert-content"
})).Return(&manager.CreateRes{
CvmId: "vm-123",
ForwardedPort: "8080",
}, nil)
},
setupCLI: func(cli *CLI) {
},
setupFiles: func(tmpDir string) error {
files := map[string]string{
"server-ca.pem": "ca-cert-content",
"client-key.pem": "client-key-content",
"client-crt.pem": "client-cert-content",
}
for filename, content := range files {
if err := os.WriteFile(filepath.Join(tmpDir, filename), []byte(content), 0o644); err != nil {
return err
}
}
return nil
},
flags: map[string]string{
"server-url": "https://server.com",
"server-ca": "server-ca.pem",
"client-key": "client-key.pem",
"client-crt": "client-crt.pem",
"ca-url": "https://ca.com",
"log-level": "debug",
"ttl": "1h",
},
expectedOutput: "✅ Virtual machine created successfully with id vm-123 and port 8080",
expectError: false,
},
{
name: "successful VM creation with minimal flags",
setupMock: func(m *mocks.ManagerServiceClient) {
m.On("CreateVm", mock.Anything, mock.MatchedBy(func(req *manager.CreateReq) bool {
return req.AgentCvmServerUrl == "https://server.com" &&
req.AgentLogLevel == "" &&
req.AgentCvmCaUrl == "" &&
req.Ttl == "" &&
len(req.AgentCvmServerCaCert) == 0 &&
len(req.AgentCvmClientKey) == 0 &&
len(req.AgentCvmClientCert) == 0
})).Return(&manager.CreateRes{
CvmId: "vm-456",
ForwardedPort: "9090",
}, nil)
},
setupCLI: func(cli *CLI) {
},
setupFiles: func(tmpDir string) error {
return nil // No files needed for minimal test
},
flags: map[string]string{
"server-url": "https://server.com",
},
expectedOutput: "✅ Virtual machine created successfully with id vm-456 and port 9090",
expectError: false,
},
{
name: "manager client initialization failure",
setupMock: func(m *mocks.ManagerServiceClient) {
// No expectations set as initialization fails
},
setupCLI: func(cli *CLI) {
cli.connectErr = errors.New("connection failed")
},
setupFiles: func(tmpDir string) error {
return nil
},
flags: map[string]string{
"server-url": "https://server.com",
},
expectedError: "Failed to connect to manager: failed to connect to grpc server : failed to exit idle mode: passthrough: received empty target in Build() ❌",
expectError: true,
},
{
name: "certificate loading failure",
setupMock: func(m *mocks.ManagerServiceClient) {
// No expectations set as cert loading fails
},
setupCLI: func(cli *CLI) {
},
setupFiles: func(tmpDir string) error {
return nil // Don't create the cert file
},
flags: map[string]string{
"server-url": "https://server.com",
"server-ca": "nonexistent-ca.pem",
},
expectedError: "Error loading certs:",
expectError: true,
},
{
name: "CreateVm API call failure",
setupMock: func(m *mocks.ManagerServiceClient) {
m.On("CreateVm", mock.Anything, mock.Anything).Return(nil, errors.New("API error"))
},
setupCLI: func(cli *CLI) {
},
setupFiles: func(tmpDir string) error {
return nil
},
flags: map[string]string{
"server-url": "https://server.com",
},
expectedError: "Error creating virtual machine: API error ❌",
expectError: true,
},
{
name: "missing required server-url flag",
setupMock: func(m *mocks.ManagerServiceClient) {
// No expectations set as command validation fails
},
setupCLI: func(cli *CLI) {
},
setupFiles: func(tmpDir string) error {
return nil
},
flags: map[string]string{}, // No server-url flag
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "cli-test-")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
oldDir, err := os.Getwd()
require.NoError(t, err)
err = os.Chdir(tmpDir)
require.NoError(t, err)
t.Cleanup(func() {
err := os.Chdir(oldDir)
require.NoError(t, err)
})
err = tt.setupFiles(tmpDir)
require.NoError(t, err)
mockClient := new(mocks.ManagerServiceClient)
tt.setupMock(mockClient)
mockCLI := &CLI{
managerClient: mockClient,
}
tt.setupCLI(mockCLI)
cmd := mockCLI.NewCreateVMCmd()
for flag, value := range tt.flags {
err := cmd.Flags().Set(flag, value)
require.NoError(t, err)
}
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err = cmd.Execute()
if tt.expectError {
if tt.expectedError != "" {
assert.Contains(t, buf.String(), tt.expectedError)
}
} else {
assert.NoError(t, err)
if tt.expectedOutput != "" {
assert.Contains(t, buf.String(), tt.expectedOutput)
}
}
mockClient.AssertExpectations(t)
})
}
}
func TestCLI_NewRemoveVMCmd(t *testing.T) {
tests := []struct {
name string
setupMock func(*mocks.ManagerServiceClient)
setupCLI func(*CLI)
args []string
expectedOutput string
expectedError string
expectError bool
}{
{
name: "successful VM removal",
setupMock: func(m *mocks.ManagerServiceClient) {
m.On("RemoveVm", mock.Anything, &manager.RemoveReq{
CvmId: "vm-123",
}).Return(&emptypb.Empty{}, nil)
},
setupCLI: func(cli *CLI) {
},
args: []string{"vm-123"},
expectedOutput: "✅ Virtual machine removed successfully",
expectError: false,
},
{
name: "manager client initialization failure",
setupMock: func(m *mocks.ManagerServiceClient) {
// No expectations set as initialization fails
},
setupCLI: func(cli *CLI) {
cli.connectErr = errors.New("connection failed")
},
args: []string{"vm-123"},
expectedError: "Failed to connect to manager: failed to connect to grpc server : failed to exit idle mode: passthrough: received empty target in Build() ❌",
expectError: true,
},
{
name: "RemoveVm API call failure",
setupMock: func(m *mocks.ManagerServiceClient) {
m.On("RemoveVm", mock.Anything, &manager.RemoveReq{
CvmId: "vm-456",
}).Return(nil, errors.New("removal failed"))
},
setupCLI: func(cli *CLI) {
},
args: []string{"vm-456"},
expectedError: "Error removing virtual machine: removal failed ❌",
expectError: true,
},
{
name: "missing VM ID argument",
setupMock: func(m *mocks.ManagerServiceClient) {
// No expectations set as command validation fails
},
setupCLI: func(cli *CLI) {
},
args: []string{}, // No VM ID provided
expectError: true,
},
{
name: "too many arguments",
setupMock: func(m *mocks.ManagerServiceClient) {
// No expectations set as command validation fails
},
setupCLI: func(cli *CLI) {
},
args: []string{"vm-123", "extra-arg"},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockClient := new(mocks.ManagerServiceClient)
tt.setupMock(mockClient)
mockCLI := &CLI{
managerClient: mockClient,
}
tt.setupCLI(mockCLI)
cmd := mockCLI.NewRemoveVMCmd()
cmd.SetArgs(tt.args)
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
err := cmd.Execute()
if tt.expectError {
if tt.expectedError != "" {
assert.Contains(t, buf.String(), tt.expectedError)
}
} else {
assert.NoError(t, err)
if tt.expectedOutput != "" {
assert.Contains(t, buf.String(), tt.expectedOutput)
}
}
mockClient.AssertExpectations(t)
})
}
}
func TestFileReader(t *testing.T) {
tests := []struct {
name string
setupFile func(string) (string, error)
path string
expectedResult []byte
expectError bool
}{
{
name: "successful file read",
setupFile: func(tmpDir string) (string, error) {
filePath := filepath.Join(tmpDir, "test.txt")
err := os.WriteFile(filePath, []byte("test content"), 0o644)
return filePath, err
},
expectedResult: []byte("test content"),
expectError: false,
},
{
name: "empty path returns nil",
setupFile: func(tmpDir string) (string, error) {
return "", nil
},
path: "",
expectedResult: nil,
expectError: false,
},
{
name: "nonexistent file returns error",
setupFile: func(tmpDir string) (string, error) {
return filepath.Join(tmpDir, "nonexistent.txt"), nil
},
expectedResult: nil,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "fileReader-test-")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
filePath, err := tt.setupFile(tmpDir)
require.NoError(t, err)
if tt.path != "" {
filePath = tt.path
}
result, err := fileReader(filePath)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, result)
}
})
}
}
func TestLoadCerts(t *testing.T) {
tests := []struct {
name string
setupFiles func(string) error
setupGlobal func(string)
expectError bool
validate func(*testing.T, *manager.CreateReq)
}{
{
name: "successful cert loading with all files",
setupFiles: func(tmpDir string) error {
files := map[string]string{
"client.key": "client-key-content",
"client.crt": "client-cert-content",
"server.ca": "server-ca-content",
}
for filename, content := range files {
if err := os.WriteFile(filepath.Join(tmpDir, filename), []byte(content), 0o644); err != nil {
return err
}
}
return nil
},
setupGlobal: func(tmpDir string) {
agentCVMClientKey = filepath.Join(tmpDir, "client.key")
agentCVMClientCrt = filepath.Join(tmpDir, "client.crt")
agentCVMServerCA = filepath.Join(tmpDir, "server.ca")
},
expectError: false,
validate: func(t *testing.T, req *manager.CreateReq) {
assert.Equal(t, []byte("client-key-content"), req.AgentCvmClientKey)
assert.Equal(t, []byte("client-cert-content"), req.AgentCvmClientCert)
assert.Equal(t, []byte("server-ca-content"), req.AgentCvmServerCaCert)
},
},
{
name: "successful cert loading with empty paths",
setupFiles: func(tmpDir string) error {
return nil
},
setupGlobal: func(tmpDir string) {
agentCVMClientKey = ""
agentCVMClientCrt = ""
agentCVMServerCA = ""
},
expectError: false,
validate: func(t *testing.T, req *manager.CreateReq) {
assert.Nil(t, req.AgentCvmClientKey)
assert.Nil(t, req.AgentCvmClientCert)
assert.Nil(t, req.AgentCvmServerCaCert)
},
},
{
name: "client key file read error",
setupFiles: func(tmpDir string) error {
return nil // Don't create client key file
},
setupGlobal: func(tmpDir string) {
agentCVMClientKey = filepath.Join(tmpDir, "nonexistent.key")
agentCVMClientCrt = ""
agentCVMServerCA = ""
},
expectError: true,
},
{
name: "client cert file read error",
setupFiles: func(tmpDir string) error {
// Create client key but not cert
return os.WriteFile(filepath.Join(tmpDir, "client.key"), []byte("key-content"), 0o644)
},
setupGlobal: func(tmpDir string) {
agentCVMClientKey = filepath.Join(tmpDir, "client.key")
agentCVMClientCrt = filepath.Join(tmpDir, "nonexistent.crt")
agentCVMServerCA = ""
},
expectError: true,
},
{
name: "server CA file read error",
setupFiles: func(tmpDir string) error {
files := map[string]string{
"client.key": "client-key-content",
"client.crt": "client-cert-content",
}
for filename, content := range files {
if err := os.WriteFile(filepath.Join(tmpDir, filename), []byte(content), 0o644); err != nil {
return err
}
}
return nil
},
setupGlobal: func(tmpDir string) {
agentCVMClientKey = filepath.Join(tmpDir, "client.key")
agentCVMClientCrt = filepath.Join(tmpDir, "client.crt")
agentCVMServerCA = filepath.Join(tmpDir, "nonexistent.ca")
},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "loadCerts-test-")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
err = tt.setupFiles(tmpDir)
require.NoError(t, err)
// Store original global variables
origClientKey := agentCVMClientKey
origClientCrt := agentCVMClientCrt
origServerCA := agentCVMServerCA
// Setup global variables for test
tt.setupGlobal(tmpDir)
// Restore original values after test
defer func() {
agentCVMClientKey = origClientKey
agentCVMClientCrt = origClientCrt
agentCVMServerCA = origServerCA
}()
result, err := loadCerts()
if tt.expectError {
assert.Error(t, err)
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.NotNil(t, result)
if tt.validate != nil {
tt.validate(t, result)
}
}
})
}
}
func TestCommandCreation(t *testing.T) {
cli := &CLI{}
t.Run("create-vm command creation", func(t *testing.T) {
cmd := cli.NewCreateVMCmd()
assert.NotNil(t, cmd)
assert.Equal(t, "create-vm", cmd.Use)
assert.Equal(t, "Create a new virtual machine", cmd.Short)
// Check that required flags are set
flag := cmd.Flags().Lookup("server-url")
assert.NotNil(t, flag)
// Note: We can't easily test MarkFlagRequired in unit tests
})
t.Run("remove-vm command creation", func(t *testing.T) {
cmd := cli.NewRemoveVMCmd()
assert.NotNil(t, cmd)
assert.Equal(t, "remove-vm", cmd.Use)
assert.Equal(t, "Remove a virtual machine", cmd.Short)
})
}
func TestTTLHandling(t *testing.T) {
tests := []struct {
name string
ttlInput string
expectedTTL time.Duration
expectError bool
}{
{
name: "valid duration",
ttlInput: "1h30m",
expectedTTL: time.Hour + 30*time.Minute,
expectError: false,
},
{
name: "zero duration",
ttlInput: "0",
expectedTTL: 0,
expectError: false,
},
{
name: "empty string",
ttlInput: "",
expectedTTL: 0,
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockCLI := &CLI{
managerClient: new(mocks.ManagerServiceClient),
}
cmd := mockCLI.NewCreateVMCmd()
if tt.ttlInput != "" {
err := cmd.Flags().Set("ttl", tt.ttlInput)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedTTL, ttl)
}
}
})
}
}
+35 -11
View File
@@ -6,39 +6,63 @@ import (
"context"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/manager"
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
"github.com/ultravioletrs/cocos/pkg/clients/grpc/agent"
managergrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/manager"
"github.com/ultravioletrs/cocos/pkg/sdk"
)
var Verbose bool
type CLI struct {
agentSDK sdk.SDK
config grpc.AgentClientConfig
client grpc.Client
connectErr error
agentSDK sdk.SDK
agentConfig grpc.AgentClientConfig
managerConfig grpc.ManagerClientConfig
client grpc.Client
managerClient manager.ManagerServiceClient
connectErr error
measurement cmdconfig.MeasurementProvider
}
func New(config grpc.AgentClientConfig) *CLI {
func New(agentConfig grpc.AgentClientConfig, managerConfig grpc.ManagerClientConfig, measurement cmdconfig.MeasurementProvider) *CLI {
return &CLI{
config: config,
agentConfig: agentConfig,
managerConfig: managerConfig,
measurement: measurement,
}
}
func (c *CLI) InitializeSDK(cmd *cobra.Command) error {
agentGRPCClient, agentClient, err := agent.NewAgentClient(context.Background(), c.config)
func (c *CLI) InitializeAgentSDK(cmd *cobra.Command) error {
agentGRPCClient, agentClient, err := agent.NewAgentClient(context.Background(), c.agentConfig)
if err != nil {
c.connectErr = err
return err
}
cmd.Println("🔗 Connected to agent using ", agentGRPCClient.Secure())
cmd.Println("🔗 Connected to agent ", agentGRPCClient.Secure())
c.client = agentGRPCClient
c.agentSDK = sdk.NewAgentSDK(agentClient)
return nil
}
func (c *CLI) Close() {
c.client.Close()
func (c *CLI) InitializeManagerClient(cmd *cobra.Command) error {
managerGRPCClient, managerClient, err := managergrpc.NewManagerClient(c.managerConfig)
if err != nil {
c.connectErr = err
return err
}
cmd.Println("🔗 Connected to manager using ", managerGRPCClient.Secure())
c.client = managerGRPCClient
c.managerClient = managerClient
return nil
}
func (c *CLI) Close() {
if c.client != nil {
c.client.Close()
}
}
+191 -167
View File
@@ -3,157 +3,231 @@
package main
import (
"bytes"
"context"
"encoding/json"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"log"
"log/slog"
"os"
"os/signal"
"syscall"
"time"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/cenkalti/backoff/v4"
"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/client"
"github.com/mdlayher/vsock"
"github.com/caarlos0/env/v11"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/api"
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
"github.com/ultravioletrs/cocos/agent/auth"
"github.com/ultravioletrs/cocos/agent/cvms"
cvmsapi "github.com/ultravioletrs/cocos/agent/cvms/api/grpc"
"github.com/ultravioletrs/cocos/agent/cvms/server"
"github.com/ultravioletrs/cocos/agent/events"
agentlogger "github.com/ultravioletrs/cocos/internal/logger"
"github.com/ultravioletrs/cocos/internal/server"
grpcserver "github.com/ultravioletrs/cocos/internal/server/grpc"
ackvsock "github.com/ultravioletrs/cocos/internal/vsock"
managerevents "github.com/ultravioletrs/cocos/manager/events"
"github.com/ultravioletrs/cocos/manager/qemu"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"golang.org/x/crypto/sha3"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/azure"
"github.com/ultravioletrs/cocos/pkg/attestation/tdx"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
cvmsgrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/cvm"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
const (
svcName = "agent"
defSvcGRPCPort = "7002"
retryInterval = 5 * time.Second
svcName = "agent"
defSvcGRPCPort = "7002"
retryInterval = 5 * time.Second
envPrefixCVMGRPC = "AGENT_CVM_GRPC_"
storageDir = "/var/lib/cocos/agent"
)
type config struct {
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"debug"`
Vmpl int `env:"AGENT_VMPL" envDefault:"2"`
AgentGrpcHost string `env:"AGENT_GRPC_HOST" envDefault:"0.0.0.0"`
CAUrl string `env:"AGENT_CVM_CA_URL" envDefault:""`
CVMId string `env:"AGENT_CVM_ID" envDefault:""`
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"`
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
cfg, err := readConfig()
if err != nil {
log.Fatalf("failed to read agent configuration from vsock %s", err.Error())
var cfg config
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
conn, err := dialVsock()
if err != nil {
log.Fatal(err)
}
defer conn.Close()
ackConn := ackvsock.NewAckWriter(conn)
var exitCode int
defer mglog.ExitWithError(&exitCode)
var level slog.Level
if err := level.UnmarshalText([]byte(cfg.AgentConfig.LogLevel)); err != nil {
if err := level.UnmarshalText([]byte(cfg.LogLevel)); err != nil {
log.Println(err)
exitCode = 1
return
}
handler := agentlogger.NewProtoHandler(ackConn, &slog.HandlerOptions{Level: level}, cfg.ID)
eventsLogsQueue := make(chan *cvms.ClientStreamMessage, 1000)
handler := agentlogger.NewProtoHandler(os.Stdout, &slog.HandlerOptions{Level: level}, eventsLogsQueue)
logger := slog.New(handler)
eventSvc, err := events.New(svcName, cfg.ID, ackConn)
eventSvc, err := events.New(svcName, eventsLogsQueue)
if err != nil {
logger.Error(fmt.Sprintf("failed to create events service %s", err.Error()))
exitCode = 1
return
}
qp, err := quoteprovider.GetQuoteProvider()
if err != nil {
logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
var provider attestation.Provider
ccPlatform := attestation.CCPlatform()
azureConfig := azure.NewEnvConfigFromAgent(
cfg.AgentOSBuild,
cfg.AgentOSType,
cfg.AgentOSDistro,
cfg.AgentMaaURL,
)
azure.InitializeDefaultMAAVars(azureConfig)
switch ccPlatform {
case attestation.SNP:
provider = vtpm.NewProvider(false, uint(cfg.Vmpl))
case attestation.SNPvTPM:
provider = vtpm.NewProvider(true, uint(cfg.Vmpl))
case attestation.Azure:
provider = azure.NewProvider()
case attestation.TDX:
provider = tdx.NewProvider()
case attestation.NoCC:
logger.Info("TEE device not found")
provider = &attestation.EmptyProvider{}
}
cvmGrpcConfig := pkggrpc.CVMClientConfig{}
if err := env.ParseWithOptions(&cvmGrpcConfig, env.Options{Prefix: envPrefixCVMGRPC}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s gRPC client configuration : %s", svcName, err))
exitCode = 1
return
}
if err := verifyManifest(cfg, qp); err != nil {
cvmGRPCClient, cvmsClient, err := cvmsgrpc.NewCVMClient(cvmGrpcConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer cvmGRPCClient.Close()
reconnectFn := func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) {
grpcClient, newClient, err := cvmsgrpc.NewCVMClient(cvmGrpcConfig)
if err != nil {
return nil, nil, err
}
// Don't defer close here as we want to keep the connection open
pc, err := newClient.Process(ctx)
if err != nil {
grpcClient.Close()
return nil, nil, err
}
return grpcClient, pc, nil
}
pc, err := cvmsClient.Process(ctx)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
setDefaultValues(&cfg)
svc := newService(ctx, logger, eventSvc, cfg, qp)
agentGrpcServerConfig := server.AgentConfig{
ServerConfig: server.ServerConfig{
BaseConfig: server.BaseConfig{
Host: cfg.AgentConfig.Host,
Port: cfg.AgentConfig.Port,
CertFile: cfg.AgentConfig.CertFile,
KeyFile: cfg.AgentConfig.KeyFile,
ServerCAFile: cfg.AgentConfig.ServerCAFile,
ClientCAFile: cfg.AgentConfig.ClientCAFile,
},
},
AttestedTLS: cfg.AgentConfig.AttestedTls,
}
registerAgentServiceServer := func(srv *grpc.Server) {
reflection.Register(srv)
agent.RegisterAgentServiceServer(srv, agentgrpc.NewServer(svc))
}
authSvc, err := auth.New(cfg)
if err != nil {
logger.Error(fmt.Sprintf("failed to create auth service %s", err.Error()))
if cfg.Vmpl < 0 || cfg.Vmpl > 3 {
logger.Error("vmpl level must be in a range [0, 3]")
exitCode = 1
return
}
gs := grpcserver.New(ctx, cancel, svcName, agentGrpcServerConfig, registerAgentServiceServer, logger, qp, authSvc)
svc := newService(ctx, logger, eventSvc, provider, cfg.Vmpl)
if err := os.MkdirAll(storageDir, 0o755); err != nil {
logger.Error(fmt.Sprintf("failed to create storage directory: %s", err))
exitCode = 1
return
}
mc, err := cvmsapi.NewClient(pc, svc, eventsLogsQueue, logger, server.NewServer(logger, svc, cfg.AgentGrpcHost, cfg.CAUrl, cfg.CVMId), storageDir, reconnectFn, cvmGRPCClient)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
g.Go(func() error {
for {
if _, err := io.Copy(io.Discard, conn); err != nil {
log.Printf("vsock connection lost: %v, reconnecting...", err)
conn.Close()
conn, err = dialVsock()
if err != nil {
log.Fatal("failed to reconnect: ", err)
}
}
time.Sleep(retryInterval)
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(ch)
select {
case <-ch:
logger.Info("Received signal, shutting down...")
cancel()
return nil
case <-ctx.Done():
return ctx.Err()
}
})
g.Go(func() error {
return gs.Start()
return mc.Process(ctx, cancel)
})
g.Go(func() error {
return server.StopHandler(ctx, cancel, logger, svcName, gs)
})
attest, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc)
if err != nil {
logger.Error(fmt.Sprintf("failed to get attestation: %s", err))
exitCode = 1
return
}
if ccPlatform == attestation.Azure {
azureAttestationResult, azureCertSerialNumber, err := azureAttestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc)
if err != nil {
logger.Error(fmt.Sprintf("failed to get attestation: %s", err))
exitCode = 1
return
}
eventsLogsQueue <- &cvms.ClientStreamMessage{
Message: &cvms.ClientStreamMessage_AzureAttestationResult{
AzureAttestationResult: &cvms.AzureAttestationResponse{
File: azureAttestationResult,
CertSerialNumber: azureCertSerialNumber,
},
},
}
}
eventsLogsQueue <- &cvms.ClientStreamMessage{
Message: &cvms.ClientStreamMessage_VTPMattestationReport{
VTPMattestationReport: &cvms.AttestationResponse{
File: attest,
CertSerialNumber: certSerialNumber,
},
},
}
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
}
}
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp agent.Computation, qp client.QuoteProvider) agent.Service {
svc := agent.New(ctx, logger, eventSvc, cmp, qp)
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, provider attestation.Provider, vmpl int) agent.Service {
svc := agent.New(ctx, logger, eventSvc, provider, vmpl)
svc = api.LoggingMiddleware(svc, logger)
counter, latency := prometheus.MakeMetrics(svcName, "api")
@@ -162,103 +236,53 @@ func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Servic
return svc
}
func readConfig() (agent.Computation, error) {
l, err := vsock.Listen(qemu.VsockConfigPort, nil)
func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Service) ([]byte, string, error) {
if certFilePath == "" {
return nil, "", nil
}
certFile, err := os.ReadFile(certFilePath)
if err != nil {
return agent.Computation{}, err
return nil, "", err
}
defer l.Close()
conn, err := l.Accept()
certPem, _ := pem.Decode(certFile)
certx509, err := x509.ParseCertificate(certPem.Bytes)
if err != nil {
return agent.Computation{}, err
}
defer conn.Close()
var buffer []byte
for {
chunk := make([]byte, 1024)
n, err := conn.Read(chunk)
if err != nil {
if err == io.EOF {
break
}
return agent.Computation{}, err
}
buffer = append(buffer, chunk[:n]...)
return nil, "", err
}
ac := agent.Computation{
AgentConfig: agent.AgentConfig{},
nonceSNP := sha512.Sum512(certFile)
nonceVTPM := sha256.Sum256(certFile)
attest, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, attestation.SNPvTPM)
if err != nil {
return nil, "", err
}
if err := json.Unmarshal(buffer, &ac); err != nil {
return agent.Computation{}, err
}
return ac, nil
return attest, certx509.SerialNumber.String(), nil
}
func setDefaultValues(cfg *agent.Computation) {
if cfg.AgentConfig.LogLevel == "" {
cfg.AgentConfig.LogLevel = "info"
func azureAttestationFromCert(ctx context.Context, certFilePath string, svc agent.Service) ([]byte, string, error) {
if certFilePath == "" {
return nil, "", nil
}
if cfg.AgentConfig.Port == "" {
cfg.AgentConfig.Port = defSvcGRPCPort
}
}
func isTEE() bool {
_, err := os.Stat("/dev/sev-guest")
return !os.IsNotExist(err)
}
func dialVsock() (*vsock.Conn, error) {
var conn *vsock.Conn
var err error
err = backoff.Retry(func() error {
conn, err = vsock.Dial(vsock.Host, managerevents.ManagerVsockPort, nil)
if err == nil {
log.Println("vsock connection established")
return nil
}
log.Printf("vsock connection failed, retrying in %s... Error: %v", retryInterval, err)
return err
}, backoff.NewExponentialBackOff())
certFile, err := os.ReadFile(certFilePath)
if err != nil {
return nil, err
return nil, "", err
}
return conn, nil
}
func verifyManifest(cfg agent.Computation, qp client.QuoteProvider) error {
if !isTEE() {
return nil
}
ar, err := qp.GetRawQuote(sha3.Sum512([]byte(cfg.ID)))
if err != nil {
return err
}
arProto, err := abi.ReportCertsToProto(ar[:abi.ReportSize])
if err != nil {
return err
}
cfgBytes, err := json.Marshal(cfg)
if err != nil {
return err
}
mcHash := sha3.Sum256(cfgBytes)
if arProto.Report.HostData == nil {
return fmt.Errorf("manifest verification failed: HostData is nil")
}
if !bytes.Equal(arProto.Report.HostData, mcHash[:]) {
return fmt.Errorf("manifest verification failed")
}
return nil
certPem, _ := pem.Decode(certFile)
certx509, err := x509.ParseCertificate(certPem.Bytes)
if err != nil {
return nil, "", err
}
nonceAzure := sha256.Sum256(certFile)
attestation, err := svc.AttestationResult(ctx, nonceAzure, attestation.AzureToken)
if err != nil {
return nil, "", err
}
return attestation, certx509.SerialNumber.String(), nil
}
-94
View File
@@ -1,94 +0,0 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"log/slog"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/events/mocks"
qpmocks "github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
)
func TestSetDefaultValues(t *testing.T) {
tests := []struct {
name string
input agent.Computation
expected agent.Computation
}{
{
name: "Empty config",
input: agent.Computation{
AgentConfig: agent.AgentConfig{},
},
expected: agent.Computation{
AgentConfig: agent.AgentConfig{
LogLevel: "info",
Port: "7002",
},
},
},
{
name: "Partial config",
input: agent.Computation{
AgentConfig: agent.AgentConfig{
LogLevel: "debug",
},
},
expected: agent.Computation{
AgentConfig: agent.AgentConfig{
LogLevel: "debug",
Port: "7002",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setDefaultValues(&tt.input)
assert.Equal(t, tt.expected, tt.input)
})
}
}
func TestNewService(t *testing.T) {
ctx := context.Background()
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventSvc := new(mocks.Service)
eventSvc.On("SendEvent", mock.Anything, mock.Anything, mock.Anything).Return(nil)
cmp := agent.Computation{
ID: "test-computation",
AgentConfig: agent.AgentConfig{
LogLevel: "info",
Port: "7002",
},
}
qp := new(qpmocks.QuoteProvider)
svc := newService(ctx, logger, eventSvc, cmp, qp)
assert.NotNil(t, svc)
}
func TestVerifyManifest(t *testing.T) {
cfg := agent.Computation{
ID: "test-computation",
AgentConfig: agent.AgentConfig{
LogLevel: "info",
Port: "7002",
},
}
mockQP := new(qpmocks.QuoteProvider)
mockQP.On("GetRawQuote", mock.Anything).Return([]byte{}, nil)
err := verifyManifest(cfg, mockQP)
assert.NoError(t, err)
}
+33 -8
View File
@@ -14,20 +14,23 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/ultravioletrs/cocos/cli"
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
cmd "github.com/virtee/sev-snp-measure-go/sevsnpmeasure/cmd"
)
const (
svcName = "cli"
envPrefixAgentGRPC = "AGENT_GRPC_"
completion = "completion"
filePermision = 0o755
cocosDirectory = ".cocos"
svcName = "cli"
envPrefixAgentGRPC = "AGENT_GRPC_"
envPrefixManagerGRPC = "MANAGER_GRPC_"
completion = "completion"
filePermision = 0o755
cocosDirectory = ".cocos"
)
type config struct {
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"info"`
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"info"`
IgvmBinaryPath string `env:"IGVM_BINARY_PATH" envDefault:"./build/igvmmeasure"`
}
func main() {
@@ -98,9 +101,24 @@ func main() {
return
}
cliSVC := cli.New(agentGRPCConfig)
managerGRPCConfig := grpc.ManagerClientConfig{}
if err := env.ParseWithOptions(&managerGRPCConfig, env.Options{Prefix: envPrefixManagerGRPC}); err != nil {
message := color.New(color.FgRed).Sprintf("failed to load %s gRPC client configuration : %s", svcName, err)
rootCmd.Println(message)
return
}
if err := cliSVC.InitializeSDK(rootCmd); err == nil {
options := cmdconfig.IgvmMeasureOptions
measurement, err := cmdconfig.NewCmdConfig(cfg.IgvmBinaryPath, options, os.Stderr)
if err != nil {
message := color.New(color.FgRed).Sprintf("failed to initialize measurement: %s", err) // Use %s instead of %w
rootCmd.Println(message)
return
}
cliSVC := cli.New(agentGRPCConfig, managerGRPCConfig, measurement)
if err := cliSVC.InitializeAgentSDK(rootCmd); err == nil {
defer cliSVC.Close()
}
@@ -119,6 +137,9 @@ func main() {
rootCmd.AddCommand(attestationPolicyCmd)
rootCmd.AddCommand(keysCmd)
rootCmd.AddCommand(cliSVC.NewCABundleCmd(directoryCachePath))
rootCmd.AddCommand(cliSVC.NewCreateVMCmd())
rootCmd.AddCommand(cliSVC.NewRemoveVMCmd())
rootCmd.AddCommand(cliSVC.NewIMAMeasurementsCmd())
// Attestation commands
attestationCmd.AddCommand(cliSVC.NewGetAttestationCmd())
@@ -126,6 +147,7 @@ func main() {
// measure.
rootCmd.AddCommand(cmd.NewRootCmd())
rootCmd.AddCommand(cliSVC.NewMeasureCmd(cfg.IgvmBinaryPath))
// Flags
keysCmd.PersistentFlags().StringVarP(
@@ -139,6 +161,9 @@ func main() {
// Attestation Policy commands
attestationPolicyCmd.AddCommand(cliSVC.NewAddMeasurementCmd())
attestationPolicyCmd.AddCommand(cliSVC.NewAddHostDataCmd())
attestationPolicyCmd.AddCommand(cliSVC.NewGCPAttestationPolicy())
attestationPolicyCmd.AddCommand(cliSVC.NewDownloadGCPOvmfFile())
attestationPolicyCmd.AddCommand(cliSVC.NewAzureAttestationPolicy())
if err := rootCmd.Execute(); err != nil {
logErrorCmd(*rootCmd, err)
+42 -50
View File
@@ -10,25 +10,24 @@ import (
"log/slog"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/caarlos0/env/v11"
"github.com/ultravioletrs/cocos/internal/server"
grpcserver "github.com/ultravioletrs/cocos/internal/server/grpc"
"github.com/ultravioletrs/cocos/manager"
"github.com/ultravioletrs/cocos/manager/api"
managerapi "github.com/ultravioletrs/cocos/manager/api/grpc"
"github.com/ultravioletrs/cocos/manager/events"
managergrpc "github.com/ultravioletrs/cocos/manager/api/grpc"
"github.com/ultravioletrs/cocos/manager/qemu"
"github.com/ultravioletrs/cocos/manager/tracing"
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
managergrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/manager"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
const (
@@ -43,7 +42,9 @@ type config struct {
JaegerURL url.URL `env:"COCOS_JAEGER_URL" envDefault:"http://localhost:4318"`
TraceRatio float64 `env:"COCOS_JAEGER_TRACE_RATIO" envDefault:"1.0"`
InstanceID string `env:"MANAGER_INSTANCE_ID" envDefault:""`
AttestationPolicyBinary string `env:"MANAGER_ATTESTATION_POLICY_BINARY" envDefault:"../../build"`
AttestationPolicyBinary string `env:"MANAGER_ATTESTATION_POLICY_BINARY" envDefault:"../../build/attestation_policy"`
IgvmMeasureBinary string `env:"MANAGER_IGVMMEASURE_BINARY" envDefault:"../../build/igvmmeasure"`
PcrValues string `env:"MANAGER_PCR_VALUES" envDefault:""`
EosVersion string `env:"MANAGER_EOS_VERSION" envDefault:""`
}
@@ -83,8 +84,22 @@ func main() {
}()
tracer := tp.Tracer(svcName)
qemuCfg := qemu.Config{}
if err := env.ParseWithOptions(&qemuCfg, env.Options{Prefix: envPrefixQemu}); err != nil {
qemuCfg, err := qemu.NewConfig()
if err != nil {
logger.Error(fmt.Sprintf("failed to create config: %v", err))
exitCode = 1
return
}
if qemuCfg.EnableTDX {
logger.Info("Manager started with TDX enabled")
} else if qemuCfg.EnableSEVSNP {
logger.Info("Manager started with SEV-SNP enabled")
} else {
logger.Info("Manager started without confidential computing support")
}
if err := env.ParseWithOptions(qemuCfg, env.Options{Prefix: envPrefixQemu}); err != nil {
logger.Error(fmt.Sprintf("failed to load QEMU configuration: %s", err))
exitCode = 1
return
@@ -92,64 +107,41 @@ func main() {
args := qemuCfg.ConstructQemuArgs()
logger.Info(strings.Join(args, " "))
managerGRPCConfig := pkggrpc.ManagerClientConfig{}
managerGRPCConfig := server.ServerConfig{}
if err := env.ParseWithOptions(&managerGRPCConfig, env.Options{Prefix: envPrefixGRPC}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s gRPC client configuration : %s", svcName, err))
exitCode = 1
return
}
managerGRPCClient, managerClient, err := managergrpc.NewManagerClient(managerGRPCConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
defer managerGRPCClient.Close()
pc, err := managerClient.Process(ctx)
svc, err := newService(logger, tracer, *qemuCfg, cfg.AttestationPolicyBinary, cfg.IgvmMeasureBinary, cfg.PcrValues, cfg.EosVersion)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
eventsChan := make(chan *manager.ClientStreamMessage, clientBufferSize)
svc, err := newService(logger, tracer, qemuCfg, eventsChan, cfg.AttestationPolicyBinary, cfg.EosVersion)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
defer func() {
if err := svc.Shutdown(); err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
}()
registerManagerServiceServer := func(srv *grpc.Server) {
reflection.Register(srv)
manager.RegisterManagerServiceServer(srv, managergrpc.NewServer(svc))
}
eventsSvc, err := events.New(logger, svc.ReportBrokenConnection, eventsChan)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
go eventsSvc.Listen(ctx)
mc := managerapi.NewClient(pc, svc, eventsChan, logger)
gs := grpcserver.New(ctx, cancel, svcName, managerGRPCConfig, registerManagerServiceServer, logger, nil, "", "")
g.Go(func() error {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(ch)
select {
case <-ch:
logger.Info("Received signal, shutting down...")
cancel()
return nil
case <-ctx.Done():
return ctx.Err()
}
return gs.Start()
})
g.Go(func() error {
return mc.Process(ctx, cancel)
return server.StopHandler(ctx, cancel, logger, svcName, gs)
})
if err := g.Wait(); err != nil {
@@ -157,8 +149,8 @@ func main() {
}
}
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, eventsChan chan *manager.ClientStreamMessage, attestationPolicyPath string, eosVersion string) (manager.Service, error) {
svc, err := manager.New(qemuCfg, attestationPolicyPath, logger, eventsChan, qemu.NewVM, eosVersion)
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, attestationPolicyPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, eosVersion string) (manager.Service, error) {
svc, err := manager.New(qemuCfg, attestationPolicyPath, igvmMeasurementBinaryPath, pcrValuesFilePath, logger, qemu.NewVM, eosVersion)
if err != nil {
return nil, err
}
+16 -10
View File
@@ -7,10 +7,13 @@ COCOS_JAEGER_TRACE_RATIO=1.0
# Manager Service Configuration
MANAGER_INSTANCE_ID=
MANAGER_ATTESTATION_POLICY_BINARY=../../build
MANAGER_IGVMMEASURE_BINARY=../../build
MANAGER_PCR_VALUES=/etc/cocos/pcr_values.json
MANAGER_GRPC_CLIENT_CERT=
MANAGER_GRPC_CLIENT_KEY=
MANAGER_GRPC_SERVER_CA_CERTS=
MANAGER_GRPC_URL=localhost:7001
MANAGER_GRPC_PORT=6101
MANAGER_GRPC_HOST=0.0.0.0
MANAGER_GRPC_TIMEOUT=60s
MANAGER_EOS_VERSION=""
@@ -21,13 +24,13 @@ MANAGER_QEMU_MAX_MEMORY=30G
MANAGER_QEMU_OVMF_CODE_IF=pflash
MANAGER_QEMU_OVMF_CODE_FORMAT=raw
MANAGER_QEMU_OVMF_CODE_UNIT=0
MANAGER_QEMU_OVMF_CODE_FILE=/usr/share/OVMF/x64/OVMF_CODE.fd
MANAGER_QEMU_OVMF_CODE_FILE=/usr/share/edk2/x64/OVMF_CODE.fd
MANAGER_QEMU_OVMF_VERSION=edk2-stable202408
MANAGER_QEMU_OVMF_CODE_READONLY=on
MANAGER_QEMU_OVMF_VARS_IF=pflash
MANAGER_QEMU_OVMF_VARS_FORMAT=raw
MANAGER_QEMU_OVMF_VARS_UNIT=1
MANAGER_QEMU_OVMF_VARS_FILE=/usr/share/OVMF/x64/OVMF_VARS.fd
MANAGER_QEMU_OVMF_VARS_FILE=/usr/share/edk2/x64/OVMF_VARS.fd
MANAGER_QEMU_NETDEV_ID=vmnic
MANAGER_QEMU_HOST_FWD_AGENT=7020
MANAGER_QEMU_GUEST_FWD_AGENT=7002
@@ -35,26 +38,29 @@ MANAGER_QEMU_VIRTIO_NET_PCI_DISABLE_LEGACY=on
MANAGER_QEMU_VIRTIO_NET_PCI_IOMMU_PLATFORM=true
MANAGER_QEMU_VIRTIO_NET_PCI_ADDR=0x2
MANAGER_QEMU_VIRTIO_NET_PCI_ROMFILE=
MANAGER_QEMU_DISK_IMG_KERNEL_FILE=/home/sammyk/Documents/cocos-ai/cmd/manager/img/bzImage
MANAGER_QEMU_DISK_IMG_ROOTFS_FILE=/home/sammyk/Documents/cocos-ai/cmd/manager/img/rootfs.cpio.gz
MANAGER_QEMU_SEV_ID=sev0
MANAGER_QEMU_SEV_CBITPOS=51
MANAGER_QEMU_SEV_REDUCED_PHYS_BITS=1
MANAGER_QEMU_DISK_IMG_KERNEL_FILE=/etc/cocos/bzImage
MANAGER_QEMU_DISK_IMG_ROOTFS_FILE=/etc/cocos/rootfs.cpio.gz
MANAGER_QEMU_SEV_SNP_ID=sev0
MANAGER_QEMU_SEV_SNP_CBITPOS=51
MANAGER_QEMU_SEV_SNP_REDUCED_PHYS_BITS=1
MANAGER_QEMU_HOST_DATA=
MANAGER_QEMU_TDX_ID=tdx0
MANAGER_QEMU_QUOTE_GENERATION_PORT=4050
MANAGER_QEMU_OVMF_FILE=/usr/share/ovmf/OVMF.fd
MANAGER_QEMU_VSOCK_ID=vhost-vsock-pci0
MANAGER_QEMU_VSOCK_GUEST_CID=3
MANAGER_QEMU_VSOCK_VNC=0
MANAGER_QEMU_BIN_PATH=qemu-system-x86_64
MANAGER_QEMU_USE_SUDO=true
MANAGER_QEMU_ENABLE_SEV=false
MANAGER_QEMU_ENABLE_SEV_SNP=false
MANAGER_QEMU_ENABLE_TDX=false
MANAGER_QEMU_IGVM_FILE=/etc/cocos/coconut-qemu.igvm
MANAGER_QEMU_ENABLE_KVM=true
MANAGER_QEMU_MACHINE=q35
MANAGER_QEMU_CPU=EPYC
MANAGER_QEMU_SMP_COUNT=4
MANAGER_QEMU_SMP_MAXCPUS=16
MANAGER_QEMU_MEM_ID=ram1
MANAGER_QEMU_KERNEL_HASH=false
MANAGER_QEMU_NO_GRAPHIC=true
MANAGER_QEMU_MONITOR=pty
MANAGER_QEMU_HOST_FWD_RANGE=6100-6200
+89 -37
View File
@@ -1,86 +1,138 @@
module github.com/ultravioletrs/cocos
go 1.23.0
go 1.23.2
toolchain go1.24.1
require (
github.com/absmach/magistrala v0.15.1
github.com/caarlos0/env/v11 v11.2.2
github.com/cenkalti/backoff/v4 v4.3.0
github.com/caarlos0/env/v11 v11.3.1
github.com/fatih/color v1.18.0
github.com/go-kit/kit v0.13.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/google/go-sev-guest v0.11.1
github.com/mdlayher/vsock v1.2.1
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/google/go-sev-guest v0.13.0
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
github.com/virtee/sev-snp-measure-go v0.0.0-20240530153610-e6e8dc9b6877
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0
go.opentelemetry.io/otel/trace v1.32.0
golang.org/x/crypto v0.30.0
golang.org/x/sync v0.10.0
google.golang.org/grpc v1.68.1
google.golang.org/protobuf v1.35.2
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0
go.opentelemetry.io/otel/trace v1.37.0
golang.org/x/crypto v0.39.0
golang.org/x/sync v0.15.0
google.golang.org/grpc v1.73.0
google.golang.org/protobuf v1.36.6
)
require (
cloud.google.com/go/storage v1.55.0
github.com/caarlos0/env/v10 v10.0.0
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/gce-tcb-verifier v0.3.1
)
require (
cel.dev/expr v0.23.0 // indirect
cloud.google.com/go v0.121.1 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.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.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/gofrs/uuid/v5 v5.3.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/certificate-transparency-go v1.1.8 // indirect
github.com/google/go-attestation v0.5.1 // indirect
github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba // 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.14.2 // indirect
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
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
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
go.opentelemetry.io/otel v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/api v0.235.0 // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gotest.tools/v3 v3.5.1 // indirect
moul.io/http2curl v1.0.0 // indirect
)
require (
github.com/absmach/certs v0.0.0-20250707105817-4c41124bba54
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
github.com/docker/docker v27.4.0+incompatible
github.com/docker/docker v28.2.2+incompatible
github.com/edgelesssys/go-azguestattestation v0.0.0-20250408071817-8c4457b235ff
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-configfs-tsm v0.2.2 // indirect
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc // indirect
github.com/google/go-tpm v0.9.5
github.com/google/go-tpm-tools v0.4.4
github.com/google/logger v1.1.1
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.59.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0
golang.org/x/text v0.26.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/virtee/sev-snp-measure-go => github.com/sammyoina/sev-snp-measure-go v0.0.0-20241107163739-38915ab517c7
replace github.com/virtee/sev-snp-measure-go => github.com/sammyoina/sev-snp-measure-go v0.0.0-20241202151803-ef189f0ff825
replace github.com/google/go-tpm-tools => github.com/danko-miladinovic/go-tpm-tools v0.0.0-20250228160324-1ebcfd79567c
+227 -74
View File
@@ -1,36 +1,95 @@
cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=
cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
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.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
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.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
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.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
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.0.0-20250707105817-4c41124bba54 h1:5pTDktMVvtJrQMY0d2vwBRFOgH0LVEoArwcaqrbuGQ4=
github.com/absmach/certs v0.0.0-20250707105817-4c41124bba54/go.mod h1:MGDO7iEXbtJCRKOwL34+HR8MQI2mo7l3wcz93e2Km68=
github.com/absmach/magistrala v0.15.1 h1:3Bk2hlyWcV591LxPYwlvRcyCXTfuZ1g/EkNmU+o3NNQ=
github.com/absmach/magistrala v0.15.1/go.mod h1:9pto6xuBt/IuCtZRdEha0iDQKNQ5tyNOjLXJgUiikYk=
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/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg=
github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc=
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
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.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/danko-miladinovic/go-tpm-tools v0.0.0-20250228160324-1ebcfd79567c h1:gFo8kqRXFoM6ttqMrK+M3xffxco+Yj80kUo3NoMe8LU=
github.com/danko-miladinovic/go-tpm-tools v0.0.0-20250228160324-1ebcfd79567c/go.mod h1:ktjTNq8yZFD6TzdBFefUfen96rF3NpYwpSb2d8bc+Y8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.4.0+incompatible h1:I9z7sQ5qyzO0BfAb9IMOawRkAGxhYsidKiTMcm0DU+A=
github.com/docker/docker v27.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
github.com/docker/docker v28.2.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-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/edgelesssys/go-azguestattestation v0.0.0-20250408071817-8c4457b235ff h1:V6A5kD0+c1Qg4X72Lg+zxhCZk+par436sQdgLvMCBBc=
github.com/edgelesssys/go-azguestattestation v0.0.0-20250408071817-8c4457b235ff/go.mod h1:Lz4QaomI4wU2YbatD4/W7vatW2Q35tnkoJezB1clscc=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
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=
@@ -38,8 +97,8 @@ github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBj
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
@@ -48,27 +107,56 @@ github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98=
github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
github.com/google/go-sev-guest v0.11.1 h1:gnww4U8fHV5DCPz4gykr1s8SEX1fFNcxCBy+vvXN24k=
github.com/google/go-sev-guest v0.11.1/go.mod h1:qBOfb+JmgsUI3aUyzQoGC13Kpp9zwLeWvuyXmA9q77w=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/certificate-transparency-go v1.1.8 h1:LGYKkgZF7satzgTak9R4yzfJXEeYVAjV6/EAEJOf1to=
github.com/google/certificate-transparency-go v1.1.8/go.mod h1:bV/o8r0TBKRf1X//iiiSgWrvII4d7/8OiA+3vG26gI8=
github.com/google/gce-tcb-verifier v0.3.1 h1:4L9YgkOtqC2U7cj4FofCUufHFCCpdD4Y0yPKI8UhOhI=
github.com/google/gce-tcb-verifier v0.3.1/go.mod h1:GZCDLQxmEOCqUTL2BMB/zjo+hgXdUrR0Wgwz1OrwRYg=
github.com/google/go-attestation v0.5.1 h1:jqtOrLk5MNdliTKjPbIPrAaRKJaKW+0LIU2n/brJYms=
github.com/google/go-attestation v0.5.1/go.mod h1:KqGatdUhg5kPFkokyzSBDxwSCFyRgIgtRkMp6c3lOBQ=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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.13.0 h1:DJB6ACdykyweMU0HGOp/TQ7cjsnbV2ecbYunu2E0qy0=
github.com/google/go-sev-guest v0.13.0/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.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
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=
github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0=
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.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
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.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8=
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
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=
@@ -78,12 +166,12 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
@@ -94,111 +182,176 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
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.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0=
github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sammyoina/sev-snp-measure-go v0.0.0-20241107163739-38915ab517c7 h1:g+a3hLU4pl41mhP+CQu3+bDhL4HSfrnPI1BvLjKfD8Y=
github.com/sammyoina/sev-snp-measure-go v0.0.0-20241107163739-38915ab517c7/go.mod h1:dEkBe8JnxU5itNjZDEQINFd7f7l4DtjfqRuzPQcit4w=
github.com/sammyoina/sev-snp-measure-go v0.0.0-20241202151803-ef189f0ff825 h1:SqNaL9udBIc026SGNEuEuiVL0/hw9fXxM5qrFhWGkdE=
github.com/sammyoina/sev-snp-measure-go v0.0.0-20241202151803-ef189f0ff825/go.mod h1:dEkBe8JnxU5itNjZDEQINFd7f7l4DtjfqRuzPQcit4w=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 h1:qtFISDHKolvIxzSs0gIaiPUPR0Cucb0F2coHC7ZLdps=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0/go.mod h1:Y+Pop1Q6hCOnETWTW4NROK/q1hv50hM7yDaUTjG8lp8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
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.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
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=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
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.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
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.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g=
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo=
google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 h1:WvBuA5rjZx9SNIzgcU53OohgZy6lKSus++uY4xLaWKc=
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:W3S/3np0/dPWsWLi1h/UymYctGXaGBM2StwzD0y140U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
+88
View File
@@ -0,0 +1,88 @@
#### memory config
MEMORY_SIZE=2048M
MEMORY_SLOTS=5
MAX_MEMORY=30G
#### ovmf code config
OVMF_CODE_IF=pflash
OVMF_CODE_FORMAT=raw
OVMF_CODE_UNIT=0
OVMF_CODE_FILE=/usr/share/OVMF/OVMF_CODE.fd
OVMF_CODE_READONLY=on
OVMF_VERSION=
#### ovmf vars config
OVMF_VARS_IF=pflash
OVMF_VARS_FORMAT=raw
OVMF_VARS_UNIT=1
OVMF_VARS_FILE=/usr/share/OVMF/OVMF_VARS.fd
#### net dev config
NET_DEV_ID=vmnic
NET_DEV_HOST_FWD_AGENT=7020
NET_DEV_GUEST_FWD_AGENT=7002
#### Virtio Net Pci Config
VIRTIO_NET_PCI_DISABLE_LEGACY=on
VIRTIO_NET_PCI_IOMMU_PLATFORM=true
VIRTIO_NET_PCI_ADDR=0x2
VIRTIO_NET_PCI_ROMFILE=
#### Disk image config
DISK_IMG_KERNEL_FILE=
DISK_IMG_ROOTFS_FILE=
KERNEL_COMMAND_LINE="quiet console=null"
#### Sev-Snp Config
SEV_SNP_ID=sev0
SEV_SNP_CBIT_POS=51
SEV_SNP_REDUCED_PHYS_BITS=1
SEV_SNP_HOST_DATA=
#### VSock Config
VSOCK_ID=vhost-vsock-pci0
VSOCK_GUEST_CID=3
BIN_PATH=qemu-system-x86_64
USE_SUDO=false
ENABLE_SEV_SNP=false
ENABLE_KVM=true
MACHINE=q35
CPU=EPYC
SMP_COUNT=8
SMP_MAXCPUS=64
MEM_ID=ram1
KERNEL_HASH=false
NO_GRAPHIC=true
MONITOR=pty
HOST_FWD_RANGE=6100-6200
CERTS_MOUNT=/etc/cocos/certs
ENV_MOUNT=/etc/cocos/environment
COCOS_AGENT_VERSION=v0.3.1
#### Base image URL and names
BASE_IMAGE_URL=https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
BASE_IMAGE=ubuntu-base.qcow2
CUSTOM_IMAGE=ubuntu-custom.qcow2
#### Paths for OVMF firmware
OVMF_CODE=/usr/share/ovmf/x64/OVMF_CODE.4m.fd
OVMF_VARS=/usr/share/ovmf/x64/OVMF_VARS.4m.fd
#### VM parameters
VM_NAME=cocos-vm
RAM=16G
DISK_SIZE=10G # Size for root filesystem
QEMU_BINARY=qemu-system-x86_64
AGENT_GRPC_SERVER_CERT=/etc/cocos/certs/server.pem
AGENT_GRPC_SERVER_KEY=/etc/cocos/certs/key.pem
AGENT_GRPC_SERVER_CA_CERTS=/etc/cocos/ca.pem
AGENT_GRPC_CLIENT_CA_CERTS=/etc/cocos/ca.pem
+114
View File
@@ -0,0 +1,114 @@
# Agent Cloud Init Setup
## Overview
The `hal/cloud` directory contains essential files required for setting up a virtual machine (VM) with cloud-init. This setup ensures the automated installation of dependencies, configuration of the environment, and deployment of the Cocos agent as a systemd service.
### Directory Contents
- **`config.yaml`**: This YAML file provides configuration instructions for the cloud image.
- **`meta-data`**: Contains VM metadata, such as instance-specific details and identifiers.
- **`qemu.sh`**: A Bash script for downloading and configuring a cloud image, running QEMU to simulate a VM with the cloud-init configuration.
- **`.env`**: Contains environment variables for starting the VM in different modes, configuring disk space, memory allocation, and other parameters.
## Configuration
### Preparing the Cloud-Config File
The `config.yaml` file defines system configurations, including user creation, package installations, file management, and command execution.
Ensure that the cloud-config file is set up with the following configurations:
- **User Credentials**: Specify the default username and password.
- **Certificates and Keys**: Certificate files for agent for secure communication.
- **Environment Variables**: Configuration parameters required by the system.
The `config.yaml` file is divided into multiple sections, each addressing a specific aspect of the setup process.
### 1. User Configuration
This section creates a default user with specific permissions and configurations:
- Creates a user named **`cocos_user`**.
- Adds `cocos_user` to the `sudo` and `docker` groups.
- Sets a default password (should be changed for production use).
- Configures the users shell as `/bin/bash`.
### 2. Package Installation
Installs essential system packages required for various operations:
- **`curl`**: For downloading files from the web.
- **`make`**: A utility for building software.
- **`git`**: Version control system for managing code repositories.
- **`python3` and `python3-dev`**: Required for running Python-based tools.
- **`net-tools`**: Provides networking utilities such as `ifconfig` and `route`.
### 3. File Management (write_files)
Creates and configures critical files required for the setup:
- **Certificates**: Cert files (`cert.pem`, `ca.pem`, `key.pem`) located at `/etc/cocos/certs/`.
- **Environment Variables**: An env file stored at `/etc/cocos/environment`.
- **Systemd Service File**: Cocos agent service configuration file at `/etc/systemd/system/cocos-agent.service` for managing the Cocos agent.
- **Agent Scripts**:
- `agent_setup.sh`: Configures network interfaces and resizes the root filesystem.
- `agent_start_script.sh`: Sets up Docker and starts the Cocos agent.
### 4. Execution of Commands (runcmd)
A sequence of commands is executed to finalize the setup:
- Creates necessary directories: `/cocos`, `/cocos_init`, `/var/log/cocos`, `/etc/cocos`.
- Downloads and installs the Cocos agent binary.
- Installs **Wasmtime** and configures its environment variables.
- Installs **Docker** and adds `cocos_user` to the Docker group.
- Reloads systemd and enables the Cocos agent service.
## Running the Agent
To test the cloud-init configuration, execute the `qemu.sh` script to bring up a VM using QEMU:
```bash
sudo ./qemu.sh
```
**Important:** The script must be executed as root.
Once the QEMU boots the VM, the Cocos agent will run as a systemd service. The service is configured to start automatically on boot and restart in case of failure.
## Debugging and Monitoring
For troubleshooting and monitoring the Cocos agent service, use the following commands within the VM:
### Manually Start the Service
To manually start the agent service, execute:
```bash
sudo systemctl start cocos-agent.service
```
### Verify Service Status
To check if the service is running properly, use:
```bash
sudo systemctl status cocos-agent.service
```
### View Service Logs
To inspect logs generated by the agent service, execute:
```bash
journalctl -u cocos-agent.service
```
### Check Standard Output and Error Logs
To check logs stored in the system, use:
```bash
cat /var/log/cocos/agent.stdout.log
cat /var/log/cocos/agent.stderr.log
```
+340
View File
@@ -0,0 +1,340 @@
#cloud-config
package_update: true
package_upgrade: false
users:
- default
- name: cocos_user
gecos: Default User
groups:
- sudo
- docker # Add cocos user to the docker group
sudo:
- ALL=(ALL:ALL) ALL
shell: /sbin/nologin
- name: algo_user
gecos: Algorithm User
groups:
- docker # Add to docker group for container execution
shell: /bin/bash
sudo:
- /usr/bin/python3 # Allow running Python
- /home/algo_user/.wasmtime/bin/wasmtime # Allow running Wasmtime
- /usr/bin/docker # Allow running Docker commands
packages:
- curl
- make
- git
- python3
- python3-dev
- net-tools # Add net-tools to install the 'route' command
- iptables-persistent # Add iptables-persistent to save firewall rules
- sudo # Ensure sudo is installed
write_files:
- path: /etc/cocos/certs/cert.pem
content: |
# Add certificate content here
permissions: "0644"
- path: /etc/cocos/certs/ca.pem
content: |
# Add CA certificate content here
permissions: "0644"
- path: /etc/cocos/certs/key.pem
content: |
# Add private key content here
permissions: "0600"
- path: /etc/cocos/environment
content: |
# Add environment variables here
permissions: "0644"
- path: /etc/systemd/system/cocos-agent.service
content: |
[Unit]
Description=Cocos AI agent
After=network.target
Before=docker.service
[Service]
WorkingDirectory=/cocos
StandardOutput=file:/var/log/cocos/agent.stdout
StandardError=file:/var/log/cocos/agent.stderr
EnvironmentFile=/etc/cocos/environment
ExecStartPre=/cocos_init/agent_setup.sh
ExecStart=/cocos_init/agent_start_script.sh
Restart=always
[Install]
WantedBy=default.target
permissions: "0644"
# Agent setup script
- path: /cocos_init/agent_setup.sh
content: |
#!/bin/sh
WORK_DIR="/cocos"
# IFACES are all network interfaces excluding lo (LOOPBACK) and sit interfaces
IFACES=$(ip link show | grep -vE 'LOOPBACK|sit*' | awk -F': ' '{print $2}')
# This for loop brings up all network interfaces in IFACES and dhclient obtains an IP address for the every interface
for IFACE in $IFACES; do
STATE=$(ip link show $IFACE | grep DOWN)
if [ -n "$STATE" ]; then
ip link set $IFACE up
fi
IP_ADDR=$(ip addr show $IFACE | grep 'inet ')
if [ -z "$IP_ADDR" ]; then
dhclient $IFACE
fi
done
if [ ! -d "$WORK_DIR" ]; then
mkdir -p $WORK_DIR
fi
# Resize the root filesystem to 100% of available space
ROOT_DEV=$(findmnt / -o SOURCE -n) # Get the root filesystem device
resize2fs "$ROOT_DEV" && echo "Root filesystem resized successfully" || echo "Failed to resize root filesystem"
permissions: "0755"
# Agent start script
- path: /cocos_init/agent_start_script.sh
content: |
#!/bin/sh
# Change the docker.service file to allow Docker to run in RAM
mkdir -p /etc/systemd/system/docker.service.d
# Create or overwrite the override.conf file with the new Environment variable
tee /etc/systemd/system/docker.service.d/override.conf > /dev/null <<EOF
[Service]
Environment=DOCKER_RAMDISK=true
EOF
systemctl daemon-reload
NUM_OF_PERMITED_IFACE=1
NUM_OF_IFACE=$(ip route | grep -Eo 'dev [a-z0-9]+' | awk '{ print $2 }' | grep -v '^docker' | sort | uniq | wc -l)
if [ $NUM_OF_IFACE -gt $NUM_OF_PERMITED_IFACE ]; then
echo "More than one network interface in the VM"
exit 1
fi
DEFAULT_IFACE=$(route | grep '^default' | grep -o '[^ ]*$')
AGENT_GRPC_HOST=$(ip -4 addr show $DEFAULT_IFACE | grep inet | awk '{print $2}' | cut -d/ -f1)
export AGENT_GRPC_HOST
exec /bin/cocos-agent
permissions: "0755"
# Firewall configuration script
- path: /cocos_init/setup_firewall.sh
content: |
#!/bin/bash
# Flush existing rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
# Set default policies to DROP
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback interface
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Allow established and related connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow port 7002 (TCP) for incoming connections
iptables -A INPUT -p tcp --dport 7002 -j ACCEPT
# Save rules to persist after reboot
mkdir -p /etc/iptables
iptables-save > /etc/iptables/rules.v4
echo "Firewall configured to allow only port 7002"
permissions: "0755"
# Setup script for algo_user
- path: /cocos_init/setup_algo_user.sh
content: |
#!/bin/bash
# Create datasets directory with the right permissions
mkdir -p /datasets
chown root:root /datasets
chmod 755 /datasets
# Create a home directory for algo_user
mkdir -p /home/algo_user
chown algo_user:algo_user /home/algo_user
chmod 750 /home/algo_user
# Set up Wasmtime for algo_user
mkdir -p /home/algo_user/.wasmtime/bin
# Link system Wasmtime to user's directory
ln -s /root/.wasmtime/bin/wasmtime /home/algo_user/.wasmtime/bin/wasmtime
chown -R algo_user:algo_user /home/algo_user/.wasmtime
# Configure sudoers for algo_user
cat > /etc/sudoers.d/algo_user << EOF
algo_user ALL=(ALL) NOPASSWD: /usr/bin/python3
algo_user ALL=(ALL) NOPASSWD: /home/algo_user/.wasmtime/bin/wasmtime
algo_user ALL=(ALL) NOPASSWD: /usr/bin/docker
Defaults:algo_user !requiretty
EOF
chmod 440 /etc/sudoers.d/algo_user
# Create a .bashrc file that restricts navigation to /datasets
cat > /home/algo_user/.bashrc << EOF
# .bashrc for algo_user
# Restrict navigation outside /datasets
function cd() {
if [[ "\$1" == /* && "\$1" != "/datasets"* && "\$1" != "/home/algo_user"* ]]; then
echo "Access denied: You can only access files in /datasets or your home directory"
return 1
else
builtin cd "\$1"
fi
}
# Set PATH to include necessary binaries
export PATH=/usr/bin:/bin:/usr/local/bin:/home/algo_user/.wasmtime/bin
# Set default directory
if [[ \$PWD != "/datasets"* && \$PWD != "/home/algo_user"* ]]; then
cd /datasets
fi
EOF
chown algo_user:algo_user /home/algo_user/.bashrc
chmod 644 /home/algo_user/.bashrc
echo "algo_user setup completed successfully"
permissions: "0755"
# IMA setup for first boot
- path: /cocos_init/linux_ima_init.sh
content: |
#!/bin/bash
GRUB_FILE="/etc/default/grub"
if ! grep -q '^GRUB_CMDLINE_LINUX=.*ima_policy' "$GRUB_FILE"; then
echo "First boot, rebooting with fix IMA appraise policy"
NEW_PARAMS='ima_policy=tcb'
# Modify the GRUB_CMDLINE_LINUX line
sed -i -E \
"s#^(GRUB_CMDLINE_LINUX=\")(.*)(\")#\1\2 $NEW_PARAMS\3#" \
"$GRUB_FILE"
echo "Updated GRUB_CMDLINE_LINUX:"
grep "^GRUB_CMDLINE_LINUX=" "$GRUB_FILE"
sudo update-grub
sudo reboot
else
sudo find / -fstype ext4 -type f -uid 0 -exec dd if='{}' of=/dev/null count=0 status=none \;
fi
permissions: "0755"
runcmd:
# Enable Linux IMA with fix policy
- echo "Enable Linux IMA with fix policy and rebooting if necessary"
- sh /cocos_init/linux_ima_init.sh
# Create necessary directories
- mkdir -p /cocos
- mkdir -p /cocos_init
- mkdir -p /var/log/cocos
- mkdir -p /etc/cocos
# Download the cocos-agent binary
- echo "[ COCOS AGENT SETUP ] Downloading the cocos-agent binary..."
- curl -L -O -J https://github.com/smithjilks/cocos/releases/download/v1.0.0/cocos-agent --progress-bar && echo "[ COCOS AGENT SETUP ] cocos-agent binary downloaded successfully" || echo "Failed to download cocos-agent binary"
# Install the agent binary
- echo "[ COCOS AGENT SETUP ] Installing cocos-agent binary..."
- install -D -m 0755 cocos-agent /bin/cocos-agent && echo "[ COCOS AGENT SETUP ] cocos-agent binary installed successfully" || echo "[ COCOS AGENT SETUP ] Failed to install cocos-agent binary"
# Install Wasmtime
- echo "Installing Wasmtime runtime..."
- curl https://wasmtime.dev/install.sh -sSf | bash && echo "Wasmtime installed successfully" || echo "Failed to install Wasmtime"
- echo "Configuring Wasmtime environment variables..."
- echo "export WASMTIME_HOME=$HOME/.wasmtime" >> /etc/profile.d/wasm_env.sh
- echo "export PATH=\$WASMTIME_HOME/bin:\$PATH" >> /etc/profile.d/wasm_env.sh
- . /etc/profile.d/wasm_env.sh && echo "Wasmtime environment variables configured successfully" || echo "Failed to configure Wasmtime environment variables"
# Install Docker
- echo "Starting Docker installation..."
- curl -fsSL https://get.docker.com -o get-docker.sh && echo "Docker install script downloaded successfully" || echo "Failed to download Docker install script"
- sh ./get-docker.sh && echo "Docker installed successfully" || echo "Failed to install Docker"
- usermod -aG docker cocos_user && echo "Added cocos_user to the docker group" || echo "Failed to add cocos_user to the docker group"
# Disable SSH service
- echo "Disabling SSH service..."
- systemctl disable ssh.service && echo "SSH service disabled successfully" || echo "Failed to disable SSH service"
- systemctl disable sshd.service && echo "SSHD service disabled successfully" || echo "Failed to disable SSHD service"
- systemctl stop ssh.service && echo "SSH service stopped successfully" || echo "Failed to stop SSH service"
- systemctl stop sshd.service && echo "SSHD service stopped successfully" || echo "Failed to stop SSHD service"
# Completely remove SSH server
- echo "Removing SSH server..."
- apt-get purge -y openssh-server && echo "SSH server removed successfully" || echo "Failed to remove SSH server"
- apt-get autoremove -y && echo "Removed dependencies successfully" || echo "Failed to remove dependencies"
# Set up firewall to only allow port 7002
- echo "Setting up firewall..."
- bash /cocos_init/setup_firewall.sh && echo "Firewall configured successfully" || echo "Failed to configure firewall"
# Ensure iptables rules persist after reboot
- echo "Ensuring iptables rules persist after reboot..."
- systemctl enable netfilter-persistent && echo "netfilter-persistent service enabled successfully" || echo "Failed to enable netfilter-persistent service"
- systemctl start netfilter-persistent && echo "netfilter-persistent service started successfully" || echo "Failed to start netfilter-persistent service"
# Set up algo_user environment
- echo "Setting up algo_user environment..."
- bash /cocos_init/setup_algo_user.sh && echo "algo_user environment setup successfully" || echo "Failed to set up algo_user environment"
# Create datasets directory and set permissions
- echo "Creating datasets directory..."
- mkdir -p /datasets && echo "datasets directory created successfully" || echo "Failed to create datasets directory"
- chown root:root /datasets && echo "datasets ownership set successfully" || echo "Failed to set datasets ownership"
- chmod 755 /datasets && echo "datasets permissions set successfully" || echo "Failed to set datasets permissions"
# Add algo_user to the docker group
- echo "Adding algo_user to the docker group..."
- usermod -aG docker algo_user && echo "Added algo_user to the docker group" || echo "Failed to add algo_user to the docker group"
# Reload systemd and enable the service
- echo "[ COCOS AGENT SETUP ] Reloading systemd daemon..."
- systemctl daemon-reload && echo "[ COCOS AGENT SETUP ] Systemd daemon reloaded successfully" || echo "[ COCOS AGENT SETUP ] Failed to reload systemd daemon"
- echo "[ COCOS AGENT SETUP ] Enabling cocos-agent.service..."
- systemctl enable cocos-agent.service && echo "[ COCOS AGENT SETUP ] cocos-agent.service enabled successfully" || echo "[ COCOS AGENT SETUP ] Failed to enable cocos-agent.service"
- echo "[ COCOS AGENT SETUP ] Starting cocos-agent.service..."
- systemctl start cocos-agent.service && echo "[ COCOS AGENT SETUP ] cocos-agent.service started successfully" || echo "[ COCOS AGENT SETUP ] Failed to start cocos-agent.service"
final_message: "Cocos agent and algo_user setup complete. Verify logs to confirm successful service startup."
+2
View File
@@ -0,0 +1,2 @@
instance-id: iid-cocos-vm
local-hostname: cocos-vm
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
# Source environment variables
source ./.env
# Required commands
REQUIRED_CMDS=("wget" "cloud-localds" "$QEMU_BINARY" "qemu-img")
# Check for required commands
for cmd in "${REQUIRED_CMDS[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
echo "Error: $cmd is not installed. Please install it and try again."
exit 1
fi
done
# Ensure script is run as root
if [[ $EUID -ne 0 ]]; then
echo "Error: This script must be run as root."
exit 1
fi
# Create the root filesystem image if it doesn't exist
if [ ! -f "$BASE_IMAGE" ]; then
echo "Downloading base Ubuntu image..."
wget -q "$BASE_IMAGE_URL" -O "$BASE_IMAGE" --show-progress
fi
# Create custom image
echo "Creating custom QEMU image..."
qemu-img create -f qcow2 -b "$BASE_IMAGE" -F qcow2 "$CUSTOM_IMAGE" "$DISK_SIZE"
# Cloud-init configuration files
CLOUD_CONFIG="config.yaml"
META_DATA="meta-data"
SEED_IMAGE="seed.img"
# Create seed image for cloud-init
echo "Creating seed image..."
cloud-localds "$SEED_IMAGE" "$CLOUD_CONFIG" "$META_DATA"
# Construct QEMU arguments from environment variables
construct_qemu_args() {
args=()
args+=("-name" "$VM_NAME")
# Virtualization (Enable KVM)
if [ "$ENABLE_KVM" == "true" ]; then
args+=("-enable-kvm")
fi
# Machine, CPU, RAM
if [ -n "$MACHINE" ]; then
args+=("-machine" "$MACHINE")
fi
if [ -n "$CPU" ]; then
args+=("-cpu" "$CPU")
fi
args+=("-boot" "d")
args+=("-smp" "$SMP_COUNT,maxcpus=$SMP_MAXCPUS")
args+=("-m" "$MEMORY_SIZE,slots=$MEMORY_SLOTS,maxmem=$MAX_MEMORY")
# OVMF (if applicable)
if [ "$ENABLE_SEV_SNP" != "true" ]; then
args+=("-drive" "if=$OVMF_CODE_IF,format=$OVMF_CODE_FORMAT,unit=$OVMF_CODE_UNIT,file=$OVMF_CODE,readonly=$OVMF_CODE_READONLY")
args+=("-drive" "if=$OVMF_VARS_IF,format=$OVMF_VARS_FORMAT,unit=$OVMF_VARS_UNIT,file=$OVMF_VARS")
fi
# Network configuration
args+=("-netdev" "user,id=$NET_DEV_ID,hostfwd=tcp::$NET_DEV_HOST_FWD_AGENT-:$NET_DEV_GUEST_FWD_AGENT")
args+=("-device" "virtio-net-pci,disable-legacy=$VIRTIO_NET_PCI_DISABLE_LEGACY,iommu_platform=$VIRTIO_NET_PCI_IOMMU_PLATFORM,netdev=$NET_DEV_ID,addr=$VIRTIO_NET_PCI_ADDR,romfile=$VIRTIO_NET_PCI_ROMFILE")
args+=("-device" "vhost-vsock-pci,id=$VSOCK_ID,guest-cid=$VSOCK_GUEST_CID")
# SEV_SNP (if enabled)
if [ "$ENABLE_SEV_SNP" == "true" ]; then
kernel_hash=""
host_data=""
args+=("-machine" "confidential-guest-support=$SEV_SNP_ID,memory-backend=$MEM_ID")
args+=("-bios" "$OVMF_CODE_FILE")
sev_snp_type="sev-snp-guest"
if [ -n "$SEV_SNP_HOST_DATA" ]; then
host_data=",host-data=$SEV_SNP_HOST_DATA"
fi
if [ "$ENABLE_KERNEL_HASH" == "true" ]; then
kernel_hash=",kernel-hashes=on"
fi
args+=("-object" "memory-backend-memfd,id=$MEM_ID,size=$MEMORY_SIZE,share=true,prealloc=false")
args+=("-object" "$sev_snp_type,id=$SEV_SNP_ID,cbitpos=$SEV_SNP_CBIT_POS,reduced-phys-bits=$SEV_SNP_REDUCED_PHYS_BITS$kernel_hash$host_data")
fi
# Disk image configuration
args+=("-drive" "file=$SEED_IMAGE,media=cdrom")
args+=("-drive" "file=$CUSTOM_IMAGE,if=none,id=disk0,format=qcow2")
args+=("-device" "virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true")
args+=("-device" "scsi-hd,drive=disk0")
# Display options
if [ "$NO_GRAPHIC" == "true" ]; then
args+=("-nographic")
fi
args+=("-monitor" "$MONITOR")
args+=("-vnc" ":9")
echo "${args[@]}"
}
qemu_args=$(construct_qemu_args)
echo "Running QEMU with the following arguments: $qemu_args"
echo "Starting QEMU VM..."
$QEMU_BINARY $qemu_args
+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 2024.11-rc2
git checkout 2025.05-rc1
make BR2_EXTERNAL=../cocos/hal/linux cocos_defconfig
# Execute 'make menuconfig' only if you want to make additional configuration changes to Buildroot.
make menuconfig
+44
View File
@@ -65,3 +65,47 @@ CONFIG_PREEMPT_DYNAMIC=n
CONFIG_DEBUG_PREEMPT=n
CONFIG_CGROUP_MISC=y
CONFIG_X86_CPUID=y
CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y
CONFIG_9P_FS=y
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_9P_FS_SECURITY=y
# TCG TPM
CONFIG_TCG_TPM=y
CONFIG_TCG_TPM2_HMAC=y
CONFIG_TCG_PLATFORM=y
# Linux IMA
CONFIG_SECURITY=y
CONFIG_SECURITYFS=y
CONFIG_INTEGRITY=y
CONFIG_INTEGRITY_SIGNATURE=y
CONFIG_IMA=y
CONFIG_IMA_MEASURE_PCR_IDX=10 # optional but good
CONFIG_IMA_LSM_RULES=y
CONFIG_IMA_APPRAISE=y # if you want appraisal (signature checking)
CONFIG_IMA_DEFAULT_TEMPLATE="ima-ng"
CONFIG_KEYS=y
CONFIG_ENCRYPTED_KEYS=y # if you plan to use encrypted keys
CONFIG_IMA_DEFAULT_HASH="sha256" # or sha1 if you prefer
#Intel TDX
CONFIG_EXPERT=y
CONFIG_VIRTUALIZATION=y
CONFIG_KVM=y
CONFIG_KVM_SW_PROTECTED_VM=y
CONFIG_KVM_INTEL=y
CONFIG_PCI_MSI=y
CONFIG_IRQ_REMAP=y
CONFIG_X86_X2APIC=y
CONFIG_CRYPTO_SHA256=y
CONFIG_X86_SGX=y
CONFIG_X86_SGX_KVM=y
CONFIG_INTEL_TDX_GUEST=y
CONFIG_KSM=n
CONFIG_EISA=n
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
CONFIG_TDX_GUEST_DRIVER=y
+18 -1
View File
@@ -6,6 +6,23 @@ 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\
sed -i '/GENERIC_SERIAL/a\
tty1::respawn:/sbin/getty -L tty1 0 vt100 # QEMU graphical window' ${TARGET_DIR}/etc/inittab
fi
# Create the mount points
# Create the mount points
mkdir -p ${TARGET_DIR}/etc/certs
mkdir -p ${TARGET_DIR}/etc/cocos
# Ensure /etc/fstab exists
if [ ! -f "${TARGET_DIR}/etc/fstab" ]; then
touch "${TARGET_DIR}/etc/fstab"
fi
# Add the 9p entries to /etc/fstab
grep -q "certs_share /etc/certs" ${TARGET_DIR}/etc/fstab || \
echo "certs_share /etc/certs 9p trans=virtio,version=9p2000.L,cache=mmap 0 0" >> "${TARGET_DIR}/etc/fstab"
grep -q "env_share /etc/cocos" ${TARGET_DIR}/etc/fstab || \
echo "env_share /etc/cocos 9p trans=virtio,version=9p2000.L,cache=mmap 0 0" >> "${TARGET_DIR}/etc/fstab"
+6 -4
View File
@@ -14,6 +14,8 @@ BR2_SYSTEM_BIN_SH_BASH=y
BR2_TARGET_ROOTFS_CPIO=y
BR2_TARGET_ROOTFS_CPIO_FULL=y
BR2_TARGET_ROOTFS_CPIO_GZIP=y
BR2_TARGET_ROOTFS_OVERLAY="overlay"
BR2_PACKAGE_9PFS=y
# Image
BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL_COCOS_PATH)/board/cocos/post-build.sh"
@@ -25,14 +27,14 @@ 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.12-rc6"
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/torvalds/linux.git"
BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="v6.12-rc6"
BR2_LINUX_KERNEL_VERSION="v6.12-rc6"
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"
+3 -4
View File
@@ -7,12 +7,11 @@ Before=docker.service
WorkingDirectory=/cocos
StandardOutput=file:/var/log/cocos/agent.stdout
StandardError=file:/var/log/cocos/agent.stderr
Environment=AGENT_GRPC_PORT=7002
Environment=AGENT_LOG_LEVEL=info
EnvironmentFile=/etc/cocos/environment
ExecStartPre=/cocos_init/agent_setup.sh
ExecStart=/cocos_init/agent_start_script.sh
Restart=always
RestartSec=5s
[Install]
WantedBy=default.target
+24 -5
View File
@@ -7,8 +7,9 @@ import (
"io"
"log/slog"
"github.com/ultravioletrs/cocos/agent/cvms"
"github.com/ultravioletrs/cocos/agent/events"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/timestamppb"
)
@@ -18,16 +19,17 @@ type handler struct {
opts slog.HandlerOptions
w io.Writer
cmpID string
queue chan *cvms.ClientStreamMessage
}
func NewProtoHandler(conn io.Writer, opts *slog.HandlerOptions, cmpID string) slog.Handler {
func NewProtoHandler(conn io.Writer, opts *slog.HandlerOptions, queue chan *cvms.ClientStreamMessage) slog.Handler {
if opts == nil {
opts = &slog.HandlerOptions{}
}
h := &handler{
opts: *opts,
w: conn,
cmpID: cmpID,
queue: queue,
}
return h
@@ -69,7 +71,18 @@ func (h *handler) Handle(_ context.Context, r slog.Record) error {
},
}
b, err := proto.Marshal(&agentLog)
h.queue <- &cvms.ClientStreamMessage{
Message: &cvms.ClientStreamMessage_AgentLog{
AgentLog: &cvms.AgentLog{
Timestamp: timestamp,
Message: chunk,
Level: level,
ComputationId: h.cmpID,
},
},
}
b, err := protojson.Marshal(&agentLog)
if err != nil {
return err
}
@@ -78,6 +91,11 @@ func (h *handler) Handle(_ context.Context, r slog.Record) error {
if err != nil {
return err
}
_, err = h.w.Write([]byte("\n"))
if err != nil {
return err
}
}
return nil
@@ -88,7 +106,8 @@ func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler {
}
func (h *handler) WithGroup(name string) slog.Handler {
panic("unimplemented")
h.cmpID = name
return h
}
func (h *handler) Close() error {
+6 -5
View File
@@ -11,6 +11,7 @@ import (
"github.com/absmach/magistrala/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/ultravioletrs/cocos/agent/cvms"
)
type failedWriter struct{}
@@ -21,14 +22,14 @@ func (f *failedWriter) Write(p []byte) (n int, err error) {
// TestNewProtoHandler tests the initialization of the ProtoHandler.
func TestNewProtoHandler(t *testing.T) {
handler := NewProtoHandler(io.Discard, nil, "testCmpID")
handler := NewProtoHandler(io.Discard, nil, make(chan *cvms.ClientStreamMessage))
assert.NotNil(t, handler, "Handler should not be nil")
}
// TestHandleMessageSuccess tests the handling of a message when the write succeeds.
func TestHandleMessageSuccess(t *testing.T) {
handler := NewProtoHandler(io.Discard, nil, "testCmpID")
handler := NewProtoHandler(io.Discard, nil, make(chan *cvms.ClientStreamMessage, 1))
record := slog.Record{
Time: time.Now(),
Message: "Test message",
@@ -42,7 +43,7 @@ func TestHandleMessageSuccess(t *testing.T) {
// TestHandleMessageFailure tests the caching mechanism when the write fails.
func TestHandleMessageFailure(t *testing.T) {
protohandler := NewProtoHandler(&failedWriter{}, nil, "testCmpID")
protohandler := NewProtoHandler(&failedWriter{}, nil, make(chan *cvms.ClientStreamMessage, 1))
record := slog.Record{
Time: time.Now(),
Message: "Test message",
@@ -56,7 +57,7 @@ func TestHandleMessageFailure(t *testing.T) {
// TestEnabled tests that the handler enables logging based on level.
func TestEnabled(t *testing.T) {
handler := NewProtoHandler(io.Discard, nil, "testCmpID")
handler := NewProtoHandler(io.Discard, nil, make(chan *cvms.ClientStreamMessage, 1))
assert.True(t, handler.Enabled(context.Background(), slog.LevelInfo), "Logging should be enabled for LevelInfo")
assert.False(t, handler.Enabled(context.Background(), slog.LevelDebug), "Logging should be disabled for LevelDebug by default")
@@ -66,7 +67,7 @@ func TestEnabled(t *testing.T) {
func TestCloseStopsRetry(t *testing.T) {
mockWriter := io.Discard
handler := NewProtoHandler(mockWriter, nil, "testCmpID").(*handler)
handler := NewProtoHandler(mockWriter, nil, make(chan *cvms.ClientStreamMessage, 1)).(*handler)
time.Sleep(2 * time.Second)
err := handler.Close()
+98 -96
View File
@@ -5,22 +5,16 @@ package grpc
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log/slog"
"math/big"
"net"
"os"
"strings"
"sync"
"time"
"github.com/google/go-sev-guest/client"
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
"github.com/ultravioletrs/cocos/agent/auth"
"github.com/ultravioletrs/cocos/internal/server"
@@ -49,18 +43,22 @@ const (
type Server struct {
server.BaseServer
mu sync.RWMutex
server *grpc.Server
registerService serviceRegister
quoteProvider client.QuoteProvider
authSvc auth.Authenticator
health *health.Server
registerService serviceRegister
authSvc auth.Authenticator
caUrl string
cvmId string
started bool
stopped bool
}
type serviceRegister func(srv *grpc.Server)
var _ server.Server = (*Server)(nil)
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.ServerConfiguration, registerService serviceRegister, logger *slog.Logger, qp client.QuoteProvider, authSvc auth.Authenticator) server.Server {
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.ServerConfiguration, registerService serviceRegister, logger *slog.Logger, authSvc auth.Authenticator, caUrl string, cvmId string) server.Server {
base := config.GetBaseConfig()
listenFullAddress := fmt.Sprintf("%s:%s", base.Host, base.Port)
return &Server{
@@ -73,12 +71,25 @@ func New(ctx context.Context, cancel context.CancelFunc, name string, config ser
Logger: logger,
},
registerService: registerService,
quoteProvider: qp,
authSvc: authSvc,
caUrl: caUrl,
cvmId: cvmId,
}
}
func (s *Server) Start() error {
s.mu.Lock()
if s.started {
s.mu.Unlock()
return fmt.Errorf("server already started")
}
if s.stopped {
s.mu.Unlock()
return fmt.Errorf("server already stopped")
}
s.started = true
s.mu.Unlock()
errCh := make(chan error)
grpcServerOptions := []grpc.ServerOption{
grpc.StatsHandler(otelgrpc.NewServerHandler()),
@@ -91,37 +102,57 @@ func (s *Server) Start() error {
}
creds := grpc.Creds(insecure.NewCredentials())
var listener net.Listener
c := s.Config.GetBaseConfig()
if agCfg, ok := s.Config.(server.AgentConfig); ok && agCfg.AttestedTLS {
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS()
if err != nil {
return fmt.Errorf("failed to create certificate: %w", err)
}
certificate, err := tls.X509KeyPair(certificateBytes, privateKeyBytes)
if err != nil {
return fmt.Errorf("falied due to invalid key pair: %w", err)
}
tlsConfig := &tls.Config{
ClientAuth: tls.NoClientCert,
Certificates: []tls.Certificate{certificate},
ClientAuth: tls.NoClientCert,
GetCertificate: atls.GetCertificate(s.caUrl, s.cvmId),
}
var mtls bool
mtls = false
// Loading Server CA file
rootCA, err := loadCertFile(c.ServerCAFile)
if err != nil {
return fmt.Errorf("failed to load server ca file: %w", err)
}
if len(rootCA) > 0 {
if tlsConfig.RootCAs == nil {
tlsConfig.RootCAs = x509.NewCertPool()
}
if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCA) {
return fmt.Errorf("failed to append server ca to tls.Config")
}
mtls = true
}
// Loading Client CA File
clientCA, err := loadCertFile(c.ClientCAFile)
if err != nil {
return fmt.Errorf("failed to load client ca file: %w", err)
}
if len(clientCA) > 0 {
if tlsConfig.ClientCAs == nil {
tlsConfig.ClientCAs = x509.NewCertPool()
}
if !tlsConfig.ClientCAs.AppendCertsFromPEM(clientCA) {
return fmt.Errorf("failed to append client ca to tls.Config")
}
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
mtls = true
}
creds = grpc.Creds(credentials.NewTLS(tlsConfig))
listener, err = atls.Listen(
s.Address,
certificateBytes,
privateKeyBytes,
)
if err != nil {
return fmt.Errorf("failed to create Listener for aTLS: %w", err)
if mtls {
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with Attested mTLS", s.Name, s.Address))
} else {
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with Attested TLS", s.Name, s.Address))
}
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with Attested TLS", s.Name, s.Address))
} else {
c := s.Config.GetBaseConfig()
switch {
case c.CertFile != "" || c.KeyFile != "":
certificate, err := loadX509KeyPair(c.CertFile, c.KeyFile)
@@ -172,32 +203,34 @@ func (s *Server) Start() error {
default:
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with TLS cert %s and key %s", s.Name, s.Address, c.CertFile, c.KeyFile))
}
listener, err = net.Listen("tcp", s.Address)
if err != nil {
return fmt.Errorf("failed to listen on port %s: %w", s.Address, err)
}
default:
var err error
listener, err = net.Listen("tcp", s.Address)
if err != nil {
return fmt.Errorf("failed to listen on port %s: %w", s.Address, err)
}
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s without TLS", s.Name, s.Address))
}
}
listener, err := net.Listen("tcp", s.Address)
if err != nil {
return fmt.Errorf("failed to listen on port %s: %w", s.Address, err)
}
grpcServerOptions = append(grpcServerOptions, creds)
s.mu.Lock()
s.server = grpc.NewServer(grpcServerOptions...)
s.health = health.NewServer()
grpchealth.RegisterHealthServer(s.server, s.health)
s.registerService(s.server)
s.health.SetServingStatus(s.Name, grpchealth.HealthCheckResponse_SERVING)
s.mu.Unlock()
go func() {
errCh <- s.server.Serve(listener)
s.mu.RLock()
server := s.server
s.mu.RUnlock()
if server != nil {
errCh <- server.Serve(listener)
}
}()
select {
@@ -210,19 +243,33 @@ func (s *Server) Start() error {
}
func (s *Server) Stop() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.stopped {
return nil
}
s.stopped = true
defer s.Cancel()
c := make(chan bool)
go func() {
defer close(c)
s.health.Shutdown()
s.server.GracefulStop()
if s.health != nil {
s.health.Shutdown()
}
if s.server != nil {
s.server.GracefulStop()
}
}()
select {
case <-c:
case <-time.After(stopWaitTime):
}
s.Logger.Info(fmt.Sprintf("%s gRPC service shutdown at %s", s.Name, s.Address))
s.Logger.Info(fmt.Sprintf("%s gRPC service shutdown at %s", s.Name, s.Address))
return nil
}
@@ -238,6 +285,8 @@ func readFileOrData(input string) ([]byte, error) {
data, err := os.ReadFile(input)
if err == nil {
return data, nil
} else {
return nil, err
}
}
return []byte(input), nil
@@ -256,50 +305,3 @@ func loadX509KeyPair(certfile, keyfile string) (tls.Certificate, error) {
return tls.X509KeyPair(cert, key)
}
func generateCertificatesForATLS() ([]byte, []byte, error) {
curve := elliptic.P256()
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate private/public key: %w", err)
}
certTemplate := &x509.Certificate{
SerialNumber: big.NewInt(202403311),
Subject: pkix.Name{
Organization: []string{organization},
Country: []string{country},
Province: []string{province},
Locality: []string{locality},
StreetAddress: []string{streetAddress},
PostalCode: []string{postalCode},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(notAfterYear, notAfterMonth, notAfterDay),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
certDERBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, &privateKey.PublicKey, privateKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to create certificate: %w", err)
}
certBytes := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certDERBytes,
})
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal the private key: %w", err)
}
keyBytes := pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: privateKeyBytes,
})
return certBytes, keyBytes, nil
}
+46 -56
View File
@@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert"
authmocks "github.com/ultravioletrs/cocos/agent/auth/mocks"
"github.com/ultravioletrs/cocos/internal/server"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
)
@@ -47,10 +47,9 @@ func TestNew(t *testing.T) {
},
}
logger := slog.Default()
qp := new(mocks.QuoteProvider)
authSvc := new(authmocks.Authenticator)
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, authSvc, "", "")
assert.NotNil(t, srv)
assert.IsType(t, &Server{}, srv)
@@ -97,10 +96,9 @@ func TestServerStartWithTLSFile(t *testing.T) {
logBuffer := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.QuoteProvider)
authSvc := new(authmocks.Authenticator)
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, authSvc, "", "")
var wg sync.WaitGroup
wg.Add(1)
@@ -144,10 +142,9 @@ func TestServerStartWithmTLSFile(t *testing.T) {
logBuffer := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.QuoteProvider)
authSvc := new(authmocks.Authenticator)
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, authSvc, "", "")
var wg sync.WaitGroup
wg.Add(1)
@@ -184,10 +181,9 @@ func TestServerStop(t *testing.T) {
}
buf := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.QuoteProvider)
authSvc := new(authmocks.Authenticator)
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, authSvc, "", "")
go func() {
err := srv.Start()
@@ -259,6 +255,8 @@ func (b *ThreadSafeBuffer) String() string {
}
func TestServerInitializationAndStartup(t *testing.T) {
vtpm.ExternalTPM = &vtpm.DummyRWC{}
testCases := []struct {
name string
config server.AgentConfig
@@ -307,60 +305,53 @@ func TestServerInitializationAndStartup(t *testing.T) {
expectedLog: "failed to load auth certificates",
},
{
name: "mTLS Server Startup",
config: server.AgentConfig{
ServerConfig: server.ServerConfig{
BaseConfig: server.BaseConfig{
Host: "localhost",
Port: "0",
},
},
},
setupCallback: setupMTLSConfig,
expectedLog: "TestServer service gRPC server listening at localhost:0 with TLS",
},
{
name: "mTLS Server Startup with Invalid Root CA",
name: "maTLS Server Startup",
config: server.AgentConfig{
ServerConfig: server.ServerConfig{
BaseConfig: server.BaseConfig{
Host: "localhost",
Port: "0",
ServerCAFile: "invalid",
},
},
},
setupCallback: setupInvalidRootCAConfig,
expectError: true,
expectedLog: "failed to append root ca to tls.Config",
},
{
name: "mTLS Server Startup with Invalid Client CA",
config: server.AgentConfig{
ServerConfig: server.ServerConfig{
BaseConfig: server.BaseConfig{
Host: "localhost",
Port: "0",
ServerCAFile: "invalid",
},
},
},
setupCallback: setupInvalidClientCAConfig,
expectError: true,
expectedLog: "failed to append client ca to tls.Config",
},
{
name: "Attested TLS Server Startup",
config: server.AgentConfig{
ServerConfig: server.ServerConfig{
BaseConfig: server.BaseConfig{
Host: "localhost",
Port: "0",
ServerCAFile: "",
ClientCAFile: "",
},
},
AttestedTLS: true,
},
expectedLog: "TestServer service gRPC server listening at localhost:0 with Attested TLS",
setupCallback: setupMTLSConfig,
expectError: false,
expectedLog: "with Attested mTLS",
},
{
name: "maTLS Server Startup with Invalid Server CA file",
config: server.AgentConfig{
ServerConfig: server.ServerConfig{
BaseConfig: server.BaseConfig{
Host: "localhost",
Port: "0",
ServerCAFile: "invalid",
},
},
AttestedTLS: true,
},
setupCallback: setupInvalidRootCAConfig,
expectError: true,
expectedLog: "failed to load server ca file",
},
{
name: "maTLS Server Startup with Invalid Clinet CA file",
config: server.AgentConfig{
ServerConfig: server.ServerConfig{
BaseConfig: server.BaseConfig{
Host: "localhost",
Port: "0",
ServerCAFile: "invalid",
},
},
AttestedTLS: true,
},
setupCallback: setupInvalidClientCAConfig,
expectError: true,
expectedLog: "failed to load client ca file",
},
}
@@ -374,10 +365,9 @@ func TestServerInitializationAndStartup(t *testing.T) {
logBuffer := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.QuoteProvider)
authSvc := new(authmocks.Authenticator)
srv := New(ctx, cancel, "TestServer", tc.config, func(srv *grpc.Server) {}, logger, qp, authSvc)
srv := New(ctx, cancel, "TestServer", tc.config, func(srv *grpc.Server) {}, logger, authSvc, "", "")
var wg sync.WaitGroup
wg.Add(1)

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