Compare commits

..

50 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
148 changed files with 17605 additions and 5675 deletions
+1
View File
@@ -9,6 +9,7 @@ on:
- "pkg/manager/*.pb.go"
- "agent/agent.proto"
- "agent/*.pb.go"
pull_request:
branches:
- main
+1 -1
View File
@@ -56,7 +56,7 @@ jobs:
with:
repository: "buildroot/buildroot"
path: buildroot
ref: 2024.11-rc2
ref: 2025.05-rc1
- name: Build hal
run: |
+4 -3
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
@@ -35,7 +36,7 @@ jobs:
mkdir coverage
- name: Run Agent tests
run: go test --tags embed -v --race -covermode=atomic -coverprofile coverage/agent.out ./agent/...
run: go test -v --race -covermode=atomic -coverprofile coverage/agent.out ./agent/...
- name: Run cli tests
run: go test -v --race -covermode=atomic -coverprofile coverage/cli.out ./cli/...
+83 -67
View File
@@ -1,84 +1,100 @@
run:
timeout: 3m
issues:
max-issues-per-linter: 10
max-same-issues: 10
exclude-rules:
- linters:
- makezero
text: "with non-zero initialized length"
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
- 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$
+1 -1
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)
+2
View File
@@ -76,3 +76,5 @@ Cocos AI is published under the permissive open-source [Apache-2.0](LICENSE) lic
- [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) | 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 | "" |
| 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
+242 -25
View File
@@ -384,6 +384,190 @@ 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 = string([]byte{
@@ -412,7 +596,21 @@ var file_agent_agent_proto_rawDesc = string([]byte{
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, 0x32, 0xfd, 0x01, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74,
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,
@@ -428,7 +626,18 @@ var file_agent_agent_proto_rawDesc = string([]byte{
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,
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,
})
@@ -444,31 +653,39 @@ func file_agent_agent_proto_rawDescGZIP() []byte {
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() }
@@ -482,7 +699,7 @@ func file_agent_agent_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
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,
},
+18
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 {
@@ -44,3 +46,19 @@ message AttestationRequest {
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;
}
+85 -5
View File
@@ -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",
}
+4
View File
@@ -57,6 +57,10 @@ func (b *binary) Stop() error {
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)
}
+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
}
+4
View File
@@ -114,6 +114,10 @@ func (p *python) Stop() error {
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)
}
+4
View File
@@ -63,6 +63,10 @@ func (w *wasm) Stop() error {
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)
}
+32 -2
View File
@@ -7,7 +7,7 @@ import (
"github.com/go-kit/kit/endpoint"
"github.com/ultravioletrs/cocos/agent"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation"
)
func algoEndpoint(svc agent.Service) endpoint.Endpoint {
@@ -71,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.TeeNonce, req.VtpmNonce, config.AttestationType(req.AttType))
file, err := svc.Attestation(ctx, req.TeeNonce, req.VtpmNonce, attestation.PlatformType(req.AttType))
if err != nil {
return attestationRes{}, err
}
@@ -79,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
}
}
+56 -3
View File
@@ -7,9 +7,10 @@ import (
"errors"
"testing"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/mocks"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation"
"golang.org/x/crypto/sha3"
)
@@ -142,11 +143,11 @@ func TestAttestationEndpoint(t *testing.T) {
}{
{
name: "Success",
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: config.SNP},
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: attestation.SNP},
},
{
name: "Service Error",
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: config.SNP},
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: attestation.SNP},
expectedErr: true,
},
}
@@ -172,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)
})
}
}
+25 -5
View File
@@ -5,7 +5,7 @@ package grpc
import (
"errors"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
@@ -44,14 +44,34 @@ func (req resultReq) validate() error {
type attestationReq struct {
TeeNonce [quoteprovider.Nonce]byte
VtpmNonce [vtpm.Nonce]byte
AttType config.AttestationType
AttType attestation.PlatformType
}
type FetchAttestationResultReq struct {
tokenNonce [vtpm.Nonce]byte
AttType attestation.PlatformType
}
func (req attestationReq) validate() error {
switch req.AttType {
case config.SNP, config.VTPM, config.SNPvTPM:
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 in attestation request")
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"`
}
+306 -101
View File
@@ -8,10 +8,12 @@ import (
"errors"
"fmt"
"io"
"strconv"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/transport/grpc"
"github.com/ultravioletrs/cocos/agent"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"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"
@@ -25,49 +27,77 @@ const (
)
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")
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,
@@ -80,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,
@@ -102,22 +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)
var reportData [quoteprovider.Nonce]byte
var nonce [vtpm.Nonce]byte
if len(req.TeeNonce) > quoteprovider.Nonce {
return nil, ErrTEENonceLength
if err := validateNonce(req.TeeNonce, quoteprovider.Nonce, &reportData); err != nil {
return nil, err
}
if len(req.VtpmNonce) > vtpm.Nonce {
return nil, ErrVTpmNonceLength
if err := validateNonce(req.VtpmNonce, vtpm.Nonce, &nonce); err != nil {
return nil, err
}
copy(reportData[:], req.TeeNonce)
copy(nonce[:], req.VtpmNonce)
return attestationReq{TeeNonce: reportData, VtpmNonce: nonce, AttType: config.AttestationType(req.Type)}, nil
return attestationReq{
TeeNonce: reportData,
VtpmNonce: nonce,
AttType: attestation.PlatformType(req.Type),
}, nil
}
func encodeAttestationResponse(_ context.Context, response interface{}) (interface{}, error) {
@@ -127,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
}
+303 -18
View File
@@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/mocks"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"google.golang.org/grpc"
@@ -68,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 {
@@ -92,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) {
@@ -102,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)
@@ -114,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)
@@ -136,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)
@@ -151,18 +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)
// 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.AttestationResponse) bool {
return len(resp.File) > 0
})).Return(nil).Once()
reportData := [quoteprovider.Nonce]byte{}
vtpmNonce := [vtpm.Nonce]byte{}
attestationType := config.SNP
mockService.On("Attestation", mock.Anything, reportData, vtpmNonce, attestationType).Return([]byte("attestation data"), nil)
attestationType := attestation.SNP
mockService.On("Attestation", mock.Anything, reportData, vtpmNonce, attestationType).Return(attestationData, nil)
err := server.Attestation(&agent.AttestationRequest{TeeNonce: reportData[:]}, mockStream)
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) {
@@ -204,11 +388,38 @@ func TestEncodeResultResponse(t *testing.T) {
}
func TestDecodeAttestationRequest(t *testing.T) {
nonce := [quoteprovider.Nonce]byte{}
req := &agent.AttestationRequest{TeeNonce: nonce[:]}
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{TeeNonce: nonce}, 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) {
@@ -216,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)
}
+29 -3
View File
@@ -13,7 +13,7 @@ import (
"time"
"github.com/ultravioletrs/cocos/agent"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
@@ -34,7 +34,7 @@ func LoggingMiddleware(svc agent.Service, logger *slog.Logger) 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.Info(message)
lm.logger.Debug(message)
}(time.Now())
return lm.svc.State()
}
@@ -106,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 [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) (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 {
@@ -118,3 +118,29 @@ func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [quotep
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)
}
+20 -2
View File
@@ -12,7 +12,7 @@ import (
"github.com/go-kit/kit/metrics"
"github.com/ultravioletrs/cocos/agent"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
@@ -92,7 +92,7 @@ func (ms *metricsMiddleware) Result(ctx context.Context) ([]byte, error) {
return ms.svc.Result(ctx)
}
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]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())
@@ -100,3 +100,21 @@ func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [quotep
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
+33 -3
View File
@@ -4,6 +4,7 @@ package grpc
import (
"context"
"encoding/json"
"log/slog"
"sync"
"time"
@@ -13,6 +14,9 @@ import (
"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"
)
@@ -42,11 +46,12 @@ type CVMSClient struct {
runReqManager *runRequestManager
sp server.AgentServer
storage storage.Storage
reconnectFn func(context.Context) (cvms.Service_ProcessClient, error)
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) (cvms.Service_ProcessClient, error)) (*CVMSClient, error) {
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
@@ -61,6 +66,7 @@ func NewClient(stream cvms.Service_ProcessClient, svc agent.Service, messageQueu
sp: sp,
storage: store,
reconnectFn: reconnectFn,
grpcClient: grpcClient,
}, nil
}
@@ -74,7 +80,7 @@ func (client *CVMSClient) Process(ctx context.Context, cancel context.CancelFunc
client.logger.Info("Connection lost, attempting to reconnect...", "error", err)
time.Sleep(reconnectInterval)
stream, err := client.reconnectFn(ctx)
grpcClient, stream, err := client.reconnectFn(ctx)
if err != nil {
client.logger.Error("Failed to reconnect", "error", err)
continue
@@ -82,6 +88,7 @@ func (client *CVMSClient) Process(ctx context.Context, cancel context.CancelFunc
client.mu.Lock()
client.stream = stream
client.grpcClient = grpcClient
client.mu.Unlock()
}
}
@@ -172,6 +179,13 @@ func (client *CVMSClient) processIncomingMessage(ctx context.Context, req *cvms.
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
}
@@ -238,6 +252,8 @@ func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.Computati
return
}
ccPlatform := attestation.CCPlatform()
client.mu.Lock()
defer client.mu.Unlock()
@@ -263,6 +279,20 @@ func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.Computati
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})
}
+56 -8
View File
@@ -13,6 +13,9 @@ import (
"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"
)
@@ -35,13 +38,13 @@ func (m *mockStream) Send(msg *cvms.ClientStreamMessage) error {
func TestManagerClient_Process(t *testing.T) {
tests := []struct {
name string
setupMocks func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer)
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) {
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{},
@@ -56,7 +59,7 @@ func TestManagerClient_Process(t *testing.T) {
},
{
name: "Run request chunks",
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer) {
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{},
@@ -67,9 +70,37 @@ func TestManagerClient_Process(t *testing.T) {
},
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) {
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer, grpcClient *clientmocks.Client) {
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{}, assert.AnError)
},
expectError: true,
@@ -88,13 +119,15 @@ func TestManagerClient_Process(t *testing.T) {
<-messageQueue
}()
client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, t.TempDir(), func(ctx context.Context) (cvms.Service_ProcessClient, error) { return nil, nil })
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)
tc.setupMocks(mockStream, mockSvc, mockServerSvc, grpcClient)
err = client.Process(ctx, cancel)
@@ -116,12 +149,26 @@ func TestManagerClient_handleRunReqChunks(t *testing.T) {
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) (cvms.Service_ProcessClient, error) { return nil, nil })
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)
@@ -167,8 +214,9 @@ func TestManagerClient_handleStopComputation(t *testing.T) {
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) (cvms.Service_ProcessClient, error) { return nil, nil })
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{
+3 -3
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
@@ -71,7 +71,7 @@ func (_c *Storage_Add_Call) RunAndReturn(run func(*cvms.ClientStreamMessage) err
return _c
}
// Clear provides a mock function with given fields:
// Clear provides a mock function with no fields
func (_m *Storage) Clear() error {
ret := _m.Called()
@@ -116,7 +116,7 @@ func (_c *Storage_Clear_Call) RunAndReturn(run func() error) *Storage_Clear_Call
return _c
}
// Load provides a mock function with given fields:
// Load provides a mock function with no fields
func (_m *Storage) Load() ([]storage.Message, error) {
ret := _m.Called()
+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)
}
+289 -138
View File
@@ -431,6 +431,7 @@ type ClientStreamMessage struct {
// *ClientStreamMessage_StopComputationRes
// *ClientStreamMessage_AgentStateRes
// *ClientStreamMessage_VTPMattestationReport
// *ClientStreamMessage_AzureAttestationResult
Message isClientStreamMessage_Message `protobuf_oneof:"message"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
@@ -527,6 +528,15 @@ func (x *ClientStreamMessage) GetVTPMattestationReport() *AttestationResponse {
return nil
}
func (x *ClientStreamMessage) GetAzureAttestationResult() *AzureAttestationResponse {
if x != nil {
if x, ok := x.Message.(*ClientStreamMessage_AzureAttestationResult); ok {
return x.AzureAttestationResult
}
}
return nil
}
type isClientStreamMessage_Message interface {
isClientStreamMessage_Message()
}
@@ -555,6 +565,10 @@ type ClientStreamMessage_VTPMattestationReport struct {
VTPMattestationReport *AttestationResponse `protobuf:"bytes,6,opt,name=vTPMattestationReport,proto3,oneof"`
}
type ClientStreamMessage_AzureAttestationResult struct {
AzureAttestationResult *AzureAttestationResponse `protobuf:"bytes,7,opt,name=azureAttestationResult,proto3,oneof"`
}
func (*ClientStreamMessage_AgentLog) isClientStreamMessage_Message() {}
func (*ClientStreamMessage_AgentEvent) isClientStreamMessage_Message() {}
@@ -567,6 +581,8 @@ func (*ClientStreamMessage_AgentStateRes) isClientStreamMessage_Message() {}
func (*ClientStreamMessage_VTPMattestationReport) isClientStreamMessage_Message() {}
func (*ClientStreamMessage_AzureAttestationResult) isClientStreamMessage_Message() {}
type ServerStreamMessage struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Types that are valid to be assigned to Message:
@@ -575,6 +591,7 @@ type ServerStreamMessage struct {
// *ServerStreamMessage_RunReq
// *ServerStreamMessage_StopComputation
// *ServerStreamMessage_AgentStateReq
// *ServerStreamMessage_DisconnectReq
Message isServerStreamMessage_Message `protobuf_oneof:"message"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
@@ -653,6 +670,15 @@ func (x *ServerStreamMessage) GetAgentStateReq() *AgentStateReq {
return nil
}
func (x *ServerStreamMessage) GetDisconnectReq() *DisconnectReq {
if x != nil {
if x, ok := x.Message.(*ServerStreamMessage_DisconnectReq); ok {
return x.DisconnectReq
}
}
return nil
}
type isServerStreamMessage_Message interface {
isServerStreamMessage_Message()
}
@@ -673,6 +699,10 @@ type ServerStreamMessage_AgentStateReq struct {
AgentStateReq *AgentStateReq `protobuf:"bytes,4,opt,name=agentStateReq,proto3,oneof"`
}
type ServerStreamMessage_DisconnectReq struct {
DisconnectReq *DisconnectReq `protobuf:"bytes,5,opt,name=disconnectReq,proto3,oneof"`
}
func (*ServerStreamMessage_RunReqChunks) isServerStreamMessage_Message() {}
func (*ServerStreamMessage_RunReq) isServerStreamMessage_Message() {}
@@ -681,6 +711,52 @@ func (*ServerStreamMessage_StopComputation) isServerStreamMessage_Message() {}
func (*ServerStreamMessage_AgentStateReq) isServerStreamMessage_Message() {}
func (*ServerStreamMessage_DisconnectReq) isServerStreamMessage_Message() {}
type DisconnectReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DisconnectReq) Reset() {
*x = DisconnectReq{}
mi := &file_agent_cvms_cvms_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DisconnectReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DisconnectReq) ProtoMessage() {}
func (x *DisconnectReq) ProtoReflect() protoreflect.Message {
mi := &file_agent_cvms_cvms_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 DisconnectReq.ProtoReflect.Descriptor instead.
func (*DisconnectReq) Descriptor() ([]byte, []int) {
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{9}
}
func (x *DisconnectReq) GetId() string {
if x != nil {
return x.Id
}
return ""
}
type RunReqChunks struct {
state protoimpl.MessageState `protogen:"open.v1"`
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
@@ -692,7 +768,7 @@ type RunReqChunks struct {
func (x *RunReqChunks) Reset() {
*x = RunReqChunks{}
mi := &file_agent_cvms_cvms_proto_msgTypes[9]
mi := &file_agent_cvms_cvms_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -704,7 +780,7 @@ func (x *RunReqChunks) String() string {
func (*RunReqChunks) ProtoMessage() {}
func (x *RunReqChunks) ProtoReflect() protoreflect.Message {
mi := &file_agent_cvms_cvms_proto_msgTypes[9]
mi := &file_agent_cvms_cvms_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -717,7 +793,7 @@ func (x *RunReqChunks) ProtoReflect() protoreflect.Message {
// Deprecated: Use RunReqChunks.ProtoReflect.Descriptor instead.
func (*RunReqChunks) Descriptor() ([]byte, []int) {
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{9}
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{10}
}
func (x *RunReqChunks) GetData() []byte {
@@ -756,7 +832,7 @@ type ComputationRunReq struct {
func (x *ComputationRunReq) Reset() {
*x = ComputationRunReq{}
mi := &file_agent_cvms_cvms_proto_msgTypes[10]
mi := &file_agent_cvms_cvms_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -768,7 +844,7 @@ func (x *ComputationRunReq) String() string {
func (*ComputationRunReq) ProtoMessage() {}
func (x *ComputationRunReq) ProtoReflect() protoreflect.Message {
mi := &file_agent_cvms_cvms_proto_msgTypes[10]
mi := &file_agent_cvms_cvms_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -781,7 +857,7 @@ func (x *ComputationRunReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use ComputationRunReq.ProtoReflect.Descriptor instead.
func (*ComputationRunReq) Descriptor() ([]byte, []int) {
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{10}
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{11}
}
func (x *ComputationRunReq) GetId() string {
@@ -842,7 +918,7 @@ type ResultConsumer struct {
func (x *ResultConsumer) Reset() {
*x = ResultConsumer{}
mi := &file_agent_cvms_cvms_proto_msgTypes[11]
mi := &file_agent_cvms_cvms_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -854,7 +930,7 @@ func (x *ResultConsumer) String() string {
func (*ResultConsumer) ProtoMessage() {}
func (x *ResultConsumer) ProtoReflect() protoreflect.Message {
mi := &file_agent_cvms_cvms_proto_msgTypes[11]
mi := &file_agent_cvms_cvms_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -867,7 +943,7 @@ func (x *ResultConsumer) ProtoReflect() protoreflect.Message {
// Deprecated: Use ResultConsumer.ProtoReflect.Descriptor instead.
func (*ResultConsumer) Descriptor() ([]byte, []int) {
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{11}
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{12}
}
func (x *ResultConsumer) GetUserKey() []byte {
@@ -888,7 +964,7 @@ type Dataset struct {
func (x *Dataset) Reset() {
*x = Dataset{}
mi := &file_agent_cvms_cvms_proto_msgTypes[12]
mi := &file_agent_cvms_cvms_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -900,7 +976,7 @@ func (x *Dataset) String() string {
func (*Dataset) ProtoMessage() {}
func (x *Dataset) ProtoReflect() protoreflect.Message {
mi := &file_agent_cvms_cvms_proto_msgTypes[12]
mi := &file_agent_cvms_cvms_proto_msgTypes[13]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -913,7 +989,7 @@ func (x *Dataset) ProtoReflect() protoreflect.Message {
// Deprecated: Use Dataset.ProtoReflect.Descriptor instead.
func (*Dataset) Descriptor() ([]byte, []int) {
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{12}
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{13}
}
func (x *Dataset) GetHash() []byte {
@@ -947,7 +1023,7 @@ type Algorithm struct {
func (x *Algorithm) Reset() {
*x = Algorithm{}
mi := &file_agent_cvms_cvms_proto_msgTypes[13]
mi := &file_agent_cvms_cvms_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -959,7 +1035,7 @@ func (x *Algorithm) String() string {
func (*Algorithm) ProtoMessage() {}
func (x *Algorithm) ProtoReflect() protoreflect.Message {
mi := &file_agent_cvms_cvms_proto_msgTypes[13]
mi := &file_agent_cvms_cvms_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -972,7 +1048,7 @@ func (x *Algorithm) ProtoReflect() protoreflect.Message {
// Deprecated: Use Algorithm.ProtoReflect.Descriptor instead.
func (*Algorithm) Descriptor() ([]byte, []int) {
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{13}
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{14}
}
func (x *Algorithm) GetHash() []byte {
@@ -1004,7 +1080,7 @@ type AgentConfig struct {
func (x *AgentConfig) Reset() {
*x = AgentConfig{}
mi := &file_agent_cvms_cvms_proto_msgTypes[14]
mi := &file_agent_cvms_cvms_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1016,7 +1092,7 @@ func (x *AgentConfig) String() string {
func (*AgentConfig) ProtoMessage() {}
func (x *AgentConfig) ProtoReflect() protoreflect.Message {
mi := &file_agent_cvms_cvms_proto_msgTypes[14]
mi := &file_agent_cvms_cvms_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1029,7 +1105,7 @@ func (x *AgentConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentConfig.ProtoReflect.Descriptor instead.
func (*AgentConfig) Descriptor() ([]byte, []int) {
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{14}
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{15}
}
func (x *AgentConfig) GetPort() string {
@@ -1091,7 +1167,7 @@ type AttestationResponse struct {
func (x *AttestationResponse) Reset() {
*x = AttestationResponse{}
mi := &file_agent_cvms_cvms_proto_msgTypes[15]
mi := &file_agent_cvms_cvms_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1103,7 +1179,7 @@ func (x *AttestationResponse) String() string {
func (*AttestationResponse) ProtoMessage() {}
func (x *AttestationResponse) ProtoReflect() protoreflect.Message {
mi := &file_agent_cvms_cvms_proto_msgTypes[15]
mi := &file_agent_cvms_cvms_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1116,7 +1192,7 @@ func (x *AttestationResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AttestationResponse.ProtoReflect.Descriptor instead.
func (*AttestationResponse) Descriptor() ([]byte, []int) {
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{15}
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{16}
}
func (x *AttestationResponse) GetFile() []byte {
@@ -1133,6 +1209,58 @@ func (x *AttestationResponse) GetCertSerialNumber() string {
return ""
}
type AzureAttestationResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
CertSerialNumber string `protobuf:"bytes,2,opt,name=certSerialNumber,proto3" json:"certSerialNumber,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AzureAttestationResponse) Reset() {
*x = AzureAttestationResponse{}
mi := &file_agent_cvms_cvms_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AzureAttestationResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AzureAttestationResponse) ProtoMessage() {}
func (x *AzureAttestationResponse) ProtoReflect() protoreflect.Message {
mi := &file_agent_cvms_cvms_proto_msgTypes[17]
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 AzureAttestationResponse.ProtoReflect.Descriptor instead.
func (*AzureAttestationResponse) Descriptor() ([]byte, []int) {
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{17}
}
func (x *AzureAttestationResponse) GetFile() []byte {
if x != nil {
return x.File
}
return nil
}
func (x *AzureAttestationResponse) GetCertSerialNumber() string {
if x != nil {
return x.CertSerialNumber
}
return ""
}
var File_agent_cvms_cvms_proto protoreflect.FileDescriptor
var file_agent_cvms_cvms_proto_rawDesc = string([]byte{
@@ -1183,7 +1311,7 @@ var file_agent_cvms_cvms_proto_rawDesc = string([]byte{
0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x93, 0x03, 0x0a, 0x13, 0x43, 0x6c, 0x69, 0x65,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xed, 0x03, 0x0a, 0x13, 0x43, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
0x2d, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4c,
@@ -1208,86 +1336,103 @@ var file_agent_cvms_cvms_proto_rawDesc = string([]byte{
0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x15, 0x76, 0x54, 0x50,
0x4d, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6f,
0x72, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x8d, 0x02,
0x0a, 0x13, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43,
0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x76,
0x6d, 0x73, 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x48,
0x00, 0x52, 0x0c, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12,
0x31, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x17, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x48, 0x00, 0x52, 0x06, 0x72, 0x75, 0x6e, 0x52,
0x65, 0x71, 0x12, 0x41, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x76,
0x6d, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0f, 0x73, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74,
0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63,
0x76, 0x6d, 0x73, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65,
0x71, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
0x65, 0x71, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x4b, 0x0a,
0x0c, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x12, 0x0a,
0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74,
0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
0x64, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4c, 0x61, 0x73, 0x74, 0x22, 0xaa, 0x02, 0x0a, 0x11, 0x43,
0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65,
0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e,
0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74,
0x73, 0x12, 0x2d, 0x0a, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x05,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x6c, 0x67, 0x6f,
0x72, 0x69, 0x74, 0x68, 0x6d, 0x52, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d,
0x12, 0x3f, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75,
0x6d, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x76, 0x6d,
0x73, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72,
0x52, 0x0f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72,
0x73, 0x12, 0x34, 0x0a, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41,
0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x2a, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c,
0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65,
0x72, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72,
0x4b, 0x65, 0x79, 0x22, 0x53, 0x0a, 0x07, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x12, 0x12,
0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61,
0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08,
0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x39, 0x0a, 0x09, 0x41, 0x6c, 0x67, 0x6f,
0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65,
0x72, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72,
0x4b, 0x65, 0x79, 0x22, 0xe5, 0x01, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f,
0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74,
0x46, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x65,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x46, 0x69, 0x6c, 0x65, 0x12,
0x24, 0x0a, 0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x5f, 0x66, 0x69, 0x6c,
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43,
0x61, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
0x63, 0x61, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x61, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c,
0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x74, 0x74, 0x65,
0x73, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b,
0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6c, 0x73, 0x22, 0x55, 0x0a, 0x13, 0x41,
0x72, 0x74, 0x12, 0x58, 0x0a, 0x16, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x41, 0x74, 0x74, 0x65, 0x73,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x07, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x61, 0x7a, 0x75, 0x72, 0x65, 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, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65,
0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62,
0x65, 0x72, 0x32, 0x50, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a,
0x07, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x19, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e,
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x1a, 0x19, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65,
0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00,
0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x63, 0x76, 0x6d, 0x73, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x73, 0x65, 0x48, 0x00, 0x52, 0x16, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x41, 0x74, 0x74, 0x65, 0x73,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x09, 0x0a, 0x07,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xca, 0x02, 0x0a, 0x13, 0x53, 0x65, 0x72, 0x76,
0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
0x38, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x52, 0x75, 0x6e,
0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x75, 0x6e,
0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x31, 0x0a, 0x06, 0x72, 0x75, 0x6e,
0x52, 0x65, 0x71, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x76, 0x6d, 0x73,
0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52,
0x65, 0x71, 0x48, 0x00, 0x52, 0x06, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x41, 0x0a, 0x0f,
0x73, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x53, 0x74, 0x6f,
0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0f,
0x73, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x3b, 0x0a, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x67,
0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x48, 0x00, 0x52, 0x0d, 0x61,
0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x12, 0x3b, 0x0a, 0x0d,
0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x18, 0x05, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f,
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x48, 0x00, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x63,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x22, 0x1f, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
0x63, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4b, 0x0a, 0x0c, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43,
0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f,
0x6c, 0x61, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4c, 0x61,
0x73, 0x74, 0x22, 0xaa, 0x02, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b,
0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29,
0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x0d, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x52,
0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x12, 0x2d, 0x0a, 0x09, 0x61, 0x6c, 0x67,
0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63,
0x76, 0x6d, 0x73, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x52, 0x09, 0x61,
0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x3f, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x73, 0x12, 0x34, 0x0a, 0x0c, 0x61, 0x67, 0x65,
0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x11, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x52, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22,
0x2a, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65,
0x72, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x22, 0x53, 0x0a, 0x07, 0x44,
0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73,
0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65,
0x72, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
0x22, 0x39, 0x0a, 0x09, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x12, 0x0a,
0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73,
0x68, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x22, 0xe5, 0x01, 0x0a, 0x0b,
0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70,
0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12,
0x1b, 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08,
0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x6b, 0x65, 0x79, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x5f, 0x63, 0x61, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a,
0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x61, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18,
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x61, 0x46,
0x69, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c,
0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c,
0x12, 0x21, 0x0a, 0x0c, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x6c, 0x73,
0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64,
0x54, 0x6c, 0x73, 0x22, 0x55, 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, 0x12, 0x2a,
0x0a, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62,
0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65,
0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x5a, 0x0a, 0x18, 0x61, 0x7a,
0x75, 0x72, 0x65, 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, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x65,
0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c,
0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x32, 0x50, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x12, 0x45, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x19, 0x2e, 0x63,
0x76, 0x6d, 0x73, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x19, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x53,
0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x63, 0x76,
0x6d, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
@@ -1302,50 +1447,54 @@ func file_agent_cvms_cvms_proto_rawDescGZIP() []byte {
return file_agent_cvms_cvms_proto_rawDescData
}
var file_agent_cvms_cvms_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
var file_agent_cvms_cvms_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
var file_agent_cvms_cvms_proto_goTypes = []any{
(*AgentStateReq)(nil), // 0: cvms.AgentStateReq
(*AgentStateRes)(nil), // 1: cvms.AgentStateRes
(*StopComputation)(nil), // 2: cvms.StopComputation
(*StopComputationResponse)(nil), // 3: cvms.StopComputationResponse
(*RunResponse)(nil), // 4: cvms.RunResponse
(*AgentEvent)(nil), // 5: cvms.AgentEvent
(*AgentLog)(nil), // 6: cvms.AgentLog
(*ClientStreamMessage)(nil), // 7: cvms.ClientStreamMessage
(*ServerStreamMessage)(nil), // 8: cvms.ServerStreamMessage
(*RunReqChunks)(nil), // 9: cvms.RunReqChunks
(*ComputationRunReq)(nil), // 10: cvms.ComputationRunReq
(*ResultConsumer)(nil), // 11: cvms.ResultConsumer
(*Dataset)(nil), // 12: cvms.Dataset
(*Algorithm)(nil), // 13: cvms.Algorithm
(*AgentConfig)(nil), // 14: cvms.AgentConfig
(*AttestationResponse)(nil), // 15: cvms.AttestationResponse
(*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp
(*AgentStateReq)(nil), // 0: cvms.AgentStateReq
(*AgentStateRes)(nil), // 1: cvms.AgentStateRes
(*StopComputation)(nil), // 2: cvms.StopComputation
(*StopComputationResponse)(nil), // 3: cvms.StopComputationResponse
(*RunResponse)(nil), // 4: cvms.RunResponse
(*AgentEvent)(nil), // 5: cvms.AgentEvent
(*AgentLog)(nil), // 6: cvms.AgentLog
(*ClientStreamMessage)(nil), // 7: cvms.ClientStreamMessage
(*ServerStreamMessage)(nil), // 8: cvms.ServerStreamMessage
(*DisconnectReq)(nil), // 9: cvms.DisconnectReq
(*RunReqChunks)(nil), // 10: cvms.RunReqChunks
(*ComputationRunReq)(nil), // 11: cvms.ComputationRunReq
(*ResultConsumer)(nil), // 12: cvms.ResultConsumer
(*Dataset)(nil), // 13: cvms.Dataset
(*Algorithm)(nil), // 14: cvms.Algorithm
(*AgentConfig)(nil), // 15: cvms.AgentConfig
(*AttestationResponse)(nil), // 16: cvms.AttestationResponse
(*AzureAttestationResponse)(nil), // 17: cvms.azureAttestationResponse
(*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp
}
var file_agent_cvms_cvms_proto_depIdxs = []int32{
16, // 0: cvms.AgentEvent.timestamp:type_name -> google.protobuf.Timestamp
16, // 1: cvms.AgentLog.timestamp:type_name -> google.protobuf.Timestamp
18, // 0: cvms.AgentEvent.timestamp:type_name -> google.protobuf.Timestamp
18, // 1: cvms.AgentLog.timestamp:type_name -> google.protobuf.Timestamp
6, // 2: cvms.ClientStreamMessage.agent_log:type_name -> cvms.AgentLog
5, // 3: cvms.ClientStreamMessage.agent_event:type_name -> cvms.AgentEvent
4, // 4: cvms.ClientStreamMessage.run_res:type_name -> cvms.RunResponse
3, // 5: cvms.ClientStreamMessage.stopComputationRes:type_name -> cvms.StopComputationResponse
1, // 6: cvms.ClientStreamMessage.agentStateRes:type_name -> cvms.AgentStateRes
15, // 7: cvms.ClientStreamMessage.vTPMattestationReport:type_name -> cvms.AttestationResponse
9, // 8: cvms.ServerStreamMessage.runReqChunks:type_name -> cvms.RunReqChunks
10, // 9: cvms.ServerStreamMessage.runReq:type_name -> cvms.ComputationRunReq
2, // 10: cvms.ServerStreamMessage.stopComputation:type_name -> cvms.StopComputation
0, // 11: cvms.ServerStreamMessage.agentStateReq:type_name -> cvms.AgentStateReq
12, // 12: cvms.ComputationRunReq.datasets:type_name -> cvms.Dataset
13, // 13: cvms.ComputationRunReq.algorithm:type_name -> cvms.Algorithm
11, // 14: cvms.ComputationRunReq.result_consumers:type_name -> cvms.ResultConsumer
14, // 15: cvms.ComputationRunReq.agent_config:type_name -> cvms.AgentConfig
7, // 16: cvms.Service.Process:input_type -> cvms.ClientStreamMessage
8, // 17: cvms.Service.Process:output_type -> cvms.ServerStreamMessage
17, // [17:18] is the sub-list for method output_type
16, // [16:17] is the sub-list for method input_type
16, // [16:16] is the sub-list for extension type_name
16, // [16:16] is the sub-list for extension extendee
0, // [0:16] is the sub-list for field type_name
16, // 7: cvms.ClientStreamMessage.vTPMattestationReport:type_name -> cvms.AttestationResponse
17, // 8: cvms.ClientStreamMessage.azureAttestationResult:type_name -> cvms.azureAttestationResponse
10, // 9: cvms.ServerStreamMessage.runReqChunks:type_name -> cvms.RunReqChunks
11, // 10: cvms.ServerStreamMessage.runReq:type_name -> cvms.ComputationRunReq
2, // 11: cvms.ServerStreamMessage.stopComputation:type_name -> cvms.StopComputation
0, // 12: cvms.ServerStreamMessage.agentStateReq:type_name -> cvms.AgentStateReq
9, // 13: cvms.ServerStreamMessage.disconnectReq:type_name -> cvms.DisconnectReq
13, // 14: cvms.ComputationRunReq.datasets:type_name -> cvms.Dataset
14, // 15: cvms.ComputationRunReq.algorithm:type_name -> cvms.Algorithm
12, // 16: cvms.ComputationRunReq.result_consumers:type_name -> cvms.ResultConsumer
15, // 17: cvms.ComputationRunReq.agent_config:type_name -> cvms.AgentConfig
7, // 18: cvms.Service.Process:input_type -> cvms.ClientStreamMessage
8, // 19: cvms.Service.Process:output_type -> cvms.ServerStreamMessage
19, // [19:20] is the sub-list for method output_type
18, // [18:19] is the sub-list for method input_type
18, // [18:18] is the sub-list for extension type_name
18, // [18:18] is the sub-list for extension extendee
0, // [0:18] is the sub-list for field type_name
}
func init() { file_agent_cvms_cvms_proto_init() }
@@ -1360,12 +1509,14 @@ func file_agent_cvms_cvms_proto_init() {
(*ClientStreamMessage_StopComputationRes)(nil),
(*ClientStreamMessage_AgentStateRes)(nil),
(*ClientStreamMessage_VTPMattestationReport)(nil),
(*ClientStreamMessage_AzureAttestationResult)(nil),
}
file_agent_cvms_cvms_proto_msgTypes[8].OneofWrappers = []any{
(*ServerStreamMessage_RunReqChunks)(nil),
(*ServerStreamMessage_RunReq)(nil),
(*ServerStreamMessage_StopComputation)(nil),
(*ServerStreamMessage_AgentStateReq)(nil),
(*ServerStreamMessage_DisconnectReq)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -1373,7 +1524,7 @@ func file_agent_cvms_cvms_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_cvms_cvms_proto_rawDesc), len(file_agent_cvms_cvms_proto_rawDesc)),
NumEnums: 0,
NumMessages: 16,
NumMessages: 18,
NumExtensions: 0,
NumServices: 1,
},
+11
View File
@@ -60,6 +60,7 @@ message ClientStreamMessage {
StopComputationResponse stopComputationRes = 4;
AgentStateRes agentStateRes = 5;
AttestationResponse vTPMattestationReport = 6;
azureAttestationResponse azureAttestationResult = 7;
}
}
@@ -69,9 +70,14 @@ message ServerStreamMessage {
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;
@@ -117,3 +123,8 @@ message AttestationResponse {
bytes file = 1;
string certSerialNumber = 2;
}
message azureAttestationResponse {
bytes file = 1;
string certSerialNumber = 2;
}
+9 -9
View File
@@ -13,7 +13,6 @@ import (
"github.com/ultravioletrs/cocos/agent/auth"
"github.com/ultravioletrs/cocos/internal/server"
grpcserver "github.com/ultravioletrs/cocos/internal/server/grpc"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
@@ -33,13 +32,17 @@ type agentServer struct {
logger *slog.Logger
svc agent.Service
host string
caUrl string
cvmId string
}
func NewServer(logger *slog.Logger, svc agent.Service, host string) AgentServer {
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,
}
}
@@ -73,15 +76,9 @@ func (as *agentServer) Start(cfg agent.AgentConfig, cmp agent.Computation) error
return err
}
qp, err := quoteprovider.GetLeveledQuoteProvider()
if err != nil {
as.logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
return err
}
ctx, cancel := context.WithCancel(context.Background())
as.gs = grpcserver.New(ctx, cancel, svcName, agentGrpcServerConfig, registerAgentServiceServer, as.logger, qp, authSvc)
as.gs = grpcserver.New(ctx, cancel, svcName, agentGrpcServerConfig, registerAgentServiceServer, as.logger, authSvc, as.caUrl, as.cvmId)
go func() {
err := as.gs.Start()
@@ -94,5 +91,8 @@ func (as *agentServer) Start(cfg agent.AgentConfig, cmp agent.Computation) error
}
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)
}
+2 -2
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
@@ -70,7 +70,7 @@ func (_c *AgentServer_Start_Call) RunAndReturn(run func(agent.AgentConfig, agent
return _c
}
// Stop provides a mock function with given fields:
// Stop provides a mock function with no fields
func (_m *AgentServer) Stop() error {
ret := _m.Called()
+2 -2
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
@@ -56,7 +56,7 @@ func (_c *Service_SendEvent_Call) Return() *Service_SendEvent_Call {
}
func (_c *Service_SendEvent_Call) RunAndReturn(run func(string, string, string, json.RawMessage)) *Service_SendEvent_Call {
_c.Call.Return(run)
_c.Run(run)
return _c
}
+138 -11
View File
@@ -1,13 +1,13 @@
// 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 (
agent "github.com/ultravioletrs/cocos/agent"
config "github.com/ultravioletrs/cocos/pkg/attestation"
attestation "github.com/ultravioletrs/cocos/pkg/attestation"
context "context"
@@ -75,7 +75,7 @@ func (_c *Service_Algo_Call) RunAndReturn(run func(context.Context, agent.Algori
}
// 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 config.AttestationType) ([]byte, error) {
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 {
@@ -84,10 +84,10 @@ func (_m *Service) Attestation(ctx context.Context, reportData [64]byte, nonce [
var r0 []byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, config.AttestationType) ([]byte, error)); ok {
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, [32]byte, config.AttestationType) []byte); ok {
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 {
@@ -95,7 +95,7 @@ func (_m *Service) Attestation(ctx context.Context, reportData [64]byte, nonce [
}
}
if rf, ok := ret.Get(1).(func(context.Context, [64]byte, [32]byte, config.AttestationType) error); ok {
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)
@@ -113,14 +113,14 @@ type Service_Attestation_Call struct {
// - ctx context.Context
// - reportData [64]byte
// - nonce [32]byte
// - attType config.AttestationType
// - 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, nonce [32]byte, attType config.AttestationType)) *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), args[2].([32]byte), args[3].(config.AttestationType))
run(args[0].(context.Context), args[1].([64]byte), args[2].([32]byte), args[3].(attestation.PlatformType))
})
return _c
}
@@ -130,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, [32]byte, config.AttestationType) ([]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
}
@@ -182,6 +242,73 @@ 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)
@@ -287,7 +414,7 @@ func (_c *Service_Result_Call) RunAndReturn(run func(context.Context) ([]byte, e
return _c
}
// State provides a mock function with given fields:
// State provides a mock function with no fields
func (_m *Service) State() string {
ret := _m.Called()
+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
}
+99 -40
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,7 +23,7 @@ import (
"github.com/ultravioletrs/cocos/agent/events"
"github.com/ultravioletrs/cocos/agent/statemachine"
"github.com/ultravioletrs/cocos/internal"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"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"
@@ -75,6 +75,11 @@ const (
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).
@@ -100,6 +105,10 @@ 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")
)
@@ -112,38 +121,40 @@ type Service interface {
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 [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]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 {
mu sync.Mutex
computation Computation // Holds the current computation request details.
algorithm algorithm.Algorithm // Filepath to the algorithm received for the computation.
result []byte // Stores the result of the computation.
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.LeveledQuoteProvider // 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.
computation Computation // Holds the current computation request details.
algorithm algorithm.Algorithm // Filepath to the algorithm received for the computation.
result []byte // Stores the result of the computation.
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.
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, quoteProvider client.LeveledQuoteProvider, vmlp int) 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,
cancel: cancel,
vmpl: vmlp,
sm: sm,
eventSvc: eventSvc,
provider: provider,
logger: logger,
cancel: cancel,
vmpl: vmlp,
}
transitions := []statemachine.Transition{
@@ -173,8 +184,12 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quot
logger.Error(err.Error())
}
}()
time.Sleep(100 * time.Millisecond)
sm.SendEvent(Start)
time.Sleep(100 * time.Millisecond)
return svc
}
@@ -213,14 +228,16 @@ 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 err := as.algorithm.Stop(); err != nil {
return fmt.Errorf("error stopping computation: %v", err)
if as.algorithm != nil {
if err := as.algorithm.Stop(); err != nil {
return fmt.Errorf("error stopping computation: %v", err)
}
}
sm := statemachine.NewStateMachine(Idle)
if err := os.RemoveAll(algorithm.DatasetsDir); err != nil {
return fmt.Errorf("error removing datasets directory: %v", err)
}
@@ -229,13 +246,28 @@ func (as *agentService) StopComputation(ctx context.Context) error {
return fmt.Errorf("error removing results directory: %v", err)
}
as.sm = sm
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
}
@@ -402,26 +434,39 @@ func (as *agentService) Result(ctx context.Context) ([]byte, error) {
return as.result, as.runError
}
func (as *agentService) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error) {
func (as *agentService) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error) {
switch attType {
case config.SNP:
rawQuote, err := as.quoteProvider.GetRawQuoteAtLevel(reportData, uint(as.vmpl))
case attestation.SNP, attestation.TDX:
rawQuote, err := as.provider.TeeAttestation(reportData[:])
if err != nil {
return []byte{}, err
return []byte{}, errors.Wrap(ErrAttestationFailed, err)
}
return rawQuote, nil
case config.VTPM:
vTPMQuote, err := vtpm.Attest(reportData[:], nonce[:], false)
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
}
}
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 vTPMQuote, nil
case config.SNPvTPM:
vTPMQuote, err := vtpm.Attest(reportData[:], nonce[:], true)
if err != nil {
return []byte{}, err
}
return vTPMQuote, nil
return token, nil
default:
return []byte{}, ErrAttestationType
}
@@ -480,3 +525,17 @@ func (as *agentService) publishEvent(status string) statemachine.Action {
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
}
+298 -24
View File
@@ -5,8 +5,10 @@ package agent
import (
"context"
"crypto/rand"
"fmt"
"log"
"os"
"path/filepath"
"testing"
"time"
@@ -16,12 +18,14 @@ 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"
@@ -36,13 +40,11 @@ var (
const datasetFile = "iris.csv"
func TestAlgo(t *testing.T) {
qp, err := quoteprovider.GetLeveledQuoteProvider()
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)
@@ -121,7 +123,7 @@ func TestAlgo(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
svc := New(ctx, mglog.NewMock(), events, qp, 0)
svc := New(ctx, mglog.NewMock(), events, &attestation.EmptyProvider{}, 0)
err := svc.InitComputation(ctx, testComputation(t))
require.NoError(t, err)
@@ -140,13 +142,11 @@ func TestAlgo(t *testing.T) {
}
func TestData(t *testing.T) {
qp, err := quoteprovider.GetLeveledQuoteProvider()
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,
@@ -216,7 +216,7 @@ func TestData(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
svc := New(ctx, mglog.NewMock(), events, qp, 0)
svc := New(ctx, mglog.NewMock(), events, &attestation.EmptyProvider{}, 0)
err := svc.InitComputation(ctx, testComputation(t))
require.NoError(t, err)
@@ -241,9 +241,6 @@ func TestData(t *testing.T) {
}
func TestResult(t *testing.T) {
qp, err := quoteprovider.GetLeveledQuoteProvider()
require.NoError(t, err)
cases := []struct {
name string
err error
@@ -301,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() {
@@ -324,29 +321,64 @@ func TestResult(t *testing.T) {
}
func TestAttestation(t *testing.T) {
qp := new(mocks2.LeveledQuoteProvider)
provider := new(mocks2.Provider)
cases := []struct {
name string
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) {
@@ -359,20 +391,78 @@ func TestAttestation(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
getQuote := qp.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return(tc.rawQuote, tc.err)
if tc.err != ErrAttestationFailed {
getQuote = qp.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return(tc.nonce, 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, qp, 0)
svc := New(ctx, mglog.NewMock(), events, provider, 0)
time.Sleep(300 * time.Millisecond)
_, err := svc.Attestation(ctx, tc.reportData, tc.nonce, 0)
_, 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 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)
@@ -402,3 +492,187 @@ func testComputation(t *testing.T) Computation {
ResultConsumers: []ResultConsumer{{UserKey: []byte("key")}},
}
}
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
}
-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"
+249 -722
View File
File diff suppressed because it is too large Load Diff
+60 -11
View File
@@ -16,8 +16,10 @@ import (
"github.com/google/go-tpm-tools/proto/attest"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"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"
)
@@ -37,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 {
@@ -226,6 +229,52 @@ func (cli *CLI) NewDownloadGCPOvmfFile() *cobra.Command {
}
}
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 {
@@ -236,14 +285,14 @@ func changeAttestationConfiguration(fileName, base64Data string, expectedLength
return errDataLength
}
ac := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
ac := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
f, err := os.ReadFile(fileName)
if err != nil {
return errors.Wrap(errReadingAttestationPolicyFile, err)
}
if err = config.ReadAttestationPolicyFromByte(f, &ac); err != nil {
if err = vtpm.ReadPolicyFromByte(f, &ac); err != nil {
return errors.Wrap(errUnmarshalJSON, err)
}
@@ -260,7 +309,7 @@ func changeAttestationConfiguration(fileName, base64Data string, expectedLength
return errAttestationPolicyField
}
fileJson, err := json.MarshalIndent(&ac, "", " ")
fileJson, err := vtpm.ConvertPolicyToJSON(&ac)
if err != nil {
return errors.Wrap(errMarshalJSON, err)
}
+250 -4
View File
@@ -3,6 +3,7 @@
package cli
import (
"bytes"
"encoding/base64"
"encoding/json"
"os"
@@ -11,7 +12,8 @@ import (
"github.com/google/go-sev-guest/proto/check"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
func TestChangeAttestationConfiguration(t *testing.T) {
@@ -19,7 +21,7 @@ func TestChangeAttestationConfiguration(t *testing.T) {
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
initialConfig := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
initialConfig := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
initialJSON, err := json.Marshal(initialConfig)
require.NoError(t, err)
@@ -87,8 +89,8 @@ func TestChangeAttestationConfiguration(t *testing.T) {
content, err := os.ReadFile(tmpfile.Name())
require.NoError(t, err)
ap := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
err = config.ReadAttestationPolicyFromByte(content, &ap)
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)
@@ -130,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
}
+800 -37
View File
@@ -4,10 +4,12 @@ package cli
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/absmach/magistrala/pkg/errors"
@@ -18,6 +20,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
mmocks "github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig/mocks"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"github.com/ultravioletrs/cocos/pkg/sdk/mocks"
@@ -49,6 +52,7 @@ func TestNewGetAttestationCmd(t *testing.T) {
teeNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce))
vtpmNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, vtpm.Nonce))
tokenNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, vtpm.Nonce))
testCases := []struct {
name string
@@ -102,7 +106,7 @@ func TestNewGetAttestationCmd(t *testing.T) {
},
{
name: "invalid vTPM data size",
args: []string{"vtpm", "-t", hex.EncodeToString(bytes.Repeat([]byte{0x00}, 33))},
args: []string{"vtpm", "--vtpm", hex.EncodeToString(bytes.Repeat([]byte{0x00}, 33))},
mockResponse: nil,
mockError: errors.New("error"),
expectedErr: "vTPM nonce must be a hex encoded string of length lesser or equal 32 bytes",
@@ -116,39 +120,60 @@ func TestNewGetAttestationCmd(t *testing.T) {
},
{
name: "failed to get attestation",
args: []string{"snp", "-e", teeNonce},
args: []string{"snp", "--tee", teeNonce},
mockResponse: nil,
mockError: errors.New("error"),
expectedErr: "Failed to get attestation due to error",
},
{
name: "Textproto report error",
args: []string{"snp", "-e", teeNonce, "--textproto"},
args: []string{"snp", "--tee", teeNonce, "--reporttextproto"},
mockResponse: []byte("mock attestation"),
mockError: nil,
expectedErr: "Error converting attestation to textproto",
expectedErr: "Fetching SEV-SNP attestation report\nError converting SNP attestation to JSON: attestation contents too small : attestation contents too small (0x10 bytes). Want at least 0x4a0 bytes ❌\n",
},
{
name: "successful Textproto report",
args: []string{"snp", "-e", teeNonce, "--textproto"},
args: []string{"snp", "--tee", teeNonce, "--reporttextproto"},
mockResponse: validattestation,
mockError: nil,
expectedOut: "Attestation result retrieved and saved successfully!",
},
{
name: "connection error",
args: []string{"snp", "-e", teeNonce},
args: []string{"snp", "--tee", teeNonce},
mockResponse: nil,
mockError: errors.New("failed to connect to agent"),
expectedErr: "Failed to connect to agent",
},
{
name: "successful Azure token retrieval",
args: []string{"azure-token", "--token", tokenNonce},
mockResponse: []byte("eyJhbGciOiAiUlMyNTYifQ.eyJzdWIiOiAidGVzdC11c2VyIn0.signature"),
mockError: nil,
expectedOut: "Fetching Azure token\nAttestation result retrieved and saved successfully!\n",
},
{
name: "failed to retrieve Azure token",
args: []string{"azure-token", "--token", tokenNonce},
mockResponse: nil,
mockError: errors.New("error"),
expectedErr: "Fetching Azure token\nFailed to get attestation result due to error: error ❌\n",
},
{
name: "invalid token nonce size",
args: []string{"azure-token", "--token", hex.EncodeToString(bytes.Repeat([]byte{0x00}, 33))},
mockResponse: nil,
mockError: errors.New("error"),
expectedErr: "Fetching Azure token\nvTPM nonce must be a hex encoded string of length lesser or equal 32 bytes ❌ \n",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Cleanup(func() {
os.Remove(attestationFilePath)
os.Remove(attestationJson)
os.Remove(attestationReportJson)
})
mockSDK := new(mocks.SDK)
cli := &CLI{agentSDK: mockSDK}
@@ -164,6 +189,11 @@ func TestNewGetAttestationCmd(t *testing.T) {
require.NoError(t, err)
})
mockSDK.On("AttestationResult", mock.Anything, [vtpm.Nonce]byte(bytes.Repeat([]byte{0x00}, vtpm.Nonce)), mock.Anything, mock.Anything).Return(tc.mockError).Run(func(args mock.Arguments) {
_, err := args.Get(3).(*os.File).Write(tc.mockResponse)
require.NoError(t, err)
})
cmd.SetArgs(tc.args)
err := cmd.Execute()
@@ -182,7 +212,8 @@ func TestNewValidateAttestationValidationCmdDefaults(t *testing.T) {
cmd := cli.NewValidateAttestationValidationCmd()
assert.Equal(t, "validate", cmd.Use)
assert.Equal(t, "Validate and verify attestation information. You can choose from 3 modes: snp,vtpm and snp-vtpm.Default mode is snp.", cmd.Short)
expectedMessage := fmt.Sprintf("Validate and verify attestation information. You can define the confidential computing cloud provider (%s, %s, %s; %s is the default) and can choose from 4 modes: %s, %s, %s, and %s. Default mode is %s.", CCNone, CCAzure, CCGCP, CCNone, SNP, VTPM, SNPvTPM, TDX, SNP)
assert.Equal(t, expectedMessage, cmd.Short)
assert.Equal(t, fmt.Sprint(defaultMinimumTcb), cmd.Flag("minimum_tcb").Value.String())
assert.Equal(t, fmt.Sprint(defaultMinimumLaunchTcb), cmd.Flag("minimum_lauch_tcb").Value.String())
@@ -283,26 +314,12 @@ func TestNewValidateAttestationValidationCmd(t *testing.T) {
})
}
type MockMeasurement struct {
mock.Mock
}
func (m *MockMeasurement) Run(igvmBinaryPath string) ([]byte, error) {
args := m.Called(igvmBinaryPath)
return nil, args.Error(0)
}
func (m *MockMeasurement) Stop() error {
args := m.Called()
return args.Error(0)
}
func TestNewMeasureCmd_RunSuccess(t *testing.T) {
cliInstance := &CLI{}
mockMeasurement := new(MockMeasurement)
mockMeasurement := new(mmocks.MeasurementProvider)
cliInstance.measurement = mockMeasurement
mockMeasurement.On("Run", "testfile.igvm").Return(nil)
mockMeasurement.On("Run", "testfile.igvm").Return([]byte{}, nil)
cmd := cliInstance.NewMeasureCmd("fake_binary_path")
buf := new(bytes.Buffer)
@@ -318,11 +335,11 @@ func TestNewMeasureCmd_RunSuccess(t *testing.T) {
func TestNewMeasureCmd_RunError(t *testing.T) {
cliInstance := &CLI{}
mockMeasurement := new(MockMeasurement)
mockMeasurement := new(mmocks.MeasurementProvider)
cliInstance.measurement = mockMeasurement
expectedError := errors.New("mocked measurement error")
mockMeasurement.On("Run", "testfile.igvm").Return(expectedError)
mockMeasurement.On("Run", "testfile.igvm").Return([]byte{}, expectedError)
cmd := cliInstance.NewMeasureCmd("fake_binary_path")
@@ -338,25 +355,34 @@ func TestNewMeasureCmd_RunError(t *testing.T) {
mockMeasurement.AssertExpectations(t)
}
func TestParseConfig(t *testing.T) {
func TestParseConfig1(t *testing.T) {
tmpfile, err := os.CreateTemp("", "attestation_policy.json")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
cfgString = ""
err := parseConfig()
err = parseConfig()
assert.NoError(t, err)
assert.NotNil(t, cfg.RootOfTrust)
assert.NotNil(t, cfg.Policy)
cfgString = `{"rootOfTrust":{"product":"test_product"},"policy":{"minimumGuestSvn":1}}`
cfgString = tmpfile.Name()
_, err = tmpfile.WriteString(`{"rootOfTrust":{"product":"test_product"},"policy":{"minimumGuestSvn":1}}`)
require.NoError(t, err)
err = parseConfig()
assert.NoError(t, err)
assert.Equal(t, "test_product", cfg.RootOfTrust.Product)
assert.Equal(t, uint32(1), cfg.Policy.MinimumGuestSvn)
cfgString = `{"invalid_json"`
_, err = tmpfile.WriteString(`{"invalid_json"`)
require.NoError(t, err)
err = parseConfig()
assert.Error(t, err)
}
func TestParseHashes(t *testing.T) {
func TestParseHashes1(t *testing.T) {
trustedAuthorHashes = []string{"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}
trustedIdKeyHashes = []string{"fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"}
@@ -390,9 +416,11 @@ func TestParseFiles(t *testing.T) {
trustedAuthorKeys = []string{authorKeyFile}
trustedIdKeys = []string{idKeyFile}
err = parseFiles()
err = parseAttestationFile()
assert.NoError(t, err)
assert.Equal(t, []byte("test attestation"), attestation)
err = parseTrustedKeys()
assert.NoError(t, err)
assert.Equal(t, []byte("test attestation"), attestationRaw)
assert.Len(t, cfg.Policy.TrustedAuthorKeys, 1)
assert.Len(t, cfg.Policy.TrustedIdKeys, 1)
@@ -401,11 +429,11 @@ func TestParseFiles(t *testing.T) {
os.Remove(idKeyFile)
attestationFile = "non_existent_file.bin"
err = parseFiles()
err = parseAttestationFile()
assert.Error(t, err)
}
func TestParseUints(t *testing.T) {
func TestParseUints1(t *testing.T) {
stepping = "10"
platformInfo = "0xFF"
@@ -430,7 +458,7 @@ func TestParseUints(t *testing.T) {
assert.Error(t, err)
}
func TestValidateInput(t *testing.T) {
func TestValidateInput1(t *testing.T) {
cfg = check.Config{}
if cfg.Policy == nil {
cfg.Policy = &check.Policy{}
@@ -455,7 +483,7 @@ func TestValidateInput(t *testing.T) {
assert.Error(t, err)
}
func TestGetBase(t *testing.T) {
func TestGetBase1(t *testing.T) {
assert.Equal(t, 16, getBase("0xFF"))
assert.Equal(t, 8, getBase("0o77"))
assert.Equal(t, 2, getBase("0b1010"))
@@ -624,3 +652,738 @@ func TestRoundTrip(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, roundTripReport)
}
func TestDecodeJWTToJSON(t *testing.T) {
tests := []struct {
name string
input []byte
err error
validate func(t *testing.T, output []byte)
}{
{
name: "Valid JWT",
input: []byte("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ." +
"SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"),
err: nil,
validate: func(t *testing.T, output []byte) {
assert.NotEmpty(t, output)
assert.Contains(t, string(output), `"header"`)
assert.Contains(t, string(output), `"payload"`)
},
},
{
name: "Invalid JWT - one part",
input: []byte("justonepart"),
err: fmt.Errorf("invalid JWT: must have at least 2 parts"),
validate: func(t *testing.T, output []byte) {
assert.Nil(t, output)
},
},
{
name: "Invalid Base64",
input: []byte("bad@@@.header"),
err: errors.New("illegal base64 data"),
validate: func(t *testing.T, output []byte) {
assert.Nil(t, output)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := decodeJWTToJSON(tt.input)
if tt.err != nil {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.err.Error())
} else {
assert.NoError(t, err)
}
tt.validate(t, got)
})
}
}
func setupTestEnvironment() func() {
originalMode := mode
originalCfgString := cfgString
originalTimeout := timeout
originalMaxRetryDelay := maxRetryDelay
originalPlatformInfo := platformInfo
originalStepping := stepping
originalTrustedAuthorKeys := trustedAuthorKeys
originalTrustedAuthorHashes := trustedAuthorHashes
originalTrustedIdKeys := trustedIdKeys
originalTrustedIdKeyHashes := trustedIdKeyHashes
originalAttestationFile := attestationFile
originalAttestationRaw := attestationRaw
originalOutput := output
originalNonce := nonce
originalFormat := format
originalTeeNonce := teeNonce
originalTokenNonce := tokenNonce
originalGetTextProtoAttestationReport := getTextProtoAttestationReport
originalGetAzureTokenJWT := getAzureTokenJWT
originalCloud := cloud
originalReportData := reportData
originalCheckCrl := checkCrl
mode = ""
cfgString = ""
timeout = 0
maxRetryDelay = 0
platformInfo = ""
stepping = ""
trustedAuthorKeys = []string{}
trustedAuthorHashes = []string{}
trustedIdKeys = []string{}
trustedIdKeyHashes = []string{}
attestationFile = ""
attestationRaw = []byte{}
output = ""
nonce = []byte{}
format = ""
teeNonce = []byte{}
tokenNonce = []byte{}
getTextProtoAttestationReport = false
getAzureTokenJWT = false
cloud = ""
reportData = []byte{}
checkCrl = false
return func() {
mode = originalMode
cfgString = originalCfgString
timeout = originalTimeout
maxRetryDelay = originalMaxRetryDelay
platformInfo = originalPlatformInfo
stepping = originalStepping
trustedAuthorKeys = originalTrustedAuthorKeys
trustedAuthorHashes = originalTrustedAuthorHashes
trustedIdKeys = originalTrustedIdKeys
trustedIdKeyHashes = originalTrustedIdKeyHashes
attestationFile = originalAttestationFile
attestationRaw = originalAttestationRaw
output = originalOutput
nonce = originalNonce
format = originalFormat
teeNonce = originalTeeNonce
tokenNonce = originalTokenNonce
getTextProtoAttestationReport = originalGetTextProtoAttestationReport
getAzureTokenJWT = originalGetAzureTokenJWT
cloud = originalCloud
reportData = originalReportData
checkCrl = originalCheckCrl
}
}
func createTempFile(t *testing.T, content []byte) string {
tmpfile, err := os.CreateTemp("", "test_*.bin")
require.NoError(t, err)
defer tmpfile.Close()
_, err = tmpfile.Write(content)
require.NoError(t, err)
return tmpfile.Name()
}
func TestNewAttestationCmdEdgeCases(t *testing.T) {
tests := []struct {
name string
args []string
expectedOutput string
hasSubcommands bool
}{
{
name: "no arguments shows help",
args: []string{},
expectedOutput: "Get and validate attestations",
hasSubcommands: true,
},
{
name: "help flag shows usage",
args: []string{"--help"},
expectedOutput: "Get and validate attestations",
hasSubcommands: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockSDK := new(mocks.SDK)
cli := &CLI{agentSDK: mockSDK}
cmd := cli.NewAttestationCmd()
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
cmd.SetArgs(tt.args)
err := cmd.Execute()
assert.NoError(t, err)
output := buf.String()
assert.Contains(t, output, tt.expectedOutput)
if tt.hasSubcommands {
assert.Contains(t, output, "Get and validate attestations")
assert.Contains(t, output, "Usage:")
assert.Contains(t, output, "Flags:")
}
})
}
}
func TestGetAttestationCmdEdgeCases(t *testing.T) {
defer setupTestEnvironment()()
testCases := []struct {
name string
args []string
setupMock func(*mocks.SDK)
expectedErr string
expectedOut string
}{
{
name: "no arguments provided",
args: []string{},
setupMock: func(sdk *mocks.SDK) {
},
expectedErr: "accepts 1 arg(s), received 0",
},
{
name: "too many arguments",
args: []string{"snp", "extra"},
setupMock: func(sdk *mocks.SDK) {
},
expectedErr: "accepts 1 arg(s), received 2",
},
{
name: "invalid attestation type",
args: []string{"invalid-type"},
setupMock: func(sdk *mocks.SDK) {
},
expectedErr: "Bad attestation type",
},
{
name: "SNP with missing TEE nonce",
args: []string{"snp"},
setupMock: func(sdk *mocks.SDK) {
},
expectedErr: "TEE nonce must be defined for SEV-SNP attestation",
},
{
name: "vTPM with missing nonce",
args: []string{"vtpm"},
setupMock: func(sdk *mocks.SDK) {
},
expectedErr: "vTPM nonce must be defined for vTPM attestation",
},
{
name: "Azure token with missing token nonce",
args: []string{"azure-token"},
setupMock: func(sdk *mocks.SDK) {
},
expectedErr: "Token nonce must be defined for Azure attestation",
},
{
name: "TEE nonce too large",
args: []string{"snp", "--tee", hex.EncodeToString(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce+1))},
setupMock: func(sdk *mocks.SDK) {
},
expectedErr: "nonce must be a hex encoded string of length lesser or equal 64 bytes",
},
{
name: "vTPM nonce too large",
args: []string{"vtpm", "--vtpm", hex.EncodeToString(bytes.Repeat([]byte{0x00}, vtpm.Nonce+1))},
setupMock: func(sdk *mocks.SDK) {
},
expectedErr: "vTPM nonce must be a hex encoded string of length lesser or equal 32 bytes",
},
{
name: "Token nonce too large",
args: []string{"azure-token", "--token", hex.EncodeToString(bytes.Repeat([]byte{0x00}, vtpm.Nonce+1))},
setupMock: func(sdk *mocks.SDK) {
},
expectedErr: "vTPM nonce must be a hex encoded string of length lesser or equal 32 bytes",
},
{
name: "successful TDX attestation",
args: []string{"tdx", "--tee", hex.EncodeToString(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce))},
setupMock: func(sdk *mocks.SDK) {
sdk.On("Attestation", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(nil).Run(func(args mock.Arguments) {
if _, err := args.Get(4).(*os.File).Write([]byte("mock tdx attestation")); err != nil {
t.Fatalf("Failed to write to attestation file: %v", err)
}
})
},
expectedOut: "Fetching TDX attestation report",
},
{
name: "file creation error",
args: []string{"snp", "--tee", hex.EncodeToString(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce))},
setupMock: func(sdk *mocks.SDK) {
},
expectedErr: "Error creating attestation file",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
os.Remove(attestationFilePath)
os.Remove(azureAttestResultFilePath)
os.Remove(azureAttestTokenFilePath)
defer func() {
os.Remove(attestationFilePath)
os.Remove(azureAttestResultFilePath)
os.Remove(azureAttestTokenFilePath)
}()
mockSDK := new(mocks.SDK)
cli := &CLI{agentSDK: mockSDK}
tc.setupMock(mockSDK)
if tc.name == "file creation error" {
err := os.Mkdir(attestationFilePath, 0o755)
require.NoError(t, err)
defer os.Remove(attestationFilePath)
}
cmd := cli.NewGetAttestationCmd()
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
cmd.SetArgs(tc.args)
err := cmd.Execute()
output := buf.String()
if tc.expectedErr != "" {
assert.Contains(t, output, tc.expectedErr)
} else {
assert.NoError(t, err)
if tc.expectedOut != "" {
assert.Contains(t, output, tc.expectedOut)
}
}
})
}
}
func TestFileOperations(t *testing.T) {
defer setupTestEnvironment()()
t.Run("openInputFile", func(t *testing.T) {
attestationFile = ""
reader, err := openInputFile()
assert.Error(t, err)
assert.Equal(t, errEmptyFile, err)
assert.Nil(t, reader)
tempFile := createTempFile(t, []byte("test content"))
defer os.Remove(tempFile)
attestationFile = tempFile
reader, err = openInputFile()
assert.NoError(t, err)
assert.NotNil(t, reader)
if file, ok := reader.(*os.File); ok {
file.Close()
}
attestationFile = "non-existent-file.bin"
reader, err = openInputFile()
assert.Error(t, err)
assert.Nil(t, reader)
})
t.Run("createOutputFile", func(t *testing.T) {
output = ""
writer, err := createOutputFile()
assert.NoError(t, err)
assert.Equal(t, os.Stdout, writer)
tempDir := t.TempDir()
output = filepath.Join(tempDir, "test_output.txt")
writer, err = createOutputFile()
assert.NoError(t, err)
assert.NotNil(t, writer)
if file, ok := writer.(*os.File); ok {
file.Close()
}
output = "/invalid/path/file.txt"
writer, err = createOutputFile()
assert.Error(t, err)
assert.Nil(t, writer)
})
}
func TestValidationFunctions(t *testing.T) {
t.Run("validateFieldLength", func(t *testing.T) {
tests := []struct {
name string
fieldName string
field []byte
expectedLength int
expectError bool
}{
{
name: "nil field",
fieldName: "test",
field: nil,
expectedLength: 32,
expectError: false,
},
{
name: "correct length",
fieldName: "test",
field: make([]byte, 32),
expectedLength: 32,
expectError: false,
},
{
name: "incorrect length",
fieldName: "test",
field: make([]byte, 16),
expectedLength: 32,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateFieldLength(tt.fieldName, tt.field, tt.expectedLength)
if tt.expectError {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.fieldName)
} else {
assert.NoError(t, err)
}
})
}
})
}
func TestDecodeJWTToJSONEdgeCases(t *testing.T) {
tests := []struct {
name string
input []byte
expected string
hasError bool
}{
{
name: "empty input",
input: []byte(""),
hasError: true,
},
{
name: "single part",
input: []byte("onlyonepart"),
hasError: true,
},
{
name: "invalid base64 in header",
input: []byte("invalid@base64.validpart"),
hasError: true,
},
{
name: "invalid base64 in payload",
input: []byte("eyJhbGciOiJIUzI1NiJ9.invalid@base64"),
hasError: true,
},
{
name: "invalid JSON in header",
input: []byte("bm90anNvbg.eyJzdWIiOiJ0ZXN0In0"),
hasError: true,
},
{
name: "invalid JSON in payload",
input: []byte("eyJhbGciOiJIUzI1NiJ9.bm90anNvbg"),
hasError: true,
},
{
name: "valid JWT with padding",
input: []byte("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.signature"),
expected: `{
"header": {
"alg": "HS256"
},
"payload": {
"sub": "test"
}
}`,
hasError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := decodeJWTToJSON(tt.input)
if tt.hasError {
assert.Error(t, err)
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.NotNil(t, result)
if tt.expected != "" {
assert.JSONEq(t, tt.expected, string(result))
}
}
})
}
}
func TestMeasureCmdEdgeCases(t *testing.T) {
tests := []struct {
name string
args []string
mockSetup func(*mmocks.MeasurementProvider)
expectedError string
expectedOut string
}{
{
name: "no arguments",
args: []string{},
mockSetup: func(m *mmocks.MeasurementProvider) {
},
expectedError: "requires at least 1 arg(s), only received 0",
},
{
name: "single line output success",
args: []string{"test.igvm"},
mockSetup: func(m *mmocks.MeasurementProvider) {
m.On("Run", "test.igvm").Return([]byte("ABCDEF123456"), nil)
},
expectedOut: "",
},
{
name: "multi-line output error",
args: []string{"test.igvm"},
mockSetup: func(m *mmocks.MeasurementProvider) {
m.On("Run", "test.igvm").Return([]byte("line1\nline2\nERROR: something went wrong"), nil)
},
expectedError: "ERROR: something went wrong",
},
{
name: "measurement run error",
args: []string{"test.igvm"},
mockSetup: func(m *mmocks.MeasurementProvider) {
m.On("Run", "test.igvm").Return(nil, errors.New("measurement failed"))
},
expectedError: "measurement failed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockMeasurement := new(mmocks.MeasurementProvider)
tt.mockSetup(mockMeasurement)
cli := &CLI{measurement: mockMeasurement}
cmd := cli.NewMeasureCmd("fake_binary_path")
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
cmd.SetArgs(tt.args)
err := cmd.Execute()
if tt.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
} else {
assert.NoError(t, err)
if tt.expectedOut != "" {
assert.Contains(t, buf.String(), tt.expectedOut)
}
}
mockMeasurement.AssertExpectations(t)
})
}
}
func TestValidateAttestationValidationCmdPreRunE(t *testing.T) {
tests := []struct {
name string
args []string
flags map[string]string
expectedErr string
}{
{
name: "no file path provided",
args: []string{},
flags: map[string]string{"mode": "snp"},
expectedErr: "please pass the attestation report file path",
},
{
name: "multiple file paths",
args: []string{"file1.bin", "file2.bin"},
flags: map[string]string{"mode": "snp"},
expectedErr: "please pass the attestation report file path",
},
{
name: "unknown mode",
args: []string{"test.bin"},
flags: map[string]string{"mode": "unknown"},
expectedErr: "unknown mode: unknown",
},
{
name: "SNP mode missing report_data",
args: []string{"test.bin"},
flags: map[string]string{"mode": "snp"},
expectedErr: "",
},
{
name: "SNP mode missing product",
args: []string{"test.bin"},
flags: map[string]string{"mode": "snp", "report_data": "123"},
expectedErr: "",
},
{
name: "vTPM mode missing nonce",
args: []string{"test.bin"},
flags: map[string]string{"mode": "vtpm"},
expectedErr: "",
},
{
name: "SNP-vTPM mode missing required flags",
args: []string{"test.bin"},
flags: map[string]string{"mode": "snp-vtpm"},
expectedErr: "",
},
{
name: "TDX mode missing report_data",
args: []string{"test.bin"},
flags: map[string]string{"mode": "tdx"},
expectedErr: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cli := &CLI{}
cmd := cli.NewValidateAttestationValidationCmd()
for key, value := range tt.flags {
if err := cmd.Flags().Set(key, value); err != nil {
}
}
err := cmd.PreRunE(cmd, tt.args)
if tt.expectedErr != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedErr)
} else {
assert.NoError(t, err)
}
})
}
}
func TestCloudProviderConfigurations(t *testing.T) {
defer setupTestEnvironment()()
tests := []struct {
name string
cloud string
expectedType string
}{
{
name: "none cloud provider",
cloud: CCNone,
expectedType: "vtpm",
},
{
name: "azure cloud provider",
cloud: CCAzure,
expectedType: "azure",
},
{
name: "gcp cloud provider",
cloud: CCGCP,
expectedType: "vtpm",
},
{
name: "default cloud provider",
cloud: "",
expectedType: "vtpm",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cli := &CLI{}
cmd := cli.NewValidateAttestationValidationCmd()
if err := cmd.Flags().Set("cloud", tt.cloud); err != nil {
t.Fatalf("Failed to set cloud flag: %v", err)
}
cloud, _ := cmd.Flags().GetString("cloud")
assert.Equal(t, tt.cloud, cloud)
assert.Contains(t, cmd.Short, tt.cloud)
})
}
}
func TestFileOperationErrors(t *testing.T) {
defer setupTestEnvironment()()
t.Run("file close error handling", func(t *testing.T) {
tempFile := createTempFile(t, []byte("test content"))
defer os.Remove(tempFile)
assert.True(t, true)
})
t.Run("file write error handling", func(t *testing.T) {
tempFile := createTempFile(t, []byte("test content"))
defer os.Remove(tempFile)
err := os.Chmod(tempFile, 0o444)
require.NoError(t, err)
err = os.WriteFile(tempFile, []byte("new content"), 0o644)
assert.Error(t, err)
})
t.Run("file read error handling", func(t *testing.T) {
tempDir := t.TempDir()
_, err := os.ReadFile(tempDir)
assert.Error(t, err)
})
}
func TestContextCancellation(t *testing.T) {
defer setupTestEnvironment()()
mockSDK := new(mocks.SDK)
cli := &CLI{agentSDK: mockSDK}
ctx, cancel := context.WithCancel(context.Background())
cancel()
mockSDK.On("Attestation", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(context.Canceled)
cmd := cli.NewGetAttestationCmd()
cmd.SetContext(ctx)
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
teeNonceHex := hex.EncodeToString(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce))
cmd.SetArgs([]string{"snp", "--tee", teeNonceHex})
err := cmd.Execute()
assert.NoError(t, err)
assert.Contains(t, buf.String(), "Failed to get attestation due to error")
}
+4 -3
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"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
const (
@@ -27,8 +28,8 @@ 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 := config.Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
err := config.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
+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)
}
})
}
}
+28 -8
View File
@@ -4,6 +4,7 @@ package cli
import (
"os"
"time"
"github.com/fatih/color"
"github.com/spf13/cobra"
@@ -15,7 +16,9 @@ const (
serverCA = "server-ca"
clientKey = "client-key"
clientCrt = "client-crt"
caUrl = "ca-url"
logLevel = "log-level"
ttlFlag = "ttl"
)
var (
@@ -23,7 +26,9 @@ var (
agentCVMServerCA string
agentCVMClientKey string
agentCVMClientCrt string
agentCVMCaUrl string
agentLogLevel string
ttl time.Duration
)
func (c *CLI) NewCreateVMCmd() *cobra.Command {
@@ -33,9 +38,11 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command {
Example: `create-vm`,
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
if err := c.InitializeManagerClient(cmd); err != nil {
printError(cmd, "Failed to connect to manager: %v ❌ ", c.connectErr)
return
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()
@@ -47,6 +54,11 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command {
createReq.AgentCvmServerUrl = agentCVMServerUrl
createReq.AgentLogLevel = agentLogLevel
createReq.AgentCvmCaUrl = agentCVMCaUrl
if ttl > 0 {
createReq.Ttl = ttl.String()
}
cmd.Println("🔗 Creating a new virtual machine")
@@ -56,7 +68,7 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command {
return
}
cmd.Println(color.New(color.FgGreen).Sprintf("✅ Virtual machine created successfully with id %s and port %s", res.SvmId, res.ForwardedPort))
cmd.Println(color.New(color.FgGreen).Sprintf("✅ Virtual machine created successfully with id %s and port %s", res.CvmId, res.ForwardedPort))
},
}
@@ -64,7 +76,13 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command {
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
}
@@ -73,11 +91,13 @@ func (c *CLI) NewRemoveVMCmd() *cobra.Command {
return &cobra.Command{
Use: "remove-vm",
Short: "Remove a virtual machine",
Example: `remove-vm <svm_id>`,
Example: `remove-vm <cvm_id>`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
if err := c.InitializeManagerClient(cmd); err == nil {
defer c.Close()
if c.managerClient == nil || c.connectErr != nil {
if err := c.InitializeManagerClient(cmd); err == nil {
defer c.Close()
}
}
if c.connectErr != nil {
@@ -87,7 +107,7 @@ func (c *CLI) NewRemoveVMCmd() *cobra.Command {
cmd.Println("🔗 Removing virtual machine")
_, err := c.managerClient.RemoveVm(cmd.Context(), &manager.RemoveReq{SvmId: args[0]})
_, err := c.managerClient.RemoveVm(cmd.Context(), &manager.RemoveReq{CvmId: args[0]})
if err != nil {
printError(cmd, "Error removing virtual machine: %v ❌ ", err)
return
+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)
}
}
})
}
}
+4 -2
View File
@@ -40,7 +40,7 @@ func (c *CLI) InitializeAgentSDK(cmd *cobra.Command) error {
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)
@@ -62,5 +62,7 @@ func (c *CLI) InitializeManagerClient(cmd *cobra.Command) error {
}
func (c *CLI) Close() {
c.client.Close()
if c.client != nil {
c.client.Close()
}
}
+93 -42
View File
@@ -8,7 +8,6 @@ import (
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"log"
"log/slog"
@@ -20,8 +19,6 @@ import (
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/caarlos0/env/v11"
"github.com/google/go-sev-guest/client"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/api"
"github.com/ultravioletrs/cocos/agent/cvms"
@@ -29,9 +26,10 @@ import (
"github.com/ultravioletrs/cocos/agent/cvms/server"
"github.com/ultravioletrs/cocos/agent/events"
agentlogger "github.com/ultravioletrs/cocos/internal/logger"
attestationconfig "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
"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"
@@ -46,9 +44,15 @@ const (
)
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"`
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() {
@@ -82,20 +86,29 @@ func main() {
return
}
var qp client.LeveledQuoteProvider
var provider attestation.Provider
ccPlatform := attestation.CCPlatform()
if !sevGuesDeviceExists() {
logger.Info("SEV-SNP device not found")
qpMock := new(mocks.LeveledQuoteProvider)
qpMock.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return([]uint8{}, errors.New("SEV-SNP device not found"))
qp = qpMock
} else {
qp, err = quoteprovider.GetLeveledQuoteProvider()
if err != nil {
logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
exitCode = 1
return
}
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{}
@@ -113,14 +126,19 @@ func main() {
}
defer cvmGRPCClient.Close()
reconnectFn := func(ctx context.Context) (cvms.Service_ProcessClient, error) {
_, newClient, err := cvmsgrpc.NewCVMClient(cvmGrpcConfig)
reconnectFn := func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) {
grpcClient, newClient, err := cvmsgrpc.NewCVMClient(cvmGrpcConfig)
if err != nil {
return nil, err
return nil, nil, err
}
// Don't defer close here as we want to keep the connection open
return newClient.Process(ctx)
pc, err := newClient.Process(ctx)
if err != nil {
grpcClient.Close()
return nil, nil, err
}
return grpcClient, pc, nil
}
pc, err := cvmsClient.Process(ctx)
@@ -136,7 +154,7 @@ func main() {
return
}
svc := newService(ctx, logger, eventSvc, qp, cfg.Vmpl)
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))
@@ -144,7 +162,7 @@ func main() {
return
}
mc, err := cvmsapi.NewClient(pc, svc, eventsLogsQueue, logger, server.NewServer(logger, svc, cfg.AgentGrpcHost), storageDir, reconnectFn)
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
@@ -170,17 +188,34 @@ func main() {
return mc.Process(ctx, cancel)
})
attestation, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc)
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: attestation,
File: attest,
CertSerialNumber: certSerialNumber,
},
},
@@ -191,8 +226,8 @@ func main() {
}
}
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, qp client.LeveledQuoteProvider, vmpl int) agent.Service {
svc := agent.New(ctx, logger, eventSvc, qp, vmpl)
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")
@@ -201,15 +236,6 @@ func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Servic
return svc
}
func sevGuesDeviceExists() bool {
d, err := client.OpenDevice()
if err != nil {
return false
}
d.Close()
return true
}
func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Service) ([]byte, string, error) {
if certFilePath == "" {
return nil, "", nil
@@ -228,7 +254,32 @@ func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Ser
nonceSNP := sha512.Sum512(certFile)
nonceVTPM := sha256.Sum256(certFile)
attestation, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, attestationconfig.SNPvTPM)
attest, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, attestation.SNPvTPM)
if err != nil {
return nil, "", err
}
return attest, certx509.SerialNumber.String(), nil
}
func azureAttestationFromCert(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 nil, "", err
}
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
}
+2
View File
@@ -139,6 +139,7 @@ func main() {
rootCmd.AddCommand(cliSVC.NewCABundleCmd(directoryCachePath))
rootCmd.AddCommand(cliSVC.NewCreateVMCmd())
rootCmd.AddCommand(cliSVC.NewRemoveVMCmd())
rootCmd.AddCommand(cliSVC.NewIMAMeasurementsCmd())
// Attestation commands
attestationCmd.AddCommand(cliSVC.NewGetAttestationCmd())
@@ -162,6 +163,7 @@ func main() {
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)
+26 -4
View File
@@ -84,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
@@ -100,19 +114,27 @@ func main() {
return
}
svc, err := newService(logger, tracer, qemuCfg, cfg.AttestationPolicyBinary, cfg.IgvmMeasureBinary, cfg.PcrValues, cfg.EosVersion)
svc, err := newService(logger, tracer, *qemuCfg, cfg.AttestationPolicyBinary, cfg.IgvmMeasureBinary, cfg.PcrValues, 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))
}
gs := grpcserver.New(ctx, cancel, svcName, managerGRPCConfig, registerManagerServiceServer, logger, nil, nil)
gs := grpcserver.New(ctx, cancel, svcName, managerGRPCConfig, registerManagerServiceServer, logger, nil, "", "")
g.Go(func() error {
return gs.Start()
+7 -4
View File
@@ -40,17 +40,20 @@ MANAGER_QEMU_VIRTIO_NET_PCI_ADDR=0x2
MANAGER_QEMU_VIRTIO_NET_PCI_ROMFILE=
MANAGER_QEMU_DISK_IMG_KERNEL_FILE=/etc/cocos/bzImage
MANAGER_QEMU_DISK_IMG_ROOTFS_FILE=/etc/cocos/rootfs.cpio.gz
MANAGER_QEMU_SEV_ID=sev0
MANAGER_QEMU_SEV_CBITPOS=51
MANAGER_QEMU_SEV_REDUCED_PHYS_BITS=1
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
+66 -49
View File
@@ -2,6 +2,8 @@ module github.com/ultravioletrs/cocos
go 1.23.2
toolchain go1.24.1
require (
github.com/absmach/magistrala v0.15.1
github.com/caarlos0/env/v11 v11.3.1
@@ -9,35 +11,42 @@ require (
github.com/go-kit/kit v0.13.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/google/go-sev-guest v0.13.0
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 // indirect
github.com/mdlayher/vsock v1.2.1
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.59.0
go.opentelemetry.io/otel/trace v1.34.0
golang.org/x/crypto v0.35.0
golang.org/x/sync v0.12.0
google.golang.org/grpc v1.71.0
google.golang.org/protobuf v1.36.5
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 (
cel.dev/expr v0.19.2 // indirect
cloud.google.com/go v0.118.3 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.4.1 // indirect
cloud.google.com/go/monitoring v1.24.0 // indirect
cloud.google.com/go/storage v1.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
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-20250121191232-2f005788dc42 // 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
@@ -45,74 +54,82 @@ require (
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/golang/protobuf v1.5.4 // indirect
github.com/google/certificate-transparency-go v1.1.2 // indirect
github.com/google/gce-tcb-verifier v0.3.1 // 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.5 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // 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
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.34.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.34.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.34.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.34.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.28.0 // indirect
golang.org/x/time v0.10.0 // indirect
google.golang.org/api v0.224.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // 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 v28.0.1+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/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc // indirect
github.com/google/go-tpm v0.9.3
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
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
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/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.34.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.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0
golang.org/x/text v0.22.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // 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
)
+173 -1289
View File
File diff suppressed because it is too large Load Diff
+5 -6
View File
@@ -35,11 +35,11 @@ DISK_IMG_ROOTFS_FILE=
KERNEL_COMMAND_LINE="quiet console=null"
#### Sev Config
SEV_ID=sev0
SEV_CBIT_POS=51
SEV_REDUCED_PHYS_BITS=1
SEV_HOST_DATA=
#### 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
@@ -47,7 +47,6 @@ VSOCK_GUEST_CID=3
BIN_PATH=qemu-system-x86_64
USE_SUDO=false
ENABLE_SEV=false
ENABLE_SEV_SNP=false
ENABLE_KVM=true
+174 -8
View File
@@ -11,14 +11,16 @@ users:
- 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
chpasswd:
list: |
cocos_user:password
expire: False
ssh_pwauth: True
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
@@ -27,6 +29,8 @@ packages:
- 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
@@ -134,7 +138,134 @@ write_files:
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
@@ -163,6 +294,41 @@ runcmd:
- 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"
@@ -171,4 +337,4 @@ runcmd:
- 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 setup complete. Verify logs to confirm successful service startup."
final_message: "Cocos agent and algo_user setup complete. Verify logs to confirm successful service startup."
+8 -12
View File
@@ -74,21 +74,18 @@ construct_qemu_args() {
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 (if enabled)
if [ "$ENABLE_SEV" == "true" ] || [ "$ENABLE_SEV_SNP" == "true" ]; then
sev_type="sev-guest"
# SEV_SNP (if enabled)
if [ "$ENABLE_SEV_SNP" == "true" ]; then
kernel_hash=""
host_data=""
args+=("-machine" "confidential-guest-support=$SEV_ID,memory-backend=$MEM_ID")
args+=("-machine" "confidential-guest-support=$SEV_SNP_ID,memory-backend=$MEM_ID")
if [ "$ENABLE_SEV_SNP" == "true" ]; then
args+=("-bios" "$OVMF_CODE_FILE")
sev_type="sev-snp-guest"
args+=("-bios" "$OVMF_CODE_FILE")
sev_snp_type="sev-snp-guest"
if [ -n "$SEV_HOST_DATA" ]; then
host_data=",host-data=$SEV_HOST_DATA"
fi
if [ -n "$SEV_SNP_HOST_DATA" ]; then
host_data=",host-data=$SEV_SNP_HOST_DATA"
fi
if [ "$ENABLE_KERNEL_HASH" == "true" ]; then
@@ -96,7 +93,7 @@ construct_qemu_args() {
fi
args+=("-object" "memory-backend-memfd,id=$MEM_ID,size=$MEMORY_SIZE,share=true,prealloc=false")
args+=("-object" "$sev_type,id=$SEV_ID,cbitpos=$SEV_CBIT_POS,reduced-phys-bits=$SEV_REDUCED_PHYS_BITS$kernel_hash$host_data")
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
@@ -111,7 +108,6 @@ construct_qemu_args() {
fi
args+=("-monitor" "$MONITOR")
args+=("-no-reboot")
args+=("-vnc" ":9")
echo "${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
+33
View File
@@ -76,3 +76,36 @@ CONFIG_9P_FS_SECURITY=y
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
+2 -2
View File
@@ -7,11 +7,11 @@ Before=docker.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
RestartSec=5s
[Install]
WantedBy=default.target
+98 -111
View File
@@ -5,27 +5,20 @@ 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"
"github.com/ultravioletrs/cocos/pkg/atls"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
@@ -50,18 +43,22 @@ const (
type Server struct {
server.BaseServer
mu sync.RWMutex
server *grpc.Server
registerService serviceRegister
quoteProvider client.LeveledQuoteProvider
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.LeveledQuoteProvider, 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{
@@ -74,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()),
@@ -92,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)
@@ -173,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 {
@@ -211,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
}
@@ -239,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
@@ -257,64 +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,
})
cert, err := x509.ParseCertificate(certDERBytes)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse certificate: %w", err)
}
pubKeyDER, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal public key to DER format: %w", err)
}
if err := vtpm.ExtendPCR(vtpm.PCR15, pubKeyDER); err != nil {
return nil, nil, fmt.Errorf("failed to extend vTPM PCR with public key: %w", err)
}
return certBytes, keyBytes, nil
}
+44 -79
View File
@@ -21,7 +21,6 @@ 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"
@@ -31,28 +30,6 @@ const bufSize = 1024 * 1024
var lis *bufconn.Listener
type DummyRWC struct{}
// Read fills p with byte(len(p)) and returns len(p).
func (l *DummyRWC) Read(p []byte) (int, error) {
n := len(p)
// Fill each byte in p with the value of n as a byte.
for i := range p {
p[i] = byte(n)
}
return n, nil
}
// Write simply returns len(p) indicating that all bytes were written.
func (l *DummyRWC) Write(p []byte) (int, error) {
// In this simple implementation, we ignore the data.
return len(p), nil
}
func (l *DummyRWC) Close() error {
return nil
}
func init() {
lis = bufconn.Listen(bufSize)
}
@@ -70,10 +47,9 @@ func TestNew(t *testing.T) {
},
}
logger := slog.Default()
qp := new(mocks.LeveledQuoteProvider)
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)
@@ -120,10 +96,9 @@ func TestServerStartWithTLSFile(t *testing.T) {
logBuffer := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.LeveledQuoteProvider)
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)
@@ -167,10 +142,9 @@ func TestServerStartWithmTLSFile(t *testing.T) {
logBuffer := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.LeveledQuoteProvider)
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)
@@ -207,10 +181,9 @@ func TestServerStop(t *testing.T) {
}
buf := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.LeveledQuoteProvider)
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()
@@ -282,7 +255,7 @@ func (b *ThreadSafeBuffer) String() string {
}
func TestServerInitializationAndStartup(t *testing.T) {
vtpm.ExternalTPM = &DummyRWC{}
vtpm.ExternalTPM = &vtpm.DummyRWC{}
testCases := []struct {
name string
@@ -332,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",
},
}
@@ -399,10 +365,9 @@ func TestServerInitializationAndStartup(t *testing.T) {
logBuffer := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.LeveledQuoteProvider)
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)
+3 -3
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
@@ -20,7 +20,7 @@ func (_m *Server) EXPECT() *Server_Expecter {
return &Server_Expecter{mock: &_m.Mock}
}
// Start provides a mock function with given fields:
// Start provides a mock function with no fields
func (_m *Server) Start() error {
ret := _m.Called()
@@ -65,7 +65,7 @@ func (_c *Server_Start_Call) RunAndReturn(run func() error) *Server_Start_Call {
return _c
}
// Stop provides a mock function with given fields:
// Stop provides a mock function with no fields
func (_m *Server) Stop() error {
ret := _m.Called()
+1 -1
View File
@@ -23,7 +23,7 @@ type ServerConfiguration interface {
type BaseConfig struct {
Host string `env:"HOST" envDefault:"localhost"`
Port string `env:"PORT" envDefault:"7001"`
ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""`
ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""`
CertFile string `env:"SERVER_CERT" envDefault:""`
KeyFile string `env:"SERVER_KEY" envDefault:""`
ClientCAFile string `env:"CLIENT_CA_CERTS" envDefault:""`
+24 -22
View File
@@ -19,7 +19,7 @@ The service is configured using the environment variables from the following tab
| MANAGER_GRPC_SERVER_CA_CERTS | The file path for the server CA certificate(s). | |
| MANAGER_GRPC_URL | The URL for the gRPC endpoint. | localhost:7001 |
| MANAGER_GRPC_TIMEOUT | The timeout for gRPC requests. | 60s |
| MANAGER_EOS_VERSION | The EOS version used for booting SVMs. | |
| MANAGER_EOS_VERSION | The EOS version used for booting CVMs. | |
| MANAGER_INSTANCE_ID | Manager service instance ID | |
| MANAGER_QEMU_MEMORY_SIZE | The total memory size for the virtual machine. Can be specified in a human-readable format like "2048M" or "4G". | 2048M |
| MANAGER_QEMU_MEMORY_SLOTS | The number of memory slots for the virtual machine. | 5 |
@@ -43,11 +43,14 @@ The service is configured using the environment variables from the following tab
| MANAGER_QEMU_VIRTIO_NET_PCI_ROMFILE | The file path for the ROM image for the virtio-net PCI device. | |
| MANAGER_QEMU_DISK_IMG_KERNEL_FILE | The file path for the kernel image. | img/bzImage |
| MANAGER_QEMU_DISK_IMG_ROOTFS_FILE | The file path for the root filesystem image. | img/rootfs.cpio.gz |
| MANAGER_QEMU_SEV_ID | The ID for the Secure Encrypted Virtualization (SEV) device. | sev0 |
| MANAGER_QEMU_SEV_CBITPOS | The position of the C-bit in the physical address. | 51 |
| MANAGER_QEMU_SEV_REDUCED_PHYS_BITS | The number of reduced physical address bits for SEV. | 1 |
| MANAGER_QEMU_ENABLE_HOST_DATA | Enable additional data for the SEV host. | false |
| MANAGER_QEMU_HOST_DATA | Additional data for the SEV host. | |
| MANAGER_QEMU_SEV_SNP_ID | The ID for the Secure Encrypted Virtualization (SEV-SNP) device. | sev0 |
| MANAGER_QEMU_SEV_SNP_CBITPOS | The position of the C-bit in the physical address. | 51 |
| MANAGER_QEMU_SEV_SNP_REDUCED_PHYS_BITS | The number of reduced physical address bits for SEV-SNP. | 1 |
| MANAGER_QEMU_ENABLE_HOST_DATA | Enable additional data for the SEV-SNP host. | false |
| MANAGER_QEMU_HOST_DATA | Additional data for the SEV-SNP host. | |
| MANAGER_QEMU_TDX_ID | The ID for the Trust Domain Extensions (TDX) device. | tdx0 |
| MANAGER_QEMU_QUOTE_GENERATION_PORT | The port number for virtual socket used to communicate with the Quote Generation Service (QGS). | 4050 |
| MANAGER_QEMU_OVMF_FILE | The file path for the OVMF file (combined OVMF_CODE and OVMF_VARS file). | /usr/share/ovmf/OVMF.fd |
| MANAGER_QEMU_IGVM_ID | The ID of the IGVM file. | igvm0 |
| MANAGER_QEMU_IGVM_FILE | The file path to the IGVM file. | /root/coconut-qemu.igvm |
| MANAGER_QEMU_VSOCK_ID | The ID for the virtual socket device. | vhost-vsock-pci0 |
@@ -55,8 +58,8 @@ The service is configured using the environment variables from the following tab
| MANAGER_QEMU_VSOCK_VNC | Whether to enable the virtual socket device for VNC. | 0 |
| MANAGER_QEMU_BIN_PATH | The file path for the QEMU binary. | qemu-system-x86_64 |
| MANAGER_QEMU_USE_SUDO | Whether to use sudo to run QEMU. | false |
| MANAGER_QEMU_ENABLE_SEV | Whether to enable Secure Encrypted Virtualization (SEV). | false |
| MANAGER_QEMU_ENABLE_SEV_SNP | Whether to enable Secure Nested Paging (SEV-SNP). | true |
| MANAGER_QEMU_ENABLE_TDX | Whether to enable Trust Domain Extensions (TDX). | false |
| MANAGER_QEMU_ENABLE_KVM | Whether to enable the Kernel-based Virtual Machine (KVM) acceleration. | true |
| MANAGER_QEMU_MACHINE | The machine type for QEMU. | q35 |
| MANAGER_QEMU_CPU | The CPU model for QEMU. | EPYC |
@@ -237,19 +240,6 @@ make manager
MANAGER_GRPC_URL=localhost:7001 \
MANAGER_LOG_LEVEL=debug \
MANAGER_QEMU_USE_SUDO=false \
MANAGER_QEMU_ENABLE_SEV=false \
./build/cocos-manager
```
To enable [AMD SEV](https://www.amd.com/en/developer/sev.html) support, start manager like this
```sh
MANAGER_GRPC_URL=localhost:7001
MANAGER_LOG_LEVEL=debug \
MANAGER_QEMU_USE_SUDO=true \
MANAGER_QEMU_ENABLE_SEV=true \
MANAGER_QEMU_SEV_CBITPOS=51 \
./build/cocos-manager
```
@@ -260,14 +250,26 @@ To enable [AMD SEV-SNP](https://www.amd.com/en/developer/sev.html) support, star
```sh
MANAGER_GRPC_URL=localhost:7001 \
MANAGER_LOG_LEVEL=debug \
MANAGER_QEMU_ENABLE_SEV=false \
MANAGER_QEMU_ENABLE_SEV_SNP=true \
MANAGER_QEMU_SEV_CBITPOS=51 \
MANAGER_QEMU_SEV_SNP_CBITPOS=51 \
MANAGER_QEMU_BIN_PATH=<path to QEMU binary> \
MANAGER_QEMU_IGVM_FILE=<path to IGVM file> \
./build/cocos-manager
```
To enable [TDX](https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html) support, start manager like this
```sh
MANAGER_GRPC_URL=localhost:7001 \
MANAGER_LOG_LEVEL=debug \
MANAGER_QEMU_ENABLE_SEV_SNP=false \
MANAGER_QEMU_ENABLE_TDX=true \
MANAGER_QEMU_CPU=host \
MANAGER_QEMU_BIN_PATH=<path to QEMU binary> \
MANAGER_QEMU_OVMF_FILE=<path to OVMF file> \
./build/cocos-manager
```
### Troubleshooting
If the `ps aux | grep qemu-system-x86_64` give you something like this
+5 -5
View File
@@ -35,22 +35,22 @@ func (s *grpcServer) CreateVm(ctx context.Context, req *manager.CreateReq) (*man
return &manager.CreateRes{
ForwardedPort: port,
SvmId: id,
CvmId: id,
}, nil
}
func (s *grpcServer) RemoveVm(ctx context.Context, req *manager.RemoveReq) (*emptypb.Empty, error) {
if err := s.svc.RemoveVM(ctx, req.SvmId); err != nil {
if err := s.svc.RemoveVM(ctx, req.CvmId); err != nil {
return nil, err
}
return &emptypb.Empty{}, nil
}
func (s *grpcServer) SVMInfo(ctx context.Context, req *manager.SVMInfoReq) (*manager.SVMInfoRes, error) {
ovmf, cpunum, cputype, eosversion := s.svc.ReturnSVMInfo(ctx)
func (s *grpcServer) CVMInfo(ctx context.Context, req *manager.CVMInfoReq) (*manager.CVMInfoRes, error) {
ovmf, cpunum, cputype, eosversion := s.svc.ReturnCVMInfo(ctx)
return &manager.SVMInfoRes{
return &manager.CVMInfoRes{
OvmfVersion: ovmf,
CpuNum: int32(cpunum),
CpuType: cputype,
+425
View File
@@ -0,0 +1,425 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/manager"
"github.com/ultravioletrs/cocos/manager/mocks"
"google.golang.org/protobuf/types/known/emptypb"
)
func TestNewServer(t *testing.T) {
mockSvc := new(mocks.Service)
server := NewServer(mockSvc)
assert.NotNil(t, server)
assert.IsType(t, &grpcServer{}, server)
grpcSrv := server.(*grpcServer)
assert.Equal(t, mockSvc, grpcSrv.svc)
}
func TestCreateVm(t *testing.T) {
tests := []struct {
name string
req *manager.CreateReq
mockPort string
mockId string
mockErr error
expectedRes *manager.CreateRes
expectedErr error
}{
{
name: "successful VM creation",
req: &manager.CreateReq{},
mockPort: "8080",
mockId: "vm-123",
mockErr: nil,
expectedRes: &manager.CreateRes{
ForwardedPort: "8080",
CvmId: "vm-123",
},
expectedErr: nil,
},
{
name: "VM creation with different port",
req: &manager.CreateReq{},
mockPort: "9090",
mockId: "vm-456",
mockErr: nil,
expectedRes: &manager.CreateRes{
ForwardedPort: "9090",
CvmId: "vm-456",
},
expectedErr: nil,
},
{
name: "VM creation failure",
req: &manager.CreateReq{},
mockPort: "",
mockId: "",
mockErr: errors.New("failed to create VM"),
expectedRes: nil,
expectedErr: errors.New("failed to create VM"),
},
{
name: "VM creation with empty request",
req: &manager.CreateReq{},
mockPort: "3000",
mockId: "vm-empty",
mockErr: nil,
expectedRes: &manager.CreateRes{
ForwardedPort: "3000",
CvmId: "vm-empty",
},
expectedErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockSvc := new(mocks.Service)
server := NewServer(mockSvc)
mockSvc.On("CreateVM", mock.Anything, tt.req).Return(tt.mockPort, tt.mockId, tt.mockErr)
res, err := server.CreateVm(context.Background(), tt.req)
if tt.expectedErr != nil {
assert.Error(t, err)
assert.Equal(t, tt.expectedErr.Error(), err.Error())
assert.Nil(t, res)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedRes, res)
}
mockSvc.AssertExpectations(t)
})
}
}
func TestRemoveVm(t *testing.T) {
tests := []struct {
name string
req *manager.RemoveReq
mockErr error
expectedErr error
}{
{
name: "successful VM removal",
req: &manager.RemoveReq{
CvmId: "vm-123",
},
mockErr: nil,
expectedErr: nil,
},
{
name: "VM removal failure",
req: &manager.RemoveReq{
CvmId: "vm-456",
},
mockErr: errors.New("VM not found"),
expectedErr: errors.New("VM not found"),
},
{
name: "VM removal with empty ID",
req: &manager.RemoveReq{
CvmId: "",
},
mockErr: errors.New("invalid VM ID"),
expectedErr: errors.New("invalid VM ID"),
},
{
name: "VM removal with non-existent ID",
req: &manager.RemoveReq{
CvmId: "non-existent-vm",
},
mockErr: errors.New("VM does not exist"),
expectedErr: errors.New("VM does not exist"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockSvc := new(mocks.Service)
server := NewServer(mockSvc)
mockSvc.On("RemoveVM", mock.Anything, tt.req.CvmId).Return(tt.mockErr)
res, err := server.RemoveVm(context.Background(), tt.req)
if tt.expectedErr != nil {
assert.Error(t, err)
assert.Equal(t, tt.expectedErr.Error(), err.Error())
assert.Nil(t, res)
} else {
assert.NoError(t, err)
assert.Equal(t, &emptypb.Empty{}, res)
}
mockSvc.AssertExpectations(t)
})
}
}
func TestCVMInfo(t *testing.T) {
tests := []struct {
name string
req *manager.CVMInfoReq
mockOvmf string
mockCpuNum int
mockCpuType string
mockEosVersion string
expectedRes *manager.CVMInfoRes
}{
{
name: "successful CVM info retrieval",
req: &manager.CVMInfoReq{
Id: "cvm-123",
},
mockOvmf: "OVMF-v1.0",
mockCpuNum: 4,
mockCpuType: "Intel-x86_64",
mockEosVersion: "EOS-v2.1",
expectedRes: &manager.CVMInfoRes{
OvmfVersion: "OVMF-v1.0",
CpuNum: 4,
CpuType: "Intel-x86_64",
EosVersion: "EOS-v2.1",
Id: "cvm-123",
},
},
{
name: "CVM info with different values",
req: &manager.CVMInfoReq{
Id: "cvm-456",
},
mockOvmf: "OVMF-v2.0",
mockCpuNum: 8,
mockCpuType: "AMD-x86_64",
mockEosVersion: "EOS-v3.0",
expectedRes: &manager.CVMInfoRes{
OvmfVersion: "OVMF-v2.0",
CpuNum: 8,
CpuType: "AMD-x86_64",
EosVersion: "EOS-v3.0",
Id: "cvm-456",
},
},
{
name: "CVM info with empty ID",
req: &manager.CVMInfoReq{
Id: "",
},
mockOvmf: "OVMF-v1.5",
mockCpuNum: 2,
mockCpuType: "ARM64",
mockEosVersion: "EOS-v1.8",
expectedRes: &manager.CVMInfoRes{
OvmfVersion: "OVMF-v1.5",
CpuNum: 2,
CpuType: "ARM64",
EosVersion: "EOS-v1.8",
Id: "",
},
},
{
name: "CVM info with zero CPU count",
req: &manager.CVMInfoReq{
Id: "cvm-zero",
},
mockOvmf: "OVMF-v1.0",
mockCpuNum: 0,
mockCpuType: "Unknown",
mockEosVersion: "EOS-v1.0",
expectedRes: &manager.CVMInfoRes{
OvmfVersion: "OVMF-v1.0",
CpuNum: 0,
CpuType: "Unknown",
EosVersion: "EOS-v1.0",
Id: "cvm-zero",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockSvc := new(mocks.Service)
server := NewServer(mockSvc)
mockSvc.On("ReturnCVMInfo", mock.Anything).Return(
tt.mockOvmf, tt.mockCpuNum, tt.mockCpuType, tt.mockEosVersion)
res, err := server.CVMInfo(context.Background(), tt.req)
assert.NoError(t, err)
assert.Equal(t, tt.expectedRes, res)
mockSvc.AssertExpectations(t)
})
}
}
func TestAttestationPolicy(t *testing.T) {
tests := []struct {
name string
req *manager.AttestationPolicyReq
mockPolicy string
mockErr error
expectedRes *manager.AttestationPolicyRes
expectedErr error
}{
{
name: "successful attestation policy fetch",
req: &manager.AttestationPolicyReq{
Id: "policy-123",
},
mockPolicy: `{"version": "1.0", "rules": ["rule1", "rule2"]}`,
mockErr: nil,
expectedRes: &manager.AttestationPolicyRes{
Info: []byte(`{"version": "1.0", "rules": ["rule1", "rule2"]}`),
Id: "policy-123",
},
expectedErr: nil,
},
{
name: "attestation policy fetch failure",
req: &manager.AttestationPolicyReq{
Id: "policy-456",
},
mockPolicy: "",
mockErr: errors.New("policy not found"),
expectedRes: nil,
expectedErr: errors.New("policy not found"),
},
{
name: "attestation policy with empty ID",
req: &manager.AttestationPolicyReq{
Id: "",
},
mockPolicy: "",
mockErr: errors.New("invalid policy ID"),
expectedRes: nil,
expectedErr: errors.New("invalid policy ID"),
},
{
name: "attestation policy with different content",
req: &manager.AttestationPolicyReq{
Id: "policy-789",
},
mockPolicy: `{"version": "2.0", "attestation_type": "SGX"}`,
mockErr: nil,
expectedRes: &manager.AttestationPolicyRes{
Info: []byte(`{"version": "2.0", "attestation_type": "SGX"}`),
Id: "policy-789",
},
expectedErr: nil,
},
{
name: "attestation policy with empty policy content",
req: &manager.AttestationPolicyReq{
Id: "policy-empty",
},
mockPolicy: "",
mockErr: nil,
expectedRes: &manager.AttestationPolicyRes{
Info: []byte{},
Id: "policy-empty",
},
expectedErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockSvc := new(mocks.Service)
server := NewServer(mockSvc)
mockSvc.On("FetchAttestationPolicy", mock.Anything, tt.req.Id).Return([]byte(tt.mockPolicy), tt.mockErr)
res, err := server.AttestationPolicy(context.Background(), tt.req)
if tt.expectedErr != nil {
assert.Error(t, err)
assert.Equal(t, tt.expectedErr.Error(), err.Error())
assert.Nil(t, res)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedRes, res)
}
mockSvc.AssertExpectations(t)
})
}
}
func TestContextCancellation(t *testing.T) {
t.Run("CreateVm with cancelled context", func(t *testing.T) {
mockSvc := new(mocks.Service)
server := NewServer(mockSvc)
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel the context immediately
req := &manager.CreateReq{}
mockSvc.On("CreateVM", mock.Anything, req).Return("", "", context.Canceled)
res, err := server.CreateVm(ctx, req)
assert.Error(t, err)
assert.Equal(t, context.Canceled, err)
assert.Nil(t, res)
mockSvc.AssertExpectations(t)
})
t.Run("RemoveVm with cancelled context", func(t *testing.T) {
mockSvc := new(mocks.Service)
server := NewServer(mockSvc)
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel the context immediately
req := &manager.RemoveReq{CvmId: "vm-123"}
mockSvc.On("RemoveVM", mock.Anything, "vm-123").Return(context.Canceled)
res, err := server.RemoveVm(ctx, req)
assert.Error(t, err)
assert.Equal(t, context.Canceled, err)
assert.Nil(t, res)
mockSvc.AssertExpectations(t)
})
}
func TestErrorHandling(t *testing.T) {
t.Run("service returns multiple error types", func(t *testing.T) {
mockSvc := new(mocks.Service)
server := NewServer(mockSvc)
// Test with different error types
customErr := errors.New("custom service error")
req := &manager.CreateReq{}
mockSvc.On("CreateVM", mock.Anything, req).Return("", "", customErr)
res, err := server.CreateVm(context.Background(), req)
assert.Error(t, err)
assert.Equal(t, customErr, err)
assert.Nil(t, res)
mockSvc.AssertExpectations(t)
})
}
+20 -3
View File
@@ -67,11 +67,28 @@ func (lm *loggingMiddleware) FetchAttestationPolicy(ctx context.Context, cmpId s
return lm.svc.FetchAttestationPolicy(ctx, cmpId)
}
func (lm *loggingMiddleware) ReturnSVMInfo(ctx context.Context) (string, int, string, string) {
func (lm *loggingMiddleware) ReturnCVMInfo(ctx context.Context) (string, int, string, string) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method ReturnSVMInfo for computation took %s to complete", time.Since(begin))
message := fmt.Sprintf("Method ReturnCVMInfo for computation took %s to complete", time.Since(begin))
lm.logger.Info(message)
}(time.Now())
return lm.svc.ReturnSVMInfo(ctx)
return lm.svc.ReturnCVMInfo(ctx)
}
func (lm *loggingMiddleware) Shutdown() (err error) {
defer func(begin time.Time) {
if err != nil {
lm.logger.Warn("Method Shutdown completed with error",
"time_taken", time.Since(begin),
"error", err,
)
return
}
lm.logger.Info("Method Shutdown completed successfully",
"time_taken", time.Since(begin),
)
}(time.Now())
return lm.svc.Shutdown()
}
+13 -4
View File
@@ -59,11 +59,20 @@ func (ms *metricsMiddleware) FetchAttestationPolicy(ctx context.Context, cmpId s
return ms.svc.FetchAttestationPolicy(ctx, cmpId)
}
func (ms *metricsMiddleware) ReturnSVMInfo(ctx context.Context) (string, int, string, string) {
func (ms *metricsMiddleware) ReturnCVMInfo(ctx context.Context) (string, int, string, string) {
defer func(begin time.Time) {
ms.counter.With("method", "ReturnSVMInfo").Add(1)
ms.latency.With("method", "ReturnSVMInfo").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "ReturnCVMInfo").Add(1)
ms.latency.With("method", "ReturnCVMInfo").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ReturnSVMInfo(ctx)
return ms.svc.ReturnCVMInfo(ctx)
}
func (ms *metricsMiddleware) Shutdown() error {
defer func(begin time.Time) {
ms.counter.With("method", "Shutdown").Add(1)
ms.latency.With("method", "Shutdown").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Shutdown()
}
+74 -31
View File
@@ -12,23 +12,71 @@ import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/google/go-sev-guest/proto/check"
"github.com/ultravioletrs/cocos/manager/qemu"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
"github.com/virtee/sev-snp-measure-go/cpuid"
"github.com/virtee/sev-snp-measure-go/guest"
"github.com/virtee/sev-snp-measure-go/vmmtypes"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
const defGuestFeatures = 0x1
func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationId string) ([]byte, error) {
ms.mu.Lock()
vm, exists := ms.vms[computationId]
ms.mu.Unlock()
if !exists {
return nil, fmt.Errorf("computationId %s not found", computationId)
}
vmi, ok := vm.GetConfig().(qemu.VMInfo)
if !ok {
return nil, fmt.Errorf("failed to cast config to qemu.VMInfo")
}
var attestPolicyCmd *cmdconfig.CmdConfig
var err error
switch {
case vmi.Config.EnableSEVSNP:
attestPolicyCmd, err = fetchSNPAttestationPolicy(ms)
case vmi.Config.EnableTDX:
attestPolicyCmd, err = fetchTDXAttestationPolicy(ms)
}
if err != nil {
return nil, err
}
var stdOutByte []byte
ms.ap.Lock()
switch {
case vmi.Config.EnableSEVSNP:
stdOutByte, err = attestPolicyCmd.Run(ms.attestationPolicyBinaryPath)
case vmi.Config.EnableTDX:
stdOutByte, err = attestPolicyCmd.Run("")
}
ms.ap.Unlock()
if err != nil {
return nil, err
}
var policy []byte
switch {
case vmi.Config.EnableSEVSNP:
policy, err = readSEVSNPPolicy(stdOutByte, ms, vmi)
case vmi.Config.EnableTDX:
policy = stdOutByte
err = nil
}
if err != nil {
return nil, err
}
return policy, nil
}
func fetchSNPAttestationPolicy(ms *managerService) (*cmdconfig.CmdConfig, error) {
var stderrBuffer bytes.Buffer
options := []string{"--policy", "196608"}
@@ -44,38 +92,33 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
return nil, err
}
ms.mu.Lock()
vm, exists := ms.vms[computationId]
ms.mu.Unlock()
if !exists {
return nil, fmt.Errorf("computationId %s not found", computationId)
}
return attestPolicyCmd, nil
}
vmi, ok := vm.GetConfig().(qemu.VMInfo)
if !ok {
return nil, fmt.Errorf("failed to cast config to qemu.VMInfo")
}
func fetchTDXAttestationPolicy(ms *managerService) (*cmdconfig.CmdConfig, error) {
var stderrBuffer bytes.Buffer
ms.ap.Lock()
stdOutByte, err := attestPolicyCmd.Run(ms.attestationPolicyBinaryPath)
ms.ap.Unlock()
stderr := bufio.NewWriter(&stderrBuffer)
attestPolicyCmd, err := cmdconfig.NewCmdConfig(ms.attestationPolicyBinaryPath, nil, stderr)
if err != nil {
return nil, err
}
attestationPolicy := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
return attestPolicyCmd, nil
}
if err = config.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
func readSEVSNPPolicy(stdOutByte []byte, ms *managerService, vmi qemu.VMInfo) ([]byte, error) {
attestationPolicy := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
if err := vtpm.ReadPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
return nil, err
}
var stderrBuffer bytes.Buffer
var measurement []byte
var err error
switch {
case vmi.Config.EnableSEV:
measurement, err = guest.CalcLaunchDigest(guest.SEV, vmi.Config.SMPCount, uint64(cpuid.CpuSigs[ms.qemuCfg.CPU]), vmi.Config.OVMFCodeConfig.File, vmi.Config.KernelFile, vmi.Config.RootFsFile, strconv.Quote(qemu.KernelCommandLine), defGuestFeatures, "", vmmtypes.QEMU, false, "", 0)
if err != nil {
return nil, err
}
case vmi.Config.EnableSEVSNP:
stderr := bufio.NewWriter(&stderrBuffer)
options := cmdconfig.IgvmMeasureOptions
@@ -110,8 +153,8 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
attestationPolicy.Config.Policy.Measurement = measurement
}
if vmi.Config.SevConfig.EnableHostData {
hostData, err := base64.StdEncoding.DecodeString(vmi.Config.SevConfig.HostData)
if vmi.Config.SEVSNPConfig.EnableHostData {
hostData, err := base64.StdEncoding.DecodeString(vmi.Config.SEVSNPConfig.HostData)
if err != nil {
return nil, err
}
@@ -120,7 +163,7 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
attestationPolicy.Config.Policy.MinimumLaunchTcb = vmi.LaunchTCB
f, err := json.MarshalIndent(attestationPolicy, "", " ")
f, err := vtpm.ConvertPolicyToJSON(&attestationPolicy)
if err != nil {
return nil, err
}
+6 -10
View File
@@ -53,22 +53,18 @@ func TestFetchAttestationPolicy(t *testing.T) {
expectedResult map[string]interface{}
}{
{
name: "Valid SEV configuration",
computationId: "sev-computation",
name: "Valid SEV-SNP configuration",
computationId: "sev-snp-computation",
binaryBehavior: "success",
vmConfig: qemu.VMInfo{
Config: qemu.Config{
EnableSEV: true,
EnableSEVSNP: false,
EnableSEVSNP: true,
SMPCount: 2,
CPU: "EPYC",
OVMFCodeConfig: qemu.OVMFCodeConfig{
File: "/path/to/OVMF_CODE.fd",
},
},
LaunchTCB: 0,
},
expectedError: "open /path/to/OVMF_CODE.fd: no such file or directory",
expectedError: "pathToBinary cannot be empty",
},
{
name: "Invalid computation ID",
@@ -90,7 +86,7 @@ func TestFetchAttestationPolicy(t *testing.T) {
binaryBehavior: "fail",
vmConfig: qemu.VMInfo{
Config: qemu.Config{
EnableSEV: true,
EnableSEVSNP: true,
},
LaunchTCB: 0,
},
@@ -102,7 +98,7 @@ func TestFetchAttestationPolicy(t *testing.T) {
binaryBehavior: "no_json",
vmConfig: qemu.VMInfo{
Config: qemu.Config{
EnableSEV: true,
EnableSEVSNP: true,
},
LaunchTCB: 0,
},
+98 -78
View File
@@ -32,6 +32,8 @@ type CreateReq struct {
AgentCvmClientKey []byte `protobuf:"bytes,3,opt,name=agent_cvm_client_key,json=agentCvmClientKey,proto3" json:"agent_cvm_client_key,omitempty"`
AgentCvmClientCert []byte `protobuf:"bytes,4,opt,name=agent_cvm_client_cert,json=agentCvmClientCert,proto3" json:"agent_cvm_client_cert,omitempty"`
AgentCvmServerUrl string `protobuf:"bytes,5,opt,name=agent_cvm_server_url,json=agentCvmServerUrl,proto3" json:"agent_cvm_server_url,omitempty"`
AgentCvmCaUrl string `protobuf:"bytes,6,opt,name=agent_cvm_ca_url,json=agentCvmCaUrl,proto3" json:"agent_cvm_ca_url,omitempty"`
Ttl string `protobuf:"bytes,7,opt,name=ttl,proto3" json:"ttl,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -101,10 +103,24 @@ func (x *CreateReq) GetAgentCvmServerUrl() string {
return ""
}
func (x *CreateReq) GetAgentCvmCaUrl() string {
if x != nil {
return x.AgentCvmCaUrl
}
return ""
}
func (x *CreateReq) GetTtl() string {
if x != nil {
return x.Ttl
}
return ""
}
type CreateRes struct {
state protoimpl.MessageState `protogen:"open.v1"`
ForwardedPort string `protobuf:"bytes,1,opt,name=forwarded_port,json=forwardedPort,proto3" json:"forwarded_port,omitempty"`
SvmId string `protobuf:"bytes,2,opt,name=svm_id,json=svmId,proto3" json:"svm_id,omitempty"`
CvmId string `protobuf:"bytes,2,opt,name=cvm_id,json=cvmId,proto3" json:"cvm_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -146,16 +162,16 @@ func (x *CreateRes) GetForwardedPort() string {
return ""
}
func (x *CreateRes) GetSvmId() string {
func (x *CreateRes) GetCvmId() string {
if x != nil {
return x.SvmId
return x.CvmId
}
return ""
}
type RemoveReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
SvmId string `protobuf:"bytes,1,opt,name=svm_id,json=svmId,proto3" json:"svm_id,omitempty"`
CvmId string `protobuf:"bytes,1,opt,name=cvm_id,json=cvmId,proto3" json:"cvm_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -190,9 +206,9 @@ func (*RemoveReq) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{2}
}
func (x *RemoveReq) GetSvmId() string {
func (x *RemoveReq) GetCvmId() string {
if x != nil {
return x.SvmId
return x.CvmId
}
return ""
}
@@ -249,7 +265,7 @@ func (x *AttestationPolicyRes) GetId() string {
return ""
}
type SVMInfoRes struct {
type CVMInfoRes struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
OvmfVersion string `protobuf:"bytes,2,opt,name=ovmf_version,json=ovmfVersion,proto3" json:"ovmf_version,omitempty"`
@@ -261,20 +277,20 @@ type SVMInfoRes struct {
sizeCache protoimpl.SizeCache
}
func (x *SVMInfoRes) Reset() {
*x = SVMInfoRes{}
func (x *CVMInfoRes) Reset() {
*x = CVMInfoRes{}
mi := &file_manager_manager_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SVMInfoRes) String() string {
func (x *CVMInfoRes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SVMInfoRes) ProtoMessage() {}
func (*CVMInfoRes) ProtoMessage() {}
func (x *SVMInfoRes) ProtoReflect() protoreflect.Message {
func (x *CVMInfoRes) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -286,47 +302,47 @@ func (x *SVMInfoRes) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use SVMInfoRes.ProtoReflect.Descriptor instead.
func (*SVMInfoRes) Descriptor() ([]byte, []int) {
// Deprecated: Use CVMInfoRes.ProtoReflect.Descriptor instead.
func (*CVMInfoRes) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{4}
}
func (x *SVMInfoRes) GetId() string {
func (x *CVMInfoRes) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *SVMInfoRes) GetOvmfVersion() string {
func (x *CVMInfoRes) GetOvmfVersion() string {
if x != nil {
return x.OvmfVersion
}
return ""
}
func (x *SVMInfoRes) GetCpuNum() int32 {
func (x *CVMInfoRes) GetCpuNum() int32 {
if x != nil {
return x.CpuNum
}
return 0
}
func (x *SVMInfoRes) GetCpuType() string {
func (x *CVMInfoRes) GetCpuType() string {
if x != nil {
return x.CpuType
}
return ""
}
func (x *SVMInfoRes) GetKernelCmd() string {
func (x *CVMInfoRes) GetKernelCmd() string {
if x != nil {
return x.KernelCmd
}
return ""
}
func (x *SVMInfoRes) GetEosVersion() string {
func (x *CVMInfoRes) GetEosVersion() string {
if x != nil {
return x.EosVersion
}
@@ -377,27 +393,27 @@ func (x *AttestationPolicyReq) GetId() string {
return ""
}
type SVMInfoReq struct {
type CVMInfoReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SVMInfoReq) Reset() {
*x = SVMInfoReq{}
func (x *CVMInfoReq) Reset() {
*x = CVMInfoReq{}
mi := &file_manager_manager_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SVMInfoReq) String() string {
func (x *CVMInfoReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SVMInfoReq) ProtoMessage() {}
func (*CVMInfoReq) ProtoMessage() {}
func (x *SVMInfoReq) ProtoReflect() protoreflect.Message {
func (x *CVMInfoReq) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -409,12 +425,12 @@ func (x *SVMInfoReq) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use SVMInfoReq.ProtoReflect.Descriptor instead.
func (*SVMInfoReq) Descriptor() ([]byte, []int) {
// Deprecated: Use CVMInfoReq.ProtoReflect.Descriptor instead.
func (*CVMInfoReq) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{6}
}
func (x *SVMInfoReq) GetId() string {
func (x *CVMInfoReq) GetId() string {
if x != nil {
return x.Id
}
@@ -427,7 +443,7 @@ var file_manager_manager_proto_rawDesc = string([]byte{
0x0a, 0x15, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x80, 0x02,
0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbb, 0x02,
0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x12, 0x26, 0x0a, 0x0f, 0x61,
0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65,
@@ -444,51 +460,55 @@ var file_manager_manager_proto_rawDesc = string([]byte{
0x2f, 0x0a, 0x14, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x76, 0x6d, 0x5f, 0x73, 0x65, 0x72,
0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x61,
0x67, 0x65, 0x6e, 0x74, 0x43, 0x76, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c,
0x22, 0x49, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x12, 0x25, 0x0a,
0x0e, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x64,
0x50, 0x6f, 0x72, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x76, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x76, 0x6d, 0x49, 0x64, 0x22, 0x22, 0x0a, 0x09, 0x52,
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x76, 0x6d, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x76, 0x6d, 0x49, 0x64, 0x22,
0x3a, 0x0a, 0x14, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xb3, 0x01, 0x0a, 0x0a,
0x53, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x76,
0x6d, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0b, 0x6f, 0x76, 0x6d, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a,
0x07, 0x63, 0x70, 0x75, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06,
0x63, 0x70, 0x75, 0x4e, 0x75, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x70, 0x75, 0x5f, 0x74, 0x79,
0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x70, 0x75, 0x54, 0x79, 0x70,
0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x6d, 0x64, 0x18,
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x43, 0x6d, 0x64,
0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x22, 0x26, 0x0a, 0x14, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1c, 0x0a, 0x0a, 0x53, 0x56, 0x4d,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0x8c, 0x02, 0x0a, 0x0e, 0x4d, 0x61, 0x6e, 0x61,
0x67, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x56, 0x6d, 0x12, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6d, 0x61, 0x6e,
0x61, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00,
0x12, 0x38, 0x0a, 0x08, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x56, 0x6d, 0x12, 0x12, 0x2e, 0x6d,
0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71,
0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x07, 0x53, 0x56,
0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e,
0x53, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x6d, 0x61, 0x6e,
0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x22,
0x00, 0x12, 0x53, 0x0a, 0x11, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e,
0x12, 0x27, 0x0a, 0x10, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x76, 0x6d, 0x5f, 0x63, 0x61,
0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x43, 0x76, 0x6d, 0x43, 0x61, 0x55, 0x72, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x74, 0x6c,
0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x74, 0x6c, 0x22, 0x49, 0x0a, 0x09, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x6f, 0x72, 0x77,
0x61, 0x72, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12,
0x15, 0x0a, 0x06, 0x63, 0x76, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x63, 0x76, 0x6d, 0x49, 0x64, 0x22, 0x22, 0x0a, 0x09, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, 0x63, 0x76, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x76, 0x6d, 0x49, 0x64, 0x22, 0x3a, 0x0a, 0x14, 0x41, 0x74,
0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52,
0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xb3, 0x01, 0x0a, 0x0a, 0x43, 0x56, 0x4d, 0x49, 0x6e,
0x66, 0x6f, 0x52, 0x65, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x76, 0x6d, 0x66, 0x5f, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x76, 0x6d,
0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x70, 0x75, 0x5f,
0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x70, 0x75, 0x4e, 0x75,
0x6d, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x70, 0x75, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x70, 0x75, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a,
0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x6d, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x52, 0x09, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x43, 0x6d, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65,
0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0a, 0x65, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x26, 0x0a, 0x14,
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x6d, 0x61, 0x6e, 0x61,
0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x79, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x02, 0x69, 0x64, 0x22, 0x1c, 0x0a, 0x0a, 0x43, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x52,
0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
0x69, 0x64, 0x32, 0x8c, 0x02, 0x0a, 0x0e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x56,
0x6d, 0x12, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x08, 0x52,
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x56, 0x6d, 0x12, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x72, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x07, 0x43, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f,
0x12, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x56, 0x4d, 0x49, 0x6e,
0x66, 0x6f, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e,
0x43, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x11,
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x41, 0x74, 0x74, 0x65,
0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71,
0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22,
0x00, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
@@ -509,19 +529,19 @@ var file_manager_manager_proto_goTypes = []any{
(*CreateRes)(nil), // 1: manager.CreateRes
(*RemoveReq)(nil), // 2: manager.RemoveReq
(*AttestationPolicyRes)(nil), // 3: manager.AttestationPolicyRes
(*SVMInfoRes)(nil), // 4: manager.SVMInfoRes
(*CVMInfoRes)(nil), // 4: manager.CVMInfoRes
(*AttestationPolicyReq)(nil), // 5: manager.AttestationPolicyReq
(*SVMInfoReq)(nil), // 6: manager.SVMInfoReq
(*CVMInfoReq)(nil), // 6: manager.CVMInfoReq
(*emptypb.Empty)(nil), // 7: google.protobuf.Empty
}
var file_manager_manager_proto_depIdxs = []int32{
0, // 0: manager.ManagerService.CreateVm:input_type -> manager.CreateReq
2, // 1: manager.ManagerService.RemoveVm:input_type -> manager.RemoveReq
6, // 2: manager.ManagerService.SVMInfo:input_type -> manager.SVMInfoReq
6, // 2: manager.ManagerService.CVMInfo:input_type -> manager.CVMInfoReq
5, // 3: manager.ManagerService.AttestationPolicy:input_type -> manager.AttestationPolicyReq
1, // 4: manager.ManagerService.CreateVm:output_type -> manager.CreateRes
7, // 5: manager.ManagerService.RemoveVm:output_type -> google.protobuf.Empty
4, // 6: manager.ManagerService.SVMInfo:output_type -> manager.SVMInfoRes
4, // 6: manager.ManagerService.CVMInfo:output_type -> manager.CVMInfoRes
3, // 7: manager.ManagerService.AttestationPolicy:output_type -> manager.AttestationPolicyRes
4, // [4:8] is the sub-list for method output_type
0, // [0:4] is the sub-list for method input_type
+7 -5
View File
@@ -12,7 +12,7 @@ option go_package = "./manager";
service ManagerService {
rpc CreateVm(CreateReq) returns (CreateRes) {}
rpc RemoveVm(RemoveReq) returns (google.protobuf.Empty) {}
rpc SVMInfo(SVMInfoReq) returns (SVMInfoRes) {}
rpc CVMInfo(CVMInfoReq) returns (CVMInfoRes) {}
rpc AttestationPolicy(AttestationPolicyReq) returns (AttestationPolicyRes) {}
}
@@ -22,15 +22,17 @@ message CreateReq{
bytes agent_cvm_client_key = 3;
bytes agent_cvm_client_cert = 4;
string agent_cvm_server_url = 5;
string agent_cvm_ca_url = 6;
string ttl = 7;
}
message CreateRes{
string forwarded_port = 1;
string svm_id = 2;
string cvm_id = 2;
}
message RemoveReq{
string svm_id = 1;
string cvm_id = 1;
}
message AttestationPolicyRes{
@@ -38,7 +40,7 @@ message AttestationPolicyRes{
string id = 2;
}
message SVMInfoRes{
message CVMInfoRes{
string id = 1;
string ovmf_version = 2;
int32 cpu_num = 3;
@@ -51,7 +53,7 @@ message AttestationPolicyReq {
string id = 1;
}
message SVMInfoReq {
message CVMInfoReq {
string id = 1;
}
+15 -15
View File
@@ -25,7 +25,7 @@ const _ = grpc.SupportPackageIsVersion9
const (
ManagerService_CreateVm_FullMethodName = "/manager.ManagerService/CreateVm"
ManagerService_RemoveVm_FullMethodName = "/manager.ManagerService/RemoveVm"
ManagerService_SVMInfo_FullMethodName = "/manager.ManagerService/SVMInfo"
ManagerService_CVMInfo_FullMethodName = "/manager.ManagerService/CVMInfo"
ManagerService_AttestationPolicy_FullMethodName = "/manager.ManagerService/AttestationPolicy"
)
@@ -35,7 +35,7 @@ const (
type ManagerServiceClient interface {
CreateVm(ctx context.Context, in *CreateReq, opts ...grpc.CallOption) (*CreateRes, error)
RemoveVm(ctx context.Context, in *RemoveReq, opts ...grpc.CallOption) (*emptypb.Empty, error)
SVMInfo(ctx context.Context, in *SVMInfoReq, opts ...grpc.CallOption) (*SVMInfoRes, error)
CVMInfo(ctx context.Context, in *CVMInfoReq, opts ...grpc.CallOption) (*CVMInfoRes, error)
AttestationPolicy(ctx context.Context, in *AttestationPolicyReq, opts ...grpc.CallOption) (*AttestationPolicyRes, error)
}
@@ -67,10 +67,10 @@ func (c *managerServiceClient) RemoveVm(ctx context.Context, in *RemoveReq, opts
return out, nil
}
func (c *managerServiceClient) SVMInfo(ctx context.Context, in *SVMInfoReq, opts ...grpc.CallOption) (*SVMInfoRes, error) {
func (c *managerServiceClient) CVMInfo(ctx context.Context, in *CVMInfoReq, opts ...grpc.CallOption) (*CVMInfoRes, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SVMInfoRes)
err := c.cc.Invoke(ctx, ManagerService_SVMInfo_FullMethodName, in, out, cOpts...)
out := new(CVMInfoRes)
err := c.cc.Invoke(ctx, ManagerService_CVMInfo_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@@ -93,7 +93,7 @@ func (c *managerServiceClient) AttestationPolicy(ctx context.Context, in *Attest
type ManagerServiceServer interface {
CreateVm(context.Context, *CreateReq) (*CreateRes, error)
RemoveVm(context.Context, *RemoveReq) (*emptypb.Empty, error)
SVMInfo(context.Context, *SVMInfoReq) (*SVMInfoRes, error)
CVMInfo(context.Context, *CVMInfoReq) (*CVMInfoRes, error)
AttestationPolicy(context.Context, *AttestationPolicyReq) (*AttestationPolicyRes, error)
mustEmbedUnimplementedManagerServiceServer()
}
@@ -111,8 +111,8 @@ func (UnimplementedManagerServiceServer) CreateVm(context.Context, *CreateReq) (
func (UnimplementedManagerServiceServer) RemoveVm(context.Context, *RemoveReq) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveVm not implemented")
}
func (UnimplementedManagerServiceServer) SVMInfo(context.Context, *SVMInfoReq) (*SVMInfoRes, error) {
return nil, status.Errorf(codes.Unimplemented, "method SVMInfo not implemented")
func (UnimplementedManagerServiceServer) CVMInfo(context.Context, *CVMInfoReq) (*CVMInfoRes, error) {
return nil, status.Errorf(codes.Unimplemented, "method CVMInfo not implemented")
}
func (UnimplementedManagerServiceServer) AttestationPolicy(context.Context, *AttestationPolicyReq) (*AttestationPolicyRes, error) {
return nil, status.Errorf(codes.Unimplemented, "method AttestationPolicy not implemented")
@@ -174,20 +174,20 @@ func _ManagerService_RemoveVm_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _ManagerService_SVMInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SVMInfoReq)
func _ManagerService_CVMInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CVMInfoReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ManagerServiceServer).SVMInfo(ctx, in)
return srv.(ManagerServiceServer).CVMInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ManagerService_SVMInfo_FullMethodName,
FullMethod: ManagerService_CVMInfo_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagerServiceServer).SVMInfo(ctx, req.(*SVMInfoReq))
return srv.(ManagerServiceServer).CVMInfo(ctx, req.(*CVMInfoReq))
}
return interceptor(ctx, in, info, handler)
}
@@ -226,8 +226,8 @@ var ManagerService_ServiceDesc = grpc.ServiceDesc{
Handler: _ManagerService_RemoveVm_Handler,
},
{
MethodName: "SVMInfo",
Handler: _ManagerService_SVMInfo_Handler,
MethodName: "CVMInfo",
Handler: _ManagerService_CVMInfo_Handler,
},
{
MethodName: "AttestationPolicy",
+75 -75
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
@@ -103,6 +103,80 @@ func (_c *ManagerServiceClient_AttestationPolicy_Call) RunAndReturn(run func(con
return _c
}
// CVMInfo provides a mock function with given fields: ctx, in, opts
func (_m *ManagerServiceClient) CVMInfo(ctx context.Context, in *manager.CVMInfoReq, opts ...grpc.CallOption) (*manager.CVMInfoRes, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for CVMInfo")
}
var r0 *manager.CVMInfoRes
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *manager.CVMInfoReq, ...grpc.CallOption) (*manager.CVMInfoRes, error)); ok {
return rf(ctx, in, opts...)
}
if rf, ok := ret.Get(0).(func(context.Context, *manager.CVMInfoReq, ...grpc.CallOption) *manager.CVMInfoRes); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*manager.CVMInfoRes)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *manager.CVMInfoReq, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ManagerServiceClient_CVMInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CVMInfo'
type ManagerServiceClient_CVMInfo_Call struct {
*mock.Call
}
// CVMInfo is a helper method to define mock.On call
// - ctx context.Context
// - in *manager.CVMInfoReq
// - opts ...grpc.CallOption
func (_e *ManagerServiceClient_Expecter) CVMInfo(ctx interface{}, in interface{}, opts ...interface{}) *ManagerServiceClient_CVMInfo_Call {
return &ManagerServiceClient_CVMInfo_Call{Call: _e.mock.On("CVMInfo",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *ManagerServiceClient_CVMInfo_Call) Run(run func(ctx context.Context, in *manager.CVMInfoReq, opts ...grpc.CallOption)) *ManagerServiceClient_CVMInfo_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*manager.CVMInfoReq), variadicArgs...)
})
return _c
}
func (_c *ManagerServiceClient_CVMInfo_Call) Return(_a0 *manager.CVMInfoRes, _a1 error) *ManagerServiceClient_CVMInfo_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *ManagerServiceClient_CVMInfo_Call) RunAndReturn(run func(context.Context, *manager.CVMInfoReq, ...grpc.CallOption) (*manager.CVMInfoRes, error)) *ManagerServiceClient_CVMInfo_Call {
_c.Call.Return(run)
return _c
}
// CreateVm provides a mock function with given fields: ctx, in, opts
func (_m *ManagerServiceClient) CreateVm(ctx context.Context, in *manager.CreateReq, opts ...grpc.CallOption) (*manager.CreateRes, error) {
_va := make([]interface{}, len(opts))
@@ -251,80 +325,6 @@ func (_c *ManagerServiceClient_RemoveVm_Call) RunAndReturn(run func(context.Cont
return _c
}
// SVMInfo provides a mock function with given fields: ctx, in, opts
func (_m *ManagerServiceClient) SVMInfo(ctx context.Context, in *manager.SVMInfoReq, opts ...grpc.CallOption) (*manager.SVMInfoRes, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for SVMInfo")
}
var r0 *manager.SVMInfoRes
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *manager.SVMInfoReq, ...grpc.CallOption) (*manager.SVMInfoRes, error)); ok {
return rf(ctx, in, opts...)
}
if rf, ok := ret.Get(0).(func(context.Context, *manager.SVMInfoReq, ...grpc.CallOption) *manager.SVMInfoRes); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*manager.SVMInfoRes)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *manager.SVMInfoReq, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ManagerServiceClient_SVMInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SVMInfo'
type ManagerServiceClient_SVMInfo_Call struct {
*mock.Call
}
// SVMInfo is a helper method to define mock.On call
// - ctx context.Context
// - in *manager.SVMInfoReq
// - opts ...grpc.CallOption
func (_e *ManagerServiceClient_Expecter) SVMInfo(ctx interface{}, in interface{}, opts ...interface{}) *ManagerServiceClient_SVMInfo_Call {
return &ManagerServiceClient_SVMInfo_Call{Call: _e.mock.On("SVMInfo",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *ManagerServiceClient_SVMInfo_Call) Run(run func(ctx context.Context, in *manager.SVMInfoReq, opts ...grpc.CallOption)) *ManagerServiceClient_SVMInfo_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*manager.SVMInfoReq), variadicArgs...)
})
return _c
}
func (_c *ManagerServiceClient_SVMInfo_Call) Return(_a0 *manager.SVMInfoRes, _a1 error) *ManagerServiceClient_SVMInfo_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *ManagerServiceClient_SVMInfo_Call) RunAndReturn(run func(context.Context, *manager.SVMInfoReq, ...grpc.CallOption) (*manager.SVMInfoRes, error)) *ManagerServiceClient_SVMInfo_Call {
_c.Call.Return(run)
return _c
}
// NewManagerServiceClient creates a new instance of ManagerServiceClient. 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 NewManagerServiceClient(t interface {
+57 -12
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
@@ -195,12 +195,12 @@ func (_c *Service_RemoveVM_Call) RunAndReturn(run func(context.Context, string)
return _c
}
// ReturnSVMInfo provides a mock function with given fields: ctx
func (_m *Service) ReturnSVMInfo(ctx context.Context) (string, int, string, string) {
// ReturnCVMInfo provides a mock function with given fields: ctx
func (_m *Service) ReturnCVMInfo(ctx context.Context) (string, int, string, string) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for ReturnSVMInfo")
panic("no return value specified for ReturnCVMInfo")
}
var r0 string
@@ -237,30 +237,75 @@ func (_m *Service) ReturnSVMInfo(ctx context.Context) (string, int, string, stri
return r0, r1, r2, r3
}
// Service_ReturnSVMInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReturnSVMInfo'
type Service_ReturnSVMInfo_Call struct {
// Service_ReturnCVMInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReturnCVMInfo'
type Service_ReturnCVMInfo_Call struct {
*mock.Call
}
// ReturnSVMInfo is a helper method to define mock.On call
// ReturnCVMInfo is a helper method to define mock.On call
// - ctx context.Context
func (_e *Service_Expecter) ReturnSVMInfo(ctx interface{}) *Service_ReturnSVMInfo_Call {
return &Service_ReturnSVMInfo_Call{Call: _e.mock.On("ReturnSVMInfo", ctx)}
func (_e *Service_Expecter) ReturnCVMInfo(ctx interface{}) *Service_ReturnCVMInfo_Call {
return &Service_ReturnCVMInfo_Call{Call: _e.mock.On("ReturnCVMInfo", ctx)}
}
func (_c *Service_ReturnSVMInfo_Call) Run(run func(ctx context.Context)) *Service_ReturnSVMInfo_Call {
func (_c *Service_ReturnCVMInfo_Call) Run(run func(ctx context.Context)) *Service_ReturnCVMInfo_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *Service_ReturnSVMInfo_Call) Return(_a0 string, _a1 int, _a2 string, _a3 string) *Service_ReturnSVMInfo_Call {
func (_c *Service_ReturnCVMInfo_Call) Return(_a0 string, _a1 int, _a2 string, _a3 string) *Service_ReturnCVMInfo_Call {
_c.Call.Return(_a0, _a1, _a2, _a3)
return _c
}
func (_c *Service_ReturnSVMInfo_Call) RunAndReturn(run func(context.Context) (string, int, string, string)) *Service_ReturnSVMInfo_Call {
func (_c *Service_ReturnCVMInfo_Call) RunAndReturn(run func(context.Context) (string, int, string, string)) *Service_ReturnCVMInfo_Call {
_c.Call.Return(run)
return _c
}
// Shutdown provides a mock function with no fields
func (_m *Service) Shutdown() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Shutdown")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Service_Shutdown_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Shutdown'
type Service_Shutdown_Call struct {
*mock.Call
}
// Shutdown is a helper method to define mock.On call
func (_e *Service_Expecter) Shutdown() *Service_Shutdown_Call {
return &Service_Shutdown_Call{Call: _e.mock.On("Shutdown")}
}
func (_c *Service_Shutdown_Call) Run(run func()) *Service_Shutdown_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Service_Shutdown_Call) Return(_a0 error) *Service_Shutdown_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Service_Shutdown_Call) RunAndReturn(run func() error) *Service_Shutdown_Call {
_c.Call.Return(run)
return _c
}
+64 -23
View File
@@ -5,11 +5,14 @@ package qemu
import (
"fmt"
"strconv"
"github.com/caarlos0/env/v10"
)
const (
BaseGuestCID = 3
KernelCommandLine = "quiet console=null"
TDXObject = "{\"qom-type\":\"tdx-guest\",\"id\":\"%s\",\"quote-generation-socket\":{\"type\": \"vsock\", \"cid\":\"2\",\"port\":\"%d\"}}"
)
type MemoryConfig struct {
@@ -52,14 +55,20 @@ type DiskImgConfig struct {
RootFsFile string `env:"DISK_IMG_ROOTFS_FILE" envDefault:"img/rootfs.cpio.gz"`
}
type SevConfig struct {
ID string `env:"SEV_ID" envDefault:"sev0"`
CBitPos int `env:"SEV_CBITPOS" envDefault:"51"`
ReducedPhysBits int `env:"SEV_REDUCED_PHYS_BITS" envDefault:"1"`
type SEVSNPConfig struct {
ID string `env:"SEV_SNP_ID" envDefault:"sev0"`
CBitPos int `env:"SEV_SNP_CBIT_POS" envDefault:"51"`
ReducedPhysBits int `env:"SEV_SNP_REDUCED_PHYS_BITS" envDefault:"1"`
EnableHostData bool `env:"ENABLE_HOST_DATA" envDefault:"false"`
HostData string `env:"HOST_DATA" envDefault:""`
}
type TDXConfig struct {
ID string `env:"TDX_ID" envDefault:"tdx0"`
QuoteGenerationPort int `env:"QUOTE_GENERATION_PORT" envDefault:"4050"`
OVMF string `env:"OVMF_FILE" envDefault:"/usr/share/ovmf/OVMF.fd"`
}
type IGVMConfig struct {
ID string `env:"IGVM_ID" envDefault:"igvm0"`
File string `env:"IGVM_FILE" envDefault:"/root/coconut-qemu.igvm"`
@@ -71,10 +80,10 @@ type VSockConfig struct {
}
type Config struct {
EnableSEVSNP bool
EnableTDX bool
QemuBinPath string `env:"BIN_PATH" envDefault:"qemu-system-x86_64"`
UseSudo bool `env:"USE_SUDO" envDefault:"false"`
EnableSEV bool `env:"ENABLE_SEV" envDefault:"false"`
EnableSEVSNP bool `env:"ENABLE_SEV_SNP" envDefault:"true"`
EnableKVM bool `env:"ENABLE_KVM" envDefault:"true"`
@@ -100,8 +109,11 @@ type Config struct {
// disk
DiskImgConfig
// SEV
SevConfig
// SEV-SNP
SEVSNPConfig
// TDX
TDXConfig
// vTPM
IGVMConfig
@@ -142,7 +154,7 @@ func (config Config) ConstructQemuArgs() []string {
config.MemoryConfig.Slots,
config.MemoryConfig.Max))
if !config.EnableSEVSNP {
if !config.EnableSEVSNP && !config.EnableTDX {
// OVMF
args = append(args, "-drive",
fmt.Sprintf("if=%s,format=%s,unit=%d,file=%s,readonly=%s",
@@ -176,23 +188,19 @@ func (config Config) ConstructQemuArgs() []string {
args = append(args, "-device", fmt.Sprintf("vhost-vsock-pci,id=%s,guest-cid=%d", config.VSockConfig.ID, config.VSockConfig.GuestCID))
// SEV
if config.EnableSEV || config.EnableSEVSNP {
sevType := "sev-guest"
// SEV-SNP
if config.EnableSEVSNP {
sevSnpType := "sev-snp-guest"
hostData := ""
args = append(args, "-machine",
fmt.Sprintf("confidential-guest-support=%s,memory-backend=%s,igvm-cfg=%s",
config.SevConfig.ID,
config.SEVSNPConfig.ID,
config.MemID,
config.IGVMConfig.ID))
if config.EnableSEVSNP {
sevType = "sev-snp-guest"
if config.SevConfig.EnableHostData {
hostData = fmt.Sprintf(",host-data=%s", config.SevConfig.HostData)
}
if config.SEVSNPConfig.EnableHostData {
hostData = fmt.Sprintf(",host-data=%s", config.SEVSNPConfig.HostData)
}
args = append(args, "-object",
@@ -202,10 +210,10 @@ func (config Config) ConstructQemuArgs() []string {
args = append(args, "-object",
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s",
sevType,
config.SevConfig.ID,
config.SevConfig.CBitPos,
config.SevConfig.ReducedPhysBits,
sevSnpType,
config.SEVSNPConfig.ID,
config.SEVSNPConfig.CBitPos,
config.SEVSNPConfig.ReducedPhysBits,
hostData))
args = append(args, "-object",
@@ -214,6 +222,26 @@ func (config Config) ConstructQemuArgs() []string {
config.IGVMConfig.File))
}
if config.EnableTDX {
args = append(args, "-object",
fmt.Sprintf(TDXObject,
config.TDXConfig.ID,
config.TDXConfig.QuoteGenerationPort))
args = append(args, "-machine",
fmt.Sprintf("confidential-guest-support=%s,memory-backend=%s,hpet=off",
config.TDXConfig.ID,
config.MemID))
args = append(args, "-object",
fmt.Sprintf("memory-backend-memfd,id=%s,size=%s,share=true,prealloc=false",
config.MemID,
config.MemoryConfig.Size))
args = append(args, "-bios", config.TDXConfig.OVMF)
args = append(args, "-nodefaults")
}
args = append(args, "-kernel", config.DiskImgConfig.KernelFile)
args = append(args, "-append", strconv.Quote(KernelCommandLine))
args = append(args, "-initrd", config.DiskImgConfig.RootFsFile)
@@ -237,3 +265,16 @@ func (config Config) ConstructQemuArgs() []string {
return args
}
func NewConfig() (*Config, error) {
cfg := Config{}
if err := env.Parse(&cfg); err != nil {
return nil, err
}
cfg.EnableSEVSNP = SEVSNPEnabledOnHost()
cfg.EnableTDX = TDXEnabledOnHost()
return &cfg, nil
}
+2 -2
View File
@@ -127,7 +127,7 @@ func TestConstructQemuArgs(t *testing.T) {
KernelFile: "img/bzImage",
RootFsFile: "img/rootfs.cpio.gz",
},
SevConfig: SevConfig{
SEVSNPConfig: SEVSNPConfig{
ID: "sev0",
CBitPos: 51,
ReducedPhysBits: 1,
@@ -174,7 +174,7 @@ func TestConstructQemuArgs(t *testing.T) {
func TestConstructQemuArgs_HostData(t *testing.T) {
config := Config{
EnableSEVSNP: true,
SevConfig: SevConfig{
SEVSNPConfig: SEVSNPConfig{
ID: "sev0",
CBitPos: 51,
ReducedPhysBits: 1,
+2 -2
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
@@ -69,7 +69,7 @@ func (_c *Persistence_DeleteVM_Call) RunAndReturn(run func(string) error) *Persi
return _c
}
// LoadVMs provides a mock function with given fields:
// LoadVMs provides a mock function with no fields
func (_m *Persistence) LoadVMs() ([]qemu.VMState, error) {
ret := _m.Called()
+52 -11
View File
@@ -4,8 +4,10 @@ package qemu
import (
"fmt"
"log/slog"
"os"
"os/exec"
"strings"
"syscall"
"time"
@@ -30,17 +32,19 @@ type VMInfo struct {
}
type qemuVM struct {
vmi VMInfo
cmd *exec.Cmd
computationId string
vmi VMInfo
cmd *exec.Cmd
cvmId string
logger *slog.Logger
vm.StateMachine
}
func NewVM(config interface{}, computationId string) vm.VM {
func NewVM(config interface{}, cvmId string, logger *slog.Logger) vm.VM {
return &qemuVM{
vmi: config.(VMInfo),
computationId: computationId,
StateMachine: vm.NewStateMachine(),
vmi: config.(VMInfo),
cvmId: cvmId,
StateMachine: vm.NewStateMachine(),
logger: logger,
}
}
@@ -57,9 +61,10 @@ func (v *qemuVM) Start() (err error) {
}
v.vmi.Config.NetDevConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.NetDevConfig.ID, id)
v.vmi.Config.SevConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.SevConfig.ID, id)
v.vmi.Config.SEVSNPConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.SEVSNPConfig.ID, id)
v.vmi.Config.TDXConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.TDXConfig.ID, id)
if !v.vmi.Config.EnableSEVSNP {
if !v.vmi.Config.EnableSEVSNP && !v.vmi.Config.EnableTDX {
// Copy firmware vars file.
srcFile := v.vmi.Config.OVMFVarsConfig.File
dstFile := fmt.Sprintf("%s/%s-%s.fd", tmpDir, firmwareVars, id)
@@ -76,8 +81,8 @@ func (v *qemuVM) Start() (err error) {
}
v.cmd = exec.Command(exe, args...)
v.cmd.Stdout = os.Stdout
v.cmd.Stderr = os.Stderr
v.cmd.Stdout = &vm.Stdout{StateMachine: v.StateMachine, Logger: v.logger.With(slog.String("cvm", v.cvmId))}
v.cmd.Stderr = &vm.Stderr{StateMachine: v.StateMachine, Logger: v.logger.With(slog.String("cvm", v.cvmId))}
return v.cmd.Start()
}
@@ -194,3 +199,39 @@ func (v *qemuVM) GetCID() int {
func (v *qemuVM) GetConfig() interface{} {
return v.vmi
}
func SEVSNPEnabled(cpuinfo, kernelParam string) bool {
return strings.Contains(cpuinfo, "sev_snp") && strings.TrimSpace(kernelParam) == "Y"
}
func TDXEnabled(cpuinfo, kernelParam string) bool {
return strings.Contains(cpuinfo, "tdx_host_platform") && strings.TrimSpace(kernelParam) == "Y"
}
func SEVSNPEnabledOnHost() bool {
cpuinfo, err := os.ReadFile("/proc/cpuinfo")
if err != nil {
return false
}
kernelParam, err := os.ReadFile("/sys/module/kvm_amd/parameters/sev_snp")
if err != nil {
return false
}
return SEVSNPEnabled(string(cpuinfo), string(kernelParam))
}
func TDXEnabledOnHost() bool {
cpuinfo, err := os.ReadFile("/proc/cpuinfo")
if err != nil {
return false
}
kernelParam, err := os.ReadFile("/sys/module/kvm_intel/parameters/tdx")
if err != nil {
return false
}
return TDXEnabled(string(cpuinfo), string(kernelParam))
}
+40 -3
View File
@@ -3,6 +3,7 @@
package qemu
import (
"log/slog"
"os"
"os/exec"
"testing"
@@ -17,7 +18,7 @@ const testComputationID = "test-computation"
func TestNewVM(t *testing.T) {
config := VMInfo{Config: Config{}}
vm := NewVM(config, testComputationID)
vm := NewVM(config, testComputationID, slog.Default())
assert.NotNil(t, vm)
assert.IsType(t, &qemuVM{}, vm)
@@ -36,7 +37,7 @@ func TestStart(t *testing.T) {
QemuBinPath: "echo",
}}
vm := NewVM(config, testComputationID).(*qemuVM)
vm := NewVM(config, testComputationID, slog.Default()).(*qemuVM)
err = vm.Start()
assert.NoError(t, err)
@@ -59,7 +60,7 @@ func TestStartSudo(t *testing.T) {
UseSudo: true,
}}
vm := NewVM(config, testComputationID).(*qemuVM)
vm := NewVM(config, testComputationID, slog.Default()).(*qemuVM)
err = vm.Start()
assert.NoError(t, err)
@@ -160,3 +161,39 @@ func TestGetConfig(t *testing.T) {
config := vm.GetConfig()
assert.Equal(t, expectedConfig, config)
}
func TestSEVSNPEnabled(t *testing.T) {
t.Run("cpuinfo and kvm param correct", func(t *testing.T) {
assert.True(t, SEVSNPEnabled("flags: sev_snp abc", "Y"))
})
t.Run("missing sev_snp in cpuinfo", func(t *testing.T) {
assert.False(t, SEVSNPEnabled("flags: abc", "1"))
})
t.Run("kernel param not enabled", func(t *testing.T) {
assert.False(t, SEVSNPEnabled("flags: sev_snp", "0"))
})
}
func TestTDXEnabled(t *testing.T) {
t.Run("cpuinfo and kvm param correct", func(t *testing.T) {
assert.True(t, TDXEnabled("flags: tdx_host_platform abc", "Y"))
})
t.Run("missing tdx_host_platform in cpuinfo", func(t *testing.T) {
assert.False(t, TDXEnabled("flags: abc", "1"))
})
t.Run("kernel param not enabled", func(t *testing.T) {
assert.False(t, TDXEnabled("flags: tdx_host_platform", "0"))
})
}
func TestSEVSNPEnabledOnHost(t *testing.T) {
assert.False(t, SEVSNPEnabledOnHost())
}
func TestTDXEnabledOnHost(t *testing.T) {
assert.False(t, TDXEnabledOnHost())
}
-28
View File
@@ -1,28 +0,0 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package qemu
import (
"encoding/json"
"github.com/mdlayher/vsock"
"github.com/ultravioletrs/cocos/agent"
)
const VsockConfigPort uint32 = 9999
func (v *qemuVM) SendAgentConfig(ac agent.Computation) error {
conn, err := vsock.Dial(uint32(v.vmi.Config.GuestCID), VsockConfigPort, nil)
if err != nil {
return err
}
defer conn.Close()
payload, err := json.Marshal(ac)
if err != nil {
return err
}
if _, err := conn.Write(payload); err != nil {
return err
}
return nil
}
+55 -25
View File
@@ -3,8 +3,6 @@
package manager
import (
"bufio"
"bytes"
"context"
"encoding/base64"
"fmt"
@@ -15,14 +13,15 @@ import (
"strconv"
"sync"
"syscall"
"time"
"github.com/absmach/magistrala/pkg/errors"
"github.com/google/go-sev-guest/proto/check"
"github.com/google/uuid"
"github.com/ultravioletrs/cocos/manager/qemu"
"github.com/ultravioletrs/cocos/manager/vm"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"github.com/ultravioletrs/cocos/pkg/manager"
"golang.org/x/crypto/sha3"
)
@@ -35,6 +34,8 @@ const (
agentCvmClientCertKey = "AGENT_CVM_GRPC_CLIENT_CERT"
agentCvmClientKey = "AGENT_CVM_GRPC_CLIENT_KEY"
agentCvmServerCaCertKey = "AGENT_CVM_GRPC_SERVER_CA_CERTS"
agentCvmId = "AGENT_CVM_ID"
agentCvmCaUrl = "AGENT_CVM_CA_URL"
defClientCertPath = "/etc/certs/cert.pem"
defClientKeyPath = "/etc/certs/key.pem"
defServerCaCertPath = "/etc/certs/ca.pem"
@@ -78,8 +79,10 @@ type Service interface {
RemoveVM(ctx context.Context, computationID string) error
// FetchAttestationPolicy measures and fetches the attestation policy.
FetchAttestationPolicy(ctx context.Context, computationID string) ([]byte, error)
// ReturnSVMInfo returns SVM information needed for attestation verification and validation.
ReturnSVMInfo(ctx context.Context) (string, int, string, string)
// ReturnCVMInfo returns CVM information needed for attestation verification and validation.
ReturnCVMInfo(ctx context.Context) (string, int, string, string)
// Shutdown gracefully shuts down the service
Shutdown() error
}
type managerService struct {
@@ -96,6 +99,7 @@ type managerService struct {
portRangeMax int
persistence qemu.Persistence
eosVersion string
ttlManager *TTLManager
}
var _ Service = (*managerService)(nil)
@@ -124,6 +128,7 @@ func New(cfg qemu.Config, attestationPolicyBinPath string, igvmMeasurementBinary
portRangeMax: end,
persistence: persistence,
eosVersion: eosVersion,
ttlManager: NewTTLManager(),
}
if err := ms.restoreVMs(); err != nil {
@@ -155,32 +160,23 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
cfg.Config.CertsMount = tmpCertsDir
cfg.Config.EnvMount = tmpEnvDir
if ms.qemuCfg.EnableSEVSNP || ms.qemuCfg.EnableSEV {
var stderrBuffer bytes.Buffer
options := []string{"--policy", "196608"}
if ms.pcrValuesFilePath != "" {
pcrValues := []string{"--pcr", ms.pcrValuesFilePath}
options = append(options, pcrValues...)
}
stderr := bufio.NewWriter(&stderrBuffer)
attestPolicyCmd, err := cmdconfig.NewCmdConfig("sudo", options, stderr)
if ms.qemuCfg.EnableSEVSNP {
attestPolicyCmd, err := fetchSNPAttestationPolicy(ms)
if err != nil {
return "", id, err
}
var stdOutByte []byte
ms.ap.Lock()
stdOutByte, err := attestPolicyCmd.Run(ms.attestationPolicyBinaryPath)
stdOutByte, err = attestPolicyCmd.Run(ms.attestationPolicyBinaryPath)
ms.ap.Unlock()
if err != nil {
return "", id, errors.Wrap(ErrFailedToCreateAttestationPolicy, err)
}
attestationPolicy := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
attestationPolicy := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
if err = config.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
if err = vtpm.ReadPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
return "", id, errors.Wrap(ErrUnmarshalFailed, err)
}
@@ -213,10 +209,10 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
if cfg.Config.EnableSEVSNP {
todo := sha3.Sum256([]byte("TODO"))
// Define host-data value of QEMU for SEV-SNP, with a base64 encoding of the computation hash.
cfg.Config.SevConfig.HostData = base64.StdEncoding.EncodeToString(todo[:])
cfg.Config.SEVSNPConfig.HostData = base64.StdEncoding.EncodeToString(todo[:])
}
cvm := ms.vmFactory(cfg, id)
cvm := ms.vmFactory(cfg, id, ms.logger)
if err = cvm.Start(); err != nil {
return "", id, err
}
@@ -224,6 +220,21 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
ms.vms[id] = cvm
ms.mu.Unlock()
if req.Ttl != "" {
ttl, err := time.ParseDuration(req.Ttl)
if err != nil {
return "", id, err
}
ms.ttlManager.SetTTL(id, ttl, func() { //nolint:contextcheck
if err := ms.RemoveVM(context.Background(), id); err != nil {
ms.logger.Error("Failed to remove VM after TTL expiry", "vmID", id, "error", err)
} else {
ms.logger.Info("Successfully removed VM after TTL expiry", "vmID", id)
}
})
}
pid := cvm.GetProcess()
state := qemu.VMState{
@@ -247,6 +258,9 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
func (ms *managerService) RemoveVM(ctx context.Context, computationID string) error {
ms.mu.Lock()
defer ms.mu.Unlock()
ms.ttlManager.CancelTTL(computationID)
cvm, ok := ms.vms[computationID]
if !ok {
return ErrNotFound
@@ -263,10 +277,24 @@ func (ms *managerService) RemoveVM(ctx context.Context, computationID string) er
return nil
}
func (ms *managerService) ReturnSVMInfo(ctx context.Context) (string, int, string, string) {
func (ms *managerService) ReturnCVMInfo(ctx context.Context) (string, int, string, string) {
return ms.qemuCfg.OVMFCodeConfig.Version, ms.qemuCfg.SMPCount, ms.qemuCfg.CPU, ms.eosVersion
}
// Shutdown gracefully shuts down the service.
func (ms *managerService) Shutdown() error {
ms.logger.Info("Shutting down manager service")
ms.ttlManager.CancelAll()
ms.mu.Lock()
defer ms.mu.Unlock()
ms.vms = make(map[string]vm.VM)
return nil
}
func getFreePort(minPort, maxPort int) (int, error) {
if checkPortisFree(minPort) {
return minPort, nil
@@ -350,7 +378,7 @@ func (ms *managerService) restoreVMs() error {
continue
}
cvm := ms.vmFactory(state.VMinfo, state.ID)
cvm := ms.vmFactory(state.VMinfo, state.ID, ms.logger)
if err = cvm.SetProcess(state.PID); err != nil {
ms.logger.Warn("Failed to reattach to process", "computation", state.ID, "pid", state.PID, "error", err)
@@ -414,6 +442,8 @@ func tmpEnvironment(id string, req *CreateReq) (string, error) {
envMap := map[string]string{
agentLogLevelKey: req.AgentLogLevel,
agentCvmGrpcUrlKey: req.AgentCvmServerUrl,
agentCvmId: id,
agentCvmCaUrl: req.AgentCvmCaUrl,
}
if req.AgentCvmClientCert != nil {
+31 -1
View File
@@ -47,24 +47,35 @@ func TestRun(t *testing.T) {
binaryBehavior string
vmStartError error
expectedError error
ttl string
}{
{
name: "Successful run",
binaryBehavior: "success",
vmStartError: nil,
expectedError: nil,
ttl: "",
},
{
name: "VM start failure",
binaryBehavior: "success",
vmStartError: assert.AnError,
expectedError: assert.AnError,
ttl: "",
},
{
name: "Invalid attestation policy",
binaryBehavior: "fail",
vmStartError: nil,
expectedError: ErrFailedToCreateAttestationPolicy,
ttl: "",
},
{
name: "With TTL",
binaryBehavior: "success",
vmStartError: nil,
expectedError: nil,
ttl: "10s",
},
}
@@ -101,11 +112,12 @@ func TestRun(t *testing.T) {
vms: make(map[string]vm.VM),
vmFactory: vmf.Execute,
persistence: persistence,
ttlManager: NewTTLManager(),
}
ctx := context.Background()
port, _, err := ms.CreateVM(ctx, &CreateReq{})
port, _, err := ms.CreateVM(ctx, &CreateReq{Ttl: tt.ttl})
if tt.expectedError != nil {
assert.Error(t, err)
@@ -165,6 +177,7 @@ func TestStop(t *testing.T) {
logger: logger,
vms: make(map[string]vm.VM),
persistence: persistence,
ttlManager: NewTTLManager(),
}
vmMock := new(mocks.VM)
@@ -287,3 +300,20 @@ func TestProcessExists(t *testing.T) {
assert.False(t, ms.processExists(1)) // PID 1 is usually the init process.
}
}
func TestShutdown(t *testing.T) {
ms := &managerService{
vms: make(map[string]vm.VM),
ttlManager: NewTTLManager(),
logger: mglog.NewMock(),
}
vmMock := new(mocks.VM)
vmMock.On("Stop").Return(nil).Once()
ms.vms["test-vm"] = vmMock
err := ms.Shutdown()
assert.NoError(t, err)
assert.Len(t, ms.vms, 0)
}
+10 -3
View File
@@ -42,9 +42,16 @@ func (tm *tracingMiddleware) FetchAttestationPolicy(ctx context.Context, computa
return tm.svc.FetchAttestationPolicy(ctx, computationId)
}
func (tm *tracingMiddleware) ReturnSVMInfo(ctx context.Context) (string, int, string, string) {
_, span := tm.tracer.Start(ctx, "return_svm_info")
func (tm *tracingMiddleware) ReturnCVMInfo(ctx context.Context) (string, int, string, string) {
_, span := tm.tracer.Start(ctx, "return_cvm_info")
defer span.End()
return tm.svc.ReturnSVMInfo(ctx)
return tm.svc.ReturnCVMInfo(ctx)
}
func (tm *tracingMiddleware) Shutdown() error {
_, span := tm.tracer.Start(context.Background(), "shutdown")
defer span.End()
return tm.svc.Shutdown()
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package manager
import (
"context"
sync "sync"
"time"
)
// TTLManager handles TTL functionality for VMs.
type TTLManager struct {
timers map[string]*time.Timer
mu sync.RWMutex
}
// NewTTLManager creates a new TTL manager.
func NewTTLManager() *TTLManager {
return &TTLManager{
timers: make(map[string]*time.Timer),
}
}
// SetTTL sets a TTL for a VM and returns a function to cancel it.
func (tm *TTLManager) SetTTL(vmID string, ttl time.Duration, onExpiry func()) context.CancelFunc {
tm.mu.Lock()
defer tm.mu.Unlock()
if timer, exists := tm.timers[vmID]; exists {
timer.Stop()
}
timer := time.AfterFunc(ttl, onExpiry)
tm.timers[vmID] = timer
return func() {
tm.mu.Lock()
defer tm.mu.Unlock()
if t, exists := tm.timers[vmID]; exists {
t.Stop()
delete(tm.timers, vmID)
}
}
}
// CancelTTL cancels the TTL for a specific VM.
func (tm *TTLManager) CancelTTL(vmID string) {
tm.mu.Lock()
defer tm.mu.Unlock()
if timer, exists := tm.timers[vmID]; exists {
timer.Stop()
delete(tm.timers, vmID)
}
}
// CancelAll cancels all active TTLs.
func (tm *TTLManager) CancelAll() {
tm.mu.Lock()
defer tm.mu.Unlock()
for vmID, timer := range tm.timers {
timer.Stop()
delete(tm.timers, vmID)
}
}
+342
View File
@@ -0,0 +1,342 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package manager
import (
"fmt"
"sync"
"testing"
"time"
)
func TestNewTTLManager(t *testing.T) {
tm := NewTTLManager()
if tm == nil {
t.Fatal("NewTTLManager() returned nil")
}
if tm.timers == nil {
t.Fatal("NewTTLManager() did not initialize timers map")
}
if len(tm.timers) != 0 {
t.Errorf("NewTTLManager() timers map should be empty, got %d entries", len(tm.timers))
}
}
func TestSetTTL_Basic(t *testing.T) {
tm := NewTTLManager()
mu := sync.Mutex{}
expired := false
vmID := "test-vm-1"
ttl := 50 * time.Millisecond
cancelFunc := tm.SetTTL(vmID, ttl, func() {
mu.Lock()
defer mu.Unlock()
expired = true
})
tm.mu.RLock()
if _, exists := tm.timers[vmID]; !exists {
t.Error("Timer was not created for VM")
}
tm.mu.RUnlock()
time.Sleep(100 * time.Millisecond)
mu.Lock()
if !expired {
t.Error("TTL did not expire as expected")
}
mu.Unlock()
cancelFunc()
tm.mu.RLock()
if _, exists := tm.timers[vmID]; exists {
t.Error("Timer should be cleaned up after expiry")
}
tm.mu.RUnlock()
}
func TestSetTTL_CancelBeforeExpiry(t *testing.T) {
tm := NewTTLManager()
expired := false
vmID := "test-vm-2"
ttl := 100 * time.Millisecond
cancelFunc := tm.SetTTL(vmID, ttl, func() {
expired = true
})
time.Sleep(20 * time.Millisecond)
cancelFunc()
time.Sleep(150 * time.Millisecond)
if expired {
t.Error("TTL should not have expired after being cancelled")
}
tm.mu.RLock()
if _, exists := tm.timers[vmID]; exists {
t.Error("Timer should be cleaned up after cancellation")
}
tm.mu.RUnlock()
}
func TestSetTTL_OverwriteExistingTimer(t *testing.T) {
tm := NewTTLManager()
mu := sync.Mutex{}
firstExpired := false
secondExpired := false
vmID := "test-vm-3"
// Set first TTL
tm.SetTTL(vmID, 200*time.Millisecond, func() {
firstExpired = true
})
// Immediately overwrite with second TTL
tm.SetTTL(vmID, 50*time.Millisecond, func() {
mu.Lock()
defer mu.Unlock()
secondExpired = true
})
// Wait for second TTL to expire
time.Sleep(100 * time.Millisecond)
if firstExpired {
t.Error("First TTL should not have expired (it was overwritten)")
}
mu.Lock()
if !secondExpired {
t.Error("Second TTL should have expired")
}
mu.Unlock()
// Verify only one timer entry exists (or none after cleanup)
tm.mu.RLock()
count := len(tm.timers)
tm.mu.RUnlock()
if count > 1 {
t.Errorf("Expected at most 1 timer entry, got %d", count)
}
}
func TestSetTTL_MultipleConcurrentTimers(t *testing.T) {
tm := NewTTLManager()
numVMs := 5
expiredCount := int32(0)
var mu sync.Mutex
for i := 0; i < numVMs; i++ {
vmID := fmt.Sprintf("vm-%d", i)
tm.SetTTL(vmID, 50*time.Millisecond, func() {
mu.Lock()
expiredCount++
mu.Unlock()
})
}
tm.mu.RLock()
if len(tm.timers) != numVMs {
t.Errorf("Expected %d timers, got %d", numVMs, len(tm.timers))
}
tm.mu.RUnlock()
time.Sleep(100 * time.Millisecond)
mu.Lock()
finalCount := expiredCount
mu.Unlock()
if int(finalCount) != numVMs {
t.Errorf("Expected %d timers to expire, got %d", numVMs, finalCount)
}
}
func TestCancelTTL_ExistingTimer(t *testing.T) {
tm := NewTTLManager()
expired := false
vmID := "test-vm-4"
tm.SetTTL(vmID, 100*time.Millisecond, func() {
expired = true
})
// Cancel the timer
tm.CancelTTL(vmID)
time.Sleep(150 * time.Millisecond)
if expired {
t.Error("TTL should not have expired after being cancelled")
}
// Verify timer was removed
tm.mu.RLock()
if _, exists := tm.timers[vmID]; exists {
t.Error("Timer should be removed after cancellation")
}
tm.mu.RUnlock()
}
func TestCancelTTL_NonExistentTimer(t *testing.T) {
tm := NewTTLManager()
// Should not panic when cancelling non-existent timer
tm.CancelTTL("non-existent-vm")
// Verify timers map is still empty
tm.mu.RLock()
if len(tm.timers) != 0 {
t.Errorf("Expected empty timers map, got %d entries", len(tm.timers))
}
tm.mu.RUnlock()
}
func TestCancelAll_MultipleTimers(t *testing.T) {
tm := NewTTLManager()
numVMs := 3
expiredCount := int32(0)
var mu sync.Mutex
for i := 0; i < numVMs; i++ {
vmID := fmt.Sprintf("vm-%d", i)
tm.SetTTL(vmID, 200*time.Millisecond, func() {
mu.Lock()
expiredCount++
mu.Unlock()
})
}
tm.mu.RLock()
if len(tm.timers) != numVMs {
t.Errorf("Expected %d timers, got %d", numVMs, len(tm.timers))
}
tm.mu.RUnlock()
tm.CancelAll()
tm.mu.RLock()
if len(tm.timers) != 0 {
t.Errorf("Expected 0 timers after CancelAll, got %d", len(tm.timers))
}
tm.mu.RUnlock()
time.Sleep(250 * time.Millisecond)
mu.Lock()
finalCount := expiredCount
mu.Unlock()
if finalCount != 0 {
t.Errorf("Expected 0 timers to expire after CancelAll, got %d", finalCount)
}
}
func TestCancelAll_EmptyManager(t *testing.T) {
tm := NewTTLManager()
tm.CancelAll()
tm.mu.RLock()
if len(tm.timers) != 0 {
t.Errorf("Expected empty timers map, got %d entries", len(tm.timers))
}
tm.mu.RUnlock()
}
func TestConcurrentAccess(t *testing.T) {
tm := NewTTLManager()
var wg sync.WaitGroup
numGoroutines := 10
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
vmID := fmt.Sprintf("concurrent-vm-%d", id)
cancelFunc := tm.SetTTL(vmID, 100*time.Millisecond, func() {})
// Sometimes cancel immediately
if id%2 == 0 {
cancelFunc()
}
}(i)
}
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
vmID := fmt.Sprintf("concurrent-vm-%d", id)
time.Sleep(10 * time.Millisecond)
tm.CancelTTL(vmID)
}(i)
}
wg.Wait()
tm.CancelAll()
// This test primarily checks that no race conditions occur
// The actual state at the end is unpredictable due to timing
}
func TestSetTTL_ZeroDuration(t *testing.T) {
tm := NewTTLManager()
mu := sync.Mutex{}
expired := false
vmID := "zero-duration-vm"
tm.SetTTL(vmID, 0, func() {
mu.Lock()
defer mu.Unlock()
expired = true
})
time.Sleep(10 * time.Millisecond)
mu.Lock()
if !expired {
t.Error("TTL with zero duration should expire immediately")
}
mu.Unlock()
}
func TestSetTTL_NegativeDuration(t *testing.T) {
tm := NewTTLManager()
mu := sync.Mutex{}
expired := false
vmID := "negative-duration-vm"
tm.SetTTL(vmID, -100*time.Millisecond, func() {
mu.Lock()
defer mu.Unlock()
expired = true
})
time.Sleep(10 * time.Millisecond)
mu.Lock()
defer mu.Unlock()
if !expired {
t.Error("TTL with negative duration should expire immediately")
}
}
+81
View File
@@ -0,0 +1,81 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package vm
import (
"bytes"
"io"
"log/slog"
"strings"
)
var (
_ io.Writer = &Stdout{}
_ io.Writer = &Stderr{}
)
const bufSize = 1024
type Stdout struct {
StateMachine StateMachine
Logger *slog.Logger
}
// Write implements io.Writer.
func (s *Stdout) Write(p []byte) (n int, err error) {
inBuf := bytes.NewBuffer(p)
buf := make([]byte, bufSize)
for {
n, err := inBuf.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return len(p) - inBuf.Len(), err
}
args := []any{
slog.String("state", s.StateMachine.State()),
}
s.Logger.Info(string(buf[:n]), args...)
}
return len(p), nil
}
type Stderr struct {
StateMachine StateMachine
Logger *slog.Logger
}
// Write implements io.Writer.
func (s *Stderr) Write(p []byte) (n int, err error) {
inBuf := bytes.NewBuffer(p)
buf := make([]byte, bufSize)
for {
n, err := inBuf.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return len(p) - inBuf.Len(), err
}
args := []any{
slog.String("state", s.StateMachine.State()),
}
if strings.Contains(string(buf[:n]), "Error") {
s.Logger.Error(string(buf[:n]), args...)
} else {
s.Logger.Warn(string(buf[:n]), args...)
}
}
return len(p), nil
}
+15 -11
View File
@@ -1,12 +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 (
slog "log/slog"
mock "github.com/stretchr/testify/mock"
vm "github.com/ultravioletrs/cocos/manager/vm"
)
@@ -23,17 +26,17 @@ func (_m *Provider) EXPECT() *Provider_Expecter {
return &Provider_Expecter{mock: &_m.Mock}
}
// Execute provides a mock function with given fields: config, computationId
func (_m *Provider) Execute(config interface{}, computationId string) vm.VM {
ret := _m.Called(config, computationId)
// Execute provides a mock function with given fields: config, computationId, logger
func (_m *Provider) Execute(config interface{}, computationId string, logger *slog.Logger) vm.VM {
ret := _m.Called(config, computationId, logger)
if len(ret) == 0 {
panic("no return value specified for Execute")
}
var r0 vm.VM
if rf, ok := ret.Get(0).(func(interface{}, string) vm.VM); ok {
r0 = rf(config, computationId)
if rf, ok := ret.Get(0).(func(interface{}, string, *slog.Logger) vm.VM); ok {
r0 = rf(config, computationId, logger)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(vm.VM)
@@ -51,13 +54,14 @@ type Provider_Execute_Call struct {
// Execute is a helper method to define mock.On call
// - config interface{}
// - computationId string
func (_e *Provider_Expecter) Execute(config interface{}, computationId interface{}) *Provider_Execute_Call {
return &Provider_Execute_Call{Call: _e.mock.On("Execute", config, computationId)}
// - logger *slog.Logger
func (_e *Provider_Expecter) Execute(config interface{}, computationId interface{}, logger interface{}) *Provider_Execute_Call {
return &Provider_Execute_Call{Call: _e.mock.On("Execute", config, computationId, logger)}
}
func (_c *Provider_Execute_Call) Run(run func(config interface{}, computationId string)) *Provider_Execute_Call {
func (_c *Provider_Execute_Call) Run(run func(config interface{}, computationId string, logger *slog.Logger)) *Provider_Execute_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(interface{}), args[1].(string))
run(args[0].(interface{}), args[1].(string), args[2].(*slog.Logger))
})
return _c
}
@@ -67,7 +71,7 @@ func (_c *Provider_Execute_Call) Return(_a0 vm.VM) *Provider_Execute_Call {
return _c
}
func (_c *Provider_Execute_Call) RunAndReturn(run func(interface{}, string) vm.VM) *Provider_Execute_Call {
func (_c *Provider_Execute_Call) RunAndReturn(run func(interface{}, string, *slog.Logger) vm.VM) *Provider_Execute_Call {
_c.Call.Return(run)
return _c
}
+2 -2
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
@@ -23,7 +23,7 @@ func (_m *StateMachine) EXPECT() *StateMachine_Expecter {
return &StateMachine_Expecter{mock: &_m.Mock}
}
// State provides a mock function with given fields:
// State provides a mock function with no fields
func (_m *StateMachine) State() string {
ret := _m.Called()
+8 -56
View File
@@ -1,15 +1,13 @@
// 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 (
agent "github.com/ultravioletrs/cocos/agent"
manager "github.com/ultravioletrs/cocos/pkg/manager"
mock "github.com/stretchr/testify/mock"
manager "github.com/ultravioletrs/cocos/pkg/manager"
)
// VM is an autogenerated mock type for the VM type
@@ -25,7 +23,7 @@ func (_m *VM) EXPECT() *VM_Expecter {
return &VM_Expecter{mock: &_m.Mock}
}
// GetCID provides a mock function with given fields:
// GetCID provides a mock function with no fields
func (_m *VM) GetCID() int {
ret := _m.Called()
@@ -70,7 +68,7 @@ func (_c *VM_GetCID_Call) RunAndReturn(run func() int) *VM_GetCID_Call {
return _c
}
// GetConfig provides a mock function with given fields:
// GetConfig provides a mock function with no fields
func (_m *VM) GetConfig() interface{} {
ret := _m.Called()
@@ -117,7 +115,7 @@ func (_c *VM_GetConfig_Call) RunAndReturn(run func() interface{}) *VM_GetConfig_
return _c
}
// GetProcess provides a mock function with given fields:
// GetProcess provides a mock function with no fields
func (_m *VM) GetProcess() int {
ret := _m.Called()
@@ -162,52 +160,6 @@ func (_c *VM_GetProcess_Call) RunAndReturn(run func() int) *VM_GetProcess_Call {
return _c
}
// SendAgentConfig provides a mock function with given fields: ac
func (_m *VM) SendAgentConfig(ac agent.Computation) error {
ret := _m.Called(ac)
if len(ret) == 0 {
panic("no return value specified for SendAgentConfig")
}
var r0 error
if rf, ok := ret.Get(0).(func(agent.Computation) error); ok {
r0 = rf(ac)
} else {
r0 = ret.Error(0)
}
return r0
}
// VM_SendAgentConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendAgentConfig'
type VM_SendAgentConfig_Call struct {
*mock.Call
}
// SendAgentConfig is a helper method to define mock.On call
// - ac agent.Computation
func (_e *VM_Expecter) SendAgentConfig(ac interface{}) *VM_SendAgentConfig_Call {
return &VM_SendAgentConfig_Call{Call: _e.mock.On("SendAgentConfig", ac)}
}
func (_c *VM_SendAgentConfig_Call) Run(run func(ac agent.Computation)) *VM_SendAgentConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(agent.Computation))
})
return _c
}
func (_c *VM_SendAgentConfig_Call) Return(_a0 error) *VM_SendAgentConfig_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *VM_SendAgentConfig_Call) RunAndReturn(run func(agent.Computation) error) *VM_SendAgentConfig_Call {
_c.Call.Return(run)
return _c
}
// SetProcess provides a mock function with given fields: pid
func (_m *VM) SetProcess(pid int) error {
ret := _m.Called(pid)
@@ -254,7 +206,7 @@ func (_c *VM_SetProcess_Call) RunAndReturn(run func(int) error) *VM_SetProcess_C
return _c
}
// Start provides a mock function with given fields:
// Start provides a mock function with no fields
func (_m *VM) Start() error {
ret := _m.Called()
@@ -299,7 +251,7 @@ func (_c *VM_Start_Call) RunAndReturn(run func() error) *VM_Start_Call {
return _c
}
// State provides a mock function with given fields:
// State provides a mock function with no fields
func (_m *VM) State() string {
ret := _m.Called()
@@ -344,7 +296,7 @@ func (_c *VM_State_Call) RunAndReturn(run func() string) *VM_State_Call {
return _c
}
// Stop provides a mock function with given fields:
// Stop provides a mock function with no fields
func (_m *VM) Stop() error {
ret := _m.Called()

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